docs: add bark battle backend ddd plan
This commit is contained in:
@@ -0,0 +1,799 @@
|
||||
# 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: "<workId>",
|
||||
sourceRoute: "<sourceRoute>",
|
||||
userId: "<userId or anonymous>"
|
||||
}
|
||||
```
|
||||
|
||||
### 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. TDD / 验收顺序与命令
|
||||
|
||||
本任务只写方案,不执行代码实现。后续落地建议按以下顺序:
|
||||
|
||||
### 9.1 domain 纯函数测试
|
||||
|
||||
先实现 `module-bark-battle`:
|
||||
|
||||
```bash
|
||||
cd server-rs
|
||||
cargo test -p module-bark-battle
|
||||
```
|
||||
|
||||
测试覆盖:
|
||||
|
||||
- runtime config 校验。
|
||||
- finish metrics 范围校验。
|
||||
- 胜负判定。
|
||||
- score / grade / leaderboard score 计算。
|
||||
- 反作弊 flags。
|
||||
|
||||
### 9.2 contracts 测试
|
||||
|
||||
再实现 shared contracts:
|
||||
|
||||
```bash
|
||||
cd server-rs
|
||||
cargo test -p shared-contracts bark_battle
|
||||
cargo check -p shared-contracts
|
||||
```
|
||||
|
||||
验收:
|
||||
|
||||
- DTO 可序列化 / 反序列化。
|
||||
- 枚举值稳定。
|
||||
- 可选字段向后兼容。
|
||||
- TypeScript mirror 或契约生成产物与 Rust contract 对齐。
|
||||
|
||||
### 9.3 SpacetimeDB / api-server check
|
||||
|
||||
实现表、reducer、migration 与 facade 后:
|
||||
|
||||
```bash
|
||||
cd server-rs
|
||||
cargo check -p spacetime-module
|
||||
cargo check -p spacetime-client
|
||||
cargo check -p api-server
|
||||
cargo test -p api-server bark_battle
|
||||
```
|
||||
|
||||
如仓库有统一命令,以统一命令为准,例如:
|
||||
|
||||
```bash
|
||||
cd server-rs
|
||||
cargo test --workspace
|
||||
```
|
||||
|
||||
验收:
|
||||
|
||||
- migration.rs 包含新增表与变更。
|
||||
- 绑定由生成命令产出,未手改生成物。
|
||||
- api-server route handler 只做编排,不内嵌复杂计分规则。
|
||||
|
||||
### 9.4 前端 contract 对齐
|
||||
|
||||
前端只在后端 contract 稳定后接入:
|
||||
|
||||
```bash
|
||||
npm run typecheck
|
||||
npm test -- bark-battle
|
||||
npm run build
|
||||
```
|
||||
|
||||
验收:
|
||||
|
||||
- runtime start / finish 请求字段与 shared contracts 一致。
|
||||
- 前端 result panel 展示后端 `RunResult`。
|
||||
- 前端本地结算只作为即时反馈,正式结果以后端返回为准。
|
||||
|
||||
### 9.5 手工验收清单
|
||||
|
||||
- 可以创建并保存 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 混做。
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
## 文档列表
|
||||
|
||||
- [BARK_BATTLE_BACKEND_DDD_TECHNICAL_PLAN_2026-05-11.md](./BARK_BATTLE_BACKEND_DDD_TECHNICAL_PLAN_2026-05-11.md):冻结“汪汪声浪大作战 / bark-battle”后端 DDD 技术方案,明确 `server-rs + Axum + SpacetimeDB` 分层边界、shared contracts、作品配置、runtime run、派生成绩、排行榜、`work_play_start` 埋点、migration/绑定生成策略,以及不保存原始麦克风音频的隐私与反作弊约束。
|
||||
- [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 等设备统一归一为通用拖拽语义,玩法组件只负责解释目标和落点。
|
||||
|
||||
Reference in New Issue
Block a user