feat: 自定义主角称呼(profile.name + {name} 占位符 + 自报句式预填 + 报告/分享卡展示)
This commit is contained in:
+38
-6
@@ -418,7 +418,7 @@ async function walkAll(eng: StoryEngine, picker: Record<string, string> = {}) {
|
||||
`推进后持久化当前位置(${snap?.chapterId}/${snap?.nodeId})`,
|
||||
);
|
||||
const eng2 = new StoryEngine(chapters, s, new MockJevClient(), undefined, progress);
|
||||
const restored = eng2.restore(progress.load());
|
||||
const restored = await eng2.restore(progress.load());
|
||||
check(
|
||||
restored && eng2.currentNode().id === 'c1-d5' && eng2.state.sessionId === snap?.sessionId,
|
||||
'重建引擎从持久化位置恢复(sessionId 一致,事件轨迹续接)',
|
||||
@@ -427,11 +427,11 @@ async function walkAll(eng: StoryEngine, picker: Record<string, string> = {}) {
|
||||
// 内容更新导致持久化节点消失 → 降级该章开头
|
||||
const eng3 = new StoryEngine(chapters, s, new MockJevClient(), undefined, progress);
|
||||
check(
|
||||
eng3.restore({ sessionId: 'ghost', chapterId: 'chapter1', nodeId: 'deleted-node', flags: {} }) &&
|
||||
(await eng3.restore({ sessionId: 'ghost', chapterId: 'chapter1', nodeId: 'deleted-node', flags: {} })) &&
|
||||
eng3.currentNode().id === chapters[0].startNodeId,
|
||||
'持久化节点已不存在 → 降级到该章开头(不崩溃)',
|
||||
);
|
||||
check(!eng3.restore({ sessionId: 'ghost', chapterId: 'chapter99', nodeId: 'x', flags: {} }), '持久化章节不存在 → 不恢复');
|
||||
check(!(await eng3.restore({ sessionId: 'ghost', chapterId: 'chapter99', nodeId: 'x', flags: {} })), '持久化章节不存在 → 不恢复');
|
||||
// replay:新 sessionId + 持久化位置重置(条件不满足的开场节点会被跳过,落点是跳过后的首个可见节点)
|
||||
eng2.replay('chapter2');
|
||||
const after = progress.load();
|
||||
@@ -461,7 +461,7 @@ class FailingJev extends MockJevClient {
|
||||
const silentVariant = c102.variants.find((v) => v.signature.includes('沉默'))!;
|
||||
const ev = s.events.find((e) => e.kind === 'narration' && e.pointId === 'C1-02');
|
||||
check(ev?.variantId === silentVariant.variantId, 'Jev 故障:情绪签名解析降级到沉默/通用档变体');
|
||||
// 12b. 身份抽取失败 → 全空字段(UI 直接进可手填确认页),不抛出
|
||||
// 12b. 身份抽取失败 → 硬过滤字段全空(称呼走本地启发式仍可预填),UI 直接进可手填确认页,不抛出
|
||||
const eng2 = new StoryEngine(chapters, new MemoryStore(), new FailingJev());
|
||||
await walkTo(eng2, 'c1-01');
|
||||
let profile: IdentityProfile | null = null;
|
||||
@@ -472,13 +472,45 @@ class FailingJev extends MockJevClient {
|
||||
threw = true;
|
||||
}
|
||||
check(
|
||||
!threw && profile !== null && Object.values(profile).every((v) => v === ''),
|
||||
'Jev 故障:身份抽取降级为全空字段进手填确认页(不抛出)',
|
||||
!threw &&
|
||||
profile !== null &&
|
||||
profile.city === '' &&
|
||||
profile.age === '' &&
|
||||
profile.gender === '' &&
|
||||
profile.occupation === '' &&
|
||||
profile.education === '',
|
||||
'Jev 故障:身份抽取降级为空字段进手填确认页(不抛出)',
|
||||
);
|
||||
await eng2.confirmIdentity(profile!);
|
||||
check(eng2.state.nodeId === 'c1-d2', 'Jev 故障:手填确认后流程照常推进');
|
||||
}
|
||||
|
||||
// --- 13. 玩家称呼:{name} 占位符替换、预填启发式、恢复后带回 ---
|
||||
{
|
||||
// 13a. 启发式:只认"我叫/叫我/称呼我 X",其余句式不瞎猜
|
||||
const s = new MemoryStore();
|
||||
const eng = new StoryEngine(chapters, s, new MockJevClient());
|
||||
await walkTo(eng, 'c1-01');
|
||||
const p1 = await eng.extractIdentity('我叫林晚,29岁,在杭州做互联网产品经理,硕士学历');
|
||||
check(p1.name === '林晚', `称呼预填:「我叫林晚」→ "${p1.name}"`);
|
||||
const p2 = await eng.extractIdentity('29岁,杭州,产品经理');
|
||||
check(p2.name === '', '称呼预填:无自报句式 → 留空手填(不硬抽)');
|
||||
// 13b. {name} 替换与回退
|
||||
await eng.confirmIdentity(p1);
|
||||
check(eng.renderText('馆长:{name},登记完成。') === '馆长:林晚,登记完成。', '{name} 替换为玩家称呼');
|
||||
const eng2 = new StoryEngine(chapters, new MemoryStore(), new MockJevClient());
|
||||
check(eng2.renderText('馆长:{name},登记完成。') === '馆长:你,登记完成。', '未设置称呼时 {name} 回退「你」');
|
||||
// 13c. 称呼随轨迹事件走:restore 后从该 session 的 profile 事件取回(旧事件缺 name 不崩溃)
|
||||
await walkTo(eng, 'c1-d5');
|
||||
const d2 = chapters[0].nodes.find((n) => n.id === 'c1-d2');
|
||||
check(d2?.type === 'dialogue' && eng.renderText(d2.text).includes('林晚'), '馆长登记台词中的 {name} 渲染为称呼');
|
||||
const progress = new MemoryProgressStore();
|
||||
progress.save({ sessionId: eng.state.sessionId, chapterId: 'chapter1', nodeId: 'c1-d5', flags: { ...eng.state.flags } });
|
||||
const eng3 = new StoryEngine(chapters, s, new MockJevClient(), undefined, progress);
|
||||
await eng3.restore(progress.load());
|
||||
check(eng3.state.playerName === '林晚', '断点恢复后从 profile 事件取回称呼');
|
||||
}
|
||||
|
||||
if (failures > 0) {
|
||||
console.error(`\n${failures} 项校验失败`);
|
||||
process.exit(1);
|
||||
|
||||
Reference in New Issue
Block a user