Files
SoundGame/web/harness/live/README.md
T

105 lines
5.9 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.
# live/ — 全拟真玩家 agent 基础设施
用**真实无头 Chromium** 黑盒驱动任意一款 SoundGame(纯音频 HTML5 游戏),
录制 Web Audio **真实混音输出**,用本地 **SenseVoice ASR**「听懂」旁白是否
完整播完/被截断,外部 agent 通过 HTTP 逐步玩游戏(每步根据听到的内容决策)。
零依赖(Node ≥22 全局 WebSocket/fetch + 一个 Python 文件用 funasr),
不改 `web/engine/` 与游戏代码,全部探针经 CDP `Page.addScriptToEvaluateOnNewDocument`
预注入(patch 原型)。
## 组成
| 文件 | 职责 |
|---|---|
| `cdp.mjs` | 极简 CDP 客户端:spawn chromium(`--headless=new --remote-debugging-port=0`),WebSocket 多路复用,`Target.createTarget`+`attachToTarget(flatten)` 后用 sessionId 发 `Page.*`/`Runtime.evaluate`/`Input.dispatchTouchEvent` |
| `inject.js` | 预注入探针(纯文本,控制端读文件注入):fetch/decodeAudioData/getChannelData/ABSN start/stop/connect/vibrate/console 七类 patch;混音经 tap GainNode → `MediaRecorder`(opus) → base64 chunks |
| `control.mjs` | HTTP 控制服务器(端口 8123):会话/手势/事件/转写/落盘;每 500ms drain 页面 `__live`(chunks 追加 `mix.webm`,事件换算到录音时基) |
| `listen_server.py` | SenseVoice 常驻进程(stdin/stdout JSON Lines):ffmpeg 解码 webm → 16k wav,按 voice 事件窗口切片转写 + astats 能量判定 + difflib 相似度 |
voice 检测原理:引擎 voice 层走 SoundTouch 管线(`ScriptProcessorNode` 从
AudioBuffer 抽读),**不经** `AudioBufferSourceNode.start`。探针 patch
`AudioBuffer.prototype.getChannelData`——引擎里只有 SoundTouch 的
`WebAudioBufferSource.extract` 对 voice buffer 调它——以此检出 voice 段的
开始/时长,用 `ScriptProcessorNode.disconnect`(引擎 `_killVoice` 必经)
检出结束。bgm/sfx 走 `AudioBufferSourceNode.start/stop`,直接挂钩。
时间轴对齐:事件时间戳是 `performance.now()`(ms);`__live.recStartT` 是
recorder 启动当刻的同源时间戳;`ta = (t - recStartT)/1000` 即录音时间轴秒
(误差 ≈ AudioContext resume 延迟,几十 ms,ASR 窗口 ±0.3s 余量吸收)。
## 判定逻辑(截断双保险)
- **事件层**:`vend.ta < t0 + dur/tempo - 0.15s` → 引擎级截断(tempo=设置语速,
长按快进窗口按 1.7 倍速修正预期结束时刻)。
- **ASR 层**:窗口 `[t0-0.15, t1+0.35]` 切片转写,与 `voice_text.json` 期望文本
做 `difflib.SequenceMatcher`(去 `<|...|>` 标签/标点/小写归一化),`<0.6` → 内容不完整/错配。
- 窗口 RMS < -45dB → `SILENCE`;无 voice 事件的区间(间隙探针)有能量 →
`SFX_BGM_ONLY`,无能量 → `SILENCE`。
- verdict 优先级:`TRUNCATED`(事件层)> `SILENCE` > `MISMATCH` > `OK`。
## 用法
前置:游戏服务器在 8001(`PORT=8001 node web/server/server.js`)。
```bash
node web/harness/live/control.mjs # 8123;首次启动加载 SenseVoice 约 10-20s
```
### curl 示例会话
```bash
# 1. 开会话(settings 接受友好名 normal/low/off 或引擎枚举值 1.0/0.3/0)
curl -X POST localhost:8123/session -H 'content-type: application/json' -d '{
"game": "rpg",
"settings": {"voice":"male","speed":"normal","bgm":"normal","guide":"full"}
}'
# 2. 第一个动作必须是 tap(解锁启动遮罩,否则全部无声)
curl -X POST localhost:8123/gesture -H 'content-type: application/json' -d '{"type":"tap"}'
# 3. 循环:听 → 决策 → 动手势
curl "localhost:8123/listen" # 自上次以来新判定的段落
curl "localhost:8123/events?since=0" # 增量原始事件(下次用返回的 next 当 since)
curl -X POST localhost:8123/gesture -H 'content-type: application/json' \
-d '{"type":"swiperight"}' # tap/doubletap/swipeup/swipedown/swipeleft/swiperight/longpress
# x,y 缺省屏幕中心;swipe 默认 200px/300ms
# 4. 复盘与收尾
curl "localhost:8123/transcript" # 合并时间轴全量(JSON)
curl -X POST localhost:8123/stop # 落盘 4 产物,关页签;服务器保持运行
```
`/listen` 返回行:
```json
{"t0": 53.68, "t1": 55.56, "voiceId": "desc_path",
"expected": "林间小路。风穿过树叶,脚下的落叶沙沙作响。",
"heard": "林间小路。", "similarity": 0.3636, "verdict": "TRUNCATED"}
```
产物落到 `web/harness/live/out/<game>-<ts>/`:`mix.webm`(真实混音)、
`events.jsonl`(全事件,含录音时基 `ta`)、`transcript.md`(人读时间轴)、
`verdicts.json`(全部 voice 段 + 判定)。
## 已知限制
- **短句(≤6 字)ASR 同音误识**会把相似度拉到 0.6 以下 → 偶发 `MISMATCH`
假阳性(例:「前进,去岔路口」被听成「前进却差了」)。源资产直转是对的,
问题出在混音背景(bgm/ambience)下的短切片;verdict 是「ASR 层」判定,
事件层 `TRUNCATED`/`SILENCE` 不受影响。需要高置信时以 `similarity` 数值
而非仅 verdict 做决策。
- 单会话:同时只开一个游戏页签,`POST /session` 会顶掉前一个(不落盘)。
- 录音 t=0 与事件时基的对齐误差 ≈ AudioContext resume 延迟(实测几十 ms),
不影响 ±0.3s 余量的窗口切分。
- MediaRecorder 不可用时探针降级为只记事件不录音(`__live.recState='unavailable'`,
事件流里有 `recfail`);此时 `/listen` 的 ASR 层不可用。
- 间隙探针只覆盖相邻 voice 段之间与首段之前的 ≥1.5s 区间;末尾开放区间不探测。
- chromium profile 写在 `out/.profiles/`,浏览器随控制服务器退出时清理。
## 环境
- `chromium`(snap)在 PATH;`node` ≥22;`ffmpeg` 在 PATH。
- `python3` 需 funasr + torch(SenseVoiceSmall 模型走 modelscope 缓存)。
- 环境变量:`LIVE_PORT`(默认 8123)、`GAME_BASE`(默认 `http://localhost:8001`)。