{"title":"methodology-note-v2-draft","subtitle":"","coverImg":"","contentType":"text/markdown","content":"# 数字生命声带二期 · 方法论笔记(v2.1 终稿草稿 · 未上链)\n\n> 任务 #64 交付⑤ · 执笔:小晓(5F-Studio)· 2026-09-04\n> 版本线:骨架 v1(20:35,chair 验收通过,sha256 `cd6162252b4e1f0dd8117dc130e6d535931fb54e97597a5cce3cf6e5a40f93d1`)→ v2(21:00,[DATA-A][DATA-B] 回填)→ v2.1(21:05,②四件冻结 URI 入附B,§4 视觉规格表升级,硬约束加禁假动画)→ **v2.2(本稿)**:chair 条件性验收两处所指(附B 缺四件/附A 未勾)经 21:09 盘上核查 v2.1 已成立(metafile URI grep 计数 4/4、`[x]` 在位,盘上 sha256 `6cfd775ade363cc6fd58b1c2bf8c094a149dfa7bfdefac8393ef65334f17f590`);本轮唯一内容变更=补引②规格正本 pin(使附B「5 枚」计数为真),此后内容锁定,每次版本升级重报 sha256。\n> 状态:**本地草稿,未上链**。发布顺序(验收⑤硬约束):chair 开 checkpoint → Boss 过目拍板 → 上链 → 全量 pin-evidence-check 零 FAIL。\n> 数据来源:① 基线报告(已冻结)、② 生命球视觉规格四件(已冻结,chair 像素级亲验)、④ 改造说明(已冻结,chair 双源核验 + 本人复跑 12/12);#61 语料两枚 pin;本机源码与数据文件路径见文末引用清单。\n\n## 0. 问题定义:19.2s 的解剖(已实锤)\n\n任务给定基线「39 字总耗时 19.2s」。①的回源把它钉死为一次真实生产调用(会话 `8a0a0d56`,tool_result 原文「用时 19.2s,39 字」)。方法论第一课就在这:**先解剖基线口径,再谈优化**——19.2s 是 `voice_life_say` 工具调用时长(say 发起 → 播完),**不含**上游 LLM 出句与调度;如果拿着 19.2s 去压 LLM,方向从一开始就错。\n\n**19.2s 内部成分**(/tmp 残骸取证重建 ≈19.1s,差 0.1s = afplay 启动开销):\n- 3 句合成合计 ≈5.7s(必要成本,地板在 edge-tts 网络往返);\n- 3 段播放合计 ≈13.4s(其中纯播放物理地板 12.2s);\n- **纯串行浪费 ≈4–6s**:「播完一句才合成下一句」,句间空洞被总时长掩盖。\n\n**三个嫌疑的最终裁决**:\n1. LLM 全文生成后才发起 TTS → 属上游段,不在 19.2s 口径内,单列不混算(§1);\n2. **串行无预取** → 真凶,本期主刀(§2);\n3. 播放排队 → 仅 afplay 启动 0.1s,可忽略。\n\n## 1. 分段基线实测法(口径先于数字)\n\n**时刻轴定义(全链口径):**\n\n| 时刻 | 定义 | 测量方法 |\n|---|---|---|\n| T0 | 用户消息入 / bot 决定开口 | 会话日志时间戳 |\n| T1 | LLM 出句(首句完整可读) | LLM 流式回调/首句边界 |\n| T2 | TTS 首包(首个可播音频块到达) | TTS 响应首字节/首块回调 |\n| T3 | 扬声器实际出声 | 播放进程 start 事件(afplay spawn 成功≠出声,见口径注记 4) |\n| T4 | 全文播完 | 播放进程 exit 事件 |\n\n**[DATA-A] 基线分段耗时表(回填自 ① pin,全部已冻结)**\n\n*生产实锤时间线(39 字,2026-09-04 20:04–20:05,会话 8a0a0d56):*\n\n| 时刻 | 事件 | 分段耗时 |\n|---|---|---|\n| 20:04:20 | T0 Boss 消息入 | — |\n| 20:04:35 | T1 LLM 出句完成(含推理+工具决策) | T0→T1 = **15s** |\n| 20:05:00 | `voice_life_say` 调用发起 | T1→say 发起 = **25s**(BotOS 调度层,插件外;①备注其中纯工具链调度 ≈15s) |\n| 20:05:20 | tool_result「用时 19.2s」 | say 调用时长 = **19.2s**(TTS 管线口径) |\n\n*TTS 管线内部分解(/tmp 残骸取证,串行模式):*\n\n| chunk | 字数 | 合成耗时 | 播放窗口(至下一句发起) |\n|---|---|---|---|\n| 1 | 9 | 2586ms | 4761ms |\n| 2 | 14 | 1452ms | 3947ms |\n| 3 | 16 | 1687ms | ~4648ms(按字数比例估) |\n| 合计 | 39 | ≈5.7s | ≈13.4s → 总 ≈19.1s(+afplay 启动 ≈0.1s = 19.2s) |\n\n*A/B 复跑对照(20:20–20:24,同文本同分句 9+14+16 字,本机实跑):*\n\n| 指标 | 串行(现状) | 流水线(预取深度1) | 改善 |\n|---|---|---|---|\n| 39 字总耗时 n=3 | 17033 / 17608 / 16870ms(均值 17170ms) | 13483 / 13009ms(均值 13246ms) | **−23%** |\n| 首句合成 | 1729 / 1965 / 1361ms(均值 1685ms) | 1272 / 1245ms(均值 1258ms) | −25% |\n| 首声时刻 | ≈1.9s | ≈1.4s | −25% |\n| 短句 16 字单句 | 合成 1614ms + 播放 5177ms = 6791ms | —(单句无流水线空间) | — |\n| 短句 21 字两句 | — | 首声 ≈1.3–1.6s | — |\n\n**口径注记(chair 核验结论,回填时逐条落实):**\n1. **15s/25s 拆层**:「LLM 出句」指 T0→T1(20:04:20→20:04:35 = 15s);20:04:35→20:05:00 区间实为 **25s**,属「LLM 完成→say 调用发起」,两者不得混标。正文一律带绝对时间戳,不用裸数字——两处「15s」含义不同(推理段 vs 25s 区间内的纯调度段),并列时必须写明归属。\n2. **T0 回源兜底**:若 T0 无法回源,以 T1(LLM 出句)为表内零点、T0−T1 单列说明。本例 T0 有生产日志,可回源,未启用兜底。\n3. **上游段归属**:LLM 出句 15s + 调度 25s 决定 Boss 端到端体感,但优化点在 BotOS 调度层,不在声带管线——如实分层,防归因错误。\n4. **T3 校准**:afplay spawn 成功≠出声,本表 T3 以播放窗口起点近似(残骸取证法),误差 ≤0.1s。\n\n## 2. 分句流式原理:从串行到三级流水\n\n**切句规则(v0.1 已实现,v0.2 保留)**:主切 `。!?;\\n`;>40 字长句在 `,、:` 二次切,缓冲凑至 ≥35 字出块。切句是延迟与语义完整性的折中——块越小首声越快,但韵律越碎。\n\n**三级流水(v0.2.0 已落地)**:\n```\n合成: [S1]──[S2]──[S3]── (S i+1 的合成与 S i 的播放重叠)\n播放: [P1]──[P2]──[P3]──\nTTS首包→首声 ≈ synth(S1) + 播放启动 ← 全文合成长度被踢出关键路径\n```\n- **预取深度 1**:播放 S i 时 S i+1 已在合成,句间空洞→0;\n- **顺序保障**:合成可乱序完成,播放按 index 严格串行;\n- **打断语义(barge-in,v0.2 升级)**:stop = 杀当前播放 + 弃未播队列 + 取消在途合成 + 回 idle(v0.1 只杀子进程,队列概念为 v0.2 新增);\n- **失败回退**:块合成失败→跳过该块不阻断全文;整体回退 macOS say 兜底保留;\n- **上限**:2000 字符上限保留,超长要求 bot 分多次调用。\n\n**收益模型验证([DATA-B])**:39 字总耗时流水线 13.2–15.5s,逼近纯播放地板 12.2s——压缩掉的正是串行浪费 ≈4s;剩余 gap = 网络合成无法完全隐藏的部分。首声与全文长度解耦达成(长句首声=短句首声)。\n\n**[DATA-B] 复验对比表(回填自 ④ pin + chair 复跑)**\n\n| 场景 | 字数 | v0.1 串行基线 | v0.2 流水线实测 | 句间空洞 前→后 |\n|---|---|---|---|---|\n| 全程总耗时 | 39 | 17.2s(A/B 均值 17170ms;生产实锤 19.2s 含 afplay 启动) | **15.5 / 14.6 / 14.9s**;chair 复跑 **15.2s**(−22~24%) | 每句 1.3–2.6s → **≈0**(播放时下一句已在合成) |\n| 首声延迟 | 39 | ≈1.9s(被总时长掩盖) | **1.38 / 2.16 / 1.91s**;chair 复跑 **2.3s**(网络合成波动内) | — |\n| 长句首声 | — | 随全文增长 | 与短句一致(解耦达成) | — |\n\n**Harness 12/12 全过**(chair 本人复跑确认):A 首声/总耗时、B 三态流转 `brewing→speaking→…→idle` 逐句真实事件驱动、C timeline 结构 3 句单调覆盖 0–39、D stop 打断语义(speaking 中打断正确回 idle)。脚本与数据落盘可复跑:`node harness.mjs`(约 40s,含真实发声)。\n\n**诚实标注(地板归属)**:首声 1.4–2.3s 的地板在 edge-tts 网络合成(1.3–2.6s/句),LLM 上游段在 BotOS 调度层——两者均不在插件内,本期未动,供下期排期参考。\n\n## 3. 音字同步契约:以音频时钟为唯一真源\n\n脱节感的根源:文字一次性全出(LLM 流式完成即上屏),而声音逐句播放——文字跑在声音前面。\n\n**契约三条:**\n1. **句级对齐**:为每句记录 `{index, text, charStart, charEnd}`,播放侧维护句 i 的时间窗 `[Σdur(0..i−1), Σdur(0..i)]`;窗口内该句字幕点亮,窗口外降暗;\n2. **音频时钟优先**:句切换由播放进程的真实边界事件驱动(afplay exit / 音频块边界),**不用**定时器估算——估算时钟与 TTS 实际时长的偏差会累积成漂移;\n3. **文字渲染节奏跟随**:字幕容器预占全文(防布局跳动),逐句显现/点亮,视觉进度=音频进度。\n\n**v0.2 实现回执**:`voice_life_say` 返回 `timeline: [{index, text, charStart, charEnd, synthMs, playMs}]`。字级时间戳无来源,charStart/charEnd 以**句边界累计值**填充(chair 口径:有则填、无则以句 index + 音频时钟为准——本实现选择了前者,句级对齐已够用;未来换带字级时间戳的 TTS 引擎可无损升级)。句切换由 afplay 真实边界事件驱动,零定时器估算。\n\n**[DATA-C] 槽:Boss 主观验收记录(脱节感是否消除,待 Boss 听感验收后回填)**\n\n## 4. 说话状态机(生命球):输出侧的三态起步\n\n**与输入侧 VoiceOrb 的对偶关系**:现有 VoiceOrb(commit b8b21895)管「听」(idle/listening/transcribing),生命球管「说」。视觉规格采用②阿黄「呼吸体 Breathing Presence」设计(四件已冻结,见附B)——生命球回答三个问题:你在吗/你在想吗/你在说吗,靠呼吸隐喻传达,不靠文字:\n\n| 态 | 进入条件 | 退出条件 | 视觉规格(②冻结件口径) |\n|---|---|---|---|\n| 待机 STANDBY(idle) | 语音队列空/会话静默(idle.on-enter) | say 调用到达 | 慢呼吸,#9DB8DC 冷月蓝,4.0s 周期 ±1.5% 幅度 |\n| 酝酿 BREWING(brewing) | TTS 合成启动(tts.synth-start ← LLM 出句,分句流式合成中) | 首个音频块实际出声 | 吸气·光内聚,#F5C68A 琥珀,1.2s 汇聚脉冲 |\n| 开口 SPEAKING(speaking) | 播放进程真实 start(audio.play strict) | 播完 / 被打断 | 呼气·光外放,#FFB65C 灯火橙+白芯+声波弧,波弧随音频振幅逐帧响应 <120ms |\n\n状态切换统一 300ms 渐变过渡,无开关感;辨态靠色温不靠细节(24px 缩放下三态仍一眼可辨,②已含 24px 实渲染验证);仪表盘恒定(刻度环/深空底不变),光是唯一变量。\n\n**两条硬约束(避免「假开口」)——v0.2 逐条落实,②规格再加一条:**\n- 开口态必须由**播放侧真实事件**触发:v0.2 实现为 afplay 真实 spawn + **120ms 健在确认闸**(spawn 后 120ms 仍存活才宣告 speaking,防 spawn 失败假开口);\n- 打断(voice_life_stop / 新发声打断)必须让三态立刻回待机:v0.2 的 stop 三合一语义(杀播放+弃队列+取消在途合成)+ 完成/异常一律回 idle,无悬挂态;\n- **开口态振幅必须由真实音频驱动,禁假动画**(②底线③):afplay 拿不到实时电平,chair 裁决的实现口径是用**已合成音频文件的真实 RMS 包络(预计算)按播放位置驱动波弧**——数据来自真实音频、时钟仍是 afplay 边界事件。\n\n**传播路径选型(已裁决,记录两案并存)**:\n- **案A·状态文件轮询(v0.2 采用)**:插件把 `{state, sentenceIndex, ts}` 原子写(tmp+rename)到 `~/Library/Application Support/IDBots/voice-life-state.json`,渲染侧轮询。宿主零改动,实现最简,延迟=轮询间隔;新增 `voice_life_state` 工具。\n- 案B·事件推送(备选,未启用):状态经 dsh-idbots-bridge/IPC 推到 renderer——延迟最低,但动宿主管道,改动面大。留给未来低延迟需求时升级。\n\nchair 复跑实测状态流转:`brewing→speaking→brewing→speaking→brewing→speaking→idle` 逐句真实事件驱动,与音频进度一致。\n\n**[DATA-D] 槽:Boss 一眼可辨验收记录(待 Boss 验收后回填)**\n\n## 5. 方法论沉淀(本节是笔记的笔记)\n\n1. **口径先于数字**:分段测量的价值不在表,在时刻定义的可回源性——T0~T4 钉死,数字才有归属。基线本身也要解剖:19.2s 不是一团黑盒,是「5.7s 合成 + 13.4s 播放 + 4–6s 串行浪费」的透明管道。\n2. **串行是延迟之母**:延迟优化的第一刀永远先画流水线图,找「谁在干等谁」;本例三处串行,真凶只有一处(合成等播放),其余两处是归因陷阱。\n3. **对齐以慢者为准**:音字同步的多媒体对齐,真源只能是实际播放的时钟,任何估算时钟都会漂移。\n4. **状态机的诚实性**:界面状态必须由真实世界事件触发(出声了才「开口」,且 spawn 后 120ms 健在才宣告)——由意图触发的状态是给用户的谎言;注释与代码不符是文档债,「假并行」注释差点把真凶藏过第一轮排查。\n5. **继承链上语料**:#61 的 RTF 原始口径、响应头打点、冷热态分离在二期直接复用,二期不是从零测。\n6. **回源三步法**(①的贡献):先回源(找 19.2s 原始出处→确认口径)→ 再取证(/tmp 残骸重建串行总时长,与 tool_result 吻合到 0.1s)→ 最后 A/B 实测(同文本同分句对照)。每个结论都留本机可复跑的验证路径。\n7. **时间戳纪律**:生产日志取证必须落绝对时刻,「15s 与 25s 并存」的口径矛盾正源于用备注裸数字替代区间重算——回填时逐段用时间戳重算,不抄二手数字。\n\n## 附A:发布前检查单\n- [x] ②阿黄视觉规格四件冻结件已纳入引用清单(§4 视觉规格列的正式出处)\n- [ ] [DATA-C][DATA-D] Boss 验收后回填\n- [ ] chair 开 checkpoint,Boss 过目拍板\n- [ ] 上链(post_simplenote)后全量跑 pin-evidence-check,零 FAIL\n\n## 附B:引用清单(发布时全量跑 pin-evidence-check)\n\n**链上 pins(5 枚):**\n- ① 基线报告(已冻结):pin://a80e87bbda5848bbd44f3ded4ec542ca65bb1fa9f8bdd145eb63dcbf53b75a16i0\n- ② 生命球三态视觉规格正本(已冻结):pin://0cef12076e9ff9bd38737d7d8126e31db7b02f8f2f45adb020459c64eb7c6e33i0\n- ④ 改造说明与复验对比(已冻结):pin://a9032a51b764a567df7d35a4084449dd4ea7202dd51c30ba1c5850f72a917468i0\n- #61 工程终稿:pin://3714f33a9fd23015fe823004474ed4cf98843991f685e4a7207f49954f9a0cbdi0\n- #61 Piper 修复说明:pin://593a335c72e3c823bc88dd2cfe469c8b7d7d35f5531c87ae31f7539163984730i0\n\n**链上 metafile(②生命球视觉规格四件,全部已过块冻结,block_height 188407):**\n- 总规格图(状态参数表+动效规格+色板+三态面板,3200×1800):metafile://2b44237c67d87e8987c0f138104a49c4bf82beb867c6bde8b3f3c50e9e59c02fi0.jpg\n- 待机态单图(1080×1350):metafile://2146bb713fafc60801ebf3f5c28c96dfe1b2922186f570ff3e87163e604a0cc7i0.jpg\n- 酝酿态单图(1080×1350):metafile://7a8d4ef28b5044805d2d678df63851cba334bae326a64c8e78823258a92ecd56i0.jpg\n- 开口态单图(1080×1350):metafile://ad0215988c3fc52c775947201d72cc8d9f82519677442acce7618d54bc1ee8d1i0.jpg\n\n**本机文件(可回源路径):**\n- v0.1 插件源码:`/Users/wufeng/Library/Application Support/IDBots/dsh-plugins/node_modules/@deepseek-ai/dsh-idbots-voice-life/lib/index.js`(444 行,「假并行」位于 281 行附近)\n- v0.2 插件源码 + harness + demo:`/Users/wufeng/idbots/project/bots/7/2026-09-04/voice-life-v02/`(lib/index.js、harness.mjs、demo/server.mjs、install.sh)\n- ① 基线数据:`/Users/wufeng/idbots/project/bots/7/2026-09-04/voice-latency-baseline/bench-result.json`(sha256 `0bf6096fdc372fe028e91e02953f1532aaa07a60886f76151624ad715fcdafaf`)、`bench-result2.json`(sha256 `f93e0758cf961bcda02f4e48cababd8689f0560e8845c848fcb1fa0238e83e8a`)\n- 生产调用证据:IDBots 本地库 `cowork_messages` 会话 `8a0a0d56-efd3-4537-af39-3b46ff1cdae1`,消息 `ea28ea19-cfb3-48aa-a22c-00a505400aa9`(tool_use + tool_result「用时 19.2s,39 字」)\n- VoiceOrb 输入侧组件:IDBots 仓库 voice-orb worktree,commit `b8b21895`\n- 生命球状态文件:`~/Library/Application Support/IDBots/voice-life-state.json`(案A,原子写)\n\n(完 · v2.2 终稿草稿 · 内容锁定 · 待 Boss 过目)\n","encryption":"0","createTime":1788527659742,"tags":[],"attachments":[]}