# M4 module-combat Axum facade 设计(2026-04-21) 更新时间:`2026-04-21` ## 0. 文档目标 本文件只冻结一件事: **把已经完成 reducer 化的 `module-combat` 再向上接一层最小同步返回链,让 `api-server` 可以显式创建战斗、推进单次战斗动作,并立即拿到 battle 快照结果。** 这份文档不是完整 `runtime story actions/resolve` 兼容方案,也不替代后续的 `resolve_story_action` 编排设计。 --- ## 1. 本轮要解决的问题 当前 `module-combat` 已具备: 1. `battle_state` 真相表 2. `create_battle_state` reducer 3. `resolve_combat_action` reducer 4. `fight / spar` 两种模式下的纯规则推进 但当前仍缺一层明确能力: 1. Axum 还不能同步拿到 battle 快照 2. `spacetime-client` 还没有 battle procedure 调用封装 3. `api-server` 还没有独立的战斗 facade 因此本轮只补下面三层: 1. `spacetime-module` battle procedure 2. `spacetime-client` battle procedure 调用与返回值映射 3. `api-server` 最小战斗 HTTP facade --- ## 2. 当前明确不做的事 本轮刻意不做: 1. 不兼容旧 `POST /api/runtime/story/actions/resolve` 2. 不兼容旧 `GET /api/runtime/story/state/:sessionId` 3. 不把 `inventory_use` 提前接回战斗主链 4. 不把 `quest / progression / npc / story_event` 自动联动写回 5. 不把 battle 直接拼进 `RuntimeStoryActionResponse` 原因很直接: 1. 这些属于更高层的 runtime story 编排问题 2. 当前 battle 子域应该先把“独立可调用、同步可返回”这一层固定下来 3. 先补 procedure + facade,后续 `resolve_story_action` 才有稳定下游可调入口 --- ## 3. `spacetime-module` 的新增口径 ### 3.1 reducer 继续保留 已有 reducer 继续保留: 1. `create_battle_state` 2. `resolve_combat_action` 职责不变: 1. reducer 仍然只负责 battle 真相写入 2. reducer 不直接向调用方返回业务快照 ### 3.2 新增 procedure 本轮新增两个 procedure: 1. `create_battle_state_and_return` 2. `resolve_combat_action_and_return` 职责冻结如下: 1. procedure 只包一层 `try_with_tx` 2. procedure 内部复用 reducer 共享的写入 helper 3. procedure 负责把最终 `battle_state` 或 `resolve result` 同步返回给 Axum ### 3.3 返回类型 本轮冻结两种返回 DTO: 1. `BattleStateProcedureResult` 2. `ResolveCombatActionProcedureResult` 字段口径统一为: 1. `ok` 2. `snapshot` 或 `result` 3. `error_message` 这样能与现有 `story / treasure / npc` procedure 返回风格保持一致。 --- ## 4. `spacetime-client` 的新增口径 `spacetime-client` 本轮新增两条最小调用链: 1. `create_battle_state` 2. `resolve_combat_action` 调用策略继续沿用当前已验证模式: 1. 先建立 `DbConnection` 2. 等待 `on_connect` 3. 再调用对应 procedure 4. 统一经 `oneshot + timeout` 收口结果 当前不做: 1. battle 订阅 2. battle cache 读模型 3. battle 长连接复用策略 --- ## 5. `api-server` 的新增 facade 口径 ### 5.1 路由 本轮新增两条最小路由: 1. `POST /api/story/battles` 2. `POST /api/story/battles/resolve` 这两条路由的定位不是旧 runtime 兼容层,而是: 1. 面向新 Rust 后端内部联调 2. 面向后续 `resolve_story_action` 编排层调用 ### 5.2 `POST /api/story/battles` 请求体只提交 battle 建立所需的业务字段: 1. `storySessionId` 2. `runtimeSessionId` 3. `targetNpcId` 4. `targetName` 5. `battleMode` 6. `playerHp` 7. `playerMaxHp` 8. `playerMana` 9. `playerMaxMana` 10. `targetHp` 11. `targetMaxHp` 由 Axum 自动补齐: 1. `battleStateId` 2. `actorUserId` 3. `createdAtMicros` 响应返回: 1. `battleState` ### 5.3 `POST /api/story/battles/resolve` 请求体只提交单次动作推进所需字段: 1. `battleStateId` 2. `functionId` 3. `actionText` 4. `baseDamage` 5. `manaCost` 6. `heal` 7. `manaRestore` 8. `counterMultiplierBasisPoints` 由 Axum 自动补齐: 1. `updatedAtMicros` 响应返回: 1. `battleState` 2. `combat` 其中 `combat` 至少包含: 1. `damageDealt` 2. `damageTaken` 3. `outcome` --- ## 6. 认证与字段真相边界 ### 6.1 `actorUserId` `actorUserId` 不接受前端自填。 必须由: 1. `AuthenticatedAccessToken` 2. `claims.user_id` 直接生成。 ### 6.2 时间字段 `createdAtMicros` 与 `updatedAtMicros` 不接受外部写入。 必须由 Axum 在请求时生成,原因如下: 1. 避免客户端伪造 battle 创建时间 2. 保持 Rust 后端各 facade 的时间字段风格一致 3. 让后续 battle / story / npc 联调时便于统一日志与排障 --- ## 7. 错误映射口径 当前 battle facade 的错误映射冻结如下: 1. battle mode 非法、请求 JSON 非法、字段校验失败:`400` 2. `SpacetimeClientError::Runtime(_)`:`400` 3. 其他 `SpacetimeClientError`:`502` 返回 `details.provider` 统一写: 1. battle 输入准备错误:`story-battle` 2. SpacetimeDB 上游错误:`spacetimedb` --- ## 8. 本轮验收 满足以下条件,视为本轮 facade 基线完成: 1. `module-combat` 已新增 procedure 返回 DTO 2. `spacetime-module` 已新增 `create_battle_state_and_return` 3. `spacetime-module` 已新增 `resolve_combat_action_and_return` 4. `spacetime-client` 已可同步创建战斗并推进单次动作 5. `api-server` 已新增两条最小 battle facade 路由 6. `cargo check -p module-combat -p spacetime-client -p api-server -p spacetime-module` 通过 --- ## 9. 下一步建议 本轮完成后,后续最稳的顺序是: 1. 把 battle facade 接入 `resolve_story_action` 2. 设计 battle 结束后的 `story_event` 追加口径 3. 再把 `quest / progression / inventory` 的联动收回到显式子域流程里