feat: wire bark battle platform loop
Some checks are pending
CI / verify (pull_request) Waiting to run

This commit is contained in:
2026-05-14 18:20:46 +08:00
parent 8c6ec9e6e4
commit 1d7ef7e4b6
73 changed files with 7933 additions and 107 deletions

View File

@@ -4,7 +4,7 @@
- 需求来源:用户提供的视频 `C:\Users\DSK\Videos\一款双方比狗叫的游戏 - 1.一款双方比狗叫的游戏(Av116504192360177,P1).mp4`,并已在 `.hermes/plans/2026-05-11_144229-bark-battle-2d-game-bdd-ddd-tdd-plan.md` 中完成抽帧分析和玩法方案整理。
- 玩法定位:浏览器 2D 声控狗叫对战小游戏,暂定中文名 `汪汪声浪大作战`,英文代号与 play type ID 建议为 `bark-battle`
- 核心玩法:双方狗狗在 30 秒限时内通过麦克风输入“狗叫声”进行声浪拔河;系统依据声音强度、有效声次数和声节奏计算推动力,实时推动顶部红蓝能量条;倒计时结束后按能量条位置判定胜负或平局。
- 核心玩法:双方狗狗在 30 秒限时内通过麦克风输入“狗叫声”进行声浪拔河;系统依据声音强度、有效声浪触发次数和声节奏计算推动力,实时推动顶部红蓝能量条;倒计时结束后按能量条位置判定胜负或平局。
- 文档目的:为产品、测试、前端、后端在编码前统一可验证验收口径;本文只定义 PRD/BDD 级行为与测试映射,不实现工程代码。
## 角色与目标
@@ -20,14 +20,14 @@
### 用户目标
- 玩家可以在开局前完成麦克风授权和环境噪音校准。
- 玩家发出有效狗叫时,能看到声计数、狗狗动画、拟声词/冲击波以及能量条变化。
- 低于阈值的背景噪音不会被误计为有效声。
- 玩家产生有效声浪触发时,能看到声计数、狗狗动画、拟声词/冲击波以及能量条变化。
- 低于阈值的背景噪音不会被误计为有效声浪触发
- 单局在 30 秒后给出明确胜负、平局和关键数据。
- 移动端和不支持麦克风的环境不会进入不可操作状态。
### 非目标
- MVP 不要求识别“是否真实狗叫”,不引入机器学习声纹/物种分类;有效输入以音量阈值、峰值间隔、持续时间和校准结果为准。
- MVP 不要求识别“是否真实狗叫”,不引入机器学习声纹/物种分类;有效输入以音量阈值、峰值冷却间隔和校准结果为准。
- MVP 不要求实时联机对战;可先按“玩家 vs AI 对手”完成单机浏览器 runtime。
- MVP 不要求成绩持久化、作品发布、作品架、广场和排行榜;若后续接入 Genarrative 作品闭环,需要另补玩法类型集成 PRD/技术文档。
- MVP 不要求在 UI 中长期展示大段规则说明;游戏界面应保持倒计时、能量条、狗狗、麦克风状态和结算信息为主。
@@ -38,10 +38,10 @@
- 单局时长:默认 30 秒,从正式进入 `playing` 阶段开始计时。
- 能量条:使用 `-100``100` 的连续值表示,负数偏对手侧,正数偏玩家侧,`0` 为中线。
- 平局阈值:倒计时结束时,若能量条绝对值小于或等于 `drawThreshold`,判定平局;具体数值由实现配置,但测试需可注入固定阈值。
- 有效声:一次有效叫声至少满足:音量超过校准后的有效阈值与上一次有效峰值间隔不小于 `minBarkGapMs`、持续时长在 `minBarkDurationMs``maxBarkDurationMs` 之间
- 背景噪音:校准阶段采集到的环境声用于计算动态阈值;低于阈值的输入不得增加声次数,也不得让能量条出现可见推进。
- 推动力:玩家推动力由音量分数、有效声频率和连击加成组成;能量条按玩家推动力与对手推动力差值移动,并被限制在 `-100``100`
- UI 反馈:有效声应触发可观察反馈,包括玩家侧狗狗张嘴/吠叫动画、拟声词或冲击波;反馈不应遮挡倒计时和顶部能量条。
- 有效声浪触发:一次有效声浪触发满足:当前麦克风采样点的归一化响度达到或超过校准后的有效阈值,且与上一次有效声浪触发间隔不小于 `minBarkGapMs`;不再要求持续高响度时长达标,也不等待响度回落
- 背景噪音:校准阶段采集到的环境声用于计算动态阈值;低于阈值的输入不得增加声浪触发次数,也不得让能量条出现可见推进。
- 推动力:玩家推动力由音量分数、有效声浪触发频率和连击加成组成;能量条按玩家推动力与对手推动力差值移动,并被限制在 `-100``100`
- UI 反馈:有效声浪触发应触发可观察反馈,包括玩家侧狗狗张嘴/吠叫动画、拟声词或冲击波;反馈不应遮挡倒计时和顶部能量条。
## 中文 Gherkin 场景
@@ -66,7 +66,7 @@
场景: 校准完成后进入开局倒计时
假如
而且
那么
而且 30
而且线
@@ -84,19 +84,19 @@
功能: 环境噪音校准
场景: 安静环境生成低但非零的有效阈值
假如 RMS 线
那么
那么
而且
场景: 嘈杂环境生成更高的有效阈值
假如 RMS 线
那么
而且
那么
而且
场景: 校准期间无法获得有效音频样本
假如
@@ -106,36 +106,36 @@
而且
```
### 功能: 有效声计数
### 功能: 有效声浪触发计数
```gherkin
功能: 有效声计数
功能: 有效声浪触发计数
背景:
假如 30 playing
而且
场景: 单次超过阈值且间隔足够的声计数加一
假如 0
而且 minBarkGapMs
那么 1
场景: 单次超过阈值且间隔足够的声浪触发计数加一
假如 0
而且 minBarkGapMs
那么 1
而且
而且
场景: 持续噪音不会被无限计数
假如 1
那么 tick
而且
场景: 持续高响度输入只按冷却节奏计数
假如 1
那么 tick
而且
场景: 间隔过短的连续峰值不重复计数
假如
minBarkGapMs
那么
假如
minBarkGapMs
那么
而且
```
@@ -145,23 +145,23 @@
功能: 声浪推动能量条
背景:
假如 30 playing
而且线
场景: 玩家推动力高于对手时能量条向玩家侧移动
假如
假如
而且
simulation tick
那么
而且
场景: 连续大声叫声触发更强反馈
假如
场景: 连续强声浪触发触发更强反馈
假如
那么
那么
而且
但是
@@ -185,31 +185,30 @@
功能: 背景噪音过滤
背景:
假如 30 playing
而且
场景: 低于阈值的背景噪音不计数
那么
那么
而且
而且
场景: 过短脉冲不计为有效叫声
假如
但是 minBarkDurationMs
那么
场景: 冷却内重复达阈值不计数
假如
minBarkGapMs
那么
而且
场景: 过长持续声被削弱为单段输入
假如
但是 maxBarkDurationMs
那么
而且
场景: 持续高响度输入只按冷却节奏产生触发
假如
那么 tick
那么
```
### 功能: 倒计时与胜负结算
@@ -227,20 +226,20 @@
30 0
那么
而且 finished
而且
而且
场景: 玩家侧占优时判定玩家胜利
假如 drawThreshold
那么
而且
而且
而且
场景: 对手侧占优时判定玩家失败
假如 -drawThreshold
那么
而且
而且
而且
场景: 能量条接近平衡时判定平局
@@ -265,7 +264,7 @@
那么 30
而且线
而且
而且
而且
场景: 结算后返回玩法入口
@@ -353,7 +352,7 @@
假如 playing
bark-battle
那么
而且沿
而且沿
```
## 测试映射
@@ -367,15 +366,15 @@
| 嘈杂环境生成更高的有效阈值 | unit | `src/games/bark-battle/domain/BarkNoiseCalibration.test.ts` | planned |
| 校准期间无法获得有效音频样本 | application/component | `src/games/bark-battle/application/BarkBattleController.test.ts`, `src/games/bark-battle/ui/BarkBattlePermissionPanel.test.tsx` | planned |
| 单次超过阈值且间隔足够的叫声计数加一 | unit | `src/games/bark-battle/domain/BarkDetector.test.ts` | planned |
| 持续噪音不会被无限计数 | unit | `src/games/bark-battle/domain/BarkDetector.test.ts` | planned |
| 持续高响度输入只按冷却节奏计数 | unit | `src/games/bark-battle/domain/BarkDetector.test.ts` | planned |
| 间隔过短的连续峰值不重复计数 | unit | `src/games/bark-battle/domain/BarkDetector.test.ts` | planned |
| 玩家推动力高于对手时能量条向玩家侧移动 | unit | `src/games/bark-battle/domain/EnergyTugOfWar.test.ts` | planned |
| 连续大声叫声触发更强反馈 | unit/integration/component | `src/games/bark-battle/domain/BarkBattleScoring.test.ts`, `src/games/bark-battle/ui/BarkBattleHud.test.tsx` | planned |
| 连续强声浪触发触发更强反馈 | unit/integration/component | `src/games/bark-battle/domain/BarkBattleScoring.test.ts`, `src/games/bark-battle/ui/BarkBattleHud.test.tsx` | planned |
| 能量条到达边界后不会越界 | unit | `src/games/bark-battle/domain/EnergyTugOfWar.test.ts` | planned |
| 对手推动力高于玩家时能量条向对手侧移动 | unit | `src/games/bark-battle/domain/EnergyTugOfWar.test.ts` | planned |
| 低于阈值的背景噪音不计数 | unit | `src/games/bark-battle/domain/BarkDetector.test.ts` | planned |
| 过短脉冲不计为有效叫声 | unit | `src/games/bark-battle/domain/BarkDetector.test.ts` | planned |
| 过长持续声被削弱为单段输入 | unit | `src/games/bark-battle/domain/BarkDetector.test.ts` | planned |
| 冷却内重复达阈值不计数 | unit | `src/games/bark-battle/domain/BarkDetector.test.ts` | planned |
| 持续高响度输入只按冷却节奏产生触发 | unit | `src/games/bark-battle/domain/BarkDetector.test.ts` | planned |
| 倒计时每秒递减并在归零时停止对战输入 | unit/application | `src/games/bark-battle/domain/BarkBattleSession.test.ts`, `src/games/bark-battle/application/BarkBattleController.test.ts` | planned |
| 玩家侧占优时判定玩家胜利 | unit/component | `src/games/bark-battle/domain/BarkBattleSession.test.ts`, `src/games/bark-battle/ui/BarkBattleResultPanel.test.tsx` | planned |
| 对手侧占优时判定玩家失败 | unit/component | `src/games/bark-battle/domain/BarkBattleSession.test.ts`, `src/games/bark-battle/ui/BarkBattleResultPanel.test.tsx` | planned |
@@ -394,8 +393,8 @@
## 验收清单
- [ ] 权限允许、拒绝、非安全上下文、API 不支持、麦克风未找到/不可读、AudioContext 被拦截、校准超时或样本不可读均有明确状态,且不会误进入 playing。
- [ ] 校准阶段会影响有效声阈值,低噪音不会增加叫声计数。
- [ ] 有效声计数具备阈值、峰值间隔、持续时长约束。
- [ ] 校准阶段会影响有效声阈值,低噪音不会增加叫声计数。
- [ ] 有效声浪触发计数具备阈值与声浪冷却约束。
- [ ] 能量条根据双方推动力差值双向移动,并限制在 `-100``100`
- [ ] 30 秒归零后停止本局输入影响,并按玩家胜利、对手胜利、平局三类结果结算。
- [ ] 移动端核心元素可见,非关键设置收起,不在主画面堆叠长规则说明。
@@ -405,8 +404,8 @@
## 开放问题
1. MVP 是否确认只做“玩家 vs AI”还是第一版需要双人同屏或联机对战
2. `drawThreshold``minBarkGapMs``minBarkDurationMs``maxBarkDurationMs` 的首版默认值由产品/调参阶段确认,还是先采用开发可配置默认值?
2. `drawThreshold``minBarkGapMs`有效声浪阈值 的首版默认值由产品/调参阶段确认,还是先采用开发可配置默认值?
3. 是否允许无麦克风设备提供键盘/点击备用输入?若允许,需要另补非声控模式场景;若不允许,当前降级只提供返回入口。
4. 是否需要在结算中记录或上报成绩、最高音量、声次数和声浪评分?若需要,需补埋点/后端持久化场景。
4. 是否需要在结算中记录或上报成绩、最高音量、声浪触发次数和声浪评分?若需要,需补埋点/后端持久化场景。
5. bark-battle 是否作为 Genarrative 正式 play type 接入创作入口、作品发布和广场,还是先作为独立 runtime 原型验证?
6. 狗狗、背景、拟声词和冲击波素材来源是临时占位、AI 生成,还是复用项目现有素材管线?

View File

@@ -4,7 +4,7 @@
### 1.1 背景
`bark-battle` / “汪汪声浪大作战”是一个浏览器 2D 声控狗叫对战玩法。玩家通过麦克风发出狗叫声,浏览器 runtime 根据音量峰值、有效声次数与节奏推动顶部红蓝能量条;每局默认 30 秒;结束后按能量条偏向判定胜负或平局。
`bark-battle` / “汪汪声浪大作战”是一个浏览器 2D 声控狗叫对战玩法。玩家通过麦克风发出狗叫声,浏览器 runtime 根据音量峰值、有效声浪触发次数与节奏推动顶部红蓝能量条;每局默认 30 秒;结束后按能量条偏向判定胜负或平局。
现有前端方案 `docs/technical/BARK_BATTLE_2D_RUNTIME_TECHNICAL_PLAN_2026-05-11.md` 已覆盖 Phaser / TypeScript / Vite / Web Audio / DOM HUD 的 runtime 落地方式,并明确不覆盖后端表结构、成绩持久化、作品发布、广场接入与实时多人协议。因此需要单独补充后端 DDD 技术方案,避免前端 runtime 在接入平台作品、正式游玩埋点、成绩、排行榜和发布闭环时承接不属于表现层的业务真相。
@@ -44,26 +44,80 @@ MVP 明确不做:
## 2. 玩法接入级别建议
### 2.1 推荐首版闭环
### 2.1 第二阶段范围:平台作品闭环
建议先支持“本地 runtime + 可发布配置化作品 + 单局结果记录 / 可选排行榜的闭环
第二阶段已明确为“Bark Battle 平台作品闭环”,不是单纯玩法表现深化。目标是让 bark-battle 成为 Genarrative 的正式 play type并完成从轻创作配置、发布、正式 runtime、run start / finish、单局结果持久化、个人历史成绩、作品统计到最小排行榜的闭环
1. 创作者创建 bark-battle 草稿,配置标题、描述、狗狗主题背景、难度、单局时长、音量阈值、AI 对手参数和排行榜开关
Phase 2 的作品配置边界是“轻创作配置作品”:创作者可以配置标题、描述、主题/背景预设、狗狗皮肤预设、难度预设和排行榜开关;不得直接配置单局时长、有效声浪阈值、`minBarkGapMs`、AI 对手细粒度参数、分数公式或反作弊阈值。难度预设只影响 AI 对手行为强度,不影响有效阈值、声浪冷却、单局时长或分数公式;排行榜必须按 `workId + difficultyPreset + rulesetVersion` 分榜,避免不同难度和不同规则版本混排
建议先支持“本地 runtime + 可发布配置化作品 + 单局结果记录 + 个人历史成绩 / 作品统计 / 最小排行榜”的闭环:
1. 创作者从玩法选择进入 bark-battle 后创建草稿,通过单页轻配置表单 + 预览卡片配置标题、描述、主题/背景预设、狗狗皮肤预设、难度预设和排行榜开关。
2. 发布为稳定作品 ID`playTypeId = "bark-battle"`
3. 玩家从作品页或广场进入 runtime前端获取发布态 runtime config。
3. 玩家从作品详情页 CTA、广场/作品卡片、我的作品/个人作品架进入正式 runtime前端使用稳定作品 ID 获取发布态 runtime config。
4. 玩家授权麦克风后在本地完成 30 秒声控对战。
5. 前端提交单局 finish 请求,只上传派生指标,例如峰值、有效声次数、节奏命中、最终能量、客户端结果摘要等。
6. 后端校验 work、config version、run token、时长、分数范围和权限后,生成服务端认可的 run result / score summary。
7. 若作品开启排行榜,则写入可投影的 leaderboard 记录
8. 正式作品级游玩埋点统一写 `work_play_start`,其中 `scope_kind=work``scope_id=稳定作品 ID`metadata 包含 `playType``workId``sourceRoute``userId`
5. 前端提交单局 finish 请求,只上传派生指标,例如峰值、有效声浪触发次数、节奏命中、最终能量、客户端结果摘要等。
6. 后端校验 work、config version、ruleset version、difficulty preset、run token、时长、派生指标范围和权限后,生成服务端裁决的 run result / score summary。
7. 写入个人历史成绩与最小作品统计投影
8. 若作品开启排行榜且后端裁决 `serverResult = player_win`,则写入可投影的 leaderboard 记录;排行榜首版只做最小排序与展示,不引入赛季、段位或复杂反作弊,并按 `workId + difficultyPreset + rulesetVersion` 分榜
9. 正式作品级游玩埋点统一写 `work_play_start`,其中 `scope_kind=work``scope_id=稳定作品 ID`metadata 包含 `playType``workId``sourceRoute``userId`
### 2.2.1 难度预设与排行榜分榜
Phase 2 只允许三个难度预设:`easy``normal``hard`。难度预设只能影响 AI 对手推动力曲线和 AI 声浪节奏;不得影响单局时长、有效声浪阈值、`minBarkGapMs`、分数公式或反作弊阈值。排行榜记录和查询必须带上 `difficultyPreset``rulesetVersion`,以 `workId + difficultyPreset + rulesetVersion` 作为分榜维度。
### 2.2.2 单局结果后端裁决
Phase 2 不信任前端提交的最终胜负和正式分数。前端 `finish` 只提交不可还原原始音频的派生指标:`runId``workId``configVersion``rulesetVersion``difficultyPreset``clientStartedAt``clientFinishedAt``durationMs``triggerCount``maxVolume``averageVolume``finalEnergy``comboMax``clientResult`,以及可选的 `sampleDigest`。其中 `clientResult` 只用于 debug/对账,不进入正式结果或排行榜。
后端必须校验 run 由 start 创建且未 finish、run token 匹配、work/config/ruleset/difficulty 与 start 时一致、duration 处于合理窗口、triggerCount 不超过 `durationMs / minBarkGapMs + tolerance`、音量/能量/连击字段在合法范围内。后端生成 `serverResult``scoreSummary``leaderboardScore``antiCheatFlags`,排行榜只使用后端裁决后的胜利局成绩。
### 2.2.3 排行榜排序口径
Phase 2 排行榜只收录 `serverResult = player_win` 且未被反作弊规则拒绝的单局结果;平局和失败仍进入个人历史成绩与作品统计,但不进入排行榜。`leaderboardScore` 由后端规则集生成,排序优先级为:`finalEnergy` 降序、`triggerCount` 降序、`maxVolume` 降序、`durationMs` 越接近标准局时长越优、`finishedAt` 越早越优。
### 2.2.4 作品统计投影口径
Phase 2 的作品统计是最小后端投影,不从排行榜反推。`playStartCount` 在 start run 成功时计入一次,并对齐 `work_play_start` 埋点;`finishCount` 在 finish 被后端接受时计入一次,包含胜利、平局和失败。`accepted_with_flags` 可以计入 `finishCount`,但必须同时计入 `flaggedCount`;未 start 成功、run token 不合法、重复 finish、被后端 rejected 的结果不计入 `finishCount`
作品统计字段首版包含:`playStartCount``finishCount``winCount``drawCount``lossCount``flaggedCount``leaderboardEntryCount``bestLeaderboardScore``bestFinalEnergy``averageFinalEnergy``updatedAt`。Phase 2 不做 DAU/留存、按小时曲线、原始音频分析或每玩家每天聚合统计。
### 2.2.5 个人历史成绩口径
Phase 2 的个人历史成绩由“最近记录列表 + 个人最佳摘要”组成,并且只允许本人查询。后端可以保存每次被接受的 finish 记录,但首版查询接口只暴露默认最近 20 条记录,可按 `workId``difficultyPreset` 过滤;最近记录包含胜利、平局、失败和是否 flagged但不展示详细反作弊原因。
个人最佳摘要按 `userId + workId + difficultyPreset + rulesetVersion` 聚合,字段包含 `bestLeaderboardScore``bestFinalEnergy``bestTriggerCount``bestMaxVolume``winCount``finishCount``lastPlayedAt`。失败、平局和 flagged 历史不对其他玩家公开排行榜只展示公开入榜的胜利成绩。Phase 2 不做无限滚动完整历史、每日/每周曲线、好友对比或普通玩家可见的详细反作弊说明。
### 2.2.6 正式作品入口闭环
Phase 2 必须接入 Bark Battle 正式作品入口闭环,但不新增独立专区、活动页、挑战分享页、好友邀请或多人房间入口。入口范围包括:创作入口/玩法选择中出现 `bark-battle`,进入单页轻配置表单 + 预览卡片;作品详情页 CTA 点击“开始游玩”进入正式 runtime广场/作品卡片可以展示、打开详情并开始游玩;我的作品/个人作品架能看到作者发布的 Bark Battle 作品runtime 路由使用稳定作品 ID 并从后端发布态 config 拉取配置。
正式 run start 成功后必须写 `work_play_start`,其中 `scope_kind=work``scope_id=稳定作品 ID`metadata 至少包含 `playType=bark-battle``workId``sourceRoute``userId`。内部试玩入口可以作为开发调试保留,但不得作为 Phase 2 正式入口。
### 2.2.7 轻配置编辑流程
Phase 2 的创作编辑形态是“单页轻配置表单 + 预览卡片”,不是多步骤向导、拖拽编辑器或完整规则编辑器。表单字段包含:标题(必填)、简介(选填)、主题/背景预设(必填枚举)、狗狗皮肤预设(必填枚举)、难度预设(必填,默认 `normal`)、排行榜开关(默认开启)。
交互流程创作者从玩法选择进入后生成草稿在同一页编辑轻配置并查看预览卡片支持保存草稿和发布发布成功后跳转作品详情可从我的作品再次编辑草稿或基于已发布作品创建新版本。Phase 2 不做 AI 生成配置、多步骤 wizard、规则参数编辑、复杂封面编辑、runtime 内嵌预览或大段玩法说明文案。
### 2.2 后续增强路径
后续再考虑多人实时
第二阶段之后再考虑
- Phase 2:排行榜、挑战分享、个人历史成绩、作品统计面板
- Phase 2.1:挑战分享、作品统计面板细化、排行榜体验优化
- Phase 3异步影子对手 / ghost replay但仍不保存原始音频只保存低维派生曲线或聚合指标。
- Phase 4实时多人对战协议需要独立同步模型、房间服务、延迟补偿、断线恢复与更严格反作弊不应混入 MVP
- Phase 4实时多人对战协议需要独立同步模型、房间服务、延迟补偿、断线恢复与更严格反作弊不应混入第二阶段平台作品闭环
## 2.3 Phase 2 技术实施顺序
Phase 2 按“契约和领域规则先行,然后最小纵切,再扩展投影”的顺序实施,避免前端 mock 堆积、后端孤岛或排行榜 UI 先行。
1. 契约与领域规则:补 `shared-contracts` DTO、`module-bark-battle` 纯领域规则、`rulesetVersion` / `difficultyPreset` / score adjudication并先写单测。
2. SpacetimeDB 表与 reducer + api-server BFF落草稿/config/发布态 config、runtime run start / finish、score record、leaderboard entry、work stats projection、personal summary projection、`migration.rs` 与绑定生成。
3. 最小前端纵切:接创作入口、单页轻配置表单、发布到稳定 workId、作品详情 CTA、runtime 拉 config、start / finish 串通、结算展示 `serverResult`
4. 投影与列表体验:接排行榜、个人历史最近记录 + 最佳摘要、作品统计、我的作品/广场卡片适配。
5. 收口验证:把 BDD 场景落到测试,执行编码检查、后端 `/healthz` + API smoke、前端人工验收路径并更新 README/文档。
## 3. DDD 分层设计
@@ -90,7 +144,7 @@ frontend/runtime
- 定义配置版本兼容规则。
- 计算提交结果的派生分数区间与胜负判定是否自洽。
- 计算 `ScoreSummary`、排行榜排序分数、统计指标。
- 定义反作弊基础规则:时长范围、有效声次数上限、峰值范围、能量范围、提交窗口、run 状态机。
- 定义反作弊基础规则:时长范围、有效声浪触发次数上限、峰值范围、能量范围、提交窗口、run 状态机。
不职责: