From 603eba3117e02a860d826a7702c741c3c3e54699 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Thu, 22 Oct 2026 00:56:38 +0800 Subject: [PATCH] =?UTF-8?q?del(docs):=20=E5=88=A0=E9=99=A4=E8=BF=87?= =?UTF-8?q?=E6=9C=9F=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- reports/提案-效果引擎无状态化-2026-10-19.md | 650 ------------------ reports/提案-武器锻造系统-2026-10-07.md | 313 --------- .../测试审计-单测与验收门禁标准-2026-10-15.md | 344 --------- 3 files changed, 1307 deletions(-) delete mode 100644 reports/提案-效果引擎无状态化-2026-10-19.md delete mode 100644 reports/提案-武器锻造系统-2026-10-07.md delete mode 100644 reports/测试审计-单测与验收门禁标准-2026-10-15.md diff --git a/reports/提案-效果引擎无状态化-2026-10-19.md b/reports/提案-效果引擎无状态化-2026-10-19.md deleted file mode 100644 index 192f3343..00000000 --- a/reports/提案-效果引擎无状态化-2026-10-19.md +++ /dev/null @@ -1,650 +0,0 @@ -# 提案:效果引擎无状态化(计数器外置·分桶) - -- **日期**:2026-10-19 -- **版本**:v1.3(2026-10-21 依需求卡片 §12 裁决订正 §7.7/§7.10.3/§7.10.12 三处口径——现行身份标记;改动沿革只查 git commit log) -- **性质**:架构重构提案(restructure,非 refactor)——现役设计被判为错误形态,不是打磨 -- **上游**:`待办清单.md:627`「计数器寿命域未统一」条目(2026-10-18 登记);本提案推翻该条目的解法方向,见 §8 -- **状态**:**设计已闭合·可写卡**——§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` 落地)——本提案若先落地,卡②的专长行需同步改形 - ---- - -## 1. 目标与完成定义 - -**目标**:把 `EffectEngine` 从「解释器 + 自带状态计数器容器」改为「**纯计算引擎(零运行时状态)**」;所有效果侧的值改由**各生命周期的宿主对象**持有,事件触发时按参数传入,引擎只做判定。 - -**完成定义**(全部满足才算完): - -1. `EffectEngine` 实例内**无任何运行时可变数据**(仅剩编译产物 `_triggers` 与只读依赖 `db`); -2. 计数器按 round / battle / level / adventure 四桶外置,各桶由宿主对象持有,桶随宿主生灭; -3. `counters_export` / `counters_seed` 两条搬运链**整段删除**,跨战持久不再靠副本同步; -4. `adventure_runner.gd:840-842` 硬编码清除名单删除,改为「关卡桶整体清空」; -5. 计数器归属机器可读且加载期可校验(写错即加载致命); -6. **同种子复放对拍**与改前一致(确定性零漂移)。对拍=**同种子重放已有模拟锚、零局新采集**(`sim-data/README.md` 配套纪律③:对拍断言 = 0 新跑,出口闸复验用锚内子集 ≤20)。🔴 **2026-10-19 复核:本条与「缺陷自动消解」互斥**——分桶会消解 `run_ledger.gd:42` 判空早退(自锁,实际是**整局**不写入,非仅首战),adventure 域四枚从「每战清零」变「跨战持久」,是行为变更 ⇒ 对拍中相关项必然出现差异。最终口径取决于 §7.7 待裁项 B。 - -> **用语订正(2026-10-19)**:草案前几版对读者直用「MC」缩写而未展开,违反 doc-conventions 第 4 节用语红线。**「MC」=蒙特卡洛(Monte Carlo)**,在本项目特指「跑大量随机冒险的模拟试玩」,入口 `res://smoke/adventure_monte_carlo.gd`(全冒险期望),产物缓存层=`sim-data/`(该目录 README 首行即「MC 缓存层」)。虽系项目既有简称(`AGENTS.md:34/43/62` 全篇在用),但**首次出现必须展开**,本草案后续一律写「全冒险模拟(蒙特卡洛)」。 - ---- - -## 2. 现状(代码级事实) - -### 2.1 引擎持有的可变成员 - -`game/core/effect_engine.gd:86-91` 共五个(🔴 草案初版行号整体 +3 偏移——因迭代 31 卡② `forgive_seal_miss` 扩签在 `:42-44` 插入三行注释;2026-10-19 复核订正): - -| 成员 | 行 | 性质 | 处置 | -|---|---|---|---| -| `_triggers` | :86 | 编译产物(装载期定死) | **保留**(=程序,非状态) | -| `_counters` | :87 | **运行时计数器容器**(counter_id→int,混装四档寿命) | **搬走** | -| `_reset_on_round_start` | :88 | 编译产物(装载期收集) | 随分桶删除 | -| `_adventure_counter_ids` | :89 | 编译产物(装载期收集) | 随分桶删除 | -| `db` | :91 | 只读依赖 | 保留 | - -### 2.2 现役引擎接口与调用点 - -| 接口 | 行 | 调用点 | -|---|---|---| -| `rebuild()` | :101 | `battle_orchestrator.gd:392`、`smoke_all.gd:1612` | -| `dispatch()` | :211 | `battle_orchestrator.gd:93`、`action_economy.gd:105/168/195`、`damage_controller.gd:78`、`reward_economy.gd:65` | -| `counter()` | :227 | `when: fact counter`(:328)、`gte_ref`/`lt_ref`(:318/:320)、`battle_orchestrator.gd:641`、`damage_controller.gd:345`、`grant_segment.gd:103/205` | -| `counter_add()` | :231 | `action_economy.gd:110/113/173`、`damage_controller.gd:351/464`、**`grant_segment.gd:327`** | -| `counter_spend()` | :240 | `battle_orchestrator.gd:717` | -| `counters_export()` / `counters_seed()` | :249 / :258 | `battle_orchestrator.gd:1127` / `:393` | -| `validate_trigger_row()` | :354(静态纯函数) | `feat_table.gd:44`、`treasure_item_table.gd:104` | - -🔴 **草案初版遗漏两处**:①第 6 个写口 `grant_segment.gd:327`(`_apply_counter_hit`——含卡② `insurance_used` 写入计数,见 §6-3);②三个 `counter()` 读口 `damage_controller.gd:345` / `grant_segment.gd:103` / `grant_segment.gd:205`。 - -### 2.3 宿主链与寿命的物理来源 - -- `level_runner.gd:452` `_start_battle` → `BattleOrchestrator.new` → `battle_orchestrator.gd:274` `_fx = EffectEngine.new(db_ref)`。 -- ⇒ **域内计数器容器的物理寿命恒等于一场战斗**;所谓「跨战持久」= 战末 `counters_export` 拷进 `run_ledger.adventure_counters`、战首 `counters_seed` 拷回,**两份副本手工搬运**。 -- 搬运链的既有缺陷:`run_ledger.gd:42` `if adventure_counters.is_empty(): return` ⇒ 容器为空时导出被整批丢弃;因该容器只有断点回填 `:206-208` 才能变非空,**形成自锁:全新开局连玩则 adventure 域四枚整局不写入**(自迭代 20 T6 即有,无整局级测试覆盖)。**本提案落地后该缺陷随宿主消失(不再有副本)**,代价=行为变更,详见 §7.8 / 待裁项 B。 -- 🔴 草案初版把「按关重置」清零点写成 `adventure_runner.gd:876-878`,实为 **`_reset_tree_limited_counters()` :840-842**(硬编码两键 `tree_sense_sharp_used` / `tree_sense_guard_used`),调用点 **`_start_current_level` :847**;新冒险置空在 **:255**(非 :258);断点写键在 **:1035**(非 :1072,该文件仅 1059 行)。 - -### 2.4 效果侧还有一批「记忆」住在编排器 - -`battle_orchestrator.gd:74` `_fx_ac_round`(轮始清零 :101)、`:76` `_fx_ac_battle`(战始清零 :371)、`:78` `_fx_new_resource_types`(战始清零 :372)——同属效果状态,是否一并归桶见 N6。 - -### 2.5 无状态化的可行性(已核) - -- `game/core/**` **零 `static var`**(无静态可变状态); -- 调度全程同步串行(`effect_engine.gd:3` 命中序=装载序),无并发,传参安全; -- `validate_trigger_row` 已是静态纯函数,形态合规。 - ---- - -## 3. 目标形态 - -### 3.1 四只桶(宿主决定寿命,不需要寿命字段) - -| 桶 | 宿主(定值,见 N1) | 清空方式 | 现役对应 | -|---|---|---|---| -| `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` | `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 引擎接口改动(2026-10-20 定稿——写口形态见 §7.10.8) - -| 现在 | 之后 | -|---|---| -| `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()` | 保留(纯函数) | -| — | 新增 `EffectContext` 类(`game/core/effect_context.gd`,四桶引用束字段)+ `static bucket_of(ctx, counter_id)`(前缀→目标桶字典,解析单点) | - -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 → 每次标错都豁免)。 - -### 3.3 归属表达(零运行时机制) - -**id 前缀即归属,加载期强校验、写错即致命**: - -| 前缀 | 桶 | -|---|---| -| `round_` | 回合桶 | -| `lvl_` | 关卡桶 | -| `adv_` | 冒险桶 | -| 无前缀 | 战斗桶(缺省) | -| `meta_` | **非法**(引擎不接存档级——存档级归 `SaveManager` meta 域) | - -现役 13 枚中 `round_blast_spent` / `round_mines` / `round_digs` 已符合;`tree_sense_*` 需改前缀或登记 `tree_`→level(改名代价见 §6)。 - ---- - -## 4. 改动清单(粗估 ≈3–5.5d) - -| T | 内容 | 估时 | -|---|---|---| -| T0 | worktree/分支;冒烟·单元·探针·三闸基线记档 | 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 验收测试清单(机制正向验收 —— 主力) - -> 🔴 **本节是草案初版的漏项(2026-10-19 补)**。原草案只有「测试迁移面」(§5:哪些旧断言要改)与「同种子复放对拍」,**把对拍当成了验收主力——这是错的**。 -> -> 对拍只能证「除已知差异外没改坏别的」(负向/兜底),**不能证新机制按设计工作**。机制改动的正向验收必须由下列断言承担。依据:`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` 冒险段 | 状态/计数器断言 | 两编排器共享同一 `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 桶**同关跨战保持、进关清零** | 冒烟 | 状态/计数器断言 | 两编排器经 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 桶直写非零 → `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 | 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-20 复核轮全绿——逐条对 §4.1) - -> 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** 前缀致命 | 新增负面套件 | ✅ **已闭合**——现役 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 写卡前未闭合项(2026-10-20 全部闭合——定值与推导见 §7.10) - -| # | 原卡点 | 闭合定值(详据→§7.10) | -|---|---|---| -| **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 条验收点保持 13 行(E2 撤销并入 F1、新增 H1 补位),**全部载体闭合**,六项卡点全部定值。**§4.1 现在可整表落卡。** - -### 4.4 实现设计缺口(2026-10-20 全部闭合——定值与推导见 §7.10) - -| # | 原缺口 | 闭合定值(详据→§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 需求澄清项(2026-10-20 全部落处置——B1/B2 本轮执行,B6 按 §7.10.14 定值) - -| # | 事项 | 处置(执行状态) | -|---|---|---| -| **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`、**`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` 内部生效)。 - -**测试**(草案初版漏项已补): - -| 面 | 位点 | -|---|---| -| `effect_words_t1_test.gd` | `:74` `counter_add("dbl_digs")`/`:86-87` `counter_add` + `counters_export`/`:93-94` `counters_seed` + `counter("pain_layers")`/`:105/:108/:110/:113` `counter_add`/`counter_spend`/`counter`/`:121/:123` `reset_at` 面 | -| `tree_rules_v11_test.gd` | `:29-39` 合成行内嵌 `domain=adventure` 字面量(前缀改名须同步) | -| `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` | `: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 用户裁不迁移)。 - ---- - -## 6. 风险与代价 - -1. **确定性风险**:读写时序若与改前不一致,同种子复放会漂移 → T7 必须做同种子复放对拍(`adventure_monte_carlo.gd`,零局新采集)。 -2. **旧档**:`counter_id` 若改名(如 `tree_sense_*`),断点里旧键失效。Alpha 阶段可接受,有先例(代码术语刷新批 2026-10-18 用户裁:不迁移)。 -3. **与迭代 31 卡②的时序(🔴 2026-10-19 复核:前提已翻转)**:草案初版写「卡②**将落**完美保险、建议本批排在卡②之后」——但 **卡② 已合入 master**(`3e076e22` merge 迭代 31 卡② 凭证件裁削批),完美保险**已按「引擎持状态 + `domain=battle`」形态在役**:`effect_engine.gd:42-44` `forgive_seal_miss` 已扩签、`feats.tres:875-877` `insurance_used` 行已落(feats.tres 首例 `domain=battle`)、`battle_orchestrator.gd:267/472/1299` `_perfect_forgiven` 已落地、`grant_segment.gd:311-317` 消费点已接线、`feat_vouchers_test.gd:132+` V25 验收组已在册。 - ⇒ **本批不再是「排在卡②之后」,而是必须把已落地的保险链路一并迁移**:`insurance_used` 归 battle 桶(无前缀缺省)、`grant_segment.gd:327` 写口改 ctx、`_perfect_forgiven`(编排器自持计数)是否并入 battle 桶见 N6。若不迁移即删 `counter_add`,保险**每战限次一次**的闸门失效(变每场无限豁免),且 V25 验收组红。 -4. **`_triggers` 仍留在引擎内**——本提案的判断是「编译产物=程序,不算状态」;若此前提被否,则需另立 `Program` 对象,工作量 +0.5d。 - ---- - -## 7. N1–N6 定值(2026-10-19 复核后改定:由既有约束推得唯一解) - -> 🔴 **本节已重写**。上一版把 N1–N6 当裁决单上交,复核后判定:**六项全部可由已生效的用户裁定 + 项目硬纪律推出唯一解,不存在灰色**。真正的待裁只剩 A/B 两项,见 §7.7。候选与淘汰依据留痕见 §11(不再作为裁决请求)。 - -| # | 项 | 定值 | 推导依据(约束 → 结论) | -|---|---|---|---| -| **N1** | round/battle 桶宿主 | **挂 `BattleOrchestrator`,不新建类**;round 桶清零挂 `_emit_round_start()`(:100 现役挂点) | ECS「数据由宿主持有、随宿主生灭」:battle 桶宿主=每战 `new` 的编排器(:274)⇒ 寿命天然吻合。round 桶粒度不匹配(轮<战),替代方案=新建轮级承载类,但①熵增闸要求新类同批修订 Project Layout+白名单;②现役 `_fx_ac_round`(:74 声明 / :101 清零)已是同一模式=既有先例 ⇒ 取最小改动,不加类 | -| **N1** | level 桶宿主 | **`LevelRunner`**(:845 每关 `new`) | 唯一满足「宿主寿命=语义寿命」的选项:每关 new ⇒ 天然按关清零,`:840-842` 硬编码名单自动消失。挂 `run_ledger` 则宿主=冒险级(名实不符)且随断点走(物理排除 N3「不进」)。代价=新增一条 `enc` 下传路径,照抄现役 `enc["ledger"]` 模式即可,属工程细节非裁决项 | -| **N2** | 归属表达 | **id 前缀 + 加载期强校验**(`round_`/`lvl_`/`adv_`/无前缀=battle,`meta_` 非法) | **用户 2026-10-19 已裁**:「不该给字符串查全局包补映射表,那是把反模式当前提;状态对象按参数传入即可,剩下只需 id 命名空间前缀 + 加载期强校验,零运行时机制」。另两候选:乙=§8 已作废的 `domain` 扩词表;丙=第二事实源,违反数值/数据单一事实源 | -| **N3** | level 桶进断点吗 | **不进** | ①宿主=`LevelRunner`,不跨断点存在;②断点只写选路停点(`adventure_runner.gd:992` 注释「战斗中不存」),level 桶在 :847 进关清零 ⇒ 写入时刻**恒为空**,「进」是空操作;③进=新增存档键+`SAVE_FORMAT_VERSION` 7→8+迁移体,零收益。遗留「退菜单续玩可刷新每关限次」=既有行为(非本批引入),按真人检测口径登记 06,不阻塞 | -| **N4** | 内容语义 | **`pain_layers` 归 battle;`twin_digs` 保 adventure;`smith_seals` 剔除** | 补查 GDD 后可定:`奇物.md:58` 定稿「疼痛记忆 on 踩雷:攒 1 层;**下一次攻击**附加层值伤害,用后清零」,且 v3.16 版本行已记「『下场战斗』系笔误,2026-10-12 追认⑥改定」⇒ **设计意图=不跨战** → 归 battle。`twin_digs`:`奇物.md:38` 只写「每第 2 次付费挖格返 1 AP」,未写范围 ⇒ **保现役(adventure)**,本批是 restructure 不做语义变更。`smith_seals` 未声明 `domain`(默认 battle,随战清零)⇒ 不属名实不符 | -| **N5** | 引擎是否保留 `counter()` | **保留,改读 ctx**(引擎不自持) | 用户 ECS 依据「状态对象按参数传入」=引擎经 ctx **只读**,不算自持状态。引擎内部 `_fact_value` 的 `fact counter`(:328)必须读(现役 4 枚条件面全走此路),外置不可行——除非每次发射 payload 预带整桶快照,成本更高。`gte_ref`/`lt_ref` 现役零数据行使用,不构成约束 | -| **N6** | 归桶范围 | **只归计数器**;`_fx_ac_round`/`_fx_ac_battle`/`_fx_new_resource_types`/`_fx_bonus_delta`/`_perfect_forgiven` **留编排器** | ECS 无状态约束的是**系统**(`EffectEngine`),编排器是宿主/世界,宿主持组件合法;且这些是**无名累积器**(非 `counter_id`),与 N2 前缀机制不兼容,强行归桶须另立键名机制(违反「零运行时机制」)。§1 完成定义 1–6 只约束引擎 ⇒ 只归计数器即全部满足 | - -### 7.7 定值:本批不发起全冒险模拟采集(2026-10-19 收回待裁项 B) - -> 排期归属(编入 roadmap 迭代号 / 与营地五卡谁先)**不属于本阶段议题**——需求卡片尚未生成,先定需求本身,排期在卡成之后。 - -**问:为什么要做全冒险模拟?** 项目里它的正当用途只有两个(`sim-data/README.md` 档位表写的):①数值调研/分析;②数值修改后的回归验证。**两者都是数值场景**。本批**一个数值表都不改** ⇒ 两个用途都不成立。 - -| 档 | 用途 | 局数上限 | 单次耗时 | -|---|---|---|---| -| 一档·调研 | 调研/数值分析 | 100+ 常用 | 16–65 分钟 | -| 二档·验证 | 数值修改后回归验证 | ≤40 | ~3.2 分钟 | -| 三档·小调 | 小调整后快检 | ≤20 | ~1.6 分钟 | -| 四档·微量 | 微调后确认没大问题 | ≤5 | <1 分钟 | - -> 🔴 **档名订正**:前几版写成「MC **一档**」——档名用错,一档是调研档;且一档明确「只能由用户发起,任务书/计划书/agent 均无权发起」。 - -**我把它挂上去的真实动机**是「想确认行为变更不是灾难级」。但这个需求有更便宜且纪律明定的手段:**同种子复放对拍**(配套纪律③——对拍断言 = **0 局新采集**,同种子重放已有锚,出口闸复验用锚内子集 ≤20)。对拍的作用是**排除意外**(除已知几处外不得有其他差异),不是量化。 - -**定值**:不发起任何档位采集。依据=配套纪律⑦「零数值改动 = 零 MC」+档位表四档触发面均为数值场景。验收=同种子复放对拍,adventure 桶相关差异列为**已知行为差异并记档**,不声称零漂移。§1 完成定义 6 同步改为「除已记档的已知差异外,逐位一致」。 -> **〔卡片裁决订正 2026-10-21,依 §12 裁①〕** 「零采集」前提失效:现行锚与本批采集面不同构,不可作复放基线。定值改为**批内重采三组各 40 局基线+批后同种子复放 40 局**(二档上限内,`sim-data/` 纪律③⑦开批次例外);其余判读口径见 §7.10.12 订正注。 - -🔴 **必须呈报的事实(削弱幅度)**:`tree_sense_sharp_used` / `tree_sense_guard_used` 修后是**显著削弱**,不是变强。 - -- 一关战斗场数:L1 单链 5 场(4 普通+Boss,`level.tres:8`);L2/L3 分支图走一条路径约 4 场(`:9`/`:10`)。 -- 现役(缺陷态):每战限 2 次 ⇒ 一关理论上限 **8–10 次**;修后:每关限 2 次 ⇒ **2 次**。 -- 理论上限比约 **4–5 : 1**;实际幅度受触发频率(踩中危险格/标错次数)限制,小于该比例,但**方向确定为削弱**。 -- 这是**回到设计意图**(`effect_engine.gd:140` 注释明写「每关限次」),不是平衡调整 ⇒ 不构成数值改动,不触发采集纪律。 -- 若你认为修完后天赋树锋/守需要重新定价,那是**独立议题**,不在本批。 - -### 7.8 B 项所指缺陷全解(2026-10-19 code-level 实查) - -**缺陷本体**:`game/core/run_ledger.gd:40-45` - -```gdscript -func counters_merge_export(exported: Dictionary) -> void: - if adventure_counters.is_empty(): # ← :42 判空早退闸 - return - for cid in exported.keys(): - adventure_counters[cid] = int(exported[cid]) -``` - -| 维度 | 内容 | -|---|---| -| **所属领域** | 核心域 `game/core/`(非呈现层、非数据层) | -| **所属系统/类** | `RunLedger`——四件共享容器的承载类(迭代 27 T3 从「四件共享容器散在 encounter 字典」收口为单类,审计 P1-4) | -| **所属链路** | EffectEngine adventure 域计数器「**战末导出 → 冒险级计数器容器 → 战首回灌**」搬运链(迭代 20 T6 建立) | -| **设计作用** | 让 `domain=adventure` 的计数器跨战斗持久:战末 `orchestrator.gd:1127` 导出 → 覆写 `run_ledger.adventure_counters`;战首 `:393` `counters_seed` 回灌引擎 | -| **实际作用** | 恒等空操作(见下「自锁」) | - -**自锁机制(不是「首战丢一次」,是整局不写入)**: - -1. 新冒险开局 `adventure_runner.gd:255` → `run_ledger.adventure_counters = {}`(空); -2. 战末导出 → `:42` 判空 → 整批 `return`,四枚值全丢; -3. 容器**仍为空** → 下一战末继续丢 → ……循环不变; -4. 唯一解锁路径=断点回填 `:206-208`(`bp_counters is Dictionary` 才赋非空)。 - -⇒ 同一份存档,**读档前每战清零、读档后跨战累积**,行为不一致且难复现。 - -**受影响四枚(`_adventure_counter_ids` 名单)**: - -| counter_id | 来源 | 语义 | 修前(全新开局连玩) | 修后 | -|---|---|---|---|---| -| `twin_digs` | 双管镐 `feats.tres:29-31` | 每第 2 次付费开格返 1 AP | 跨战进度丢失 → 实为「每战第 2 次」 | 真跨战累积 | -| `pain_layers` | 疼痛记忆 `feats.tres:374-377` | 触陷阱攒层,下次攻击附加,用后清零 | 踩陷阱后未攻击即战末 → 层数丢失 | 层数带入下一战 | -| `tree_sense_sharp_used` | 天赋树 sense_sharp(引擎合成 `:142-148`) | 踩中危险格伤害减半,**每关限 2 次** | 一关多战(`battle_index` 关卡域 1 基序)⇒ 实为**每战限 2 次** | 真每关限 2 次(配合 `:840-842` 按关 erase) | -| `tree_sense_guard_used` | 天赋树 sense_guard(引擎合成 `:152-158`) | 标错返还 AP,**每关限 2 次** | 同上,实为每战限 2 次 | 同上 | - -**时序定性(是「时序问题」吗?——部分成立,但性质是两步叠加,非单点漏做)**: - -五个时点上的调用**全都在**(开局置空 `:255`、进关 erase `:840-842`、战首 seed `:393`、战斗累积 `:231`、战末 export `:1127`),不存在「整个步骤被漏写」。出问题的是两处: - -| 时点 | 性质 | 说明 | -|---|---|---| -| **T0 开局** | **缺初始化**(该做没做) | 应播种四枚零值计数(容器初始非空),实际只给 `{}` 空字典 ⇒ 为 T3 守卫的误判提供了触发条件 | -| **T3 战末** | **守卫误判**(做了但被跳过) | 应无条件覆写四枚,实际被 `is_empty()` 早退闸整批丢弃 ⇒ **病根** | - -两者**任一修好即可解开自锁**:只修 T0(播种零值计数)→ 容器非空 → T3 守卫放行;只修 T3(删守卫)→ 无条件覆写 → 即使 T0 空也通。T1/T4 的偏差是这两处的**后果**,不是独立缺陷。 - -⇒ 本提案走的是「修 T3」这条路,且连同整条搬运链一起删(无副本即无守卫可附),T0 的初始化缺口随之失去意义。 - -**守卫来源考据(`git log -S "adventure_counters.is_empty()"` → `6fd7d782` 迭代 27 T3 收口时逐字搬入)**: - -收口**前**的等价实现(`6fd7d782^:game/core/battle_orchestrator.gd:402-403` / `:1157`): - -```gdscript -# 战首:从 enc 取共享引用;enc 没带(调试/单场战斗)⇒ 退化成临时字典 -_fx_adventure_counters_ref = counters_ref if counters_ref is Dictionary else {} -# 战末 -if _fx != null and not _fx_adventure_counters_ref.is_empty(): # ← 守卫原型 - for cid in exported.keys(): - _fx_adventure_counters_ref[cid] = int(exported[cid]) -``` - -**旧形态下这个守卫是合理的**:它判的不是「有没有数据」,而是**「这个引用是不是真的指向共享计数器容器」**——`is_empty()` 只是当时成立的**代理判据**。enc 没带 `adventure_counters` 时引用退化为临时 `{}`,写进去没人看得到,所以跳过。 - -**收口后判据与被防风险脱钩**:该容器变成 `RunLedger` 的**成员字段**(`run_ledger.gd:12`),永远存在、永远有效,不再有「引用无效」这回事;此时 `is_empty()` 只剩「还没写入过任何值」的含义。**判据没改,但它要防的东西已经不存在了** ⇒ 这就是「误判」的准确含义——不是写错了条件,是**迁移时把一个已失效的代理判据原样保留**。 - -**修法对比(伪代码)**: - -```gdscript -# 甲 · 删守卫(最小改动,2 行)——推荐 -func counters_merge_export(exported): - for cid in exported.keys(): - adventure_counters[cid] = int(exported[cid]) - -# 乙 · 改判据(判"本次有没有东西可导",而非"目标容器空不空") -func counters_merge_export(exported): - if exported.is_empty(): # 注:counters_export 对名单全量输出,恒非空 ⇒ 本分支恒不触发 - return # ⇒ 乙 实际等价于甲,但保留了一个恒真的死条件 - for cid in exported.keys(): - adventure_counters[cid] = int(exported[cid]) - -# 丙 · 补 T0 初始化(不改守卫,改上游) -# adventure_runner.gd:255 由 {} 改为播种四枚 0 ⇒ 容器恒非空 ⇒ 守卫恒放行 -# 可解自锁,但保留了一个语义已错的条件,后人仍会踩 ⇒ 不推荐 - -# 丁 · 本提案:整个函数连同搬运链删除 -# adventure 桶直接住 RunLedger,引擎不再持有副本 ⇒ 无"导出"动作 ⇒ 无守卫可附 -``` - -**结构性根因(为什么无状态化后这类问题整体消失)**: - -先澄清一个误解——**旧形态不是「写不进去」**。GDScript 的 `Dictionary` 是引用类型,写外部字典毫无技术障碍;旧守卫防的不是可写性,而是**有效性**:写进一个无人可见的临时副本等于白写。 - -真正的结构性根因是「**引擎持内部计数器容器 + 战末一次性搬运**」这个两段式结构本身,它同时制造了三类风险: - -| 风险 | 来源 | -|---|---| -| 引用可能无效(临时 `{}`) | 内部计数器容器需要「导出到某处」,而"某处"可能不存在 | -| 需要额外时点(战末/战首) | 搬运只能在战斗边界发生 ⇒ 时点错了就丢数据 | -| 副本与真身的一致性 | 两份数据 ⇒ 谁先谁后、谁覆写谁 | - -无状态化后这三类风险的**结构源头被拆掉**:桶住在外部宿主上,`dispatch` 时**经 ctx 就地写入外部桶**(状态按参数传入,N2 已裁)⇒ 「写外部」从「战末一次性搬运」降级为「每次事件的常规动作」,**不再有特殊时点、不再有副本、不再有守卫可附**。 - -> 换言之:本提案不是「修好了这个守卫」,而是**让「导出」这个动作本身不再存在**——守卫连同它要防的风险一起失去依附对象。 - -> 四者都能解开自锁;甲/丁 是同一结果的两条路径(甲最小、丁是本提案目标形态)。 - -**与提案的关系**:提案删除整条搬运链(`counters_export` / `counters_seed` / `counters_merge_export`),adventure 桶直接住 `RunLedger`(唯一真身,无副本)。**早退闸随宿主一起消失——不是「修 bug」,是无处可附**。副作用=行为变更(上表右列)。 - -**B 三选一**: - -- **B1 不发起采集(纪律⑦ 默认)**:验收=同种子复放对拍(零局新跑,锚内子集 ≤20),adventure 桶相关差异列为**已知行为差异并记档**,不声称零漂移。 -- **B2 破例发起一次二档采集**(需你明确发起,~3.2 分钟):把「跨战持久」后的 AP 经济/通关率位移量化,确认不是灾难级破坏。 -- **B3 保留 `:42` 行为、另立缺陷批**:对拍零漂移,但等于为对齐而复刻一个 bug,**不推荐**。 - -> 若 B 选 B1/B2,§1 完成定义 6 须同步改写(现文「与改前一致」与缺陷消解直接冲突)。 - ---- - -### 7.9 本轮自行定值(2026-10-19,可否决——均属约束可推,不占用裁决位) - -| # | 定值 | 推导依据 | -|---|---|---| -| 1 | **立项判据=用户 2026-10-19 给出的 ECS 架构依据**(系统无状态/数据随宿主生灭/状态按参数传入),不依赖 architecture.md 现有条文 | ECS 依据即已生效的用户裁定,判据充分。`architecture.md`/`architecture.yaml`/`entities.yaml` 检索「无状态」「纯计算」零命中属**文档缺口**,处理方式=登记 06(来源=会话提及,设计面=文档轨)另立文档批补条目,**不阻塞本批** | -| 2 | 分桶后数据行 `domain` 与 `reset_at` 两个字段**删除** | 前缀是归属的唯一声明;保留即**双事实源**(红线)。`reset_at` 由「round 桶轮始整体清空」取代,`domain` 由 `adv_`/`lvl_` 前缀取代 | -| 3 | 容器键名 `run_ledger.adventure_counters` **保留不改** | 改名会破坏旧档断点键;最小改动+单一事实源(桶的容器名不是归属声明) | -| 4 | counter_id 改名**不迁移旧档** | 仅 `tree_sense_*`→`lvl_*`、`twin_digs`→`adv_twin_digs`、`instinct_atk`→`round_instinct_atk` 三处会废掉旧档断点内旧键;Alpha 阶段可接受,**有先例**(2026-10-18 代码术语刷新批用户裁不迁移) | -| 5 | 引擎两个编译产物名单 `_reset_on_round_start` / `_adventure_counter_ids` **一并删除** | 归属改由前缀在加载期强校验,名单即第二事实源 | - -> 以上五条均为「约束给得出唯一解」,故不进裁决单;若与你的判断不符,直接指出即可推翻。 - ---- - -### 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,写错即调试期致命。 -> **〔卡片裁决订正 2026-10-21,依卡片 §12 裁②〕** 前缀判据定值改松版:只禁 `meta_` 前缀,其余(含未知前缀)一律落 battle 桶即归属——`feats.tres` 现有 14 枚 id 全含下划线、首段除三枚 `round_` 外全是普通词,本节严版误伤全部既有 battle id,且封闭前缀词表=第二事实源(2026-10-19 registry 注销裁定同类)。自造新前缀静默落 battle 的残余风险改由装载期引用闭合校验(写读成对核验,拦「字面改一半」)正面承接,不再单靠人工评审;判定函数=`counter_id_legal`。本节其余双落点结构不变。**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 削弱定性)。 -> **〔卡片裁决订正 2026-10-21,依卡片 §12 裁①与本卡〕** 实采字段面为 33 个(本节原记 27 系清点时漏量);分组判读由两组扩为三组——单一「`levels_cleared` 不得上行」判据把增强组(双管镐跨战累积返还得真)与削弱组(sense 每关限 2)混判,作废。现行判读口径=**off 组全字段逐位一致;formed 组与 rules 组输入面字段恒等、其余字段允许 RNG 级联位移、方向只记报告不作判据**。下表按此执行。 - -同种子复放比对判读分组(现行口径): - -| 组 | 判定条件(由 RUNSUM 自身字段可判) | 判据 | -|---|---|---| -| 未受影响局 | feat_mode=off,或装备集不含双管镐与 sense 两组 | **全字段逐位一致**(结构迁移对未触路径必须透明——零漂移回归面) | -| 受影响局(formed/rules 两组) | 装备集含受影响件(formed=表驱动装配、rules=树规则注入,采集分组见卡片 T0/T7) | 输入面字段恒等(policy/seed/atk_pct/hp_pct/route/attune/equipped/feat_mode);其余字段允许 RNG 级联位移(轮数一变后续流消费全移);**方向只记报告不作判据**(增强组与削弱组分列读数随实现报告记档) | - -复验规模(本节原案,作废——现行锚 24 字段面与本批采面不同构、且 `sim-data/` 不入 git 致 worktree 内无锚文件可校):~~锚内 normal 档前 20 种子(81000–81019,配套纪律③子集 ≤20)~~。**现行口径〔卡片裁决订正 2026-10-21,依 §12 裁①〕=批内重采三组各 40 局基线+批后同种子复放 40 局**,二档上限内、`sim-data/` 纪律③⑦的批次例外已按其反哺条登记;现行锚与读数文件一律不动。逐位差异明细随实现报告记档。 - -#### 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 裁决单)**整体作废**。作废理由:该方案的隐含前提是「引擎持有状态、需要一个字段说明它活多久」;改为分桶后,**寿命由宿主对象天然决定,不需要寿命字段**。 - -原议题中仍然成立的部分只剩两条:①`feats.tres` 名实不符(`pain_layers` / `twin_digs`;**`smith_seals` 已于 2026-10-19 复核剔除**——它未声明 `domain`,默认 battle 随战清零,不属名实不符)→ 已在 §7 N4 定值(`pain_layers` 归 battle/`twin_digs` 保 adventure,依据 `奇物.md:58` 与 v3.16 追认⑥);②`run_ledger.gd:42` 空容器早退缺陷 → 本提案落地后自动消解,无需单独修(但消解=行为变更,触发 §7.7 待裁项 B)。 - ---- - -## 9. 不在范围内 - -- `charge_reset`(秘宝充能周期,`treasure_item_table.gd:46`)——已自带枚举与校验,另属一族,不动; -- 凭证件 runner 侧裸标量(`_pending_rap` / `_time_sand_queue`)——一次性结算,非效果引擎状态; -- `SaveManager` meta 域(生涯计数 / 钥石 / 树节点 / 锻造路线)——存档级,本提案明确不并入引擎; -- 断点快照结构除 N3 外不变。 - ---- - -## 10. 复核订正记录(2026-10-19 code-level 复核,逐条实查) - -### 10.1 行号订正汇总 - -> 🔴 **2026-10-20 再漂移注记**:本表「实际」列的行号以提案复核时点为准;其后的 `c5200b6b`(禁写变更记录批·代码注释清偿)已使本文件部分行号再度漂移(如引擎成员块现役 :83-88、`counters_seed` :255、`EffectEngine.new` :271、树合成行 :136-155)。行号时效以开工前代码实查为准(对应需求卡片已随迭代 32 退役,全文见 git 提交 f2bb5871),本节数字仅作沿革参考。 - -| 草案初版写法 | 实际 | 说明 | -|---|---|---| -| `effect_engine.gd` :83/84/85/86/88 | :86/87/88/89/91 | 整体 +3——迭代 31 卡② 在 `:42-44` 插入 `forgive_seal_miss` 三行注释 | -| `effect_engine.gd` rebuild :98 / dispatch :208 / counter :224 / counter_add :228 / counter_spend :237 / export :246 / seed :255 / validate :351 | :101 / :211 / :227 / :231 / :240 / :249 / :258 / :354 | 同上 +3 | -| `adventure_runner.gd:876-878` 硬编码清除名单 | **:840-842**(`_reset_tree_limited_counters`),调用点 :847 | 差 36 行 | -| `adventure_runner.gd:258` 新冒险置空 | :255 | — | -| `adventure_runner.gd:1072` 断点写键 | **:1035**(该文件仅 1059 行,:1072 越界);续玩回填 :206-208 | — | -| `feat_vouchers_test.gd:92-103` | :114 / :119 / :128 | V16 断点往返在 :114 起 | -| 计数器「13 枚」 | **16 枚**(round 4 + battle 8 + adventure 4) | 13 系卡② 前旧数;且与草案自身分桶相加 4+8+4=16 矛盾 | - -### 10.2 遗漏面订正 - -1. **第 6 个写口** `settlement/grant_segment.gd:327`(`_apply_counter_hit`)——卡② `insurance_used` 写入计数唯一通路,漏=保险限次失效。 -2. **3 个读口** `damage_controller.gd:345`、`grant_segment.gd:103`(`value_counter` 层值:安全帽 `helm_layers`/疼痛记忆 `pain_layers`/陷阱本能 `instinct_atk`/弃图兑换 `stop_swap`)、`grant_segment.gd:205`(`per` 反打层值)。 -3. **测试迁移面漏 8+3+2 处**:`smoke_all.gd` 另 8 处 `fx.counter_add` 直调(:1427/1436/1445/1466/1478/1525/1566/1583)、`settle_segments_test.gd:32-33`、`treasure_effects_test.gd:34/45/53-56` 裸 `dispatch`。T7 估时(0.5–1d)应上修。 -4. **`smoke_all.gd:1612`** `orch0._fx.rebuild([], [], [])`——`rebuild` 签名若改须同步。 - -### 10.3 判据出处存疑(须用户确认,本轮不代裁) - -§1 的立项判据「`_counters` 被判为违反『系统无状态』原则」——**`design/dev/architecture/architecture.md`、`design/registry/architecture.yaml`、`design/registry/entities.yaml` 三处均无「无状态」/「纯计算」原则条目**(2026-10-19 全仓检索零命中)。architecture.yaml:34-35 对效果引擎的登记只写「发布订阅解释器/四词表封闭/遍历序=声明序保种子复放」,**未含无状态约束**。 -⇒ 该判据目前**无权威文档出处**。若本批以此立项,需先定:①在 architecture.md 补一条「系统无状态」原则(文轨动作,另立批);②或改以「消除双副本搬运 / 消除硬编码清除名单」为立项理由(这两条是实查成立的事实,无需新原则)。**本轮只呈报,不代裁。** - -### 10.4 顺带发现(不在本批范围,仅呈报) - -1. **死读数**:`battle_orchestrator.gd:641` `_fx.counter("dig_combo")`——全仓无任何数据行声明 `counter_id="dig_combo"`,**该读数恒 0**,`_feat_yield_adjust` 的 `combo_cap` 分支实为死路。属既有缺陷,与本批正交;本批迁移后仍恒 0,不影响对拍。 -2. **06 上游条目行号亦已漂移**:`待办清单.md:629` 内引 `adventure_runner.gd:873-877`、`effect_engine.gd:32`、`:119-121` 三处均非现役行号(实为 :840-842、:31、:124-125)。本批落地回写 06 时须一并订正。 -3. **roadmap 当前位 stale**:`roadmap.md:8` 仍写「迭代 31 待开工」,但卡② 已合入 master(`3e076e22`)。属并行会话反哺迟到,非本批范围。 - ---- - -## 11. N1–N6 候选与淘汰依据(决策留痕,非裁决请求) - -> 🔴 2026-10-19 复核后本节**降级为留痕**:六项已由既有约束收敛到定值(见 §7),下表保留「当初并列了什么、各自为什么不选」供后人复核。仅 §7.7 的 A/B 两项仍需用户裁。 - -### N1 · 桶的宿主 - -**战斗侧(round + battle 桶)** - -| 候选 | 内容 | 不选它的理由 | -|---|---|---| -| **甲** 挂 `BattleOrchestrator`(两 Dictionary 成员,battle 桶随 `new` 天然空) | 最小改动、零新类 | 编排器已 1299 行、已持 9 个 `_fx_*` 运行态;再加两桶=状态只是从引擎搬到编排器,**「无状态化」对系统层未发生**。round/battle 寿命差异靠人工清零点保证(现役 `_fx_ac_round` 正是此形态,即缺陷原形) | -| **乙** 新建 `BattleCounters` 类(持 round/battle 两桶 + `reset_round()`) | 寿命由类型表达;两桶清零各一方法;可独立单测 | +1 新类;ctx 传参多一层;`dispatch` 签名改动面略大 | -| **丙** 单一 `EffectCounters` 承载全部四桶 | ctx 只需一个参数 | 把冒险级 `adventure` 桶与战斗级桶塞进同一对象,寿命又退回「靠人工清零」,与本提案立意相消 | - -**关卡侧(level 桶)** - -| 候选 | 内容 | 不选它的理由 | -|---|---|---| -| **甲** 挂 `run_ledger`(与 `adventure_counters` 同处) | 断点零新增键(复用同字典);`_reset_tree_limited_counters` 改成「清 level 前缀键」 | RunLedger 名义=冒险级共享容器(四件),装关卡桶=名实不符;且它随快照走 ⇒ **N3「不进断点」选项被物理排除**(选甲即强制选「进」) | -| **乙** 挂 `LevelRunner`(每关 new,随宿主生灭=天然按关清零) | 寿命天然正确;硬编码名单自动消失 | 需新增一条 `enc` 下传路径(现役只下传 `enc["ledger"]`);N3 若选「进断点」须新增存档键 | - -### N2 · 归属表达 - -| 候选 | 内容 | 不选它的理由 | -|---|---|---| -| **甲** id 前缀(`round_`/`lvl_`/`adv_`/无前缀=battle,`meta_` 非法) | 零运行时机制;加载期 `validate_trigger_row` 强校验 | 前缀=命名约束,改名要动 `effect_engine.gd:143-158` 合成行 + `tree_rules_v11_test.gd:29-39` 字面量,且**旧档断点内旧键失效**;`bucket_of()` 是字符串约定,无类型系统兜底 | -| **乙** 数据行显式键(`domain` 扩为 round/battle/level/adventure 四值) | 与现有 `domain` 键同面,改动直观 | **正是 2026-10-19 前半场已作废的方向**——保留 `domain` 键=保留「引擎需要知道寿命」的心智模型,分桶的意义被抵消(§8) | -| **丙** 集中注册表(新常量表 counter_id → 桶) | 单点可查 | **第二事实源**,与 `feats.tres` 行内声明易双向漂移(ADR 漂移教训同类红线);每加一枚计数器须改两处 | - -### N3 · level 桶进断点吗 - -| 候选 | 内容 | 不选它的理由 | -|---|---|---| -| **甲 进** | 新增存档键 + `SAVE_FORMAT_VERSION` 7→8 | 需写迁移体(v6→v7 是「空迁移=缺键 `_normalize` 补齐」先例,成本可控)。**但**——见下方「前提核查」 | -| **乙 不进**(现值) | 零存档改动 | 续玩时每关限次归零 ⇒ 玩家可「退菜单续玩」刷新限次,与 2026-10-13 修掉的 `attune_refresh_y`「用完退菜单再续玩满血复活」**同类可利用漏洞**(属既有缺陷,非本批引入) | - -🔴 **前提核查(影响 N3 实质收益,须先确认)**:现役断点只写在**选路停点**(`adventure_runner.gd:992` 注释明确「选路停点=唯一可恢复停点形态;战斗中不存」),而 level 桶在 `_start_current_level`(:847)进关时清零。⇒ 断点写入时刻 level 桶**恒为空**,「进断点」很可能是**空操作**。若将来支持战斗中存点才真正有意义。**请裁:是否按「当前恒空」判定 N3 收益≈0、直接选乙。** - -### N4 · 内容语义(名实不符两枚,已剔除 `smith_seals`) - -| 计数器 | 候选 | 不选它的理由 | -|---|---|---| -| `twin_digs`(现役声明 adventure) | ①真 adventure(跨战累积,保现役)②归 battle(每第 2 次按战重算,每战首次付费开格不返) | ①=承认现役,但 `run_ledger.gd:42` 空容器早退使**首战导出全丢**=首战攒的次数丢失(现役真 bug);②=强度实质变化(每战少返一次),属数值改动,须另走数值裁 | -| `pain_layers`(现役声明 adventure,但 `:407` 行 `set:0` 用后清零) | ①归 battle(与「用后清零」自洽,跨战持久本就不成立)②保留 adventure 但删 `set:0` 行(变真跨战累积层)③归 level | ①最省事且语义自洽,但**改变了「上一战残留层可带进下一战」这一现役行为**(要确认是否为设计意图);②=明确的内容加强,属数值裁;③两不靠,无现役依据 | - -### N5 · 引擎是否保留 `counter()` 只读 - -| 候选 | 内容 | 不选它的理由 | -|---|---|---| -| **甲 保留**(纯查询,读 ctx) | `_fact_value` 的 `fact counter`(:328)在 `dispatch` 内部必须能读到——现役 4 枚条件面(`twin_digs`/`pain_layers`/`insurance_used`/`tree_sense_*`)全走此路;外部 4 读口也走 ctx | 「引擎接触计数器容器对象」——严格说是持**只读引用**,是否算「状态」取决于 §1 完成定义 1 的严格度 | -| **乙 连读也外置** | 外部 4 读口直读桶,不经引擎 | **引擎内部仍必须读**:`when` 条件的 `fact counter` 判定在 `_cond_ok` 内发生,无法外置到调用点,除非改成「payload 预携带全部计数器现值」——那等于每次发射都要快照整桶,成本与复杂度都更高。⇒ 乙实质只能做到「外部调用点不再经引擎」,引擎内部读不可消除 | - -补充事实:`gte_ref` / `lt_ref` 两算子现役**零数据行使用**(仅登记在 `OPS` 词表),不构成 N5 约束。 - -### N6 · 归桶范围 - -| 候选 | 内容 | 不选它的理由 | -|---|---|---| -| **甲 全归**(`_fx_ac_round`→round 桶;`_fx_ac_battle`/`_fx_bonus_delta`/`_fx_new_resource_types`/`_perfect_forgiven`→battle 桶) | 效果态彻底外置,无状态化名副其实 | ①这些是**无名 int/Dictionary 累积器**,不是 `counter_id`,进桶后与 N2 前缀机制不兼容,须另立桶内键名;②`_fx_bonus_delta` 有「战末汇入 ledger 再清零」的跨战语义,归 battle 桶后该步骤仍需保留;③`_perfect_forgiven`(卡②,契约 2 禁改写 `_perfect_hit_false`)并入桶=把刚验收的保险链路再动一次 | -| **乙 只归计数器**(不动编排器 5 个效果态) | 改动面最小;§1 完成定义 1–6 条**全部仍可满足**(完成定义只约束引擎) | 「无状态化」只对引擎成立,编排器仍持 6 个效果运行态,提案立意打折 | -| **丙 分两步**(本批乙,编排器效果态另立一批) | 本批可达完成定义且风险最低;遗留项明确登记 06 | 需多一次立项;两批之间编排器仍持态(可接受) | diff --git a/reports/提案-武器锻造系统-2026-10-07.md b/reports/提案-武器锻造系统-2026-10-07.md deleted file mode 100644 index 69ab3195..00000000 --- a/reports/提案-武器锻造系统-2026-10-07.md +++ /dev/null @@ -1,313 +0,0 @@ -# 提案:武器锻造系统(2026-10-07)· 设计提案待裁决稿 - -> **这是什么**:把用户的武器锻造想法写成一份可以逐点拍板的设计提案。本文件只做设计,不写代码、不改数据表、不占排期——每一个设计分岔点都把全部备选方案(甲/乙/丙)并排列出,附淘汰理由与各自代价,交用户盘问裁决。裁决通过后,实装另立批次走完整流程。 -> **想法来源**:用户 2026-10-06 手记(`../playnote.md` 第 14-23 行),已登记 [06-待办清单](../待办清单.md)「武器锻造系统」专条。 -> **状态**:待用户盘问裁决。文中所有数值只有量纲和参考锚点,全部标注 provisional,终值归 Alpha 收官 M4 数值批统一调整(该批未排期)。 - ---- - -## 〇、用户原案逐条对照(提案与原案的关系显式化) - -先引原案原文,再说明本提案对每一条是放大、收窄还是替代。**不静默改案**。 - -**原案一(playnote.md:16-17)**:「剑(普通攻击):通过锻造升级可以增加攻击力,升到最高等级时,还可以增加一些额外的效果。」 - -→ **提案是放大**。游戏现在已经有"攻击锻造"这个营地消费点(营地花钥石,攻击力 +1,可重复买——`game/data/tables/meta.tres:14-18`)。提案把它改造成一条有等级、有成长曲线、有满级特效的"剑"升级路线。方向与原案一致,范围扩大。 - -**原案二(playnote.md:19-22)**:「长枪与法杖:后续可以增加长枪、法杖等武器,它们的攻击类型要分别变为"穿刺"和"魔法"。同样在最高等级时,也会有一些特殊的效果。……实现武器之间的差异化。」 - -→ **提案是放大,且承接现役结构**。考证发现:主角的攻击类型字段已经登记在数据表里(`hero.tres:11`,取值 normal/pierce/magic),是 2026-09-18 一个前置批次立好的、当前没有数值效果的休眠轴。提案直接唤醒这根轴来承载"武器决定攻击类型",不需要新建任何类型系统。差异化的深度(武器是标签还是实体)是本提案第三节的三个备选案。 - -**原案三(playnote.md:23)**:「同样的护甲,我们也可以做成不同的护甲,然后去做升级,一开始我们只有轻甲,提升只会提升我们的 AC 值;但是在最高等级的时候,我们就可以拿到不同的额外效果;解锁魔法甲或者重甲之后,我们得到的基础 AC 值可能是不一样的,也可以通过提升来提高它的 AC 值,最后还有一些不同的额外效果」 - -→ **提案收窄一处、放大一处**。收窄:原案读起来像"换甲型立刻改变战斗手感"。考证后必须说清——敌方的攻击现行不带类型、不查克制矩阵(`game/core/damage_controller.gd:19` 单据类型栏允许空串、`damage_controller.gd:513` 空串恒不查矩阵),所以换甲型在"克制"这个维度上暂时没有任何数值效果。甲型差异在现行边界下玩家唯一能立刻感知的就是基础 AC 不同。提案把这一点写成显式边界,而不是含糊承诺。放大:三种甲各自的基础 AC、各自独立的升级路线、各自的满级特效,都是原案的直接承接与展开。 - -**成长曲线反馈(playnote.md:5,同日原文)**:「一开始不能让角色的成长系数太高。比如在没经过升级之前,你的成长系数是跟不上关卡怪物难度提升的;但是后续能力解锁之后,成长系数才能匹配得上」 - -→ **提案第六节正面回应**:锻造的成长曲线形状与解锁节奏,就是为这条反馈服务的。 - ---- - -## 一、现状事实基准(本提案全部论断的依据,2026-10-07 逐条复核) - -以下六条是提案的对齐基准,全部经过代码级复核。引用格式 = 文件:行号。 - -| # | 事实 | 出处 | -|---|---|---| -| F1 | 攻击力提升现有两个来源,都是直接加数字,没有"武器"这个实体:①营地属性锻造(攻击锻造 +1 / 生命锻造 +3 / 护甲锻造 +1,单次价 20 钥石、每次涨价 150%——货币=钥石:消费口 `meta_progression.gd:59` spend_keystone,meta.tres 仅存计价参数)②关卡内商店货架(六行共享池+乘法价格公式,`game/core/config_db.gd:329-334` 现役访问器)。商店里还有一组旧的三行商品定义(`shop.tres:36-57`),只在对照模式承载,属遗留(`game/data/schemas/shop_table.gd:16`、`shop.tres:107-109`) | `meta.tres:7-25`、`shop.tres:68-94` | -| F2 | 主角攻击类型轴**已存在、休眠中**:`hero.tres:11` 值为 "normal",字段枚举 {normal, pierce, magic}(`hero_table.gd:13`)。战斗段主角基础攻击按此查克制矩阵。棋盘上的穿刺/魔法直伤奖励件走同一张矩阵但必中全额不掷点(`battle_orchestrator.gd:830` 注、`:846-854`)。**克制矩阵已经是 3×3 九格满格**(`settlement.tres:31-47`:普通/穿刺/魔法 × 轻甲/魔法护甲/重甲)。专长里的三个条件值(pierce_vs_light / magic_vs_heavy / magic_vs_magic)是专长的触发条件值、不是矩阵本体——其中 magic_vs_magic 在现行甲型域下永远为假(见第四节裁点 2c)。敌方装甲类型域 {light, ward, heavy}(`enemy_table.gd:110-113`,缺省 light),实际分布:light×14 行 / heavy×4 行 / ward×1 行(`enemy.tres`;L1 全轻甲、L2 只有 Boss 蛛化卓尔精灵是魔法护甲、L3 灰矮人族四行重甲)。敌方攻击无类型、不查矩阵(`damage_controller.gd:19/:513`)。克制矩阵的定义文档 = [战斗结算](../design/gdd/systems/战斗循环/战斗结算.md)与[敌人系统](../design/gdd/systems/战斗循环/敌人系统.md) | 见左 | -| F3 | 主角护甲现状:AC(护甲等级,掷点减伤判定用)单值现役生效(`hero.tres:9` 值 10,判定消费 `battle_orchestrator.gd:172`);**甲型结构位已在册休眠**(`hero.tres:10` armor_type="light",`hero_table.gd:12`)——休眠原因是敌方攻击不查矩阵,属结构性而非数值性(《敌人系统》主角字段表注记)。甲型域三值 = {light, ward, heavy}。AC 现有提升渠道 = 营地护甲锻造 +1 / 商店货架护甲件 / 局内奇物属性件 | 见左 | -| F4 | 局内属性件:奇物三分类里的"属性值提升"分类 = 生命上限 / AC / 攻击三轴,进冒险加算区,关间不清零([奇物](../design/gdd/systems/冒险循环/奇物.md) §1.2,分类封闭:"只有这三项,多一项就要走文档修订")。它与 F1 的营地/商店构成"账号层永久 + 局内单冒险"两层结构,现役并存 | `design/gdd/systems/冒险循环/奇物.md` | -| F5 | 相邻系统:营地三个柜台(属性锻造 / 道具工坊 / 奖励格图鉴,[元进度](../design/gdd/systems/跨局循环/元进度.md));商店经济两个价格参数 provisional 在册;效果引擎已落地——效果类型闭集 24 种(`effect_engine.gd:31-35`,源码注记"23"系笔误),武器满级特效优先复用现役效果类型,需要新效果类型的部分=实装批的代码面 | `design/gdd/systems/跨局循环/元进度.md` | -| F6 | 成长曲线反馈原文见第〇节 | `playnote.md:5` | - ---- - -## 二、范围与不做什么(红线) - -1. **纯文档**:零代码、零数据表改动、零数值投值。本提案全部表格值只有量纲与锚点。 -2. **不占排期**:提案裁决通过后实装另立批次,本提案不在路线图预占实装卡。 -3. **不动现役系统**:第十节"迁移面"只描述改造方案,不执行。 -4. **白话**:全文按文档规范用语纪律写作,不自造机制名词;武器/甲的正式名字本身是裁点之一,随裁决定名后才登记术语表。 -5. **不与奇物既有裁决冲突**:凡涉及属性值提升分类或获取渠道的地方,显式引用《奇物》现行口径并说明关系(见第五节末)。 - ---- - -## 三、武器实体化程度(本提案最核心的裁点) - -**这一节回答的问题**:"武器"在游戏里到底是什么东西?是攻击力数字旁边的一个标签,还是玩家能看见、能持有、能更换的一件装备?三个备选案按实装量从小到大排列,**每一案都回答两个通用必答题**(携带几把、什么时候能换)。 - -### 案甲:纯升级路线——"武器"只是攻击锻造这条路线的新名字 - -**做法**:营地攻击锻造改名为"武器锻造",从"花钱买 +1"改成一条等级路线(1 级到上限,每级攻击力按成长曲线上涨);玩家在营地随时选择武器类型(剑=普通 / 长枪=穿刺 / 法杖=魔法),类型写进现役的 attack_type 字段;到达满级解锁该类型的专属特效。 - -- 与现有系统的关系:直接吸收现役攻击锻造行(`meta.tres:14-18`);与局内奇物属性件并存(两层现役结构不变);商店货架不动。 -- 携带数:单持——它本质就是一个字段,一次只有一种类型在身。 -- 换械时机:营地内任意时刻可换,零成本。 - -**优点**:实装量三案最小(一条等级数据+一个字段切换+特效挂点);与现役"营地永久强化换攻击类型 = 改字段"的既有设计注记逐字同源(`hero.tres:25-27` 注记)。 - -**缺点(为什么不够)**:"武器"没有实体感——玩家看到的只是数字和伤害类型的区别。原案的核心诉求是"实现武器之间的差异化"(playnote.md:22),甲案给的是"一条路线+三个标签",差异化感知最弱。另外满级特效只能挂在类型上:换类型=特效跟着换,三件武器不可能各自满级并存。 - -### 案乙:武器槽实体——武器是一件装备 - -**做法**:新增"武器"装备位。武器是有编号、有等级、有类型、有特效的实体;玩家持有武器库、装备其中一把;每把武器的等级独立。 - -- 与现有系统的关系:现役攻击锻造行改造为"初始剑"的等级路线;新增一种实体类型——存档结构、营地界面、装备逻辑全部新做;还要新增一段说明:武器不属于奇物三分类的任何一类(它不是单局获得的奇物,是跨局装备)。 -- 携带数:单持(一生主械);若做多持=双武器轮换,代价见通用必答。 -- 换械时机:营地内换;战斗中禁换(理由见通用必答)。 - -**优点**:差异化感知最强;满级特效有清晰的承载主体;未来扩展面宽(武器稀有度、词条、分解重铸都有地方挂)。 - -**缺点(为什么不推荐现在做)**:实装量三案最大——本项目目前没有任何"装备"概念,为三把武器单开一套实体系统(库存、存档域、装备界面),投入超出"武器差异化"这个原案诉求所需的复杂度;且这个决定不易回退(实体一旦进存档,删实体=存档迁移)。若未来确需装备系统,丙案是它的可逆前站。 - -### 案丙(推荐):折中——"当前武器"单选 + 每件武器一条独立成长路线 - -**做法**:营地武器锻造柜台呈现三件武器(剑/长枪/法杖)。每件武器有自己的等级(1 到上限)、自己的攻击成长曲线、自己的满级特效。玩家指定其中一件为"当前武器"出场——类型由武器决定(选长枪=穿刺),写现役 attack_type 字段。三件武器的等级各自独立保留,换"当前武器"不丢任何进度。 - -- 与现有系统的关系:现役攻击锻造行 = 剑路线的前身(吸收改造,见第十节迁移面);存档面 = 三行等级数字 + 一个当前选中标记,**没有实体列表、没有库存**;生命锻造行的处置见第七节裁点 5a。 -- 携带数:单持(同一时刻只有一件武器出场)。 -- 换械时机:营地内随时换,切换不收费。理由:等级都是玩家花资源练出来的,切换只是决定"今天带谁出门",二次收费会惩罚尝试性构筑,与成长曲线反馈"能力解锁后才能匹配关卡难度"的诉求相逆(玩家需要低成本试错来找到当前关卡匹配的武器)。 - -**优点**:差异化感知接近乙案(三件武器各有名字、等级、特效,玩家分得清);实装量中等(无库存管理面);数据面是现役 meta 表锻造行族的直接扩形(加列,不是新表);"选哪把武器"天然承载类型切换,attack_type 轴的营地换型口径原样生效。 - -**缺点**:比甲案多两组等级数据与界面;武器库在界面上要克制呈现(三张卡,不是背包)。 - -**为什么推荐丙**:它在"差异化感知"与"实装复杂度"之间取中点,且**两向可逆**——砍掉独立等级就退化成甲案,加上库存实体就升级成乙案。裁决者可以先取丙,日后按玩家反馈滑向两侧,不需要推倒重来。 - -### 通用必答一:携带数——单持还是多持? - -**呈交:单持**(三案同答)。多持(同时带两把、战斗内轮换)的淘汰依据: -1. 收益小——多武器轮换的战术价值是"面对不同甲型敌人换着打",但这个价值棋盘上的穿刺弹/魔法矢直伤件已经承载(`battle_orchestrator.gd:846-854`,必中全额、自己查矩阵行),轮换武器的增量收益有限。 -2. 代价大——战斗内换武器要新开一套动作语义(算不算行动?耗不耗行动点?战斗结算的流程与伤害单据九栏都没有"中途改类型"的位置,`design/gdd/systems/战斗循环/战斗结算.md` 战斗段一节),牵动行动经济。 - -### 通用必答二:换械时机——营地还是战斗中? - -**呈交:营地内可换,战斗中禁换**(三案同答)。依据:战斗段结算次序固定(主角先手→逐敌还手→地图账),伤害单据的类型在开单时声明、管线内不变;战斗中换械=给结算管线新开一个"类型可变"维度,收益(每场战斗微调克制)不成比例于管线改动与测试面。这个边界与"克制应对从局内攒弹药改为冒险前配型"的既有定稿方向一致(`战斗结算.md`:原"挖掘攒充能换攻击分量"的弹药系统 2026-09-19 已整体退役,克制策略归冒险前配置)。 - ---- - -## 四、攻击类型轴关系 - -**这一节回答的问题**:武器带来的攻击类型,和棋盘上现存的穿刺/魔法直伤奖励件,是什么关系? - -### 现状(复核结论) - -游戏里现在有两处"类型":①主角基础攻击的类型(attack_type 字段,休眠中,F2)——决定主角先手攻击查克制矩阵的哪一行;②棋盘格面资源的类型(穿刺弹/魔法矢等奖励件)——必中全额、自己查矩阵行。两处消费**同一张 3×3 克制矩阵**(`settlement.tres:31-47`,九格满格,结构冻结)。 - -### 裁点 2a:共存方式 - -- **甲=叠加(推荐)**:武器类型只改主角基础攻击分量查矩阵的行;格面直伤件照旧独立。两根轴互不干涉、共用一张矩阵。零新机制。 -- **乙=替代**:武器类型接管一切类型化伤害(选法杖后穿刺弹也按魔法算)。**淘汰依据**:格面资源是地图侧的独立九型字典(定义文档 [地图战斗资源](../design/gdd/systems/战斗循环/地图战斗资源.md)),把它的类型语义绑到武器上=两个系统强耦合;且没换法杖的玩家,魔法矢这件奖励就没有存在意义了——砍掉内容池换不来任何设计收益。 -- **丙=互斥**:选了某武器,相同类型的格面件不再出现(法杖玩家抽不到魔法矢)。**淘汰依据**:没有设计依据的池子收窄。同类型叠加是构筑加深不是冗余——魔法武器+魔法矢=魔法构筑的一致性,正是差异化的价值所在。 - -**呈交甲,并要求把一句防混述声明写进 [战斗结算](../design/gdd/systems/战斗循环/战斗结算.md) 定义文档**:「武器类型(主角基础攻击的属性)与格面资源类型(棋盘直伤件的属性)是两根独立参数,只共用同一张克制矩阵。」这与战斗结算既有纪律同源(攻击类型是伤害分量的固有属性,定义里不含任何棋盘来源)。 - -### 裁点 2b:要不要加第四种类型或第四种甲型? - -- **甲=不加(推荐)**:矩阵九格结构冻结是既有条款(`战斗结算.md` 参数表"键集 3×3 满格结构冻结")。武器三型正好映射现有三行,矩阵零改动。 -- **乙=加第四甲型**(若原案的"魔法甲"不映射现有 ward):触发矩阵扩成 4×3、战斗结算结构冻结条款修订、敌方甲型数据面重配、专长条件值族扩充——为一型甲付出全链成本。**淘汰依据**见裁点 3a:现役 ward 的中文正名就是"魔法护甲",原案"魔法甲"可直接对号,第四型没有可感知的差异点。 - -### 裁点 2c:专长联动与死条件清理 - -**专长会被武器选择影响**:专长(改规则的奇物件)里有一族"克制面强化"件,条件是"当前对局出现某某对某某的克制 matchup"。武器锻造唤醒类型轴后,玩家选什么武器会改变主角攻击的 matchup 行——选长枪会激活穿刺系专长的价值。这是设计意图内的联动,不是缺陷。 - -**死条件现状**:`feats.tres:29-31` 有一件专长条件值为 magic_vs_magic(魔法打魔法甲)。现行甲型域是 {light, ward, heavy},没有"magic"这个甲型,所以对局中的 matchup 字符串永远不会是 magic_vs_magic——**这件专长在现行数据下永远无法触发,等于一张废卡**。三个处理方案: - -- **甲=删行**(随实装批执行):先例=2026-10-06 内容数据驱动批删除旧挂点枚举(06-待办清单在册)。代价:若日后甲域真的扩第四型,要重新加回。 -- **乙=保留+文档注记**:零代码。代价:一张永假废卡继续占专长池配额,玩家视角是坏体验。 -- **丙=改值复活(推荐,归实装批执行)**:把条件值改成活条件——候选 magic_vs_ward(魔法打魔法护甲;现役 L2 Boss 蛛化卓尔精灵正是 ward,`enemy.tres`),一行 value 字段的改动把废卡变成"Boss 战专长"。代价:改专长行为=行为变更,必须随实装批一起做、配行为测试;且本提案阶段不改值(纯文档边界)。 - ---- - -## 五、护甲实体化 - -### 现状(复核结论) - -AC(护甲等级)单值现役生效(`hero.tres:9`=10;敌方攻击掷点对它的减伤判定在 `battle_orchestrator.gd:172` 合流入参)。甲型结构位已登记休眠(`hero.tres:10`,域 {light, ward, heavy})。休眠原因=敌方攻击不带类型、不查矩阵,是结构性的(《敌人系统》主角字段表注记:"轴生效待敌方技能攻击类型批"——这个批次在敌方系统侧有登记方向,未排期)。 - -### 裁点 3a:甲域定名映射——"魔法甲"是什么? - -- **甲=魔法甲就是 ward(推荐)**:现役甲域第三型 ward 的中文正名就是"魔法护甲"(《敌人系统》敌方字段表:甲域 {轻甲, 魔法护甲, 重甲})。用户原案的"魔法甲"直接对号入座。零扩域、零扩矩阵、零数据面改动。 -- **乙=新造第四甲域**:"魔法甲"作为独立于 ward 的新甲型。触发裁点 2b 乙案的全部成本(矩阵 4×3、结构冻结条款修订、敌方数据面、专长条件值族)。**淘汰依据**:"魔法护甲"和"魔法甲"在玩家语感上是同一个东西,第四型给不出可感知的差异点。 - -### 裁点 3b:甲型差异的可感面(对原案的显式收窄处) - -**必须诚实写清的边界**:敌方攻击现行无类型、不查矩阵(`damage_controller.gd:19/:513`)。所以在现行边界下,换甲型在"克制"维度上没有任何数值效果——"魔法甲怕穿刺打"这类克制反向要等敌方攻击带类型的批次才生效。两个处理方案: - -- **甲=现在就落甲型结构(推荐)**:甲型差异以"基础 AC 各异"为现行唯一可感轴(这正是原案明说的诉求),克制反向作为前瞻结构位随轴休眠。理由:①基础 AC 差异本身就有构筑价值(重甲硬、魔法甲便宜灵活之类的取舍);②甲型位是现役代码已留好的扩展缝,现在立结构、敌方攻击类型批到来时零结构改动;③不需要动矩阵就能完整兑现原案。 -- **乙=等敌方攻击类型批一起落**:甲型推迟到克制反向能生效时再上。**淘汰依据**:无限期推迟原案明说的"基础 AC 各异"诉求;且把两件独立的事(甲型结构、敌方攻击类型)捆绑排期,违背"实装批另行立项"的本批边界。 - -**基础 AC 锚点(provisional)**:轻甲 =10(现役主角值,不动);重甲 =12(+2);魔法甲 =9(−1)。量纲依据:现役掷点折扣档为缺口 1-3 打七五折(`settlement.tres:50-51`),每 1 点 AC 的期望减伤约 3.75%量级(`settlement.tres:74` EV 复算锚:AC10 期望减免 21.25%、AC11 25%)。终值全部归 Alpha 收官 M4 数值批。 - -### 裁点 3c:护甲的升级路线结构 - -- **甲=每型甲一条独立等级路线(推荐)**:轻甲路线=现役护甲锻造行改造(每级 AC +1);解锁重甲/魔法甲后各自从 1 级练起、各自升级、各自满级特效;"当前甲"单选与"当前武器"单选同构。与原案"一开始只有轻甲(只提 AC)→解锁后基础 AC 各异→各自可续升→各带满级效果"逐句对齐。 -- **乙=单一 AC 路线+甲型当门槛**:AC 一条轨,甲型只是轨上的里程碑(练到 3 级才能换重甲)。**淘汰依据**:三型基础 AC 不同,一条数字轨表达不了三型各异的基础值;原案明说"解锁之后基础 AC 值可能是不一样的"。 - -### 与《奇物》现行口径的显式关系(红线第 5 条) - -《奇物》现行口径:属性值提升分类 = 生命上限/AC/攻击三轴**封闭**("只有这三项,多一项就要走文档修订",§1.2);获取渠道 = 局内(奖励节点/商店训练/共享池),关间不清零;渠道开关表已有用户定案。 - -**本提案的关系 = 分层并存,互不侵入**:武器/护甲锻造动的是**账号层基础值**(营地永久强化,冒险开始时经参数面传给战斗——《元进度》执行口径:开局按存档内永久强化状态算好覆盖参数逐字段传入);奇物属性件动的是**单次冒险加算区**(关间不清零、冒险结束清零——[冒险结构](../design/gdd/systems/冒险循环/冒险结构.md) 统一冒险加算区四轴)。本提案不扩奇物三轴、不动渠道开关表、不往局内池加任何东西——武器锻造不是奇物,是永久强化的内容改造。入战 AC 的三层语义清晰可加:甲型基础 AC + 营地甲路线等级加值 + 局内加算区累加。 - ---- - -## 六、成长曲线定位(回应 F6) - -用户反馈原文(playnote.md:5):前期成长系数不能太高——没升级前跟不上怪物难度提升,能力解锁后才能匹配。武器锻造恰恰是执行这条曲线诉求的最佳载体,因为它同时握着两样东西:**数字成长的速率**(每级加多少)和**能力解锁的节奏**(什么时候拿到新武器/新甲/满级特效)。 - -### 裁点 4a:成长曲线形状 - -- **甲=前缓后陡(推荐)**:低等级每级攻击增量小(+0 或 +1 量级),高等级每级 +1 或 +2 量级。前期刻意压数字——玩家靠挖掘技巧过关(这正是 F6 要求的"没升级前跟不上",是特性不是缺陷);能力解锁(新武器、满级特效)成为曲线的台阶。 -- **乙=线性每级 +1**:现役锻造就是线性。**淘汰依据**:与 F6 反馈零呼应——数字匀速涨,"能力解锁"的节奏感全靠特效单腿支撑,曲线本身不参与。 -- **丙=阶梯解锁制**:平时每级 +0,每两级跳 +2。**淘汰依据**:数字体感跳跃,中间等级像白升级;provisional 调参空间最小(只有跳变点可调)。 - -**呈交甲,但只锁形状方向不锁数值**——曲线参数全体 provisional 归 M4 数值批。 - -### 裁点 4b:解锁节奏从哪来 - -- **甲=锻造内自闭环(推荐)**:新武器/新甲型的解锁条件在锻造系统内部(练满前一档或花一次性解锁价,见第七节);满级特效是每条路线的终点奖励。这是原案的精神("最高等级时拿到额外效果""最高等级时解锁重甲/魔法甲")。 -- **乙=绑关卡进度**:L1 通关解锁长枪之类。**淘汰依据**:①把永久强化做成关卡奖励,与"营地 = 消费钥石的成长枢纽"的现役定位(《元进度》营地定义)冲突;②首通关卡前玩家零武器选择,第一局体验被绑死在剑上——而第一局恰恰是教学局。 - -### 裁点 4c:与难度曲线的匹配义务(显式登记,不在本批执行) - -锻造改的是账号层成长,会直接平移全部关卡的通关率——这是数值敏感变更。**显式登记义务**:实装批必须带改前/改后同随机种子配对读数验证难度影响;统一调整归 Alpha 收官 M4 数值批(未排期)。本提案零数值投值、零模拟跑批(纯文档批零测试纪律)。 - ---- - -## 七、经济与解锁 - -### 裁点 5a:柜台承载——武器锻造放哪? - -现状:营地三个柜台 = 属性锻造(生命/攻击/护甲三档直购)/ 道具工坊 / 奖励格图鉴(《元进度》营地三柜台节)。 - -- **甲=就地改造属性锻造柜台(推荐)**:柜台改名"武器与护甲锻造"——攻击行改造为武器锻造区(三件武器路线),护甲行改造为护甲锻造区(三型甲路线),**生命行原样保留**(原案没有覆盖生命,保留是唯一不失义的处理)。三柜台结构不动,营地界面零重排。 -- **乙=新开第四柜台**:武器锻造独立成柜,属性锻造柜台的攻击/护甲两行退役。**淘汰依据**:属性锻造被拆剩一行生命,柜台语义残废;营地从三柜台变四柜台=界面结构改动;"锻造"两个字同时出现在两个柜台(外加商店货架的"攻击锻升","锻"字已有三处)——词汇更乱。 -- **丙=塞进道具工坊**:**淘汰依据**:工坊职责已有明确归属(秘宝解锁与同调装配,《元进度》形态沿革节),塞武器=职责混装。注(v1.1 订正):工坊与武器锻造在货币上同源——工坊现役=通关次数渠道+钥石直购预留(`meta.tres:39-88`),与武器锻造同属营地钥石经济;原「金币定价体系不同源」论据作废,本案淘汰仅依职责混装。 - -### 裁点 5b:等级上限与定价结构 - -- **等级上限**:每件武器/每型甲各自封顶(provisional 锚点 = 5 级量级)。量纲理由:满级特效要"值得等",级数太多会稀释解锁节奏(与 F6 冲突)。终值归 M4。 -- **定价结构**:沿用现役几何递增——每级价格 = 基础价 × 150%^(已购级数)(现役参数:单次 20 钥石、每级涨 150%,`meta.tres:11-24`;《元进度》定价曲线几何递增是已拍板条款)。甲型解锁 = 一次性解锁价(量纲 ≈ 中段几级锻造的花费,provisional)。全部终值归 M4。 -- **货币裁点(v1.1 重组——现役营地一切消费货币=钥石非金币,事实依据见本文文末审查回写段)**: - - **甲=纯钥石(推荐)**:与现役属性锻造/天赋树(2026-10-07 用户裁甲后树全钥石单币)/同调刷新升级同币同柜——原案语义是"攻击力提升改造",即同一笔钱换个花法,语义保真;零新经济面;Boss 击杀掉落产出与几何递增定价配套现成(`meta.tres:26-38`)。 - - **乙=引入局外持久金币余额域**(金币入存档、可付营地消费):**淘汰依据**:须推翻两币分工拍板条款(《元进度》:"GP(金币)= 冒险内一次性属性提升货币——局内清零、不入存档……两币永不互兑",元进度.md:55)+ 新存档域 + 金币出口入账规则一套;与"金币无法带出冒险"(2026-10-07 用户判定)直接相逆。 - - **丙=第三货币锻造币**(独立于钥石/金币的专用锻造币):**淘汰依据**:无产出渠道凭空造经济——产出渠道、兑换规则、存档域、呈现位全套新建,实装量三案最大。 - -### 裁点 5c:材料经济——钥石之外要不要材料/掉落物?(v1.1 前提由金币换钥石) - -**现状钩子**:效果引擎有效果类型 grant_material(`effect_engine.gd:32`;`feats.tres` 四件专长在用,形态=局内材料计数器,如"雷区图谱")。注意:这个钩子现役是**局内**计数器,不是账号层库存——提案若引入账号层锻造材料,是全新经济面(存档域、获取渠道、囤积规则全要新建)。 - -- **甲=纯钥石零材料(推荐)**:零新经济面。原案全文只字未提材料;武器差异化的价值轴是类型与特效,不是采集刷料。(v1.1 注:原「不要材料」结论存活,计价前提由金币换钥石。) -- **乙=钥石+材料**:敌杀掉落锻造材料,高阶解锁或满级特效需要材料(grant_material 账号层化)。**代价(不淘汰,并列呈交)**:新存档域+新掉落配置+囤积/回收规则;且"局内材料计数器"与"账号层锻造材料"同名易混,呈现层必须区分两个概念。 -- **丙=纯材料**:钥石买不了武器,只有材料能锻。**淘汰依据**:与现役钥石直购脱钩=换系统而非改造;双货币双定价体系,实装量三案最大。 - -### 裁点 5d:配比预设挂起批的边界注记(强制条目) - -冒险前资源配比预设批已于 2026-10-05 用户令挂起冻结(06-待办清单在册:"重启须用户另行发令",八项裁决与需求卡片全部冻结保留)。**边界声明**:该批的预算点数、容量柜台、克制注记三面都会直接受"武器类型轴"与"柜台结构"变更影响——本提案**不预设该批的重启口径、不占用其任何冻结裁决、不改其冻结文本**。若提案裁决通过且实装落地后该批重启,其需求卡片须显式核对两处交汇面:①预算包络是否把武器类型计入克制注;②容量柜台与武器锻造柜台(同在营地)的资源竞争关系。同理,裁点 2c 若裁"改值复活 magic_vs_ward",也只登记不预设——玩家可控类型增多会改变配比博弈面,该影响归配比批重启时统一核对。 - ---- - -## 八、满级特效清单框架 - -**原则**:每件武器/每型甲满级各 1-2 条特效;逐条标注数据驱动兼容性——**复用现役效果引擎效果类型(24 种闭集,`effect_engine.gd:31-35`)= 实装批零新效果代码**;需要新效果类型或新参数位的 = 实装批代码面,显式标出。 - -下表是**框架示意**(每格给量纲方向),不是承诺清单——终选清单与数值归实装批需求卡片+M4 数值批: - -### 裁点 6:特效清单的实现深度(三案并列) - -- **甲=全复用现役效果类型**:只从零代码候选里选(破甲/先手连击/开局魔法矢/反震/护盾),剑与轻甲改用复用型特效或减配。代价:清单表现力受限——"暴击面扩大""折扣档放缓"这类现役表上恒定的参数做不进特效。 -- **乙=复用为主+三条新参数位(推荐)**:上面框架表的完整形态——四条零代码、三条需新参数位(暴击面/折扣档/护盾量进战斗开场参数),后者是实装批的小改代码面(改表值常量读法,不加效果类型词)。代价:实装批多三处代码改动与对应测试。 -- **丙=引入新效果类型**:为特效往引擎效果类型闭集加新词。**淘汰依据**:效果类型闭集是高代价变更面(加载校验、文档词表、回归测试三处连带),本框架所有候选特效都能用现有词表或参数位表达,无一真正需要新词——为表达便利付引擎级代价不成比例。 - -| 件 | 满级特效候选(量纲方向) | 数据驱动兼容性 | -|---|---|---| -| 剑 | 破甲:命中重甲时克制修正额外 +1 点量级 | 复用现役——modify_ticket 效果类型+matchup 条件算子均已在册(`effect_engine.gd:32/:46`),零新代码面 | -| 剑(第二条) | 暴击面扩大:暴击骰面 −1 量级 | **需新参数位**——暴击面现役是表上恒定值(`settlement.tres:52`),武器级可变=实装批小改(标红) | -| 长枪 | 先手连击:战斗开场追加一次普攻量级的攻击 | 复用现役——attack_segments 效果类型在册(`effect_engine.gd:32`),零新代码面 | -| 法杖 | 开局魔法矢:每场战斗开场自动对最前敌人放一次魔法直伤(必中全额量级) | 复用现役——spawn_spell 效果类型在册,零新代码面(次数/回合数 provisional) | -| 轻甲 | 灵巧:掷点折扣临界档再缓一档量级 | **需新参数位**——折扣档现役读表恒定(`settlement.tres:51`),标红 | -| 重甲 | 反震:被命中时对攻击者反贴 1 点量级伤害 | 复用现役——spike 效果类型在册,零新代码面 | -| 魔法甲 | 护盾:每场战斗开场自带若干层护盾(X 层量级) | 复用现役——shield 效果类型在册;护盾层数进战斗开场参数=实装批桥接面(标黄) | - -**呈交立场**:特效清单走"复用为主"——上表七条中四条零新代码、三条需新参数位/桥接(标红/标黄)。需要新**效果类型**(往引擎闭集加词)的方案一件都没有:引擎闭集动词表是高代价变更(校验面+文档面+回归面),本框架刻意避开。 - ---- - -## 九、数值框架(全部 provisional) - -**总承诺**:本提案零数值投值。下表全部是量纲与锚点,终值统一归 Alpha 收官 M4 数值批(未排期)。实装批立项前不动任何数据表行值。 - -| 项 | 量纲/锚点 | 锚点出处 | 归宿 | -|---|---|---|---| -| 武器成长曲线 | 前缓后陡:低级每级攻击 +0/+1 量级,高级 +1/+2 量级 | 现役攻击锻造 +1/次(`meta.tres:16-17`);主角基础攻击 =2(`hero.tres:8`) | M4 | -| 甲型基础 AC | 轻 10(现役不动)/ 重 +2 / 魔法甲 −1 | 主角 AC=10(`hero.tres:9`);每 1 AC ≈ 3.75% 期望减伤(`settlement.tres:74` EV 锚) | M4 | -| 等级上限 | 每件/每型各自 5 级量级 | 满级特效节奏需要(F6) | M4 | -| 升级定价 | 基础价 20 钥石 × 150%^(级数),几何递增〔该参数本系现役钥石计价——meta.tres:11-24 经 meta_progression.gd:59 钥石消费口生效,量纲不变〕 | 现役参数沿用(`meta.tres:11-24`、《元进度》几何递增条款) | M4 | -| 甲型解锁价 | ≈ 中段几级锻造花费的一次性价 | 同上量纲系 | M4 | -| 满级特效值 | 各 +1 点/若干层量级,逐条配 | 第八节框架表 | M4 | -| 数值验证义务 | 实装批改前/改后同种子配对读数;统一调整归 M4 | 裁点 4c | M4 | - ---- - -## 十、迁移面与裁点汇总 - -### 迁移面(只描述方案,不执行——红线第 3 条) - -1. **`meta.tres` forge_products(`meta.tres:7-25`)**:攻击行 → 武器路线改造(吸收,随裁点 5a 裁决改名);护甲行 → 护甲路线改造;生命行保留原样。同表 boss_keystone / workshop_items / conversion / attune_refresh_upgrade 四行族零触碰。 -2. **`shop.tres` pool_shelf 现役六行(`shop.tres:68-92`)**:局内货架的"攻击锻升/护甲锻升"与营地武器锻造是两层并存(局内层进冒险加算区、账号层是永久强化——现役分工:《冒险结构》统一冒险加算区节、《元进度》执行口径节)。**提案不动行集**;唯一迁移裁点 = 现名是否去"锻"字防混淆(甲=保留现名零改动 / 乙=改"训练·攻击"式中性名——呈现面文字变更,归实装批呈现细节,随裁决)。 -3. **`shop.tres` products 行族(`shop.tres:36-57`)**:**遗留代码路径**(只在对照模式承载,`shop_table.gd:16`、`shop.tres:107-109`)。本提案零触碰;它的退役是既有在册义务(归对照锚换代批),不是本提案新增。 -4. **`hero.tres` 两根轴(`hero.tres:10-11`)**:唤醒语义 = 营地选中的武器/甲型写轴。这与 2026-09-18 营地扩展前置批的既有设计注记(`hero.tres:21-27`:"营地永久强化换攻击类型 = 改此字段")逐字同源——**不是新设计,是既有预留位的启用**。 -5. **`feats.tres` magic_vs_magic 死行(`feats.tres:29-31`)**:三案随裁点 2c 裁决(删/注记/改值复活),执行归实装批。 -6. **术语登记**:武器/甲型的正式名("魔法甲"="魔法护甲"=ward 之类的定名)随裁决定案后,实装批随批登记 `design/registry/entities.yaml` 术语条目。本提案阶段零登记(名字未定)。 - -### 全裁点一览表(供盘问轮逐点拍板) - -| # | 裁点 | 甲 | 乙 | 丙 | 提案推荐 | -|---|---|---|---|---|---| -| 1 | 武器实体化程度 | 纯升级路线(一条轨+类型标签) | 武器槽实体(装备+库存) | 当前武器单选+每件独立路线 | **丙** | -| 1a | 携带数 | 单持 | 多持(轮换) | — | 单持 | -| 1b | 换械时机 | 营地内可换 | 战斗中可换 | — | 营地内 | -| 1c | 换械是否收费 | 免费 | 小额收费 | — | 免费 | -| 2a | 类型轴共存 | 叠加(两根独立轴共用矩阵) | 替代(武器类型接管) | 互斥(同类型件不出现) | 叠加 | -| 2b | 矩阵扩维 | 不扩(九格冻结) | 加第四甲型 | — | 不扩 | -| 2c | 死条件 magic_vs_magic | 删行 | 保留+注记 | 改值复活(magic_vs_ward) | 丙(执行归实装批) | -| 3a | 甲域定名 | 魔法甲=ward(现役域对号) | 新造第四甲域 | — | 甲 | -| 3b | 甲型可感面 | 基础 AC 差异先行,克制反向留结构位 | 等敌方攻击类型批一起落 | — | 甲 | -| 3c | 护甲升级结构 | 每型独立路线 | 单轨+甲型当门槛 | — | 甲 | -| 4a | 成长曲线形状 | 前缓后陡 | 线性 | 阶梯 | 甲(只锁方向,值归 M4) | -| 4b | 解锁节奏 | 锻造内自闭环 | 绑关卡进度 | — | 甲 | -| 5a | 柜台承载 | 就地改造属性锻造柜台 | 新开第四柜台 | 塞进道具工坊 | 甲 | -| 5b-1 | 货币 | 纯钥石 | 局外金币余额域 | 第三货币锻造币 | 纯钥石 | -| 5c | 材料经济 | 纯钥石(零材料) | 钥石+材料 | 纯材料 | 纯钥石(零材料) | -| 6 | 满级特效 | 全复用现役效果类型 | 含新参数位(三条标红) | 引入新效果类型 | 乙(复用为主,零新效果类型) | -| 8a | 货架现名 | 保留"攻击锻升"现名 | 改"训练·攻击"式中性名 | — | 随实装批呈现细节定 | - ---- - -## 附:本提案的裁决方式 - -每个裁点独立拍板,互不绑死(推荐列只是提案立场,不是打包方案)。裁决结果落 06-待办清单「武器锻造系统」专条与路线图卡③行;裁决通过后实装批另行立项,以本提案+裁决记录为需求卡片输入。 - ---- diff --git a/reports/测试审计-单测与验收门禁标准-2026-10-15.md b/reports/测试审计-单测与验收门禁标准-2026-10-15.md deleted file mode 100644 index e247f126..00000000 --- a/reports/测试审计-单测与验收门禁标准-2026-10-15.md +++ /dev/null @@ -1,344 +0,0 @@ -# 测试审计 — 单测覆盖与验收门禁标准(2026-10-15) - -> 审计会话产物。**只审计不改实现**(讨论期只改文档铁律),本文件是结论载体,不是执行批。 -> 加载能力:`godot-builder`(headless / CI 编排 / 探针闸)+ `godot-auditor`(never-list 执法、类型与信号衰减扫描)。 - -## 〇、审计口径(先声明,避免误读结论) - -| 项 | 口径 | -|---|---| -| 是否跑测 | **未跑**。本会话属审计类,不在 AGENTS.md「测试执行两入口」(迭代 loop / 用户点名)之内,全程零游戏调用 | -| 结论来源 | 静态阅读 + 仓库内既有登记数据(README / 架构文档 / AGENTS.md / active.md)+ 两个 deterministic 扫描器实跑(类型闸、信号衰减闸,只读) | -| 未验证项 | 「当前是否全绿」不在本次范围;本报告不主张任何红绿状态 | -| 覆盖判定 | 以「测试/冒烟源码中**直接指名**该类或该文件名」为据。**不排除经调用链的间接覆盖**——故「零指名」应读作「无直接断言面」,不等于「逻辑完全未被执行」 | -| **证据等级** | 见 §九。**A 类结论=代码/配置直读,确凿;B 类=仓库文档登记可查;C 类=静态推断,需实测坐实**。下表凡标 ⚠ 者为 C 类 | -| **CI 实态** | **已移除(2026-10-15 用户令)**。`git remote -v` 无输出=无远端;`.github/workflows/` 预接线件从未执行过一次,用户裁「CI 没有用、后面也不打算用、不留死配置」⇒ **全数删除**。本报告凡提「CI」处,均为**删除前的历史状态描述**;现行门禁一律读「**本地闸**」 | - ---- - -## 一、结论速览 - -| 诉求 | 现状 | 判定 | -|---|---|---| -| ① 最单元的逻辑代码有单元测试 | `game/tests/unit/` 27 套件全登记入 `run_all.gd` manifest(零孤儿测试文件),412 处断言;core 层 28 个模块中 **4 个零直接指名** | **基本成立,有明确缺口** | -| ② 整体模块有接口测试 | `tests/integration/` 空壳已删;契约面仅 `system_contracts_test.gd` 15 断言单点 | **不成立 —— 集成层零承载** | -| ③ 系统级 UI 与场景切换测试 | UI 有 4 探针入 CI(结构/对比度/布局/输入),但**判定靠 PASS 串**;场景(屏壳)切换在 CI 上**零断言** | **半成立 —— 有面无判、切换无覆盖** | -| ④ 门禁不是「只冒烟」 | CI 三处判定缺陷(不查退出码 / 无 SCRIPT ERROR 闸 / 无断言数下限) | **不成立 —— 现状语义就是「没崩即过」** | - -**一句话**:仓库里其实已经有很强的一套东西(`smoke_all.gd` 6128 行 / 967 处断言点 / 全系统覆盖,且**在本地轻闸里每次变更都跑**),但它叫「冒烟」;CI 里跑的那套叫「逻辑冒烟」的其实是单元测试——**而 CI 从未通电**(无远端)。术语与强度倒挂,加上门禁全靠人记得跑,共同构成了「冒烟观念」的制度性源头。 - ---- - -## 二、L1 — 单元层盘点 - -### 2.1 资产面 - -| 指标 | 实测 | -|---|---| -| 单测文件 | 27 个(`game/tests/unit/` 五族:battle_settlement / contracts / core / data_config / presentation) | -| manifest 登记 | `run_all.gd:_suites()` **27 条,与磁盘文件一一对应**(含 `example_test`;无孤儿、无漏登) | -| 断言点 | 412 处(`Kit.check` / `Kit.check_quiet` / 各套件本地 `_check()` 合计) | -| 断言出口 | 24 套件走 `test_kit.gd`,**4 套件自建**(`tree_requires_level` / `tree_rules_v11` / `config_db_fatal_oob` / `pacing_queue`) | -| 致命类用例 | 3 条 `expect=1`(config_db_fatal_oob / treasure_target_violation / reward_dangling_treasure),独立进程判退出码 | - -### 2.2 断言质量抽查(正向证据) - -抽查的三个套件是真值断言、且**带反例**,不是「存在即通过」: - -- `tree_requires_level_test.gd`:schema 四拒(悬空键 / 阈值 <1 / 类型错 / 合法零违例)+ 判定三态(未达拒购 / 无阈值不受影响 / 达标开放)+ 投影两态(`gate_text` 拼装与清空),逐项比对期望字符串。 -- `tree_rules_v11_test.gd`:四键合成行与 `rebuild` 实装面**逐字段行数对拍**(实现删分支/漂移即红),聚散两键断零行,并显式留了「反例护栏失效」断言——即**测试自身也在被测**(防假绿)。这是本项目里质量最高的一件。 -- `damage_math_test` / `damage_controller_test`:纯函数 + 伤害单据五配方,34 断言。 - -⇒ 单元层**不存在**「普遍只判非空」的问题。已扫 `!= null` / `== null` 类断言 36 处,散布于各套件,未构成主体。 - -### 2.3 覆盖缺口(core 层直接指名 = 0) - -| 模块 | 生产码引用 | 单测/冒烟直接指名 | 备注 | -|---|---|---|---| -| `minesweeper_rules`(MinesweeperRules) | 5 处 | **0** | 挖矿核心规则(揭示/扩散判定),无直接断言面 | -| `status_container`(StatusContainer) | 5 处 | **0** | 状态(buff/debuff)生命周期容器 | -| `config_db_resolver`(ConfigDbResolver) | 4 处 | **0** | 表解析 | -| `config_db_cross_validator`(ConfigDbCrossValidator) | 3 处 | **0** | 跨表护栏本体(config_db_test 只间接经 `db.load_tables` 触发) | - -另有 `skill_lottery`(4 处引用 / 4 处指名 ✓)、`board_data`(42 / 4 ✓)、`reward_economy`(smoke 24 命中 ✓)已补位。 - -### 2.4 呈现层(presentation)缺口 - -`game/presentation/` 共 49 个 `.gd`(含 views 28 / scenes 3)。**有单测直接指名的约 11 个**:main、modal_flow、modal_stack、run_scope、treasure_interact_controller、board_view、camp_overlay_view、curio_pick_overlay、node_overlay、ui_kit、row_pacer(经 `pacing_queue_test` 承载,命名不对应)。 - -零单测指名的 **38 个**,其中高频/高风险的包括:`hud_presenter`、`battle_narrator`、`ui_assembler`、`stage_world`、`board_world`、`board_zone_view`、`board_cell_unit`、`standee_body`、`status_panel`、`status_slot_row`、`toast_stack`、`settings_store`、`camp_shop_presenter`、`curio_interact_controller`、`debug_session`、`adventure_screen` / `camp_screen` / `menu_screen`(三屏壳)等。 - -承载情况:其中 `board_view`(7) / `ui_kit`(4) / `status_panel`(5) / `standee_body`(2) / 三屏壳(1-2) 被 `game/smoke/*` 探针指名——**有覆盖,但覆盖的是结构 / 对比度 / 布局几何,不是行为**。 - ---- - -## 三、L2 — 接口 / 集成层盘点 - -**结论:零承载。** - -- `game/tests/README.md` 自述:`integration/` 目录空壳已于迭代 19 卡① T4 删除,仅留章程句。章程登记的四类待建用例 —— Core-Core 集成、回放确定性(同种子步骤流逐行相同)、存档/RNG 快照往返、A1×A2 双包含端到端 —— **全部未落地**。 -- 契约面唯一实体:`tests/unit/contracts/system_contracts_test.gd`(15 断言)。它是**单元级契约断言**,不是跨系统集成执行。 -- 有一件**已建但不入任何闸**的重器:`tools/parity_anchor.py` + `game/tests/evidence/parity/anchor.json` —— 迭代 27 建的确定性对拍锚,三变体(base / attune / shop)× 20 局 × RUNSUM+JSONL 双面,字节级逐位一致。功能上它**正好是章程里「回放确定性」那一类集成用例**,但它是 `tools/` 下的手工工具,README 未登记、CI 零调用、闸表未收录。 - -⇒ 这是三层里**缺口最大的一层**,且已有现成零件未被收编。 - ---- - -## 四、L3 — 系统层(UI 行为 + 场景/屏切换)盘点 - -### 4.1 「场景切换」在本项目的真实形态 - -全库 `change_scene` / `change_scene_to_file` / `change_scene_to_packed` **零命中** —— 项目不用引擎级场景切换,改为 `main.gd` 三屏壳(`menu_screen` / `camp_screen` / `adventure_screen`)+ `visible` 互斥编排(迭代 27 T1 场景拆分)。 - -⇒ 因此「场景切换测试」在本项目应落实为:**屏壳显隐互斥 + 流程编排(菜单→冒险→营地→战斗→结算→回营地)的状态断言**。 - -### 4.2 现状 - -| 面 | 载体 | 是否入 CI | -|---|---|---| -| 屏壳拆分结构 | `smoke_all.gd:_smoke_scene_split()`(:147) | **否**(smoke_all 不进 CI) | -| 屏切换行为走查 | `ui_click_probe.gd`(含 `_exit_level_screen()`、结算终局、放弃二击) | **否**(归 `probe_gate --click`) | -| UI 结构(互压/等宽/出屏/零尺寸) | `ui_struct_probe` | 是 | -| UI 文字对比度 ≥4.5:1 | `ui_contrast_probe` | 是 | -| 视口/四区几何恒定(1280/1920 两档) | `ui_layout_probe` | 是 | -| 真实 `push_input` 输入路径十七断言 | `ui_input_probe` | 是 | -| 视觉 golden 逐像素 | `render_gate.sh` + `golden_compare.py` | **否**(窗口化,禁入 headless) | - -⇒ **CI 上的场景/屏切换断言 = 0**。四个入 CI 的探针全是**静态结构与几何**,没有一件断言「点了这个钮以后屏切了、状态对了」。 - ---- - -## 五、门禁标准审计(本次重点) - -### 5.0 前置事实:无远端、无 CI,门禁 = 本地闸(A 类,实测;2026-10-15 后续处置已执行) - -`git remote -v` 无输出——**仓库无远端**。`.github/workflows/tests.yml` L4 自陈:「仓库已初始化 git(尚无远端)——本 workflow 在首次 push 到 GitHub 后自动生效」。⇒ 这份配置**从未在 GitHub Actions 上执行过一次**,是 2026-10-08「CI 预接线批」留下的预接线件。 - -**用户 2026-10-15 裁决**:「CI 是没有用的,后面也不打算去用它」⇒ **`.github/workflows/` 全数删除,不留死配置**(已由 `ci-removal-20261015` 分支执行)。同时用户立规:**不建、不留、不参考任何 CI 配置;改门禁只改本地闸**(已入 AGENTS.md 硬性红线)。 - -⇒ **验收门禁自始至终就是本地闸**,由 AGENTS.md 纪律驱动人工执行: - -| 本地闸件 | 是否每天在跑 | 判定是否有洞 | -|---|---|---| -| `tools/run_smoke.py`(冒烟三闸) | 批末四闸之一 | **无洞**(退出码+摘要失败数+脚本错误扫描) | -| `game/tests/run_all.gd`(27 套件) | 批末四闸之一 | **有洞**(无脚本错误扫描、无断言下限) | -| `tools/probe_gate.sh`(探针段) | 批末/轻闸 | **有洞**(四件不查退出码) | -| `tools/render_gate.sh`(golden) | 周期重闸 | 容差带宽,时效存疑(§5.5) | -| `game/tests/static_scan.sh` | 双跑(本地+CI) | 八条域只落地四条(§5.6) | - -**这对前文的修正**:§5.2 列的三处判定缺陷**不是"CI 的缺陷",是本地闸的缺陷**——它们此刻就在生效,每天在跑。CI workflow 只是把同一套有洞的判定**抄了一份到 Linux 形态**(L59-75 的 `run_probe` 明写「镜像 probe_gate 判定逻辑的 Linux 形态」)。 - -⇒ 因此**修判定必须修本地闸**;只改 CI 等于给一台没插电的机器换零件(而该机器现已拆除)。 - -同时暴露一个更尖锐的事实:**没有 CI,意味着"跑不跑闸"这件事没有任何机制约束,全靠人记得。** 本地闸判定再严密,不跑就等于没有。 - -### 5.1 术语倒挂 —— 「冒烟观念」的制度性源头 - -| 名 | 实际是什么 | 规模 | 在哪跑 | -|---|---|---|---| -| 「冒烟」(`smoke_all.gd`) | **系统级逻辑 + 接口 + UI 全栈断言面** | 6128 行 / 967 处 check 调用 / 运行期 **1086 项**(active.md 494 条最新登记,2026-10-15 closeup 批) | 仅本地 `tools/run_smoke.py`;**CI 零调用** | -| 「逻辑冒烟」(`run_all.gd`) | **单元测试套件** | 27 套件 / 412 处断言 | CI | -| ~~「Headless smoke」(CI job 名)~~ | ~~静态扫描以外的全部 CI 验证~~ | — | **已随 CI 删除(2026-10-15)** | - -最强的一层叫「冒烟」,进了 CI 的那层也叫「冒烟」。当所有东西都叫冒烟,团队的验收心智就自然停在「跑一遍不崩」——**这不是人的问题,是命名的问题**。 - -> **v1.2 修正(重要,别被上一段带偏)**:「最强的一层不被执行」这个说法**不准确**。`AGENTS.md:116` 的**轻闸定义首项就是 `run_smoke` 全量**(即 `smoke_all` 1086 项)+ `ui_input_probe`,且「可随变更执行」。也就是说它**确实在跑**,只是靠人记得跑、不在自动门禁里。 -> 准确的结论是:**`smoke_all` 有执行、无自动门禁;`run_all` 有执行、进了 CI(而 CI 未通电)。两者都不缺执行,缺的是「判定可靠」与「机制约束」。** - -「冒烟」一词同时指代逻辑单元测试与系统级断言面(2026-09-12 测试载体决策的遗留词汇),**没有单元 / 集成 / 系统三层概念**;其验收判据只有一句「冒烟 + 探针全绿为提交门」。AGENTS.md:106 的「批末四闸」也沿用同一词汇表。 - -### 5.2 CI 判定语义的三处缺陷 - -**① 探针不查退出码** -`tools/probe_gate.sh` 与 `.github/workflows/tests.yml` 对 `ui_input` / `ui_struct` / `ui_contrast` / `ui_layout` 四件的判定是: - -``` -超时(124/137) → FAIL ; 否则 日志含 "PASS" 串 → 绿 -``` - -**退出码完全不参与判定**(全库仅 `ui_click_probe` 查了退出码)。探针若在打印 PASS 之后才崩溃/遇脚本错误,CI 判绿。这是「只跑不判」的标准形态——与测试载体决策自己在 §Migration/§Risks 里警惕的路径一致。 - -**② 无 SCRIPT ERROR 闸门(最关键)** -`tools/run_smoke.py` 有三闸:退出码 + 摘要失败数 + **扫描输出中的 `SCRIPT ERROR` / `Parse Error`**。本地生效。 - -CI 直接调 `godot --headless --script res://tests/run_all.gd`,**③闸缺失**。 - -而项目自己记录过这条灾害(AGENTS.md:21、`run_smoke.py` 头注):*GDScript 运行时脚本错误不改变进程退出码、不计入失败数,且会静默中断所在函数的剩余断言 —— 首跑「309 项 0 失败」实为断言未跑全*。真实复发两次有档可查: - -- active.md 476 条:迭代 21 基线冒烟 864/0,但 `run_smoke` 判 FAIL(SCRIPT ERROR 51 行,`_flush_pending_reveals` 空 source_class 触发)。 -- active.md 494 条坑册②:`--script` 模式编译器不解析裸 autoload 标识符 → **`pacing_queue_test` 实证 Parse Error**。 - -⇒ 第二次那条尤其要紧:它证明 **Parse Error 就发生在 CI 会跑到的套件里**。有 `run_smoke` ③闸时它被当场抓住;没有③闸的 CI 上,它只会表现为"断言少跑了一截、退出码仍是 0"。 - -⇒ **这条灾害路径在 CI 上完全敞开**,且 CI 的 `run_all.gd` 是逐套件子进程模型,任一套件脚本错误中断 → 该套件 `failures` 仍为 0 → `Kit.finish` 返回 0 → CI 绿。⚠(C 类:判定链为代码直读,但「CI 上实际会假绿」需造反例实测坐实,见 §九 T0-①) - -**③ 无断言数下限闸** -`test_kit.gd:finish()` 在 `failures == 0` 时打印 `all green (%d checks)` 并 `quit(0)`。`checks == 0` 同样算绿。套件因异常早退、或 manifest 被误删条目,CI 都不红。 - -### 5.2b 附带缺陷:CI 注释漂移(新发现,A 类) - -`.github/workflows/tests.yml` L7 头注写的是「四探针=ui_input/ui_struct/ui_contrast/ui_layout」,但 L52 / L55 的执行段注释仍写「轻闸**三**探针」,实际 L72–L75 跑的是**四个**。注释与代码不符,是「注释即文档」纪律的破口——后续维护者照注释改闸极易漏件。 - -### 5.2c 附带缺陷:示例测试在 CI 占位跑(A 类) - -`run_all.gd:29` 把 `example_test.gd`(26 行 / 2 断言,样板件)登记进生产门禁。它每次实跑并计入「27 套件」,既虚增计数又占一个子进程。 - -### 5.3 自动门禁 弱于 本地重闸 - -> **v1.3 订正**:本节原有的「本地重闸 vs CI」对比表**已作废**——CI 侧于 2026-10-15 全数删除,不再存在第二套门禁。作废表保留于下仅供历史沿革,勿据此决策。 - -| | 本地重闸(AGENTS.md:116,唯一门禁) | 已删除的 CI 配置(历史) | -|---|---|---| -| 静态扫描 | S1–S4 + S9 + S10 | 同(static-scan job,从未执行) | -| 单元 | run_all 全套 | 同 | -| 系统逻辑 | **run_smoke 全量(1086 项,轻闸首项)** | 无 | -| 探针 | 轻闸 + `--click` + `--full`(八件) | 仅 4 件 | -| 渲染 golden | render_gate | 无 | -| doc 三闸 / 熵增闸 | 有 | 无 | -| 确定性对拍 | parity_anchor(手工) | 无 | -| SCRIPT ERROR 闸 | 有(run_smoke ③) | 无 | - -⇒ **唯一的门禁就是本地闸,且它全靠人记得跑。** 这既是好消息(不存在"两套里弱的那一套"问题,改一处即可),也是坏消息(没有任何机制强制"跑"这个动作)。 - -### 5.4 测试资产台账缺口:`game/smoke/` 17 个无宿主脚本(新发现,A 类) - -`game/smoke/` 下 38 个 `.gd` 中,只有 11 个配了同名 `.tscn`(即探针形态);其余 **17 个是无 `.tscn` 宿主的纯脚本**: - -``` -adventure_monte_carlo auto_player battle_driver board_stat_probe -calibrate_boards damage_probe defense_scan dump_battle -experiment_density level_autoplay monte_carlo probe_ui_util -reward_expectation rhythm_batch skill_rhythm_probe smoke_all -verifier_batch -``` - -其中 `smoke_all` 是已知的系统断言面(T0 级,1086 项),`probe_ui_util` 是探针公共库,两者定位清楚。**其余 15 个(MC 模拟、防御扫描、节奏批、标定批、验证批、掉落期望、autoplay 等)性质介于「测量工具」与「测试」之间**:无宿主、无闸、README 未登记、CI 零调用。它们既不入 `run_all` manifest,也不入任何闸表,属于**测试资产台账上的游离件**——无人能回答「它们是否仍在役、是否还能跑、结果由谁判」。 - -### 5.5 golden 视觉基线时效存疑(新发现,B 类) - -`tools/golden_compare.py:11` 载明基准冻结于 **2026-09-30**(v3.5 视觉落码 + 营地终稿 + 分辨率案甲后)。此后迭代 25(全局主题资源挂载、两处局部主题做法退役)与迭代 26(按钮五态、全局纹理过滤 Nearest、三处换墨、光柱 alpha)两次大改呈现层。 - -active.md 只记「render_gate golden 0.00%(登记即结未重制)」——即**基线未随呈现层换代重制仍判 0.00%**。两种可能:① 换代面确实未落在 golden 裁剪区(则基线仍有效);② 基线已失效而容差带(灰度差 >16 占比 ≤2%)宽到吃下了变化(则闸形同虚设)。⚠ 需实测判别(见 §九 T0-⑤)。 - -### 5.6 静态扫描域:登记八条只落地四条(A 类) - -`game/tests/static_scan.sh` 实跑:S1/S2(全局随机+RNG 自建)、S3(warning 级)、S4(core 禁 UI 依赖)、S9(类型闸)、S10(文件头闸)。 - -**S5 / S6 / S7 / S8 四条仍在 TODO 段、从未实现**(脚本末 `# TODO(正式工程落地后精化为扫描配置并升格为硬失败)`)。其中 S5(core 禁裸引用 autoload 全局标识符)恰恰是 active.md 494 条坑册②实证踩过的坑(`--script` 模式 Parse Error)——**有真实反面案例,按 code-conventions「新违规案例必须先补扫描规则再修码」纪律,本就该补规则**。 - -⇒ 扫描域半数未落地,且未落地的那半里有已踩过坑的一条。 - ---- - -## 六、缺口清单(分级) - -### A 级 — 门禁语义(不改代码,只改判定;收益最高) - -1. **本地闸补 SCRIPT ERROR 闸门** —— 复用 `run_smoke.py` ③闸逻辑,对 `run_all` 的每个子套件输出扫 `SCRIPT ERROR` / `Parse Error`。 -2. **`probe_gate.sh` 探针判定改为「退出码优先 + PASS 串兜底」** —— 四件探针全部对齐 `ui_click_probe` 段的口径。 -3. **断言数下限闸** —— `Kit.finish` 在 `checks == 0` 时判红;`run_all` 汇总校验套件数等于 manifest 条目数。 -4. ~~**CI 纳入 run_smoke(系统逻辑面)**~~ —— **v1.3 作废**:CI 已删除;且 `run_smoke` 本就是本地轻闸首项(1086 项随变更跑),此条从不成立。 -5. ~~**CI 卫生两件**(订正 L52/L55「三探针」注释)~~ —— **v1.3 随 CI 删除而消失**。保留其中一件:`example_test.gd` 退出生产 manifest(或转正为真实套件)。 - -### B 级 — L2 集成层(缺口最大) - -5. 重建 `tests/integration/`,优先收编现成零件:`parity_anchor.py` 纳入闸表(它本就是章程里的「回放确定性」用例)。 -6. 补「存档 ⇄ RNG 快照往返」「Core-Core 编排」两类章程已登记的集成用例。 - -### C 级 — L3 场景/屏切换 - -7. **屏壳切换断言件**:三屏壳 `visible` 互斥 + 菜单→冒险→营地→战斗→结算→回营地 主流程状态机断言,做成可入 headless CI 的探针(当前 `ui_click_probe` 是唯一近似物,但不入 CI)。 - -### D 级 — 覆盖补洞 - -8. 补 `minesweeper_rules` / `status_container` / `config_db_resolver` / `config_db_cross_validator` 四个 core 零指名模块的直接单测。 -9. 呈现层 38 个零单测件按风险排序补面(建议先 `hud_presenter` / `battle_narrator` / 三屏壳)。 - -### E 级 — 测试代码自身卫生(Aurelius 视角) - -10. **S9 类型闸显式豁免 smoke/tests**(`tools/audit_type_hints.py:5` 注释「smoke/tests 豁免——测量面容器直构是惯例」)⇒ 测量面实测 untyped `Array`/`Dictionary`:**`game/tests/` 24 处 + `game/smoke/` 133 处 = 157 处**无闸管(产品码侧则归零)。这是项目既有裁决,此处仅呈报事实,不构成改判建议。 -11. **`test_kit.gd` 用 `static var` 全局计数器** —— 注释已声明「单进程跑多套件须先 `reset()`」。当前靠「一套件 = 一子进程」回避,是**约定不是机制**;将来改并发执行即串台。 -12. **断言出口不统一** —— 4 套件自建出口,`Kit` 24 套件;汇总口径不可比。 -13. **`load("res://core/xxx.gd")` 字符串路径**(`tree_rules_v11:15-16`、`tree_requires_level:11-12` 等)—— 重命名即静默返回 null → SCRIPT ERROR → 在**无③闸的 CI 上可能假绿**(与 A-1 联动)。 -14. **`.connect(func(...))` lambda 订阅共 76 处**(`game/tests/` 10 + `game/smoke/` 62 + 生产码 4)—— lambda 不自动断连,信号源存活久于目标即泄漏。测试侧集中在 `breakpoint_resume_test` / `settle_segments_test` / `forge_workbench_test` / `reward_overlay_test` / `ui_input_probe`。 - -### F 级 — 台账与时效(v1.1 新增) - -15. **`game/smoke/` 15 个游离测量脚本**定性(工具 vs 测试):在役的入台账 + 入闸或明示不入闸;失效的退役。**S10 文件头闸已能管住格式,但管不住"这东西还算不算数"**。 -16. **golden 视觉基线时效判定**(冻结 2026-09-30,经历 iter25/26 两次呈现层换代)——判有效则留,判失效则显式重制并呈报。 -17. **补 S5 扫描规则**(core 禁裸引用 autoload 全局标识符)——按 code-conventions 纪律,已有真实反面案例(active.md 494 坑册②)却无规则;S6/S7/S8 是否同批补另行裁决。 - ---- - -## 七、建议的门禁分层标准(待裁,未落任何文件) - -### 7.1 术语订正(建议) - -| 现名 | 建议改为 | 理由 | -|---|---|---| -| 「冒烟」(`smoke_all.gd` / `run_smoke.py`) | **系统级断言面(system suite)** | 它是全栈逻辑+接口+UI 断言,不是冒烟 | -| 「逻辑冒烟」(`run_all.gd`) | **单元套件(unit suite)** | 它就是单元测试 | -| ~~CI job `Headless smoke`~~ | ~~`Unit + System + Probe`~~ | **v1.3 作废**——CI 已删除,无 job 可改 | - -> 冒烟(smoke)一词建议**只保留给**「能否启动、能否加载表、能否进主屏」这类秒级存活检查,**不再作为验收主词**。 - -### 7.2 三层语义与判定口径(建议) - -| 层 | 验什么 | 判定(三条并行,缺一即红) | -|---|---|---| -| **L1 单元** | 纯逻辑 / 公式 / schema 校验 / 状态机 | ①退出码 ②断言有真值比对,禁「非空即过」 ③**断言数 > 0** | -| **L2 接口** | 模块间契约 / 跨系统编排 / 确定性回放 / 存档往返 | ①退出码 ②**逐位一致对拍**(已有 parity_anchor 现成) ③脚本错误扫描 | -| **L3 系统** | UI 行为 / 屏壳切换流程 / 视觉回归 | ①退出码 ②**行为断言(点击→状态变更)**,非仅几何结构 ③golden 逐像素(周期闸) | - -**通用三条硬闸(对所有层)**: -1. 退出码 —— 唯一权威判据,**PASS 串只作日志,不作判据**; -2. 脚本错误扫描 —— `SCRIPT ERROR` / `Parse Error` 命中即红(消灭「假绿 0 失败」); -3. 断言数下限 —— 零断言即红(消灭「静默早退」)。 - -外加既有的:外部 wall-clock 超时(124/137 = 挂死判红)。 - ---- - -## 八、未决项(待用户裁决,本报告不代决) - -| # | 事项 | 说明 | -|---|---|---| -| Q1 | 是否按 §7.1 订正术语 | 涉及 AGENTS.md、README 的措辞 | -| ~~Q2~~ | ~~`smoke_all.gd` 是否并入 CI~~ | **v1.3 作废**——CI 已删除;且 `run_smoke` 本就在本地轻闸首项 | -| Q3 | `tests/integration/` 是否重建 | 重建则 L2 有了承载;也可选择只把 `parity_anchor` 收进闸表而不建目录 | -| Q4 | S9 类型闸是否收回对 tests/smoke 的豁免 | 现行豁免是项目既有裁决(E-10),仅呈报 | -| Q5 | 屏壳切换探针是新建还是改造 `ui_click_probe` 入**本地轻闸** | 新建面更纯;改造零成本但 `ui_click_probe` 实测耗时较长 | - ---- - -## 九、报告自评 —— 这份审计本身哪里还不够(用户问,据实答) - -### 9.1 十条自评(按严重程度) - -| # | 不足 | 后果 | 补法 | -|---|---|---|---| -| 1 | **全静态、零实测** | 最硬的结论(探针不查退出码 → 会假绿)是**推断**,不是证据。审计拿推断当结论,本身就不严谨 | 造反例实测:让一件探针/套件必然失败,看 CI 判定是否真绿(T0-①) | -| 2 | **覆盖靠 grep 指名** | 「零指名」不等于「零覆盖」,我已在 §〇 打了预防针,但**没有替代度量手段** | Godot 无内建行覆盖率;退而求其次 = 用脚本加载/调用登记反查,或引进第三方覆盖率工具(待裁) | -| 3 | **没给执行矩阵** | §七 只给三层语义,没给「谁在什么时机跑」——门禁不落到时机等于没落 | 补:每次提交 / 每次 PR / 周期 / 里程碑 的四级时机表 | -| 4 | **没测时长预算** | 原定 <30s,`run_all` 已是 27 个子进程、CI 给 120s;套件再增就撞墙 | 实测 run_all 全套 + smoke_all 时长,重定超时值(T0-②③) | -| 5 | **漏审 `game/smoke/` 游离件** | v1.0 完全没提 17 个无宿主脚本 —— 测试资产台账缺一大块 | 已补 §5.4 / F-15 | -| 6 | **漏问 golden 基线时效** | 基线冻结于 iter25/26 两次 UI 大改之前,却仍判 0.00%,没追问 | 已补 §5.5 / F-16 | -| 7 | **漏审扫描域未落地** | 只在 E-10 提了 S9 豁免,没把「扫描域八条只落地四条」当独立缺口 | 已补 §5.6 / F-17 | -| 8 | **漏抓 CI 注释漂移与 example_test 占位** | v1.0 没扫 workflow 注释与 manifest 内容质量 | 已补 §5.2b / §5.2c / A-5 | -| 9 | **A–E 分级无排序依据** | 分档是我主观排的,没绑投入产出比 | A 级之所以排第一:**改判定不改代码、半小时收益最大**;F 级排最后:属台账清理,不影响正确性 | -| 10 | **没区分证据等级** | 读者无从判断哪条可信、哪条要验 | 本版起 A/B/C 三级标注(§〇)+ ⚠ 记号 | -| 11 | **没核实 CI 到底通不通电**(v1.2 补,最严重的一条) | 我把 CI 当主战场审了一整章,`git remote -v` 一查——**无远端、从未跑过**。整章的结论"方向"没错(判定逻辑确实有洞),但"落点"错了:洞在**本地闸**里,此刻就在生效;CI 只是一份没插电的镜像。差点把改造矛头指向一台从没开过的机器 | 已补 §5.0;改造对象改为本地闸为主、CI 同步对齐为辅 | - -### 9.2 证据等级表 - -| 等级 | 定义 | 本报告中的典型 | -|---|---|---| -| **A(确凿)** | 代码/配置直读可复现 | 探针判定不查退出码(`probe_gate.sh` / `tests.yml:59-75`)、`Kit.finish` 零断言仍绿(`test_kit.gd:41-49`)、CI 无 SCRIPT ERROR 闸、S5–S8 未实现、CI 注释三 vs 四、17 个游离脚本 | -| **B(文档可查)** | 仓库登记在册 | 灾害「309 项 0 失败」、active.md 476 条 51 行 SCRIPT ERROR、494 条坑册② Parse Error、golden 冻结日、integration 空壳已删 | -| **C(推断,⚠)** | 逻辑推演,未实测 | 「CI 上实际会假绿」、「run_all 时长将撞 120s」、「golden 基线已失效」 | - -### 9.3 一句话 - -这份审计**找方向是准的**(术语倒挂、三处判定缺陷、L2 零承载,三条都是代码级确凿),但**证据硬度不够**——它是"读出来的结论",不是"跑出来的结论"。扭转方案的第一步必须是**先把 C 类结论全部坐实成 A 类**,否则后面所有改动都建立在我自己的推断上。 - ----