From 0791da2a2ed9fcf552570b9ef1b999f07d973795 Mon Sep 17 00:00:00 2001 From: server_kizuna Date: Mon, 3 Aug 2026 08:24:51 +0800 Subject: [PATCH 01/13] =?UTF-8?q?=E6=94=B6=E5=8F=A3=20R10=20P0=EF=BC=9A?= =?UTF-8?q?=E5=86=BB=E7=BB=93=20inventory=20=E4=BA=8B=E5=8A=A1=E4=B8=8E?= =?UTF-8?q?=E5=90=B8=E6=94=B6=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 基于现役代码验真拆分职责、满包落地策略、跨轨所有权与旧布局迁移门禁,避免 Wave 2 实施继续复制不一致交付语义。 Model: cc-sonnet-high Co-Authored-By: Claude --- docs/plan-refactor-inventory-core-v1.md | 192 ++++++++++++++++++ .../plan-refactor-inventory-core-v1.md | 48 ----- 2 files changed, 192 insertions(+), 48 deletions(-) create mode 100644 docs/plan-refactor-inventory-core-v1.md delete mode 100644 docs/plans-skeleton/plan-refactor-inventory-core-v1.md diff --git a/docs/plan-refactor-inventory-core-v1.md b/docs/plan-refactor-inventory-core-v1.md new file mode 100644 index 000000000..7b0adcf28 --- /dev/null +++ b/docs/plan-refactor-inventory-core-v1.md @@ -0,0 +1,192 @@ +# plan-refactor-inventory-core-v1 — Inventory 巨石拆分 + 网格/交付事务一致性(重构轨 R10) + +> 所属总纲:`docs/plans-skeleton/plan-refactor-master-v1.md`。一句话:拆分 2 万行级 `server/src/inventory/mod.rs`,以统一 `InventoryTxn` 收口给予、交付、消费、拾取、堆叠与网格占格,使物品移动可原子验证、可观测且不静默丢失。 + +## 阶段总览 + +- ✅ 2026-08-03 P0 设计收口 + 吸收清单验真 +- ⬜ P1 巨石拆分(行为不变)+ `InventoryTxn` 骨架 +- ⬜ P2 交付/消费路径统一 +- ⬜ P3 网格/堆叠一致性 + 老存档布局迁移 +- ⬜ P4 bot 验收 + 被吸收 plan 收口 + +> **实施门禁**:按 master §4/§5,R10 实现属于 Wave 2,必须等待 R3 P1 提供稳定 persistence inventory slice/hydration 接缝。当前仅 P0 完成;P1–P4 不得提前启动。2026-08-03 核验时 R3 仅有 P0 PR #1308 OPEN,R3 P1 尚未落地。 + +## 现状证据(2026-08-03 复核) + +- `server/src/inventory/mod.rs` 当前 20479 行;原骨架记录的 20165 行已过时。该文件同时承载 domain model、registry/loadout、实例创建、交付/堆叠、网格移动、装备与套包、掉落拾取、遗骸转移、耐久/磨损接缝及大量内联测试。 +- `add_item_to_player_inventory_or_ground`(`server/src/inventory/mod.rs:1839`)只在真实容量不足时降级为地面掉落,其它结构错误原样返回;这是统一满包策略的现役生产先例。 +- `stack_identity_matches`(`server/src/inventory/mod.rs:2223`)与现有 staged grant 已具备“完整动态身份才合并、验证成功后再提交”的正确基线,R10 应抽取复用而非改弱为仅比较 `template_id`。 +- `force_attach_item_to_inventory`(`server/src/inventory/mod.rs:4733`)在无合法位置时直接把物品压入 `(0, 0)`;`transfer_all_inventory_contents`(`:4675`)会调用它,故占格重叠缺口仍可达。 +- `pickup_dropped_loot_instance`(`server/src/inventory/mod.rs:5337`)只找空 footprint,不尝试合并已有同身份堆叠,拾取合并缺口仍存在。 +- server 的 `apply_inventory_move`(`server/src/inventory/mod.rs:3828`)会正确交换旋转后的 `grid_w/grid_h`,但 `InventoryEventV1::Moved`(`server/src/schema/inventory.rs:294`)只带 from/to;client `InventoryEventHandler`(`:85`)复用旧 item view,因此 delta 路径会保留旧 footprint。 +- pack move 成功分支在 `handle_inventory_move`(`server/src/network/client_request_handler.rs:15310`)执行 `worn_pack_rebuild` snapshot 后提前返回;普通 `send_moved_event`(`:15590`)未覆盖该分支,稳定成功回执缺口仍存在。 +- `emit_changed_inventory_snapshots`(`server/src/network/inventory_snapshot_emit.rs:93`)会为 `Changed` 自动推送权威快照;`production_forge_station_real_place.py:114-158` 已用 `/give` 后真实 `instance_id` 放砧并断言消耗快照,故“生产物品实例不可见”旧缺口已闭环,只需归档旧 skeleton。 +- `load_player_inventory_from_sqlite`(`server/src/player/state.rs:1372`)已形成 schema-version 分流、纯内存迁移和 hydration 后校验接缝;pre-#249 布局迁移应在 R3 P1 抽出的接缝上调用 R10 的纯迁移函数,不应在 persistence 巨石中再造一套 inventory 规则。 + +## 接入面 + +- **进料**:R1 session 产物交付、R4 gate 通过后的 inventory 请求、world dropped-loot registry、R3 persistence inventory slice/hydration、craft/alchemy/forge/botany 等产出域。 +- **出料**:`InventoryTxn` 的交付/消费/拾取 receipt、权威 `InventoryRevision`、世界掉落记录、网格变更事实;公共 S2C 经 R6 拥有的 emit/proto 接缝发送。 +- **共享类型**:保留 `PlayerInventory`、`ItemInstance`、`ContainerState`、`InventoryLocationV1`、`InventoryRevision`、`owner_instance_id` 和现有 `ItemCategory` 合法集;不建立兼容层或平行模型。 +- **跨仓库契约**:server 产出 `inventory_snapshot` / `inventory_event`;client 消费权威 footprint;bot 通过同一 wire payload 验证 instance、revision、move/merge/spill。R10 只冻结所需 payload 事实,R6 拥有公共 S2C schema/emit 接缝改动。 +- **worldview 锚点**:物品不凭空消失、满包产物落在争夺现场,对齐 `worldview.md` §十三末法物资稀缺与争夺语义;唯一真货币仍为骨币,R10 不改经济定义。 +- **qi_physics 锚点**:普通物品归属/位置变化不是新的真元流,R10 不新增真元常数或公式。含真元物品的磨损、销毁、衰变仍调用 R5/既有 ledger 语义;spill 与堆叠不得重置或吞掉动态真元字段。 + +## P0 — 设计收口 + 吸收清单验真(✅ 2026-08-03) + +### P0.1 职责拆分图 + +P1 按下表迁移 symbol;先做机械拆分和测试平移,不在同一提交中改行为。 + +| 目标模块 | 独占职责 | 首批迁入 symbol / 数据 | +|---|---|---| +| `inventory/model.rs` | inventory 领域模型与 revision/receipt 基础类型 | `PlayerInventory`、`ItemInstance`、`ContainerState`、`PlacedItemState`、装备/位置/修订类型 | +| `inventory/registry.rs` | template registry、TOML/loadout 解析、实例创建 | `ItemRegistry`、template lookup、instance allocator/construction | +| `inventory/grid.rs` | footprint、碰撞、fit、attach/detach、move/swap/rotate | `find_free_slot`、`find_first_fit_container_location`、`validate_attach_fits`、`attach_at_location`、`apply_inventory_move` | +| `inventory/txn.rs` | staged delivery/consume/pickup/merge 与统一 receipt | staged grant、`stack_identity_matches`、新增 `InventoryTxn` | +| `inventory/container.rs` | 穿戴套包 owner、派生容器重建、容量/重量、overflow spill | worn-pack helpers、`rebuild_containers_from_equipment`、`rebuild_and_drop_overflow` | +| `inventory/corpse.rs` | 死亡掉落、遗骸 inventory 与全量转移编排 | 保留现有 corpse 模块;将 `transfer_all_inventory_contents` 改为消费 txn/grid API,不再强塞 | +| `inventory/freshness.rs` | 仅 freshness 数据与 inventory construction 接缝 | 保留现有模块;衰变公式继续由 shelflife/R5 单一实现,不复制到 txn | +| `inventory/mod.rs` | 模块声明、稳定 re-export、Bevy plugin/system wiring | 不再承载具体算法或大块测试 | + +测试跟随所属模块迁移;跨模块契约测试放 `inventory/tests` 或现有集成测试文件,不把测试继续堆回 `mod.rs`。 + +### P0.2 冻结 `InventoryTxn` 契约 + +P1/P2 必须落地以下稳定语义;具体 Rust lifetime 可在实现时按借用检查器调整,但名称、输入事实、结果事实和错误边界不得漂移: + +```rust +pub struct InventoryTxn<'a> { /* staged view over one PlayerInventory */ } + +impl InventoryTxn<'_> { + pub fn deliver( + &mut self, + request: DeliveryRequest, + spill: Option<&mut SpillContext<'_>>, + ) -> Result; + + pub fn consume_checked( + &mut self, + request: ConsumeRequest, + ) -> Result; + + pub fn pickup_and_merge( + &mut self, + request: PickupRequest, + dropped: &mut DroppedLootRegistry, + ) -> Result; +} +``` + +- `DeliveryRequest` 必须同时表达“按 template 创建”与“交付既有 `ItemInstance`”;后者保留 instance id、耐久、freshness、attributes/NBT 等动态身份,不允许重建成默认实例。 +- `InventoryDeliveryReceipt` 至少包含:最终 `revision`、`created_instance_ids`、直接放入的 instance ids、逐项 merge 的 source/target/count、逐项 spill 的 dropped id/instance id。允许“部分 merge + 剩余落地”,但总量必须守恒。 +- `InventoryConsumeReceipt` 至少包含最终 `revision` 与逐 instance 扣除量;所有 template、数量、身份和持有量前置条件须在 mutation 前完成验证,失败时 inventory/revision 不变。 +- `InventoryPickupReceipt` 至少包含最终 `revision`、被移除的 dropped id、merge/placement 明细;只有 inventory commit 成功后才能从 `DroppedLootRegistry` 删除世界掉落。 +- 堆叠资格统一复用完整 identity 规则;仅 `template_id` 相等不足以合并。freshness、耐久、attributes/NBT 或其它影响物品身份的字段不一致时必须分栈。 +- 所有成功 txn 至多 bump 一次 revision;结构拒绝与容量拒绝不得 bump。receipt 是调用域和 bot 的权威观察面,不再依赖错误字符串前缀或猜测 snapshot 差异。 +- `InventoryTxnError` 至少区分:unknown template、zero quantity、missing container/location、instance id conflict、identity mismatch、insufficient items、capacity exceeded、invalid grid placement、missing spill context。结构错误绝不伪装成 spill。 + +### P0.3 冻结满包与网格策略 + +1. **统一策略:机制结算点/玩家脚下地面掉落,不建个人暂存箱。** craft、alchemy、forge、give、loot 使用同一 delivery fallback;世界坐标和 dimension 由调用者提供。 +2. 只有经过完整验证后确认的容量不足可以 spill。unknown template、零数量、无容器、ID 冲突、非法 footprint 等必须返回结构错误并保持源状态不变。 +3. 调用者没有真实 `SpillContext` 时 fail closed,返回 `CapacityExceeded`/`MissingSpillContext`;不得用虚构坐标,也不得清 session、扣材料或删除世界掉落。 +4. spill 必须携带原 `ItemInstance`;对 minted stack 可按合法最大堆叠拆分,对既有动态实例不得通过重新 mint 丢字段。 +5. 所有 attach 都必须先通过 `validate_attach_fits` 并由 `attach_at_location` 提交;删除 `force_attach_item_to_inventory` 的 `(0,0)` 强塞 fallback。全量转移放不下的剩余项进入显式 spill receipt,而不是重叠占格。 +6. rotate/move receipt 必须携带变更后的完整 item view 或等价 `grid_w/grid_h + rotated` 事实。R10 定义所需事实,R6 负责公共 S2C schema/emit 接缝,client 以权威新 footprint 替换旧 view。 + +### P0.4 文件所有权与跨轨边界 + +- **R10 独占**:`server/src/inventory/**`;各产出域只改为调用冻结的 transaction API,不接管其 session/玩法状态机。 +- **R1**:拥有 session 生命周期。R10 返回 delivery 成败与 receipt;R1 只能在成功存入或成功 spill 后清 session,失败必须保留可重试状态。 +- **R3**:拥有 `server/src/persistence/**`、autosave 与 hydration 编排。R10 在 `inventory/migration.rs` 提供纯、幂等的旧布局转换/校验;R3 P1 的 slice loader 调用它。R10 不直接重构 persistence 巨石。 +- **R4**:拥有 C2S gate、距离/权限/状态校验;通过 gate 后才调用 R10 txn。R10 不重复做网络身份鉴权。 +- **R5**:拥有 qi ledger、磨损/衰变物理;R10 只保留动态字段并调用其稳定接口。 +- **R6**:拥有公共 S2C emit/proto 接缝。rotate footprint、merge/spill/move receipt 的 wire 扩展必须消费 R6 冻结 API或交由 R6 落地,不在 R10 私造平行 payload。 +- **R7**:拥有 `InspectScreen`;R10 只提供权威 inventory model/receipt,不改 UI 导航架构。 + +### P0.5 吸收清单验真 + +| plan(省略 `plan-bughunt-` / `-v1`) | 2026-08-03 裁决 | 第一性证据与 R10 落点 | +|---|---|---| +| `alchemy-takeback-full-inventory-loss` | **吸收交付部分** | 满包产物必须先 deliver/spill 成功再由 R1 清 session;session teardown 本身仍归 R1。P2 delivery matrix 锁定。 | +| `dropped-loot-pickup-stack-merge` | **吸收,仍真实** | `pickup_dropped_loot_instance` 只找空 footprint,不 merge。P2 `pickup_and_merge` + incoming-only attrition receipt。 | +| `force-attach-grid-collision` | **吸收,仍真实** | `force_attach_item_to_inventory` 可直接压入 `(0,0)`,且全量转移可达。P3 删除强塞并用合法 attach/spill。 | +| `rotate-footprint-sync` | **吸收,仍真实** | server 已旋转 footprint,`Moved` S2C 不带新 view,client 复用旧尺寸。P3 与 R6 接缝联动。 | +| `bot-inventory-pack-feedback` | **吸收,仍真实** | pack rebuild 成功路径 snapshot 后提前返回,未发普通 moved receipt。P3 统一 receipt;P4 bot 断言。 | +| `bot-production-inventory-instance-visibility` | **已闭环,只归档** | `Changed` 自动 snapshot;现有 forge bot 已从 `/give` 快照取真实 instance id 放砧并验证消耗。不再新增实现。 | +| `forge-outcome-full-inventory-loss` | **吸收,仍真实** | #1294 已于 2026-07-27 merge,但只建立 bughunt skeleton,并非修复在飞。P2 forge outcome 改走统一 deliver/spill。 | + +**不吸收**: + +- `craft-refund-full-inventory-loss`:已在 `docs/finished_plans/` 独立闭环,不重复实现。 +- `container-filter-and-completion`:独立 feature plan,R10 只提供稳定 transaction/grid 底座。 +- `nested-pack-base`:已在 `docs/finished_plans/` 以 WITHDRAWN/独立结案,不复活旧范围。 + +### P0.6 pre-#249 老布局迁移决议 + +**决议**:并入 R10 P3,一次清账,但严格等待 R3 P1。 + +1. R10 在 `server/src/inventory/migration.rs` 提供纯函数,例如 `migrate_legacy_inventory_layout(value, schema_version) -> MigrationOutcome`;输入/输出只涉及 inventory slice,不执行 SQL。 +2. 迁移挂到 R3 的 hydration seam:现有依据为 `server/src/player/state.rs:1372-1433` 的 schema-version 分流与反序列化路径;R3 P1 拆分后由稳定 loader 调用。 +3. 迁移幂等:新布局不变;旧 5×7/main-pack 形态转换为当前容器布局;所有 item instance 与动态字段保留;无法合法放入的物品走显式 migration spill/handoff,绝不静默截断。 +4. 迁移成功后由 R3 保存新 schema 形状;R10 不直接写 `inventories` 表、不修改 autosave 所有权。 +5. P3 测试至少覆盖:旧布局 happy path、空库存、边界填满、动态字段保留、重复迁移不变、非法/损坏 JSON 拒绝、overflow 明细守恒。 + +**落点**:`server/src/player/state.rs:1372-1433`(当前依据)→ R3 P1 后的 inventory slice loader;本 plan §P3。 + +### P0.7 开放问题决议(pre-P0 收口,2026-08-03) + +#### #1 满包溢出策略 + +**决议**:选择玩家脚下/机制结算点地面掉落,拒绝个人暂存箱。 + +- 复用现役 `add_item_to_player_inventory_or_ground` 与 `rebuild_and_drop_overflow` 语义,减少第二套持久化、权限、过期与 UI 生命周期。 +- 地面掉落保留末法争夺风险;通过 dropped id + instance id + world position/dimension receipt 保证可追踪。 +- 仅容量不足可降级,结构错误 fail closed;无真实世界上下文不得伪造 fallback。 + +**落点**:`server/src/inventory/mod.rs:1839`、`:5016`(依据);本 plan §P0.2、§P0.3、§P2。 + +#### #2 pre-#249 老布局迁移是否并入 + +**决议**:并入 P3,但在 R3 P1 后实施;R10 提供纯迁移,R3 负责 load/save 编排。 + +**落点**:`server/src/player/state.rs:1372-1433`(依据);本 plan §P0.6、§P3。 + +原开放问题全部已在本节收口。原题保留如下以备追溯,**实施时以 §P0.7 决议为准**: + +1. 满包溢出策略:脚下掉落 vs 个人暂存箱。 +2. pre-#249 老存档迁移是否并入本轨 P3。 + +## P1 — 巨石拆分 + `InventoryTxn` 骨架(⬜) + +> 前置:R3 P1 已 merge;仅机械拆分,不夹带 P2/P3 行为修复。 + +- 按 §P0.1 建立 `model.rs`、`registry.rs`、`grid.rs`、`txn.rs`、`container.rs`、`migration.rs`,收薄 `mod.rs`;现有 public import 直接迁到新 canonical 路径并统一 re-export,不建立双实现兼容层。 +- 平移现有测试并保持行为等价;新增 module ownership pin,禁止 grid/txn 算法回流 `mod.rs`。 +- 落地 §P0.2 类型骨架、staged validation/commit 基础设施与 error/receipt pin tests;生产调用暂不全量切换。 +- 验收:server 完整门禁全绿;`mod.rs` 不再承担具体 inventory 算法;现有 inventory 测试数量不得减少。 + +## P2 — 交付/消费路径统一(⬜) + +- give、craft、alchemy、forge、loot/pickup 全部改走 `InventoryTxn::deliver` / `consume_checked` / `pickup_and_merge`。 +- 删除按错误字符串前缀判断 public fallback 的调用契约,改用 typed error/disposition。 +- session 类调用严格执行“txn 成功(stored 或 spilled)后才 teardown”;失败不扣料、不删 session、不删 dropped entry。 +- 饱和测试覆盖:空包、恰好放满、少一格、同身份全 merge/部分 merge、不同动态身份拒绝 merge、无 spill context、每类结构错误、revision 单次 bump、总量守恒。 + +## P3 — 网格/堆叠一致性 + 迁移(⬜) + +- 删除 `force_attach_item_to_inventory` 强塞路径;全量转移、遗骸与套包重建统一合法 attach,overflow 返回显式 spill receipt。 +- 拾取合并、rotate footprint、pack move receipt 落地;公共 S2C 变更服从 R6 文件所有权。 +- 按 §P0.6 完成 pre-#249 幂等迁移并接入 R3 hydration seam。 +- 饱和测试覆盖每个 footprint 边界、碰撞/swap/rotate 状态、pack rebuild overflow、迁移正反/幂等/字段守恒。 + +## P4 — bot 验收 + 被吸收 plan 收口(⬜) + +1. `inv_full_delivery_matrix`:满包下 craft 完工、取丹、锻造出炉、give,断言 stored + spilled 总量不丢且失败不提前 teardown。 +2. `inv_stack_merge`:拾取同身份掉落,断言合并既有堆叠、revision 与 receipt;不同动态身份保持分栈。 +3. `inv_footprint_sync`:旋转/移动后断言 server snapshot、S2C event 与 client footprint 一致。 +4. `inv_pack_feedback`:pack stow/rebuild 成功和 overflow 均有稳定 typed receipt/权威 snapshot。 +5. `inv_give_visibility`:保留现有 production forge 真实 instance 可见性回归,不重复搭建平行场景。 +6. 按 §P0.5 逐项核验被吸收 skeleton 的代码、测试与证据,再依仓库三态规则分别收口;本 plan 未全部完成前不写 `Finish Evidence`、不归档。 diff --git a/docs/plans-skeleton/plan-refactor-inventory-core-v1.md b/docs/plans-skeleton/plan-refactor-inventory-core-v1.md deleted file mode 100644 index 63e88cae2..000000000 --- a/docs/plans-skeleton/plan-refactor-inventory-core-v1.md +++ /dev/null @@ -1,48 +0,0 @@ -# plan-refactor-inventory-core-v1 — Inventory 巨石拆分 + 网格/交付事务一致性(重构轨 R10) - -> 所属总纲:`plan-refactor-master-v1.md`。一句话:拆掉 20165 行的 `inventory/mod.rs`,把"给予/交付/拾取/堆叠/占格"改成统一事务 API(满包不丢物、先校验后扣、堆叠必合并、占格必同步)——物品凭空消失/孤儿物品整簇收口。 - -## 现状证据(2026-07-27 侦察) - -- `server/src/inventory/mod.rs` 20165 行、目录 89% 代码在单文件——全仓第一大 god module。 -- 交付路径各写各的:满包退款只记日志(craft-refund,active 接近完成)、满包取丹先清 session 丢产物(alchemy-takeback)、锻造产物满包丢失(forge-outcome,#1294 在飞)、满包强塞无碰撞检测叠孤儿物品(force-attach-grid-collision)。 -- 一致性缺口:世界掉落拾取不合并已有堆叠(dropped-loot-pickup-stack-merge)、旋转后占格不同步 client(rotate-footprint-sync)、give 后快照拾取不到实例(bot-production-inventory-instance-visibility)、pack stow 无稳定回执(bot-inventory-pack-feedback)。 -- 历史包袱:pre-#249 老存档卡旧布局(memory 已记,值得随拆分一并处理);tarkov 套包 `owner_instance_id` 全栈已落地是既定架构,不动。 - -## 接入面 - -- **进料**:R1 session 产物交付调用、R4 gate 通过后的物品类请求、掉落物系统、R3 的持久化 slice(inventories 表)。 -- **出料**:统一 `InventoryTxn` API:`deliver(items) -> Delivered | Spilled(fallback)`(满包溢出策略统一:脚下掉落/暂存箱,按 worldview 拍板)、`consume_checked`(先校验后扣)、`merge_stack`、占格变更事件(S2C 经 R6)。 -- **共享类型**:`ItemCategory` 合法集不动(无 Material,材料用 Misc——历史坑);`owner_instance_id` 架构不动。 -- **worldview 锚点**:物品不凭空消失对齐末法稀缺经济(§十三物资锚点);含真元物品的销毁/溢出走 R5 ledger。 - -## 阶段 - -- ⬜ P0 设计收口 + 吸收清单验真:mod.rs 职责普查出拆分图(grid/txn/container/corpse/shelflife 接缝);冻结 `InventoryTxn` API 与满包溢出策略;等 craft-refund P4、#1294 相关项定基线。 -- ⬜ P1 巨石拆分:按职责拆文件(行为不变,测试平移),`InventoryTxn` 骨架上线。 -- ⬜ P2 交付路径统一:give/craft/alchemy/forge/loot 全部改走 `deliver`;先校验后扣全量化;满包场景全绿。 -- ⬜ P3 网格/堆叠一致性:拾取合并、占格同步、pack 回执、老存档布局迁移补课。 -- ⬜ P4 bot 验收 + 吸收 plan 批量归档。 - -## 吸收清单(短名省略 plan-bughunt- 前缀与 -v1 后缀) - -skeleton:alchemy-takeback-full-inventory-loss(交付垫层部分;session teardown 归 R1)、dropped-loot-pickup-stack-merge、force-attach-grid-collision、rotate-footprint-sync、bot-inventory-pack-feedback、bot-production-inventory-instance-visibility;在飞 #1294:forge-outcome-full-inventory-loss。 -**不吸收**:craft-refund-full-inventory-loss(active,P0-P3 已 ✅,独立收尾 P4);container-filter-and-completion(feature,独立);nested-pack-base(已 WITHDRAWN,#1275)。 - -## 文件所有权与边界 - -- 独占:`server/src/inventory/**`、各域交付调用点的替换行。 -- 不碰:`InspectScreen`(R7 域);session 生命周期(R1);掉落物拾取的 gate 校验(R4)。 -- 依赖:R3 P1(persistence 拆分先行,inventories 表接缝清晰);与 R1 的交付接缝 API 由本轨定义、R1 消费。Wave 2 开工,P0 普查可先行。 - -## bot 验收场景 - -1. `inv_full_delivery_matrix`:满包状态下 craft 完工/取丹/锻造出炉/给予→断言产物按统一溢出策略落地,总数不丢。 -2. `inv_stack_merge`:拾取同类掉落→断言合并入既有堆叠。 -3. `inv_footprint_sync`:旋转/移动占格物品→断言 client 快照占格一致(P6 protobuf 深断言)。 -4. `inv_give_visibility`:dev give 后立即快照→断言实例可见可拾取(修 bot 基建自身的假阳性)。 - -## 开放问题(pre-P0 收口) - -1. 满包溢出策略正典拍板:脚下掉落(可被他人捡走,符合末法残酷)vs 个人暂存箱(体验友好)——需人工定。 -2. pre-#249 老存档迁移是否并入本轨 P3(倾向并入,一次清账)。 From 63a0d8b35ae650c6322a480bb4fdce774507f9a8 Mon Sep 17 00:00:00 2001 From: server_kizuna Date: Mon, 3 Aug 2026 09:36:04 +0800 Subject: [PATCH 02/13] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20R10=20P0=EF=BC=9A?= =?UTF-8?q?=E8=A1=A5=E9=BD=90=E8=B7=A8=E5=B1=82=E5=9B=9E=E6=89=A7=E9=AA=8C?= =?UTF-8?q?=E6=94=B6=E8=B4=A3=E4=BB=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 恢复灵田锄实例深链回归,收紧背包移动的动作级 accepted/rejected 门禁,并把 inventory outcome 到 S2C/client 的接线明确分配给 R10、R6、R4 具体阶段。 Model: cc-sonnet-high Co-Authored-By: Claude --- docs/plan-refactor-inventory-core-v1.md | 30 ++++++++++++++++--------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/docs/plan-refactor-inventory-core-v1.md b/docs/plan-refactor-inventory-core-v1.md index 7b0adcf28..797a1c671 100644 --- a/docs/plan-refactor-inventory-core-v1.md +++ b/docs/plan-refactor-inventory-core-v1.md @@ -21,7 +21,7 @@ - `pickup_dropped_loot_instance`(`server/src/inventory/mod.rs:5337`)只找空 footprint,不尝试合并已有同身份堆叠,拾取合并缺口仍存在。 - server 的 `apply_inventory_move`(`server/src/inventory/mod.rs:3828`)会正确交换旋转后的 `grid_w/grid_h`,但 `InventoryEventV1::Moved`(`server/src/schema/inventory.rs:294`)只带 from/to;client `InventoryEventHandler`(`:85`)复用旧 item view,因此 delta 路径会保留旧 footprint。 - pack move 成功分支在 `handle_inventory_move`(`server/src/network/client_request_handler.rs:15310`)执行 `worn_pack_rebuild` snapshot 后提前返回;普通 `send_moved_event`(`:15590`)未覆盖该分支,稳定成功回执缺口仍存在。 -- `emit_changed_inventory_snapshots`(`server/src/network/inventory_snapshot_emit.rs:93`)会为 `Changed` 自动推送权威快照;`production_forge_station_real_place.py:114-158` 已用 `/give` 后真实 `instance_id` 放砧并断言消耗快照,故“生产物品实例不可见”旧缺口已闭环,只需归档旧 skeleton。 +- `emit_changed_inventory_snapshots`(`server/src/network/inventory_snapshot_emit.rs:93`)会为 `Changed` 自动推送权威快照;forge 路径已在 `production_forge_station_real_place.py:114-158` 从 `/give` 后快照取得真实 `instance_id` 并完成放砧/消耗断言,但该证据**不能代表所有生产系统已闭环**。`production_lingtian_gathering_intents.py:9-11,53-68` 仍明确使用 `hoe_instance_id: 0`,只等任意 server-data 回流;`hoe_iron` 的 producer→snapshot→equip/held→`lingtian_start_till` 深链仍未覆盖。 - `load_player_inventory_from_sqlite`(`server/src/player/state.rs:1372`)已形成 schema-version 分流、纯内存迁移和 hydration 后校验接缝;pre-#249 布局迁移应在 R3 P1 抽出的接缝上调用 R10 的纯迁移函数,不应在 persistence 巨石中再造一套 inventory 规则。 ## 接入面 @@ -94,18 +94,25 @@ impl InventoryTxn<'_> { 3. 调用者没有真实 `SpillContext` 时 fail closed,返回 `CapacityExceeded`/`MissingSpillContext`;不得用虚构坐标,也不得清 session、扣材料或删除世界掉落。 4. spill 必须携带原 `ItemInstance`;对 minted stack 可按合法最大堆叠拆分,对既有动态实例不得通过重新 mint 丢字段。 5. 所有 attach 都必须先通过 `validate_attach_fits` 并由 `attach_at_location` 提交;删除 `force_attach_item_to_inventory` 的 `(0,0)` 强塞 fallback。全量转移放不下的剩余项进入显式 spill receipt,而不是重叠占格。 -6. rotate/move receipt 必须携带变更后的完整 item view 或等价 `grid_w/grid_h + rotated` 事实。R10 定义所需事实,R6 负责公共 S2C schema/emit 接缝,client 以权威新 footprint 替换旧 view。 +6. rotate/move receipt 必须携带变更后的完整 item view 或等价 `grid_w/grid_h + rotated` 事实。R10 的 inventory outcome 提供所需事实;R4 在其独占 handler 中把 outcome 交给 emit API;R6 扩展公共 S2C schema/emit 并让 client 以权威新 footprint 替换旧 view。三段任一未完成均不得判 P3/P4 完成。 ### P0.4 文件所有权与跨轨边界 - **R10 独占**:`server/src/inventory/**`;各产出域只改为调用冻结的 transaction API,不接管其 session/玩法状态机。 - **R1**:拥有 session 生命周期。R10 返回 delivery 成败与 receipt;R1 只能在成功存入或成功 spill 后清 session,失败必须保留可重试状态。 - **R3**:拥有 `server/src/persistence/**`、autosave 与 hydration 编排。R10 在 `inventory/migration.rs` 提供纯、幂等的旧布局转换/校验;R3 P1 的 slice loader 调用它。R10 不直接重构 persistence 巨石。 -- **R4**:拥有 C2S gate、距离/权限/状态校验;通过 gate 后才调用 R10 txn。R10 不重复做网络身份鉴权。 +- **R4**:拥有 C2S gate 与 `server/src/network/client_request_handler.rs`。通过 gate 后调用 R10 txn;对于 rotate/pack move,R4 还负责把 R10 outcome 的 request identity、revision 和新 item view 传给 R6 emit API,但不定义 wire schema。 - **R5**:拥有 qi ledger、磨损/衰变物理;R10 只保留动态字段并调用其稳定接口。 -- **R6**:拥有公共 S2C emit/proto 接缝。rotate footprint、merge/spill/move receipt 的 wire 扩展必须消费 R6 冻结 API或交由 R6 落地,不在 R10 私造平行 payload。 +- **R6**:拥有公共 S2C schema/emit 与 client network consumer。R6 **P0 吸收登记**必须把 `rotate-footprint-sync`、`bot-inventory-pack-feedback` 的 wire 部分正式加入 `plan-refactor-wire-s2c-v1`;R6 **P4 inventory receipt contract 子批次**负责修改 `server/src/schema/inventory.rs` 及双端 samples/convert,使成功 `moved`(或等价 accepted receipt)携带请求可归因字段、结果 revision 和权威 item view,修改 inventory emit API 与 `client/.../InventoryEventHandler.java`,并补 server schema/emit、client handler、Python decoder contract tests。**在 R6 P0 登记与 P4 子批次 merge 前,R10 P3 不得标 ✅、P4 不得归档相关 skeleton;若 R6 尚未登记,R10 实施必须停在此门而不是用 snapshot 替代。** - **R7**:拥有 `InspectScreen`;R10 只提供权威 inventory model/receipt,不改 UI 导航架构。 +#### 跨轨执行顺序(P3 硬门) + +1. **R10 P3 inventory PR**:在 `server/src/inventory/**` 产出 rotate/pack move 的 typed outcome,含 `instance_id/from/to/revision/item_view`;不触碰 wire 文件。 +2. **R6 P0 + P4 wire PR**:P0 先更新 `plan-refactor-wire-s2c-v1` 的吸收清单/阶段;P4 的 inventory receipt contract 子批次再落地 schema → sample/convert → emit API → Fabric client consumer → 双端 contract tests,其 API 对 R4 可调用。 +3. **R4 P2 handler PR**:在按域拆分 inventory handler 时,把 R10 outcome 交给 R6 emit;成功与拒绝都必须逐请求发稳定机器回执,snapshot 仅作状态修正。若 R4 P2 尚未启动,需先开该阶段的最小 inventory-handler 接线 PR,不得由 R10/R6 越权改 `client_request_handler.rs`。 +4. 三个 PR 均 merge 后,R10 才运行 §P4 的跨层 bot gate,并以实际 server→wire→client/bot 链路作为吸收归档证据。 + ### P0.5 吸收清单验真 | plan(省略 `plan-bughunt-` / `-v1`) | 2026-08-03 裁决 | 第一性证据与 R10 落点 | @@ -115,7 +122,7 @@ impl InventoryTxn<'_> { | `force-attach-grid-collision` | **吸收,仍真实** | `force_attach_item_to_inventory` 可直接压入 `(0,0)`,且全量转移可达。P3 删除强塞并用合法 attach/spill。 | | `rotate-footprint-sync` | **吸收,仍真实** | server 已旋转 footprint,`Moved` S2C 不带新 view,client 复用旧尺寸。P3 与 R6 接缝联动。 | | `bot-inventory-pack-feedback` | **吸收,仍真实** | pack rebuild 成功路径 snapshot 后提前返回,未发普通 moved receipt。P3 统一 receipt;P4 bot 断言。 | -| `bot-production-inventory-instance-visibility` | **已闭环,只归档** | `Changed` 自动 snapshot;现有 forge bot 已从 `/give` 快照取真实 instance id 放砧并验证消耗。不再新增实现。 | +| `bot-production-inventory-instance-visibility` | **部分闭环,剩余契约吸收** | forge 已从 `/give` 快照取真实 anvil instance 并跑通放砧;灵田场景仍把 `hoe_instance_id` 写死为 `0`,未验证 `hoe_iron` 的 snapshot/equip/真实 till 链。P4 必须恢复该深断言后才能归档。 | | `forge-outcome-full-inventory-loss` | **吸收,仍真实** | #1294 已于 2026-07-27 merge,但只建立 bughunt skeleton,并非修复在飞。P2 forge outcome 改走统一 deliver/spill。 | **不吸收**: @@ -178,7 +185,8 @@ impl InventoryTxn<'_> { ## P3 — 网格/堆叠一致性 + 迁移(⬜) - 删除 `force_attach_item_to_inventory` 强塞路径;全量转移、遗骸与套包重建统一合法 attach,overflow 返回显式 spill receipt。 -- 拾取合并、rotate footprint、pack move receipt 落地;公共 S2C 变更服从 R6 文件所有权。 +- R10 在 `server/src/inventory/**` 落地拾取合并及 rotate/pack typed outcome;不得把权威 snapshot 当作动作级 receipt。 +- **跨轨阻塞交付**:严格执行 §P0.4 的 R10 → R6 → R4 顺序。R6 PR 必须完成 schema/sample/convert/emit/client handler/contract tests,R4 PR 必须完成 handler 调用;两者未 merge 时,本阶段保持 ⬜,不得归档 `rotate-footprint-sync` 或 `bot-inventory-pack-feedback`。 - 按 §P0.6 完成 pre-#249 幂等迁移并接入 R3 hydration seam。 - 饱和测试覆盖每个 footprint 边界、碰撞/swap/rotate 状态、pack rebuild overflow、迁移正反/幂等/字段守恒。 @@ -186,7 +194,9 @@ impl InventoryTxn<'_> { 1. `inv_full_delivery_matrix`:满包下 craft 完工、取丹、锻造出炉、give,断言 stored + spilled 总量不丢且失败不提前 teardown。 2. `inv_stack_merge`:拾取同身份掉落,断言合并既有堆叠、revision 与 receipt;不同动态身份保持分栈。 -3. `inv_footprint_sync`:旋转/移动后断言 server snapshot、S2C event 与 client footprint 一致。 -4. `inv_pack_feedback`:pack stow/rebuild 成功和 overflow 均有稳定 typed receipt/权威 snapshot。 -5. `inv_give_visibility`:保留现有 production forge 真实 instance 可见性回归,不重复搭建平行场景。 -6. 按 §P0.5 逐项核验被吸收 skeleton 的代码、测试与证据,再依仓库三态规则分别收口;本 plan 未全部完成前不写 `Finish Evidence`、不归档。 +3. `inv_footprint_sync`:发送 `rotated=true` 的 2×1 move 后,必须收到**该请求之后**、匹配 `instance_id/from/to` 与结果 revision 的 accepted/moved receipt,且 receipt 中权威 item view 为 1×2;Fabric handler contract test 应把本地模型替换为 1×2。拒绝、1×1、非网格目标不得误改 footprint。全量 snapshot 不能替代此动作级断言。 +4. `inv_pack_feedback`:对 stow / 空 pack unequip / equip 三个成功请求,逐个等待发送时间锚之后、匹配 `instance_id/from/to` 的 accepted/moved receipt,并断言 receipt revision 与随后 snapshot 一致;对非法位置、非空 pack(若规则禁止)逐个断言 `inventory_move_rejected` 的 reason/instance/from/to,连接保持;预置同 instance 的旧 moved/rejected 事件,证明时间锚与结果 revision 不会误读 stale feedback。VFX 和 snapshot 只可附加验证,**不得作为唯一动作结果或 accepted/rejected 的替代**。 +5. `inv_give_visibility_forge`:保留 production forge 的 `/give fan_iron_anvil` → snapshot 真实 instance id → `forge_station_place` → 消耗快照深链回归。 +6. `inv_give_visibility_lingtian`:改造 `production_lingtian_gathering_intents.py`,禁止 `hoe_instance_id: 0` 和“任意 server-data 即成功”;`/give hoe_iron` 后必须在带时间锚的新 `inventory_snapshot` 找到 `hoe_iron` 及真实 instance/location,按生产规则把该实例移到 main-hand held(等待匹配请求的 accepted receipt 与权威 snapshot),再用同一非零 instance id 发 `lingtian_start_till`,断言真实 `lingtian_session`/明确业务拒绝回执且请求未因 instance mismatch 被拒。只有 producer→snapshot→equip→till 全链通过,才可归档 visibility skeleton。 +7. Python server-data decoder 对新增/扩展 inventory receipt 做正反样本测试;bot 场景的 request correlation 必须同时使用发送时间锚、instance id、from/to 和 revision,不得命中历史事件。 +8. 按 §P0.5 逐项核验被吸收 skeleton 的代码、跨轨 merge SHA、client/bot 测试与证据,再依仓库三态规则分别收口;本 plan 未全部完成前不写 `Finish Evidence`、不归档。 From e3a28428f3ffee042643f3c95630396fab181937 Mon Sep 17 00:00:00 2001 From: server_kizuna Date: Mon, 3 Aug 2026 11:26:37 +0800 Subject: [PATCH 03/13] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20R10=20P0=EF=BC=9A?= =?UTF-8?q?=E6=94=B6=E7=B4=A7=E6=8E=89=E8=90=BD=E4=B8=8E=E8=B7=A8=E8=BD=A8?= =?UTF-8?q?=E5=A5=91=E7=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Model: cc-sonnet-high Co-Authored-By: Claude --- docs/plan-refactor-inventory-core-v1.md | 35 +++++++++++-------- .../plan-refactor-persistence-slices-v1.md | 10 +++--- .../plan-refactor-qi-ledger-v1.md | 3 +- .../plan-refactor-wire-s2c-v1.md | 10 +++--- 4 files changed, 33 insertions(+), 25 deletions(-) diff --git a/docs/plan-refactor-inventory-core-v1.md b/docs/plan-refactor-inventory-core-v1.md index 797a1c671..bc0154f6b 100644 --- a/docs/plan-refactor-inventory-core-v1.md +++ b/docs/plan-refactor-inventory-core-v1.md @@ -80,9 +80,10 @@ impl InventoryTxn<'_> { ``` - `DeliveryRequest` 必须同时表达“按 template 创建”与“交付既有 `ItemInstance`”;后者保留 instance id、耐久、freshness、attributes/NBT 等动态身份,不允许重建成默认实例。 -- `InventoryDeliveryReceipt` 至少包含:最终 `revision`、`created_instance_ids`、直接放入的 instance ids、逐项 merge 的 source/target/count、逐项 spill 的 dropped id/instance id。允许“部分 merge + 剩余落地”,但总量必须守恒。 +- `InventoryDeliveryReceipt` 至少包含:最终 `revision`、`created_instance_ids`、直接放入的 instance ids、逐项 merge 的 source/target/count、逐项 spill 的 dropped id/instance id。允许“部分 merge + 剩余落地”,但总量必须守恒。交付既有 `ItemInstance` 时,receipt 中的 placed/spilled source id 必须仍是请求携带的 id,不能归入 `created_instance_ids`。 - `InventoryConsumeReceipt` 至少包含最终 `revision` 与逐 instance 扣除量;所有 template、数量、身份和持有量前置条件须在 mutation 前完成验证,失败时 inventory/revision 不变。 -- `InventoryPickupReceipt` 至少包含最终 `revision`、被移除的 dropped id、merge/placement 明细;只有 inventory commit 成功后才能从 `DroppedLootRegistry` 删除世界掉落。 +- `InventoryPickupReceipt` 至少包含最终 `revision`、被移除的 dropped id、merge/placement 明细,以及 `PickupAttritionBasis { target_instance_id, incoming_instance_id, incoming_stack_count, incoming_abs_qi_before }`。`target_instance_id` 指向提交后真实承载物品的实例(纯放置时等于 incoming id,merge 时为既有 stack id);`incoming_abs_qi_before = dropped.item.spirit_quality * incoming_stack_count`,禁止调用者再从合并后整栈反推。只有 inventory commit 成功后才能从 `DroppedLootRegistry` 删除世界掉落。 +- R5 的稳定 pickup attrition API 必须消费 `PickupAttritionBasis`:只从 `incoming_abs_qi_before` 计算损耗并归还 zone;merge 后目标整栈的绝对真元更新为 `preexisting_abs_qi + incoming_abs_qi_after`,不得对 pre-existing quantity 重复磨损。placement 路径同样通过 receipt 定位,不再假设 dropped id 必然可在 inventory 中二次查到。 - 堆叠资格统一复用完整 identity 规则;仅 `template_id` 相等不足以合并。freshness、耐久、attributes/NBT 或其它影响物品身份的字段不一致时必须分栈。 - 所有成功 txn 至多 bump 一次 revision;结构拒绝与容量拒绝不得 bump。receipt 是调用域和 bot 的权威观察面,不再依赖错误字符串前缀或猜测 snapshot 差异。 - `InventoryTxnError` 至少区分:unknown template、zero quantity、missing container/location、instance id conflict、identity mismatch、insufficient items、capacity exceeded、invalid grid placement、missing spill context。结构错误绝不伪装成 spill。 @@ -94,23 +95,25 @@ impl InventoryTxn<'_> { 3. 调用者没有真实 `SpillContext` 时 fail closed,返回 `CapacityExceeded`/`MissingSpillContext`;不得用虚构坐标,也不得清 session、扣材料或删除世界掉落。 4. spill 必须携带原 `ItemInstance`;对 minted stack 可按合法最大堆叠拆分,对既有动态实例不得通过重新 mint 丢字段。 5. 所有 attach 都必须先通过 `validate_attach_fits` 并由 `attach_at_location` 提交;删除 `force_attach_item_to_inventory` 的 `(0,0)` 强塞 fallback。全量转移放不下的剩余项进入显式 spill receipt,而不是重叠占格。 -6. rotate/move receipt 必须携带变更后的完整 item view 或等价 `grid_w/grid_h + rotated` 事实。R10 的 inventory outcome 提供所需事实;R4 在其独占 handler 中把 outcome 交给 emit API;R6 扩展公共 S2C schema/emit 并让 client 以权威新 footprint 替换旧 view。三段任一未完成均不得判 P3/P4 完成。 +6. dropped-loot registry 是有界 durable queue,不是无限容器:R10 定义 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 单一常量(初值 `4096`,测试只引用该常量),所有 delivery/discard/overflow 路径在 mutation 前按“本事务新增 dropped entry 数”原子预留容量。`len + required > MAX` 时返回 typed `DroppedLootCapacityExceeded { current, required, limit }`,inventory、revision、session、材料、世界掉落与 persistence 均保持不变;禁止逐项插入后才发现超限。启动 hydration 若数据库行数超过上限必须进入 R3 load-failure guard/只读降级并告警,不能截断、驱逐或覆盖旧行。 +7. 正常回收只允许玩家 pickup 或后续显式管理员运维;本 plan 不以 TTL/LRU/按价值驱逐静默销毁稀缺物品。每次 pickup 与 durable delete 同事务释放槽位。R6 的 `dropped_loot_sync` 必须分页/分片,每 payload 至多 `256` entries,携带 `snapshot_revision/page_index/page_count`;client 收齐同 revision 全部分片后原子替换视图,内容变化时不再对每个 client 构建/排序一份无界全量 payload。 +8. rotate/move receipt 必须携带变更后的完整 item view 或等价 `grid_w/grid_h + rotated` 事实。R10 的 inventory outcome 提供所需事实;R4 在其独占 handler 中把 outcome 交给 emit API;R6 扩展公共 S2C schema/emit 并让 client 以权威新 footprint 替换旧 view。三段任一未完成均不得判 P3/P4 完成。 ### P0.4 文件所有权与跨轨边界 - **R10 独占**:`server/src/inventory/**`;各产出域只改为调用冻结的 transaction API,不接管其 session/玩法状态机。 - **R1**:拥有 session 生命周期。R10 返回 delivery 成败与 receipt;R1 只能在成功存入或成功 spill 后清 session,失败必须保留可重试状态。 -- **R3**:拥有 `server/src/persistence/**`、autosave 与 hydration 编排。R10 在 `inventory/migration.rs` 提供纯、幂等的旧布局转换/校验;R3 P1 的 slice loader 调用它。R10 不直接重构 persistence 巨石。 +- **R3**:拥有 `server/src/persistence/**`、autosave 与 hydration 编排。R3 P1 先冻结 inventory slice hydration seam;R10 P3 再在 `inventory/migration.rs` 提供纯、幂等的旧布局转换/校验;R3 P4 的 inventory migration consumer 子批次调用它、保存新 schema,并执行 dropped-loot 有界 hydration guard。三项工作均已登记进 R3 canonical plan;R10 不直接重构 persistence 巨石。 - **R4**:拥有 C2S gate 与 `server/src/network/client_request_handler.rs`。通过 gate 后调用 R10 txn;对于 rotate/pack move,R4 还负责把 R10 outcome 的 request identity、revision 和新 item view 传给 R6 emit API,但不定义 wire schema。 -- **R5**:拥有 qi ledger、磨损/衰变物理;R10 只保留动态字段并调用其稳定接口。 -- **R6**:拥有公共 S2C schema/emit 与 client network consumer。R6 **P0 吸收登记**必须把 `rotate-footprint-sync`、`bot-inventory-pack-feedback` 的 wire 部分正式加入 `plan-refactor-wire-s2c-v1`;R6 **P4 inventory receipt contract 子批次**负责修改 `server/src/schema/inventory.rs` 及双端 samples/convert,使成功 `moved`(或等价 accepted receipt)携带请求可归因字段、结果 revision 和权威 item view,修改 inventory emit API 与 `client/.../InventoryEventHandler.java`,并补 server schema/emit、client handler、Python decoder contract tests。**在 R6 P0 登记与 P4 子批次 merge 前,R10 P3 不得标 ✅、P4 不得归档相关 skeleton;若 R6 尚未登记,R10 实施必须停在此门而不是用 snapshot 替代。** +- **R5**:拥有 qi ledger、磨损/衰变物理;R10 保留动态字段并产出 `PickupAttritionBasis`,R5 P3 已登记 incoming-only pickup attrition API 与守恒断言,R4 只传 receipt 不重算整栈真元。 +- **R6**:拥有公共 S2C schema/emit 与 client network consumer。R6 canonical plan 的 P0/吸收清单已登记 `rotate-footprint-sync`、`bot-inventory-pack-feedback`;R6 P1/P4 已登记 dropped-loot 分片快照与 inventory receipt contract 子批次,负责修改 `server/src/schema/inventory.rs` 及双端 samples/convert,使成功 `moved`(或等价 accepted receipt)携带请求可归因字段、结果 revision 和权威 item view,修改 inventory emit API 与 `client/.../InventoryEventHandler.java`,并补 server schema/emit、client handler、Python decoder contract tests。**在 R6 P4 子批次 merge 前,R10 P3 不得标 ✅、P4 不得归档相关 skeleton。** - **R7**:拥有 `InspectScreen`;R10 只提供权威 inventory model/receipt,不改 UI 导航架构。 #### 跨轨执行顺序(P3 硬门) 1. **R10 P3 inventory PR**:在 `server/src/inventory/**` 产出 rotate/pack move 的 typed outcome,含 `instance_id/from/to/revision/item_view`;不触碰 wire 文件。 2. **R6 P0 + P4 wire PR**:P0 先更新 `plan-refactor-wire-s2c-v1` 的吸收清单/阶段;P4 的 inventory receipt contract 子批次再落地 schema → sample/convert → emit API → Fabric client consumer → 双端 contract tests,其 API 对 R4 可调用。 -3. **R4 P2 handler PR**:在按域拆分 inventory handler 时,把 R10 outcome 交给 R6 emit;成功与拒绝都必须逐请求发稳定机器回执,snapshot 仅作状态修正。若 R4 P2 尚未启动,需先开该阶段的最小 inventory-handler 接线 PR,不得由 R10/R6 越权改 `client_request_handler.rs`。 +3. **R4 P2 handler PR**:在按域拆分 inventory handler 时,把 R10 outcome 交给 R6 emit,并把 `PickupAttritionBasis` 交给 R5 pickup attrition API;成功与拒绝都必须逐请求发稳定机器回执,snapshot 仅作状态修正。若 R4 P2 尚未启动,需先开该阶段的最小 inventory-handler 接线 PR,不得由 R10/R6 越权改 `client_request_handler.rs`。 4. 三个 PR 均 merge 后,R10 才运行 §P4 的跨层 bot gate,并以实际 server→wire→client/bot 链路作为吸收归档证据。 ### P0.5 吸收清单验真 @@ -136,7 +139,7 @@ impl InventoryTxn<'_> { **决议**:并入 R10 P3,一次清账,但严格等待 R3 P1。 1. R10 在 `server/src/inventory/migration.rs` 提供纯函数,例如 `migrate_legacy_inventory_layout(value, schema_version) -> MigrationOutcome`;输入/输出只涉及 inventory slice,不执行 SQL。 -2. 迁移挂到 R3 的 hydration seam:现有依据为 `server/src/player/state.rs:1372-1433` 的 schema-version 分流与反序列化路径;R3 P1 拆分后由稳定 loader 调用。 +2. 迁移挂到 R3 冻结的 hydration seam:现有依据为 `server/src/player/state.rs:1372-1433` 的 schema-version 分流与反序列化路径;R3 P1 先冻结 loader seam,R10 P3 落纯迁移,R3 P4 inventory migration consumer 子批次再调用并保存。 3. 迁移幂等:新布局不变;旧 5×7/main-pack 形态转换为当前容器布局;所有 item instance 与动态字段保留;无法合法放入的物品走显式 migration spill/handoff,绝不静默截断。 4. 迁移成功后由 R3 保存新 schema 形状;R10 不直接写 `inventories` 表、不修改 autosave 所有权。 5. P3 测试至少覆盖:旧布局 happy path、空库存、边界填满、动态字段保留、重复迁移不变、非法/损坏 JSON 拒绝、overflow 明细守恒。 @@ -150,7 +153,8 @@ impl InventoryTxn<'_> { **决议**:选择玩家脚下/机制结算点地面掉落,拒绝个人暂存箱。 - 复用现役 `add_item_to_player_inventory_or_ground` 与 `rebuild_and_drop_overflow` 语义,减少第二套持久化、权限、过期与 UI 生命周期。 -- 地面掉落保留末法争夺风险;通过 dropped id + instance id + world position/dimension receipt 保证可追踪。 +- 地面掉落保留末法争夺风险;通过 dropped id + instance id + world position/dimension receipt 保证可追踪,并受 §P0.3 的 durable registry 固定上限、原子容量预留和 R6 分片同步约束。 +- 不用 TTL/LRU 静默清理物品;队列满时 delivery fail closed 并保留源状态,玩家 pickup/管理员显式运维释放容量。 - 仅容量不足可降级,结构错误 fail closed;无真实世界上下文不得伪造 fallback。 **落点**:`server/src/inventory/mod.rs:1839`、`:5016`(依据);本 plan §P0.2、§P0.3、§P2。 @@ -171,9 +175,9 @@ impl InventoryTxn<'_> { > 前置:R3 P1 已 merge;仅机械拆分,不夹带 P2/P3 行为修复。 - 按 §P0.1 建立 `model.rs`、`registry.rs`、`grid.rs`、`txn.rs`、`container.rs`、`migration.rs`,收薄 `mod.rs`;现有 public import 直接迁到新 canonical 路径并统一 re-export,不建立双实现兼容层。 -- 平移现有测试并保持行为等价;新增 module ownership pin,禁止 grid/txn 算法回流 `mod.rs`。 +- 平移现有测试并保持行为等价;新增 module ownership pin,禁止 grid/txn 算法回流 `mod.rs`。按 master §0 的重构轨测试覆盖规则,仅契约 pin 必须保留/迁移;与被删实现绑定的旧单测允许随实现删除,不以原始数量为门禁。 - 落地 §P0.2 类型骨架、staged validation/commit 基础设施与 error/receipt pin tests;生产调用暂不全量切换。 -- 验收:server 完整门禁全绿;`mod.rs` 不再承担具体 inventory 算法;现有 inventory 测试数量不得减少。 +- 验收:server 完整门禁全绿;`mod.rs` 不再承担具体 inventory 算法;contract-pin 清单逐项迁移且无缺失,删除的 implementation-coupled tests 在 PR 描述中列出原测试名与删除理由。 ## P2 — 交付/消费路径统一(⬜) @@ -181,6 +185,8 @@ impl InventoryTxn<'_> { - 删除按错误字符串前缀判断 public fallback 的调用契约,改用 typed error/disposition。 - session 类调用严格执行“txn 成功(stored 或 spilled)后才 teardown”;失败不扣料、不删 session、不删 dropped entry。 - 饱和测试覆盖:空包、恰好放满、少一格、同身份全 merge/部分 merge、不同动态身份拒绝 merge、无 spill context、每类结构错误、revision 单次 bump、总量守恒。 +- 既有实例 round-trip contract pin:构造非默认 `instance_id/durability/freshness/mineral_id/charges/forge_*/alchemy/lingering_owner_qi` 的 `ItemInstance`,分别验证直接 placement 与 spill 后所有字段逐项不变、receipt 将原 id 记入 placed/spilled 而非 created;merge 只允许完整 identity 相等且 receipt 保留 source/target/count。 +- dropped registry bound pin:`limit-1`、恰好 `limit`、`limit+1`/单事务多 spill 越界;越界时 inventory/revision/session/材料/registry/persistence 全不变,pickup 后容量可复用,超限 hydration 进入 guard 而不截断。 ## P3 — 网格/堆叠一致性 + 迁移(⬜) @@ -193,10 +199,11 @@ impl InventoryTxn<'_> { ## P4 — bot 验收 + 被吸收 plan 收口(⬜) 1. `inv_full_delivery_matrix`:满包下 craft 完工、取丹、锻造出炉、give,断言 stored + spilled 总量不丢且失败不提前 teardown。 -2. `inv_stack_merge`:拾取同身份掉落,断言合并既有堆叠、revision 与 receipt;不同动态身份保持分栈。 +2. `inv_stack_merge`:拾取同身份掉落,断言合并既有堆叠、revision 与 receipt;receipt 的 `incoming_stack_count/incoming_abs_qi_before/target_instance_id` 驱动 pickup attrition,断言 ledger 只扣 incoming 绝对真元、旧 stack 原有绝对真元不变且玩家+zone 守恒;不同动态身份保持分栈。 3. `inv_footprint_sync`:发送 `rotated=true` 的 2×1 move 后,必须收到**该请求之后**、匹配 `instance_id/from/to` 与结果 revision 的 accepted/moved receipt,且 receipt 中权威 item view 为 1×2;Fabric handler contract test 应把本地模型替换为 1×2。拒绝、1×1、非网格目标不得误改 footprint。全量 snapshot 不能替代此动作级断言。 4. `inv_pack_feedback`:对 stow / 空 pack unequip / equip 三个成功请求,逐个等待发送时间锚之后、匹配 `instance_id/from/to` 的 accepted/moved receipt,并断言 receipt revision 与随后 snapshot 一致;对非法位置、非空 pack(若规则禁止)逐个断言 `inventory_move_rejected` 的 reason/instance/from/to,连接保持;预置同 instance 的旧 moved/rejected 事件,证明时间锚与结果 revision 不会误读 stale feedback。VFX 和 snapshot 只可附加验证,**不得作为唯一动作结果或 accepted/rejected 的替代**。 5. `inv_give_visibility_forge`:保留 production forge 的 `/give fan_iron_anvil` → snapshot 真实 instance id → `forge_station_place` → 消耗快照深链回归。 6. `inv_give_visibility_lingtian`:改造 `production_lingtian_gathering_intents.py`,禁止 `hoe_instance_id: 0` 和“任意 server-data 即成功”;`/give hoe_iron` 后必须在带时间锚的新 `inventory_snapshot` 找到 `hoe_iron` 及真实 instance/location,按生产规则把该实例移到 main-hand held(等待匹配请求的 accepted receipt 与权威 snapshot),再用同一非零 instance id 发 `lingtian_start_till`,断言真实 `lingtian_session`/明确业务拒绝回执且请求未因 instance mismatch 被拒。只有 producer→snapshot→equip→till 全链通过,才可归档 visibility skeleton。 -7. Python server-data decoder 对新增/扩展 inventory receipt 做正反样本测试;bot 场景的 request correlation 必须同时使用发送时间锚、instance id、from/to 和 revision,不得命中历史事件。 -8. 按 §P0.5 逐项核验被吸收 skeleton 的代码、跨轨 merge SHA、client/bot 测试与证据,再依仓库三态规则分别收口;本 plan 未全部完成前不写 `Finish Evidence`、不归档。 +7. Python server-data decoder 对新增/扩展 inventory receipt 与 dropped-loot 分片快照做正反样本测试;bot 场景的 request correlation 必须同时使用发送时间锚、instance id、from/to 和 revision,不得命中历史事件。 +8. `inv_dropped_loot_bound`:预置到 `MAX_DURABLE_DROPPED_LOOT_ENTRIES`,再触发 craft/alchemy/forge/give/loot spill,逐域断言 typed capacity rejection、源状态/session/材料不变;pickup 一件后重试成功。重启超限数据库必须进入 guard 而非截断;分片 sync 每页 ≤ `DROPPED_LOOT_SYNC_PAGE_SIZE`,同 revision 缺页不得替换 client 视图。 +9. 按 §P0.5 逐项核验被吸收 skeleton 的代码、跨轨 merge SHA、client/bot 测试与证据,再依仓库三态规则分别收口;本 plan 未全部完成前不写 `Finish Evidence`、不归档。 diff --git a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md index 71bcd7ccf..fdcdc1ca3 100644 --- a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md +++ b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md @@ -21,10 +21,10 @@ ## 阶段 - ⬜ P0 设计收口 + 吸收清单验真:53 张表普查归域;冻结 Slice trait(载入守护语义:读失败 = 保留旧行 + 告警 + 只读降级,绝不写回空态;flush registry;tick rebase 协议);等 #1288/#1289/#1261/#1259 merge 后定基线。 -- ⬜ P1 框架落地 + 巨石拆分:`persistence/` 按域拆文件(迁移链不变、行为不变);Slice 框架上线,KnownTechniques/Lifecycle(在飞 PR 的成果)平移为首批宿主。 -- ⬜ P2 载入守护推广:全部玩家 slice(SkillSet/Wounds/状态 buff/身份键……)收编,#1290 模式全量落地;身份主键统一(identity-persist-key-mismatch)。 +- ⬜ P1 框架落地 + 巨石拆分:`persistence/` 按域拆文件(迁移链不变、行为不变);Slice 框架上线,KnownTechniques/Lifecycle(在飞 PR 的成果)平移为首批宿主;冻结 inventory slice hydration seam 及 `MigrationOutcome` consumer 边界,R3 不复制 inventory 网格规则,待 R10 P3 提供纯幂等迁移函数后接入。 +- ⬜ P2 载入守护推广:全部玩家 slice(SkillSet/Wounds/状态 buff/身份键……)收编,#1290 模式全量落地;身份主键统一(identity-persist-key-mismatch);dropped-loot slice 在 deserialize/发布 Resource 前检查 R10 `MAX_DURABLE_DROPPED_LOOT_ENTRIES`,超限进入统一 load-failure guard/只读降级并告警,禁止 `take(limit)` 截断、驱逐旧条目或以空 registry 覆盖数据库。 - ⬜ P3 关服 flush + tick rebase 批次:shutdown flush registry 收编全部"节流落盘"域;绝对 tick 全部改相对基准;autosave/事件写入竞态互斥(coffin-autosave-inflight-race 模式)。 -- ⬜ P4 遗漏运行态补持久化批次:ActiveEvents、TiandaoAttention、状态效果、化虚冷却、灵眼、地表遗缴、散灵珠、可放置实体、dormant 往返身份完整性(heiwushi)等——逐个按 Slice 框架补表。 +- ⬜ P4 遗漏运行态补持久化批次:ActiveEvents、TiandaoAttention、状态效果、化虚冷却、灵眼、地表遗缴、散灵珠、可放置实体、dormant 往返身份完整性(heiwushi)等——逐个按 Slice 框架补表;在 R10 P3 merge 后落 inventory migration consumer 子批次,调用 `inventory::migration::migrate_legacy_inventory_layout`,只在成功后保存新 schema,失败走 load guard。 - ⬜ P5 bot 验收 + 吸收 plan 批量归档。 ## 吸收清单(短名省略 plan-bughunt- 前缀与 -v1 后缀) @@ -40,9 +40,9 @@ skeleton:coffin-autosave-inflight-race、identity-persist-key-mismatch、miner ## bot 验收场景 -1. `restart_player_slices`:bot 建号→修炼/学功法/受伤→关服重启→重连→断言功法/伤势/濒死后果/buff 全部还原。 +1. `restart_player_slices`:bot 建号→修炼/学功法/受伤→关服重启→重连→断言功法/伤势/濒死后果/buff 全部还原;另以 pre-#249 inventory fixture 验证 R10 纯迁移保留全部 instance/dynamic fields、重复载入幂等并保存新 schema。 2. `restart_world_runtime`:触发矿脉枯竭/配方解锁/zone influence→SIGTERM 关服→重启→断言无回滚无复活。 -3. `load_failure_guard`:注入一行损坏 slice 数据→启动→断言该玩家进入守护降级而非清零覆盖(配 dev 命令注入)。 +3. `load_failure_guard`:注入一行损坏 slice 数据→启动→断言该玩家进入守护降级而非清零覆盖(配 dev 命令注入);注入超过 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 的 dropped-loot rows 同样必须 guard,数据库行数与内容不得被截断/清空。 4. `tick_rebase`:带冷却/再生倒计时重启→断言倒计时按真实流逝折算(对齐 #1289 的 deadline 折算先例)。 ## 开放问题(pre-P0 收口) diff --git a/docs/plans-skeleton/plan-refactor-qi-ledger-v1.md b/docs/plans-skeleton/plan-refactor-qi-ledger-v1.md index 8790ec9bb..9fe529123 100644 --- a/docs/plans-skeleton/plan-refactor-qi-ledger-v1.md +++ b/docs/plans-skeleton/plan-refactor-qi-ledger-v1.md @@ -23,7 +23,7 @@ - ⬜ P0 设计收口 + 吸收清单验真:~200 个直写点全量普查分类(合法初始化/regen/衰减/战斗/UI 镜像);冻结封装方案(`qi_current` 收私有 + 类型化访问器:`gain_from_zone` / `release_to_zone` / `transfer_to` / `set_for_init`,每个访问器内嵌 ledger 记账);负灵域语义、qi_max 缩容语义写成决议。 - ⬜ P1 类型封装落地:字段收私有,访问器上线;既有 ~130 处 QiTransfer 调用点平移;编译期扫清全部直写(编译器就是审计器)。 - ⬜ P2 修复批次 A(cultivation + 消耗品 + lingtian):regen/服丹/plot_qi/经脉淬炼影子账全部走访问器归账。 -- ⬜ P3 修复批次 B(combat 各流派 + fauna/npc/boss):overflow/drain/打断退款/日程回气/暗器 imprint 回滚;离屏死亡走 `release_dormant_qi_to_zone`。 +- ⬜ P3 修复批次 B(combat 各流派 + fauna/npc/boss + inventory pickup):overflow/drain/打断退款/日程回气/暗器 imprint 回滚;离屏死亡走 `release_dormant_qi_to_zone`;为 R10 `PickupAttritionBasis { target_instance_id, incoming_instance_id, incoming_stack_count, incoming_abs_qi_before }` 提供稳定 incoming-only pickup attrition API,只磨损 incoming 绝对真元并把损耗守恒归还 zone,merge 后不得重扣既有 stack 的绝对真元。 - ⬜ P4 守恒审计常绿 + 归档:`assert_conservation` 进 bot e2e 每场景收尾断言;吸收 plan 批量归档。 ## 吸收清单(短名省略 plan-bughunt- 前缀与 -v1 后缀) @@ -43,6 +43,7 @@ skeleton:attrition-overflow-ledger、bonecoin-qi-facevalue、carrier-resonance 2. `qi_skill_roundtrip`:单招释放前后玩家+zone 总量不变。 3. `qi_death_release`:击杀带真元的 NPC/离屏战死→断言 zone 收到等额释放。 4. `qi_negative_zone`:负灵域内释放/吸收→断言赤字被记账不被 max(0) 抹平。 +5. `qi_pickup_merge_incoming_only`:已有 stack 与同 identity dropped item 合并,断言 attrition 只基于 receipt 的 `incoming_abs_qi_before`,既有 stack 原绝对真元不变、目标 stack + zone 总量守恒,且不依赖 consumed dropped instance id 在 inventory 中仍存在。 ## 开放问题(pre-P0 收口) diff --git a/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md b/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md index 3ba4aa422..bd639677c 100644 --- a/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md +++ b/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md @@ -18,17 +18,17 @@ ## 阶段 -- ⬜ P0 设计收口 + 吸收清单验真:28 旁路逐个普查(收编 vs 豁免理由);100 emit 文件的重复模式取样归纳 builder API;枚举前缀剥离点全量清点;冻结 scope 语义与 join 首包快照集清单。 -- ⬜ P1 emit builder + scope 落地:builder 上线,vfx/audio/env 三类先挂 scope(跨维 bleed 立灭);跨位面切换时 env/season 全量重发。 -- ⬜ P2 client 桥接层收敛:枚举前缀剥离收敛到单点(含 forge-session 修复);`ServerDataRouter` 注册表整备(分域注册文件,不再单个 1547 行 switch 追加)。 +- ⬜ P0 设计收口 + 吸收清单验真:28 旁路逐个普查(收编 vs 豁免理由);100 emit 文件的重复模式取样归纳 builder API;枚举前缀剥离点全量清点;冻结 scope 语义与 join 首包快照集清单;正式登记 `rotate-footprint-sync`、`bot-inventory-pack-feedback` 的 inventory wire/feedback 工作,并冻结 `dropped_loot_sync` 分片 envelope(`snapshot_revision/page_index/page_count`、每页上限常量 `DROPPED_LOOT_SYNC_PAGE_SIZE = 256`)。 +- ⬜ P1 emit builder + scope 落地:builder 上线,vfx/audio/env 三类先挂 scope(跨维 bleed 立灭);跨位面切换时 env/season 全量重发;dropped-loot 内容变化与 join sync 通过共享 builder 按固定页大小发送,同一 snapshot 只排序/编码一次后复用于目标 clients,禁止 per-client 重建无界全量 payload。 +- ⬜ P2 client 桥接层收敛:枚举前缀剥离收敛到单点(含 forge-session 修复);`ServerDataRouter` 注册表整备(分域注册文件,不再单个 1547 行 switch 追加);dropped-loot client store 仅在同 revision 全部分片收齐后原子替换,缺页/混 revision 保留旧视图并请求/等待重发。 - ⬜ P3 旁路归一批次:28 channel 逐批收编入 server_data envelope 或登记豁免(资源包/握手类可豁免);删除散装 receiver。 -- ⬜ P4 契约 pin 全量化:双向 sample 对拍测试补齐(113 C2S + 144 S2C 每变体至少一条正反 sample,schema 改动连 sample 一起改);emit 迁移到 builder 的长尾批次。 +- ⬜ P4 契约 pin 全量化:双向 sample 对拍测试补齐(113 C2S + 144 S2C 每变体至少一条正反 sample,schema 改动连 sample 一起改);emit 迁移到 builder 的长尾批次;完成 inventory receipt contract 子批次:`InventoryEventV1::Moved`(或等价 accepted receipt)必须携带 request identity、结果 revision、权威 item view,覆盖 schema/sample/convert/emit API、Fabric `InventoryEventHandler` 与 Python decoder,供 R4 handler 消费 R10 typed outcome。分片 dropped-loot 正反样本必须覆盖空/单页/恰好 256/257/末页缺失/混 revision。 - ⬜ P5 bot 验收 + 吸收 plan 批量归档。 ## 吸收清单(短名省略 plan-bughunt- 前缀与 -v1 后缀) active:server-data-s2c-schema-union-drift(TS union 补齐走 regenerate)、spirit-treasure-chat-key-conflict 除外(归 R7)。 -skeleton:vfx-audio-dimension-bleed、q-world-season-dimension-env-resync、forge-session-enum-unstripped(#1294 在飞)、client-request-schema-drift(C2S 契约 pin 部分)、cl-ningmai-meridian-target-drop(payload 字段丢失)、alchemy-recipe-fragment-handoff(id 前缀契约)、vfx-event-slash-contract(event_id 格式契约;agent 侧改动最小化)、npc-trade-bundle-count-bridge(展示/结算数量桥)、dropped-loot-g-pickup-range-desync(拾取范围下发对齐部分)、skillbar-cast-source-drift 与 skillconfig-castsync 除外(归 R9)。 +skeleton:vfx-audio-dimension-bleed、q-world-season-dimension-env-resync、forge-session-enum-unstripped(#1294 在飞)、client-request-schema-drift(C2S 契约 pin 部分)、cl-ningmai-meridian-target-drop(payload 字段丢失)、alchemy-recipe-fragment-handoff(id 前缀契约)、vfx-event-slash-contract(event_id 格式契约;agent 侧改动最小化)、npc-trade-bundle-count-bridge(展示/结算数量桥)、dropped-loot-g-pickup-range-desync(拾取范围下发对齐部分)、rotate-footprint-sync(R10 typed outcome → moved/accepted 权威 item view)、bot-inventory-pack-feedback(成功/拒绝动作级机器回执)、skillbar-cast-source-drift 与 skillconfig-castsync 除外(归 R9)。 注:server↔agent 方向的桥(anticheat-tiandao-drop、niche-guardian-redis-dispatch、npc-combat-relic-schema-drift、pseudo-vein-agent-deadwire、war-participate-agent-command-drift、天道叙事簇 14 项)**不吸收**——agent 不在本次重构范围,独立保留(见总纲 §6 独立轨)。 ## 文件所有权与边界 From 630a2467990699d2f0baaeae3919e1fe9746c9ba Mon Sep 17 00:00:00 2001 From: server_kizuna Date: Mon, 3 Aug 2026 12:30:47 +0800 Subject: [PATCH 04/13] =?UTF-8?q?=E6=94=B6=E7=B4=A7=20R10/R3=20=E6=8E=89?= =?UTF-8?q?=E8=90=BD=E5=AE=B9=E9=87=8F=E4=B8=8E=E8=BF=81=E7=A7=BB=E6=B6=88?= =?UTF-8?q?=E8=B4=B9=E5=A5=91=E7=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Model: cc-sonnet-high Co-Authored-By: Claude --- docs/plan-refactor-inventory-core-v1.md | 20 ++++++++-------- .../plan-refactor-persistence-slices-v1.md | 24 +++++++++++++++---- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/docs/plan-refactor-inventory-core-v1.md b/docs/plan-refactor-inventory-core-v1.md index bc0154f6b..70afb6c9c 100644 --- a/docs/plan-refactor-inventory-core-v1.md +++ b/docs/plan-refactor-inventory-core-v1.md @@ -95,7 +95,7 @@ impl InventoryTxn<'_> { 3. 调用者没有真实 `SpillContext` 时 fail closed,返回 `CapacityExceeded`/`MissingSpillContext`;不得用虚构坐标,也不得清 session、扣材料或删除世界掉落。 4. spill 必须携带原 `ItemInstance`;对 minted stack 可按合法最大堆叠拆分,对既有动态实例不得通过重新 mint 丢字段。 5. 所有 attach 都必须先通过 `validate_attach_fits` 并由 `attach_at_location` 提交;删除 `force_attach_item_to_inventory` 的 `(0,0)` 强塞 fallback。全量转移放不下的剩余项进入显式 spill receipt,而不是重叠占格。 -6. dropped-loot registry 是有界 durable queue,不是无限容器:R10 定义 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 单一常量(初值 `4096`,测试只引用该常量),所有 delivery/discard/overflow 路径在 mutation 前按“本事务新增 dropped entry 数”原子预留容量。`len + required > MAX` 时返回 typed `DroppedLootCapacityExceeded { current, required, limit }`,inventory、revision、session、材料、世界掉落与 persistence 均保持不变;禁止逐项插入后才发现超限。启动 hydration 若数据库行数超过上限必须进入 R3 load-failure guard/只读降级并告警,不能截断、驱逐或覆盖旧行。 +6. dropped-loot registry 是有界 durable queue,不是无限容器:R10 P1 定义 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 单一常量(初值 `4096`,测试只引用该常量)以及 `DroppedLootRegistry::try_insert` / `try_insert_batch` 原子容量 API。任何生产写入都必须先通过该 API;单条写入在 mutation 前预留 1 槽,批量写入在首条 mutation 前一次性预留全部槽位,禁止调用方直接操作 `entries.insert`。生产 writer 的穷举清单是:`add_item_to_player_inventory_or_ground`、`rebuild_and_drop_overflow`、`enforce_intrinsic_gate_on_morph_release`、`apply_death_drop_on_revive` 的 TSY/主世界分支、`apply_termination_drop_on_terminate` 的 world-drop fallback 分支、`spawn_template_dropped_loot`、`discard_inventory_item_to_dropped_loot`、`tsy_loot_spawn::spawn_for_layer` 以及该文件内所有 placeable-break/TSY layer event writer;P2/P3 必须逐一迁移,测试 fixture 允许直接构造 registry 但不得代表生产接线。所有 delivery/discard/overflow/death/TSY/placeable-break 路径在 mutation 前按“本事务新增 dropped entry 数”原子预留容量。`len + required > MAX` 时返回 typed `DroppedLootCapacityExceeded { current, required, limit }`,inventory、revision、session、材料、世界掉落与 persistence 均保持不变;禁止逐项插入后才发现超限。启动 hydration 的行数检查由 R3 提供 guard 接缝、由 R10 P1 的常量/API 接入;在 R10 P1 合入前 R3 不得引用该 symbol。超限时必须进入 R3 load-failure guard/只读降级并告警,不能截断、驱逐或覆盖旧行。 7. 正常回收只允许玩家 pickup 或后续显式管理员运维;本 plan 不以 TTL/LRU/按价值驱逐静默销毁稀缺物品。每次 pickup 与 durable delete 同事务释放槽位。R6 的 `dropped_loot_sync` 必须分页/分片,每 payload 至多 `256` entries,携带 `snapshot_revision/page_index/page_count`;client 收齐同 revision 全部分片后原子替换视图,内容变化时不再对每个 client 构建/排序一份无界全量 payload。 8. rotate/move receipt 必须携带变更后的完整 item view 或等价 `grid_w/grid_h + rotated` 事实。R10 的 inventory outcome 提供所需事实;R4 在其独占 handler 中把 outcome 交给 emit API;R6 扩展公共 S2C schema/emit 并让 client 以权威新 footprint 替换旧 view。三段任一未完成均不得判 P3/P4 完成。 @@ -103,7 +103,7 @@ impl InventoryTxn<'_> { - **R10 独占**:`server/src/inventory/**`;各产出域只改为调用冻结的 transaction API,不接管其 session/玩法状态机。 - **R1**:拥有 session 生命周期。R10 返回 delivery 成败与 receipt;R1 只能在成功存入或成功 spill 后清 session,失败必须保留可重试状态。 -- **R3**:拥有 `server/src/persistence/**`、autosave 与 hydration 编排。R3 P1 先冻结 inventory slice hydration seam;R10 P3 再在 `inventory/migration.rs` 提供纯、幂等的旧布局转换/校验;R3 P4 的 inventory migration consumer 子批次调用它、保存新 schema,并执行 dropped-loot 有界 hydration guard。三项工作均已登记进 R3 canonical plan;R10 不直接重构 persistence 巨石。 +- **R3**:拥有 `server/src/persistence/**`、autosave 与 hydration 编排。R3 P1 先冻结 inventory slice hydration seam;R10 P1 同时提供 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 与 `DroppedLootRegistry::{try_insert,try_insert_batch}` 这一容量契约,R3 P2/P4 只能在 R10 P1 merge 后接入并引用它;在此之前 R3 以未超限 guard seam 为占位,不得复制常量。R10 P3 再在 `inventory/migration.rs` 提供纯、幂等的旧布局转换/校验;R3 P4 的 inventory migration consumer 子批次调用它、保存新 schema,并为迁移 overflow 提供真实 `SpillContext`(player identity、mechanism/world position、dimension、durable registry 与 reservation)。R3 consumer 必须先在临时内存/副本上完成 migration,成功且所有 overflow 已通过容量预留并持久化后才提交新 schema;缺上下文、容量不足或持久化失败时保留旧行并返回 retryable load failure,不得伪造坐标、清空旧行或把 overflow 留在无主纯值中。R10 不直接重构 persistence 巨石。 - **R4**:拥有 C2S gate 与 `server/src/network/client_request_handler.rs`。通过 gate 后调用 R10 txn;对于 rotate/pack move,R4 还负责把 R10 outcome 的 request identity、revision 和新 item view 传给 R6 emit API,但不定义 wire schema。 - **R5**:拥有 qi ledger、磨损/衰变物理;R10 保留动态字段并产出 `PickupAttritionBasis`,R5 P3 已登记 incoming-only pickup attrition API 与守恒断言,R4 只传 receipt 不重算整栈真元。 - **R6**:拥有公共 S2C schema/emit 与 client network consumer。R6 canonical plan 的 P0/吸收清单已登记 `rotate-footprint-sync`、`bot-inventory-pack-feedback`;R6 P1/P4 已登记 dropped-loot 分片快照与 inventory receipt contract 子批次,负责修改 `server/src/schema/inventory.rs` 及双端 samples/convert,使成功 `moved`(或等价 accepted receipt)携带请求可归因字段、结果 revision 和权威 item view,修改 inventory emit API 与 `client/.../InventoryEventHandler.java`,并补 server schema/emit、client handler、Python decoder contract tests。**在 R6 P4 子批次 merge 前,R10 P3 不得标 ✅、P4 不得归档相关 skeleton。** @@ -138,11 +138,11 @@ impl InventoryTxn<'_> { **决议**:并入 R10 P3,一次清账,但严格等待 R3 P1。 -1. R10 在 `server/src/inventory/migration.rs` 提供纯函数,例如 `migrate_legacy_inventory_layout(value, schema_version) -> MigrationOutcome`;输入/输出只涉及 inventory slice,不执行 SQL。 -2. 迁移挂到 R3 冻结的 hydration seam:现有依据为 `server/src/player/state.rs:1372-1433` 的 schema-version 分流与反序列化路径;R3 P1 先冻结 loader seam,R10 P3 落纯迁移,R3 P4 inventory migration consumer 子批次再调用并保存。 -3. 迁移幂等:新布局不变;旧 5×7/main-pack 形态转换为当前容器布局;所有 item instance 与动态字段保留;无法合法放入的物品走显式 migration spill/handoff,绝不静默截断。 -4. 迁移成功后由 R3 保存新 schema 形状;R10 不直接写 `inventories` 表、不修改 autosave 所有权。 -5. P3 测试至少覆盖:旧布局 happy path、空库存、边界填满、动态字段保留、重复迁移不变、非法/损坏 JSON 拒绝、overflow 明细守恒。 +1. R10 在 `server/src/inventory/migration.rs` 提供纯函数,例如 `migrate_legacy_inventory_layout(value, schema_version) -> MigrationOutcome`;输入/输出只涉及 inventory slice,不执行 SQL。`MigrationOutcome` 必须显式携带 `overflow: Vec`,不得把无法安放的物品丢弃或藏在不可消费的字符串中。 +2. 迁移挂到 R3 冻结的 hydration seam:现有依据为 `server/src/player/state.rs:1372-1433` 的 schema-version 分流与反序列化路径;R3 P1 先冻结 loader seam,R10 P3 落纯迁移,R3 P4 inventory migration consumer 子批次再调用并保存。R3 consumer 负责把本次加载的玩家身份、真实机制结算点/世界坐标、dimension 与可写 `DroppedLootRegistry` 组装为真实 `SpillContext`,并负责持久化 overflow 与容量 reservation;R10 的纯函数不自行猜位置、不执行 SQL。 +3. 迁移幂等:新布局不变;旧 5×7/main-pack 形态转换为当前容器布局;所有 item instance 与动态字段保留;无法合法放入的物品必须进入 `MigrationOutcome::overflow`,由 R3 consumer 通过 `InventoryTxn::deliver`/显式 migration spill handoff 原子落入 durable registry。缺少真实上下文、容量不足或 overflow 持久化失败时,R3 必须保留旧数据库行并返回可重试的 load failure/只读降级;不得伪造坐标、先写新 schema 后再补 spill,或把 overflow 留在无主内存。 +4. 迁移成功后由 R3 保存新 schema 形状;只有“新 schema 与全部 overflow 均已持久化”才算成功。R10 不直接写 `inventories` 表、不修改 autosave 所有权。 +5. P3 测试至少覆盖:旧布局 happy path、空库存、边界填满、动态字段保留、重复迁移不变、非法/损坏 JSON 拒绝、overflow 明细守恒,以及 R3 consumer 在有真实 `SpillContext` 时成功提交、缺上下文/容量不足/持久化失败时保留旧行并可重试。 **落点**:`server/src/player/state.rs:1372-1433`(当前依据)→ R3 P1 后的 inventory slice loader;本 plan §P3。 @@ -184,9 +184,9 @@ impl InventoryTxn<'_> { - give、craft、alchemy、forge、loot/pickup 全部改走 `InventoryTxn::deliver` / `consume_checked` / `pickup_and_merge`。 - 删除按错误字符串前缀判断 public fallback 的调用契约,改用 typed error/disposition。 - session 类调用严格执行“txn 成功(stored 或 spilled)后才 teardown”;失败不扣料、不删 session、不删 dropped entry。 -- 饱和测试覆盖:空包、恰好放满、少一格、同身份全 merge/部分 merge、不同动态身份拒绝 merge、无 spill context、每类结构错误、revision 单次 bump、总量守恒。 +- 饱和测试覆盖:空包、恰好放满、少一格、同身份全 merge/部分 merge、不同动态身份拒绝 merge、无 spill context、每类结构错误、revision 单次 bump、总量守恒;另设 `consume_checked` 专属 contract pin:成功消费逐 instance 扣除精确数量、receipt 逐项记录 source/count、revision 只 bump 一次;库存不足/未知 instance/zero quantity 任一失败时 inventory、revision、session 与材料均逐字节/逐字段保持不变,且不得出现部分扣除。 - 既有实例 round-trip contract pin:构造非默认 `instance_id/durability/freshness/mineral_id/charges/forge_*/alchemy/lingering_owner_qi` 的 `ItemInstance`,分别验证直接 placement 与 spill 后所有字段逐项不变、receipt 将原 id 记入 placed/spilled 而非 created;merge 只允许完整 identity 相等且 receipt 保留 source/target/count。 -- dropped registry bound pin:`limit-1`、恰好 `limit`、`limit+1`/单事务多 spill 越界;越界时 inventory/revision/session/材料/registry/persistence 全不变,pickup 后容量可复用,超限 hydration 进入 guard 而不截断。 +- dropped registry bound pin:`limit-1`、恰好 `limit`、`limit+1`/单事务多 spill 越界;越界时 inventory/revision/session/材料/世界掉落与 persistence 全不变,pickup 后容量可复用,超限 hydration 进入 guard 而不截断。 ## P3 — 网格/堆叠一致性 + 迁移(⬜) @@ -205,5 +205,5 @@ impl InventoryTxn<'_> { 5. `inv_give_visibility_forge`:保留 production forge 的 `/give fan_iron_anvil` → snapshot 真实 instance id → `forge_station_place` → 消耗快照深链回归。 6. `inv_give_visibility_lingtian`:改造 `production_lingtian_gathering_intents.py`,禁止 `hoe_instance_id: 0` 和“任意 server-data 即成功”;`/give hoe_iron` 后必须在带时间锚的新 `inventory_snapshot` 找到 `hoe_iron` 及真实 instance/location,按生产规则把该实例移到 main-hand held(等待匹配请求的 accepted receipt 与权威 snapshot),再用同一非零 instance id 发 `lingtian_start_till`,断言真实 `lingtian_session`/明确业务拒绝回执且请求未因 instance mismatch 被拒。只有 producer→snapshot→equip→till 全链通过,才可归档 visibility skeleton。 7. Python server-data decoder 对新增/扩展 inventory receipt 与 dropped-loot 分片快照做正反样本测试;bot 场景的 request correlation 必须同时使用发送时间锚、instance id、from/to 和 revision,不得命中历史事件。 -8. `inv_dropped_loot_bound`:预置到 `MAX_DURABLE_DROPPED_LOOT_ENTRIES`,再触发 craft/alchemy/forge/give/loot spill,逐域断言 typed capacity rejection、源状态/session/材料不变;pickup 一件后重试成功。重启超限数据库必须进入 guard 而非截断;分片 sync 每页 ≤ `DROPPED_LOOT_SYNC_PAGE_SIZE`,同 revision 缺页不得替换 client 视图。 +8. `inv_dropped_loot_bound`:预置到 `MAX_DURABLE_DROPPED_LOOT_ENTRIES`,再分别触发 craft/alchemy/forge/give/loot spill、player discard、death revive 的 TSY/主世界 drop、termination 的 world-drop fallback、TSY relic layer spawn、placeable-break template spawn、backpack unequip overflow、morph-release gate overflow;每个生产 writer 逐域断言通过 `try_insert/try_insert_batch` 获得 typed capacity rejection,源 inventory/revision/session/材料/世界掉落与 persistence 不变;pickup 或显式 durable delete 一件后重试成功。重启超限数据库必须进入 guard 而非截断;分片 sync 每页 ≤ `DROPPED_LOOT_SYNC_PAGE_SIZE`,同 revision 缺页不得替换 client 视图。 9. 按 §P0.5 逐项核验被吸收 skeleton 的代码、跨轨 merge SHA、client/bot 测试与证据,再依仓库三态规则分别收口;本 plan 未全部完成前不写 `Finish Evidence`、不归档。 diff --git a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md index fdcdc1ca3..6db24f717 100644 --- a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md +++ b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md @@ -21,10 +21,10 @@ ## 阶段 - ⬜ P0 设计收口 + 吸收清单验真:53 张表普查归域;冻结 Slice trait(载入守护语义:读失败 = 保留旧行 + 告警 + 只读降级,绝不写回空态;flush registry;tick rebase 协议);等 #1288/#1289/#1261/#1259 merge 后定基线。 -- ⬜ P1 框架落地 + 巨石拆分:`persistence/` 按域拆文件(迁移链不变、行为不变);Slice 框架上线,KnownTechniques/Lifecycle(在飞 PR 的成果)平移为首批宿主;冻结 inventory slice hydration seam 及 `MigrationOutcome` consumer 边界,R3 不复制 inventory 网格规则,待 R10 P3 提供纯幂等迁移函数后接入。 -- ⬜ P2 载入守护推广:全部玩家 slice(SkillSet/Wounds/状态 buff/身份键……)收编,#1290 模式全量落地;身份主键统一(identity-persist-key-mismatch);dropped-loot slice 在 deserialize/发布 Resource 前检查 R10 `MAX_DURABLE_DROPPED_LOOT_ENTRIES`,超限进入统一 load-failure guard/只读降级并告警,禁止 `take(limit)` 截断、驱逐旧条目或以空 registry 覆盖数据库。 +- ⬜ P1 框架落地 + 巨石拆分:`persistence/` 按域拆文件(迁移链不变、行为不变);Slice 框架上线,KnownTechniques/Lifecycle(在飞 PR 的成果)平移为首批宿主;冻结 inventory slice hydration seam 及 `MigrationOutcome` consumer 边界,R3 不复制 inventory 网格规则,待 R10 P3 提供纯幂等迁移函数后接入。R3 P1 只能冻结该 seam,不得引用尚未合入的 R10 常量或实现。 +- ⬜ P2 载入守护推广:全部玩家 slice(SkillSet/Wounds/状态 buff/身份键……)收编,#1290 模式全量落地;dropped-loot slice 的有界 hydration guard 依赖 R10 P1 已 merge 的容量契约:仅在该前置成立后引用 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 与 `DroppedLootRegistry::try_insert/try_insert_batch`;超限进入统一 load-failure guard/只读降级并告警,禁止 `take(limit)` 截断、驱逐旧条目或以空 registry 覆盖数据库。在 R10 P1 未 merge 时,R3 P2 不得编译或复制临时常量。 - ⬜ P3 关服 flush + tick rebase 批次:shutdown flush registry 收编全部"节流落盘"域;绝对 tick 全部改相对基准;autosave/事件写入竞态互斥(coffin-autosave-inflight-race 模式)。 -- ⬜ P4 遗漏运行态补持久化批次:ActiveEvents、TiandaoAttention、状态效果、化虚冷却、灵眼、地表遗缴、散灵珠、可放置实体、dormant 往返身份完整性(heiwushi)等——逐个按 Slice 框架补表;在 R10 P3 merge 后落 inventory migration consumer 子批次,调用 `inventory::migration::migrate_legacy_inventory_layout`,只在成功后保存新 schema,失败走 load guard。 +- ⬜ P4 遗漏运行态补持久化批次:ActiveEvents、TiandaoAttention、状态效果、化虚冷却、灵眼、地表遗缴、散灵珠、可放置实体、dormant 往返身份完整性(heiwushi)等——逐个按 Slice 框架补表;在 R10 P3 merge 后落 inventory migration consumer 子批次,调用 `inventory::migration::migrate_legacy_inventory_layout`,用玩家 identity、真实机制结算点/世界 position、dimension 组装 `SpillContext`,把 `MigrationOutcome::overflow` 通过 R10 capacity API 持久化到 durable registry;仅在新 schema 与全部 overflow 都成功持久化后提交新 schema,缺上下文/容量/持久化失败则保留旧行并进入可重试 load guard。该 consumer 明确依赖 R10 P1/P3 merge,不得提前实现或另造容量常量。 - ⬜ P5 bot 验收 + 吸收 plan 批量归档。 ## 吸收清单(短名省略 plan-bughunt- 前缀与 -v1 后缀) @@ -42,10 +42,26 @@ skeleton:coffin-autosave-inflight-race、identity-persist-key-mismatch、miner 1. `restart_player_slices`:bot 建号→修炼/学功法/受伤→关服重启→重连→断言功法/伤势/濒死后果/buff 全部还原;另以 pre-#249 inventory fixture 验证 R10 纯迁移保留全部 instance/dynamic fields、重复载入幂等并保存新 schema。 2. `restart_world_runtime`:触发矿脉枯竭/配方解锁/zone influence→SIGTERM 关服→重启→断言无回滚无复活。 -3. `load_failure_guard`:注入一行损坏 slice 数据→启动→断言该玩家进入守护降级而非清零覆盖(配 dev 命令注入);注入超过 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 的 dropped-loot rows 同样必须 guard,数据库行数与内容不得被截断/清空。 +3. `load_failure_guard`:基础损坏 slice 仍断言守护降级而非清零覆盖;超过 R10 P1 提供的 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 的 dropped-loot rows 只有在 R10 P1 已 merge 后执行同一 guard,数据库行数与内容不得被截断/清空;R10 前置未满足时该断言保持待接线,不得引用不存在的 symbol。 4. `tick_rebase`:带冷却/再生倒计时重启→断言倒计时按真实流逝折算(对齐 #1289 的 deadline 折算先例)。 ## 开放问题(pre-P0 收口) 1. 载入守护的玩家体验:只读降级 vs 拒绝进服 vs 回滚到上一备份?需人工拍板。 2. 迁移链是否借机做一次 squash(v1-v39 合并基线)?风险与老存档兼容性需评估。 + +## § P0 决议锚点(待 R3 P0 开工时补齐) + +- `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 的引用门:R3 P2/P4 依赖 R10 P1 merge,R3 不复制常量或在此前编译引用。 +- inventory migration consumer:R3 P4 在 R10 P3 merge 后,使用真实 `SpillContext` 完成 overflow 持久化;失败保留旧行并可重试。 + +## 验收测试声明 + +- `cargo test --package bong-server persistence -- --nocapture`:Slice load guard、flush registry、tick rebase、migration consumer、dropped-loot hydration bound。 +- bot e2e:`restart_player_slices`、`restart_world_runtime`、`load_failure_guard`、`tick_rebase`。 + +## 实施边界 + +- R3 P1 只冻结接缝与框架,不提前实现 R10 inventory migration 或 dropped-loot capacity API。 +- R3 P2/P4 在依赖 merge 后才实现对应消费者;所有 migration overflow 必须有真实 spill context 和 durable sink。 +- 本 skeleton 不直接改 `server/src/inventory/**`;R10 拥有 inventory 生产 writer 与容量 API。 From 0ce81775ccef4afd8dc8802e80ab3d18317d79c5 Mon Sep 17 00:00:00 2001 From: server_kizuna Date: Mon, 3 Aug 2026 13:14:53 +0800 Subject: [PATCH 05/13] =?UTF-8?q?=E8=A1=A5=E9=BD=90=E5=BA=93=E5=AD=98?= =?UTF-8?q?=E9=87=8D=E6=9E=84=E8=B7=A8=E8=BD=A8=E5=A5=91=E7=BA=A6=E8=BE=B9?= =?UTF-8?q?=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Model: claude-sonnet-5 Co-Authored-By: Claude --- docs/plan-refactor-inventory-core-v1.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/plan-refactor-inventory-core-v1.md b/docs/plan-refactor-inventory-core-v1.md index 70afb6c9c..c00ece819 100644 --- a/docs/plan-refactor-inventory-core-v1.md +++ b/docs/plan-refactor-inventory-core-v1.md @@ -95,8 +95,8 @@ impl InventoryTxn<'_> { 3. 调用者没有真实 `SpillContext` 时 fail closed,返回 `CapacityExceeded`/`MissingSpillContext`;不得用虚构坐标,也不得清 session、扣材料或删除世界掉落。 4. spill 必须携带原 `ItemInstance`;对 minted stack 可按合法最大堆叠拆分,对既有动态实例不得通过重新 mint 丢字段。 5. 所有 attach 都必须先通过 `validate_attach_fits` 并由 `attach_at_location` 提交;删除 `force_attach_item_to_inventory` 的 `(0,0)` 强塞 fallback。全量转移放不下的剩余项进入显式 spill receipt,而不是重叠占格。 -6. dropped-loot registry 是有界 durable queue,不是无限容器:R10 P1 定义 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 单一常量(初值 `4096`,测试只引用该常量)以及 `DroppedLootRegistry::try_insert` / `try_insert_batch` 原子容量 API。任何生产写入都必须先通过该 API;单条写入在 mutation 前预留 1 槽,批量写入在首条 mutation 前一次性预留全部槽位,禁止调用方直接操作 `entries.insert`。生产 writer 的穷举清单是:`add_item_to_player_inventory_or_ground`、`rebuild_and_drop_overflow`、`enforce_intrinsic_gate_on_morph_release`、`apply_death_drop_on_revive` 的 TSY/主世界分支、`apply_termination_drop_on_terminate` 的 world-drop fallback 分支、`spawn_template_dropped_loot`、`discard_inventory_item_to_dropped_loot`、`tsy_loot_spawn::spawn_for_layer` 以及该文件内所有 placeable-break/TSY layer event writer;P2/P3 必须逐一迁移,测试 fixture 允许直接构造 registry 但不得代表生产接线。所有 delivery/discard/overflow/death/TSY/placeable-break 路径在 mutation 前按“本事务新增 dropped entry 数”原子预留容量。`len + required > MAX` 时返回 typed `DroppedLootCapacityExceeded { current, required, limit }`,inventory、revision、session、材料、世界掉落与 persistence 均保持不变;禁止逐项插入后才发现超限。启动 hydration 的行数检查由 R3 提供 guard 接缝、由 R10 P1 的常量/API 接入;在 R10 P1 合入前 R3 不得引用该 symbol。超限时必须进入 R3 load-failure guard/只读降级并告警,不能截断、驱逐或覆盖旧行。 -7. 正常回收只允许玩家 pickup 或后续显式管理员运维;本 plan 不以 TTL/LRU/按价值驱逐静默销毁稀缺物品。每次 pickup 与 durable delete 同事务释放槽位。R6 的 `dropped_loot_sync` 必须分页/分片,每 payload 至多 `256` entries,携带 `snapshot_revision/page_index/page_count`;client 收齐同 revision 全部分片后原子替换视图,内容变化时不再对每个 client 构建/排序一份无界全量 payload。 +6. dropped-loot registry 是有界 durable queue,不是无限容器:R10 P1 定义 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 单一常量(初值 `4096`,测试只引用该常量)以及 `DroppedLootRegistry::try_insert` / `try_insert_batch` 原子容量 API。任何生产写入都必须先通过该 API;单条写入在 mutation 前预留 1 槽,批量写入在首条 mutation 前一次性预留全部槽位,禁止调用方直接操作 `entries.insert`。生产 writer 的穷举清单是:`add_item_to_player_inventory_or_ground`、`rebuild_and_drop_overflow`、`enforce_intrinsic_gate_on_morph_release`、`apply_death_drop_on_revive` 的 TSY/主世界分支、`apply_termination_drop_on_terminate` 的 world-drop fallback 分支、`spawn_template_dropped_loot`、`discard_inventory_item_to_dropped_loot`、`tsy_loot_spawn::spawn_for_layer` 以及该文件内所有 placeable-break/TSY layer event writer;P2/P3 必须逐一迁移,测试 fixture 允许直接构造 registry 但不得代表生产接线。所有 delivery/discard/overflow/death/TSY/placeable-break 路径在 mutation 前按“本事务新增 dropped entry 数”原子预留容量。成功 spill 的 source mutation、registry entry 与 durable dropped-loot row 必须由 R3 persistence transaction/outbox seam 作为同一可恢复提交单元落盘;任一持久化失败或进程重启恢复未完成时,源状态保持可重试且不得出现仅 source 已删或仅 drop 已写的单边状态,重试按 dropped id/source revision 幂等。`len + required > MAX` 时返回 typed `DroppedLootCapacityExceeded { current, required, limit }`,inventory、revision、session、材料、世界掉落与 persistence 均保持不变;禁止逐项插入后才发现超限。启动 hydration 的行数检查由 R3 提供 guard 接缝、由 R10 P1 的常量/API 接入;在 R10 P1 合入前 R3 不得引用该 symbol。超限时必须进入 R3 load-failure guard/只读降级并告警,不能截断、驱逐或覆盖旧行。 +7. 正常回收只允许玩家 pickup 或后续显式管理员运维;本 plan 不以 TTL/LRU/按价值驱逐静默销毁稀缺物品。每次 pickup 与 durable delete 同事务释放槽位。R6 的 `dropped_loot_sync` 必须分页/分片,每 payload 至多 `256` entries,携带 `snapshot_revision/page_index/page_count`;client 收齐同 revision 全部分片后原子替换视图,内容变化时不再对每个 client 构建/排序一份无界全量 payload。sync 组装必须先按接收者可见性过滤:至少要求目标玩家与 entry 同 dimension,且 entry 位于该玩家当前授权观察范围(空间距离/zone 由 R6 接入面冻结;owner/private drop 仅 owner 或显式授权管理员可见);分页、计数、revision 和原子替换均只针对过滤后的 recipient view,禁止把全服 registry 快照直接广播给普通 client。 8. rotate/move receipt 必须携带变更后的完整 item view 或等价 `grid_w/grid_h + rotated` 事实。R10 的 inventory outcome 提供所需事实;R4 在其独占 handler 中把 outcome 交给 emit API;R6 扩展公共 S2C schema/emit 并让 client 以权威新 footprint 替换旧 view。三段任一未完成均不得判 P3/P4 完成。 ### P0.4 文件所有权与跨轨边界 @@ -106,7 +106,7 @@ impl InventoryTxn<'_> { - **R3**:拥有 `server/src/persistence/**`、autosave 与 hydration 编排。R3 P1 先冻结 inventory slice hydration seam;R10 P1 同时提供 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 与 `DroppedLootRegistry::{try_insert,try_insert_batch}` 这一容量契约,R3 P2/P4 只能在 R10 P1 merge 后接入并引用它;在此之前 R3 以未超限 guard seam 为占位,不得复制常量。R10 P3 再在 `inventory/migration.rs` 提供纯、幂等的旧布局转换/校验;R3 P4 的 inventory migration consumer 子批次调用它、保存新 schema,并为迁移 overflow 提供真实 `SpillContext`(player identity、mechanism/world position、dimension、durable registry 与 reservation)。R3 consumer 必须先在临时内存/副本上完成 migration,成功且所有 overflow 已通过容量预留并持久化后才提交新 schema;缺上下文、容量不足或持久化失败时保留旧行并返回 retryable load failure,不得伪造坐标、清空旧行或把 overflow 留在无主纯值中。R10 不直接重构 persistence 巨石。 - **R4**:拥有 C2S gate 与 `server/src/network/client_request_handler.rs`。通过 gate 后调用 R10 txn;对于 rotate/pack move,R4 还负责把 R10 outcome 的 request identity、revision 和新 item view 传给 R6 emit API,但不定义 wire schema。 - **R5**:拥有 qi ledger、磨损/衰变物理;R10 保留动态字段并产出 `PickupAttritionBasis`,R5 P3 已登记 incoming-only pickup attrition API 与守恒断言,R4 只传 receipt 不重算整栈真元。 -- **R6**:拥有公共 S2C schema/emit 与 client network consumer。R6 canonical plan 的 P0/吸收清单已登记 `rotate-footprint-sync`、`bot-inventory-pack-feedback`;R6 P1/P4 已登记 dropped-loot 分片快照与 inventory receipt contract 子批次,负责修改 `server/src/schema/inventory.rs` 及双端 samples/convert,使成功 `moved`(或等价 accepted receipt)携带请求可归因字段、结果 revision 和权威 item view,修改 inventory emit API 与 `client/.../InventoryEventHandler.java`,并补 server schema/emit、client handler、Python decoder contract tests。**在 R6 P4 子批次 merge 前,R10 P3 不得标 ✅、P4 不得归档相关 skeleton。** +- **R6**:拥有公共 S2C schema/emit 与 client network consumer。R6 canonical plan 的 P0/吸收清单已登记 `rotate-footprint-sync`、`bot-inventory-pack-feedback`;R6 P1/P4 已登记 dropped-loot 分片快照与 inventory receipt contract 子批次,负责修改 `server/src/schema/inventory.rs` 及双端 samples/convert,使成功 `moved`(或等价 accepted receipt)携带请求可归因字段、结果 revision 和权威 item view;同一 R6 P4 子批次同时负责 `InventoryMoveRejectedV1` 的 `instance/from/to` correlation 字段、Rust mirror/proto conversion、emit 与 client consumer/contract samples,覆盖 accepted 与 rejected 两条结果边界。R6 还负责修改 inventory emit API 与 `client/.../InventoryEventHandler.java`,并补 server schema/emit、client handler、Python decoder contract tests。dropped-loot sync 必须在 emit 前按接收者 dimension + 授权空间/zone 范围过滤,owner/private entry 仅 owner 或显式授权管理员可见,分页计数与 revision 绑定过滤后的 recipient view。**在 R6 P4 子批次 merge 前,R10 P3 不得标 ✅、P4 不得归档相关 skeleton。** - **R7**:拥有 `InspectScreen`;R10 只提供权威 inventory model/receipt,不改 UI 导航架构。 #### 跨轨执行顺序(P3 硬门) @@ -199,7 +199,7 @@ impl InventoryTxn<'_> { ## P4 — bot 验收 + 被吸收 plan 收口(⬜) 1. `inv_full_delivery_matrix`:满包下 craft 完工、取丹、锻造出炉、give,断言 stored + spilled 总量不丢且失败不提前 teardown。 -2. `inv_stack_merge`:拾取同身份掉落,断言合并既有堆叠、revision 与 receipt;receipt 的 `incoming_stack_count/incoming_abs_qi_before/target_instance_id` 驱动 pickup attrition,断言 ledger 只扣 incoming 绝对真元、旧 stack 原有绝对真元不变且玩家+zone 守恒;不同动态身份保持分栈。 +2. `inv_stack_merge`:拾取同身份掉落,断言合并既有堆叠、revision 与 receipt;receipt 的 `incoming_stack_count/incoming_abs_qi_before/target_instance_id` 驱动 pickup attrition,断言 ledger 只扣 incoming 绝对真元、旧 stack 原有绝对真元不变且玩家+zone 守恒;不同动态身份保持分栈。另设 placement-only pickup(无可合并目标但有合法空 footprint),确认 incoming 实例原 id 被放置且仅在 inventory commit 成功后删除 dropped entry;再设 attach/capacity/validation 失败路径,断言 inventory/revision 不变且 dropped entry 仍存在可重试。 3. `inv_footprint_sync`:发送 `rotated=true` 的 2×1 move 后,必须收到**该请求之后**、匹配 `instance_id/from/to` 与结果 revision 的 accepted/moved receipt,且 receipt 中权威 item view 为 1×2;Fabric handler contract test 应把本地模型替换为 1×2。拒绝、1×1、非网格目标不得误改 footprint。全量 snapshot 不能替代此动作级断言。 4. `inv_pack_feedback`:对 stow / 空 pack unequip / equip 三个成功请求,逐个等待发送时间锚之后、匹配 `instance_id/from/to` 的 accepted/moved receipt,并断言 receipt revision 与随后 snapshot 一致;对非法位置、非空 pack(若规则禁止)逐个断言 `inventory_move_rejected` 的 reason/instance/from/to,连接保持;预置同 instance 的旧 moved/rejected 事件,证明时间锚与结果 revision 不会误读 stale feedback。VFX 和 snapshot 只可附加验证,**不得作为唯一动作结果或 accepted/rejected 的替代**。 5. `inv_give_visibility_forge`:保留 production forge 的 `/give fan_iron_anvil` → snapshot 真实 instance id → `forge_station_place` → 消耗快照深链回归。 From a1c9de7f6cab153cbd530374cb0c2adf9115d2af Mon Sep 17 00:00:00 2001 From: server_kizuna Date: Mon, 3 Aug 2026 14:12:53 +0800 Subject: [PATCH 06/13] =?UTF-8?q?=E9=87=8D=E5=86=99=20R10=20P0=EF=BC=9A?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E5=BA=93=E5=AD=98=E4=B8=8E=E6=8E=89=E8=90=BD?= =?UTF-8?q?=E4=BA=8B=E5=8A=A1=E5=A5=91=E7=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Model: cc-sonnet-high Co-Authored-By: Claude --- docs/plan-refactor-inventory-core-v1.md | 244 ++++++------------ .../plan-refactor-c2s-gate-v1.md | 4 +- .../plan-refactor-persistence-slices-v1.md | 4 +- .../plan-refactor-wire-s2c-v1.md | 2 + 4 files changed, 82 insertions(+), 172 deletions(-) diff --git a/docs/plan-refactor-inventory-core-v1.md b/docs/plan-refactor-inventory-core-v1.md index c00ece819..c14cf8fcc 100644 --- a/docs/plan-refactor-inventory-core-v1.md +++ b/docs/plan-refactor-inventory-core-v1.md @@ -1,209 +1,117 @@ -# plan-refactor-inventory-core-v1 — Inventory 巨石拆分 + 网格/交付事务一致性(重构轨 R10) +# plan-refactor-inventory-core-v1 — Inventory 核心事务契约(重构轨 R10) -> 所属总纲:`docs/plans-skeleton/plan-refactor-master-v1.md`。一句话:拆分 2 万行级 `server/src/inventory/mod.rs`,以统一 `InventoryTxn` 收口给予、交付、消费、拾取、堆叠与网格占格,使物品移动可原子验证、可观测且不静默丢失。 +> 所属总纲:`plan-refactor-master-v1.md`。P0 只冻结事实、失败边界、跨轨 owner 与审核要求的 pins;不把设计当实现。 -## 阶段总览 +## 阶段 -- ✅ 2026-08-03 P0 设计收口 + 吸收清单验真 -- ⬜ P1 巨石拆分(行为不变)+ `InventoryTxn` 骨架 -- ⬜ P2 交付/消费路径统一 -- ⬜ P3 网格/堆叠一致性 + 老存档布局迁移 -- ⬜ P4 bot 验收 + 被吸收 plan 收口 +- ✅ 2026-08-03 P0 完整契约面重写 +- ⬜ A:inventory 拆分 + txn/capacity 骨架 +- ⬜ B:全部 production writer 迁移 +- ⬜ C:R3 durable seam + R6 wire/client + R4 handler +- ⬜ D:legacy consumer + bot/e2e + plan 收口 -> **实施门禁**:按 master §4/§5,R10 实现属于 Wave 2,必须等待 R3 P1 提供稳定 persistence inventory slice/hydration 接缝。当前仅 P0 完成;P1–P4 不得提前启动。2026-08-03 核验时 R3 仅有 P0 PR #1308 OPEN,R3 P1 尚未落地。 +实现属 Wave 2;跨轨工作须登记 owning plan。 -## 现状证据(2026-08-03 复核) +## 1. 完整 contract surface -- `server/src/inventory/mod.rs` 当前 20479 行;原骨架记录的 20165 行已过时。该文件同时承载 domain model、registry/loadout、实例创建、交付/堆叠、网格移动、装备与套包、掉落拾取、遗骸转移、耐久/磨损接缝及大量内联测试。 -- `add_item_to_player_inventory_or_ground`(`server/src/inventory/mod.rs:1839`)只在真实容量不足时降级为地面掉落,其它结构错误原样返回;这是统一满包策略的现役生产先例。 -- `stack_identity_matches`(`server/src/inventory/mod.rs:2223`)与现有 staged grant 已具备“完整动态身份才合并、验证成功后再提交”的正确基线,R10 应抽取复用而非改弱为仅比较 `template_id`。 -- `force_attach_item_to_inventory`(`server/src/inventory/mod.rs:4733`)在无合法位置时直接把物品压入 `(0, 0)`;`transfer_all_inventory_contents`(`:4675`)会调用它,故占格重叠缺口仍可达。 -- `pickup_dropped_loot_instance`(`server/src/inventory/mod.rs:5337`)只找空 footprint,不尝试合并已有同身份堆叠,拾取合并缺口仍存在。 -- server 的 `apply_inventory_move`(`server/src/inventory/mod.rs:3828`)会正确交换旋转后的 `grid_w/grid_h`,但 `InventoryEventV1::Moved`(`server/src/schema/inventory.rs:294`)只带 from/to;client `InventoryEventHandler`(`:85`)复用旧 item view,因此 delta 路径会保留旧 footprint。 -- pack move 成功分支在 `handle_inventory_move`(`server/src/network/client_request_handler.rs:15310`)执行 `worn_pack_rebuild` snapshot 后提前返回;普通 `send_moved_event`(`:15590`)未覆盖该分支,稳定成功回执缺口仍存在。 -- `emit_changed_inventory_snapshots`(`server/src/network/inventory_snapshot_emit.rs:93`)会为 `Changed` 自动推送权威快照;forge 路径已在 `production_forge_station_real_place.py:114-158` 从 `/give` 后快照取得真实 `instance_id` 并完成放砧/消耗断言,但该证据**不能代表所有生产系统已闭环**。`production_lingtian_gathering_intents.py:9-11,53-68` 仍明确使用 `hoe_instance_id: 0`,只等任意 server-data 回流;`hoe_iron` 的 producer→snapshot→equip/held→`lingtian_start_till` 深链仍未覆盖。 -- `load_player_inventory_from_sqlite`(`server/src/player/state.rs:1372`)已形成 schema-version 分流、纯内存迁移和 hydration 后校验接缝;pre-#249 布局迁移应在 R3 P1 抽出的接缝上调用 R10 的纯迁移函数,不应在 persistence 巨石中再造一套 inventory 规则。 +| 面 | P0 冻结的事实 | +|---|---| +| `InventoryTxn` | staged `deliver` / `consume_checked` / `pickup_and_merge`;成功最多一次 revision bump,所有验证先于 mutation | +| spill/overflow | 原实例守恒、全 writer 统一 capacity API、source 与 durable drop 可恢复原子提交 | +| dropped sync | server 按 recipient 授权投影后分页;无全局 snapshot 广播/复用 | +| pickup | authoritative dimension/range/ownership 授权;inventory commit 后才删 drop | +| legacy migration | R10 纯转换,R3 用真实 world context 消费 overflow;全成才写新 schema | -## 接入面 - -- **进料**:R1 session 产物交付、R4 gate 通过后的 inventory 请求、world dropped-loot registry、R3 persistence inventory slice/hydration、craft/alchemy/forge/botany 等产出域。 -- **出料**:`InventoryTxn` 的交付/消费/拾取 receipt、权威 `InventoryRevision`、世界掉落记录、网格变更事实;公共 S2C 经 R6 拥有的 emit/proto 接缝发送。 -- **共享类型**:保留 `PlayerInventory`、`ItemInstance`、`ContainerState`、`InventoryLocationV1`、`InventoryRevision`、`owner_instance_id` 和现有 `ItemCategory` 合法集;不建立兼容层或平行模型。 -- **跨仓库契约**:server 产出 `inventory_snapshot` / `inventory_event`;client 消费权威 footprint;bot 通过同一 wire payload 验证 instance、revision、move/merge/spill。R10 只冻结所需 payload 事实,R6 拥有公共 S2C schema/emit 接缝改动。 -- **worldview 锚点**:物品不凭空消失、满包产物落在争夺现场,对齐 `worldview.md` §十三末法物资稀缺与争夺语义;唯一真货币仍为骨币,R10 不改经济定义。 -- **qi_physics 锚点**:普通物品归属/位置变化不是新的真元流,R10 不新增真元常数或公式。含真元物品的磨损、销毁、衰变仍调用 R5/既有 ledger 语义;spill 与堆叠不得重置或吞掉动态真元字段。 - -## P0 — 设计收口 + 吸收清单验真(✅ 2026-08-03) - -### P0.1 职责拆分图 - -P1 按下表迁移 symbol;先做机械拆分和测试平移,不在同一提交中改行为。 - -| 目标模块 | 独占职责 | 首批迁入 symbol / 数据 | -|---|---|---| -| `inventory/model.rs` | inventory 领域模型与 revision/receipt 基础类型 | `PlayerInventory`、`ItemInstance`、`ContainerState`、`PlacedItemState`、装备/位置/修订类型 | -| `inventory/registry.rs` | template registry、TOML/loadout 解析、实例创建 | `ItemRegistry`、template lookup、instance allocator/construction | -| `inventory/grid.rs` | footprint、碰撞、fit、attach/detach、move/swap/rotate | `find_free_slot`、`find_first_fit_container_location`、`validate_attach_fits`、`attach_at_location`、`apply_inventory_move` | -| `inventory/txn.rs` | staged delivery/consume/pickup/merge 与统一 receipt | staged grant、`stack_identity_matches`、新增 `InventoryTxn` | -| `inventory/container.rs` | 穿戴套包 owner、派生容器重建、容量/重量、overflow spill | worn-pack helpers、`rebuild_containers_from_equipment`、`rebuild_and_drop_overflow` | -| `inventory/corpse.rs` | 死亡掉落、遗骸 inventory 与全量转移编排 | 保留现有 corpse 模块;将 `transfer_all_inventory_contents` 改为消费 txn/grid API,不再强塞 | -| `inventory/freshness.rs` | 仅 freshness 数据与 inventory construction 接缝 | 保留现有模块;衰变公式继续由 shelflife/R5 单一实现,不复制到 txn | -| `inventory/mod.rs` | 模块声明、稳定 re-export、Bevy plugin/system wiring | 不再承载具体算法或大块测试 | - -测试跟随所属模块迁移;跨模块契约测试放 `inventory/tests` 或现有集成测试文件,不把测试继续堆回 `mod.rs`。 - -### P0.2 冻结 `InventoryTxn` 契约 - -P1/P2 必须落地以下稳定语义;具体 Rust lifetime 可在实现时按借用检查器调整,但名称、输入事实、结果事实和错误边界不得漂移: +### 1.1 `InventoryTxn` ```rust -pub struct InventoryTxn<'a> { /* staged view over one PlayerInventory */ } - -impl InventoryTxn<'_> { - pub fn deliver( - &mut self, - request: DeliveryRequest, - spill: Option<&mut SpillContext<'_>>, - ) -> Result; - - pub fn consume_checked( - &mut self, - request: ConsumeRequest, - ) -> Result; - - pub fn pickup_and_merge( - &mut self, - request: PickupRequest, - dropped: &mut DroppedLootRegistry, - ) -> Result; -} +InventoryTxn::deliver(DeliveryRequest, Option<&mut SpillContext>) + -> Result +InventoryTxn::consume_checked(ConsumeRequest) + -> Result +InventoryTxn::pickup_and_merge(PickupRequest, PickupAuthorization, &mut DroppedLootRegistry) + -> Result ``` -- `DeliveryRequest` 必须同时表达“按 template 创建”与“交付既有 `ItemInstance`”;后者保留 instance id、耐久、freshness、attributes/NBT 等动态身份,不允许重建成默认实例。 -- `InventoryDeliveryReceipt` 至少包含:最终 `revision`、`created_instance_ids`、直接放入的 instance ids、逐项 merge 的 source/target/count、逐项 spill 的 dropped id/instance id。允许“部分 merge + 剩余落地”,但总量必须守恒。交付既有 `ItemInstance` 时,receipt 中的 placed/spilled source id 必须仍是请求携带的 id,不能归入 `created_instance_ids`。 -- `InventoryConsumeReceipt` 至少包含最终 `revision` 与逐 instance 扣除量;所有 template、数量、身份和持有量前置条件须在 mutation 前完成验证,失败时 inventory/revision 不变。 -- `InventoryPickupReceipt` 至少包含最终 `revision`、被移除的 dropped id、merge/placement 明细,以及 `PickupAttritionBasis { target_instance_id, incoming_instance_id, incoming_stack_count, incoming_abs_qi_before }`。`target_instance_id` 指向提交后真实承载物品的实例(纯放置时等于 incoming id,merge 时为既有 stack id);`incoming_abs_qi_before = dropped.item.spirit_quality * incoming_stack_count`,禁止调用者再从合并后整栈反推。只有 inventory commit 成功后才能从 `DroppedLootRegistry` 删除世界掉落。 -- R5 的稳定 pickup attrition API 必须消费 `PickupAttritionBasis`:只从 `incoming_abs_qi_before` 计算损耗并归还 zone;merge 后目标整栈的绝对真元更新为 `preexisting_abs_qi + incoming_abs_qi_after`,不得对 pre-existing quantity 重复磨损。placement 路径同样通过 receipt 定位,不再假设 dropped id 必然可在 inventory 中二次查到。 -- 堆叠资格统一复用完整 identity 规则;仅 `template_id` 相等不足以合并。freshness、耐久、attributes/NBT 或其它影响物品身份的字段不一致时必须分栈。 -- 所有成功 txn 至多 bump 一次 revision;结构拒绝与容量拒绝不得 bump。receipt 是调用域和 bot 的权威观察面,不再依赖错误字符串前缀或猜测 snapshot 差异。 -- `InventoryTxnError` 至少区分:unknown template、zero quantity、missing container/location、instance id conflict、identity mismatch、insufficient items、capacity exceeded、invalid grid placement、missing spill context。结构错误绝不伪装成 spill。 - -### P0.3 冻结满包与网格策略 - -1. **统一策略:机制结算点/玩家脚下地面掉落,不建个人暂存箱。** craft、alchemy、forge、give、loot 使用同一 delivery fallback;世界坐标和 dimension 由调用者提供。 -2. 只有经过完整验证后确认的容量不足可以 spill。unknown template、零数量、无容器、ID 冲突、非法 footprint 等必须返回结构错误并保持源状态不变。 -3. 调用者没有真实 `SpillContext` 时 fail closed,返回 `CapacityExceeded`/`MissingSpillContext`;不得用虚构坐标,也不得清 session、扣材料或删除世界掉落。 -4. spill 必须携带原 `ItemInstance`;对 minted stack 可按合法最大堆叠拆分,对既有动态实例不得通过重新 mint 丢字段。 -5. 所有 attach 都必须先通过 `validate_attach_fits` 并由 `attach_at_location` 提交;删除 `force_attach_item_to_inventory` 的 `(0,0)` 强塞 fallback。全量转移放不下的剩余项进入显式 spill receipt,而不是重叠占格。 -6. dropped-loot registry 是有界 durable queue,不是无限容器:R10 P1 定义 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 单一常量(初值 `4096`,测试只引用该常量)以及 `DroppedLootRegistry::try_insert` / `try_insert_batch` 原子容量 API。任何生产写入都必须先通过该 API;单条写入在 mutation 前预留 1 槽,批量写入在首条 mutation 前一次性预留全部槽位,禁止调用方直接操作 `entries.insert`。生产 writer 的穷举清单是:`add_item_to_player_inventory_or_ground`、`rebuild_and_drop_overflow`、`enforce_intrinsic_gate_on_morph_release`、`apply_death_drop_on_revive` 的 TSY/主世界分支、`apply_termination_drop_on_terminate` 的 world-drop fallback 分支、`spawn_template_dropped_loot`、`discard_inventory_item_to_dropped_loot`、`tsy_loot_spawn::spawn_for_layer` 以及该文件内所有 placeable-break/TSY layer event writer;P2/P3 必须逐一迁移,测试 fixture 允许直接构造 registry 但不得代表生产接线。所有 delivery/discard/overflow/death/TSY/placeable-break 路径在 mutation 前按“本事务新增 dropped entry 数”原子预留容量。成功 spill 的 source mutation、registry entry 与 durable dropped-loot row 必须由 R3 persistence transaction/outbox seam 作为同一可恢复提交单元落盘;任一持久化失败或进程重启恢复未完成时,源状态保持可重试且不得出现仅 source 已删或仅 drop 已写的单边状态,重试按 dropped id/source revision 幂等。`len + required > MAX` 时返回 typed `DroppedLootCapacityExceeded { current, required, limit }`,inventory、revision、session、材料、世界掉落与 persistence 均保持不变;禁止逐项插入后才发现超限。启动 hydration 的行数检查由 R3 提供 guard 接缝、由 R10 P1 的常量/API 接入;在 R10 P1 合入前 R3 不得引用该 symbol。超限时必须进入 R3 load-failure guard/只读降级并告警,不能截断、驱逐或覆盖旧行。 -7. 正常回收只允许玩家 pickup 或后续显式管理员运维;本 plan 不以 TTL/LRU/按价值驱逐静默销毁稀缺物品。每次 pickup 与 durable delete 同事务释放槽位。R6 的 `dropped_loot_sync` 必须分页/分片,每 payload 至多 `256` entries,携带 `snapshot_revision/page_index/page_count`;client 收齐同 revision 全部分片后原子替换视图,内容变化时不再对每个 client 构建/排序一份无界全量 payload。sync 组装必须先按接收者可见性过滤:至少要求目标玩家与 entry 同 dimension,且 entry 位于该玩家当前授权观察范围(空间距离/zone 由 R6 接入面冻结;owner/private drop 仅 owner 或显式授权管理员可见);分页、计数、revision 和原子替换均只针对过滤后的 recipient view,禁止把全服 registry 快照直接广播给普通 client。 -8. rotate/move receipt 必须携带变更后的完整 item view 或等价 `grid_w/grid_h + rotated` 事实。R10 的 inventory outcome 提供所需事实;R4 在其独占 handler 中把 outcome 交给 emit API;R6 扩展公共 S2C schema/emit 并让 client 以权威新 footprint 替换旧 view。三段任一未完成均不得判 P3/P4 完成。 +共同规则:验证在 staged view 完成;失败时 inventory/revision/session/material/registry 不变;成功最多 bump 一次 revision。堆叠必须完整 identity 相同,不能只比 `template_id`。错误至少区分 unknown/zero/insufficient/identity mismatch/invalid placement/capacity/missing spill context/unauthorized/wrong dimension/persistence unavailable。 -### P0.4 文件所有权与跨轨边界 +`deliver` 同时支持 minted template 与既有 `ItemInstance`。后者必须保留 id、durability、freshness、attributes/NBT、charges、forge/alchemy 与 owner-qi 等动态字段,禁止重建默认实例。receipt 含 request id、revision、created ids、placed existing ids、merge source/target/count、spill dropped/source/count/location,且 `stored + spilled == requested`;既有 id 不得记作 created。 -- **R10 独占**:`server/src/inventory/**`;各产出域只改为调用冻结的 transaction API,不接管其 session/玩法状态机。 -- **R1**:拥有 session 生命周期。R10 返回 delivery 成败与 receipt;R1 只能在成功存入或成功 spill 后清 session,失败必须保留可重试状态。 -- **R3**:拥有 `server/src/persistence/**`、autosave 与 hydration 编排。R3 P1 先冻结 inventory slice hydration seam;R10 P1 同时提供 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 与 `DroppedLootRegistry::{try_insert,try_insert_batch}` 这一容量契约,R3 P2/P4 只能在 R10 P1 merge 后接入并引用它;在此之前 R3 以未超限 guard seam 为占位,不得复制常量。R10 P3 再在 `inventory/migration.rs` 提供纯、幂等的旧布局转换/校验;R3 P4 的 inventory migration consumer 子批次调用它、保存新 schema,并为迁移 overflow 提供真实 `SpillContext`(player identity、mechanism/world position、dimension、durable registry 与 reservation)。R3 consumer 必须先在临时内存/副本上完成 migration,成功且所有 overflow 已通过容量预留并持久化后才提交新 schema;缺上下文、容量不足或持久化失败时保留旧行并返回 retryable load failure,不得伪造坐标、清空旧行或把 overflow 留在无主纯值中。R10 不直接重构 persistence 巨石。 -- **R4**:拥有 C2S gate 与 `server/src/network/client_request_handler.rs`。通过 gate 后调用 R10 txn;对于 rotate/pack move,R4 还负责把 R10 outcome 的 request identity、revision 和新 item view 传给 R6 emit API,但不定义 wire schema。 -- **R5**:拥有 qi ledger、磨损/衰变物理;R10 保留动态字段并产出 `PickupAttritionBasis`,R5 P3 已登记 incoming-only pickup attrition API 与守恒断言,R4 只传 receipt 不重算整栈真元。 -- **R6**:拥有公共 S2C schema/emit 与 client network consumer。R6 canonical plan 的 P0/吸收清单已登记 `rotate-footprint-sync`、`bot-inventory-pack-feedback`;R6 P1/P4 已登记 dropped-loot 分片快照与 inventory receipt contract 子批次,负责修改 `server/src/schema/inventory.rs` 及双端 samples/convert,使成功 `moved`(或等价 accepted receipt)携带请求可归因字段、结果 revision 和权威 item view;同一 R6 P4 子批次同时负责 `InventoryMoveRejectedV1` 的 `instance/from/to` correlation 字段、Rust mirror/proto conversion、emit 与 client consumer/contract samples,覆盖 accepted 与 rejected 两条结果边界。R6 还负责修改 inventory emit API 与 `client/.../InventoryEventHandler.java`,并补 server schema/emit、client handler、Python decoder contract tests。dropped-loot sync 必须在 emit 前按接收者 dimension + 授权空间/zone 范围过滤,owner/private entry 仅 owner 或显式授权管理员可见,分页计数与 revision 绑定过滤后的 recipient view。**在 R6 P4 子批次 merge 前,R10 P3 不得标 ✅、P4 不得归档相关 skeleton。** -- **R7**:拥有 `InspectScreen`;R10 只提供权威 inventory model/receipt,不改 UI 导航架构。 +`consume_checked` receipt 逐 instance 记录扣除量/剩余量;insufficient、unknown、zero 任一失败不得部分扣除。 -#### 跨轨执行顺序(P3 硬门) +`pickup` receipt 含 request id、revision、removed drop、merge/placement、`target_instance_id`、`incoming_instance_id/count/abs_qi_before`。placement 的 target 等于 incoming;merge 的 target 是提交后既有 stack。R5 只按 incoming absolute qi 做 attrition:`target_after = preexisting_abs_qi + incoming_after`,不得磨损旧数量或由合并后整栈反推。 -1. **R10 P3 inventory PR**:在 `server/src/inventory/**` 产出 rotate/pack move 的 typed outcome,含 `instance_id/from/to/revision/item_view`;不触碰 wire 文件。 -2. **R6 P0 + P4 wire PR**:P0 先更新 `plan-refactor-wire-s2c-v1` 的吸收清单/阶段;P4 的 inventory receipt contract 子批次再落地 schema → sample/convert → emit API → Fabric client consumer → 双端 contract tests,其 API 对 R4 可调用。 -3. **R4 P2 handler PR**:在按域拆分 inventory handler 时,把 R10 outcome 交给 R6 emit,并把 `PickupAttritionBasis` 交给 R5 pickup attrition API;成功与拒绝都必须逐请求发稳定机器回执,snapshot 仅作状态修正。若 R4 P2 尚未启动,需先开该阶段的最小 inventory-handler 接线 PR,不得由 R10/R6 越权改 `client_request_handler.rs`。 -4. 三个 PR 均 merge 后,R10 才运行 §P4 的跨层 bot gate,并以实际 server→wire→client/bot 链路作为吸收归档证据。 +## 2. Spill / overflow 守恒 -### P0.5 吸收清单验真 +只允许容量不足 spill 到真实玩家脚下/机制结算点;结构错误、无真实 dimension/position、ID 冲突、非法 footprint 均 fail closed。`SpillContext` 必须含 source identity/revision、真实 dimension/position、registry、durable seam 与 transaction id;不得猜位置或留下无主 handoff。 -| plan(省略 `plan-bughunt-` / `-v1`) | 2026-08-03 裁决 | 第一性证据与 R10 落点 | -|---|---|---| -| `alchemy-takeback-full-inventory-loss` | **吸收交付部分** | 满包产物必须先 deliver/spill 成功再由 R1 清 session;session teardown 本身仍归 R1。P2 delivery matrix 锁定。 | -| `dropped-loot-pickup-stack-merge` | **吸收,仍真实** | `pickup_dropped_loot_instance` 只找空 footprint,不 merge。P2 `pickup_and_merge` + incoming-only attrition receipt。 | -| `force-attach-grid-collision` | **吸收,仍真实** | `force_attach_item_to_inventory` 可直接压入 `(0,0)`,且全量转移可达。P3 删除强塞并用合法 attach/spill。 | -| `rotate-footprint-sync` | **吸收,仍真实** | server 已旋转 footprint,`Moved` S2C 不带新 view,client 复用旧尺寸。P3 与 R6 接缝联动。 | -| `bot-inventory-pack-feedback` | **吸收,仍真实** | pack rebuild 成功路径 snapshot 后提前返回,未发普通 moved receipt。P3 统一 receipt;P4 bot 断言。 | -| `bot-production-inventory-instance-visibility` | **部分闭环,剩余契约吸收** | forge 已从 `/give` 快照取真实 anvil instance 并跑通放砧;灵田场景仍把 `hoe_instance_id` 写死为 `0`,未验证 `hoe_iron` 的 snapshot/equip/真实 till 链。P4 必须恢复该深断言后才能归档。 | -| `forge-outcome-full-inventory-loss` | **吸收,仍真实** | #1294 已于 2026-07-27 merge,但只建立 bughunt skeleton,并非修复在飞。P2 forge outcome 改走统一 deliver/spill。 | +`DroppedLootRegistry` 是有界 durable queue: -**不吸收**: - -- `craft-refund-full-inventory-loss`:已在 `docs/finished_plans/` 独立闭环,不重复实现。 -- `container-filter-and-completion`:独立 feature plan,R10 只提供稳定 transaction/grid 底座。 -- `nested-pack-base`:已在 `docs/finished_plans/` 以 WITHDRAWN/独立结案,不复活旧范围。 +```text +MAX_DURABLE_DROPPED_LOOT_ENTRIES = 4096 +try_insert / try_insert_batch +``` -### P0.6 pre-#249 老布局迁移决议 +所有 production writer 禁止直接 `entries.insert`,包括 give/craft/alchemy/forge/loot、player discard、container/pack overflow、death/revive、termination、morph release、`spawn_template_dropped_loot`、`tsy_loot_spawn::spawn_for_layer`、placeable-break 与 TSY layer/relic writers,以及实现波次枚举出的同类 producer。批量事务在 source mutation 前一次 reservation;超限返回 `{current, required, limit}`,所有状态与 DB 不变。pickup/授权 durable delete 才释放容量;不以 TTL/LRU/价值驱逐静默销毁。超限 hydration 由 R3 只读降级并告警,禁止截断或空表覆盖。 -**决议**:并入 R10 P3,一次清账,但严格等待 R3 P1。 +内存 reservation 不等于 durability。R3 transaction/outbox 必须把 source inventory/session/material mutation、drop insert/delete、source revision、drop/transaction id 作为一个 recoverable commit。失败/崩溃/重启不得形成“只删 source”或“只写 drop”;按 `(transaction_id, source_revision, dropped_id)` 幂等重试,不丢不重。 -1. R10 在 `server/src/inventory/migration.rs` 提供纯函数,例如 `migrate_legacy_inventory_layout(value, schema_version) -> MigrationOutcome`;输入/输出只涉及 inventory slice,不执行 SQL。`MigrationOutcome` 必须显式携带 `overflow: Vec`,不得把无法安放的物品丢弃或藏在不可消费的字符串中。 -2. 迁移挂到 R3 冻结的 hydration seam:现有依据为 `server/src/player/state.rs:1372-1433` 的 schema-version 分流与反序列化路径;R3 P1 先冻结 loader seam,R10 P3 落纯迁移,R3 P4 inventory migration consumer 子批次再调用并保存。R3 consumer 负责把本次加载的玩家身份、真实机制结算点/世界坐标、dimension 与可写 `DroppedLootRegistry` 组装为真实 `SpillContext`,并负责持久化 overflow 与容量 reservation;R10 的纯函数不自行猜位置、不执行 SQL。 -3. 迁移幂等:新布局不变;旧 5×7/main-pack 形态转换为当前容器布局;所有 item instance 与动态字段保留;无法合法放入的物品必须进入 `MigrationOutcome::overflow`,由 R3 consumer 通过 `InventoryTxn::deliver`/显式 migration spill handoff 原子落入 durable registry。缺少真实上下文、容量不足或 overflow 持久化失败时,R3 必须保留旧数据库行并返回可重试的 load failure/只读降级;不得伪造坐标、先写新 schema 后再补 spill,或把 overflow 留在无主内存。 -4. 迁移成功后由 R3 保存新 schema 形状;只有“新 schema 与全部 overflow 均已持久化”才算成功。R10 不直接写 `inventories` 表、不修改 autosave 所有权。 -5. P3 测试至少覆盖:旧布局 happy path、空库存、边界填满、动态字段保留、重复迁移不变、非法/损坏 JSON 拒绝、overflow 明细守恒,以及 R3 consumer 在有真实 `SpillContext` 时成功提交、缺上下文/容量不足/持久化失败时保留旧行并可重试。 +## 3. Recipient-specific dropped sync -**落点**:`server/src/player/state.rs:1372-1433`(当前依据)→ R3 P1 后的 inventory slice loader;本 plan §P3。 +R6 在编码前对每个 recipient 用 server authority 过滤:同 dimension、在授权 distance/zone observation 范围、owner/private 仅 owner 或授权管理员可见。排序、revision、page count 都针对过滤后的 projection;只有 visibility key 完全相同者可复用编码页,禁止一个 global snapshot 发给所有 client。 -### P0.7 开放问题决议(pre-P0 收口,2026-08-03) +## 4. Pickup transaction + authorization -#### #1 满包溢出策略 +R4 从 ECS 构造不可由 client 覆盖的 `PickupAuthorization`:player UUID、`CurrentDimension`、authoritative position/observation range、owner/private permission、server-resolved entry、revision/anti-replay fact。txn 重新验证 entry/instance、同维、距离/zone、权限和 freshness;知道 dropped id、曾收到 sync 或跨维相同 XYZ 都不构成授权。 -**决议**:选择玩家脚下/机制结算点地面掉落,拒绝个人暂存箱。 +顺序固定:authorize + validate merge/placement/capacity → staged inventory commit → durable inventory revision 与 dropped delete 同事务 → receipt/R5 attrition。attach/capacity/auth/persistence 任一失败时 drop 仍在且可重试;placement-only 与 merge 都必须覆盖,成功前不得删除 entry。 -- 复用现役 `add_item_to_player_inventory_or_ground` 与 `rebuild_and_drop_overflow` 语义,减少第二套持久化、权限、过期与 UI 生命周期。 -- 地面掉落保留末法争夺风险;通过 dropped id + instance id + world position/dimension receipt 保证可追踪,并受 §P0.3 的 durable registry 固定上限、原子容量预留和 R6 分片同步约束。 -- 不用 TTL/LRU 静默清理物品;队列满时 delivery fail closed 并保留源状态,玩家 pickup/管理员显式运维释放容量。 -- 仅容量不足可降级,结构错误 fail closed;无真实世界上下文不得伪造 fallback。 +所有 attach 必须 `validate_attach_fits` 后 `attach_at_location`,删除 `(0,0)` 强塞。move/rotate/pack accepted outcome 含 request id、revision、instance/from/to、权威 item view;rejected outcome 含 reason/instance/from/to。snapshot 仅作状态修正,不是动作级反馈。 -**落点**:`server/src/inventory/mod.rs:1839`、`:5016`(依据);本 plan §P0.2、§P0.3、§P2。 +## 5. Legacy migration -#### #2 pre-#249 老布局迁移是否并入 +```text +migrate_legacy_inventory_layout(value, schema_version) + -> MigrationOutcome { migrated_value, overflow: Vec } +``` -**决议**:并入 P3,但在 R3 P1 后实施;R10 提供纯迁移,R3 负责 load/save 编排。 +R10 函数纯且幂等,保留所有实例/动态字段,不执行 SQL、不猜 world context、不隐藏 overflow。R3 在临时副本上以真实 player/dimension/position 与 capacity/durable seam 消费;全成才写新行,失败保留旧行可重试且不重复 drop。 -**落点**:`server/src/player/state.rs:1372-1433`(依据);本 plan §P0.6、§P3。 +## 6. 所有权与顺序 -原开放问题全部已在本节收口。原题保留如下以备追溯,**实施时以 §P0.7 决议为准**: +- **R10**:`server/src/inventory/**` model/grid/txn/capacity、writer enumeration、typed outcome、纯 migration。 +- **R3**:SQL/outbox、spill/pickup recoverable commit、hydration guard、migration consumer。 +- **R4**:C2S gate/handler、authoritative pickup context、调用 R10 并转交 R6 outcome。 +- **R5**:incoming-only qi attrition/ledger。 +- **R6**:receipt wire/client、recipient projection/page、decoder;canonical plan 登记 rotate、pack feedback、dropped sync。 +- **R1**:txn stored/spilled 成功后才 teardown,失败保留 session。 +- **R7**:UI 消费,不拥有事务。 -1. 满包溢出策略:脚下掉落 vs 个人暂存箱。 -2. pre-#249 老存档迁移是否并入本轨 P3。 +顺序:R3 P1 → R10 A/B → R3 durable → R6 wire/client → R4 handler → R3 legacy → e2e。R10 不越权改 persistence、wire、handler 或 client。 -## P1 — 巨石拆分 + `InventoryTxn` 骨架(⬜) +## 7. 审核要求的 contract pins -> 前置:R3 P1 已 merge;仅机械拆分,不夹带 P2/P3 行为修复。 +仅保留下列 demanded pins;refactor 可删除 implementation-coupled 旧测试,不以数量为门: -- 按 §P0.1 建立 `model.rs`、`registry.rs`、`grid.rs`、`txn.rs`、`container.rs`、`migration.rs`,收薄 `mod.rs`;现有 public import 直接迁到新 canonical 路径并统一 re-export,不建立双实现兼容层。 -- 平移现有测试并保持行为等价;新增 module ownership pin,禁止 grid/txn 算法回流 `mod.rs`。按 master §0 的重构轨测试覆盖规则,仅契约 pin 必须保留/迁移;与被删实现绑定的旧单测允许随实现删除,不以原始数量为门禁。 -- 落地 §P0.2 类型骨架、staged validation/commit 基础设施与 error/receipt pin tests;生产调用暂不全量切换。 -- 验收:server 完整门禁全绿;`mod.rs` 不再承担具体 inventory 算法;contract-pin 清单逐项迁移且无缺失,删除的 implementation-coupled tests 在 PR 描述中列出原测试名与删除理由。 +1. `consume_checked` 成功精确扣除;insufficient/unknown/zero 失败无 mutation/revision。 +2. existing instance placement/spill 逐字段保留,created 与 placed/spilled ids 分离。 +3. capacity 的 limit-1/limit/limit+1/batch;逐个生产 writer 证明走统一 gate,失败全状态不变。 +4. spill durable write failure、commit interruption、restart/retry:无单边状态、无重复 drop。 +5. pickup 同维成功;跨维、超距/zone、owner/private 拒绝;merge、placement-only、failed attach/capacity/validation/persistence 后 entry 仍在;成功后才删。 +6. incoming-only attrition receipt + R5 ledger:旧 stack absolute qi 不变且总量守恒。 +7. 两 recipient 的 dimension/range/owner-private 正反 visibility;page/revision 按 projection;缺页/混 revision 不替换。 +8. accepted/rejected move correlation;pack stow/equip/unequip 与拒绝必须动作级 receipt,stale event 和 snapshot-only baseline 不通过。 +9. forge 深链保留;另锁 `/give hoe_iron → 新 snapshot → 真实非零 instance → held/equip → lingtian_start_till`,禁止 `instance_id=0` 或任意 server-data 冒充成功。 +10. migration pure happy/empty/full/dynamic/idempotent/invalid;R3 consumer 对真实 context 成功,缺 context/capacity/persistence failure 保留旧行可重试。 -## P2 — 交付/消费路径统一(⬜) +## 8. Deferred-to-implementation-wave -- give、craft、alchemy、forge、loot/pickup 全部改走 `InventoryTxn::deliver` / `consume_checked` / `pickup_and_merge`。 -- 删除按错误字符串前缀判断 public fallback 的调用契约,改用 typed error/disposition。 -- session 类调用严格执行“txn 成功(stored 或 spilled)后才 teardown”;失败不扣料、不删 session、不删 dropped entry。 -- 饱和测试覆盖:空包、恰好放满、少一格、同身份全 merge/部分 merge、不同动态身份拒绝 merge、无 spill context、每类结构错误、revision 单次 bump、总量守恒;另设 `consume_checked` 专属 contract pin:成功消费逐 instance 扣除精确数量、receipt 逐项记录 source/count、revision 只 bump 一次;库存不足/未知 instance/zero quantity 任一失败时 inventory、revision、session 与材料均逐字节/逐字段保持不变,且不得出现部分扣除。 -- 既有实例 round-trip contract pin:构造非默认 `instance_id/durability/freshness/mineral_id/charges/forge_*/alchemy/lingering_owner_qi` 的 `ItemInstance`,分别验证直接 placement 与 spill 后所有字段逐项不变、receipt 将原 id 记入 placed/spilled 而非 created;merge 只允许完整 identity 相等且 receipt 保留 source/target/count。 -- dropped registry bound pin:`limit-1`、恰好 `limit`、`limit+1`/单事务多 spill 越界;越界时 inventory/revision/session/材料/世界掉落与 persistence 全不变,pickup 后容量可复用,超限 hydration 进入 guard 而不截断。 +P0 不决定 Rust lifetime、SQL/outbox 实现、锁粒度、distance/zone 数值、visibility-key 编码、client 重发、管理员运维或 UI;由 owning PR 设计并受上述 contract/pins 约束。P0 不迁移 writer、不删旧入口、不改 runtime。未跑真实 server→wire→client/bot 链前,不以 forge、snapshot 或文档声明归档 bug skeleton。 -## P3 — 网格/堆叠一致性 + 迁移(⬜) +## 9. 吸收边界 -- 删除 `force_attach_item_to_inventory` 强塞路径;全量转移、遗骸与套包重建统一合法 attach,overflow 返回显式 spill receipt。 -- R10 在 `server/src/inventory/**` 落地拾取合并及 rotate/pack typed outcome;不得把权威 snapshot 当作动作级 receipt。 -- **跨轨阻塞交付**:严格执行 §P0.4 的 R10 → R6 → R4 顺序。R6 PR 必须完成 schema/sample/convert/emit/client handler/contract tests,R4 PR 必须完成 handler 调用;两者未 merge 时,本阶段保持 ⬜,不得归档 `rotate-footprint-sync` 或 `bot-inventory-pack-feedback`。 -- 按 §P0.6 完成 pre-#249 幂等迁移并接入 R3 hydration seam。 -- 饱和测试覆盖每个 footprint 边界、碰撞/swap/rotate 状态、pack rebuild overflow、迁移正反/幂等/字段守恒。 +吸收:`alchemy-takeback-full-inventory-loss`、`dropped-loot-pickup-stack-merge`、`force-attach-grid-collision`、`rotate-footprint-sync`、`bot-inventory-pack-feedback`、`bot-production-inventory-instance-visibility`、`forge-outcome-full-inventory-loss`、`dropped-loot-cross-dimension-pickup`。 -## P4 — bot 验收 + 被吸收 plan 收口(⬜) +不吸收:已闭环 `craft-refund-full-inventory-loss`;独立 feature `container-filter-and-completion`;已撤回 `nested-pack-base`。 -1. `inv_full_delivery_matrix`:满包下 craft 完工、取丹、锻造出炉、give,断言 stored + spilled 总量不丢且失败不提前 teardown。 -2. `inv_stack_merge`:拾取同身份掉落,断言合并既有堆叠、revision 与 receipt;receipt 的 `incoming_stack_count/incoming_abs_qi_before/target_instance_id` 驱动 pickup attrition,断言 ledger 只扣 incoming 绝对真元、旧 stack 原有绝对真元不变且玩家+zone 守恒;不同动态身份保持分栈。另设 placement-only pickup(无可合并目标但有合法空 footprint),确认 incoming 实例原 id 被放置且仅在 inventory commit 成功后删除 dropped entry;再设 attach/capacity/validation 失败路径,断言 inventory/revision 不变且 dropped entry 仍存在可重试。 -3. `inv_footprint_sync`:发送 `rotated=true` 的 2×1 move 后,必须收到**该请求之后**、匹配 `instance_id/from/to` 与结果 revision 的 accepted/moved receipt,且 receipt 中权威 item view 为 1×2;Fabric handler contract test 应把本地模型替换为 1×2。拒绝、1×1、非网格目标不得误改 footprint。全量 snapshot 不能替代此动作级断言。 -4. `inv_pack_feedback`:对 stow / 空 pack unequip / equip 三个成功请求,逐个等待发送时间锚之后、匹配 `instance_id/from/to` 的 accepted/moved receipt,并断言 receipt revision 与随后 snapshot 一致;对非法位置、非空 pack(若规则禁止)逐个断言 `inventory_move_rejected` 的 reason/instance/from/to,连接保持;预置同 instance 的旧 moved/rejected 事件,证明时间锚与结果 revision 不会误读 stale feedback。VFX 和 snapshot 只可附加验证,**不得作为唯一动作结果或 accepted/rejected 的替代**。 -5. `inv_give_visibility_forge`:保留 production forge 的 `/give fan_iron_anvil` → snapshot 真实 instance id → `forge_station_place` → 消耗快照深链回归。 -6. `inv_give_visibility_lingtian`:改造 `production_lingtian_gathering_intents.py`,禁止 `hoe_instance_id: 0` 和“任意 server-data 即成功”;`/give hoe_iron` 后必须在带时间锚的新 `inventory_snapshot` 找到 `hoe_iron` 及真实 instance/location,按生产规则把该实例移到 main-hand held(等待匹配请求的 accepted receipt 与权威 snapshot),再用同一非零 instance id 发 `lingtian_start_till`,断言真实 `lingtian_session`/明确业务拒绝回执且请求未因 instance mismatch 被拒。只有 producer→snapshot→equip→till 全链通过,才可归档 visibility skeleton。 -7. Python server-data decoder 对新增/扩展 inventory receipt 与 dropped-loot 分片快照做正反样本测试;bot 场景的 request correlation 必须同时使用发送时间锚、instance id、from/to 和 revision,不得命中历史事件。 -8. `inv_dropped_loot_bound`:预置到 `MAX_DURABLE_DROPPED_LOOT_ENTRIES`,再分别触发 craft/alchemy/forge/give/loot spill、player discard、death revive 的 TSY/主世界 drop、termination 的 world-drop fallback、TSY relic layer spawn、placeable-break template spawn、backpack unequip overflow、morph-release gate overflow;每个生产 writer 逐域断言通过 `try_insert/try_insert_batch` 获得 typed capacity rejection,源 inventory/revision/session/材料/世界掉落与 persistence 不变;pickup 或显式 durable delete 一件后重试成功。重启超限数据库必须进入 guard 而非截断;分片 sync 每页 ≤ `DROPPED_LOOT_SYNC_PAGE_SIZE`,同 revision 缺页不得替换 client 视图。 -9. 按 §P0.5 逐项核验被吸收 skeleton 的代码、跨轨 merge SHA、client/bot 测试与证据,再依仓库三态规则分别收口;本 plan 未全部完成前不写 `Finish Evidence`、不归档。 +P0 完成只表示上述完整 surface、owner、失败边界与 pins 已冻结,不表示后续实现完成。 diff --git a/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md b/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md index fffb0beec..ca2bfd788 100644 --- a/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md +++ b/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md @@ -20,7 +20,7 @@ - ⬜ P0 设计收口 + 吸收清单验真:113 个变体普查(每个标注应有的门禁四元组现状);冻结 `GateSpec` 与拒绝回执语义;等 #1287(冷却重构,同文件大改)merge 定基线。 - ⬜ P1 门禁中间件落地:gate 层上线,先给"已知漏洞簇"的 ~20 个请求类型挂 spec(吸收清单全命中),旧内联校验保留并行断言一个版本期。 -- ⬜ P2 巨石拆分批次 A:巨型 match 拆为按域 handler 注册表(combat/production/world/social/npc 五组),行为不变,bot 场景锁住。 +- ⬜ P2 巨石拆分批次 A:巨型 match 拆为按域 handler 注册表(combat/production/world/social/npc 五组),行为不变,bot 场景锁住;inventory pickup handler 必须从 ECS `CurrentDimension`、authoritative position/observation range 与 owner/private permission 构造 R10 `PickupAuthorization`,禁止仅凭 client XYZ/instance id 调用 txn,且把 R10 accepted/rejected outcome 交给 R6 emit API。 - ⬜ P3 巨石拆分批次 B + 全量挂 spec + 删旧:113 变体全部声明门禁(含显式 `no_gate` 声明,杜绝静默无门禁);删除各域内联距离常量与重复维度判断。 - ⬜ P4 bot 验收 + 吸收 plan 批量归档。 @@ -37,7 +37,7 @@ skeleton:alchemy-furnace-scope-gate、block-place-reach-gate、coffin-reclaim- ## bot 验收场景 -1. `gate_cross_dimension`:bot 在 TSY 维度对主世界坐标发 workbench/zhenfa/coffin/trade 请求→全部拒绝且回执带原因。 +1. `gate_cross_dimension`:bot 在 TSY 维度对主世界坐标发 workbench/zhenfa/coffin/trade/pickup 请求→全部拒绝且回执带原因;pickup 即使 dropped id 与 XYZ 已知,也必须以 server `CurrentDimension` 拒绝跨维请求。 2. `gate_reach`:超距放方块/开炉/采灵田→拒绝;贴脸→放行。 3. `gate_ownership`:拆他人棺/取他人容器→拒绝。 4. `gate_state_precondition`:给丹先校验后扣(满包/死亡目标不吞丹);丹毒超阈值禁服。 diff --git a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md index 6db24f717..23fa0c22d 100644 --- a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md +++ b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md @@ -22,7 +22,7 @@ - ⬜ P0 设计收口 + 吸收清单验真:53 张表普查归域;冻结 Slice trait(载入守护语义:读失败 = 保留旧行 + 告警 + 只读降级,绝不写回空态;flush registry;tick rebase 协议);等 #1288/#1289/#1261/#1259 merge 后定基线。 - ⬜ P1 框架落地 + 巨石拆分:`persistence/` 按域拆文件(迁移链不变、行为不变);Slice 框架上线,KnownTechniques/Lifecycle(在飞 PR 的成果)平移为首批宿主;冻结 inventory slice hydration seam 及 `MigrationOutcome` consumer 边界,R3 不复制 inventory 网格规则,待 R10 P3 提供纯幂等迁移函数后接入。R3 P1 只能冻结该 seam,不得引用尚未合入的 R10 常量或实现。 -- ⬜ P2 载入守护推广:全部玩家 slice(SkillSet/Wounds/状态 buff/身份键……)收编,#1290 模式全量落地;dropped-loot slice 的有界 hydration guard 依赖 R10 P1 已 merge 的容量契约:仅在该前置成立后引用 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 与 `DroppedLootRegistry::try_insert/try_insert_batch`;超限进入统一 load-failure guard/只读降级并告警,禁止 `take(limit)` 截断、驱逐旧条目或以空 registry 覆盖数据库。在 R10 P1 未 merge 时,R3 P2 不得编译或复制临时常量。 +- ⬜ P2 载入守护推广:全部玩家 slice(SkillSet/Wounds/状态 buff/身份键……)收编,#1290 模式全量落地;dropped-loot slice 的有界 hydration guard 依赖 R10 P1 已 merge 的容量契约:仅在该前置成立后引用 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 与 `DroppedLootRegistry::try_insert/try_insert_batch`;超限进入统一 load-failure guard/只读降级并告警,禁止 `take(limit)` 截断、驱逐旧条目或以空 registry 覆盖数据库。在 R10 P1 未 merge 时,R3 P2 不得编译或复制临时常量。同步冻结 spill/pickup persistence transaction/outbox seam:source inventory/session/material mutation、dropped row insert/delete 与幂等 transaction id 必须构成可恢复提交单元,供 R10 production writers 使用。 - ⬜ P3 关服 flush + tick rebase 批次:shutdown flush registry 收编全部"节流落盘"域;绝对 tick 全部改相对基准;autosave/事件写入竞态互斥(coffin-autosave-inflight-race 模式)。 - ⬜ P4 遗漏运行态补持久化批次:ActiveEvents、TiandaoAttention、状态效果、化虚冷却、灵眼、地表遗缴、散灵珠、可放置实体、dormant 往返身份完整性(heiwushi)等——逐个按 Slice 框架补表;在 R10 P3 merge 后落 inventory migration consumer 子批次,调用 `inventory::migration::migrate_legacy_inventory_layout`,用玩家 identity、真实机制结算点/世界 position、dimension 组装 `SpillContext`,把 `MigrationOutcome::overflow` 通过 R10 capacity API 持久化到 durable registry;仅在新 schema 与全部 overflow 都成功持久化后提交新 schema,缺上下文/容量/持久化失败则保留旧行并进入可重试 load guard。该 consumer 明确依赖 R10 P1/P3 merge,不得提前实现或另造容量常量。 - ⬜ P5 bot 验收 + 吸收 plan 批量归档。 @@ -42,7 +42,7 @@ skeleton:coffin-autosave-inflight-race、identity-persist-key-mismatch、miner 1. `restart_player_slices`:bot 建号→修炼/学功法/受伤→关服重启→重连→断言功法/伤势/濒死后果/buff 全部还原;另以 pre-#249 inventory fixture 验证 R10 纯迁移保留全部 instance/dynamic fields、重复载入幂等并保存新 schema。 2. `restart_world_runtime`:触发矿脉枯竭/配方解锁/zone influence→SIGTERM 关服→重启→断言无回滚无复活。 -3. `load_failure_guard`:基础损坏 slice 仍断言守护降级而非清零覆盖;超过 R10 P1 提供的 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 的 dropped-loot rows 只有在 R10 P1 已 merge 后执行同一 guard,数据库行数与内容不得被截断/清空;R10 前置未满足时该断言保持待接线,不得引用不存在的 symbol。 +3. `load_failure_guard`:基础损坏 slice 仍断言守护降级而非清零覆盖;超过 R10 P1 提供的 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 的 dropped-loot rows 只有在 R10 P1 已 merge 后执行同一 guard,数据库行数与内容不得被截断/清空;R10 前置未满足时该断言保持待接线,不得引用不存在的 symbol。另注入 spill durable write failure 与 commit interruption/restart,断言无 source-only/drop-only 单边状态,按 transaction id 重试不重复 drop。 4. `tick_rebase`:带冷却/再生倒计时重启→断言倒计时按真实流逝折算(对齐 #1289 的 deadline 折算先例)。 ## 开放问题(pre-P0 收口) diff --git a/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md b/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md index bd639677c..5ed4bf87a 100644 --- a/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md +++ b/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md @@ -25,6 +25,8 @@ - ⬜ P4 契约 pin 全量化:双向 sample 对拍测试补齐(113 C2S + 144 S2C 每变体至少一条正反 sample,schema 改动连 sample 一起改);emit 迁移到 builder 的长尾批次;完成 inventory receipt contract 子批次:`InventoryEventV1::Moved`(或等价 accepted receipt)必须携带 request identity、结果 revision、权威 item view,覆盖 schema/sample/convert/emit API、Fabric `InventoryEventHandler` 与 Python decoder,供 R4 handler 消费 R10 typed outcome。分片 dropped-loot 正反样本必须覆盖空/单页/恰好 256/257/末页缺失/混 revision。 - ⬜ P5 bot 验收 + 吸收 plan 批量归档。 +R10 dropped-loot 契约优先:编码前按 recipient dimension/range/owner 投影,仅同 visibility key 复用;rejected receipt 含 reason/instance/from/to,并测两 recipient 正反可见性。 + ## 吸收清单(短名省略 plan-bughunt- 前缀与 -v1 后缀) active:server-data-s2c-schema-union-drift(TS union 补齐走 regenerate)、spirit-treasure-chat-key-conflict 除外(归 R7)。 From dd83c9c7af4117e8dbc26d28ed9f7a833febd2c6 Mon Sep 17 00:00:00 2001 From: server_kizuna Date: Mon, 3 Aug 2026 15:17:46 +0800 Subject: [PATCH 07/13] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20R10=20=E9=87=8D?= =?UTF-8?q?=E5=86=99=E5=9B=9E=E5=BD=92=EF=BC=9A=E6=81=A2=E5=A4=8D=E9=AA=8C?= =?UTF-8?q?=E6=94=B6=E4=B8=8E=E5=8E=9F=E5=AD=90=E6=8F=90=E4=BA=A4=E5=A5=91?= =?UTF-8?q?=E7=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Model: cc-sonnet-high Co-Authored-By: Claude --- docs/plan-refactor-inventory-core-v1.md | 55 +++++++++++++------ .../plan-refactor-persistence-slices-v1.md | 4 +- 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/docs/plan-refactor-inventory-core-v1.md b/docs/plan-refactor-inventory-core-v1.md index c14cf8fcc..854047376 100644 --- a/docs/plan-refactor-inventory-core-v1.md +++ b/docs/plan-refactor-inventory-core-v1.md @@ -4,11 +4,11 @@ ## 阶段 -- ✅ 2026-08-03 P0 完整契约面重写 -- ⬜ A:inventory 拆分 + txn/capacity 骨架 -- ⬜ B:全部 production writer 迁移 -- ⬜ C:R3 durable seam + R6 wire/client + R4 handler -- ⬜ D:legacy consumer + bot/e2e + plan 收口 +- ✅ 2026-08-03 P0 完整契约面重写 + absorption audit +- ⬜ P1:inventory 拆分 + txn/capacity 骨架 +- ⬜ P2:全部 production writer 迁移 +- ⬜ P3:纯 migration + R3 durable seam + R6 wire/client + R4 handler +- ⬜ P4:R3 legacy consumer + bot/e2e + plan 收口 实现属 Wave 2;跨轨工作须登记 owning plan。 @@ -58,13 +58,15 @@ try_insert / try_insert_batch ## 3. Recipient-specific dropped sync -R6 在编码前对每个 recipient 用 server authority 过滤:同 dimension、在授权 distance/zone observation 范围、owner/private 仅 owner 或授权管理员可见。排序、revision、page count 都针对过滤后的 projection;只有 visibility key 完全相同者可复用编码页,禁止一个 global snapshot 发给所有 client。 +`DroppedLootEntry` 必须持久化 `owner: Option` 与 `visibility: Public | OwnerOnly`;producer 从机制权威 source 写入,普通 world loot 为 `Public`,私人 spill/drop 为 `OwnerOnly`。管理员授权来自 server permission,不写入 client payload;R3 migration/hydration 原样保留这些字段,缺失旧数据仅按明确 migration 规则补 `Public`,不得从请求猜 owner。 + +R6 在编码前对每个 recipient 用 server authority 过滤:同 dimension、在授权 distance/zone observation 范围、`OwnerOnly` 仅 owner 或授权管理员可见。排序、revision、page count 都针对过滤后的 projection;只有 visibility key 完全相同者可复用编码页,禁止一个 global snapshot 发给所有 client。 ## 4. Pickup transaction + authorization R4 从 ECS 构造不可由 client 覆盖的 `PickupAuthorization`:player UUID、`CurrentDimension`、authoritative position/observation range、owner/private permission、server-resolved entry、revision/anti-replay fact。txn 重新验证 entry/instance、同维、距离/zone、权限和 freshness;知道 dropped id、曾收到 sync 或跨维相同 XYZ 都不构成授权。 -顺序固定:authorize + validate merge/placement/capacity → staged inventory commit → durable inventory revision 与 dropped delete 同事务 → receipt/R5 attrition。attach/capacity/auth/persistence 任一失败时 drop 仍在且可重试;placement-only 与 merge 都必须覆盖,成功前不得删除 entry。 +顺序固定:authorize + validate merge/placement/capacity → staged attach/merge 与 incoming-only R5 attrition transfer(item qi → authoritative zone + ledger)→ 同一 durable transaction 原子提交 attrited item、inventory revision、zone balance/ledger 与 dropped delete → receipt。任一步失败或崩溃恢复都不得只应用其中一侧;drop 保留且可按 transaction id 重试。placement-only 与 merge 都必须覆盖。 所有 attach 必须 `validate_attach_fits` 后 `attach_at_location`,删除 `(0,0)` 强塞。move/rotate/pack accepted outcome 含 request id、revision、instance/from/to、权威 item view;rejected outcome 含 reason/instance/from/to。snapshot 仅作状态修正,不是动作级反馈。 @@ -87,31 +89,52 @@ R10 函数纯且幂等,保留所有实例/动态字段,不执行 SQL、不 - **R1**:txn stored/spilled 成功后才 teardown,失败保留 session。 - **R7**:UI 消费,不拥有事务。 -顺序:R3 P1 → R10 A/B → R3 durable → R6 wire/client → R4 handler → R3 legacy → e2e。R10 不越权改 persistence、wire、handler 或 client。 +顺序:R3 P1 → R10 P1/P2 → R3 durable → R6 wire/client → R4 handler → R10 P3 pure migration → R3 legacy → e2e。R10 不越权改 persistence、wire、handler 或 client。 ## 7. 审核要求的 contract pins 仅保留下列 demanded pins;refactor 可删除 implementation-coupled 旧测试,不以数量为门: 1. `consume_checked` 成功精确扣除;insufficient/unknown/zero 失败无 mutation/revision。 -2. existing instance placement/spill 逐字段保留,created 与 placed/spilled ids 分离。 +2. `deliver` 对 same-template/different-identity、duplicate id、illegal footprint/placement、容量不足但缺 `SpillContext` 逐项 typed reject,且无 mutation/revision;existing instance placement/spill 逐字段保留,created 与 placed/spilled ids 分离。 3. capacity 的 limit-1/limit/limit+1/batch;逐个生产 writer 证明走统一 gate,失败全状态不变。 4. spill durable write failure、commit interruption、restart/retry:无单边状态、无重复 drop。 5. pickup 同维成功;跨维、超距/zone、owner/private 拒绝;merge、placement-only、failed attach/capacity/validation/persistence 后 entry 仍在;成功后才删。 -6. incoming-only attrition receipt + R5 ledger:旧 stack absolute qi 不变且总量守恒。 +6. incoming-only attrition receipt + R5 ledger:旧 stack absolute qi 不变;注入 attrition 后、durable commit 中断与 restart/retry,断言 attrited item + zone/ledger + drop delete 原子且总量守恒。 7. 两 recipient 的 dimension/range/owner-private 正反 visibility;page/revision 按 projection;缺页/混 revision 不替换。 8. accepted/rejected move correlation;pack stow/equip/unequip 与拒绝必须动作级 receipt,stale event 和 snapshot-only baseline 不通过。 9. forge 深链保留;另锁 `/give hoe_iron → 新 snapshot → 真实非零 instance → held/equip → lingtian_start_till`,禁止 `instance_id=0` 或任意 server-data 冒充成功。 10. migration pure happy/empty/full/dynamic/idempotent/invalid;R3 consumer 对真实 context 成功,缺 context/capacity/persistence failure 保留旧行可重试。 -## 8. Deferred-to-implementation-wave +## 8. Named bot acceptance(P4) -P0 不决定 Rust lifetime、SQL/outbox 实现、锁粒度、distance/zone 数值、visibility-key 编码、client 重发、管理员运维或 UI;由 owning PR 设计并受上述 contract/pins 约束。P0 不迁移 writer、不删旧入口、不改 runtime。未跑真实 server→wire→client/bot 链前,不以 forge、snapshot 或文档声明归档 bug skeleton。 +以下名称即 `scripts/bot/scenarios/.py` 的稳定身份: + +1. `inv_full_delivery_matrix`:craft/alchemy/forge/give 满包时 `stored + spilled == requested`,失败不 teardown。 +2. `inv_stack_merge`:同 identity merge、异 identity 分栈;placement-only 与拒绝路径保留 drop;attrition durable 中断/restart 仍原子守恒。 +3. `inv_footprint_sync`:2×1 rotate 后以 request/instance/from/to/revision 锚定 1×2 authoritative receipt;snapshot 不代替回执。 +4. `inv_pack_feedback`:stow/equip/unequip 的 accepted/rejected 均按时间锚与 correlation 匹配,stale event 不通过。 +5. `inv_give_visibility_forge`:`/give fan_iron_anvil` → 新 snapshot 真实 id → `forge_station_place`。 +6. `inv_give_visibility_lingtian`:`/give hoe_iron` → 新 snapshot 非零 id → held/equip receipt → `lingtian_start_till`。 +7. `inv_dropped_loot_bound`:全 writer 容量拒绝、delete 后重试、超限 hydration guard、recipient 分页/可见性。 -## 9. 吸收边界 +## 9. 吸收边界与 P0 验真 -吸收:`alchemy-takeback-full-inventory-loss`、`dropped-loot-pickup-stack-merge`、`force-attach-grid-collision`、`rotate-footprint-sync`、`bot-inventory-pack-feedback`、`bot-production-inventory-instance-visibility`、`forge-outcome-full-inventory-loss`、`dropped-loot-cross-dimension-pickup`。 +| plan | 2026-08-03 验真结论 | 证据/落点 | +|---|---|---| +| `alchemy-takeback-full-inventory-loss` | 部分吸收 | 满包 deliver/spill 后才由 R1 teardown。 | +| `dropped-loot-pickup-stack-merge` | 仍真实,吸收 | 当前 pickup 只找空 footprint;落 `pickup_and_merge`。 | +| `force-attach-grid-collision` | 仍真实,吸收 | `(0,0)` 强塞仍可达;改合法 attach/spill。 | +| `rotate-footprint-sync` | 仍真实,吸收 | `Moved` 缺权威新 footprint;R6 receipt 补齐。 | +| `bot-inventory-pack-feedback` | 仍真实,吸收 | pack 成功路径可只有 snapshot;动作级 receipt 补齐。 | +| `bot-production-inventory-instance-visibility` | 部分闭环,吸收剩余 | forge 已用真实 id;lingtian 仍有 id=0 baseline,场景 6 锁定。 | +| `forge-outcome-full-inventory-loss` | 仍真实,吸收 | #1294 只建立 skeleton;outcome 改走统一 deliver/spill。 | +| `dropped-loot-cross-dimension-pickup` | 仍真实,吸收 | 当前 entry 有 dimension 但 pickup 未获 `CurrentDimension`;R4 authorization 补齐。 | -不吸收:已闭环 `craft-refund-full-inventory-loss`;独立 feature `container-filter-and-completion`;已撤回 `nested-pack-base`。 +不吸收:已闭环 `craft-refund-full-inventory-loss`;独立 feature `container-filter-and-completion`;已撤回 `nested-pack-base`。P0 仅在逐项复读代码/plan 并记录上述 live/fixed/invalid 结论后完成;P4 仍须按表逐项核验 merge SHA 与 bot/client 证据后才能归档。 + +## 10. Deferred-to-implementation-wave + +P0 不决定 Rust lifetime、SQL/outbox 实现、锁粒度、distance/zone 数值、visibility-key 编码、client 重发、管理员运维或 UI;由 owning PR 设计并受上述 contract/pins 约束。P0 不迁移 writer、不删旧入口、不改 runtime。未跑真实 server→wire→client/bot 链前,不以 forge、snapshot 或文档声明归档 bug skeleton。 -P0 完成只表示上述完整 surface、owner、失败边界与 pins 已冻结,不表示后续实现完成。 +P0 完成只表示上述 surface、owner、失败边界、pins 与逐项 absorption audit 已冻结,不表示后续实现完成。 diff --git a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md index 23fa0c22d..03302a3aa 100644 --- a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md +++ b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md @@ -22,7 +22,7 @@ - ⬜ P0 设计收口 + 吸收清单验真:53 张表普查归域;冻结 Slice trait(载入守护语义:读失败 = 保留旧行 + 告警 + 只读降级,绝不写回空态;flush registry;tick rebase 协议);等 #1288/#1289/#1261/#1259 merge 后定基线。 - ⬜ P1 框架落地 + 巨石拆分:`persistence/` 按域拆文件(迁移链不变、行为不变);Slice 框架上线,KnownTechniques/Lifecycle(在飞 PR 的成果)平移为首批宿主;冻结 inventory slice hydration seam 及 `MigrationOutcome` consumer 边界,R3 不复制 inventory 网格规则,待 R10 P3 提供纯幂等迁移函数后接入。R3 P1 只能冻结该 seam,不得引用尚未合入的 R10 常量或实现。 -- ⬜ P2 载入守护推广:全部玩家 slice(SkillSet/Wounds/状态 buff/身份键……)收编,#1290 模式全量落地;dropped-loot slice 的有界 hydration guard 依赖 R10 P1 已 merge 的容量契约:仅在该前置成立后引用 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 与 `DroppedLootRegistry::try_insert/try_insert_batch`;超限进入统一 load-failure guard/只读降级并告警,禁止 `take(limit)` 截断、驱逐旧条目或以空 registry 覆盖数据库。在 R10 P1 未 merge 时,R3 P2 不得编译或复制临时常量。同步冻结 spill/pickup persistence transaction/outbox seam:source inventory/session/material mutation、dropped row insert/delete 与幂等 transaction id 必须构成可恢复提交单元,供 R10 production writers 使用。 +- ⬜ P2 载入守护推广:全部玩家 slice(SkillSet/Wounds/状态 buff/身份键……)收编,#1290 模式全量落地;dropped-loot slice 的有界 hydration guard 依赖 R10 P1 已 merge 的容量契约:仅在该前置成立后引用 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 与 `DroppedLootRegistry::try_insert/try_insert_batch`;超限进入统一 load-failure guard/只读降级并告警,禁止 `take(limit)` 截断、驱逐旧条目或以空 registry 覆盖数据库。在 R10 P1 未 merge 时,R3 P2 不得编译或复制临时常量。同步冻结 spill/pickup persistence transaction/outbox seam:source mutation、attrited item、zone balance/qi ledger、drop insert/delete 与幂等 transaction id 必须构成一个 recoverable commit,供 R10 production writers 使用。 - ⬜ P3 关服 flush + tick rebase 批次:shutdown flush registry 收编全部"节流落盘"域;绝对 tick 全部改相对基准;autosave/事件写入竞态互斥(coffin-autosave-inflight-race 模式)。 - ⬜ P4 遗漏运行态补持久化批次:ActiveEvents、TiandaoAttention、状态效果、化虚冷却、灵眼、地表遗缴、散灵珠、可放置实体、dormant 往返身份完整性(heiwushi)等——逐个按 Slice 框架补表;在 R10 P3 merge 后落 inventory migration consumer 子批次,调用 `inventory::migration::migrate_legacy_inventory_layout`,用玩家 identity、真实机制结算点/世界 position、dimension 组装 `SpillContext`,把 `MigrationOutcome::overflow` 通过 R10 capacity API 持久化到 durable registry;仅在新 schema 与全部 overflow 都成功持久化后提交新 schema,缺上下文/容量/持久化失败则保留旧行并进入可重试 load guard。该 consumer 明确依赖 R10 P1/P3 merge,不得提前实现或另造容量常量。 - ⬜ P5 bot 验收 + 吸收 plan 批量归档。 @@ -42,7 +42,7 @@ skeleton:coffin-autosave-inflight-race、identity-persist-key-mismatch、miner 1. `restart_player_slices`:bot 建号→修炼/学功法/受伤→关服重启→重连→断言功法/伤势/濒死后果/buff 全部还原;另以 pre-#249 inventory fixture 验证 R10 纯迁移保留全部 instance/dynamic fields、重复载入幂等并保存新 schema。 2. `restart_world_runtime`:触发矿脉枯竭/配方解锁/zone influence→SIGTERM 关服→重启→断言无回滚无复活。 -3. `load_failure_guard`:基础损坏 slice 仍断言守护降级而非清零覆盖;超过 R10 P1 提供的 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 的 dropped-loot rows 只有在 R10 P1 已 merge 后执行同一 guard,数据库行数与内容不得被截断/清空;R10 前置未满足时该断言保持待接线,不得引用不存在的 symbol。另注入 spill durable write failure 与 commit interruption/restart,断言无 source-only/drop-only 单边状态,按 transaction id 重试不重复 drop。 +3. `load_failure_guard`:基础损坏 slice 仍断言守护降级而非清零覆盖;超过 R10 P1 提供的 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 的 dropped-loot rows 只有在 R10 P1 已 merge 后执行同一 guard,数据库行数与内容不得被截断/清空;R10 前置未满足时该断言保持待接线,不得引用不存在的 symbol。另注入 spill durable write failure,以及 pickup attrition staged 后的 commit interruption/restart,断言 attrited item、zone/ledger 与 drop delete 无单边状态,按 transaction id 重试不重复应用。 4. `tick_rebase`:带冷却/再生倒计时重启→断言倒计时按真实流逝折算(对齐 #1289 的 deadline 折算先例)。 ## 开放问题(pre-P0 收口) From 1b0983c2bc08ce0545c7e69d00d414b0b3e03054 Mon Sep 17 00:00:00 2001 From: server_kizuna Date: Mon, 3 Aug 2026 16:08:37 +0800 Subject: [PATCH 08/13] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20inventory=20?= =?UTF-8?q?=E8=B7=A8=E8=BD=A8=E9=97=A8=E7=A6=81=E4=B8=8E=E4=B8=A2=E5=BC=83?= =?UTF-8?q?=E5=AE=B9=E9=87=8F=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Model: cc-sonnet-high Co-Authored-By: Claude --- docs/plan-refactor-inventory-core-v1.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/plan-refactor-inventory-core-v1.md b/docs/plan-refactor-inventory-core-v1.md index 854047376..53e8fd242 100644 --- a/docs/plan-refactor-inventory-core-v1.md +++ b/docs/plan-refactor-inventory-core-v1.md @@ -49,10 +49,12 @@ InventoryTxn::pickup_and_merge(PickupRequest, PickupAuthorization, &mut DroppedL ```text MAX_DURABLE_DROPPED_LOOT_ENTRIES = 4096 +MAX_OWNER_ONLY_DISCARD_ENTRIES_PER_PLAYER = 256 +SYSTEM_RESERVED_DURABLE_DROPPED_LOOT_ENTRIES = 512 try_insert / try_insert_batch ``` -所有 production writer 禁止直接 `entries.insert`,包括 give/craft/alchemy/forge/loot、player discard、container/pack overflow、death/revive、termination、morph release、`spawn_template_dropped_loot`、`tsy_loot_spawn::spawn_for_layer`、placeable-break 与 TSY layer/relic writers,以及实现波次枚举出的同类 producer。批量事务在 source mutation 前一次 reservation;超限返回 `{current, required, limit}`,所有状态与 DB 不变。pickup/授权 durable delete 才释放容量;不以 TTL/LRU/价值驱逐静默销毁。超限 hydration 由 R3 只读降级并告警,禁止截断或空表覆盖。 +所有 production writer 禁止直接 `entries.insert`,包括 give/craft/alchemy/forge/loot、player discard、container/pack overflow、death/revive、termination、morph release、`spawn_template_dropped_loot`、`tsy_loot_spawn::spawn_for_layer`、placeable-break 与 TSY layer/relic writers,以及实现波次枚举出的同类 producer。批量事务在 source mutation 前一次 reservation;超限返回 `{current, required, limit}`,所有状态与 DB 不变。`OwnerOnly` 的 player discard 同时受每个 `PlayerId` 的 `MAX_OWNER_ONLY_DISCARD_ENTRIES_PER_PLAYER` 配额和 `SYSTEM_RESERVED_DURABLE_DROPPED_LOOT_ENTRIES` 系统保留容量约束,不得消耗系统保留区;craft/alchemy/forge/loot/death/termination 等 production/system writer 才能预留保留区。单一 owner 因此不能填满全局 queue,其他玩家的 production spill 在 discard 洪峰下仍保有 bounded admission。pickup/授权 durable delete 才释放容量;不以 TTL/LRU/价值驱逐静默销毁。超限 hydration 由 R3 只读降级并告警,禁止截断或空表覆盖。 内存 reservation 不等于 durability。R3 transaction/outbox 必须把 source inventory/session/material mutation、drop insert/delete、source revision、drop/transaction id 作为一个 recoverable commit。失败/崩溃/重启不得形成“只删 source”或“只写 drop”;按 `(transaction_id, source_revision, dropped_id)` 幂等重试,不丢不重。 @@ -81,15 +83,15 @@ R10 函数纯且幂等,保留所有实例/动态字段,不执行 SQL、不 ## 6. 所有权与顺序 -- **R10**:`server/src/inventory/**` model/grid/txn/capacity、writer enumeration、typed outcome、纯 migration。 -- **R3**:SQL/outbox、spill/pickup recoverable commit、hydration guard、migration consumer。 -- **R4**:C2S gate/handler、authoritative pickup context、调用 R10 并转交 R6 outcome。 -- **R5**:incoming-only qi attrition/ledger。 -- **R6**:receipt wire/client、recipient projection/page、decoder;canonical plan 登记 rotate、pack feedback、dropped sync。 +- **R10**:`server/src/inventory/**` model/grid/txn/capacity、writer enumeration、typed outcome、纯 migration。P1 仅在 **R3 P1** 的 inventory/overflow seam 冻结后实现 txn/capacity 骨架;P2 production writers 只有在 **R3 P3** durable spill/pickup recoverable-commit seam 已合入后才可迁移并宣称完成;P3 pickup/attrition consumer 只有在 **R5 P3** incoming-only attrition/ledger API 与 **R6 P4** receipt wire/client API 已合入后才可接通。 +- **R3**:SQL/outbox、spill/pickup recoverable commit、hydration guard、migration consumer;R10 只消费 R3 P1/P3/P4 已冻结的接口。 +- **R4**:C2S gate/handler、authoritative pickup context、调用 R10 并转交 R6 outcome;R4 handler/consumer phase 必须等待 **R6 P4** receipt API 与 **R5 P3** attrition API,不得以 R10 mock 或仅 R6 P1 schema 代替。 +- **R5**:incoming-only qi attrition/ledger;provider phase 为 R5 P3。 +- **R6**:receipt wire/client、recipient projection/page、decoder;canonical plan 登记 rotate、pack feedback、dropped sync;receipt provider phase 为 R6 P4。 - **R1**:txn stored/spilled 成功后才 teardown,失败保留 session。 - **R7**:UI 消费,不拥有事务。 -顺序:R3 P1 → R10 P1/P2 → R3 durable → R6 wire/client → R4 handler → R10 P3 pure migration → R3 legacy → e2e。R10 不越权改 persistence、wire、handler 或 client。 +顺序:**R3 P1 → R10 P1 → R3 P3 → R10 P2 production writers → R5 P3 + R6 P4 → R4 handler/pickup consumer → R10 P3 migration/consumer → R3 P4 legacy consumer → e2e**。R10 P2 在 R3 P3 durable seam 未合入前只能保留 skeleton,R4 pickup consumer 在 R5 P3/R6 P4 provider 未合入前不得宣称完成;R10 不越权改 persistence、wire、handler 或 client。 ## 7. 审核要求的 contract pins @@ -97,7 +99,7 @@ R10 函数纯且幂等,保留所有实例/动态字段,不执行 SQL、不 1. `consume_checked` 成功精确扣除;insufficient/unknown/zero 失败无 mutation/revision。 2. `deliver` 对 same-template/different-identity、duplicate id、illegal footprint/placement、容量不足但缺 `SpillContext` 逐项 typed reject,且无 mutation/revision;existing instance placement/spill 逐字段保留,created 与 placed/spilled ids 分离。 -3. capacity 的 limit-1/limit/limit+1/batch;逐个生产 writer 证明走统一 gate,失败全状态不变。 +3. capacity 的 limit-1/limit/limit+1/batch;逐个生产 writer 证明走统一 gate,失败全状态不变;owner-only player discard 覆盖单 `PlayerId` 的 quota-1/quota/quota+1 与 system-reserved boundary,证明一个 owner 不能耗尽 global capacity,系统 writer 在 discard 洪峰下仍可 admission。 4. spill durable write failure、commit interruption、restart/retry:无单边状态、无重复 drop。 5. pickup 同维成功;跨维、超距/zone、owner/private 拒绝;merge、placement-only、failed attach/capacity/validation/persistence 后 entry 仍在;成功后才删。 6. incoming-only attrition receipt + R5 ledger:旧 stack absolute qi 不变;注入 attrition 后、durable commit 中断与 restart/retry,断言 attrited item + zone/ledger + drop delete 原子且总量守恒。 From c8c0f613b0c3427f0d917212954df4991b4c6b29 Mon Sep 17 00:00:00 2001 From: server_kizuna Date: Mon, 3 Aug 2026 16:58:55 +0800 Subject: [PATCH 09/13] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20R10=20writer=20?= =?UTF-8?q?=E8=BF=81=E7=A7=BB=E9=97=A8=E4=B8=8E=E9=98=B6=E6=AE=B5=E5=BD=92?= =?UTF-8?q?=E5=B1=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Model: cc-sonnet-high Co-Authored-By: Claude --- docs/plan-refactor-inventory-core-v1.md | 14 +++++++------- .../plan-refactor-persistence-slices-v1.md | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/plan-refactor-inventory-core-v1.md b/docs/plan-refactor-inventory-core-v1.md index 53e8fd242..47f9c6a8a 100644 --- a/docs/plan-refactor-inventory-core-v1.md +++ b/docs/plan-refactor-inventory-core-v1.md @@ -5,10 +5,10 @@ ## 阶段 - ✅ 2026-08-03 P0 完整契约面重写 + absorption audit -- ⬜ P1:inventory 拆分 + txn/capacity 骨架 -- ⬜ P2:全部 production writer 迁移 -- ⬜ P3:纯 migration + R3 durable seam + R6 wire/client + R4 handler -- ⬜ P4:R3 legacy consumer + bot/e2e + plan 收口 +- ⬜ P1:inventory 拆分 + txn/capacity 骨架(依赖 R3 P1 seam) +- ⬜ P2:全部 production writer 迁移(依赖 R3 P2 atomic commit seam 已实现且 crash/retry pins 常绿) +- ⬜ P3:pickup/merge txn + 纯 migration(依赖 R5 P3 attrition API、R6 P4 receipt API) +- ⬜ P4:联合 bot/e2e + plan 收口(依赖 R4 handler 与 R3 P4 legacy consumer) 实现属 Wave 2;跨轨工作须登记 owning plan。 @@ -83,15 +83,15 @@ R10 函数纯且幂等,保留所有实例/动态字段,不执行 SQL、不 ## 6. 所有权与顺序 -- **R10**:`server/src/inventory/**` model/grid/txn/capacity、writer enumeration、typed outcome、纯 migration。P1 仅在 **R3 P1** 的 inventory/overflow seam 冻结后实现 txn/capacity 骨架;P2 production writers 只有在 **R3 P3** durable spill/pickup recoverable-commit seam 已合入后才可迁移并宣称完成;P3 pickup/attrition consumer 只有在 **R5 P3** incoming-only attrition/ledger API 与 **R6 P4** receipt wire/client API 已合入后才可接通。 -- **R3**:SQL/outbox、spill/pickup recoverable commit、hydration guard、migration consumer;R10 只消费 R3 P1/P3/P4 已冻结的接口。 +- **R10**:`server/src/inventory/**` model/grid/txn/capacity、writer enumeration、typed outcome、纯 migration。P1 仅在 **R3 P1** 的 inventory/overflow seam 冻结后实现 txn/capacity 骨架;P2 production writers 只有在 **R3 P2** 已实现 durable spill/pickup recoverable-commit seam 且 crash/retry pins 常绿后才可开始迁移;P3 pickup/attrition consumer 只有在 **R5 P3** incoming-only attrition/ledger API 与 **R6 P4** receipt wire/client API 已合入后才可接通。 +- **R3**:SQL/outbox、spill/pickup recoverable commit、hydration guard、migration consumer;R10 只消费 R3 P1/P2/P4 已冻结并实现的接口。 - **R4**:C2S gate/handler、authoritative pickup context、调用 R10 并转交 R6 outcome;R4 handler/consumer phase 必须等待 **R6 P4** receipt API 与 **R5 P3** attrition API,不得以 R10 mock 或仅 R6 P1 schema 代替。 - **R5**:incoming-only qi attrition/ledger;provider phase 为 R5 P3。 - **R6**:receipt wire/client、recipient projection/page、decoder;canonical plan 登记 rotate、pack feedback、dropped sync;receipt provider phase 为 R6 P4。 - **R1**:txn stored/spilled 成功后才 teardown,失败保留 session。 - **R7**:UI 消费,不拥有事务。 -顺序:**R3 P1 → R10 P1 → R3 P3 → R10 P2 production writers → R5 P3 + R6 P4 → R4 handler/pickup consumer → R10 P3 migration/consumer → R3 P4 legacy consumer → e2e**。R10 P2 在 R3 P3 durable seam 未合入前只能保留 skeleton,R4 pickup consumer 在 R5 P3/R6 P4 provider 未合入前不得宣称完成;R10 不越权改 persistence、wire、handler 或 client。 +顺序:**R3 P1 → R10 P1 → R3 P2 atomic seam 实现 + crash/retry pins → R10 P2 production writers → R5 P3 + R6 P4 → R10 P3 pickup/merge txn + pure migration → R4 handler/pickup consumer → R3 P4 legacy consumer → R10 P4 联合 e2e**。R10 P2 在 R3 P2 atomic seam 未合入前不得开始 writer 迁移,R10 P3 在 R5 P3/R6 P4 provider 未合入前不得开始 pickup consumer,R4/R3 的外轨交付物只作 R10 P4 验收前置、不计入 R10 自身 phase;R10 不越权改 persistence、wire、handler 或 client。 ## 7. 审核要求的 contract pins diff --git a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md index 03302a3aa..ef44a02da 100644 --- a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md +++ b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md @@ -22,7 +22,7 @@ - ⬜ P0 设计收口 + 吸收清单验真:53 张表普查归域;冻结 Slice trait(载入守护语义:读失败 = 保留旧行 + 告警 + 只读降级,绝不写回空态;flush registry;tick rebase 协议);等 #1288/#1289/#1261/#1259 merge 后定基线。 - ⬜ P1 框架落地 + 巨石拆分:`persistence/` 按域拆文件(迁移链不变、行为不变);Slice 框架上线,KnownTechniques/Lifecycle(在飞 PR 的成果)平移为首批宿主;冻结 inventory slice hydration seam 及 `MigrationOutcome` consumer 边界,R3 不复制 inventory 网格规则,待 R10 P3 提供纯幂等迁移函数后接入。R3 P1 只能冻结该 seam,不得引用尚未合入的 R10 常量或实现。 -- ⬜ P2 载入守护推广:全部玩家 slice(SkillSet/Wounds/状态 buff/身份键……)收编,#1290 模式全量落地;dropped-loot slice 的有界 hydration guard 依赖 R10 P1 已 merge 的容量契约:仅在该前置成立后引用 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 与 `DroppedLootRegistry::try_insert/try_insert_batch`;超限进入统一 load-failure guard/只读降级并告警,禁止 `take(limit)` 截断、驱逐旧条目或以空 registry 覆盖数据库。在 R10 P1 未 merge 时,R3 P2 不得编译或复制临时常量。同步冻结 spill/pickup persistence transaction/outbox seam:source mutation、attrited item、zone balance/qi ledger、drop insert/delete 与幂等 transaction id 必须构成一个 recoverable commit,供 R10 production writers 使用。 +- ⬜ P2 载入守护推广:全部玩家 slice(SkillSet/Wounds/状态 buff/身份键……)收编,#1290 模式全量落地;dropped-loot slice 的有界 hydration guard 依赖 R10 P1 已 merge 的容量契约:仅在该前置成立后引用 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 与 `DroppedLootRegistry::try_insert/try_insert_batch`;超限进入统一 load-failure guard/只读降级并告警,禁止 `take(limit)` 截断、驱逐旧条目或以空 registry 覆盖数据库。在 R10 P1 未 merge 时,R3 P2 不得编译或复制临时常量。同步冻结并实现 spill/pickup persistence transaction/outbox seam:source mutation、attrited item、zone balance/qi ledger、drop insert/delete 与幂等 transaction id 构成一个 recoverable commit,且 crash/retry pins 常绿后才允许 R10 P2 迁移 production writers。 - ⬜ P3 关服 flush + tick rebase 批次:shutdown flush registry 收编全部"节流落盘"域;绝对 tick 全部改相对基准;autosave/事件写入竞态互斥(coffin-autosave-inflight-race 模式)。 - ⬜ P4 遗漏运行态补持久化批次:ActiveEvents、TiandaoAttention、状态效果、化虚冷却、灵眼、地表遗缴、散灵珠、可放置实体、dormant 往返身份完整性(heiwushi)等——逐个按 Slice 框架补表;在 R10 P3 merge 后落 inventory migration consumer 子批次,调用 `inventory::migration::migrate_legacy_inventory_layout`,用玩家 identity、真实机制结算点/世界 position、dimension 组装 `SpillContext`,把 `MigrationOutcome::overflow` 通过 R10 capacity API 持久化到 durable registry;仅在新 schema 与全部 overflow 都成功持久化后提交新 schema,缺上下文/容量/持久化失败则保留旧行并进入可重试 load guard。该 consumer 明确依赖 R10 P1/P3 merge,不得提前实现或另造容量常量。 - ⬜ P5 bot 验收 + 吸收 plan 批量归档。 From fca923acfb72077aeb5663ecc993a10cc82885a1 Mon Sep 17 00:00:00 2001 From: server_kizuna Date: Mon, 3 Aug 2026 20:27:03 +0800 Subject: [PATCH 10/13] =?UTF-8?q?=E8=A1=A5=E9=BD=90=E5=BA=93=E5=AD=98?= =?UTF-8?q?=E9=87=8D=E6=9E=84=E8=BF=81=E7=A7=BB=E4=B8=8E=E5=8F=AF=E8=A7=81?= =?UTF-8?q?=E6=80=A7=E9=97=A8=E7=A6=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为旧掉落记录指定 R3 迁移消费者,校正 R6 provider 顺序并锁全可见性授权矩阵。 Model: cc-sonnet-high Co-Authored-By: Claude --- docs/plan-refactor-inventory-core-v1.md | 10 +++++----- .../plan-refactor-persistence-slices-v1.md | 4 ++-- docs/plans-skeleton/plan-refactor-wire-s2c-v1.md | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/plan-refactor-inventory-core-v1.md b/docs/plan-refactor-inventory-core-v1.md index 47f9c6a8a..9325ab31d 100644 --- a/docs/plan-refactor-inventory-core-v1.md +++ b/docs/plan-refactor-inventory-core-v1.md @@ -7,7 +7,7 @@ - ✅ 2026-08-03 P0 完整契约面重写 + absorption audit - ⬜ P1:inventory 拆分 + txn/capacity 骨架(依赖 R3 P1 seam) - ⬜ P2:全部 production writer 迁移(依赖 R3 P2 atomic commit seam 已实现且 crash/retry pins 常绿) -- ⬜ P3:pickup/merge txn + 纯 migration(依赖 R5 P3 attrition API、R6 P4 receipt API) +- ⬜ P3:pickup/merge txn + inventory-layout/dropped-loot 纯 migration(依赖 R5 P3 attrition API、R6 P4 receipt API) - ⬜ P4:联合 bot/e2e + plan 收口(依赖 R4 handler 与 R3 P4 legacy consumer) 实现属 Wave 2;跨轨工作须登记 owning plan。 @@ -79,7 +79,7 @@ migrate_legacy_inventory_layout(value, schema_version) -> MigrationOutcome { migrated_value, overflow: Vec } ``` -R10 函数纯且幂等,保留所有实例/动态字段,不执行 SQL、不猜 world context、不隐藏 overflow。R3 在临时副本上以真实 player/dimension/position 与 capacity/durable seam 消费;全成才写新行,失败保留旧行可重试且不重复 drop。 +R10 的 inventory-layout 与 dropped-loot migration 函数均纯且幂等,保留所有实例/动态字段,不执行 SQL、不猜 world context、不隐藏 overflow;其中 dropped-loot 迁移把旧 `entry_json` 缺失的 `owner`/`visibility` 明确补为 `owner = None`、`visibility = Public`。R3 hydration consumer 必须先按 persisted schema version 解码/迁移旧 dropped-loot JSON,再构造 `DroppedLootEntry`;inventory-layout migration 则在临时副本上以真实 player/dimension/position 与 capacity/durable seam 消费。两类 migration 都须全成才写新行,失败保留旧行可重试且不重复 drop。 ## 6. 所有权与顺序 @@ -91,7 +91,7 @@ R10 函数纯且幂等,保留所有实例/动态字段,不执行 SQL、不 - **R1**:txn stored/spilled 成功后才 teardown,失败保留 session。 - **R7**:UI 消费,不拥有事务。 -顺序:**R3 P1 → R10 P1 → R3 P2 atomic seam 实现 + crash/retry pins → R10 P2 production writers → R5 P3 + R6 P4 → R10 P3 pickup/merge txn + pure migration → R4 handler/pickup consumer → R3 P4 legacy consumer → R10 P4 联合 e2e**。R10 P2 在 R3 P2 atomic seam 未合入前不得开始 writer 迁移,R10 P3 在 R5 P3/R6 P4 provider 未合入前不得开始 pickup consumer,R4/R3 的外轨交付物只作 R10 P4 验收前置、不计入 R10 自身 phase;R10 不越权改 persistence、wire、handler 或 client。 +顺序:**R3 P1 → R10 P1 → R3 P2 atomic seam 实现 + crash/retry pins → R10 P2 production writers → R5 P3 + R6 P4 → R10 P3 pickup/merge txn + inventory-layout/dropped-loot pure migration → R4 handler/pickup consumer → R3 P4 legacy inventory-layout + dropped-loot hydration consumers → R10 P4 联合 e2e**。R10 P2 在 R3 P2 atomic seam 未合入前不得开始 writer 迁移,R10 P3 在 R5 P3/R6 P4 provider 未合入前不得开始 pickup consumer,R4/R3 的外轨交付物只作 R10 P4 验收前置、不计入 R10 自身 phase;R10 不越权改 persistence、wire、handler 或 client。 ## 7. 审核要求的 contract pins @@ -103,10 +103,10 @@ R10 函数纯且幂等,保留所有实例/动态字段,不执行 SQL、不 4. spill durable write failure、commit interruption、restart/retry:无单边状态、无重复 drop。 5. pickup 同维成功;跨维、超距/zone、owner/private 拒绝;merge、placement-only、failed attach/capacity/validation/persistence 后 entry 仍在;成功后才删。 6. incoming-only attrition receipt + R5 ledger:旧 stack absolute qi 不变;注入 attrition 后、durable commit 中断与 restart/retry,断言 attrited item + zone/ledger + drop delete 原子且总量守恒。 -7. 两 recipient 的 dimension/range/owner-private 正反 visibility;page/revision 按 projection;缺页/混 revision 不替换。 +7. visibility matrix:同维/范围内 `Public` 对非 owner 可见,`OwnerOnly` 对 owner 可见、对普通非 owner 不可见、对 server-authorized admin 可见;另测跨维/超距拒绝。page/revision 按每个 recipient projection;缺页/混 revision 不替换。 8. accepted/rejected move correlation;pack stow/equip/unequip 与拒绝必须动作级 receipt,stale event 和 snapshot-only baseline 不通过。 9. forge 深链保留;另锁 `/give hoe_iron → 新 snapshot → 真实非零 instance → held/equip → lingtian_start_till`,禁止 `instance_id=0` 或任意 server-data 冒充成功。 -10. migration pure happy/empty/full/dynamic/idempotent/invalid;R3 consumer 对真实 context 成功,缺 context/capacity/persistence failure 保留旧行可重试。 +10. inventory-layout migration pure happy/empty/full/dynamic/idempotent/invalid;dropped-loot migration 覆盖旧 `entry_json` 缺 owner/visibility → `None`/`Public`、已有字段原样保留、malformed/幂等;R3 consumer 对真实 context 成功,缺 context/capacity/persistence/migration failure 保留旧行可重试。 ## 8. Named bot acceptance(P4) diff --git a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md index ef44a02da..a59d80dea 100644 --- a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md +++ b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md @@ -24,7 +24,7 @@ - ⬜ P1 框架落地 + 巨石拆分:`persistence/` 按域拆文件(迁移链不变、行为不变);Slice 框架上线,KnownTechniques/Lifecycle(在飞 PR 的成果)平移为首批宿主;冻结 inventory slice hydration seam 及 `MigrationOutcome` consumer 边界,R3 不复制 inventory 网格规则,待 R10 P3 提供纯幂等迁移函数后接入。R3 P1 只能冻结该 seam,不得引用尚未合入的 R10 常量或实现。 - ⬜ P2 载入守护推广:全部玩家 slice(SkillSet/Wounds/状态 buff/身份键……)收编,#1290 模式全量落地;dropped-loot slice 的有界 hydration guard 依赖 R10 P1 已 merge 的容量契约:仅在该前置成立后引用 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 与 `DroppedLootRegistry::try_insert/try_insert_batch`;超限进入统一 load-failure guard/只读降级并告警,禁止 `take(limit)` 截断、驱逐旧条目或以空 registry 覆盖数据库。在 R10 P1 未 merge 时,R3 P2 不得编译或复制临时常量。同步冻结并实现 spill/pickup persistence transaction/outbox seam:source mutation、attrited item、zone balance/qi ledger、drop insert/delete 与幂等 transaction id 构成一个 recoverable commit,且 crash/retry pins 常绿后才允许 R10 P2 迁移 production writers。 - ⬜ P3 关服 flush + tick rebase 批次:shutdown flush registry 收编全部"节流落盘"域;绝对 tick 全部改相对基准;autosave/事件写入竞态互斥(coffin-autosave-inflight-race 模式)。 -- ⬜ P4 遗漏运行态补持久化批次:ActiveEvents、TiandaoAttention、状态效果、化虚冷却、灵眼、地表遗缴、散灵珠、可放置实体、dormant 往返身份完整性(heiwushi)等——逐个按 Slice 框架补表;在 R10 P3 merge 后落 inventory migration consumer 子批次,调用 `inventory::migration::migrate_legacy_inventory_layout`,用玩家 identity、真实机制结算点/世界 position、dimension 组装 `SpillContext`,把 `MigrationOutcome::overflow` 通过 R10 capacity API 持久化到 durable registry;仅在新 schema 与全部 overflow 都成功持久化后提交新 schema,缺上下文/容量/持久化失败则保留旧行并进入可重试 load guard。该 consumer 明确依赖 R10 P1/P3 merge,不得提前实现或另造容量常量。 +- ⬜ P4 遗漏运行态补持久化批次:ActiveEvents、TiandaoAttention、状态效果、化虚冷却、灵眼、地表遗缴、散灵珠、可放置实体、dormant 往返身份完整性(heiwushi)等——逐个按 Slice 框架补表;在 R10 P3 merge 后落 inventory migration consumer 子批次,调用 `inventory::migration::migrate_legacy_inventory_layout`,用玩家 identity、真实机制结算点/世界 position、dimension 组装 `SpillContext`,把 `MigrationOutcome::overflow` 通过 R10 capacity API 持久化到 durable registry;同阶段 dropped-loot hydration consumer 必须先调用 R10 的纯 migration,把旧 `dropped_loot.entry_json` 缺失字段补成 `owner = None`、`visibility = Public`,再反序列化为 `DroppedLootEntry`。两类 consumer 仅在新 schema 与全部 overflow 都成功持久化后提交新行,缺上下文/容量/持久化或 JSON migration 失败则保留旧行并进入可重试 load guard。该 consumer 子批次明确依赖 R10 P1/P3 merge,不得提前实现或另造容量常量。 - ⬜ P5 bot 验收 + 吸收 plan 批量归档。 ## 吸收清单(短名省略 plan-bughunt- 前缀与 -v1 后缀) @@ -42,7 +42,7 @@ skeleton:coffin-autosave-inflight-race、identity-persist-key-mismatch、miner 1. `restart_player_slices`:bot 建号→修炼/学功法/受伤→关服重启→重连→断言功法/伤势/濒死后果/buff 全部还原;另以 pre-#249 inventory fixture 验证 R10 纯迁移保留全部 instance/dynamic fields、重复载入幂等并保存新 schema。 2. `restart_world_runtime`:触发矿脉枯竭/配方解锁/zone influence→SIGTERM 关服→重启→断言无回滚无复活。 -3. `load_failure_guard`:基础损坏 slice 仍断言守护降级而非清零覆盖;超过 R10 P1 提供的 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 的 dropped-loot rows 只有在 R10 P1 已 merge 后执行同一 guard,数据库行数与内容不得被截断/清空;R10 前置未满足时该断言保持待接线,不得引用不存在的 symbol。另注入 spill durable write failure,以及 pickup attrition staged 后的 commit interruption/restart,断言 attrited item、zone/ledger 与 drop delete 无单边状态,按 transaction id 重试不重复应用。 +3. `load_failure_guard`:基础损坏 slice 仍断言守护降级而非清零覆盖;超过 R10 P1 提供的 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 的 dropped-loot rows 只有在 R10 P1 已 merge 后执行同一 guard,数据库行数与内容不得被截断/清空;R10 前置未满足时该断言保持待接线,不得引用不存在的 symbol。另覆盖旧 `entry_json` 无 owner/visibility → `owner = None` + `Public` 的 migration/hydration 正例,以及 malformed/migration failure 保留旧行可重试;再注入 spill durable write failure,以及 pickup attrition staged 后的 commit interruption/restart,断言 attrited item、zone/ledger 与 drop delete 无单边状态,按 transaction id 重试不重复应用。 4. `tick_rebase`:带冷却/再生倒计时重启→断言倒计时按真实流逝折算(对齐 #1289 的 deadline 折算先例)。 ## 开放问题(pre-P0 收口) diff --git a/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md b/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md index 5ed4bf87a..9a6aef51f 100644 --- a/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md +++ b/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md @@ -19,7 +19,7 @@ ## 阶段 - ⬜ P0 设计收口 + 吸收清单验真:28 旁路逐个普查(收编 vs 豁免理由);100 emit 文件的重复模式取样归纳 builder API;枚举前缀剥离点全量清点;冻结 scope 语义与 join 首包快照集清单;正式登记 `rotate-footprint-sync`、`bot-inventory-pack-feedback` 的 inventory wire/feedback 工作,并冻结 `dropped_loot_sync` 分片 envelope(`snapshot_revision/page_index/page_count`、每页上限常量 `DROPPED_LOOT_SYNC_PAGE_SIZE = 256`)。 -- ⬜ P1 emit builder + scope 落地:builder 上线,vfx/audio/env 三类先挂 scope(跨维 bleed 立灭);跨位面切换时 env/season 全量重发;dropped-loot 内容变化与 join sync 通过共享 builder 按固定页大小发送,同一 snapshot 只排序/编码一次后复用于目标 clients,禁止 per-client 重建无界全量 payload。 +- ⬜ P1 emit builder + scope 落地:builder 上线,vfx/audio/env 三类先挂 scope(跨维 bleed 立灭);跨位面切换时 env/season 全量重发;**仅在 R10 P2 的 `DroppedLootEntry.owner/visibility` metadata provider 合入后**,dropped-loot 内容变化与 join sync 才通过共享 builder 按固定页大小发送,同一 visibility key 的 snapshot 只排序/编码一次后复用于目标 clients,禁止 per-client 重建无界全量 payload。 - ⬜ P2 client 桥接层收敛:枚举前缀剥离收敛到单点(含 forge-session 修复);`ServerDataRouter` 注册表整备(分域注册文件,不再单个 1547 行 switch 追加);dropped-loot client store 仅在同 revision 全部分片收齐后原子替换,缺页/混 revision 保留旧视图并请求/等待重发。 - ⬜ P3 旁路归一批次:28 channel 逐批收编入 server_data envelope 或登记豁免(资源包/握手类可豁免);删除散装 receiver。 - ⬜ P4 契约 pin 全量化:双向 sample 对拍测试补齐(113 C2S + 144 S2C 每变体至少一条正反 sample,schema 改动连 sample 一起改);emit 迁移到 builder 的长尾批次;完成 inventory receipt contract 子批次:`InventoryEventV1::Moved`(或等价 accepted receipt)必须携带 request identity、结果 revision、权威 item view,覆盖 schema/sample/convert/emit API、Fabric `InventoryEventHandler` 与 Python decoder,供 R4 handler 消费 R10 typed outcome。分片 dropped-loot 正反样本必须覆盖空/单页/恰好 256/257/末页缺失/混 revision。 @@ -37,7 +37,7 @@ skeleton:vfx-audio-dimension-bleed、q-world-season-dimension-env-resync、for - 独占:server `network/*_emit.rs` 公共模式与新 `network/emit/`、`schema/proto_convert.rs`;client `network/`(ProtoServerDataBridge、ServerDataRouter、BongNetworkHandler 的 channel 注册区段)。 - 不碰:`BongNetworkHandler.clearClientStateOnDisconnect` 区段(R2 域,同文件分区段,merge 前互相 fetch);`client_request_handler.rs`(R4);各 emit 的业务语义。 -- 依赖:无硬前置;R2 先合(同文件低冲突区段);R4 P2 建议在本轨 P1 后开。 +- 依赖:无通用硬前置;R2 先合(同文件低冲突区段);R4 P2 建议在本轨 P1 后开。**P1 的 dropped-loot projection/page 子项例外:硬依赖 R10 P2 owner/visibility metadata provider 合入,不能随 P1 其他 scope 子项提前实施。** ## bot 验收场景 From 3fb07fad788584c1e01e8fef3838f4aed9812aa2 Mon Sep 17 00:00:00 2001 From: server_kizuna Date: Mon, 3 Aug 2026 21:34:26 +0800 Subject: [PATCH 11/13] =?UTF-8?q?=E6=94=B6=E6=95=9B=20R10=20P0=20=E6=8E=92?= =?UTF-8?q?=E7=A8=8B=E4=B8=8E=E5=BB=B6=E5=90=8E=E8=AE=BE=E8=AE=A1=E5=86=B3?= =?UTF-8?q?=E7=AD=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Model: cc-sonnet-high Co-Authored-By: Claude --- docs/plan-refactor-inventory-core-v1.md | 21 ++++++++++++------- .../plan-refactor-c2s-gate-v1.md | 2 +- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/docs/plan-refactor-inventory-core-v1.md b/docs/plan-refactor-inventory-core-v1.md index 9325ab31d..7bb5998fb 100644 --- a/docs/plan-refactor-inventory-core-v1.md +++ b/docs/plan-refactor-inventory-core-v1.md @@ -5,10 +5,10 @@ ## 阶段 - ✅ 2026-08-03 P0 完整契约面重写 + absorption audit -- ⬜ P1:inventory 拆分 + txn/capacity 骨架(依赖 R3 P1 seam) -- ⬜ P2:全部 production writer 迁移(依赖 R3 P2 atomic commit seam 已实现且 crash/retry pins 常绿) -- ⬜ P3:pickup/merge txn + inventory-layout/dropped-loot 纯 migration(依赖 R5 P3 attrition API、R6 P4 receipt API) -- ⬜ P4:联合 bot/e2e + plan 收口(依赖 R4 handler 与 R3 P4 legacy consumer) +- ⬜ P1:inventory 拆分 + txn/capacity 骨架 + inventory-layout/dropped-loot 纯 migration helpers(依赖 R3 P1 seam) +- ⬜ P2:全部 production writer 迁移(依赖 R3 P2 atomic commit seam 已实现、旧 dropped-loot 已可迁移且 crash/retry pins 常绿) +- ⬜ P3:pickup/merge txn(依赖 R5 P3 attrition API、R6 P4 receipt API) +- ⬜ P4:联合 bot/e2e + plan 收口(依赖 R4 handler 与 R3 P4 legacy inventory-layout consumer) 实现属 Wave 2;跨轨工作须登记 owning plan。 @@ -83,15 +83,15 @@ R10 的 inventory-layout 与 dropped-loot migration 函数均纯且幂等,保 ## 6. 所有权与顺序 -- **R10**:`server/src/inventory/**` model/grid/txn/capacity、writer enumeration、typed outcome、纯 migration。P1 仅在 **R3 P1** 的 inventory/overflow seam 冻结后实现 txn/capacity 骨架;P2 production writers 只有在 **R3 P2** 已实现 durable spill/pickup recoverable-commit seam 且 crash/retry pins 常绿后才可开始迁移;P3 pickup/attrition consumer 只有在 **R5 P3** incoming-only attrition/ledger API 与 **R6 P4** receipt wire/client API 已合入后才可接通。 +- **R10**:`server/src/inventory/**` model/grid/txn/capacity、writer enumeration、typed outcome、纯 migration。P1 仅在 **R3 P1** 的 inventory/overflow seam 冻结后实现 txn/capacity 骨架;P2 production writers 只有在 **R3 P2** 已实现 durable spill/pickup recoverable-commit seam、旧 dropped-loot migration compatibility 已就绪且 crash/retry pins 常绿后才可开始迁移;P3 pickup/attrition consumer 只有在 **R5 P3** incoming-only attrition/ledger API 与 **R6 P4** receipt wire/client API 已合入后才可接通。 - **R3**:SQL/outbox、spill/pickup recoverable commit、hydration guard、migration consumer;R10 只消费 R3 P1/P2/P4 已冻结并实现的接口。 -- **R4**:C2S gate/handler、authoritative pickup context、调用 R10 并转交 R6 outcome;R4 handler/consumer phase 必须等待 **R6 P4** receipt API 与 **R5 P3** attrition API,不得以 R10 mock 或仅 R6 P1 schema 代替。 +- **R4**:C2S gate/handler、authoritative pickup context、调用 R10 并转交 R6 outcome;R4 handler/consumer phase 必须等待 **R10 P3 pickup txn、R6 P4** receipt API 与 **R5 P3** attrition API,不得以 R10 mock 或仅 R6 P1 schema 代替。 - **R5**:incoming-only qi attrition/ledger;provider phase 为 R5 P3。 - **R6**:receipt wire/client、recipient projection/page、decoder;canonical plan 登记 rotate、pack feedback、dropped sync;receipt provider phase 为 R6 P4。 - **R1**:txn stored/spilled 成功后才 teardown,失败保留 session。 - **R7**:UI 消费,不拥有事务。 -顺序:**R3 P1 → R10 P1 → R3 P2 atomic seam 实现 + crash/retry pins → R10 P2 production writers → R5 P3 + R6 P4 → R10 P3 pickup/merge txn + inventory-layout/dropped-loot pure migration → R4 handler/pickup consumer → R3 P4 legacy inventory-layout + dropped-loot hydration consumers → R10 P4 联合 e2e**。R10 P2 在 R3 P2 atomic seam 未合入前不得开始 writer 迁移,R10 P3 在 R5 P3/R6 P4 provider 未合入前不得开始 pickup consumer,R4/R3 的外轨交付物只作 R10 P4 验收前置、不计入 R10 自身 phase;R10 不越权改 persistence、wire、handler 或 client。 +顺序:**R3 P1 → R10 P1(含纯 inventory-layout/dropped-loot migration helpers)→ R3 P2 atomic seam 实现 + legacy dropped-loot migration/hydration compatibility pins → R10 P2 production writers → R5 P3 + R6 P4 → R10 P3 pickup/merge txn → R4 handler/pickup consumer → R3 P4 legacy inventory-layout consumer → R10 P4 联合 e2e**。R10 P2 在 R3 P2 atomic seam 与旧 dropped-loot migration compatibility 未合入前不得开始 writer 迁移,R10 P3 在 R5 P3/R6 P4 provider 未合入前不得开始 pickup consumer,R4/R3 的外轨交付物只作 R10 P4 验收前置、不计入 R10 自身 phase;R10 不越权改 persistence、wire、handler 或 client。 ## 7. 审核要求的 contract pins @@ -139,4 +139,11 @@ R10 的 inventory-layout 与 dropped-loot migration 函数均纯且幂等,保 P0 不决定 Rust lifetime、SQL/outbox 实现、锁粒度、distance/zone 数值、visibility-key 编码、client 重发、管理员运维或 UI;由 owning PR 设计并受上述 contract/pins 约束。P0 不迁移 writer、不删旧入口、不改 runtime。未跑真实 server→wire→client/bot 链前,不以 forge、snapshot 或文档声明归档 bug skeleton。 +本轮新增 deferred decisions(不扩 P0 实现范围): + +1. **Pickup freshness / anti-replay**:`PickupAuthorization` 的 revision/anti-replay fact 如何生成、绑定和失效,留待 R10 P3 pickup txn 设计时决定;理由是必须与真实 durable transaction/idempotency 语义共同冻结,避免 P0 先拍一个不可验证的 token 形状;交叉引用 §6 顺序与总纲 §3 Wave 2。 +2. **Receipt correlation / C2S request ID**:accepted/rejected inventory receipt 的 request identity 是否新增到 `inventory_move_intent` 并贯通 Rust/proto/TypeBox/Java,留待 R6 P4 receipt wire/client 设计时决定;理由是响应相关性必须与双向 schema 变更和 breaking gate 一起落地;交叉引用 §6 R6 ownership 与 `docs/plans-skeleton/plan-refactor-wire-s2c-v1.md` P4。 +3. **Recipient-context revocation**:移动、换维度、管理员权限变化是否触发 dropped-loot projection 重发/撤销,留待 R6 P1/P2 projection/store 设计时决定;理由是这是 recipient lifecycle 与 client stale-snapshot 清理的联合契约,P0 不预先指定触发矩阵;交叉引用 §3 与 `docs/plans-skeleton/plan-refactor-wire-s2c-v1.md` P1-P2。 +4. **Snapshot multiplicativity bound**:recipient-specific full snapshot 的 coalescing、增量/空间索引或 aggregate rate bound,留待 R6 P1 emit builder 设计时决定;理由是分页和 key reuse 是否足以控制 aggregate cost 需要结合真实 recipient cardinality 与 wire budget 评估;交叉引用 §3 与 `docs/plans-skeleton/plan-refactor-wire-s2c-v1.md` P1。 + P0 完成只表示上述 surface、owner、失败边界、pins 与逐项 absorption audit 已冻结,不表示后续实现完成。 diff --git a/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md b/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md index ca2bfd788..bb02a4f60 100644 --- a/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md +++ b/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md @@ -20,7 +20,7 @@ - ⬜ P0 设计收口 + 吸收清单验真:113 个变体普查(每个标注应有的门禁四元组现状);冻结 `GateSpec` 与拒绝回执语义;等 #1287(冷却重构,同文件大改)merge 定基线。 - ⬜ P1 门禁中间件落地:gate 层上线,先给"已知漏洞簇"的 ~20 个请求类型挂 spec(吸收清单全命中),旧内联校验保留并行断言一个版本期。 -- ⬜ P2 巨石拆分批次 A:巨型 match 拆为按域 handler 注册表(combat/production/world/social/npc 五组),行为不变,bot 场景锁住;inventory pickup handler 必须从 ECS `CurrentDimension`、authoritative position/observation range 与 owner/private permission 构造 R10 `PickupAuthorization`,禁止仅凭 client XYZ/instance id 调用 txn,且把 R10 accepted/rejected outcome 交给 R6 emit API。 +- ⬜ P2 巨石拆分批次 A:巨型 match 拆为按域 handler 注册表(combat/production/world/social/npc 五组),行为不变,bot 场景锁住;inventory pickup handler 必须从 ECS `CurrentDimension`、authoritative position/observation range 与 owner/private permission 构造 R10 `PickupAuthorization`,禁止仅凭 client XYZ/instance id 调用 txn,且把 R10 accepted/rejected outcome 交给 R6 emit API。**本批次的 inventory pickup consumer 仅在 R10 P3 pickup/merge txn、R5 P3 attrition API 与 R6 P4 receipt API 均已合入后实施;此前不得以 mock 或旧 R6 P1 schema 接线。** - ⬜ P3 巨石拆分批次 B + 全量挂 spec + 删旧:113 变体全部声明门禁(含显式 `no_gate` 声明,杜绝静默无门禁);删除各域内联距离常量与重复维度判断。 - ⬜ P4 bot 验收 + 吸收 plan 批量归档。 From 377a3d76af8967fdeba475d455cc29d85113dd6a Mon Sep 17 00:00:00 2001 From: server_kizuna Date: Tue, 4 Aug 2026 02:52:40 +0800 Subject: [PATCH 12/13] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20R10=20=E6=8E=89?= =?UTF-8?q?=E8=90=BD=E8=BF=81=E7=A7=BB=E6=8E=92=E7=A8=8B=E4=B8=8E=20API=20?= =?UTF-8?q?=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 明确 R3 legacy dropped-loot migration 先于 R6 projection 消费,并补齐 R10 P1 纯迁移 helper 的签名与 owner phase。 Model: cc-sonnet-high Co-Authored-By: Claude --- docs/plan-refactor-inventory-core-v1.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/plan-refactor-inventory-core-v1.md b/docs/plan-refactor-inventory-core-v1.md index 7bb5998fb..92f40a47a 100644 --- a/docs/plan-refactor-inventory-core-v1.md +++ b/docs/plan-refactor-inventory-core-v1.md @@ -77,7 +77,11 @@ R4 从 ECS 构造不可由 client 覆盖的 `PickupAuthorization`:player UUID ```text migrate_legacy_inventory_layout(value, schema_version) -> MigrationOutcome { migrated_value, overflow: Vec } -``` + +migrate_legacy_dropped_loot_entry(value, schema_version) + -> Result + // owning phase: R10 P1 pure migration helper; consumed by R3 P4 hydration + R10 的 inventory-layout 与 dropped-loot migration 函数均纯且幂等,保留所有实例/动态字段,不执行 SQL、不猜 world context、不隐藏 overflow;其中 dropped-loot 迁移把旧 `entry_json` 缺失的 `owner`/`visibility` 明确补为 `owner = None`、`visibility = Public`。R3 hydration consumer 必须先按 persisted schema version 解码/迁移旧 dropped-loot JSON,再构造 `DroppedLootEntry`;inventory-layout migration 则在临时副本上以真实 player/dimension/position 与 capacity/durable seam 消费。两类 migration 都须全成才写新行,失败保留旧行可重试且不重复 drop。 @@ -87,11 +91,11 @@ R10 的 inventory-layout 与 dropped-loot migration 函数均纯且幂等,保 - **R3**:SQL/outbox、spill/pickup recoverable commit、hydration guard、migration consumer;R10 只消费 R3 P1/P2/P4 已冻结并实现的接口。 - **R4**:C2S gate/handler、authoritative pickup context、调用 R10 并转交 R6 outcome;R4 handler/consumer phase 必须等待 **R10 P3 pickup txn、R6 P4** receipt API 与 **R5 P3** attrition API,不得以 R10 mock 或仅 R6 P1 schema 代替。 - **R5**:incoming-only qi attrition/ledger;provider phase 为 R5 P3。 -- **R6**:receipt wire/client、recipient projection/page、decoder;canonical plan 登记 rotate、pack feedback、dropped sync;receipt provider phase 为 R6 P4。 +- **R6**:receipt wire/client、recipient projection/page、decoder;canonical plan 登记 rotate、pack feedback、dropped sync;dropped-loot projection/page consumer 为 R6 P1,必须在 R3 legacy dropped-loot migration/hydration consumer 完成后才可消费;receipt provider phase 为 R6 P4。 - **R1**:txn stored/spilled 成功后才 teardown,失败保留 session。 - **R7**:UI 消费,不拥有事务。 -顺序:**R3 P1 → R10 P1(含纯 inventory-layout/dropped-loot migration helpers)→ R3 P2 atomic seam 实现 + legacy dropped-loot migration/hydration compatibility pins → R10 P2 production writers → R5 P3 + R6 P4 → R10 P3 pickup/merge txn → R4 handler/pickup consumer → R3 P4 legacy inventory-layout consumer → R10 P4 联合 e2e**。R10 P2 在 R3 P2 atomic seam 与旧 dropped-loot migration compatibility 未合入前不得开始 writer 迁移,R10 P3 在 R5 P3/R6 P4 provider 未合入前不得开始 pickup consumer,R4/R3 的外轨交付物只作 R10 P4 验收前置、不计入 R10 自身 phase;R10 不越权改 persistence、wire、handler 或 client。 +顺序:**R3 P1 → R10 P1(含纯 inventory-layout/dropped-loot migration helpers)→ R3 P2 atomic seam 实现 + legacy dropped-loot migration/hydration compatibility pins → R10 P2 production writers → R3 P4 legacy dropped-loot migration/hydration consumer → R6 P1 dropped-loot projection/page consumer → R5 P3 + R6 P4 → R10 P3 pickup/merge txn → R4 handler/pickup consumer → R3 P4 legacy inventory-layout consumer → R10 P4 联合 e2e**。R10 P2 在 R3 P2 atomic seam 与旧 dropped-loot migration compatibility 未合入前不得开始 writer 迁移;R3 P4 legacy dropped-loot migration/hydration consumer 必须先于 R6 P1 dropped-loot projection/page consumer,确保旧 `entry_json` 已先升级为带 `owner`/`visibility` 的 canonical entry;R10 P3 在 R5 P3/R6 P4 provider 未合入前不得开始 pickup consumer,R4/R3 的外轨交付物只作 R10 P4 验收前置、不计入 R10 自身 phase;R10 不越权改 persistence、wire、handler 或 client。 ## 7. 审核要求的 contract pins From 5a6feda459f28cf3cedf622db9914b543bab8eb4 Mon Sep 17 00:00:00 2001 From: server_kizuna Date: Tue, 4 Aug 2026 12:51:43 +0800 Subject: [PATCH 13/13] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20R10=20=E6=8E=89?= =?UTF-8?q?=E8=90=BD=E6=B6=88=E8=B4=B9=E9=97=A8=E7=A6=81=E4=B8=8E=E9=98=B6?= =?UTF-8?q?=E6=AE=B5=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 拆分 R3 dropped-loot hydration 与 inventory-layout overflow consumer,校正 R10 P1 migration helper、R6 projection 和 OwnerOnly writer 的顺序,并修复迁移示例 fence。 Model: cc-haiku-max Co-Authored-By: Claude --- docs/plan-refactor-inventory-core-v1.md | 12 ++++++------ .../plan-refactor-persistence-slices-v1.md | 12 ++++++++---- docs/plans-skeleton/plan-refactor-wire-s2c-v1.md | 4 ++-- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/docs/plan-refactor-inventory-core-v1.md b/docs/plan-refactor-inventory-core-v1.md index 92f40a47a..d261a58d8 100644 --- a/docs/plan-refactor-inventory-core-v1.md +++ b/docs/plan-refactor-inventory-core-v1.md @@ -6,7 +6,7 @@ - ✅ 2026-08-03 P0 完整契约面重写 + absorption audit - ⬜ P1:inventory 拆分 + txn/capacity 骨架 + inventory-layout/dropped-loot 纯 migration helpers(依赖 R3 P1 seam) -- ⬜ P2:全部 production writer 迁移(依赖 R3 P2 atomic commit seam 已实现、旧 dropped-loot 已可迁移且 crash/retry pins 常绿) +- ⬜ P2:production writer 迁移分为 P2a metadata/provider + Public writer path 与 P2b OwnerOnly private-writer activation;P2a 依赖 R3 P2 atomic commit seam 与旧 dropped-loot compatibility,P2b 必须等 R3 P4 dropped-loot migration/hydration、R6 P1 recipient projection/page、R5 P3 + R6 P4、R10 P3 pickup txn 及 R4 pickup consumer 全部完成后才可启用。 - ⬜ P3:pickup/merge txn(依赖 R5 P3 attrition API、R6 P4 receipt API) - ⬜ P4:联合 bot/e2e + plan 收口(依赖 R4 handler 与 R3 P4 legacy inventory-layout consumer) @@ -81,21 +81,21 @@ migrate_legacy_inventory_layout(value, schema_version) migrate_legacy_dropped_loot_entry(value, schema_version) -> Result // owning phase: R10 P1 pure migration helper; consumed by R3 P4 hydration - +``` R10 的 inventory-layout 与 dropped-loot migration 函数均纯且幂等,保留所有实例/动态字段,不执行 SQL、不猜 world context、不隐藏 overflow;其中 dropped-loot 迁移把旧 `entry_json` 缺失的 `owner`/`visibility` 明确补为 `owner = None`、`visibility = Public`。R3 hydration consumer 必须先按 persisted schema version 解码/迁移旧 dropped-loot JSON,再构造 `DroppedLootEntry`;inventory-layout migration 则在临时副本上以真实 player/dimension/position 与 capacity/durable seam 消费。两类 migration 都须全成才写新行,失败保留旧行可重试且不重复 drop。 ## 6. 所有权与顺序 -- **R10**:`server/src/inventory/**` model/grid/txn/capacity、writer enumeration、typed outcome、纯 migration。P1 仅在 **R3 P1** 的 inventory/overflow seam 冻结后实现 txn/capacity 骨架;P2 production writers 只有在 **R3 P2** 已实现 durable spill/pickup recoverable-commit seam、旧 dropped-loot migration compatibility 已就绪且 crash/retry pins 常绿后才可开始迁移;P3 pickup/attrition consumer 只有在 **R5 P3** incoming-only attrition/ledger API 与 **R6 P4** receipt wire/client API 已合入后才可接通。 -- **R3**:SQL/outbox、spill/pickup recoverable commit、hydration guard、migration consumer;R10 只消费 R3 P1/P2/P4 已冻结并实现的接口。 +- **R10**:`server/src/inventory/**` model/grid/txn/capacity、writer enumeration、typed outcome、纯 migration。P1 仅在 **R3 P1** 的 inventory/overflow seam 冻结后实现 txn/capacity 骨架;P2a 负责 metadata/provider 与 `Public` writer path,依赖 **R3 P2** durable spill/pickup recoverable-commit seam、旧 dropped-loot migration compatibility 与 crash/retry pins;P2b 才能启用 `OwnerOnly` private writers,且必须等 **R3 P4 dropped-loot migration/hydration、R6 P1 recipient projection/page、R5 P3 + R6 P4、R10 P3 pickup txn、R4 pickup consumer** 全部完成;P3 pickup/attrition consumer 只有在 **R5 P3** incoming-only attrition/ledger API 与 **R6 P4** receipt wire/client API 已合入后才可接通。 +- **R3**:SQL/outbox、spill/pickup recoverable commit、hydration guard、migration consumer;R10 只消费 R3 P1/P2/P4 已冻结并实现的接口。P4 必须拆成 dropped-loot hydration 子批次与 inventory-layout overflow 子批次,前者不等待 R10 P3,后者才等待其实际 durable/capacity 前置。 - **R4**:C2S gate/handler、authoritative pickup context、调用 R10 并转交 R6 outcome;R4 handler/consumer phase 必须等待 **R10 P3 pickup txn、R6 P4** receipt API 与 **R5 P3** attrition API,不得以 R10 mock 或仅 R6 P1 schema 代替。 - **R5**:incoming-only qi attrition/ledger;provider phase 为 R5 P3。 -- **R6**:receipt wire/client、recipient projection/page、decoder;canonical plan 登记 rotate、pack feedback、dropped sync;dropped-loot projection/page consumer 为 R6 P1,必须在 R3 legacy dropped-loot migration/hydration consumer 完成后才可消费;receipt provider phase 为 R6 P4。 +- **R6**:receipt wire/client、recipient projection/page、decoder;canonical plan 登记 rotate、pack feedback、dropped sync;dropped-loot projection/page consumer 为 R6 P1,必须在 **R10 P2a owner/visibility metadata provider** 与 **R3 P4 dropped-loot migration/hydration consumer** 完成后才可消费;receipt provider phase 为 R6 P4。 - **R1**:txn stored/spilled 成功后才 teardown,失败保留 session。 - **R7**:UI 消费,不拥有事务。 -顺序:**R3 P1 → R10 P1(含纯 inventory-layout/dropped-loot migration helpers)→ R3 P2 atomic seam 实现 + legacy dropped-loot migration/hydration compatibility pins → R10 P2 production writers → R3 P4 legacy dropped-loot migration/hydration consumer → R6 P1 dropped-loot projection/page consumer → R5 P3 + R6 P4 → R10 P3 pickup/merge txn → R4 handler/pickup consumer → R3 P4 legacy inventory-layout consumer → R10 P4 联合 e2e**。R10 P2 在 R3 P2 atomic seam 与旧 dropped-loot migration compatibility 未合入前不得开始 writer 迁移;R3 P4 legacy dropped-loot migration/hydration consumer 必须先于 R6 P1 dropped-loot projection/page consumer,确保旧 `entry_json` 已先升级为带 `owner`/`visibility` 的 canonical entry;R10 P3 在 R5 P3/R6 P4 provider 未合入前不得开始 pickup consumer,R4/R3 的外轨交付物只作 R10 P4 验收前置、不计入 R10 自身 phase;R10 不越权改 persistence、wire、handler 或 client。 +顺序:**R3 P1 → R10 P1(含纯 inventory-layout/dropped-loot migration helpers)→ R3 P2 atomic seam 实现 + legacy dropped-loot migration/hydration compatibility pins → R10 P2a metadata/provider + Public writer path → R3 P4 dropped-loot migration/hydration consumer → R6 P1 dropped-loot projection/page consumer → R5 P3 + R6 P4 → R10 P3 pickup/merge txn → R4 handler/pickup consumer → R10 P2b OwnerOnly private-writer activation → R3 P4 inventory-layout overflow consumer → R10 P4 联合 e2e**。R10 P2a 在 R3 P2 atomic seam 与旧 dropped-loot migration compatibility 未合入前不得开始;R3 P4 dropped-loot migration/hydration consumer 必须先于 R6 P1 dropped-loot projection/page consumer,确保旧 `entry_json` 已先升级为带 `owner`/`visibility` 的 canonical entry;R10 P2b 只有在 R3 P4 dropped-loot hydration、R6 P1 projection/page、R5 P3 + R6 P4、R10 P3 pickup txn 与 R4 pickup consumer 全部完成后才可启用,避免 OwnerOnly writer 在授权消费链闭合前广播或转移私有掉落;R10 P3 在 R5 P3/R6 P4 provider 未合入前不得开始 pickup consumer;R4/R3 的外轨交付物只作 R10 P4 验收前置、不计入 R10 自身 phase;R10 不越权改 persistence、wire、handler 或 client。 ## 7. 审核要求的 contract pins diff --git a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md index a59d80dea..c582a0f86 100644 --- a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md +++ b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md @@ -21,10 +21,13 @@ ## 阶段 - ⬜ P0 设计收口 + 吸收清单验真:53 张表普查归域;冻结 Slice trait(载入守护语义:读失败 = 保留旧行 + 告警 + 只读降级,绝不写回空态;flush registry;tick rebase 协议);等 #1288/#1289/#1261/#1259 merge 后定基线。 -- ⬜ P1 框架落地 + 巨石拆分:`persistence/` 按域拆文件(迁移链不变、行为不变);Slice 框架上线,KnownTechniques/Lifecycle(在飞 PR 的成果)平移为首批宿主;冻结 inventory slice hydration seam 及 `MigrationOutcome` consumer 边界,R3 不复制 inventory 网格规则,待 R10 P3 提供纯幂等迁移函数后接入。R3 P1 只能冻结该 seam,不得引用尚未合入的 R10 常量或实现。 -- ⬜ P2 载入守护推广:全部玩家 slice(SkillSet/Wounds/状态 buff/身份键……)收编,#1290 模式全量落地;dropped-loot slice 的有界 hydration guard 依赖 R10 P1 已 merge 的容量契约:仅在该前置成立后引用 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 与 `DroppedLootRegistry::try_insert/try_insert_batch`;超限进入统一 load-failure guard/只读降级并告警,禁止 `take(limit)` 截断、驱逐旧条目或以空 registry 覆盖数据库。在 R10 P1 未 merge 时,R3 P2 不得编译或复制临时常量。同步冻结并实现 spill/pickup persistence transaction/outbox seam:source mutation、attrited item、zone balance/qi ledger、drop insert/delete 与幂等 transaction id 构成一个 recoverable commit,且 crash/retry pins 常绿后才允许 R10 P2 迁移 production writers。 +- ⬜ P1 框架落地 + 巨石拆分:`persistence/` 按域拆文件(迁移链不变、行为不变);Slice 框架上线,KnownTechniques/Lifecycle(在飞 PR 的成果)平移为首批宿主;冻结 inventory slice hydration seam 及 `MigrationOutcome` consumer 边界,R3 不复制 inventory 网格规则,待 R10 P1 提供纯幂等迁移函数后接入。R3 P1 只能冻结该 seam,不得引用尚未合入的 R10 常量或实现。 +- ⬜ P2 载入守护推广:全部玩家 slice(SkillSet/Wounds/状态 buff/身份键……)收编,#1290 模式全量落地;dropped-loot slice 的有界 hydration guard 依赖 R10 P1 已 merge 的容量契约:仅在该前置成立后引用 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 与 `DroppedLootRegistry::try_insert/try_insert_batch`;超限进入统一 load-failure guard/只读降级并告警,禁止 `take(limit)` 截断、驱逐旧条目或以空 registry 覆盖数据库。在 R10 P1 未 merge 时,R3 P2 不得编译或复制临时常量。同步冻结并实现 spill/pickup persistence transaction/outbox seam:source mutation、attrited item、zone balance/qi ledger、drop insert/delete 与幂等 transaction id 构成一个 recoverable commit,且 crash/retry pins 常绿后才允许 R10 P2a 迁移 Public writer path;R10 P2b OwnerOnly private writers 另受 R10 P3、R4、R6 与 R3 P4 consumer gates 约束。 - ⬜ P3 关服 flush + tick rebase 批次:shutdown flush registry 收编全部"节流落盘"域;绝对 tick 全部改相对基准;autosave/事件写入竞态互斥(coffin-autosave-inflight-race 模式)。 -- ⬜ P4 遗漏运行态补持久化批次:ActiveEvents、TiandaoAttention、状态效果、化虚冷却、灵眼、地表遗缴、散灵珠、可放置实体、dormant 往返身份完整性(heiwushi)等——逐个按 Slice 框架补表;在 R10 P3 merge 后落 inventory migration consumer 子批次,调用 `inventory::migration::migrate_legacy_inventory_layout`,用玩家 identity、真实机制结算点/世界 position、dimension 组装 `SpillContext`,把 `MigrationOutcome::overflow` 通过 R10 capacity API 持久化到 durable registry;同阶段 dropped-loot hydration consumer 必须先调用 R10 的纯 migration,把旧 `dropped_loot.entry_json` 缺失字段补成 `owner = None`、`visibility = Public`,再反序列化为 `DroppedLootEntry`。两类 consumer 仅在新 schema 与全部 overflow 都成功持久化后提交新行,缺上下文/容量/持久化或 JSON migration 失败则保留旧行并进入可重试 load guard。该 consumer 子批次明确依赖 R10 P1/P3 merge,不得提前实现或另造容量常量。 +- ⬜ P4 遗漏运行态补持久化批次:ActiveEvents、TiandaoAttention、状态效果、化虚冷却、灵眼、地表遗缴、散灵珠、可放置实体、dormant 往返身份完整性(heiwushi)等——逐个按 Slice 框架补表;P4 拆为两个独立 consumer 子批次: + - **dropped-loot hydration 子批次**:在 R10 P1 merge、R3 P2 persistence seam 与旧行 compatibility pins 就绪后,调用 `inventory::migration::migrate_legacy_dropped_loot_entry`,把旧 `dropped_loot.entry_json` 缺失字段补成 `owner = None`、`visibility = Public`,再反序列化为 `DroppedLootEntry`;此子批次先于 R6 P1 projection/page,且不等待 R10 P3/R5/R6 P4/R4。 + - **inventory-layout overflow 子批次**:仅在 R10 P3 merge 后,调用 `inventory::migration::migrate_legacy_inventory_layout`,用玩家 identity、真实机制结算点/世界 position、dimension 组装 `SpillContext`,把 `MigrationOutcome::overflow` 通过 R10 capacity API 持久化到 durable registry。 + 两类 consumer 仅在新 schema 与各自全部输出成功持久化后提交新行,缺上下文/容量/持久化或 JSON migration 失败则保留旧行并进入可重试 load guard;dropped-loot 子批次依赖 R10 P1,inventory-layout 子批次依赖 R10 P3,不得合并为一个跨越两者的门禁或另造容量常量。 - ⬜ P5 bot 验收 + 吸收 plan 批量归档。 ## 吸收清单(短名省略 plan-bughunt- 前缀与 -v1 后缀) @@ -53,7 +56,8 @@ skeleton:coffin-autosave-inflight-race、identity-persist-key-mismatch、miner ## § P0 决议锚点(待 R3 P0 开工时补齐) - `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 的引用门:R3 P2/P4 依赖 R10 P1 merge,R3 不复制常量或在此前编译引用。 -- inventory migration consumer:R3 P4 在 R10 P3 merge 后,使用真实 `SpillContext` 完成 overflow 持久化;失败保留旧行并可重试。 +- dropped-loot hydration consumer:R3 P4 在 R10 P1 migration helper、R3 P2 persistence seam 与旧行 compatibility pins 就绪后执行,且必须先于 R6 P1 projection/page;失败保留旧行并可重试。 +- inventory-layout migration consumer:R3 P4 的独立 overflow 子批次在 R10 P3 merge 后,使用真实 `SpillContext` 完成 overflow 持久化;失败保留旧行并可重试。 ## 验收测试声明 diff --git a/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md b/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md index 9a6aef51f..02af8d5f7 100644 --- a/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md +++ b/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md @@ -19,7 +19,7 @@ ## 阶段 - ⬜ P0 设计收口 + 吸收清单验真:28 旁路逐个普查(收编 vs 豁免理由);100 emit 文件的重复模式取样归纳 builder API;枚举前缀剥离点全量清点;冻结 scope 语义与 join 首包快照集清单;正式登记 `rotate-footprint-sync`、`bot-inventory-pack-feedback` 的 inventory wire/feedback 工作,并冻结 `dropped_loot_sync` 分片 envelope(`snapshot_revision/page_index/page_count`、每页上限常量 `DROPPED_LOOT_SYNC_PAGE_SIZE = 256`)。 -- ⬜ P1 emit builder + scope 落地:builder 上线,vfx/audio/env 三类先挂 scope(跨维 bleed 立灭);跨位面切换时 env/season 全量重发;**仅在 R10 P2 的 `DroppedLootEntry.owner/visibility` metadata provider 合入后**,dropped-loot 内容变化与 join sync 才通过共享 builder 按固定页大小发送,同一 visibility key 的 snapshot 只排序/编码一次后复用于目标 clients,禁止 per-client 重建无界全量 payload。 +- ⬜ P1 emit builder + scope 落地:builder 上线,vfx/audio/env 三类先挂 scope(跨维 bleed 立灭);跨位面切换时 env/season 全量重发;**仅在 R10 P2a 的 `DroppedLootEntry.owner/visibility` metadata provider 与 R3 P4 dropped-loot migration/hydration consumer 均合入后**,dropped-loot 内容变化与 join sync 才通过共享 builder 按固定页大小发送,同一 visibility key 的 snapshot 只排序/编码一次后复用于目标 clients,禁止 per-client 重建无界全量 payload;在上述两个前置完成前不得启用该 private projection path。 - ⬜ P2 client 桥接层收敛:枚举前缀剥离收敛到单点(含 forge-session 修复);`ServerDataRouter` 注册表整备(分域注册文件,不再单个 1547 行 switch 追加);dropped-loot client store 仅在同 revision 全部分片收齐后原子替换,缺页/混 revision 保留旧视图并请求/等待重发。 - ⬜ P3 旁路归一批次:28 channel 逐批收编入 server_data envelope 或登记豁免(资源包/握手类可豁免);删除散装 receiver。 - ⬜ P4 契约 pin 全量化:双向 sample 对拍测试补齐(113 C2S + 144 S2C 每变体至少一条正反 sample,schema 改动连 sample 一起改);emit 迁移到 builder 的长尾批次;完成 inventory receipt contract 子批次:`InventoryEventV1::Moved`(或等价 accepted receipt)必须携带 request identity、结果 revision、权威 item view,覆盖 schema/sample/convert/emit API、Fabric `InventoryEventHandler` 与 Python decoder,供 R4 handler 消费 R10 typed outcome。分片 dropped-loot 正反样本必须覆盖空/单页/恰好 256/257/末页缺失/混 revision。 @@ -37,7 +37,7 @@ skeleton:vfx-audio-dimension-bleed、q-world-season-dimension-env-resync、for - 独占:server `network/*_emit.rs` 公共模式与新 `network/emit/`、`schema/proto_convert.rs`;client `network/`(ProtoServerDataBridge、ServerDataRouter、BongNetworkHandler 的 channel 注册区段)。 - 不碰:`BongNetworkHandler.clearClientStateOnDisconnect` 区段(R2 域,同文件分区段,merge 前互相 fetch);`client_request_handler.rs`(R4);各 emit 的业务语义。 -- 依赖:无通用硬前置;R2 先合(同文件低冲突区段);R4 P2 建议在本轨 P1 后开。**P1 的 dropped-loot projection/page 子项例外:硬依赖 R10 P2 owner/visibility metadata provider 合入,不能随 P1 其他 scope 子项提前实施。** +- 依赖:无通用硬前置;R2 先合(同文件低冲突区段);R4 P2 建议在本轨 P1 后开。**P1 的 dropped-loot projection/page 子项例外:硬依赖 R10 P2a owner/visibility metadata provider 与 R3 P4 dropped-loot migration/hydration consumer,不能随其他 scope 子项提前实施。** ## bot 验收场景