研究闭环: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
542 lines
48 KiB
Markdown
542 lines
48 KiB
Markdown
# 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。
|