Files
SoundGame/docs/design/23-hunt.md
T
xsl d78df35b5c fix(hunt): v2 九修 S1-S9 落地——10 人并发模拟迭代裁定(同阵容复测:去留 6/10→10/10)
研究闭环:v1 三轮 28 局四死因(失衡/社交真空/交互摩擦/观光羞辱)→ 四视角分析 →
synthesis S1-S9 裁定 → 实施 → 同 10 人配置复测两轮 20 局。依据 research/hunt-sim/
(synthesis.md / metrics-v2.md / report.md),设计登记 23-hunt.md §16。

服务端 hunt-room.js:
- S5 协作人声包:真人座位角色接线(杀熊/三连杀/旋风斩≥3 播角色句、greet 全座位);
  满编真人房呼喊 2.5s 无 bot 应答 → 邻座角色代答 help_ack;喊话预算 10s 窗 2→3
- S7 难度重构:waveCounts 4 人房 ×4/3;alignPincers 第 3-5 波齐压拍(跨座位同种类
  同秒进内圈,种子确定性保持);mobYielded bot 对人类防区怪让位 2s(只约束 bot,
  不拦真人避免挥空真话说谎);gradeForEx 双轴评级(S=耐久≥9 且 ≥2 人各 6 杀,
  单人带飞局上限 A)

客户端 game.js:
- S1 WAVE 下滑恒出击(无怪=空挥真话),菜单收窄到波间休整窗(E3 修订)——
  v1 误触菜单单人单局最多 14 次清零
- S2 冷却首拦才震 + 就绪 beat 轻叮(v1 全场 666 次乱滑连震)
- S3 0 杀不播 kill_band0(「你还没开张」下架;复测 P08 0杀→12杀)
- S4 看门狗重定义:本波出过手一级语音不触发、语音闲置每局 ≤1、一级降纯震
- S5③ help_call/help_ack 永不超时丢弃
- S6 后半平面 loop 低通 750Hz 第二线索(N/S 判读 63/69%→75/81%)
- S8 教学作答步(T1-T3)下滑不再误跳
- S9 状态链按变化播(「固若金汤」复读归零)

测试同步:hunt-sim A1/A2/A12 断言更新(真人 greet/波次缩放/预算 3)、
playtest Block 3 按 S1 新语义改写(战斗中下滑=空挥 + 波间菜单)

回归:hunt-sim 16/16 + playtest 213 + guide 132 + engine-voice 19 全绿;
balance-scan:挂机局 5/8 落 C/失守带(v1 恒满血)、optimal 单人带飞封顶 A
遗留:营地零损耗未破(18/20 局余10)——v3 头号,参数方案 report.md §5
2026-09-19 11:06:18 +08:00

