# v0.16 — 解码阈值叙事事件 + loreLayer 已读 + idleHistory 详情 + 移动端适配 > 回响星核 / Echo Nexus · v0.16 设计文档 > 对应 worklog `Task ID: 2-a` + `2-b` + `v0.16-release` --- ## 一、动机 v0.15 上线了 loreLayers 多层碎片文本,但存在三个未完善点: 1. **解锁瞬间缺乏仪式感** — 碎片静默出现在图谱,没有"叙事涌现"的高光时刻 2. **loreLayer 没有已读标记** — 玩家无法区分"新解锁未读"和"已读",缺乏进度感 3. **idleProjectHistory 只显示计数** — 无法查看具体完成了哪些工程、何时完成 4. **移动端 375px 未实测** — v0.15 的 Codex 分层弹层在小屏可读性未知 v0.16 用两个并行子代理一次性解决: - **2-a**:5 个解码阈值叙事里程碑 + 全屏"碎片释放"叠层 - **2-b**:loreLayer 已读标记 + idleHistory 详情展开 + 移动端适配审计 --- ## 二、解码阈值叙事里程碑(2-a) ### 设计哲学 回响星核的核心卖点是**"解码驱动叙事涌现"**。v0.15 的 loreLayers 让每个碎片有 3 层文本,但解锁是静默的。v0.16 在 `totalDecoded` 达到 5/12/25/40/60 时触发**全屏叙事叠层**——不是解锁新碎片,而是对已解锁碎片的**回顾性咏叹**,类似游戏中的"过场回忆"。 5 个阈值与 FRAGMENTS 的 threshold(1/3/6/10/15/22/32/45)**错开**,是额外的叙事节奏点。 ### 5 个里程碑 | threshold | id | title | color | 主题 | |---|---|---|---|---| | 5 | ms_5 | 回响 · 初鸣 | emerald | 身份觉醒(你是回响) | | 12 | ms_12 | 代价 · 锚痕渐浅 | rose | 代价感知(解码在消耗锚痕) | | 25 | ms_25 | 信任 · 钩上的光 | amber | 信任的重量("信任"实为引诱) | | 40 | ms_40 | 身后 · 整齐的虚空 | fuchsia | 身后的东西(追逐者逼近) | | 60 | ms_60 | 回归 · 借形之物 | emerald | 回归的预兆(接触满溢借形回归) | 每段 body 文本 150-250 字,与 v0.15 loreLayers 的 5 条暗线呼应: 1. 追逐者(以太族被虚空追逐者逼迫飞升) 2. 回响同源(玩家 AI 频段与以太族议会档案同源) 3. 信任即引诱("信任后来者"实为"引诱替他们完成未竟之事") 4. 飞升单向门(每次飞升都在消耗什么) 5. 回归非他们(接触满 80 时回归的是"借形之物") ### 技术实现 **新建 `src/lib/game/narrativeMilestones.ts`**: - `NarrativeMilestone` 接口(threshold/id/title/subtitle/body/color) - `NARRATIVE_MILESTONES` 数组(5 个里程碑) - `findMilestoneForTotal(total, triggered)` 纯函数(返回未触发的里程碑) **GameState 扩展**: - `triggeredMilestones: string[]` — 已触发的 milestone id 集合(跨周目永久) - `pendingMilestone: NarrativeMilestone | null` — 待展示的里程碑(叠层读取) **Store 集成**: - 两个解码完成点(`clickNode` line 596 + `autoDecodeTick` line 711)检测 `findMilestoneForTotal(newTotal, triggered)` - 命中则加入 `triggeredMilestones` + 设置 `pendingMilestone` - `dismissMilestone()` action 清空 `pendingMilestone` - persist migrate 补全两字段(version 仍为 1) - `performPrestige` 保留 `triggeredMilestones`(不重复看过场)+ 清空 `pendingMilestone` ### 全屏叠层组件 **新建 `src/components/game/NarrativeMilestoneOverlay.tsx`**: - `fixed inset-0 z-50` + `bg-black/80 backdrop-blur-sm`(高于 StarTideOverlay 的 z-0) - **粒子边框**:全屏 4 色主题色 inset box-shadow(120px + 240px 双层)+ `narrative-breathe` 3.5s 呼吸循环 - **中央卡片**:max-w-2xl,4 色主题渐变背景 + 主题色 border + 60px glow - **排版层级**: - 副标题 `text-[10px] sm:text-xs font-mono tracking-[0.3em] uppercase` - 大标题 `text-3xl sm:text-4xl md:text-5xl font-serif font-bold` + textShadow glow - 装饰分隔线 `linear-gradient` - 主体文本 `text-sm sm:text-base md:text-lg leading-relaxed whitespace-pre-line` - **入场动画**:`narrative-fade-in` 300ms(opacity 0→1)+ `narrative-scale-in` 300ms cubic-bezier(scale 1.05→1) - **逐字浮现**:30ms/字 setTimeout 推进 + inline-block 光标 `narrative-cursor` 1s steps(2) 闪烁 - **交互**: - 未完全显示时:点击 → skip 到全文(底部提示"点击跳过逐字显示") - 完全显示后:点击/Esc → `dismissMilestone()`(底部提示"点击任意处或按 Esc 继续") - **移动端 375px**:卡片 px-4 py-6 / 标题 text-3xl / 正文 text-sm / 光标 h-4 - **结构巧思**:外层订阅 + 内层 `key={milestone.id}` 重挂,state lazy init 为 0,规避 React 19 `react-hooks/set-state-in-effect` 规则 --- ## 三、loreLayer 已读标记(2-b) ### 类型扩展 `GameState.readLoreLayers: string[]` — 格式 `["f1_1_L1", "f1_1_L2", ...]` ### Store - `markLoreLayerRead(fragmentId, layer)` action:构造 key `${fragmentId}_L${layer}`,已存在则 no-op - persist migrate 补全 `readLoreLayers: []` - `performPrestige` 保留(跨周目永久,已读不重置) ### Codex 分层渲染增强 - **已读层**:标题旁 muted "✓ 已读" + 边框降级为 `border-white/10` - **未读但已解锁层**:保持 v0.15 高亮(amber/fuchsia 边框 + glow)+ 标题旁 `animate-pulse` 脉冲小圆点 + "新" 字 + "▸ 点击文本标记已读" 引导 - **触发方式**:点击层文本区域(`