- 程序章程 research/new8/00-program.md(8款概念/回合协议/验收门/进度账) - 设计:docs/design/15-radio.md+voice-ids(132句,6状态9抉择4结局,电话滤波资产侧带通)/16-dungeon.md+voice-ids(125句,脉冲回声/7层seed生成/三档战斗/元进度遗产) - 资产:TTS 970文件(GPU flock串行)+ Mureka BGM 8首 $0.24(台账 mureka_log.md)+ 程序合成 sfx(含预pan侧墙/开口、phone_ring铃声、amb_cave呼吸层、turn_tick)+ 终检 check_all_assets 8游戏全绿 - radio:game.js+playtest 221断言(4结局矩阵/pendingChoice盲玩公平/RING两级看门狗/菜单循环/存档schema) - dungeon:game.js+playtest 1610断言(seed全网格回声一致性/学期零死/格挡80%/死亡归因真话/元进度/菜单冻结/上滑5段状态链) - 引擎修复:audio.js _playVoice 加载竞态补杀 token(R1真机71分钟语音风暴根因)+ 同id 3s窗口≥5次熔断保险丝 + gesture.js 长按boost事件门(+66行,9套件回归全绿) - 大厅:新增 radio/dungeon 卡片 + GAMES 数组 - harness:sessions/radio.mjs+dungeon.mjs(主线+压力双剧本)+ transcribe 注册 + voice_text.json 刷新(8游戏704句)+ 13号文档 §20 登记 - R1 存档:4人设评审×2游戏 + 真机试玩报告×2(radio 5/5愿意玩;dungeon 0/4→确诊风暴)+ synthesis×2 + 转写8份 - R2 修复:radio F1-F5(看门狗补挂/rep_low改词重录/真铃声/ASK续玩语义/链末跳过保护)+ dungeon D0-1..D1-6(风暴根治/回声真实性/密度下限+呼吸层/导航最短路/菜单关闭反馈/金币档同账本)+ 实现核对登记
50 KiB
16 · 《回声地窟》完整设计(dungeon / Echo Cavern)
给开发者的实现要点摘要
- 听声辨位 Roguelike:网格地窟 + 面朝转向。单击=声波脉冲(回声"看"地形:正前距离 4 档×材质 4 种,左右侧 墙/开口 2 档,预 pan 立体声资产),按住=前进、左右滑=转身(豁免登记见 §2),双击=重发脉冲(免费重听)、上滑=状态、下滑=菜单(三件套不豁免)。战斗是 rpg 式回合制(前摇双击格挡),Boss 战加入绕位(脚步声报方位、转身对准才打得到)。
- 方位全程降维 ≤4:探索报前/左/右三向(身后不报,转身即查),战斗相对方位只有面前/左/右/背后。回声=静态地形、脚步=动态怪物、播报=绝对方位——三套信息由同一
grid单源生成,机制上不可能互相矛盾。- 单局=一命一局,7 层满通约 24 分钟;死了重开但金币进元进度("遗产"5 项永久强化,死亡不清档)。学习期零惩罚:L1 怪 ATK=0、L2 ATK=1,学期内不可能死;药水自动喝;撞墙无伤;无任何倒计时。
- 随机性全走种子 PRNG(mulberry32,
opts.seed可注入,playtest 传固定 seed 复现);回声/脚步全部用「预生成 sfx 资产 + setTimeout 调度 + volume」实现,不用引擎外实时节点(playtest 虚拟时钟可断言)。- 台词表 16-dungeon-voice-ids.md(125 句 ×2 性别,无 NPC——怪物不说话);本作向导句 gd_* 需在开发轮次登记进 14 号文档增补(§6 纪律:queue:true、idle-only、onceKey)。
- 规模对标 escape+rpg 折中:单文件 game.js 预计 900-1100 行,bgm 4 + sfx ~34 + voice 125×2。
1. 概述
- 一句话:你是盲眼探窟者,单击发出声波脉冲、用回声"看"每次都不同的地窟,听脚步辨怪,逐层下潜,死了带着金币重来、越死越强。
- 题材:黑暗洞窟探险。核心命题「伸手不见五指,声音就是你的眼睛」——题材与纯音频互为 justification,与《剑与声》的"耳即眼"呼应但玩法完全不同:rpg 是选项列表 RPG,本作是空间探索型 roguelike(方法论 §4.1:状态层重点=方位参数映射+撞墙归因+目标方向信标)。
- 为什么适合纯音频:回声定位本来就是"用声音看空间"——视觉游戏的 minimap 在这里天然就是一段回声;黑暗=无画面是叙事设定而非技术妥协。移动/转身/距离全部可离散化(≤4 方位×4 距离档),不需要连续角度(文献结论:方位降维 ≤4)。
- 单局:一命一局,出生到死亡/通关。7 层,满通约 24 分钟,普通玩家死于 4-6 层(约 14-19 分钟)。死亡结算 ≤15s 必答归因三问,金币入元进度,死亡不清档位进度。
- 与现有 6 游戏的关系:空间感承接 escape(网格+带路提示),回合战斗承接 rpg(前摇格挡),演练教学承接 space(D 步逐项实操),元进度为本作新增系统(8 款新模式探索点的核心增量)。
2. 手势映射与豁免登记
2.1 探索态(EXPLORE / DRILL / DESCEND)
| 手势 | 作用 | 反馈 |
|---|---|---|
| 单击 | 发出声波脉冲(核心动词,1.2s 冷却防连打糊声流) | sfx/pulse + 调度回声组(§3) |
| 按住(longpress,450ms 起) | 前进:按住朝面朝方向连续走(每格 0.5s),松开(longpressend)即停;撞墙自动停 | 每格 step_own + swipe 震;撞墙 bump + 停,无伤 |
| 左滑 | 左转 90°(面朝逆时针 N→W→S→E) | step_turn + swipe 震 |
| 右滑 | 右转 90° | step_turn + swipe 震 |
| 双击 | 重发脉冲(免费重听回声,不消耗任何资源、无冷却惩罚) | 同单击(宪法"双击=重听"的本作落地:线索=回声) |
| 上滑 | 状态播报链:层 → 血量档 → 金币档(元进度总账)→ 面朝 → 周围威胁 | 5 段链播 |
| 下滑 | 模态菜单(§2.4) | menu earcon |
2.2 战斗态(BATTLE)
| 手势 | 作用 | 反馈 |
|---|---|---|
| 单击 | 挥击:怪在你面前才命中;不在(Boss/绕位怪换位后)= 挥空 | atk_swing→命中 atk_hit / 挥空 + btl_swish(归因真话) |
| 双击 | 格挡:仅敌方前摇音窗口内(1.2s)有效;面对攻击来源减伤 80%,未面对 0%(背后挨刀) | parry / hurt + hit 震 |
| 左滑/右滑 | 转身(只对绕位怪/Boss 有意义;缠斗怪时无效) | step_turn / 纠错(§10) |
| 按住 | 尝试逃跑:普通怪必成、但撤退挨一下(真实掉血);Boss 战拒绝 | btl_flee / btl_flee_boss |
| 上滑 | 战斗状态:我方血量档 → 敌方血量档 →(Boss/绕位战)它的当前方位 | 3 段链播 |
| 下滑 | 模态菜单(战斗暂停,回合制无实时流,无需 count-in) | menu earcon |
2.3 标题态(TITLE)/ 结算态(SETTLE)
| 手势 | TITLE | SETTLE |
|---|---|---|
| 单击 | 继续(有存档)/ 开始探窟(无存档) | 再来一局(新周目,重掷 seed) |
| 右滑 | 放弃当前周目,重掷新周目(元进度保留) | —(无效,纠错) |
| 双击 | 重听 continue_ask / 元进度播报 | 重听结算链 |
| 上滑 | 元进度播报:金币档 → 最深层数档 → 遗产清单(已解锁各一句)→ unlock_none(不够时) |
重听元进度段 |
| 下滑 | 菜单(帮助/返回两项) | 菜单 |
| 按住/左右滑 | 无效(error 震 + gd_invalid) |
同左 |
2.4 模态菜单(遵守 00 文档 §5.2 全球约定,无豁免)
下滑呼出 → 单击循环切换(每切播项名)→ 双击确认 → 上滑=帮助 → 下滑=关闭。打开即暂停(战斗回合制天然可暂停)。选项:返回探索(战斗态同)→ 保存并回大厅 → 重新听帮助 → 重新开始本周目。
2.5 豁免登记(必须显式)
| # | 豁免条目 | 理由(先例) |
|---|---|---|
| E1 | 单击=脉冲(非"确认/继续") | 核心动词必须是最快手势;脉冲是"检查"类非即时操作,300ms tap 延迟无碍(escape 单击=检查同款) |
| E2 | 左右滑=转身、按住=前进(探索态四向滑+长按被玩法占用) | 网格移动玩法(escape 四向滑=移动先例);上下滑让位给不可豁免的状态/菜单三件套,前进改绑长按——"按住就走"零认知负荷(P4),单手免视(P3) |
| E3 | 对局期 audio.setRateBoostEnabled(false)(探索/战斗/教学),菜单/结算恢复 true |
长按=移动是潜在玩法输入,长按快进必须豁免(rhythm/space 对局期豁免先例) |
| E4 | 战斗态下滑=菜单保留,但菜单在战斗内语义为暂停 | 回合制,菜单即暂停,无并发压力;不沿用 rpg"战斗态下滑=逃跑"豁免(本作逃跑绑长按,更贴"转身就跑"直觉) |
教学(DRILL)中逐条讲清 E1/E2/E4 的偏离(tut_* 句),符合 §5.4"偏离必须在开场交互教学中讲清"。longpressend 仅作停走信号,不承载第二语义;不开启 immediateTap(无 tapcancel 路径)。
3. 核心动词:声波脉冲与回声定位
3.1 声学编码(全在引擎能力内)
| 维度 | 编码 | 实现 |
|---|---|---|
| 正前距离 | 4 档:贴身(1格)/近(2格)/中(3-4格)/远(5+格) | 脉冲后 setTimeout 调度 echo_1..4,延迟 0.25/0.50/0.80/1.10s,volume 0.9/0.7/0.5/0.35 |
| 正前材质 | 4 种:石墙/木门/铁梯(楼梯)/水 | 回声到达后 +120ms 播材质尾音 mat_stone/door/stair/water(0.2s 短音色) |
| 左右侧 | 2 档:墙(闷)/开口(空旷回灵) | 脉冲后 +0.35s 同时播 side_wall_l/r 或 side_open_l/r(预 pan 立体声资产:能量只在一个声道,引擎 play() 无实时 pan,space whoosh_left/right 同工艺) |
| 身后 | 不报 | 刻意降维:回声只照面前和两侧,转身即查(教学句 tut_side_rule 讲明规则;不报≠信息矛盾) |
- 节点说明:不用引擎外实时 WebAudio 节点(playtest mock 只 mock bufferSource/gain)。全部=预渲染 sfx 资产 +
play()volume + setTimeout 调度——虚拟时钟下回声调度可精确断言。 - 降级方案:若 SA3 立体声分离度不达标,侧向回声改 SYN 程序合成(直接写立体声 AudioBuffer 的 L/R 通道数据,管线能力内);材质尾音全部 SYN 可兜底。
- 频带分离防糊:己方脚步/怪物脚步=低频(100-300Hz),脉冲+回声=中频(800Hz-2kHz),BGM 垫底——探索期并发声流 ≤2(你的动作反馈 + 怪物脚步)恒成立。
- 脉冲冷却 1.2s:防连打;双击重发不受额外惩罚(P1"回声可重听且免费")。冷却期单击/双击 → 仅 error 震(无语音惩罚)。
3.2 三套信息自洽(P1 专项,机制级保证)
| 信息 | 声源 | 内容 | 坐标系 |
|---|---|---|---|
| 回声 | 脉冲触发 | 静态地形(墙/门/楼梯/水 + 距离) | 相对(前/左/右) |
| 怪物脚步 | 怪物移动即播(每步,间隔 1.5-2.5s) | 动态威胁(左右 pan + 距离档音量;背后=中央音量减半,转身后恢复=方位反馈) | 相对(左/前/右/后) |
| 播报 | 上滑/事件 | 层数/血量/金币/面朝(绝对)/威胁(相对左右) | 面朝=绝对,其余=相对 |
三者全部由同一 game._floor.grid 与怪物位置单源查询生成,无独立状态;模板句固定(方位句 4 句、威胁句 4 句)。回声永不报怪物、脚步永不报地形——语义域零重叠。威胁播报只报左右不报背后(同回声降维,规则在教学讲明)。
4. 状态机
4.1 状态列表
TITLE / DRILL / EXPLORE / BATTLE / MENU(overlay) / DESCEND / SETTLE。MENU 是挂起 overlay(记录返回态),其余为正交主态。
4.2 转移图
start ──> TITLE ──单击(有档)──> EXPLORE(恢复存档+局面复述)
│ │单击(无档) │
│ ├─────> DRILL(仅首次) ──完成/下滑跳过──> EXPLORE(L1)
│ └右滑=重掷新周目 ──────────────┘(已学过教学,直达)
v
EXPLORE <──────────────┐
│ 走进怪格/怪走进格 │ 胜利(原地)
v │
BATTLE ────────────────┘
│ HP≤0
v
SETTLE(死亡) <── EXPLORE(第7层终Boss胜=通关SETTLE)
│单击
v
TITLE(元进度入账后)
EXPLORE ──踏上楼梯格+单击──> DESCEND ──> EXPLORE(floor+1)
EXPLORE/BATTLE ──下滑──> MENU ──返回──> 原状态
DESCEND 演出:stairs_down 轰隆 + evt_descend + 下一层生成 + st_floor_N,约 5s。
4.3 每状态手势映射总表
| 手势 | TITLE | DRILL | EXPLORE | BATTLE | MENU | SETTLE |
|---|---|---|---|---|---|---|
| 单击 | 继续/开始 | 步骤作答 | 脉冲 | 挥击 | 循环切项 | 再来一局 |
| 双击 | 重听 | 步骤作答(D6) | 重发脉冲 | 格挡(前摇窗口) | 确认执行 | 重听结算 |
| 左滑 | error | 步骤作答(D5) | 左转 | 转身(绕位怪有效) | 上一项 | error |
| 右滑 | 重掷新周目 | 步骤作答(D5) | 右转 | 转身(绕位怪有效) | 下一项 | error |
| 上滑 | 元进度 | 状态(教学后可用) | 状态链 | 战斗状态链 | 帮助 | 重听元进度段 |
| 下滑 | 菜单 | 跳过教学 | 菜单 | 菜单(暂停) | 关闭 | 菜单 |
| 长按 | error | 步骤作答(D4) | 前进 | 尝试逃跑 | — | error |
| 长按松开 | — | 停 | 停 | — | — | — |
无效手势一律 error 震;full 档语音纠错 gd_invalid(onceKey 同局限 1 次,之后仅 error 震)。idle-only:纠错/看门狗语音在播时跳过(引擎 guide 已实现)。
5. 教学编排(DRILL,首局内嵌于第 1 层)
每步=一句短旁白→一次实操→反馈;做错重教同一步(新短句,不逐字重播)≤2 次后自动演示通过(不卡流程,space 先例)。教学寄生局内:DRILL 就在 L1 地图起点房内进行,D8 的教学怪是 L1 两只怪之一。全程可下滑跳过(tut_skip),跳过标志存进度,二周目不再进 DRILL。
| 步 | 旁白 | 等待动作 | 通过条件/反馈 |
|---|---|---|---|
| D0 | intro_1+intro_2+tut_d0 | 单击 | confirm 震 |
| D1 | — | 单击 | pulse+回声组 + tut_echo_come。首输入≤60s 达成点(≈t+15s) |
| D2 | tut_front_rule | 单击继续 | 纯听规则句 |
| D3 | tut_side_rule | 单击继续 | 纯听规则句 |
| D4 | tut_walk | 按住走 ≥2 格 | 脚步声(撞墙也算过,bump+那边是墙由既有 bump 路径播) |
| D5 | tut_turn | 左滑+右滑各 1 | 转身声 |
| D6 | tut_replay | 双击 | echo 重播 + tut_echo_come 复用 |
| D7 | tut_status + tut_menu | 上滑(状态链播)→ 下滑开菜单→再关 | 两键各一次 |
| D8 | tut_mob → 走向教学怪触发战斗(盲眼蝠 ATK=0)→ tut_parry →(前摇)双击 | 格挡成/败都继续:成功 tut_parry_ok,失败 tut_parry_miss |
然后 tut_attack → 单击挥击至击杀 tut_kill。首正反馈≤3min 达成点 |
| D9 | tut_stairs | 脉冲听到铁器回声后自行走到楼梯 | 踏上楼梯格 tut_stairs_on → 单击下潜 → EXPLORE |
D9 后不再教学;上滑=状态链随时可查。生成约束(教学局 L1,优先于随机):起点房前方 2 格内保证有墙(D1 回声演示)、起点房门口固定一只盲眼蝠(D8)、房内 1 处金币(拾取体验)。
FTUE 核对:遮罩点按→D0 三句(~8s)→D1 首次脉冲 ≈ t+15s(<60s ✓);首个正反馈=教学战胜利 ≈ t+2min(≤3min ✓);学习期 L1-2 怪 ATK 0/1(死亡不可能 ✓)。
6. 地窟生成
6.1 seed 用法
rng(floor) = mulberry32((seed ^ (floor * 0x9E3779B9)) >>> 0) // 每层独立子种子
opts.seed可注入(playtest 传 42);正式局seed = Math.floor(Math.random()*1e9)(开局掷一次,存入 run)。同 seed → L1..L7 全部可复现。- 生成步骤(每层):① 尺寸定表(§6.2)→ ② 放 3-6 个矩形房间(2×2~3×4,不重叠)→ ③ L 型走廊按 MST 顺序连通全房间 → ④ 起点房=第 1 房中心,楼梯房=BFS 距起点最远房间中心,楼梯格=其中心 → ⑤ 宝物撒入房间格 → ⑥ 怪物放在 BFS 距起点 ≥3 格的格位(开局安全区)。
- 复现断言口径:同 seed 两次构造 → grid/怪物/宝物/楼梯逐格相等(测试读
game._floor重算不变式,不写魔法坐标)。
6.2 层规格与难度曲线
| 层 | 尺寸 | 怪物(型×数) | 宝物 | 目标用时 | 累计 |
|---|---|---|---|---|---|
| 1 教学 | 7×7 | 盲眼蝠×2(ATK0) | 金币×1 | 3.0 | 3.0 |
| 2 | 7×7 | 窟鼠×3 | 金币×1+药水×1 | 3.0 | 6.0 |
| 3 窟主 | 9×9 | 蘑菇怪×3 + 窟主石甲蟹 | 金币×1+短刃 | 3.5 | 9.5 |
| 4 | 9×9 | 绕行者×2+洞蛛×2 | 金币×1+皮甲 | 3.5 | 13.0 |
| 5 | 11×11 | 洞蛛×3+绕行者×2 | 金币×1+药水×1 | 3.5 | 16.5 |
| 6 窟主 | 11×11 | 洞蛛×2+绕行者×2 + 窟主腐沼蟒 | 金币×1+阔刃 | 3.5 | 20.0 |
| 7 终局 | 11×11 | 石像卫×3 + 窟中之王 | 金币×1 | 4.0 | 24.0 |
- 窟主守楼梯格:未击败时踏上楼梯格播
evt_stairs_lock(楼梯不可用);击败后解锁。终 Boss 击败=通关。 - 敌型见 §7.2 表。每层开场氛围:
st_floor_N+ 层 BGM 不变(探索曲统一),深度感靠evt_descend与怪物构成。
7. 遭遇与战斗
7.1 遭遇
- 全明雷:怪物脚步声持续可闻(rpg 暗雷教训——纯音频里突袭没有信息缓冲)。你走进怪格或怪走进你格 →
btl_start_<mob>+ battle earcon + BGM 切换。 - 遭遇瞬间自动"面对"怪物(播报名即=它在面前)。怪移动的脚步:每步播
mob_step_<class>_<l|c|r>,pan 按相对方位(左/前/右),背后用中央资产音量减半;距离档音量 ≤2 格 0.9 / 3-4 格 0.6 / 5-8 格 0.35 / 更远不播。距离 ≤2 格加 heartbeat 震(威胁前置预警窗,方法论 L2)。
7.2 回合时序(实现契约)
进 BATTLE → 玩家回合(guide.turn) → 单击挥击/长按逃跑/转身 → 结算
→ 若怪活:怪行动(绕位怪先移动:脚步报新方位;缠斗怪原地)→ 攻击前摇 1.2s(tel_* 音)
→ 前摇内双击=格挡判定(面对攻击源:×0.2 / 未面对:×1.0)→ 受击/格挡音+震
→ 玩家 HP≤25% 且腰带有药 → 自动喝药(potion 音+播报)
→ 回到玩家回合。HP≤0 → 死亡归因链 → SETTLE
- 缠斗怪(蝠/鼠/菇/蛛):不绕位,恒正面,左右滑无效(
btl_no_turn纠错,每场 1 次)。 - 绕位怪(绕行者/石像卫):每 2 回合移动到 左/右/背后 之一(脚步 pan 报位),攻击前摇随其方位(tel 派生 pan 变体);
btl_behind在背后挨刀时播(归因真话)。 - Boss(石甲蟹/腐沼蟒/窟中之王):绕位+高血,半血播
btl_half_*+ ATK+1(王二阶段)。Boss 战长按逃跑拒绝(btl_flee_boss)。 - 格挡判定与 rpg 一致绑双击(规避 tap 300ms 延迟);窗口外双击=error 震不生效。
7.3 逃跑
长按 = 尝试逃跑:普通怪/绕位怪必成(无 RNG,P4 友好),撤退挨一下(掉血=怪 ATK,真实账目),落回探索态、怪留在原格;拉开距离后脚步声自然远去。Boss 拒绝。逃跑无次数限制(容错拉满,代价=掉血+丢进攻回合)。
7.4 掉落与元进度
- 战斗胜利:金币入账 +
btl_loot_1..3(按单场所得 3 档)+ 概率掉药水/装备(§6.2 宝物表为拾取点,怪掉落仅金币)。 - 每击杀 4 只 atkLv+1,播
btl_lvup("这层我变强了"的即时成长,P2 钩子)。 - 元进度(遗产):金币永续累计(meta.gold),死亡/通关结算入账,不清档。5 项解锁(下一局开场自动生效+播报):
| 遗产 | 价格(金币) | 效果 |
|---|---|---|
| unlock_potion 行囊药 | 90 | 开局自带 1 瓶腰带药 |
| unlock_vigor 体魄 | 150 | 起始 maxHp 20→26 |
| unlock_veil 护符 | 220 | 每层首次受击减半 |
| unlock_echo 回声强化 | 300 | 侧向回声加报距离档(墙闷/开口各 2 档→按远近换 2 组资产音量) |
| unlock_blade 武器 | 450 | 开局自带短刃(atk+1) |
- 经济核算:一局 40-120 金(怪 3-15/只 + 堆 10-30 + 窟主 50-80)→ 约 2-5 局解锁一项,死亡有可见成长(P2「下周目你会更强」)。
8. 数值表
8.1 玩家与敌人
maxHp = 20 (+vigor 6);atk = 2 + atkLv(0..4, 每4杀+1) + 武器(0..2)
dmg = max(0, atk - 敌def) ;敌打你 = max(0, 敌atk - 甲) × 格挡系数(0.2/1.0)
格挡钳制:擦伤 min 0;L1-2 怪 ATK 0-1 → 学期零惩罚
| 敌型 | 层 | HP | ATK | 行为 | 脚步音色类 | 掉金币 |
|---|---|---|---|---|---|---|
| 盲眼蝠 | 1 | 4 | 0 | 缠斗 | small | 3 |
| 窟鼠 | 2 | 6 | 1 | 缠斗 | small | 5 |
| 蘑菇怪 | 3 | 8 | 2 | 缠斗 | bug | 8 |
| 窟主·石甲蟹 | 3 | 20 | 3 | Boss 绕位 | heavy | 50 |
| 绕行者 | 4 | 10 | 2 | 绕位 | bug | 10 |
| 洞蛛 | 5 | 12 | 3 | 缠斗 | bug | 12 |
| 窟主·腐沼蟒 | 6 | 26 | 4 | Boss 绕位 | heavy | 80 |
| 石像卫 | 7 | 16 | 4 | 绕位 | heavy | 15 |
| 窟中之王 | 7 | 36 | 5→6 | Boss 绕位 | heavy | 通关 |
装备(拾取即自动装备,仅播报不管理):短刃 atk+1(L3 起)、阔刃 atk+2(L6 起)、皮甲 敌伤-1(L4 起)。药水:腰带限 1 瓶,HP≤25% 自动喝(回 40% maxHp,向上取整)。
平衡校验:L1 atk2 打蝠 2 刀;L3 atk3+短刃=4 打蟹 5 刀、蟹打裸装 3/下 vs 20HP+药 → 不格挡危险、会格挡稳过;L7 atk2+3+2=7 打王 6 刀、王打 5-6/下 vs 26HP → 必须格挡+药,Boss 战有真实技巧收益。
9. 音频三层
9.1 BGM(4 首,Mureka,-20 LUFS,ogg+m4a,无缝循环)
| id | 状态映射 | Mureka prompt(英文) |
|---|---|---|
| bgm/menu | TITLE / MENU / SETTLE(结算 volume 0.5) | "Dark cavern ambient pad, deep sub drone with distant water drips, calm and mysterious, no drums, no vocals, seamless loop" |
| bgm/explore | DRILL / EXPLORE / DESCEND | "Dark cave exploration ambient, low ominous drone, sparse echoing sonar-like pings, slow dripping rhythm, tense but calm, no vocals, seamless loop" |
| bgm/battle | BATTLE 普通怪 | "Tense dungeon battle music, pounding tribal drums 95 bpm, dissonant low stabs, claustrophobic urgency, no vocals, seamless loop" |
| bgm/boss | BATTLE 窟主/终 Boss | "Epic dark cavern boss theme, heavy drums 115 bpm, low brass swells, rising dread, no vocals, seamless loop" |
切换全部走引擎 crossfade;语音 duck 20% 引擎自动。danger 不切 BGM(guide danger earcon + heartbeat 震 + st_hp_4 承担)。
9.2 SFX(~34 个;SA3=Stable Audio 3,SYN=程序合成;全部双格式、真峰值 ≤-3dBFS)
| 类别 | id | 方式 | prompt 草稿 / 说明 |
|---|---|---|---|
| 脉冲 | pulse |
SYN | 180→60Hz 正弦扫频+噪声瞬态,0.15s,中频突出 |
| 正前回声 | echo_1..4 |
SA3 | "close stone wall echo slap-back, bright, short, dry"(2:"mid distance cave echo, moderate tail" 3:"far cave echo, darker longer" 4:"very far cavern echo, long dark tail, huge space") |
| 材质尾音 | mat_stone mat_door mat_stair mat_water |
SYN | 闷石敲/空心木咚/金属当啷/水滴湿滑,各 0.2-0.25s |
| 侧向回声 | side_wall_l/r side_open_l/r |
SYN | 立体声文件:能量只在 L 或 R 声道;闷哒 vs 空旷回灵(SA3 不达标时程序写 L/R 通道兜底) |
| 移动 | step_own step_turn bump |
SYN | 单步/拖步转身/撞墙闷响 |
| 拾取 | coin potion gear |
SYN | 金币叮当/喝药咕咚/换装金属轻响 |
| 转层 | stairs_down |
SA3 | "descending old metal stairs, deep cavern reverb" |
| 怪物脚步 | mob_step_small_l/c/r mob_step_bug_l/c/r mob_step_heavy_l/c/r |
SA3 ×3 + ffmpeg pan 派生 ×6 | "small beast skittering steps, dry";"insect chitin crawling clicks";"heavy armored thudding steps"——各生成 1 条单声道,派生左/中/右三份 |
| 攻击前摇 | tel_small tel_bug tel_heavy(+派生 l/r) |
SA3 | 小怪扑击嘶声/虫肢节摩擦/重怪蓄力重踏低吼 |
| 战斗 | atk_swing atk_hit parry hurt enemy_die |
SYN/SA3 | 挥拳风声(SYN)/击中甲壳钝响/格挡亮叮(SYN)/受击闷击(SYN)/倒地(SA3) |
| 升档 | levelup |
SYN | 上行钟声琶音(内容音,允许) |
monsters 脚步 pan 派生工艺:ffmpeg -i in.ogg -af "stereotools=mlev=0.7:sbal=-.8" out_l.ogg 等三向派生,省 SA3 任务数(SA3 实际 ~14 个)。
9.3 voice
125 句 ×2 性别(Serena / Uncle_Fu),无 NPC(怪物不说话,遭遇靠 btl_start_* 报名)。分组、台词、触发全表见 16-dungeon-voice-ids.md。数字全分档整句(层 7 句、血量 4、金币 4、面朝 4、威胁 4、战利 3、死因 3、元进度 3),无运行时拼接。
10. 向导层接入(engine/guide.js,本作不新增 earcon 词表)
10.1 状态签名(进入状态时播)
| 状态 | guide.state() | 时点 |
|---|---|---|
| TITLE / MENU | menu |
进标题、开菜单 |
| DRILL / EXPLORE / DESCEND | explore |
进教学、进探索、下楼完成 |
| BATTLE | battle |
遭遇即播(与 btl_start_* 同点) |
| SETTLE | settle |
死亡/通关结算进入 |
| danger | danger |
血量首次 ≤25%(每层 1 次)+ Boss 半血 |
choice 签名本作不使用(无抉择态;菜单是 menu)。菜单回绕不播 wrap 句(菜单 ≤4 项,循环成本低,省台词预算)。
10.2 turn cue 与看门狗
| 等待点 | arm | at | say(full 档;L2 级一律纯 nudge,绝不逐字重播) |
|---|---|---|---|
| BATTLE 玩家回合(每回合) | guide.turn | — | turn earcon + confirm 震 |
| BATTLE 玩家回合无操作 | watchdog | [15s, 30s] | gd_turn / null |
| EXPLORE 静止 | watchdog | [10s, 25s] | gd_pulse / null |
| DRILL 每步等待 | watchdog | [8s] | 无语音,纯 nudge(教学句不重播) |
10.3 卡关三级提示(EXPLORE,escape 先例;"无操作时长"计时,任何有效手势重置)
进展事件=拾取/战斗胜利/踏上楼梯格/击败窟主/下潜。无进展 60/120/180s →:
| 级 | 内容 | 附带 |
|---|---|---|
| L1 暗示 | 楼梯未找到 → stair_hint_a;窟主未清 → stair_hint_b |
warning 震 |
| L2 明示 | stair_dir_e/s/w/n(绝对方位;与上滑面朝句同坐标系,自洽) |
warning 震 |
| L3 带路 | 进入导航模式:每步播 nav_e/s/w/n,抵达播 nav_done;此后每 60s 重复直至进展 |
warning 震 |
提示到点遇语音在播 → 入待播队列(escape round2 礼仪),玩家操作即作废未播级别。纠错句 idle-only + 过期丢弃(引擎已实现)。
11. 存档设计
{
"schema": 1, "game": "dungeon", "updatedAt": 0,
"summary": "第3层 · 伤势不轻 · 金币268 · 遗产2/5",
"meta": { "gold": 268, "unlocks": ["potion", "vigor"], "bestFloor": 5, "runs": 7, "kills": 23 },
"run": {
"seed": 42, "floor": 3, "pos": [5, 2], "face": "E",
"hp": 11, "atkLv": 2, "weapon": 1, "armor": 0, "potion": 1,
"veilUsedFloor": false, "killsRun": 9, "looted": [], "cleared": [], "parries": 4
}
}
- summary 生成规则:
第{floor}层 · {血量档名} · 金币{meta.gold} · 遗产{unlocks.length}/5;死亡后 run=null,summary=深处归来 · 金币{gold} · 遗产{n}/5。 - 保存时机:拾取/战斗结算/下潜/菜单"保存并回大厅"/死亡结算。localStorage + PUT 服务器(00 文档 §4)。
- 读取:有 run → TITLE 播
sys_continue_ask,单击继续 → 恢复run+ 重放该层 seed 生成 + 局面复述链(st_floor+st_hp+st_face+st_threat,≤5s 可操作);右滑 = 重掷新周目(run 清空重掷 seed,meta 保留)。无 run 单击 = 新周目(meta.unlocks 生效播报后进 DRILL/EXPLORE)。 - 归因真话依据:
parries(全程格挡数)、lastHit 记录(击杀者类型/是否背刺/是否未格挡)在死亡结算选die_cause_*与die_adv_*,句必须与真实行为对应。
12. playtest 断言清单(登记 13 号文档新增 §dungeon;mock 机制对齐 web/rpg/test/playtest.mjs)
关键路径(seed=42,首周目 L1→L3 窟主,≤260 手势):
- 启动 → TITLE(无存档)→ 单击 → DRILL:D0..D9 逐步断言 voice 序(tut_d0→…→tut_done)与 drillStep 递进;D1 断言 pulse 与 echo 播出;D4 长按断言 pos 前进 ≥2;D8 乱按/不格挡断言 HP 不降(教学怪 ATK=0);下滑跳过教学路径断言(二周目不再进 DRILL)。
- seed 复现:
opts.seed=42两次构造 →_floor.grid、楼梯/怪物/宝物坐标逐格相等;L1 尺寸 ===7;起点安全区(怪 BFS 距离 ≥3)。 - 脉冲回声信息正确:起点面朝 'N',按 grid 计算正前墙距 → 断言 echo_N 于脉冲后对应延迟±50ms 播出、volume 档正确、材质尾音 id 正确;左右侧向 side_* id 与 grid 一致;遍历 seed=42 的 L1 全网格 ×3 面向抽查 ≥30 组(回声编码 === grid 真值);双击重发断言资源零消耗且回声 id 与上次一致(未移动时)。
- 拾取:走到金币格 → coin 音 + meta.gold 增加且 st_gold 档位相应跳档;药水满腰带 → evt_potion_full 且药留在原地。
- 战斗路径(L2 窟鼠):进入 BATTLE → 回合流转(玩家回合 turn→敌前摇→结算循环);前摇内双击 → 敌伤 ×0.2(HP 差值断言);窗口外双击 → error 震、全额伤;长按逃跑 → 必成且 HP 真实减 ATK、怪留原格;击杀 → loot 档句 + meta.gold 入账。
- 下潜:踏上楼梯格 → sys 提示句 → 单击 → DESCEND → floor=2 且 grid 由 floorSeed 重生成(再断言复现)。
- 死亡与元进度:L3 窟主战挂机至死 → 归因链序
die_intro → die_cause_*(与 lastHit 真实来源一致,Boss 重击 → die_cause_2)→ die_adv_*(按 parries=0 → die_adv_1)→ st_floor_3;断言 meta.gold = 死亡前+本局所得、bestFloor更新;单击 → TITLE → 新周目 seed≠42、meta.unlocks 按金币阈值播 unlock_* 且起始 maxHp 生效(vigor)。 - 菜单/帮助:探索下滑 → 模态菜单(单击循环播项名、双击确认、上滑=help、下滑=关闭);"保存并回大厅" → PUT 断言 data.summary 命中生成规则。
- 中断复述:存档重进 → sys_continue_ask → 单击继续 → 落在存档点 → 自动局面复述链(st_floor→st_hp→st_face→st_threat 顺序断言);上滑同链可重放。
边界断言:
- 学习期零惩罚:L1 全怪 ATK===0、L2 ≤1(读表断言);L1 全程乱按 HP 不变。
- 撞墙:长按顶墙 → bump + pos 不变 + HP 不变。
- 方位自洽:任意格转身后重脉冲 → 左右 side_* 与 grid 随面朝交换;身后方向从不产生回声资产。
- 怪物脚步:怪每步播 mob_step__,pan/音量档与相对方位、距离一致(含背后=中央减半口径)。
- 绕位怪背刺:未转身挨打 → 全额伤 +
btl_behind;转身后格挡 → ×0.2。 - 缠斗怪左右滑 →
btl_no_turn(每场 1 次)+ error 震。 - 自动喝药:HP≤25% 且有药 → HP 回升档 + potion 音;无药时不播不炸。
- 卡关三级:无操作快进 60/120/180s → stair_hint_* → stair_dir_* → nav_* 链;任意进展事件后重置分级;提示在语音在播时排队不顶句。
- 看门狗:探索 10s/25s、战斗回合 15s/30s 逐级触发且第 2 级纯 nudge 无语音。
- 脉冲冷却 1.2s:冷却期单击/双击 → error 震、无 echo。
- 屏蔽任意 voice 资产:流程不卡死(看门狗兜底推进)。
- guide=off:全静、零定时器(全局既定契约)。
- 资产齐套:voice 双性别目录 id 集合一致、sfx/bgm 双格式(assetExists 口径)。
失败即不通过:死亡归因句与真实死因不符(归因假话);同 seed 两次生成不一致;教学怪造成 HP 损失;双击重发扣资源;回声编码与 grid 矛盾。
13. 节奏曲线与单局核算
- 每层节奏:进层签名句(5s) → 探索(脉冲-移动循环,2-3 分钟)→ 1-3 场战斗(每场 40-60s,窟主 90s)→ 拾取 1-3 处 → 楼梯。每 30-60s 一个奖励点(金币/击杀升档/装备),满足方法论 L5。
- 满通 24.0 分钟(§6.2 累计列);普通玩家死于 L4-6 → 单局约 14-19 分钟;二周目起(遗产加持)更深。首局若速死(L2-3,~8 分钟)由元进度钩子拉重开——roguelike 死亡结算 ≤15s 且必带「下周目更强」,重开入口单击 ≤2s(A4/L5)。
- 无任何倒计时(P4);高手速度感=长按连走+免听完整回声直走(P2),脉冲冷却不限制移动。
14. 自检清单
| # | 硬约束 | 自检 |
|---|---|---|
| 1 | FTUE 首输入 <60s | D1 首次脉冲 ≈t+15s ✓(§5) |
| 2 | 首个正反馈 ≤3min | D8 教学战胜利 ≈t+2min;L1 首金币 ≈t+2.5min ✓ |
| 3 | 学习期零惩罚 | L1 怪 ATK=0、L2 ≤1,学期内死亡不可能;撞墙无伤;格挡失败无额外惩罚 ✓ |
| 4 | 失败三问归因 + 真话律 | 死亡链 die_cause_/die_adv_ 按 lastHit 与 parries 真实选择;挥空/背刺/逃跑挨打均有真话句 ✓(§12 失败即不通过项锁定) |
| 5 | 手势宪法三件套不豁免 | 上滑=状态、下滑=菜单、双击=重听(重发脉冲) 全状态保留 ✓;移动/脉冲偏离已登记 E1-E4 且教学讲清 ✓ |
| 6 | 向导层 | 签名 5 态(不新增 earcon);turn cue 每回合;看门狗 [10/25][15/30]s 第 2 级纯 nudge;卡关 60/120/180 三级不报答案(带路只给方向);纠错 idle-only ✓(§10) |
| 7 | 方法论反模式 | 提示永不逐字重播(L2 级纯音频);成功后只给新短句肯定;单次播报 ≤3 选项(菜单逐项);并发声流 ≤2(频带分离+冷却);语义靠语音、earcon 用引擎统一词表 ✓ |
| 8 | 方位降维 ≤4 | 回声前/左/右(身后不报);战斗面前/左/右/背后;无连续角度 ✓ |
| 9 | 台词规范 | 一句一文件整句;全部 ≤25 字(<40 上限);数字全分档整句;"重听"写"重新听"、"重发"写"再发一次";同游戏同 instruct 常量;无语速词 ✓(voice-ids 注记) |
| 10 | P1 视障评委 | 双击免费重发脉冲;status key 全状态可用;方位/威胁模板句固定;归因真话;可打断/长按快进/2.2 档语速引擎既有;绝不当白痴(不简化深度:绕位/Boss/元进度保留)✓ |
| 11 | P3 通勤 | 单手免视全手势可达;上滑一句话复述局面(层/血量/面朝/威胁);任意时刻菜单存档退出;死亡不清 meta ✓ |
| 12 | P4 工人 | 教学逐字跟练+失败 2 次自动过;容错高(ATK0 怪/自动喝药/必成逃跑);无倒计时;零新手势(复用 swipe/tap/longpress)✓ |
| 13 | P2 大学生 | 元进度结算「下周目你会更强」;击杀升档+装备即时成长;长按连走速度感;死亡重开入口 ≤2s ✓ |
| 14 | seed 可复现 | floorSeed 派生公式 + opts.seed 注入 + 复现断言(§6.1/§12.2),与 playtest 种子 PRNG/强制队列/虚拟时钟机制兼容 ✓ |
| 15 | 三套信息自洽 | 单源 grid 查询;回声=静态地形/脚步=动态怪物/播报=绝对面朝,模板句固定,语义域零重叠 ✓(§3.2) |
| 16 | 引擎能力边界 | 无实时 pan(预 pan 资产);无引擎外节点(setTimeout+volume 调度);tap 300ms 延迟规避(格挡绑双击);30MB 预算内 ✓ |
实现核对(测试 agent,2026-09-02)
测试载体:
web/dungeon/test/playtest.mjs(mock 层+虚拟时钟照web/rpg/test/playtest.mjs, 1277 断言全绿,tools/node/bin/node web/dungeon/test/playtest.mjs退出码 0); 会话剧本web/harness/sessions/dungeon.mjs(session主线弧线 +sessionStress乱按压力, 经web/harness/transcribe.mjs dungeon [--session sessionStress]跑通,全程零资产缺失、零死锁)。
§12 关键路径
| # | 断言 | 结果 |
|---|---|---|
| 1 | 启动→TITLE→DRILL:D0..D9 逐步 voice 序(intro_1/2+tut_d0→…→tut_done)与 drillStep 递进;D1 pulse+echo;D4 长按 pos ≥2;D8 乱按 HP 不降;下滑跳过+二周目不再进 DRILL | ✅(场景 2;D2 错手势重教、D3 连错 2 次自动通过、D8 乱按 80 手势 HP=20 一并覆盖) |
| 2 | seed=42 两次构造 grid/怪/宝/楼梯逐格相等;L1 尺寸 7;起点安全区 BFS≥3(教学局非门怪 ≥4) | ✅(场景 1:7 层全查 + 实流 _floor 与纯函数一致) |
| 3 | 回声编码 === grid 真值:全地板格 ×4 面向扫描(≥30 组),echo_N 延迟/音量、材质尾音 +120ms、侧向 +350ms 预 pan 资产 L/R;双击重发零消耗且编码一致 | ✅(场景 2:104 组合全扫,代表组精确到 0ms;身后不报=恰两路侧向) |
| 4 | 拾取:coin 音+meta.gold+金币档跳档(45+堆≥55→st_gold_2);药水满腰带 evt_potion_full 且药留原地 | ✅(场景 4) |
| 5 | 战斗:单击命中、缠斗 btl_no_turn 每场 1 次、前摇内双击 ×0.2(钩子 atk=10 → 伤 2)、窗口外/不格挡全额、长按逃跑必成挨一下怪留原格、击杀 loot 档+meta.gold | ✅(场景 5) |
| 6 | 下潜:踏上楼梯 sys 提示→单击→DESCEND→floor=2 且 grid 重生成复现 | ✅(场景 2) |
| 7 | 死亡与元进度:归因链序+真话、meta.gold 入账、bestFloor/runs、遗产购置播报、新周目 seed 重掷+继承(vigor 26/potion 1) | ✅(场景 6a/6b/6c 三种死因矩阵) |
| 8 | 菜单:单击循环/双击确认/上滑帮助/下滑关闭;「保存并回大厅」→ sys_saved → '../';summary 生成规则 |
✅(场景 7 + 场景 2 D7) |
| 9 | 中断复述:sys_continue_ask→单击→st_floor→st_hp→st_face→st_threat 有序;上滑同链可重放 | ✅(场景 8) |
§12 边界断言
| 断言 | 结果 |
|---|---|
学习期零惩罚:L1 全怪 ATK=0、L2 ≤1(读 _battle.atk);L1 乱按 HP 不变 |
✅ |
| 撞墙:bump + pos 不变 + HP 不变 | ✅ |
| 方位自洽:转身后重脉冲左右 side 随面朝交换(含全扫描 4 面向) | ✅ |
| 怪物脚步:pan/音量档(0.9/0.6/0.35)、背后中央减半(1 格 0.45、6 格 0.175)、>8 格不播、≤2 格 heartbeat | ✅ |
| 绕位怪:每 2 回合换位(右→后→左确定性轮换)、脚步报位、背刺 btl_behind+全额、转身追击命中、转身后格挡 ×0.2 | ✅ |
| 缠斗怪左右滑 → btl_no_turn(每场 1 次)+ error 震 | ✅ |
| 自动喝药:HP≤25% 有药 → 回 40% maxHp(ceil)+ potion 音+档播报;Boss 半血 ATK+1 + btl_half_*;Boss 逃跑拒绝 btl_flee_boss | ✅ |
| 卡关三级 60/120/180s:stair_hint_a→stair_dir_→nav_ + 导航每步播方向 + nav_done;进展重置分级(重置后从 L1 重新触发);语音在播入队不顶句、操作作废、未播级别可重新触发 | ✅(场景 9) |
| 看门狗:探索 10s gd_pulse、战斗 15s gd_turn(guide=full);DRILL 8s 纯 nudge 无语音 | ✅(场景 10) |
| 脉冲冷却 1.2s:冷却期单击仅 error 震、无第二次回声 | ✅ |
| 屏蔽任意 voice:遭遇报名/下潜旁白被屏蔽 → guard 兜底推进不卡死 | ✅(场景 11) |
| guide=off:30s 空闲零播放、零定时器 | ✅ |
| 资产齐套:voice 双性别 125 句集合一致、sfx/bgm 双格式、game.js 静态引用展开全存在、运行期零缺失 | ✅ |
| E3:对局期 setRateBoostEnabled(false)(DRILL/EXPLORE/BATTLE/DESCEND)、TITLE/MENU/SETTLE 恢复 true | ✅(rateLog 断言) |
| E4:菜单=暂停——探索游走冻结、战斗前摇剩余窗口冻结、恢复恰结算一次(无重复回合) | ✅(场景 7;前摇内下滑原本不可达,见 bug ⑧) |
§12 失败即不通过项:死亡归因真话(cause/adv 与 _deathInfo.lastHit 一致,三种死因矩阵验证)、同 seed 两次生成一致、教学怪零伤害、双击重发零消耗、回声编码与 grid 一致——全部锁定 ✅。
与设计文档的偏差(记录不大改)
- 归因句粒度:台词表死因仅 3 档(小怪拖垮/Boss 重击/背刺),无「指认具体怪 A」整句资产;真话律落地为
_deathInfo.lastHit.type与 cause 档位一致(playtest 断言lastHit.type==='crab'/'rat'/behind↔ die_cause_2/1/3)。资产表内不存在更细句子,不算偏差。 - 战斗上滑第 2 段:§2.2「敌方血量档」以
btl_half_*(半血阶段句)近似——台词表无 foe_hp_* 分档资产。 - §5 D4「撞墙也算过」:实现额外把 D4 自动面向东侧 2 格走道+边界,保证 ≥2 格可达成(生成约束注释已写明)。
- L1 安全区:非门怪 BFS≥4(严于规格 ≥3),避免 D4/D8 演练路径撞上第二只怪,注释已写明。
- die_adv 兜底:挡过且跑过(parries>0 && flees>0)死于非背刺 → die_adv_1(建议句非过去事实句,规格未覆盖该象限,取最无风险句)。
- 格挡成功播
btl_parried:规格表 B 组 trigger=「格挡成功(减伤 80%)」,原实现该资产死引用,已接线(见 bug ②)。
修过的 bug(web/dungeon/game.js)
- 归因真话律:
run.parries++原在双击瞬间计(背对双击也计),歪曲 die_adv 选句依据——改为仅结算判成「面对且格挡成功」时计数。 voice/btl_parried死资产:格挡成功(减伤 80%)从不播报——接线进_enemyResolve反馈链。- 卡关提示「未播级别计入 shown」:入待播队列即标已提示,被玩家操作作废后该级永不重触发(违反 §10.3/escape round2 礼仪)——改为真正播出才计入,另加待播去重。
- 终局层 L3 带路死路:
_navRouteVoices/_navSpeakStep只查stairs,L7 无楼梯致导航哑火——回退stairs || throne(与 L2 明示同口径)。 - SETTLE 双击=重听结算链:原只重播
settle_again尾句——存_settleChain整链重放(§2.3)。 - DRILL D1 无看门狗:
_drillEnter(1)无分支——补_drillWatchdog(1)(§10.2 教学每步 [8s])。 - DRILL D8 上滑=做错:§4.3 状态键「教学后可用」(D7 已教)——D8 补 up→状态链。
- E4 豁免半实现:战斗前摇(telegraph)内下滑走 error 分支、菜单打不开,
_menuPause冻结机制不可达——前摇内补down→_openMenu(),战斗任意时刻可暂停。
遗留 TODO
- 教学期 L1 游走静默:DRILL 内非教学怪不游走(
_startMobs仅 EXPLORE 调用),设计未明确,维持现状(D9 后下潜即恢复)。 - 木门/水材质(mat_door/mat_water):生成器不产出,资产与编码路径已备(
_echoScan注释),待未来层型。 meta.runs不统计 TITLE 右滑放弃的周目(仅死亡/通关计入);规格未明确。- 会话剧本主线弧在随机图极小概率把窟主打死(玩家成长超预期时)——剧本有「没死就继续走」兜底分支,不影响零死锁承诺。
R2 修复回归(2026-09-02,D0-1..D0-3 + D1-1..D1-6 结构修复轮)
证据源:research/new8/dungeon/round1/(synthesis + live-player + p1-blind)。本轮只修结构不加新内容; 引擎侧按产品裁定允许改动(web/engine/audio.js、gesture.js)。回归:8 套 playtest + guide.test 全绿 (dungeon 1277→1610 断言;escape 140 / radio 221 / rhythm 141 / rpg 188 / simon 132 / space 155 / storyline 82 / guide 122,0 失败,radio 在并行修复中仍全绿)。
D0-1 语音抢占死循环(P0)
- 根因(引擎级竞态,非 DRILL 泄漏本身):
WebAudioEngine._playVoice的「顶掉当前对白」检查在await this._load(id)之前,而this._voice = token在其后无条件赋值——同一 tick 双调度语音 (教学 D1:反馈句tut_echo_come裸_speak+_drillPass立即_speakGuarded下一句指令)在加载 竞速后,输家的 token 被静默顶号:ScriptProcessorNode 永远无人 disconnect(僵尸节点空转抽 buffer), 其 endTimer 亦因_finishVoice的 token 校验提前 return 而永不清理。R1 live 证据完全吻合:t=130.2s D1 首脉冲后 tut_echo_come 僵尸节点持续抽读,探针按「voice buffer 被抽读」记段 → 71 分钟 35,745 次 「播放」99.7% 被「掐死」、nav_n↔tut_echo_come 互掐 33,297 次;跳教学二周目 0 抢占(无 D1 双调度)。 - 修:①
audio.js _playVoice在 await 之后、赋值之前补杀当前 token(顶掉语义在加载完成后复核, 后到者胜、败者必被杀);② 游戏 D1/D6 反馈句并入_speakGuarded推进链(消灭同 tick 双 play 的源头),_drillDone/_drillSkip收尾显式guide.disarm();③ 全线风暴保险丝(audio.js play()voice 入口): 同一 voiceId 3s 窗口内被调度 ≥5 次 → 丢弃后续并 console.error(每 id 一次)。 - 断言:
保险丝:同 id 3s 窗口内前 4 次调度放行 / 第 5 次起丢弃 / 其他 id 不受累 / 窗口滑出恢复 / 只作用于 voice 层(真实 WebAudioEngine 单元断言,场景 12A);D1: 反馈句与下一步指令句串行(场景 2);纯探索 60s 零操作语音调度有上界(≤3 句;R1 风暴同期 >2000 句)(场景 12C)。
D0-2 回声信息真实性(P0)
- 根因:① 侧向
_sideScan把「整条侧射线到边界全通」才判开口——房间/走廊地型恒为假 → side_open 两局 40+ 脉冲 0 次、能走的方向报墙;② 站在楼梯格上脉冲走射线扫描(楼梯在脚下不在面前)→ mat_stair 0 兑现,教学「铁器=楼梯」成谎言(P1 专项:165 脉冲 0 铁器,含站在楼梯上 t+142.2)。 - 修:回声与 grid 单源重接——侧向第一格地板=开口(§3.1「墙=闷/开口=空旷」2 档口径,墙保留射线距离);
_echoScan站楼梯格 → 贴身(dist1)铁器回声。 - 断言:场景 2 D9 全扫描(全地板格 × 4 面向真脉冲)truthScan 同步更新;场景 12B 全网格一致性:
seed=42 L1..L7 每格 × 4 面向
_echoScan/_sideScan与测试侧独立真值逐格相等(≥200 格)+全网格存在侧向开口(R1 全程 0 次 → 现扫到 N 格次)+ 各层站楼梯=贴身铁器。转写复核:主线局 mat_stair/side_open 均稳定出现。
D0-3 探索内容真空(P0)
- ① 密度下限:
_fillMainPathDensity——start→楼梯(终局=王座)最短路(BFS 距离梯度回溯,确定性) 每 ≤12 格必有内容遭遇,超限段中点补本层已有普通怪型(不加新怪种,≤8 只,安全区口径不变)。 断言:场景 1 各层主路径内容密度(最长真空 ≤12 格)+ 构成 ⊇ 规格表。 - ② 探索呼吸层:新 SYN 资产
sfx/amb_cave(gen_sfx.py:45-320Hz 带通空气声 + 4 整周期/10s 呼吸 LFO + 两粒远处水滴;10s 无缝循环源:去直流、环回交叉淡化、不首尾淡化,解码回读 ogg -4.20dBFS / m4a -4.00dBFS、|直流|≤0.001、循环接缝 -25dB(rel peak);20 文件 ×2 格式全过 QC)。接线:EXPLORE 静默 ≥20s(无手势无人声)播 amb_cave(volume 0.35,20s 节奏不连播)。guide=off 契约不变(环境垫属内容层, 场景 4 的零播放断言已按此口径更新并加 amb 专项断言)。 - ③ 下潜演出:游戏代码本身完好(playtest 场景 2 一直断言 stairs_down→evt_descend→st_floor_N 通过);
R1 转写零演出为剧本伪影:(a) navigate 按 1 格/按建模,但 harness
waitVirtual(120)在虚拟时钟会 越过 +500ms 步进计时器(advance 至少推进到下一到期定时器)致每按多走 1+ 格、永远踩不准楼梯格; (b) D9 用pulse:false违反「玩家靠耳朵」红线且无到站重试。修:navigate 立即松开(恰 1 格)+until: 'stairs'到站口径 + 逐层验证的下潜循环(tapDescend 只在真踩楼梯时出手,DRILL D9 同样受理)。 复核:主线转写两次运行弧线完全一致(两次下潜全套演出:stairs_down→evt_descend→st_floor_N)。 - ④ 卡关计时器:计时与分级 R1 live 已证完好(64s/120s/180s 准时);转写 350s 未触发系剧本持续手势
重置 lastAct(§10.3 口径「任何有效手势重置」)+ 上句的导航伪影。断言:场景 12C
卡关 L1 暗示 60s 自然 触发(纯探索、无 hook)。
D1 级修复
- D1-1 菜单关闭无回执:live 复核实为「关了但零反馈,下次下滑又重开」——路由本身无恙。修:
_closeMenu补状态签名 earcon(回 EXPLORE/DRILL=explore、BATTLE=battle、SETTLE=settle、TITLE=menu)。断言:场景 10菜单关闭播回程状态签名(menu → explore)。 - D1-2 D9 看门狗兜底:
_drillWatchdog9()(8s nudge、30s 升级播 stair_hint_a——自寻路步必须有救援); D9 每次手势后重挂(onGesture 的 disarm 会清掉它,而 D9 的脉冲/转身不推进步骤)。断言:场景 10 两级触发- 升级句。
- D1-3 导航指墙 + 蘑菇软封锁:① 带路改为最短路第一步方向(
_navFirstStepDir:从目标反向 BFS 取 梯度下降邻格),逐步校正;同向 ≥1.5s 才提醒(改向必播)——既不指墙也不撞全线保险丝(导航句同 id 连发 会计熔断)。断言:场景 9带路方向与 grid 最短路一致 + 第一步必可通行 + 节流单测(同向连呼 3 次只播 1)。 ② 蘑菇/所有游走怪相邻即扑击接战(_mobStep移动后与玩家正交相邻 →_encounter),杜绝 R1 L3 「贴身绕圈、堵格不开战」35 分钟软封锁。断言:场景 12C 相邻怪必然开战。 - D1-4 战斗结束 BGM:教学战胜利/逃跑回 DRILL 的两条路径原先不切回 bgm/explore(只有菜单关闭路径恢复
——R1 转写 t=35.8 起战斗鼓点泡全程的组成因素之一)。两条路径补
bgm/explore淡入。断言:场景 5 胜利/ 逃跑后lastBgmEntry === bgm/explore。 - D1-5 金币档:SETTLE(
_die/_settleWin)原用 run 金币自设阈值(<20/<60),TITLE 用_goldBand(meta.gold)(<50/≤200/≤600)——同一句 id 两套口径(live:SETTLE meta_gold_3 vs TITLE meta_gold_2)。统一为同一分档函数同一账本(遗产购置后的 meta.gold)。断言:场景 6aSETTLE 金币档 = _goldBand(meta.gold)。 - D1-6 三件:① 长按 boost 闪跳——E3 豁免期快进本就不生效,但 gesture.js 仍外发
soundgame:longpress(+end)窗口事件(仪器记为 boost on→off 闪跳):setRateBoostEnabled现同步经setLongpressBoostEventsEnabled抑制窗口事件(游戏手势回调不受影响)。② D7「菜单被系统自动收」经 live 事件流复核为风暴伪影(菜单开场链 sys_menu→首项名被僵尸节点反复重放,被听成自动执行 sys_menu_resume) ——D0-1 修复即愈,无额外代码;playtest 场景 2 已有「菜单保持打开直到玩家下滑关闭」断言。③ 战斗回合 15s 无操作播合成回执sfx/turn_tick(880Hz 木鱼族,gen_sfx.py,-3.87dBFS)——standard 档无 gd_turn 语音也有「还到你」的确认。断言:场景 10 turn_tick + 引擎豁免事件抑制(rateLog 既有断言不受影响)。
伪影修正(web/harness/sessions/dungeon.mjs)
- 「元进度不兑现」关案依据 + 三处剧本脱轨根因:①
waitVirtual(120)虚拟时钟越表多走(见 D0-3③); ② tapDescend 不受理 DRILL(D9 最后一击点不响);③ 单发 navigate+tapDescend 无重试,任何被怪打断即脱轨 ——逐层验证下潜循环修复。另修 navigate 中「走位途中被扑→长按按到战斗里=瞬间逃跑」(R1 live btl_flee×9 的剧本同款)。复核:主线转写 2 次弧线逐句一致(含归因链与金币档);压力转写 2 次均零死锁走到死亡-重开。
R2 新增/变更断言口径登记
- 场景 1:怪物构成由「逐字相等」放宽为「⊇ 规格表 + 补怪限本层怪种 ≤8」(密度下限所需)。
- 场景 4:
guide=off 30s 零播放改为「零语音/零向导播放,唯 amb_cave 按呼吸层口径除外」(内容层 vs 向导层切分)+ 呼吸层节奏/重置断言。 - 场景 9:L3 带路从「整段先东西后南北 + 每步必播」改为「最短路首步 + 改向必播/同向 1.5s 节流」 (§10.3 L3 口径的听感与保险丝友好修订)。
- 台词/资产表:无新增 voice;新增 sfx
amb_cave、turn_tick(SYN,双格式,QC 数字见上)。