diff --git a/apps/negentropy-influence/episodes/claude-code-explained-video/README.md b/apps/negentropy-influence/episodes/claude-code-explained-video/README.md index 958053c5a..20cf4be7d 100644 --- a/apps/negentropy-influence/episodes/claude-code-explained-video/README.md +++ b/apps/negentropy-influence/episodes/claude-code-explained-video/README.md @@ -1,7 +1,9 @@ # 《执行层:一个循环,就是全部》 > 系列「Claude Code Harness Engineering」首作(发布顺序 SSOT:[../series.json](../../series.json);口播永不携带集数序号)。 -> **改造状态(2026-09-03 运动层重制)**:全片动效收敛至 frozen 运动层(`video/src/motion/`,跨集共享「怎么动」);HarnessStack 系列装置落地(P0 开场落板+顶边常驻条+P6 收尾);配音全量重合成(音频版本库入库,改稿只重配变更句)。 +> **改造状态(2026-09-06 内容勘误 + 3D 表意强化)**:官方文档复核修正 3 句(第五样勘误 / hook 事件数改抗漂移口径 / 取数日期)并补录轨 C 六条官方信源; +> 5-D「壳」与 4-C「插座插头」3D 化(新增 `components/solids-3d.tsx` 原语层,`PlateSlab3D` 收敛且逐字节零变化)。 +> **前次(2026-09-03 运动层重制)**:全片动效收敛至 frozen 运动层(`video/src/motion/`,跨集共享「怎么动」);HarnessStack 系列装置落地(P0 开场落板+顶边常驻条+P6 收尾);配音全量重合成(音频版本库入库,改稿只重配变更句)。 > 选题:开源课程 [Learn Claude Code](https://learn.shareai.run/zh/s01/) 的「工具与执行」四章 > —— s01 Agent Loop / s02 Tool Use / s03 Permission / s04 Hooks。 > 形态:1080p30 横屏,代码动画图解 + 本人音色克隆旁白,无真人出镜、无 BGM。 @@ -21,8 +23,12 @@ | 主题对比度 | ✅ `--check-theme` FAIL 0(core 6.06 / mech 9.18 / deny 6.00 : 1,全过 4.5:1) | | 尾幕渐黑 | ✅ 末 beat 中部 0.085 → 前 24 帧 0.056 → 末帧 0.012,窗口贴合 beat 结尾(无提前收尾的长黑屏) | | 草渲 + 抽帧 QA | ✅ 25164 帧;七幕中点 + **beat 头部连抽**(39 镜 × 4 帧,ISSUE-170 入场瞬态补盲)+ 末 6 句全量 **FAIL 0 · WARN 0**;A/B 对拍(基线=重制前代码、同 manifest):167 帧中仅 2 帧 >5%(p0-01/p6-08 = HarnessStack 新装置),其余 ≤1.5% 且全部归因(顶边常驻条 / SceneTag 移位右上 / 令牌化缓动 / 硬门改淡入)| -| **终渲交付** | ✅ `out/final.mp4` **13:58.80**(838.8 秒 / 25164 帧,与草渲同 manifest 同帧数)· 1920×1080 @30fps · 43.6 MB;`cover.png` = 标题卡帧 1031
时长**可复算**:`total_duration_in_frames(narration.json, timing.json)` = 25164 帧 @30fps = 838.8s
终渲全分辨率七幕抽帧 **FAIL 0 · WARN 1**(p0-09/10 帧指纹相同 = 0-D 标题卡跨两句静止,刻意)
**2026-08-22 画面优化版**(narration 与配音零改动,纯 tier-i 视觉增强 + `--update` 走查):P6 三挂件卡物理咬合上环 + 信源卡增站点/双钉/实测口径诚实行;P0 走秒芯片(凝住即停)、轮次钢印 01/02/03、载荷箭头(命令/输出);P5 环第 6 次出场 + 20–28 行实测基准带;1-D 诚实角注(站点教学版仍按停止标记);3-C 三判定小抄(allow/ask/deny 各带载荷);`📣`/`⚠` emoji 全部替换为绘制图形
**同日评审四修**(逐帧抽帧实证,见下方「评审修复」):6-A 挂件卡文字朝向与落位、6-A 挂件卡**入场瞬态**压字幕安全带、5-B 基准带锚点与标注避让、3-C 小抄不再遮挡闸门名。末次重渲 473s,七幕抽帧复检 **FAIL 0 · WARN 1**(仍为 0-D 刻意静止) | +| **终渲交付(2026-09-06 本轮)** | ✅ `out/final.mp4` **13:59.90**(839.90 秒 / **25197 帧**,`total_duration_in_frames` 复算恒等)· 1920×1080 @30fps · h264/yuvj420p + aac 189kb/s · **42.2 MB**;`cover.png` = 标题卡帧 1072(画面已验第五辐条为「循环」核色)
时长较前版 13:58.80 仅 **+1.10 秒**(改稿净增 10 字),双口径均落窗 [13.0, 14.6]
**终渲全分辨率复检 FAIL 0 · WARN 0**(七幕 + 末 6 句;草渲期唯一的 p2-07/p2-10 哈希 WARN 在全分辨率下消失,佐证其为均值哈希假阳性)
草渲期门:七幕 FAIL 0 WARN 1 · `--last-n 6` FAIL 0 WARN 0 · **`--beat-heads 4`(39 镜 ×4 帧)FAIL 0 WARN 0**
⚠️ 过程教训:两个渲染进程并发写同一 `draft.mp4` 会产出**索引损坏**的文件(seek 全部落回第 0 帧、`dup=11768`),据此抽出的 52 条 WARN 全是伪信号——**抽帧前先验产物 seek 正常**(同一文件取两个时间戳应得不同 md5) | +| 前版终渲 | `out/final.mp4` **13:58.80**(838.8 秒 / 25164 帧,与草渲同 manifest 同帧数)· 1920×1080 @30fps · 43.6 MB;`cover.png` = 标题卡帧 1031
时长**可复算**:`total_duration_in_frames(narration.json, timing.json)` = 25164 帧 @30fps = 838.8s
终渲全分辨率七幕抽帧 **FAIL 0 · WARN 1**(p0-09/10 帧指纹相同 = 0-D 标题卡跨两句静止,刻意)
**2026-08-22 画面优化版**(narration 与配音零改动,纯 tier-i 视觉增强 + `--update` 走查):P6 三挂件卡物理咬合上环 + 信源卡增站点/双钉/实测口径诚实行;P0 走秒芯片(凝住即停)、轮次钢印 01/02/03、载荷箭头(命令/输出);P5 环第 6 次出场 + 20–28 行实测基准带;1-D 诚实角注(站点教学版仍按停止标记);3-C 三判定小抄(allow/ask/deny 各带载荷);`📣`/`⚠` emoji 全部替换为绘制图形
**同日评审四修**(逐帧抽帧实证,见下方「评审修复」):6-A 挂件卡文字朝向与落位、6-A 挂件卡**入场瞬态**压字幕安全带、5-B 基准带锚点与标注避让、3-C 小抄不再遮挡闸门名。末次重渲 473s,七幕抽帧复检 **FAIL 0 · WARN 1**(仍为 0-D 刻意静止) | | 交付件 | ✅ `final.mp4` + `captions.srt` + `captions.vtt` + `cover.png`(标题卡帧,1920×1080) | +| 内容勘误(2026-09-06) | ✅ 官方文档逐条复核(glossary / how-claude-code-works / hooks / permissions / auto-mode / harnessing 六页,取数 2026-09-06)修正 **3 句**:`p0-06` 第五样「护栏」→「**把这些串起来的循环**」(官方原文 "the loop that chains actions together";**原稿此断言在事实源中无锚点**,见 [ISSUE-178](../../../../docs/.agents/issue.md))· `p4-23`「三十一个」→「**三十多个**」(实测已漂移到 33,绝对数属活数据,[ISSUE-179](../../../../docs/.agents/issue.md))· `p6-06` 取数日期。字数 3979 → **3989**(+10),重合成仅 3 句(其余 164 句版本库回收);连带改 P0-C 第五辐条(染 `core`,成为 P1 环诞生的引信)、4-F 计数 31→33、P0/P6 取数日期 | +| 信源台账补录 | ✅ 轨 C · 官方文档六条入 `sources.toml`(`cc-glossary` / `cc-how-it-works` / `cc-hooks` / `cc-permissions` / `cc-auto-mode` / `cc-harness-blog`),`verify` 受检面 **12 → 18 条 FAIL 0**;`source-notes.md` 新增轨 C 与 **§十 官方断言表**(12 条断言逐条附原文)。⚠️ 补录前 verify 报「受检 12 · FAIL 0」全绿——**门是绿的,只因为出问题的六页不在受检集里** | +| **3D 表意强化** | ✅ 新增 `video/src/components/solids-3d.tsx`(298 行原语层,seeded 档;零 motion hook、运动量全部 prop 注入)。落地两处:**5-D「壳」**——p5-17 是全片唯一把整套结构命名为**三维实体名词**的句子,原实现只是 3px 边框 div(读作「框」);改为四层实心板嵌套(层序即 z 序:循环最内、插口最外),终端沉在井底,「里面」成为几何事实。**4-C「插座插头」**——插座的定义就是「凹进去的洞」,原 22×22 方框只是**画在墙上的符号**、「插入」动作在平面上根本不存在;3D 咬合的侧棱遮挡是「它真的进去了」的唯一无歧义信号。`PlateSlab3D` 收敛到 `Slab3D`,**同帧 PNG md5 逐字节相同**(并删除对 basic 材质空操作的 `ambientLight`)。时序**零改动**:四层合拢开在既有 26 帧 shell 进度的子窗口(frozen `win()`)、插头直接吃既有 `useSpring('snap')` ⇒ 无新 hook、无新 `@动词`、总帧数由 manifest 结构性推导。三轮实景抽帧修复见 [ISSUE-180](../../../../docs/.agents/issue.md)(线框堆 / 面色与 bg 同色 / `zIndex:-1` 沉底)。
⚠️ **本轮评审后修(2026-09-06 晚)**:5-D 四层的 `depth` 映射与声明的「自内向外」相反,成片里 core 色的「循环」被渲染成**最外圈**(见 [ISSUE-182](../../../../docs/.agents/issue.md));同时把壳画布按投影实测加高(原余量漏算合拢期的 z 后推量,最外层底缘被画布下沿直切 635px)。**修后 `out/final.mp4` 已过期,5-D 段需重渲**;4-C 帧 17392 修前后逐像素相同(`skin.face` 本就是空操作)。 | | 动效工具三轨改造 | ✅ 2026-09-04 按 [调研 §8](../../../../docs/research/video-production/160-video-motion-modeling-web-visual-tooling.md) 落地:**A** `@remotion/paths` evolvePath ×2(P3 兜底弧 / P4 回流弧,零时序改动)+ `@remotion/three` 3D 层板(P0/P6 开收尾,HarnessBadge 不变);**C** Lottie 管线(`LottieEmphasis` + 手工占位资产 plug-pulse 替换 4-C 二帧闪,见 [public/lottie/README.md](./video/public/lottie/README.md));**B** HyperFrames 垂直切片 PoC 渲染打通(确定性/重定时实证,audio-first 通道待 TTS,详见 [./poc-hyperframes/FINDINGS.md](./poc-hyperframes/FINDINGS.md));评审面 `video/src/dev/retrofit-gallery.tsx`(`remotion still` 三帧目检过)| | 三轨改造·全链回归 | ✅ 2026-09-05:IndexTTS(8766)启动 + `refs.py rebuild` 按 sha1 台账重建 me-bright.wav → **TTS 音频版本库 167/167 句 1.7s 回收**(零重合成,纯语音 12.95 分钟与成片一致)→ 总帧数复算 **25164 = 已交付成片**(逐帧对齐前提);全片草渲 → 七幕 `--check` FAIL 0(唯一 WARN 为 p2-07/p2-10 16×16 均值哈希假阳性——实测差异 13.83%≈基线 13.72%,画面未冻)+ `--beat-heads` P0/P4/P6 FAIL 0 + 末 6 句 FAIL 0;**A/B 对拍**(基线=2722929e 同 manifest 硬链接)167 帧仅 **p6-08 一帧 >5%**(3D 层板所在,目检归因)其余 ≤1%;回归中修掉三处实景缺陷(badge 塌陷/Lottie 定位/3D 读色,教训入 [ISSUE-177](../../../../docs/.agents/issue.md)——A/B 低差异≠无回归,小面积严重缺陷须逐帧目检且覆盖非改动幕)| diff --git a/apps/negentropy-influence/episodes/claude-code-explained-video/research/source-notes.md b/apps/negentropy-influence/episodes/claude-code-explained-video/research/source-notes.md index c4463bf92..f4ece41e8 100644 --- a/apps/negentropy-influence/episodes/claude-code-explained-video/research/source-notes.md +++ b/apps/negentropy-influence/episodes/claude-code-explained-video/research/source-notes.md @@ -11,6 +11,13 @@ > 站点为 Next.js **SSG 预渲染**(关键数字同时出现在可见文本与 Flight 载荷 `"children":[102," LOC"]` 中), > 故 HTML 文本即渲染结果,不存在 [ISSUE-162](../../../../../docs/.agents/issue.md) 的「未水合占位符」风险。 > +> - **轨 C · 官方文档(Anthropic 一手)**:`code.claude.com/docs` 的 glossary / how-claude-code-works / +> hooks / permissions 四页 + `anthropic.com/engineering/claude-code-auto-mode` + +> `claude.com/blog/harnessing-claudes-intelligence`,取数 **2026-09-06**,已入 [sources.toml](./sources.toml) +> (`kind=site`,正文指纹漂移报 WARN)。**本轨断言等同【二】级**(可断言,属官方的讲法)。 +> ⚠️ 补录背景(2026-09):Harness Engineering 改造引入的这批官方锚点此前**一条都不在台账里**, +> 导致「写错了没有事实源能拦、写对了也会随官方更新悄悄陈旧」——本集 §十 记录的两处缺陷正是其后果。 +> > **证据三级(本集最重要的真实性纪律)** > | 级 | 含义 | 口播允许的表述 | > |---|---|---| @@ -435,3 +442,42 @@ def agent_loop(messages): 6. **不说**「循环一行都没改」—— 用 §五 校准后的口径。 7. **不出现**任何其他系列的片名、集数序号、「上一集/本系列」等顺序词(`check_series.py` 规则 1 会拦)。 8. **英文标识符不进口播**(`stop_reason` / `tool_use` / `TOOL_HANDLERS` / `PreToolUse` 等一律说中文白话,英文只进画面角标);唯一例外是不可回避的产品名 **Claude Code**。 + +--- + +## 十、轨 C 官方文档断言表(2026-09-06 逐条对原文核验) + +> 取数日期 2026-09-06;指纹见 [sources.toml](./sources.toml) 的 `cc-*` 条目。 +> 复核方式:逐页取原文、与口播句逐条对照。**下表是 P0/P3/P4/P5/P6 全部官方断言的唯一回溯点。** + +| 口播句 | 断言 | 官方原文(节录) | 出处 | 判定 | +|---|---|---|---|---| +| p0-04/05 | Claude Code 是 harness、Claude 是里面的模型 | "Claude Code is the harness; Claude is the model inside it." | cc-glossary | ✅ | +| **p0-06** | **Harness 供给五样:文件、命令、权限、记忆,和把这些串起来的循环** | "The harness supplies file access, shell execution, permission gating, memory loading, **and the loop that chains actions together**." | cc-glossary | ✅(**2026-09 勘误**,见下) | +| p1-25..27 | 三相交融、可随时打断 | "These phases blend together."/"You can interrupt at any point…" | cc-how-it-works | ✅ | +| p3-06 | 权限由这层代码执行,不由模型执行 | "Permission rules are enforced by Claude Code, not by the model." | cc-permissions | ✅ | +| p3-25..27 | 拒绝→询问→放行顺序求值、首中即出局、具体度不改顺序 | "Rules are evaluated in order: deny, then ask, then allow. The first match in that order determines the outcome, and rule specificity doesn't change the order." | cc-permissions | ✅ | +| p3-28 | 裸名拒绝令整件工具从模型身上消失 | "A bare tool name like `Bash` removes the tool from Claude's context entirely, so Claude never sees it." | cc-permissions | ✅ | +| p3-31 | 官方遥测:权限提示九成三被批准 | "Claude Code users approve 93% of permission prompts." | cc-auto-mode | ✅ | +| p3-32..34 | 审判者只看用户消息与裸命令;辩解与工具输出被剥离 | "The classifier sees only user messages and the agent's tool calls; we strip out Claude's own messages and tool outputs, making it reasoning-blind by design." | cc-auto-mode | ✅ | +| **p4-23** | **这样的时机有三十多个,官方文档还在往上加** | 事件表 2026-09-06 实测 **33 个**(2026-08 为 31) | cc-hooks | ✅(**2026-09 改口径**,见下) | +| p4-24 | 分三种节奏:会话一次、回合一次、每次调用前后 | "Events fall into three cadences: once per session… once per turn… on every tool call inside the agentic loop…" | cc-hooks | ✅ | +| p4-29..31 | hook 的放行仍要穿过后面的拒绝与询问;单向只能加限制 | "Hook decisions don't bypass permission rules. Claude Code evaluates deny and ask rules regardless of what a PreToolUse hook returns…" | cc-permissions | ✅ | +| p5-05 | 官方给 Harness 的定义正好四件:循环、工具、上下文管理、护栏 | "An agent harness is the software scaffolding around a model: the loop, tools, context management, and guardrails." | cc-harness-blog | ✅ | + +### 2026-09-06 复核发现的两处缺陷(已修) + +1. **p0-06 第五样说错了**:原稿写「文件、命令、权限、记忆、**护栏**」,而 glossary 的第五样是 + **「把这些动作串起来的循环」**。这不只是口径偏差——本集整整 14 分钟讲的就是那个循环, + P0 命名帧却把它从官方定义里漏掉。改对后叙事反而收紧:P0 埋下「循环」,P1 第一句直接接上。 + 与 p5-05 的四件套不冲突(两个官方来源,交集恰好是「循环」,是呼应不是矛盾)。 + **它能溜进成片的根因**:这条断言在本文件里**没有任何对应条目**——违反了开篇第一纪律 + 「回溯不到的断言不得进入口播」。轨 C 的建立就是为了让这类断言从此有处可回溯。 +2. **p4-23「三十一个」已漂移**:2026-08 计数 31,2026-09-06 实测 **33**(新增 + `UserPromptExpansion`/`PostToolBatch`/`MessageDisplay`/`PreModelSwitch`/`PostModelSwitch`/`DirectoryAdded` 等)。 + 该数同时踩了 §九禁用清单第 1、2 条(**活数据不进口播**)——它被当成静态口径写死,注定周期性陈旧。 + 现口播改为抗漂移表述「三十多个,官方文档还在往上加」,绝对数降到画面角标并带取数日期。 + +> **机制性收口**:这两条现在都由 `source_ledger.py verify` 守着——`kind=site` 条目正文漂移会报 +> WARN(「去复核引自该页的断言是否仍成立」)。台账建立前,`verify` 报的是「受检 12 · FAIL 0」全绿, +> 而出问题的六个页面根本不在受检集里:**门是绿的,只因为它没在看**。 diff --git a/apps/negentropy-influence/episodes/claude-code-explained-video/research/sources.toml b/apps/negentropy-influence/episodes/claude-code-explained-video/research/sources.toml index 1d88b3853..6ecbccc7b 100644 --- a/apps/negentropy-influence/episodes/claude-code-explained-video/research/sources.toml +++ b/apps/negentropy-influence/episodes/claude-code-explained-video/research/sources.toml @@ -132,3 +132,63 @@ text_sha256 = "213cc53568f96038" bytes = 71867 lines = 351 via = "SSG 预渲染页面,数字同现于可见文本与 Flight 载荷" + +[cc-glossary] +url = "https://code.claude.com/docs/en/glossary" +kind = "site" +accessed = "2026-09-06" +raw_sha256 = "b3ae1cff6879cf03" +text_sha256 = "39bca61153269e1c" +bytes = 508870 +lines = 1638 +via = "官方术语表:Agentic harness 条目(p0-04..06 的事实源)" + +[cc-how-it-works] +url = "https://code.claude.com/docs/en/how-claude-code-works" +kind = "site" +accessed = "2026-09-06" +raw_sha256 = "f4b63e44fbe11870" +text_sha256 = "b54834d7df19dc35" +bytes = 470041 +lines = 1588 +via = "官方:harness 定位与三相循环(p0-05 / p1-25..27)" + +[cc-hooks] +url = "https://code.claude.com/docs/en/hooks" +kind = "site" +accessed = "2026-09-06" +raw_sha256 = "6f322570dbd2969d" +text_sha256 = "47dda5eb49c0ddaf" +bytes = 2825738 +lines = 3765 +via = "官方 hooks reference:事件表与三节奏(p4-23..24)" + +[cc-permissions] +url = "https://code.claude.com/docs/en/permissions" +kind = "site" +accessed = "2026-09-06" +raw_sha256 = "2fb2620abe12af7b" +text_sha256 = "3baad949cefcd0c1" +bytes = 696014 +lines = 1857 +via = "官方权限文档:由代码执行 / deny-ask-allow 顺序 / 裸名移除(p3-06, p3-25..29, p4-29..31)" + +[cc-auto-mode] +url = "https://www.anthropic.com/engineering/claude-code-auto-mode" +kind = "site" +accessed = "2026-09-06" +raw_sha256 = "fc213c54fe9b041e" +text_sha256 = "5d496b6dde71cb5c" +bytes = 212315 +lines = 1 +via = "工程博客:分类器视野与 93% 遥测(p3-31..34)" + +[cc-harness-blog] +url = "https://claude.com/blog/harnessing-claudes-intelligence" +kind = "site" +accessed = "2026-09-06" +raw_sha256 = "24c81f91d442494b" +text_sha256 = "90bd98f8e376428a" +bytes = 511488 +lines = 3496 +via = "工程博客:harness 四件套定义(p5-05)" diff --git a/apps/negentropy-influence/episodes/claude-code-explained-video/research/verification-2026-08-23.md b/apps/negentropy-influence/episodes/claude-code-explained-video/research/verification-2026-08-23.md index ef0a6ab71..a11df3217 100644 --- a/apps/negentropy-influence/episodes/claude-code-explained-video/research/verification-2026-08-23.md +++ b/apps/negentropy-influence/episodes/claude-code-explained-video/research/verification-2026-08-23.md @@ -36,3 +36,42 @@ - 27→31 口径切换已在画面角标注明「官方文档口径」;旧 27 数字不再出现。 - 93% 为历史遥测口径(2026-03),非活数据,口播带「官方遥测」限定。 - State 十字段降级为角标清单(原 1-E 十抽屉详述压缩)——信息未删,入口移画面。 + +--- + +# 附:2026-09-06 官方文档复核(3D 强化轮) + +> 范围:对轨 C 六个官方锚点逐页取原文、与口播逐条对照。完整断言表见 +> [source-notes.md §十](./source-notes.md)。 + +## 一、修正(3 句) + +| 句 | 原 | 新 | 依据 | +|---|---|---|---| +| p0-06 | 「…权限、记忆、**护栏**」 | 「…权限、记忆,**和把这些串起来的循环**」 | glossary:第五样是 "the loop that chains actions together"。**原稿此断言在事实源中无锚点**——违反「回溯不到不得进口播」 | +| p4-23 | 「官方文档口径**三十一个**」 | 「有**三十多个**,官方文档还在往上加」 | hooks 页 2026-09-06 实测 33(2026-08 为 31)。绝对数属活数据,踩 §九禁用清单第 1、2 条;改抗漂移表述,数字降入画面角标 | +| p6-06 | 「取数2026年**8**月」 | 「取数2026年**9**月」 | 本轮复核日期;写法保持年份与「年」间无空格(READING_TRAPS FAIL 项) | + +字数 3979 → 3989(+10),估算 14.25 分、实测含时距 14.0 分,双口径均落窗 [13.0, 14.6]。 +重合成仅 3 句,其余 164 句由音频版本库回收。 + +## 二、复核通过、未动的断言 + +p0-04/05(harness 命名)· p1-25..27(三相与打断)· p3-06(权限由代码执行)· +p3-25..28(deny→ask→allow / 首中即出局 / 裸名移除工具)· p3-31(93% 遥测)· +p3-32..34(分类器 reasoning-blind)· p4-24(三节奏)· p4-29..31(hook 放行穿两站 / 单向)· +p5-05(四件套)—— 全部 ✅ VERIFIED,原文见 source-notes §十。 + +## 三、根因与机制性收口 + +三处缺陷的共同根因:**Harness Engineering 改造引入的六个官方锚点从未进入 `sources.toml`** +(全系列 5 集皆如此)。后果双重——写错了没有事实源能拦,写对了也会随官方更新悄悄陈旧。 + +本轮已用 `source_ledger.py fetch --kind site` 补录 `cc-glossary` / `cc-how-it-works` / +`cc-hooks` / `cc-permissions` / `cc-auto-mode` / `cc-harness-blog` 六条,`verify` 受检面 +12 → 18 条。此后官方页正文漂移会自动报 WARN。 + +> 补录前 `verify` 报的是「受检 12 · FAIL 0」全绿,而出问题的六页根本不在受检集里—— +> **门是绿的,只因为它没在看**。这是本轮最值得记住的一条。 + +EP2–5 同款缺口尚在,跟进不在本轮范围。 diff --git a/apps/negentropy-influence/episodes/claude-code-explained-video/script/narration.json b/apps/negentropy-influence/episodes/claude-code-explained-video/script/narration.json index 59cfc8014..99c64ba0e 100644 --- a/apps/negentropy-influence/episodes/claude-code-explained-video/script/narration.json +++ b/apps/negentropy-influence/episodes/claude-code-explained-video/script/narration.json @@ -27,7 +27,7 @@ { "id": "p0-06", "scene": "P0", - "text": "Harness 供给它五样东西:文件、命令、权限、记忆、护栏。" + "text": "Harness 供给它五样:文件、命令、权限、记忆,和把这些串起来的循环。" }, { "id": "p0-07", @@ -632,7 +632,7 @@ { "id": "p4-23", "scene": "P4", - "text": "真实产品里这样的时机,官方文档口径三十一个," + "text": "真实产品里这样的时机有三十多个,官方文档还在往上加," }, { "id": "p4-24", @@ -822,7 +822,7 @@ { "id": "p6-06", "scene": "P6", - "text": "本片机制依据官方文档,取数2026年8月,以官方最新表述为准。" + "text": "本片机制依据官方文档,取数2026年9月,以官方最新表述为准。" }, { "id": "p6-07", diff --git a/apps/negentropy-influence/episodes/claude-code-explained-video/script/narration.md b/apps/negentropy-influence/episodes/claude-code-explained-video/script/narration.md index 16c912064..f57a4f0fb 100644 --- a/apps/negentropy-influence/episodes/claude-code-explained-video/script/narration.md +++ b/apps/negentropy-influence/episodes/claude-code-explained-video/script/narration.md @@ -19,7 +19,7 @@ - [p0-03] 从「写出命令」到「真的跑起来」,缺的那一层,官方起了名字。 - [p0-04] 名字就叫 Harness:把动力源,套进一个可控的结构里。 - [p0-05] 官方说得直白:这套东西是 Harness,模型装在 Harness 里面。 -- [p0-06] Harness 供给它五样东西:文件、命令、权限、记忆、护栏。 +- [p0-06] Harness 供给它五样:文件、命令、权限、记忆,和把这些串起来的循环。 - [p0-07] 这集就把这个 Harness,一层一层拆开看。 ## P1 一个循环,就是全部 @@ -193,7 +193,7 @@ > 金句卡(serif):「这个循环是故意保持无聊的。」 -- [p4-23] 真实产品里这样的时机,官方文档口径三十一个, +- [p4-23] 真实产品里这样的时机有三十多个,官方文档还在往上加, - [p4-24] 分三种节奏:会话一次、回合一次、每次调用前后。 - [p4-25] 处理器也不只是跑脚本,还能让小模型单轮判决。 - [p4-27] 表态的契约值得单独记:沉默,不等于批准—— @@ -254,6 +254,6 @@ - [p6-03] 循环给它手脚,分发表给它工具,闸门守着底线,插口留给你发挥。 - [p6-04] 这几层结构不神秘,加起来两百多行,一个下午能读完。 - [p6-05] 学会给 Harness 做加减法,这本身就是一门手艺。 -- [p6-06] 本片机制依据官方文档,取数2026年8月,以官方最新表述为准。 +- [p6-06] 本片机制依据官方文档,取数2026年9月,以官方最新表述为准。 - [p6-07] 教学代码来自开源实现,行数均为实测,链接在简介。 - [p6-08] 下期,拆它怎么安排自己看到的东西:计划写在哪,活儿怎么分出去。 diff --git a/apps/negentropy-influence/episodes/claude-code-explained-video/script/storyboard.md b/apps/negentropy-influence/episodes/claude-code-explained-video/script/storyboard.md index 05b9accc0..c19a51378 100644 --- a/apps/negentropy-influence/episodes/claude-code-explained-video/script/storyboard.md +++ b/apps/negentropy-influence/episodes/claude-code-explained-video/script/storyboard.md @@ -15,7 +15,7 @@ |---|---|---|---| | 0-A | p0-01..02 | 全屏终端窗口(`panel` 底 + `panelBorder` 框),顶栏三个灰点。命令打完光标变灰凝住;右侧对话框浮出 | 终端打字机(每字 2 帧);命令打完后光标**停止闪烁并变灰**,画面凝住 0.6s;p0-02 三轮「复制→粘贴→回车→回贴」搬运弧线**加速**塞满灰色残影(节奏比旧版快一倍) ;`@stagger` `@progress`(系列栈落板错峰 + 搬运弧线等速行程——机械感是主题,用纯 progress) | | 0-B | p0-03..05 | 全部搬运残影瞬间收束成一条外框线,包住一枚「模型」内核——**命名帧**:外框即Harness | 外框自四面向内合拢包住内核(spring);「Harness」二字落框(serif 大字),英文 agentic harness 进角标;p0-05 官方引文条压上("Claude Code is the harness, Claude is the model" 中英并列淡入) ;`@spring` `@breathe` `@progress`(命名帧合拢为本集豁免运动层的签名镜头,弹簧直接驱动几何) | -| 0-C | p0-06..07 | Harness供给图:外框上长出五根辐条(文件 / 命令 / 权限 / 记忆 / 护栏),内侧连着模型内核 | p0-06 五根辐条逐根点亮(`mech` 辉光各 8 帧);「记忆」那根先灰并浮「后面拆」小标;p0-07 外框整体轻旋一次,片名标题卡压入(`core` 色细线自中心向两侧生长) ;`@stagger` `@spring` `@progress` | +| 0-C | p0-06..07 | Harness供给图:外框上长出五根辐条(文件 / 命令 / 权限 / 记忆 / **循环**——官方 glossary 第五样是「把这些串起来的循环」,故这根独用 `core` 色,成为 P1 环诞生的视觉引信),内侧连着模型内核 | p0-06 五根辐条逐根点亮(前四根 `mech` 辉光各 8 帧、第五根「循环」染 `core`);「记忆」那根先灰并浮「后面拆」小标;p0-07 外框整体轻旋一次,片名标题卡压入(`core` 色细线自中心向两侧生长) ;`@stagger` `@spring` `@progress` | ## P1 一个循环,就是全部 @@ -59,10 +59,10 @@ |---|---|---|---| | 4-A | p4-01..04 | 环形循环恢复居中(`core`,仍同色同线宽)。右侧列出三条「想加的东西」 | p4-02/03 三条需求以 `FadeUp` 依次浮现;p4-04 三条需求各射出一支箭头**扎进环形循环内部**,环的线宽被撑粗一档并出现 `deny` 色应力点 ;`@stagger` `@progress`(辐条应力辉光 Math.max 钳制) | | 4-B | p4-05..07 | 退化的循环代码卡:循环体内被塞进五行杂活 | 五行以每句 2 行的节奏插入,每插入一行代码卡**整体下移**并把最外层的 `while` 挤出可视区;p4-06 结束时代码卡边框变 `deny` 虚线;p4-07 金句卡覆盖上层 ;`@progress`(量化舞台刻意离散) | -| 4-C | p4-08..12 | 代码卡收回,五行杂活被「拔」出循环、变成五张回调卡浮在环外;环恢复原线宽 | p4-08 五行代码逐行飞出环外并变形为卡片(0.5s 形变);p4-09/10 环外浮出「时机 → 函数列表」两列表格;p4-11/12 环与卡片之间出现四个插座图形,一张卡片「啪」地插入(`spring` 过冲 + 插入瞬间 `mech` 闪一帧) ;`@spring` `@stagger` `@progress`(插头咬合 = snap 轻过冲) | +| 4-C | p4-08..12 | 代码卡收回,五行杂活被「拔」出循环、变成五张回调卡浮在环外;环恢复原线宽。**插座与插头是 3D**——插座的定义就是「一个凹进去的洞」,2D 方框只是画在墙上的插座**符号**,而「插入」这个动作在平面上根本不存在 | p4-08 五行代码逐行飞出环外并变形为卡片(0.5s 形变);p4-09/10 环外浮出「时机 → 函数列表」两列表格;p4-11/12 环外四个插口,其中**右上那个是真插座(3D)**:轴测方口有井深,插头沿轴线进入、侧棱被口沿逐渐吃掉(`spring('snap')` 过冲 ζ=0.6 峰值 1.095,行程内部钳制防穿底)+ Lottie 咬合脉冲;其余三个保持平面方框(口播说的是「一张卡片插入」,一个就够) ;`@spring` `@stagger` `@progress`(插头咬合 = snap 轻过冲) | | 4-D | p4-13..19 | **插槽注册板**:环形循环外围四个插槽,按一整轮的时间顺序分布在环的四个方位 | 四个插槽逐句亮起并各插入对应回调卡:p4-14 入口槽(补背景信息的卡片飞入并在消息上加一小块 `mech` 补丁);p4-15/16 执行前槽——**P3 的三道闸门整体缩小、平移进这个槽**(跨幕视觉呼应),环上原本的检查标记同时消失;p4-17 执行后槽;p4-18/19 停机前槽亮 `core`,卡片返回一句话 → 一条回流箭头把它送回环的入口,环重新转起来 ;`@stagger` `@progress`(插槽点亮) | | 4-E | p4-20..22 | 四个插槽同时点亮,环上标出四个时机点;环本身刻意**不加任何装饰** | 四点沿环依次脉冲一圈(顺时针,各 8 帧);p4-21 环上只浮出四个极简的喇叭图标("只喊一声");p4-22 金句卡;金句期间环继续匀速转动,不做任何强调动效——用"无动效"表达"故意保持无聊" ;`@progress`(刻意零动效的「无聊」表达) | -| 4-F | p4-23..25 | **31 事件 × 三层嵌套**(官方口径):外层会话框(一次)套中层回合框(×N)套内层工具调用链(工具前→执行→工具后→整批落定);右栏异步事件网格;右下计数徽章 | 外层成形→中层边框闪两下浮 ×N→内层虚线框内一个光点沿四节点链循环跑(80 帧一圈);右栏 6 个异步事件逐条打出;计数从 0 滚到 31(官方文档事件表逐行计数) ;`@stagger` `@progress`(六格异步事件墙) | +| 4-F | p4-23..25 | **33 事件 × 三层嵌套**(官方口径):外层会话框(一次)套中层回合框(×N)套内层工具调用链(工具前→执行→工具后→整批落定);右栏异步事件网格;右下计数徽章 | 外层成形→中层边框闪两下浮 ×N→内层虚线框内一个光点沿四节点链循环跑(80 帧一圈);右栏 6 个异步事件逐条打出;计数从 0 滚到 33(官方文档事件表逐行计数,取数2026年9月;口播只说「三十多个」——绝对数是活数据,不进口播) ;`@stagger` `@progress`(六格异步事件墙) | | 4-G1 | p4-27..31 | **六道闸门流水线**(官方 SDK 权限判定图):hook/deny/ask/settings/mode/默认 六站横排,两端 Execute/Blocked 终点 | 轨道描出→一枚「我批了」光点出发→行至 deny 站被截入 Blocked(`deny` 辉光+冲击波);p4-31 切全放行模式重跑、光点仍在 deny 站被截 + 「全放行模式也一样:拒绝压得住」 ;`@stagger` `@progress`(六站管线 rail 保留 bespoke) | | 4-G2 | p4-32..34 | **印章对撞**(保留高潮母题):「我批了」印章 vs 「禁止/询问」印章 + 单向箭头 | 印章落下对撞、持续抵抗抖动;单向箭头(只允许「加限制」方向);金句卡「扩展点能加限制,不能解除限制」收口 ;`@progress`(对撞冲击波为周期包络,保留内联) | | 4-H | p4-35..37 | 「别停」插槽与环的一段形成小闭环,反复自激;随后一枚标记落下把闭环打断 | p4-36 小闭环连转三圈,每圈提速一档、边缘 `deny` 色渐强(自激失控感);p4-37 一枚 `core` 色方形标记从上落下嵌入闭环,第四圈行进到标记处被**截断**,整体归于静止 ;`@accelTravel` `@progress`(自激闭环→模型,精确一致) | @@ -74,7 +74,7 @@ | 5-A | p5-01..06 | 四层横向卡片:循环(`core` 实心)/ 分发表 / 闸门 / 插槽(后三者 `mech` 描边);卡片上方 p5-05 落下**官方四件套标尺**(循环 / 工具 / 上下文管理 / 护栏) | 四张卡逐句自下弹入并水平排开;p5-05 标尺自上淡入落位;p5-06 三根竖连线把本集三层接到对应格(循环↔循环、分发表↔工具、插口↔护栏),「上下文管理」格虚线空置 + 「空着 · 后面拆」小标;随后第一张卡**留在原地并加实底**,后三张同时上浮 8px 并转为半透明——"骨架 vs 挂件"的视觉分层 ;`@progress`(四卡阵 stagger 为 map 内纯 spring+SPRING) | | 5-B | p5-07..10 | **四级台阶**:四段柱体高度递增(141/191/241/255),每根柱子内部有一段等高的 `core` 色块 | p5-08/09 四根柱体自左向右逐根升起(`spring`),柱顶数字滚动到位;p5-10 时四根柱子里的 `core` 色块**同时描出**——四段高度肉眼相同且色值相同,柱体其余部分降到 30% 亮度 ;`@progress`(基准带实测数据驱动) | | 5-C | p5-11..14 | 环形循环回到画面中央(第五次出现,仍完全同色同线宽),执行节点位置换过四次写法的四个标签轮播 | p5-11/12 环上四个节点依次脉冲一圈;p5-13/14 执行节点上方的标签以 0.4s 间隔翻牌四次(写死命令 → 查表 → 闸门 → 插槽),**环本身一帧未变**;末尾金句卡 ;`@progress`(翻牌离散保持内联) | -| 5-D | p5-15..19 | 回到 P0 的终端画面(同构图),但这次命令自己跑了起来、输出自动回流;末尾英文原句金句卡 | p5-16 P0 的灰色搬运轨迹以 `core` 色重画一次(这次是自动的);人形剪影淡出;p5-18 四层结构在终端外围合拢成一个壳;p5-19 英文原句 `serif` 与中文并列淡入 ;`@progress` | +| 5-D | p5-15..19 | 回到 P0 的终端画面(同构图),但这次命令自己跑了起来、输出自动回流;四层合拢成 3D 壳(p5-17 是全片唯一把整套结构命名为**三维实体名词**的句子);末尾英文原句金句卡 | p5-16 P0 的灰色搬运轨迹以 `core` 色重画一次(这次是自动的);人形剪影淡出;p5-18 四层结构在终端外围合拢成一个**真的壳(3D)**:四块实心板自内向外逐层放大并后移(层序即 z 序:循环最内、插口最外),内层遮住外层中央区、井口由遮挡自然形成,终端沉在井底 —— 「里面」成为几何事实而非约定(框是闭合曲线、壳是有内外之分的体);p5-19 英文原句 `serif` 与中文并列淡入 ;`@progress` | ## P6 收尾 diff --git a/apps/negentropy-influence/episodes/claude-code-explained-video/video/src/components/harness-stack.tsx b/apps/negentropy-influence/episodes/claude-code-explained-video/video/src/components/harness-stack.tsx index b0c5a3d15..9506e617d 100644 --- a/apps/negentropy-influence/episodes/claude-code-explained-video/video/src/components/harness-stack.tsx +++ b/apps/negentropy-influence/episodes/claude-code-explained-video/video/src/components/harness-stack.tsx @@ -15,11 +15,11 @@ * 所有幕的左上内容最早从 y=56 起。故常驻形式取顶边横条(紧贴上缘,不占字幕安全带 * 也不与 SceneTag 相干)。缩退以交叉淡出衔接,避免纵向→横向的形态跳变。 */ -import React, {useMemo} from 'react'; +import React from 'react'; import {AbsoluteFill} from 'remotion'; import {ThreeCanvas} from '@remotion/three'; -import * as THREE from 'three'; import {theme} from '../design/theme'; +import {Slab3D} from './solids-3d'; import {DUR, useBreathe, useDim, useProgress, useStagger} from '../motion'; import series from '../series-layers.json'; @@ -35,21 +35,6 @@ const STACK_CROSSFADE_FRAMES = 8; * 刻意**不**用 core 本色——整块变实心暖色会压掉白色层名的对比度(首版实测)。 */ const PLATE_LIT = '#241C1E'; -/** 正面轮廓线顶点(矩形四角闭合):edges 的单像素棱线在暗底不足以读作 2px 描边, - * 故在板正面再叠一圈 lineLoop 加权。 */ -const faceOutline = (w: number, h: number) => { - const x = w / 2; - const y = h / 2; - return { - attributes: { - position: new THREE.BufferAttribute( - new Float32Array([-x, -y, 0, x, -y, 0, x, y, 0, -x, y, 0]), - 3, - ), - }, - }; -}; - /** 开场全尺寸栈与常驻顶边条开始交叉淡化的局部帧。 */ export const harnessStackCrossAt = (recedeAt: number): number => recedeAt + DUR.f6 - STACK_CROSSFADE_FRAMES; @@ -146,7 +131,6 @@ export const PlateSlab3D: React.FC<{ const settleTilt = (-12 * Math.PI) / 180; const yaw = (8 * Math.PI) / 180; const rotationX = settleTilt + (1 - settle) * ((-18 * Math.PI) / 180); - const boxGeo = useMemo(() => new THREE.BoxGeometry(width, height, depth), [width, height, depth]); return (
- {/* 灯光只服务侧/顶棱的明暗区分;面色不吃光(basic 材质),保证读色与平面 Plate 一致 */} - + {/* 零光源(solids-3d 宪法三):basic 材质本就不吃光,此前的 ambientLight 是空操作。 + 板体恒等于 theme.panel 深底,active 的暖意只走描边与 PLATE_LIT 的轻微提亮—— + 绝不让整块变成实心亮块(ISSUE-177 教训二:emissive 把点亮层烧成橙块、压掉文字对比)。 */} - {/* 板体:恒等于 theme.panel 深底(不受光)。active 的暖意只走描边与轻微提亮, - 绝不让整块变成实心亮块——基线读色是「描边框 + 深底 + 文字浮起」, - 首版用 meshStandardMaterial+emissive 把点亮层烧成橙色实心块,反而压掉了文字对比。 */} - - - - - {/* 描边:edges 单像素在暗底几乎不可见 ⇒ 叠一圈正面轮廓线加权, - 读作平面 Plate 的 2px border(active=core / 常态=panelBorder) */} - - - - - - - - + {/* 文字层:绝对定位叠在 canvas 上、下移 headroom/2 与板视觉中心对位(文字永不进 3D)。 diff --git a/apps/negentropy-influence/episodes/claude-code-explained-video/video/src/components/solids-3d.tsx b/apps/negentropy-influence/episodes/claude-code-explained-video/video/src/components/solids-3d.tsx new file mode 100644 index 000000000..0cb8004c5 --- /dev/null +++ b/apps/negentropy-influence/episodes/claude-code-explained-video/video/src/components/solids-3d.tsx @@ -0,0 +1,298 @@ +/** 本集 3D 原语层(seeded 档,与 motifs.tsx 严格平行)。 + * + * 分层理由与 frozen 边界一致:**frozen 的 motion/ 共享「怎么动」(机制), + * 本层给「画什么的三维形状」(策略)**。故本层: + * - 零 useCurrentFrame、零 motion hook、零 import '../motion'; + * 所有运动量(settle/close/seat/opacity)由调用方以 prop 注入; + * - 只读 theme 颜色 token(与 motifs.tsx 同权限)。 + * + * ── 三条本集 3D 宪法(改这里之前先读) ───────────────────────────── + * ① **只做直角体,不做任何圆/环/曲面**。画面里的「圆」已被 LoopRing 独占 + * (恒 core 色、恒 6px 线宽、六次出场同形),它是「循环始终不变」这个 + * 主题**被看见**的唯一载体。一旦出现三维的环/球/柱面,读法就变成 + * 「环有两种」,唯一性即失。副作用是工程护栏:元素词表被钉死在 + * box/edges/lineLoop/basic 材质 —— 恰好等于 PlateSlab3D 已跑通并过 + * tsc 的那一组,新增 3D **不引入任何新类型面**。 + * ② **读感来自转物体,不来自动相机**。正交 + zoom:1 下 1 世界单位 = 1 CSS + * 像素,这是 3D 与 DOM 叠层能手算对位的唯一前提。相机一动,所有 DOM + * 叠层坐标都要走投影反算(P5Stack 的 BAR_BASE 45 就是这类手算偏移 + * 留下的疤)。故**相机全片零动画**;深度感靠物体静置俯角/偏航。 + * 副产品:没有相机插值就没有浮点累积 ⇒ 无头渲染确定性。 + * ③ **每个颜色必须是 theme token 字面值或有注释的确定性派生,永不是 + * 「光 × 材质」的运行时乘积**。真光照让最终像素成为运行时乘积——没有 + * 名字、不可 grep、--check-theme 看不见、WCAG 无法预先计算,而 + * ISSUE-177 教训二(emissive 把点亮层烧成实心橙块、压掉文字对比)正是 + * 这个形态。故本层**零光源、只用 unlit 材质**,明暗靠手写面色梯度。 + * + * ── ISSUE-177 教训一写进类型 ──────────────────────────────────────── + * 容器的 position/width 属**调用点契约**,不属被抽取内容。故本层 + * **没有任何导出会产生带 position 的 DOM 节点**:实体原语一律返回 , + * Stage3D 的 style 在类型上就 Omit 掉了定位属性。 + */ +import React, {useMemo} from 'react'; +import {ThreeCanvas} from '@remotion/three'; +import * as THREE from 'three'; +import {theme} from '../design/theme'; + +// ── 面色梯度(宪法三:字面量,可 grep、可算 WCAG) ───────────────────── +// +// 深 → 浅:bg < SOCKET_WALL < panel < PLATE_LIT +// 对 theme.text #F2F5FA 的对比度(与 qa_frames --check-theme 同算法): +// panel #171C26 15.62:1 · PLATE_LIT #241C1E 15.26:1 · SOCKET_WALL #141922 16.31:1 +// —— 全部 ≥ 12:1 的预算线。 +// ⚠️ 这些常量**刻意不进 theme.ts**:--check-theme 会遍历 theme 的所有色 token +// 并按「概念色 on bg」判 4.5:1,深色面板色会被当概念色查而直接 FAIL。 + +/** 壳壁的四层面色梯度(自内向外**逐层变暗**)。 + * + * ⚠️ 这组值来自实景抽帧的修复:首版四层全用 theme.panel(对 bg 只差一档), + * 面在暗底上读不出来,棱线成了唯一可见物,四层叠加于是读成**一堆细线框**而不是 + * 「有内外之分的体」。壳与框的差别本就在「面是否可见」,所以面色必须自己拉开层次。 + * 自内向外变暗 = 越靠外越背光,符合「井」的朴素光感(仍是手绘梯度,非光照计算)。 + * 对 theme.text #F2F5FA 的对比度:#39435A 9.66 · #2E374B 11.45 · #242C3C 13.13 · + * #1B212D 14.62 —— 全部远超「文字 on 面色 ≥ 7:1」的下限(本层无文字叠加,仅作余量)。 + * ⚠️ 首版取 #232A36→#12161E(灰度 0.10–0.14),在 bg #0E1116 上几乎与背景同色, + * 面根本读不出来 ⇒ 只剩棱线可见、整组读成线框堆。**面要被看见,必须与 bg 拉开**。 */ +export const SHELL_FACES = ['#39435A', '#2E374B', '#242C3C', '#1B212D'] as const; + +/** 插座井壁:比 panel 更暗一档 = 手绘的「凹进去」暗部。 */ +export const SOCKET_WALL = '#141922'; + +/** 房屋轴测:全片静置角度,与既有 PlateSlab3D 同源。 */ +export const AXO = {pitch: -12, yaw: 8} as const; + +export const axoRotation = (o: {pitch?: number; yaw?: number} = {}): [number, number, number] => [ + ((o.pitch ?? AXO.pitch) * Math.PI) / 180, + ((o.yaw ?? AXO.yaw) * Math.PI) / 180, + 0, +]; + +/** 读色皮肤——「平面语义 → 3D 属性」的唯一载体。 + * face 大面积**永不**用概念色(core/mech/deny);概念色只走 edge。 */ +export type SolidSkin = { + /** 面色:不吃光(材质恒 basic) */ + face: string; + /** 棱线色:概念色只在此出现 */ + edge: string; + /** 整体不透明度(对应平面档的 dim) */ + opacity?: number; + /** 棱线不透明度(缺省随 opacity) */ + edgeOpacity?: number; + /** 正面加权描边:edges 的单像素棱线在暗底读不出 2px border,故正面再叠 lineLoop。缺省 true */ + faceOutline?: boolean; + /** 半透明壳套住实心内容时必须 true:关 depthWrite,否则内容被壳的深度缓冲剔除 */ + seeThrough?: boolean; + /** 关掉 box 的 12 条 edges,只留正面轮廓。 + * ⚠️ 多层嵌套时必须关:N 层 × 每层 4 条 Slab × (12 edges + 1 lineLoop) 的线条密度会 + * 压倒面,整组读成「一堆细线框」而不是「体」(5-D 壳首版实景抽帧的修复)。 */ + noEdges?: boolean; +}; + +/** 世界约定(全片唯一):正交 + zoom 1 ⇒ 1 世界单位 = 1 CSS px;原点在画布中心。 + * 相机全片零动画(宪法二)。 + * ⚠️ style 在类型上就禁掉了定位属性——定位是调用点契约(ISSUE-177 教训一)。 */ +export const Stage3D: React.FC<{ + width: number; + height: number; + style?: Omit< + React.CSSProperties, + 'position' | 'inset' | 'top' | 'left' | 'right' | 'bottom' | 'width' | 'height' + >; + children?: React.ReactNode; +}> = ({width, height, style, children}) => ( + + {/* 零光源(宪法三):basic 材质不吃光,明暗全部来自手写面色梯度 */} + {children} + +); + +/** 正面轮廓线顶点(矩形四角闭合):edges 的单像素棱线在暗底不足以读作 2px 描边, + * 故在正面再叠一圈 lineLoop 加权。 */ +const faceOutlineAttrs = (w: number, h: number) => { + const x = w / 2; + const y = h / 2; + return { + attributes: { + position: new THREE.BufferAttribute( + new Float32Array([-x, -y, 0, x, -y, 0, x, y, 0, -x, y, 0]), + 3, + ), + }, + }; +}; + +/** 板:box + edges + 正面 lineLoop 的最小实体。 + * ★ 返回 ——不建画布、不设定位、不含文字(文字永不进 3D)。 */ +export const Slab3D: React.FC<{ + width: number; + height: number; + depth?: number; + skin: SolidSkin; + position?: [number, number, number]; + rotation?: [number, number, number]; + renderOrder?: number; +}> = ({width, height, depth = 14, skin, position, rotation, renderOrder}) => { + const boxGeo = useMemo( + // ★ 依赖数组刻意不含 frame:几何缓存跨帧复用是无头渲染确定性的一部分 + () => new THREE.BoxGeometry(width, height, depth), + [width, height, depth], + ); + const op = skin.opacity ?? 1; + const eop = skin.edgeOpacity ?? op; + return ( + + + + {/* basic:面色恒等于给定字面量,不受光(宪法三)。 + 半透明壳须关 depthWrite,否则套在里面的实心体会被深度缓冲剔掉 */} + + + {skin.noEdges ? null : ( + + + + + )} + {skin.faceOutline === false ? null : ( + + + + + )} + + ); +}; + +/** 矩形井壁的一圈(中空框 = 上下左右四条 Slab3D,无前盖)。 + * + * 5-D 的「壳」由内向外四圈叠成:层序即 z 序 —— 循环在最内(最小、最靠前), + * 插口在最外(最大、最靠后,从四周包住内层),于是口播的「挂在外面」在几何上成立。 + * 井壁内表面朝向观者,这是「里面」不再靠约定、而成为几何事实的原因 + * (2D 边框只能画外轮廓)。 + * + * 合拢由调用方注入:close 0 = 四条各自在框外 openDist 处,1 = 合拢到位。 */ +export const Rim3D: React.FC<{ + /** 合拢到位时的内沿尺寸(CSS px) */ + innerWidth: number; + innerHeight: number; + /** 框条宽 */ + band: number; + depth?: number; + skin: SolidSkin; + /** 合拢进度 0..1(调用方以 win(parentProgress,[s,e]) 注入——不新增时点) */ + close: number; + /** 未合拢时四条各自的起始外移距离(px) */ + openDist?: number; + position?: [number, number, number]; + renderOrder?: number; +}> = ({innerWidth, innerHeight, band, depth = 16, skin, close, openDist = 90, position, renderOrder}) => { + const c = close < 0 ? 0 : close > 1 ? 1 : close; + const off = (1 - c) * openDist; + // 四条的落位:上下条横跨内沿+两侧框条宽,左右条只占内沿高(避免角部重叠加深) + const hx = innerWidth / 2 + band / 2; + const hy = innerHeight / 2 + band / 2; + const outerW = innerWidth + band * 2; + // 未合拢时整体更透(effects 通道纯线性映射,非弹簧——铁律③) + const op = (skin.opacity ?? 1) * (0.35 + 0.65 * c); + const bandSkin: SolidSkin = {...skin, opacity: op, edgeOpacity: op}; + return ( + + {/* 上 / 下:横条 */} + + + {/* 左 / 右:竖条 */} + + + + ); +}; + +/** 插座(4-C):内凹方口 —— 口沿四条 + 井底。 + * 井底用 theme.bg(全片最深)⇒ 读作「洞」;井壁 SOCKET_WALL 比 panel 更暗一档。 + * ★ skin **不吃 face**:两处面色都是「凹陷」这一读法的组成部分(宪法三要求它们是 + * 字面量,不能由调用方运行时决定),故类型上就 Omit 掉,避免调用点传了个空操作。 */ +export const Socket3D: React.FC<{ + size: number; + /** 井深;也是插头行程的下限锚 */ + wellDepth?: number; + skin: Omit; + position?: [number, number, number]; + rotation?: [number, number, number]; +}> = ({size, wellDepth = 26, skin, position, rotation}) => { + const band = Math.max(6, Math.round(size * 0.16)); + const inner = size - band * 2; + return ( + + {/* 井底:沉在 -wellDepth 处的暗面板 —— 「洞」的底 */} + + {/* 井壁四条:面色比 panel 更暗(凹陷暗部),棱线仍是概念色(口沿读法) */} + + + ); +}; + +/** 插头(4-C):沿插座轴线进入的方块 + 两根插脚。 + * + * seat 0 = 完全在外,1 = 咬合到底。 + * ★ 内部对 z 行程取 min(seat,1):SPRING.snap 的 ζ=0.6 ⇒ 峰值 **1.095**, + * 不钳制插头会穿透井底。过冲不丢 —— 它被转成棱线短暂提亮(edgeBoost)。 */ +export const Plug3D: React.FC<{ + size: number; + seat: number; + /** 完全拔出时距咬合位的行程(px) */ + travel?: number; + skin: SolidSkin; + position?: [number, number, number]; + rotation?: [number, number, number]; +}> = ({size, seat, travel = 96, skin, position, rotation}) => { + const s = seat < 0 ? 0 : seat > 1 ? 1 : seat; + // 过冲量(seat > 1 的部分)转为棱线提亮,绝不转为额外行程 + const edgeBoost = Math.min(1, Math.max(0, seat - 1) * 6); + const z = (1 - s) * travel; + const pinW = Math.max(4, Math.round(size * 0.14)); + const pinLen = Math.round(size * 0.42); + const eop = Math.min(1, (skin.edgeOpacity ?? skin.opacity ?? 1) + edgeBoost * 0.6); + const body: SolidSkin = {...skin, edgeOpacity: eop}; + return ( + + + {/* 插头本体 */} + + {/* 两根插脚:先于本体进洞,是「沿轴线进入」的方向指示 */} + + + + + ); +}; diff --git a/apps/negentropy-influence/episodes/claude-code-explained-video/video/src/dev/retrofit-gallery.tsx b/apps/negentropy-influence/episodes/claude-code-explained-video/video/src/dev/retrofit-gallery.tsx index dbb419c22..9869ea48c 100644 --- a/apps/negentropy-influence/episodes/claude-code-explained-video/video/src/dev/retrofit-gallery.tsx +++ b/apps/negentropy-influence/episodes/claude-code-explained-video/video/src/dev/retrofit-gallery.tsx @@ -15,6 +15,7 @@ import {AbsoluteFill, Composition, registerRoot, useCurrentFrame} from 'remotion import {evolvePath} from '@remotion/paths'; import {theme} from '../design/theme'; import {PlateSlab3D, HarnessStackP0, LAYERS, ACTIVE_INDEX} from '../components/harness-stack'; +import {Plug3D, SHELL_FACES, Slab3D, Socket3D, Stage3D, axoRotation} from '../components/solids-3d'; import {LottieEmphasis} from '../components/LottieEmphasis'; import {SLOT_GAP, SLOT_W} from '../components/motifs'; import {useProgress} from '../motion'; @@ -23,8 +24,10 @@ const CELLS: Array<{name: string; left: number; top: number; w: number; h: numbe {name: 'p3 arc', left: 60, top: 60, w: 800, h: 300}, {name: 'p4 backflow', left: 900, top: 60, w: 560, h: 420}, {name: 'slab3d active/dim', left: 1500, top: 60, w: 380, h: 260}, - {name: 'stack p0', left: 60, top: 400, w: 700, h: 640}, + {name: 'stack p0', left: 60, top: 400, w: 700, h: 360}, {name: 'lottie plug-pulse', left: 900, top: 520, w: 560, h: 420}, + {name: 'housing close (5-D)', left: 1500, top: 340, w: 380, h: 320}, + {name: 'socket seat 0/.5/1/1.095 (4-C)', left: 60, top: 780, w: 800, h: 260}, ]; const Cell: React.FC<{c: (typeof CELLS)[number]; children: React.ReactNode}> = ({c, children}) => ( @@ -132,6 +135,67 @@ const LottieCell: React.FC = () => { ); }; +/** ⑥ 5-D 壳:close 三态并排(0 / 0.5 / 1)——层序是否可读、面是否读作「体」 */ +const HousingCell: React.FC = () => { + const frame = useCurrentFrame(); + const closes = [0, 0.5, Math.min(1, Math.max(0, (frame - 10) / 21))]; + return ( +
+ {closes.map((c, k) => ( +
+ + + {[0, 1, 2, 3].map((i) => { + // 与 P5Stack 同口径:i=0(循环) 最内 ⇒ 最小最靠前;i=3(插口) 最外 ⇒ 最大最靠后 + const d = i; + return ( + + ); + })} + + +
+ {`close ${c.toFixed(2)}`} +
+
+ ))} +
+ ); +}; + +/** ⑦ 4-C 插座:seat 四态含 **1.095**(SPRING.snap 的 ζ=0.6 过冲峰)——插头是否穿透井底 */ +const SocketCell: React.FC = () => ( +
+ {[0, 0.5, 1, 1.095].map((s) => ( +
+ + + + + + +
1 ? theme.deny : theme.dim}}> + {`seat ${s}`} +
+
+ ))} +
+); + const RetrofitGallery: React.FC = () => (
@@ -151,6 +215,8 @@ const RetrofitGallery: React.FC = () => (
) : null} {c.name === 'lottie plug-pulse' ? : null} + {c.name === 'housing close (5-D)' ? : null} + {c.name === 'socket seat 0/.5/1/1.095 (4-C)' ? : null} ))}
diff --git a/apps/negentropy-influence/episodes/claude-code-explained-video/video/src/scenes/P0Hook.tsx b/apps/negentropy-influence/episodes/claude-code-explained-video/video/src/scenes/P0Hook.tsx index 08c4a5028..c5ee63a86 100644 --- a/apps/negentropy-influence/episodes/claude-code-explained-video/video/src/scenes/P0Hook.tsx +++ b/apps/negentropy-influence/episodes/claude-code-explained-video/video/src/scenes/P0Hook.tsx @@ -216,7 +216,7 @@ const NamingFrame: React.FC<{officialAt: number}> = ({officialAt}) => { {'“Claude Code is the harness, Claude is the model.”'}
- {'— 官方文档 how-claude-code-works(code.claude.com,取数2026年8月)'} + {'— 官方文档 how-claude-code-works(code.claude.com,取数2026年9月)'}
@@ -234,7 +234,9 @@ const SupplySpokes: React.FC<{titleAt: number}> = ({titleAt}) => { {label: '命令', zh: '执行与回传'}, {label: '权限', zh: '闸门与审批'}, {label: '记忆', zh: '上下文与规则', later: true}, - {label: '护栏', zh: '拦截与兜底'}, + // 官方 glossary 的第五样是「把这些串起来的循环」(不是护栏)——它就是本集主题, + // 故独用 core 色:P0-C 由此成为 P1 环诞生的视觉引信 + {label: '循环', zh: '把这些串起来', core: true}, ]; const title = useSpring('settle', {at: titleAt}); // 标题卡细线生长(beat 级慢动作,刻意不走 micro token) @@ -273,6 +275,8 @@ const SupplySpokes: React.FC<{titleAt: number}> = ({titleAt}) => { const ix = CX + (fx - CX) * 0.24; const iy = CY - 40; const glow = sp.later ? 0 : 0.5 + 0.5 * Math.sin((frame - i * 8) / 9); + // 第五辐条(循环)是本集内核 ⇒ core;其余机制辐条 mech;「记忆」留灰 + const hue = sp.later ? theme.dim : sp.core ? theme.core : theme.mech; return ( = ({titleAt}) => { y1={fy} x2={fx + (ix - fx) * t} y2={fy + (iy - fy) * t} - stroke={sp.later ? theme.dim : theme.mech} + stroke={hue} strokeWidth={5} - style={sp.later ? undefined : {filter: `drop-shadow(0 0 ${4 + glow * 8}px ${theme.mech}88)`}} + style={sp.later ? undefined : {filter: `drop-shadow(0 0 ${4 + glow * 8}px ${hue}88)`}} /> - + {sp.label} diff --git a/apps/negentropy-influence/episodes/claude-code-explained-video/video/src/scenes/P4Hooks.tsx b/apps/negentropy-influence/episodes/claude-code-explained-video/video/src/scenes/P4Hooks.tsx index 6f3c6dc46..260cca9e7 100644 --- a/apps/negentropy-influence/episodes/claude-code-explained-video/video/src/scenes/P4Hooks.tsx +++ b/apps/negentropy-influence/episodes/claude-code-explained-video/video/src/scenes/P4Hooks.tsx @@ -22,6 +22,7 @@ import { } from '../components/motifs'; import {HarnessBadge} from '../components/harness-stack'; import {LottieEmphasis} from '../components/LottieEmphasis'; +import {Plug3D, Socket3D, Stage3D, axoRotation} from '../components/solids-3d'; import {DUR, useAccelTravel, useProgress, useSpring, useStagger} from '../motion'; const SLOTS = [ @@ -120,6 +121,10 @@ const DegradedLoop: React.FC<{quoteAt: number}> = ({quoteAt}) => { ); }; +/** 插座画布:紧贴插座外沿裁切(含插头拔出时的行程余量) */ +const SOCKET_SIZE = 92; +const SOCKET_CANVAS = 190; + /** 4-C 杂活被拔出循环、变成回调卡,环恢复原线宽 */ const PullOut: React.FC<{tableAt: number; plugAt: number}> = ({tableAt, plugAt}) => { const frame = useCurrentFrame(); @@ -143,6 +148,10 @@ const PullOut: React.FC<{tableAt: number; plugAt: number}> = ({tableAt, plugAt}) {[0, 1, 2, 3].map((i) => { const ang = -60 + i * 40; const rad = (ang * Math.PI) / 180; + // 节点 0(ang=-60°)改用**真插座 + 插头**(3D):见下方 3D 插座说明。 + // 其余三个保持平面方框——口播说的是「一张卡片『啪』地插入」,一个就够; + // 四个全做会把另外三个读成「三个空洞」。 + if (i === 0) return null; return (
= ({tableAt, plugAt}) /> ); })} + {/* ★ 插座与插头(3D)——p4-11/12「循环是插座,功能是插头;加功能是插一个插头, + 不是重新布线」的物理读出。此前这里只有一个 22×22 的 border 方框:那是**画在 + 墙上的插座符号**,而「插入」这个动作在画面上根本不存在(只有一行文字滑进来)。 + 3D 独有的读出是**咬合瞬间的遮挡**:插头侧棱被口沿逐渐吃掉——「它真的进去了」 + 的唯一无歧义信号,2D 的任何 translate 都做不到。 + 时序零新增:seat 直接吃既有的 `plug = useSpring('snap', {at: plugAt})`。 + 画布紧贴插座裁切(填充成本 ∝ 面积);定位是调用点契约(ISSUE-177 教训一)。 */} +
+ + + + + + +
{frame >= seatAt ? ( - // 锚定节点 1(ang=-20°,环右上「工具执行之前」插口):中心 (394.9, 115.4) - // = 190+218·cos(-20°) / 190+218·sin(-20°),96×96 画布减半居中。 - // 不用节点 0(ang=-60°,中心 y≈1.2)——脉冲圈会溢出容器上沿。 + // 咬合脉冲跟随 3D 插座(现锚定节点 0,ang=-60°):中心 + // (190+218·cos(-60°), 190+218·sin(-60°)) = (299, 1.2),96×96 减半居中。 + // 脉冲圈会略微溢出容器上沿——插座本身已表达咬合,脉冲只作轻强调,可接受。 ) : null}
@@ -374,9 +415,9 @@ const BoringOnPurpose: React.FC<{quoteAt: number}> = ({quoteAt}) => { ); }; -/** 4-F 31 事件 × 三节奏三层嵌套(Harness Engineering 改造版,官方文档口径) +/** 4-F 33 事件 × 三节奏三层嵌套(Harness Engineering 改造版,官方文档口径) * 三层嵌套:外层 = 会话(一次)、中层 = 回合(每轮一次)、内层 = 工具调用(每次前后)。 - * 计数徽章滚到 31;右栏异步事件(不入嵌套的独立时机)。 */ + * 计数徽章滚到 33;右栏异步事件(不入嵌套的独立时机)。 */ const HookNesting: React.FC<{outerAt: number; midAt: number; innerAt: number; countAt: number; asyncAt: number}> = ({ outerAt, midAt, @@ -520,12 +561,12 @@ const HookNesting: React.FC<{outerAt: number; midAt: number; innerAt: number; co gap: 12, }} > - + {'个时机'} - {'31 个 hook 事件 × 三种节奏 —— 官方文档 hooks reference(事件表逐行计数,取数2026年8月)'} + {'33 个 hook 事件 × 三种节奏 —— 官方文档 hooks reference(事件表逐行计数,取数2026年9月)'} ); @@ -863,7 +904,7 @@ export const P4Hooks: React.FC<{scene: SceneRange}> = ({scene}) => { - + diff --git a/apps/negentropy-influence/episodes/claude-code-explained-video/video/src/scenes/P5Stack.tsx b/apps/negentropy-influence/episodes/claude-code-explained-video/video/src/scenes/P5Stack.tsx index adfa4fc67..f54419ac2 100644 --- a/apps/negentropy-influence/episodes/claude-code-explained-video/video/src/scenes/P5Stack.tsx +++ b/apps/negentropy-influence/episodes/claude-code-explained-video/video/src/scenes/P5Stack.tsx @@ -11,6 +11,7 @@ import type {SceneRange} from '../types'; import {QuoteCard} from '../components/cards'; import {Footnote, LoopRing, Panel, Terminal, useRingDot} from '../components/motifs'; import {HarnessBadge} from '../components/harness-stack'; +import {SHELL_FACES, Slab3D, Stage3D, axoRotation} from '../components/solids-3d'; import {DUR, SPRING, useProgress, win} from '../motion'; const CHAPTERS = [ @@ -230,7 +231,16 @@ const FourLayers: React.FC<{splitAt: number; mapAt: number}> = ({splitAt, mapAt} ); }; -/** 5-B 四级台阶:柱高递增,柱内 core 段几乎不变;coreAt 后横贯 20–28 行的「实测带」 */ +/** 5-B 四级台阶:柱高递增,柱内 core 段几乎不变;coreAt 后横贯 20–28 行的「实测带」。 + * + * **本幕评估后不做 3D(2026-09)**,两条理由都成立且不可绕过: + * ① 试做过「柱右缘加侧向厚度板」,实景抽帧发现侧板必然压住柱下方的章名标签(柱与标签 + * 在同一 flex 列,3D 画布是覆盖式绝对定位),且遮挡了 20–28 实测带——**带是仪器, + * 柱是被测物**,仪器被遮等于把这幕的判据本身弄丢了; + * ② 更根本的是「一根芯贯穿四柱」的想法本身要否决:四根柱是四个不同版本的**独立实测** + * (23/20/24/28 的不相等恰是「四次独立测量」的证据),连续芯等于视觉上断言「同一段 + * 代码的四次采样」——那是数据造假。这个信息已有正确载体:20–28 实测带横贯四柱但 + * **不属于**任何一柱。带与柱的区分不该被合并。 */ const GrowthBars: React.FC<{barAt: number; coreAt: number}> = ({barAt, coreAt}) => { const frame = useCurrentFrame(); const {fps} = useVideoConfig(); @@ -241,6 +251,8 @@ const GrowthBars: React.FC<{barAt: number; coreAt: number}> = ({barAt, coreAt}) // 实测带 16f:距 f5/f6 均 >3(Δ4/Δ5),保留显式时长;原线性 → standard const bandT = useProgress(coreAt + 6, 16); const yFor = (lines: number) => (lines / maxLines) * H; + // 四柱弹簧进度先算后用(裸 spring 纯函数,非 hook——铁律①:hook 禁入 map) + const grows = CHAPTERS.map((_, i) => spring({frame: frame - (barAt + i * 9), fps, config: SPRING.settle})); //: 柱底距容器底的实测像素:幕名标签(22px 字,行盒 ~33)+ `marginTop: 12`。 //: 基准带必须减掉它才能落在柱子自己的行数刻度上——此前写 90,带体整体上浮 45px。 const BAR_BASE = 45; @@ -248,9 +260,8 @@ const GrowthBars: React.FC<{barAt: number; coreAt: number}> = ({barAt, coreAt})
{CHAPTERS.map((c, i) => { - const at = barAt + i * 9; - // 四柱错峰生长:map 内只跑纯 spring()(hook 禁入 map),settle = 既有 damping 200 - const e = spring({frame: frame - at, fps, config: SPRING.settle}); + // 四柱错峰生长:预计算数组,settle = 既有 damping 200 + const e = grows[i]; const h = (c.total / maxLines) * H * e; const ch = (c.loop / maxLines) * H * e; return ( @@ -430,7 +441,65 @@ const SameRingFourLabels: React.FC<{flipAt: number; quoteAt: number}> = ({flipAt ); }; -/** 5-D 回到 P0 的终端,这次它自己跑起来 */ +/** 5-D 回到 P0 的终端,这次它自己跑起来;四层结构合拢成一个**真的壳**。 + * + * ★ 本集最大的形式-内容同构(3D 化的第一优先落点): + * p5-17 是全片唯一把整套结构命名为**三维实体名词**的句子——「一个愿意反复跑、 + * 能查表、会拦门、留了插口的**壳**」。此前这里是一个 3px 边框 div,读作「一个框」。 + * 框与壳的差别恰是维度差:**框是一条闭合曲线,壳是一个有内外之分的体**。观众要 + * 读出「终端在壳**里面**」,需要看见壳壁的**内表面**——2D 只能画外轮廓、内外靠约定。 + * + * 四圈井壁自内向外 = 循环 / 分发表 / 闸门 / 插口:**层序即 z 序**,于是口播反复说的 + * 「挂在外面」在几何上成立——外层尺寸更大、z 更靠后,在画面上从四周包住内层。 + * 这层语义在单层边框里彻底丢失。 + * + * 它同时兑现 0-B:命名帧给的是**平面轮廓线**(那时观众只知道缺了一层),此处四层 + * 拆完,同一轮廓**长出实体**。轮廓→实体是刻意的戏剧弧,故 0-B 保持平面不动。 */ + +/** 四层井壁(自内向外)。色即语义,与 P6-A 三张挂件卡逐项相同——是回收不是新造: + * 循环 = core(唯一用 core 的一层,它是骨架)· 分发表/插口 = mech · 闸门 = deny。 */ +const SHELL_RIMS = [ + {name: '循环', edge: 'core', grow: 0}, + {name: '分发表', edge: 'mech', grow: 1}, + {name: '闸门', edge: 'deny', grow: 2}, + {name: '插口', edge: 'mech', grow: 3}, +] as const; + +/** 四圈的合拢窗口:开在**既有那一条** 26 帧 shell 进度上(win 是 frozen 纯函数, + * 可安全用于 map)——不新增任何时点,故总帧数与 beat 边界一字未动。 */ +const RIM_WIN: readonly (readonly [number, number])[] = [ + [0, 0.4], + [0.15, 0.55], + [0.3, 0.7], + [0.45, 0.9], +]; + +/** 壳的几何:终端 1120×400,四圈逐层外扩 SHELL_STEP。 */ +const TERM_W = 1120; +const TERM_H = 400; +/** 逐层外扩量:外层比内层每边大 SHELL_STEP,露出的那一圈就是这一层的「壁」 */ +const SHELL_STEP = 30; +/** 层厚 = 层间 z 距:相邻层首尾相接,读作连续井壁而非悬浮的四块板 */ +const SHELL_DEPTH = 26; +/** 合拢起点的额外后推量:四层自后方沿 −z 推入到位 */ +const SHELL_PUSH_IN = 180; +/** 最外层(depth = 层数−1)的半展宽,含 close=0 时 SHELL_PUSH_IN 被 AXO 投影出的外扩。 + * ⚠️ 余量**不对称**:俯角 −12° 把后推量几乎整份折算进 y,x 只吃到偏航 8° 的一小截。 + * 以 THREE.Euler('XYZ', −12°, 8°) 投影最外层八角实测(close=0 / close=1): + * |x|max 698.3 / 673.2 ⇒ W ≥ 1397;|y|max 375.5 / 338.4 ⇒ H ≥ 751。 + * 首版只加了对称的 +60(且算式误用 SHELL_DEPTH 26 当外扩量、又漏了后推量), + * H 差 77px ⇒ 合拢初期最外层底缘被画布下沿直切出一条硬直边(frame 23502 实证 635px)。 + * 下方两个常量按实测取整并留 2px 抗锯齿余量。 */ +const SHELL_PAD_X = 66; +const SHELL_PAD_Y = 142; +const SHELL_SPAN = (SHELL_RIMS.length - 1) * 2 * SHELL_STEP; +const SHELL_CANVAS_W = TERM_W + 34 + SHELL_SPAN + SHELL_PAD_X; // 1400 +const SHELL_CANVAS_H = TERM_H + 34 + SHELL_SPAN + SHELL_PAD_Y; // 756 +// ⚠️ 字幕安全带预算(qa_frames SUBTITLE_BAND_PX=160 ⇒ SAFE_TOP_Y=920):终端居中 ⇒ 视觉 +// 中心 y=540;最外层底缘 close=0 时落在 540+375.5=915.5、close=1 收到 878.4,均 < 920 +// (抽帧实测最低非背景像素:frame 23502 = 914、frame 23525 = 878,与算式一致)。 +// ★ 画布加高不改变几何,只是不再裁掉它——安全带余量由上面那两个实测值直接背书。 + const SelfRunning: React.FC<{shellAt: number; quoteAt: number}> = ({shellAt, quoteAt}) => { const frame = useCurrentFrame(); // 壳合拢是幕级动作:26f ≥22 不入 micro token;原线性 → standard。 @@ -448,6 +517,8 @@ const SelfRunning: React.FC<{shellAt: number; quoteAt: number}> = ({shellAt, quo return (
+ {/* 终端浮在壳之上(壳是它的「外围」)——z 层级是调用点契约,不写进原语 */} +
= ({shellAt, quo {text: 'python ./tools/fmt.py → 完成', color: theme.mech, delay: 80}, ]} /> - {/* 四层结构在终端外围合拢成一个壳。常驻渲染:透明度随 shell 自 0 起(替代原布尔门) */} +
+ {/* 四层结构在终端外围合拢成一个**壳**(3D)。 + 构造:每层是**一整块实心板**(不是四条边框),自外向内逐层缩小并前移; + 内层遮住外层的中央区,井口由此自然形成——「洞」是遮挡的产物,不是画出来的框。 + ⚠️ 首版用四条 Rim 边框拼井壁:4 层 × 4 条 × 轮廓线 = 16 个矩形边,线条密度 + 压倒了面,整组读成「一堆细线框」而非「体」(实景抽帧两轮修复的结论)。 + 定位是**调用点契约**(Stage3D 类型上已 Omit 定位属性,ISSUE-177 教训一)。 */}
+ > + + + {SHELL_RIMS.map((r, i) => { + // 层序即 z 序,且**尺寸序与之同向**:循环是骨架故在最里(最小、最靠前), + // 插口挂在最外(最大、最靠后,于是它从四周包住内层)。 + // ⚠️ 决定「谁看起来在里面」的是尺寸与色阶,不只是 z——三者必须同向。 + const depth = i; // 0..3,越大越靠外 + const close = win(shell, RIM_WIN[i]); + const w = TERM_W + 34 + depth * 2 * SHELL_STEP; + const h = TERM_H + 34 + depth * 2 * SHELL_STEP; + return ( + + ); + })} + + +
{'一个愿意反复跑、能查表、会拦门、留了插口的壳'}
diff --git a/apps/negentropy-influence/episodes/claude-code-explained-video/video/src/scenes/P6Ending.tsx b/apps/negentropy-influence/episodes/claude-code-explained-video/video/src/scenes/P6Ending.tsx index 606cc8402..c25f25cb4 100644 --- a/apps/negentropy-influence/episodes/claude-code-explained-video/video/src/scenes/P6Ending.tsx +++ b/apps/negentropy-influence/episodes/claude-code-explained-video/video/src/scenes/P6Ending.tsx @@ -170,7 +170,7 @@ const SourceAndFade: React.FC<{beatDurationInFrames: number; seriesAt: number}> // 信源四行 + 诚实行依次浮现(stride 4、单项 f4) const rowsOn = useStagger(4, {at: 8, stride: 4, dur: DUR.f4}); const rows = [ - ['官方文档', 'code.claude.com/docs · 取数2026年8月'], + ['官方文档', 'code.claude.com/docs · 取数2026年9月'], ['工程博客', 'anthropic.com/engineering'], ['源码分析', '第三方逆向分析 · 片中逐处标注'], ['数字口径', '开源仓库钉版实测 · 行数均为非空非注释口径'], diff --git a/apps/negentropy-influence/pipeline/skills/06-remotion-implementation.md b/apps/negentropy-influence/pipeline/skills/06-remotion-implementation.md index 9b9e341d0..c5b8e0ece 100644 --- a/apps/negentropy-influence/pipeline/skills/06-remotion-implementation.md +++ b/apps/negentropy-influence/pipeline/skills/06-remotion-implementation.md @@ -104,6 +104,58 @@ md5 门执法——判据与「不读 theme token」约束见 tests/test_skeleto ../out/motion-gallery.png --frame=30`——全部模型 × 变体一屏秒级出图;纯函数单测 `node --test scripts/motion.test.ts`(Node ≥ 23.6 原生跑 TS)。 +## 3D 点缀(`@remotion/three`,2026-09 EP1 落地) + +**定位**(调研 §5.4 判 P2「轻量 3D 点缀」):仅当平面表达不出「体积 / 内外 / 咬合」时才上。 +落点放各集 `components/`(seeded),**不进 frozen**——frozen 共享「怎么动」,3D 给的是 +「画什么的三维形状」。3D 组件**零 motion hook**,运动量全部由调用方以 prop 注入。 + +### 三条宪法(EP1 实景抽帧得出,跨集适用) + +1. **只做直角体,不做圆/环/曲面**。画面里的「圆」若已被某个不变量母题独占(EP1 的 + `LoopRing`:恒色恒线宽、六次出场同形,承载主题句本身),任何三维环/球/柱面都会让 + 它读成「有两种」,唯一性即失。副产品是工程护栏:元素词表被钉死在 + `box / edges / lineLoop / basic 材质`,新增 3D **不引入新类型面**(tsc 风险为零)。 +2. **读感来自转物体,不来自动相机**。正交 + `zoom:1` 下 1 世界单位 = 1 CSS 像素,这是 + 3D 与 DOM 叠层能手算对位的唯一前提;相机一动就要走投影反算(本仓 `BAR_BASE 45` 那道 + 疤即此类)。**相机零动画**,深度靠物体静置俯角/偏航。副产品:无相机插值 ⇒ 无浮点累积。 +3. **每个颜色是 token 字面值或有注释的确定性派生,永不是「光 × 材质」的乘积**。真光照让 + 像素成为运行时乘积——没有名字、不可 grep、`--check-theme` 看不见、WCAG 无法预算,而 + ISSUE-177 教训二正是此形态。故**零光源、只用 unlit 材质**,明暗靠手写面色梯度。 + ⚠️ `` 对 `meshBasicMaterial` 是**空操作**,写了也不起作用。 + +### 读色契约(改 3D 必查) + +- 概念色只走**棱线**,大面积面色永远是深底族(`panel` 及其派生)。 +- 面色常量**放模块内,绝不进 `theme.ts`**——`--check-theme` 会遍历 theme 全部色 token 按 + 「概念色 on bg ≥ 4.5:1」判,深色面板色会被当概念色查而直接 FAIL。 +- 新面色须人工算两项(`--check-theme` **不检查**「文字 on 面色」,这是工具盲区): + `text` 对面色 ≥ 12:1、`dim` 对面色 ≥ 4.5:1。 +- **面要被看见,必须与 bg 拉开**:EP1 首版四层壳全取 0.10–0.14 灰度(与 bg `#0E1116` 几乎 + 同色),面读不出来、只剩棱线,整组读成「一堆细线框」而非「体」。 +- **多层嵌套必须关 edges**:N 层 × 每层的 12 条棱 + 轮廓线,线条密度会压倒面。层身份靠 + 每层**一条**正面轮廓表达即可。 + +### 构造经验 + +- **井/壳用「实心板嵌套」,不用「四条边框拼框」**:每层一整块板逐层放大后移,内层遮住外层 + 中央区,**洞是遮挡的产物**。四条边框拼出的是 N×4 个矩形轮廓,读作线框堆。 +- **弹簧过冲要钳行程**:`SPRING.snap` 的 ζ=0.6 ⇒ 峰值 **1.095**,直接喂给「进入深度」会 + 穿透底面。内部 `min(seat,1)` 钳制,过冲转为棱线提亮(不丢手感)。 +- **一个 beat 一个 `ThreeCanvas`**:每个画布是独立 WebGL 上下文,Chrome 单进程约 16 个上限、 + 超出静默丢弃最旧的。多个实体共用一个舞台,绝不「一个物体一个画布」。 +- **画布紧贴 3D 区域裁切**,绝不用 `AbsoluteFill` 尺寸——填充成本 ∝ 画布面积 × beat 帧数。 +- **z 层级是调用点契约**:3D 画布与同容器内的 DOM(终端/卡片)谁在前,由调用点给 + `position:relative + zIndex`,不写进原语(`zIndex:-1` 会让整个画布沉到兄弟节点之后)。 + +### 验收 + +`Config.setChromiumOpenGlRenderer('angle')` 必须在位(无头渲染强制)。禁 `useFrame` / +`Math.random` / `Date.now`;几何 `useMemo` 依赖数组**不含 frame**;不引 drei(无官方确定性 +背书清单)。**收敛既有 3D 组件时以「同帧 PNG 逐字节相同」为门**(EP1 的 `PlateSlab3D` → +`Slab3D` 收敛即以 md5 相等验收)。3D 是 `--check` 判据的盲区(它只看黑帧/冻帧/字幕带/对比度), +**必须逐帧目视**,且抽帧要覆盖弹簧过冲峰值那几帧。 + ## 场景组件模式 ```tsx diff --git a/docs/.agents/issue.md b/docs/.agents/issue.md index e556c72cb..2cbcce205 100644 --- a/docs/.agents/issue.md +++ b/docs/.agents/issue.md @@ -3872,3 +3872,66 @@ R7 后浏览器对照 Section 2.1 区域发现两类正交缺陷: 3. 改动若触及**跨幕常驻元素**(badge/角标/水印一类),验证抽帧必须至少覆盖一个「非改动幕」——本例改的是 P0/P6 的层板,坏的却是 P1–P6 的常驻条,只看 P0/P6 会全过。 - **同类问题影响**:EP2–5 尚无 `harness-stack.tsx`(该组件目前仅 EP1 有),暂无同款风险;但「共用件带定位」的形态在 `motifs.tsx` 的 `Panel`/`Footnote` 一类容器组件里同样可能出现,后续抽取时按防范 1 执行。 - **同批次第二处(材质读色)**:同一次 3D 化里,板体用 `meshStandardMaterial` + `emissive={theme.core}` 表达 active,实景 P6 收尾帧目检发现**点亮层被烧成实心橙块**、压掉白色层名对比度,未点亮层则因棱线只有单像素而在暗底近乎不可见——3D 材质的默认读色与平面 Plate 的「描边框 + 深底 + 文字浮起」契约完全脱节。修法:面色改 `meshBasicMaterial`(不吃光、恒等于 `theme.panel`,active 仅掺极少量暖意 `#241C1E`),描边在 `edgesGeometry` 之外再叠一圈正面 `lineLoop` 加权。**教训**:把 2D 组件换成 3D 渲染后端时,「读色契约」不会自动继承——受光材质 + 自发光是 3D 的惯用法,但它表达的是「发光体」,而原设计表达的是「深底描边框」,两者视觉语义不同;换后端必须逐档目检点亮/常态两态,不能只验几何。 + +## ISSUE-178 口播断言在事实源里没有锚点也能进成片——真实性纪律写在散文里,没有任何门在执行(2026-09-06) + +- **表因**:EP1《执行层》已交付成片的 `p0-06`「Harness 供给它五样东西:文件、命令、权限、记忆、**护栏**」,与官方 glossary 原文不符——第五样是 "**the loop that chains actions together**"(把动作串起来的循环)。而这一集整整 14 分钟讲的就是那个循环,P0 命名帧却把它从官方定义里漏掉、换成了别的东西。 +- **根因(两层)**: + 1. **该断言在 `research/source-notes.md` 里根本没有对应条目**。该文件开篇第一句写着「每一条断言都必须能回溯到本文件的某一节;回溯不到的断言不得进入口播」——但这条纪律**只存在于散文里,没有任何机器门在执行**。`check_script.py` 查覆盖性/时长/读法陷阱/beat 互比,唯独不查「这句话有没有事实源」。 + 2. **更上游**:2026-08 的 Harness Engineering 改造把六个官方文档(glossary / how-claude-code-works / hooks / permissions / auto-mode 博客 / harnessing 博客)引为新事实源,却**一条都没有进 `sources.toml`**。于是既没有条目可回溯(缺陷 1 的直接成因),也没有漂移预警(见 ISSUE-179)。 +- **处理方式**:① 修正 `p0-06` 口播与 P0-C 第五辐条(并借机让它染 `core`,成为 P1 环诞生的视觉引信);② `source_ledger.py fetch --kind site` 补录六条官方信源,`verify` 受检面 12 → 18;③ `source-notes.md` 新增「轨 C · 官方文档」与 §十 断言表,把 P0/P3/P4/P5 全部官方断言的原文逐条落纸。 +- **后续防范**: + 1. **「门是绿的」不等于「查过了」——先问它在看什么**。补录前 `source_ledger verify` 报的是「受检 12 · FAIL 0」全绿,而出问题的六个页面根本不在受检集里。判定行不带**受检面**,等于把「是否真的查过」交给记忆(与 [ISSUE-168](#issue-168)/[ISSUE-170](#issue-170) 同族:那两条是检查面被静默缩小与采样密度不足,这条是**受检集从一开始就不含它**)。 + 2. **引入新事实源时,登记台账与写进 source-notes 是同一个动作的两半**,缺任一半都会退化成「散文里的纪律」。 + 3. 双重校验(skills/04)的定稿门槛是「RISKY 归零」,而 RISKY 的定义正是「无锚点或与原文有出入」——`p0-06` 本应判 RISKY。**校验是人做的,所以它只在有台账可查时才有效**。 +- **同类问题影响**:Harness Engineering 系列 **5 集全部**缺官方文档信源(`grep -l code.claude.com` 全系列零命中)。本轮在 EP1 建立轨 C 范式,EP2–5 待跟进。 + +## ISSUE-179 静态口径写死的官方数字会周期性陈旧——「31 个 hook 事件」四个月后变成 33(2026-09-06) + +- **表因**:EP1 `p4-23`「真实产品里这样的时机,官方文档口径**三十一个**」。2026-09-06 复核官方 hooks 页事件表:**33 个**(较 2026-08 新增 `UserPromptExpansion` / `PostToolBatch` / `MessageDisplay` / `PreModelSwitch` / `PostModelSwitch` / `DirectoryAdded` 等)。 +- **根因**:该数字被当作「静态口径」写进口播,但它**是活数据**——同时踩了本集 `source-notes.md` §九禁用清单第 1、2 条(不引用活数据)。2026-08 从 27(第三方数源码)切到 31(官方文档口径)时只换了数字,没有意识到**换来的仍是一个会变的数**。口播一旦录音就冻住,而官方文档每月都在长。 +- **处理方式**:口播改抗漂移表述「有**三十多个**,官方文档还在往上加」;绝对数降入画面角标并带取数日期(既有纪律:画面给数字须带口径 + 取数日期);`Counter` 0→33、角标注「取数2026年9月」。 +- **后续防范**: + 1. **判断「是不是活数据」不看它此刻是否精确,而看它未来会不会变**。产品文档里的枚举计数、章节数、star 数、事件表长度全属此类;口播用「三十多个 / 一大批 / 还在加」,精确值留画面。 + 2. 与 ISSUE-178 配套:把来源页登记进 `sources.toml`(`kind=site`)后,正文漂移会自动报 WARN「去复核引自该页的断言是否仍成立」——**这是活数据陈旧的唯一自动预警**。 + 3. 反例留档:同批的 `p4-24`「三种节奏」与 `p3-31`「九成三」复核后**未变**——结构性表述(分类、比例)比枚举计数稳定得多,优先选前者入口播。 +- **同类问题影响**:全系列凡引用官方文档枚举计数处;EP2–5 起草时按防范 1 自检。 + +## ISSUE-180 3D 化「壳/井」用四条边框拼,读成一堆细线框——面色与 bg 同色时,3D 只剩棱线(2026-09-06) + +- **表因**:EP1 5-D「四层合拢成一个壳」首版 3D 化后实景抽帧:画面读作**一堆嵌套的细线框**,完全没有「壳」的体积感,与替换前的 3px 边框 div 相比几乎没有表意增益。连修两轮(改面色梯度、关 edges)仍是线框。 +- **根因(两条叠加)**: + 1. **构造错了**:井壁用「四条边框 Slab 拼一圈」× 4 层 = 16 条板,每条自带 12 条 edges + 正面轮廓 ⇒ 屏幕上是几十个矩形轮廓,线条密度压倒面。正解是**每层一整块实心板**逐层放大后移,内层遮住外层中央区——**洞是遮挡的产物,不是画出来的框**。 + 2. **面色与背景同色**:首版四层面色取 `#232A36`→`#12161E`(灰度 0.10–0.14),而 bg 是 `#0E1116`(0.09)。面在暗底上根本读不出来,于是只剩棱线可见。**「面要被看见」必须与 bg 拉开**,提到 `#39435A`→`#1B212D` 后体积感立刻成立。 +- **处理方式**:改实心板嵌套 + 面色梯度提亮 + `noEdges`(层身份只留每层一条正面轮廓)+ 外层棱线逐层压淡(`0.85 - depth*0.17`,防最外圈彩边抢过主体)+ 终端 `zIndex:1` 浮于壳上。 +- **后续防范**: + 1. **3D 是 `--check` 的完全盲区**(它只看黑帧/冻帧/字幕带侵入/对比度),本例三轮修复全靠 `remotion still` 逐帧目视。3D 改动的验收成本主要在目视,排期时按「每轮出图 4–5s + 判读」计。 + 2. **`zIndex:-1` 会让 3D 画布沉到同容器所有兄弟节点之后**(本例壳整个消失)。z 层级属调用点契约,不写进原语。 + 3. 规格已公共化到 [skills/06 「3D 点缀」节](../../apps/negentropy-influence/pipeline/skills/06-remotion-implementation.md)(三条宪法 + 读色契约 + 构造经验 + 验收),EP2–5 采纳 3D 前先读。 +- **同类问题影响**:任何「嵌套容器 / 井 / 盒」类 3D 表达;`Socket3D` 的井壁同样按实心板 + 遮挡构造。 + +## ISSUE-181 两个渲染进程并发写同一 mp4 → 索引损坏,抽帧 seek 全部落回第 0 帧,伪造出 52 条 WARN(2026-09-06) + +- **表因**:EP1 3D 改造后草渲,七幕 `--check` 报 **FAIL 0 · WARN 52**(基线只有 1 条已知假阳性):P0/P1 大面积「字幕带内无文字亮度像素(字幕缺失?)」、P2/P3 大面积「帧指纹相同(疑似冻帧)」。而同代码态用 `remotion still` 直接渲染的帧完全正常。 +- **根因**:先用 `nohup ... &` 起了一次后台渲染(被判定完成),又因 `setsid` 在 macOS 上不存在而重跑了一次前台渲染——**两个 remotion render 进程并发写同一个 `out/draft.mp4`**。产物 mp4 的 moov/索引因此损坏:`ffmpeg -ss <任意时间戳>` 全部解码回第 0 帧(诊断特征:日志出现 `dup=11768`,且不同时间戳抽出的 PNG **md5 完全相同**)。于是「每一帧都是同一帧」⇒ 相邻采样帧指纹必然相同(伪冻帧)、第 0 帧无字幕(伪字幕缺失)。杀掉进程、删文件、干净重渲后:**FAIL 0 · WARN 1**,与交付基线逐条一致。 +- **处理方式**:`pkill -f "remotion render"` → `rm out/draft.mp4` → 单进程重渲(363s)→ 抽帧 seek 正常(两个时间戳得不同 md5)→ 复跑体检恢复基线。 +- **后续防范**: + 1. **抽帧 QA 之前先验产物本身**:同一文件取两个相距较远的时间戳,md5 必须不同。这一步只要 5 秒,却能挡住「整轮 QA 结论全是伪信号」这类最昂贵的错误——**判据正确不等于输入正确**。 + 2. **渲染是独占写操作,不可并发**:本仓 `pipeline.py render` 无锁。起后台渲染前先 `ps aux | grep "remotion render"` 确认无在跑实例;macOS **没有 `setsid`**(Linux 习惯直接搬过来会静默失败),长跑用 `nohup ... &` 或前台跑。 + 3. 与 [ISSUE-177](#issue-177) 防范 2 互补:那条说「低差异不等于无回归」(判据不敏感),这条说「高 WARN 也未必是真回归」(**输入已损坏**)。异常数量的 WARN 突然出现时,先怀疑产物而不是代码——52 条 WARN 集中在**未改动的 P0–P3**、而改动的 P4/P5 反而干净,这个分布本身就是产物损坏的信号。 +- **同类问题影响**:所有 `out/*.mp4` 产物;`--compare` A/B 对拍尤其危险(两个损坏文件对拍会得出「差异为 0」的假结论)。 + +## ISSUE-182 注释写对了、代码做反了:3D 层序只对了 z 一个维度,尺寸与色阶反向 —— 评审面复制了同一个反转所以也验不出来(2026-09-06) + +- **表因**:EP1 5-D「四层合拢成壳」交付后评审复核,实景抽帧 frame 23510 发现由内向外的圈序是 `插口(mech) / 闸门(deny) / 分发表(mech) / 循环(core)`,即 core 色的**「循环」被渲染成最外圈**——与 storyboard 5-D、README、组件文档块三处白纸黑字的「循环最内、插口最外」完全相反。口播此刻正说「一个愿意反复跑、能查表、会拦门、留了插口的壳」,观众看到的层序是倒的。 +- **根因(三条叠加)**: + 1. **「里面」是多个量共同表达的,只对齐了其中一个**。`const depth = SHELL_RIMS.length - 1 - i` 让 i=0(循环)拿到 depth=3:z 序确实是「循环最深」(作者验的就是这一项,所以自查通过),但 `w/h`、`SHELL_FACES[depth]`、`edgeOpacity = 0.85 - depth*0.17` 三项全部同时反了。正交投影下 z 只体现为极小的遮挡差,**真正决定「谁看起来在里面」的是尺寸**。 + 2. **注释与代码在同一次编辑里分了叉**。该行上方的注释自己写着「循环…(z 最深、**尺寸最小**)」,下一行代码却给了它最大尺寸——注释记录的是**意图**,代码实现的是**另一半意图**,两者相邻却没人对账。 + 3. **评审面复制了同一个反转**。`dev/retrofit-gallery.tsx` 的 `HousingCell` 用 `const d = 3 - i` 手抄了同一段逻辑,于是这块「一屏看层序对不对」的专用画布,**看到的也是错的层序**——它验证不了自己复制过来的那个错误。 +- **处理方式**:`depth = i`(尺寸/色阶/edgeOpacity 三项随之自洽),同步改 `P5Stack` 文档块、`Rim3D` 文档块与 gallery 的 `d`;连带修掉画布余量(原 `+60` 漏算合拢期 `-(1-close)*180` 的 z 后推量被俯角投影出的 ~75px,最外层底缘被画布下沿在 y=876 直切 635px——用 `THREE.Euler('XYZ',−12°,8°)` 投影八角实测得 W≥1397 / H≥751,改为 `SHELL_PAD_X=66` / `SHELL_PAD_Y=142`)。 +- **后续防范**: + 1. **判据要落在成片像素上,不落在某一个中间量上**。「层序即 z 序」这句话本身没错,错在把它当成了充分条件——正确的自查问题是「抽一帧,从内向外读颜色,和口播顺序一致吗」,而不是「z 是不是递减」。 + 2. **评审面不能手抄被评审的逻辑**。`HousingCell` 应当直接消费生产代码的同一份映射(或至少把 `depth` 的计算提成一个可共享的纯函数),否则它只是把错误画了第二遍。与 [ISSUE-177](#issue-177) 防范 3 同族:验证面必须与被验对象**正交**,不能同源。 + 3. **相邻注释与代码不一致时,先信抽帧**。本例注释、storyboard、README 三处一致而代码孤立——多数派是对的,但确认它的方式仍然是出图,不是投票。 + 4. **3D 画布余量必须按投影算,不能按几何尺寸算**:任何沿 z 的位移都会被俯角折算进 y。余量算式里出现「层厚」而不是「投影后的外扩量」就是错的信号(本例还误用了 `SHELL_DEPTH` 26 代替 `SHELL_STEP` 30)。 +- **同类问题影响**:`Socket3D` 的井壁同为「多量共同表达一个读法」的形态,但它的层序由 `wellDepth` 单量决定,无此风险;EP2–5 若采纳 5-D 这类多层嵌套,按防范 1、2 执行。修复后 `out/final.mp4` 的 5-D 段已过期,需重渲。