From fa61eeb0b0b6d8d7837bdc545ff50b725bcb8d51 Mon Sep 17 00:00:00 2001 From: kdletters Date: Mon, 11 May 2026 16:10:48 +0800 Subject: [PATCH] docs: add bark battle BDD acceptance scenarios --- docs/prd/BARK_BATTLE_BDD_2026-05-11.md | 412 +++++++++++++++++++++++++ 1 file changed, 412 insertions(+) create mode 100644 docs/prd/BARK_BATTLE_BDD_2026-05-11.md diff --git a/docs/prd/BARK_BATTLE_BDD_2026-05-11.md b/docs/prd/BARK_BATTLE_BDD_2026-05-11.md new file mode 100644 index 00000000..6d48c7cb --- /dev/null +++ b/docs/prd/BARK_BATTLE_BDD_2026-05-11.md @@ -0,0 +1,412 @@ +# 汪汪声浪大作战 / bark-battle BDD 验收场景 + +## 背景 + +- 需求来源:用户提供的视频 `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 秒限时内通过麦克风输入“狗叫声”进行声浪拔河;系统依据声音强度、有效叫声次数和叫声节奏计算推动力,实时推动顶部红蓝能量条;倒计时结束后按能量条位置判定胜负或平局。 +- 文档目的:为产品、测试、前端、后端在编码前统一可验证验收口径;本文只定义 PRD/BDD 级行为与测试映射,不实现工程代码。 + +## 角色与目标 + +### 主要角色 + +- 浏览器玩家:进入 `bark-battle` 玩法,用麦克风发声参与对战。 +- 移动端玩家:在手机浏览器中进入玩法,需要看到核心游戏信息并完成授权、对战、结算。 +- 无麦克风或无 API 支持玩家:设备或浏览器不支持声控输入时,需要得到明确降级反馈并可返回。 +- 测试人员:依据本文场景验证权限、校准、叫声识别、能量条、胜负结算和布局兼容。 +- 前端实现人员:依据本文拆分 Web Audio 输入、领域规则、Phaser/DOM HUD 表现和自动化测试。 + +### 用户目标 + +- 玩家可以在开局前完成麦克风授权和环境噪音校准。 +- 玩家发出有效狗叫时,能看到叫声计数、狗狗动画、拟声词/冲击波以及能量条变化。 +- 低于阈值的背景噪音不会被误计为有效叫声。 +- 单局在 30 秒后给出明确胜负、平局和关键数据。 +- 移动端和不支持麦克风的环境不会进入不可操作状态。 + +### 非目标 + +- MVP 不要求识别“是否真实狗叫”,不引入机器学习声纹/物种分类;有效输入以音量阈值、峰值间隔、持续时间和校准结果为准。 +- MVP 不要求实时联机对战;可先按“玩家 vs AI 对手”完成单机浏览器 runtime。 +- MVP 不要求成绩持久化、作品发布、作品架、广场和排行榜;若后续接入 Genarrative 作品闭环,需要另补玩法类型集成 PRD/技术文档。 +- MVP 不要求在 UI 中长期展示大段规则说明;游戏界面应保持倒计时、能量条、狗狗、麦克风状态和结算信息为主。 +- MVP 不处理竞技级反作弊;播放录音、拍桌、喊叫等非狗叫输入只作为后续公平性风险记录。 + +## 业务规则口径 + +- 单局时长:默认 30 秒,从正式进入 `playing` 阶段开始计时。 +- 能量条:使用 `-100` 到 `100` 的连续值表示,负数偏对手侧,正数偏玩家侧,`0` 为中线。 +- 平局阈值:倒计时结束时,若能量条绝对值小于或等于 `drawThreshold`,判定平局;具体数值由实现配置,但测试需可注入固定阈值。 +- 有效叫声:一次有效叫声至少满足:音量超过校准后的有效阈值、与上一次有效峰值间隔不小于 `minBarkGapMs`、持续时长在 `minBarkDurationMs` 到 `maxBarkDurationMs` 之间。 +- 背景噪音:校准阶段采集到的环境声用于计算动态阈值;低于阈值的输入不得增加叫声次数,也不得让能量条出现可见推进。 +- 推动力:玩家推动力由音量分数、有效叫声频率和连击加成组成;能量条按玩家推动力与对手推动力差值移动,并被限制在 `-100` 到 `100`。 +- UI 反馈:有效叫声应触发可观察反馈,包括玩家侧狗狗张嘴/吠叫动画、拟声词或冲击波;反馈不应遮挡倒计时和顶部能量条。 + +## 中文 Gherkin 场景 + +### 功能: 麦克风授权与开局准备 + +```gherkin +功能: 狗叫对战麦克风准备 + 为了让玩家能用声音参与对战 + 作为浏览器玩家 + 我希望游戏在正式开局前请求麦克风权限并完成环境校准 + + 背景: + 假如玩家进入 bark-battle 玩法页面 + 而且浏览器支持 navigator.mediaDevices.getUserMedia + + 场景: 玩家允许麦克风权限后进入环境噪音校准 + 当玩家同意浏览器麦克风授权 + 那么系统应进入环境噪音校准阶段 + 而且游戏不应在校准完成前进入 playing 阶段 + 而且界面应显示麦克风已授权的可观察状态 + + 场景: 校准完成后进入开局倒计时 + 假如玩家已允许麦克风权限 + 而且系统已采集足够的环境噪音样本 + 当校准计算出有效叫声阈值 + 那么系统应进入开局倒计时阶段 + 而且倒计时结束后应进入 30 秒对战阶段 + 而且初始能量条应位于中线 + + 场景: 玩家拒绝麦克风权限后不能开始声控对战 + 当玩家拒绝浏览器麦克风授权 + 那么系统应停留在无法声控游玩的状态 + 而且应提供重新授权或返回入口 + 而且不应进入校准、倒计时或 playing 阶段 +``` + +### 功能: 环境噪音校准 + +```gherkin +功能: 环境噪音校准 + 为了减少背景噪音误触发 + 作为浏览器玩家 + 我希望游戏在开局前根据当前环境设置有效叫声阈值 + + 场景: 安静环境生成低但非零的有效阈值 + 假如校准阶段采集到的环境噪音 RMS 稳定低于默认噪音基线 + 当系统完成校准 + 那么有效叫声阈值应高于环境噪音平均值 + 而且阈值不应低于系统配置的最小阈值 + + 场景: 嘈杂环境生成更高的有效阈值 + 假如校准阶段采集到的环境噪音 RMS 高于默认噪音基线 + 当系统完成校准 + 那么有效叫声阈值应随环境噪音上调 + 而且低于该阈值的后续输入不应计为有效叫声 + + 场景: 校准期间无法获得有效音频样本 + 假如麦克风授权成功但音频样本持续为空或不可读 + 当校准超过系统配置的最长等待时间 + 那么系统应展示麦克风输入不可用状态 + 而且应提供重试校准入口 + 而且不应直接开始对战 +``` + +### 功能: 有效叫声计数 + +```gherkin +功能: 有效叫声计数 + 为了把玩家的狗叫行为转换为可计分输入 + 作为玩家 + 我希望每次符合规则的短促叫声只被计数一次 + + 背景: + 假如游戏处于 30 秒 playing 阶段 + 而且系统已完成环境噪音校准 + + 场景: 单次超过阈值且间隔足够的叫声计数加一 + 假如玩家当前叫声次数为 0 + 而且上一次有效叫声时间早于 minBarkGapMs + 当麦克风输入出现一次超过有效阈值且持续时长合规的峰值 + 那么玩家叫声次数应变为 1 + 而且玩家侧应出现一次吠叫动画反馈 + 而且画面应出现一次拟声词或冲击波反馈 + + 场景: 持续噪音不会被无限计数 + 假如玩家当前叫声次数为 1 + 当麦克风输入持续超过阈值但没有新的峰值间隔 + 那么玩家叫声次数不应在每个 tick 中持续增加 + 而且系统最多只应记录当前连续声音段内的一次有效叫声 + + 场景: 间隔过短的连续峰值不重复计数 + 假如玩家刚刚产生一次有效叫声 + 当麦克风输入在 minBarkGapMs 内再次出现峰值 + 那么玩家叫声次数不应增加 + 而且连击或推动力不应因该峰值重复加成 +``` + +### 功能: 声音大小和连续叫声推动能量条 + +```gherkin +功能: 声浪推动能量条 + 为了复刻双方比狗叫的核心体验 + 作为玩家 + 我希望更响、更连续的有效叫声能把顶部能量条推向自己一侧 + + 背景: + 假如游戏处于 30 秒 playing 阶段 + 而且能量条当前位于中线 + + 场景: 玩家推动力高于对手时能量条向玩家侧移动 + 假如玩家在短时间窗口内产生多次有效叫声 + 而且玩家推动力高于对手推动力 + 当系统推进一个 simulation tick + 那么能量条数值应向玩家侧增加 + 而且顶部红蓝能量条的玩家侧占比应变大 + + 场景: 连续大声叫声触发更强反馈 + 假如玩家连续产生多次高于强叫声阈值的有效叫声 + 当系统计算玩家连击加成 + 那么玩家侧推动力应高于单次普通叫声推动力 + 而且玩家侧声浪或冲击波反馈应比普通叫声更明显 + 但是反馈不应遮挡倒计时和能量条 + + 场景: 能量条到达边界后不会越界 + 假如能量条已经接近玩家侧最大值 + 而且玩家推动力仍高于对手推动力 + 当系统推进多个 simulation tick + 那么能量条数值不应超过 100 + 而且界面不应显示超出容器范围的能量条 + + 场景: 对手推动力高于玩家时能量条向对手侧移动 + 假如对手推动力高于玩家推动力 + 当系统推进一个 simulation tick + 那么能量条数值应向对手侧减少 + 而且顶部红蓝能量条的对手侧占比应变大 +``` + +### 功能: 低噪音和无效输入过滤 + +```gherkin +功能: 背景噪音过滤 + 为了避免环境声替玩家自动得分 + 作为玩家 + 我希望低于阈值或不合规的声音不会被当作有效狗叫 + + 背景: + 假如游戏处于 30 秒 playing 阶段 + 而且系统已完成环境噪音校准 + + 场景: 低于阈值的背景噪音不计数 + 当麦克风只接收到低于有效叫声阈值的背景噪音 + 那么玩家叫声次数不应增加 + 而且玩家侧不应播放吠叫动画 + 而且能量条不应因为该背景噪音出现可见推进 + + 场景: 过短脉冲不计为有效叫声 + 假如麦克风输入峰值超过有效阈值 + 但是持续时长短于 minBarkDurationMs + 当系统完成该声音段判定 + 那么玩家叫声次数不应增加 + 而且不应触发连击加成 + + 场景: 过长持续声被削弱为单段输入 + 假如麦克风输入持续超过有效阈值 + 但是持续时长长于 maxBarkDurationMs + 当系统完成该声音段判定 + 那么系统不应按多个叫声重复计数 + 而且该声音段的推动力应按持续噪音削弱规则处理 +``` + +### 功能: 倒计时与胜负结算 + +```gherkin +功能: 狗叫对战胜负结算 + 为了让单局对抗有明确目标 + 作为玩家 + 我希望 30 秒倒计时结束后根据能量条位置得到胜负或平局 + + 背景: + 假如游戏已经进入 30 秒 playing 阶段 + + 场景: 倒计时每秒递减并在归零时停止对战输入 + 当系统时间从 30 秒推进到 0 秒 + 那么界面应显示倒计时归零 + 而且系统应进入 finished 结算阶段 + 而且归零后的麦克风输入不应再改变本局能量条和叫声次数 + + 场景: 玩家侧占优时判定玩家胜利 + 假如倒计时归零时能量条数值大于 drawThreshold + 当系统进入结算阶段 + 那么系统应判定玩家胜利 + 而且结算面板应展示玩家叫声次数、最大音量和声浪评分 + 而且应提供再来一局入口 + + 场景: 对手侧占优时判定玩家失败 + 假如倒计时归零时能量条数值小于 -drawThreshold + 当系统进入结算阶段 + 那么系统应判定对手胜利 + 而且结算面板应展示玩家叫声次数、最大音量和声浪评分 + 而且应提供再来一局入口 + + 场景: 能量条接近平衡时判定平局 + 假如倒计时归零时能量条数值位于 -drawThreshold 到 drawThreshold 之间 + 当系统进入结算阶段 + 那么系统应判定为平局 + 而且结算面板应展示双方接近平衡的结果 + 而且应提供再来一局入口 +``` + +### 功能: 再来一局与状态重置 + +```gherkin +功能: 对战重开 + 为了让玩家快速再次挑战 + 作为玩家 + 我希望结算后可以开始新的一局且旧状态不会污染新局 + + 场景: 结算后点击再来一局重置本局状态 + 假如系统处于 finished 结算阶段 + 而且结算面板展示上一局结果 + 当玩家选择再来一局 + 那么系统应重置剩余时间为 30 秒 + 而且能量条应回到中线 + 而且玩家叫声次数、最大音量、连击和胜负结果应清零 + 而且系统应重新进入校准或开局倒计时流程 + + 场景: 结算后返回玩法入口 + 假如系统处于 finished 结算阶段 + 当玩家选择返回入口 + 那么系统应离开当前对战运行态 + 而且不应继续采集麦克风输入 +``` + +### 功能: 移动端布局 + +```gherkin +功能: 移动端 bark-battle 布局 + 为了让手机浏览器玩家可以完成声控对战 + 作为移动端玩家 + 我希望核心信息在窄屏中保持可见且可操作 + + 场景: 移动端进入对战页面时核心元素可见 + 假如玩家使用宽度不超过 430px 的移动端视口 + 当玩家进入 bark-battle 页面 + 那么顶部能量条应完整显示在首屏可见区域内 + 而且倒计时应可见 + 而且双方狗狗主体不应被权限、设置或说明面板长期遮挡 + 而且非关键设置应收起到菜单或次级入口中 + + 场景: 移动端授权和开始必须由用户手势触发 + 假如玩家使用移动端浏览器 + 当玩家点击开始或授权入口 + 那么系统才应请求麦克风权限并激活音频上下文 + 而且不应在页面自动加载时直接启动 AudioContext + + 场景: 移动端结算面板不遮挡主要操作 + 假如玩家在移动端完成一局对战 + 当系统展示结算面板 + 那么胜负结果、再来一局和返回入口应在不横向滚动的情况下可见 + 而且结算面板不应要求玩家阅读大段规则说明才能继续 +``` + +### 功能: 无 getUserMedia 或不可用环境降级 + +```gherkin +功能: 无麦克风 API 降级 + 为了避免不支持设备进入卡死状态 + 作为无麦克风或无 API 支持玩家 + 我希望系统明确告知无法声控游玩并提供退出路径 + + 场景: 当前浏览器不支持 getUserMedia + 假如玩家设备不支持 navigator.mediaDevices.getUserMedia + 当玩家进入 bark-battle 页面 + 那么系统应显示设备或浏览器不支持麦克风输入的状态 + 而且应提供返回入口 + 而且不应展示可开始声控对战的按钮 + + 场景: getUserMedia 调用失败但浏览器 API 存在 + 假如浏览器支持 getUserMedia + 但是请求麦克风时返回 NotFoundError 或 NotReadableError + 当系统接收到失败结果 + 那么系统应展示麦克风不可用状态 + 而且应提供重试授权或返回入口 + 而且不应进入 playing 阶段 + + 场景: 非安全上下文导致麦克风不可用 + 假如页面运行在浏览器不允许麦克风的非安全上下文 + 当玩家进入 bark-battle 页面 + 那么系统应展示当前环境无法使用麦克风的状态 + 而且应提示使用受支持的安全环境或返回 + 而且不应开始对战倒计时 +``` + +### 功能: 刷新与退出时释放麦克风资源 + +```gherkin +功能: 麦克风资源释放 + 为了保护用户隐私并避免浏览器资源泄漏 + 作为浏览器玩家 + 我希望离开对战时麦克风采集被停止 + + 场景: 对战中离开页面停止采集 + 假如玩家已经授权麦克风并处于 playing 阶段 + 当玩家离开 bark-battle 页面或运行态卸载 + 那么系统应停止当前 MediaStream 的所有音轨 + 而且不应继续推进本局 simulation tick + + 场景: 刷新页面后不沿用旧局临时状态 + 假如玩家在 playing 阶段刷新页面 + 当页面重新加载 bark-battle + 那么系统应重新进入权限检查或授权准备状态 + 而且不应沿用刷新前的剩余时间、能量条和叫声次数作为新局结果 +``` + +## 测试映射 + +| 场景 | 测试层级 | 建议目标文件 | 自动化状态 | +| --- | --- | --- | --- | +| 玩家允许麦克风权限后进入环境噪音校准 | application/component | `src/games/bark-battle/application/BarkBattleController.test.ts`, `src/games/bark-battle/ui/BarkBattlePermissionPanel.test.tsx` | planned | +| 校准完成后进入开局倒计时 | application/unit | `src/games/bark-battle/application/BarkBattleController.test.ts`, `src/games/bark-battle/domain/BarkBattleSession.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/BarkNoiseCalibration.test.ts` | planned | +| 嘈杂环境生成更高的有效阈值 | 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/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 | `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/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 | +| 能量条接近平衡时判定平局 | unit/component | `src/games/bark-battle/domain/BarkBattleSession.test.ts`, `src/games/bark-battle/ui/BarkBattleResultPanel.test.tsx` | planned | +| 结算后点击再来一局重置本局状态 | application/component | `src/games/bark-battle/application/BarkBattleController.test.ts`, `src/games/bark-battle/ui/BarkBattleResultPanel.test.tsx` | planned | +| 结算后返回玩法入口 | integration/smoke | `src/games/bark-battle/application/BarkBattleController.test.ts`, Playwright 或人工 smoke 清单 | planned | +| 移动端进入对战页面时核心元素可见 | component/visual/smoke | `src/games/bark-battle/ui/BarkBattleHud.test.tsx`, Playwright 移动端视口 smoke | planned | +| 移动端授权和开始必须由用户手势触发 | application/e2e-smoke | `src/games/bark-battle/application/BrowserMicrophoneInput.test.ts`, Playwright 移动端 smoke | planned | +| 移动端结算面板不遮挡主要操作 | component/visual/smoke | `src/games/bark-battle/ui/BarkBattleResultPanel.test.tsx`, Playwright 移动端视口 smoke | planned | +| 当前浏览器不支持 getUserMedia | component/application | `src/games/bark-battle/application/BrowserMicrophoneInput.test.ts`, `src/games/bark-battle/ui/BarkBattlePermissionPanel.test.tsx` | planned | +| getUserMedia 调用失败但浏览器 API 存在 | component/application | `src/games/bark-battle/application/BrowserMicrophoneInput.test.ts`, `src/games/bark-battle/ui/BarkBattlePermissionPanel.test.tsx` | planned | +| 非安全上下文导致麦克风不可用 | component/application | `src/games/bark-battle/application/BrowserMicrophoneInput.test.ts`, `src/games/bark-battle/ui/BarkBattlePermissionPanel.test.tsx` | planned | +| 对战中离开页面停止采集 | application/integration | `src/games/bark-battle/application/BrowserMicrophoneInput.test.ts`, `src/games/bark-battle/application/BarkBattleController.test.ts` | planned | +| 刷新页面后不沿用旧局临时状态 | integration/smoke | Playwright 或人工 smoke 清单 | planned | + +## 验收清单 + +- [ ] 权限允许、拒绝、API 不支持、麦克风不可读均有明确状态,且不会误进入 playing。 +- [ ] 校准阶段会影响有效叫声阈值,低噪音不会增加叫声计数。 +- [ ] 有效叫声计数具备阈值、峰值间隔、持续时长约束。 +- [ ] 能量条根据双方推动力差值双向移动,并限制在 `-100` 到 `100`。 +- [ ] 30 秒归零后停止本局输入影响,并按玩家胜利、对手胜利、平局三类结果结算。 +- [ ] 移动端核心元素可见,非关键设置收起,不在主画面堆叠长规则说明。 +- [ ] 离开页面或返回入口时停止麦克风采集。 +- [ ] 每个编码依据场景已在测试映射中标注测试层级和建议文件。 + +## 开放问题 + +1. MVP 是否确认只做“玩家 vs AI”,还是第一版需要双人同屏或联机对战? +2. `drawThreshold`、`minBarkGapMs`、`minBarkDurationMs`、`maxBarkDurationMs` 的首版默认值由产品/调参阶段确认,还是先采用开发可配置默认值? +3. 是否允许无麦克风设备提供键盘/点击备用输入?若允许,需要另补非声控模式场景;若不允许,当前降级只提供返回入口。 +4. 是否需要在结算中记录或上报成绩、最高音量、叫声次数和声浪评分?若需要,需补埋点/后端持久化场景。 +5. bark-battle 是否作为 Genarrative 正式 play type 接入创作入口、作品发布和广场,还是先作为独立 runtime 原型验证? +6. 狗狗、背景、拟声词和冲击波素材来源是临时占位、AI 生成,还是复用项目现有素材管线?