# M4 Combat Reward Inventory Integration(2026-04-22) 更新时间:`2026-04-22` ## 0. 文档目标 本文件只冻结一件事: **把 `resolve_combat_action(Victory)` 从“只发经验”推进到“经验与战利品可在同一事务内结算”的最小主链口径。** 本轮不回收完整 runtime item 导演层,也不在 `module-combat` 内直接做 AI 语义生成;只承接已经编译好的 reward item 快照。 --- ## 1. 本轮落地范围 本轮只落实下面 4 件事: 1. 在 `module-combat` 中为 `battle_state` 补充 `reward_items` 字段。 2. 允许 `BattleStateInput` 在初始化时携带已经编译好的战利品快照。 3. 在 `spacetime-module::resolve_combat_action` 中,当结果为 `Victory` 时同步把 `reward_items` 写入 `inventory_slot`。 4. 保持 `module-combat` 仍然是纯规则 crate,不直接依赖 `module-inventory`。 --- ## 2. 当前冻结的战利品口径 ### 2.1 `battle_state.reward_items` 首版字段固定复用 `module-runtime-item::RuntimeItemRewardItemSnapshot`。 原因: 1. 宝箱链已经用这套 reward item contract 打通到 `inventory_slot`。 2. 任务奖励当前仍有独立 `QuestRewardItem`,但战斗奖励更接近 runtime item 导演层。 3. 先复用现有 reward item 快照,避免本轮再发明第三套 combat 专属掉落结构。 ### 2.2 battle 初始化来源 当前 `battle_state.reward_items` 不在战斗 reducer 内生成,只允许由上游在创建 battle 时传入: 1. `resolve_npc_battle_interaction_and_return` 2. 后续 Axum façade / runtime story orchestration 3. 其它明确的 battle create 聚合入口 也就是说: 1. `module-combat` 只消费已确定的 reward item 快照 2. 不在 reducer 内做随机、提示词、外部世界图谱推导 当前已接通: 1. `resolve_npc_battle_interaction_and_return` 2. `POST /api/story/npc/battle` 3. `POST /api/story/battles` 这几条入口都只负责透传已编译奖励,不负责现场生成掉落。 --- ## 3. Victory 发物规则 当 `resolve_combat_action` 结算结果满足: 1. `outcome == Victory` 则 `spacetime-module` 需要继续执行: 1. `experience_reward > 0` 时写 `player_progression / chapter_progression` 2. `reward_items.len() > 0` 时写 `inventory_slot` ### 3.1 发物方式 当前固定规则: 1. 每个 reward item 显式映射成一条 `InventoryMutation::GrantItem` 2. `source_kind = CombatDrop` 3. `source_reference_id = battle_state_id` 4. 同一 `battle_state_id` 只允许发放一次 ### 3.2 幂等约束 本轮先采用与 quest / treasure 一致的“按来源引用查重”思路: 1. 若当前 actor 的 `inventory_slot` 中已经存在 `source_reference_id = battle_state_id` 2. 视为该 battle reward 已发放 3. Victory 再次重放时跳过发物,但不影响 battle_state 已收束结果 --- ## 4. 与既有链路的边界 ### 4.1 与 `module-combat` `module-combat` 继续只负责: 1. `battle_state` 结构 2. `resolve_combat_action` 状态推进 3. 胜负结果收束 不负责: 1. inventory 写表 2. progression 写表 3. runtime item 生成 ### 4.2 与 `module-runtime-item` 本轮不把战斗奖励映射 helper 上提到 `module-runtime-item`。 原因: 1. 当前 `RuntimeItemRewardItemSnapshot -> InventoryItemSnapshot` 的 helper 语义固定为 `TreasureReward` 2. 若直接复用,会把 `source_kind` 写错成 `TreasureReward` 3. 本轮先在 `spacetime-module` 里补一个 combat 专用映射,后续再统一抽象 --- ## 5. 当前刻意未做 本轮明确不做下面这些扩张: 1. 不把 Node 版 `monster_drop` AI 导演层整体迁到 Rust 2. 不在 `resolve_npc_battle_interaction_and_return` 里现场计算掉落 3. 不处理 battle reward 的货币、好感、情报 4. 不处理战斗内 `inventory_use` 5. 不把掉落展示或 Battle Reward 面板接到前端 --- ## 6. 验证要求 本轮完成后至少执行: 1. `npm run check:encoding` 2. `cargo test -p module-combat --manifest-path D:\\Genarrative\\server-rs\\Cargo.toml` 3. `cargo check -p module-combat -p spacetime-module --manifest-path D:\\Genarrative\\server-rs\\Cargo.toml` --- ## 7. 下一步建议 按当前节奏,后续应继续按下面顺序推进: 1. 把 Node 侧 `monster_drop` runtime item 编译逻辑迁到 Rust 聚合层。 2. 视复用收益决定是否把 battle / treasure 的 reward item 归一化 helper 上提到 `module-runtime-item`。 3. 最后再把 battle reward 展示、story patch 和前端接口切到新后端。