diff --git a/docs_dev/plans/2026-07-31-shelf-c-attribute-shop.md b/docs_dev/plans/2026-07-31-shelf-c-attribute-shop.md index 1395025..123624c 100644 --- a/docs_dev/plans/2026-07-31-shelf-c-attribute-shop.md +++ b/docs_dev/plans/2026-07-31-shelf-c-attribute-shop.md @@ -869,7 +869,7 @@ Expected: `FAILS=0`,`帧距=3`(`wand_basic`),`原因=已达上限`。 > ⚠️ 脚本里的 `0.5` 是 `wand_basic` 的基准间隔。**若开局装备的不是 `wand_basic`,此常数与期望帧距都要改** —— 用 `_equipped_core.cast_interval` 读实际值更稳。断言的实质是「帧距 > 1(未饱和)」,不是「恰好等于 3」。实测确认开局装备确为 `wand_basic`(`combat_manager.gd:54` `_equipped_core = WandPreset.make_core_by_id("wand_basic")`),故 `0.5` 常数有效。 -**实际输出**:`FAILS=0 买了22次 mod=0.0985 原因=已达上限 帧距=3(应>1,证明未饱和)`。**校准**:到 `soft` 门槛实测需 **22 次购买**(`cast_delay_mod` 每次 `pct` 复利 `step=0.10`,`combine=inverse`,`merged=1-(1-0.10)^n`,`base×(1-merged)`≤`soft`(0.1) 在 `n=22` 时首次成立)——简报未写具体次数,此处补全为确定值,供日后回归对照。 +**实际输出**:`FAILS=0 买了22次 mod=0.0985 原因=已达上限 帧距=3(应>1,证明未饱和)`。**校准**:到 `soft` 门槛实测需 **22 次购买**(`cast_delay_mod` 每次 `pct` 复利 `step=0.10`,`combine=inverse`,`merged=1-(1-0.10)^n`,`base×(1-merged)`≤`soft`(0.1) 在 `n=22` 时首次成立)——`task-6-brief.md` 未写具体次数;「22」这一数字的出处是 `docs_dev/specs/2026-07-31-shelf-c-attribute-shop-design.md:133` 的设计表,本步运行时实测与之吻合(`1×0.9²²≈0.0985≤0.1` 首次在 `n=22` 成立),此处补全为确定值,供日后回归对照。 - [x] **Step 4: `cpu_limit` 的 `add_int` 硬约束(验收 11)** @@ -944,6 +944,8 @@ ShopManager.reset() ``` **实际输出**:`DISK_ROUNDTRIP FAILS=0 cleared_hp_max=100.00 cleared_cpu=5 loaded_keys=["attr_purchases", "player_stats", "saved_at", "schema_version", "shop_seed", "wand", "wave_num"] attr_purchases={ "cpu_limit": 2.0, "hp_max": 3.0 } hp=130.0000(应130.0000) hp_max=133.1000(应133.1000) cpu=7(应7)`。`attr_purchases` 经 JSON 序列化后数值型变为 `float`(`2.0`/`3.0`),`get_attr_purchases_save()` 内部按 `int(d[k])` 转换,往返后与购买前逐位相等。 > **踩坑记录**:第一轮跑此脚本时得到 `FAILS=1`(`cpu=7` 应 `2`),排查后发现是脚本本身的问题——上一段测试脚本的清理代码里手误调了 `PlayerStats.reset_for_run()`(会连带清掉法杖的 `"core"` 加成来源且不会自动重建),污染了本段测试开始前的基线,与被测代码无关。教训:`reset_for_run()` 只应由 `combat_manager` 在真正开局/重开时调用,且必须伴随法杖重新装备(`_rebuild_wand()` 会重建 `"core"` 加成);手工验收脚本里绝不能单独调它做"清空状态",否则会污染同会话后续脚本的基线。 +> +> **附带作用(评审发现)**:本步直接调用真实 `ProfileManager.apply_run()`(而非手工内联复现顺序),故它同时是**该函数顺序回归的哨兵**——对调 `profile_manager.gd:68-69` 的两行顺序会使本步 `FAILS=1`(评审实测 `hp=100.0000` 应 `130.0000`,脚本本身未改一字)。Step 6 验证的是机制本身(见下),本步(Step 5b)才是覆盖 `apply_run()` 实现的回归哨兵。 - [x] **Step 6: 回读顺序不吞血(Task 3 的核心风险)** @@ -976,7 +978,7 @@ _mcp_print("CORRECT ORDER FAILS=%d final hp=%.2f hp_max=%.2f(应 130/133.1)" - 错误顺序:`WRONG after load_save_data only: hp=130.00 hp_max=110.00` → `WRONG ORDER final: hp=100.00 hp_max=133.10`(吞 30 血,复现 Task 3 报告的原始发现)。 - 正确顺序:`CORRECT ORDER FAILS=0 final hp=130.00 hp_max=133.10`(未被钳,与 `hp_before`=130 完全一致)。 -这独立复核了 Task 3 的核心风险与其修复,且证明了断言本身非空——删掉 `apply_run` 里的顺序修复会让这条断言真正变红。 +本步独立复核 Task 3 的核心风险机制(`_modifiers` 非空时 `remove_modifiers_from` 才真正触发 `_recompute_attrs`,从而在「先 load 后 apply」下产生错误的中间钳位)。**注意本步是手工内联两种顺序、独立于 `apply_run()` 实现**(脚本全程未调用 `ProfileManager.apply_run()`),故它验证的是机制本身而非 `apply_run()`——对调 `apply_run()` 内部两行顺序**不会**影响本步的红/绿结果(评审已实测确认);`apply_run()` 的顺序回归哨兵是 **Step 5b**(见下)。 - [x] **Step 7: `shop` 段缺失即不上架(验收 16)**