From f513463d27e5c85228d57860757e71687d7ce13a Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Tue, 20 Oct 2026 06:46:12 +0800 Subject: [PATCH] =?UTF-8?q?docs(=E6=95=88=E6=9E=9C=E5=BC=95=E6=93=8E?= =?UTF-8?q?=E6=97=A0=E7=8A=B6=E6=80=81=E5=8C=96=C2=B7=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=E9=97=AD=E5=90=88):=20=E6=8F=90=E6=A1=88=20v1.2=20=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E6=94=B9=E3=80=8C=E5=8F=AF=E5=86=99=E5=8D=A1=E3=80=8D?= =?UTF-8?q?=E2=80=94=E2=80=94=C2=A74.3=20=E5=85=AD=E5=8D=A1=E7=82=B9/?= =?UTF-8?q?=C2=A74.4=20=E5=85=AB=E7=BC=BA=E5=8F=A3/=C2=A74.5=20=E5=85=AD?= =?UTF-8?q?=E6=BE=84=E6=B8=85=E5=85=A8=E9=97=AD=E5=90=88=EF=BC=8C=E5=AE=9A?= =?UTF-8?q?=E5=80=BC=E9=9B=86=E4=B8=AD=20=C2=A77.10=EF=BC=88ctx=3DEffectCo?= =?UTF-8?q?ntext=20=E5=BC=95=E7=94=A8=E6=9D=9F=E6=AF=8F=E6=88=98=E4=B8=80?= =?UTF-8?q?=E6=AC=A1=EF=BC=8F=E5=86=99=E5=8F=A3=E6=94=B9=E9=9D=99=E6=80=81?= =?UTF-8?q?=E7=BA=AF=E5=87=BD=E6=95=B0=20bucket=5Fof=20=E5=8D=95=E7=82=B9?= =?UTF-8?q?=EF=BC=8F=E5=89=8D=E7=BC=80=E6=A0=A1=E9=AA=8C=E5=8F=8C=E8=90=BD?= =?UTF-8?q?=E7=82=B9=EF=BC=8BE2=20=E5=B9=B6=E5=85=A5=20F1=20=E5=B8=A6?= =?UTF-8?q?=E5=BC=95=E5=8F=B7=E6=97=A7=20id=20=E9=9B=B6=E5=91=BD=E4=B8=AD?= =?UTF-8?q?=EF=BC=8FU2=20=E9=9A=8F=E5=AF=BC=E5=87=BA=E6=B6=88=E5=A4=B1?= =?UTF-8?q?=E8=80=8C=E6=B6=88=E8=A7=A3=EF=BC=8Fround=5Fstart=20=E5=AE=9A?= =?UTF-8?q?=E4=BD=8D=20:97=EF=BC=8FG1=20=E5=88=86=E7=BB=84=E5=88=A4?= =?UTF-8?q?=E8=AF=BB=20pain=5Flayers=20=E9=9B=B6=E4=BD=8D=E7=A7=BB?= =?UTF-8?q?=EF=BC=8FB6=20dig=5Fcombo=20=E6=AD=BB=E8=B7=AF=E5=88=A0?= =?UTF-8?q?=E9=99=A4=EF=BC=89=EF=BC=9B06=20=E8=AE=A1=E6=95=B0=E5=99=A8?= =?UTF-8?q?=E6=9D=A1=E7=9B=AE=E6=94=B9=E5=86=99=E6=8E=A8=E8=BF=9B=E5=BE=85?= =?UTF-8?q?=E5=86=99=E5=8D=A1=EF=BC=8B=E6=96=B0=E5=A2=9E=E6=97=A0=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E5=8E=9F=E5=88=99=E7=BC=BA=E6=9D=A1=E7=9B=AE=E7=99=BB?= =?UTF-8?q?=E8=AE=B0=EF=BC=88--docs=20=E9=97=B8=E5=85=A8=E7=BB=BF=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/gdd/06-待办清单.md | 16 +- .../提案-效果引擎无状态化-2026-10-19.md | 246 +++++++++++++----- 2 files changed, 188 insertions(+), 74 deletions(-) diff --git a/design/gdd/06-待办清单.md b/design/gdd/06-待办清单.md index de5f2bc8..bbf26225 100644 --- a/design/gdd/06-待办清单.md +++ b/design/gdd/06-待办清单.md @@ -632,11 +632,11 @@ #### 计数器寿命域未统一(2026-10-18 登记,来源=会话提及,设计面=文档轨) -**现状**:效果的「寿命/作用域」现由三套互不同属的机制拼成,无统一枚举——①`domain` 键仅作用于计数器,只有 `battle`(缺省,每战清零)/`adventure`(跨战持久)两值;②`reset_at: round_start` 表达回合级重置(`feats.tres` 用 4 处);③「按关重置」是**硬编码名单**(`adventure_runner.gd:873-877` 按关清零 `tree_*_used`),不在任何词表内;④**效果自身(非计数器)的寿命没有任何字段**——`effect_engine.gd:32` 的「封印桩本轮/石肤本场」是拿 cap 参数凑的注释,机器不可读。另有名实不符:`domain=adventure` 实际承担「按关重置」语义(代码现状即证:`effect_engine.gd` 词表无寿命字段)。`effect_engine.gd:119-121` 自称「生命周期四态」(跨战持久/按关重置/新冒险清零/断点保留),却未按枚举落地。**后果**:任何带寿命权重的核算(期望收益/强度摊销/「每 X 一次」折算)无法在数据层进行,须逐条读注释或读清除侧代码。**用户已裁:本批不动寿命域**,完美保险按现役 `domain=battle` 落地,此项仅登记、不入本批改动面。 +**现状**:2026-10-19 用户裁定推翻「寿命域统一口径」方向(原方案五档/四档枚举、`domain` 键扩词表整体作废——作废因由=该方案隐含前提「引擎持有状态、需要字段说明它活多久」被推翻,见提案 §8),改走**效果引擎无状态化重构**:引擎零运行时状态,计数器按 round/battle/level/adventure 四桶外置、由各生命周期宿主对象持有(寿命由宿主生灭决定,不再需要寿命字段),归属用 counter_id 前缀表达(`round_`/`lvl_`/`adv_`/无前缀=battle,`meta_` 非法)+加载期强校验。设计定值与实现设计缺口已全部闭合(提案=design/research/提案-效果引擎无状态化-2026-10-19.md,§7 定值+§7.10 闭合)。本条目原指认的三套机制并存问题由该重构一并消解:①`domain`/`reset_at` 两字段随重构删除;②「按关重置」硬编码名单(`adventure_runner.gd:838-841`)随 level 桶宿主化消失;③`run_ledger.gd:42` 空容器早退自锁缺陷随搬运链删除自动消解(代价=行为变更:adventure 域计数器变真跨战持久、天赋树锋/守变真每关限 2 次——削弱方向已定性,提案 §7.7/§7.8)。副产定值:`pain_layers` 名实不符归 battle、`dig_combo` 死读数随重构顺带删除(提案 §7.10.14)。 **归属**:architecture.md 与 game/core/effect_engine.gd(待立独立批) -**状态**:待裁决(是否统一寿命域口径) +**状态**:待裁决(是否统一寿命域口径——2026-10-19 已裁:统一口径方案作废,改走无状态化重构)+文档设计已闭合(design/research/提案-效果引擎无状态化-2026-10-19.md v1.2——2026-10-20 §7.10 闭合)+待写卡 --- @@ -681,3 +681,15 @@ **归属**:①`奇物.md` §1.1/§2.2/v2.2 行(已订正写入文档);②`奇物.md` §1.3(:121/:223/:229)与 `02-系统清单.md:49`(待裁) **状态**:已写入(design/gdd/systems/冒险循环/奇物.md v3.19——第①项专长凭证归类订正)+待裁决(第②项资源判据句,心智模型未对齐,不列候选不提前处置) + +--- + +#### 效果引擎「系统无状态」原则缺架构文档条目(2026-10-20 登记,来源=会话提及,设计面=文档轨) + +**现状**:用户 2026-10-19 会话裁定的 ECS 架构依据(系统无状态——效果引擎这类系统零运行时可变状态、数据由宿主对象持有并随宿主生灭、状态按参数传入系统、系统只做判定)是「效果引擎无状态化」重构批的立项判据(上游条目=本清单「计数器寿命域未统一」,提案=design/research/提案-效果引擎无状态化-2026-10-19.md §7.9 #1),但 `design/engineering/architecture/architecture.md`「架构原则」节(现行 7 条)无「系统无状态」条目——立项判据暂无权威文档出处(2026-10-19 全仓检索「无状态/纯计算」零命中,提案 §10.3 实查;原 `design/registry/` 两 yaml 已随 2026-10-19 用户裁注销删除,无可迁凭据)。重构批是代码批、禁写 `design/`,文档级补条只能另立文档批。 + +**缺口**:一次文档设计讨论——「系统无状态」原则的条文表述(与现行 7 条架构原则的交叉与序位:核心层纯逻辑/fail-fast 两原则与之相邻但不等同)+写入 architecture.md 的批次编排。 + +**归属**:design/engineering/architecture/architecture.md(架构原则节) + +**状态**:待文档设计 diff --git a/design/research/提案-效果引擎无状态化-2026-10-19.md b/design/research/提案-效果引擎无状态化-2026-10-19.md index ecc8ff84..63508a4d 100644 --- a/design/research/提案-效果引擎无状态化-2026-10-19.md +++ b/design/research/提案-效果引擎无状态化-2026-10-19.md @@ -1,9 +1,10 @@ # 提案:效果引擎无状态化(计数器外置·分桶) - **日期**:2026-10-19 +- **版本**:v1.2(2026-10-20 设计闭合版——现行身份标记;改动沿革只查 git commit log) - **性质**:架构重构提案(restructure,非 refactor)——现役设计被判为错误形态,不是打磨 - **上游**:`design/gdd/06-待办清单.md:627`「计数器寿命域未统一」条目(2026-10-18 登记);本提案推翻该条目的解法方向,见 §8 -- **状态**:**草案·设计未闭合**——§7 设计已定值、§7.7 采集定不发起,但 §4.2–§4.5 核验出 3 项阻塞级实现设计缺口(A1 ctx 结构/A2 桶接口/A3 树合成行漏校验)+ 5 项次级缺口 + 6 项需求澄清未对齐,**尚不可写卡** +- **状态**:**设计已闭合·可写卡**——§7 设计定值、§7.7 采集定不发起;§4.3 六项卡点、§4.4 八项实现设计缺口、§4.5 六项需求澄清已于 2026-10-20 复核轮全部闭合(定值与推导依据集中在 §7.10,§3.2/§4.1–§4.5/§5 各节已按定值同步改写);其中 B6(dig_combo 死读数删除)与 §7.9 五条同口径属「约束可推、可否决」项 - **范围**:代码 + 文档,**不含美术** - **关联已定项**:迭代 31 卡①代码术语刷新批 / 卡②凭证件裁削批(完美保险按现役 `domain=battle` 落地)——本提案若先落地,卡②的专长行需同步改形 @@ -77,29 +78,31 @@ ### 3.1 四只桶(宿主决定寿命,不需要寿命字段) -| 桶 | 宿主(候选,见 N1) | 清空方式 | 现役对应 | +| 桶 | 宿主(定值,见 N1) | 清空方式 | 现役对应 | |---|---|---|---| -| `round` | 战斗域对象 | 轮始 `= {}` | `_reset_on_round_start` 名单(4 枚:`round_blast_spent` / `instinct_atk` / `round_mines` / `round_digs`) | +| `round` | `BattleOrchestrator` 公开成员(ctx 构造时挂引用) | 轮始整桶清空(引擎 dispatch 的 round_start 特判泛化——**就地清空**,禁 `= {}` 重赋值断引用) | `_reset_on_round_start` 名单(4 枚:`round_blast_spent` / `instinct_atk` / `round_mines` / `round_digs`) | | `battle` | `BattleOrchestrator`(每战 new) | 随宿主销毁 | `rebuild` 的 `_counters = {}`(8 枚:`geo_types` / `helm_layers` / `skip_pts` / `echo_seals` / `smith_seals` / `fuse_pts` / `stop_swap` / `insurance_used`) | -| `level` | 关卡域对象 | 进关 `= {}` | `adventure_runner.gd:840-842` 硬编码名单 | +| `level` | `LevelRunner.level_counters` 公开成员 | 进关随宿主 new 天然空 | `adventure_runner.gd:840-842` 硬编码名单 | | `adventure` | `run_ledger.adventure_counters`(沿用) | 新冒险置空(`adventure_runner.gd:255`) | `domain=adventure`(4 枚:`twin_digs` / `pain_layers` / `tree_sense_sharp_used` / `tree_sense_guard_used`) | 🔴 **枚数订正**:现役 distinct counter_id **共 16 枚**(4+8+4),非草案初版的「13 枚」。13 系卡② 落地前的旧数(未含 `insurance_used`),且与草案自身分桶子项相加(4+8+4=16)自相矛盾。 **寿命语义不再需要任何字段**:对象活着值就活着,对象重建值自然归零。 -### 3.2 引擎接口改动 +### 3.2 引擎接口改动(2026-10-20 定稿——写口形态见 §7.10.8) | 现在 | 之后 | |---|---| -| `dispatch(event, payload, facts)` | 增 ctx 参数(携带本战可读写计数器容器引用集) | -| `counter()` | 改从 ctx 读(纯查询,不存) | -| `counter_add()` / `counter_spend()` | **删除**——写由发射点落到传入计数器容器 | +| `dispatch(event, payload, facts)` | `dispatch(ctx, event, payload, facts)`——ctx=`EffectContext` 四桶引用束(§7.10.1),每战构造一次 | +| `counter()` | `counter(ctx, counter_id)`(纯查询读 ctx,引擎不自持——N5) | +| `counter_add()` / `counter_spend()` | **实例方法删除**;改**静态纯函数**(ctx 首参)——7 个施加点调用形不变、语义单点(§7.10.8) | | `counters_export()` / `counters_seed()` | **删除** | | `has_effect_on()` / `validate_trigger_row()` | 保留(纯函数) | -| — | 新增纯函数 `bucket_of(counter_id)`(按前缀判桶) | +| — | 新增 `EffectContext` 类(`game/core/effect_context.gd`,四桶引用束字段)+ `static bucket_of(ctx, counter_id)`(前缀→目标桶字典,解析单点) | -写口迁移(**共 6 处,非 5 处**):`action_economy.gd:110/113/173`、`damage_controller.gd:351/464`、**`grant_segment.gd:327`** 由 `_fx.counter_add(...)` 改为对 ctx 计数器容器写入;`battle_orchestrator.gd:717` 的 `counter_spend` 同理。 +round_start 清零点保留在引擎 `dispatch` 特判内(`:210-212` 现役挂点),由按名单清零泛化为 `ctx.round_bucket.clear()`——前缀分桶后名单集合≡整桶键集,行为逐位等价(挂点不挪的依据见 §7.10.1)。 + +写口迁移(**共 7 处**:action_economy.gd `:108/:111/:169`、damage_controller.gd `:351/:464`、grant_segment.gd `:327`(`_apply_counter_hit`——卡② 保险写入唯一通路)、battle_orchestrator.gd `:714`(counter_spend,受理闸))由 `_fx.counter_add(...)` 改为静态 `EffectEngine.counter_add(ctx, ...)`;调用点行号系 2026-10-20 实查(草案初版 :110/:113/:173/:717 系 +2/+4 行漂移,A6 补列面见 §7.10.6)。 🔴 `grant_segment.gd:327` 是**卡② 完美保险的写入计数唯一通路**(`:315` `_apply_counter_hit(h)` 落 `insurance_used`),漏掉它=保险「每战限次一次」失效(恒 0 → 每次标错都豁免)。 @@ -124,14 +127,14 @@ | T | 内容 | 估时 | |---|---|---| | T0 | worktree/分支;冒烟·单元·探针·三闸基线记档 | 0.25d | -| T1 | 桶承载对象新建 + 四宿主接线 | 0.5–1d | -| T2 | 引擎删状态 + `dispatch` 增 ctx + `bucket_of` | 0.5–1d | -| T3 | 施加点改写(action_economy / damage_controller / orchestrator) | 0.5–1d | -| T4 | 三处清零点就位(轮始 / 进关 / 战斗 new) | 0.25–0.5d | -| T5 | 前缀校验进 `validate_trigger_row` | 0.25d | -| T6 | 数据行与树合成行 counter_id 调整 | 0.25d | -| T7 | **机制正向验收断言新增** + 旧测试迁移 + 变异验证 + 同种子复放对拍(详见 §4.1) | 1–1.5d | -| T8 | 文档回写(architecture / 06 / README / entities.yaml) | 0.25d | +| T1 | `EffectContext` 新建 + 四宿主接线(编排器两桶成员/ctx 挂载、LevelRunner 桶成员+enc 下传、set_fx 三处扩参) | 0.5–1d | +| T2 | 引擎删状态 + `dispatch`/`counter` 增 ctx + 写口改静态纯函数 + `bucket_of` | 0.5–1d | +| T3 | 7 个施加点改写(action_economy ×3 / damage_controller ×2 / grant_segment ×1 / orchestrator spend ×1)+dig_combo 死路删除 | 0.5–1d | +| T4 | 轮始清零泛化(引擎 round_start 特判整桶清空;进关/战斗 new 两处免费——§7.10.1) | 0.25d | +| T5 | 前缀校验双落点(`validate_trigger_row` + `_push_trigger`) | 0.25d | +| T6 | 数据行与树合成行 counter_id 调整(改名 4 处+`domain`/`reset_at` 键删除) | 0.25d | +| T7 | **机制正向验收断言新增** + 旧测试迁移 + 变异验证 + 同种子复放比对(详见 §4.1/§7.10.12) | 1–1.5d | +| T8 | 文档回写(architecture 反哺点归 06 条目 / 06 状态推进 / README / 卡片关联同步面) | 0.25d | ### 4.1 验收测试清单(机制正向验收 —— 主力) @@ -139,91 +142,96 @@ > > 对拍只能证「除已知差异外没改坏别的」(负向/兜底),**不能证新机制按设计工作**。机制改动的正向验收必须由下列断言承担。依据:`test-conventions.md` §1「存在必须证明可达且正确」/§3「缺陷修复必须沉淀回归断言」/§5 落码规范/§6 变异验证。 +> 🔴 **编号辨析**:本表验收点编号(A1–H1)与 §4.4 缺口编号(A1–A8)是两套命名空间,互不指涉——本表编号沿用 2026-10-19 首版验收清单,§4.4 编号沿用缺口盘点,改表不改号(卡内引用以「§4.1 V-」前缀消歧,写卡时逐条带编号尾缀)。 +> +> 🔴 **2026-10-20 定稿**:本表按 §7.10 闭合定值改写——A1 注入口径(§7.10.10)、B2 构造路径(§7.10.13)、D1 载体(§7.10.5)、E2 撤销并入 F1(§7.10.3)、G1 分组判读(§7.10.12)、新增 H1 旧档加载(§7.10.15)。 + | # | 验收点 | 执行载体 | 断言词汇 | 怎么测(注入粒度) | |---|---|---|---|---| -| **A1** | **adventure 桶跨战持久**(=`run_ledger.gd:42` 缺陷消解的**回归断言**,修前恒 0) | 冒烟 `smoke_all.gd` 冒险段 | 状态/计数器断言 | 新冒险开局 → 第 1 战末置 `adv_twin_digs=1` → 进入第 2 战首读该键 ==1 | -| **A2** | adventure 桶新冒险置空 | 冒烟 | 状态/计数器断言 | `start_adventure` 后桶 size==0 | +| **A1** | **adventure 桶跨战持久**(=`run_ledger.gd:42` 缺陷消解的**回归断言**,修前恒 0) | 冒烟 `smoke_all.gd` 冒险段 | 状态/计数器断言 | 两编排器共享同一 `RunLedger`(V25 同构):第 1 战 `orch1.action_economy.request_dig(0, false)` 真实挖一次(无返还,`adv_twin_digs`=1)→ 第 2 战(同 ledger 起 orch2)再挖一次(mod_eq 预评估读持久值判「第 2 次」→ 返还成立+桶值 2)——读/写两路径均走真实事件面(§7.10.10) | +| **A2** | adventure 桶新冒险置空 | 单测 `feat_vouchers_test.gd`(start_adventure 先例 `:25`) | 状态/计数器断言 | `start_adventure` 后 `run_ledger.adventure_counters` 为空 | | **A3** | adventure 桶随断点往返 | 单测 `feat_vouchers_test.gd`(承 V16,键名随前缀改) | 快照比对 | 写断点 → 回读 → 桶内容逐位相等 | -| **B1** | level 桶**同关跨战保持、进关清零** | 冒烟 | 状态/计数器断言 | 同关第 2 战读 `lvl_tree_sense_*` 保持;进下一关后归零 | -| **B2** | **tree_sense 每关限 2 次**(削弱行为的正面确认) | 冒烟 | 状态/计数器断言 | 同关第 1 战用满 2 次 → 第 2 战 `lt 2` 不命中 → 无减伤/无返还(修前第 2 战仍有额度) | -| **C1** | battle 桶随宿主销毁 | 冒烟 | 状态/计数器断言 | 第 1 战置 `insurance_used=1`;第 2 战读 ==0 | +| **B1** | level 桶**同关跨战保持、进关清零** | 冒烟 | 状态/计数器断言 | 两编排器经 enc["level_counters"] 共享同一字典(LevelRunner 真实下传形态):第 2 战读 `lvl_tree_sense_*` 保持;新 LevelRunner(进下一关形态)新桶天然为空(§7.10.11) | +| **B2** | **tree_sense 每关限 2 次**(削弱行为的正面确认) | 冒烟 | 状态/计数器断言 | B1 同款共享桶两编排器:第 1 战两次 `_fx.dispatch(ctx, "mine_hit", {})` 命中(damage_mult+counter_add 双行,桶值=2)→ 第 2 战同 dispatch 返回空(lt 2 闸关)且桶值不动——when 只读 counter fact、零 payload 依赖(§7.10.13) | +| **C1** | battle 桶随宿主销毁 | 冒烟 | 状态/计数器断言 | 第 1 战置 `insurance_used=1`;第 2 战(新编排器)读 ==0 | | **C2** | 完美保险「每战限次一次」仍成立(卡② V25 迁移保绿) | 单测 `feat_vouchers_test.gd:132+` | 状态/计数器断言 | 沿用 V25 注入口径,改走新桶 | -| **D1** | round 桶轮始清空 | 单测/冒烟 | 状态/计数器断言 | 轮始后 `round_*` 全 0 | -| **E1** | counter_id 无前缀或带 `meta_` 前缀 → **加载致命** | 单测(新增) | fail-fast | 合成一行喂 `validate_trigger_row` → 命中止/assert | -| **E2** | 同 counter_id 跨前缀重复 → 致命 | 单测(新增) | fail-fast | 同上 | -| **F1** | 旧件零残留 | 静态扫描面 | 快照比对 | 全仓 `counters_export` / `counters_seed` / `_reset_tree_limited_counters` 零命中 | -| **F2** | **旧断言同批摘除**(test-conventions §5.5 换代同生死) | — | — | `effect_words_t1_test.gd:86-94`、`smoke_all.gd:6083-6090`、`:6131-6134` 随旧件同批删除——旧件断言残留=假绿来源 | -| **G1** | 同种子复放对拍(**兜底,非主力**) | `adventure_monte_carlo` 同种子重放 | 重放 | 除已记档的已知差异外逐位一致;零局新采集(配套纪律③) | +| **D1** | round 桶轮始清空 | 冒烟 | 状态/计数器断言 | round 桶直写非零 → `orch._emit_round_start()`(真实发射函数 `:97`,§7.10.5)→ 全零 | +| **E1** | counter_id 带非法前缀(`meta_` 等)→ **加载致命**;树合成行同规则 | 单测(新增负面套件,manifest 登记)+`_push_trigger` assert 面 | fail-fast | 夹具表注入坏前缀行喂 `load_tables` → 拒绝(reward_dangling_treasure_test 同构 expect 1);合成路径坏前缀走 rebuild assert | +| **F1** | 旧件零残留(含旧 id 字面) | `static_scan.sh` 新增 S 段 | 快照比对 | 全仓 `counters_export` / `counters_seed` / `counters_merge_export` / `_reset_tree_limited_counters` 零命中 + **带引号旧 id 字面** `"tree_sense_sharp_used"` / `"tree_sense_guard_used"` / `"twin_digs"` / `"instinct_atk"` 零命中(带引号匹配不误伤 `"lvl_tree_sense_sharp_used"` 等新名——E2 撤销后的改名完整性防线,§7.10.3) | +| **F2** | **旧断言同批摘除**(test-conventions §5.5 换代同生死) | — | — | `effect_words_t1_test.gd:77-94`(export/seed 往返块)、`:96-124`(reset_at/counter_spend 块,随字段删除重写为桶语义)、`smoke_all.gd:6076-6083`(counter_add+export/seed 链)、`:6124-6128`(直呼 `_reset_tree_limited_counters`)、`:1419-1602` 裸引擎段 17 处 dispatch+8 处 counter_add 直调(改 ctx 形态)——旧件断言残留=假绿来源 | +| **G1** | 同种子复放比对(**兜底,非主力**;分组判读制) | `adventure_monte_carlo` 同种子重放 | 重放 | 锚内 normal 档前 20 种子(81000–81019,配套纪律③子集 ≤20):未受影响局全字段逐位一致;受影响局(保守超集=装备集含双管镐/sense 两组且 `mine_hits>0` 或 `seal_miss>0`)输入面字段恒等+`levels_cleared` 分布不得上行(§7.10.12) | +| **H1** | 旧档断点加载不崩(B4 补条) | 单测 `feat_vouchers_test.gd`(V16 旧档同构先例 `:124-127`) | 快照比对 | 旧断点 `adventure_counters` 含旧键(`tree_sense_sharp_used`=2 等)→ `resume_adventure` 受理零崩溃,旧键留桶成死键(无迁移——2026-10-18 先例) | **变异验证必做项**(test-conventions §6——无变异红记录只写「已实现,拦截能力未证」): | 断言 | 变异注入点 | 期望 | |---|---|---| -| A1 | 把 adventure 桶写入改回「判空早退」(复刻 `run_ledger.gd:42`) | A1 必须红 | -| B2 | 摘掉进关清零点(T4 产物) | B2 必须红 | +| A1 | begin_battle 挂载 ctx 时把 adventure 桶引用换成新建空字典(复刻「副本不同步/判空早退」缺陷类——§7.8 守卫原形的同构形态) | A1 必须红 | +| B2 | 两编排器改传各自新建 level 字典(复刻 `:840-842` 旧形态「跨关不清理、每战重置」) | B2 必须红 | | E1 | 注释掉 `validate_trigger_row` 内的前缀校验分支 | E1 必须红 | +| F1 | 任一源文件临时写回一处带引号旧 id 字面(如 `"counters_export"` 调用形) | F1 扫描必须红 | > 三步闭环:注入 → 只跑覆盖该断言的最窄一道闸 → `git checkout -- <文件>` 精确还原并复跑回绿 → 逐条进实现报告变异验证记录表。 **真人目检登记**:tree_sense 锋/守由「每战限 2 次」变「每关限 2 次」是玩家可感知削弱 ⇒ 按「只登记不阻塞」登记进 `07-体验验证清单`,不构成收口前置。 -### 4.2 载体可达性核验(2026-10-19 实查,逐条对 §4.1) +### 4.2 载体可达性核验(2026-10-20 复核轮全绿——逐条对 §4.1) -> test-conventions §5:「写卡期查不出执行载体的条目=不可测,当场呈报,禁拖到实现期」。下表是逐条实查结果。 +> test-conventions §5:「写卡期查不出执行载体的条目=不可测,当场呈报,禁拖到实现期」。下表 2026-10-19 首查,2026-10-20 复核轮六项未闭合项全部闭合(定值见 §7.10)。 | 验收点 | 载体 | 核验结论 | 证据(file:line) | |---|---|---|---| | **C2** 保险限次一次 | 单测 `feat_vouchers_test.gd` | ✅ **已闭合**——V25 现成「第 1 战用满 → 第 2 战重新豁免」双编排器骨架;`orch2.begin_battle({"battle_index":2,…})` + `orch2._grant_seg._judge_seal(...)` 即真实 `seal_missed` | `:143-186` | -| **E1/E2** 前缀致命 | 新增负面套件 | ✅ **已闭合**——现役 fail-fast 测试模式:`config_db_fatal_oob_test.gd`(独立进程,`expect:1` + `negative:true`),manifest 协议在役 | `tests/run_all.gd:37`/`:48` | -| **F1** 旧件零残留 | `static_scan.sh` 新增 S 段 | 🟡 **半闭合**——S1–S10 分检制在位;本批新增 S 段承载,按纪律「闸件增减只改该脚本+`run_all_gates.sh` 登记」,卡里须写明改哪两个文件 | `static_scan.sh:38-82` | -| **A1/A2/A3** adventure 桶 | 冒烟/单测新段 | 🟡 **半闭合**——RunLedger 经 `enc["ledger"]` 下传(`battle_orchestrator.gd:375`),冒烟可自建共享计数器容器喂两个 orch;`AdventureRunner.run_ledger` 公开字段可达(`smoke_all.gd:6131` 先例)。**卡在 U2** | `battle_orchestrator.gd:375` | -| **C1** battle 桶 | 单测/冒烟 | 🟡 **半闭合**——编排器可 new + begin_battle(`smoke_all.gd:2680-2687` 先例);桶须为公开字段或 API,测试才读写得了 | `smoke_all.gd:2680-2687` | -| **B1/B2** level 桶 | 冒烟新段或探针 | 🟡 **半闭合**——LevelRunner 真实驱动有先例(`board_shot_probe.gd:5`);但现役 level 桶**寄居在 `run_ledger.adventure_counters` 字段内**(`adventure_runner.gd:840-842` erase 的就是该字段),搬去 LevelRunner 后须新定公开接口。**卡在 U3** | `adventure_runner.gd:840-842` | -| **D1** round 桶 | 冒烟 | 🔴 **未闭合**——`round_start` 的真实发射入口未定位 | — | -| **G1** 对拍已知差异 | `analyze.py` 字段 | 🔴 **未闭合**——RUNSUM 哪些字段会变未列 | — | +| **E1** 前缀致命 | 新增负面套件 | ✅ **已闭合**——现役 fail-fast 测试模式:`config_db_fatal_oob_test.gd`(独立进程,`expect:1` + `negative:true`)、`reward_dangling_treasure_test.gd`(夹具表注入→load_tables 拒绝)双先例,manifest 协议在役 | `tests/run_all.gd:37`/`:48` | +| **F1** 旧件零残留 | `static_scan.sh` 新增 S 段 | ✅ **已闭合**——S1–S10 分检制在位;本批新增 S 段承载(旧件符号+带引号旧 id 字面两组规则),改面锁定两文件=`static_scan.sh`+`run_all_gates.sh` 登记(域划分唯一出处) | `static_scan.sh:38-82` | +| **A1/A2/A3** adventure 桶 | 冒烟/单测新段 | ✅ **已闭合**——两编排器共享同一 `RunLedger`(V25 骨架)+写入走真实 `request_dig`(U2 随战末导出消失而解除,§7.10.4);`RunLedger` 公开字段直读直写先例在役 | `feat_vouchers_test.gd:175-187`、`smoke_all.gd:6124` | +| **C1** battle 桶 | 单测/冒烟 | ✅ **已闭合**——编排器可 new + begin_battle(`smoke_all.gd:2680-2687` 先例);桶=编排器公开成员字段,测试直读(§7.10.2) | `smoke_all.gd:2680-2687` | +| **B1/B2** level 桶 | 冒烟新段 | ✅ **已闭合**——LevelRunner 直构在役(`smoke_all.gd:6130` `level_runner.gd.new(_db, rng)`);桶=`LevelRunner.level_counters` 公开成员(U3 解,§7.10.11),enc["level_counters"] 下传接线照抄 `"ledger"` 行 | `smoke_all.gd:6130`、`level_runner.gd:466-477` | +| **D1** round 桶 | 冒烟 | ✅ **已闭合**——`round_start` 真实发射点定位=`_emit_round_start()`(U4 解,§7.10.5);直呼该函数即真实路径 | `battle_orchestrator.gd:97`(调用点 `:425`/`:1077`) | +| **G1** 同种子复放比对 | `adventure_monte_carlo` 重放 | ✅ **已闭合**——RUNSUM 27 字段清点完毕(`adventure_monte_carlo.gd:114-149`),分组判读制与允许差异面定值(U5 解,§7.10.12) | `adventure_monte_carlo.gd:114-149` | -### 4.3 写卡前未闭合项(禁拖到实现期) +### 4.3 写卡前未闭合项(2026-10-20 全部闭合——定值与推导见 §7.10) -| # | 未闭合项 | 具体卡点 | +| # | 原卡点 | 闭合定值(详据→§7.10) | |---|---|---| -| **U1** | **A1 注入口径写错了** | 原稿写「置 `adv_twin_digs=1`」**不可行**——新形态引擎无 `counter_add` 公开写口(T2 删除),置数必须走**真实事件**(如触发一次 `dig_paid`)。这条必须重写 | -| **U2** | `_battle_over` 能否孤立调用 | 战末入口在 `battle_orchestrator.gd:1111`(私有)。直接调它会连带 despawn/映射退役/奖励结算。要确认能否被测试单独调用,或另找更窄的战末钩子 | -| **U3** | level 桶公开接口 | LevelRunner 持桶后,测试读写走什么(公开字段/getter/方法)——不定这条,B1/B2 就不可测 | -| **U4** | `round_start` 真实发射入口 | 未定位;定位不到,D1 只能走裸 `dispatch`(弱于真实路径) | -| **U5** | G1 已知差异字段清单 | 需列出 RUNSUM 中预期变化的具体字段,否则对拍红了无法区分「预期」与「意外」 | -| **U6** | B2 的 `mine_hit` 构造路径 | `seal_missed` 有 V25 的 `_judge_seal` 先例;`mine_hit` 的等价构造路径未定位 | +| **U1** | A1 注入口径写错(引擎无 counter_add 公开写口) | 改走真实 `dig_paid` 路径:`orch.action_economy.request_dig(0, false)` 一次即 +1(双管镐 counter_add 行无 when,`feats.tres:26-32` 实证);断言链含第 2 战返还判定,证明读路径同样走真实事件面(§7.10.10) | +| **U2** | `_battle_over` 能否孤立调用 | **不需要**——新形态下战末导出动作消失,A1 断言改走共享桶+两编排器(V25 同构),无验收点依赖该私有入口(§7.10.4) | +| **U3** | level 桶公开接口 | `LevelRunner.level_counters` 公开类型化成员,enc["level_counters"] 下传,测试直读直写(§7.10.11) | +| **U4** | `round_start` 真实发射入口 | 已定位=`battle_orchestrator.gd:97 _emit_round_start()`(调用点 `:425`/`:1077`);D1 直呼该函数即真实路径(§7.10.5) | +| **U5** | G1 已知差异字段清单 | RUNSUM 27 字段清点完毕,分组判读制:未受影响局逐位一致/受影响局输入面恒等+`levels_cleared` 不得上行(§7.10.12) | +| **U6** | B2 的 `mine_hit` 构造路径 | 两编排器共享 level 桶+各自 `_fx.dispatch(ctx, "mine_hit", {})` 直驱(sense 行 when 只读 counter fact,零 payload 依赖);`_facts_reveals` 捏造路径不取(§7.10.13) | -**结论**:13 条验收点中 **2 条已闭合**(C2/E1E2)、**4 条半闭合**(A1A2A3/C1/B1B2/F1)、**2 条未闭合**(D1/G1),另有 6 项卡点(U1–U6)必须先解。**§4.1 现阶段不可直接落码成卡的验收清单。** +**结论**:13 条验收点保持 13 行(E2 撤销并入 F1、新增 H1 补位),**全部载体闭合**,六项卡点全部定值。**§4.1 现在可整表落卡。** -### 4.4 实现设计缺口(2026-10-19 第三轮盘点——写卡前必须补) +### 4.4 实现设计缺口(2026-10-20 全部闭合——定值与推导见 §7.10) -| # | 缺口 | 具体未定 | 阻塞等级 | -|---|---|---|---| -| **A1** | **`ctx` 结构未定义**(最大缺口) | `dispatch` 增的 ctx 里装什么?四桶引用怎么命名/定型?**谁构造 ctx、多久构造一次**?——若 ctx 每轮重建,round 桶天然是新的 `{}`(清零点免费),但 level/adventure 引用要每轮重挂;若 ctx 每战构造一次,round 桶的清零点要显式做。两种走法决定了 T4「三处清零点」到底是三处还是两处 | 🔴 阻塞 | -| **A2** | 桶容器类型与测试接口 | 四桶是否统一为 `Dictionary[String, int]`?level/adventure 是宿主字段、ctx 只带引用——那测试凭什么读写(公开字段/getter/方法)未定(同 U3) | 🔴 阻塞 | -| **A3** | **树合成行走不到生产侧前缀校验** | `feats.tres`/`treasure_items.tres` 的行经 `validate_trigger_row`(`feat_table.gd:44`/`treasure_item_table.gd:104`);但**树合成行**(`effect_engine.gd:142-158`)走 `_push_trigger`,该函数只 `assert(EVENTS.has(on))` 校事件域,**对 counter_id 前缀零校验**。`tree_rules_v11_test.gd:60` 是测试在代校,不是生产在校 ⇒ 前缀校验必须同时落 `_push_trigger`,否则树规则写错前缀不致命,与「加载期强校验」的完成定义 5 直接冲突 | 🔴 阻塞 | -| **A4** | `_battle_over` 孤立调用(同 U2) | 战末入口私有(`orchestrator.gd:1111`),直接调连带 despawn/映射退役/奖励结算 | 🟡 | -| **A5** | `round_start` 真实发射入口(同 U4) | 未定位 ⇒ D1 可能只能走裸 `dispatch`,弱于真实路径 | 🟡 | -| **A6** | `counter_spend` 施加面漏列 | §3.2 只列了写口 `orchestrator.gd:717`;**漏了读施加面 `action_economy.gd:106`**(按 hits 的 `kind == "counter_spend"` 分支走加收对价)——ctx 改动后这条路径怎么走未写 | 🟡 | -| **A7** | counter_id 改名传播面未清点 | 已知:`feats.tres` 数据行、`effect_engine.gd` 树合成行硬编码、`smoke_all.gd` 断言字符串、`tests/` 断言字符串。**未查:呈现层/HUD 是否显示 counter_id**。T6 估时 0.25d 可能不够 | 🟡 | -| **A8** | 保险双计数一致性(N6 遗留) | `_perfect_forgiven`(编排器自持计数,N6 定「留编排器」)与 `insurance_used`(battle 桶,`grant_segment.gd:327` 写)是两份。V25 对两者都断言;`when counter lt 1` 读的是桶内那份 ⇒ 需厘清 `_perfect_forgiven` 是镜像还是另有语义,否则同一次标错可能被两处各写一次 | 🟡 | +| # | 原缺口 | 闭合定值(详据→§7.10) | +|---|---|---| +| **A1** | **ctx 结构未定义**(最大缺口) | `EffectContext` 纯引用束类(`game/core/effect_context.gd`,四桶 `Dictionary[Variant, Variant]` 字段、零方法);每战构造一次:编排器 `_init` 建并挂自有两桶、`begin_battle` 挂 adventure/level 两引用;round 桶清零留引擎 dispatch 特判(就地整桶清空,行为逐位等价)——T4 三处清零点=轮始一处+宿主 new 两处免费(§7.10.1) | +| **A2** | 桶容器类型与测试接口 | 四桶统一 `Dictionary[Variant, Variant]`(S9 现行口径);测试接口=宿主公开字段直读直写(§7.10.2,同 U3) | +| **A3** | **树合成行走不到生产侧前缀校验** | 前缀校验双落点:`validate_trigger_row`(.tres 路径→errs 加载致命)+`_push_trigger`(合成行 assert),共用同一静态判定函数;E2 撤销(无指称),改名完整性防线归 F1 带引号旧 id 字面零命中(§7.10.3) | +| **A4** | `_battle_over` 孤立调用(同 U2) | 阻塞消解——无验收点依赖(§7.10.4) | +| **A5** | `round_start` 真实发射入口(同 U4) | 已定位 `:97 _emit_round_start()`(§7.10.5) | +| **A6** | `counter_spend` 施加面漏列 | `action_economy.gd:103-108`(dig_paid 加收对价+积计数分支)列入迁移面;行号漂移订正(§7.10.6) | +| **A7** | counter_id 改名传播面未清点 | 呈现层零引用(全目录检索实证),传播面收窄至数据行/引擎字面/测试断言三面,T6 估时维持(§7.10.7) | +| **A8** | 保险双计数一致性 | 厘清=**同点双记各司其职**(`_perfect_forgiven` 供判定段两账相减、`insurance_used` 供 when 限次闸),无重复写风险;本批零改动,卡内立「不改面」条目(§7.10.9) | -### 4.5 需求澄清未对齐(不影响本批动工,但需登记或后续裁) +### 4.5 需求澄清项(2026-10-20 全部落处置——B1/B2 本轮执行,B6 按 §7.10.14 定值) -| # | 事项 | 现状 | 处置 | -|---|---|---|---| -| **B1** | **立项依据无文档载体** | 「系统无状态」在 `architecture.md`/`architecture.yaml`/`entities.yaml` **零命中**;本批是代码批、禁写 `design/` ⇒ 卡片的立项依据只能引用「用户 2026-10-19 会话裁定」,不是文档级依据 | 登记 06(来源=会话提及,设计面=文档轨)→ 后续文档批补 architecture.md 条目;本批报告里作反哺点 | -| **B2** | **06:627 条目解法方向被推翻** | 「计数器寿命域未统一」条目的解法(寿命域统一口径)已被本提案推翻,但 06 是待办唯一登记处,**条目不改,后人看 06 仍会按旧方向想** | 同上,登记 06 改写/作废;本批只作反哺点 | -| **B3** | 天赋树锋/守是否重新定价 | 本批把它从「每战 2 次」修回「每关 2 次」=削弱(§7.7) | 已定不改数值;「是否重新定价」=独立议题,不进本批 | -| **B4** | 旧档断点旧键失效 | `tree_sense_*`→`lvl_*` 后旧档内旧键成死键 | 已按 2026-10-18 先例定不迁移;**但卡的验收应加一条「旧档加载不崩」**(现在没有) | -| **B5** | `roadmap.md` 当前位 stale | 仍写「迭代 31 待开工」,卡②已合入 | 已呈报,不进本批 | -| **B6** | `_fx.counter("dig_combo")` 死读数 | `orchestrator.gd:641`,无任何数据行声明该 id ⇒ combo 加成恒 0 死路 | 已呈报。**建议本批顺手删**(迁到 ctx 后仍恒 0,删它零风险),需你一句确认 | +| # | 事项 | 处置(执行状态) | +|---|---|---| +| **B1** | **立项依据无文档载体** | ✅ **本轮已执行**——新增 06 条目「效果引擎系统无状态原则缺架构文档条目」(文档轨·待文档设计),后续文档批补 architecture.md「架构原则」条目;实现批报告仍作反哺点。§10.3 所引 `design/registry/` 两 yaml 已随 2026-10-19 用户裁注销删除,该节对 yaml 的引用失义、以本条为准;立项判据=§7.9 #1(用户 2026-10-19 ECS 架构裁定) | +| **B2** | **06:627 条目解法方向被推翻** | ✅ **本轮已执行**——06「计数器寿命域未统一」条目已改写(现状换代=无状态化重构方向、补「寿命域统一口径」方案作废因由并指向本提案;状态推进「文档设计已闭合+待写卡」)——后人从 06 进入即见新方向 | +| **B3** | 天赋树锋/守是否重新定价 | **已闭合**——本批把它从「每战 2 次」修回「每关 2 次」=削弱(§7.7);不改数值,「是否重新定价」=独立议题不进本批(无需登记——现值非缺陷非待办,若用户日后提起再立条目) | +| **B4** | 旧档断点旧键失效 | **已闭合**——按 2026-10-18 先例定不迁移;验收已补 **H1 旧档加载不崩**(§4.1、§7.10.15) | +| **B5** | `roadmap.md` 当前位 stale | **已呈报在案**,归并行会话,不进本批不动 | +| **B6** | `_fx.counter("dig_combo")` 死读数 | **定值=本批删除**(§7.10.14——全仓无声明行实证+位点本批必触+零行为差;原文「需一句确认」按约束推导闭合,可否决) | --- ## 5. 影响面 -**代码**:`effect_engine.gd`、`battle_orchestrator.gd`、`action_economy.gd`、`damage_controller.gd`、`reward_economy.gd`(`set_fx` 面)、**`settlement/grant_segment.gd`**(草案初版漏——6 个用点:`:103` 读 / `:205` 读 / `:227` `has_effect_on` / `:311-317` `forgive_seal_miss` 消费 / `:327` 写)、`run_ledger.gd`、`adventure_runner.gd`、`level_runner.gd`、`feats.tres`。 +**代码**:`effect_engine.gd`、**`effect_context.gd`(新建——ctx 引用束,§7.10.1)**、`battle_orchestrator.gd`(两桶成员+ctx 构造挂载+dig_combo 死路删除)、`action_economy.gd`、`damage_controller.gd`、`reward_economy.gd`(`set_fx` 面扩 ctx 参——三处注入点 `battle_orchestrator.gd:280-282`)、**`settlement/grant_segment.gd`**(草案初版漏——6 个用点:`:103` 读 / `:205` 读 / `:227` `has_effect_on` / `:311-317` `forgive_seal_miss` 消费 / `:327` 写 / `:54/:61/:62` combo_cap 收集随死路删除)、`run_ledger.gd`(`counters_seed_snapshot`/`counters_merge_export` 随搬运链删除)、`adventure_runner.gd`、`level_runner.gd`(`level_counters` 成员+enc 下传)、`feats.tres`、`data/schemas/feat_table.gd`/`treasure_item_table.gd`(校验调用面零改——前缀校验在 `validate_trigger_row` 内部生效)。 **测试**(草案初版漏项已补): @@ -234,8 +242,9 @@ | `feat_vouchers_test.gd` | **`:114`/`:119`/`:128`**(V16 断点往返 `pain_layers`/`twin_digs`;初版写 `:92-103` 系错行)+ `:132+` 卡② V25 完美保险验收组 | | `settle_segments_test.gd` | `:32-33` `_apply_counter_hit` + `_fx.counter("dig_combo")` | | `treasure_effects_test.gd` | `:34/:45/:53-56` 裸 `dispatch` 面(无 ctx 参数则签名不兼容) | -| `smoke_all.gd` | `:6083/:6085/:6088` `counter_add`+`counters_export`+`counters_seed`/`:6131-6134` 直呼 `_reset_tree_limited_counters`(删函数须同步)/**另 8 处 `fx.counter_add` 直调 `:1427/:1436/:1445/:1466/:1478/:1525/:1566/:1583`(初版全漏)**/`:1410` 断点 `adventure_counters` 键断言/`:1612` `_fx.rebuild`/`:6096/:6124/:6148/:6157` enc 携带 `adventure_counters` 空容器 | -| `run_all.gd` | 套件表(增改套件须登记) | +| `smoke_all.gd` | `:6076-6083` `counter_add`+`counters_export`+`counters_seed` 链/`:6124-6128` 直呼 `_reset_tree_limited_counters`(删函数须同步)/**`:1419-1602` 裸引擎段 17 处 `dispatch`+8 处 `counter_add` 直调(改 ctx 形态——冒烟 rig 自建 ctx 或用 `orch.counters_ctx`)**/`:1410` 断点 `adventure_counters` 键断言/`:1612` `_fx.rebuild`/`:6086-6156` 各 rig enc 携带 `"adventure_counters"` 死键(迭代 27 收口前旧形态残留——编排器只读 `enc["ledger"]`,`battle_orchestrator.gd:372` 实证;同批顺手清键) | +| `run_all.gd` | 套件表(**新增 E1 负面套件登记**) | +| `settle_segments_test.gd` | `:32-33` `_apply_counter_hit` 样本 id `dig_combo` 随死路删除换活 id(§7.10.14) | **存档/断点**:`adventure` 桶已在断点(写键 `adventure_runner.gd:1035`,续玩回填 `:206-208`);`level` 桶是否进断点见 N3。仅改归属不改 id → 旧档零影响;**若采纳 N2 前缀改名(`tree_sense_*` → `lvl_*`)则旧档断点内旧键失效**(Alpha 阶段可接受,有先例:代码术语刷新批 2026-10-18 用户裁不迁移)。 @@ -427,6 +436,99 @@ func counters_merge_export(exported): --- +### 7.10 写卡前缺口闭合定值(2026-10-20 复核轮——由既有裁定与硬纪律推得) + +> 本节把 §4.3 六项卡点、§4.4 八项实现设计缺口、§4.5 六项需求澄清一次闭合。逐项给「定值+推导依据」,全部不占用裁决位——依据三源:用户已裁方向(2026-10-19 ECS 架构裁定、N1–N6 定值)、项目硬纪律(code-conventions/test-conventions/doc-conventions)、本仓代码实证(2026-10-20 逐点实查,file:line 在案)。若与你的判断不符,指出即推翻(同 §7.9 口径)。 + +#### 7.10.1 A1——ctx 结构定值 + +**定值**:ctx=新建纯引用束类 `EffectContext`(`game/core/effect_context.gd`,RefCounted),四个公开类型化字段 `round_bucket`/`battle_bucket`/`level_bucket`/`adventure_bucket`(各 `Dictionary[Variant, Variant]`,int 值),零方法零行为。**每战构造一次**:编排器 `_init` 建 ctx 并挂入自有两桶引用;`begin_battle` 挂入 adventure(`_ledger.adventure_counters`)与 level(enc 新键 `"level_counters"`,LevelRunner `_start_battle` 下传)两引用;ctx 以编排器公开成员 `counters_ctx` 在役——段类经 `oc` 引用即达;三协同件(action_economy/damage_controller/reward_economy)经现役 `set_fx` 注入面扩一参收同一 ctx 引用(后续挂载是同对象内字段赋值,注入时序无感)。 + +**推导依据**: +- 「每战一次」唯一:ctx 持桶**引用**且 round 桶清零用**就地清空**,则每轮重建 ctx 毫无收益、徒增重挂面。「每轮重建则 round 桶免费清零」的备选不成立——它把 level/adventure 引用变成每轮重挂的高频面,换来的只是把一处 `.clear()` 挪进构造。 +- round 桶清零点**留在引擎 `dispatch` 特判内**(`effect_engine.gd:210-212` 现役挂点),由按名单清零泛化为 `ctx.round_bucket.clear()`:前缀分桶后 round 桶内键集≡现役名单集合,行为逐位等价。挂点不挪的依据=现役语义是「重置随事件发生」而非「随发射位点发生」——裸 `dispatch("round_start")` 路径(冒烟 `:6060`/`:1573` 先例)依赖此语义。⇒ **T4「三处清零点」定为:轮始一处(引擎特判泛化)+战斗 new/进关 new 两处免费(宿主 new 天然空桶)**。 +- 「类」而非裸 Dictionary:RunLedger 先例(审计 P1-4 收口)已把「函数签名上不可见的共享可变引用」换成签名可见的显式类;ctx 跨四类边界传递(编排器→引擎→三协同件),code-conventions §2.2 跨层标识符语义成文 ⇒ 显式类。S9 类型闸下裸 `Dictionary` 参数只有 `Dictionary[Variant, Variant]` 注解可走且键名无校验,类字段即自文档。 +- 熵增闸不触:`tools/repo_structure_audit.py` 六查只巡顶层目录/根文件/图像白名单/退役路径/design 子目录/art 内容,`game/core/` 内新增 .gd 不触任何一查(§11 N1 甲「①熵增闸要求新类同批修订白名单」的顾虑经实查不成立于 core 内新文件——该顾虑不再构成「不新建类」的理由,但 N1 定值照旧:桶宿主挂编排器、ctx 只是参数面引用束,两者不冲突)。 +- 字段名取 `round_bucket` 族:`round` 会遮蔽 GDScript 内建函数 `round()`。 + +#### 7.10.2 A2——容器类型与测试接口定值 + +**定值**:四桶统一 `Dictionary[Variant, Variant]`(int 值;S9 现行口径「账本类字典标注 `Dictionary[Variant, Variant]` 即合格」,元素类型精化不入硬纪律)。测试读写接口=**宿主公开字段直读直写**:round/battle 桶=`BattleOrchestrator` 两个公开成员(`fx_counters_round`/`fx_counters_battle`,ctx 构造时挂引用,与 ctx 内字段同物异名同字典);level 桶=`LevelRunner.level_counters` 公开成员;adventure 桶=`RunLedger.adventure_counters`(现役字段名不动,§7.9 #3)。 + +**推导依据**:test-conventions §5.4 调试注入=测试代码直调核心层写口(直改计数器/存档域)——公开字段直写是该条现行承载形态(冒烟 `:6124` `ar.run_ledger.adventure_counters = {...}`、V16 `:114` 同款在役先例);「公开字段 vs getter vs 方法」三候选中后两者无任何约束要求,最小改动取字段。 + +#### 7.10.3 A3——前缀校验双落点定值(E2 并入 F1) + +**定值**:前缀合法性校验落两处、共用同一静态判定函数——①`validate_trigger_row`(.tres 行路径):`do` 含 `counter_id` 时校前缀(无前缀=合法 battle;`round_`/`lvl_`/`adv_` 合法;`meta_` 及其余未知前缀→errs 追加违例,加载致命);②`_push_trigger`(树合成行路径):同规则 assert,写错即调试期致命。**E2 撤销**:「同 counter_id 跨前缀重复」在新形态下无指称——前缀即归属,同一字符串必落同一桶,「跨桶同名」不可构造;「同一语义计数器两行写了两套 id」(改名漏改一类)不可机器判定(两串本不同),其防线归 F1:静态扫描 S 段新增**带引号旧 id 字面零命中**规则(`"tree_sense_sharp_used"`/`"tree_sense_guard_used"`/`"twin_digs"`/`"instinct_atk"`——带引号匹配不误伤 `"lvl_tree_sense_sharp_used"`/`"adv_twin_digs"`/`"round_instinct_atk"` 等新名)。 + +**推导依据**:完成定义 5「写错即加载致命」对 .tres 行经 errs→load_tables 拒绝通道成立(feat_table.gd:44/treasure_item_table.gd:104 调用面零改、校验在函数内部生效);树合成行不经 .tres 校验、只在 rebuild 期经 `_push_trigger` 生成(`effect_engine.gd:142-158`),漏②=A3 原判「与完成定义 5 直接冲突」成立。负面套件载体=`reward_dangling_treasure_test.gd` 同构(copy_tables 夹具注入坏行→load_tables 拒→expect 1+negative:true,run_all.gd manifest 登记)。 + +#### 7.10.4 A4/U2——`_battle_over` 不再需要孤立调用(阻塞消解) + +**定值**:无任何验收点依赖 `_battle_over` 孤立可达。原判的隐含前提是「adventure 跨战持久仍靠战末导出、断言须在战末时点取值」;新形态下导出动作整体消失,写入经 ctx 就地落共享桶,A1 断言改为「第 1战中经真实事件写入共享桶 → 第 2 战 `begin_battle`(同 ledger)经引擎读/返还判定验值」(V25 两编排器同构)——跨战持久由结构保证(同一字典引用),战末时点无待测行为。 + +#### 7.10.5 A5/U4——`round_start` 发射入口已定位 + +**定值**:真实发射点=`battle_orchestrator.gd:97 _emit_round_start()`(调用点 `:425` 首轮/`:1077` 续轮)。D1 载体=冒烟直呼 `orch._emit_round_start()`——即真实发射函数本身,窄于完整轮切换;跨下划线直呼先例在役(V25 `_judge_seal`、冒烟 `:6125` `_reset_tree_limited_counters`)。 + +#### 7.10.6 A6——施加面补列与行号订正 + +**定值**:`action_economy.gd:103-108`(dig_paid 的 counter_spend 订阅施加=加收对价+积计数)列入 §3.2 迁移面——该分支的「读-判-写」序列逐字保留,仅写口改静态函数。行号订正(2026-10-20 实查):action_economy 三写口实为 `:108/:111/:169`(草案 :110/:113/:173 系漂移);orchestrator counter_spend 实为 `:714`(草案 :717)。 + +#### 7.10.7 A7——改名传播面清点完成 + +**定值**:呈现层零引用(`game/presentation/` 全目录检索 counter_id 与 16 枚 id 字面零命中)——呈现面不入改名传播。传播面收窄为三面:`feats.tres` 行(id 改名 4 处+`domain`/`reset_at` 键删除约 12 行)、`effect_engine.gd` 合成行字面与 DO_KEYS 常量、测试断言字符串(§5 表)。T6 估时 0.25d 维持。 + +#### 7.10.8 写口形态定值(§3.2 修订——静态纯函数) + +**定值**:`counter_add`/`counter_spend` **实例方法删除**,改**静态纯函数** `static func counter_add(ctx, counter_id, value, set_mode=false)`/`static func counter_spend(ctx, counter_id, value) -> bool`,与 `validate_trigger_row`/`bucket_of` 同类(零实例状态,完成定义 1 不受触)。桶解析单点=`static func bucket_of(ctx, counter_id) -> Dictionary`(按前缀返回目标桶字典,前缀规则唯一出处)。 + +**推导依据**:§3.2 原稿「写由发射点落到传入计数器容器」的直写形态作废——7 个施加点各自内联桶解析+算术会把「spend 阈值闸」「set_mode」语义复制七份,同种子复放对语义单点的要求不容许;用户 N2 裁定的红线是「引擎不持有状态」,静态函数无实例态不触红线;调用形与现役一致(`_fx.counter_add(...)` → `EffectEngine.counter_add(ctx, ...)`)使 7 个施加点 diff 收窄为加一参。 + +#### 7.10.9 A8——保险双账语义厘清(不改面契约) + +**定值**:`_perfect_forgiven`(编排器自有账)与 `insurance_used`(battle 桶)**非镜像、系同点双记各司其职**:同一标错事件同一分支内各写一次(`grant_segment.gd:314` forgiven 加一/`:315` `_apply_counter_hit` 落桶)——前者供判定段「两账相减」裁定(`perfect_sweep_earned`),后者供引擎 when `lt 1` 限次闸;无重复写风险(闸序:dispatch 仅在 `insurance_used<1` 时返回豁免命中,随后两写同分支各一次)。本批零改动(N6 已定留编排器);卡内立「不改面」条目:两账同点双记关系迁移后必须原样保持(V25 对两者都断言,迁移保绿即证)。 + +#### 7.10.10 U1——A1 注入口径重写(真实事件面) + +**定值**:A1 注入走真实 `dig_paid` 路径——`orch.action_economy.request_dig(0, false)`(核心写口直调,test-conventions §5.4 口径)。可行性实证:双管镐 counter_add 行**无 when**(`feats.tres:26-32`),一次 request_dig 即 `adv_twin_digs` 加一。断言链=第 1 战挖一次(无返还——mod_eq 预评估读 0;桶值 1)→ 第 2 战(同 ledger 起 orch2)挖一次(预评估读持久值 1 判「第 2 次」→ 返还成立+桶值 2)——**返还判定证明第 2 战引擎经 ctx 读到了第 1 战的持久值**,读写两路径均走真实事件面。前置核过:首轮 `notify_round_start` 后 Active 相位、基础 AP 覆盖 dig 基础价,两连挖无经济障碍。 + +#### 7.10.11 U3——level 桶测试接口 + +**定值**:`LevelRunner.level_counters` 公开类型化成员(`Dictionary[Variant, Variant]`),`_start_battle` enc 增 `"level_counters"` 键下传(`level_runner.gd:466-477` 照抄 `"ledger"` 行位置),编排器 begin_battle 挂入 ctx。B1/B2 载体=冒烟 LevelRunner 直构(现役先例 `:6130` `level_runner.gd.new(_db, rng)` 在役)+公开字段直读直写(§7.10.2 同口径)。 + +#### 7.10.12 U5——G1 已知差异面(分组判读制) + +**定值**:RUNSUM 字段面清点完毕(`adventure_monte_carlo.gd:114-149` 共 27 字段)。行为变更只触 adventure 桶四枚中的三枚——**pain_layers 迁 battle 对全新开局零位移**(缺陷态下本就不跨战〔§7.8 自锁〕,battle 桶同样不跨战,前后皆无跨战携带);受影响枚=`adv_twin_digs`(返还得真跨战累积)与 `lvl_tree_sense_*`(每战限 2 → 每关限 2,§7.7 削弱定性)。同种子复放比对判读分两组: + +| 组 | 判定条件(由 RUNSUM 自身字段可判) | 判据 | +|---|---|---| +| 未受影响局 | feat_mode=off,或装备集不含双管镐与 sense 两组 | **全字段逐位一致**(结构迁移对未触路径必须透明——零漂移回归面) | +| 受影响局 | 保守超集:装备集含受影响件且(`mine_hits>0` 或 `seal_miss>0` 或付费挖格≥2) | 输入面字段恒等(policy/seed/atk_pct/hp_pct/route/attune/equipped/feat_mode);其余字段允许 RNG 级联位移(轮数一变后续流消费全移);统计面判据一条:**`levels_cleared` 分布不得上行**(削弱批清关率上移即意外,§7.7 定性) | + +复验规模:锚内 normal 档前 20 种子(81000–81019,配套纪律③子集 ≤20;normal 单局 1.21s,20 局约 25 秒)。逐位差异明细随实现报告记档。 + +#### 7.10.13 U6——`mine_hit` 构造路径 + +**定值**:`mine_hit` 无单事件判定函数(发射内联在授予段 run 的 mine 分支 `grant_segment.gd:92`),B2 构造=**两编排器共享 level 桶+引擎 dispatch 直驱**:`enc["level_counters"]` 传同一字典引用(V25 两编排器同构+挂载路径同测),各自 `_fx.dispatch(各自ctx, "mine_hit", {})`——sense 行 when 只读 counter fact、零 payload 依赖,裸 dispatch 即完整真实判定路径。`_facts_reveals` 捏造路径**不取**:mine 分支连带 `_grant_routed` 极性行路由,捏造快照须伪造 cell_type 行集,脆且越过被测面。第 2 战 dispatch 返回空=「无减伤/无返还」断言本体(lt 2 闸关)。 + +#### 7.10.14 B6——dig_combo 死读数删除 + +**定值**:本批删除,范围=死读数+combo 加成分支+`combo_cap` 参数管线一个整体(`battle_orchestrator.gd:635-647` 读数与分支、grant_segment.gd `:54/:61/:62` 收集与实参、`_feat_yield_adjust` 第五参)。实证:全仓无任何数据行声明 `dig_combo`(feats.tres/treasure_items.tres/树合成行三源零命中);`combo_cap>0` 需 yield_mult 行声明 cap>0——feats.tres 唯一 `"cap"` 行属石肤 ac_bonus(`:576-583`),yield_mult 带 cap 行零个 ⇒ `combo_val` 恒 0 且 `combo_cap` 恒 ≤0,分支恒短路,**删除零行为差**(同种子复放/V25/全部回归零影响)。删除依据:该位点(`_feat_yield_adjust` 读数行)本批必触(counter 签名改 ctx),迁移一个已证死路与重构立意相悖;`settle_segments_test.gd:32-33` 以 dig_combo 为样本 id 同批换活 id。**原文「需你一句确认」按约束推导闭合为定值,可否决——否决的代价=死路代码以新签名迁入新机制。** + +#### 7.10.15 B4——旧档验收补条 + +**定值**:§4.1 增 **H1(旧档断点加载不崩)**:旧断点 `adventure_counters` 含旧键(`tree_sense_sharp_used`=2 等)→ `resume_adventure` 受理零崩溃、旧键留桶成死键(无迁移——2026-10-18 代码术语刷新批先例)。载体=`feat_vouchers_test.gd` V16 旧档同构先例(`:124-127` bp_legacy 注入旧键续玩受理)。 + +#### 7.10.16 B1/B2/B3/B5——登记与归属 + +- **B1**:新增 06 条目「效果引擎系统无状态原则缺架构文档条目」(文档轨·待文档设计)——后续文档批补 architecture.md「架构原则」条目;实现批报告仍作反哺点。§10.3 所引 `design/registry/` 两 yaml 已随 2026-10-19 用户裁注销删除,该节对 yaml 的引用失义、以本条与 §7.9 #1 为准。 +- **B2**:06「计数器寿命域未统一」条目本轮改写(现状换代=无状态化重构方向、状态推进「文档设计已闭合+待写卡」)——后人从 06 进入即见新方向。 +- **B3**:天赋树锋/守重新定价=独立议题,不登记不进批(现值非缺陷非待办;若日后提起再立条目)。 +- **B5**:roadmap 当前位 stale 已呈报在案,归并行会话,不动。 + +--- + ## 8. 已作废的旧方向(防重走) 2026-10-19 前半场提出的「**寿命域统一口径**」方案(五档/四档/两档枚举、`domain` 键扩词表、`reset_at` 退役、D1–D9 裁决单)**整体作废**。作废理由:该方案的隐含前提是「引擎持有状态、需要一个字段说明它活多久」;改为分桶后,**寿命由宿主对象天然决定,不需要寿命字段**。