Files
Genarrative/docs/technical/BARK_BATTLE_BACKEND_DDD_TECHNICAL_PLAN_2026-05-11.md

35 KiB
Raw Permalink Blame History

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-moduleSpacetimeDB 表、reducer、migration。
  • spacetime-clientSpacetimeDB 绑定调用 facade。
  • api-serverHTTP / 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. 发布为稳定作品 IDplayTypeId = "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=workscope_id=稳定作品 IDmetadata 包含 playTypeworkIdsourceRouteuserId

2.2 后续增强路径

后续再考虑多人实时:

  • Phase 2排行榜、挑战分享、个人历史成绩、作品统计面板。
  • Phase 3异步影子对手 / ghost replay但仍不保存原始音频只保存低维派生曲线或聚合指标。
  • Phase 4实时多人对战协议需要独立同步模型、房间服务、延迟补偿、断线恢复与更严格反作弊不应混入 MVP。

3. DDD 分层设计

3.1 总体分层

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 bindingsapi-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

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

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

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

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

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

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

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 可选类型

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:发布配置版本。
  • titledescriptioncover_asset_id
  • runtime_config_json:发布态 runtime 配置 JSON字段需由 shared contracts 校验。
  • leaderboard_enabled
  • statusdraft / published / archived
  • created_atupdated_atpublished_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
  • statusstarted / finished / rejected / expired
  • server_started_atserver_finished_atexpires_at
  • client_elapsed_ms
  • final_energy
  • client_winnerserver_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 创建 / 保存草稿

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 发布 / 获取作品

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

POST /api/bark-battle/runs/start

请求:BarkBattleRunStartRequest。响应:BarkBattleRunStartResponse

职责:

  • 校验作品存在、发布态、可游玩。
  • 校验 config version必要时返回最新版本。
  • 创建 run_id 与一次性 run_token
  • 写正式作品级游玩埋点:work_play_start

埋点要求:

event_key: work_play_start
scope_kind: work
scope_id: <稳定作品 ID>
metadata: {
  playType: "bark-battle",
  workId: "<workId>",
  sourceRoute: "<sourceRoute>",
  userId: "<userId or anonymous>"
}

6.4 finish runtime

POST /api/bark-battle/runs/{runId}/finish

请求:BarkBattleRunFinishRequest。响应:BarkBattleRunResult

职责:

  • 校验 run token。
  • 校验 run 仍处于 started 且未过期。
  • 校验 work_id + config_version 与 start 时一致。
  • 校验时长、finalEnergy、metrics 范围。
  • 使用 module-bark-battle 生成服务端认可的 serverWinnerscoreScoreSummaryantiCheatFlags
  • 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

track_event(
  event_key = "work_play_start",
  scope_kind = "work",
  scope_id = workId,
  metadata = { playType, workId, sourceRoute, userId }
)

6.6 可选排行榜

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

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 是否在配置范围。
  • peakVolumeMaxpeakVolumeAvg 是否在 0..1
  • validBarkCountrhythmHitCountlongestCombo 是否在物理合理上限。
  • 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 发布态作品运行配置

功能: 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 与统一游玩埋点

功能: 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 派生成绩提交

功能: 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 排行榜投影

功能: bark-battle 排行榜投影
  为了让玩家比较同一作品内的有效成绩
  作为玩家
  我希望排行榜只展示被后端接受的成绩

  场景: 作品开启排行榜且成绩被接受
    假如作品 leaderboardEnabled 为 true
    而且玩家提交的 finish 结果 accepted 为 true
    后端完成成绩写入
    那么后端应写入或更新该作品的排行榜投影
    而且排行榜排序应优先按 score 降序

  场景: 作品关闭排行榜
    假如作品 leaderboardEnabled 为 false
    玩家完成一局合法成绩
    那么后端应记录 run 与 score
    而且不应写入排行榜 entry

  场景: 带反作弊 flag 的成绩不进入默认榜单
    假如玩家提交的结果为 accepted_with_flags
    后端处理排行榜投影
    那么默认排行榜不应展示该成绩
    而且该 run 的反作弊标记应可供后台审计

功能: bark-battle 隐私边界

功能: 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.rsserver-rs/crates/api-server/src/bark_battle.rs 新 DTO / route 不存在导致编译或断言失败
玩家请求未发布作品的 runtime config domain + API server-rs/crates/module-bark-battle/src/domain.rsserver-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.rsserver-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

cd server-rs
cargo test -p shared-contracts bark_battle_contract_uses_camel_case --no-default-features

先新增测试,断言:

  • BarkBattleRunStartRequest 序列化为 workIdconfigVersionsourceRoute
  • BarkBattleRunFinishRequest 只包含 runIdrunTokenworkIdconfigVersionelapsedMsfinalEnergyclientWinnermetrics
  • metrics 只包含派生聚合字段,不包含 audioaudioBase64waveformpcmSamples

GREEN新增 shared-contracts/src/bark_battle.rslib.rs 导出,让测试通过。

REFACTOR统一枚举命名、字段注释和默认值策略。

Slice 2: domain 校验 runtime config 与 finish metrics

RED

cd server-rs
cargo test -p module-bark-battle validates_runtime_config_and_finish_metrics --no-default-features

先新增测试,断言:

  • playTypeId 必须为 bark-battle
  • durationMsenergyMinenergyMaxbarkThreshold 必须在合法范围。
  • peakVolumeMaxpeakVolumeAvg 必须在 0..1
  • 过短时长产生 elapsed_too_short
  • 不可能叫声次数产生 impossible_bark_count

GREEN新增 module-bark-battle 纯领域类型与校验函数。

REFACTOR把常量收敛为领域常量避免 magic number 分散。

Slice 3: domain 重新裁决成绩与排行榜资格

RED

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 时不生成榜单投影。

GREENscore_finish_result(...)is_leaderboard_eligible(...) 等纯函数。

REFACTOR拆分 score、winner、anti-cheat、leaderboard 四组小函数。

Slice 4: SpacetimeDB 表、reducer 与 migration

RED

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 bindingsmapper 命名与现有玩法一致。

Slice 5: api-server start / finish BFF

RED

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 成功返回 runIdrunTokenruntimeConfig
  • start 成功后主动补写 work_play_start,不只依赖 route-level 兜底。
  • finish 重复提交被拒绝。
  • finish 不接受非法 metrics。

GREEN新增 Axum route、handler、错误映射、tracking 调用。

REFACTORhandler 只做编排,把规则留在 domain把持久化留在 facade。

Slice 6: 前端 contract 对齐

RED

npm run test -- bark-battle
npm run typecheck

先新增前端 contract / client 测试,预期因 TS 类型或 client 缺失失败。

GREEN补前端共享 contract mirror、runtime client 调用 start / finish。

REFACTOR删除重复类型保持后端 DTO 为事实源。

9.4 后端验收命令

按切片逐步运行,不要等全部实现后一次性补测:

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

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 稳定后接入:

npm run typecheck
npm test -- bark-battle
npm run build

验收:

  • runtime start / finish 请求字段与 shared contracts 一致。
  • 前端 result panel 展示后端 RunResult
  • 前端本地结算只作为即时反馈,正式结果以后端返回为准。

9.6 手工验收清单

  • 可以创建并保存 bark-battle 草稿。
  • 可以发布成稳定作品 IDplayTypeId = bark-battle
  • runtime start 返回 config、runId、runToken。
  • start 写入 work_play_startscope 与 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 混做。