# bark-battle 后端 DDD 技术方案(2026-05-11) ## 1. 背景、范围与非目标 ### 1.1 背景 `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 在接入平台作品、正式游玩埋点、成绩、排行榜和发布闭环时承接不属于表现层的业务真相。 本方案遵循当前 Genarrative 后端路线:`server-rs` + Axum + SpacetimeDB。DDD 边界保持为: - `module-*`:纯领域规则与 use case 约束。 - `spacetime-module`:SpacetimeDB 表、reducer、migration。 - `spacetime-client`:SpacetimeDB 绑定调用 facade。 - `api-server`:HTTP / SSE / BFF 门面与权限校验入口。 - `shared-contracts`:前后端共享 DTO、请求、响应、错误码与 schema 约束。 - `platform-*`:作品、发布、广场、埋点等平台能力的领域边界。 ### 1.2 本文范围 本文覆盖后端方案,不实现代码: 1. bark-battle 玩法接入级别建议。 2. 后端 DDD 分层与职责边界。 3. shared contracts 草案。 4. SpacetimeDB 数据模型草案。 5. HTTP API / facade 草案。 6. SpacetimeDB migration 与绑定生成策略。 7. 安全、隐私与反作弊约束。 8. TDD / 验收顺序与可执行命令。 9. 与现有前端方案和 BDD 文档的关系。 ### 1.3 非目标 MVP 明确不做: - 不保存原始麦克风音频、音频片段、可还原语音内容的 waveform 或频谱明细。 - 不做实时多人在线对战;首版只支持本地 runtime + 后端记录派生结果。 - 不做复杂 AI 声纹识别、狗叫语义识别、身份声纹比对或真人/动物声纹分类。 - 不由前端直接写正式成绩、排行榜或作品发布状态。 - 不把 Phaser / Web Audio / DOM HUD 逻辑迁入后端。 - 不在本方案中实现代码、建表或生成绑定。 ## 2. 玩法接入级别建议 ### 2.1 推荐首版闭环 建议先支持“本地 runtime + 可发布配置化作品 + 单局结果记录 / 可选排行榜”的闭环: 1. 创作者创建 bark-battle 草稿,配置标题、描述、狗狗主题、背景、难度、单局时长、音量阈值、AI 对手参数和排行榜开关。 2. 发布为稳定作品 ID,`playTypeId = "bark-battle"`。 3. 玩家从作品页或广场进入 runtime,前端获取发布态 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`。 ### 2.2 后续增强路径 后续再考虑多人实时: - Phase 2:排行榜、挑战分享、个人历史成绩、作品统计面板。 - Phase 3:异步影子对手 / ghost replay,但仍不保存原始音频,只保存低维派生曲线或聚合指标。 - Phase 4:实时多人对战协议,需要独立同步模型、房间服务、延迟补偿、断线恢复与更严格反作弊;不应混入 MVP。 ## 3. DDD 分层设计 ### 3.1 总体分层 ```text frontend/runtime -> api-server HTTP BFF -> shared-contracts DTO -> spacetime-client facade -> generated SpacetimeDB bindings -> spacetime-module reducers / tables / migration.rs -> module-bark-battle pure domain -> platform-work / platform-publish / platform-tracking / platform-leaderboard ``` ### 3.2 `module-bark-battle` 职责 建议新增 `server-rs/crates/module-bark-battle/`,只放纯领域规则,不依赖 Axum、SpacetimeDB SDK、HTTP、数据库绑定或前端类型。 职责: - 校验 bark-battle 配置合法性。 - 定义配置版本兼容规则。 - 计算提交结果的派生分数区间与胜负判定是否自洽。 - 计算 `ScoreSummary`、排行榜排序分数、统计指标。 - 定义反作弊基础规则:时长范围、有效叫声次数上限、峰值范围、能量范围、提交窗口、run 状态机。 不职责: - 不访问数据库。 - 不处理 HTTP 请求。 - 不生成 SpacetimeDB 表。 - 不处理麦克风音频采样。 ### 3.3 `shared-contracts` 职责 建议新增 `server-rs/crates/shared-contracts/src/bark_battle.rs`,并在前端共享契约生成流程中对齐同名 DTO。 职责: - 定义草稿、发布态 runtime config、work summary、run start、run finish、run result、score summary、leaderboard DTO。 - 定义 request / response / error code。 - 保持字段命名、枚举值、时间单位和数值范围稳定。 约束: - DTO 可以表达业务数据,但不承载领域算法。 - 前端不得手写与后端不一致的正式契约;如果存在 TypeScript mirror,需要由契约生成或测试保证一致。 ### 3.4 `spacetime-module` 职责 建议在现有 SpacetimeDB 模块边界内新增 bark-battle 表与 reducer,或按当前仓库约定新增独立模块文件。职责: - 定义表:作品配置、发布版本、runtime run、score/stat、leaderboard entry。 - 定义 reducer / procedure:保存草稿、发布版本、开始 run、结束 run、写排行榜、查询投影所需索引。 - 维护 migration。 必须明确: - 所有表结构变更进入 `migration.rs`。 - SpacetimeDB 绑定通过既有生成命令生成。 - 不手改生成物,不手改 generated bindings。 ### 3.5 `spacetime-client` 职责 职责: - 封装 generated bindings,向 `api-server` 提供稳定 facade。 - 隐藏 reducer 名称、表订阅细节和绑定类型差异。 - 将 SpacetimeDB 错误转换为后端内部错误模型。 不职责: - 不放业务规则主逻辑。 - 不绕过 `module-bark-battle` 做胜负和分数裁决。 ### 3.6 `api-server` 职责 Axum HTTP / BFF 门面职责: - 鉴权、用户上下文、work 权限、发布态读取权限。 - 解析请求、调用 domain 校验、调用 spacetime-client facade。 - 将内部错误映射成 HTTP status + shared error code。 - 负责正式作品级游玩埋点入口,统一写 `work_play_start`。 - 为前端 runtime 提供一次性 start token / run id,避免匿名 finish 直接刷榜。 ### 3.7 `platform-*` 职责 - `platform-work`:稳定作品 ID、作品所有权、作品摘要、作品状态。 - `platform-publish`:草稿到发布态版本、config version、发布可见性。 - `platform-tracking`:统一埋点,尤其 `work_play_start`。 - `platform-leaderboard`:若已有通用排行榜能力,bark-battle 只提供 score projection,不重复建设平台级排名系统。 ### 3.8 `frontend/runtime` 职责边界 前端只做: - Phaser / DOM HUD 表现。 - Web Audio 采样、环境噪声校准和本地即时反馈。 - 本地临时 UI 状态:权限、倒计时、动画、结算展示。 - 调用 start / finish / leaderboard API。 前端不得承接正式业务真相: - 不直接决定正式排行榜结果。 - 不直接写作品发布状态。 - 不绕过后端写成绩。 - 不上传原始麦克风音频。 ## 4. shared contracts 设计草案 以下为字段草案,具体 Rust / TypeScript 命名按仓库契约规范落地。 ### 4.1 BarkBattleDraft ```text BarkBattleDraft { draftId: string ownerUserId: string playTypeId: "bark-battle" title: string description?: string theme: BarkBattleTheme runtimeConfig: BarkBattleRuntimeConfigDraft leaderboardEnabled: boolean visibility: "private" | "unlisted" | "public" createdAt: string updatedAt: string } ``` ### 4.2 BarkBattleRuntimeConfig ```text BarkBattleRuntimeConfig { workId: string configVersion: number playTypeId: "bark-battle" durationMs: number // MVP 默认 30000 energyMin: number // 默认 -100 energyMax: number // 默认 100 winEnergyThreshold: number // 可选:低于阈值可平局 barkThreshold: number // 归一化 0..1 peakWeight: number barkCountWeight: number rhythmWeight: number opponent: { difficulty: "easy" | "normal" | "hard" basePower: number variance: number } theme: { playerDogSkin: string opponentDogSkin: string stageId: string soundPackId?: string } leaderboardEnabled: boolean publishedAt: string } ``` ### 4.3 WorkSummary ```text BarkBattleWorkSummary { workId: string playTypeId: "bark-battle" title: string description?: string coverAssetId?: string authorUserId: string authorDisplayName?: string configVersion: number leaderboardEnabled: boolean totalPlayCount: number publishedAt: string updatedAt: string } ``` ### 4.4 RunStart ```text BarkBattleRunStartRequest { workId: string configVersion: number sourceRoute?: string clientRuntimeVersion?: string } BarkBattleRunStartResponse { runId: string runToken: string workId: string configVersion: number serverStartedAt: string expiresAt: string runtimeConfig: BarkBattleRuntimeConfig } ``` ### 4.5 RunFinish ```text BarkBattleRunFinishRequest { runId: string runToken: string workId: string configVersion: number clientStartedAt?: string clientFinishedAt?: string elapsedMs: number finalEnergy: number clientWinner: "player" | "opponent" | "draw" metrics: BarkBattleDerivedMetrics clientRuntimeVersion?: string } BarkBattleDerivedMetrics { peakVolumeMax: number // 0..1 peakVolumeAvg: number // 0..1 validBarkCount: number rhythmHitCount: number longestCombo: number sampleWindowCount?: number calibrationNoiseFloor?: number // 0..1 } ``` ### 4.6 RunResult ```text BarkBattleRunResult { runId: string workId: string userId?: string configVersion: number accepted: boolean serverWinner: "player" | "opponent" | "draw" finalEnergy: number score: number scoreSummary: BarkBattleScoreSummary leaderboardEntry?: BarkBattleLeaderboardEntry antiCheatFlags: string[] finishedAt: string } ``` ### 4.7 ScoreSummary ```text BarkBattleScoreSummary { score: number grade: "S" | "A" | "B" | "C" | "D" finalEnergy: number winMargin: number validBarkCount: number peakVolumeMax: number rhythmHitCount: number longestCombo: number elapsedMs: number } ``` ### 4.8 Leaderboard 可选类型 ```text BarkBattleLeaderboardQuery { workId: string configVersion?: number period: "all" | "daily" | "weekly" limit: number cursor?: string } BarkBattleLeaderboardEntry { rank?: number runId: string workId: string userId?: string displayName?: string score: number scoreSummary: BarkBattleScoreSummary createdAt: string } BarkBattleLeaderboardResponse { workId: string entries: BarkBattleLeaderboardEntry[] viewerBest?: BarkBattleLeaderboardEntry nextCursor?: string } ``` ## 5. 数据模型草案 ### 5.1 作品配置表 建议表:`bark_battle_work_config`。 字段草案: - `work_id`:稳定作品 ID,关联平台作品。 - `draft_id`:草稿 ID,可选。 - `owner_user_id`:创作者。 - `play_type_id`:固定 `bark-battle`。 - `config_version`:发布配置版本。 - `title`、`description`、`cover_asset_id`。 - `runtime_config_json`:发布态 runtime 配置 JSON;字段需由 shared contracts 校验。 - `leaderboard_enabled`。 - `status`:`draft` / `published` / `archived`。 - `created_at`、`updated_at`、`published_at`。 约束: - 同一 `work_id + config_version` 不可变;新发布生成新版本。 - runtime 请求只读发布态配置。 ### 5.2 runtime run 表 建议表:`bark_battle_runtime_run`。 字段草案: - `run_id`。 - `run_token_hash`:保存 token hash,不保存明文 token。 - `work_id`。 - `config_version`。 - `user_id`:匿名时可空或使用匿名会话 ID。 - `source_route`。 - `status`:`started` / `finished` / `rejected` / `expired`。 - `server_started_at`、`server_finished_at`、`expires_at`。 - `client_elapsed_ms`。 - `final_energy`。 - `client_winner`、`server_winner`。 - `anti_cheat_flags_json`。 - `client_runtime_version`。 ### 5.3 score / stat 表 建议表:`bark_battle_score_record`。 字段草案: - `score_id`。 - `run_id`。 - `work_id`。 - `config_version`。 - `user_id`。 - `score`。 - `grade`。 - `final_energy`。 - `valid_bark_count`。 - `peak_volume_max`。 - `peak_volume_avg`。 - `rhythm_hit_count`。 - `longest_combo`。 - `elapsed_ms`。 - `metrics_json`:只保存派生聚合指标。 - `created_at`。 明确禁止: - 不保存原始麦克风音频。 - 不保存可还原语音的 PCM、Opus、MP3、WAV、base64 音频、逐帧 waveform。 - 不保存高精度声纹向量。 ### 5.4 leaderboard 表 若平台已有通用排行榜,优先复用平台 leaderboard 投影;否则可新增 `bark_battle_leaderboard_entry`: - `entry_id`。 - `work_id`。 - `config_version`。 - `run_id`。 - `user_id`。 - `score`。 - `tie_breaker_energy`。 - `tie_breaker_elapsed_ms`。 - `created_at`。 排序建议: 1. `score` 降序。 2. `final_energy` / `winMargin` 降序。 3. `elapsed_ms` 更接近配置时长者优先,避免异常短局刷分。 4. `created_at` 升序或按平台既有规则。 ## 6. API 草案 路径仅为草案,落地时按 `api-server` 当前路由命名规范调整。 ### 6.1 创建 / 保存草稿 ```text POST /api/bark-battle/drafts PUT /api/bark-battle/drafts/{draftId} GET /api/bark-battle/drafts/{draftId} ``` 职责: - 仅创作者可创建和保存。 - 校验 `playTypeId = bark-battle`。 - 调用 `module-bark-battle` 校验 runtime config 范围。 ### 6.2 发布 / 获取作品 ```text POST /api/bark-battle/drafts/{draftId}/publish GET /api/works/{workId}/bark-battle GET /api/bark-battle/works/{workId}/runtime-config ``` 职责: - 发布生成稳定 `workId` 和递增 `configVersion`。 - 获取作品只返回发布态配置与展示摘要。 - 未发布或无权限作品返回明确错误。 ### 6.3 start runtime ```text POST /api/bark-battle/runs/start ``` 请求:`BarkBattleRunStartRequest`。响应:`BarkBattleRunStartResponse`。 职责: - 校验作品存在、发布态、可游玩。 - 校验 config version,必要时返回最新版本。 - 创建 `run_id` 与一次性 `run_token`。 - 写正式作品级游玩埋点:`work_play_start`。 埋点要求: ```text event_key: work_play_start scope_kind: work scope_id: <稳定作品 ID> metadata: { playType: "bark-battle", workId: "", sourceRoute: "", userId: "" } ``` ### 6.4 finish runtime ```text POST /api/bark-battle/runs/{runId}/finish ``` 请求:`BarkBattleRunFinishRequest`。响应:`BarkBattleRunResult`。 职责: - 校验 run token。 - 校验 run 仍处于 `started` 且未过期。 - 校验 `work_id + config_version` 与 start 时一致。 - 校验时长、finalEnergy、metrics 范围。 - 使用 `module-bark-battle` 生成服务端认可的 `serverWinner`、`score`、`ScoreSummary`、`antiCheatFlags`。 - 写 `bark_battle_runtime_run` finish 状态与 `bark_battle_score_record`。 - 如开启 leaderboard 且结果 accepted,写排行榜。 ### 6.5 作品级游玩埋点 `start runtime` 内部必须触发统一埋点;不建议前端单独调用一个 bark-battle 专用埋点 API。若平台已有通用 tracking API,则 api-server 内部调用 platform tracking facade: ```text track_event( event_key = "work_play_start", scope_kind = "work", scope_id = workId, metadata = { playType, workId, sourceRoute, userId } ) ``` ### 6.6 可选排行榜 ```text GET /api/bark-battle/works/{workId}/leaderboard?period=all&limit=50&cursor=... GET /api/bark-battle/works/{workId}/leaderboard/me ``` 职责: - 只读已接受成绩。 - 支持分页。 - 支持匿名用户时隐藏或弱化身份展示。 - 若作品关闭排行榜,返回空投影或明确 disabled 状态。 ## 7. SpacetimeDB 与 migration 策略 ### 7.1 表 / reducer / procedure 边界 SpacetimeDB 侧只承载持久化、索引、reducer 原子写入和可查询投影,不承载 HTTP 鉴权或前端表现。 建议 reducer / procedure: - `bark_battle_save_draft_config`:保存草稿配置。 - `bark_battle_publish_work_config`:发布配置版本。 - `bark_battle_start_run`:创建 runtime run。 - `bark_battle_finish_run`:结束 run 并写 score。 - `bark_battle_upsert_leaderboard_entry`:写排行榜投影。 - `bark_battle_get_work_runtime_config`:读取发布态配置。 - `bark_battle_get_leaderboard`:读取排行榜投影。 如当前架构要求 reducer 仅由 `spacetime-client` 调用,则 api-server 不直接操作 SpacetimeDB SDK。 ### 7.2 migration.rs 所有表结构变更必须进入 `migration.rs`: - 新增 bark-battle 表时写显式 migration。 - 新增索引、唯一约束或版本字段时写 migration。 - 从草稿 JSON 拆字段时写数据迁移说明。 - 不允许只改 Rust struct 而不补 migration。 ### 7.3 绑定生成 涉及 SpacetimeDB schema / reducer 变更后: 1. 运行仓库既有 SpacetimeDB 绑定生成命令。 2. 检查 generated bindings 变化。 3. `spacetime-client` 只引用生成物,不手改生成物。 4. shared contracts 与 generated bindings 的差异通过 facade 消化,不让前端直接依赖数据库绑定。 明确要求:不手改生成物,不手改 generated bindings,不用临时复制粘贴类型绕过生成流程。 ### 7.4 api-server facade `api-server` 通过 `spacetime-client` facade 调用 SpacetimeDB: ```text BarkBattleService -> BarkBattleDomainPolicy -> BarkBattleSpacetimeClient -> generated bindings ``` 这样可以保证: - HTTP 层易测试。 - domain 纯函数可独立测试。 - SpacetimeDB 绑定变更不会扩散到 route handler。 ## 8. 安全、隐私与反作弊 ### 8.1 隐私 - 不上传原始音频。 - 不保存原始音频。 - 不保存可还原用户声音的高精度采样曲线。 - 只保存派生指标:峰值、均值、有效叫声次数、节奏命中、最终能量、分数、耗时。 - 前端权限文案必须说明麦克风只用于本地玩法输入,MVP 不上传原始声音。 ### 8.2 不信任前端胜负 后端不能直接信任: - `clientWinner`。 - `score`。 - `finalEnergy`。 - `elapsedMs`。 - `validBarkCount`。 后端必须校验并重算服务端认可结果。MVP 因不上传音频,无法完全证明声音真实性,但仍需做边界反作弊。 ### 8.3 校验规则 必须校验: - run token 是否匹配且未使用。 - run 是否未过期。 - `work_id + config_version` 是否与 start 时一致。 - 用户是否有权限游玩该 work。 - 提交时长是否接近配置时长,例如 30 秒局允许少量网络 / 页面调度误差。 - `finalEnergy` 是否在配置范围。 - `peakVolumeMax`、`peakVolumeAvg` 是否在 `0..1`。 - `validBarkCount`、`rhythmHitCount`、`longestCombo` 是否在物理合理上限。 - `clientStartedAt` / `clientFinishedAt` 与服务端时间窗口是否合理。 - 同一用户 / 匿名会话的频率限制。 ### 8.4 反作弊处理 建议结果状态: - `accepted`:写 score,可进入排行榜。 - `accepted_with_flags`:写 score,但标记异常,默认不入榜或降低可信度。 - `rejected`:不入榜,只记录 run 失败原因。 常见 flags: - `elapsed_too_short` - `elapsed_too_long` - `metric_out_of_range` - `config_version_mismatch` - `token_invalid` - `duplicate_finish` - `rate_limited` - `impossible_bark_count` ## 9. BDD 行为场景、TDD 落地顺序与验收命令 本任务只写方案,不执行代码实现。后续落地必须先用 BDD 锁定可观察行为,再按 TDD 做 RED-GREEN-REFACTOR。没有先失败的测试,不进入生产代码实现。 ### 9.1 BDD 场景清单 以下场景用于约束后端行为,场景标题应映射到后续 Rust 测试名、API 测试名或 smoke 用例名。 #### 功能: bark-battle 发布态作品运行配置 ```gherkin 功能: bark-battle 发布态作品运行配置 为了让玩家进入稳定的声控狗叫对战作品 作为已发布作品的玩家 我希望后端只返回发布态且版本一致的运行配置 背景: 假如平台中存在 playTypeId 为 "bark-battle" 的作品配置 场景: 玩家请求已发布作品的 runtime config 假如作品处于 published 状态 而且请求携带的 configVersion 与当前发布版本一致 当玩家请求 bark-battle runtime config 那么后端应返回 BarkBattleRuntimeConfig 而且响应中的 workId 应为稳定作品 ID 而且响应中的 playTypeId 应为 "bark-battle" 而且响应不应包含草稿态配置或创作者私有字段 场景: 玩家请求未发布作品的 runtime config 假如作品处于 draft 状态 当玩家请求 bark-battle runtime config 那么后端应返回不可游玩的错误 而且不应创建 runtime run 场景: 玩家携带过期配置版本进入 runtime 假如作品已发布 configVersion 为 3 的版本 当玩家使用 configVersion 为 2 的请求开始 runtime 那么后端应拒绝开始本局或返回最新版本提示 而且不应写入正式成绩 ``` #### 功能: bark-battle runtime start 与统一游玩埋点 ```gherkin 功能: bark-battle runtime start 与统一游玩埋点 为了统计正式作品游玩行为 作为数据分析人员 我希望玩家开始 bark-battle 正式游玩时写入统一 work_play_start 事件 场景: 玩家成功开始已发布作品运行态 假如存在一个已发布的 bark-battle 作品 而且玩家有权限游玩该作品 当玩家调用 start runtime 那么后端应创建状态为 started 的 run 而且应返回 runId、一次性 runToken 与 runtimeConfig 而且应写入 work_play_start 事件 而且事件 scope_kind 应为 work 而且事件 scope_id 应为稳定作品 ID 而且 metadata 应包含 playType、workId、sourceRoute 和 userId 场景: 无权限玩家尝试开始运行态 假如作品不可被当前玩家访问 当玩家调用 start runtime 那么后端应返回权限错误 而且不应创建 run 而且不应写入 work_play_start 事件 ``` #### 功能: bark-battle runtime finish 派生成绩提交 ```gherkin 功能: bark-battle runtime finish 派生成绩提交 为了保护用户隐私并保证成绩可信 作为后端服务 我只接受派生指标并重新裁决正式结果 场景: 玩家完成一局并提交合法派生指标 假如玩家已经成功 start runtime 而且 run 处于 started 状态 而且提交的 runToken 与 start 返回一致 当玩家提交 elapsedMs、finalEnergy 和 BarkBattleDerivedMetrics 那么后端应校验派生指标范围 而且应重新计算 serverWinner、score 和 ScoreSummary 而且应把 run 状态改为 finished 而且响应中的 accepted 应为 true 而且请求与持久化记录中不应包含原始麦克风音频 场景: 玩家重复提交同一个 run 的 finish 假如某个 run 已经处于 finished 状态 当玩家再次提交 finish 那么后端应拒绝重复提交 而且不应重复写入成绩 而且不应重复写入排行榜 场景: 玩家提交不合理的时长 假如作品配置 durationMs 为 30000 当玩家提交 elapsedMs 明显短于配置时长 那么后端应返回 rejected 或 accepted_with_flags 而且 antiCheatFlags 应包含 elapsed_too_short 而且该结果默认不应进入排行榜 场景: 玩家提交超出范围的声音指标 假如玩家已经成功 start runtime 当玩家提交 peakVolumeMax 大于 1 或 validBarkCount 超过物理合理上限 那么后端应拒绝或打 flag 而且 antiCheatFlags 应包含 metric_out_of_range 或 impossible_bark_count ``` #### 功能: bark-battle 排行榜投影 ```gherkin 功能: bark-battle 排行榜投影 为了让玩家比较同一作品内的有效成绩 作为玩家 我希望排行榜只展示被后端接受的成绩 场景: 作品开启排行榜且成绩被接受 假如作品 leaderboardEnabled 为 true 而且玩家提交的 finish 结果 accepted 为 true 当后端完成成绩写入 那么后端应写入或更新该作品的排行榜投影 而且排行榜排序应优先按 score 降序 场景: 作品关闭排行榜 假如作品 leaderboardEnabled 为 false 当玩家完成一局合法成绩 那么后端应记录 run 与 score 而且不应写入排行榜 entry 场景: 带反作弊 flag 的成绩不进入默认榜单 假如玩家提交的结果为 accepted_with_flags 当后端处理排行榜投影 那么默认排行榜不应展示该成绩 而且该 run 的反作弊标记应可供后台审计 ``` #### 功能: bark-battle 隐私边界 ```gherkin 功能: bark-battle 隐私边界 为了保护玩家声音隐私 作为平台 我不能上传或保存原始麦克风音频 场景: finish 请求包含原始音频字段 假如前端误传 audio、audioBase64、waveform 或 pcmSamples 字段 当后端解析 finish 请求 那么后端应拒绝请求或忽略并记录非法字段 而且持久化记录中不应出现可还原声音的数据 场景: 正常 finish 只提交派生聚合指标 假如玩家完成一局 bark-battle 当玩家提交 finish 请求 那么请求体应只包含峰值、均值、有效叫声次数、节奏命中、最终能量和耗时等派生指标 而且后端只保存这些派生指标与裁决结果 ``` ### 9.2 BDD 到测试映射 | BDD 场景 | 测试层级 | 建议目标文件 | RED 期望 | | --- | --- | --- | --- | | 玩家请求已发布作品的 runtime config | contract + API | `server-rs/crates/shared-contracts/src/bark_battle.rs`、`server-rs/crates/api-server/src/bark_battle.rs` | 新 DTO / route 不存在导致编译或断言失败 | | 玩家请求未发布作品的 runtime config | domain + API | `server-rs/crates/module-bark-battle/src/domain.rs`、`server-rs/crates/api-server/src/bark_battle.rs` | 未发布状态未被拒绝 | | 玩家携带过期配置版本进入 runtime | domain | `server-rs/crates/module-bark-battle/src/domain.rs` | configVersion mismatch 未返回错误 | | 玩家成功开始已发布作品运行态 | API + integration | `server-rs/crates/api-server/src/bark_battle.rs` | start 未返回 runToken 或未写 tracking draft | | 无权限玩家尝试开始运行态 | API | `server-rs/crates/api-server/src/bark_battle.rs` | 无权限仍创建 run 或写埋点 | | 玩家完成一局并提交合法派生指标 | domain + facade | `server-rs/crates/module-bark-battle/src/domain.rs`、`server-rs/crates/spacetime-client/src/bark_battle.rs` | score / serverWinner 未由后端计算 | | 玩家重复提交同一个 run 的 finish | SpacetimeDB reducer / facade | `server-rs/crates/spacetime-module/src/...`、`server-rs/crates/spacetime-client/src/bark_battle.rs` | duplicate finish 被接受 | | 玩家提交不合理的时长 | domain | `server-rs/crates/module-bark-battle/src/domain.rs` | 未产生 elapsed_too_short flag | | 玩家提交超出范围的声音指标 | domain | `server-rs/crates/module-bark-battle/src/domain.rs` | 未产生 metric_out_of_range / impossible_bark_count | | 作品开启排行榜且成绩被接受 | domain + projection | `server-rs/crates/module-bark-battle/src/domain.rs`、SpacetimeDB reducer 测试 | accepted 成绩未生成 leaderboard projection | | 作品关闭排行榜 | domain + projection | `server-rs/crates/module-bark-battle/src/domain.rs`、SpacetimeDB reducer 测试 | leaderboardEnabled=false 仍写榜 | | 带反作弊 flag 的成绩不进入默认榜单 | domain | `server-rs/crates/module-bark-battle/src/domain.rs` | flagged 成绩仍进入默认榜 | | finish 请求包含原始音频字段 | contract + API | `server-rs/crates/shared-contracts/src/bark_battle.rs`、API 反序列化测试 | DTO 接受或持久化原始音频字段 | | 正常 finish 只提交派生聚合指标 | contract | `server-rs/crates/shared-contracts/src/bark_battle.rs` | DTO 缺字段或包含隐私风险字段 | ### 9.3 TDD RED-GREEN-REFACTOR 切片 每个切片必须先写失败测试并运行到预期失败,再写最小实现。 #### Slice 1: shared contracts 固定请求/响应形状 RED: ```bash cd server-rs cargo test -p shared-contracts bark_battle_contract_uses_camel_case --no-default-features ``` 先新增测试,断言: - `BarkBattleRunStartRequest` 序列化为 `workId`、`configVersion`、`sourceRoute`。 - `BarkBattleRunFinishRequest` 只包含 `runId`、`runToken`、`workId`、`configVersion`、`elapsedMs`、`finalEnergy`、`clientWinner`、`metrics`。 - `metrics` 只包含派生聚合字段,不包含 `audio`、`audioBase64`、`waveform`、`pcmSamples`。 GREEN:新增 `shared-contracts/src/bark_battle.rs` 与 `lib.rs` 导出,让测试通过。 REFACTOR:统一枚举命名、字段注释和默认值策略。 #### Slice 2: domain 校验 runtime config 与 finish metrics RED: ```bash cd server-rs cargo test -p module-bark-battle validates_runtime_config_and_finish_metrics --no-default-features ``` 先新增测试,断言: - `playTypeId` 必须为 `bark-battle`。 - `durationMs`、`energyMin`、`energyMax`、`barkThreshold` 必须在合法范围。 - `peakVolumeMax`、`peakVolumeAvg` 必须在 `0..1`。 - 过短时长产生 `elapsed_too_short`。 - 不可能叫声次数产生 `impossible_bark_count`。 GREEN:新增 `module-bark-battle` 纯领域类型与校验函数。 REFACTOR:把常量收敛为领域常量,避免 magic number 分散。 #### Slice 3: domain 重新裁决成绩与排行榜资格 RED: ```bash cd server-rs cargo test -p module-bark-battle scores_finish_result_and_filters_leaderboard --no-default-features ``` 先新增测试,断言: - 后端根据 `finalEnergy` 重算 `serverWinner`,不直接信任 `clientWinner`。 - 合法胜利生成 `accepted=true` 与稳定 `ScoreSummary`。 - 带 flag 的结果默认 `leaderboardEligible=false`。 - `leaderboardEnabled=false` 时不生成榜单投影。 GREEN:补 `score_finish_result(...)`、`is_leaderboard_eligible(...)` 等纯函数。 REFACTOR:拆分 score、winner、anti-cheat、leaderboard 四组小函数。 #### Slice 4: SpacetimeDB 表、reducer 与 migration RED: ```bash cd server-rs cargo check -p spacetime-module --no-default-features cargo test -p spacetime-client bark_battle_facade_maps_run_records --no-default-features ``` 先新增 facade / mapper 测试或编译断言,预期因绑定、表或 reducer 缺失失败。 GREEN:新增表、reducer/procedure、`migration.rs`、生成绑定、`spacetime-client` facade。 REFACTOR:确保 api-server 不直接依赖 generated bindings,mapper 命名与现有玩法一致。 #### Slice 5: api-server start / finish BFF RED: ```bash cd server-rs cargo test -p api-server bark_battle_start_records_work_play_start --no-default-features cargo test -p api-server bark_battle_finish_rejects_duplicate_or_invalid_metrics --no-default-features ``` 先新增 API 测试,断言: - start 成功返回 `runId`、`runToken`、`runtimeConfig`。 - start 成功后主动补写 `work_play_start`,不只依赖 route-level 兜底。 - finish 重复提交被拒绝。 - finish 不接受非法 metrics。 GREEN:新增 Axum route、handler、错误映射、tracking 调用。 REFACTOR:handler 只做编排,把规则留在 domain,把持久化留在 facade。 #### Slice 6: 前端 contract 对齐 RED: ```bash npm run test -- bark-battle npm run typecheck ``` 先新增前端 contract / client 测试,预期因 TS 类型或 client 缺失失败。 GREEN:补前端共享 contract mirror、runtime client 调用 start / finish。 REFACTOR:删除重复类型,保持后端 DTO 为事实源。 ### 9.4 后端验收命令 按切片逐步运行,不要等全部实现后一次性补测: ```bash cd server-rs cargo test -p shared-contracts bark_battle --no-default-features cargo test -p module-bark-battle --no-default-features cargo check -p spacetime-module --no-default-features cargo check -p spacetime-client --no-default-features cargo test -p api-server bark_battle --no-default-features ``` 如涉及 API smoke: ```bash npm run api-server # 另开终端执行对应 bark-battle start / finish smoke 或项目既有 API 测试脚本 ``` 每次表结构变更后必须同步: 1. 更新 `migration.rs`。 2. 重新生成 SpacetimeDB bindings。 3. 检查 generated bindings 变更只来自生成命令。 4. 运行 `cargo check -p spacetime-module -p spacetime-client --no-default-features`。 ### 9.5 前端 contract 对齐验收 前端只在后端 contract 稳定后接入: ```bash npm run typecheck npm test -- bark-battle npm run build ``` 验收: - runtime start / finish 请求字段与 shared contracts 一致。 - 前端 result panel 展示后端 `RunResult`。 - 前端本地结算只作为即时反馈,正式结果以后端返回为准。 ### 9.6 手工验收清单 - 可以创建并保存 bark-battle 草稿。 - 可以发布成稳定作品 ID,`playTypeId = bark-battle`。 - runtime start 返回 config、runId、runToken。 - start 写入 `work_play_start`,scope 与 metadata 符合要求。 - finish 不上传音频,只上传派生指标。 - finish 返回服务端认可的 result。 - 异常时长、重复提交、config version mismatch 会被拒绝或打 flag。 - 排行榜关闭时不写榜;开启时只写 accepted 结果。 ## 10. 与现有前端方案和 BDD 文档的关系 ### 10.1 依赖文档 - 前端 runtime 方案:`docs/technical/BARK_BATTLE_2D_RUNTIME_TECHNICAL_PLAN_2026-05-11.md`。 - BDD / DDD / TDD 总计划:`.hermes/plans/2026-05-11_144229-bark-battle-2d-game-bdd-ddd-tdd-plan.md`。 - 当前后端实现基线:`docs/technical/CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`。 - SpacetimeDB 表结构变更约束:`docs/technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md`。 ### 10.2 与前端方案的对齐点 - 前端方案负责 Phaser / Web Audio / DOM HUD;本文负责作品、成绩、排行榜、发布和埋点。 - 前端 `BarkBattleSnapshot` 可用于本地即时表现,但正式 `RunResult` 以后端返回为准。 - 前端不上传原始音频,只上传 `BarkBattleDerivedMetrics`。 - 前端本地 config 应来自后端发布态 `BarkBattleRuntimeConfig`,不能在生产游玩中使用未发布临时配置。 - 前端 result panel 应能展示后端返回的 score、grade、antiCheatFlags 与 leaderboard entry。 ### 10.3 与 BDD 的对齐点 后续 BDD 场景应覆盖: - 玩家从作品页进入 bark-battle runtime。 - 玩家授权麦克风后开始 30 秒对战。 - 玩家完成单局后看到后端确认结果。 - 未授权麦克风时可以看到降级说明,但不写正式成绩。 - 作品关闭排行榜时不展示排名入口。 - 作品开启排行榜时展示当前作品排名。 - 重复 finish / 过期 run / 配置版本不一致时返回可解释错误。 ### 10.4 后端落地顺序建议 1. 先只做 contract + domain,固定 `playTypeId = bark-battle` 与配置 schema。 2. 再做草稿 / 发布态作品配置读写。 3. 再做 start / finish run 与 `work_play_start` 埋点。 4. 最后做排行榜投影。 5. 实时多人协议另起方案,不与 MVP 混做。