diff --git a/.hermes/plans/2026-05-11_144229-bark-battle-2d-game-bdd-ddd-tdd-plan.md b/.hermes/plans/2026-05-11_144229-bark-battle-2d-game-bdd-ddd-tdd-plan.md new file mode 100644 index 00000000..9bcdfd00 --- /dev/null +++ b/.hermes/plans/2026-05-11_144229-bark-battle-2d-game-bdd-ddd-tdd-plan.md @@ -0,0 +1,561 @@ +# 声控狗叫对战 2D 浏览器游戏设计与实现计划 + +## 目标 + +基于用户提供的视频: + +`C:\Users\DSK\Videos\一款双方比狗叫的游戏 - 1.一款双方比狗叫的游戏(Av116504192360177,P1).mp4` + +提取其中“双方比狗叫”的核心玩法,并按照 BDD / TDD / DDD 的方法,为 Genarrative 中可运行于浏览器的 2D 游戏方案生成一份可落地设计与实现思路。实现方向遵循仓库内 `game-studio` 插件工作流,默认采用 2D Phaser + TypeScript + Vite + DOM HUD 的浏览器游戏架构。 + +本计划仅做方案设计,不直接编码。 + +## 当前上下文与输入分析 + +### 已识别视频核心画面 + +通过抽帧观察,视频中的游戏呈现出以下稳定特征: + +- 画面是横版 2D 手绘舞台,场景包括公园、海边等固定关卡背景。 +- 双方各有一只狗作为对战角色,站在左右两侧。 +- 中央有明显倒计时,例如 `30`、`28`。 +- 顶部有红蓝双方拉锯式能量条 / 进度条。 +- 中央提示出现:`对着麦克风汪一声`、`用声音大小 + 叫声次数推动能量条!` +- 玩家输入不是传统键鼠,而是麦克风声音。 +- 玩家需要模仿狗叫,系统根据声音大小与叫声次数推动能量条。 +- 屏幕会根据叫声出现 `BARK`、`WOOF`、`WAN`、`WANGOOF` 等拟声词与冲击波视觉反馈。 +- 回合结束时,根据能量条偏向或推进结果判定胜负。 + +### 提炼出的核心玩法 + +这是一个“声控拔河式狗叫对战”小游戏: + +- 两名玩家 / 一名玩家对 AI 分别代表左右两只狗。 +- 每局限时 30 秒。 +- 玩家通过麦克风持续发出狗叫声。 +- 游戏实时分析音量峰值、叫声次数、叫声节奏。 +- 声音越大、叫声越密集,己方推动力越强。 +- 顶部能量条在双方推动力差值下左右移动。 +- 时间结束后,能量条偏向哪一方,哪一方获胜。 + +### 需要合理抽象的地方 + +视频中存在直播弹幕、贴图、表情包、遮挡层,这些不是游戏本体机制。本方案只吸收游戏本体核心: + +- 双方狗狗对叫 +- 麦克风输入 +- 声音强度 + 次数判定 +- 红蓝拉锯能量条 +- 限时回合 +- 夸张拟声词与冲击波反馈 + +## game-studio 插件路线 + +根据仓库内 `.hermes/plugins/game-studio` 技能: + +- 早期游戏工作先走 `game-studio` 总入口。 +- 2D 浏览器游戏默认选择 Phaser。 +- 架构上需要分离 simulation 与 renderer。 +- HUD / 菜单 / 设置优先使用 DOM overlay,不把密集文字塞进 canvas。 +- 玩法状态不应由 Phaser Scene 直接持有,Scene 只负责渲染、动画、相机、输入适配。 + +因此本方案采用: + +- Runtime:Phaser 3 +- Language:TypeScript +- Build:Vite +- UI:React/DOM HUD overlay 或项目现有 DOM UI 层 +- Audio input:Web Audio API + MediaDevices.getUserMedia +- Simulation:纯 TS domain/service 层 +- Renderer:Phaser Scene 读取 simulation snapshot 并播放动画/特效 + +## 游戏概念设计 + +### 游戏名建议 + +- 中文:`汪汪声浪大作战` +- 英文代号:`bark-battle` +- Play type ID 建议:`bark-battle` + +### 玩家幻想 + +玩家不是通过按键战斗,而是真的对着麦克风“汪汪叫”,把自己的狗狗声浪推向对手。游戏目标是在倒计时结束前用更响、更密集、更有节奏的叫声赢得声浪拔河。 + +### 核心动词 + +- 叫:对麦克风发出狗叫声。 +- 推:通过叫声推动能量条。 +- 压制:让能量条持续向对手方向倾斜。 +- 爆发:短时间内连续高质量叫声触发冲击波。 +- 防守:对手强势时通过持续叫声把能量条拉回。 + +### 单局流程 + +1. 准备阶段 + - 展示双方狗狗、地图、麦克风权限提示。 + - 用户授权麦克风。 + - 系统检测环境噪音并校准阈值。 + +2. 倒计时阶段 + - 3、2、1 或中央 `30` 倒计时开始。 + - 玩家看到提示:`对着麦克风汪一声`。 + +3. 对战阶段 + - 每帧或固定 tick 采集麦克风音量。 + - 根据音量峰值与短促叫声次数计算本方 barkPower。 + - AI 或远端对手产生 opponentPower。 + - 能量条根据 `playerPower - opponentPower` 拉锯。 + - 狗狗张嘴动画、拟声词、冲击波按声音强度生成。 + +4. 结算阶段 + - 30 秒结束。 + - 能量条偏玩家侧则胜利,偏对手侧则失败,接近中线则平局。 + - 展示叫声次数、最大音量、平均节奏、声浪评分。 + +5. 重开 / 返回 + - 支持再来一局。 + - 支持返回玩法入口或结果页。 + +## 规则设计 + +### 关键状态 + +```ts +type BarkBattlePhase = 'permission' | 'calibration' | 'countdown' | 'playing' | 'finished' + +type BarkBattleSnapshot = { + phase: BarkBattlePhase + remainingMs: number + energy: number // -100 到 100,负数偏对手,正数偏玩家 + player: BarkSideState + opponent: BarkSideState + winner: 'player' | 'opponent' | 'draw' | null +} + +type BarkSideState = { + barkCount: number + currentVolume: number + recentPeak: number + combo: number + power: number + isBarking: boolean +} +``` + +### 输入判定 + +#### 音量采样 + +- 使用 Web Audio API 创建 `AnalyserNode`。 +- 每个 simulation tick 读取频域或时域数据。 +- 计算 RMS 或 peak volume。 +- 根据校准后的环境噪音设置动态阈值。 + +#### 一次“叫声”的判定 + +一次有效叫声建议满足: + +- 音量超过 `barkThreshold`。 +- 与上一次叫声峰值至少间隔 `minBarkGapMs`,避免持续噪音被无限计数。 +- 持续时长在合理范围,例如 80ms 到 1200ms。 +- 可选:频谱能量集中在中高频,不强制做复杂语音识别,MVP 先用音量 + 峰值节奏。 + +#### 推动力计算 + +```text +playerPower = volumeScore * 0.65 + barkRateScore * 0.35 + comboBonus +opponentPower = aiPower 或远端玩家 power +energyDelta = (playerPower - opponentPower) * deltaTime * balanceFactor +energy = clamp(energy + energyDelta, -100, 100) +``` + +### AI 对手 MVP + +若先做单机浏览器版,右侧对手可由 AI 模拟: + +- 简单难度:周期性小叫,power 低。 +- 普通难度:有节奏地爆发,power 中等。 +- 困难难度:根据玩家领先程度自适应追赶,但不得作弊到不可赢。 + +后续可扩展为多人实时对战。 + +## BDD 行为场景 + +### 功能: 麦克风授权与准备 + +```gherkin +功能: 狗叫对战麦克风准备 + 为了让玩家能用声音参与对战 + 作为浏览器玩家 + 我希望游戏在开局前明确请求麦克风权限并完成环境校准 + + 场景: 玩家允许麦克风权限后进入准备倒计时 + 假如玩家打开狗叫对战页面 + 当玩家同意浏览器麦克风授权 + 那么系统应进入环境噪音校准阶段 + 而且校准完成后应显示开局倒计时 + + 场景: 玩家拒绝麦克风权限 + 假如玩家打开狗叫对战页面 + 当玩家拒绝浏览器麦克风授权 + 那么系统应显示无法声控游玩的提示 + 而且应提供重试授权入口 + 而且不应直接开始对战 +``` + +### 功能: 声音推动能量条 + +```gherkin +功能: 声音大小和叫声次数推动能量条 + 为了复刻双方比狗叫的核心体验 + 作为玩家 + 我希望自己的叫声能实时推动顶部能量条 + + 场景: 玩家发出一次有效狗叫 + 假如游戏处于 playing 阶段 + 而且麦克风输入音量超过有效叫声阈值 + 当系统检测到一次新的叫声峰值 + 那么玩家叫声次数应增加 1 + 而且玩家狗狗应播放张嘴吠叫动画 + 而且画面应出现拟声词反馈 + + 场景: 玩家连续大声狗叫压制对手 + 假如游戏处于 playing 阶段 + 而且玩家在短时间内产生多次有效叫声 + 当玩家推动力高于对手推动力 + 那么顶部能量条应向玩家侧移动 + 而且玩家侧声浪特效应增强 + + 场景: 环境噪音低于阈值不计入叫声 + 假如游戏处于 playing 阶段 + 当麦克风只有低于阈值的背景噪音 + 那么玩家叫声次数不应增加 + 而且能量条不应因为背景噪音明显移动 +``` + +### 功能: 限时胜负结算 + +```gherkin +功能: 狗叫对战胜负结算 + 为了让单局对抗有明确目标 + 作为玩家 + 我希望倒计时结束后根据能量条位置判定胜负 + + 场景: 倒计时结束时玩家侧占优 + 假如游戏剩余时间归零 + 而且能量条位于玩家侧 + 当系统进入结算阶段 + 那么系统应判定玩家胜利 + 而且展示玩家叫声次数、最大音量和声浪评分 + + 场景: 倒计时结束时双方接近平衡 + 假如游戏剩余时间归零 + 而且能量条处于平局阈值范围内 + 当系统进入结算阶段 + 那么系统应判定为平局 + 而且展示再来一局入口 +``` + +### 功能: 移动端与无麦克风降级 + +```gherkin +功能: 声控游戏移动端与无麦克风降级 + 为了让不同设备玩家都能理解当前状态 + 作为移动端或无麦克风环境玩家 + 我希望系统给出清晰、可操作的降级路径 + + 场景: 当前浏览器不支持麦克风 API + 假如玩家设备不支持 getUserMedia + 当玩家进入狗叫对战页面 + 那么系统应显示设备不支持麦克风输入 + 而且提供返回入口 + + 场景: 移动端进入对战页面 + 假如玩家使用移动端浏览器 + 当玩家进入狗叫对战页面 + 那么主要能量条、倒计时和狗狗角色应保持可见 + 而且非关键设置应收起到菜单中 +``` + +## DDD 领域划分 + +### 领域层:bark-battle domain + +职责:只处理玩法规则,不依赖 Phaser、DOM、Web Audio、后端。 + +建议模块: + +- `BarkBattleSession` + - 管理 phase、remainingMs、energy、winner。 +- `BarkDetector` + - 根据音量样本判断是否形成一次有效叫声。 +- `EnergyTugOfWar` + - 根据双方 power 更新能量条。 +- `BarkBattleScoring` + - 计算最大音量、叫声次数、combo、评分。 +- `OpponentStrategy` + - 单机 AI 对手策略接口。 + +领域规则必须可用纯单元测试验证。 + +### 应用层:use case / controller + +职责:编排麦克风输入、simulation tick、AI 对手、结果输出。 + +建议用例: + +- `requestMicrophonePermission()` +- `calibrateAmbientNoise()` +- `startBarkBattleSession()` +- `submitAudioSample(sample)` +- `tickBarkBattle(deltaMs)` +- `finishBarkBattle()` + +### 基础设施层 + +职责:浏览器 API 与引擎适配。 + +- `BrowserMicrophoneInput` + - 封装 `navigator.mediaDevices.getUserMedia`。 + - 输出 normalized volume samples。 +- `PhaserBarkBattleScene` + - 渲染狗狗、背景、拟声词、冲击波。 + - 不持有核心玩法规则。 +- `DomBarkBattleHud` + - 展示倒计时、能量条、权限提示、结算面板。 + +### 表现层 + +- Phaser Canvas:地图、狗狗、声浪、粒子、拟声词。 +- DOM HUD:顶部能量条、倒计时、权限/结算/设置面板。 + +## TDD 落地顺序 + +### 第一轮:领域规则 RED-GREEN-REFACTOR + +先写纯 TS 单元测试,不接 Phaser,不接麦克风。 + +目标测试: + +- `BarkDetector`:超过阈值且间隔足够时计为一次叫声。 +- `BarkDetector`:持续噪音不会无限增加叫声次数。 +- `EnergyTugOfWar`:玩家 power 高于对手时 energy 向玩家侧移动。 +- `EnergyTugOfWar`:energy 被 clamp 在 -100 到 100。 +- `BarkBattleSession`:倒计时归零后进入 finished。 +- `BarkBattleSession`:根据 energy 判定 player/opponent/draw。 + +### 第二轮:应用层测试 + +- 模拟音频 sample 输入,验证 session snapshot 更新。 +- 模拟 AI 对手 power,验证能量条拉锯。 +- 模拟权限失败,验证 phase 不进入 playing。 + +### 第三轮:组件 / 集成测试 + +- HUD 根据 snapshot 显示倒计时。 +- HUD 根据 energy 渲染红蓝能量条比例。 +- 权限拒绝时显示重试入口。 +- 结算阶段显示胜负与再来一局。 + +### 第四轮:浏览器 smoke / playtest + +- 本地启动页面。 +- 授权麦克风。 +- 对麦克风发声后看到拟声词与能量条变化。 +- 移动端宽度下主游戏画面不被 HUD 遮挡。 + +## 建议文件结构 + +如果作为独立前端玩法原型,可采用: + +```text +src/games/bark-battle/ + domain/ + BarkBattleSession.ts + BarkDetector.ts + EnergyTugOfWar.ts + BarkBattleScoring.ts + OpponentStrategy.ts + application/ + BarkBattleController.ts + BrowserMicrophoneInput.ts + phaser/ + BarkBattleScene.ts + BarkBattlePreloadScene.ts + barkBattleAssets.ts + ui/ + BarkBattleHud.tsx + BarkBattleResultPanel.tsx + BarkBattlePermissionPanel.tsx + tests/ + BarkDetector.test.ts + EnergyTugOfWar.test.ts + BarkBattleSession.test.ts +``` + +如果接入 Genarrative 玩法类型闭环,后续还需要按 `genarrative-play-type-integration` 扩展: + +```text +src/components/bark-battle-runtime/BarkBattleRuntimeShell.tsx +src/components/bark-battle-result/BarkBattleResultView.tsx +src/services/barkBattleRuntimeClient.ts +packages/shared/src/contracts/barkBattle.ts +server-rs/crates/shared-contracts/src/bark_battle.rs +``` + +MVP 阶段建议先做浏览器单机 runtime 原型,再决定是否进入创作入口、作品发布、广场和后端持久化。 + +## UI / 视觉方向 + +### 画面 + +- 横版固定舞台。 +- 左右两只狗对峙。 +- 背景可先做公园一张图,后续扩展海边、街区等地图。 +- 狗狗用 2D sprite 或简单骨架帧动画。 + +### HUD + +- 顶部:红蓝声浪能量条。 +- 中央:大号倒计时,只在开局和关键时间突出显示。 +- 左右:双方狗狗状态,不堆叠复杂面板。 +- 底部或角落:麦克风状态、小型重试按钮。 +- 结算:居中弹出简洁面板,显示胜负和关键数据。 + +### 动效 + +- 叫声触发狗狗张嘴。 +- 声音越大,拟声词越大,冲击波越宽。 +- combo 时触发短暂屏幕震动,但不能遮挡能量条。 +- 尊重 reduced motion,非必要动画可降级。 + +## 测试映射 + +| BDD 场景 | 测试层级 | 目标文件 | 状态 | +| --- | --- | --- | --- | +| 玩家允许麦克风权限后进入准备倒计时 | application/component | `BarkBattleController.test.ts`, `BarkBattlePermissionPanel.test.tsx` | planned | +| 玩家拒绝麦克风权限 | application/component | `BarkBattleController.test.ts`, `BarkBattlePermissionPanel.test.tsx` | planned | +| 玩家发出一次有效狗叫 | unit | `BarkDetector.test.ts` | planned | +| 玩家连续大声狗叫压制对手 | unit/integration | `EnergyTugOfWar.test.ts`, `BarkBattleController.test.ts` | planned | +| 环境噪音低于阈值不计入叫声 | unit | `BarkDetector.test.ts` | planned | +| 倒计时结束时玩家侧占优 | unit | `BarkBattleSession.test.ts` | planned | +| 倒计时结束时双方接近平衡 | unit | `BarkBattleSession.test.ts` | planned | +| 当前浏览器不支持麦克风 API | component | `BarkBattlePermissionPanel.test.tsx` | planned | +| 移动端进入对战页面 | visual/smoke | Playwright 或人工 playtest 清单 | planned | + +## 验证命令建议 + +具体命令以后续实际落地位置为准,建议包括: + +```bash +npm run test -- --run src/games/bark-battle/**/*.test.ts +npm run test -- --run src/games/bark-battle/**/*.test.tsx +npm run typecheck +npm run check:encoding +``` + +若接入 Genarrative 后端或玩法配置,还需要追加: + +```bash +cd server-rs && cargo check -p api-server -p shared-contracts --no-default-features +npm run test -- src/components/platform-entry/platformEntryCreationTypes.test.ts +``` + +## 实施阶段拆分 + +### Phase 0:产品与技术定稿 + +- 确认玩法 ID:`bark-battle`。 +- 确认 MVP 只做单机玩家 vs AI,不做实时多人。 +- 确认是否只做 runtime 原型,还是接入 Genarrative 创作入口。 +- 确认是否允许浏览器麦克风权限作为核心输入。 + +### Phase 1:纯领域模型 + +- 建立 bark-battle domain。 +- 按 TDD 写 `BarkDetector`、`EnergyTugOfWar`、`BarkBattleSession` 测试。 +- 实现最小规则让测试通过。 + +### Phase 2:麦克风输入适配 + +- 封装 Web Audio API。 +- 支持权限请求、权限失败、环境噪音校准。 +- 使用 mock input 完成自动化测试,真实麦克风做 smoke。 + +### Phase 3:Phaser 2D runtime + +- 新建 Phaser Scene。 +- 绘制或占位加载公园背景、左右狗狗、声浪特效。 +- Scene 只消费 snapshot,不写规则。 +- 接入 DOM HUD。 + +### Phase 4:反馈与结算 + +- 加入拟声词、冲击波、狗狗张嘴动画。 +- 加入结算面板。 +- 加入再来一局与返回入口。 + +### Phase 5:Genarrative 集成可选项 + +若要正式接入玩法类型: + +- 补 `shared-contracts` 中 bark-battle runtime/result DTO。 +- 补前端 service 与 runtime shell。 +- 补入口配置数据库 seed。 +- 补作品架 / 发布 / 广场链路,若需要持久化成绩或作品。 +- 按 `genarrative-play-type-integration` 执行完整闭环验证。 + +## 风险与权衡 + +### 麦克风权限风险 + +浏览器麦克风权限受 HTTPS、浏览器策略、用户设置影响。MVP 需要明确: + +- 本地开发可在 localhost 使用。 +- 线上必须 HTTPS。 +- 权限拒绝需要可恢复。 + +### 声音识别准确性风险 + +MVP 不建议做复杂“是否真的是狗叫”的 AI 识别,否则实现成本高、误判多。建议先用: + +- 音量阈值 +- 峰值次数 +- 节奏间隔 +- 环境噪音校准 + +后续再考虑加入频谱特征或 ML 分类。 + +### 噪音作弊风险 + +玩家可以喊叫、拍桌子或播放音频。若是娱乐派对玩法可以接受;若要竞技公平,需要后续加入: + +- 频谱特征 +- 输入冷却 +- 异常持续噪音削弱 +- 本地/服务端反作弊策略 + +### 移动端兼容风险 + +移动端 Web Audio 可能需要用户手势激活 AudioContext。计划中需把“开始”按钮作为显式用户手势,避免自动启动失败。 + +### UI 遮挡风险 + +视频原型中的核心可读信息非常少:倒计时、能量条、狗狗、拟声词。实现时应避免把说明文案、复杂面板长期铺在画面上。 + +## 开放问题 + +1. MVP 是“玩家 vs AI”,还是需要从第一版开始支持双人同屏 / 联机? +2. 是否要作为 Genarrative 新玩法入口完整接入,还是先做独立 runtime 原型? +3. 是否需要记录成绩、发布作品、进入作品架和广场? +4. 狗狗与背景素材是使用临时占位、AI 生成,还是需要复用项目既有素材系统? +5. 是否允许游戏强依赖麦克风权限,还是必须提供键盘备用输入? + +## 推荐下一步 + +建议下一步先执行 Phase 0 + Phase 1: + +1. 明确 MVP 边界:单机玩家 vs AI。 +2. 写 `BarkDetector` / `EnergyTugOfWar` / `BarkBattleSession` 的 BDD 对应单元测试。 +3. 不接 Phaser、不接麦克风,先把核心规则用 TDD 跑通。 +4. 规则稳定后再接 Web Audio 与 Phaser runtime。 diff --git a/.hermes/plans/frame_003.jpg b/.hermes/plans/frame_003.jpg new file mode 100644 index 00000000..e2adf321 Binary files /dev/null and b/.hermes/plans/frame_003.jpg differ diff --git a/.hermes/plans/frame_010.jpg b/.hermes/plans/frame_010.jpg new file mode 100644 index 00000000..f7680827 Binary files /dev/null and b/.hermes/plans/frame_010.jpg differ diff --git a/.hermes/plans/frame_020.jpg b/.hermes/plans/frame_020.jpg new file mode 100644 index 00000000..9ce68639 Binary files /dev/null and b/.hermes/plans/frame_020.jpg differ diff --git a/.hermes/plans/frame_035.jpg b/.hermes/plans/frame_035.jpg new file mode 100644 index 00000000..03442627 Binary files /dev/null and b/.hermes/plans/frame_035.jpg differ diff --git a/docs/technical/BARK_BATTLE_2D_RUNTIME_TECHNICAL_PLAN_2026-05-11.md b/docs/technical/BARK_BATTLE_2D_RUNTIME_TECHNICAL_PLAN_2026-05-11.md new file mode 100644 index 00000000..4cf72f39 --- /dev/null +++ b/docs/technical/BARK_BATTLE_2D_RUNTIME_TECHNICAL_PLAN_2026-05-11.md @@ -0,0 +1,662 @@ +# bark-battle 2D Runtime 前端技术方案(2026-05-11) + +## 1. 背景与目标 + +本方案基于 `.hermes/plans/2026-05-11_144229-bark-battle-2d-game-bdd-ddd-tdd-plan.md`,为“汪汪声浪大作战 / bark-battle”细化前端与浏览器游戏 runtime 的技术实现路线。 + +本任务只产出技术方案,不直接实现代码。后续编码应以本文作为 runtime 层设计约束,再按 TDD 小步落地。 + +### 1.1 玩法定位 + +`bark-battle` 是一个声控拔河式 2D 浏览器小游戏:玩家对麦克风发出狗叫声,系统根据音量峰值、有效叫声次数和节奏计算本方声浪推动力,在限时 30 秒内推动顶部红蓝能量条,时间结束后按能量条偏向判定胜负。 + +### 1.2 本文范围 + +本文覆盖: + +- Phaser + TypeScript + Vite 栈选择 +- simulation / render / HUD 边界 +- 建议目录结构 +- 核心 domain 类型 +- Web Audio 输入适配 +- Phaser Scene 切分 +- DOM HUD 设计 +- 移动端与权限降级 +- 测试与验证命令 + +本文不覆盖: + +- 后端表结构、持久化成绩、作品发布、广场接入 +- 实时多人对战协议 +- 复杂 AI 狗叫识别模型 +- 美术素材正式生产流程 + +## 2. 技术栈选择 + +### 2.1 推荐栈 + +```text +Runtime Renderer: Phaser 3 +Language: TypeScript +Build: Vite +Host UI: React / DOM overlay +Audio Input: Web Audio API + MediaDevices.getUserMedia +Test: Vitest + Testing Library + 浏览器 smoke / Playwright 可选 +``` + +### 2.2 选择理由 + +1. 玩法是横版 2D 舞台,核心表现是狗狗 sprite、声浪、拟声词、粒子、屏幕震动与能量条反馈,Phaser 对 2D 渲染、时间循环、sprite animation、camera、粒子和 Scene 生命周期支持成熟。 +2. 当前项目主前端已使用 TypeScript + Vite,继续复用现有构建和测试体系,避免引入独立构建链。 +3. 文字密集、权限提示、结算、设置和移动端响应式布局适合 DOM HUD;Canvas 保持负责 playfield 和动态特效。 +4. Web Audio API 可以在浏览器端完成 MVP 所需音量采样、RMS/peak 计算、环境噪音校准和输入归一化,不需要首版接入后端音频处理。 + +### 2.3 不选择其它路线的原因 + +- 不使用 Three.js / 3D:当前玩法画面是 2D 横版舞台,不需要 3D 相机、模型和材质管线。 +- 不把 HUD 全部塞入 Phaser Canvas:权限说明、重试、结算、移动端布局和可访问性更适合 DOM。 +- 不在前端实现正式业务真相:浏览器 runtime 可承载单局即时 simulation,但若后续涉及成绩、作品、排行榜、发布和奖励,必须交给后端投影/API 裁决。 + +## 3. 总体架构 + +### 3.1 分层总览 + +```text +React Runtime Shell + ├─ DOM HUD / Panels + │ ├─ PermissionPanel + │ ├─ TopEnergyBar + │ ├─ TimerChip + │ └─ ResultPanel + │ + ├─ Application Controller + │ ├─ permission / calibration orchestration + │ ├─ simulation tick + │ ├─ audio sample submission + │ └─ snapshot publish + │ + ├─ Pure Domain / Simulation + │ ├─ BarkBattleSession + │ ├─ BarkDetector + │ ├─ EnergyTugOfWar + │ ├─ BarkBattleScoring + │ └─ OpponentStrategy + │ + ├─ Infrastructure Adapters + │ ├─ BrowserMicrophoneInput + │ ├─ AudioAnalyserSampler + │ └─ PhaserGameHost + │ + └─ Phaser Renderer + ├─ BootScene + ├─ PreloadScene + ├─ BattleScene + ├─ FxScene / DebugScene(可选) + └─ Asset manifest +``` + +### 3.2 强制边界 + +1. `domain/` 不依赖 Phaser、Web Audio、DOM、React、浏览器全局对象或后端 API。 +2. `domain/` 只接收 plain data,例如时间增量、归一化音量样本、对手 power、配置参数。 +3. `application/` 负责编排权限、校准、音频输入、AI 对手、tick 和 snapshot 分发。 +4. Phaser Scene 只消费 `BarkBattleSnapshot`,把 snapshot 映射成 sprite、动画、粒子、camera 和 sound effect;不得持有核心胜负、计数和能量条规则。 +5. DOM HUD 只消费 snapshot 和少量 runtime UI 状态,负责展示、按钮和弹层;不得重复实现核心胜负规则。 +6. 若后续接入平台作品/成绩/排行榜,前端只调用后端 API 和展示投影,不在本地绕过后端生成正式结论。 + +## 4. 建议目录结构 + +首版建议以独立 runtime 原型落在 `src/games/bark-battle/`,避免提前侵入平台创作链路。 + +```text +src/games/bark-battle/ + domain/ + BarkBattleTypes.ts + BarkBattleSession.ts + BarkDetector.ts + EnergyTugOfWar.ts + BarkBattleScoring.ts + OpponentStrategy.ts + __tests__/ + BarkDetector.test.ts + EnergyTugOfWar.test.ts + BarkBattleSession.test.ts + BarkBattleScoring.test.ts + + application/ + BarkBattleController.ts + BarkBattleConfig.ts + BarkBattleSnapshotStore.ts + __tests__/ + BarkBattleController.test.ts + + infrastructure/ + BrowserMicrophoneInput.ts + AudioAnalyserSampler.ts + MicrophonePermission.ts + __tests__/ + AudioAnalyserSampler.test.ts + + phaser/ + BarkBattleGameHost.ts + scenes/ + BarkBattleBootScene.ts + BarkBattlePreloadScene.ts + BarkBattleScene.ts + BarkBattleFxScene.ts + assets/ + barkBattleAssetManifest.ts + + ui/ + BarkBattleRuntimeShell.tsx + BarkBattleHud.tsx + BarkBattlePermissionPanel.tsx + BarkBattleResultPanel.tsx + BarkBattleMobileControls.tsx + BarkBattleHud.css + __tests__/ + BarkBattleHud.test.tsx + BarkBattlePermissionPanel.test.tsx + BarkBattleResultPanel.test.tsx +``` + +若后续进入 Genarrative 正式玩法类型闭环,再按 `genarrative-play-type-integration` 扩展到: + +```text +src/components/bark-battle-runtime/BarkBattleRuntimeShell.tsx +src/components/bark-battle-result/BarkBattleResultView.tsx +src/services/barkBattleRuntimeClient.ts +packages/shared/src/contracts/barkBattle.ts +server-rs/crates/shared-contracts/src/bark_battle.rs +``` + +首版不建议直接新增后端表或正式作品链路,除非产品明确要求成绩、发布和广场能力。 + +## 5. 核心 Domain 类型 + +### 5.1 基础枚举与数值约定 + +```ts +export type BarkBattlePhase = + | 'permission' + | 'calibration' + | 'countdown' + | 'playing' + | 'finished' + | 'unsupported' + | 'permission-denied' + +export type BarkBattleSide = 'player' | 'opponent' + +export type BarkBattleWinner = BarkBattleSide | 'draw' | null + +export type BarkBattleDifficulty = 'easy' | 'normal' | 'hard' +``` + +关键数值: + +- `energy`: `-100..100`,正数偏玩家侧,负数偏对手侧。 +- `currentVolume`: `0..1`,音频采样归一化后的瞬时音量。 +- `recentPeak`: `0..1`,短窗口内峰值。 +- `power`: `0..1` 或 `0..100` 二选一,建议 domain 内统一 `0..1`,HUD 显示再转百分比。 +- `remainingMs`: 单局剩余毫秒。 + +### 5.2 Snapshot + +```ts +export type BarkBattleSnapshot = { + phase: BarkBattlePhase + elapsedMs: number + remainingMs: number + countdownMs: number + energy: number + player: BarkSideState + opponent: BarkSideState + winner: BarkBattleWinner + result: BarkBattleResult | null + lastEvents: BarkBattleVisualEvent[] +} + +export type BarkSideState = { + side: BarkBattleSide + barkCount: number + currentVolume: number + recentPeak: number + combo: number + power: number + isBarking: boolean + lastBarkAtMs: number | null + maxVolume: number +} + +export type BarkBattleResult = { + winner: BarkBattleWinner + finalEnergy: number + playerBarkCount: number + playerMaxVolume: number + playerAveragePower: number + score: number +} +``` + +### 5.3 输入样本与叫声事件 + +```ts +export type BarkAudioSample = { + atMs: number + volume: number + peak: number + rms: number +} + +export type BarkDetectedEvent = { + id: string + atMs: number + side: BarkBattleSide + volume: number + strength: number + combo: number +} +``` + +### 5.4 视觉事件 + +视觉事件由 domain 或 application 生成,但只包含 plain data,不包含 Phaser 对象: + +```ts +export type BarkBattleVisualEvent = + | { + type: 'bark-word' + id: string + side: BarkBattleSide + atMs: number + strength: number + text: 'BARK' | 'WOOF' | 'WAN' | 'WANGOOF' + } + | { + type: 'shockwave' + id: string + side: BarkBattleSide + atMs: number + strength: number + } + | { + type: 'combo-burst' + id: string + side: BarkBattleSide + atMs: number + combo: number + } +``` + +Phaser 只根据这些事件播放一次性特效,并维护已消费事件 ID,避免重复播放。 + +## 6. Domain 模块职责 + +### 6.1 BarkDetector + +职责:把连续音频样本转换为有效叫声事件。 + +输入: + +- `BarkAudioSample` +- 校准后的 `ambientNoiseFloor` +- `barkThreshold` +- `minBarkGapMs` +- `minBarkDurationMs` +- `maxBarkDurationMs` + +规则建议: + +1. 音量超过动态阈值进入 candidate 状态。 +2. 峰值回落到阈值以下或持续时长达到上限时结束 candidate。 +3. candidate 持续时间在 `80ms..1200ms` 且与上一次有效叫声间隔足够时,记为一次叫声。 +4. 长时间持续噪音不应无限计数,只能按冷却和峰值回落形成新事件。 +5. MVP 不要求识别“是否真狗叫”,先基于音量峰值、时长和间隔判断。 + +### 6.2 EnergyTugOfWar + +职责:更新红蓝拉锯条。 + +建议公式: + +```text +playerPower = volumeScore * 0.65 + barkRateScore * 0.35 + comboBonus +opponentPower = opponentStrategy.tick(...) +energyDelta = (playerPower - opponentPower) * deltaSeconds * balanceFactor +energy = clamp(energy + energyDelta, -100, 100) +``` + +约束: + +- `EnergyTugOfWar` 不知道玩家来自麦克风还是 mock input。 +- `EnergyTugOfWar` 不知道 Phaser 能量条宽度。 +- 平衡参数集中在 `BarkBattleConfig`,不要散落在 Scene 或 HUD 中。 + +### 6.3 BarkBattleSession + +职责:管理局内 phase、计时、胜负和 snapshot。 + +状态机建议: + +```text +permission → calibration → countdown → playing → finished + ↘ permission-denied + ↘ unsupported +``` + +关键规则: + +- `countdown` 结束才进入 `playing`。 +- `playing` 时 `remainingMs` 随 tick 递减。 +- `remainingMs <= 0` 后进入 `finished`。 +- `energy > drawThreshold` 判定玩家胜利。 +- `energy < -drawThreshold` 判定对手胜利。 +- `abs(energy) <= drawThreshold` 判定平局。 + +### 6.4 OpponentStrategy + +职责:为单机 MVP 提供对手推动力。 + +```ts +export interface OpponentStrategy { + tick(input: OpponentTickInput): OpponentTickOutput +} +``` + +普通难度建议: + +- 周期性小叫声提供基础压力。 +- 每 3~6 秒一次短爆发。 +- 玩家大幅领先时可轻微增强,但不能追到不可赢。 + +## 7. Web Audio 输入适配 + +### 7.1 BrowserMicrophoneInput + +职责:封装浏览器麦克风权限与音频流生命周期。 + +建议 API: + +```ts +export interface MicrophoneInputPort { + isSupported(): boolean + requestPermission(): Promise + stop(): void +} + +export interface MicrophoneSession { + sample(atMs: number): BarkAudioSample + stop(): void +} +``` + +实现要点: + +1. 使用 `navigator.mediaDevices?.getUserMedia({ audio: true })`。 +2. 在用户点击“开始”后创建或 resume `AudioContext`,避免移动端自动播放策略拦截。 +3. 使用 `AnalyserNode` 读取时域数据,计算 RMS 与 peak。 +4. 输出归一化样本,不把 `MediaStream`、`AudioContext`、`AnalyserNode` 泄漏到 domain。 +5. 退出、重开、页面卸载时停止 track,避免麦克风占用残留。 + +### 7.2 校准流程 + +`calibration` 阶段建议持续 `800ms..1500ms`: + +1. 收集静默环境样本。 +2. 计算 `ambientNoiseFloor`,例如 `p75` 或均值 + 标准差。 +3. 设置动态阈值: + +```text +barkThreshold = clamp(ambientNoiseFloor + 0.12, 0.18, 0.55) +``` + +4. 若环境噪音过高,HUD 给出简短提示和“继续 / 重新校准”入口,但不要把长说明常驻在画面上。 + +### 7.3 权限与错误分类 + +```ts +export type MicrophoneFailureReason = + | 'unsupported' + | 'permission-denied' + | 'not-found' + | 'not-readable' + | 'audio-context-blocked' + | 'unknown' +``` + +前端只根据错误分类展示可操作状态:重试授权、返回、或使用调试备用输入。不要把浏览器原始错误堆栈展示给玩家。 + +## 8. Phaser Scene 切分 + +### 8.1 BarkBattleBootScene + +职责: + +- 初始化 Phaser 全局配置。 +- 注册 scale、background color、全局事件桥。 +- 不加载重资源,不处理玩法规则。 + +### 8.2 BarkBattlePreloadScene + +职责: + +- 根据 `barkBattleAssetManifest` 加载背景、狗狗 sprite、声浪 FX、拟声词 bitmap / atlas、轻量音效。 +- 使用稳定 manifest key,不在 gameplay 代码中散写文件路径。 +- 加载完成后进入 `BarkBattleScene`。 + +### 8.3 BarkBattleScene + +职责: + +- 创建横版舞台、左右狗狗、背景层、声浪层、拟声词层。 +- 每帧读取最新 `BarkBattleSnapshot`。 +- 根据 snapshot 更新: + - 狗狗 idle / bark / win / lose 动画 + - 声浪强度 + - camera shake + - transient bark words + - shockwave +- 把可选的调试输入 action 传给 controller,但不处理麦克风和规则。 + +不得在 Scene 中实现: + +- 叫声计数 +- 胜负判定 +- 能量条规则 +- 权限流程 +- 结算数据计算 + +### 8.4 BarkBattleFxScene(可选) + +如果特效复杂,可拆出叠加 Scene: + +- 专门处理拟声词、粒子、冲击波和 camera shake。 +- 通过视觉事件 ID 去重。 +- 对 `prefers-reduced-motion` 或低端设备降级。 + +首版也可以先把 FX 保持在 `BarkBattleScene` 内,但必须仍然只消费 snapshot / visual events。 + +## 9. DOM HUD 设计 + +### 9.1 HUD 层级 + +DOM HUD 建议覆盖在 Phaser Canvas 上方: + +```text +BarkBattleRuntimeShell + ├─
+ └─ +``` + +HUD 分区: + +- 顶部:红蓝声浪能量条 + 小型剩余时间。 +- 中央:仅在倒计时、关键提示或结算时短暂展示大号状态。 +- 左右边缘:双方简洁状态,例如叫声次数 / combo chip。 +- 底部角落:麦克风状态、重试、小菜单。 +- 结算:独立居中面板,显示胜负、叫声次数、最大音量、评分、再来一局、返回。 + +### 9.2 Playfield 保护 + +遵循 game UI 约束: + +1. 正常 playing 阶段保持中心和下中部 playfield 清爽,不常驻长文案。 +2. 不把规则说明、长控制说明、多段提示默认铺在画面上。 +3. 权限、设置、结算使用独立面板或弹层,不在当前面板下面展开一大块内容。 +4. 移动端优先保证顶部能量条、倒计时、狗狗和重试入口可见可点。 +5. 大动效不能遮挡顶部能量条和倒计时。 + +### 9.3 CSS 设计建议 + +- 使用局部 CSS class 或 CSS module,避免污染全站。 +- 使用 CSS 变量定义主题: + - `--bark-player-color` + - `--bark-opponent-color` + - `--bark-panel-bg` + - `--bark-safe-bottom` +- 使用 `dvh` / `svh` 和 safe-area inset 处理移动端地址栏与刘海。 +- `pointer-events` 分层:HUD 容器默认 `pointer-events: none`,按钮和面板恢复 `pointer-events: auto`。 + +## 10. 移动端与权限降级 + +### 10.1 移动端输入约束 + +移动端浏览器通常要求用户手势才能启动 AudioContext。开局流程必须是: + +```text +玩家点击“开始” → requestPermission → 创建/恢复 AudioContext → calibration → countdown → playing +``` + +不要在页面加载时自动请求或自动启动 AudioContext。 + +### 10.2 响应式布局 + +移动端建议: + +- 横屏优先呈现完整舞台;竖屏可保持舞台居中并缩小 HUD。 +- 顶部能量条高度保持可读,但不要占满大面积。 +- 结算面板宽度使用 `min(92vw, 420px)`。 +- 底部按钮避开 `env(safe-area-inset-bottom)`。 +- 非关键设置折叠进小菜单。 + +### 10.3 权限失败降级 + +权限失败时: + +- `unsupported`:展示“当前浏览器不支持麦克风输入”,提供返回入口。 +- `permission-denied`:展示简短说明和“重新授权”入口。 +- `not-found`:提示未检测到麦克风,提供返回入口。 +- `audio-context-blocked`:提示点击重试。 + +可选开发调试降级: + +- 本地 dev 可启用键盘 mock input,例如按住空格模拟音量峰值。 +- mock input 必须标记为开发/调试能力,不作为正式竞技能力。 + +## 11. 测试策略 + +### 11.1 Domain 单元测试(优先) + +目标:不接 Phaser、不接 DOM、不接 Web Audio。 + +建议测试: + +- `BarkDetector`:超过阈值且间隔足够时计为一次有效叫声。 +- `BarkDetector`:持续噪音不会无限计数。 +- `BarkDetector`:低于环境噪音阈值不计入叫声。 +- `EnergyTugOfWar`:玩家 power 高于对手时 energy 向玩家侧移动。 +- `EnergyTugOfWar`:energy clamp 在 `-100..100`。 +- `BarkBattleSession`:倒计时结束进入 playing。 +- `BarkBattleSession`:剩余时间归零进入 finished。 +- `BarkBattleSession`:按 energy 和 drawThreshold 判定胜 / 负 / 平。 + +### 11.2 Application 测试 + +目标:验证输入样本、AI、tick 和 snapshot 编排。 + +建议测试: + +- 权限允许后进入校准,再进入倒计时。 +- 权限拒绝后 phase 为 `permission-denied`,不进入 playing。 +- 提交 mock audio sample 后 snapshot 中玩家状态更新。 +- AI 对手 power 参与能量条拉锯。 +- `lastEvents` 只发布新增视觉事件。 + +### 11.3 HUD 组件测试 + +目标:验证 snapshot 到 DOM 的展示映射。 + +建议测试: + +- playing 阶段展示倒计时和能量条。 +- energy 正负值映射到玩家 / 对手侧比例。 +- permission-denied 展示重试入口。 +- finished 展示胜负、叫声次数和再来一局。 +- 移动端 class / 结构不依赖 Phaser Canvas 才能渲染。 + +### 11.4 Phaser 集成与 smoke + +自动化层面不强行在 Vitest 中完整启动 Phaser。建议: + +- 用 adapter mock 测试 Scene 消费 snapshot 的纯映射函数。 +- 浏览器 smoke 验证真实 Canvas、Web Audio 和动画。 +- 若后续引入 Playwright,再做最小视觉和交互 smoke。 + +## 12. 验证命令建议 + +文档阶段只需要编码检查和 diff 检查: + +```bash +npm run check:encoding +git diff -- docs/technical/BARK_BATTLE_2D_RUNTIME_TECHNICAL_PLAN_2026-05-11.md +``` + +后续实现 domain 后建议: + +```bash +npm run test -- --run src/games/bark-battle/domain/**/*.test.ts +npm run typecheck +npm run check:encoding +``` + +后续实现 HUD 后建议: + +```bash +npm run test -- --run src/games/bark-battle/ui/**/*.test.tsx +npm run lint:eslint +npm run typecheck +npm run check:encoding +``` + +后续接入浏览器 runtime 后建议: + +```bash +npm run dev:web +# 人工 smoke:授权麦克风 → 校准 → 发声 → 能量条变化 → 结算 → 再来一局 +``` + +若未来接入 Genarrative 正式玩法类型、后端持久化或发布链路,再追加对应契约、api-server 和 SpacetimeDB 验证;首版 runtime 原型不应提前新增这些命令作为门槛。 + +## 13. 后续落地顺序 + +建议后续实现按以下顺序推进: + +1. 先建 `domain/` 和纯单元测试。 +2. 实现 `BarkDetector`、`EnergyTugOfWar`、`BarkBattleSession` 的最小规则。 +3. 建 `application/` controller,用 mock audio sample 跑通 snapshot。 +4. 实现 DOM HUD 的 permission / energy / timer / result 展示。 +5. 接入 `BrowserMicrophoneInput` 和校准流程。 +6. 接入 Phaser host、Scene 和 asset manifest,占位素材先跑通视觉反馈。 +7. 做移动端视口和权限失败 smoke。 +8. 产品确认后再决定是否进入正式玩法类型、作品发布和后端真相链。 + +## 14. 关键技术决策 + +1. 默认采用 Phaser 3 + TypeScript + Vite,符合 2D 浏览器游戏默认路线。 +2. 核心 simulation 放在纯 TypeScript domain,严格不依赖 Phaser / Web Audio / DOM。 +3. Web Audio 只作为输入 adapter,输出归一化 `BarkAudioSample`。 +4. Phaser Scene 是 renderer,只消费 snapshot 和 visual events,不承载规则真相。 +5. HUD 使用 DOM overlay,承载权限、能量条、倒计时、结算和移动端响应式布局。 +6. MVP 不做复杂狗叫语义识别,先用音量峰值、持续时长、冷却和环境噪音校准。 +7. MVP 建议玩家 vs AI 单机 runtime,正式成绩、排行榜、发布和奖励后续再交给后端链路。 diff --git a/docs/technical/README.md b/docs/technical/README.md index 913b62c3..fefb6a1f 100644 --- a/docs/technical/README.md +++ b/docs/technical/README.md @@ -4,6 +4,7 @@ ## 文档列表 +- [BARK_BATTLE_2D_RUNTIME_TECHNICAL_PLAN_2026-05-11.md](./BARK_BATTLE_2D_RUNTIME_TECHNICAL_PLAN_2026-05-11.md):冻结“汪汪声浪大作战 / bark-battle”2D 浏览器 runtime 技术方案,明确 Phaser + TypeScript + Vite 选型、纯 TS simulation 与 Phaser renderer/DOM HUD 边界、Web Audio 输入适配、移动端权限降级和后续测试验证命令。 - [CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md](./CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md):冻结儿童动作识别互动玩法 Demo 固定热身关的开发落地规格,覆盖横屏展示、摄像头背景虚化、角色剪影、绿色圆环 2 秒保持、动作教学、当前会话内空间边界记录和后续关卡安全暂停规则。 - [RUNTIME_INPUT_DEVICE_ABSTRACTION_2026-05-10.md](./RUNTIME_INPUT_DEVICE_ABSTRACTION_2026-05-10.md):记录运行态输入设备抽象层,明确鼠标、触控、mocap 等设备统一归一为通用拖拽语义,玩法组件只负责解释目标和落点。 - [RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md](./RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md):记录 `server-rs` Cargo 依赖集中配置口径,第三方版本和 workspace 内部 crate path 统一维护在根 `server-rs/Cargo.toml`,成员 crate 只保留 feature/optional 差异。