Files
Genarrative/docs/technical/M4_MODULE_COMBAT_AXUM_FACADE_DESIGN_2026-04-21.md
2026-04-22 12:34:49 +08:00

5.7 KiB
Raw Blame History

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_stateresolve result 同步返回给 Axum

3.3 返回类型

本轮冻结两种返回 DTO

  1. BattleStateProcedureResult
  2. ResolveCombatActionProcedureResult

字段口径统一为:

  1. ok
  2. snapshotresult
  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 时间字段

createdAtMicrosupdatedAtMicros 不接受外部写入。

必须由 Axum 在请求时生成,原因如下:

  1. 避免客户端伪造 battle 创建时间
  2. 保持 Rust 后端各 facade 的时间字段风格一致
  3. 让后续 battle / story / npc 联调时便于统一日志与排障

7. 错误映射口径

当前 battle facade 的错误映射冻结如下:

  1. battle mode 非法、请求 JSON 非法、字段校验失败:400
  2. SpacetimeClientError::Runtime(_)400
  3. 其他 SpacetimeClientError502

返回 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 的联动收回到显式子域流程里