fix: 首启同意门(consent 事件存证)+ 条件渲染系统(flags 推导 27 变量)+ 10 类内容-界面不一致修复
This commit is contained in:
@@ -3,6 +3,11 @@ import { FOLLOW_UP_DIMENSIONS, FOLLOW_UP_PRIORITY } from './types';
|
||||
import type { EventStore, StoryEvent } from './eventStore';
|
||||
import { makeEvent } from './eventStore';
|
||||
import type { JevClient, JevQuestion } from '../services/jev';
|
||||
import type { Flags } from './flags';
|
||||
import { choiceFlags, narrationFlags, resolveConditionalText } from './flags';
|
||||
|
||||
/** 告知文案版本:文案变动时升版,consent 事件存证所同意的版本(ADR-0004) */
|
||||
export const CONSENT_VERSION = 'act1-consent-v1';
|
||||
|
||||
export interface EngineState {
|
||||
sessionId: string;
|
||||
@@ -12,6 +17,8 @@ export interface EngineState {
|
||||
activeVariant: SceneVariant | null;
|
||||
/** 追问补全:讲述后检测到缺失维度时挂起,等用户回答或"记不清了" */
|
||||
pendingFollowUp: PendingFollowUp | null;
|
||||
/** 分支变量(附录 A):从本轨迹的选择/讲述事件增量推导,驱动条件文本 */
|
||||
flags: Flags;
|
||||
/** 全部三章完成后为 true → 进入画像报告 */
|
||||
finished: boolean;
|
||||
}
|
||||
@@ -78,8 +85,10 @@ export class StoryEngine {
|
||||
nodeId: start.startNodeId,
|
||||
activeVariant: null,
|
||||
pendingFollowUp: null,
|
||||
flags: {},
|
||||
finished: false,
|
||||
};
|
||||
this.skipHiddenNodes();
|
||||
}
|
||||
|
||||
currentChapter(): Chapter {
|
||||
@@ -110,6 +119,29 @@ export class StoryEngine {
|
||||
} else {
|
||||
throw new Error(`advance() 不适用于 ${node.type} 节点`);
|
||||
}
|
||||
this.skipHiddenNodes();
|
||||
}
|
||||
|
||||
/**
|
||||
* 条件节点跳过:节点级【若 …】条件不满足的 dialogue / cutscene 对玩家不可见,
|
||||
* 直接走到 next(条件分支不产生死路)。每次状态迁移后调用。
|
||||
*/
|
||||
private skipHiddenNodes(): void {
|
||||
for (let guard = 0; guard < 100; guard++) {
|
||||
const node = this.currentNode();
|
||||
if (node.type !== 'dialogue' && node.type !== 'cutscene') return;
|
||||
const text = node.type === 'dialogue' ? node.text : node.caption;
|
||||
if (resolveConditionalText(text, this.state.flags) !== null) return;
|
||||
this.state.nodeId = node.next;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染文本:求值文案中的全部【若 …】条件标记(节点级 + 内联段),
|
||||
* 返回玩家可见文本。条件标记本身永不渲染给玩家。
|
||||
*/
|
||||
renderText(text: string): string {
|
||||
return resolveConditionalText(text, this.state.flags) ?? '';
|
||||
}
|
||||
|
||||
/** B 类选择:落事件并按选项跳转 */
|
||||
@@ -129,7 +161,9 @@ export class StoryEngine {
|
||||
optionLabel: option.label,
|
||||
}),
|
||||
);
|
||||
Object.assign(this.state.flags, choiceFlags(node.pointId, option.id));
|
||||
this.state.nodeId = option.next;
|
||||
this.skipHiddenNodes();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -235,8 +269,10 @@ export class StoryEngine {
|
||||
transcript,
|
||||
});
|
||||
await this.store.append(event);
|
||||
Object.assign(this.state.flags, narrationFlags(node.pointId, variant.variantId, transcript));
|
||||
this.state.activeVariant = variant;
|
||||
this.state.nodeId = node.next;
|
||||
this.skipHiddenNodes();
|
||||
return { variant, event };
|
||||
}
|
||||
|
||||
@@ -370,6 +406,30 @@ export class StoryEngine {
|
||||
}),
|
||||
);
|
||||
this.state.nodeId = node.next;
|
||||
this.skipHiddenNodes();
|
||||
}
|
||||
|
||||
/**
|
||||
* 同意存证(ADR-0004):告知与同意的 UX 由 ConsentGate 承担,
|
||||
* 同意动作在这里 append 一条 consent 事件(含版本与时间戳)。
|
||||
* 事件存在于任意轨迹即视为已同意——同意门全剧只出现一次。
|
||||
*/
|
||||
async hasConsent(): Promise<boolean> {
|
||||
return (await this.store.list()).some((e) => e.kind === 'consent');
|
||||
}
|
||||
|
||||
async grantConsent(): Promise<void> {
|
||||
if (await this.hasConsent()) return;
|
||||
await this.store.append(
|
||||
makeEvent({
|
||||
sessionId: this.state.sessionId,
|
||||
chapterId: this.state.chapterId,
|
||||
kind: 'consent',
|
||||
pointId: 'CONSENT',
|
||||
dimensions: [],
|
||||
consentVersion: CONSENT_VERSION,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -384,6 +444,8 @@ export class StoryEngine {
|
||||
this.state.nodeId = chapter.startNodeId;
|
||||
this.state.activeVariant = null;
|
||||
this.state.pendingFollowUp = null;
|
||||
this.state.flags = {};
|
||||
this.state.finished = false;
|
||||
this.skipHiddenNodes();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user