This commit is contained in:
202
docs/technical/M4_MODULE_COMBAT_STATE_QUERY_DESIGN_2026-04-22.md
Normal file
202
docs/technical/M4_MODULE_COMBAT_STATE_QUERY_DESIGN_2026-04-22.md
Normal file
@@ -0,0 +1,202 @@
|
||||
# M4 module-combat battle state 查询设计(2026-04-22)
|
||||
|
||||
更新时间:`2026-04-22`
|
||||
|
||||
补充状态:`2026-04-22`
|
||||
|
||||
当前 battle query 纵切片已经完成到“真实可编译、可生成 binding、可被 Axum 调用”的状态:
|
||||
|
||||
1. `spacetime-module` 中的 `get_battle_state` procedure 已稳定存在。
|
||||
2. `spacetime-client/src/module_bindings` 已重新执行 `spacetime generate`,当前已真实包含:
|
||||
- `battle_state_query_input_type`
|
||||
- `get_battle_state_procedure`
|
||||
- `battle_state.reward_items` 对应字段
|
||||
3. `spacetime-client/src/lib.rs` 里原本返回“binding 尚未生成”的占位 `get_battle_state(...)` 已替换为真实 procedure 调用。
|
||||
4. `cargo check -p spacetime-client` 与 `cargo check -p api-server` 已再次通过。
|
||||
|
||||
当前仍未完成的只有长时回归验证:
|
||||
|
||||
1. `cargo test -p api-server --bin api-server story_battles --manifest-path D:\\Genarrative\\server-rs\\Cargo.toml` 在当前机器上编译耗时较长,尚未在单次时窗内拿到最终断言结果。
|
||||
2. `npm run check:encoding` 已启动但尚未在单次时窗内跑完。
|
||||
|
||||
## 0. 文档目标
|
||||
|
||||
本文件只冻结当前 `M4` 的一个最小新增切片:
|
||||
|
||||
**新增 `GET /api/story/battles/:battleStateId`,让 Axum 能从 `SpacetimeDB` 同步读取单个 `battle_state` 当前快照,不提前承诺旧 runtime story state 兼容。**
|
||||
|
||||
这轮目标不是实现旧 `GET /api/runtime/story/state/:sessionId` 的战斗子视图兼容,也不是把 `battle + story_event + currentStory` 一次性收口进 `resolve_story_action`。
|
||||
|
||||
---
|
||||
|
||||
## 1. 为什么先补这个切片
|
||||
|
||||
当前 battle 链路已经具备:
|
||||
|
||||
1. `module-combat` 已冻结 `battle_state` 领域类型与纯结算规则。
|
||||
2. `spacetime-module` 已有 `create_battle_state_and_return`、`resolve_combat_action_and_return`。
|
||||
3. `spacetime-client` 与 `api-server` 已能创建战斗并推进单次动作。
|
||||
|
||||
但现在仍缺一个最基本的恢复能力:
|
||||
|
||||
1. battle 建立后,Axum 还不能按 `battle_state_id` 重新读取真相态。
|
||||
2. 页面刷新、重连或后续 story 编排都缺一个稳定的单战斗查询入口。
|
||||
3. 后续若要把 battle 收口进 `resolve_story_action`,也需要先有独立 battle query 可复用。
|
||||
|
||||
因此本轮先补最小 `battle state` 查询切片,不提前跳到更重的 runtime story 兼容。
|
||||
|
||||
---
|
||||
|
||||
## 2. 当前冻结范围
|
||||
|
||||
本轮只包含以下能力:
|
||||
|
||||
1. 新增公开接口:`GET /api/story/battles/:battleStateId`
|
||||
2. 认证方式:Bearer JWT
|
||||
3. 数据来源:`SpacetimeDB procedure get_battle_state`
|
||||
4. 返回体只包含:
|
||||
- `battleState`
|
||||
|
||||
本轮明确不做:
|
||||
|
||||
1. 不兼容旧 `GET /api/runtime/story/state/:sessionId`
|
||||
2. 不补 battle 列表查询
|
||||
3. 不做 `battle_state` 订阅与 cache 读模型
|
||||
4. 不在查询链路里拼装 `story_event / npc / quest / inventory`
|
||||
5. 不把 battle query 直接拼回旧 `RuntimeStoryActionResponse`
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口 contract
|
||||
|
||||
### 3.1 请求
|
||||
|
||||
- 方法:`GET`
|
||||
- 路径:`/api/story/battles/:battleStateId`
|
||||
- 认证:必须携带 Bearer JWT
|
||||
- 路径参数:
|
||||
- `battleStateId`:目标战斗状态 ID
|
||||
|
||||
### 3.2 成功响应
|
||||
|
||||
成功响应延续当前 `api-server` 统一 envelope,`data` 字段结构为:
|
||||
|
||||
```json
|
||||
{
|
||||
"battleState": {
|
||||
"battleStateId": "battle_xxx",
|
||||
"storySessionId": "storysess_xxx",
|
||||
"runtimeSessionId": "runtime_xxx",
|
||||
"actorUserId": "user_xxx",
|
||||
"chapterId": "chapter_xxx",
|
||||
"targetNpcId": "npc_xxx",
|
||||
"targetName": "黑爪狼",
|
||||
"battleMode": "fight",
|
||||
"status": "ongoing",
|
||||
"playerHp": 42,
|
||||
"playerMaxHp": 60,
|
||||
"playerMana": 12,
|
||||
"playerMaxMana": 20,
|
||||
"targetHp": 18,
|
||||
"targetMaxHp": 30,
|
||||
"experienceReward": 18,
|
||||
"rewardItems": [],
|
||||
"turnIndex": 1,
|
||||
"lastActionFunctionId": "battle_attack_basic",
|
||||
"lastActionText": "普通攻击",
|
||||
"lastResultText": "普通攻击命中了黑爪狼,本次攻击已经完成结算。",
|
||||
"lastDamageDealt": 12,
|
||||
"lastDamageTaken": 4,
|
||||
"lastOutcome": "ongoing",
|
||||
"version": 2,
|
||||
"createdAt": "2026-04-22T00:00:00.000000Z",
|
||||
"updatedAt": "2026-04-22T00:01:00.000000Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 错误响应
|
||||
|
||||
当前延续 battle facade 已有策略:
|
||||
|
||||
1. `SpacetimeClientError::Runtime(_)` 映射为 `400`
|
||||
2. 其他 `SpacetimeClientError` 映射为 `502`
|
||||
3. 错误 `details.provider` 固定为 `spacetimedb`
|
||||
|
||||
---
|
||||
|
||||
## 4. 分层职责
|
||||
|
||||
### 4.1 `module-combat`
|
||||
|
||||
职责:
|
||||
|
||||
1. 冻结 `BattleStateQueryInput`
|
||||
2. 负责 query input builder 与 validator
|
||||
3. 继续复用 `BattleStateProcedureResult` 作为最小查询返回壳
|
||||
|
||||
不负责:
|
||||
|
||||
1. HTTP 路径解析
|
||||
2. JWT 鉴权
|
||||
3. battle view model 编译
|
||||
|
||||
### 4.2 `spacetime-module`
|
||||
|
||||
职责:
|
||||
|
||||
1. 读取 `battle_state`
|
||||
2. 校验 `battle_state_id`
|
||||
3. 返回单个 `BattleStateSnapshot`
|
||||
|
||||
### 4.3 `spacetime-client`
|
||||
|
||||
职责:
|
||||
|
||||
1. 构造 `BattleStateQueryInput`
|
||||
2. 调用 `get_battle_state`
|
||||
3. 把 generated binding 结果映射为 `BattleStateRecord`
|
||||
|
||||
当前实现补充:
|
||||
|
||||
1. `reward_items` 已按 generated binding 映射回 `BattleStateRecord.reward_items`,不再用空集合占位。
|
||||
2. battle query 当前不再依赖 façade stub 或手写假返回。
|
||||
|
||||
### 4.4 `api-server`
|
||||
|
||||
职责:
|
||||
|
||||
1. 暴露 `GET /api/story/battles/:battleStateId`
|
||||
2. 做 Bearer JWT 鉴权
|
||||
3. 透传 `battleStateId`
|
||||
4. 把 `BattleStateRecord` 映射到 battle JSON payload
|
||||
|
||||
---
|
||||
|
||||
## 5. 验收口径
|
||||
|
||||
本轮验收只要求以下几点:
|
||||
|
||||
1. `api-server` 路由树已真实挂出该接口
|
||||
2. 未登录访问返回 `401`
|
||||
3. 在 `SpacetimeDB` 未发布或未连通时返回 `502`
|
||||
4. `cargo test -p api-server story_battles` 可通过
|
||||
5. `cargo check -p module-combat -p spacetime-module -p spacetime-client -p api-server` 可通过
|
||||
6. `npm run check:encoding` 已执行,确保新增中文文档没有编码损坏
|
||||
|
||||
当前验证状态:
|
||||
|
||||
1. 第 5 条已达成。
|
||||
2. 第 4、6 条仍在继续追,不应提前宣称通过。
|
||||
|
||||
---
|
||||
|
||||
## 6. 后续边界
|
||||
|
||||
这条最小 battle query 落地后,后续再继续拆下一层:
|
||||
|
||||
1. 评估 battle 查询是否需要补 actor ownership 校验
|
||||
2. 设计 battle 结束事件如何接入 `story_event`
|
||||
3. 再把 battle query 与 `story state / resolve_story_action / currentStory` 汇总到更高层编排
|
||||
|
||||
在这些 contract 未冻结前,不应把当前接口误称为“旧 runtime story state 已迁移完成”。
|
||||
Reference in New Issue
Block a user