优化:心法升级趋势

This commit is contained in:
taojiacheng committed 2026-07-27 00:22:15 +08:00
1 parent e912485cf1
commit 3d8d63a6c2
8 files changed
+195 -56

No files matched your search

@@ -30,7 +30,7 @@ function getMethodDisabledReason(args: {
};
}) {
const targetLevel = args.currentLevel + 1;
if (targetLevel > args.data.methodLevelCap) return '已达当前境界上限';
if (targetLevel > args.data.methodLevelCap) return '已达当前心法上限';
const primary = args.data.definition?.methods.find(
(method) => method.isPrimary,
);
+29
View File
@@ -66,6 +66,35 @@ CultivatorSectState
- 多个节点修改同一组神通时,建立流派私有 Build Facade,用语义特征组合变化;禁止恢复 `nodes.has(id)` 中央分派。
- 节点每次应用必须造成可观察的最终运行时变化,空实现会在注册时失败。
## 标准心法成长
标准宗门心法覆盖 1~180 级。目标等级 `L` 的单级研习成本独立于境界突破经验表:
```text
修为 = ceil((50 × 1.05^(L - 1)) / 10) × 10
灵石 = ceil((修为 × 3) / 100) × 100
道心感悟 = 0
```
跨级研习逐级取整后累加。境界每个小阶段开放 5 级;有效上限取境界、弟子身份与藏经设施三者的最小值。
| 弟子身份 | 心法上限 |
| --- | ---: |
| 记名弟子 | 45 |
| 外门弟子 | 90 |
| 内门弟子 | 135 |
| 真传弟子 | 180 |
| 藏经设施等级 | 心法上限 |
| ---: | ---: |
| 1 | 40 |
| 2 | 75 |
| 3 | 110 |
| 4 | 145 |
| 5 | 180 |
外门、内门、真传的最低晋升境界依次为炼气、筑基、金丹;贡献、任务与试炼仍由标准组织策略判定。
## 边界约束
- 每个宗门固定六本心法,每本至少拥有一个基础神通。
@@ -51,7 +51,20 @@ describe('LingxiaoOrganizationModule', () => {
});
it('owns rank, economy, task, and construction content', () => {
expect(LINGXIAO_ORGANIZATION.ranks.methodLevelCap('registered')).toBe(5);
expect(
(
['registered', 'outer', 'inner', 'true'] as const
).map((rank) => LINGXIAO_ORGANIZATION.ranks.methodLevelCap(rank)),
).toEqual([45, 90, 135, 180]);
expect(LINGXIAO_ORGANIZATION.ranks.requirement('outer')).toMatchObject({
minRealm: '炼气',
contribution: 100,
dailyCompletions: 3,
});
expect(LINGXIAO_ORGANIZATION.ranks.requirement('inner')).toMatchObject({
minRealm: '筑基',
contribution: 500,
});
expect(LINGXIAO_ORGANIZATION.ranks.requirement('true')).toMatchObject({
minRealm: '金丹',
contribution: 3000,
@@ -66,13 +79,20 @@ describe('LingxiaoOrganizationModule', () => {
expect(LINGXIAO_ORGANIZATION.construction.facilityPriority[0]).toBe(
'archive',
);
expect(
[1, 2, 3, 4, 5].map((archiveLevel) =>
LINGXIAO_ORGANIZATION.benefits.methodLevelCap(
new Map([['archive', archiveLevel]]),
),
),
).toEqual([40, 75, 110, 145, 180]);
const levels = new Map([
['archive', 5],
['cultivation_room', 5],
['workshop', 5],
['spirit_vein', 5],
]);
expect(LINGXIAO_ORGANIZATION.benefits.methodLevelCap(levels)).toBe(100);
expect(LINGXIAO_ORGANIZATION.benefits.methodLevelCap(levels)).toBe(180);
expect(
LINGXIAO_ORGANIZATION.benefits.retreatMultiplier(levels, 'outer'),
).toBe(1.1);
@@ -28,12 +28,12 @@ export const SECT_RANK_LABELS: Record<SectDiscipleRank, string> = {
true: '真传弟子',
};
export const SECT_RANK_METHOD_CAP: Record<SectDiscipleRank, number> = {
registered: 5,
outer: 20,
inner: 40,
true: Number.MAX_SAFE_INTEGER,
};
export const SECT_RANK_METHOD_CAP = {
registered: 45,
outer: 90,
inner: 135,
true: 180,
} as const satisfies Record<SectDiscipleRank, number>;
export interface SectFacilityState {
key: SectFacilityKey;
@@ -4,7 +4,11 @@ import {
} from '@shared/config/realmProgression';
import type { PillSpec } from '@shared/types/consumable';
import type { Attributes, Cultivator } from '@shared/types/cultivator';
import type { SectDiscipleRank, SectRankRequirement } from '../domain';
import {
SECT_RANK_METHOD_CAP,
type SectDiscipleRank,
type SectRankRequirement,
} from '../domain';
import { StandardSectCapabilityPolicy } from './StandardSectCapabilityPolicy';
import {
SECT_CRAFT_CONTEXTS,
@@ -50,6 +54,10 @@ const capabilities = new StandardSectCapabilityPolicy(
new Set(['sect.formation.view']),
);
export const STANDARD_SECT_ARCHIVE_METHOD_CAP = [
0, 40, 75, 110, 145, 180,
] as const;
function taskPresentation(
title: string,
description: string,
@@ -824,12 +832,7 @@ class StandardSectRankPolicy implements SectRankPolicy {
}
methodLevelCap(rank: SectDiscipleRank): number {
return {
registered: 5,
outer: 20,
inner: 40,
true: Number.MAX_SAFE_INTEGER,
}[rank];
return SECT_RANK_METHOD_CAP[rank];
}
requirement(
@@ -1004,7 +1007,10 @@ class StandardSectBenefitPolicy implements SectBenefitPolicy {
1,
Math.min(5, Math.floor(this.archiveLevel(levels))),
);
return [0, 20, 40, 60, 80, 100][level] ?? 20;
return (
STANDARD_SECT_ARCHIVE_METHOD_CAP[level] ??
STANDARD_SECT_ARCHIVE_METHOD_CAP[1]
);
}
gardenLevel(levels: ReadonlyMap<string, number>): number {
@@ -1,11 +1,6 @@
import { EXP_CAP_TABLE } from '@shared/config/cultivationProgress';
import { getRealmStageRank } from '@shared/config/realmProgression';
import {
REALM_STAGE_VALUES,
REALM_VALUES,
type RealmStage,
type RealmType,
} from '@shared/types/constants';
import type { RealmStage, RealmType } from '@shared/types/constants';
import type {
SectMethodId,
SectPathDefinition,
@@ -22,10 +17,6 @@ export interface SectPathProgressProjection {
export interface SectProgressionPolicy {
methodLevelCap(realm: RealmType, stage: RealmStage): number;
minimumRealmStageForMethodLevel(level: number): {
realm: RealmType;
stage: RealmStage;
};
methodTrainingCost(fromLevel: number, targetLevel: number): SectTrainingCost;
pathProgress(args: {
path: SectPathDefinition;
@@ -44,6 +35,35 @@ export interface SectProgressionPolicy {
}): SectPathLayerDefinition;
}
export const STANDARD_SECT_METHOD_COST_CURVE = {
baseCultivationExp: 50,
growthRate: 1.05,
cultivationRoundUnit: 10,
spiritStoneMultiplier: 3,
spiritStoneRoundUnit: 100,
} as const;
function roundUpToUnit(value: number, unit: number): number {
return Math.ceil(value / unit) * unit;
}
function methodLevelTrainingCost(level: number): SectTrainingCost {
const cultivationExp = roundUpToUnit(
STANDARD_SECT_METHOD_COST_CURVE.baseCultivationExp *
STANDARD_SECT_METHOD_COST_CURVE.growthRate ** (level - 1),
STANDARD_SECT_METHOD_COST_CURVE.cultivationRoundUnit,
);
return {
cultivationExp,
comprehensionInsight: 0,
spiritStones: roundUpToUnit(
cultivationExp *
STANDARD_SECT_METHOD_COST_CURVE.spiritStoneMultiplier,
STANDARD_SECT_METHOD_COST_CURVE.spiritStoneRoundUnit,
),
};
}
function pathLayerCost(
realm: RealmType,
stage: RealmStage,
@@ -143,18 +163,6 @@ export class StandardSectProgressionPolicy implements SectProgressionPolicy {
return (getRealmStageRank(realm, stage) + 1) * 5;
}
minimumRealmStageForMethodLevel(level: number): {
realm: RealmType;
stage: RealmStage;
} {
for (const realm of REALM_VALUES) {
for (const stage of REALM_STAGE_VALUES) {
if (this.methodLevelCap(realm, stage) >= level) return { realm, stage };
}
}
return { realm: '渡劫', stage: '圆满' };
}
methodTrainingCost(fromLevel: number, targetLevel: number): SectTrainingCost {
const total: SectTrainingCost = {
cultivationExp: 0,
@@ -162,10 +170,9 @@ export class StandardSectProgressionPolicy implements SectProgressionPolicy {
spiritStones: 0,
};
for (let level = fromLevel + 1; level <= targetLevel; level += 1) {
const { realm, stage } = this.minimumRealmStageForMethodLevel(level);
const cultivationExp = Math.ceil(EXP_CAP_TABLE[realm][stage] * 0.01);
total.cultivationExp += cultivationExp;
total.spiritStones += Math.max(50, cultivationExp * 2);
const cost = methodLevelTrainingCost(level);
total.cultivationExp += cost.cultivationExp;
total.spiritStones += cost.spiritStones;
}
return total;
}
@@ -1,20 +1,53 @@
import { describe, expect, it } from 'vitest';
import {
SECT_RANK_METHOD_CAP,
getEffectiveSectMethodLevelCap,
hasSectRank,
} from '../domain';
describe('宗门组织成长', () => {
it('四种弟子身份均匀开放至180级', () => {
expect(SECT_RANK_METHOD_CAP).toEqual({
registered: 45,
outer: 90,
inner: 135,
true: 180,
});
});
it('按境界、弟子职阶和藏经阁取心法最低上限', () => {
expect(getEffectiveSectMethodLevelCap({ realmCap: 80, rank: 'registered', facilityCap: 100 })).toBe(5);
expect(getEffectiveSectMethodLevelCap({ realmCap: 80, rank: 'outer', facilityCap: 20 })).toBe(20);
expect(getEffectiveSectMethodLevelCap({ realmCap: 35, rank: 'inner', facilityCap: 100 })).toBe(35);
expect(getEffectiveSectMethodLevelCap({ realmCap: 120, rank: 'true', facilityCap: 60 })).toBe(60);
expect(
getEffectiveSectMethodLevelCap({
realmCap: 180,
rank: 'registered',
facilityCap: 180,
}),
).toBe(45);
expect(
getEffectiveSectMethodLevelCap({
realmCap: 80,
rank: 'outer',
facilityCap: 180,
}),
).toBe(80);
expect(
getEffectiveSectMethodLevelCap({
realmCap: 180,
rank: 'inner',
facilityCap: 110,
}),
).toBe(110);
expect(
getEffectiveSectMethodLevelCap({
realmCap: 180,
rank: 'true',
facilityCap: 145,
}),
).toBe(145);
});
it('消费贡献不会参与职阶比较或造成降阶', () => {
expect(hasSectRank('inner', 'outer')).toBe(true);
expect(hasSectRank('outer', 'inner')).toBe(false);
});
});
@@ -9,20 +9,64 @@ import {
import { HEAVY_SWORD_PATH, SWIFT_SWORD_PATH } from '../../content/lingxiao';
describe('通用宗门成长', () => {
it('按目标等级的最低境界阶段计算修为与灵石', () => {
it('每个境界阶段开放五级并在渡劫圆满达到180级', () => {
expect(getSectMethodLevelCap('炼气', '初期')).toBe(5);
expect(getSectMethodLevelCap('筑基', '初期')).toBe(25);
expect(getSectMethodTrainingCost(0, 1)).toEqual({
cultivationExp: 5,
comprehensionInsight: 0,
spiritStones: 50,
});
expect(getSectMethodLevelCap('渡劫', '圆满')).toBe(180);
});
it.each([
[1, 50, 200],
[50, 550, 1_700],
[100, 6_270, 18_900],
[120, 16_620, 49_900],
[150, 71_810, 215_500],
[180, 310_360, 931_100],
])(
'%i级按独立指数曲线计算单级修为与灵石',
(level, cultivationExp, spiritStones) => {
expect(getSectMethodTrainingCost(level - 1, level)).toEqual({
cultivationExp,
comprehensionInsight: 0,
spiritStones,
});
},
);
it('逐级取整后累加跨级成本', () => {
expect(getSectMethodTrainingCost(4, 6)).toEqual({
cultivationExp: 11,
cultivationExp: 140,
comprehensionInsight: 0,
spiritStones: 100,
spiritStones: 600,
});
});
it('1至180级累计成本与分段累加一致', () => {
const total = getSectMethodTrainingCost(0, 180);
const first = getSectMethodTrainingCost(0, 120);
const second = getSectMethodTrainingCost(120, 180);
expect(total).toEqual({
cultivationExp: 6_517_250,
comprehensionInsight: 0,
spiritStones: 19_560_400,
});
expect(total.cultivationExp).toBe(
first.cultivationExp + second.cultivationExp,
);
expect(total.spiritStones).toBe(
first.spiritStones + second.spiritStones,
);
expect(first.comprehensionInsight + second.comprehensionInsight).toBe(0);
});
it('所有可用等级的灵石成本均高于修为成本', () => {
for (let level = 1; level <= 180; level += 1) {
const cost = getSectMethodTrainingCost(level - 1, level);
expect(cost.spiritStones).toBeGreaterThan(cost.cultivationExp);
expect(cost.comprehensionInsight).toBe(0);
}
});
it('参悟只允许选择已解锁层且同层互斥', () => {
expect(() =>
validateMeridianNodeIds({