剧情追踪卡片 · 字段净化与输出规则 v1
状态:v1 草案 适用:
src/components/StoryTrackingCard.tsx及其下游(DesktopWorkspacePanel / PortraitWorkspacePanel / SystemHubPanel / App.tsx 的 storyTrackingSnapshot 构造) 触发原因:截图显示剧情追踪面板里把开发者内部 ID、tag 拼接、阶段平铺当玩家文案直接渲染(tax_reported、brief=risk-tax_reported、6 段验收阶段用->平铺等)
1. 现状(截图所见)
StoryTrackingCard 的玩家可见区域被分成 4 块,每块都出现了"开发者内容当成玩家文案"的现象:
| 区块 | 截图所见(原文照抄) | 玩家应该看到什么 |
| --- | --- | --- |
| 主标题 + 下一步 | 【终局危机】税吏翻账・偷税漏税被举报 + 下一步:危机源头:官府稽税黑名单 | 当前主线剧情标题 + 一句话剧情化下一步提示 |
| 主链条(紫色小条) | 主语:【终局危机】税吏翻账...・压首稽税黑名单 / tax_reported・种术激活:预警...・引 brief=risk-tax_reported / tax_reported・终局优先 | 不应该存在——这块本身是技术桥接日志 |
| YEAR1 规划 phaseLine | 月 1 开张适应期・开业教学 / 基础经营闭环 | 当前月阶段 + 一句剧情叙事(这个相对可接受) |
| YEAR1 规划 attributeLine | 属性:服务 / 厨艺 / 账房 / 巡防 | 不应该存在——4 个属性平铺,没有上下文,是开发者切片 |
| YEAR1 规划 itemLine | 投放:生存恢复 / 轻量侦查 / 古钱币 / 幸运制钱 | 半污染——focus 是剧情化但 storyItems 是内部物品 ID,混着显示 |
| YEAR1 规划 checklistLine | 验收:开张教学闭环 -> 街访导入与首轮风险闭环 -> 扩建与半年经营闭环 -> 两转升温与年度预热闭环 -> 擂台高潮闭环 -> 岁末收束闭环 | 不应该存在——6 个阶段全量平铺,没有"当前所处阶段"高亮,-> 是开发者分隔符 |
2. 字段分类标准(输出规则的核心)
2.1 三类字段
| 类别 | 定义 | 处理方式 |
| --- | --- | --- |
| 玩家文案(Player-Facing) | 描述剧情 / 经营 / 状态,没有任何内部 ID、tag、阶段 key、技术路径 | 可直接显示,但要走 §2.3 的措辞审查 |
| 开发者桥接(Developer Bridge) | 跨模块传递用的内部 ID / tag / 状态机 key(如 tax_reported、risk-tax_reported、mainchainLine、briefingRefs) | 不直接渲染;要么不显示,要么只在开发者调试面板(?dev=1)显示 |
| 元数据(Metadata) | 玩家可见但应"低调"展示(如 第 8 日・深 06:00 时间戳、章节名) | 可显示,但要有视觉降噪(灰底、字号 ≤10px) |
2.2 4 条快速判断标准
给一个新字段,往下走一遍,任意一条命中 → 不能直接渲染:
- 包含下划线 / 蛇形 key?
tax_reported、risk-tax_reported、fail-chain-tax_reported、briefing-refs-...—— 这些都是开发者 ID,必须走映射表(如FAILURE_CHAIN_TITLE_LABELS、FAILURE_SEED_TAG_LABELS)后才能显示。 - 包含路径分隔符或 URL 参数?
brief=risk-tax_reported / tax_reported、a / b / c这种/或->或=>平铺 4 个以上 token 的,都是技术拼接。 - 超过 3 个同类 token 平铺?
服务 / 厨艺 / 账房 / 巡防、开张教学闭环 -> 街访导入... -> 扩建...—— 玩家不会读 6 段平铺;超过 3 个同类 token 平铺一律降级。 - 字段名暗示是技术桥接?
*Bridge、*Refs、*Tags、*Source、*Seed、*mainchainLine、pressureSourceTags、failureSeedTags、briefingRefs—— 名字里有Bridge / Refs / Tags / Source / Seed / mainchain的,默认是开发者字段。
2.3 玩家文案的措辞审查
哪怕走通了 §2.2 不算开发者字段,渲染前还要过一遍:
- 不能有「·」或「/」分号超过 2 个连用
- 不能以
:/:结尾(玩家应该看到完整句子) - 不能用全大写 + 下划线(
YEAR1_PLAN这种 snake_case) - 不能有英文单词混在中文里(除非是专有名词,如
ID) - 数字前不能直接堆单位符号(
16 文✅,铜钱:16 文❌)
3. 受污染字段清单(按代码位置)
3.1 App.tsx 第 2997-3128 行 storyTrackingSnapshot 构造
| 字段 | 当前来源 | 污染等级 | 处理 |
| --- | --- | --- | --- |
| title | primaryBond?.title ?? briefingLead?.title ?? yearTracking?.currentChapterTitle ?? '同福剧情总览' | 🟢 良 | 保留 |
| nextHint | primaryBond?.unlockEffects[0] ?? briefingLead?.detail ?? briefingLead?.summary ?? '下一章:...' | 🟡 半 | detail 经常是 危机源头:xxx,语义偏技术。需要给 briefing 在生成 detail 时限制最大长度 + 强制叙事化(中文短句,不带 : 之后超过 20 字的列表) |
| progressLabel | primaryBond.stageLabel + value/100 / 今日线索 N 条 / 年线 N% | 🟢 良 | 保留 |
| pressureSourceTags | 把 entry.title、entry.summary、entry.detail、entry.sourceId、entry.id、chapterId、phaseLine 等塞进数组 | 🔴 严重 | 不应进入玩家视图。只用于内部 bridge 构建(已经走 useTasklineFailureBridge),渲染层不要碰 |
| failureSeedTags | 同上,加上 failureRecord.seedTags、endgameEntry.failureSeedTags | 🔴 严重 | 同上 |
| briefingRefs | entry.id、entry.sourceId、chapterId、slotId、failureId | 🔴 严重 | 同上 |
| trackingJumpTarget | 'endgame' \| 'briefing' 枚举 | 🟢 元数据 | 渲染层只用它决定跳哪个按钮,不要把它当文案渲染 |
| year1Plan | 整个 Year1PlanSnapshot | 🟡 半 | 详情见 §3.3 |
3.2 StoryTrackingCard.tsx 第 501-505 行 mainchainLine 渲染
// 当前(污染):
{bridge?.mainchainLine && (
<div className="mt-2 rounded-lg ...">
主链:{bridge.mainchainLine}
</div>
)}
mainchainLine 是 buildMainchainLine() 用 pressureSourceTags + failureSeedTags + briefingRefs 拼成的 主语:xxx・压 xxx・种 xxx・引 xxx・终 xxx 格式 —— 这是开发者调试日志,不是玩家文案。
建议:直接删除这段渲染。bridge 只服务于跳转按钮(TasklineFailureJumpButton)和跨模块状态共享,不应在玩家界面露出。如果想保留"主链"概念让玩家理解当前危机上下文,应该从 bridge 里抽 riskLevel + currentTitle + nextAction 三个字段单独构造玩家文案,而不是复用 mainchainLine。
3.3 year1Plan.phaseLine / attributeLine / itemLine / checklistLine 渲染
getYear1PlanSnapshot() 在 src/data/year1Plan.ts:348-371 构造这 4 个字段,它本身就是开发者快照(命名带 Snapshot,就是为了排查状态用的)。但被 StoryTrackingCard.tsx:507-523 直接渲染了。
| 字段 | 当前拼法 | 处理 |
| --- | --- | --- |
| phaseLine | ${monthPhase.label} ・ ${monthPhase.headline} | 🟢 可保留(剧情叙事句) |
| attributeLine | YEAR1_PLAN_ATTRIBUTE_LINKS.slice(0, 4).map(label).join(' / ') | 🔴 删除渲染。属性不应该在追踪卡出现,玩家在 STAFF 面板和 HUD 里看 |
| itemLine | ${itemBand.focus} ・ ${itemBand.storyItems.slice(0, 2).join(' / ')} | 🟡 改为只显示 itemBand.focus,丢掉 storyItems 切片 |
| checklistLine | YEAR1_PLAN_CHECKLIST_STAGES.map(stage => stage.label).join(' -> ') | 🔴 删除渲染。改为显示当前阶段(checklistStage.label + checklistStage.passSignal),用 ✓ / ○ 高亮 mustNodes 中已完成 / 未完成 |
3.4 bridge.trackingJumpTarget 文案
StoryTrackingCard.tsx:494 用 bridge.trackingJumpTarget === 'endgame' ? '打开终局解释层' : '回看江湖简报' —— 这个 OK,按钮文案是叙事化中文,不是开发者字段。但 target 本身('endgame' \| 'briefing')作为开发者枚举,在按钮 label 上没暴露,没问题。
4. 输出规则(落到 AGENTS.md 的段落建议)
把以下段落贴到 AGENTS.md 第 7 节「不要做的事」后面,作为新一节:
4.1 玩家文案 vs 开发者字段
禁止把开发者字段当玩家文案渲染。
当一段文本包含以下任意一项时,禁止直接
{{text}}进 JSX:
- 内部 ID(snake_case、带下划线的,如
tax_reported、brief-refs-...)- 技术 tag(
pressureSourceTags、failureSeedTags、briefingRefs、sourceId等数组元素)- 路径 / URL 参数(
brief=risk-xxx / xxx)- 3 个以上同类 token 用
/、->、=>平铺的拼接串- 字段名带
Bridge/Refs/Tags/Source/Seed/mainchain正确做法:
- 走映射表(如
FAILURE_CHAIN_TITLE_LABELS、FAILURE_SEED_TAG_LABELS)翻译成中文剧情文案- 只在
?dev=1调试面板渲染- 或者干脆不显示(让玩家在剧情面板 / 简报面板里通过正常交互触发信息)
审查工具:
src/components/StoryTrackingCard.tsx任何 JSX 里的{xxx}都得过这一关。
4.2 阶段平铺规则
禁止在玩家界面把 4 段以上的"阶段名"平铺展示(如验收阶段 6 段、年度规划 12 段)。
正确做法:
- 只显示当前所处阶段 + 1-2 句剧情叙事
- 完整 12 阶段 / 6 验收放玩家设置 / 说明面板
- 如果一定要显示进度,用
当前:月 3 初遇挑战期+ 进度条,而不是 12 段平铺
5. 整改清单(按优先级)
| # | 改动 | 位置 | 工作量 |
| --- | --- | --- | --- |
| 1 | 删除 bridge.mainchainLine 渲染 | StoryTrackingCard.tsx:501-505 | 5 行 |
| 2 | 删除 year1Plan.attributeLine 渲染 | StoryTrackingCard.tsx:516 | 1 行 |
| 3 | 改写 year1Plan.checklistLine 为当前阶段高亮 + mustNodes 进度 | StoryTrackingCard.tsx:519-521 + getYear1PlanSnapshot | 30 行 |
| 4 | 简化 year1Plan.itemLine 只保留 focus | year1Plan.ts:357 + StoryTrackingCard.tsx:517 | 5 行 |
| 5 | 给 nextHint 加长度截断(≤24 字)和叙事化过滤 | App.tsx:3044-3050 | 15 行 |
| 6 | 把「开发者字段不直接渲染」写入 AGENTS.md 第 4 节输出规则 | AGENTS.md | 段落级 |
| 7 | (可选)新增 ?dev=1 调试入口,把 pressureSourceTags 等桥接数据放进单独抽屉 | StoryTrackingCard.tsx | 50 行 |
预期:前 5 项落地后,截图所示的污染全部消失。?dev=1 是 nice-to-have,给后续排错留口子。
6. 自检脚本(可选)
写一个小工具扫所有 src/components/**/*.tsx,找:
{bridge.mainchainLine}这种直接渲染mainchainLine的.tags?.map直接渲染 tag 数组的.join(' / ')/.join(' -> ')在 JSX 文本节点里出现的
跑一遍就能发现下一个 StoryTrackingCard 类型的污染源。
7. 关联文档
docs/design/active/道具系统新核心UI视觉规范-v1.md—— 视觉降噪的同类问题AGENTS.md第 1 节三层架构 —— 玩家文案(UI 层)和开发者字段(逻辑层)的边界问题本质就是跨层污染