542 lines
48 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 23 · 《围猎》完整设计(hunt / Night Hunt)
> **给开发者的实现要点摘要**
> 1. **2-4 人实时联机纯音频协作守卫**,项目"纯音频 MMORPG"路线的**第一阶段最小验证切片**(§1.3)。本作只验证三件事:实时同步(零依赖 WebSocket + 服务端权威)、音频信道拥挤控制(§8,全文重点)、空间音频协作(方位=pan+防区分工)。其余一切从简。
> 2. 圆形猎场,猎人位于营地中心。野兽从 8 罗盘方位边缘逼近(8-12s),**四向滑=出击**(滑向映射 90° 弧,击杀弧内内圈最近一只,冷却 2.5s/人);突入中心 = 共享营地耐久 -1(巨兽 -2,初始 10);单击=呼喊支援(队友收到你防区方位的空间化喊话 + warning 震,bot 补位);双击=旋风斩(清内圈全部,冷却 8s;**冷却中双击=重听威胁播报**,宪法双击语义由此保留)。上滑=状态链,下滑=菜单(**不暂停**,联机实时豁免 E3)。
> 3. 服务端权威:怪物生成/移动/命中/冷却/评级全在 `web/server/` 裁决,客户端只发意图(join/act/leave)。bot 由服务器驱动,与真人同一套事件协议。房间 3-4 座,开局空位由 bot 补至 ≥3 猎人;断线 30s 宽限重连回槽,超时转 bot。
> 4. 协议全文 §6(两条传输消息 + 三类服务端消息,JSON 字段表逐条给出);零依赖 WS 握手与帧解析规范 §6.1(Node 原生 http upgrade + 手写帧,不引 npm)。
> 5. 拥挤控制硬规则 R1-R8(§8.1):旁白单句队列(voice 层)、bot 喊话 sfx 层同刻 ≤1、怪物移动 loop ≤3 路(最近 3 只)、非语音信息全部 <1s 短音效、听觉视野裁剪只报内圈(<4s 抵达)。
> 6. **引擎改动唯一一处(后向兼容)**:`engine/audio.js` 的 sfx/voice 播放各加可选 `opts.pan`(-1..1,StereoPannerNode 接入对应总线),缺省行为逐字节不变(§9)。怪物移动 loop 走游戏层句柄(abyss `_startLoop` 先例 + 游戏层自建 panner),不占引擎改动面。
> 7. 台词表 [23-hunt-voice-ids.md](23-hunt-voice-ids.md):旁白/系统音 **60 句 ×2 性别**(male=Uncle_Fu / female=Serena,预算打满)+ bot 喊话 **3 角色 ×8 句 = 24 条固定单音色**(阿岩=Uncle_Fu / 小筝=Serena / 老周=Eric;因走 sfx 层带 pan,**落盘 `audio/sfx/`**,异例说明见台词表头注)+ 键盘变体 11 对。数字信息全分档整句,无运行时拼接。
> 8. 设计裁决与对任务书核心案的修正记录(双击矛盾、出击冷却、防区模型、房码 UX 范围等 9 条)见 §15,**实施前必读**。断言清单住 [13-playtest-criteria.md](13-playtest-criteria.md) §24,本文 §13 只留摘要与指针。
---
## 1. 概述
### 1.1 一句话
夜幕降临,野兽从四面八方围上来——你和队友背背守住营地篝火,**听声音的方向滑过去就是一刀**,撑过五波,天亮就赢。
### 1.2 题材与纯音频相性
守夜围猎是"方位即生死"的题材:怪物唯一的接近预告就是它逼近的声像。视觉游戏里这是小地图+tower defense;纯音频里,**声像本身就是小地图**——题材与媒介互为 justification,与 dungeon「声音就是你的眼睛」同源,但本作是**实时+协作**,不是回合制探索。
### 1.3 定位:纯音频 MMORPG 路线 · 第一阶段最小验证切片
本项目长线方向是纯音频 MMORPG(持久世界、多玩家同图协作)。《围猎》是该路线的**第一个可玩切片**,刻意把范围切到最小,只验证三个技术-设计命题:
| 验证点 | 本文档落点 | 通过信号 |
|---|---|---|
| **实时同步**:零依赖 WS、服务端权威战斗、事件流+快照兜底、断线重连 | §6 协议 | 局域网 2-4 端同局,事件一致,断线 30s 内重连无感回槽 |
| **音频信道拥挤控制**:多源并发(旁白/bot 喊话/怪物 loop/定位音效)不糊 | §8 R1-R8 | 最坏并发 7 声流下威胁可辨、警报不丢(§8.4 验证口径) |
| **空间音频协作**:靠 pan 的方位分工与支援是否成立 | §3 防区 + §7 bot 补位 | 玩家能用呼喊-补位闭环分担压力,无需任何视觉 |
**明确不做(切片纪律)**:无持久世界、无跨房聊天、无跨玩家排行榜("排行榜"= 进度 API 的个人最佳桶 + 大厅卡片 summary,§12)、无房码音频录入 UX(协议保留,客户端 v1 只做快速匹配,§6.4)、无内购/社交。这些列 roadmap,不在本文档。
### 1.4 单局
5 波为一局,约 5 分钟(含 lobby ≈ 5.5 分钟;首局含教学 ≈ 6.5 分钟)。营地耐久初始 10、共享;耐久归零 = 营地失守(立即终局,突围结算,不入榜);撑过 5 波 = 胜利结算(评级 S~C + 个人击杀分档 + 排行榜写入)。
### 1.5 与现有 12 游戏的关系
实时方位感承接 space(呼啸来袭+同向反应),但改为**协作分工**;状态分档链承接 dungeon(上滑多段链);威胁裁剪承接 dungeon 脚步距离档音量规则并升级为显式"听觉视野"(§8.2);bot 喊话平铺资产承接 rpg/radio/detective NPC 先例,但首次走 **sfx 层空间化**(§8 R2);结算链死窗沿用 dungeon H1 先例(800ms 手势死窗);看门狗/向导层全照 00 §6。**两个"第一"**:第一个联机游戏、第一个用到运行时 pan(引擎 opts.pan,§9)。
### 1.6 本作三大特有风险(均有显式对策)
1. **多源并发糊耳**(5 波峰值:旁白+喊话+3 路 loop+定位音效)→ §8 规则表 + §8.4 验证断言。
2. **实时联机与纯音频教学的根本矛盾**(不能暂停真人局教学)→ 首局教学房(§5):单人+bot、教学波、学期零惩罚,教完同场接续第 1 波。
3. **协作流于形式**(都能打所有方向=不需要队友)→ 出击冷却 2.5s 限单人吞吐 + 软防区 + bot 补位闭环(§3.3、§7)。
## 2. 手势映射与豁免登记
### 2.1 战斗态(WAVE,含教学局 TEACH 的战斗段)
| 手势 | 作用 | 反馈 |
|---|---|---|
| 四向滑(上/下/左/右) | **出击**:滑向映射 90° 弧(上→{NW,N,NE}、右→{NE,E,SE}、下→{SE,S,SW}、左→{SW,W,NW}),击杀弧内内圈(eta≤4s)最近一只;冷却 2.5s | 引擎 swipe 震 + `atk_whoosh`;命中 `kill_*`+pickup 震;挥空 `atk_swish`+error 震 |
| 单击 | **呼喊支援**:全队友收到你防区方位的空间化喊话 + warning 震;bot 朝你侧补位 10s(§7.3);节流 5s/人 | 引擎 confirm 震 + 本机播自己座位角色的 help_call(中央) |
| 双击 | **旋风斩就绪** → 放出:清全场内圈(eta≤4s)全部怪物,冷却 8s;**冷却中** → 重听当前威胁播报(threat_*,宪法"双击=重听"落地) | 放出:success 震 + `skill_spin`;冷却中:引擎 confirm 震 + threat_* |
| 上滑 | 状态链:营地耐久档 → 波次档 → 在场猎人数 → 自己击杀档(4 段链播 queue:true) | hp_band_* → st_wave_* → team_* → kill_band_* |
| 下滑 | 模态菜单(**不暂停**,豁免 E3,§2.4) | menu earcon |
| 长按 | 非玩法输入:引擎旁白快进默认行为保留 | — |
- 出击冷却 2.5s 内四向滑 → 仅 error 震(无语音、不发 act;dungeon 脉冲冷却先例)。
- 单击呼喊是**非即时操作**,300ms tap 延迟无碍(escape 单击=检查先例)。
- 旋风斩就绪时刻播 `skill_ready`(queue:true,绝不顶句;每轮冷却完成只播一次)。
- **事件震动总表**:命中 pickup / 挥空 error / 旋风斩放出 success / 突入 hit / 收到呼喊 warning / 波次开始(ev wave)heartbeat / 耐久首次 ≤2 danger(每局 1 次,配 danger_low)。手势识别震动(swipe/confirm)由引擎内置承担,游戏层不重复(00 §2.3)。
### 2.2 标题/匹配/大厅等待(TITLE / MATCHING / LOBBY)
| 手势 | TITLE | MATCHING | LOBBY |
|---|---|---|---|
| 单击 | 快速匹配(首局=教学房) | —(无效,error 震) | 立即开局(act start) |
| 双击 | 重听开场/提示 | 重听 | 重听 room_wait |
| 上滑 | 个人最佳播报(有档时:best 评级+击杀档) | error 震 | 在场猎人播报(team_*) |
| 下滑 | 菜单(帮助/回大厅) | 菜单(含"取消匹配") | 菜单(退出房间回大厅) |
| 左右滑 | error 震 | error 震 | error 震 |
### 2.3 结算态(END)
单击=再来一局(重新进匹配);双击=重听结算链;上滑=重听个人最佳段;下滑=菜单(回大厅)。**结算链进入时盖 800ms 手势死窗**(dungeon H1 先例:吞掉致命事件前后在途的 ghost 手势,死窗后手势照常)。
### 2.4 模态菜单(豁免 E3:联机实时不暂停)
下滑呼出 → 单击循环 → 双击确认 → 上滑=帮助(播 `help_main`)→ 下滑=关闭(00 §5.2 交互骨架不变)。**菜单打开期间战斗照常进行**(怪物照走、突入照扣)——联机不可单方暂停,这是与 00 §5.2"菜单打开时暂停计时类玩法"的显式豁免。菜单项固定 3 项:`sys_menu_resume`(回到猎场)/ `sys_menu_help`(重新听帮助)/ `sys_menu_home`(退出房间回大厅 = 发 leave,槽位按 §6.6 处理)。无"重新开始"(联机房局不可单方重开)。菜单打开即播 `sys_menu` + 首项;菜单内另播 `settings_hint`(设置只读、回大厅修改,00 §1.2)。
### 2.5 豁免登记(显式)
| # | 豁免条目 | 理由(先例) |
|---|---|---|
| E1 | 四向滑=出击(非选择/移动) | 以滑动为核心玩法(space 四向滑=闪避同款豁免);三件套(上滑/下滑/双击重听)全部保留 |
| E2 | 双击优先=旋风斩,仅冷却中=重听威胁 | 旋风斩是即时技,必须绑零延迟双击(00 §5.4:即时判定绑滑动或双击);冷却中重听保留宪法语义 |
| E3 | 菜单打开**不暂停** | 联机实时,暂停不可能;菜单项压到 3 项、打开即播提示,把"逛菜单被咬"的代价做成可感知风险 |
| E4 | 单击=呼喊(非确认/继续) | 非即时操作,300ms 延迟无碍(escape 单击=检查先例) |
教学(TEACH)逐条讲清 E1/E2/E4(§5);不占用长按(快进保留,无需 rateBoost 豁免);不开 immediateTap。
## 3. 世界模型与数值
### 3.1 猎场与怪物
- 圆形猎场:猎人位于圆心营地,野兽从边缘(8 罗盘方位 N/NE/E/SE/S/SW/W/NW)沿直线匀速逼近圆心。逼近全程 8-12s(按波次表)。
- 怪物状态由**服务器**持有:`{ id, mkind, dir, eta }`——eta = 距突入的秒数(服务端单调时钟)。客户端不推算位置,只按事件流+快照渲染(§6.7)。
- 怪物 2 型:
| 型 | mkind | 逼近 | 突入扣耐久 | 声音 |
|---|---|---|---|---|
| 饿狼 | `wolf` | 8-10s | -1 | `howl_wolf` 生成呼啸 + `loop_wolf` 移动 |
| 暴熊 | `bear` | 11-12s | **-2** | `howl_bear` 低吼 + `loop_bear` 重脚步 |
- **距离分档**(听觉视野,§8.2):外圈 eta>6s(仅生成呼啸一声);中圈 4-6s(移动 loop 可闻,低音量);内圈 eta≤4s(可击杀的近战窗,loop 升档,计入威胁播报)。
### 3.2 出击与技能(服务端裁决,§6.5)
- **出击**:`act attack dir` → 弧 = dir±45°(3 个罗盘向)→ 弧内活着且 eta≤4.0 的怪中取 eta 最小者击杀;无 → `miss`。冷却 2.5s/座(服务器计时,违规 `err cooldown`)。
- **旋风斩**:`act skill` → 清除全场 eta≤4.0 全部怪(不分方向)。冷却 8s/座(违规 `err skill_cd`)。
- **呼喊**:`act callout` → 广播 `ev callout`;bot 补位(§7.3)。节流 5s/座。
- 击杀归属 = act 来源槽位(结算个人击杀账)。
### 3.3 防区(软分工)
- 每座位固定**主防区**(连续罗盘弧)与防区中心(喊话 pan 用):
| 猎人数 | 座位 0 | 座位 1 | 座位 2 | 座位 3 |
|---|---|---|---|---|
| 3 | [N,NE,E],中心 NE | [SE,S,SW],中心 S | [W,NW],中心 WNW | — |
| 4 | [N,NE],中心 NNE | [E,SE],中心 ESE | [S,SW],中心 SSW | [W,NW],中心 WNW |
- 防区**不是硬限制**:任何玩家可打任何方向(弧映射见 §2.1)。防区用于:① bot 目标选择偏置(§7.2);② 呼喊/喊话的 pan 方位来源;③ 刷怪剧本的方位均衡(§3.5)。
- 座位角色(voice 身份):座 0=阿岩(yan)、座 1=小筝(zheng)、座 2=老周(zhou);**座 3 仅 4 人局存在,无专属角色**(呼喊=`call_whistle` 哨音,见 §15 裁决 6)。真人"扮演"座位角色:你的呼喊用你座位角色的预录声,断线转 bot 后声音身份无缝延续。
### 3.4 波次表(5 波,服务器剧本)
| 波 | 狼 | 熊 | 狼逼近 s | 熊逼近 s | 刷怪间隔 s | 波时长约 |
|---|---|---|---|---|---|---|
| 1 | 4 | 0 | 10.0 | — | 3.0 | ~21s |
| 2 | 6 | 0 | 9.5 | — | 2.5 | ~24s |
| 3 | 7 | 1 | 9.0 | 12.0 | 2.2 | ~29s |
| 4 | 8 | 1 | 8.5 | 11.5 | 1.8 | ~28s |
| 5 | 10 | 2 | 8.0 | 11.0 | 1.5 | ~30s |
- 波间休息 8s(`wave_clear` → 休整 → 号角 + 下一波播报)。波内怪物匀速逼近不止步,一波必在有限时间内出清(杀或漏),无需超时兜底。
- 每波开始播对应整句 `wave_band1`…`wave_band5`(台词表 C 组;先号角 `horn_wave` 后播报)。
- 全程怪物总数 39,耐久预算 10(熊 -2)。平衡目标:3 bot 水准下 solo 余耐久 6-9(评级 A/B),真人协作越好评级越高。
- 局时长核算:战斗 ≈ 132s + 休息 32s + lobby ≤12s ≈ **3 分钟战斗 + 首尾 ≈ 5 分钟** ✓(任务书"约 5 分钟")。
### 3.5 刷怪方位与种子
- 房间 seed 开局掷一次(`joined.seed`),mulberry32 派生:`rng(wave) = mulberry32((seed ^ (wave * 0x9E3779B9)) >>> 0)`。
- 每波方位分配:把本波怪物按座位数轮转发牌到各座位防区内的随机方位(同一方位同刻最多 2 只在场,防叠声);同 seed → 同 spawn 剧本(服务器单测可复现断言)。
- 服务端战斗模拟 tick = 100ms;快照 2Hz(§6.3)。
### 3.6 耐久与评级
| 结算时剩余耐久 | 评级 |
|---|---|
| ≥9 | S |
| 7-8 | A |
| 4-6 | B |
| 1-3 | C |
| 0(波内归零) | 失守(立即终局,无评级,不入榜) |
评级由服务器算好在 `ev end.grade` 下发(客户端不算分,防双源)。
**终局播报链**:胜利 = `end_win → grade_s/a/b/c → kill_band_*(自己击杀档)→ end_rank → end_again`;失守 = `end_lose → kill_band_* → end_again`(失守不播评级、不播 end_rank、不写 best,§12)。进 END 盖 800ms 手势死窗(§2.3)。
## 4. 状态机(客户端)
### 4.1 状态列表
`TITLE / MATCHING / LOBBY / TEACH / WAVE / MENU(overlay) / END / LOST`。MENU 为挂起 overlay(记录返回态,**不暂停战斗**);LOST = 连接不可恢复(回大厅出口)。
### 4.2 转移图
```
TITLE ──单击──> MATCHING ──joined──> LOBBY ──act start / 12s 到 / 满4人──> (ev wave 1)
│ 首局(tutDone=false) → join teach=true → joined.state.phase='teach' → TEACH
MATCHING ──连接失败──> LOST(net_lost→net_gone) ──单击──> TITLE
TEACH ──教学完成 / 下滑跳过──> WAVE(wave 1)
WAVE ──ev end──> END ──单击──> MATCHING(再来一局)
WAVE/LOBBY ──下滑──> MENU ──返回──> 原态(战斗不暂停)
WAVE ──WS 断开──> 本地静默重连 ≤30s(net_lost) ──joined(resume)──> WAVE(net_back)
└──超时/房已灭──> LOST(net_gone)
```
### 4.3 每状态手势映射总表
| 手势 | TITLE | MATCHING | LOBBY | TEACH | WAVE | MENU | END |
|---|---|---|---|---|---|---|---|
| 四向滑 | error | error | error | 步骤作答/出击 | 出击 | error | error |
| 单击 | 快速匹配 | error | 立即开局 | 步骤作答/呼喊 | 呼喊 | 循环切项 | 再来一局 |
| 双击 | 重听 | 重听 | 重听 | 步骤作答/旋风斩 | 旋风斩/重听威胁 | 确认执行 | 重听结算链 |
| 上滑 | 最佳播报 | error | team_* | 状态链(T4 起) | 状态链 | 帮助 | 重听最佳段 |
| 下滑 | 菜单 | 菜单(取消匹配) | 菜单 | **跳过教学** | 菜单(不暂停) | 关闭 | 菜单 |
| 长按 | 快进 | 快进 | 快进 | 快进 | 快进(非玩法) | — | 快进 |
无效手势一律 error 震;full 档语音纠错 `gd_invalid`(onceKey 同局 1 次)。看门狗语音 idle-only。
## 5. 教学编排(TEACH,首局教学房)
**矛盾与解法**:实时联机局不能暂停教学 → 首次进入(进度 `tutDone` 为假)走**教学房**:单人 + 2 bot(教学段 bot AI 挂起)、先行一波**教学波**(3 狼,逼近 12s,学期零惩罚:教学波突入**不扣耐久**),教学完成或下滑跳过后,同一房间直接接续第 1 波(bot 激活)。教学即真联机局——这本身就是切片验证点(首局就跑了完整的 WS/事件流路径)。
| 步 | 旁白 | 等待动作 | 通过/反馈 |
|---|---|---|---|
| T0 | `intro_1` + `intro_2` + `tut_rule` | 单击 | confirm 震 |
| T1 | tut_attack(服务器放教学狼 1,方位 N) | 朝声音方向滑 | kill → tut_kill_ok;挥空/错向 → gd_idle 重教(≤2 次后自动放过该狼) |
| T2 | tut_callout | 单击呼喊 | callout ev → bot `npc_*_help_ack` 响应演示(教学 bot 必答)→ tut_callout_ok(呼喊-响应闭环点明) |
| T3 | tut_skill(服务器放教学狼 2、3,贴近 eta 5s) | 双击旋风斩 | cleared=2 → tut_skill_cd(讲冷却与冷却中双击=重听) |
| T4 | tut_status | 上滑(状态链) | 链播完 → tut_breach → tut_done → 进 WAVE 1 |
| 任意 | — | 下滑 | tut_skip → 进 WAVE 1(`tutDone` 写入进度) |
- 教学波零惩罚:教学狼突入不扣耐久(明说规则前不漏真话);T1 首输入 ≈ t+20s(<60s ✓);首个正反馈 = T1 首杀 ≈ t+40s(≤3min ✓)。
- 看门狗:每步 8s nudge 纯震(教学句不重播),T1/T3 卡 30s 自动放过当前狼推进(space 先例),不卡流程。
- 二周目起 TITLE 单击直达快速匹配(tutDone 存进度,§12)。
## 6. 多人协议(两个开发 agent 的实现契约)
### 6.1 传输:零依赖 WebSocket(server.js 增补)
- 端点:`GET /ws/hunt`,与静态/REST 同一 HTTP 服务器、同一端口。`http.createServer` 的 `'upgrade'` 事件接管:校验 `Upgrade: websocket`、`Sec-WebSocket-Key`,回 `101` + `Sec-WebSocket-Accept = base64(sha1(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))`(node:crypto,无 npm)。
- 帧解析(手写,约 100 行,建议独立 `web/server/ws.js` 便于单测):客户端→服务器帧**必须带 mask**(不带 → close 1002);支持 opcode 0(continuation 拼装)/1(text)/8(close)/9(ping→回 pong)/10(pong);payload 7/16/64 位长度全支持;单条消息上限 **4KB**(超限 close 1009);二进制帧 close 1003。服务器→客户端帧不 mask。
- 活性:服务器每 15s ping,45s 无 pong 判死(等同断线走 §6.6);每连接应用消息速率帽 20 条/s(超限 err `rate`,再犯 close)。
- 发送背压:每连接一个待发队列;`snap` 可丢(新顶旧),`ev/joined/err` 不丢;队列 >200 → close 1013。
- 每消息 = 1 条 text 帧 = 1 个 JSON 对象,必有 `t` 字段。协议版本 `ver: 1`(join 携带,不符 err `bad_msg`)。
### 6.2 客户端→服务器消息(3 条)
**join**(连接后首条,5s 内不发则 close):
| 字段 | 类型 | 含义 |
|---|---|---|
| `t` | `"join"` | — |
| `ver` | number | 协议版本,恒 1 |
| `room` | string | `"quick"` = 快速匹配;或 4 位房码(大写无歧义字符 `23456789ABCDEFGHJKMNPQRSTUVWXYZ`) |
| `name` | string | 显示名(1-24 字符,取自档案;服务端截断兜底) |
| `teach` | bool? | true = 请求教学房(首局;服务端强制单人房+教学波) |
| `resume` | string? | 重连凭证(`joined.resume` 原值);携带时按 §6.6 回槽 |
**act**(对局意图;服务器按到达序裁决):
| 字段 | 类型 | 含义 |
|---|---|---|
| `t` | `"act"` | — |
| `kind` | `"attack" \| "callout" \| "skill" \| "start"` | start 仅 LOBBY(立即开局) |
| `dir` | `"N"\|"NE"\|"E"\|"SE"\|"S"\|"SW"\|"W"\|"NW"` | attack 必填(客户端四向滑只发 N/E/S/W 四基向,弧解析在服务端;bot 可发 8 向) |
| `seq` | number | 客户端单调递增(去重/乱序检测;乱序不拒,仅日志) |
| `at` | number | 客户端发出时刻 ms(仅诊断用,裁决以服务端到达序为准) |
**leave**:`{ "t": "leave" }` —— 主动退出(菜单"回大厅"),槽位立即按 §6.6 处理(无 30s 宽限)。
### 6.3 服务器→客户端消息(joined / ev / snap / err)
**joined**(join 受理;resume 重连亦发):
| 字段 | 类型 | 含义 |
|---|---|---|
| `t` | `"joined"` | — |
| `room` | string | 实际房码 |
| `slot` | number | 你的座位号 0-3 |
| `resume` | string | 重连凭证(16 hex,每槽位签发一次,连接存活期内不变) |
| `char` | `"yan"\|"zheng"\|"zhou"\|null` | 座位角色(座 3 恒 null) |
| `sector` | string[] | 你的主防区方位集(§3.3) |
| `seed` | number | 房间种子(复现用) |
| `state` | object | `{ phase: "lobby"\|"teach"\|"wave"\|"end", wave: number, dur: number }` |
| `roster` | array | 全花名册:`[{ slot, name, bot, char, sector }]`(bot=true 时 name=角色名) |
**ev**(事件流,即时下发;`ts` = 房间单调时钟 ms(开局为 0)):
| kind | 附加字段 | 含义 |
|---|---|---|
| `spawn` | `id, mkind("wolf"\|"bear"), dir, eta` | 怪物生成(eta=逼近全程秒数) |
| `kill` | `id, slot, dir, mkind` | 出击命中击杀(归属 slot) |
| `miss` | `slot, dir` | 挥空(弧内无可打怪) |
| `skill` | `slot, cleared: number[]` | 旋风斩放出,cleared=被清怪 id 表 |
| `breach` | `id, mkind, dir, dur` | 突入:耐久新值 dur(wolf -1 / bear -2 已结算) |
| `wave` | `wave` | 第 wave 波开始(1-5) |
| `waveclear` | `wave` | 第 wave 波出清 |
| `callout` | `slot` | 求援呼喊(真人单击或 bot 压力呼救;接收端播该座角色 help_call + warning 震) |
| `botline` | `slot, line` | bot 喊话(line ∈ `greet/help_ack/cover/kill/breach/skill/wave`;客户端按 char+line 播 `sfx/npc_<char>_<line>`) |
| `roster` | `roster` | 花名册变更(加入/离开/断线/转 bot;形状同 joined.roster) |
| `end` | `win, dur, grade("S"\|"A"\|"B"\|"C"\|null), kills` | 终局;kills=`{ "0": 5, ... }` 按 slot 的个人击杀 |
**snap**(2Hz 兜底快照):
| 字段 | 类型 | 含义 |
|---|---|---|
| `t` | `"snap"` | — |
| `ts` | number | 房间单调时钟 ms |
| `phase` / `wave` / `dur` | — | 同 joined.state |
| `mobs` | array | 在场怪物全量 `[{ id, mkind, dir, eta }]`(eta 为 ts 时刻值) |
| `slots` | array | `[{ slot, alive, bot, cd, skillCd }]`(cd/skillCd=剩余冷却秒,重连恢复冷却 UI/判定用) |
**err**:`{ "t": "err", "code": "cooldown"|"skill_cd"|"bad_dir"|"not_ingame"|"room_full"|"room_gone"|"bad_msg"|"rate", "msg": "人话描述" }`(cooldown/skill_cd 为正常违规拒绝,客户端不应惊扰玩家——客户端本地已先拦)。
### 6.4 房间管理与匹配
- 快速匹配:找 `phase='lobby'` 且有空人类座的房 → 进;没有 → 建房(房码 4 位随机)。**客户端 v1 只发 `room:"quick"`**;显式房码 join 协议完整、服务端实现并单测,客户端录入 UX 列 roadmap(§15 裁决 5)。
- LOBBY 上限 12s:到点、任一人类 `act start`、或满 4 人 → 开局(播 `room_start`,随后 `ev wave 1` + `wave_band1`)。开局时空位由 bot 补至 **max(3, 人类数)** 猎人:1 人→2 bot、2 人→1 bot、3 人→0 bot(3 座)、4 人→0 bot(4 座)。第 4 座仅 4 人局启用。
- join 只在 lobby 受理;对局中 join → err `room_full`(v1 不做中途加入)。
- 教学房:join `teach:true` 且本地 tutDone=false → 强制单人房(§5 剧本);教学波完直接续 WAVE 1。
### 6.5 服务端权威战斗(hunt-room.js,建议独立模块便于单测)
- 房间结构:`{ seed, phase, slots[≤4], mobs: Map, wave, dur, tickTimer, snapTimer }`;tick 100ms:eta 递减、突入结算、波剧本推进;snap 500ms。
- 裁决规则(§3.2 数值的权威实现):弧映射、eta≤4.0 近战窗、冷却 2.5/8s、呼喊节流 5s——全部只在服务端生效;客户端可做本地预扣(冷却 UI),但以 err/ev 为准。
- bot = 特殊 slot(bot:true,无连接):同一套裁决管线,由 §7 AI 产生 act。
- **时钟**:ts 为房间单调 ms。客户端在 joined/snap 到达时记 `offset = ts - performance.now()`,渲染用 `nowServer = performance.now() + offset`;局域网无需 RTT 补偿(逼近时长 8-12s,误差淹没在听觉分辨率内)。
### 6.6 断线与重连
- 连接断开(或 45s 无 pong)→ 槽位标记断线(roster 广播 alive=false),**30s 宽限**:槽位静默(无 act 产出,猎人"愣在原地"——这是真实的协作代价)。
- 宽限内带 `resume` 重连 → 回槽:重发 joined(同 slot/seed/char)+ 紧随 snap 全量,客户端无缝续渲染(net_back)。
- 宽限超时:剩余人类 ≥3 → 槽位移除(roster);否则槽位转 bot(roster,bot=true,char 沿用座角色,补位 bot 播 `npc_*_cover`,其余客户端播 `net_to_bot`)。转 bot 不可逆(该局内)。
- 最后一个人类断开:房间保留至其 30s 宽限耗尽后销毁(无人类即灭,bot 不续命)。房灭后迟到 resume → err `room_gone`(客户端 net_gone → LOST)。
- 终局 end 后房间保留 30s(各端写排行榜)随后 close 1000。
### 6.7 客户端渲染模型(client agent 契约)
- 本地怪表 = ev 增删 + snap 校正:每 snap 对账——本地多出的 id 删、缺的按 snap 补、eta 偏差 >0.5s 校正。事件流为主、快照兜底,二者矛盾以 snap 为准。
- 每 500ms 重估渲染集:eta>6s 静默;≤6s 按 eta 升序取前 3 只起 loop(§8 R3);loop 音量按档(6-4s:0.30 / 4-2.5s:0.55 / <2.5s:0.80);pan 按方位角映射(§8.3)。
- 断网期间:本地怪表按最后已知 eta 继续衰减渲染(怪物照爬——戏剧张力保留),恢复后以 snap 校正。
## 7. Bot 设计(服务端驱动)
### 7.1 角色与声音资产
| 座位 | 角色 | char | speaker | instruct 常量 | 性格 |
|---|---|---|---|---|---|
| 0 | 阿岩 | yan | Uncle_Fu | `憨厚壮实的中年男声,猎户,话少踏实` | 憨厚 |
| 1 | 小筝 | zheng | Serena | `轻快爽利的年轻女声,猎手,干脆利落` | 轻快 |
| 2 | 老周 | zhou | Eric | `沙哑沉稳的老年男声,老猎手,带点调侃` | 沙哑 |
每角色 8 句固定短句(事件触发,全集见台词表 N 组):`greet / help_call / help_ack / cover / kill / breach / skill / wave`。**固定单音色平铺、不做双性别**(rpg NPC 先例);因经 sfx 层带 pan 播放,资产落 `audio/sfx/npc_<char>_<line>.{ogg,m4a}`(§15 裁决 7)。
### 7.2 战斗 AI(tick 500ms,确定性)
- 出击:冷却就绪且存在目标方向时出手。方向压力 `P(d) = Σ(1/eta)`(d 内活怪,仅计 eta≤4);主防区方向 ×1.5 偏置;取 max P 方向发 `act attack`(dir=该方向精确罗盘向)。**拟人反应带**:只在最优目标 eta ≤ 2.0 + rng()×1.0 时才出手,且每次出手决策带 80% 空转概率(rng 走房间 seed,复现可断言),避免" eta 一进 4 秒就秒"的机械感(数值为 §15 裁决 10 调参后口径)。
- 旋风斩:全场 eta≤2.5 的怪 ≥3 只且技能就绪,且 60% 概率想起 → `act skill`(裁决 10)。
- 呼救:主防区压力 ≥3(≥3 只 eta≤6)且 20s 节流 → `act callout`。
- 难度校准:bot 不失误(挥空不存在——只打有目标的弧),但受 2.5s 冷却、反应带与空转概率上限;3 bot 局实测评级 A/B 为主(§3.4 平衡目标,多种子扫描见 §15 裁决 10),给真人留出"比 bot 强"的空间。
### 7.3 补位(呼喊响应)
收到 `ev callout`(真人或 bot 发出)后:**空闲 bot**(当前 500ms 无待发 act 者,多取座位号小者)播 `help_ack`,且其后 10s 内目标选择偏置改为呼喊者防区方向 ×2.0(覆盖主防区偏置)——即"朝你侧补位"。补位 bot 首次在新防区击杀后再无怪时播 `cover`。呼喊本身不改变 bot 的冷却/反应带(公平)。
### 7.4 喊话调度(与 §8 R2 联动的服务端纪律)
- botline 触发源:greet(开局补位完成,逐只间隔 ≥1.5s)/ cover(转 bot 接管、补位落定)/ kill(杀熊或三连杀,15s 节流)/ breach(本防区漏怪,20s 节流)/ skill(放旋风斩)/ wave(第 2 波起,轮转一只 bot)。
- 服务端对 botline/callout 全局节流:**10s 窗口 ≤2 条**,超出丢弃(喊话是氛围,不是信息主通道);help_call/help_ack 优先级最高(呼救语义不可被氛围句顶掉)。
## 8. 音频信道拥挤控制(本文档重点章节)
### 8.1 硬规则表 R1-R8
| # | 规则 | 数值 |
|---|---|---|
| R1 | **旁白/系统音单句队列**(引擎 voice 层既有):同刻 1 句,链播 onComplete 串行;警报类(breach/breach_heavy/danger_low)可顶掉状态/播报类(上滑链、skill_ready),教学/结算链不可顶 | 同刻 voice=1 |
| R2 | **bot 喊话走 sfx 层**(带 pan,与旁白并发):同刻在播 ≤1 条;待播队列深 ≤1(新到顶掉未播的过期句);旁白在播时延迟投递 ≤2s,超时丢弃;服务端全局 10s ≤2 条(§7.4) | 同刻喊话=1 |
| R3 | **怪物移动 loop ≤3 路**:只渲染 eta 最小的 3 只(500ms 重估),其余静默;loop 经游戏层句柄 + 自建 StereoPannerNode(abyss `_startLoop` 先例,不占引擎改动面) | 同刻 loop≤3 |
| R4 | **定位 one-shot ≤2 并发**(howl/击杀/突入/旋风斩):同类间隔 ≥0.15s;波次生成潮的呼啸队列深 ≤2,超出丢弃(怪近后 loop 已接管信息,呼啸可弃) | 同刻 one-shot≤2 |
| R5 | **非语音信息全部 <1s 短音效**:号角 0.9s、join_chime 0.4s、call_whistle 0.6s、kill 0.5s 量级;语音通道只给人声 | — |
| R6 | **听觉视野裁剪**(§8.2):威胁播报只计内圈(eta≤4);状态链不报具体方位(方位由 loop pan 承载,语音永不逐只报方位——8 向 × 距离档的语音枚举被明确禁止) | — |
| R7 | **电平分层**:voice 层播时 BGM 自动 duck 20%(引擎既有);bot 喊话(sfx 层)不触发 duck → 游戏层统一 vol 0.55 压一档,防压旁白;loop 音量档上限 0.80 | — |
| R8 | **最坏并发核算**:旁白 1 + 喊话 1 + loop 3 + one-shot 2 = **7 声流**;频带分离(loop 低频 100-300Hz / 人声 300Hz-3kHz / howl 中高 1-3kHz / BGM 垫底),引擎限幅器兜底。峰值场景(第 5 波 + 警报 + 呼喊)不得丢警报、不得叠爆 | ≤7 声流 |
### 8.2 听觉视野裁剪规则
8 方位 × 3 距离档:
| 档 | eta | 可听内容 | 可被打 |
|---|---|---|---|
| 外圈 | >6s | 仅生成呼啸一声(one-shot,pan+距离音量 0.4) | 否 |
| 中圈 | 4-6s | 移动 loop(vol 0.30;受 R3 路数上限约束) | 否 |
| 内圈 | ≤4s | loop 升档(0.55/0.80);计入 threat_* 威胁播报与 danger 评估 | **是(近战窗)** |
- threat 口径:`threat_none`(内圈 0 只)/ `threat_few`(1-2 只)/ `threat_many`(≥3 只)——**只报数量档,不报方位**;方位信息永远由 loop/呼啸的 pan 实时承载(这本身就是切片要验证的"空间音频协作"命题)。
- 播报裁剪:状态链/威胁播报里禁止出现逐方位枚举句;台词表不含此类 id(结构性杜绝)。
### 8.3 方位 → pan 映射(全作统一)
方位角 az(N=0°、E=90°、S=180°、W=270°):`pan = sin(az)`(N/S=0、NE/SE=±0.7、E/W=±1、NW/SW=∓0.7)。**前/后歧义**(StereoPanner 不能分前后):后半平面(90°<az<270°)的 loop/呼啸音量 ×0.6("身后发闷",dungeon"背后=中央减半"先例);教学 tut_rule 讲明此约定。呼喊/喊话的 pan 取座位防区中心方位(§3.3)。
### 8.4 验证口径(harness/测试 agent 记录点)
- 逐事件记录:任一时刻 voice 在播句数、sfx 层人声句数、loop 路数、one-shot 并发数——断言不超 R1-R4 上限。
- 警报可达性:第 5 波峰值剧本下,breach/danger_low/wave 播报零丢失(可被顶掉的状态类句不计)。
- 实机听测 checklist(roadmap 真机轮):戴耳机分辨"左前狼 + 正右熊 + 旁白警报"三源。
## 9. 引擎改动(唯一一处,后向兼容)
`web/engine/audio.js`:
1. `_playSfx(id, { volume = 1, pan } = {})`:`typeof pan === 'number' && isFinite(pan)` 且 `pan !== 0` 时,clamp 到 [-1,1],链路改为 `source → gain → StereoPannerNode(pan) → sfxBus`;pan 缺省或 0 → **原路径逐字节不变**(不建 panner 节点)。`ctx.createStereoPanner` 不存在(老 Safari)→ 静默忽略 pan(不炸、不报错音)。
2. `_playVoice` 同款:opts.pan 存在时把 panner 插在 voice gain 与 voiceBus 之间;缺省不变。(**本作旁白不使用 voice pan**——对称能力一次给足,后续游戏可用;见 §15 裁决 7。)
3. 回归断言(harness 增补):全部既有游戏 play() 调用(无 pan)行为不变;pan=0 与缺省等价;pan=-1/1 时 panner 值正确。
本作使用面:bot 喊话、生成呼啸、击杀/突入/技能音全部 one-shot 带 pan;怪物 loop 走游戏层句柄(§8 R3)。
## 10. 音频三层资产
### 10.1 BGM(2 首,Mureka,-20 LUFS,ogg+m4a,90s 无缝循环)
| id | 状态映射 | prompt 草稿(英文) |
|---|---|---|
| `bgm/menu` | TITLE / MATCHING / LOBBY / END(结算 vol 0.5) | "Night camp ambient pad, distant crickets and low wind, warm fireplace crackle subtle, calm anticipation, no vocals, seamless loop" |
| `bgm/hunt` | TEACH / WAVE | "Tense night hunt drums, low tribal percussion 100 bpm, ominous strings pulses, cold wind, no vocals, seamless loop" |
切波不改 BGM(波次张力靠号角+怪密度);失守/胜利走引擎 crossfade 到 menu。语音 duck 20% 引擎自动。
### 10.2 SFX(13 + 24 喊话;SYN=程序合成,SA3=Stable Audio 3;双格式、真峰值 ≤-3dBFS)
| id | 方式 | 说明 |
|---|---|---|
| `atk_whoosh` | SYN | 挥击风声 0.3s(带 pan=出击方位) |
| `atk_swish` | SYN | 挥空空荡风声 0.3s(归因真话:没打到) |
| `kill_wolf` / `kill_bear` | SA3 | 狼哀嚎倒地 0.5s / 熊闷重倒地 0.7s(带 pan) |
| `skill_spin` | SA3 | 旋风斩气旋 0.8s(中央,大动态) |
| `breach_thud` | SA3 | 营地被撞闷响+木栅裂 0.6s(中央) |
| `horn_wave` | SA3 | 波次号角 0.9s(中央) |
| `join_chime` | SYN | 队友加入 0.4s(双音上行) |
| `call_whistle` | SYN | 求援哨音 0.6s(座 3 呼喊用,带 pan) |
| `howl_wolf` / `howl_bear` | SA3 | 生成呼啸 0.8s / 低吼 1.0s(带 pan,后半平面 ×0.6) |
| `loop_wolf` / `loop_bear` | SA3 | 移动循环:狼碎步+低呜 / 熊重步+喘息(4s 无缝循环源,游戏层 loop,pan+距离档音量) |
| `npc_<char>_<line>` ×24 | TTS | bot 喊话(voice 管线生成、落盘 sfx/,峰值归一 0.89 沿用 voice 策略;游戏层 vol 0.55,§8 R7) |
### 10.3 voice(旁白/系统音)
60 句 ×2 性别(male=Uncle_Fu / female=Serena,全表同一 instruct 常量/性别),分组与全文见 [23-hunt-voice-ids.md](23-hunt-voice-ids.md)。数字全分档整句:耐久 4 档 / 波次 3 档 + 5 整句 / 在场猎人 2 句 / 击杀 3 档 / 威胁 3 档 / 评级 4 整句。键盘变体 11 对(V 组,`gesture.inputMode()` 选播)。
规模核算:voice 60×2×2fmt ≈ 12MB + npc 24×2 ≈ 2MB + key 变体 11×2×2 ≈ 2MB + sfx 13×2 ≈ 1MB + bgm 2×2 ≈ 6MB ≈ **23MB < 30MB 预算** ✓。
## 11. 向导层接入(engine/guide.js,不新增 earcon 词表)
| 状态 | guide.state() | 时点 |
|---|---|---|
| TITLE / MATCHING / LOBBY / MENU | `menu` | 进态即播 |
| TEACH / WAVE | `battle` | 开局/波开始 |
| END | `settle` | 结算进入 |
| danger | `danger` | 耐久首次 ≤2(每局 1 次,配 danger 震 + danger_low) |
- turn cue 不使用(无回合制);看门狗:WAVE/LOBBY 无操作 [10s, 25s] → `gd_idle` / null(第 2 级纯 nudge);TEACH 每步 [8s] 纯 nudge(教学句不重播)。玩家任意手势复位;guide=off 全静零定时器(既定契约)。
- 纠错:无效手势 error 震 + `gd_invalid`(onceKey 同局 1 次,之后仅震)。
- 无卡关三级提示(本作无"卡住"形态:压力来自怪物而非谜题)。
## 12. 存档与排行榜(进度 API)
```json
{
"schema": 1, "game": "hunt", "updatedAt": 0,
"summary": "最佳评级A · 击杀14 · 营地余8 · 共3局",
"best": { "grade": "A", "kills": 14, "durLeft": 8, "at": "<ISO>" },
"runs": 3, "totalKills": 31, "tutDone": true
}
```
- **写入时机**:终局结算(每局一次,胜利/失守都写 runs/totalKills;`best` 只被胜利局按 评级 > 营地余耐久 > 击杀 的序更新)。客户端在收到 `ev end` 后自行 PUT(Bearer = 档案 token;离线只写 localStorage `soundgame.progress.hunt`)。00 §4 约定遵守:data 自由格式 + **必带 summary**(大厅卡片直接显示)。
- **summary 生成规则**:`最佳评级{grade} · 击杀{kills} · 营地余{durLeft} · 共{runs}局`;无 best(全败)时 `共{runs}局 · 还没守到天亮过`。
- **排行榜形态**:本切片无跨玩家排行 API——"排行榜"= 个人最佳桶 + 大厅卡片 summary(00 §4.3 既有机制)。跨玩家榜列 roadmap。
- 读取:TITLE 有 best 时上滑播最佳段(评级+击杀档);tutDone 控制是否进教学房(§5)。
## 13. playtest 断言(摘要;全表住 13 号文档 §24)
- **关键路径**:遮罩解锁 → TITLE → 快速匹配(首局教学房 T0-T4 逐步)→ LOBBY → 开局 bot 补位(roster 断言 3 猎人)→ 5 波(spawn/kill/breach/wave 事件序列与 §3.4 表逐波核对)→ 终局结算链(grade 与 dur 一致)→ PUT progress(summary 命中生成规则)→ 再来一局。
- **边界**:断线 30s 转 bot / 宽限内 resume 回槽无缝 / 旋风斩冷却与冷却中双击=重听 / 呼喊→bot 补位(10s 内 bot act 方向落入呼喊者防区)/ 听觉裁剪(eta>6 静默、loop ≤3、threat 只计内圈)/ 攻击冷却服务端拒绝 / snap 校正 / 菜单不暂停照扣耐久。
- **失败即不通过**:评级与 dur 不符(归因假话);服务器接受违规 act;ev 与 snap 矛盾不收敛;bot 喊话压过旁白(R1/R2 破);loop >3 路;教学狼扣耐久。
- 全表与全局通用断言见 [13-playtest-criteria.md](13-playtest-criteria.md) §24。
## 14. 自检清单
| # | 硬约束 | 自检 |
|---|---|---|
| 1 | FTUE 首输入 <60s / 首正反馈 ≤3min | T1 首滑 ≈t+20s;首杀 ≈t+40s ✓(§5) |
| 2 | 学习期零惩罚 | 教学波突入不扣耐久;做错重教 ≤2 次自动放过;挥空仅 error 震 ✓ |
| 3 | 手势宪法 | 上滑=状态、下滑=菜单、双击=重听(冷却中落地)保留;E1-E4 登记且教学讲清 ✓(§2.5) |
| 4 | 向导层 | 签名 4 态不新增词表;看门狗 [10/25]s 二级纯 nudge;纠错 onceKey;guide=off 全静 ✓(§11) |
| 5 | 旁白规范 | 60 句 ≤ 预算;整句无拼接;数字全分档;同 instruct 常量;无语速词;"重新听"规范写法 ✓(台词表头注) |
| 6 | 拥挤控制 | R1-R8 可断言;最坏并发 ≤7 声流;警报零丢失口径 ✓(§8) |
| 7 | 方位降维 | 8 方位 × 3 距离档,pan 七值 + 后半平面 ×0.6;无连续角度、无逐方位语音枚举 ✓(§8.2/§8.3) |
| 8 | 协议自洽 | C→S 3 条、S→C 4 类字段表全;服务端权威+快照兜底;断线/重连/房灭路径闭合 ✓(§6) |
| 9 | 引擎边界 | 引擎改动仅 opts.pan 一处且后向兼容;loop 走游戏层先例;30MB 预算内 ✓(§9/§10.3) |
| 10 | 服务器约束 | 零依赖(手写 WS 帧);数据落盘/REST 零改动;单实例约束不恶化(房间全内存) ✓(§6.1) |
| 11 | P1 视障 | 双击免费重听威胁;上滑一键复述;方位由声像承载且规则教学讲明;无倒计时焦虑(波次自然出清) ✓ |
| 12 | P3 通勤 | 单手免视;菜单不暂停但 3 项极简;局 5 分钟随玩随放 ✓ |
| 13 | 失败归因真话 | 评级=dur 服务端直发;结算击杀档=个人真账;挥空/失守有真话反馈(atk_swish/end_lose) ✓ |
| 14 | seed 可复现 | 房间 seed + mulberry32 派生;刷怪剧本/bot 反应带全走 seed;服务器单测可复现 ✓(§3.5/§7.2) |
## 15. 设计裁决与矛盾修正记录(实施前必读)
对任务书核心案的细化/修正,逐条登记(核心形态零改动):
1. **双击矛盾修正**:任务书"双击=旋风斩,8 秒冷却;**无冷却时**双击=重听威胁播报"自相矛盾(无冷却=就绪=放旋风斩)。裁定为:**旋风斩就绪时双击=放出;冷却中双击=重听威胁播报**——既保留技能,又让宪法"双击=重听"在全时段有定义(豁免 E2 登记)。
2. **补出击冷却 2.5s/人**:任务书未给出击冷却。无冷却则单人滑动吞吐无上限,协作失去必要性(§1.6 风险③)。2.5s 使单人峰值约 1 杀/窗,三人局才有分工意义。数值写在 §3.2,服务端裁决。
3. **防区=软分工模型**:任务书"玩家位于营地中心"与"bot 朝你侧补位"并存但无站位定义。裁定为**软防区**(§3.3):不设站位硬锁(任何方向都能打),防区只做 bot 偏置、喊话 pan 来源、刷怪均衡——保留"补位"语义且不引入连续几何。
4. **四向滑 → 90° 弧映射**:任务书"四向滑映射到最近罗盘方位"在 8 方位刷怪下不可玩(斜向怪永远打不到)。裁定:滑向映射 ±45° 弧,击杀弧内内圈 eta 最小者(§2.1/§3.2);协议 act.dir 仍用 8 罗盘枚举(bot 用满 8 向),客户端只发四基向。
5. **房码 UX 范围**:音频录入 4 位房码需数字语音资产(10 条)会击穿 60 句预算。裁定:协议 join.room 完整支持房码(服务端实现+单测),**客户端 v1 只做快速匹配**;音频录码 UX 列 roadmap(届时加数字整句资产)。
6. **第 4 座无角色声音**:bot 角色预算 3×8=24(任务书定死),而 4 人局第 4 座真人的呼喊无角色可用。裁定:座 3 呼喊=`call_whistle` 哨音(带 pan,<1s,R5 合规),bot 照常响应补位;哨音本身是有效求援信号,明示于 §3.3。
7. **bot 喊话落盘 sfx/(异例)**:任务书两处表述有张力——拥挤控制章"bot 喊话走 sfx 层空间化播放"与引擎章"voice 层同样支持 pan(bot 喊话用)"。裁定:喊话必须与旁白并发(R1/R2 的核心),voice 层单流做不到 → 走 sfx 层,引擎按 id 前缀分发故资产落 `audio/sfx/npc_*`(voice 管线生成不变);voice 层 opts.pan 能力同步提供但**本作不使用**。旁白 narrator 与 bot 同 speaker 的区分度风险(阿岩×male 旁白、小筝×female 旁白)按 abyss 先例标注"中",缓解=空间 pan + instruct 差异 + 语境(bot 只讲 1-2s 短喊话)。
8. **巨兽 -2**:任务书"突入=耐久-1"为默认口径;第 3 波起加入暴熊(-2)作为重威胁,给击杀优先级一个明确信号(重 loop/重倒地音可辨),数值与播报(breach_heavy)配套,不改核心形态。
9. **失守与入榜**:任务书"评级 S~C"隐含必完 5 波。裁定:耐久波内归零=失守终局(end_lose,无评级、不写 best,runs/totalKills 照记);胜利局才按 §3.6 评级写入 best——排行榜不被败局污染,失守有真话结算。
10. **bot 强度调参(§7.2 数值修订,协议字段不变)**:初版严格按 §7.2 公式(反应带 eta≤2.2+rng×1.2、无空转、旋风斩 eta≤3≥3 必放)实测**零泄漏**——多种子仿真 3 bot 水准局余耐久恒 10(S),甚至真人全程挂机 2 bot 独守也恒 10,§3.4"3 bot 水准余 6-9"与"协作必要性"(§1.6 风险③)落空。根因是吞吐超配:3 猎人 1.2 杀/s vs 峰值到达 0.67 杀/s,冷却 2.5s 不构成约束。裁定修订为:反应带 **eta ≤ 2.0+rng×1.0**;出手决策加 **80% 空转概率**(500ms tick 内愣神,等效出手周期 ~4.5s);旋风斩 **eta≤2.5 ≥3 只且 60% 概率想起**。调后 24 种子扫描(`web/server/test/hunt-balance-scan.mjs`):挂机局 17/24 失守或余 ≤3(6 场失守,mean 3.3,C/失守口径 ✓);3 bot 水准局 17/24 余 6-9(mean 7.5,A/B 为主 ✓);真人最优仍 23/24 满耐久 S("比 bot 强"空间保留 ✓)。bot"不失误"语义保留(只打有目标的弧),弱化的是**反应速度与出手频率**(拟人迟钝),不是命中率。
## 16. v2 修订记录(2026-09-19,10 人并发模拟迭代;依据与数据见 research/hunt-sim/)
> 迭代章程 research/hunt-sim/00-plan.md;v1 诊断 analysis/01-04;裁定 synthesis.md(S1-S9);
> 复测对比 metrics-v2.md(去留 6/10→10/10,弧错 22%→11%,N/S 可命中 63/69%→75/81%)。
1. **E3 修订(S1)**:WAVE 战斗态下滑**恒出击**(弧内无怪=空挥真话 atk_swish);
模态菜单入口收窄到**波间休整窗**(waveclear 后 8s)与 LOBBY/END。原「无目标下滑=
菜单」的语境折中废止——v1 实测多人战斗中误触菜单(最多单局 14 次),弹菜单+语音
播报双重打断是退游死因之一。上滑无目标=状态链维持不变。
2. **冷却反馈(S2)**:冷却内被拦只在**每个冷却窗首次**给 error 震(连拦静默);
本窗被拦过则就绪时刻补一记 `beat` 轻震(无语音占用)。v1 实测全场 666 次乱滑连震。
3. **kill_band0 下架(S3)**:个人击杀 0 时状态链/结算链**跳过**「你还没开张」
(沉默优于羞辱;正向改写句列 TTS 批次)。0 杀玩家的去留反转(v2 P08:0杀→12杀)
是本项最直接证据。
4. **看门狗重定义(S4)**:WAVE 内本波有出手/击杀 → 一级语音永不触发;一级降纯震;
语音闲置每局 ≤1 次;重教句只留在教学局。判定线=「耳朵在场」(等待是合法玩法)。
5. **协作人声包(S5)**:①真人「扮演座位角色」(§3.3 既有设定)正式接线——杀熊/
三连杀/旋风斩清 ≥3 时由该座角色播 kill/skill 句(服务端 creditKill/adjudicateSkill
放开 slot.bot 限制);开局 greet 覆盖全部有角色座位;②满编真人房呼喊无 bot 可应 →
2.5s 后由邻座真人角色代答 help_ack(服务端 pendingLines 兜底,v3 若加真人应答手势
则替换);③help_call/help_ack 客户端**永不超时丢弃**(氛围句才受 R2 ≤2s 上限);
④喊话全局预算 10s 窗 2→**3** 条。
6. **后半平面第二线索(S6)**:身后 loop 在 ×0.6 响度之上叠加 **750Hz 低通**(音色
发闷 ≠ 响度发闷)。N/S 轴从单线索变双线索,模拟实测可命中 63/69%→75/81%,新手
自述「破案」。§8.3 的 pan 红线不变(方位仍由声像承载)。
7. **难度与评级重构(S7)**:①波次量按座位缩放 `waveCounts`:4 人房 ×4/3(3 人房
原值,§3.4 表为 3 人基准);②第 3-5 波插入**齐压拍** `alignPincers`:1-2 组跨座位
同种类怪重定时到同秒进内圈(数量与 eta 不变、种子确定性保持;齐压在 startWave
层应用,buildWaveScript 纯函数不动);③**bot 让位窗**:怪进内圈头 2s,bot 不与
该防区人类座位抢(`mobYielded`,只约束 bot——对真人不可见不拦截,避免「挥空
真话说谎」的暗墙;真人房的参与真空 v3 用分层匹配解决);④**评级双轴**
`gradeForEx`:S = 耐久 ≥9 **且** ≥2 名猎人各 ≥6 个人击杀(单人带飞局上限 A)。
8. **教学跳过保护(S8)**:作答步(T1/T2/T3)内下滑=答错处理,不再跳过教学;
跳过仅在 T0/T4/完成等待态(v1 实测 P04 教学局手滑跳过、记恨三局)。
9. **状态链按变化播(S9)**:耐久档/波次档/猎人数/击杀档与上次播报一致的分段跳过;
全部无变化=仅 confirm 轻震(「营地固若金汤」复读投诉归零)。
**v3 头号遗留(未修,如实登记)**:营地零损耗未破(v2 复测 18/20 局余 10)——
4 人房 ×4/3 + 齐压拍不足以穿透专家房吞吐(单人最多 34/51 杀)。下一档参数与
分层匹配/保底猎物方案见 research/hunt-sim/report.md §5。