优化:提升流派感悟消耗优化

This commit is contained in:
taojiacheng committed 2026-07-27 10:26:22 +08:00
1 parent 26e4d9c286
commit 6eb36d627c
14 files changed
+323 -67

No files matched your search

@@ -83,7 +83,7 @@ function ArchiveConversation({
messages.push({
id: 'catalog',
speaker: actor.name,
body: `阁中现有${data.definition.methods.map((method) => `《${method.name}》`).join('、')},都可逐卷查问。`,
body: `阁中现有${data.definition.methods.map((method) => `${method.name}`).join('、')},都可逐卷查问。`,
});
if (topic === 'limit' && data)
messages.push({
@@ -199,6 +199,7 @@ function PathDrawer({
createMeridianDrafts(state),
);
const [confirmUnlock, setConfirmUnlock] = useState(false);
const [confirmSave, setConfirmSave] = useState(false);
const [discardConfirm, setDiscardConfirm] = useState(false);
const draftsRef = useRef(drafts);
const savedRef = useRef(saved);
@@ -220,6 +221,7 @@ function PathDrawer({
mergeFreshMeridianState(draftsRef.current, savedRef.current, fresh),
);
setSaved(fresh);
setConfirmSave(false);
if (state) setSlot((current) => current ?? state.activeMeridianSlot);
}, [state, stateSignature]);
@@ -289,6 +291,7 @@ function PathDrawer({
sectJsonRequest('PUT', { nodeIds: drafts[slot] }),
);
setSaved((current) => ({ ...current, [slot]: [...drafts[slot]] }));
setConfirmSave(false);
};
const footer = discardConfirm ? (
@@ -349,6 +352,22 @@ function PathDrawer({
busy={busy}
onConfirm={() => void unlockLayer()}
/>
) : meridianFooterAction === 'save' && confirmSave ? (
<div className="flex flex-wrap items-center justify-between gap-3 text-sm">
<p className="text-crimson">
本次选定保存后暂不可更改,重置功能尚未开放。
</p>
<div className="flex gap-2">
<InkButton onClick={() => setConfirmSave(false)}>返回检查</InkButton>
<InkButton
variant="primary"
disabled={busy}
onClick={() => void saveDraft()}
>
{busy ? '保存中' : '确认保存'}
</InkButton>
</div>
</div>
) : meridianFooterAction === 'save' ? (
<div className="flex flex-wrap items-center justify-between gap-3 text-sm">
<p className="text-crimson">
@@ -358,9 +377,9 @@ function PathDrawer({
<InkButton
variant="primary"
disabled={busy}
onClick={() => void saveDraft()}
onClick={() => setConfirmSave(true)}
>
{busy ? '保存中' : `保存${terms.meridianLoadout}`}
{`保存${terms.meridianLoadout}`}
</InkButton>
</div>
) : meridianFooterAction === 'resolve-dirty' ? (
@@ -405,6 +424,7 @@ function PathDrawer({
onChange={(value) => {
setTab(value as DrawerTab);
setConfirmUnlock(false);
setConfirmSave(false);
setDiscardConfirm(false);
}}
/>
@@ -520,18 +540,24 @@ function PathDrawer({
)}
</section>
{state ? (
{state &&
StandardSectRules.enabledMeridianLoadoutSlots.length > 1 ? (
<InkTabs
items={StandardSectRules.meridianLoadoutSlots.map((value) => ({
value: String(value),
label: `${terms.meridianLoadout}${value}${state.activeMeridianSlot === value ? ' · 当前' : ''}${isMeridianDraftDirty(drafts, saved, value) ? ' · 未保存' : ''}`,
}))}
items={StandardSectRules.enabledMeridianLoadoutSlots.map(
(value) => ({
value: String(value),
label: `${terms.meridianLoadout}${value}${state.activeMeridianSlot === value ? ' · 当前' : ''}${isMeridianDraftDirty(drafts, saved, value) ? ' · 未保存' : ''}`,
}),
)}
activeValue={String(slot)}
onChange={(value) => setSlot(Number(value) as MeridianSlot)}
onChange={(value) => {
setConfirmSave(false);
setSlot(Number(value) as MeridianSlot);
}}
/>
) : (
) : !state ? (
<InkNotice>参悟第一层后,方可配置参悟方案。</InkNotice>
)}
) : null}
<div className="space-y-3">
{[...path.layers]
@@ -550,13 +576,23 @@ function PathDrawer({
.filter((node) => node.layerId === layer.id)
.map((node) => {
const selected = drafts[slot].includes(node.id);
const savedNodeId = saved[slot].find(
(nodeId) =>
path.nodes.find(
(candidate) => candidate.id === nodeId,
)?.layerId === layer.id,
);
const selectionLocked = Boolean(savedNodeId);
return (
<button
type="button"
key={node.id}
disabled={!available || busy}
disabled={
!available || busy || selectionLocked
}
onClick={() => {
setDiscardConfirm(false);
setConfirmSave(false);
setDrafts((current) => ({
...current,
[slot]: toggleMeridianNode({
@@ -566,9 +602,12 @@ function PathDrawer({
}),
}));
}}
className={`flex h-full flex-col items-stretch justify-start p-3 text-left text-sm leading-6 ${selected ? 'bg-crimson/10 text-crimson' : 'bg-ink/5'} ${available ? '' : 'cursor-not-allowed opacity-50'}`}
className={`flex h-full flex-col items-stretch justify-start p-3 text-left text-sm leading-6 ${selected ? 'bg-crimson/10 text-crimson' : 'bg-ink/5'} ${available && !selectionLocked ? '' : 'cursor-not-allowed opacity-50'}`}
>
<strong className="block">{node.name}</strong>
<strong className="block">
{node.name}
{savedNodeId === node.id ? ' · 已锁定' : ''}
</strong>
<p className="mt-1">
{preview.nodes.find(
(previewNode) => previewNode.id === node.id,
@@ -7,6 +7,7 @@ import {
MeridianLoadoutSpecification,
MethodTrainingSpecification,
SectTradition,
StandardSectRules,
type CultivatorSectState,
type SectPathDefinition,
type SectRuntime,
@@ -197,15 +198,19 @@ export class SectTraditionApplicationService {
slot: number,
nodeIds: string[],
) {
const normalizedSlot = this.requireSlot(slot);
const normalizedSlot = this.requireEnabledSlot(slot);
const { sect, path, pathState } = await this.requirePath(
cultivatorId,
pathId,
);
let validated: string[];
try {
validated = this.meridianLoadout.validate({
validated = this.meridianLoadout.validateUpdate({
path,
currentNodeIds:
pathState.meridianLoadouts.find(
(loadout) => loadout.slot === normalizedSlot,
)?.nodeIds ?? [],
nodeIds,
unlockedLayerIds: pathState.unlockedLayerIds,
methods: sect.methods,
@@ -236,7 +241,7 @@ export class SectTraditionApplicationService {
pathId: string,
slot: number,
) {
const normalizedSlot = this.requireSlot(slot);
const normalizedSlot = this.requireEnabledSlot(slot);
const { sect } = await this.requirePath(cultivatorId, pathId);
SectTradition.rehydrate(sect).activateMeridianLoadout(
pathId,
@@ -325,9 +330,19 @@ export class SectTraditionApplicationService {
);
}
private requireSlot(slot: number): 1 | 2 | 3 {
private requireEnabledSlot(slot: number): 1 | 2 | 3 {
if (slot !== 1 && slot !== 2 && slot !== 3)
throw new SectError('SECT_INVALID_MERIDIAN', '经脉方案槽无效', 400);
if (
!(StandardSectRules.enabledMeridianLoadoutSlots as readonly number[]).includes(
slot,
)
)
throw new SectError(
'SECT_INVALID_MERIDIAN',
'参悟方案暂未开放',
400,
);
return slot;
}
+22 -1
View File
@@ -95,13 +95,34 @@ CultivatorSectState
外门、内门、真传的最低晋升境界依次为炼气、筑基、金丹;贡献、任务与试炼仍由标准组织策略判定。
## 标准流派参悟
标准流派共六层,参悟成本独立于境界突破经验表。第 `N` 层成本为:
```text
修为 = 5,000 × 4^(N - 1)
灵石 = 修为 × 5
道心感悟 = 100
```
| 层级 | 境界要求 | 修为 | 灵石 | 道心感悟 |
| --- | --- | ---: | ---: | ---: |
| 第一层 | 筑基中期 | 5,000 | 25,000 | 100 |
| 第二层 | 金丹圆满 | 20,000 | 100,000 | 100 |
| 第三层 | 化神中期 | 80,000 | 400,000 | 100 |
| 第四层 | 炼虚圆满 | 320,000 | 1,600,000 | 100 |
| 第五层 | 大乘中期 | 1,280,000 | 6,400,000 | 100 |
| 终式 | 渡劫圆满 | 5,120,000 | 25,600,000 | 100 |
每个流派底层保留三套参悟方案,当前只开放方案一。节点首次保存后锁定;未选择节点的已解锁层级仍可继续补选,替换、移除和重置暂不开放。两个已习得流派可以随时免费切换。
## 边界约束
- 每个宗门固定六本心法,每本至少拥有一个基础神通。
- 流派层数与每层节点数由内容声明;层 ID、顺序和节点归属必须稳定且可校验。
- 玩家只保存已按顺序解锁的层 ID;流派编译器不得从等级推导倍率或层级。
- 四个主动槽固定、不可重复,只能装配已解锁主动神通。
- 每个流派独立保存等级、战术和三套参悟方案,只能激活一个流派。
- 每个流派独立保存层级、战术和三套参悟方案,只能激活一个流派;当前开放方案由标准规则统一声明。
- 未注册 ID、未知配置版本和非法持久化状态必须明确失败。
- battle-v5 运行时标签只能通过 `GameplayTags` 生成。
- 普通宗门不得复制组织模块,不得绕过 `SectAbilityFactory`/battle-v5 `AbilityFactory`,不得在流派编译器中按节点 ID 集中分派。
@@ -20,7 +20,7 @@ const HEAVY_SWORD_DEFINITION: SectPathDefinitionWithoutNodes = {
description:
'重剑不争一时之快,以身承势,以守养锋;剑意未足时稳住自身,剑意既成后,以一剑决定胜负。',
minRealm: '筑基',
minRealmStage: '初期',
minRealmStage: '中期',
layers: [...STANDARD_PATH_LAYERS],
defaultTacticId: 'heavy-break',
presentation: {
@@ -20,7 +20,7 @@ const SWIFT_SWORD_DEFINITION: SectPathDefinitionWithoutNodes = {
description:
'剑随身走,身随势变;以迅疾剑式连缀攻势,在交锋之间留下剑痕,最终将诸般剑影收束于《此剑平生》。',
minRealm: '筑基',
minRealmStage: '初期',
minRealmStage: '中期',
layers: [...STANDARD_PATH_LAYERS],
defaultTacticId: 'aggressive',
presentation: {
@@ -29,7 +29,7 @@ const hetuDefinition: SectPathDefinitionWithoutNodes = {
description:
'河图示其流,前法不灭,后法由此而生。三数成图之后,伤势、气血与法力都在同一轮转中得到续接。',
minRealm: '筑基',
minRealmStage: '初期',
minRealmStage: '中期',
layers: [...STANDARD_PATH_LAYERS],
defaultTacticId: 'small-cycle',
tactics: [
@@ -55,7 +55,7 @@ const luoshuDefinition: SectPathDefinitionWithoutNodes = {
description:
'洛书定其位,不等万法自然流转。移一宫、断一势、藏一印,在敌势真正成形之前先改写它的去处。',
minRealm: '筑基',
minRealmStage: '初期',
minRealmStage: '中期',
layers: [...STANDARD_PATH_LAYERS],
defaultTacticId: 'break-pattern',
tactics: [
@@ -28,7 +28,7 @@ const mirrorDefinition: SectPathDefinitionWithoutNodes = {
description:
'以承受制造因,以魔相兑现果。来力不急于拒绝,只把每一道因果留在镜中;佛相立因,魔相现报,无相令因果同时照见。',
minRealm: '筑基',
minRealmStage: '初期',
minRealmStage: '中期',
layers: [...STANDARD_PATH_LAYERS],
defaultTacticId: 'guard',
tactics: [
@@ -93,7 +93,7 @@ const demonDefinition: SectPathDefinitionWithoutNodes = {
description:
'以佛相主动沉血,以魔相强渡两息,以无相令燃血与渡厄同时显化。气血不是怒气的别名,而是每一门神通真正支付的渡河之资。',
minRealm: '筑基',
minRealmStage: '初期',
minRealmStage: '中期',
layers: [...STANDARD_PATH_LAYERS],
defaultTacticId: 'trial-fire',
tactics: [
@@ -23,7 +23,7 @@ const tideDefinition: SectPathDefinitionWithoutNodes = {
id: YOUDU_TIDE_PATH_ID,
name: '招魂渡夜',
description: '一声唤名落入黑水,千里游魂都听见自己的回音。此道让忘川一寸寸漫过归路。',
minRealm: '筑基', minRealmStage: '初期',
minRealm: '筑基', minRealmStage: '中期',
layers: [...STANDARD_PATH_LAYERS],
defaultTacticId: 'tide-cycle',
tactics: [
@@ -48,7 +48,7 @@ const decreeDefinition: SectPathDefinitionWithoutNodes = {
id: YOUDU_DECREE_PATH_ID,
name: '镇魄司命',
description: '见影而知魂,知魂而书名;铁钉落下之前,门人已量过每一道离身缝隙。',
minRealm: '筑基', minRealmStage: '初期',
minRealm: '筑基', minRealmStage: '中期',
layers: [...STANDARD_PATH_LAYERS],
defaultTacticId: 'pin-the-caster',
tactics: [
@@ -4,6 +4,7 @@ export const StandardSectRules = Object.freeze({
methodSlots: Object.freeze([1, 2, 3, 4, 5, 6] as const),
activeAbilitySlotCount: 4,
meridianLoadoutSlots: Object.freeze([1, 2, 3] as const),
enabledMeridianLoadoutSlots: Object.freeze([1] as Array<1 | 2 | 3>),
meridianNodeTransportLimit: 64,
combatResourceCount: 1,
});
@@ -16,6 +16,7 @@ import { createAbilitySlots } from '../presentation/abilityLoadout';
import {
assertMethodTrainingTarget,
isAbilityUnlocked,
validateMeridianLoadoutUpdate,
validateMeridianNodeIds,
} from '../progression/progression';
@@ -160,6 +161,12 @@ export class MeridianLoadoutSpecification {
validate(candidate: Parameters<typeof validateMeridianNodeIds>[0]): string[] {
return validateMeridianNodeIds(candidate);
}
validateUpdate(
candidate: Parameters<typeof validateMeridianLoadoutUpdate>[0],
): string[] {
return validateMeridianLoadoutUpdate(candidate);
}
}
export class AbilityLoadoutSpecification {
@@ -1,4 +1,3 @@
import { EXP_CAP_TABLE } from '@shared/config/cultivationProgress';
import { getRealmStageRank } from '@shared/config/realmProgression';
import type { RealmStage, RealmType } from '@shared/types/constants';
import type {
@@ -43,6 +42,13 @@ export const STANDARD_SECT_METHOD_COST_CURVE = {
spiritStoneRoundUnit: 100,
} as const;
export const STANDARD_SECT_PATH_COST_CURVE = {
baseCultivationExp: 5_000,
growthMultiplier: 4,
spiritStoneMultiplier: 5,
comprehensionInsight: 100,
} as const;
function roundUpToUnit(value: number, unit: number): number {
return Math.ceil(value / unit) * unit;
}
@@ -64,16 +70,16 @@ function methodLevelTrainingCost(level: number): SectTrainingCost {
};
}
function pathLayerCost(
realm: RealmType,
stage: RealmStage,
comprehensionInsight: number,
): SectTrainingCost {
const cultivationExp = Math.ceil(EXP_CAP_TABLE[realm][stage] * 0.5);
function pathLayerCost(order: number): SectTrainingCost {
const cultivationExp =
STANDARD_SECT_PATH_COST_CURVE.baseCultivationExp *
STANDARD_SECT_PATH_COST_CURVE.growthMultiplier ** (order - 1);
return {
cultivationExp,
comprehensionInsight,
spiritStones: cultivationExp * 10,
comprehensionInsight:
STANDARD_SECT_PATH_COST_CURVE.comprehensionInsight,
spiritStones:
cultivationExp * STANDARD_SECT_PATH_COST_CURVE.spiritStoneMultiplier,
};
}
@@ -83,48 +89,48 @@ export const STANDARD_PATH_LAYERS: readonly SectPathLayerDefinition[] = [
order: 1,
label: '第一层',
minRealm: '筑基',
minRealmStage: '初期',
cost: pathLayerCost('筑基', '初期', 10),
minRealmStage: '中期',
cost: pathLayerCost(1),
},
{
id: '2',
order: 2,
label: '第二层',
minRealm: '筑基',
minRealm: '金丹',
minRealmStage: '圆满',
cost: pathLayerCost('筑基', '圆满', 15),
cost: pathLayerCost(2),
},
{
id: '3',
order: 3,
label: '第三层',
minRealm: '金丹',
minRealmStage: '圆满',
cost: pathLayerCost('金丹', '圆满', 20),
minRealm: '化神',
minRealmStage: '中期',
cost: pathLayerCost(3),
},
{
id: '4',
order: 4,
label: '第四层',
minRealm: '元婴',
minRealm: '炼虚',
minRealmStage: '圆满',
cost: pathLayerCost('元婴', '圆满', 25),
cost: pathLayerCost(4),
},
{
id: '5',
order: 5,
label: '第五层',
minRealm: '化神',
minRealm: '大乘',
minRealmStage: '中期',
cost: pathLayerCost('化神', '中期', 30),
cost: pathLayerCost(5),
},
{
id: 'ultimate',
order: 6,
label: '终式',
minRealm: '化神',
minRealm: '渡劫',
minRealmStage: '圆满',
cost: pathLayerCost('化神', '圆满', 40),
cost: pathLayerCost(6),
},
] as const;
@@ -97,6 +97,21 @@ export function validateMeridianNodeIds(args: {
return uniqueIds;
}
export function validateMeridianLoadoutUpdate(args: {
path: SectPathDefinition;
currentNodeIds: string[];
nodeIds: string[];
unlockedLayerIds: string[];
methods: Partial<Record<SectMethodId, number>>;
}): string[] {
const validated = validateMeridianNodeIds(args);
const nextNodeIds = new Set(validated);
if (args.currentNodeIds.some((nodeId) => !nextNodeIds.has(nodeId))) {
throw new Error('已保存参悟节点不可更改,重置功能尚未开放');
}
return validated;
}
export function assertMethodTrainingTarget(args: {
definition: SectDefinition;
methodId: SectMethodId;
@@ -3,7 +3,9 @@ import {
getPathProgress,
getSectMethodLevelCap,
getSectMethodTrainingCost,
StandardSectRules,
standardSectProgression,
validateMeridianLoadoutUpdate,
validateMeridianNodeIds,
} from '..';
import { HEAVY_SWORD_PATH, SWIFT_SWORD_PATH } from '../../content/lingxiao';
@@ -95,38 +97,65 @@ describe('通用宗门成长', () => {
});
it('六层按顺序、境界和精确资源成本解锁', () => {
expect(HEAVY_SWORD_PATH.layers.map((layer) => layer.cost)).toEqual([
{ cultivationExp: 950, comprehensionInsight: 10, spiritStones: 9_500 },
expect(
HEAVY_SWORD_PATH.layers.map((layer) => ({
realm: `${layer.minRealm}${layer.minRealmStage}`,
cost: layer.cost,
})),
).toEqual([
{
cultivationExp: 2_500,
comprehensionInsight: 15,
spiritStones: 25_000,
realm: '筑基中期',
cost: {
cultivationExp: 5_000,
comprehensionInsight: 100,
spiritStones: 25_000,
},
},
{
cultivationExp: 13_500,
comprehensionInsight: 20,
spiritStones: 135_000,
realm: '金丹圆满',
cost: {
cultivationExp: 20_000,
comprehensionInsight: 100,
spiritStones: 100_000,
},
},
{
cultivationExp: 47_000,
comprehensionInsight: 25,
spiritStones: 470_000,
realm: '化神中期',
cost: {
cultivationExp: 80_000,
comprehensionInsight: 100,
spiritStones: 400_000,
},
},
{
cultivationExp: 65_000,
comprehensionInsight: 30,
spiritStones: 650_000,
realm: '炼虚圆满',
cost: {
cultivationExp: 320_000,
comprehensionInsight: 100,
spiritStones: 1_600_000,
},
},
{
cultivationExp: 125_000,
comprehensionInsight: 40,
spiritStones: 1_250_000,
realm: '大乘中期',
cost: {
cultivationExp: 1_280_000,
comprehensionInsight: 100,
spiritStones: 6_400_000,
},
},
{
realm: '渡劫圆满',
cost: {
cultivationExp: 5_120_000,
comprehensionInsight: 100,
spiritStones: 25_600_000,
},
},
]);
const progress = getPathProgress({
path: HEAVY_SWORD_PATH,
unlockedLayerIds: ['1', '2', '3', '4'],
realm: '化神',
realm: '大乘',
stage: '中期',
});
expect(progress.unlockedLayers.map((layer) => layer.id)).toEqual([
@@ -148,4 +177,127 @@ describe('通用宗门成长', () => {
}),
).toThrow('按顺序解锁');
});
it('六层在各自境界节点开放且不绑定心法', () => {
const gates = [
{
layerId: '1',
before: ['筑基', '初期'],
reached: ['筑基', '中期'],
},
{
layerId: '2',
before: ['金丹', '后期'],
reached: ['金丹', '圆满'],
},
{
layerId: '3',
before: ['化神', '初期'],
reached: ['化神', '中期'],
},
{
layerId: '4',
before: ['炼虚', '后期'],
reached: ['炼虚', '圆满'],
},
{
layerId: '5',
before: ['大乘', '初期'],
reached: ['大乘', '中期'],
},
{
layerId: 'ultimate',
before: ['渡劫', '后期'],
reached: ['渡劫', '圆满'],
},
] as const;
for (const [index, gate] of gates.entries()) {
const unlockedLayerIds = HEAVY_SWORD_PATH.layers
.slice(0, index)
.map((layer) => layer.id);
const before = getPathProgress({
path: HEAVY_SWORD_PATH,
unlockedLayerIds,
realm: gate.before[0],
stage: gate.before[1],
methods: {},
});
const reached = getPathProgress({
path: HEAVY_SWORD_PATH,
unlockedLayerIds,
realm: gate.reached[0],
stage: gate.reached[1],
methods: {},
});
expect(before.nextLayer?.id).toBe(gate.layerId);
expect(before.nextLayerAvailable).toBe(false);
expect(reached.nextLayer?.id).toBe(gate.layerId);
expect(reached.nextLayerAvailable).toBe(true);
}
});
it('流派指数成本累计正确且每层均消耗满额道心感悟', () => {
const onePath = HEAVY_SWORD_PATH.layers.reduce(
(total, layer) => ({
cultivationExp: total.cultivationExp + layer.cost.cultivationExp,
comprehensionInsight:
total.comprehensionInsight + layer.cost.comprehensionInsight,
spiritStones: total.spiritStones + layer.cost.spiritStones,
}),
{ cultivationExp: 0, comprehensionInsight: 0, spiritStones: 0 },
);
expect(onePath).toEqual({
cultivationExp: 6_825_000,
comprehensionInsight: 600,
spiritStones: 34_125_000,
});
expect({
cultivationExp: onePath.cultivationExp * 2,
comprehensionInsight: onePath.comprehensionInsight * 2,
spiritStones: onePath.spiritStones * 2,
}).toEqual({
cultivationExp: 13_650_000,
comprehensionInsight: 1_200,
spiritStones: 68_250_000,
});
for (const layer of HEAVY_SWORD_PATH.layers) {
expect(layer.cost.comprehensionInsight).toBe(100);
expect(layer.cost.spiritStones).toBeGreaterThan(
layer.cost.cultivationExp,
);
}
});
it('当前只开放方案一并保留三槽底层容量', () => {
expect(StandardSectRules.meridianLoadoutSlots).toEqual([1, 2, 3]);
expect(StandardSectRules.enabledMeridianLoadoutSlots).toEqual([1]);
});
it('已保存节点不可移除或替换,但可为新层级追加选择', () => {
const base = {
path: HEAVY_SWORD_PATH,
currentNodeIds: ['heavy-opening'],
unlockedLayerIds: ['1', '2'],
methods: {},
};
expect(
validateMeridianLoadoutUpdate({
...base,
nodeIds: ['heavy-opening', 'heavy-triple-ridge'],
}),
).toEqual(['heavy-opening', 'heavy-triple-ridge']);
expect(() =>
validateMeridianLoadoutUpdate({
...base,
nodeIds: [],
}),
).toThrow('重置功能尚未开放');
expect(() =>
validateMeridianLoadoutUpdate({
...base,
nodeIds: ['heavy-hidden-weight'],
}),
).toThrow('重置功能尚未开放');
});
});