From c25daa30cdba19156639d1679cd8c1b489bb5c45 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Sun, 25 Oct 2026 23:46:01 +0800 Subject: [PATCH] =?UTF-8?q?docs(=E8=BF=AD=E4=BB=A337=E7=BB=93=E9=A1=B9):?= =?UTF-8?q?=20=E6=89=B9=E5=90=8E=E6=94=B6=E5=B0=BE=E2=80=94=E2=80=94?= =?UTF-8?q?=E5=8F=8D=E5=93=BA=E8=90=BD=E6=96=87=E5=85=AD=E5=A4=84=EF=BC=8B?= =?UTF-8?q?=E8=B4=A6=E9=9D=A2=E6=8D=A2=E4=BB=A3=EF=BC=8B=E8=BF=AD=E4=BB=A3?= =?UTF-8?q?37=E4=BA=A7=E5=87=BA=E7=89=A9=E4=B9=9D=E4=BB=B6=E9=80=80?= =?UTF-8?q?=E5=BD=B9=EF=BC=88=E5=9B=9B=E5=8D=A1=E6=89=B92026-10-25?= =?UTF-8?q?=E5=90=88=E5=9B=9E8faf1763=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 反哺落文(reports/iter37-反哺点 逐条出账,清单随批退役): - architecture.md v1.12→v1.13:模块归属表登记迭代37拆件新模块(CurioDeck/ShopSession/board_resolvers/main_ui_build/main_adventure_flow/main_overlay_flow/main_debug_flow/TilePalette/HeroSprite/BoardWorld 在册补行+HudPresenter 数据投影+文案拼装静态族加注;表内原无 HudPresenter 行,按新增落表)。 - 存档序列化.md:断点域豁免段注记 27 顶层键登记单源=core/adventure_runner.gd const BREAKPOINT_FIELDS(写侧 _bp_parts()、读侧 _bp_read/_bp_read_int)。 - 冒险结构.md 共享池账本节:载体注记 AdventureRunner._bonus_deck→CurioDeck(池与判定)+ShopSession(货架/定价/购买/刷新),规则文字零改动。 - code-conventions.md §1.1:新立拆分判据硬纪律——重构卡禁为迎合机检计数保留无效成员/别名,机检键与卡片契约互斥时实现段停卡呈报(反哺条7+条9口径同落;行访问器直读=db即ConfigDB,口径由该条与既有数值单点纪律覆盖)。.zcode/.qoder 两份 req-card 技能同步第13项。 - game/tests/验证集清单.md:extract_checks.py --extract --force 重生成(64源1583调用点);〔wr2〕三条走 check_quiet 通道不在提取器现行口径(旧清单同口径零条),属工具覆盖面既成事实,未扩口径。 - 反哺条4/10 批内已执行、条8 无义务、条5/6 分别由本批重生成/转待办新条出账。 代拍板:B-1~B-8 由用户 2026-10-25 结项发令整单追认(E-1/E-2 补救已入账于 P0 待办条)。 账面换代:roadmap 迭代37行=已完成(2026-10-25)+卡列/报告列退役保留字+当前位三行换代;待办清单删「game代码结构审查批」条(未决六小件迁「结构审查未立项小件候审」、gdstyle基线陈旧键迁新条)、P0「AdventureRunner职责真拆分」吸收拆分卡缺口件(撤装置计数读数/五件收编范围/验收面预案);体验验证清单录迭代37实机目检七条。 退役九件(判据逐份核对,引用面改写后 git rm):iter37 产出物五份(代拍板/反哺点/实现报告/拆分卡缺口/验收审计)+四张需求卡片(迭代已完成,roadmap 已填退役保留字)。 --- .../engineering/architecture/architecture.md | 15 +- design/engineering/code-conventions.md | 2 + design/gdd/systems/冒险循环/冒险结构.md | 2 +- design/gdd/systems/表现与支撑/存档序列化.md | 2 +- game/tests/验证集清单.md | 3689 +++++++++-------- reports/iter37-代拍板-2026-10-25.md | 31 - reports/iter37-反哺点-2026-10-25.md | 61 - reports/iter37-实现报告-2026-10-25.md | 281 -- reports/iter37-拆分卡缺口-2026-10-25.md | 47 - reports/iter37-验收审计-四卡-2026-10-25.md | 273 -- ...œ€求卡片-game行为缺陷定点修批-2026-10-24.md | 313 -- ...需求卡片-重构boardview拆件批-2026-10-24.md | 155 - reports/需求卡片-重构main判离批-2026-10-24.md | 169 - ...重构runner拆件与断点schema单点批-2026-10-24.md | 173 - roadmap.md | 6 +- 体验验证清单.md | 18 + 待办清单.md | 26 +- 17 files changed, 1932 insertions(+), 3331 deletions(-) delete mode 100644 reports/iter37-代拍板-2026-10-25.md delete mode 100644 reports/iter37-反哺点-2026-10-25.md delete mode 100644 reports/iter37-实现报告-2026-10-25.md delete mode 100644 reports/iter37-拆分卡缺口-2026-10-25.md delete mode 100644 reports/iter37-验收审计-四卡-2026-10-25.md delete mode 100644 reports/需求卡片-game行为缺陷定点修批-2026-10-24.md delete mode 100644 reports/需求卡片-重构boardview拆件批-2026-10-24.md delete mode 100644 reports/需求卡片-重构main判离批-2026-10-24.md delete mode 100644 reports/需求卡片-重构runner拆件与断点schema单点批-2026-10-24.md diff --git a/design/engineering/architecture/architecture.md b/design/engineering/architecture/architecture.md index acfbe5f1..b349cf95 100644 --- a/design/engineering/architecture/architecture.md +++ b/design/engineering/architecture/architecture.md @@ -2,8 +2,8 @@ ## 文档状态 -- 版本:1.12 -- 最近更新:2026-10-25(S6/S7/S8 三闸落地批收口——静态扫描承接清单三行改在位;2026-10-25 用户发令三闸落地) +- 版本:1.13 +- 最近更新:2026-10-25(迭代 37 反哺落文——模块归属表登记本批拆件新模块) - 引擎:Godot 4.7.2-stable(GDScript 单语言;2026-09-07 锁定) - 评审模式:solo(`review-mode.txt`,2026-09-12 用户拍板——TD/LP 门禁按 solo 跳过,用户授权架构产出自主推进、迭代期回调) - 覆盖 GDD:现行全集——`design/gdd/` 顶层 00–08 共 9 件 + `systems/` 五域 25 件(排雷循环 4 / 战斗循环 5 / 冒险循环 5 / 跨局循环 5 / 表现与支撑 6)+ `ux/` 8 件;原英文命名 12 份系统 GDD 已全部迁入中文域目录,原「未设计」预留(A5 扫雷秘宝 / C1–C4 / B5 / D3–D6)均已立项成文 @@ -108,6 +108,8 @@ | **unit_container**(B2) | hero/enemy 单位实例(hp/atk/实例池:池快照+冷却+摇定动作+保底计数)、行值快照 | spawn / query_alive / rollable_skills / special_skills / set_pending_action / pending_action / basic_streak / commit_action / tick_cooldowns / apply_damage / heal / default_target / check_skill_unlocks / despawn_all(2026-09-16 敌人技能池批:query_beat/query_attack/advance 随行为环退役) | ConfigDB(EnemyTable/SkillTable/HeroTable) | 无 | | **reward_economy**(B4) | 格面资源身份映射(install 期预分配、Ready 只读、换图映射消亡)、极性路由字典面((类型, 极性)→ 字典行查询;路由执行归 B1 授予段)、F6 当量供给投影 | 封闭 API:roll_board / query_identity / query_full_snapshot / grant / grant_clear / retire_mapping / polarity_row / phase / weighted_pick;另有 is_mapped 静默布尔投影(批量工具用) | ConfigDB(RewardTable);effect_engine(事件发射 dispatch——上层消费基础层);reward_roll 流(scope = 每次 install 批) | 无 | | **helper_items**(A5,切片已实装秘宝域) | HelperItemTable、探测显影语义 | (契约规范源:`../../gdd/systems/跨局循环/秘宝.md`——桥接位:A2 查询接口 + A4 counter_item_ids) | A2 / A4 / B4 | 无 | +| **CurioDeck**(`core/curio_deck.gd`,A5 秘宝域令牌池件,迭代 37 自 AdventureRunner 拆出) | 三选一/商店共享池的持久多重集、抽签(候选抽取)、cap 库存与专长凭证三计数与判定唯一所有者——件只管池与判定,停点编排与兑付推进留冒险编排器 | `draw_candidates(channel)` 抽签;`consume_deck_token` / `consume_pool_purchase` 池消耗;`cap_ok` 上限判定;`feat_ticket_consider` 凭证判定;`stock_snapshot` / `pool_stock` 只读投影;宿主兑付单点经 `bind_handlers` 注入方法 Callable(选中摘牌后的加算区/账本落点仍在宿主) | ConfigDB(秘宝令牌行集);注入 RngServiceCore(reward_pick 封闭流,禁自建随机) | 无(RefCounted 纯逻辑) | +| **ShopSession**(`core/shop_session.gd`,商店会话件,迭代 37 自 LevelRunner 拆出) | 关卡商店双模式(legacy 固定三件加定价/pool 共享令牌池)的货架、定价、购买与刷新唯一宿主——账本/钱包/装配集归宿主 LevelRunner(共享引用经 `bind` 注入,出账直接落同一引用改写宿主账本);legacy 模式零 RNG 零池消费(off 对照承载) | 货架与定价只读面(`shop_products` / `shop_shelf` / `current_price` / `shop_refresh_cost`)+写入口(`buy_product` / `buy_pool_item` / `shop_refresh` / `player_request_refresh` / `open_pool`);外部调用面由 LevelRunner 同签名委托承载(外部零变化) | ConfigDB(商店行集);注入 RngServiceCore;宿主钱包/账本引用;pool 模式令牌消耗与判定经注入 Callable 转 CurioDeck | 无 | ### 功能层 @@ -132,6 +134,15 @@ |---|---|---|---|---| | **D1 HUD**(呈现面拆分持续换代:组合根 main.gd + 三屏壳(MenuScreen/CampScreen/AdventureScreen)+ views/ 视图单元(现行承载面见「呈现层装配与会话生命周期」节;早期 9 单元清单与 LevelStripView 已随场景换代退役);营地覆盖层=四柜台并排〔树淬炼主界面/锻造工坊·秘宝匣钥匙门/图鉴常开——迭代 21 T6 程序占位重排〕) | 全部 Control/CanvasLayer(例外:BoardView 棋盘本体与 BattleStageView 站桩本体 = Control 壳 + SubViewportContainer + SubViewport + Node2D 世界〔BoardWorld/CellUnit;StageWorld/StandeeBody——B 区 slot_row 伴随件留壳层〕,探针契约面不变——棋盘 Node2D 迁移批 Phase A 2026-09-23 / 战斗舞台 Phase B 2026-09-26)、输入→意图翻译(**唯一提交路径 = settle_batch,不直调 A3**)、呈现义务状态记录(A2×6 / B2×4 / B3×4 / B4×5 / B1×3) | 视图单元接口(信号外发、setter 只读注入) | A2 compute_move_path / ActionOutcome;A3 ap/rap;B1 步骤流行协议(数据源);ConfigDB display_name | **MEDIUM**:Control / CanvasLayer / draw_* / InputEvent / RichTextLabel——对照 breaking-changes.md 4.5–4.7 条目(CanvasItem 线条无抗锯齿羽化、设备 ID 常量、RichTextLabel 图片参数两轮变化) | | **组合根拆件**(呈现侧写口子件:curio_interact_controller / camp_shop_presenter / treasure_interact_controller) | 代组合根执行核心写调用(冒险节点后续推进/营地购买与刷新/道具使用——依赖与回调由 main 构造注入) | 各拆件的写入口(仅组合根装配面可达;视图零直连) | 构造注入的核心写引用(推进/商店/道具各域——详见各拆件头注) | 无 | +| **HudPresenter**(`presentation/hud_presenter.gd`,迭代 37 加注) | 战斗屏**数据投影+文案拼装静态族**——核心状态→呈现文案的全部翻译集中于此(B/C/D 区文案与投影),静态无状态 | `refresh(…)` 全量重查装配(组合根每次动作/结算返回后调用,漏调即陈旧呈现)+只读静态投影族 | 核心读取只走 `settlement.presentation_snapshot()` 成文快照面(禁链式穿透结算内部);ConfigDB display_name | 无(RefCounted 静态) | +| **board_resolvers**(`presentation/board_resolvers.gd`,迭代 37 拆出) | 棋盘每格渲染数据的只读解析回调四函数(奖励徽章身份——挖掘期 Active 相位内、地形变异数字遮蔽与角标、调试加成生效值)——有态绑定件,与无态静态投影族 HudPresenter 职责互斥 | 构造注入 db、调试浮窗与结算活态取值,产出绑定 Callable 供组合根接线 | ConfigDB / DebugOverlay / 结算活态取值 Callable | 无(纯投影) | +| **main_ui_build**(`presentation/main_ui_build.gd`,迭代 37 拆出) | `_build_ui` 八步拆分件——组合根 UI 装配分步承接(三屏壳实例化、视图句柄接线、覆盖层装配与模态编排注入按步拆开);main 侧留同名薄委托,装配序与 z 序零改动 | 分步静态函数(成员访问经 root 显式传入) | 层序逻辑不在此件——唯一事实源仍是 `main.LAYER_CONTRACT` + `_apply_layer_contract()` | 同 D1(Control 装配面) | +| **main_adventure_flow**(`presentation/main_adventure_flow.gd`,迭代 37 拆出) | main 冒险流程族——菜单开局/续玩后的冒险编排接线、节点事件分发、终局面与选路屏开合;逐字搬运,main 侧留同名薄委托(探针 `main.X` 路径零迁移) | 流程静态函数(root 显式) | 局态全局住 run_scope 节点——本件零成员态、零新增信号连接(连接语句随原段搬运) | 无 | +| **main_overlay_flow**(`presentation/main_overlay_flow.gd`,迭代 37 拆出) | main 浮层流程族——设置/状态面板/生涯页/选档界面/图例/钱包事件的开合与数据投喂;逐字搬运,main 侧留同名薄委托 | 流程静态函数(root 显式) | 全部开合走组合根模态单口(`root._open_modal/_close_modal`)——本件零成员态 | 同 D1 | +| **main_debug_flow**(`presentation/main_debug_flow.gd`,迭代 37 拆出) | main 调试流程族——调试浮窗开合与上下文同步、批量模拟入口、两层调试对局起手、诊断转储;逐字搬运,main 侧留同名薄委托 | 流程静态函数(root 显式) | 调试会话状态机归 debug_session.gd——本件只接线与出 UI,零成员态 | 同 D1 | +| **TilePalette**(`presentation/views/tile_palette.gd`,迭代 37 拆出) | 瓦片排版/装载:高墙/地面瓦片整批装载与运行时镜像合成、地板明度归一、数字墨族按板自适应判据、格坐标确定性散列与十六正交情形墙瓦片查表 | 查表族(读件持棋盘引用——棋盘视图 rebuild 经 `bind_board` 同步);装载缓存七成员持于件内(同板型零重载、换板重载时序同源) | 零绘制面、零玩法数据写入(绘制调用面留棋盘视图);纹理路径单点在件内 | Resource 纹理加载——LOW | +| **HeroSprite**(`presentation/views/hero_sprite.gd`,迭代 37 拆出) | 英雄精灵四态与落位装配(落位与位移演出、撞墙帧闪、挖掘弹跳、脚底锚与持握手位出口) | 由棋盘视图 `_init` 建为子件(注入世界根与壳层容器引用)——精灵本体经世界根 ensure_hero 建于 SubViewport 世界内,本件只驱动其状态 | BoardWorld 世界根;素材缺位=零精灵(棋盘视图字形「人」回退路径在役) | Node2D / create_tween(Tween 系 Node 方法——MEDIUM 域对照 breaking-changes.md) | +| **BoardWorld**(`presentation/views/board_world.gd`,迭代 37 在册补行) | 棋盘 Node2D 世界根(SubViewport 内 CellUnit 容器):世界级去饱和材质与字体注入挂点、英雄精灵持有位 | 无 class_name——使用方经 preload 引用挂树 | BoardView 壳层(SubViewportContainer + SubViewport 结构不变——棋盘 Node2D 迁移批 Phase A 既定) | Node2D/SubViewport——同 D1 | | **battle_presentation**(B5,垂直切片后) | 结算动画 / 伤害数字 / 战斗舞台 | (契约规范源:`../../gdd/systems/战斗循环/战斗表现.md`——前置「战斗表现美术测试」锁双舞台陷阱) | B1 步骤流(回放脚本源)、D1 | **MEDIUM**:AnimationPlayer(String→StringName 变更)/ Tween | | **audio**(D4,Alpha) | 音频挂点 | (挂点 = B1 明细行类型流;负反馈聚合——单结算至多一次) | B1 流水 | AudioStreamPlayer(4.7 area_mask 默认 1→0 注意) | | **tutorial**(D5,Alpha) | 教学条目 | (口径来源:A2/A3/B3/B1 各教学条目) | 各上游 | 无 | diff --git a/design/engineering/code-conventions.md b/design/engineering/code-conventions.md index 5824697e..b23d020a 100644 --- a/design/engineering/code-conventions.md +++ b/design/engineering/code-conventions.md @@ -12,6 +12,8 @@ ### 1.1 禁止上帝对象 一个类只承载一个职责。场景主脚本(main)= **组合根**:模式流转、信号接线、核心写调用的唯一出口;渲染、文案拼装、状态装配一律拆分为独立视图/装配器类。单文件超过 ~400 行即触发审视。 +拆分判据(硬纪律):重构类需求卡片必须写明——禁止为迎合机检消息文本(含计数)而保留或新增无效成员/别名;机检键与卡片契约互斥时,实现段一律停卡呈报,不得自造第三条路。 + ### 1.2 一切功能以类封装,禁止裸函数堆 设计稿与实现同尺度:功能 = 类(状态 + 行为 + 信号),类是封装的最小交付单位。工具性纯函数只允许挂在明确的宿主类下(静态装配器/工厂),不允许无主散落。 diff --git a/design/gdd/systems/冒险循环/冒险结构.md b/design/gdd/systems/冒险循环/冒险结构.md index e95d792f..bf2b98ee 100644 --- a/design/gdd/systems/冒险循环/冒险结构.md +++ b/design/gdd/systems/冒险循环/冒险结构.md @@ -152,7 +152,7 @@ GP(显示名「GP」)是冒险内的一次性属性提升货币:战斗胜 掉落掷定消费 gp_drop 封闭随机流(主种子派生、可回放)。**商店货架抽签消费 shop_draw 封闭随机流**(v0.13 扩签——每次抽 3〔进店+每次买后重抽〕恰一 scope,与 reward_pick 分签防取数序互扰)。掉落与价格的现行行值读商店表,数值所有权归[难度曲线与Boss](难度曲线与Boss.md)曲线调参批——本批骨架先行,全部数值 provisional。 -**共享池账本(v0.13 P1 共享池单点;v0.14 令牌库换代;迭代 20 案D 四级 104 张——池机制事实源=[奇物](奇物.md)「池结构与抽取」节)**:商店货架与奖励节点三选一是**同一个池**——货架候选 = 池∖{GP 袋, 刷新件}∪{凭证} = {攻击, 生命上限, 护甲, 行动点, 回复, 凭证} 六种(GP 袋计价物不自售切边、刷新件不上货架、秘宝件已随同调批出池;凭证与回复随迭代15② 入架——shop.tres pool_shelf 六行);**库存 = 剩余令牌数**(TP0——「池消耗即上限」,种类次数上限退役,与奖励兑付摘牌同一令牌库承载,全冒险一次性跨关卡不补给):商店买一件,奖励面就少一枚;「无限购买」收敛为**钱尽或池空为止**(池空=硬闸、A 链爬价=软闸),空货架合法态。货架 = **令牌级抽签**(同 kind 去重每 kind 至多一卡,价格公式 tier-blind 维持批②裁定,购买消费在架令牌本体 kind+tier)。禁止两渠道各自私记库存——消费走同一入口写同一令牌库(实现=AdventureRunner._bonus_deck 单点,冒险结构 owner;kind 维度派生计数 _bonus_taken 承载断点附随件与读数)。**货架独立刷新(v0.15 MC-1 2026-10-04——P-C2+Q2 裁)**:进店免费刷新次数(基础 0,天赋/营地侧增益位预留)用尽后 GP 付费刷新(ceil(20×1.8^已付费刷) provisional——调参表旋钮 shop_refresh_*);重抽=同 pool_draw 共享池抽签(shop_draw 恰一新 scope),池空=不可刷新合法态;进店/离开双向清零付费刷计数。**三选一同款付费档(v0.16 2026-10-06 用户令——刷新对齐商店)**:三选一免费双源(刷新件 X/冒险池 Y,机制现行事实源=[秘宝](../跨局循环/秘宝.md)三选一刷新双源节)用尽后 GP 付费刷新——读同 shop_refresh_* 两旋钮(单点调参)、扣同一 GP 冒险账本(LevelRunner.spend_gp 单口)、重抽同 reward_pick 共享池抽签;付费刷计数每停点清零(与进店清零同构);钮常驻选择态、免费与付费两源皆穷=禁用。 +**共享池账本(v0.13 P1 共享池单点;v0.14 令牌库换代;迭代 20 案D 四级 104 张——池机制事实源=[奇物](奇物.md)「池结构与抽取」节)**:商店货架与奖励节点三选一是**同一个池**——货架候选 = 池∖{GP 袋, 刷新件}∪{凭证} = {攻击, 生命上限, 护甲, 行动点, 回复, 凭证} 六种(GP 袋计价物不自售切边、刷新件不上货架、秘宝件已随同调批出池;凭证与回复随迭代15② 入架——shop.tres pool_shelf 六行);**库存 = 剩余令牌数**(TP0——「池消耗即上限」,种类次数上限退役,与奖励兑付摘牌同一令牌库承载,全冒险一次性跨关卡不补给):商店买一件,奖励面就少一枚;「无限购买」收敛为**钱尽或池空为止**(池空=硬闸、A 链爬价=软闸),空货架合法态。货架 = **令牌级抽签**(同 kind 去重每 kind 至多一卡,价格公式 tier-blind 维持批②裁定,购买消费在架令牌本体 kind+tier)。禁止两渠道各自私记库存——消费走同一入口写同一令牌库(实现=CurioDeck 件(`core/curio_deck.gd`)的令牌多重集单点——池与判定归件,AdventureRunner 为宿主构造与持有、停点编排留宿主;商店货架/定价/购买/刷新归 ShopSession 件(`core/shop_session.gd`),外部调用面由 LevelRunner 同签名委托承载;kind 维度派生计数 taken(件内成员)承载断点附随件与读数)。**货架独立刷新(v0.15 MC-1 2026-10-04——P-C2+Q2 裁)**:进店免费刷新次数(基础 0,天赋/营地侧增益位预留)用尽后 GP 付费刷新(ceil(20×1.8^已付费刷) provisional——调参表旋钮 shop_refresh_*);重抽=同 pool_draw 共享池抽签(shop_draw 恰一新 scope),池空=不可刷新合法态;进店/离开双向清零付费刷计数。**三选一同款付费档(v0.16 2026-10-06 用户令——刷新对齐商店)**:三选一免费双源(刷新件 X/冒险池 Y,机制现行事实源=[秘宝](../跨局循环/秘宝.md)三选一刷新双源节)用尽后 GP 付费刷新——读同 shop_refresh_* 两旋钮(单点调参)、扣同一 GP 冒险账本(LevelRunner.spend_gp 单口)、重抽同 reward_pick 共享池抽签;付费刷计数每停点清零(与进店清零同构);钮常驻选择态、免费与付费两源皆穷=禁用。 ### 关卡大奖励:通关回血 + 专长三选一(钥石退役出大奖励) diff --git a/design/gdd/systems/表现与支撑/存档序列化.md b/design/gdd/systems/表现与支撑/存档序列化.md index af6dc55b..6307f906 100644 --- a/design/gdd/systems/表现与支撑/存档序列化.md +++ b/design/gdd/systems/表现与支撑/存档序列化.md @@ -56,7 +56,7 @@ SaveManager 不依赖任何玩法系统就能工作:它是被动的承载方 第二条边界是反面清单:局内资源不入存档。行动点、储备行动点、道具库存、生命、GP 全部单局清零——各归各的系统管,没有局内数值需要持久化。若未来某局内资源要开放跨局携带(如储备行动点跨战斗携带),须修订其池规则并向本系统登记持久化归属,禁止静默超域。 -**唯一豁免 = `breakpoint` 断点域**(2026-09-29 迭代 8 卡①顺带修订):断点是冒险进行中的瞬态快照,不是跨局携带——里面的生命/GP/道具库存只在「续玩上局」时还原进局内,冒险收束(通关/失败/放弃)即整域清除,永远不会渗进永久进度。反面清单的管辖对象因此收窄为 `meta` 永久进度域(冒烟域界扫描顺带收窄到 meta 域);断点域自身的字段口径归[冒险结构](../冒险循环/冒险结构.md)登记。 +**唯一豁免 = `breakpoint` 断点域**(2026-09-29 迭代 8 卡①顺带修订):断点是冒险进行中的瞬态快照,不是跨局携带——里面的生命/GP/道具库存只在「续玩上局」时还原进局内,冒险收束(通关/失败/放弃)即整域清除,永远不会渗进永久进度。反面清单的管辖对象因此收窄为 `meta` 永久进度域(冒烟域界扫描顺带收窄到 meta 域);断点域自身的字段口径归[冒险结构](../冒险循环/冒险结构.md)登记。断点 27 顶层键的登记单源=`core/adventure_runner.gd` 内 `const BREAKPOINT_FIELDS`(键→回退策略标签),写侧由 `_bp_parts()` 拼装、读侧由 `_bp_read` / `_bp_read_int` 两处派生。 **三存档位(迭代 33 卡①,2026-10-22 反哺写入)**:存档是 3 个独立的 JSON 文件(`user://save_slot_1/2/3.json`,槽文件名只经 `slot_path()` 单点派生,槽 1 禁复用旧单档名),字典键一律字符串。写盘目标=当前激活槽的文件;启动(boot)不读任何槽文件——启动态=无激活态,不读写;激活态由选档界面的「新档」(`create_slot`)或「进档」(`activate_slot`)建立(选档界面归迭代 33 卡③)。数据模型(现行 = v6;示例值全为零值默认态): diff --git a/game/tests/验证集清单.md b/game/tests/验证集清单.md index 8cd3ef66..61bed943 100644 --- a/game/tests/验证集清单.md +++ b/game/tests/验证集清单.md @@ -1,8 +1,8 @@ # 验证集清单(断言快照·冻结) -> 生成日期:2026-10-25 · 基点 commit:`1753e581a43f48254425881bd77b84ac7fd263b1` · 源:cross_system_flow_test.gd、node_map_determinism_test.gd、save_rng_roundtrip_test.gd、system_test.gd、damage_controller_test.gd、damage_math_test.gd、roll_multiplier_test.gd、round_flow_skill_pool_test.gd、seal_polarity_test.gd、skill_unlock_test.gd、variant_placement_test.gd、source_guard_test.gd、system_contracts_test.gd、adventure_feats_test.gd、board_gen_reward_test.gd、board_generator_test.gd、breakpoint_resume_test.gd、config_db_cross_validator_test.gd、config_db_resolver_test.gd、curio_refresh_paid_test.gd、effect_words_t1_test.gd、feat_vouchers_test.gd、level_map_type_vis_test.gd、level_shop_economy_test.gd、level_table_validate_test.gd、meta_progression_forge_test.gd、minesweeper_rules_test.gd、node_map_generator_test.gd、node_map_growth_test.gd、pool_case_d_test.gd、save_boot_no_activate_test.gd、save_manager_domain_test.gd、save_slot_isolation_test.gd、save_slot_summary_test.gd、settle_segments_test.gd、treasure_effects_test.gd、treasure_items_test.gd、tree_requires_level_test.gd、tree_rules_v11_test.gd、config_db_fatal_oob_test.gd、config_db_test.gd、counter_prefix_fatal_test.gd、forge_table_test.gd、level_gen_params_test.gd、reward_dangling_treasure_test.gd、rng_service_test.gd、rng_stream_boot_test.gd、table_structure_guard_test.gd、treasure_target_violation_test.gd、example_test.gd、battle_narrator_test.gd、forge_workbench_test.gd、hud_presenter_test.gd、level_map_info_fog_test.gd、modal_flow_test.gd、pacing_queue_test.gd、player_copy_sweep_test.gd、reward_overlay_test.gd、terrain_tile_sets_test.gd、theme_projection_test.gd、type_glow_test.gd、ui_font_asset_test.gd、test_kit.gd +> 生成日期:2026-10-25 · 基点 commit:`d1c95ade6fbfe01a9cdc4bee2de29cf0729ce15b` · 源:cross_system_flow_test.gd、node_map_determinism_test.gd、save_rng_roundtrip_test.gd、system_test.gd、damage_controller_test.gd、damage_math_test.gd、roll_multiplier_test.gd、round_flow_skill_pool_test.gd、seal_polarity_test.gd、skill_unlock_test.gd、variant_placement_test.gd、source_guard_test.gd、system_contracts_test.gd、adventure_feats_test.gd、board_gen_reward_test.gd、board_generator_test.gd、breakpoint_resume_test.gd、config_db_cross_validator_test.gd、config_db_resolver_test.gd、curio_refresh_paid_test.gd、effect_words_t1_test.gd、feat_vouchers_test.gd、level_map_type_vis_test.gd、level_shop_economy_test.gd、level_table_validate_test.gd、meta_progression_forge_test.gd、minesweeper_rules_test.gd、node_map_generator_test.gd、node_map_growth_test.gd、pool_case_d_test.gd、save_boot_no_activate_test.gd、save_manager_domain_test.gd、save_slot_isolation_test.gd、save_slot_summary_test.gd、settle_segments_test.gd、treasure_effects_test.gd、treasure_items_test.gd、tree_requires_level_test.gd、tree_rules_v11_test.gd、config_db_fatal_oob_test.gd、config_db_test.gd、counter_prefix_fatal_test.gd、forge_table_test.gd、level_gen_params_test.gd、reward_dangling_treasure_test.gd、rng_service_test.gd、rng_stream_boot_test.gd、table_structure_guard_test.gd、treasure_target_violation_test.gd、example_test.gd、battle_narrator_test.gd、forge_workbench_test.gd、hud_presenter_test.gd、level_map_info_fog_test.gd、modal_flow_test.gd、pacing_queue_test.gd、player_copy_sweep_test.gd、reward_overlay_test.gd、standee_fx_sweep_test.gd、terrain_tile_sets_test.gd、theme_projection_test.gd、type_glow_test.gd、ui_font_asset_test.gd、test_kit.gd > 提取口径:静态 `check(` 调用点,括号配平归集(可跨行);缺省源=三宿主遍历 -> (unit/**+integration/**+system_test.gd)。本基点实测 1544 个静态调用点;行级 +> (unit/**+integration/**+system_test.gd)。本基点实测 1583 个静态调用点;行级 > `grep -c "check("` 是另一口径(含 `func check(` 定义行与注释行),两口径各自浮动, > 禁互相换算(tests/README「断言计数双口径」)。运行时计数大于静态计数=循环展开条目。 > 生成命令:`python tools/extract_checks.py --extract`(可复现;重新生成须 --force; @@ -18,43 +18,43 @@ | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1 | 55 | "事件日志会话落盘(2026-09-27 统一流裁决)" | FileAccess.file_exists("user://logs/rng_log_4242.jsonl") | RNG 事件日志按会话落盘成文件(2026-09-27 统一流裁决的落地面)。 | 迁集成测试(tests/integration/cross_system_flow_test.gd) | | +| 1 | 55 | "事件日志会话落盘(2026-09-27 统一流裁决)" | FileAccess.file_exists("user://logs/rng_log_4242.jsonl") | (待填) | | | ## S02 _feats_breakpoint_roundtrip(行 91–97 · 3 条 · 源 game/tests/integration/cross_system_flow_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 2 | 91 | "断点附随件:relics 持有集入快照(迭代 12 T1 扩列)" | (bp_r.get("relics", []) as Array).has("x3") | 断点快照附随 relics 持有集(续玩还原面)。 | 迁集成测试(tests/integration/cross_system_flow_test.gd) | | -| 3 | 92 | "断点附随件:adventure 计数器账本键在案(T6/T7 扩列)" | bp_r.has("adventure_counters") | 断点快照附随 adventure 计数器账本键。 | 迁集成测试(tests/integration/cross_system_flow_test.gd) | | -| 4 | 97 | "断点余库:已兑付 relic 令牌逐枚还原(x3 零残留——续玩不重复获得)" | bp_deck_x3 == 0 | 断点余库逐枚还原:已兑付 relic 令牌零残留(续玩不重复获得)。 | 迁集成测试(tests/integration/cross_system_flow_test.gd) | | +| 2 | 91 | "断点附随件:relics 持有集入快照(迭代 12 T1 扩列)" | (bp_r.get("relics", []) as Array).has("x3") | (待填) | | | +| 3 | 92 | "断点附随件:adventure 计数器账本键在案(T6/T7 扩列)" | bp_r.has("adventure_counters") | (待填) | | | +| 4 | 97 | "断点余库:已兑付 relic 令牌逐枚还原(x3 零残留——续玩不重复获得)" | bp_deck_x3 == 0 | (待填) | | | ## S03 _save_summary_integration(行 119–132 · 10 条 · 源 game/tests/integration/cross_system_flow_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 5 | 119 | "摘要集成:冒险驱动满配通关(seed 42)" | String(st9.get("result", "")) == "completed" | 摘要集成:真实局满配通关。 | 迁集成测试(tests/integration/cross_system_flow_test.gd) | | -| 6 | 120 | "集成钥石余额 = ΣBoss 首杀行值(%d——逐杀掉落全源)" ↩ % f9.keystone_balance() | f9.keystone_balance() == ks_run | 集成钥石余额=ΣBoss 首杀行值。 | 迁集成测试(tests/integration/cross_system_flow_test.gd) | | -| 7 | 121 | "集成通关次数表全 1" | f9.clear_count(1) == 1 and f9.clear_count(2) == 1 and f9.clear_count(3) == 1 | 集成通关次数表全 1。 | 迁集成测试(tests/integration/cross_system_flow_test.gd) | | -| 8 | 122 | "集成三关首杀标记齐" | f9.boss_first_kill_marked(1) and f9.boss_first_kill_marked(2) and f9.boss_first_kill_marked(3) | 集成三关首杀标记齐。 | 迁集成测试(tests/integration/cross_system_flow_test.gd) | | -| 9 | 123 | "集成 recent 1 条且 seed 42" | f9.recent_summaries().size() == 1 and int(f9.recent_summaries()[0].get("master_seed", -1)) == 42 | 集成摘要 1 条且种子正确。 | 迁集成测试(tests/integration/cross_system_flow_test.gd) | | -| 10 | 126 | "第二局后通关次数全 2" | f9.clear_count(1) == 2 and f9.clear_count(2) == 2 and f9.clear_count(3) == 2 | 第二局后通关次数全 2。 | 迁集成测试(tests/integration/cross_system_flow_test.gd) | | -| 11 | 127 | "第二局后余额翻倍(2 × Σ首杀——模拟驱动恒首杀口径)" | f9.keystone_balance() == 2 * ks_run | 第二局后余额翻倍。 | 迁集成测试(tests/integration/cross_system_flow_test.gd) | | -| 12 | 128 | "集成 recent 长度 2" | f9.recent_summaries().size() == 2 | 集成摘要长度 2。 | 迁集成测试(tests/integration/cross_system_flow_test.gd) | | -| 13 | 129 | "集成写盘成功" | f9.write_to_disk() | 集成写盘成功。 | 迁集成测试(tests/integration/cross_system_flow_test.gd) | | -| 14 | 131 | "集成写盘读回全量保持" | r9.load_from_disk() and r9.keystone_balance() == 2 * ks_run and r9.clear_count(3) == 2 and r9.recent_summaries().size() == 2 and r9.boss_first_kill_marked(3) | 集成写盘读回全量保持。 | 迁集成测试(tests/integration/cross_system_flow_test.gd) | | +| 5 | 119 | "摘要集成:冒险驱动满配通关(seed 42)" | String(st9.get("result", "")) == "completed" | (待填) | | | +| 6 | 120 | "集成钥石余额 = ΣBoss 首杀行值(%d——逐杀掉落全源)" ↩ % f9.keystone_balance() | f9.keystone_balance() == ks_run | (待填) | | | +| 7 | 121 | "集成通关次数表全 1" | f9.clear_count(1) == 1 and f9.clear_count(2) == 1 and f9.clear_count(3) == 1 | (待填) | | | +| 8 | 122 | "集成三关首杀标记齐" | f9.boss_first_kill_marked(1) and f9.boss_first_kill_marked(2) and f9.boss_first_kill_marked(3) | (待填) | | | +| 9 | 123 | "集成 recent 1 条且 seed 42" | f9.recent_summaries().size() == 1 and int(f9.recent_summaries()[0].get("master_seed", -1)) == 42 | (待填) | | | +| 10 | 126 | "第二局后通关次数全 2" | f9.clear_count(1) == 2 and f9.clear_count(2) == 2 and f9.clear_count(3) == 2 | (待填) | | | +| 11 | 127 | "第二局后余额翻倍(2 × Σ首杀——模拟驱动恒首杀口径)" | f9.keystone_balance() == 2 * ks_run | (待填) | | | +| 12 | 128 | "集成 recent 长度 2" | f9.recent_summaries().size() == 2 | (待填) | | | +| 13 | 129 | "集成写盘成功" | f9.write_to_disk() | (待填) | | | +| 14 | 131 | "集成写盘读回全量保持" | r9.load_from_disk() and r9.keystone_balance() == 2 * ks_run and r9.clear_count(3) == 2 and r9.recent_summaries().size() == 2 and r9.boss_first_kill_marked(3) | (待填) | | | ## S04 _smoke_playtest_logger(行 149–182 · 8 条 · 源 game/tests/integration/cross_system_flow_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 15 | 149 | "会话文件开启(%s)" ↩ % path | path.contains("playtest_20260912") | 试玩日志会话文件开启(文件名含种子)。 | 迁集成测试(tests/integration/cross_system_flow_test.gd) | | -| 16 | 150 | "快照战斗启动" | settlement.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(_db.hero.max_hp)}) | 快照战斗启动。 | 迁集成测试(tests/integration/cross_system_flow_test.gd) | | -| 17 | 154 | "每结算一快照(%d 次结算 → %d 快照)" ↩ % [2, snaps.size()] | snaps.size() == 2 | 每结算恰一快照(2 结算→2 快照)。 | 迁集成测试(tests/integration/cross_system_flow_test.gd) | | -| 18 | 161 | "快照字段齐备(缺 %s)" ↩ % str(missing) | missing.is_empty() | 快照必需字段齐备(12 键零缺)。 | 迁集成测试(tests/integration/cross_system_flow_test.gd) | | -| 19 | 162 | "快照含英雄/经济/板序号(板 seq=%d)" ↩ % int(s1.board.seq) | int(s1.hero.max_hp) == int(_db.hero.max_hp) and s1.economy.has("phase") and s1.board.has("seq") | 快照含英雄/经济/板序号。 | 迁集成测试(tests/integration/cross_system_flow_test.gd) | | -| 20 | 165 | "快照 spawn_count 在位且 == unit_count(无击杀轮:%d/%d——全灭/空场可判别)" ↩ % [int(s1.get("spawn_count", -1)), int(s1.get("unit_count", -1))] | int(s1.get("spawn_count", -1)) >= 1 and int(s1.get("spawn_count", -1)) == int(s1.get("unit_count", -2)) | 快照 spawn_count 在位且等于单位数(无击杀轮全灭/空场可判别——Bug#1 回归)。 | 迁集成测试(tests/integration/cross_system_flow_test.gd) | | -| 21 | 169 | "诊断转储文件生成(%s)" ↩ % diag | diag.contains("diag_") | 诊断转储文件生成。 | 迁集成测试(tests/integration/cross_system_flow_test.gd) | | -| 22 | 182 | "JSONL 全部行可解析(%d 行 = session + 2 结算)" ↩ % lines | lines >= 3 and parse_ok | JSONL 全部行可解析(会话+2 结算)。 | 迁集成测试(tests/integration/cross_system_flow_test.gd) | | +| 15 | 149 | "会话文件开启(%s)" ↩ % path | path.contains("playtest_20260912") | (待填) | | | +| 16 | 150 | "快照战斗启动" | settlement.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(_db.hero.max_hp)}) | (待填) | | | +| 17 | 154 | "每结算一快照(%d 次结算 → %d 快照)" ↩ % [2, snaps.size()] | snaps.size() == 2 | (待填) | | | +| 18 | 161 | "快照字段齐备(缺 %s)" ↩ % str(missing) | missing.is_empty() | (待填) | | | +| 19 | 162 | "快照含英雄/经济/板序号(板 seq=%d)" ↩ % int(s1.board.seq) | int(s1.hero.max_hp) == int(_db.hero.max_hp) and s1.economy.has("phase") and s1.board.has("seq") | (待填) | | | +| 20 | 165 | "快照 spawn_count 在位且 == unit_count(无击杀轮:%d/%d——全灭/空场可判别)" ↩ % [int(s1.get("spawn_count", -1)), int(s1.get("unit_count", -1))] | int(s1.get("spawn_count", -1)) >= 1 and int(s1.get("spawn_count", -1)) == int(s1.get("unit_count", -2)) | (待填) | | | +| 21 | 169 | "诊断转储文件生成(%s)" ↩ % diag | diag.contains("diag_") | (待填) | | | +| 22 | 182 | "JSONL 全部行可解析(%d 行 = session + 2 结算)" ↩ % lines | lines >= 3 and parse_ok | (待填) | | | ## S05 _smoke_generated_map_determinism(行 43–112 · 21 条 · 源 game/tests/integration/node_map_determinism_test.gd · 去向:待分类回填) @@ -79,8 +79,8 @@ | 39 | 94 | "缺生成图代差标记键的旧断点被显式拒绝(禁按新语义静默回放)〔v8〕" | adr_old.resume_adventure(bp_old, {}) == false | (待填) | | | | 40 | 101 | "level 表三行均无 templates 键(模板数据已清出)〔v9〕" | tpl_free and _db.level_count() == 3 | (待填) | | | | 41 | 106 | "新档首次冒险布尔位 = false(默认域——首局恒走固定种子 42)" | sv.is_first_adventure_done() == false | (待填) | | | -| 42 | 110 | "冒险收束点置位(三结局同写——写入只在收束挂点)" | sv.is_first_adventure_done() == true | 冒险收束点把布尔位置 true(三结局同写、只在收束挂点)。 | 迁集成测试(tests/integration/node_map_determinism_test.gd) | | -| 43 | 112 | "置位幂等(重复收束不翻转)" | sv.is_first_adventure_done() == true | 置位幂等:重复收束不翻转。 | 迁集成测试(tests/integration/node_map_determinism_test.gd) | | +| 42 | 110 | "冒险收束点置位(三结局同写——写入只在收束挂点)" | sv.is_first_adventure_done() == true | (待填) | | | +| 43 | 112 | "置位幂等(重复收束不翻转)" | sv.is_first_adventure_done() == true | (待填) | | | ## S06 _init(行 27–27 · 1 条 · 源 game/tests/integration/save_rng_roundtrip_test.gd · 去向:待分类回填) @@ -97,2476 +97,2545 @@ | 47 | 89 | "旧档用例:盘上档可解析" | false | (待填) | | | | 48 | 98 | "旧档用例:旧档可落盘" | false | (待填) | | | -## S08 _split_tail(行 45–45 · 1 条 · 源 game/tests/system_test.gd · 去向:待分类回填) +## S08 _split_tail(行 46–46 · 1 条 · 源 game/tests/system_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 49 | 45 | "V2/V3 前置:autoload 链未就绪(ConfigDB/SaveManager)" | false | autoload 链(ConfigDB/SaveManager)未就绪时必须显式红,禁静默跳过尾段。 | 迁系统测试(tests/system_test.gd) | | +| 49 | 46 | "V2/V3 前置:autoload 链未就绪(ConfigDB/SaveManager)" | false | (待填) | | | -## S09 _smoke_scene_split(行 58–110 · 11 条 · 源 game/tests/system_test.gd · 去向:待分类回填) +## S09 _smoke_scene_split(行 59–111 · 11 条 · 源 game/tests/system_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 50 | 58 | "V2 菜单屏壳独立实例化(start_overlay+操作组三钮在位——主菜单三入口化)〔卡② mn1〕" | menu.start_overlay != null and menu.entersavebtn != null and menu.settingsbtn != null and menu.quitbtn != null | 菜单屏壳独立实例化后开始浮层与操作组六钮句柄齐备。 | 迁系统测试(tests/system_test.gd) | | -| 51 | 60 | "V2 菜单层默认可见(非模态全屏态)" | menu.start_overlay.visible | 开始层(菜单)默认可见(非模态全屏态)。 | 迁系统测试(tests/system_test.gd) | | -| 52 | 61 | "V2 屏壳根零鼠标截获(IGNORE——全屏 STOP 壳吃输入病灶回归闸)" | menu.mouse_filter == Control.MOUSE_FILTER_IGNORE | 菜单屏壳根不截获鼠标(IGNORE——全屏 STOP 壳吃输入的病灶回归闸)。 | 迁系统测试(tests/system_test.gd) | | -| 53 | 66 | "V2 营地屏壳独立实例化(camp_overlay 在位)" | camp.camp_overlay != null | 营地屏壳独立实例化营地浮层在位。 | 迁系统测试(tests/system_test.gd) | | -| 54 | 67 | "V2 营地层默认隐藏(模态纪律)" | not camp.camp_overlay.visible | 营地浮层默认隐藏(模态纪律)。 | 迁系统测试(tests/system_test.gd) | | -| 55 | 72 | "V2 冒险屏壳独立实例化(战斗宿主+四区句柄+关卡地图屏在位)" | adv.battle_root != null and adv.stage != null and adv.board_zone != null and adv.board_view != null and adv.ap_bar != null and adv.level_screen != null | 冒险屏壳独立实例化:战斗宿主、四区句柄、关卡地图屏在位。 | 迁系统测试(tests/system_test.gd) | | -| 56 | 83 | "V1a 启动态顶层可见层=start_overlay(实测顶层=%s——菜单被冒险屏盖住即红)〔装配生命批 V1〕" ↩ % (top.name if top != null else "null") | top == main._start_overlay | 启动态自树顶向下第一个可见内容层=开始浮层(菜单被冒险屏盖住即红)。 | 迁系统测试(tests/system_test.gd) | | -| 57 | 87 | "V1b 菜单屏壳在冒险屏壳之上(start_overlay 盖住战斗内容)〔装配生命批 V1〕" | main._menu_screen.get_index() > main._adventure_screen.get_index() | 菜单屏壳树序在冒险屏壳之上(开始层盖住战斗内容)。 | 迁系统测试(tests/system_test.gd) | | -| 58 | 105 | "V1c 合同树序链逐对保序(运行期树序==LAYER_CONTRACT 序;违例:%s)〔装配生命批 V1〕" ↩ % chain_detail | chain_ok | 运行期组合根树序与层序合同逐对保序(违例细节进标签)。 | 迁系统测试(tests/system_test.gd) | | -| 59 | 107 | "V2 组合根句柄指向屏壳成员(探针挂载假设 main.X 零迁移)" | main._start_overlay == main._menu_screen.start_overlay and main.camp_overlay == main._camp_screen.camp_overlay and main.level_screen == main._adventure_screen.level_screen and main._battle_root == main._adventure_screen.battle_root and main.board_view == main._adventure_screen.board_view | 组合根一级句柄指向屏壳成员(探针挂载假设 main.X 零迁移)。 | 迁系统测试(tests/system_test.gd) | | -| 60 | 109 | "V2 闲时会话自 _ready 即在(永非 null;迭代 27 T3——gp 影子账退役无账本成员)" | main.run_scope != null and not main.run_scope.is_adventure | 闲时会话自 _ready 即在、永非空且非冒险态。 | 迁系统测试(tests/system_test.gd) | | +| 50 | 59 | "V2 菜单屏壳独立实例化(start_overlay+操作组三钮在位——主菜单三入口化)〔卡② mn1〕" | menu.start_overlay != null and menu.entersavebtn != null and menu.settingsbtn != null and menu.quitbtn != null | (待填) | | | +| 51 | 61 | "V2 菜单层默认可见(非模态全屏态)" | menu.start_overlay.visible | (待填) | | | +| 52 | 62 | "V2 屏壳根零鼠标截获(IGNORE——全屏 STOP 壳吃输入病灶回归闸)" | menu.mouse_filter == Control.MOUSE_FILTER_IGNORE | (待填) | | | +| 53 | 67 | "V2 营地屏壳独立实例化(camp_overlay 在位)" | camp.camp_overlay != null | (待填) | | | +| 54 | 68 | "V2 营地层默认隐藏(模态纪律)" | not camp.camp_overlay.visible | (待填) | | | +| 55 | 73 | "V2 冒险屏壳独立实例化(战斗宿主+四区句柄+关卡地图屏在位)" | adv.battle_root != null and adv.stage != null and adv.board_zone != null and adv.board_view != null and adv.ap_bar != null and adv.level_screen != null | (待填) | | | +| 56 | 84 | "V1a 启动态顶层可见层=start_overlay(实测顶层=%s——菜单被冒险屏盖住即红)〔装配生命批 V1〕" ↩ % (top.name if top != null else "null") | top == main._start_overlay | (待填) | | | +| 57 | 88 | "V1b 菜单屏壳在冒险屏壳之上(start_overlay 盖住战斗内容)〔装配生命批 V1〕" | main._menu_screen.get_index() > main._adventure_screen.get_index() | (待填) | | | +| 58 | 106 | "V1c 合同树序链逐对保序(运行期树序==LAYER_CONTRACT 序;违例:%s)〔装配生命批 V1〕" ↩ % chain_detail | chain_ok | (待填) | | | +| 59 | 108 | "V2 组合根句柄指向屏壳成员(探针挂载假设 main.X 零迁移)" | main._start_overlay == main._menu_screen.start_overlay and main.camp_overlay == main._camp_screen.camp_overlay and main.level_screen == main._adventure_screen.level_screen and main._battle_root == main._adventure_screen.battle_root and main.board_view == main._adventure_screen.board_view | (待填) | | | +| 60 | 110 | "V2 闲时会话自 _ready 即在(永非 null;迭代 27 T3——gp 影子账退役无账本成员)" | main.run_scope != null and not main.run_scope.is_adventure | (待填) | | | -## S10 _smoke_boot_chain(行 156–208 · 15 条 · 源 game/tests/system_test.gd · 去向:待分类回填) +## S10 _smoke_boot_chain(行 157–207 · 14 条 · 源 game/tests/system_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 61 | 156 | "V2a ConfigDB 就绪且表已载(is_ready+hero 表在座)〔装配生命批 V2〕" | db.is_ready and db.hero != null | autoload ConfigDB 就绪且主角表已载。 | 迁系统测试(tests/system_test.gd) | | -| 62 | 161 | "V2b 设置已应用·音量(Master mute/volume_db=settings_store 现值 linear=%f)〔装配生命批 V2〕" ↩ % ss.volume_linear | AudioServer.is_bus_mute(master) == (ss.volume_linear <= 0.001) and is_equal_approx(AudioServer.get_bus_volume_db(master), linear_to_db(maxf(ss.volume_linear, 0.001))) | 引擎 Master 总线静音/音量与设置存储现值一致(真实设置文件已应用)。 | 迁系统测试(tests/system_test.gd) | | -| 63 | 165 | "V2b 设置已应用·减动效(UiKit.motion_scale=%s=settings_store.reduced_motion=%s)〔装配生命批 V2〕" ↩ % [ str(ui_kit.motion_scale), str(ss.reduced_motion)] | ui_kit.motion_scale == (0.5 if ss.reduced_motion else 1.0) | UiKit 减动效系数随设置 reduced_motion 取 0.5/1.0。 | 迁系统测试(tests/system_test.gd) | | -| 64 | 169 | "V2c 启动态 _start_overlay.visible=true(boot 后玩家见菜单)〔装配生命批 V2〕" | main._start_overlay.visible | 启动后开始层可见(玩家开机见菜单)。 | 迁系统测试(tests/system_test.gd) | | -| 65 | 175 | "V2d boot 无断点在档(启动态不隐式激活——卡① sv2 钉死)〔装配生命批 V2·卡② mn8 改写〕" | not sm.has_breakpoint() | (待填) | | | -| 66 | 178 | "V2-s1 boot 后无激活态(active_slot=0、写盘目标空)〔卡① sv2〕" | sm.active_slot() == 0 and not sm.has_write_target() | boot 后无激活态:激活槽 0、无写盘目标。 | 迁系统测试(tests/system_test.gd) | | -| 67 | 180 | "V2-s2 boot 内存=默认合法空态(钥石/断点/总局数全零值)〔卡① sv2〕" | sm.keystone_balance() == 0 and not sm.has_breakpoint() and sm.adventure_total() == 0 | boot 内存为合法空态:钥石 0、无断点、总局数 0。 | 迁系统测试(tests/system_test.gd) | | -| 68 | 182 | "V2-s3 boot 无激活写盘拒绝(write_to_disk=false 生产入口)〔卡① sv3〕" | not sm.write_to_disk() | 无激活态调写盘被拒(生产入口返回 false)。 | 迁系统测试(tests/system_test.gd) | | -| 69 | 184 | "V2-s4 旧单档迁移不变式(旧档在则槽 1 必在——Q1 甲一次性迁移已发生;旧档在盘面罕见,迁移逻辑本体由 save_manager_domain v0 迁移链断言覆盖)〔卡① sv12·核代码增补注〕" | not (FileAccess.file_exists("user://save.json") and not FileAccess.file_exists(sm.slot_path(1))) | 旧档在盘则迁移后槽 1 必在(一次性迁移不变式)。 | 已删除(2026-10-25 用户裁——世界态断言读真实磁盘 user://,红前提「旧档在而槽 1 缺」本机永久不可达;迁移本体由 save_manager_domain v0 迁移链断言覆盖) | | -| 70 | 192 | "V2d 三钮在位·进入存档/设置/退出游戏(续玩钮族退役后主菜单操作组承接)〔卡② mn1/mn8〕" | main._menu_screen.entersavebtn != null and main._menu_screen.settingsbtn != null and main._menu_screen.quitbtn != null | 隔离档无断点时续玩钮隐藏(刷新单口与 boot 路径同源)。 | 迁系统测试(tests/system_test.gd) | | -| 71 | 196 | "V2d 四旧钮句柄零命中·%s(彻底撤出——禁隐藏态禁置灰态残留)〔卡② mn2〕" ↩ % old_btn | not (old_btn in main._menu_screen) | 设断点后续玩钮可见。 | 迁系统测试(tests/system_test.gd) | | -| 72 | 198 | "V2d 渲染面可见按钮计数==3(旧钮接回即红——渲染面锚,句柄缝盲区闭合)〔卡② mn1/mn2〕" | _count_visible_buttons(main._start_overlay) == 3 | 清断点后续玩钮回隐藏。 | 迁系统测试(tests/system_test.gd) | | -| 73 | 201 | "V2e run_scope 非空且为闲时会话(is_adventure=false+is_inside_tree)〔装配生命批 V2〕" | main.run_scope != null and not main.run_scope.is_adventure and main.run_scope.is_inside_tree() | 闲时会话 _ready 即建、在树、非冒险态(永非空)。 | 迁系统测试(tests/system_test.gd) | | -| 74 | 205 | "V2f 调试浮窗 boot 不自动开(菜单优先)〔2026-10-16 用户令〕" | not main.debug_overlay.visible | boot 不自动开调试浮窗(菜单优先)。 | 迁系统测试(tests/system_test.gd) | | -| 75 | 207 | "V2f 调试浮层角标 boot 可见(全局浮层入口在座)〔2026-10-16 用户令〕" | main.debug_badge != null and main.debug_badge.visible | 调试浮层入口角标 boot 可见。 | 迁系统测试(tests/system_test.gd) | | +| 61 | 157 | "V2a ConfigDB 就绪且表已载(is_ready+hero 表在座)〔装配生命批 V2〕" | db.is_ready and db.hero != null | (待填) | | | +| 62 | 162 | "V2b 设置已应用·音量(Master mute/volume_db=settings_store 现值 linear=%f)〔装配生命批 V2〕" ↩ % ss.volume_linear | AudioServer.is_bus_mute(master) == (ss.volume_linear <= 0.001) and is_equal_approx(AudioServer.get_bus_volume_db(master), linear_to_db(maxf(ss.volume_linear, 0.001))) | (待填) | | | +| 63 | 166 | "V2b 设置已应用·减动效(UiKit.motion_scale=%s=settings_store.reduced_motion=%s)〔装配生命批 V2〕" ↩ % [ str(ui_kit.motion_scale), str(ss.reduced_motion)] | ui_kit.motion_scale == (0.5 if ss.reduced_motion else 1.0) | (待填) | | | +| 64 | 170 | "V2c 启动态 _start_overlay.visible=true(boot 后玩家见菜单)〔装配生命批 V2〕" | main._start_overlay.visible | (待填) | | | +| 65 | 176 | "V2d boot 无断点在档(启动态不隐式激活——卡① sv2 钉死)〔装配生命批 V2·卡② mn8 改写〕" | not sm.has_breakpoint() | (待填) | | | +| 66 | 179 | "V2-s1 boot 后无激活态(active_slot=0、写盘目标空)〔卡① sv2〕" | sm.active_slot() == 0 and not sm.has_write_target() | (待填) | | | +| 67 | 181 | "V2-s2 boot 内存=默认合法空态(钥石/断点/总局数全零值)〔卡① sv2〕" | sm.keystone_balance() == 0 and not sm.has_breakpoint() and sm.adventure_total() == 0 | (待填) | | | +| 68 | 183 | "V2-s3 boot 无激活写盘拒绝(write_to_disk=false 生产入口)〔卡① sv3〕" | not sm.write_to_disk() | (待填) | | | +| 69 | 191 | "V2d 三钮在位·进入存档/设置/退出游戏(续玩钮族退役后主菜单操作组承接)〔卡② mn1/mn8〕" | main._menu_screen.entersavebtn != null and main._menu_screen.settingsbtn != null and main._menu_screen.quitbtn != null | (待填) | | | +| 70 | 195 | "V2d 四旧钮句柄零命中·%s(彻底撤出——禁隐藏态禁置灰态残留)〔卡② mn2〕" ↩ % old_btn | not (old_btn in main._menu_screen) | (待填) | | | +| 71 | 197 | "V2d 渲染面可见按钮计数==3(旧钮接回即红——渲染面锚,句柄缝盲区闭合)〔卡② mn1/mn2〕" | _count_visible_buttons(main._start_overlay) == 3 | (待填) | | | +| 72 | 200 | "V2e run_scope 非空且为闲时会话(is_adventure=false+is_inside_tree)〔装配生命批 V2〕" | main.run_scope != null and not main.run_scope.is_adventure and main.run_scope.is_inside_tree() | (待填) | | | +| 73 | 204 | "V2f 调试浮窗 boot 不自动开(菜单优先)〔2026-10-16 用户令〕" | not main.debug_overlay.visible | (待填) | | | +| 74 | 206 | "V2f 调试浮层角标 boot 可见(全局浮层入口在座)〔2026-10-16 用户令〕" | main.debug_badge != null and main.debug_badge.visible | (待填) | | | -## S11 _smoke_run_lifecycle(行 232–264 · 11 条 · 源 game/tests/system_test.gd · 去向:待分类回填) +## S11 _smoke_run_lifecycle(行 231–263 · 11 条 · 源 game/tests/system_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 76 | 232 | "V3 开局建会话(is_adventure+adventure 在座)" | main.run_scope != null and main.run_scope.is_adventure and main.run_scope.adventure != null | 开局建冒险会话:冒险态真、冒险对象在座。 | 迁系统测试(tests/system_test.gd) | | -| 77 | 234 | "V3 新会话局态零值" | main.run_scope.hints_shown.is_empty() | 新会话的提示已显示集为空(局态零值起点)。 | 迁系统测试(tests/system_test.gd) | | -| 78 | 243 | "V3 放弃受理(入口停点天然非战斗期)" | main.run_scope.adventure.abandon() | 入口停点放弃受理。 | 迁系统测试(tests/system_test.gd) | | -| 79 | 248 | "V3 冒险终局到达(abandon→adventure_finished 同步链)" | finished.done | 放弃→冒险完成信号同步链到达(终局必达)。 | 迁系统测试(tests/system_test.gd) | | -| 80 | 249 | "V3 终局面板弹起" | main._end_overlay.is_open() | 终局面板弹起。 | 迁系统测试(tests/system_test.gd) | | -| 81 | 252 | "V3 会话收束(新闲时会话零冒险/关卡对象)" | not main.run_scope.is_adventure and main.run_scope.adventure == null and main.run_scope.runner == null | 会话收束后闲时会话零冒险/关卡/战斗对象。 | 迁系统测试(tests/system_test.gd) | | -| 82 | 254 | "V3 局态封印/账本全复位(queue_free 物化——注入脏状态零残留)" | main.run_scope.hints_shown.is_empty() and not main.run_scope.boss_active and main.run_scope.last_gain_amount == 0 | 注入的脏状态零残留:提示集/旗标/账本值全复位。 | 迁系统测试(tests/system_test.gd) | | -| 83 | 256 | "V3 回开始菜单(终局出口路径)" | main._start_overlay.visible | 终局出口路径回到开始菜单。 | 迁系统测试(tests/system_test.gd) | | -| 84 | 258 | "V3 放弃终局清断点(无残留断点)" | not sm.has_breakpoint() | (待填) | | | -| 85 | 262 | "V3 第二连局全新会话(两连局零泄漏)" | main.run_scope.is_adventure and main.run_scope.adventure != null and main.run_scope.hints_shown.is_empty() | 第二连局全新会话且局态零值(两连局零泄漏)。 | 迁系统测试(tests/system_test.gd) | | -| 86 | 264 | "V3 直进入局(菜单隐)" | not main._start_overlay.visible | 直进入局后开始菜单隐藏。 | 迁系统测试(tests/system_test.gd) | | +| 75 | 231 | "V3 开局建会话(is_adventure+adventure 在座)" | main.run_scope != null and main.run_scope.is_adventure and main.run_scope.adventure != null | (待填) | | | +| 76 | 233 | "V3 新会话局态零值" | main.run_scope.hints_shown.is_empty() | (待填) | | | +| 77 | 242 | "V3 放弃受理(入口停点天然非战斗期)" | main.run_scope.adventure.abandon() | (待填) | | | +| 78 | 247 | "V3 冒险终局到达(abandon→adventure_finished 同步链)" | finished.done | (待填) | | | +| 79 | 248 | "V3 终局面板弹起" | main._end_overlay.is_open() | (待填) | | | +| 80 | 251 | "V3 会话收束(新闲时会话零冒险/关卡对象)" | not main.run_scope.is_adventure and main.run_scope.adventure == null and main.run_scope.runner == null | (待填) | | | +| 81 | 253 | "V3 局态封印/账本全复位(queue_free 物化——注入脏状态零残留)" | main.run_scope.hints_shown.is_empty() and not main.run_scope.boss_active and main.run_scope.last_gain_amount == 0 | (待填) | | | +| 82 | 255 | "V3 回开始菜单(终局出口路径)" | main._start_overlay.visible | (待填) | | | +| 83 | 257 | "V3 放弃终局清断点(无残留断点)" | not sm.has_breakpoint() | (待填) | | | +| 84 | 261 | "V3 第二连局全新会话(两连局零泄漏)" | main.run_scope.is_adventure and main.run_scope.adventure != null and main.run_scope.hints_shown.is_empty() | (待填) | | | +| 85 | 263 | "V3 直进入局(菜单隐)" | not main._start_overlay.visible | (待填) | | | -## S12 _stage_faces(行 354–354 · 1 条 · 源 game/tests/system_test.gd · 去向:待分类回填) +## S12 _smoke_sim_guards(行 284–300 · 4 条 · 源 game/tests/system_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 87 | 354 | "%s(引用面违例:%s)〔装配生命批 V3〕" ↩ % [tag, bad] | bad.is_empty() | 阶段引用面零违例:恒活面对象全有效、现值回调与 run_scope 同源、存活态与附加判据全过。 | 迁系统测试(tests/system_test.gd) | | +| 86 | 284 | "前置:闲时会话(settlement 空 + is_adventure=false)" | main.run_scope.settlement == null and not main.run_scope.is_adventure | (待填) | | | +| 87 | 289 | "模拟运行态下调试两层入口守卫早退(开始层不收起)〔wd6〕" | main.run_scope.settlement == null and main._start_overlay.visible | (待填) | | | +| 88 | 296 | "前置:真实冒险关间态(选路停点,非战斗)" | main.run_scope.is_adventure and main.run_scope.settlement == null | (待填) | | | +| 89 | 299 | "冒险关间态下批量模拟入口守卫早退(d7 补 is_adventure)〔wd7〕" | not main._tune_sim.is_running() | (待填) | | | -## S13 _smoke_lifecycle_matrix(行 427–550 · 9 条 · 源 game/tests/system_test.gd · 去向:待分类回填) +## S13 _stage_faces(行 394–394 · 1 条 · 源 game/tests/system_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 88 | 427 | "V3d 活战斗驱动完毕(战斗已收场——%d 轮,finished=%s)〔装配生命批 V3〕" ↩ % [guard, str(finished.done)] | main.run_scope.settlement == null or not main.run_scope.settlement.is_battle_live() | 活战斗自动驱动能把战斗打到收场(防驱动死循环)。 | 迁系统测试(tests/system_test.gd) | | -| 89 | 441 | "V3d2 放弃受理(选路停点天然非战斗期)〔装配生命批 V3〕" | main.run_scope.adventure.abandon() | 胜利停点后的选路停点放弃受理(天然非战斗期)。 | 迁系统测试(tests/system_test.gd) | | -| 90 | 452 | "V3e 终局面到达(adventure_finished+终局面板开)〔装配生命批 V3〕" | finished.done and main._end_overlay.is_open() | 冒险终局到达:完成信号已发+终局面板已开。 | 迁系统测试(tests/system_test.gd) | | -| 91 | 458 | "V3f 收束换会话无真空窗(新闲时会话即刻在树)〔装配生命批 V3〕" | main.run_scope != old_scope and main.run_scope.is_inside_tree() | 收束回菜单换新闲时会话且即刻在树(无真空窗)。 | 迁系统测试(tests/system_test.gd) | | -| 92 | 461 | "V3f 收束后旧局经 queue_free 消亡(is_instance_valid=false)〔装配生命批 V3〕" | not is_instance_valid(old_scope) | 旧局会话对象经队列释放真正消亡(无僵尸引用)。 | 迁系统测试(tests/system_test.gd) | | -| 93 | 481 | "V3g 营地入口缺失(选档层新档钮驱动中断)〔装配生命批 V3〕" | false | 营地入口按钮缺失时驱动显式红(防夹具静默中断)。 | 迁系统测试(tests/system_test.gd) | | -| 94 | 519 | "V3h 两连局第二会话在座(开局停点——新冒险会话非空)〔装配生命批 V3〕" | main.run_scope.adventure != null and main.run_scope.adventure != first_adv | (待填) | | | -| 95 | 522 | "V3h 放弃受理(第二局)〔装配生命批 V3〕" | main.run_scope.adventure.abandon() | 第二局放弃受理。 | 迁系统测试(tests/system_test.gd) | | -| 96 | 549 | "V3i 续玩换会话无真空窗(新冒险会话即刻在树)〔装配生命批 V3〕" | main.run_scope != idle_scope and main.run_scope.is_inside_tree() | 续玩换新冒险会话即刻在树(无真空窗)。 | 迁系统测试(tests/system_test.gd) | | +| 90 | 394 | "%s(引用面违例:%s)〔装配生命批 V3〕" ↩ % [tag, bad] | bad.is_empty() | (待填) | | | -## S14 _init(行 27–27 · 1 条 · 源 game/tests/unit/battle_settlement/damage_controller_test.gd · 去向:待分类回填) +## S14 _smoke_lifecycle_matrix(行 467–590 · 9 条 · 源 game/tests/system_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 97 | 27 | "前置:表加载零 error" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | +| 91 | 467 | "V3d 活战斗驱动完毕(战斗已收场——%d 轮,finished=%s)〔装配生命批 V3〕" ↩ % [guard, str(finished.done)] | main.run_scope.settlement == null or not main.run_scope.settlement.is_battle_live() | (待填) | | | +| 92 | 481 | "V3d2 放弃受理(选路停点天然非战斗期)〔装配生命批 V3〕" | main.run_scope.adventure.abandon() | (待填) | | | +| 93 | 492 | "V3e 终局面到达(adventure_finished+终局面板开)〔装配生命批 V3〕" | finished.done and main._end_overlay.is_open() | (待填) | | | +| 94 | 498 | "V3f 收束换会话无真空窗(新闲时会话即刻在树)〔装配生命批 V3〕" | main.run_scope != old_scope and main.run_scope.is_inside_tree() | (待填) | | | +| 95 | 501 | "V3f 收束后旧局经 queue_free 消亡(is_instance_valid=false)〔装配生命批 V3〕" | not is_instance_valid(old_scope) | (待填) | | | +| 96 | 521 | "V3g 营地入口缺失(选档层新档钮驱动中断)〔装配生命批 V3〕" | false | (待填) | | | +| 97 | 559 | "V3h 两连局第二会话在座(开局停点——新冒险会话非空)〔装配生命批 V3〕" | main.run_scope.adventure != null and main.run_scope.adventure != first_adv | (待填) | | | +| 98 | 562 | "V3h 放弃受理(第二局)〔装配生命批 V3〕" | main.run_scope.adventure.abandon() | (待填) | | | +| 99 | 589 | "V3i 续玩换会话无真空窗(新冒险会话即刻在树)〔装配生命批 V3〕" | main.run_scope != idle_scope and main.run_scope.is_inside_tree() | (待填) | | | -## S15 _run_recipe_sequence(行 73–163 · 11 条 · 源 game/tests/unit/battle_settlement/damage_controller_test.gd · 去向:待分类回填) +## S15 _init(行 27–27 · 1 条 · 源 game/tests/unit/battle_settlement/damage_controller_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 98 | 73 | "前置:units.spawn(1,0) 成功(遭遇行在案)" | false | (待填) | | | -| 99 | 111 | "五配方单据 flush 后全部 redeemed(B 模式段界结账)" | outcomes_redeemed | (待填) | | | -| 100 | 116 | "主角扣血 = 敌方技行值 + 反噬行值(逐行归因闭合)" | hero_before - hero_after == int(r2.get("value", -1)) + int(r5.get("value", -1)) | (待填) | | | -| 101 | 118 | "敌方技行值 ≥ 下限 + rider(rider 后置不进乘区)" | int(r2.get("value", 0)) >= floor_v + floor_v | (待填) | | | -| 102 | 119 | "反噬行值 = 落地下限(中性无类型表值)" | int(r5.get("value", 0)) == floor_v | (待填) | | | -| 103 | 123 | "主角攻击行 AC = 防守方 AC + 攻击加成(开单前快照一致)" | int(ex1.get("ac", -1)) == threshold_expected | (待填) | | | -| 104 | 124 | "掷点 ∈ [1, 骰面]" | int(ex1.get("roll", -1)) >= 1 and int(ex1.get("roll", -1)) <= int(h.db.settlement.roll_faces) | (待填) | | | -| 105 | 131 | "掷点档位 ∈ 表档位值 ∪ 满额" | tier_ok | (待填) | | | -| 106 | 147 | "敌方总扣血 ≥ 攻击+克制+群伤(死亡溢出合法,下界归因)" | enemy_drop_total >= attack_landed + typed_landed + int(r4.get("value", 0)) * maxi(0, alive_at_blast) | (待填) | | | -| 107 | 150 | "敌方总扣血 = 攻击 + 克制直伤 + 群伤×存活数(逐行归因闭合)" | enemy_drop_total == attack_landed + typed_landed + int(r4.get("value", 0)) * alive_at_blast | (待填) | | | -| 108 | 163 | "行协议公共字段封闭(type/source_class/display/value≥下限/extra.target)" | protocol_ok | (待填) | | | +| 100 | 27 | "前置:表加载零 error" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | -## S16 _test_five_recipes_and_replay(行 174–176 · 2 条 · 源 game/tests/unit/battle_settlement/damage_controller_test.gd · 去向:待分类回填) +## S16 _run_recipe_sequence(行 75–165 · 11 条 · 源 game/tests/unit/battle_settlement/damage_controller_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 109 | 174 | "五配方序列产出 5 行伤害行" | run1.size() == 5 | (待填) | | | -| 110 | 176 | "同种子双跑行序列逐位一致(回放确定性契约)" | run1 == run2 | (待填) | | | +| 101 | 75 | "前置:units.spawn(1,0) 成功(遭遇行在案)" | false | (待填) | | | +| 102 | 113 | "五配方单据 flush 后全部 redeemed(B 模式段界结账)" | outcomes_redeemed | (待填) | | | +| 103 | 118 | "主角扣血 = 敌方技行值 + 反噬行值(逐行归因闭合)" | hero_before - hero_after == int(r2.get("value", -1)) + int(r5.get("value", -1)) | (待填) | | | +| 104 | 120 | "敌方技行值 ≥ 下限 + rider(rider 后置不进乘区)" | int(r2.get("value", 0)) >= floor_v + floor_v | (待填) | | | +| 105 | 121 | "反噬行值 = 落地下限(中性无类型表值)" | int(r5.get("value", 0)) == floor_v | (待填) | | | +| 106 | 125 | "主角攻击行 AC = 防守方 AC + 攻击加成(开单前快照一致)" | int(ex1.get("ac", -1)) == threshold_expected | (待填) | | | +| 107 | 126 | "掷点 ∈ [1, 骰面]" | int(ex1.get("roll", -1)) >= 1 and int(ex1.get("roll", -1)) <= int(h.db.settlement.roll_faces) | (待填) | | | +| 108 | 133 | "掷点档位 ∈ 表档位值 ∪ 满额" | tier_ok | (待填) | | | +| 109 | 149 | "敌方总扣血 ≥ 攻击+克制+群伤(死亡溢出合法,下界归因)" | enemy_drop_total >= attack_landed + typed_landed + int(r4.get("value", 0)) * maxi(0, alive_at_blast) | (待填) | | | +| 110 | 152 | "敌方总扣血 = 攻击 + 克制直伤 + 群伤×存活数(逐行归因闭合)" | enemy_drop_total == attack_landed + typed_landed + int(r4.get("value", 0)) * alive_at_blast | (待填) | | | +| 111 | 165 | "行协议公共字段封闭(type/source_class/display/value≥下限/extra.target)" | protocol_ok | (待填) | | | -## S17 _test_policy_immediate_mode(行 190–191 · 2 条 · 源 game/tests/unit/battle_settlement/damage_controller_test.gd · 去向:待分类回填) +## S17 _test_five_recipes_and_replay(行 176–178 · 2 条 · 源 game/tests/unit/battle_settlement/damage_controller_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 111 | 190 | "A 模式开单即结(无 flush 即 redeemed)" | h.ctrl.outcome(t) == "redeemed" | (待填) | | | -| 112 | 191 | "A 模式主角即时扣血" | int(h.units.hero_unit().hp) == int(h.db.hero.max_hp) - floor_v | (待填) | | | +| 112 | 176 | "五配方序列产出 5 行伤害行" | run1.size() == 5 | (待填) | | | +| 113 | 178 | "同种子双跑行序列逐位一致(回放确定性契约)" | run1 == run2 | (待填) | | | -## S18 _test_source_death_void(行 210–211 · 2 条 · 源 game/tests/unit/battle_settlement/damage_controller_test.gd · 去向:待分类回填) +## S18 _test_policy_immediate_mode(行 192–193 · 2 条 · 源 game/tests/unit/battle_settlement/damage_controller_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 113 | 210 | "来源敌死亡后其未结单据 voided(死亡即不动手)" | h.ctrl.outcome(t) == "voided" | (待填) | | | -| 114 | 211 | "作废单零行零扣血" | int(h.units.hero_unit().hp) == hero_before and _rows.is_empty() | (待填) | | | +| 114 | 192 | "A 模式开单即结(无 flush 即 redeemed)" | h.ctrl.outcome(t) == "redeemed" | (待填) | | | +| 115 | 193 | "A 模式主角即时扣血" | int(h.units.hero_unit().hp) == int(h.db.hero.max_hp) - floor_v | (待填) | | | -## S19 _test_requires_gate_and_zero(行 227–236 · 3 条 · 源 game/tests/unit/battle_settlement/damage_controller_test.gd · 去向:待分类回填) +## S19 _test_source_death_void(行 212–213 · 2 条 · 源 game/tests/unit/battle_settlement/damage_controller_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 115 | 227 | "flush_submit 同步开单即结(清图批契约)" | h.ctrl.outcome(t_clear) == "redeemed" and h.units.query_alive("enemy").is_empty() | (待填) | | | -| 116 | 235 | "无存活敌 blast = 合法零兑付(zero 非 voided)" | h.ctrl.outcome(t_zero) == "zero" | (待填) | | | -| 117 | 236 | "前置单未实际有效 → 后继单 voided(requires 门)" | h.ctrl.outcome(t_gated) == "voided" | (待填) | | | +| 116 | 212 | "来源敌死亡后其未结单据 voided(死亡即不动手)" | h.ctrl.outcome(t) == "voided" | (待填) | | | +| 117 | 213 | "作废单零行零扣血" | int(h.units.hero_unit().hp) == hero_before and _rows.is_empty() | (待填) | | | -## S20 _test_hero_death_halt(行 252–253 · 2 条 · 源 game/tests/unit/battle_settlement/damage_controller_test.gd · 去向:待分类回填) +## S20 _test_identical_content_dual_enemy(行 233–253 · 3 条 · 源 game/tests/unit/battle_settlement/damage_controller_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 118 | 252 | "致死单 redeemed 且主角 hp 归零" | h.ctrl.outcome(t_kill) == "redeemed" and int(h.units.hero_unit().hp) == 0 | (待填) | | | -| 119 | 253 | "主角倒下后余单 voided(flush 终止)" | h.ctrl.outcome(t_after) == "voided" | (待填) | | | +| 118 | 233 | "前置:双敌字典内容全同且非同一实例" | a == b and not is_same(a, b) | (待填) | | | +| 119 | 243 | "同内容异实例敌死亡不误废他人单据〔wd1〕" | h.ctrl.outcome(t) == "redeemed" | (待填) | | | +| 120 | 253 | "来源敌自身死亡单据作废(正向对照)〔wd1〕" | h2.ctrl.outcome(t2) == "voided" | (待填) | | | -## S21 _test_ac_merge(行 264–280 · 4 条 · 源 game/tests/unit/battle_settlement/damage_controller_test.gd · 去向:待分类回填) +## S21 _test_target_id_payload(行 263–277 · 4 条 · 源 game/tests/unit/battle_settlement/damage_controller_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 120 | 264 | "缺省兜底:defender_ac(hero) = hero 表值" | h.ctrl.defender_ac(h.units.hero_unit()) == base_ac | (待填) | | | -| 121 | 267 | "override 合流入参生效" | h.ctrl.defender_ac(h.units.hero_unit()) == override_v | (待填) | | | -| 122 | 275 | "前置:状态表存在 hero 侧 ac 型行" | false | (待填) | | | -| 123 | 280 | "ac 型效果叠层入 AC(层数×幅度,不入池)" | h.ctrl.defender_ac(h.units.hero_unit()) == expected | (待填) | | | +| 121 | 263 | "前置:units.spawn(1,0) 成功(target_id 断言)" | false | (待填) | | | +| 122 | 267 | "前置:目标敌携非空 enemy_id" | expect_id != "" | (待填) | | | +| 123 | 271 | "attack_commit fx 载荷携 target_id〔wd12〕" | String(fx_payload.get("target_id", "")) == expect_id and String(fx_payload.get("target", "")) != "" | (待填) | | | +| 124 | 276 | "伤害行携 target_id == 目标敌 enemy_id〔wd12〕" | not row.is_empty() and String(row.extra.get("target_id", "")) == expect_id | (待填) | | | -## S22 _test_pool_clamp(行 292–337 · 7 条 · 源 game/tests/unit/battle_settlement/damage_controller_test.gd · 去向:待分类回填) +## S22 _test_requires_gate_and_zero(行 291–300 · 3 条 · 源 game/tests/unit/battle_settlement/damage_controller_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 124 | 292 | "空效果池读 0(表 clamp 域含 0 前提)" | h.ctrl.clamped_pool("hero_offense") == 0 and lo <= 0 and hi >= 0 | (待填) | | | -| 125 | 302 | "前置:hero_offense 池存在 percent 型 hero 侧行" | false | (待填) | | | -| 126 | 307 | "池值 = 单实例 L×m 加算后 clamp" | first_ok and h.ctrl.clamped_pool("hero_offense") == expected1 | (待填) | | | -| 127 | 309 | "refresh 政策重复授予受理且层数恒 1(叠层改续时裁决)——池值不变" | second_ok and h.ctrl.clamped_pool("hero_offense") == expected1 | (待填) | | | -| 128 | 320 | "ac 型效果不入池(percent 池隔离)" | h.ctrl.clamped_pool("hero_offense") == before | (待填) | | | -| 129 | 328 | "前置:状态表存在 stack 政策行" | false | (待填) | | | -| 130 | 337 | "stack 政策恰授予 max_stack 层后拒绝(满层拒绝原子)" | granted == max_n | (待填) | | | +| 125 | 291 | "flush_submit 同步开单即结(清图批契约)" | h.ctrl.outcome(t_clear) == "redeemed" and h.units.query_alive("enemy").is_empty() | (待填) | | | +| 126 | 299 | "无存活敌 blast = 合法零兑付(zero 非 voided)" | h.ctrl.outcome(t_zero) == "zero" | (待填) | | | +| 127 | 300 | "前置单未实际有效 → 后继单 voided(requires 门)" | h.ctrl.outcome(t_gated) == "voided" | (待填) | | | -## S23 _init(行 17–17 · 1 条 · 源 game/tests/unit/battle_settlement/damage_math_test.gd · 去向:待分类回填) +## S23 _test_hero_death_halt(行 316–317 · 2 条 · 源 game/tests/unit/battle_settlement/damage_controller_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 131 | 17 | "前置:settlement 表加载零 error" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | +| 128 | 316 | "致死单 redeemed 且主角 hp 归零" | h.ctrl.outcome(t_kill) == "redeemed" and int(h.units.hero_unit().hp) == 0 | (待填) | | | +| 129 | 317 | "主角倒下后余单 voided(flush 终止)" | h.ctrl.outcome(t_after) == "voided" | (待填) | | | -## S24 _test_pool_damage(行 34–39 · 6 条 · 源 game/tests/unit/battle_settlement/damage_math_test.gd · 去向:待分类回填) +## S24 _test_ac_merge(行 328–344 · 4 条 · 源 game/tests/unit/battle_settlement/damage_controller_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 132 | 34 | "中性双池 (0,0) 原样透传" | DamageMathScript.pool_damage(d, 0, 0) == d | (待填) | | | -| 133 | 35 | "负敌方池压低不增高" | DamageMathScript.pool_damage(d, 0, int(db.settlement.pool_clamp_lo)) <= d | (待填) | | | -| 134 | 36 | "正·主角池抬升不低于原值" | DamageMathScript.pool_damage(d, int(db.settlement.pool_clamp_hi), 0) >= d | (待填) | | | -| 135 | 37 | "主角池单调不减(+50% vs +49% 探针档)" | DamageMathScript.pool_damage(d, 50, 0) >= DamageMathScript.pool_damage(d, 49, 0) | (待填) | | | -| 136 | 38 | "对称性:池值互换结果不变(乘算交换)" | DamageMathScript.pool_damage(d, 30, -20) == DamageMathScript.pool_damage(d, -20, 30) | (待填) | | | -| 137 | 39 | "输出非负(maxi(0,·) 封底)" | DamageMathScript.pool_damage(d, int(db.settlement.pool_clamp_lo), int(db.settlement.pool_clamp_lo)) >= 0 | (待填) | | | +| 130 | 328 | "缺省兜底:defender_ac(hero) = hero 表值" | h.ctrl.defender_ac(h.units.hero_unit()) == base_ac | (待填) | | | +| 131 | 331 | "override 合流入参生效" | h.ctrl.defender_ac(h.units.hero_unit()) == override_v | (待填) | | | +| 132 | 339 | "前置:状态表存在 hero 侧 ac 型行" | false | (待填) | | | +| 133 | 344 | "ac 型效果叠层入 AC(层数×幅度,不入池)" | h.ctrl.defender_ac(h.units.hero_unit()) == expected | (待填) | | | -## S25 _test_counter_component(行 46–50 · 4 条 · 源 game/tests/unit/battle_settlement/damage_math_test.gd · 去向:待分类回填) +## S25 _test_pool_clamp(行 356–401 · 7 条 · 源 game/tests/unit/battle_settlement/damage_controller_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 138 | 46 | "空桶(点数≤0)恒 0 不吃修正" | DamageMathScript.counter_component(0, floor_v, floor_v) == 0 and DamageMathScript.counter_component(-1, 5, floor_v) == 0 | (待填) | | | -| 139 | 48 | "正向修正直加" | DamageMathScript.counter_component(floor_v, floor_v, floor_v) == floor_v * 2 | (待填) | | | -| 140 | 49 | "负修正触底保落地伤害下限" | DamageMathScript.counter_component(floor_v, -floor_v * 10, floor_v) == floor_v | (待填) | | | -| 141 | 50 | "下限参数缺省 = 0(无下限语义面)" | DamageMathScript.counter_component(floor_v, -floor_v * 10) == 0 | (待填) | | | +| 134 | 356 | "空效果池读 0(表 clamp 域含 0 前提)" | h.ctrl.clamped_pool("hero_offense") == 0 and lo <= 0 and hi >= 0 | (待填) | | | +| 135 | 366 | "前置:hero_offense 池存在 percent 型 hero 侧行" | false | (待填) | | | +| 136 | 371 | "池值 = 单实例 L×m 加算后 clamp" | first_ok and h.ctrl.clamped_pool("hero_offense") == expected1 | (待填) | | | +| 137 | 373 | "refresh 政策重复授予受理且层数恒 1(叠层改续时裁决)——池值不变" | second_ok and h.ctrl.clamped_pool("hero_offense") == expected1 | (待填) | | | +| 138 | 384 | "ac 型效果不入池(percent 池隔离)" | h.ctrl.clamped_pool("hero_offense") == before | (待填) | | | +| 139 | 392 | "前置:状态表存在 stack 政策行" | false | (待填) | | | +| 140 | 401 | "stack 政策恰授予 max_stack 层后拒绝(满层拒绝原子)" | granted == max_n | (待填) | | | -## S26 _test_roll_discount_percent(行 59–74 · 5 条 · 源 game/tests/unit/battle_settlement/damage_math_test.gd · 去向:待分类回填) +## S26 _init(行 17–17 · 1 条 · 源 game/tests/unit/battle_settlement/damage_math_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 142 | 59 | "前置:档位数 = 边界数 + 1(查档契约——档数 = 边界数 + 1)" | discounts.size() == bounds.size() + 1 and discounts.size() > 0 | (待填) | | | -| 143 | 60 | "掷点 ≥ 阈值恒全额 100" | DamageMathScript.roll_discount_percent(threshold, threshold, bounds, discounts) == 100 and DamageMathScript.roll_discount_percent(threshold + 5, threshold, bounds, discounts) == 100 | (待填) | | | -| 144 | 65 | "缺口 1 落首档" | DamageMathScript.roll_discount_percent(threshold - 1, threshold, bounds, discounts) == tier0_expected | (待填) | | | -| 145 | 68 | "缺口超末边界落末档" | DamageMathScript.roll_discount_percent(threshold - int(bounds[-1]) - 1, threshold, bounds, discounts) == last_expected | (待填) | | | -| 146 | 74 | "档位值随档位加深不升(折扣单调)" | descending | (待填) | | | +| 141 | 17 | "前置:settlement 表加载零 error" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | -## S27 _test_rolled_component(行 82–87 · 5 条 · 源 game/tests/unit/battle_settlement/damage_math_test.gd · 去向:待分类回填) +## S27 _test_pool_damage(行 34–39 · 6 条 · 源 game/tests/unit/battle_settlement/damage_math_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 147 | 82 | "满额折扣无暴击 = floor(基线) + 修正 0" | DamageMathScript.rolled_component(base, 100, false, crit_mult, 0) == int(floor(base)) | (待填) | | | -| 148 | 83 | "满额折扣暴击按表倍率放大" | DamageMathScript.rolled_component(base, 100, true, crit_mult, 0) == int(floor(base * float(crit_mult) / 100.0)) | (待填) | | | -| 149 | 84 | "折扣降低不高于满额" | DamageMathScript.rolled_component(base, 50, false, crit_mult, 0) <= DamageMathScript.rolled_component(base, 100, false, crit_mult, 0) | (待填) | | | -| 150 | 85 | "克制修正加在 floor 之后(加法核)" | DamageMathScript.rolled_component(base, 100, false, crit_mult, _floor_val()) == int(floor(base)) + _floor_val() | (待填) | | | -| 151 | 86 | "复合序 = 基线×折扣%×暴击%(floor 只在链末)" | DamageMathScript.rolled_component(base, 50, true, crit_mult, 0) == int(floor(base * 50.0 / 100.0 * float(crit_mult) / 100.0)) | (待填) | | | +| 142 | 34 | "中性双池 (0,0) 原样透传" | DamageMathScript.pool_damage(d, 0, 0) == d | (待填) | | | +| 143 | 35 | "负敌方池压低不增高" | DamageMathScript.pool_damage(d, 0, int(db.settlement.pool_clamp_lo)) <= d | (待填) | | | +| 144 | 36 | "正·主角池抬升不低于原值" | DamageMathScript.pool_damage(d, int(db.settlement.pool_clamp_hi), 0) >= d | (待填) | | | +| 145 | 37 | "主角池单调不减(+50% vs +49% 探针档)" | DamageMathScript.pool_damage(d, 50, 0) >= DamageMathScript.pool_damage(d, 49, 0) | (待填) | | | +| 146 | 38 | "对称性:池值互换结果不变(乘算交换)" | DamageMathScript.pool_damage(d, 30, -20) == DamageMathScript.pool_damage(d, -20, 30) | (待填) | | | +| 147 | 39 | "输出非负(maxi(0,·) 封底)" | DamageMathScript.pool_damage(d, int(db.settlement.pool_clamp_lo), int(db.settlement.pool_clamp_lo)) >= 0 | (待填) | | | -## S28 _smoke_enemy_attack_type(行 55–98 · 4 条 · 源 game/tests/unit/battle_settlement/roll_multiplier_test.gd · 去向:待分类回填) +## S28 _test_counter_component(行 46–50 · 4 条 · 源 game/tests/unit/battle_settlement/damage_math_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 152 | 55 | "敌方 %d 行 attack_type 全填 normal(迭代 24 N3 轴开列,越轨行%s)" ↩ % [ _db.enemy.rows.size(), at_detail if at_detail != "" else "无"] | at_ok | 敌方全部行 attack_type 全填 normal(轴开列)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 153 | 95 | "敌方单位快照 attack_type 非空(spawn 注入面——迭代 24 N3)" | snap_ok | 敌方单位快照 attack_type 非空(spawn 注入面)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 154 | 96 | "克制矩阵命中证词:同种子三甲型实测 heavy %d == light %d − 1 · ward %d == light %d + 1(迭代 24 N3 全局平移 −1/0/+1)" ↩ % [ t_heavy, t_light, t_ward, t_light] | matrix_found and t_light >= 2 | 克制矩阵命中证词:同种子三甲型实测 heavy=light−1、ward=light+1。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 155 | 98 | "敌方伤害技单据载荷 attack_type 非空(= %s——第 9 参传值面,迭代 24 N3)" ↩ % payload_at | payload_at == "normal" | 敌方伤害单据载荷 attack_type 非空。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 148 | 46 | "空桶(点数≤0)恒 0 不吃修正" | DamageMathScript.counter_component(0, floor_v, floor_v) == 0 and DamageMathScript.counter_component(-1, 5, floor_v) == 0 | (待填) | | | +| 149 | 48 | "正向修正直加" | DamageMathScript.counter_component(floor_v, floor_v, floor_v) == floor_v * 2 | (待填) | | | +| 150 | 49 | "负修正触底保落地伤害下限" | DamageMathScript.counter_component(floor_v, -floor_v * 10, floor_v) == floor_v | (待填) | | | +| 151 | 50 | "下限参数缺省 = 0(无下限语义面)" | DamageMathScript.counter_component(floor_v, -floor_v * 10) == 0 | (待填) | | | -## S29 _smoke_armor_roll(行 117–178 · 15 条 · 源 game/tests/unit/battle_settlement/roll_multiplier_test.gd · 去向:待分类回填) +## S29 _test_roll_discount_percent(行 59–74 · 5 条 · 源 game/tests/unit/battle_settlement/damage_math_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 156 | 117 | "查档 AC%d 全掷点域分布(100/75/50/25 = %s)" ↩ % [cfg.ac, str([dist[100], dist[75], dist[50], dist[25]])] | tier_ok and dist == cfg.expect | 查档纯函数全掷点域分布逐位正确(AC10/AC12)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 157 | 121 | "AC10 EV 锚:期望系数合计 %d/2000 = 期望减免 21.25%%(nat20 恒全额含内)" ↩ % ev_sum | ev_sum == 1575 | AC10 期望系数锚=1575/2000(期望减免 21.25%)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 158 | 131 | "nat20 种子可达(seed=%d——主角先手首掷命中 %d)" ↩ % [crit_seed, int(_db.settlement.crit_roll)] | crit_seed > 0 | nat20 种子可达(主角先手首掷命中 20)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 159 | 132 | "nat20 → 行留档 roll=20 + crit 标记(主角先手普通分量)" | int(crit_rows.get("roll", -1)) == int(_db.settlement.crit_roll) and bool(crit_rows.get("crit", false)) | nat20 行留档 roll=20+crit 标记。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 160 | 134 | "nat20 → 普通分量 ×2(%d = 2×%d,暴击倍率只乘该分量,穿刺/魔法不吃)" ↩ % [int(crit_rows.get("normal", -1)), int(_db.hero.atk)] | int(crit_rows.get("normal", -1)) == 2 * int(_db.hero.atk) and int(crit_rows.get("value", -1)) == 2 * int(_db.hero.atk) | nat20 普通分量×2(暴击只乘普通分量)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 161 | 139 | "削弱重复授予 → 层数保持 1、剩余结算数重置 2(续时语义——结构性消饱和)" | int(weaken_probe.get("layers", -1)) == 1 and int(weaken_probe.get("remaining", -1)) == 2 | 削弱重复授予:层数保持 1、剩余结算数重置(续时语义)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 162 | 141 | "削弱单层池读数 = 行值(enemy_output 池单层贡献——×2 叠满触底 ×0 不可达,免伤轮根治)" | int(weaken_probe.get("pool", -999)) == int(_db.status_effect_row("weaken").magnitude) | 削弱单层池读数=行值。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 163 | 152 | "AC11 攻击拍逐拍判定(×%d 拍全部落档域 {100,75,50,25})" ↩ % tiers.size() | tiers_valid and tiers.size() >= 3 | AC11 攻击拍逐拍判定全落档域。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 164 | 153 | "折扣档实际流动(AC11 实测档位 %s——≥2 档可见)" ↩ % str(tier_distinct.keys()) | tier_distinct.size() >= 2 | 折扣档实际流动(≥2 档可见)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 165 | 170 | "全战斗流零 v=0 伤害行(伤害下限全局生效)" | all_positive | 全战斗流零 0 值伤害行(伤害下限全局生效)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 166 | 171 | "hero_attack/enemy_action 伤害行掷点读数+档位随行留档(P2 归因)" | attack_ok | 攻击类伤害行掷点读数+档位随行留档(归因)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 167 | 172 | "棋盘来源伤害行零掷点字段(爆裂/直伤/清障炸药/奖励即时必中全额)" | board_ok | 棋盘来源伤害行零掷点字段(必中全额)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 168 | 176 | "棋盘克制直伤贴底 → 下限 1(点数 2 vs 不利修正 −1)" | DamageMath.counter_component(2, -1, df) == 1 | 棋盘克制直伤贴底下限 1。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 169 | 177 | "克制修正触底穿透 → 下限 1(点数 1 vs 修正 −3)" | DamageMath.counter_component(1, -3, df) == 1 | 克制修正触底穿透下限 1。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 170 | 178 | "空桶不吃修正恒 0(零点数分量无伤害可落地——边界不变不受下限影响)" | DamageMath.counter_component(0, 5, df) == 0 | 空桶不吃修正恒 0(边界不受下限影响)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 152 | 59 | "前置:档位数 = 边界数 + 1(查档契约——档数 = 边界数 + 1)" | discounts.size() == bounds.size() + 1 and discounts.size() > 0 | (待填) | | | +| 153 | 60 | "掷点 ≥ 阈值恒全额 100" | DamageMathScript.roll_discount_percent(threshold, threshold, bounds, discounts) == 100 and DamageMathScript.roll_discount_percent(threshold + 5, threshold, bounds, discounts) == 100 | (待填) | | | +| 154 | 65 | "缺口 1 落首档" | DamageMathScript.roll_discount_percent(threshold - 1, threshold, bounds, discounts) == tier0_expected | (待填) | | | +| 155 | 68 | "缺口超末边界落末档" | DamageMathScript.roll_discount_percent(threshold - int(bounds[-1]) - 1, threshold, bounds, discounts) == last_expected | (待填) | | | +| 156 | 74 | "档位值随档位加深不升(折扣单调)" | descending | (待填) | | | -## S30 _smoke_pool_wiring(行 310–317 · 4 条 · 源 game/tests/unit/battle_settlement/roll_multiplier_test.gd · 去向:待分类回填) +## S30 _test_rolled_component(行 82–87 · 5 条 · 源 game/tests/unit/battle_settlement/damage_math_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 171 | 310 | "单分量基线在位(normal 分量 = 落地值、基线 %d——充能退役批)" ↩ % base_v | int(base_row.get("normal", -1)) == base_v and base_v > 0 | 单分量基线在位(normal 分量=落地值)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 172 | 312 | "增伤经乘区加成合计(%d → %d = ×1.5——attack_up 直乘旁路退役,覆盖面=攻击合计)" ↩ % [base_v, int(up_row.get("value", -1))] | int(up_row.get("value", -1)) == int(floor(base_v * 1.5)) | 增伤经乘区合计×1.5(直乘旁路退役)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 173 | 314 | "乏力经乘区削减合计(%d → %d = ×0.75)" ↩ % [base_v, int(sapped_row.get("value", -1))] | int(sapped_row.get("value", -1)) == int(floor(base_v * 0.75)) | 乏力经乘区削减×0.75。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 174 | 316 | "硬化折半方向(%d → %d = ×0.5 且 < 基线——敌方防御池负值减伤,翻倍 bug 退役)" ↩ % [base_v, int(fortify_row.get("value", -1))] | int(fortify_row.get("value", -1)) == int(floor(base_v * 0.5)) and int(fortify_row.get("value", -1)) < base_v | 硬化折半方向×0.5 且<基线(翻倍 bug 退役)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 157 | 82 | "满额折扣无暴击 = floor(基线) + 修正 0" | DamageMathScript.rolled_component(base, 100, false, crit_mult, 0) == int(floor(base)) | (待填) | | | +| 158 | 83 | "满额折扣暴击按表倍率放大" | DamageMathScript.rolled_component(base, 100, true, crit_mult, 0) == int(floor(base * float(crit_mult) / 100.0)) | (待填) | | | +| 159 | 84 | "折扣降低不高于满额" | DamageMathScript.rolled_component(base, 50, false, crit_mult, 0) <= DamageMathScript.rolled_component(base, 100, false, crit_mult, 0) | (待填) | | | +| 160 | 85 | "克制修正加在 floor 之后(加法核)" | DamageMathScript.rolled_component(base, 100, false, crit_mult, _floor_val()) == int(floor(base)) + _floor_val() | (待填) | | | +| 161 | 86 | "复合序 = 基线×折扣%×暴击%(floor 只在链末)" | DamageMathScript.rolled_component(base, 50, true, crit_mult, 0) == int(floor(base * 50.0 / 100.0 * float(crit_mult) / 100.0)) | (待填) | | | -## S31 _smoke_damage_types(行 345–379 · 10 条 · 源 game/tests/unit/battle_settlement/roll_multiplier_test.gd · 去向:待分类回填) +## S31 _smoke_enemy_attack_type(行 55–98 · 4 条 · 源 game/tests/unit/battle_settlement/roll_multiplier_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 175 | 345 | "goblin 护甲 = light(轻甲——教学位)" | enemy_system.spawn(1, 0, int(_db.hero.max_hp)) and String(enemy_system.all_enemies()[0].armor_type) == "light" | 地精护甲=轻甲(教学位)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 176 | 348 | "bugbear 护甲 = light(护甲类型重配批 2026-09-18:关卡 1 全轻甲)" | enemy_system_b.spawn(1, 2, int(_db.hero.max_hp)) and String(enemy_system_b.all_enemies()[0].armor_type) == "light" | 熊地精护甲=轻甲(第 1 关全轻甲)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 177 | 351 | "ogre_chief 护甲 = light(Boss 行集——护甲类型重配批 2026-09-18:关卡 1 全轻甲)" | enemy_system_c.spawn(1, 4, int(_db.hero.max_hp), [], -1, -1, [], "", "boss") and String(enemy_system_c.all_enemies()[0].armor_type) == "light" | 食人魔首领护甲=轻甲。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 178 | 355 | "普通行:轻甲 0 / 魔法护甲 +1 / 重甲 −1(加法核)" | int(am.normal.light) == 0 and int(am.normal.ward) == 1 and int(am.normal.heavy) == -1 | 克制矩阵普通行表值在位(0/+1/−1 加法核)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 179 | 357 | "穿刺行:轻甲 −1 / 魔法护甲 +1 / 重甲 0 中性(用户拍板)" | int(am.pierce.light) == -1 and int(am.pierce.ward) == 1 and int(am.pierce.heavy) == 0 | 克制矩阵穿刺行表值在位。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 180 | 359 | "魔法行:轻甲 0 / 魔法护甲 −1 / 重甲 +1(加法核)" | int(am.magic.light) == 0 and int(am.magic.ward) == -1 and int(am.magic.heavy) == 1 | 克制矩阵魔法行表值在位。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 181 | 373 | "标错封印产生 seal_miss 归因行(×%d)" ↩ % miss_rows | miss_rows >= 1 | 标错封印产生 seal_miss 归因行。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 182 | 374 | "标错封印兑付该安全格资源的负向字典行(×%d——attack/defense/weaken 效果行或 heal 直伤行)" ↩ % negative_rows | negative_rows >= 1 | 标错封印兑付安全格资源负向字典行。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 183 | 375 | "封印命中兑付该危险格资源的正向字典行(×%d——充能退役后无魔法分量,正向=格面资源)" ↩ % hit_rows | hit_rows >= 1 | 封印命中兑付危险格资源正向字典行。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 184 | 378 | "标错惩罚轮回放断言:同种子步骤流逐行相同(类型化负向兑付确定性)" | JSON.stringify(run1.rows) == JSON.stringify(run2.rows) | 标错惩罚轮回放:同种子步骤流逐行相同。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 162 | 55 | "敌方 %d 行 attack_type 全填 normal(迭代 24 N3 轴开列,越轨行%s)" ↩ % [ _db.enemy.rows.size(), at_detail if at_detail != "" else "无"] | at_ok | (待填) | | | +| 163 | 95 | "敌方单位快照 attack_type 非空(spawn 注入面——迭代 24 N3)" | snap_ok | (待填) | | | +| 164 | 96 | "克制矩阵命中证词:同种子三甲型实测 heavy %d == light %d − 1 · ward %d == light %d + 1(迭代 24 N3 全局平移 −1/0/+1)" ↩ % [ t_heavy, t_light, t_ward, t_light] | matrix_found and t_light >= 2 | (待填) | | | +| 165 | 98 | "敌方伤害技单据载荷 attack_type 非空(= %s——第 9 参传值面,迭代 24 N3)" ↩ % payload_at | payload_at == "normal" | (待填) | | | -## S32 _smoke_attack_chain_fresh_pool(行 445–460 · 3 条 · 源 game/tests/unit/battle_settlement/roll_multiplier_test.gd · 去向:待分类回填) +## S32 _smoke_armor_roll(行 117–178 · 15 条 · 源 game/tests/unit/battle_settlement/roll_multiplier_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 185 | 445 | "fresh 池回归战斗启动(Boss 行)" | settlement.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | fresh 池回归战斗启动(Boss 行)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 186 | 452 | "非施放攻击拍采样充足(%d 拍——增益施放行零兑付为设计语义不计)" ↩ % ticks.size() | ticks.size() >= 2 | 非施放攻击拍采样充足(≥2 拍)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | -| 187 | 460 | "fresh 池非蓄力拍 d_tick = max(1, 掷点档位基线)(Boss 攻击链健康——Bug#2 回归断言)" | all_full | fresh 池攻击拍伤害=掷点档位足额(池空不吞伤——Bug#2 回归)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 166 | 117 | "查档 AC%d 全掷点域分布(100/75/50/25 = %s)" ↩ % [cfg.ac, str([dist[100], dist[75], dist[50], dist[25]])] | tier_ok and dist == cfg.expect | (待填) | | | +| 167 | 121 | "AC10 EV 锚:期望系数合计 %d/2000 = 期望减免 21.25%%(nat20 恒全额含内)" ↩ % ev_sum | ev_sum == 1575 | (待填) | | | +| 168 | 131 | "nat20 种子可达(seed=%d——主角先手首掷命中 %d)" ↩ % [crit_seed, int(_db.settlement.crit_roll)] | crit_seed > 0 | (待填) | | | +| 169 | 132 | "nat20 → 行留档 roll=20 + crit 标记(主角先手普通分量)" | int(crit_rows.get("roll", -1)) == int(_db.settlement.crit_roll) and bool(crit_rows.get("crit", false)) | (待填) | | | +| 170 | 134 | "nat20 → 普通分量 ×2(%d = 2×%d,暴击倍率只乘该分量,穿刺/魔法不吃)" ↩ % [int(crit_rows.get("normal", -1)), int(_db.hero.atk)] | int(crit_rows.get("normal", -1)) == 2 * int(_db.hero.atk) and int(crit_rows.get("value", -1)) == 2 * int(_db.hero.atk) | (待填) | | | +| 171 | 139 | "削弱重复授予 → 层数保持 1、剩余结算数重置 2(续时语义——结构性消饱和)" | int(weaken_probe.get("layers", -1)) == 1 and int(weaken_probe.get("remaining", -1)) == 2 | (待填) | | | +| 172 | 141 | "削弱单层池读数 = 行值(enemy_output 池单层贡献——×2 叠满触底 ×0 不可达,免伤轮根治)" | int(weaken_probe.get("pool", -999)) == int(_db.status_effect_row("weaken").magnitude) | (待填) | | | +| 173 | 152 | "AC11 攻击拍逐拍判定(×%d 拍全部落档域 {100,75,50,25})" ↩ % tiers.size() | tiers_valid and tiers.size() >= 3 | (待填) | | | +| 174 | 153 | "折扣档实际流动(AC11 实测档位 %s——≥2 档可见)" ↩ % str(tier_distinct.keys()) | tier_distinct.size() >= 2 | (待填) | | | +| 175 | 170 | "全战斗流零 v=0 伤害行(伤害下限全局生效)" | all_positive | (待填) | | | +| 176 | 171 | "hero_attack/enemy_action 伤害行掷点读数+档位随行留档(P2 归因)" | attack_ok | (待填) | | | +| 177 | 172 | "棋盘来源伤害行零掷点字段(爆裂/直伤/清障炸药/奖励即时必中全额)" | board_ok | (待填) | | | +| 178 | 176 | "棋盘克制直伤贴底 → 下限 1(点数 2 vs 不利修正 −1)" | DamageMath.counter_component(2, -1, df) == 1 | (待填) | | | +| 179 | 177 | "克制修正触底穿透 → 下限 1(点数 1 vs 修正 −3)" | DamageMath.counter_component(1, -3, df) == 1 | (待填) | | | +| 180 | 178 | "空桶不吃修正恒 0(零点数分量无伤害可落地——边界不变不受下限影响)" | DamageMath.counter_component(0, 5, df) == 0 | (待填) | | | -## S33 _smoke_full_battle(行 50–78 · 7 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S33 _smoke_pool_wiring(行 310–317 · 4 条 · 源 game/tests/unit/battle_settlement/roll_multiplier_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 188 | 50 | "%s 战斗在轮次上限内终局(%d 轮,%s)" ↩ % [label, run1.rounds, run1.result] | run1.terminated | 完整战斗在轮次上限内终局。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 189 | 58 | "%s 回放断言:结算步骤流逐行相同" ↩ % label | JSON.stringify(run1.stream) == JSON.stringify(run2.stream) | 同种子两遍结算步骤流逐行相同(战斗回放断言)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 190 | 59 | "%s 回放断言:事件流逐位相同" ↩ % label | JSON.stringify(run1.events) == JSON.stringify(run2.events) | 同种子两遍事件流逐位相同。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 191 | 60 | "%s 结算步骤流非空(%d 行)" ↩ % [label, run1.stream.size()] | run1.stream.size() > 0 | 结算步骤流非空。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 192 | 74 | "%s 步骤流含判定行" ↩ % label | has_verdict | 步骤流含判定行。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 193 | 77 | "%s 主角先手攻击行必存在(2026-09-11 拍板① + 案C 地图账殿后)×%d" ↩ % [label, hero_attacks] | hero_attacks >= 1 | 主角先手攻击行必存在(先手必兑+地图账殿后)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 194 | 78 | "%s 主角攻击行携带类型键与掷点读数(单分量——充能退役批)" ↩ % label | hero_breakdown_ok | 主角攻击行携带类型键与掷点读数(归因面)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 181 | 310 | "单分量基线在位(normal 分量 = 落地值、基线 %d——充能退役批)" ↩ % base_v | int(base_row.get("normal", -1)) == base_v and base_v > 0 | (待填) | | | +| 182 | 312 | "增伤经乘区加成合计(%d → %d = ×1.5——attack_up 直乘旁路退役,覆盖面=攻击合计)" ↩ % [base_v, int(up_row.get("value", -1))] | int(up_row.get("value", -1)) == int(floor(base_v * 1.5)) | (待填) | | | +| 183 | 314 | "乏力经乘区削减合计(%d → %d = ×0.75)" ↩ % [base_v, int(sapped_row.get("value", -1))] | int(sapped_row.get("value", -1)) == int(floor(base_v * 0.75)) | (待填) | | | +| 184 | 316 | "硬化折半方向(%d → %d = ×0.5 且 < 基线——敌方防御池负值减伤,翻倍 bug 退役)" ↩ % [base_v, int(fortify_row.get("value", -1))] | int(fortify_row.get("value", -1)) == int(floor(base_v * 0.5)) and int(fortify_row.get("value", -1)) < base_v | (待填) | | | -## S34 _run_battle(行 110–110 · 1 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S34 _smoke_damage_types(行 345–379 · 10 条 · 源 game/tests/unit/battle_settlement/roll_multiplier_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 195 | 110 | "战斗收束后行动经济相位回 Idle(notify_combat_end 生效,实测=%s)" ↩ % String(action_economy.phase) | action_economy.phase == "Idle" | 战斗收束后行动经济相位回 Idle(notify_combat_end 生效)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 185 | 345 | "goblin 护甲 = light(轻甲——教学位)" | enemy_system.spawn(1, 0, int(_db.hero.max_hp)) and String(enemy_system.all_enemies()[0].armor_type) == "light" | (待填) | | | +| 186 | 348 | "bugbear 护甲 = light(护甲类型重配批 2026-09-18:关卡 1 全轻甲)" | enemy_system_b.spawn(1, 2, int(_db.hero.max_hp)) and String(enemy_system_b.all_enemies()[0].armor_type) == "light" | (待填) | | | +| 187 | 351 | "ogre_chief 护甲 = light(Boss 行集——护甲类型重配批 2026-09-18:关卡 1 全轻甲)" | enemy_system_c.spawn(1, 4, int(_db.hero.max_hp), [], -1, -1, [], "", "boss") and String(enemy_system_c.all_enemies()[0].armor_type) == "light" | (待填) | | | +| 188 | 355 | "普通行:轻甲 0 / 魔法护甲 +1 / 重甲 −1(加法核)" | int(am.normal.light) == 0 and int(am.normal.ward) == 1 and int(am.normal.heavy) == -1 | (待填) | | | +| 189 | 357 | "穿刺行:轻甲 −1 / 魔法护甲 +1 / 重甲 0 中性(用户拍板)" | int(am.pierce.light) == -1 and int(am.pierce.ward) == 1 and int(am.pierce.heavy) == 0 | (待填) | | | +| 190 | 359 | "魔法行:轻甲 0 / 魔法护甲 −1 / 重甲 +1(加法核)" | int(am.magic.light) == 0 and int(am.magic.ward) == -1 and int(am.magic.heavy) == 1 | (待填) | | | +| 191 | 373 | "标错封印产生 seal_miss 归因行(×%d)" ↩ % miss_rows | miss_rows >= 1 | (待填) | | | +| 192 | 374 | "标错封印兑付该安全格资源的负向字典行(×%d——attack/defense/weaken 效果行或 heal 直伤行)" ↩ % negative_rows | negative_rows >= 1 | (待填) | | | +| 193 | 375 | "封印命中兑付该危险格资源的正向字典行(×%d——充能退役后无魔法分量,正向=格面资源)" ↩ % hit_rows | hit_rows >= 1 | (待填) | | | +| 194 | 378 | "标错惩罚轮回放断言:同种子步骤流逐行相同(类型化负向兑付确定性)" | JSON.stringify(run1.rows) == JSON.stringify(run2.rows) | (待填) | | | -## S35 _smoke_active_refresh(行 159–214 · 17 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S35 _smoke_attack_chain_fresh_pool(行 445–460 · 3 条 · 源 game/tests/unit/battle_settlement/roll_multiplier_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 196 | 159 | "轮内刷新战斗启动(Boss 行 150HP——承载多轮样本)" | settlement.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | 轮内刷新战斗启动(Boss 行多轮样本)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 197 | 165 | "三轮停手攒 RAP=%d(round=%d rap=%d)" ↩ % [rap_banked, settlement.round_index, settlement.action_economy.rap] | settlement.round_index == 4 and settlement.action_economy.rap == rap_banked | 三轮停手攒 RAP=3×每轮 AP。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 198 | 166 | "第 4 轮 Active 相位" | settlement.action_economy.phase == "Active" | 第 4 轮进入 Active 相位。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 199 | 176 | "轮内第 1 次刷新受理" | ok1 | 轮内第 1 次刷新受理。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 200 | 177 | "相位保持 Active、AP 保留不重置(ap=%d)" ↩ % settlement.action_economy.ap | settlement.action_economy.phase == "Active" and settlement.action_economy.ap == ap_before | 刷新后相位保持 Active、AP 保留不重置。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 201 | 178 | "RAP -刷新价(%d)" ↩ % settlement.action_economy.rap | settlement.action_economy.rap == rap_banked - int(_db.action_economy.rap_refresh_cost) | 刷新扣 RAP 价(表值)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 202 | 179 | "当场换新图(board_changed +1)" | int(ct["board_swaps"]) == swaps_before + 1 and minesweeper.current_board() != board_before | 当场换新图(board_changed 事件+板对象更换)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 203 | 185 | "轮内第 2 次刷新受理(RAP=%d)" ↩ % settlement.action_economy.rap | ok2 | 轮内第 2 次刷新受理。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 204 | 191 | "第 3 次刷新被拒(每轮兜底上限 2——RAP %d 足额仍拒)" ↩ % settlement.action_economy.rap | not ok3 | 第 3 次刷新被拒(每轮兜底上限 2,RAP 足额仍拒)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 205 | 193 | "V5 快照刷新计数=2(受理单源——main 副本已删)〔迭代27 T3〕" | int(settlement.presentation_snapshot().get("refresh_used", -1)) == 2 | 快照刷新计数=2(受理单源,main 副本已删)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 206 | 194 | "V5 上限达门禁一致(can_refresh 与快照计数同源)〔迭代27 T3〕" | not settlement.can_refresh() | 上限达门禁与快照计数同源。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 207 | 206 | "轮 4 挖至 AP 耗尽自动结算(round=%d result='%s')" ↩ % [settlement.round_index, settlement.battle_result] | settlement.round_index == 5 and settlement.battle_result == "" | 拒绝后挖至 AP 耗尽自动结算进第 5 轮。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 208 | 207 | "跨图 facts 完整兑现(safe_cell 结算行 %d == safe 事件 %d——快照批次隔离)" ↩ % [int(ct["safe_rows"]), int(ct["safe_events"])] | int(ct["safe_rows"]) == int(ct["safe_events"]) | 跨图 facts 完整兑现(safe_cell 行数==事件数)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 209 | 208 | "跨图挖掘 safe 事件 ≥3(%d——断言非空转)" ↩ % int(ct["safe_events"]) | int(ct["safe_events"]) >= 3 | 跨图挖掘 safe 事件≥3(断言非空转)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 210 | 209 | "结算段内(Waiting 相位)刷新被拒(等待期换图通道已移除)" | bool(ct["waiting_refresh_rejected"]) and bool(ct["waiting_probe_hit"]) | 结算段内(Waiting 相位)刷新被拒。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 211 | 213 | "轮内刷新回放断言:同种子两遍步骤流逐行相同" | JSON.stringify(rep1) == JSON.stringify(rep2) | 轮内刷新回放:同种子两遍步骤流逐行相同。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 212 | 214 | "轮内刷新步骤流非空(%d 行)" ↩ % rep1.size() | rep1.size() > 0 | 轮内刷新步骤流非空。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 195 | 445 | "fresh 池回归战斗启动(Boss 行)" | settlement.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | (待填) | | | +| 196 | 452 | "非施放攻击拍采样充足(%d 拍——增益施放行零兑付为设计语义不计)" ↩ % ticks.size() | ticks.size() >= 2 | (待填) | | | +| 197 | 460 | "fresh 池非蓄力拍 d_tick = max(1, 掷点档位基线)(Boss 攻击链健康——Bug#2 回归断言)" | all_full | (待填) | | | -## S36 _smoke_encounter_roll(行 253–311 · 8 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S36 _smoke_full_battle(行 50–78 · 7 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 213 | 253 | "遭遇扫描战斗启动(quiet 计数档——计数入总账,成败由后续 spawn 断言归因)" | settlement.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(_db.hero.max_hp)}) | 遭遇扫描 20 种子战斗启动(quiet 计数档)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 214 | 256 | "D0 区间 [1,2] 两值均出现(分布 %s)" ↩ % str(counts) | counts.has(1) and counts.has(2) | D0 区间 [1,2] 两值均出现。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 215 | 262 | "全部计数在域内 [1,2](分布 %s)" ↩ % str(counts) | all_in_domain | 全部遭遇计数在域内 [1,2]。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 216 | 266 | "同种子遭遇骰可回放(%s == %s)" ↩ % [str(r1), str(r2)] | r1 == r2 | 同种子遭遇骰可回放。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 217 | 271 | "D1 战斗启动" | settlement_d1.begin_battle({"level_index": 1, "node_depth": 1, "hero_hp": int(_db.hero.max_hp)}) | D1 战斗启动。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 218 | 273 | "D1 区间 [2,3] 域内(%d 只)" ↩ % n1 | n1 >= 2 and n1 <= 3 | D1 区间 [2,3] 域内。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 219 | 286 | "熊地精首遇带(1,battle,%d——扫表取最低带)固定 1 不加骰〔fv23〕" ↩ % bugbear_lead_band | settlement_d2.presentation_snapshot().enemies.size() == 1 \\ and int(bugbear_lead_row.get("unit_count", -1)) == 1 and not bugbear_lead_row.has("unit_count_max") | 熊地精首遇带固定 1 不加骰(教学位)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 220 | 311 | "全深度 × 12 种子 spawn 全域内(%d 场,∈[unit_count, unit_count_max] 且 ≥1)——空遭遇不可达(Bug#1 回归)" ↩ % scan_total | scan_bad == 0 | 全深度×12 种子 spawn 全域内且≥1(空遭遇不可达——Bug#1 回归)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 198 | 50 | "%s 战斗在轮次上限内终局(%d 轮,%s)" ↩ % [label, run1.rounds, run1.result] | run1.terminated | (待填) | | | +| 199 | 58 | "%s 回放断言:结算步骤流逐行相同" ↩ % label | JSON.stringify(run1.stream) == JSON.stringify(run2.stream) | (待填) | | | +| 200 | 59 | "%s 回放断言:事件流逐位相同" ↩ % label | JSON.stringify(run1.events) == JSON.stringify(run2.events) | (待填) | | | +| 201 | 60 | "%s 结算步骤流非空(%d 行)" ↩ % [label, run1.stream.size()] | run1.stream.size() > 0 | (待填) | | | +| 202 | 74 | "%s 步骤流含判定行" ↩ % label | has_verdict | (待填) | | | +| 203 | 77 | "%s 主角先手攻击行必存在(2026-09-11 拍板① + 案C 地图账殿后)×%d" ↩ % [label, hero_attacks] | hero_attacks >= 1 | (待填) | | | +| 204 | 78 | "%s 主角攻击行携带类型键与掷点读数(单分量——充能退役批)" ↩ % label | hero_breakdown_ok | (待填) | | | -## S37 _smoke_settlement_segments(行 379–444 · 5 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S37 _run_battle(行 110–110 · 1 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 221 | 379 | "segment_order 五段冻结在位(表值 = 拍板序恒等)" | Array(_db.settlement.segment_order) == SettlementTable.FROZEN_SEGMENT_ORDER and SettlementTable.FROZEN_SEGMENT_ORDER == ["grant", "buff_debuff", "combat", "decay", "verdict"] | 结算五段序冻结在位(表值=拍板序恒等)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 222 | 390 | "行型封闭 10 类(删五类零残留:item/consumed_ineffective/use_rejected/window_open/window_skip)" | legacy_total == 0 | 行型封闭 10 类:已删五类零残留。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 223 | 417 | "每轮行相位单调(授予→增益/减益→战斗→衰减→判定)" | order_ok | 每轮行相位单调(授予→增益减益→战斗→衰减→判定)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 224 | 443 | "案C 段内序:地图伤害账殿后(主角先手→敌方逐个→地图账;连珠矢随先手段)" | mapdmg_ok | 案C 段内序:地图伤害账殿后(先手→敌方→地图账)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 225 | 444 | "衰减/判定段每结算恰一段(先兑付后衰减——含末轮)" | decay_verdict_ok | 衰减/判定段每结算恰一段。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 205 | 110 | "战斗收束后行动经济相位回 Idle(notify_combat_end 生效,实测=%s)" ↩ % String(action_economy.phase) | action_economy.phase == "Idle" | (待填) | | | -## S38 _smoke_skill_pool_data(行 495–670 · 27 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S38 _smoke_active_refresh(行 159–214 · 17 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 226 | 495 | "技能表十四行(普攻/突刺/蓄力/重击/硬化 + 撕咬/毒爆/蛛网束缚 + 大地脉动 + 震地怒击/遏毒/岩崩 + 毒吐/盾击——前三批 + Boss 半血解锁技三行〔迭代14④ T5〕+ 普通敌错峰两行〔迭代 24 N2〕)" | _db.skill.rows.size() == 14 | 技能表恰 14 行(三批+Boss 半血解锁技+普通敌错峰两行)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 227 | 497 | "普攻行:倍率恒 1 · 冷却恒 0(域校验锁死——池恒非空的结构前提)" | not basic.is_empty() and String(basic.type) == "basic" and int(basic.cooldown_beats) == 0 and (not basic.has("damage_mult") or is_equal_approx(float(basic.damage_mult), 1.0)) | 普攻行:倍率恒 1、冷却恒 0(池恒非空的结构前提)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 228 | 501 | "突刺行(伤害 ×1.5 · 冷却 2——提案 §2.5 建议池新行)" | not lunge.is_empty() and is_equal_approx(float(lunge.damage_mult), 1.5) and int(lunge.cooldown_beats) == 2 | 突刺行(伤害 ×1.5、冷却 2)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 229 | 503 | "蓄力行 = 纯攻击增益技(0 伤害、buff_ref → empower——与重击无组合语义)" | not charge_row.is_empty() and String(charge_row.buff_ref) == "empower" | 蓄力行=纯攻击增益技(buff_ref 指向 empower)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 230 | 505 | "撕咬行(伤害 ×1.5 · 冷却 1——巨蜘蛛池载体,与突刺同倍率错开冷却轴)" | not bite.is_empty() and is_equal_approx(float(bite.damage_mult), 1.5) and int(bite.cooldown_beats) == 1 | 撕咬行(伤害 ×1.5、冷却 1)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 231 | 507 | "毒爆行(伤害 ×2.5 · 冷却 4 · 必中附加 ×0.75——蛛化卓尔精灵专属池独占技,新效应词汇批)" | not venom.is_empty() and is_equal_approx(float(venom.damage_mult), 2.5) and int(venom.cooldown_beats) == 4 and is_equal_approx(float(venom.rider_mult), 0.75) | 毒爆行(伤害 ×2.5、冷却 4、必中附加 ×0.75)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 232 | 515 | "毒爆独占性(仅蛛化卓尔精灵池引用——Boss 专属池独占技纪律)" | venom_sole_ok | 毒爆独占性:仅蛛化卓尔精灵池引用。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 233 | 518 | "蛛网束缚行(妨害型 · buff_ref → off_guard 失衡 · duration 2 = 1 有效拍 · 冷却 4——新效应词汇批首发)" | not snare.is_empty() and String(snare.type) == "debuff" and String(snare.buff_ref) == "off_guard" and int(snare.buff_duration) == 2 and int(snare.cooldown_beats) == 4 | 蛛网束缚行(妨害型、指向失衡、有效 1 拍、冷却 4)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 234 | 526 | "蛛网束缚独占性(仅蛛化卓尔精灵池引用——实装面收窄,其余敌种不动)" | snare_sole_ok | 蛛网束缚独占性:仅蛛化卓尔精灵池引用。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 235 | 528 | "失衡状态行(hero · ac 轴——妨害技落点,ConfigDB side 守卫正例)" | not off_guard_row.is_empty() and String(off_guard_row.target_side) == "hero" and String(off_guard_row.unit) == "ac" | 失衡状态行在位(主角侧 ac 轴——妨害技落点)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 236 | 531 | "毒吐行(伤害 ×1.6 · 冷却 2 · 无 rider——低 atk 下 rider 恒被 damage_floor 兜成 1 白占字段,迭代 24 N2)" | not spit.is_empty() and String(spit.display_name) == "毒吐" and String(spit.type) == "damage" \\ and is_equal_approx(float(spit.damage_mult), 1.6) and int(spit.cooldown_beats) == 2 and not spit.has("rider_mult") | 毒吐行(伤害 ×1.6、冷却 2、无附加分量)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 237 | 535 | "盾击行(伤害 ×1.7 · 冷却 3 · 无 rider——迭代 24 N2)" | not bash.is_empty() and String(bash.display_name) == "盾击" and String(bash.type) == "damage" \\ and is_equal_approx(float(bash.damage_mult), 1.7) and int(bash.cooldown_beats) == 3 and not bash.has("rider_mult") | 盾击行(伤害 ×1.7、冷却 3、无附加分量)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 238 | 549 | "毒吐投放面(仅 depth_band > 巨蜘蛛首遇带(%d)的巨蜘蛛族行——首遇带池不动,迭代 24 N2/Q6)〔fv24〕" ↩ % spider_lead_band | spit_sole_ok | 毒吐投放面:仅巨蜘蛛族首遇带以深的行。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 239 | 550 | "盾击投放面(仅 (3,·) 灰矮人族——钩爪恐魔零触碰,迭代 24 N2)" | bash_sole_ok | 盾击投放面:仅第 3 关灰矮人族。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 240 | 558 | "巨蜘蛛首遇带行 pool 恒 {attack:6, bite:2, charge:1}(教学位池零新招——毒吐首投在更深的带)〔fv24〕" | spider_lead_pool == {"attack": 6, "bite": 2, "charge": 1} | 巨蜘蛛首遇带行池恒 attack6/bite2/charge1(教学位零新招)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 241 | 604 | "二十八行九种池模板 = 建议池行值((L,row_kind,depth_band,enemy_id) 四元键逐行精确比对;L1 地精 9/1 · 熊地精 6/2/2 · 酋长 6/2/2;L2 熊地精 6/1/2 · 蜘蛛 6/2/1+毒吐2〔首遇带不投——迭代 24 N2〕 · 重击手 5/2/2 · 蛛化卓尔精灵 6/2/2/2〔Boss〕;L3 灰矮人 6/2/2+盾击2 · 钩爪恐魔 6/2/1 · 重装手 5/2/2+盾击2 · 石君王 6/2/2〔earth_pulse〕——全部 provisional;三 Boss 池尾 +1 门控条目〔quake_smash/venom_curb/rockslide 权重 2+unlock_hp_pct 50——迭代14④ T5〕;混编行集同带多行各一池合法——首批投放 (2,battle,5) 双行〔迭代 18 卡⑤:重击手+巢群〕;迭代 35 覆盖批八行为顶档池克隆,逐行同检)" | pools_ok | (待填) | | | -| 242 | 614 | "每池恰一行普攻(空池不可能的加载校验同款)" | basic_count_ok | 每池恰一行普攻(空池不可能的结构前提)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 243 | 631 | "逐池增益 ≤1 · 妨害 ≤1(池构成纪律——迭代 24 N2 毒吐/盾击投放后复查)" | comp_ok | 逐池增益≤1 且妨害≤1(池构成纪律投放后复查)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 244 | 636 | "前置:卓尔 Boss 战受理(2,9——解锁技 venom_curb 门槛 50)" | sett_u.begin_battle({"level_index": 2, "node_depth": 9, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | 卓尔 Boss 战受理(半血解锁技载体)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 245 | 639 | "前置:Boss 压到阈值 65(=round(130×0.5))" | int(boss_u.hp) == 65 | Boss 血量被压到阈值 65(=半血线)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 246 | 645 | "解锁归因行恰一次(×%d——闩去重,二次检查零行)" ↩ % unlock_rows.size() | unlock_rows.size() == 1 | 解锁归因行恰一次(闩去重——二次检查零行)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 247 | 647 | "解锁行文本 =「蛛化卓尔精灵 解锁了 遏毒」(高光键 skill_unlock——呈现层一次性技能名 toast 路由键)" | String(unlock_rows[0].get("display_name", "")) == "蛛化卓尔精灵 解锁了 遏毒" | 解锁行文本格式正确(一次性技能名 toast 路由键)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 248 | 648 | "解锁行型 = highlight 既有行型(零行型新增——行协议封闭)" | String(unlock_rows[0].get("type", "")) == "highlight" | 解锁行型=highlight 既有行型(行协议封闭零新增)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 249 | 662 | "全深度实例 spawn 后可摇集合恒非空(普攻恒冷却 0——容器级断言,L1/L2 双关卡)" | rollable_ok | 全深度实例 spawn 后可摇集合恒非空(普攻恒冷却 0)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 250 | 666 | "蓄力状态行 empower(enemy +100 percent——敌方输出 ×2 走既有 enemy_output 池)" | not empower.is_empty() and String(empower.target_side) == "enemy" and int(empower.magnitude) == 100 | 蓄力状态行 empower 在位(敌方输出 ×2)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 251 | 667 | "硬化状态行 fortify(enemy −50 percent——敌方防御池负值 = 主角攻击折半;+100 符号 bug 2026-09-18 纠正,provisional 归 状态层重设计批)" | not fortify.is_empty() and String(fortify.target_side) == "enemy" and int(fortify.magnitude) == -50 | 硬化状态行 fortify 在位(敌方防御 −50%)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 252 | 668 | "effect_pool_map 收编 empower/fortify(敌方防御池 enemy_defense 入表)" | String(_db.settlement.effect_pool_map.get("empower", "")) == "enemy_output" \\ and String(_db.settlement.effect_pool_map.get("fortify", "")) == "enemy_defense" | 效果池映射收编 empower/fortify(enemy_output/enemy_defense)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 206 | 159 | "轮内刷新战斗启动(Boss 行 150HP——承载多轮样本)" | settlement.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | (待填) | | | +| 207 | 165 | "三轮停手攒 RAP=%d(round=%d rap=%d)" ↩ % [rap_banked, settlement.round_index, settlement.action_economy.rap] | settlement.round_index == 4 and settlement.action_economy.rap == rap_banked | (待填) | | | +| 208 | 166 | "第 4 轮 Active 相位" | settlement.action_economy.phase == "Active" | (待填) | | | +| 209 | 176 | "轮内第 1 次刷新受理" | ok1 | (待填) | | | +| 210 | 177 | "相位保持 Active、AP 保留不重置(ap=%d)" ↩ % settlement.action_economy.ap | settlement.action_economy.phase == "Active" and settlement.action_economy.ap == ap_before | (待填) | | | +| 211 | 178 | "RAP -刷新价(%d)" ↩ % settlement.action_economy.rap | settlement.action_economy.rap == rap_banked - int(_db.action_economy.rap_refresh_cost) | (待填) | | | +| 212 | 179 | "当场换新图(board_changed +1)" | int(ct["board_swaps"]) == swaps_before + 1 and minesweeper.current_board() != board_before | (待填) | | | +| 213 | 185 | "轮内第 2 次刷新受理(RAP=%d)" ↩ % settlement.action_economy.rap | ok2 | (待填) | | | +| 214 | 191 | "第 3 次刷新被拒(每轮兜底上限 2——RAP %d 足额仍拒)" ↩ % settlement.action_economy.rap | not ok3 | (待填) | | | +| 215 | 193 | "V5 快照刷新计数=2(受理单源——main 副本已删)〔迭代27 T3〕" | int(settlement.presentation_snapshot().get("refresh_used", -1)) == 2 | (待填) | | | +| 216 | 194 | "V5 上限达门禁一致(can_refresh 与快照计数同源)〔迭代27 T3〕" | not settlement.can_refresh() | (待填) | | | +| 217 | 206 | "轮 4 挖至 AP 耗尽自动结算(round=%d result='%s')" ↩ % [settlement.round_index, settlement.battle_result] | settlement.round_index == 5 and settlement.battle_result == "" | (待填) | | | +| 218 | 207 | "跨图 facts 完整兑现(safe_cell 结算行 %d == safe 事件 %d——快照批次隔离)" ↩ % [int(ct["safe_rows"]), int(ct["safe_events"])] | int(ct["safe_rows"]) == int(ct["safe_events"]) | (待填) | | | +| 219 | 208 | "跨图挖掘 safe 事件 ≥3(%d——断言非空转)" ↩ % int(ct["safe_events"]) | int(ct["safe_events"]) >= 3 | (待填) | | | +| 220 | 209 | "结算段内(Waiting 相位)刷新被拒(等待期换图通道已移除)" | bool(ct["waiting_refresh_rejected"]) and bool(ct["waiting_probe_hit"]) | (待填) | | | +| 221 | 213 | "轮内刷新回放断言:同种子两遍步骤流逐行相同" | JSON.stringify(rep1) == JSON.stringify(rep2) | (待填) | | | +| 222 | 214 | "轮内刷新步骤流非空(%d 行)" ↩ % rep1.size() | rep1.size() > 0 | (待填) | | | -## S39 _smoke_skill_pool_replay_independence(行 703–709 · 2 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S39 _smoke_encounter_roll(行 253–311 · 8 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 253 | 703 | "多实例种子可达(seed=%d——D3 遭遇 ≥2 实例)" ↩ % seed_val | seed_val > 0 | 多实例种子可达(D3 遭遇≥2 实例)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 254 | 709 | "同种子回放:各实例摇定动作序列逐位相同(×%d 轮 × %d 实例——子流独立复放面随并流退役)" ↩ % [rounds, int(run1.count)] | not run1.is_empty() and JSON.stringify(run1.seqs) == JSON.stringify(run2.seqs) | 同种子回放各实例摇定动作序列逐位相同。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 223 | 253 | "遭遇扫描战斗启动(quiet 计数档——计数入总账,成败由后续 spawn 断言归因)" | settlement.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(_db.hero.max_hp)}) | (待填) | | | +| 224 | 256 | "D0 区间 [1,2] 两值均出现(分布 %s)" ↩ % str(counts) | counts.has(1) and counts.has(2) | (待填) | | | +| 225 | 262 | "全部计数在域内 [1,2](分布 %s)" ↩ % str(counts) | all_in_domain | (待填) | | | +| 226 | 266 | "同种子遭遇骰可回放(%s == %s)" ↩ % [str(r1), str(r2)] | r1 == r2 | (待填) | | | +| 227 | 271 | "D1 战斗启动" | settlement_d1.begin_battle({"level_index": 1, "node_depth": 1, "hero_hp": int(_db.hero.max_hp)}) | (待填) | | | +| 228 | 273 | "D1 区间 [2,3] 域内(%d 只)" ↩ % n1 | n1 >= 2 and n1 <= 3 | (待填) | | | +| 229 | 286 | "熊地精首遇带(1,battle,%d——扫表取最低带)固定 1 不加骰〔fv23〕" ↩ % bugbear_lead_band | settlement_d2.presentation_snapshot().enemies.size() == 1 \\ and int(bugbear_lead_row.get("unit_count", -1)) == 1 and not bugbear_lead_row.has("unit_count_max") | (待填) | | | +| 230 | 311 | "全深度 × 12 种子 spawn 全域内(%d 场,∈[unit_count, unit_count_max] 且 ≥1)——空遭遇不可达(Bug#1 回归)" ↩ % scan_total | scan_bad == 0 | (待填) | | | -## S40 _smoke_skill_pool_ev_anchor(行 791–791 · 1 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S40 _smoke_settlement_segments(行 379–444 · 5 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 255 | 791 | "EV 抽样对锚 %s(实测 %.4f ∈ [%.2f, %.2f] 含闸长期速率,×%d 拍)" ↩ % [eid, measured, float(band[0]), float(band[1]), n_beats] | in_band | 三敌种 EV 抽样实测落在登记带宽内(5000 拍含保底闸长期速率)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 231 | 379 | "segment_order 五段冻结在位(表值 = 拍板序恒等)" | Array(_db.settlement.segment_order) == SettlementTable.FROZEN_SEGMENT_ORDER and SettlementTable.FROZEN_SEGMENT_ORDER == ["grant", "buff_debuff", "combat", "decay", "verdict"] | (待填) | | | +| 232 | 390 | "行型封闭 10 类(删五类零残留:item/consumed_ineffective/use_rejected/window_open/window_skip)" | legacy_total == 0 | (待填) | | | +| 233 | 417 | "每轮行相位单调(授予→增益/减益→战斗→衰减→判定)" | order_ok | (待填) | | | +| 234 | 443 | "案C 段内序:地图伤害账殿后(主角先手→敌方逐个→地图账;连珠矢随先手段)" | mapdmg_ok | (待填) | | | +| 235 | 444 | "衰减/判定段每结算恰一段(先兑付后衰减——含末轮)" | decay_verdict_ok | (待填) | | | -## S41 _smoke_skill_pool_death_void(行 811–830 · 5 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S41 _smoke_skill_pool_data(行 495–670 · 27 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 256 | 811 | "死亡打断种子可达(seed=%d——D2 首拍摇中特殊技)" ↩ % void_seed | void_seed > 0 | 死亡打断种子可达(D2 首拍摇中特殊技)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 257 | 817 | "死亡打断战斗启动(hero_atk 999——先手必杀 40HP 熊地精)" | settlement.begin_battle({"level_index": 1, "node_depth": 2, "hero_hp": int(_db.hero.max_hp), "hero_atk": 999}) | 死亡打断战斗启动受理(主角一击必杀构造)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 258 | 820 | "开轮已摇定本拍动作(待发 = %s)" ↩ % pending | pending != "" | 开轮已摇定本拍动作(待发非空)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 259 | 826 | "先手击杀 → 摇定技能作废(第 1 轮零敌方行动行——死亡即不动手)" | enemy_rows == 0 | 先手击杀后第 1 轮零敌方行动行(摇定技能作废=死亡不动手)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 260 | 830 | "作废不进账(全池冷却 0 · 保底计数 0——执行上报未发生)" | cd_sum == 0 and int(u.basic_streak) == 0 | 作废不进账:全池冷却 0 且保底计数 0。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 236 | 495 | "技能表十四行(普攻/突刺/蓄力/重击/硬化 + 撕咬/毒爆/蛛网束缚 + 大地脉动 + 震地怒击/遏毒/岩崩 + 毒吐/盾击——前三批 + Boss 半血解锁技三行〔迭代14④ T5〕+ 普通敌错峰两行〔迭代 24 N2〕)" | _db.skill.rows.size() == 14 | (待填) | | | +| 237 | 497 | "普攻行:倍率恒 1 · 冷却恒 0(域校验锁死——池恒非空的结构前提)" | not basic.is_empty() and String(basic.type) == "basic" and int(basic.cooldown_beats) == 0 and (not basic.has("damage_mult") or is_equal_approx(float(basic.damage_mult), 1.0)) | (待填) | | | +| 238 | 501 | "突刺行(伤害 ×1.5 · 冷却 2——提案 §2.5 建议池新行)" | not lunge.is_empty() and is_equal_approx(float(lunge.damage_mult), 1.5) and int(lunge.cooldown_beats) == 2 | (待填) | | | +| 239 | 503 | "蓄力行 = 纯攻击增益技(0 伤害、buff_ref → empower——与重击无组合语义)" | not charge_row.is_empty() and String(charge_row.buff_ref) == "empower" | (待填) | | | +| 240 | 505 | "撕咬行(伤害 ×1.5 · 冷却 1——巨蜘蛛池载体,与突刺同倍率错开冷却轴)" | not bite.is_empty() and is_equal_approx(float(bite.damage_mult), 1.5) and int(bite.cooldown_beats) == 1 | (待填) | | | +| 241 | 507 | "毒爆行(伤害 ×2.5 · 冷却 4 · 必中附加 ×0.75——蛛化卓尔精灵专属池独占技,新效应词汇批)" | not venom.is_empty() and is_equal_approx(float(venom.damage_mult), 2.5) and int(venom.cooldown_beats) == 4 and is_equal_approx(float(venom.rider_mult), 0.75) | (待填) | | | +| 242 | 515 | "毒爆独占性(仅蛛化卓尔精灵池引用——Boss 专属池独占技纪律)" | venom_sole_ok | (待填) | | | +| 243 | 518 | "蛛网束缚行(妨害型 · buff_ref → off_guard 失衡 · duration 2 = 1 有效拍 · 冷却 4——新效应词汇批首发)" | not snare.is_empty() and String(snare.type) == "debuff" and String(snare.buff_ref) == "off_guard" and int(snare.buff_duration) == 2 and int(snare.cooldown_beats) == 4 | (待填) | | | +| 244 | 526 | "蛛网束缚独占性(仅蛛化卓尔精灵池引用——实装面收窄,其余敌种不动)" | snare_sole_ok | (待填) | | | +| 245 | 528 | "失衡状态行(hero · ac 轴——妨害技落点,ConfigDB side 守卫正例)" | not off_guard_row.is_empty() and String(off_guard_row.target_side) == "hero" and String(off_guard_row.unit) == "ac" | (待填) | | | +| 246 | 531 | "毒吐行(伤害 ×1.6 · 冷却 2 · 无 rider——低 atk 下 rider 恒被 damage_floor 兜成 1 白占字段,迭代 24 N2)" | not spit.is_empty() and String(spit.display_name) == "毒吐" and String(spit.type) == "damage" \\ and is_equal_approx(float(spit.damage_mult), 1.6) and int(spit.cooldown_beats) == 2 and not spit.has("rider_mult") | (待填) | | | +| 247 | 535 | "盾击行(伤害 ×1.7 · 冷却 3 · 无 rider——迭代 24 N2)" | not bash.is_empty() and String(bash.display_name) == "盾击" and String(bash.type) == "damage" \\ and is_equal_approx(float(bash.damage_mult), 1.7) and int(bash.cooldown_beats) == 3 and not bash.has("rider_mult") | (待填) | | | +| 248 | 549 | "毒吐投放面(仅 depth_band > 巨蜘蛛首遇带(%d)的巨蜘蛛族行——首遇带池不动,迭代 24 N2/Q6)〔fv24〕" ↩ % spider_lead_band | spit_sole_ok | (待填) | | | +| 249 | 550 | "盾击投放面(仅 (3,·) 灰矮人族——钩爪恐魔零触碰,迭代 24 N2)" | bash_sole_ok | (待填) | | | +| 250 | 558 | "巨蜘蛛首遇带行 pool 恒 {attack:6, bite:2, charge:1}(教学位池零新招——毒吐首投在更深的带)〔fv24〕" | spider_lead_pool == {"attack": 6, "bite": 2, "charge": 1} | (待填) | | | +| 251 | 604 | "二十八行九种池模板 = 建议池行值((L,row_kind,depth_band,enemy_id) 四元键逐行精确比对;L1 地精 9/1 · 熊地精 6/2/2 · 酋长 6/2/2;L2 熊地精 6/1/2 · 蜘蛛 6/2/1+毒吐2〔首遇带不投——迭代 24 N2〕 · 重击手 5/2/2 · 蛛化卓尔精灵 6/2/2/2〔Boss〕;L3 灰矮人 6/2/2+盾击2 · 钩爪恐魔 6/2/1 · 重装手 5/2/2+盾击2 · 石君王 6/2/2〔earth_pulse〕——全部 provisional;三 Boss 池尾 +1 门控条目〔quake_smash/venom_curb/rockslide 权重 2+unlock_hp_pct 50——迭代14④ T5〕;混编行集同带多行各一池合法——首批投放 (2,battle,5) 双行〔迭代 18 卡⑤:重击手+巢群〕;迭代 35 覆盖批八行为顶档池克隆,逐行同检)" | pools_ok | (待填) | | | +| 252 | 614 | "每池恰一行普攻(空池不可能的加载校验同款)" | basic_count_ok | (待填) | | | +| 253 | 631 | "逐池增益 ≤1 · 妨害 ≤1(池构成纪律——迭代 24 N2 毒吐/盾击投放后复查)" | comp_ok | (待填) | | | +| 254 | 636 | "前置:卓尔 Boss 战受理(2,9——解锁技 venom_curb 门槛 50)" | sett_u.begin_battle({"level_index": 2, "node_depth": 9, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | (待填) | | | +| 255 | 639 | "前置:Boss 压到阈值 65(=round(130×0.5))" | int(boss_u.hp) == 65 | (待填) | | | +| 256 | 645 | "解锁归因行恰一次(×%d——闩去重,二次检查零行)" ↩ % unlock_rows.size() | unlock_rows.size() == 1 | (待填) | | | +| 257 | 647 | "解锁行文本 =「蛛化卓尔精灵 解锁了 遏毒」(高光键 skill_unlock——呈现层一次性技能名 toast 路由键)" | String(unlock_rows[0].get("display_name", "")) == "蛛化卓尔精灵 解锁了 遏毒" | (待填) | | | +| 258 | 648 | "解锁行型 = highlight 既有行型(零行型新增——行协议封闭)" | String(unlock_rows[0].get("type", "")) == "highlight" | (待填) | | | +| 259 | 662 | "全深度实例 spawn 后可摇集合恒非空(普攻恒冷却 0——容器级断言,L1/L2 双关卡)" | rollable_ok | (待填) | | | +| 260 | 666 | "蓄力状态行 empower(enemy +100 percent——敌方输出 ×2 走既有 enemy_output 池)" | not empower.is_empty() and String(empower.target_side) == "enemy" and int(empower.magnitude) == 100 | (待填) | | | +| 261 | 667 | "硬化状态行 fortify(enemy −50 percent——敌方防御池负值 = 主角攻击折半;+100 符号 bug 2026-09-18 纠正,provisional 归 状态层重设计批)" | not fortify.is_empty() and String(fortify.target_side) == "enemy" and int(fortify.magnitude) == -50 | (待填) | | | +| 262 | 668 | "effect_pool_map 收编 empower/fortify(敌方防御池 enemy_defense 入表)" | String(_db.settlement.effect_pool_map.get("empower", "")) == "enemy_output" \\ and String(_db.settlement.effect_pool_map.get("fortify", "")) == "enemy_defense" | (待填) | | | -## S42 _smoke_skill_pool_validation_reject(行 841–877 · 13 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S42 _smoke_skill_pool_replay_independence(行 703–709 · 2 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 261 | 841 | "加载校验拒绝:池条目 skill_id 悬空致命" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "dangling_skill") | 加载校验拒绝:池条目技能 id 悬空致命。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 262 | 843 | "加载校验拒绝:池普攻行数 ≠ 1(空池不可能前提)" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "pool_composition") | 加载校验拒绝:池普攻行数≠1(空池不可能前提)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 263 | 848 | "加载校验拒绝:池增益技 > 1(每池增益 ≤1 纪律)" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "pool_composition") | 加载校验拒绝:池增益技>1(每池增益≤1)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 264 | 850 | "加载校验拒绝:普攻配倍率 ≠ 1(域校验锁死)" | not skill_table.validate().is_empty() | 加载校验拒绝:普攻配倍率≠1(域校验锁死)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 265 | 852 | "加载校验拒绝:普攻配冷却 ≠ 0(域校验锁死)" | not skill_table.validate().is_empty() | 加载校验拒绝:普攻配冷却≠0(域校验锁死)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 266 | 854 | "加载校验拒绝:damage_mult 越域 [1.1,16]" | not skill_table.validate().is_empty() | 加载校验拒绝:伤害倍率越域 [1.1,16]。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 267 | 857 | "加载校验拒绝:池权重 ≤ 0" | not et2.validate().is_empty() | 加载校验拒绝:池权重≤0。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 268 | 859 | "加载校验拒绝:空池(禁)" | not et2.validate().is_empty() | 加载校验拒绝:空池(禁)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 269 | 865 | "加载校验拒绝:池妨害技 > 1(每池妨害 ≤1 纪律)" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "pool_composition") | 加载校验拒绝:池妨害技>1(每池妨害≤1)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 270 | 867 | "加载校验拒绝:妨害技缺 buff_ref(效果引用)" | not skill_table.validate().is_empty() | 加载校验拒绝:妨害技缺 buff_ref。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 271 | 869 | "加载校验拒绝:rider_mult 越域 (0,1]" | not skill_table.validate().is_empty() | 加载校验拒绝:必中附加分量越域 (0,1]。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 272 | 871 | "加载校验拒绝:非伤害技带必中附加分量" | not skill_table.validate().is_empty() | 加载校验拒绝:非伤害技带必中附加分量。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 273 | 877 | "加载校验拒绝:妨害技引用敌方侧状态(施放类 side 守卫)" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table, _db.status_effects), "cast_side") | 加载校验拒绝:妨害技引用敌方侧状态(施放类 side 守卫)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 263 | 703 | "多实例种子可达(seed=%d——D3 遭遇 ≥2 实例)" ↩ % seed_val | seed_val > 0 | (待填) | | | +| 264 | 709 | "同种子回放:各实例摇定动作序列逐位相同(×%d 轮 × %d 实例——子流独立复放面随并流退役)" ↩ % [rounds, int(run1.count)] | not run1.is_empty() and JSON.stringify(run1.seqs) == JSON.stringify(run2.seqs) | (待填) | | | -## S43 _smoke_debuff_rider(行 955–957 · 3 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S43 _smoke_skill_pool_ev_anchor(行 791–791 · 1 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 274 | 955 | "毒爆必中附加分量种子可达(seed=%d,毒爆伤害行 ×%d)" ↩ % [venom_seed, venom_rows_seen] | venom_seed > 0 and venom_rows_seen >= 1 | 毒爆必中附加分量种子可达(伤害行样本在案)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 275 | 956 | "毒爆伤害行逐拍精确复算:终值 = max(1, 掷点档位主分量) + 必中附加(rider 只随 rider_mult 行出现)" | venom_check_ok | 毒爆伤害行逐拍精确复算:终值=掷点主分量+必中附加(rider 键只随授权行出现)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 276 | 957 | "蛛网束缚落主角:施放后掷点阈值 = 基准 AC + 失衡幅度(off_guard ac 轴端到端,seed=%d)" ↩ % snare_seed | snare_seed > 0 | 蛛网束缚落主角:施放后掷点阈值=基准 AC+失衡幅度(端到端)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 265 | 791 | "EV 抽样对锚 %s(实测 %.4f ∈ [%.2f, %.2f] 含闸长期速率,×%d 拍)" ↩ % [eid, measured, float(band[0]), float(band[1]), n_beats] | in_band | (待填) | | | -## S44 _smoke_counter_buckets(行 980–1008 · 6 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S44 _smoke_skill_pool_death_void(行 811–830 · 5 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 277 | 980 | "V1 第1战真实 request_dig 受理(begin_battle 后 ACTIVE 相位)〔无状态化 V1〕" | dig1 | 第 1 战真实付费挖格受理(Active 相位)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 278 | 981 | "V1 第1战付费挖格经 ctx 就地写 adventure 桶=1(无 export 搬运时点)〔无状态化 V1〕" | int(led.adventure_counters.get("adv_twin_digs", 0)) == 1 | 第 1 战付费计数经上下文就地写 adventure 桶=1。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 279 | 986 | "V1 第2战 begin_battle 经 ctx 读到上一战持久值=1(读路径走宿主字典引用,非副本种子回灌)〔无状态化 V1〕" | EffectEngine.counter(o2.counters_ctx, "adv_twin_digs") == 1 | 第 2 战开战经上下文读到上一战持久值=1。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 280 | 988 | "V1 第2战挖后 adventure 桶跨战累积=2(run_ledger:42 判空早退缺陷消解的回归钉面——旧副本态恒 1)〔无状态化 V1〕" | int(led.adventure_counters.get("adv_twin_digs", 0)) == 2 | 第 2 战挖后 adventure 桶跨战累积=2(副本搬运态在此红)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 281 | 1003 | "V6 第1战标错落 battle 桶 insurance_used=1(_apply_counter_hit 静态写口经 ctx)〔无状态化 V6〕" | EffectEngine.counter(oA.counters_ctx, "insurance_used") == 1 | 第 1 战标错落 battle 桶 insurance_used=1。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | -| 282 | 1008 | "V6 battle 桶随宿主销毁:次战新编排器 fx_counters_battle 天然空,读 insurance_used=0〔无状态化 V6〕" | EffectEngine.counter(oB.counters_ctx, "insurance_used") == 0 | battle 桶随宿主销毁:次战新编排器读 0。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 266 | 811 | "死亡打断种子可达(seed=%d——D2 首拍摇中特殊技)" ↩ % void_seed | void_seed > 0 | (待填) | | | +| 267 | 817 | "死亡打断战斗启动(hero_atk 999——先手必杀 40HP 熊地精)" | settlement.begin_battle({"level_index": 1, "node_depth": 2, "hero_hp": int(_db.hero.max_hp), "hero_atk": 999}) | (待填) | | | +| 268 | 820 | "开轮已摇定本拍动作(待发 = %s)" ↩ % pending | pending != "" | (待填) | | | +| 269 | 826 | "先手击杀 → 摇定技能作废(第 1 轮零敌方行动行——死亡即不动手)" | enemy_rows == 0 | (待填) | | | +| 270 | 830 | "作废不进账(全池冷却 0 · 保底计数 0——执行上报未发生)" | cd_sum == 0 and int(u.basic_streak) == 0 | (待填) | | | -## S45 _smoke_seal_ap_exhausted(行 54–80 · 5 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) +## S45 _smoke_skill_pool_validation_reject(行 841–877 · 13 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 283 | 54 | "回归战斗启动(Boss 行——D0 20HP 级会被盲挖粉尘秒杀,无法承载封印回归场景)" | settlement.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | 封印回归战斗启动(Boss 行承载)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 284 | 68 | "%d 次挖掘后恰剩 1 AP(phase=%s ap=%d)" ↩ % [ap_total - 1, str(settlement.action_economy.phase), settlement.action_economy.ap] | settlement.action_economy.phase == "Active" and settlement.action_economy.ap == 1 | 表驱动次数挖掘后恰剩 1 AP。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 285 | 73 | "仍存在可挖目标(封印目标来源)" | not plan2.is_empty() | 仍存在可挖目标(封印目标来源)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 286 | 79 | "末点 AP 封印被受理" | not out.is_empty() and bool(out[0].accepted) | 末点 AP 封印被受理。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 287 | 80 | "封印动作耗尽 AP 触发轮末结算并进入第 2 轮(rounds=%s)" ↩ % str(rounds) | rounds.size() >= 2 | 末点封印触发轮末结算进入第 2 轮(playtest 缺陷②回归)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 271 | 841 | "加载校验拒绝:池条目 skill_id 悬空致命" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "dangling_skill") | (待填) | | | +| 272 | 843 | "加载校验拒绝:池普攻行数 ≠ 1(空池不可能前提)" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "pool_composition") | (待填) | | | +| 273 | 848 | "加载校验拒绝:池增益技 > 1(每池增益 ≤1 纪律)" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "pool_composition") | (待填) | | | +| 274 | 850 | "加载校验拒绝:普攻配倍率 ≠ 1(域校验锁死)" | not skill_table.validate().is_empty() | (待填) | | | +| 275 | 852 | "加载校验拒绝:普攻配冷却 ≠ 0(域校验锁死)" | not skill_table.validate().is_empty() | (待填) | | | +| 276 | 854 | "加载校验拒绝:damage_mult 越域 [1.1,16]" | not skill_table.validate().is_empty() | (待填) | | | +| 277 | 857 | "加载校验拒绝:池权重 ≤ 0" | not et2.validate().is_empty() | (待填) | | | +| 278 | 859 | "加载校验拒绝:空池(禁)" | not et2.validate().is_empty() | (待填) | | | +| 279 | 865 | "加载校验拒绝:池妨害技 > 1(每池妨害 ≤1 纪律)" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "pool_composition") | (待填) | | | +| 280 | 867 | "加载校验拒绝:妨害技缺 buff_ref(效果引用)" | not skill_table.validate().is_empty() | (待填) | | | +| 281 | 869 | "加载校验拒绝:rider_mult 越域 (0,1]" | not skill_table.validate().is_empty() | (待填) | | | +| 282 | 871 | "加载校验拒绝:非伤害技带必中附加分量" | not skill_table.validate().is_empty() | (待填) | | | +| 283 | 877 | "加载校验拒绝:妨害技引用敌方侧状态(施放类 side 守卫)" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table, _db.status_effects), "cast_side") | (待填) | | | -## S46 _smoke_clear_reward_chain(行 329–341 · 5 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) +## S46 _smoke_debuff_rider(行 955–957 · 3 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 288 | 329 | "① 完美封印清图达成(%d 轮)" ↩ % int(run.rounds) | bool(run.cleared) | 完美封印清图达成(真实流)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 289 | 336 | "① 清图奖励双份真实兑付(clear_reward 伤害行 ×%d——clear_blast 身份两份各打一发)" ↩ % dmg.size() | dmg.size() == 2 | 清图奖励双份真实兑付(两发 clear_reward 伤害行)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 290 | 338 | "① 第二份按身份类型走真实兑付(首份『清图奖励』/ 第二份『完美第二份』)" | String(dmg[0].display_name).contains("清图奖励") and String(dmg[1].display_name).contains("完美第二份") | 第二份按身份类型走真实兑付(首份/第二份显示名区分)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 291 | 339 | "④ 高光行 value = 实际有效份数 2(不记名义值;行数 %d)" ↩ % hl.size() | hl.size() == 1 and int(hl[0].value) == 2 | 高光行 value=实际有效份数 2(不记名义值)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 292 | 341 | "高光标记 = perfect_sweep" | String(hl[0].get("highlight", "")) == "perfect_sweep" | 高光标记=perfect_sweep。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 284 | 955 | "毒爆必中附加分量种子可达(seed=%d,毒爆伤害行 ×%d)" ↩ % [venom_seed, venom_rows_seen] | venom_seed > 0 and venom_rows_seen >= 1 | (待填) | | | +| 285 | 956 | "毒爆伤害行逐拍精确复算:终值 = max(1, 掷点档位主分量) + 必中附加(rider 只随 rider_mult 行出现)" | venom_check_ok | (待填) | | | +| 286 | 957 | "蛛网束缚落主角:施放后掷点阈值 = 基准 AC + 失衡幅度(off_guard ac 轴端到端,seed=%d)" ↩ % snare_seed | snare_seed > 0 | (待填) | | | -## S47 _smoke_clear_reward_zero_paths(行 359–399 · 11 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) +## S47 _smoke_counter_buckets(行 980–1008 · 6 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 293 | 359 | "零兑付构造战斗启动" | s.begin_battle({"level_index": 1, "node_depth": CLEAR_SMOKE_DEPTH, "hero_hp": 80, "hero_max_hp": 80, "node_type": "boss"}) | 零兑付构造战斗启动。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 294 | 363 | "清图槽身份 = blast 型(clear_default 唯一活行 clear_blast——第二份同身份兑付)" | snap != null and String(snap.cell_type) == "blast" | 清图槽身份=blast 型(clear_blast)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 295 | 366 | "② 即时类首份零兑付(无存活敌)→ first_ok=false" | not first_ok | 无存活敌时首份零兑付 first_ok=false。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 296 | 367 | "② 零兑付产出 reward_ineffective 行(batch_key=clear_blast)" | _has_source_row(rows, "reward_ineffective", "clear_reward", "clear_blast") | 零兑付产出未生效归因行。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 297 | 374 | "高光守卫构造战斗启动" | s_b.begin_battle({"level_index": 1, "node_depth": CLEAR_SMOKE_DEPTH, "hero_hp": 80, "hero_max_hp": 80, "node_type": "boss"}) | 高光守卫构造战斗启动。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 298 | 381 | "② _grant_clear_reward 守卫路径重走首份 → 未生效行仍在案" | _has_source_row(rows_b, "reward_ineffective", "clear_reward", "clear_blast") | 守卫路径重走首份→未生效行仍在案。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 299 | 382 | "② 首份零兑付 → 高光行不发(不记名义值 2)" | _rows_of_type(rows_b, "highlight").is_empty() | 首份零兑付→高光行不发(不记名义值)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 300 | 389 | "第二份被拒构造战斗启动" | s2.begin_battle({"level_index": 1, "node_depth": CLEAR_SMOKE_DEPTH, "hero_hp": 80, "hero_max_hp": 80, "node_type": "boss"}) | 第二份被拒构造战斗启动。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 301 | 396 | "③ 首份 aoe 真实兑付(击杀唯一存活敌)" | _has_source_row(rows2, "damage", "clear_reward") | 首份 aoe 真实兑付(击杀唯一存活敌)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 302 | 397 | "③ 第二份被拒产出 reward_ineffective 行(零兑付)" | _has_source_row(rows2, "reward_ineffective", "clear_reward", "clear_blast") | 第二份被拒产出未生效行(零兑付)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 303 | 399 | "④ 高光行 value = 实际有效份数 1(不记名义值 2)" | hl2.size() == 1 and int(hl2[0].value) == 1 | 高光行 value=实际有效份数 1。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 287 | 980 | "V1 第1战真实 request_dig 受理(begin_battle 后 ACTIVE 相位)〔无状态化 V1〕" | dig1 | (待填) | | | +| 288 | 981 | "V1 第1战付费挖格经 ctx 就地写 adventure 桶=1(无 export 搬运时点)〔无状态化 V1〕" | int(led.adventure_counters.get("adv_twin_digs", 0)) == 1 | (待填) | | | +| 289 | 986 | "V1 第2战 begin_battle 经 ctx 读到上一战持久值=1(读路径走宿主字典引用,非副本种子回灌)〔无状态化 V1〕" | EffectEngine.counter(o2.counters_ctx, "adv_twin_digs") == 1 | (待填) | | | +| 290 | 988 | "V1 第2战挖后 adventure 桶跨战累积=2(run_ledger:42 判空早退缺陷消解的回归钉面——旧副本态恒 1)〔无状态化 V1〕" | int(led.adventure_counters.get("adv_twin_digs", 0)) == 2 | (待填) | | | +| 291 | 1003 | "V6 第1战标错落 battle 桶 insurance_used=1(_apply_counter_hit 静态写口经 ctx)〔无状态化 V6〕" | EffectEngine.counter(oA.counters_ctx, "insurance_used") == 1 | (待填) | | | +| 292 | 1008 | "V6 battle 桶随宿主销毁:次战新编排器 fx_counters_battle 天然空,读 insurance_used=0〔无状态化 V6〕" | EffectEngine.counter(oB.counters_ctx, "insurance_used") == 0 | (待填) | | | -## S48 _smoke_refresh_provenance_guard(行 410–421 · 5 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) +## S48 _smoke_seal_ap_exhausted(行 54–80 · 5 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 304 | 410 | "轮内刷新受理(三轮停手攒 RAP 后 Active 相位发起)" | bool(run.refreshed) | 轮内刷新受理(三轮攒 RAP 后发起)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 305 | 411 | "刷新图清图达成(%d 轮)" ↩ % int(run.rounds) | bool(run.cleared) | 刷新图清图达成。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 306 | 419 | "刷新图清图 → 无 clear_reward 行(守卫在位——实测 %d 行)" ↩ % cr | cr == 0 | 刷新图清图零 clear_reward 行(来源守卫在位)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 307 | 420 | "刷新图清图 → 无高光行(守卫在位——实测 %d 行)" ↩ % hl | hl == 0 | 刷新图清图零高光行。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 308 | 421 | "对照基线:常规换图清图有 clear_reward 行(×%d——守卫非全局静默)" ↩ % _regular_clear_reward_rows | _regular_clear_reward_rows > 0 | 对照基线:常规清图有 clear_reward 行(守卫非全局静默)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 293 | 54 | "回归战斗启动(Boss 行——D0 20HP 级会被盲挖粉尘秒杀,无法承载封印回归场景)" | settlement.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | (待填) | | | +| 294 | 68 | "%d 次挖掘后恰剩 1 AP(phase=%s ap=%d)" ↩ % [ap_total - 1, str(settlement.action_economy.phase), settlement.action_economy.ap] | settlement.action_economy.phase == "Active" and settlement.action_economy.ap == 1 | (待填) | | | +| 295 | 73 | "仍存在可挖目标(封印目标来源)" | not plan2.is_empty() | (待填) | | | +| 296 | 79 | "末点 AP 封印被受理" | not out.is_empty() and bool(out[0].accepted) | (待填) | | | +| 297 | 80 | "封印动作耗尽 AP 触发轮末结算并进入第 2 轮(rounds=%s)" ↩ % str(rounds) | rounds.size() >= 2 | (待填) | | | -## S49 _smoke_polarity_routing(行 442–549 · 12 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) +## S49 _smoke_clear_reward_chain(行 329–341 · 5 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 309 | 442 | "极性路由构造种子可达(seed=%d——并流后行为面重锚)" ↩ % polarity_seed | polarity_seed > 0 | 极性路由构造种子可达(行为面重锚)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 310 | 443 | "构造触达:标中真雷 + 标空安全格均入队" | bool(probe["seal_hit_done"]) and bool(probe["seal_miss_done"]) | 构造触达:标中真雷+标空安全格均入队。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 311 | 444 | "构造触达:挖到危险格 ≥1(%d)" ↩ % int(probe["mine_events"]) | int(probe["mine_events"]) >= 1 | 构造触达:挖到危险格≥1。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 312 | 445 | "构造触达:挖到安全格 ≥1(%d)" ↩ % int(probe["safe_events"]) | int(probe["safe_events"]) >= 1 | 构造触达:挖到安全格≥1。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 313 | 462 | "挖安全格 → 只出正向兑付(效果行 ∈ 正向字典 + 每格 ≥1 行——%d 格)" ↩ % probe["safe_cells"].size() | safe_all_pos | 挖安全格只出正向兑付(效果行∈正向字典+每格≥1 行)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 314 | 479 | "挖危险格 → 只出负向兑付(效果行 ∈ 负向字典 + 每格 ≥1 行——%d 格)" ↩ % probe["mine_cells"].size() | mine_all_neg | 挖危险格只出负向兑付(效果行∈负向字典+每格≥1 行)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 315 | 492 | "封印命中 → 该危险格资源的正向兑付行(origin 指回危险格,效果 ∈ 正向字典;action 型按行值符号辨极性)" | hit_ok | 封印命中→该危险格正向兑付行(origin 指回格,效果∈正向字典)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 316 | 498 | "封印溢价配置在位(seal_hit_premium=125 provisional——形态 a 兑付溢价)" | prem_pct == 125 | 封印溢价配置在位(125 provisional)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 317 | 526 | "标中兑付溢价(×%d 行逐型=同型正向 ×%d/100——等号压线注记:δ=1.25 时条件二 E[标中]=1.25·E_dig 恰等成立)" ↩ % [prem_rows, prem_pct] | prem_rows >= 1 and prem_ok | 标中兑付逐型溢价=同型正向×1.25。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 318 | 532 | "标空惩罚零溢价(负向直伤行=固定表值原值——溢价只挂标中正向)" | miss_flat_ok | 标空惩罚零溢价(负向直伤=固定表值原值)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 319 | 543 | "标空封印 → 封印未中归因行 + 该安全格资源负向兑付(同格同批)" | not miss_rows.is_empty() and miss_neg_ok | 标空封印→归因行+安全格负向兑付(同格同批)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 320 | 549 | "封印判定兑付仅封印判定批两封印在案(撤封印抵消过滤零泄漏——%d 格)" ↩ % seal_srcs.size() | int(seal_srcs.size()) <= 2 | 封印判定兑付仅两封印在案(撤封印抵消过滤零泄漏)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 298 | 329 | "① 完美封印清图达成(%d 轮)" ↩ % int(run.rounds) | bool(run.cleared) | (待填) | | | +| 299 | 336 | "① 清图奖励双份真实兑付(clear_reward 伤害行 ×%d——clear_blast 身份两份各打一发)" ↩ % dmg.size() | dmg.size() == 2 | (待填) | | | +| 300 | 338 | "① 第二份按身份类型走真实兑付(首份『清图奖励』/ 第二份『完美第二份』)" | String(dmg[0].display_name).contains("清图奖励") and String(dmg[1].display_name).contains("完美第二份") | (待填) | | | +| 301 | 339 | "④ 高光行 value = 实际有效份数 2(不记名义值;行数 %d)" ↩ % hl.size() | hl.size() == 1 and int(hl[0].value) == 2 | (待填) | | | +| 302 | 341 | "高光标记 = perfect_sweep" | String(hl[0].get("highlight", "")) == "perfect_sweep" | (待填) | | | -## S50 _smoke_seal_irreversible_quadrants(行 571–663 · 21 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) +## S50 _smoke_clear_reward_zero_paths(行 359–399 · 11 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 321 | 571 | "四情况 A:开局受理" | settle_a.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | 四情况场景 A 开局受理。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 322 | 574 | "四情况 A:找到安全格封印目标" | not t_a.is_empty() | 场景 A 找到安全格封印目标。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 323 | 578 | "四情况 A:封印受理" | ms_a.settle_batch([{"action": "seal_place", "cell": t_a.seal_cell}])[0].accepted | 场景 A 封印受理。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 324 | 580 | "四情况 A:seal_pull 出目录拒绝(撤封印退役)" | not pull_out.is_empty() and not bool(pull_out[0].get("accepted", true)) and String(pull_out[0].get("sub_reason", "")) == "out_of_catalog" | 场景 A:撤封印动作出目录拒绝(不可逆)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 325 | 582 | "四情况 A:重复封印仍被拒(seal_present)" | not dup_place.is_empty() and not bool(dup_place[0].get("accepted", true)) and String(dup_place[0].get("sub_reason", "")) == "seal_present" | 场景 A:重复封印仍被拒(seal_present)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 326 | 584 | "四情况 A:封印格直接挖开被受理(不再须先撤封印)" | not dig_a.is_empty() and bool(dig_a[0].get("accepted", false)) | 场景 A:封印格直接挖开被受理(不须先撤)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 327 | 585 | "四情况 A:封印随挖掘隐式退役(has_seal = false)" | not ms_a.has_seal(t_a.seal_cell) | 场景 A:封印随挖掘隐式退役。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 328 | 600 | "四情况 A:标空判定零兑付(×%d——封印已擦除不入轮末基线)" ↩ % miss_a | miss_a == 0 | 场景 A:标空判定零兑付(封印已擦除不入轮末基线)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 329 | 601 | "四情况 A:安全格正向兑付作废(×%d——零奖励)" ↩ % grant_a | grant_a == 0 | 场景 A:安全格正向兑付作废(零奖励)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 330 | 602 | "四情况 A:兑付作废归因行恰一次(×%d——零惩罚零奖励留痕)" ↩ % void_a | void_a == 1 | 场景 A:兑付作废归因行恰一次(零惩罚零奖励留痕)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 331 | 609 | "四情况 B:开局受理" | settle_b.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | 四情况场景 B 开局受理。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 332 | 612 | "四情况 B:找到危险格封印目标" | not t_b.is_empty() | 场景 B 找到危险格封印目标。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 333 | 616 | "四情况 B:封印受理" | ms_b.settle_batch([{"action": "seal_place", "cell": t_b.seal_cell}])[0].accepted | 场景 B 封印受理。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 334 | 618 | "四情况 B:封印危险格直接挖开被受理" | not dig_b.is_empty() and bool(dig_b[0].get("accepted", false)) | 场景 B:封印危险格直接挖开受理。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 335 | 619 | "四情况 B:危险格已揭示(挖雷本体照常)" | ms_b.is_revealed(t_b.seal_cell) and ms_b.current_board().is_mine(t_b.seal_cell) | 场景 B:危险格已揭示(挖雷本体照常)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 336 | 631 | "四情况 B:挖雷惩罚照常兑付(×%d——只受挖雷一层惩罚)" ↩ % mine_hit_b | mine_hit_b >= 1 | 场景 B:挖雷惩罚照常(只受一层惩罚)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 337 | 632 | "四情况 B:标中不发(×%d——封印已擦除不判)" ↩ % hit_b | hit_b == 0 | 场景 B:标中不发(封印已擦除不判)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 338 | 639 | "四情况 C:开局受理" | settle_c.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | 四情况场景 C 开局受理。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 339 | 643 | "四情况 C:安全格+危险格封印目标齐备" | not t_safe.is_empty() and not t_mine.is_empty() | 场景 C:安全格+危险格封印目标齐备。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 340 | 662 | "四情况 C:标空惩罚照常(归因×%d 负向兑付×%d)" ↩ % [miss_c, miss_neg_c] | miss_c >= 1 and miss_neg_c >= 1 | 场景 C:标空惩罚照常(归因+负向兑付)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 341 | 663 | "四情况 C:标中奖励照常(×%d)" ↩ % hit_c | hit_c >= 1 | 场景 C:标中奖励照常。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 303 | 359 | "零兑付构造战斗启动" | s.begin_battle({"level_index": 1, "node_depth": CLEAR_SMOKE_DEPTH, "hero_hp": 80, "hero_max_hp": 80, "node_type": "boss"}) | (待填) | | | +| 304 | 363 | "清图槽身份 = blast 型(clear_default 唯一活行 clear_blast——第二份同身份兑付)" | snap != null and String(snap.cell_type) == "blast" | (待填) | | | +| 305 | 366 | "② 即时类首份零兑付(无存活敌)→ first_ok=false" | not first_ok | (待填) | | | +| 306 | 367 | "② 零兑付产出 reward_ineffective 行(batch_key=clear_blast)" | _has_source_row(rows, "reward_ineffective", "clear_reward", "clear_blast") | (待填) | | | +| 307 | 374 | "高光守卫构造战斗启动" | s_b.begin_battle({"level_index": 1, "node_depth": CLEAR_SMOKE_DEPTH, "hero_hp": 80, "hero_max_hp": 80, "node_type": "boss"}) | (待填) | | | +| 308 | 381 | "② _grant_clear_reward 守卫路径重走首份 → 未生效行仍在案" | _has_source_row(rows_b, "reward_ineffective", "clear_reward", "clear_blast") | (待填) | | | +| 309 | 382 | "② 首份零兑付 → 高光行不发(不记名义值 2)" | _rows_of_type(rows_b, "highlight").is_empty() | (待填) | | | +| 310 | 389 | "第二份被拒构造战斗启动" | s2.begin_battle({"level_index": 1, "node_depth": CLEAR_SMOKE_DEPTH, "hero_hp": 80, "hero_max_hp": 80, "node_type": "boss"}) | (待填) | | | +| 311 | 396 | "③ 首份 aoe 真实兑付(击杀唯一存活敌)" | _has_source_row(rows2, "damage", "clear_reward") | (待填) | | | +| 312 | 397 | "③ 第二份被拒产出 reward_ineffective 行(零兑付)" | _has_source_row(rows2, "reward_ineffective", "clear_reward", "clear_blast") | (待填) | | | +| 313 | 399 | "④ 高光行 value = 实际有效份数 1(不记名义值 2)" | hl2.size() == 1 and int(hl2[0].value) == 1 | (待填) | | | -## S51 _smoke_negative_dictionary(行 675–755 · 8 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) +## S51 _smoke_refresh_provenance_guard(行 410–421 · 5 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 342 | 675 | "sapped 行在位(hero · −25 percent · dur 1 · max 16 · stack——weaken 型负向)" | not sapped.is_empty() and String(sapped.get("target_side", "")) == "hero" \\ and int(sapped.get("magnitude", 0)) == -25 and int(sapped.get("duration_settlements", 0)) == 1 \\ and int(sapped.get("max_stack", 0)) == 16 and String(sapped.get("stack_policy", "")) == "stack" | sapped 状态行在位(−25% 持续 1 上限 16 可叠)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 343 | 679 | "effect_pool_map 收编 sapped → hero_offense(attack_up 镜像池)" | String(_db.settlement.effect_pool_map.get("sapped", "")) == "hero_offense" | 效果池映射收编 sapped→hero_offense(attack_up 镜像池)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 344 | 716 | "扫描 6 板内出现五型齐备板(%s)" ↩ % str(by_type.keys()) | by_type.size() == 5 | 6 板扫描内出现五型齐备板。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 345 | 726 | "%s 型负向 → %s 效果行(mine_hit 来源指回该格)" ↩ % [ctype, eff] | _has_source_row(rows, "effect", "mine_hit") and _has_effect_id(rows, eff) | attack/defense/weaken 型负向→对应效果行(来源指回该格)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 346 | 738 | "heal 型负向 → 主角直伤 %d(%d → %d,不经增益状态)" ↩ % [backlash_power, hp_before, hp_after] | hp_after == hp_before - backlash_power | heal 型负向→主角直伤表值(不经增益状态)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 347 | 748 | "blast 型负向 → 警觉(敌方护甲等级 +1 状态在位)" | alert_ok | blast 型负向→警觉状态在位(敌 AC+1)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 348 | 753 | "action 型负向 → 下轮行动点 −1(排队)" | settlement.action_economy.pending_ap_delta() == d0 - 1 | action 型负向→下轮行动点 −1(排队)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 349 | 755 | "action 型正负抵消 → 排队净值归零" | settlement.action_economy.pending_ap_delta() == d0 | action 型正负抵消→排队净值归零。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 314 | 410 | "轮内刷新受理(三轮停手攒 RAP 后 Active 相位发起)" | bool(run.refreshed) | (待填) | | | +| 315 | 411 | "刷新图清图达成(%d 轮)" ↩ % int(run.rounds) | bool(run.cleared) | (待填) | | | +| 316 | 419 | "刷新图清图 → 无 clear_reward 行(守卫在位——实测 %d 行)" ↩ % cr | cr == 0 | (待填) | | | +| 317 | 420 | "刷新图清图 → 无高光行(守卫在位——实测 %d 行)" ↩ % hl | hl == 0 | (待填) | | | +| 318 | 421 | "对照基线:常规换图清图有 clear_reward 行(×%d——守卫非全局静默)" ↩ % _regular_clear_reward_rows | _regular_clear_reward_rows > 0 | (待填) | | | -## S52 _smoke_resource_surface(行 785–804 · 5 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) +## S52 _smoke_polarity_routing(行 442–549 · 12 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 350 | 785 | "chain/dig 揭示事件 resource 恒 true(雷安同池——违例 %d)" ↩ % digchain_bad | digchain_bad == 0 | chain/dig 揭示事件恒携带资源标记(雷安同池)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 351 | 786 | "initial 揭示事件(封锁格)resource 恒 false(×%d 事件)" ↩ % initial_count | initial_bad == 0 and initial_count >= 1 | initial 揭示事件(封锁格)资源标记恒 false。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 352 | 787 | "危险格揭示携带资源(mine ×resource=true %d——危险格不再恒无)" ↩ % mine_with_resource | mine_with_resource >= 1 | 危险格揭示携带资源(×resource=true≥1)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 353 | 803 | "封锁格(R₀)身份查询 = null(不入摇号清单)" | settlement.query_reward_identity(r0_first) == null | 封锁格(出生区)身份查询=null。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | -| 354 | 804 | "未开危险格身份恒可查({reward_id, cell_type, tier}——秘宝 桥接前提)" | mine_unrevealed != null and settlement.query_reward_identity(mine_unrevealed) != null | 未开危险格身份恒可查(秘宝桥接前提)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 319 | 442 | "极性路由构造种子可达(seed=%d——并流后行为面重锚)" ↩ % polarity_seed | polarity_seed > 0 | (待填) | | | +| 320 | 443 | "构造触达:标中真雷 + 标空安全格均入队" | bool(probe["seal_hit_done"]) and bool(probe["seal_miss_done"]) | (待填) | | | +| 321 | 444 | "构造触达:挖到危险格 ≥1(%d)" ↩ % int(probe["mine_events"]) | int(probe["mine_events"]) >= 1 | (待填) | | | +| 322 | 445 | "构造触达:挖到安全格 ≥1(%d)" ↩ % int(probe["safe_events"]) | int(probe["safe_events"]) >= 1 | (待填) | | | +| 323 | 462 | "挖安全格 → 只出正向兑付(效果行 ∈ 正向字典 + 每格 ≥1 行——%d 格)" ↩ % probe["safe_cells"].size() | safe_all_pos | (待填) | | | +| 324 | 479 | "挖危险格 → 只出负向兑付(效果行 ∈ 负向字典 + 每格 ≥1 行——%d 格)" ↩ % probe["mine_cells"].size() | mine_all_neg | (待填) | | | +| 325 | 492 | "封印命中 → 该危险格资源的正向兑付行(origin 指回危险格,效果 ∈ 正向字典;action 型按行值符号辨极性)" | hit_ok | (待填) | | | +| 326 | 498 | "封印溢价配置在位(seal_hit_premium=125 provisional——形态 a 兑付溢价)" | prem_pct == 125 | (待填) | | | +| 327 | 526 | "标中兑付溢价(×%d 行逐型=同型正向 ×%d/100——等号压线注记:δ=1.25 时条件二 E[标中]=1.25·E_dig 恰等成立)" ↩ % [prem_rows, prem_pct] | prem_rows >= 1 and prem_ok | (待填) | | | +| 328 | 532 | "标空惩罚零溢价(负向直伤行=固定表值原值——溢价只挂标中正向)" | miss_flat_ok | (待填) | | | +| 329 | 543 | "标空封印 → 封印未中归因行 + 该安全格资源负向兑付(同格同批)" | not miss_rows.is_empty() and miss_neg_ok | (待填) | | | +| 330 | 549 | "封印判定兑付仅封印判定批两封印在案(撤封印抵消过滤零泄漏——%d 格)" ↩ % seal_srcs.size() | int(seal_srcs.size()) <= 2 | (待填) | | | -## S53 _init(行 18–18 · 1 条 · 源 game/tests/unit/battle_settlement/skill_unlock_test.gd · 去向:待分类回填) +## S53 _smoke_seal_irreversible_quadrants(行 571–663 · 21 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 355 | 18 | "前置:表加载零 error" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | +| 331 | 571 | "四情况 A:开局受理" | settle_a.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | (待填) | | | +| 332 | 574 | "四情况 A:找到安全格封印目标" | not t_a.is_empty() | (待填) | | | +| 333 | 578 | "四情况 A:封印受理" | ms_a.settle_batch([{"action": "seal_place", "cell": t_a.seal_cell}])[0].accepted | (待填) | | | +| 334 | 580 | "四情况 A:seal_pull 出目录拒绝(撤封印退役)" | not pull_out.is_empty() and not bool(pull_out[0].get("accepted", true)) and String(pull_out[0].get("sub_reason", "")) == "out_of_catalog" | (待填) | | | +| 335 | 582 | "四情况 A:重复封印仍被拒(seal_present)" | not dup_place.is_empty() and not bool(dup_place[0].get("accepted", true)) and String(dup_place[0].get("sub_reason", "")) == "seal_present" | (待填) | | | +| 336 | 584 | "四情况 A:封印格直接挖开被受理(不再须先撤封印)" | not dig_a.is_empty() and bool(dig_a[0].get("accepted", false)) | (待填) | | | +| 337 | 585 | "四情况 A:封印随挖掘隐式退役(has_seal = false)" | not ms_a.has_seal(t_a.seal_cell) | (待填) | | | +| 338 | 600 | "四情况 A:标空判定零兑付(×%d——封印已擦除不入轮末基线)" ↩ % miss_a | miss_a == 0 | (待填) | | | +| 339 | 601 | "四情况 A:安全格正向兑付作废(×%d——零奖励)" ↩ % grant_a | grant_a == 0 | (待填) | | | +| 340 | 602 | "四情况 A:兑付作废归因行恰一次(×%d——零惩罚零奖励留痕)" ↩ % void_a | void_a == 1 | (待填) | | | +| 341 | 609 | "四情况 B:开局受理" | settle_b.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | (待填) | | | +| 342 | 612 | "四情况 B:找到危险格封印目标" | not t_b.is_empty() | (待填) | | | +| 343 | 616 | "四情况 B:封印受理" | ms_b.settle_batch([{"action": "seal_place", "cell": t_b.seal_cell}])[0].accepted | (待填) | | | +| 344 | 618 | "四情况 B:封印危险格直接挖开被受理" | not dig_b.is_empty() and bool(dig_b[0].get("accepted", false)) | (待填) | | | +| 345 | 619 | "四情况 B:危险格已揭示(挖雷本体照常)" | ms_b.is_revealed(t_b.seal_cell) and ms_b.current_board().is_mine(t_b.seal_cell) | (待填) | | | +| 346 | 631 | "四情况 B:挖雷惩罚照常兑付(×%d——只受挖雷一层惩罚)" ↩ % mine_hit_b | mine_hit_b >= 1 | (待填) | | | +| 347 | 632 | "四情况 B:标中不发(×%d——封印已擦除不判)" ↩ % hit_b | hit_b == 0 | (待填) | | | +| 348 | 639 | "四情况 C:开局受理" | settle_c.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | (待填) | | | +| 349 | 643 | "四情况 C:安全格+危险格封印目标齐备" | not t_safe.is_empty() and not t_mine.is_empty() | (待填) | | | +| 350 | 662 | "四情况 C:标空惩罚照常(归因×%d 负向兑付×%d)" ↩ % [miss_c, miss_neg_c] | miss_c >= 1 and miss_neg_c >= 1 | (待填) | | | +| 351 | 663 | "四情况 C:标中奖励照常(×%d)" ↩ % hit_c | hit_c >= 1 | (待填) | | | -## S54 _boss(行 30–30 · 1 条 · 源 game/tests/unit/battle_settlement/skill_unlock_test.gd · 去向:待分类回填) +## S54 _smoke_negative_dictionary(行 675–755 · 8 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 356 | 30 | "前置:spawn (%d,%d) 受理" ↩ % [L, D] | ok | (待填) | | | +| 352 | 675 | "sapped 行在位(hero · −25 percent · dur 1 · max 16 · stack——weaken 型负向)" | not sapped.is_empty() and String(sapped.get("target_side", "")) == "hero" \\ and int(sapped.get("magnitude", 0)) == -25 and int(sapped.get("duration_settlements", 0)) == 1 \\ and int(sapped.get("max_stack", 0)) == 16 and String(sapped.get("stack_policy", "")) == "stack" | (待填) | | | +| 353 | 679 | "effect_pool_map 收编 sapped → hero_offense(attack_up 镜像池)" | String(_db.settlement.effect_pool_map.get("sapped", "")) == "hero_offense" | (待填) | | | +| 354 | 716 | "扫描 6 板内出现五型齐备板(%s)" ↩ % str(by_type.keys()) | by_type.size() == 5 | (待填) | | | +| 355 | 726 | "%s 型负向 → %s 效果行(mine_hit 来源指回该格)" ↩ % [ctype, eff] | _has_source_row(rows, "effect", "mine_hit") and _has_effect_id(rows, eff) | (待填) | | | +| 356 | 738 | "heal 型负向 → 主角直伤 %d(%d → %d,不经增益状态)" ↩ % [backlash_power, hp_before, hp_after] | hp_after == hp_before - backlash_power | (待填) | | | +| 357 | 748 | "blast 型负向 → 警觉(敌方护甲等级 +1 状态在位)" | alert_ok | (待填) | | | +| 358 | 753 | "action 型负向 → 下轮行动点 −1(排队)" | settlement.action_economy.pending_ap_delta() == d0 - 1 | (待填) | | | +| 359 | 755 | "action 型正负抵消 → 排队净值归零" | settlement.action_economy.pending_ap_delta() == d0 | (待填) | | | -## S55 _test_gate_predicate(行 49–69 · 9 条 · 源 game/tests/unit/battle_settlement/skill_unlock_test.gd · 去向:待分类回填) +## S55 _smoke_resource_surface(行 785–804 · 5 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 357 | 49 | "前置:卓尔 max_hp=130(表值——难度乘数空表恒 1.0)" | mhp == 130 | (待填) | | | -| 358 | 52 | "门谓词:hp=66 > 阈值 65 → 零解锁" | ec.check_skill_unlocks(u).is_empty() | (待填) | | | -| 359 | 53 | "双面过滤:未解锁 venom_curb 不入可摇集合" | not _has_sid(ec.rollable_skills(u), "venom_curb") | (待填) | | | -| 360 | 54 | "双面过滤:未解锁 venom_curb 不入保底特殊技集合" | not _has_sid(ec.special_skills(u), "venom_curb") | (待填) | | | -| 361 | 57 | "前置:补一刀至阈值 65 受理" | hit65 and int(u.hp) == 65 | (待填) | | | -| 362 | 59 | "门谓词:hp=65 ≤ 阈值 65 → 恰解锁 venom_curb 一次" | unlocked.size() == 1 and String(unlocked[0]) == "venom_curb" | (待填) | | | -| 363 | 61 | "解锁即可摇:venom_curb 入可摇集合(冷却初值 0)" | _has_sid(ec.rollable_skills(u), "venom_curb") | (待填) | | | -| 364 | 62 | "解锁即可摇:venom_curb 入保底特殊技集合" | _has_sid(ec.special_skills(u), "venom_curb") | (待填) | | | -| 365 | 69 | "保底不逼锁定:锁定期保底集合 = heavy+venom_burst+web_snare(venom_curb 过滤不出)" | specials.size() == 3 and _has_sid(specials, "heavy") and _has_sid(specials, "web_snare") and _has_sid(specials, "venom_burst") and not _has_sid(specials, "venom_curb") | (待填) | | | +| 360 | 785 | "chain/dig 揭示事件 resource 恒 true(雷安同池——违例 %d)" ↩ % digchain_bad | digchain_bad == 0 | (待填) | | | +| 361 | 786 | "initial 揭示事件(封锁格)resource 恒 false(×%d 事件)" ↩ % initial_count | initial_bad == 0 and initial_count >= 1 | (待填) | | | +| 362 | 787 | "危险格揭示携带资源(mine ×resource=true %d——危险格不再恒无)" ↩ % mine_with_resource | mine_with_resource >= 1 | (待填) | | | +| 363 | 803 | "封锁格(R₀)身份查询 = null(不入摇号清单)" | settlement.query_reward_identity(r0_first) == null | (待填) | | | +| 364 | 804 | "未开危险格身份恒可查({reward_id, cell_type, tier}——秘宝 桥接前提)" | mine_unrevealed != null and settlement.query_reward_identity(mine_unrevealed) != null | (待填) | | | -## S56 _test_latch_irreversible(行 77–80 · 3 条 · 源 game/tests/unit/battle_settlement/skill_unlock_test.gd · 去向:待分类回填) +## S56 _init(行 18–18 · 1 条 · 源 game/tests/unit/battle_settlement/skill_unlock_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 366 | 77 | "前置:半血触发解锁" | ec.check_skill_unlocks(u).size() == 1 | (待填) | | | -| 367 | 79 | "闩不可逆:满血复查零新解锁(单向闩不解锁回退)" | ec.check_skill_unlocks(u).is_empty() | (待填) | | | -| 368 | 80 | "闩不可逆:可摇集合仍含 venom_curb" | _has_sid(ec.rollable_skills(u), "venom_curb") | (待填) | | | +| 365 | 18 | "前置:表加载零 error" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | -## S57 _test_plain_rows_zero_drift(行 91–103 · 4 条 · 源 game/tests/unit/battle_settlement/skill_unlock_test.gd · 去向:待分类回填) +## S57 _boss(行 30–30 · 1 条 · 源 game/tests/unit/battle_settlement/skill_unlock_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 369 | 91 | "零扰动:无字段行池快照零 unlock 键(16/19 行逐位同前)" | not any_unlock_key | (待填) | | | -| 370 | 92 | "零扰动:满血复查零解锁" | ec.check_skill_unlocks(u).is_empty() | (待填) | | | -| 371 | 95 | "零扰动:可摇集合 = 全池(普攻冷却恒 0,过滤恒空转)" | rollable.size() == pool_count | (待填) | | | -| 372 | 103 | "确定性:两实例同输入可摇集合逐位同(容器零随机纪律)" | a == b | (待填) | | | +| 366 | 30 | "前置:spawn (%d,%d) 受理" ↩ % [L, D] | ok | (待填) | | | -## S58 _smoke_variant_placement(行 60–132 · 9 条 · 源 game/tests/unit/battle_settlement/variant_placement_test.gd · 去向:待分类回填) +## S58 _test_gate_predicate(行 49–69 · 9 条 · 源 game/tests/unit/battle_settlement/skill_unlock_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 373 | 60 | "L%d 布置段战斗启动失败" ↩ % L | false | 布置段战斗启动失败显式红。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 374 | 63 | "L%d 布置段板非空" ↩ % L | b != null | 布置段板非空。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 375 | 84 | "L%d 变异 id 全在候选池(门控一致性:%s)" ↩ % [L, str(pool_ids)] | not bad_id | 变异 id 全在候选池(门控一致性)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 376 | 85 | "L%d 每图至多一种变异类型(单格单 id + 全图同 id)" ↩ % L | single_type | 每图至多一种变异类型(单格单 id+全图同 id)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 377 | 93 | "L%d 变异格全在初始展开区外且不吃危险格(布置域结构保障——closed_only)" ↩ % L | domain_ok | 变异格全在初始展开区外且不吃危险格。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 378 | 100 | "L%d 变异格数 %d ≤ 公式上限 %d(可挖池百分比 + 关卡递增)" ↩ % [L, vcells.size(), expect_cap] | vcells.size() <= expect_cap | 变异格数≤公式上限(可挖池百分比+关卡递增)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 379 | 103 | "L%d 公式量>0 时非零布置(极小池 round 至 0 = 合法零布置——2026-09-21 基数修正批)" ↩ % L | expect_cap == 0 or vcells.size() > 0 | 公式量>0 时非零布置(极小池 round 至 0=合法零布置)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 380 | 125 | "L%d 全部连通片 ≤ %d 格(全连通生长、2×2/1×4/1×3+1 等形)" ↩ % [L, pack_max] | pack_ok | 全部变异连通片≤pack_max 格。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 381 | 132 | "L%d 同种子变异布置逐格相同(variant_place 流确定性)" ↩ % L | b2 != null and _variant_dump(b2) == _variant_dump(b) | 同种子变异布置逐格相同(variant_place 流确定性)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 367 | 49 | "前置:卓尔 max_hp=130(表值——难度乘数空表恒 1.0)" | mhp == 130 | (待填) | | | +| 368 | 52 | "门谓词:hp=66 > 阈值 65 → 零解锁" | ec.check_skill_unlocks(u).is_empty() | (待填) | | | +| 369 | 53 | "双面过滤:未解锁 venom_curb 不入可摇集合" | not _has_sid(ec.rollable_skills(u), "venom_curb") | (待填) | | | +| 370 | 54 | "双面过滤:未解锁 venom_curb 不入保底特殊技集合" | not _has_sid(ec.special_skills(u), "venom_curb") | (待填) | | | +| 371 | 57 | "前置:补一刀至阈值 65 受理" | hit65 and int(u.hp) == 65 | (待填) | | | +| 372 | 59 | "门谓词:hp=65 ≤ 阈值 65 → 恰解锁 venom_curb 一次" | unlocked.size() == 1 and String(unlocked[0]) == "venom_curb" | (待填) | | | +| 373 | 61 | "解锁即可摇:venom_curb 入可摇集合(冷却初值 0)" | _has_sid(ec.rollable_skills(u), "venom_curb") | (待填) | | | +| 374 | 62 | "解锁即可摇:venom_curb 入保底特殊技集合" | _has_sid(ec.special_skills(u), "venom_curb") | (待填) | | | +| 375 | 69 | "保底不逼锁定:锁定期保底集合 = heavy+venom_burst+web_snare(venom_curb 过滤不出)" | specials.size() == 3 and _has_sid(specials, "heavy") and _has_sid(specials, "web_snare") and _has_sid(specials, "venom_burst") and not _has_sid(specials, "venom_curb") | (待填) | | | -## S59 _smoke_global_variant(行 154–255 · 30 条 · 源 game/tests/unit/battle_settlement/variant_placement_test.gd · 去向:待分类回填) +## S59 _test_latch_irreversible(行 77–80 · 3 条 · 源 game/tests/unit/battle_settlement/skill_unlock_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 382 | 154 | "互锁负样本③:resource_face + scope=region 被拒(resource_face ⇒ global)" | _errs_has_cross_field(vt.validate()) | 互锁负样本③:resource_face+scope=region 被拒。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 383 | 158 | "互锁负样本④:scope=global + effect=hide_number 被拒(global ⇒ resource_face)" | _errs_has_cross_field(vt.validate()) | 互锁负样本④:global+hide_number 被拒。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 384 | 162 | "互锁负样本⑤:scope=global + trigger=on_open 被拒(global ⇒ always 整图恒在)" | _errs_has_cross_field(vt.validate()) | 互锁负样本⑤:global+on_open 被拒。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 385 | 166 | "互锁负样本⑥:scope=global + placement=closed_only 被拒(global ⇒ any 无格子布置域)" | _errs_has_cross_field(vt.validate()) | 互锁负样本⑥:global+closed_only 被拒。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 386 | 175 | "互锁负样本⑦:resource_face effect_value=51 越域 [0,50] 被拒(不适用 flat_damage [1,9] 加法核)" | domain_hit | 互锁负样本⑦:resource_face 值 51 越域被拒。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 387 | 180 | "门控三维:L2 underdark boss 池 = region 两行(贫矿脉 2026-10-16 屏蔽删行后无 global 行)" | pool_boss_ids == ["magma", "ash"] | 门控三维:L2 boss 池=region 两行(贫矿脉屏蔽后无 global 行)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 388 | 184 | "门控三维:battle 节点静默跳过 global 行(普通图零报错零警告)" | pool_battle_ids == ["magma", "ash"] | 门控三维:battle 节点静默跳过 global 行。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 389 | 185 | "门控三维:缺省双参调用 = global 行跳过(既有调用口径零扰动)" | _db.variant_pool(2, "underdark").is_empty() == false and _db.variant_pool(2, "underdark").size() == 2 | 缺省双参调用=global 行跳过(既有口径零扰动)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 390 | 186 | "L1 Boss 免疫:unlock_level=2 门控在 boss 节点同样生效(教学保护带 B)" | _db.variant_pool(1, "underdark", "boss").is_empty() | L1 Boss 免疫:解锁门控在 boss 节点同样生效。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 391 | 187 | "全局决议:battle 节点恒无全局型" | _db.global_variant_row(2, "underdark", "battle").is_empty() | 全局决议:battle 节点恒无全局型。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 392 | 188 | "全局决议:L2 boss 全局池空 → 空行(贫矿脉屏蔽后兜底回落承载)" | _db.global_variant_row(2, "underdark", "boss").is_empty() | 全局决议:L2 boss 全局池空→空行(兜底回落承载)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 393 | 189 | "全局决议:L1 boss 全局池空 → 空行(兜底回落承载)" | _db.global_variant_row(1, "underdark", "boss").is_empty() and _db.global_variant_row(1, "dungeon", "boss").is_empty() | 全局决议:L1 boss 全局池空→空行。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 394 | 190 | "elite 前向词位:boss 同池恒空(贫矿脉屏蔽后——词表位不造行)" | _db.global_variant_row(2, "underdark", "elite").is_empty() | elite 前向词位:boss 同池恒空(词表位不造行)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 395 | 196 | "L2 Boss 战斗启动(node_depth=9 Boss 终点——兜底回落断言载体)" | boss_ok | L2 Boss 战斗启动受理(兜底回落断言载体)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 396 | 202 | "屏蔽后回落:Boss 图无整图全局型(board_variants 空——全局池空合法态)" | bb.board_variant_ids().is_empty() | 屏蔽后回落:Boss 图无整图全局型(合法空态)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 397 | 207 | "屏蔽后回落:Boss 图 region 变异恰一行且在候选池内(实测 %s)" ↩ % str(boss_vids.keys()) | boss_vids.size() == 1 and (boss_vids.has("magma") or boss_vids.has("ash")) | 屏蔽后回落:Boss 图 region 变异恰一行且在候选池内。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 398 | 208 | "屏蔽后回落:Boss 图恢复 region 变异格布置(原替换制跳过 region 的逻辑随全局池空失效)" | not bb.variants.is_empty() | 屏蔽后回落:Boss 图恢复 region 变异格布置。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 399 | 209 | "屏蔽后回落:variant_place 恢复取号(原全局取用零取号态消失)" | crng.variant_calls > 0 | 屏蔽后回落:variant_place 恢复取号。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 400 | 211 | "快照投影:board_variants 空态一致(横幅/角标唯一读取面——无全局型即零预告)" | _arr_str_eq(snap.get("board_variants", []), bb.board_variant_ids()) | 快照投影 board_variants 空态一致(横幅唯一读取面)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 401 | 221 | "确定性:同种子 Boss 图两遍 board_variants/变异格/取号逐位相同(region 布置路径)" | _arr_str_eq(bb2.board_variant_ids(), boss1_ids) and bb2.variants == boss1_variants and crng2.variant_calls == boss1_calls | 同种子 Boss 图两遍布置/变异/取号逐位相同(region 布置路径确定性)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 402 | 226 | "偏斜基线:正向池现行合计 77(挖掘风险批口径)" | base_sum == 77 | 资源偏斜基线:正向池合计 77。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 403 | 229 | "off 态零扰动:board_skew=0 行集与基线逐位相同(不进投影域)" | off_rows.size() == base_rows.size() and _rows_weights_equal(off_rows, base_rows) | off 态零扰动:skew=0 行集与基线逐位相同。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 404 | 232 | "合计守恒:偏斜后正向池合计 = 77(整数最大余数重归一)" | eco._pool_weight(on_rows) == 77 | 合计守恒:偏斜后正向池合计仍 77。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 405 | 234 | "白名单豁免:weaken/gp/action 三行偏斜后逐位不动" | wmap.get("weaken", -1) == 8 and wmap.get("ap_up", -1) == 6 and wmap.get("gp_coin", -1) == 2 | 白名单豁免:weaken/gp/action 三行偏斜后逐位不动。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 406 | 235 | "生存族下调:heal 4→3 / defense 11→7(v=30 构造例)" | wmap.get("heal_minor", -1) == 3 and wmap.get("ac_up", -1) == 7 | 生存族下调构造例(heal 4→3、defense 11→7)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 407 | 236 | "进攻族上浮:attack 14→15 / blast 16→18 / pierce 8→9 / magic 8→9(v=30 构造例,等比上浮守恒)" | wmap.get("attack_up", -1) == 15 and wmap.get("spark_aoe", -1) == 18 and wmap.get("pierce_strike", -1) == 9 and wmap.get("magic_bolt", -1) == 9 | 进攻族上浮构造例(等比上浮守恒)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 408 | 240 | "合成序:图级偏斜先(乘法面)件级 luck_bias 后(加法 +2×四稀有类)——合计 77+8" | eco._pool_weight(combo_rows) == 77 + 4 * 2 | 合成序:图级偏斜先乘、件级加算后加(合计 77+8)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 409 | 241 | "合成序构造例:skew(heal 3)+2=5 / gp 2+2=4 / pierce 9+2=11 / magic 9+2=11(图级定型后玩家构筑叠加)" | cw.get("heal_minor", -1) == 5 and cw.get("gp_coin", -1) == 4 and cw.get("pierce_strike", -1) == 11 and cw.get("magic_bolt", -1) == 11 | 合成序构造例逐行值正确。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 410 | 244 | "域上界 v=50:守恒成立且逐行权重恒正(域 [0,50] 安全)" | eco._pool_weight(eco._biased_cell_rows(0)) == 77 and int(v50.get("heal_minor", -1)) > 0 | 域上界 v=50:守恒成立且逐行权重恒正。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | -| 411 | 255 | "确定性:同种子普通图布置/摇号两遍逐位相同(off 全链路)" | n1 == _board_resource_dump(normal2) and not n1.is_empty() | 同种子普通图布置/摇号两遍逐位相同(off 全链路确定性)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 376 | 77 | "前置:半血触发解锁" | ec.check_skill_unlocks(u).size() == 1 | (待填) | | | +| 377 | 79 | "闩不可逆:满血复查零新解锁(单向闩不解锁回退)" | ec.check_skill_unlocks(u).is_empty() | (待填) | | | +| 378 | 80 | "闩不可逆:可摇集合仍含 venom_curb" | _has_sid(ec.rollable_skills(u), "venom_curb") | (待填) | | | -## S60 _smoke_type_gate(行 25–29 · 2 条 · 源 game/tests/unit/contracts/source_guard_test.gd · 去向:待分类回填) +## S60 _test_plain_rows_zero_drift(行 91–103 · 4 条 · 源 game/tests/unit/battle_settlement/skill_unlock_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 412 | 25 | "V7 类型闸 warning 配置在位(untyped_declaration=1 显形)〔迭代27 T5〕" | pj.contains("gdscript/warnings/untyped_declaration=1") | project.godot 里 untyped_declaration=1 警告配置在位(类型闸的配置面源码断言)。 | 迁单元测试(tests/unit/contracts/source_guard_test.gd) | | -| 413 | 28 | "V7 类型审计器闸机在位(static_scan S9 → tools/audit_type_hints.py)〔迭代27 T5〕" | scan_src.contains("audit_type_hints.py") | 静态扫描脚本里类型审计器接线在座(audit_type_hints.py 被引用)。 | 迁单元测试(tests/unit/contracts/source_guard_test.gd) | | +| 379 | 91 | "零扰动:无字段行池快照零 unlock 键(16/19 行逐位同前)" | not any_unlock_key | (待填) | | | +| 380 | 92 | "零扰动:满血复查零解锁" | ec.check_skill_unlocks(u).is_empty() | (待填) | | | +| 381 | 95 | "零扰动:可摇集合 = 全池(普攻冷却恒 0,过滤恒空转)" | rollable.size() == pool_count | (待填) | | | +| 382 | 103 | "确定性:两实例同输入可摇集合逐位同(容器零随机纪律)" | a == b | (待填) | | | -## S61 _smoke_hygiene_gates(行 56–77 · 6 条 · 源 game/tests/unit/contracts/source_guard_test.gd · 去向:待分类回填) +## S61 _smoke_variant_placement(行 60–132 · 9 条 · 源 game/tests/unit/battle_settlement/variant_placement_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 414 | 56 | "V8 字符串 Callable 产品码零命中(实测应 0——方法引用换装闸)〔迭代27 T6〕" | callables == 0 | core/presentation/data 三目录产品码零字符串形式 Callable(self,…)——方法引用必须换装。 | 迁单元测试(tests/unit/contracts/source_guard_test.gd) | | -| 415 | 58 | "V8 FeatsEngine 整文件退役(class_name 注销=文件删除)〔迭代27 T6〕" | not FileAccess.file_exists("res://core/feats_engine.gd") | core/feats_engine.gd 文件已删(FeatsEngine 退役=文件消失,不是注释掉)。 | 迁单元测试(tests/unit/contracts/source_guard_test.gd) | | -| 416 | 61 | "V8 死代码抽查·_attune_row 零残留〔迭代27 T6〕" | not bo.contains("func _attune_row") | 战斗编排器源码无 _attune_row 死函数残留。 | 迁单元测试(tests/unit/contracts/source_guard_test.gd) | | -| 417 | 64 | "V8 死代码抽查·attunement_counters 零残留〔迭代27 T6〕" | not sm.contains("func attunement_counters") | 存档管理器源码无 attunement_counters 死函数残留。 | 迁单元测试(tests/unit/contracts/source_guard_test.gd) | | -| 418 | 67 | "V8 GP 影子账残留零命中(main 源码面——文件已删+委托缝已拆)〔迭代27 T3〕" | not mv.contains("gp_shadow_ledger") and not mv.contains("_gp_ledger") | main 源码无 GP 影子账标识符残留(gp_shadow_ledger/_gp_ledger 双词面零命中)。 | 迁单元测试(tests/unit/contracts/source_guard_test.gd) | | -| 419 | 76 | "V1d 层序落位单口(_apply_layer_contract 之外 move_child 调用点=%d 应=0)〔装配生命批 V1〕" ↩ % outside_calls | outside_calls == 0 | main 的 move_child 调用全部收在 _apply_layer_contract 一个函数内(层序合同单口,函数外零调用)。 | 迁单元测试(tests/unit/contracts/source_guard_test.gd) | | +| 383 | 60 | "L%d 布置段战斗启动失败" ↩ % L | false | (待填) | | | +| 384 | 63 | "L%d 布置段板非空" ↩ % L | b != null | (待填) | | | +| 385 | 84 | "L%d 变异 id 全在候选池(门控一致性:%s)" ↩ % [L, str(pool_ids)] | not bad_id | (待填) | | | +| 386 | 85 | "L%d 每图至多一种变异类型(单格单 id + 全图同 id)" ↩ % L | single_type | (待填) | | | +| 387 | 93 | "L%d 变异格全在初始展开区外且不吃危险格(布置域结构保障——closed_only)" ↩ % L | domain_ok | (待填) | | | +| 388 | 100 | "L%d 变异格数 %d ≤ 公式上限 %d(可挖池百分比 + 关卡递增)" ↩ % [L, vcells.size(), expect_cap] | vcells.size() <= expect_cap | (待填) | | | +| 389 | 103 | "L%d 公式量>0 时非零布置(极小池 round 至 0 = 合法零布置——2026-09-21 基数修正批)" ↩ % L | expect_cap == 0 or vcells.size() > 0 | (待填) | | | +| 390 | 125 | "L%d 全部连通片 ≤ %d 格(全连通生长、2×2/1×4/1×3+1 等形)" ↩ % [L, pack_max] | pack_ok | (待填) | | | +| 391 | 132 | "L%d 同种子变异布置逐格相同(variant_place 流确定性)" ↩ % L | b2 != null and _variant_dump(b2) == _variant_dump(b) | (待填) | | | -## S62 _init(行 22–22 · 1 条 · 源 game/tests/unit/contracts/system_contracts_test.gd · 去向:待分类回填) +## S62 _smoke_global_variant(行 154–255 · 30 条 · 源 game/tests/unit/battle_settlement/variant_placement_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 420 | 22 | "前置:表加载零 error" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | +| 392 | 154 | "互锁负样本③:resource_face + scope=region 被拒(resource_face ⇒ global)" | _errs_has_cross_field(vt.validate()) | (待填) | | | +| 393 | 158 | "互锁负样本④:scope=global + effect=hide_number 被拒(global ⇒ resource_face)" | _errs_has_cross_field(vt.validate()) | (待填) | | | +| 394 | 162 | "互锁负样本⑤:scope=global + trigger=on_open 被拒(global ⇒ always 整图恒在)" | _errs_has_cross_field(vt.validate()) | (待填) | | | +| 395 | 166 | "互锁负样本⑥:scope=global + placement=closed_only 被拒(global ⇒ any 无格子布置域)" | _errs_has_cross_field(vt.validate()) | (待填) | | | +| 396 | 175 | "互锁负样本⑦:resource_face effect_value=51 越域 [0,50] 被拒(不适用 flat_damage [1,9] 加法核)" | domain_hit | (待填) | | | +| 397 | 180 | "门控三维:L2 underdark boss 池 = region 两行(贫矿脉 2026-10-16 屏蔽删行后无 global 行)" | pool_boss_ids == ["magma", "ash"] | (待填) | | | +| 398 | 184 | "门控三维:battle 节点静默跳过 global 行(普通图零报错零警告)" | pool_battle_ids == ["magma", "ash"] | (待填) | | | +| 399 | 185 | "门控三维:缺省双参调用 = global 行跳过(既有调用口径零扰动)" | _db.variant_pool(2, "underdark").is_empty() == false and _db.variant_pool(2, "underdark").size() == 2 | (待填) | | | +| 400 | 186 | "L1 Boss 免疫:unlock_level=2 门控在 boss 节点同样生效(教学保护带 B)" | _db.variant_pool(1, "underdark", "boss").is_empty() | (待填) | | | +| 401 | 187 | "全局决议:battle 节点恒无全局型" | _db.global_variant_row(2, "underdark", "battle").is_empty() | (待填) | | | +| 402 | 188 | "全局决议:L2 boss 全局池空 → 空行(贫矿脉屏蔽后兜底回落承载)" | _db.global_variant_row(2, "underdark", "boss").is_empty() | (待填) | | | +| 403 | 189 | "全局决议:L1 boss 全局池空 → 空行(兜底回落承载)" | _db.global_variant_row(1, "underdark", "boss").is_empty() and _db.global_variant_row(1, "dungeon", "boss").is_empty() | (待填) | | | +| 404 | 190 | "elite 前向词位:boss 同池恒空(贫矿脉屏蔽后——词表位不造行)" | _db.global_variant_row(2, "underdark", "elite").is_empty() | (待填) | | | +| 405 | 196 | "L2 Boss 战斗启动(node_depth=9 Boss 终点——兜底回落断言载体)" | boss_ok | (待填) | | | +| 406 | 202 | "屏蔽后回落:Boss 图无整图全局型(board_variants 空——全局池空合法态)" | bb.board_variant_ids().is_empty() | (待填) | | | +| 407 | 207 | "屏蔽后回落:Boss 图 region 变异恰一行且在候选池内(实测 %s)" ↩ % str(boss_vids.keys()) | boss_vids.size() == 1 and (boss_vids.has("magma") or boss_vids.has("ash")) | (待填) | | | +| 408 | 208 | "屏蔽后回落:Boss 图恢复 region 变异格布置(原替换制跳过 region 的逻辑随全局池空失效)" | not bb.variants.is_empty() | (待填) | | | +| 409 | 209 | "屏蔽后回落:variant_place 恢复取号(原全局取用零取号态消失)" | crng.variant_calls > 0 | (待填) | | | +| 410 | 211 | "快照投影:board_variants 空态一致(横幅/角标唯一读取面——无全局型即零预告)" | _arr_str_eq(snap.get("board_variants", []), bb.board_variant_ids()) | (待填) | | | +| 411 | 221 | "确定性:同种子 Boss 图两遍 board_variants/变异格/取号逐位相同(region 布置路径)" | _arr_str_eq(bb2.board_variant_ids(), boss1_ids) and bb2.variants == boss1_variants and crng2.variant_calls == boss1_calls | (待填) | | | +| 412 | 226 | "偏斜基线:正向池现行合计 77(挖掘风险批口径)" | base_sum == 77 | (待填) | | | +| 413 | 229 | "off 态零扰动:board_skew=0 行集与基线逐位相同(不进投影域)" | off_rows.size() == base_rows.size() and _rows_weights_equal(off_rows, base_rows) | (待填) | | | +| 414 | 232 | "合计守恒:偏斜后正向池合计 = 77(整数最大余数重归一)" | eco._pool_weight(on_rows) == 77 | (待填) | | | +| 415 | 234 | "白名单豁免:weaken/gp/action 三行偏斜后逐位不动" | wmap.get("weaken", -1) == 8 and wmap.get("ap_up", -1) == 6 and wmap.get("gp_coin", -1) == 2 | (待填) | | | +| 416 | 235 | "生存族下调:heal 4→3 / defense 11→7(v=30 构造例)" | wmap.get("heal_minor", -1) == 3 and wmap.get("ac_up", -1) == 7 | (待填) | | | +| 417 | 236 | "进攻族上浮:attack 14→15 / blast 16→18 / pierce 8→9 / magic 8→9(v=30 构造例,等比上浮守恒)" | wmap.get("attack_up", -1) == 15 and wmap.get("spark_aoe", -1) == 18 and wmap.get("pierce_strike", -1) == 9 and wmap.get("magic_bolt", -1) == 9 | (待填) | | | +| 418 | 240 | "合成序:图级偏斜先(乘法面)件级 luck_bias 后(加法 +2×四稀有类)——合计 77+8" | eco._pool_weight(combo_rows) == 77 + 4 * 2 | (待填) | | | +| 419 | 241 | "合成序构造例:skew(heal 3)+2=5 / gp 2+2=4 / pierce 9+2=11 / magic 9+2=11(图级定型后玩家构筑叠加)" | cw.get("heal_minor", -1) == 5 and cw.get("gp_coin", -1) == 4 and cw.get("pierce_strike", -1) == 11 and cw.get("magic_bolt", -1) == 11 | (待填) | | | +| 420 | 244 | "域上界 v=50:守恒成立且逐行权重恒正(域 [0,50] 安全)" | eco._pool_weight(eco._biased_cell_rows(0)) == 77 and int(v50.get("heal_minor", -1)) > 0 | (待填) | | | +| 421 | 255 | "确定性:同种子普通图布置/摇号两遍逐位相同(off 全链路)" | n1 == _board_resource_dump(normal2) and not n1.is_empty() | (待填) | | | -## S63 _test_damage_controller_vocabularies(行 43–57 · 4 条 · 源 game/tests/unit/contracts/system_contracts_test.gd · 去向:待分类回填) +## S63 _smoke_type_gate(行 25–29 · 2 条 · 源 game/tests/unit/contracts/source_guard_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 421 | 43 | "五配方常量非空(开单面唯一分派键)" | nonempty | (待填) | | | -| 422 | 47 | "五配方常量互异(词表封闭无别名)" | unique_kinds.size() == kinds.size() | (待填) | | | -| 423 | 55 | "三目标规则常量互异(结账时刻现场解析词表)" | unique_targets.size() == targets.size() | (待填) | | | -| 424 | 56 | "双结账政策常量互异(A 即结 / B 攒批回切位)" | String(DamageControllerScript.POLICY_IMMEDIATE) != String(DamageControllerScript.POLICY_SEGMENT) | (待填) | | | +| 422 | 25 | "V7 类型闸 warning 配置在位(untyped_declaration=1 显形)〔迭代27 T5〕" | pj.contains("gdscript/warnings/untyped_declaration=1") | (待填) | | | +| 423 | 28 | "V7 类型审计器闸机在位(static_scan S9 → tools/audit_type_hints.py)〔迭代27 T5〕" | scan_src.contains("audit_type_hints.py") | (待填) | | | -## S64 _test_rng_tag_closure(行 64–67 · 2 条 · 源 game/tests/unit/contracts/system_contracts_test.gd · 去向:待分类回填) +## S64 _smoke_hygiene_gates(行 56–77 · 6 条 · 源 game/tests/unit/contracts/source_guard_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 425 | 64 | "settlement.armor_roll_stream ∈ RngService 封闭标签词表(EC12)" | RngServiceScript.PURPOSE_TAGS.has(stream_label) | (待填) | | | -| 426 | 66 | "RngService 用途标签词表非空且互异(封闭语义由流改标签)" | RngServiceScript.PURPOSE_TAGS.size() > 0 | (待填) | | | +| 424 | 56 | "V8 字符串 Callable 产品码零命中(实测应 0——方法引用换装闸)〔迭代27 T6〕" | callables == 0 | (待填) | | | +| 425 | 58 | "V8 FeatsEngine 整文件退役(class_name 注销=文件删除)〔迭代27 T6〕" | not FileAccess.file_exists("res://core/feats_engine.gd") | (待填) | | | +| 426 | 61 | "V8 死代码抽查·_attune_row 零残留〔迭代27 T6〕" | not bo.contains("func _attune_row") | (待填) | | | +| 427 | 64 | "V8 死代码抽查·attunement_counters 零残留〔迭代27 T6〕" | not sm.contains("func attunement_counters") | (待填) | | | +| 428 | 67 | "V8 GP 影子账残留零命中(main 源码面——文件已删+委托缝已拆)〔迭代27 T3〕" | not mv.contains("gp_shadow_ledger") and not mv.contains("_gp_ledger") | (待填) | | | +| 429 | 76 | "V1d 层序落位单口(_apply_layer_contract 之外 move_child 调用点=%d 应=0)〔装配生命批 V1〕" ↩ % outside_calls | outside_calls == 0 | (待填) | | | -## S65 _test_ticket_nine_columns(行 83–85 · 3 条 · 源 game/tests/unit/contracts/system_contracts_test.gd · 去向:待分类回填) +## S65 _init(行 22–22 · 1 条 · 源 game/tests/unit/contracts/system_contracts_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 427 | 83 | "单据九栏 + 可选两栏缺省齐备(开单面只写差异栏前提)" | all_present | (待填) | | | -| 428 | 84 | "缺省无类型轴(attack_type 空 = 恒不查克制矩阵)" | String(t.attack_type) == "" | (待填) | | | -| 429 | 85 | "缺省无前置依赖(requires = -1)" | int(t.requires) == -1 | (待填) | | | - -## S66 _test_status_boolean_acceptance(行 107–121 · 5 条 · 源 game/tests/unit/contracts/system_contracts_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 430 | 107 | "前置:状态表非空" | not any_row.is_empty() | (待填) | | | -| 431 | 112 | "域闭授予拒绝(EC1——战斗域外 apply_effect = false)" | effects.apply_effect(probe_id, src) == false | (待填) | | | -| 432 | 113 | "未知 effect_id 拒绝(EC2)" | effects.apply_effect("no_such_effect_id__", src) == false | (待填) | | | -| 433 | 115 | "开域后受理已知 id(布尔受理口径 true 通道)" | effects.apply_effect(probe_id, src) == true | (待填) | | | -| 434 | 121 | "受理后投影 effective_value = 层数×授予时快照(抗热重载 EC9 面)" | projected | (待填) | | | - -## S67 _smoke_adventure_api(行 61–82 · 11 条 · 源 game/tests/unit/core/adventure_feats_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 435 | 61 | "冒险启动受理(主种子注入恰一次——state=running;种子 42=首局固定种子生成图——精确路径断言需要确定图(卡② 代差))" | adr.start_adventure(42) | (待填) | | | -| 436 | 62 | "进行中重复 start_adventure 拒绝(重开须待结算完毕)" | not adr.start_adventure(43) | 进行中重复启动被拒(重开须待结算完毕)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 437 | 63 | "停点前移次序契约:level_started 先于入口候选呈现(反序选路口会被呈现层 runner 空防御丢弃)" | sig_seq.size() == 2 and String(sig_seq[0]) == "level_started" and String(sig_seq[1]) == "choices_presented" | 信号次序契约:关卡开始信号先于入口候选呈现。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 438 | 64 | "入口停点:选路态 + 入口单候选 + 首战未开(呈现零 RNG 消耗——pick 前零战斗域)" | adr.runner().awaiting_choice() and (obs["entry_cands"] as Array).size() == 1 \\ and adr.runner().current_battle() == null | 入口停点形态:选路待选+单候选+首战未开(呈现零 RNG 消耗)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 439 | 67 | "pick 入口候选受理(同一 _enter_node——与旧自动进入逐位等价)" | adr.runner().pick_next_node(int(adr.runner().next_candidate_ids()[0])) | 选路点选入口候选受理。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 440 | 68 | "pick 后 node_entered 到达 + 首战在飞(接口①开始关卡——停点闭合直达战斗节点)" | sig_seq.size() == 3 and String(sig_seq[2]) == "node_entered" and adr.runner().current_battle() != null | 点选后节点进入信号到达且首战在飞。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 441 | 69 | "战斗中 abandon 拒绝(战斗域单事务在飞)" | not adr.abandon() | 战斗中放弃被拒(战斗域单事务在飞)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 442 | 78 | "首战胜后选路态(非战斗期——current_battle 置空)" | adr.runner().current_battle() == null and adr.runner().awaiting_choice() | 首战胜后回到选路态(战斗对象置空)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 443 | 79 | "非战斗期 abandon 受理(选路态——同失败型结算)" | adr.abandon() | 非战斗期放弃受理。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 444 | 81 | "放弃结算:result=abandoned · cleared=false(已获面不追回——钥石累计 %d)" ↩ % int(sum.get("keystone_total", -1)) | String(sum.get("result", "")) == "abandoned" and not bool(sum.get("cleared", true)) | 放弃结算:结果=abandoned、未打穿(已获面不追回)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 445 | 82 | "结算后重复 abandon 拒绝(结算受理只算一次——state=idle)" | not adr.abandon() | 结算后重复放弃被拒(结算只受理一次)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | - -## S68 _smoke_adventure(行 113–438 · 57 条 · 源 game/tests/unit/core/adventure_feats_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 446 | 113 | "序列关数 = 行集关数(三关正式序列生效)" | int(_db.level.rows.size()) == 3 | 关卡行集 3 行(三关正式序列生效)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 447 | 130 | "生成图每个战斗/Boss 节点经解析链命中敌行集与棋盘行(扫描 %d 节点,缺失 %d)〔fv24〕" ↩ % [cov_scan, cov_miss] | cov_scan > 0 and cov_miss == 0 | (待填) | | | -| 448 | 135 | "满配通关(42 / atk+900 / hp+400——cleared=true;v2 怪阶梯后旧 200/100 档不再满配)" | String(st["result"]) == "completed" and bool(sum.get("cleared", false)) | 满配通关(调试攻血覆盖)达成 cleared=true。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 449 | 136 | "通关几关 = 3(levels_cleared)" | int(sum.get("levels_cleared", -1)) == 3 | 通关数=3。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 450 | 140 | "钥石合计 = ΣBoss 首杀行值(%d——逐杀掉落新口径;断点未注入恒首杀)" ↩ % int(sum.get("keystone_total", -1)) | int(sum.get("keystone_total", -1)) == ks_exp | 钥石合计=各关 Boss 首杀行值之和(逐杀掉落口径)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 451 | 142 | "逐杀掉落明细恰 3 笔(L1/L2/L3——additive 摘要键 keystone_drops)" | drops.size() == 3 | 逐杀掉落明细恰 3 笔(每关一笔)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 452 | 143 | "摘要携带主种子(42)" | int(sum.get("master_seed", -1)) == 42 | 冒险摘要携带主种子 42。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 453 | 154 | "通关三选一恰呈现 2 次、恰兑付 2 次(L1/L2 后——末关不发;resolves 含节点回执另计)" | picks.size() == 2 and resolves.size() == 2 + node_picks | 通关三选一恰呈现 2 次、恰兑付 2 次(末关不发)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 454 | 165 | "三关生成图各含 ≥1 奖励节点(节点三选一候选位在图)〔v4〕" | reward_levels == 3 | (待填) | | | -| 455 | 169 | "奖励分支点选→三选一候选可取且停点在途(候选 %s 件)" ↩ % str((rw.get("cands", []) as Array).size()) | bool(rw.get("pending", false)) and (rw.get("cands", []) as Array).size() >= 1 | (待填) | | | -| 456 | 182 | "奖励三选一点选兑付受理(件=%s)" ↩ % String(rw_pc.get("kind", "")) | rw_pick_ok | (待填) | | | -| 457 | 188 | "进战载荷两样=关卡序号+节点深度(实测 depth=%s 句柄=%s)〔v6〕" ↩ % [str(enc_probe.get("node_depth", -1)), str(node_probe.get("id", -1))] | not enc_probe.is_empty() and int(enc_probe.get("node_depth", -1)) == int(node_probe.get("depth", -1)) and int(enc_probe.get("level_index", -1)) == 1 and int(node_probe.get("id", -1)) != int(node_probe.get("depth", -1)) | (待填) | | | -| 458 | 197 | "奖励节点兑付回执与经停逐笔相等(context=reward_node——同账计数 %d/%d)" ↩ % [node_resolves, node_picks] | node_resolves == node_picks | 奖励节点兑付回执与经停数逐笔相等(同账计数)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 459 | 244 | "三选一候选 2–3 枚且零重复(令牌洗牌不放回——kind+tier 复合身份)" | cand_ok | 三选一候选 2–3 枚且零重复(令牌洗牌不放回)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 460 | 245 | "通关部分回血封顶(floor(观测上限×比例) 封顶——购血观测值精确断言,核代码增补)" | heal_ok | 通关回血按生效上限比例封顶(血回满语义退役,含购血容差窗口)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 461 | 246 | "钥石逐杀入账(首杀档位 + 累计逐位)" | ks_gain_ok | 钥石逐杀入账:首杀档位+累计前缀逐位一致。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 462 | 247 | "点选恰兑付选中一件(值=令牌档位表值;未选项零生效)" | resolve_ok | 点选恰兑付选中一件且值=表值(未选项零生效)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 463 | 258 | "各战上限入参 ∈ [基础, 基础+3×件值+购血余量](覆盖值通道——双渠道共享上限 3;模拟档贪心购血合法叠加〔数值修复批 v3.4〕;首战=基础 %d)" ↩ % exp_base | seq_ok | 各战敌人上限入参落在[基础, 基础+件值+购血余量]窗口(覆盖值通道)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 464 | 261 | "同种子两局逐位可复现(summary/上限序列/行进序列/三选一载荷——reward_pick 封闭流)" | st2["summary"] == sum and st2["encounter_max_hp_seq"] == seq and st2["visited_ids"] == st["visited_ids"] and st2["reward_events"] == st["reward_events"] | 同种子两局全量逐位可复现(摘要/上限序列/行进/三选一载荷)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 465 | 271 | "零配 L1 败北种子可达(seed=%s)" ↩ % str(lose.get("s", -1)) | not lose.is_empty() | 零配 L1 败北种子可达(败北线存在性)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 466 | 274 | "L1 败北摘要:未打穿 · 通关 0 关 · 钥石 0 · 止步第 1 层" | not bool(sl["cleared"]) and int(sl["levels_cleared"]) == 0 \\ and int(sl["keystone_total"]) == 0 and int(sl["stopped_at"]) == 1 | L1 败北摘要:未打穿、0 关、0 钥石、止步第 1 层。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 467 | 278 | "L1 败北败退明细:层号/节点编号/节点类型在案(数值验证取数面 2026-09-22)" | int(dl.get("level_index", -1)) == 1 and int(dl.get("node_id", -1)) >= 0 \\ and String(dl.get("node_type", "")) in ["battle", "boss"] | L1 败北明细:层号/节点编号/节点类型在案(数值验证取数面)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 468 | 287 | "半配(atk+50)L2 败北种子可达(seed=%s)" ↩ % str(half.get("s", -1)) | not half.is_empty() | 半配 L2 败北种子可达。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 469 | 290 | "L2 败北摘要:L1 已过(钥石=L1 首杀掉落)· 止步第 2 层" | not bool(sh["cleared"]) and int(sh["levels_cleared"]) == 1 \\ and int(sh["keystone_total"]) == int(_db.meta_boss_keystone_row(1).get("first_kill", 0)) \\ and int(sh["stopped_at"]) == 2 | L2 败北摘要:已过 L1(钥石=L1 首杀)且止步第 2 层。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 470 | 295 | "L2 败北败退明细:层号=2 · 节点在案(数值验证取数面 2026-09-22)" | int(dh.get("level_index", -1)) == 2 and int(dh.get("node_id", -1)) >= 0 \\ and String(dh.get("node_type", "")) in ["battle", "boss"] | L2 败北明细:层号=2、节点在案。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 471 | 301 | "重复拒绝场景:冒险启动(seed 7——生成图确定同上)" | adr6.start_adventure(7) | (待填) | | | -| 472 | 309 | "重复拒绝场景就位:选路态放弃 → 已结算" | adr6.abandon() | 选路态放弃→已结算(场景推进到可重复宣布态)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 473 | 312 | "重复关卡结束宣布拒绝(账本与摘要零变化——契约 7)" | adr6.summary == sum6_before | 重复关卡结束宣布被拒:摘要零变化(契约 7)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 474 | 322 | "每战完成入账恰一次(gain %d = battles %d——GP 袋件入账单列)" ↩ % [gains, int(st["battles"])] | gains == int(st["battles"]) | 每战完成 GP 入账恰一次(三选一 GP 袋件入账单列不算)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 475 | 323 | "钱包 clear 恰一次且为末事件(关卡收束不清——GP 冒险收束清零归冒险结构)" | clears == 1 and wallet.size() > 0 and String(wallet[wallet.size() - 1]["event"]) == "clear" | 钱包 clear 事件恰一次且为末事件(关卡收束不清账)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 476 | 325 | "三关商店房各到达一次(每关入口→终点路径恰穿一店的驱动投影)且首达余额 > 0(%d——GP 跨关延续)" ↩ % (shop_seq[0] if shop_seq.size() > 0 else -1) | shop_seq.size() == 3 and int(shop_seq[0]) > 0 | (待填) | | | -| 477 | 336 | "同账场景种子可达(seed=%s——L2 商店房就位)" ↩ % str(same.get("s", -1)) | not same.is_empty() | 同账场景种子可达(L2 商店房就位)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 478 | 349 | "进店余额 ≥ hp_up L2 首价(%d = 基价 %d + 关卡加成 %d——配置推导)" ↩ % [hp_price + l2_price_bonus, hp_price, l2_price_bonus] | gp0 >= hp_price + l2_price_bonus | 进店余额≥hp_up 该关首价(基价+关卡加成,配置推导)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 479 | 350 | "购买 hp_up 受理(出账 + 加算区)" | r8.buy_product("hp_up") | 购买 hp_up 受理(出账+进加算区)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 480 | 352 | "统一冒险加算区含商店值(max_hp=%d ≥ 商店 3——L1 三选一件值另计)" ↩ % acc_shop | acc_shop >= 3 | 统一冒险加算区含商店购得值(同账)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 481 | 355 | "L2 通关三选一停点在案(直调兑付前——零自动推进)" | not a8._curio_pending.is_empty() | L2 通关后三选一停点在案(零自动推进)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 482 | 356 | "未兑付推进拒绝(选择停点零自动推进契约)" | not a8.continue_to_next_level() | 未兑付时推进被拒(选择停点零自动推进契约)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 483 | 359 | "三选一点选兑付受理(候选取首件)" | a8.pick_curio(String(l2_pick.get("kind", "")), String(l2_pick.get("tier", "")), String(l2_pick.get("feat_id", ""))) | 三选一点选兑付受理。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 484 | 362 | "三选一属性件与商店购买同账累加(max_hp=%d = %d + 三选一%s)" ↩ % [int(a8.run_ledger.bonus.get("max_hp", -1)), acc_shop, picked2] | int(a8.run_ledger.bonus.get("max_hp", -1)) == expect_acc | 三选一属性件与商店购买同账累加(同一本账)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 485 | 364 | "通关部分回血封顶(hp %d ≤ 生效上限 %d——血回满语义退役)" ↩ % [int(a8.ledger["hero_hp"]), max_eff8] | int(a8.ledger["hero_hp"]) <= max_eff8 | 通关回血后血量不越生效上限(封顶口径)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 486 | 365 | "兑付后推进受理(下一关开始)" | a8.continue_to_next_level() | 兑付后推进受理(下一关开始)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 487 | 368 | "恢复点数学:floor(100×30%)=30 · 封顶缺口 5 · 满血回 0 合法" | int(a9.apply_rest(30, 70, 100)) == 30 and int(a9.apply_rest(30, 95, 100)) == 5 \\ and int(a9.apply_rest(30, 100, 100)) == 0 | 恢复点数学三读数:30% 回复/封顶缺口/满血回 0。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 488 | 372 | "同一公式两个落点逐位一致(LevelRunner.rest_heal_amount 内置默认)" | int(lr9.rest_heal_amount(30, 70, 100)) == 30 and int(lr9.rest_heal_amount(30, 95, 100)) == 5 | 同一恢复公式在两个落点逐位一致。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 489 | 376 | "放弃场景:冒险启动(seed 15——生成图确定同上)" | a10.start_adventure(15) | (待填) | | | -| 490 | 377 | "放弃场景:入口停点 pick 进首战(停点前移批 2026-09-23 驱动口径)" | a10.runner().pick_next_node(int(a10.runner().next_candidate_ids()[0])) | 入口停点点选进首战。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 491 | 378 | "战斗中 abandon 拒绝(战斗域单事务在飞)" | not a10.abandon() | 战斗中放弃被拒。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 492 | 385 | "非战斗期(选路态)abandon 受理" | a10.runner().current_battle() == null and a10.abandon() | 非战斗期放弃受理(选路态)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 493 | 389 | "剔牌库/替换场景:冒险启动(seed 11——道具解锁集只含 wide_scout_band)" | a11.start_adventure(11, {"treasure_unlocked": {"wide_scout_band": true}}) | (待填) | | | -| 494 | 398 | "池耗即枯竭(attack/ap 令牌耗尽不出候选——%s)" ↩ % str(draw) | not draw_kinds.has("attack") and not draw_kinds.has("ap") | attack/ap 令牌耗尽后候选零出现(池耗即枯竭)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 495 | 399 | "池耗后候选恒 ≥2(稀有/罕见档余量——防「三选项全不可选」死角)" | (draw as Array).size() >= 2 | 池耗后候选仍≥2(防三选项全不可选死角)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 496 | 408 | "池收缩——40 次重抽零 aux 候选(停点渠道 aux 卡退役,迭代 11 第①批 T7)" | not treasure_seen | 40 次重抽零 aux 候选(停点渠道 aux 卡退役)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 497 | 417 | "满血通关回血 0 合法(封顶口径——heal_gained=0 非负溢出)" | int(cap["payload"].get("heal_gained", -1)) == 0 | 满血通关回血 0 合法(封顶口径非负溢出)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 498 | 420 | "节点共享池场景:冒险启动(seed 13)" | a13.start_adventure(13, {"treasure_unlocked": {"wide_scout_band": true}}) | (待填) | | | -| 499 | 431 | "奖励节点共享池:令牌耗尽同样不出候选(同库同池——%s)" ↩ % str(ncands13) | not has_attack13 | 令牌耗尽后奖励节点同样零出候选(同库同池)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 500 | 435 | "奖励节点 GP 件入账(钱包「奖励入账」收口——+%d)" ↩ % int(_db.adventure.curio_value("gp", "common")) | a13.apply_node_curio("gp", "common") and int(a13.ledger.get("gp", 0)) == gp13 + int(_db.adventure.curio_value("gp", "common")) | 奖励节点 GP 件入账走钱包收口。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 501 | 436 | "节点停点态兑付后清零(恰一次语义同构)" | a13._node_curio_pending.is_empty() | 节点停点态兑付后清零(恰一次语义)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 502 | 438 | "放弃摘要:abandoned · 未打穿(已获大奖励不追回——同失败型结算)" | String(s10.get("result", "")) == "abandoned" and not bool(s10.get("cleared", true)) | 放弃摘要:abandoned、未打穿(已获不追回)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | - -## S69 _smoke_feats(行 449–761 · 68 条 · 源 game/tests/unit/core/adventure_feats_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 503 | 449 | "feat_table 已加载(30 件=基础 27+迁入 2+卡② 完美保险 1——迭代 31 卡② 换代)" | _db.feats != null and (_db.feats.rows as Array).size() == 30 | 专长表已加载且恰 30 行(基础 27+迁入 2+完美保险 1)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 504 | 450 | "feat_table.validate 零违例(四级 rarity/cap 域/trigger 词表/行键白名单全过)" | _db.feats.validate().is_empty() | 专长表自校验零违例(四级稀有度/上限域/触发词表/行键白名单)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 505 | 452 | "专长带场景:冒险启动(seed 71)" | a_r0.start_adventure(71) | (待填) | | | -| 506 | 460 | "专长带入池:30 件四级直映射(普通 12/优秀 9/稀有 9/传说 0)——牌库 102+30=132〔专长批 V2+卡② 完美保险迁入〕" | feat_tokens == 30 and int(tier_counts.get("common", 0)) == 12 and int(tier_counts.get("excellent", 0)) == 9 and int(tier_counts.get("rare", 0)) == 9 and a_r0._curio_deck.size() == 132 | 30 件专长以 relic 令牌入池且档位直映射(12/9/9/0),牌库 102+30=132。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 507 | 462 | "旧 27 件已退役:arcane_ember/magma_body 表内零残留〔专长批 V2〕" | _db.feats.row_by_id("arcane_ember").is_empty() and _db.feats.row_by_id("magma_body").is_empty() | 旧 27 件代表行(arcane_ember/magma_body)表内零残留(旧代已退役)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 508 | 463 | "迁出收尾:treasure_items 无 venom_edge/seal_burst 行(同调目录自然收窄)〔专长批 V13〕" | _db.treasure_items.row_by_id("venom_edge").is_empty() and _db.treasure_items.row_by_id("seal_burst").is_empty() | 迁出收尾:秘宝表无 venom_edge/seal_burst 行(同调目录收窄)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 509 | 470 | "层级纪律:牌库 kind 全集=CURIO_KINDS+relic(秘宝 item_id 零入池)〔专长批 V18〕" | kinds_ok | 层级纪律:牌库 kind 全集=表内 CURIO_KINDS+relic(秘宝 id 零入池)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 510 | 476 | "棋盘事实不可变:新 30 行零棋盘改写类订阅(rewrite_board 已除名消费面)〔专长批 V19〕" | rewrite_rows == 0 | 30 行触发器零棋盘改写类订阅(rewrite_board 已除名消费面)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 511 | 483 | "对外命名纪律:30 行 display_name/desc 零「雷/旗」字面(封印/陷阱风味系)〔专长批 V21 初筛〕" | bad_words == 0 | 30 行显示名与描述零「雷/旗」字面(对外命名纪律初筛)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 512 | 484 | "数值纪律:陷阱爆发倍率>1 · 血蜜回复<典型陷阱伤 · 石肤上限 3 在册(结构锚形态内 provisional)〔专长批 V22 机检半〕" | float((_db.feats.row_by_id("trap_burst").get("triggers", [{}])[0] as Dictionary).get("do", {}).get("mult", 0)) > 1.0 and int((_db.feats.row_by_id("blood_honey").get("triggers", [{}])[0] as Dictionary).get("do", {}).get("value", 99)) < 5 and int((_db.feats.row_by_id("stone_skin").get("triggers", [{}])[0] as Dictionary).get("do", {}).get("cap", 0)) == 3 | 数值纪律锚:陷阱爆发倍率>1、血蜜回复<典型陷阱伤、石肤上限 3。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 513 | 502 | "新 schema 构造表 validate 零违例(四级 rarity/cap 字段/词表全过——流派标签已退役零 archetype)" | rt.validate().is_empty() | 新 schema 手搓 5 行临时表自校验零违例(流派标签已退役零 archetype)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 514 | 506 | "专长带场景:冒险启动(seed 72——注入 5 行临时表)" | a_r.start_adventure(72) | (待填) | | | -| 515 | 511 | "专长带入池:5 行 relic 每件 1 张(四级 rarity 直映射)——牌库 102+5=107" | deck_relic == 5 and a_r._curio_deck.size() == 107 | 5 行 relic 每件 1 张入池,牌库 102+5=107。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 516 | 522 | "通关渠道只出专长(60 次重抽 ≥2 种不同 relic——身份键 feat_id 同档可多件)" | feat_seen.size() >= 2 | 通关渠道 60 次重抽出≥2 种专长(只出专长)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 517 | 523 | "节点渠道零专长(60 次重抽零 relic——专长在非 Boss 渠道只以专长凭证形态出现)" | not node_relic_seen | 节点渠道 60 次重抽零专长(专长在非 Boss 渠道只以凭证形态出现)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 518 | 531 | "A5 专长凭证兑付受理(节点渠道——拿到即开)" | a_t.apply_node_curio("feat_ticket", "excellent") | 专长凭证节点兑付受理(拿到即开)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 519 | 537 | "A5 拿到即开——停点候选已替换为专长三选一(全 relic)" | tkt_all_relic | 凭证兑付后停点候选已替换为全 relic 专长三选一。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 520 | 538 | "A5 零摘牌——专长凭证兑付不动令牌库(不进持有集)" | a_t.pool_stock("attack") == int(stock_before["attack"]) and a_t.pool_stock("gp") == int(stock_before["gp"]) | 凭证兑付零摘牌:令牌库库存不动(凭证不进持有集)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 521 | 539 | "A5 无专长凭证残留持有集" | not (a_t.feats_held() as Array).has("feat_ticket") | 凭证本体不在持有集残留。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 522 | 542 | "A5 专长面兑付受理(专长凭证开出候选可正常入持有集)" | (a_t.feats_held() as Array).size() == feats_n + 1 | 凭证开出候选可正常兑付入持有集(计数+1)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 523 | 547 | "A6 保底触发(pity=5 → 第 6 次展示必出——概率无关)" | a_t._feat_ticket_consider(rg_t) | 保底 pity 满步时下 consider 必触发(概率无关)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 524 | 548 | "A6 触发后清零" | a_t._feat_ticket_pity == 0 | 保底触发后 pity 清零。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 525 | 551 | "A6 限购满不再触发(保底冻结)" | not a_t._feat_ticket_consider(rg_t) | 限购满后保底冻结不再触发。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 526 | 559 | "A6 概率面存在(chance=20% 200 展示内必出现——零触发=概率面失守)" | offered | 概率面存在:20% 概率 200 次展示内必出现(零触发=概率面失守)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 527 | 561 | "relic 兑付:入持有集 + 牌库摘牌(选中踢池——池库存 %d→%d)" ↩ % [deck_relic, deck_relic - 1] | a_r.pick_curio("relic", "rare", "x3") and (a_r.feats_held() as Array).has("x3") and a_r.pool_stock("relic") == deck_relic - 1 | relic 兑付:入持有集+牌库摘牌(选中踢池)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 528 | 585 | "双管镐:第 %d 次付费挖格返 1 AP(每第 2 次——mod_eq 预评估口径)" ↩ % (i + 1) | refund | 双管镐:每第 2 次付费挖格返 1 AP(mod_eq 预评估口径)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 529 | 587 | "双管镐反例:第 %d 次不返(未满 2 次)" ↩ % (i + 1) | not refund | 双管镐反例:未满 2 次不返 AP。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 530 | 591 | "地质学家:首开新类型集 1 点〔专长批 V3〕" | orch._hits_of(geo_hits, "counter_add").size() == 1 | 地质学家:首开新资源类型积 1 点。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 531 | 595 | "地质学家:满 4 种(预评估现值 3+本格)→揭示 1 格·计数清零〔专长批 V3〕" | orch._hits_of(geo_hits2, "reveal_tiles").size() == 1 and EffectEngine.counter(orch.counters_ctx, "geo_types") == 0 | 地质学家满 4 种:揭示 1 格且计数清零。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 532 | 597 | "地质学家反例:同类型重复首开不叠点〔专长批 V3〕" | orch._hits_of(geo_hits3, "counter_add").is_empty() | 地质学家反例:同类型重复首开不叠点。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 533 | 601 | "安全帽:开安全格攒层 ×2〔专长批 V3〕" | EffectEngine.counter(orch.counters_ctx, "helm_layers") == 2 | 安全帽:开安全格攒层 ×2。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 534 | 607 | "安全帽:踩雷耗层减伤=层值 2(一次清零随施加面 set 行)〔专长批 V3〕" | hh_reduce == 2 | 安全帽:踩雷耗层减伤=层值 2(一次清零)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 535 | 611 | "跳格采掘:空白 0 格攒 1 点〔专长批 V3〕" | orch._hits_of(sk_zero, "counter_add").size() == 1 | 跳格采掘:空白 0 格攒 1 点。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 536 | 615 | "跳格采掘:满 3 点→揭示 1 格〔专长批 V3〕" | orch._hits_of(sk_full, "reveal_tiles").size() == 1 | 跳格采掘满 3 点:揭示 1 格。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 537 | 616 | "跳格采掘反例:普通开格不计点〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_reveal", {"is_blank_zero": false}), "counter_add").is_empty() | 跳格采掘反例:普通开格不计点。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 538 | 623 | "爆破会计:本轮挖格花费≥5→+1 GP〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "round_end", {}), "grant_gp").size() == 1 | 爆破会计:本轮挖格花费≥5 轮末+1 GP。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 539 | 625 | "边陲矿工:边缘格产出 +1·中心格反例零产出〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_reveal", {"is_edge_cell": true}), "grant_material").size() == 1 and orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_reveal", {"is_edge_cell": false}), "grant_material").is_empty() | 边陲矿工:边缘格产出材料、中心格反例零产出。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 540 | 633 | "封印回响:标中计数≥2→轮末返 1 AP(下轮 queue)〔专长批 V3〕" | orch._hits_of(se_hits, "grant_ap").size() == 1 and String((orch._hits_of(se_hits, "grant_ap")[0] as Dictionary).get("mode", "")) == "queue" | 封印回响:标中计数≥2 轮末返 1 AP(queue 模式)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 541 | 642 | "封印匠:标中每第 3 次→中立移除恰一次(频率闸死)〔专长批 V3〕" | sm_remove == 1 | 封印匠:标中每第 3 次中立移除恰一次(频率闸死)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 542 | 644 | "封印桩:标中本轮 AC +N(round 域——round_start 清零)〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "ac_bonus").size() == 1 and String((orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "ac_bonus")[0] as Dictionary).get("mode", "")) == "round" | 封印桩:标中本轮 AC 加值(round 域,轮始清零)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 543 | 648 | "示警烟火:标中揭示 3×3 资源类型(不揭安危位置)〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "preview_area").size() == 1 | 示警烟火:标中揭示 3×3 资源类型(不揭安危位置)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 544 | 657 | "疼痛记忆:攻击附加层值伤害=层值 2·用后清零(set 行随施加面)〔专长批 V3〕" | pm_bonus == 2 | 疼痛记忆:攻击附加层值伤害且用后清零。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 545 | 664 | "保险丝反例:单次 <5 不攒点不触发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "damage_taken", {"kind": "terrain", "amount": 3}), "shield").is_empty() | 保险丝反例:单次受伤<5 不攒点不触发。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 546 | 668 | "保险丝:首点不出盾(2 点才出——预评估口径)〔专长批 V3〕" | fw_shield == 0 | 保险丝首点不出盾(2 点才出,预评估口径)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 547 | 670 | "见血开窍:踩雷揭示 1 格〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {}), "reveal_tiles").size() == 1 | 见血开窍:踩雷揭示 1 格。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 548 | 677 | "陷阱本能:踩雷本轮攻击 +2(attack_bonus 通道——面板加算)〔专长批 V3〕" | ti_val == 2 | 陷阱本能:踩雷本轮攻击 +2(attack_bonus 通道)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 549 | 682 | "连环陷阱:同轮第 2 颗起每颗额外 +1 层(疼痛记忆同账)〔专长批 V3〕" | EffectEngine.counter(orch.counters_ctx, "pain_layers") == ct_before + 2 | 连环陷阱:同轮第 2 颗起每颗额外 +1 层(与疼痛记忆同账)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 550 | 685 | "陷阱爆发:全体敌雷伤×倍率直伤(blast_scaled——施加面值=damage×mult)〔专长批 V3〕" | orch._hits_of(tb_hits, "blast_scaled").size() == 1 and float((orch._hits_of(tb_hits, "blast_scaled")[0] as Dictionary).get("mult", 0)) > 1.0 | 陷阱爆发:全体敌雷伤×倍率直伤(blast_scaled 通道)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 551 | 689 | "血蜜:踩雷回复 N 生命〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {}), "heal").size() == 1 | 血蜜:踩雷回复 N 生命。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 552 | 692 | "石肤:受伤 AC+1(本场域 cap 3——达上限不再涨由累积器封顶)〔专长批 V3〕" | orch._hits_of(ss_hits, "ac_bonus").size() == 1 and int((orch._hits_of(ss_hits, "ac_bonus")[0] as Dictionary).get("cap", 0)) == 3 | 石肤:受伤 AC+1(本场域 cap 3,封顶归累积器)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 553 | 696 | "背水:生命≤50% 减伤·过半血反例零减伤〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "damage_taken", {"kind": "enemy_skill", "amount": 5, "source": "s", "round": 1, "hp_ratio": 40}), "damage_reduce").size() == 1 and orch._hits_of(fx.dispatch(orch.counters_ctx, "damage_taken", {"kind": "enemy_skill", "amount": 5, "source": "s", "round": 2, "hp_ratio": 80}), "damage_reduce").is_empty() | 背水:生命≤50% 减伤触发、过半血反例零减伤。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 554 | 701 | "破封一击:挖开带封印危险格 +1 层·无封印反例零触发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {"dug_under_seal": true}), "counter_add").size() == 1 and orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {"dug_under_seal": false}), "counter_add").is_empty() | 破封一击:挖开带封印危险格 +1 层、无封印反例零触发。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 555 | 709 | "弃图兑换:停手剩余 AP≥3 入账〔专长批 V3〕" | orch._hits_of(me_hits, "counter_add").size() == 1 | 弃图兑换:停手剩余 AP≥3 入账。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 556 | 713 | "弃图兑换:折为攻击伤害=剩余 AP 全额·用后清零〔专长批 V3〕" | me_atk == 4 | 弃图兑换:折为攻击伤害=剩余 AP 全额且用后清零。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 557 | 714 | "弃图兑换反例:剩余 AP<3 不触发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_stop", {"ap_left": 2}), "counter_add").is_empty() | 弃图兑换反例:剩余 AP<3 不触发。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 558 | 717 | "一滴不剩:上轮 AP 耗尽→+1 AP〔专长批 V3〕" | orch._hits_of(ld_hits, "grant_ap").size() == 1 | 一滴不剩:上轮 AP 耗尽→本轮 +1 AP。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 559 | 718 | "一滴不剩反例:上轮有余不发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "round_start", {"round": 3, "ap_exhausted_last": false}), "grant_ap").is_empty() | 一滴不剩反例:上轮有余不发。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 560 | 724 | "收盘:本轮开格≥8→+1 GP〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "round_end", {}), "grant_gp").size() == 1 | 收盘:本轮开格≥8 轮末+1 GP。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 561 | 727 | "斩首学者:击杀揭示 1 格〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "kill", {"target": "敌", "overflow": 0}), "reveal_tiles").size() == 1 | 斩首学者:击杀揭示 1 格。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 562 | 730 | "处刑台:敌血量≤30% 挂单位标记加伤·阈值外反例零命中〔专长批 V3〕" | orch._hits_of(ex_hits, "target_attack_bonus").size() == 1 and orch._hits_of(fx.dispatch(orch.counters_ctx, "enemy_hp_threshold", {"unit": "e1", "pct": 60, "is_boss": false}), "target_attack_bonus").is_empty() | 处刑台:敌血量≤30% 挂标记加伤、阈值外反例零命中。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 563 | 734 | "战地包扎:战斗胜利回复 N 生命(battle_won 新挂点)〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "battle_won", {"round": 2}), "heal").size() == 1 | 战地包扎:战斗胜利回复 N 生命(battle_won 挂点)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 564 | 737 | "血税:双向同局账本(踩陷阱−2max_hp/封印命中+1atk——bonus_delta 战末汇入)〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {}), "bonus_delta").size() == 1 and String((orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "bonus_delta")[0] as Dictionary).get("axis", "")) == "atk" | 血税:双向同局账本(踩陷阱减 max_hp/封印命中加 atk,战末汇入)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 565 | 742 | "淬毒刃纹(迁入):克制正面(magic_vs_heavy 矩阵值>0)追加·中性面反例零触发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "clamp_check", {"matchup": "magic_vs_heavy"}), "modify_ticket").size() == 1 and orch._hits_of(fx.dispatch(orch.counters_ctx, "clamp_check", {"matchup": "normal_vs_light"}), "modify_ticket").is_empty() | 淬毒刃纹(迁入):克制正面追加效果、中性面反例零触发。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 566 | 746 | "封印爆裂(迁入):标中全体敌群伤〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "open_ticket").size() == 1 | 封印爆裂(迁入):标中全体敌群伤。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 567 | 750 | "零持有订阅=零命中(relic 关着零扰动——伤害管线既有行为逐位不变)" | orch0._fx_emit("attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 0}).is_empty() and orch0._fx_emit("dig_reveal", {"cell": Vector2i(1, 1), "round": 1, "is_first_dig_of_battle": true, "terrain_kind": []}).is_empty() and orch0._fx_emit("mine_hit", {"damage": 5}).is_empty() and orch0._fx_emit("battle_won", {"round": 1}).is_empty() | 零持有订阅零命中(relic 关着零扰动,伤害管线既有行为不变)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 568 | 758 | "特性段空态隐藏(视图空态纪律同特性段)" | not panel._feat_section.visible | 特性段空态隐藏(视图空态纪律)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 569 | 760 | "特性段持有呈现(逐件行常驻可读——「局内奖励选了什么看不到」缺口承载)" | panel._feat_section.visible and panel._feat_list.get_child_count() == 2 | 特性段持有呈现:逐件行常驻可读。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | -| 570 | 761 | "特性卡文案表值化:desc 行字段直读(零硬编码映射)" | String(load("res://presentation/battle_narrator.gd").feat_card_text({"feat_id": "pain_memory", "display_name": "疼痛记忆", "desc": "触陷阱攒 1 层"})).contains("触陷阱攒 1 层") | 特性卡文案表值化:desc 字段直读零硬编码映射。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | - -## S70 _smoke_layout_cell_formula(行 65–68 · 2 条 · 源 game/tests/unit/core/board_gen_reward_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 571 | 65 | "L%d %dx%d 板网格高 %d ≤ 可用高 %d − 预留 24(格边 %dpx——下缘不贴边不裁切)" ↩ % [L, b.width, b.height, grid_h, int(avail.y), int(cell)] | grid_h <= int(avail.y) - 24 | 网格实高(含格间距)≤ 可用高−24 预留(下缘不贴边不裁切——真人试玩 bug⑥ 回归)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 572 | 67 | "L%d 格边在 26–46 档内(%dpx)" ↩ % [L, int(cell)] | cell >= 26.0 and cell <= 46.0 | 格边长在 26–46px 档内。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | - -## S71 _smoke_reward_identity_unit(行 83–116 · 10 条 · 源 game/tests/unit/core/board_gen_reward_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 573 | 83 | "roll_board 受理" | reward_economy.roll_board(eligible) | 资源格摇号 roll_board 受理。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 574 | 84 | "重复 roll_board 被拒(恰一次——double-roll 守卫)" | not reward_economy.roll_board(eligible) | 重复 roll_board 被拒(恰一次守卫)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 575 | 95 | "query_identity 投影 = {reward_id, cell_type, tier} 且五型封闭(样本 %s)" ↩ % str(types_seen.keys()) | types_ok | 身份查询投影={reward_id,cell_type,tier} 且五型封闭。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 576 | 97 | "清单外坐标查询 = null(封锁格无身份同口径)" | reward_economy.query_identity(Vector2i(99, 99)) == null | 清单外坐标身份查询=null(封锁格无身份)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 577 | 101 | "同格重复 grant 幂等(同一快照——受理恰一次)" | JSON.stringify(snap1) == JSON.stringify(snap2) | 同格重复 grant 幂等(同一快照)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 578 | 102 | "清单外 grant 拒绝" | reward_economy.grant(Vector2i(99, 99)) == null | 清单外坐标 grant 拒绝。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 579 | 106 | "清图批尾身份 = clear_blast(clear_default 唯一活行)" | String(cs1.cell_type) == "blast" and String(cs1.reward_id) == "clear_blast" | 清图批尾身份=clear_blast(clear_default 唯一活行)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 580 | 111 | "清图奖励 power ∈ 表区间 [%d, %d](roll 值 %d——配置推导)" ↩ % [cb_range[0], cb_range[1], int(cs1.power)] | int(cs1.power) >= int(cb_range[0]) and int(cs1.power) <= int(cb_range[1]) | 清图奖励 power 落在表区间(配置推导)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 581 | 113 | "grant_clear 同批次重调幂等" | JSON.stringify(cs1) == JSON.stringify(cs2) | grant_clear 同批次重调幂等。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 582 | 116 | "映射退役后 grant 拒绝(生命周期闭合)" | reward_economy.grant(eligible[0].cell) == null | 映射退役后 grant 拒绝(生命周期闭合)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 430 | 22 | "前置:表加载零 error" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | -## S72 _smoke_board_generator(行 135–210 · 24 条 · 源 game/tests/unit/core/board_gen_reward_test.gd · 去向:待分类回填) +## S66 _test_damage_controller_vocabularies(行 43–57 · 4 条 · 源 game/tests/unit/contracts/system_contracts_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 583 | 135 | "BoardGen 行解析 (%d,0)" ↩ % L | not row.is_empty() | 棋盘生成行解析 (L,0) 非空。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 584 | 141 | "BoardGen 精确命中 (2,3)(覆盖批键域补齐)" | int(exact_row.get("node_depth", -1)) == 3 | (待填) | | | -| 585 | 151 | "R1 ceil 能力不失(夹具摘除 (2,3)→向上取整到 4·偏难口径)" | int(ceil_row.get("node_depth", -1)) == 4 | (待填) | | | -| 586 | 153 | "夹具表原样还原(行数与解析回到覆盖批现值)" | _db.board_gen.rows.size() == rows_before and int(_db.resolve_board_gen(2, 3).get("node_depth", -1)) == 3 | (待填) | | | -| 587 | 156 | "L%d 生成产物非空" ↩ % L | b1 != null | 生成产物非空。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 588 | 159 | "L%d clearable(安全格 8 邻连通)" ↩ % L | board_generator.is_clearable(b1) | 可清性成立(安全格 8 邻连通)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 589 | 160 | "L%d legality 全过" ↩ % L | board_generator.legality(b1, row, _db) | 合法性全过。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 590 | 167 | "L%d inferability %.3f %s target %.2f(accept_mode=%s)" ↩ % [L, inf, "≥" if inf >= float(row.inferability_target) else "<", float(row.inferability_target), mode] | inf >= float(row.inferability_target) or mode == "accept_best" | 出图验收:首过≥可推理目标或诚实降级 accept_best。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 591 | 169 | "L%d 生成器产物零变体格(变异布置归战斗初始化第 4 步——生成器管线不布置,2026-09-19 用户拍板棋盘设置顺序)" ↩ % L | b1.variants.is_empty() | 生成器产物零变体格(变异布置归战斗初始化第 4 步)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 592 | 170 | "L%d 指标集输出降级标记与目标雷数(三分出口表)" ↩ % L | b1.metrics.has("degraded_product") and b1.metrics.has("target_mines") | 指标集输出降级标记与目标雷数。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 593 | 171 | "L%d 降级产物标记 = (accept_mode==accept_best)(%s→%s)" ↩ % [L, mode, str(b1.degraded_product)] | b1.degraded_product == (mode == "accept_best") | 降级标记=(accept_mode==accept_best)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 594 | 173 | "L%d 非法产物标记 = (accept_mode==accept_worst)(%s→%s)" ↩ % [L, mode, str(b1.illegal_product)] | b1.illegal_product == (mode == "accept_worst") | 非法产物标记=(accept_mode==accept_worst)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 595 | 175 | "L%d 降级/非法产物标记互斥(两出口不可同真)" ↩ % L | not b1.degraded_product or not b1.illegal_product | 降级/非法产物标记互斥。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 596 | 177 | "L%d 产物断言:实际雷数 %d ≤ 目标雷数 N=%d(先布后删只减不增)" ↩ % [L, int(b1.metrics.actual_mines), int(b1.metrics.target_mines)] | int(b1.metrics.actual_mines) <= int(b1.metrics.target_mines) | 实际雷数≤目标雷数(先布后删只减不增)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 597 | 179 | "L%d attempts_used ∈ [1,%d]" ↩ % [L, int(row.max_gen_attempts)] | b1.metrics.get("attempts_used", 99) >= 1 and b1.metrics.get("attempts_used", 99) <= int(row.max_gen_attempts) | 尝试次数在 [1,上限] 域内。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 598 | 181 | "L%d 非法产物标记 = false" ↩ % L | not b1.illegal_product | 非法产物标记=false(正常出图)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 599 | 185 | "L%d 同种子两次生成逐格相同" ↩ % L | _board_dump(b2) == _board_dump(b3) | 同种子两次生成逐格相同(确定性)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 600 | 187 | "L%d 异种子产物不同(退化护栏)" ↩ % L | _board_dump(b2) != _board_dump(b4) | 异种子产物不同(退化护栏)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 601 | 193 | "L%d r0_max_cap 正式行投值在位(%d ≥ 出生保障面 9——板面 30%% 锚)" ↩ % [L, cap_val] | cap_val >= BoardData.SPAWN_AREA_CELLS | r0 上限正式行投值在位(≥出生保障面 9)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 602 | 195 | "L%d 结构断言 R₀=%d ≤ cap=%d(R₀≤cap 重掷闸承载)" ↩ % [L, int(b1.metrics.r0_count), cap_val] | int(b1.metrics.r0_count) <= cap_val | 结构断言:出生展开数≤cap(重掷闸承载)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 603 | 197 | "L%d r0_cap_rejected 尝试事实指标在位(本图 %d 次触发)" ↩ % [L, int(b1.metrics.get("r0_cap_rejected", -1))] | int(b1.metrics.get("r0_cap_rejected", -1)) >= 0 | cap 拒绝计数指标在位。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 604 | 199 | "L%d ring_removed=%d ≤ 剃雷域候选危险格数((3+2r)²−9——全域剃雷退役)" ↩ % [L, int(b1.metrics.get("ring_removed", 99))] | int(b1.metrics.get("ring_removed", 99)) <= (2 + int(row.get("shave_ring_radius", 1))) * (2 + int(row.get("shave_ring_radius", 1))) * 4 - 5 | 剃雷量≤域内候选危险格数(全域剃雷退役)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 605 | 207 | "L1 cap 压线 9:出图 R₀=%d %s(三分出口诚实降级——超阈即 accept_worst 非法标记,玩家永不卡死)" ↩ % [int(b9.metrics.r0_count), "≤9" if int(b9.metrics.r0_count) <= BoardData.SPAWN_AREA_CELLS else ">9→illegal_product"] | b9 != null and (int(b9.metrics.r0_count) <= BoardData.SPAWN_AREA_CELLS or b9.illegal_product) | cap 压线 9:出图 R₀≤9 或诚实降级非法标记(玩家永不卡死)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 606 | 209 | "L1 cap 压线出图指标集完整(accept_mode=%s / 闸触发 %d 次)" ↩ % [String(b9.metrics.get("accept_mode", "")), int(b9.metrics.get("r0_cap_rejected", 0))] | int(b9.metrics.get("r0_cap_rejected", 0)) >= 0 and String(b9.metrics.get("accept_mode", "")) != "" | cap 压线出图指标集完整(accept_mode+闸触发数)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 431 | 43 | "五配方常量非空(开单面唯一分派键)" | nonempty | (待填) | | | +| 432 | 47 | "五配方常量互异(词表封闭无别名)" | unique_kinds.size() == kinds.size() | (待填) | | | +| 433 | 55 | "三目标规则常量互异(结账时刻现场解析词表)" | unique_targets.size() == targets.size() | (待填) | | | +| 434 | 56 | "双结账政策常量互异(A 即结 / B 攒批回切位)" | String(DamageControllerScript.POLICY_IMMEDIATE) != String(DamageControllerScript.POLICY_SEGMENT) | (待填) | | | -## S73 _smoke_power_interval(行 240–298 · 5 条 · 源 game/tests/unit/core/board_gen_reward_test.gd · 去向:待分类回填) +## S67 _test_rng_tag_closure(行 64–67 · 2 条 · 源 game/tests/unit/contracts/system_contracts_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 607 | 240 | "区间行配对齐全且 power = floor 中点(配置推导 %d 行:%s)" ↩ % [interval_rows.size(), str(interval_rows.keys())] | anchors_ok and interval_rows.size() >= 1 | 区间行配对齐全且 power=floor 中点(配置推导)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 608 | 282 | "24 板全区间行快照 power ∈ [power_min, power_max](含清图槽)" | in_domain | 24 板全区间行快照 power 在区间内(含清图槽)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 609 | 283 | "定值行快照 power 恒等表值——区间批零外溢" | fixed_ok | 定值行快照 power 恒等表值(区间批零外溢)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 610 | 297 | "区间行上下阶实际流动(≥2 样本的区间行须 ≥2 个不同 roll 值)" | variance_ok | 区间行上下阶实际流动(≥2 样本须≥2 不同值)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | -| 611 | 298 | "定值行(lo=hi)roll 恒等表值" | fixed_rows_ok | 定值行(lo=hi)roll 恒等表值。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 435 | 64 | "settlement.armor_roll_stream ∈ RngService 封闭标签词表(EC12)" | RngServiceScript.PURPOSE_TAGS.has(stream_label) | (待填) | | | +| 436 | 66 | "RngService 用途标签词表非空且互异(封闭语义由流改标签)" | RngServiceScript.PURPOSE_TAGS.size() > 0 | (待填) | | | -## S74 _init(行 17–17 · 1 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) +## S68 _test_ticket_nine_columns(行 83–85 · 3 条 · 源 game/tests/unit/contracts/system_contracts_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 612 | 17 | "数值表加载" | false | (待填) | | | +| 437 | 83 | "单据九栏 + 可选两栏缺省齐备(开单面只写差异栏前提)" | all_present | (待填) | | | +| 438 | 84 | "缺省无类型轴(attack_type 空 = 恒不查克制矩阵)" | String(t.attack_type) == "" | (待填) | | | +| 439 | 85 | "缺省无前置依赖(requires = -1)" | int(t.requires) == -1 | (待填) | | | + +## S69 _test_status_boolean_acceptance(行 107–121 · 5 条 · 源 game/tests/unit/contracts/system_contracts_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 440 | 107 | "前置:状态表非空" | not any_row.is_empty() | (待填) | | | +| 441 | 112 | "域闭授予拒绝(EC1——战斗域外 apply_effect = false)" | effects.apply_effect(probe_id, src) == false | (待填) | | | +| 442 | 113 | "未知 effect_id 拒绝(EC2)" | effects.apply_effect("no_such_effect_id__", src) == false | (待填) | | | +| 443 | 115 | "开域后受理已知 id(布尔受理口径 true 通道)" | effects.apply_effect(probe_id, src) == true | (待填) | | | +| 444 | 121 | "受理后投影 effective_value = 层数×授予时快照(抗热重载 EC9 面)" | projected | (待填) | | | + +## S70 _smoke_adventure_api(行 61–82 · 11 条 · 源 game/tests/unit/core/adventure_feats_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 445 | 61 | "冒险启动受理(主种子注入恰一次——state=running;种子 42=首局固定种子生成图——精确路径断言需要确定图(卡② 代差))" | adr.start_adventure(42) | (待填) | | | +| 446 | 62 | "进行中重复 start_adventure 拒绝(重开须待结算完毕)" | not adr.start_adventure(43) | (待填) | | | +| 447 | 63 | "停点前移次序契约:level_started 先于入口候选呈现(反序选路口会被呈现层 runner 空防御丢弃)" | sig_seq.size() == 2 and String(sig_seq[0]) == "level_started" and String(sig_seq[1]) == "choices_presented" | (待填) | | | +| 448 | 64 | "入口停点:选路态 + 入口单候选 + 首战未开(呈现零 RNG 消耗——pick 前零战斗域)" | adr.runner().awaiting_choice() and (obs["entry_cands"] as Array).size() == 1 \\ and adr.runner().current_battle() == null | (待填) | | | +| 449 | 67 | "pick 入口候选受理(同一 _enter_node——与旧自动进入逐位等价)" | adr.runner().pick_next_node(int(adr.runner().next_candidate_ids()[0])) | (待填) | | | +| 450 | 68 | "pick 后 node_entered 到达 + 首战在飞(接口①开始关卡——停点闭合直达战斗节点)" | sig_seq.size() == 3 and String(sig_seq[2]) == "node_entered" and adr.runner().current_battle() != null | (待填) | | | +| 451 | 69 | "战斗中 abandon 拒绝(战斗域单事务在飞)" | not adr.abandon() | (待填) | | | +| 452 | 78 | "首战胜后选路态(非战斗期——current_battle 置空)" | adr.runner().current_battle() == null and adr.runner().awaiting_choice() | (待填) | | | +| 453 | 79 | "非战斗期 abandon 受理(选路态——同失败型结算)" | adr.abandon() | (待填) | | | +| 454 | 81 | "放弃结算:result=abandoned · cleared=false(已获面不追回——钥石累计 %d)" ↩ % int(sum.get("keystone_total", -1)) | String(sum.get("result", "")) == "abandoned" and not bool(sum.get("cleared", true)) | (待填) | | | +| 455 | 82 | "结算后重复 abandon 拒绝(结算受理只算一次——state=idle)" | not adr.abandon() | (待填) | | | + +## S71 _smoke_adventure(行 113–438 · 57 条 · 源 game/tests/unit/core/adventure_feats_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 456 | 113 | "序列关数 = 行集关数(三关正式序列生效)" | int(_db.level.rows.size()) == 3 | (待填) | | | +| 457 | 130 | "生成图每个战斗/Boss 节点经解析链命中敌行集与棋盘行(扫描 %d 节点,缺失 %d)〔fv24〕" ↩ % [cov_scan, cov_miss] | cov_scan > 0 and cov_miss == 0 | (待填) | | | +| 458 | 135 | "满配通关(42 / atk+900 / hp+400——cleared=true;v2 怪阶梯后旧 200/100 档不再满配)" | String(st["result"]) == "completed" and bool(sum.get("cleared", false)) | (待填) | | | +| 459 | 136 | "通关几关 = 3(levels_cleared)" | int(sum.get("levels_cleared", -1)) == 3 | (待填) | | | +| 460 | 140 | "钥石合计 = ΣBoss 首杀行值(%d——逐杀掉落新口径;断点未注入恒首杀)" ↩ % int(sum.get("keystone_total", -1)) | int(sum.get("keystone_total", -1)) == ks_exp | (待填) | | | +| 461 | 142 | "逐杀掉落明细恰 3 笔(L1/L2/L3——additive 摘要键 keystone_drops)" | drops.size() == 3 | (待填) | | | +| 462 | 143 | "摘要携带主种子(42)" | int(sum.get("master_seed", -1)) == 42 | (待填) | | | +| 463 | 154 | "通关三选一恰呈现 2 次、恰兑付 2 次(L1/L2 后——末关不发;resolves 含节点回执另计)" | picks.size() == 2 and resolves.size() == 2 + node_picks | (待填) | | | +| 464 | 165 | "三关生成图各含 ≥1 奖励节点(节点三选一候选位在图)〔v4〕" | reward_levels == 3 | (待填) | | | +| 465 | 169 | "奖励分支点选→三选一候选可取且停点在途(候选 %s 件)" ↩ % str((rw.get("cands", []) as Array).size()) | bool(rw.get("pending", false)) and (rw.get("cands", []) as Array).size() >= 1 | (待填) | | | +| 466 | 182 | "奖励三选一点选兑付受理(件=%s)" ↩ % String(rw_pc.get("kind", "")) | rw_pick_ok | (待填) | | | +| 467 | 188 | "进战载荷两样=关卡序号+节点深度(实测 depth=%s 句柄=%s)〔v6〕" ↩ % [str(enc_probe.get("node_depth", -1)), str(node_probe.get("id", -1))] | not enc_probe.is_empty() and int(enc_probe.get("node_depth", -1)) == int(node_probe.get("depth", -1)) and int(enc_probe.get("level_index", -1)) == 1 and int(node_probe.get("id", -1)) != int(node_probe.get("depth", -1)) | (待填) | | | +| 468 | 197 | "奖励节点兑付回执与经停逐笔相等(context=reward_node——同账计数 %d/%d)" ↩ % [node_resolves, node_picks] | node_resolves == node_picks | (待填) | | | +| 469 | 244 | "三选一候选 2–3 枚且零重复(令牌洗牌不放回——kind+tier 复合身份)" | cand_ok | (待填) | | | +| 470 | 245 | "通关部分回血封顶(floor(观测上限×比例) 封顶——购血观测值精确断言,核代码增补)" | heal_ok | (待填) | | | +| 471 | 246 | "钥石逐杀入账(首杀档位 + 累计逐位)" | ks_gain_ok | (待填) | | | +| 472 | 247 | "点选恰兑付选中一件(值=令牌档位表值;未选项零生效)" | resolve_ok | (待填) | | | +| 473 | 258 | "各战上限入参 ∈ [基础, 基础+3×件值+购血余量](覆盖值通道——双渠道共享上限 3;模拟档贪心购血合法叠加〔数值修复批 v3.4〕;首战=基础 %d)" ↩ % exp_base | seq_ok | (待填) | | | +| 474 | 261 | "同种子两局逐位可复现(summary/上限序列/行进序列/三选一载荷——reward_pick 封闭流)" | st2["summary"] == sum and st2["encounter_max_hp_seq"] == seq and st2["visited_ids"] == st["visited_ids"] and st2["reward_events"] == st["reward_events"] | (待填) | | | +| 475 | 271 | "零配 L1 败北种子可达(seed=%s)" ↩ % str(lose.get("s", -1)) | not lose.is_empty() | (待填) | | | +| 476 | 274 | "L1 败北摘要:未打穿 · 通关 0 关 · 钥石 0 · 止步第 1 层" | not bool(sl["cleared"]) and int(sl["levels_cleared"]) == 0 \\ and int(sl["keystone_total"]) == 0 and int(sl["stopped_at"]) == 1 | (待填) | | | +| 477 | 278 | "L1 败北败退明细:层号/节点编号/节点类型在案(数值验证取数面 2026-09-22)" | int(dl.get("level_index", -1)) == 1 and int(dl.get("node_id", -1)) >= 0 \\ and String(dl.get("node_type", "")) in ["battle", "boss"] | (待填) | | | +| 478 | 287 | "半配(atk+50)L2 败北种子可达(seed=%s)" ↩ % str(half.get("s", -1)) | not half.is_empty() | (待填) | | | +| 479 | 290 | "L2 败北摘要:L1 已过(钥石=L1 首杀掉落)· 止步第 2 层" | not bool(sh["cleared"]) and int(sh["levels_cleared"]) == 1 \\ and int(sh["keystone_total"]) == int(_db.meta_boss_keystone_row(1).get("first_kill", 0)) \\ and int(sh["stopped_at"]) == 2 | (待填) | | | +| 480 | 295 | "L2 败北败退明细:层号=2 · 节点在案(数值验证取数面 2026-09-22)" | int(dh.get("level_index", -1)) == 2 and int(dh.get("node_id", -1)) >= 0 \\ and String(dh.get("node_type", "")) in ["battle", "boss"] | (待填) | | | +| 481 | 301 | "重复拒绝场景:冒险启动(seed 7——生成图确定同上)" | adr6.start_adventure(7) | (待填) | | | +| 482 | 309 | "重复拒绝场景就位:选路态放弃 → 已结算" | adr6.abandon() | (待填) | | | +| 483 | 312 | "重复关卡结束宣布拒绝(账本与摘要零变化——契约 7)" | adr6.summary == sum6_before | (待填) | | | +| 484 | 322 | "每战完成入账恰一次(gain %d = battles %d——GP 袋件入账单列)" ↩ % [gains, int(st["battles"])] | gains == int(st["battles"]) | (待填) | | | +| 485 | 323 | "钱包 clear 恰一次且为末事件(关卡收束不清——GP 冒险收束清零归冒险结构)" | clears == 1 and wallet.size() > 0 and String(wallet[wallet.size() - 1]["event"]) == "clear" | (待填) | | | +| 486 | 325 | "三关商店房各到达一次(每关入口→终点路径恰穿一店的驱动投影)且首达余额 > 0(%d——GP 跨关延续)" ↩ % (shop_seq[0] if shop_seq.size() > 0 else -1) | shop_seq.size() == 3 and int(shop_seq[0]) > 0 | (待填) | | | +| 487 | 336 | "同账场景种子可达(seed=%s——L2 商店房就位)" ↩ % str(same.get("s", -1)) | not same.is_empty() | (待填) | | | +| 488 | 349 | "进店余额 ≥ hp_up L2 首价(%d = 基价 %d + 关卡加成 %d——配置推导)" ↩ % [hp_price + l2_price_bonus, hp_price, l2_price_bonus] | gp0 >= hp_price + l2_price_bonus | (待填) | | | +| 489 | 350 | "购买 hp_up 受理(出账 + 加算区)" | r8.buy_product("hp_up") | (待填) | | | +| 490 | 352 | "统一冒险加算区含商店值(max_hp=%d ≥ 商店 3——L1 三选一件值另计)" ↩ % acc_shop | acc_shop >= 3 | (待填) | | | +| 491 | 355 | "L2 通关三选一停点在案(直调兑付前——零自动推进)" | not a8._curio_pending.is_empty() | (待填) | | | +| 492 | 356 | "未兑付推进拒绝(选择停点零自动推进契约)" | not a8.continue_to_next_level() | (待填) | | | +| 493 | 359 | "三选一点选兑付受理(候选取首件)" | a8.pick_curio(String(l2_pick.get("kind", "")), String(l2_pick.get("tier", "")), String(l2_pick.get("feat_id", ""))) | (待填) | | | +| 494 | 362 | "三选一属性件与商店购买同账累加(max_hp=%d = %d + 三选一%s)" ↩ % [int(a8.run_ledger.bonus.get("max_hp", -1)), acc_shop, picked2] | int(a8.run_ledger.bonus.get("max_hp", -1)) == expect_acc | (待填) | | | +| 495 | 364 | "通关部分回血封顶(hp %d ≤ 生效上限 %d——血回满语义退役)" ↩ % [int(a8.ledger["hero_hp"]), max_eff8] | int(a8.ledger["hero_hp"]) <= max_eff8 | (待填) | | | +| 496 | 365 | "兑付后推进受理(下一关开始)" | a8.continue_to_next_level() | (待填) | | | +| 497 | 368 | "恢复点数学:floor(100×30%)=30 · 封顶缺口 5 · 满血回 0 合法" | int(a9.apply_rest(30, 70, 100)) == 30 and int(a9.apply_rest(30, 95, 100)) == 5 \\ and int(a9.apply_rest(30, 100, 100)) == 0 | (待填) | | | +| 498 | 372 | "同一公式两个落点逐位一致(LevelRunner.rest_heal_amount 内置默认)" | int(lr9.rest_heal_amount(30, 70, 100)) == 30 and int(lr9.rest_heal_amount(30, 95, 100)) == 5 | (待填) | | | +| 499 | 376 | "放弃场景:冒险启动(seed 15——生成图确定同上)" | a10.start_adventure(15) | (待填) | | | +| 500 | 377 | "放弃场景:入口停点 pick 进首战(停点前移批 2026-09-23 驱动口径)" | a10.runner().pick_next_node(int(a10.runner().next_candidate_ids()[0])) | (待填) | | | +| 501 | 378 | "战斗中 abandon 拒绝(战斗域单事务在飞)" | not a10.abandon() | (待填) | | | +| 502 | 385 | "非战斗期(选路态)abandon 受理" | a10.runner().current_battle() == null and a10.abandon() | (待填) | | | +| 503 | 389 | "剔牌库/替换场景:冒险启动(seed 11——道具解锁集只含 wide_scout_band)" | a11.start_adventure(11, {"treasure_unlocked": {"wide_scout_band": true}}) | (待填) | | | +| 504 | 398 | "池耗即枯竭(attack/ap 令牌耗尽不出候选——%s)" ↩ % str(draw) | not draw_kinds.has("attack") and not draw_kinds.has("ap") | (待填) | | | +| 505 | 399 | "池耗后候选恒 ≥2(稀有/罕见档余量——防「三选项全不可选」死角)" | (draw as Array).size() >= 2 | (待填) | | | +| 506 | 408 | "池收缩——40 次重抽零 aux 候选(停点渠道 aux 卡退役,迭代 11 第①批 T7)" | not treasure_seen | (待填) | | | +| 507 | 417 | "满血通关回血 0 合法(封顶口径——heal_gained=0 非负溢出)" | int(cap["payload"].get("heal_gained", -1)) == 0 | (待填) | | | +| 508 | 420 | "节点共享池场景:冒险启动(seed 13)" | a13.start_adventure(13, {"treasure_unlocked": {"wide_scout_band": true}}) | (待填) | | | +| 509 | 431 | "奖励节点共享池:令牌耗尽同样不出候选(同库同池——%s)" ↩ % str(ncands13) | not has_attack13 | (待填) | | | +| 510 | 435 | "奖励节点 GP 件入账(钱包「奖励入账」收口——+%d)" ↩ % int(_db.adventure.curio_value("gp", "common")) | a13.apply_node_curio("gp", "common") and int(a13.ledger.get("gp", 0)) == gp13 + int(_db.adventure.curio_value("gp", "common")) | (待填) | | | +| 511 | 436 | "节点停点态兑付后清零(恰一次语义同构)" | a13._node_curio_pending.is_empty() | (待填) | | | +| 512 | 438 | "放弃摘要:abandoned · 未打穿(已获大奖励不追回——同失败型结算)" | String(s10.get("result", "")) == "abandoned" and not bool(s10.get("cleared", true)) | (待填) | | | + +## S72 _smoke_feats(行 449–761 · 68 条 · 源 game/tests/unit/core/adventure_feats_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 513 | 449 | "feat_table 已加载(30 件=基础 27+迁入 2+卡② 完美保险 1——迭代 31 卡② 换代)" | _db.feats != null and (_db.feats.rows as Array).size() == 30 | (待填) | | | +| 514 | 450 | "feat_table.validate 零违例(四级 rarity/cap 域/trigger 词表/行键白名单全过)" | _db.feats.validate().is_empty() | (待填) | | | +| 515 | 452 | "专长带场景:冒险启动(seed 71)" | a_r0.start_adventure(71) | (待填) | | | +| 516 | 460 | "专长带入池:30 件四级直映射(普通 12/优秀 9/稀有 9/传说 0)——牌库 102+30=132〔专长批 V2+卡② 完美保险迁入〕" | feat_tokens == 30 and int(tier_counts.get("common", 0)) == 12 and int(tier_counts.get("excellent", 0)) == 9 and int(tier_counts.get("rare", 0)) == 9 and a_r0._curio_deck.size() == 132 | (待填) | | | +| 517 | 462 | "旧 27 件已退役:arcane_ember/magma_body 表内零残留〔专长批 V2〕" | _db.feats.row_by_id("arcane_ember").is_empty() and _db.feats.row_by_id("magma_body").is_empty() | (待填) | | | +| 518 | 463 | "迁出收尾:treasure_items 无 venom_edge/seal_burst 行(同调目录自然收窄)〔专长批 V13〕" | _db.treasure_items.row_by_id("venom_edge").is_empty() and _db.treasure_items.row_by_id("seal_burst").is_empty() | (待填) | | | +| 519 | 470 | "层级纪律:牌库 kind 全集=CURIO_KINDS+relic(秘宝 item_id 零入池)〔专长批 V18〕" | kinds_ok | (待填) | | | +| 520 | 476 | "棋盘事实不可变:新 30 行零棋盘改写类订阅(rewrite_board 已除名消费面)〔专长批 V19〕" | rewrite_rows == 0 | (待填) | | | +| 521 | 483 | "对外命名纪律:30 行 display_name/desc 零「雷/旗」字面(封印/陷阱风味系)〔专长批 V21 初筛〕" | bad_words == 0 | (待填) | | | +| 522 | 484 | "数值纪律:陷阱爆发倍率>1 · 血蜜回复<典型陷阱伤 · 石肤上限 3 在册(结构锚形态内 provisional)〔专长批 V22 机检半〕" | float((_db.feats.row_by_id("trap_burst").get("triggers", [{}])[0] as Dictionary).get("do", {}).get("mult", 0)) > 1.0 and int((_db.feats.row_by_id("blood_honey").get("triggers", [{}])[0] as Dictionary).get("do", {}).get("value", 99)) < 5 and int((_db.feats.row_by_id("stone_skin").get("triggers", [{}])[0] as Dictionary).get("do", {}).get("cap", 0)) == 3 | (待填) | | | +| 523 | 502 | "新 schema 构造表 validate 零违例(四级 rarity/cap 字段/词表全过——流派标签已退役零 archetype)" | rt.validate().is_empty() | (待填) | | | +| 524 | 506 | "专长带场景:冒险启动(seed 72——注入 5 行临时表)" | a_r.start_adventure(72) | (待填) | | | +| 525 | 511 | "专长带入池:5 行 relic 每件 1 张(四级 rarity 直映射)——牌库 102+5=107" | deck_relic == 5 and a_r._curio_deck.size() == 107 | (待填) | | | +| 526 | 522 | "通关渠道只出专长(60 次重抽 ≥2 种不同 relic——身份键 feat_id 同档可多件)" | feat_seen.size() >= 2 | (待填) | | | +| 527 | 523 | "节点渠道零专长(60 次重抽零 relic——专长在非 Boss 渠道只以专长凭证形态出现)" | not node_relic_seen | (待填) | | | +| 528 | 531 | "A5 专长凭证兑付受理(节点渠道——拿到即开)" | a_t.apply_node_curio("feat_ticket", "excellent") | (待填) | | | +| 529 | 537 | "A5 拿到即开——停点候选已替换为专长三选一(全 relic)" | tkt_all_relic | (待填) | | | +| 530 | 538 | "A5 零摘牌——专长凭证兑付不动令牌库(不进持有集)" | a_t.pool_stock("attack") == int(stock_before["attack"]) and a_t.pool_stock("gp") == int(stock_before["gp"]) | (待填) | | | +| 531 | 539 | "A5 无专长凭证残留持有集" | not (a_t.feats_held() as Array).has("feat_ticket") | (待填) | | | +| 532 | 542 | "A5 专长面兑付受理(专长凭证开出候选可正常入持有集)" | (a_t.feats_held() as Array).size() == feats_n + 1 | (待填) | | | +| 533 | 547 | "A6 保底触发(pity=5 → 第 6 次展示必出——概率无关)" | a_t._feat_ticket_consider(rg_t) | (待填) | | | +| 534 | 548 | "A6 触发后清零" | a_t._feat_ticket_pity == 0 | (待填) | | | +| 535 | 551 | "A6 限购满不再触发(保底冻结)" | not a_t._feat_ticket_consider(rg_t) | (待填) | | | +| 536 | 559 | "A6 概率面存在(chance=20% 200 展示内必出现——零触发=概率面失守)" | offered | (待填) | | | +| 537 | 561 | "relic 兑付:入持有集 + 牌库摘牌(选中踢池——池库存 %d→%d)" ↩ % [deck_relic, deck_relic - 1] | a_r.pick_curio("relic", "rare", "x3") and (a_r.feats_held() as Array).has("x3") and a_r.pool_stock("relic") == deck_relic - 1 | (待填) | | | +| 538 | 585 | "双管镐:第 %d 次付费挖格返 1 AP(每第 2 次——mod_eq 预评估口径)" ↩ % (i + 1) | refund | (待填) | | | +| 539 | 587 | "双管镐反例:第 %d 次不返(未满 2 次)" ↩ % (i + 1) | not refund | (待填) | | | +| 540 | 591 | "地质学家:首开新类型集 1 点〔专长批 V3〕" | orch._hits_of(geo_hits, "counter_add").size() == 1 | (待填) | | | +| 541 | 595 | "地质学家:满 4 种(预评估现值 3+本格)→揭示 1 格·计数清零〔专长批 V3〕" | orch._hits_of(geo_hits2, "reveal_tiles").size() == 1 and EffectEngine.counter(orch.counters_ctx, "geo_types") == 0 | (待填) | | | +| 542 | 597 | "地质学家反例:同类型重复首开不叠点〔专长批 V3〕" | orch._hits_of(geo_hits3, "counter_add").is_empty() | (待填) | | | +| 543 | 601 | "安全帽:开安全格攒层 ×2〔专长批 V3〕" | EffectEngine.counter(orch.counters_ctx, "helm_layers") == 2 | (待填) | | | +| 544 | 607 | "安全帽:踩雷耗层减伤=层值 2(一次清零随施加面 set 行)〔专长批 V3〕" | hh_reduce == 2 | (待填) | | | +| 545 | 611 | "跳格采掘:空白 0 格攒 1 点〔专长批 V3〕" | orch._hits_of(sk_zero, "counter_add").size() == 1 | (待填) | | | +| 546 | 615 | "跳格采掘:满 3 点→揭示 1 格〔专长批 V3〕" | orch._hits_of(sk_full, "reveal_tiles").size() == 1 | (待填) | | | +| 547 | 616 | "跳格采掘反例:普通开格不计点〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_reveal", {"is_blank_zero": false}), "counter_add").is_empty() | (待填) | | | +| 548 | 623 | "爆破会计:本轮挖格花费≥5→+1 GP〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "round_end", {}), "grant_gp").size() == 1 | (待填) | | | +| 549 | 625 | "边陲矿工:边缘格产出 +1·中心格反例零产出〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_reveal", {"is_edge_cell": true}), "grant_material").size() == 1 and orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_reveal", {"is_edge_cell": false}), "grant_material").is_empty() | (待填) | | | +| 550 | 633 | "封印回响:标中计数≥2→轮末返 1 AP(下轮 queue)〔专长批 V3〕" | orch._hits_of(se_hits, "grant_ap").size() == 1 and String((orch._hits_of(se_hits, "grant_ap")[0] as Dictionary).get("mode", "")) == "queue" | (待填) | | | +| 551 | 642 | "封印匠:标中每第 3 次→中立移除恰一次(频率闸死)〔专长批 V3〕" | sm_remove == 1 | (待填) | | | +| 552 | 644 | "封印桩:标中本轮 AC +N(round 域——round_start 清零)〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "ac_bonus").size() == 1 and String((orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "ac_bonus")[0] as Dictionary).get("mode", "")) == "round" | (待填) | | | +| 553 | 648 | "示警烟火:标中揭示 3×3 资源类型(不揭安危位置)〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "preview_area").size() == 1 | (待填) | | | +| 554 | 657 | "疼痛记忆:攻击附加层值伤害=层值 2·用后清零(set 行随施加面)〔专长批 V3〕" | pm_bonus == 2 | (待填) | | | +| 555 | 664 | "保险丝反例:单次 <5 不攒点不触发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "damage_taken", {"kind": "terrain", "amount": 3}), "shield").is_empty() | (待填) | | | +| 556 | 668 | "保险丝:首点不出盾(2 点才出——预评估口径)〔专长批 V3〕" | fw_shield == 0 | (待填) | | | +| 557 | 670 | "见血开窍:踩雷揭示 1 格〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {}), "reveal_tiles").size() == 1 | (待填) | | | +| 558 | 677 | "陷阱本能:踩雷本轮攻击 +2(attack_bonus 通道——面板加算)〔专长批 V3〕" | ti_val == 2 | (待填) | | | +| 559 | 682 | "连环陷阱:同轮第 2 颗起每颗额外 +1 层(疼痛记忆同账)〔专长批 V3〕" | EffectEngine.counter(orch.counters_ctx, "pain_layers") == ct_before + 2 | (待填) | | | +| 560 | 685 | "陷阱爆发:全体敌雷伤×倍率直伤(blast_scaled——施加面值=damage×mult)〔专长批 V3〕" | orch._hits_of(tb_hits, "blast_scaled").size() == 1 and float((orch._hits_of(tb_hits, "blast_scaled")[0] as Dictionary).get("mult", 0)) > 1.0 | (待填) | | | +| 561 | 689 | "血蜜:踩雷回复 N 生命〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {}), "heal").size() == 1 | (待填) | | | +| 562 | 692 | "石肤:受伤 AC+1(本场域 cap 3——达上限不再涨由累积器封顶)〔专长批 V3〕" | orch._hits_of(ss_hits, "ac_bonus").size() == 1 and int((orch._hits_of(ss_hits, "ac_bonus")[0] as Dictionary).get("cap", 0)) == 3 | (待填) | | | +| 563 | 696 | "背水:生命≤50% 减伤·过半血反例零减伤〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "damage_taken", {"kind": "enemy_skill", "amount": 5, "source": "s", "round": 1, "hp_ratio": 40}), "damage_reduce").size() == 1 and orch._hits_of(fx.dispatch(orch.counters_ctx, "damage_taken", {"kind": "enemy_skill", "amount": 5, "source": "s", "round": 2, "hp_ratio": 80}), "damage_reduce").is_empty() | (待填) | | | +| 564 | 701 | "破封一击:挖开带封印危险格 +1 层·无封印反例零触发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {"dug_under_seal": true}), "counter_add").size() == 1 and orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {"dug_under_seal": false}), "counter_add").is_empty() | (待填) | | | +| 565 | 709 | "弃图兑换:停手剩余 AP≥3 入账〔专长批 V3〕" | orch._hits_of(me_hits, "counter_add").size() == 1 | (待填) | | | +| 566 | 713 | "弃图兑换:折为攻击伤害=剩余 AP 全额·用后清零〔专长批 V3〕" | me_atk == 4 | (待填) | | | +| 567 | 714 | "弃图兑换反例:剩余 AP<3 不触发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_stop", {"ap_left": 2}), "counter_add").is_empty() | (待填) | | | +| 568 | 717 | "一滴不剩:上轮 AP 耗尽→+1 AP〔专长批 V3〕" | orch._hits_of(ld_hits, "grant_ap").size() == 1 | (待填) | | | +| 569 | 718 | "一滴不剩反例:上轮有余不发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "round_start", {"round": 3, "ap_exhausted_last": false}), "grant_ap").is_empty() | (待填) | | | +| 570 | 724 | "收盘:本轮开格≥8→+1 GP〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "round_end", {}), "grant_gp").size() == 1 | (待填) | | | +| 571 | 727 | "斩首学者:击杀揭示 1 格〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "kill", {"target": "敌", "overflow": 0}), "reveal_tiles").size() == 1 | (待填) | | | +| 572 | 730 | "处刑台:敌血量≤30% 挂单位标记加伤·阈值外反例零命中〔专长批 V3〕" | orch._hits_of(ex_hits, "target_attack_bonus").size() == 1 and orch._hits_of(fx.dispatch(orch.counters_ctx, "enemy_hp_threshold", {"unit": "e1", "pct": 60, "is_boss": false}), "target_attack_bonus").is_empty() | (待填) | | | +| 573 | 734 | "战地包扎:战斗胜利回复 N 生命(battle_won 新挂点)〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "battle_won", {"round": 2}), "heal").size() == 1 | (待填) | | | +| 574 | 737 | "血税:双向同局账本(踩陷阱−2max_hp/封印命中+1atk——bonus_delta 战末汇入)〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {}), "bonus_delta").size() == 1 and String((orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "bonus_delta")[0] as Dictionary).get("axis", "")) == "atk" | (待填) | | | +| 575 | 742 | "淬毒刃纹(迁入):克制正面(magic_vs_heavy 矩阵值>0)追加·中性面反例零触发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "clamp_check", {"matchup": "magic_vs_heavy"}), "modify_ticket").size() == 1 and orch._hits_of(fx.dispatch(orch.counters_ctx, "clamp_check", {"matchup": "normal_vs_light"}), "modify_ticket").is_empty() | (待填) | | | +| 576 | 746 | "封印爆裂(迁入):标中全体敌群伤〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "open_ticket").size() == 1 | (待填) | | | +| 577 | 750 | "零持有订阅=零命中(relic 关着零扰动——伤害管线既有行为逐位不变)" | orch0._fx_emit("attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 0}).is_empty() and orch0._fx_emit("dig_reveal", {"cell": Vector2i(1, 1), "round": 1, "is_first_dig_of_battle": true, "terrain_kind": []}).is_empty() and orch0._fx_emit("mine_hit", {"damage": 5}).is_empty() and orch0._fx_emit("battle_won", {"round": 1}).is_empty() | (待填) | | | +| 578 | 758 | "特性段空态隐藏(视图空态纪律同特性段)" | not panel._feat_section.visible | (待填) | | | +| 579 | 760 | "特性段持有呈现(逐件行常驻可读——「局内奖励选了什么看不到」缺口承载)" | panel._feat_section.visible and panel._feat_list.get_child_count() == 2 | (待填) | | | +| 580 | 761 | "特性卡文案表值化:desc 行字段直读(零硬编码映射)" | String(load("res://presentation/battle_narrator.gd").feat_card_text({"feat_id": "pain_memory", "display_name": "疼痛记忆", "desc": "触陷阱攒 1 层"})).contains("触陷阱攒 1 层") | (待填) | | | + +## S73 _smoke_layout_cell_formula(行 65–68 · 2 条 · 源 game/tests/unit/core/board_gen_reward_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 581 | 65 | "L%d %dx%d 板网格高 %d ≤ 可用高 %d − 预留 24(格边 %dpx——下缘不贴边不裁切)" ↩ % [L, b.width, b.height, grid_h, int(avail.y), int(cell)] | grid_h <= int(avail.y) - 24 | (待填) | | | +| 582 | 67 | "L%d 格边在 26–46 档内(%dpx)" ↩ % [L, int(cell)] | cell >= 26.0 and cell <= 46.0 | (待填) | | | + +## S74 _smoke_reward_identity_unit(行 83–116 · 10 条 · 源 game/tests/unit/core/board_gen_reward_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 583 | 83 | "roll_board 受理" | reward_economy.roll_board(eligible) | (待填) | | | +| 584 | 84 | "重复 roll_board 被拒(恰一次——double-roll 守卫)" | not reward_economy.roll_board(eligible) | (待填) | | | +| 585 | 95 | "query_identity 投影 = {reward_id, cell_type, tier} 且五型封闭(样本 %s)" ↩ % str(types_seen.keys()) | types_ok | (待填) | | | +| 586 | 97 | "清单外坐标查询 = null(封锁格无身份同口径)" | reward_economy.query_identity(Vector2i(99, 99)) == null | (待填) | | | +| 587 | 101 | "同格重复 grant 幂等(同一快照——受理恰一次)" | JSON.stringify(snap1) == JSON.stringify(snap2) | (待填) | | | +| 588 | 102 | "清单外 grant 拒绝" | reward_economy.grant(Vector2i(99, 99)) == null | (待填) | | | +| 589 | 106 | "清图批尾身份 = clear_blast(clear_default 唯一活行)" | String(cs1.cell_type) == "blast" and String(cs1.reward_id) == "clear_blast" | (待填) | | | +| 590 | 111 | "清图奖励 power ∈ 表区间 [%d, %d](roll 值 %d——配置推导)" ↩ % [cb_range[0], cb_range[1], int(cs1.power)] | int(cs1.power) >= int(cb_range[0]) and int(cs1.power) <= int(cb_range[1]) | (待填) | | | +| 591 | 113 | "grant_clear 同批次重调幂等" | JSON.stringify(cs1) == JSON.stringify(cs2) | (待填) | | | +| 592 | 116 | "映射退役后 grant 拒绝(生命周期闭合)" | reward_economy.grant(eligible[0].cell) == null | (待填) | | | -## S75 _test_generate_shape_and_mines(行 41–54 · 5 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) +## S75 _smoke_board_generator(行 135–210 · 24 条 · 源 game/tests/unit/core/board_gen_reward_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 613 | 41 | "生成返回棋盘" | false | (待填) | | | -| 614 | 46 | "棋盘尺寸与 board_gen 表一致(%dx%d)" ↩ % [want_w, want_h] | b.width == want_w and b.height == want_h | (待填) | | | -| 615 | 48 | "雷数非零且不超过总格数" | b.mines.size() > 0 and b.mines.size() < b.width * b.height | (待填) | | | -| 616 | 53 | "全部危险格在界内" | all_in_bounds | (待填) | | | -| 617 | 54 | "生成期指标集已落盘(metrics 非空)" | not b.metrics.is_empty() | (待填) | | | +| 593 | 135 | "BoardGen 行解析 (%d,0)" ↩ % L | not row.is_empty() | (待填) | | | +| 594 | 141 | "BoardGen 精确命中 (2,3)(覆盖批键域补齐)" | int(exact_row.get("node_depth", -1)) == 3 | (待填) | | | +| 595 | 151 | "R1 ceil 能力不失(夹具摘除 (2,3)→向上取整到 4·偏难口径)" | int(ceil_row.get("node_depth", -1)) == 4 | (待填) | | | +| 596 | 153 | "夹具表原样还原(行数与解析回到覆盖批现值)" | _db.board_gen.rows.size() == rows_before and int(_db.resolve_board_gen(2, 3).get("node_depth", -1)) == 3 | (待填) | | | +| 597 | 156 | "L%d 生成产物非空" ↩ % L | b1 != null | (待填) | | | +| 598 | 159 | "L%d clearable(安全格 8 邻连通)" ↩ % L | board_generator.is_clearable(b1) | (待填) | | | +| 599 | 160 | "L%d legality 全过" ↩ % L | board_generator.legality(b1, row) | (待填) | | | +| 600 | 167 | "L%d inferability %.3f %s target %.2f(accept_mode=%s)" ↩ % [L, inf, "≥" if inf >= float(row.inferability_target) else "<", float(row.inferability_target), mode] | inf >= float(row.inferability_target) or mode == "accept_best" | (待填) | | | +| 601 | 169 | "L%d 生成器产物零变体格(变异布置归战斗初始化第 4 步——生成器管线不布置,2026-09-19 用户拍板棋盘设置顺序)" ↩ % L | b1.variants.is_empty() | (待填) | | | +| 602 | 170 | "L%d 指标集输出降级标记与目标雷数(三分出口表)" ↩ % L | b1.metrics.has("degraded_product") and b1.metrics.has("target_mines") | (待填) | | | +| 603 | 171 | "L%d 降级产物标记 = (accept_mode==accept_best)(%s→%s)" ↩ % [L, mode, str(b1.degraded_product)] | b1.degraded_product == (mode == "accept_best") | (待填) | | | +| 604 | 173 | "L%d 非法产物标记 = (accept_mode==accept_worst)(%s→%s)" ↩ % [L, mode, str(b1.illegal_product)] | b1.illegal_product == (mode == "accept_worst") | (待填) | | | +| 605 | 175 | "L%d 降级/非法产物标记互斥(两出口不可同真)" ↩ % L | not b1.degraded_product or not b1.illegal_product | (待填) | | | +| 606 | 177 | "L%d 产物断言:实际雷数 %d ≤ 目标雷数 N=%d(先布后删只减不增)" ↩ % [L, int(b1.metrics.actual_mines), int(b1.metrics.target_mines)] | int(b1.metrics.actual_mines) <= int(b1.metrics.target_mines) | (待填) | | | +| 607 | 179 | "L%d attempts_used ∈ [1,%d]" ↩ % [L, int(row.max_gen_attempts)] | b1.metrics.get("attempts_used", 99) >= 1 and b1.metrics.get("attempts_used", 99) <= int(row.max_gen_attempts) | (待填) | | | +| 608 | 181 | "L%d 非法产物标记 = false" ↩ % L | not b1.illegal_product | (待填) | | | +| 609 | 185 | "L%d 同种子两次生成逐格相同" ↩ % L | _board_dump(b2) == _board_dump(b3) | (待填) | | | +| 610 | 187 | "L%d 异种子产物不同(退化护栏)" ↩ % L | _board_dump(b2) != _board_dump(b4) | (待填) | | | +| 611 | 193 | "L%d r0_max_cap 正式行投值在位(%d ≥ 出生保障面 9——板面 30%% 锚)" ↩ % [L, cap_val] | cap_val >= BoardData.SPAWN_AREA_CELLS | (待填) | | | +| 612 | 195 | "L%d 结构断言 R₀=%d ≤ cap=%d(R₀≤cap 重掷闸承载)" ↩ % [L, int(b1.metrics.r0_count), cap_val] | int(b1.metrics.r0_count) <= cap_val | (待填) | | | +| 613 | 197 | "L%d r0_cap_rejected 尝试事实指标在位(本图 %d 次触发)" ↩ % [L, int(b1.metrics.get("r0_cap_rejected", -1))] | int(b1.metrics.get("r0_cap_rejected", -1)) >= 0 | (待填) | | | +| 614 | 199 | "L%d ring_removed=%d ≤ 剃雷域候选危险格数((3+2r)²−9——全域剃雷退役)" ↩ % [L, int(b1.metrics.get("ring_removed", 99))] | int(b1.metrics.get("ring_removed", 99)) <= (2 + int(row.get("shave_ring_radius", 1))) * (2 + int(row.get("shave_ring_radius", 1))) * 4 - 5 | (待填) | | | +| 615 | 207 | "L1 cap 压线 9:出图 R₀=%d %s(三分出口诚实降级——超阈即 accept_worst 非法标记,玩家永不卡死)" ↩ % [int(b9.metrics.r0_count), "≤9" if int(b9.metrics.r0_count) <= BoardData.SPAWN_AREA_CELLS else ">9→illegal_product"] | b9 != null and (int(b9.metrics.r0_count) <= BoardData.SPAWN_AREA_CELLS or b9.illegal_product) | (待填) | | | +| 616 | 209 | "L1 cap 压线出图指标集完整(accept_mode=%s / 闸触发 %d 次)" ↩ % [String(b9.metrics.get("accept_mode", "")), int(b9.metrics.get("r0_cap_rejected", 0))] | int(b9.metrics.get("r0_cap_rejected", 0)) >= 0 and String(b9.metrics.get("accept_mode", "")) != "" | (待填) | | | -## S76 _test_digits_equal_neighbor_mine_count(行 60–74 · 2 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) +## S76 _smoke_power_interval(行 240–298 · 5 条 · 源 game/tests/unit/core/board_gen_reward_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 618 | 60 | "生成返回棋盘(数字段)" | false | (待填) | | | -| 619 | 74 | "每个安全格的数字恒等于八邻雷数(全盘逐格复算,错 %d 格)" ↩ % bad | bad == 0 | (待填) | | | +| 617 | 240 | "区间行配对齐全且 power = floor 中点(配置推导 %d 行:%s)" ↩ % [interval_rows.size(), str(interval_rows.keys())] | anchors_ok and interval_rows.size() >= 1 | (待填) | | | +| 618 | 282 | "24 板全区间行快照 power ∈ [power_min, power_max](含清图槽)" | in_domain | (待填) | | | +| 619 | 283 | "定值行快照 power 恒等表值——区间批零外溢" | fixed_ok | (待填) | | | +| 620 | 297 | "区间行上下阶实际流动(≥2 样本的区间行须 ≥2 个不同 roll 值)" | variance_ok | (待填) | | | +| 621 | 298 | "定值行(lo=hi)roll 恒等表值" | fixed_rows_ok | (待填) | | | -## S77 _test_same_seed_bit_identical(行 81–84 · 3 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) +## S77 _init(行 17–17 · 1 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 620 | 81 | "生成返回棋盘(确定性段)" | false | (待填) | | | -| 621 | 83 | "同种子生成的雷集逐位一致" | str(a.mines) == str(b.mines) | (待填) | | | -| 622 | 84 | "同种子生成的数字集逐位一致" | str(a.digits) == str(b.digits) | (待填) | | | +| 622 | 17 | "数值表加载" | false | (待填) | | | -## S78 _test_clearable_and_inferability_selfconsistent(行 90–97 · 3 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) +## S78 _test_generate_shape_and_mines(行 42–55 · 5 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 623 | 90 | "生成返回棋盘(可推理段)" | false | (待填) | | | -| 624 | 93 | "可推理性度量返回非空指标集" | not m.is_empty() | (待填) | | | -| 625 | 96 | "清图判定与安全格总数同在(安全格 %d 个,可清=%s)" ↩ % [safe_total, str(clearable)] | safe_total > 0 | (待填) | | | +| 623 | 42 | "生成返回棋盘" | false | (待填) | | | +| 624 | 47 | "棋盘尺寸与 board_gen 表一致(%dx%d)" ↩ % [want_w, want_h] | b.width == want_w and b.height == want_h | (待填) | | | +| 625 | 49 | "雷数非零且不超过总格数" | b.mines.size() > 0 and b.mines.size() < b.width * b.height | (待填) | | | +| 626 | 54 | "全部危险格在界内" | all_in_bounds | (待填) | | | +| 627 | 55 | "生成期指标集已落盘(metrics 非空)" | not b.metrics.is_empty() | (待填) | | | -## S79 _test_all_links_valid(行 59–59 · 1 条 · 源 game/tests/unit/core/config_db_cross_validator_test.gd · 去向:待分类回填) +## S79 _test_target_mine_count_single_source(行 75–83 · 3 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 626 | 59 | "全对齿的技能池零错(正向面——实际报错 %s)" ↩ % str(_cats(errs)) | errs.is_empty() | (待填) | | | +| 628 | 75 | "target_mine_count 与原式同参同值(%d 档对拍全等)〔wd14〕" ↩ % checked | checked > 0 and bad == 0 | (待填) | | | +| 629 | 78 | "生成返回棋盘(雷数单点段)" | false | (待填) | | | +| 630 | 81 | "产物断言读数 = target_mine_count 同源(metrics.target_mines)〔wd14〕" | int(b.metrics.get("target_mines", -1)) == BoardGeneratorScript.target_mine_count({ "w": int(row1.board_width), "h": int(row1.board_height), "density": float(row1.mine_density)}) | (待填) | | | -## S80 _test_dangling_skill_id(行 70–72 · 2 条 · 源 game/tests/unit/core/config_db_cross_validator_test.gd · 去向:待分类回填) +## S80 _test_digits_equal_neighbor_mine_count(行 89–103 · 2 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 627 | 70 | "池内悬空 skill_id 被报(跨表护栏不静默放过)" | not errs.is_empty() | (待填) | | | -| 628 | 71 | "悬空项的错误类别可定位(category 非空)" | not errs.is_empty() and String((errs[0] as Dictionary).get("category", "")) != "" | (待填) | | | +| 631 | 89 | "生成返回棋盘(数字段)" | false | (待填) | | | +| 632 | 103 | "每个安全格的数字恒等于八邻雷数(全盘逐格复算,错 %d 格)" ↩ % bad | bad == 0 | (待填) | | | -## S81 _test_cast_side_mismatch(行 84–84 · 1 条 · 源 game/tests/unit/core/config_db_cross_validator_test.gd · 去向:待分类回填) +## S81 _test_same_seed_bit_identical(行 110–113 · 3 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 629 | 84 | "buff 挂主角侧状态被报(施放类 side 守卫)" | _cats(errs).has("cast_side") | (待填) | | | +| 633 | 110 | "生成返回棋盘(确定性段)" | false | (待填) | | | +| 634 | 112 | "同种子生成的雷集逐位一致" | str(a.mines) == str(b.mines) | (待填) | | | +| 635 | 113 | "同种子生成的数字集逐位一致" | str(a.digits) == str(b.digits) | (待填) | | | -## S82 _test_band_hit(行 58–62 · 2 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) +## S82 _test_clearable_and_inferability_selfconsistent(行 119–126 · 3 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 630 | 58 | "带命中取偏难一侧:请求深度 4 命中带 5(逐字不回带 0)〔fv1〕" | hit.size() == 1 and String((hit[0] as Dictionary).get("enemy_id", "")) == "hobgoblin" | (待填) | | | -| 631 | 61 | "精确落带:请求深度 5 命中带 5〔fv1〕" | exact.size() == 1 and String((exact[0] as Dictionary).get("enemy_id", "")) == "hobgoblin" | (待填) | | | +| 636 | 119 | "生成返回棋盘(可推理段)" | false | (待填) | | | +| 637 | 122 | "可推理性度量返回非空指标集" | not m.is_empty() | (待填) | | | +| 638 | 125 | "清图判定与安全格总数同在(安全格 %d 个,可清=%s)" ↩ % [safe_total, str(clearable)] | safe_total > 0 | (待填) | | | -## S83 _test_rowset_order_preserved(行 67–72 · 2 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) +## S83 _test_all_links_valid(行 59–59 · 1 条 · 源 game/tests/unit/core/config_db_cross_validator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 632 | 67 | "行集返回同带全部行(2 行)〔fv2〕" | rows.size() == 2 | (待填) | | | -| 633 | 71 | "行集按表文件行序返回(goblin 先于 bugbear——行序即契约)〔fv2〕" | ids == ["goblin", "bugbear"] | (待填) | | | +| 639 | 59 | "全对齿的技能池零错(正向面——实际报错 %s)" ↩ % str(_cats(errs)) | errs.is_empty() | (待填) | | | -## S84 _test_top_band_clamp(行 78–87 · 3 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) +## S84 _test_dangling_skill_id(行 70–72 · 2 条 · 源 game/tests/unit/core/config_db_cross_validator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 634 | 78 | "越顶带 clamp 取顶带行集(battle 顶带 5)〔fv3〕" | rows.size() == 1 and String((rows[0] as Dictionary).get("enemy_id", "")) == "hobgoblin" | (待填) | | | -| 635 | 84 | "越顶带发 warning resolve_depth_clamp〔fv3〕" | clamp_hit | (待填) | | | -| 636 | 87 | "同键重复请求警告去重(四元组注册表)〔fv3〕" | _diags.size() == warn_before | (待填) | | | +| 640 | 70 | "池内悬空 skill_id 被报(跨表护栏不静默放过)" | not errs.is_empty() | (待填) | | | +| 641 | 71 | "悬空项的错误类别可定位(category 非空)" | not errs.is_empty() and String((errs[0] as Dictionary).get("category", "")) != "" | (待填) | | | -## S85 _test_row_kind_routing(行 93–95 · 1 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) +## S85 _test_cast_side_mismatch(行 84–84 · 1 条 · 源 game/tests/unit/core/config_db_cross_validator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 637 | 93 | "行类路由:同请求深度 battle 与 boss 命中不同键域〔fv4〕" | battle_rows.size() == 2 and boss_rows.size() == 1 and String((boss_rows[0] as Dictionary).get("enemy_id", "")) == "ogre_chief" | (待填) | | | +| 642 | 84 | "buff 挂主角侧状态被报(施放类 side 守卫)" | _cats(errs).has("cast_side") | (待填) | | | -## S86 _test_request_oob(行 101–107 · 3 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) +## S86 _test_band_hit(行 58–62 · 2 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 638 | 101 | "关卡序号 <1 → fatal request_oob〔fv5〕" | _has_fatal_oob() | (待填) | | | -| 639 | 104 | "节点深度 <0 → fatal request_oob〔fv5〕" | _has_fatal_oob() | (待填) | | | -| 640 | 107 | "row_kind 非法 → fatal request_oob〔fv5〕" | _has_fatal_oob() | (待填) | | | +| 643 | 58 | "带命中取偏难一侧:请求深度 4 命中带 5(逐字不回带 0)〔fv1〕" | hit.size() == 1 and String((hit[0] as Dictionary).get("enemy_id", "")) == "hobgoblin" | (待填) | | | +| 644 | 61 | "精确落带:请求深度 5 命中带 5〔fv1〕" | exact.size() == 1 and String((exact[0] as Dictionary).get("enemy_id", "")) == "hobgoblin" | (待填) | | | -## S87 _test_difficulty_clamp(行 119–122 · 3 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) +## S87 _test_rowset_order_preserved(行 67–72 · 2 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 641 | 119 | "难度乘数越域被 clamp:hp 99 → 上限 10" | absf(float(m.get("hp_mult", 0.0)) - 10.0) < 0.001 | (待填) | | | -| 642 | 120 | "难度乘数越域被 clamp:atk 0.01 → 下限 0.1" | absf(float(m.get("atk_mult", 0.0)) - 0.1) < 0.001 | (待填) | | | -| 643 | 121 | "未登记坐标返回空修饰(零值不伪装成 ×1)" | _resolver.resolve_difficulty_modifiers(3, 3).is_empty() | (待填) | | | +| 645 | 67 | "行集返回同带全部行(2 行)〔fv2〕" | rows.size() == 2 | (待填) | | | +| 646 | 71 | "行集按表文件行序返回(goblin 先于 bugbear——行序即契约)〔fv2〕" | ids == ["goblin", "bugbear"] | (待填) | | | -## S88 _test_field_in_table(行 106–114 · 4 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) +## S88 _test_top_band_clamp(行 78–87 · 3 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 644 | 106 | "N 落表:level.tres 读数 2(实际 %d)〔fv6〕" ↩ % n_in_table | n_in_table == 2 | (待填) | | | -| 645 | 110 | "N=1 合法:validate 零错〔fv6〕" | t.validate().is_empty() | (待填) | | | -| 646 | 112 | "N 越域 0:validate 判 domain 错〔fv6〕" | _has_n_domain_err(t.validate()) | (待填) | | | -| 647 | 114 | "N 越域 -1:validate 判 domain 错〔fv6〕" | _has_n_domain_err(t.validate()) | (待填) | | | +| 647 | 78 | "越顶带 clamp 取顶带行集(battle 顶带 5)〔fv3〕" | rows.size() == 1 and String((rows[0] as Dictionary).get("enemy_id", "")) == "hobgoblin" | (待填) | | | +| 648 | 84 | "越顶带发 warning resolve_depth_clamp〔fv3〕" | clamp_hit | (待填) | | | +| 649 | 87 | "同键重复请求警告去重(四元组注册表)〔fv3〕" | _diags.size() == warn_before | (待填) | | | -## S89 _test_layer_distance(行 131–133 · 3 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) +## S89 _test_row_kind_routing(行 93–95 · 1 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 648 | 131 | "层距 N 内可见:锚层 1 取节点 4(层距 2)〔fv1〕" | vis.has(4) | (待填) | | | -| 649 | 132 | "层距 N+1 不可见:锚层 1 排节点 5(层距 3)〔fv1〕" | not vis.has(5) | (待填) | | | -| 650 | 133 | "层距 N+2 不可见:锚层 1 排节点 6(层距 4)〔fv1〕" | not vis.has(6) | (待填) | | | +| 650 | 93 | "行类路由:同请求深度 battle 与 boss 命中不同键域〔fv4〕" | battle_rows.size() == 2 and boss_rows.size() == 1 and String((boss_rows[0] as Dictionary).get("enemy_id", "")) == "ogre_chief" | (待填) | | | -## S90 _test_candidate_exempt(行 142–143 · 2 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) +## S90 _test_request_oob(行 101–107 · 3 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 651 | 142 | "候选恒显:跨层跳边候选(层距 N+1)仍在可见集〔fv4〕" | vis.has(3) | (待填) | | | -| 652 | 143 | "候选豁免不改层规则:同锚点非候选节点 2(层距 4)不可见〔fv4〕" | not vis.has(2) | (待填) | | | +| 651 | 101 | "关卡序号 <1 → fatal request_oob〔fv5〕" | _has_fatal_oob() | (待填) | | | +| 652 | 104 | "节点深度 <0 → fatal request_oob〔fv5〕" | _has_fatal_oob() | (待填) | | | +| 653 | 107 | "row_kind 非法 → fatal request_oob〔fv5〕" | _has_fatal_oob() | (待填) | | | -## S91 _test_boss_exempt(行 152–155 · 4 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) +## S91 _test_difficulty_clamp(行 119–122 · 3 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 653 | 152 | "入口停点锚层 0:层距 N 内(节点 2)可见〔fv1〕" | vis.has(2) | (待填) | | | -| 654 | 153 | "入口停点锚层 0:层距 N+2(节点 4)不可见〔fv1〕" | not vis.has(4) | (待填) | | | -| 655 | 154 | "Boss 恒显:层距 6 的末位 Boss 仍在可见集〔fv3〕" | vis.has(7) | (待填) | | | -| 656 | 155 | "Boss 豁免不扩散:同层距的非 Boss 节点 6 不在可见集〔fv3〕" | not vis.has(6) | (待填) | | | +| 654 | 119 | "难度乘数越域被 clamp:hp 99 → 上限 10" | absf(float(m.get("hp_mult", 0.0)) - 10.0) < 0.001 | (待填) | | | +| 655 | 120 | "难度乘数越域被 clamp:atk 0.01 → 下限 0.1" | absf(float(m.get("atk_mult", 0.0)) - 0.1) < 0.001 | (待填) | | | +| 656 | 121 | "未登记坐标返回空修饰(零值不伪装成 ×1)" | _resolver.resolve_difficulty_modifiers(3, 3).is_empty() | (待填) | | | -## S92 _test_single_circle(行 165–167 · 3 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) +## S92 _test_field_in_table(行 106–114 · 4 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 657 | 165 | "单圈回雾:已过远端节点 0(层距 5)不因 visited 豁免〔fv7〕" | not vis.has(0) | (待填) | | | -| 658 | 166 | "单圈回雾:已过中距节点 2(层距 4)不可见〔fv7〕" | not vis.has(2) | (待填) | | | -| 659 | 167 | "单圈只按锚层取窗:已过近端节点 4(层距 2)仍可见〔fv7〕" | vis.has(4) | (待填) | | | +| 657 | 106 | "N 落表:level.tres 读数 2(实际 %d)〔fv6〕" ↩ % n_in_table | n_in_table == 2 | (待填) | | | +| 658 | 110 | "N=1 合法:validate 零错〔fv6〕" | t.validate().is_empty() | (待填) | | | +| 659 | 112 | "N 越域 0:validate 判 domain 错〔fv6〕" | _has_n_domain_err(t.validate()) | (待填) | | | +| 660 | 114 | "N 越域 -1:validate 判 domain 错〔fv6〕" | _has_n_domain_err(t.validate()) | (待填) | | | -## S93 _test_single_chain_entry(行 176–179 · 3 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) +## S93 _test_layer_distance(行 131–133 · 3 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 660 | 176 | "单链退化形态:入口锚层 0,句柄 2 可见〔fv1〕" | vis.has(2) | (待填) | | | -| 661 | 177 | "单链退化形态:层距 N+1(句柄 3)不可见〔fv1〕" | not vis.has(3) | (待填) | | | -| 662 | 178 | "单链编号空间 = 图句柄 id:Boss 以句柄 6 入集(链上层距 4 的战斗节点 4 入雾)〔fv1〕" | vis.has(6) and not vis.has(4) | (待填) | | | +| 661 | 131 | "层距 N 内可见:锚层 1 取节点 4(层距 2)〔fv1〕" | vis.has(4) | (待填) | | | +| 662 | 132 | "层距 N+1 不可见:锚层 1 排节点 5(层距 3)〔fv1〕" | not vis.has(5) | (待填) | | | +| 663 | 133 | "层距 N+2 不可见:锚层 1 排节点 6(层距 4)〔fv1〕" | not vis.has(6) | (待填) | | | -## S94 _test_n_from_table(行 188–196 · 3 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) +## S94 _test_candidate_exempt(行 142–143 · 2 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 663 | 188 | "N 读表:N=1 时节点 4(层距 2)出窗〔fv6〕" | not r.type_visible_ids().has(4) | (待填) | | | -| 664 | 190 | "N 读表:N=3 时节点 5(层距 3)入窗〔fv6〕" | r.type_visible_ids().has(5) | (待填) | | | -| 665 | 196 | "N 读表:N ≥ 全图层高 → 全部非豁免节点可见〔fv6〕" | all_in | (待填) | | | +| 664 | 142 | "候选恒显:跨层跳边候选(层距 N+1)仍在可见集〔fv4〕" | vis.has(3) | (待填) | | | +| 665 | 143 | "候选豁免不改层规则:同锚点非候选节点 2(层距 4)不可见〔fv4〕" | not vis.has(2) | (待填) | | | -## S95 _test_resume_rebuild(行 234–235 · 1 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) +## S95 _test_boss_exempt(行 152–155 · 4 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 666 | 234 | "续玩重建同值:resume 路径与同停点直入 type_visible_ids() 逐位一致〔fv7〕" | resumed.type_visible_ids() == direct.type_visible_ids() | (待填) | | | +| 666 | 152 | "入口停点锚层 0:层距 N 内(节点 2)可见〔fv1〕" | vis.has(2) | (待填) | | | +| 667 | 153 | "入口停点锚层 0:层距 N+2(节点 4)不可见〔fv1〕" | not vis.has(4) | (待填) | | | +| 668 | 154 | "Boss 恒显:层距 6 的末位 Boss 仍在可见集〔fv3〕" | vis.has(7) | (待填) | | | +| 669 | 155 | "Boss 豁免不扩散:同层距的非 Boss 节点 6 不在可见集〔fv3〕" | not vis.has(6) | (待填) | | | -## S96 _smoke_generated_coverage(行 268–305 · 6 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) +## S96 _test_single_circle(行 165–167 · 3 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 667 | 268 | "L%d 参数档缺行(覆盖断言无源可扫)〔v11〕" ↩ % L | false | (待填) | | | -| 668 | 275 | "L%d 种子 %d 生成图为空(覆盖扫描面空转)〔v11〕" ↩ % [L, seed_val] | false | (待填) | | | -| 669 | 302 | "生成图覆盖扫描面非空(三关参数档×种子 %d–%d 共 %d 个节点读数)〔v11〕" ↩ % [SWEEP_SEED_FROM, SWEEP_SEED_TO - 1, scanned] | scanned > 0 | (待填) | | | -| 670 | 303 | "生成图每个 battle/boss 节点深度的敌侧解析落带 ≥ 请求深度(band_of 非 -1·顶 clamp 消解禁)〔v11〕" | enemy_ok | (待填) | | | -| 671 | 304 | "生成图每个节点深度在 board_gen 精确命中(非空且 node_depth 等值)〔v11〕" | board_ok | (待填) | | | -| 672 | 305 | "多终点 Boss 各终点解析带键两两互异(L2/L3×20 种子)〔v11〕" | boss_band_ok | (待填) | | | +| 670 | 165 | "单圈回雾:已过远端节点 0(层距 5)不因 visited 豁免〔fv7〕" | not vis.has(0) | (待填) | | | +| 671 | 166 | "单圈回雾:已过中距节点 2(层距 4)不可见〔fv7〕" | not vis.has(2) | (待填) | | | +| 672 | 167 | "单圈只按锚层取窗:已过近端节点 4(层距 2)仍可见〔fv7〕" | vis.has(4) | (待填) | | | -## S97 _smoke_level(行 316–372 · 17 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) +## S97 _test_single_chain_entry(行 176–179 · 3 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 673 | 316 | "L1 现行起关图取数非空(种子 %d × 参数档 → 节点 %d 颗)" ↩ % [ACTIVE_GRAPH_SEED, nodes1.size()] | not nodes1.is_empty() | (待填) | | | -| 674 | 319 | "关卡·满配(atk+200/hp+100)通关(实测:%s @节点 %d)" ↩ % [win.result, win.defeat_node_index] | win.result == "victory" | 关卡满配通关达成。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 675 | 327 | "L1 通关路径逐跳为图出边(入口起步·末节点无出边——句柄轴行进自洽)" | _path_in_graph(nodes1, win.visited_ids) | (待填) | | | -| 676 | 328 | "L1 通关路径战斗/Boss 节点数 = 行进 battles 读数(路径 %d 节点·战斗 %d 颗·battles %d)" ↩ % [win.visited_ids.size(), path_battles, int(win.battles)] | path_battles == int(win.battles) and path_battles >= 2 | (待填) | | | -| 677 | 338 | "L1 终点 Boss 深度 = 参数档层高 %d 且通关路径末节点落该深度(实测终点深度 %d·末句柄 %d)" ↩ % [int(params1.get("layers", -1)), boss_depth1, last_id] | boss_depth1 == int(params1.get("layers", -1)) and int(by_id1.get(last_id, {}).get("depth", -1)) == boss_depth1 | (待填) | | | -| 678 | 340 | "L1 终点恰一个(大岔口自 L2 起的图侧投影)" | _terminal_ids(nodes1).size() == 1 and _ids_of_type(nodes1, "boss").size() == 1 | (待填) | | | -| 679 | 341 | "L1 生成图含岔口(出度 ≥2 节点 %d 处)" ↩ % _fork_ids(nodes1).size() | _fork_ids(nodes1).size() >= 1 | (待填) | | | -| 680 | 342 | "进 Boss HP 携带在域内(%d)" ↩ % win.hp_at_boss | win.hp_at_boss >= 1 and win.hp_at_boss <= int(_db.hero.max_hp) * 3 | 进 Boss 时血量携带在域内。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 681 | 348 | "恢复点公式无源(现行图入口后一位无可直达的恢复候选)" | false | (待填) | | | -| 682 | 351 | "恢复点公式·缺口充分档:回复 = floor(生效max_hp×%d%%) = %d(生效 max %d,实测 %d)" ↩ % [pct1, expect_pct, int(rest_wide["max"]), int(rest_wide["healed"])] | int(rest_wide["healed"]) == expect_pct and expect_pct > 0 | (待填) | | | -| 683 | 355 | "恢复点公式缺口封顶档无源(同图二次驱动未达恢复候选)" | false | (待填) | | | -| 684 | 357 | "恢复点公式·缺口不足档:回复封顶到缺口 2(实测 %d,比例项 %d)" ↩ % [int(rest_narrow["healed"]), expect_pct] | int(rest_narrow["healed"]) == mini(expect_pct, 2) and expect_pct > 2 | (待填) | | | -| 685 | 361 | "关卡·裸装(atk≈1/hp≈10)败北路径可达(实测:%s)" ↩ % lose.result | lose.result == "defeat" | 裸装败北路径可达(Roguelike 基线难度)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 686 | 362 | "败北节点记录在域内(节点 %d,图节点数 %d)" ↩ % [lose.defeat_node_index, nodes1.size()] | lose.defeat_node_index >= 0 and lose.defeat_node_index < nodes1.size() | (待填) | | | -| 687 | 367 | "关卡回放断言:同种子两遍结算步骤流逐行相同" | JSON.stringify(rep1.rows) == JSON.stringify(rep2.rows) | 同种子两遍结算步骤流逐行相同(关卡回放确定性)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 688 | 368 | "关卡步骤流非空(%d 行)" ↩ % rep1.rows.size() | rep1.rows.size() > 0 | 关卡步骤流非空。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 689 | 370 | "模拟后 hero 表恢复基线(atk=%d hp=%d ac=%d——对配置快照)" ↩ % [int(_db.hero.atk), int(_db.hero.max_hp), int(_db.hero.armor_class)] | int(_db.hero.atk) == int(hero_base.atk) and int(_db.hero.max_hp) == int(hero_base.hp) \\ and int(_db.hero.armor_class) == int(hero_base.ac) | 模拟后主角表恢复基线(覆盖不泄漏到后续会话)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 673 | 176 | "单链退化形态:入口锚层 0,句柄 2 可见〔fv1〕" | vis.has(2) | (待填) | | | +| 674 | 177 | "单链退化形态:层距 N+1(句柄 3)不可见〔fv1〕" | not vis.has(3) | (待填) | | | +| 675 | 178 | "单链编号空间 = 图句柄 id:Boss 以句柄 6 入集(链上层距 4 的战斗节点 4 入雾)〔fv1〕" | vis.has(6) and not vis.has(4) | (待填) | | | -## S98 _smoke_level_graph(行 427–747 · 70 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) +## S98 _test_n_from_table(行 188–196 · 3 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 690 | 427 | "LevelTable 行级校验零错误(图合法性移交 validate_graph)" | _db.level.validate().is_empty() | (待填) | | | -| 691 | 431 | "L2 现行起关图取数非空(种子 %d × 参数档 → 节点 %d 颗)" ↩ % [ACTIVE_GRAPH_SEED, nodes2.size()] | not nodes2.is_empty() | (待填) | | | -| 692 | 434 | "L2 节点数 %d ≥ 层高 %d+1 且 = 句柄去重读数 %d(全员句柄唯一)" ↩ % [nodes2.size(), int(params2.get("layers", -1)), _nodes_by_id(nodes2).size()] | nodes2.size() >= int(params2.get("layers", -1)) + 1 and _nodes_by_id(nodes2).size() == nodes2.size() | (待填) | | | -| 693 | 436 | "L2 五型计数合计 = 节点数(battle %d + boss %d + reward %d + rest %d + shop %d = %d)" ↩ % [tcount["battle"], tcount["boss"], tcount["reward"], tcount["rest"], tcount["shop"], nodes2.size()] | int(tcount["battle"]) + int(tcount["boss"]) + int(tcount["reward"]) + int(tcount["rest"]) + int(tcount["shop"]) == nodes2.size() | (待填) | | | -| 694 | 438 | "L2 战斗节点数 %d ≥ 参数档战斗保底 %d 且 Boss 节点数 %d = 参数档出口数 %d" ↩ % [tcount["battle"], int(params2.get("battle_floor", -1)), tcount["boss"], int(params2.get("exit_count", -1))] | int(tcount["battle"]) >= int(params2.get("battle_floor", -1)) and int(tcount["boss"]) == int(params2.get("exit_count", -1)) | (待填) | | | -| 695 | 440 | "L2 恢复/奖励/商店节点各 ≥1(实测 rest %d · reward %d · shop %d——计数由逐节点扫描同一读数)" ↩ % [tcount["rest"], tcount["reward"], tcount["shop"]] | int(tcount["rest"]) >= 1 and int(tcount["reward"]) >= 1 and int(tcount["shop"]) >= 1 | (待填) | | | -| 696 | 442 | "L2 岔口(出度 ≥2 的节点)= %d 处(≥1)" ↩ % branch | branch >= 1 | (待填) | | | -| 697 | 452 | "图校验正例:两岔合流小图零错误〔v1〕" | LT.validate_graph(good).is_empty() | (待填) | | | -| 698 | 455 | "图校验拒绝:句柄重复(图内唯一句柄)〔v1〕" | _has_err_category(LT.validate_graph(dup), "dup_key") | (待填) | | | -| 699 | 458 | "图校验正例:多入口合法(入口唯一判据随模板池退役)〔v1〕" | LT.validate_graph(dual).is_empty() | (待填) | | | -| 700 | 461 | "图校验拒绝:Boss 带出边(终点不可外出)〔v1〕" | _has_err_category(LT.validate_graph(boss_out), "structural") | (待填) | | | -| 701 | 464 | "图校验拒绝:回边 1→0(目标深度须严格大于源)〔v1〕" | _has_err_category(LT.validate_graph(cyc), "structural") | (待填) | | | -| 702 | 467 | "图校验拒绝:出边指向不存在句柄(悬空致命)〔v1〕" | _has_err_category(LT.validate_graph(dang), "dangling_next") | (待填) | | | -| 703 | 473 | "图校验拒绝:非 Boss 终点(终点恒为 Boss)〔v1〕" | _has_err_category(LT.validate_graph(orphan), "structural") | (待填) | | | -| 704 | 476 | "L2·满配 min 路径通关(实测:%s)" ↩ % win_min.result | win_min.result == "victory" | L2 满配 min 路径通关。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 705 | 481 | "L2 min 路径逐跳为图出边·末节点为 Boss(实测路径 %s·末句柄型别 %s)" ↩ % [str(win_min.visited_ids), n2_last_type] | _path_in_graph(nodes2, win_min.visited_ids) and n2_last_type == "boss" | (待填) | | | -| 706 | 483 | "L2 min 路径终点深度 = 参数档层高 %d(行进读数 %d——终点落最长路径步数)" ↩ % [int(params2.get("layers", -1)), int(win_min.boss_depth)] | int(win_min.boss_depth) == int(params2.get("layers", -1)) | (待填) | | | -| 707 | 485 | "L2 min 路径穿商店恰 1 家(路径句柄 %s 型别计数 = 1——必经恰一店的行进面)" ↩ % str(win_min.visited_ids) | _path_shop_count(nodes2, win_min.visited_ids) == 1 | (待填) | | | -| 708 | 487 | "选路口发生 %d 次(每节点兑付后——零自动推进)" ↩ % win_min.choices | win_min.choices >= 3 | 选路口发生≥3 次(每节点兑付后零自动推进)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 709 | 489 | "L2·满配 max 路径(候选取最大句柄)通关(实测:%s·路径 %s)" ↩ % [win_max.result, str(win_max.visited_ids)] | win_max.result == "victory" | (待填) | | | -| 710 | 494 | "L2 max 路径逐跳为图出边·末节点为 Boss·穿商店恰 1 家" | _path_in_graph(nodes2, win_max.visited_ids) and n2_last_max_type == "boss" and _path_shop_count(nodes2, win_max.visited_ids) == 1 | (待填) | | | -| 711 | 497 | "L2 max 与 min 路径分岔(方向轴生效——两路终点句柄 %d / %d 不同)" ↩ % [n2_last, n2_last_max] | n2_last != n2_last_max and win_max.visited_ids != win_min.visited_ids | (待填) | | | -| 712 | 504 | "手动驱动:L2 启动(入口战斗直接开打——入口无前置兑付;图 = 参数档 × 种子 %d)" ↩ % ACTIVE_GRAPH_SEED | runner.start_level(2, ACTIVE_GRAPH_SEED) | (待填) | | | -| 713 | 511 | "首战胜利后:settlement 释放 + 进选路态(战斗胜利不再自动推进——接口漂移清偿)" | String(res_box.get("result", "")) == "victory" and runner.current_battle() == null and runner.awaiting_choice() | 首战胜利后释放战斗并进选路态(不再自动推进)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 714 | 514 | "runner 消费面图 = 生成面图(节点数 %d/%d 且句柄集逐位相同)" ↩ % [rnodes.size(), nodes2.size()] | rnodes.size() == nodes2.size() and _ids_of_type(rnodes, "battle").size() + _ids_of_type(rnodes, "boss").size() + _ids_of_type(rnodes, "reward").size() \\ + _ids_of_type(rnodes, "rest").size() + _ids_of_type(rnodes, "shop").size() == nodes2.size() and _nodes_by_id(rnodes) == _nodes_by_id(nodes2) | (待填) | | | -| 715 | 522 | "入口选路口候选非空且全在图内(候选 %s·类型 %s)" ↩ % [str(cand), str(cand_types)] | not cand.is_empty() and not cand_types.has("<不在图>") | (待填) | | | -| 716 | 523 | "候选下一跳类型两两互异(岔口去路类型不重复契约——实测 %s)" ↩ % str(cand_types) | cand_types.size() == _uniq(cand_types).size() | (待填) | | | -| 717 | 530 | "候选外句柄 %d 拒绝且仍待选(拒绝语义)" ↩ % outside_id | outside_id >= 0 and not runner.pick_next_node(outside_id) and runner.awaiting_choice() | (待填) | | | -| 718 | 540 | "入口岔口含奖励位与战斗位(句柄 %d/%d——候选型别可定位)" ↩ % [reward_cand, battle_cand] | reward_cand >= 0 and battle_cand >= 0 | (待填) | | | -| 719 | 541 | "选奖励候选 %d 受理" ↩ % reward_cand | runner.pick_next_node(reward_cand) | (待填) | | | -| 720 | 542 | "到达奖励点:直通兑付(确认前不出选路口)" | String(runner.current_node().get("type", "")) == "reward" and not runner.awaiting_choice() | 到达奖励点直通兑付(确认前不出选路口)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 721 | 544 | "兑付确认后才出现选路口(continue_after_node 泛化语义)候选 = 该点图出边 %s" ↩ % str(reward_outs) | runner.continue_after_node() and runner.awaiting_choice() and _sorted_ids(runner.next_candidate_ids()) == _sorted_ids(reward_outs) | (待填) | | | -| 722 | 551 | "选下一跳战斗候选 %d 开战" ↩ % next_battle | next_battle >= 0 and runner.pick_next_node(next_battle) and runner.current_battle() != null | (待填) | | | -| 723 | 576 | "首遇位零踩雷:完美档 12 种子(节点 0 熊地精)恒零踩雷" | green_ok | L2 首遇位完美档 12 种子恒零踩雷。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 724 | 577 | "首遇战胜局 ≥7/12(实际 %d——阶段拔高后锚行退出绿档,多数胜口径)" ↩ % green_wins | green_wins >= 7 | L2 首遇战胜局≥7/12(多数胜口径)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 725 | 578 | "首遇战胜后 HP 下探读数 %d(>0——绿档记录)" ↩ % green_min_hp | green_min_hp > 0 | L2 首遇战胜后 HP 下探读数>0(绿档记录)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 726 | 582 | "跨表覆盖扫描面非空:L2 现行图战斗/Boss 节点 %d 颗" ↩ % int(cov2["nodes"]) | int(cov2["nodes"]) >= 1 | (待填) | | | -| 727 | 583 | "L2 现行图每战斗/Boss 节点深度敌侧解析落带 ≥ 请求深度(违例 %d 处)" ↩ % int(cov2["enemy_bad"]) | int(cov2["enemy_bad"]) == 0 | (待填) | | | -| 728 | 584 | "L2 现行图每节点深度 board_gen 精确命中(非空且 node_depth 等值·违例 %d 处)" ↩ % int(cov2["board_bad"]) | int(cov2["board_bad"]) == 0 | (待填) | | | -| 729 | 592 | "巨蜘蛛首遇带(2,battle,%d——扫表取最低带)行集 = {giant_spider} 恰一行且 unit_count=2 无 unit_count_max(固定 2 只——首遇教学位不加骰)〔fv22〕" ↩ % l2_spider_band | l2_spider_band >= 0 and l2_spider_ids.has("giant_spider") and l2_spider_ids.size() == 1 and l2_spider_set.size() == 1 \\ and int(l2_spider_set[0].get("unit_count", -1)) == 2 and not l2_spider_set[0].has("unit_count_max") | 巨蜘蛛首遇带行集=恰一行固定 2 只不加骰。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 730 | 602 | "L2 全部终点深度 %s 的 Boss 行集均 = {drider} 恰一行且首行魔法甲(护甲类型重配批:关卡 2 唯一非轻甲;P4 乙 Boss 行集多行合法——零内容期仍单行)〔fv24〕" ↩ % str(l2_terms) | l2_boss_ok | (待填) | | | -| 731 | 609 | "首批混编行集 (2,battle,5) = 双行且行序 [bugbear 重击手 → giant_spider 巢群](R6 行序契约——文件序 = 声明序 = 生成序,首行默认目标 = 前排坦克;迭代 18 卡⑤投放)〔fv18〕" | mix_shape_ok | 首批混编行集 (2,battle,5) 双行且行序正确。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 732 | 613 | "首批混编行集 (2,battle,5) 静态最坏骰和 = Σ(unit_count_max,固定行缺省=unit_count) = %d ≤ 4(行集实例总数守卫同口径——重击手固定 1 + 巢群 [2,3])〔fv19〕" ↩ % mix_sum_max | mix_sum_max <= 4 | 混编行集静态最坏骰和≤4(实例总数守卫)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 733 | 614 | "首批混编新行 (2,battle,5,giant_spider) 行值 = 复制巢群行(P4 甲零数值新增——2-3 只/hp 26/atk [1,2]/light/ac 2;既有 19 行现值零变更)" | int(l2_mix_set[1].get("unit_count", 0)) == 2 and int(l2_mix_set[1].get("unit_count_max", 0)) == 3 \\ and int(l2_mix_set[1].get("hp", 0)) == 26 and int(l2_mix_set[1].get("armor_class", -1)) == 2 | 混编新行行值=复制巢群行(零数值新增)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 734 | 621 | "L3 现行起关图取数非空(种子 %d × 参数档 → 节点 %d 颗)" ↩ % [ACTIVE_GRAPH_SEED, nodes3.size()] | not nodes3.is_empty() | (待填) | | | -| 735 | 624 | "L3 节点数 %d ≥ 层高 %d+1 且 = 句柄去重读数 %d(全员句柄唯一)" ↩ % [nodes3.size(), int(params3.get("layers", -1)), _nodes_by_id(nodes3).size()] | nodes3.size() >= int(params3.get("layers", -1)) + 1 and _nodes_by_id(nodes3).size() == nodes3.size() | (待填) | | | -| 736 | 626 | "L3 五型计数合计 = 节点数(battle %d + boss %d + reward %d + rest %d + shop %d = %d)" ↩ % [tcount3["battle"], tcount3["boss"], tcount3["reward"], tcount3["rest"], tcount3["shop"], nodes3.size()] | int(tcount3["battle"]) + int(tcount3["boss"]) + int(tcount3["reward"]) + int(tcount3["rest"]) + int(tcount3["shop"]) == nodes3.size() | (待填) | | | -| 737 | 628 | "L3 战斗节点数 %d ≥ 参数档战斗保底 %d 且 Boss 节点数 %d = 参数档出口数 %d" ↩ % [tcount3["battle"], int(params3.get("battle_floor", -1)), tcount3["boss"], int(params3.get("exit_count", -1))] | int(tcount3["battle"]) >= int(params3.get("battle_floor", -1)) and int(tcount3["boss"]) == int(params3.get("exit_count", -1)) | (待填) | | | -| 738 | 630 | "L3 恢复/奖励/商店节点各 ≥1(实测 rest %d · reward %d · shop %d——计数由逐节点扫描同一读数)" ↩ % [tcount3["rest"], tcount3["reward"], tcount3["shop"]] | int(tcount3["rest"]) >= 1 and int(tcount3["reward"]) >= 1 and int(tcount3["shop"]) >= 1 | (待填) | | | -| 739 | 632 | "L3 岔口(出度 ≥2 的节点)= %d 处(≥1)" ↩ % branch3 | branch3 >= 1 | (待填) | | | -| 740 | 633 | "L3 入口数 %d = 参数档 entry_count · 终点数 %d = exit_count ≥2(多终点大岔口自 L2 起)" ↩ % [_entry_ids(nodes3).size(), _terminal_ids(nodes3).size()] | _entry_ids(nodes3).size() == int(params3.get("entry_count", -1)) and _terminal_ids(nodes3).size() == int(params3.get("exit_count", -1)) and _terminal_ids(nodes3).size() >= 2 | (待填) | | | -| 741 | 637 | "跨表覆盖扫描面非空:L3 现行图战斗/Boss 节点 %d 颗" ↩ % int(cov3["nodes"]) | int(cov3["nodes"]) >= 1 | (待填) | | | -| 742 | 638 | "L3 现行图每战斗/Boss 节点深度敌侧解析落带 ≥ 请求深度(违例 %d 处)" ↩ % int(cov3["enemy_bad"]) | int(cov3["enemy_bad"]) == 0 | (待填) | | | -| 743 | 639 | "L3 现行图每节点深度 board_gen 精确命中(非空且 node_depth 等值·违例 %d 处)" ↩ % int(cov3["board_bad"]) | int(cov3["board_bad"]) == 0 | (待填) | | | -| 744 | 646 | "灰矮人首遇带(3,battle,%d——扫表取最低带)行集 = {duergar} 恰一行、首行重甲、固定 1 只不加骰〔fv23〕" ↩ % l3_lead_band | l3_lead_band >= 0 and l3_lead_ids.has("duergar") and l3_lead_ids.size() == 1 \\ and String(l3_lead_set[0].get("armor_type", "")) == "heavy" \\ and int(l3_lead_set[0].get("unit_count", -1)) == 1 and not l3_lead_set[0].has("unit_count_max") | 灰矮人首遇带行集=恰一行重甲固定 1 只。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 745 | 657 | "L3 全部终点深度 %s 的 Boss 行集均 = {duergar_king} 恰一行且首行重甲(重甲收束「轻甲→魔法甲→重甲」教学梯度)〔fv24〕" ↩ % str(l3_terms) | l3_boss_ok | (待填) | | | -| 746 | 680 | "L3 首遇位绿档:成长代理档口径(atk_pct=200/hp_pct=100)完美档 12 种子首遇战(节点 0 灰矮人)全胜且零踩雷" | green3_ok | L3 首遇位成长代理档完美档 12 种子全胜零踩雷。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 747 | 681 | "L3 首遇战胜后 HP 下探读数 %d(>0——绿档记录)" ↩ % green3_min_hp | green3_min_hp > 0 | L3 首遇战胜后 HP 下探读数>0。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 748 | 691 | "混编行集 spawn 受理((1,battle,5) 双行 地精1+熊地精2——定值无区间路径,行集构成纪律放开面正向)" | mix_sys.spawn(1, 5, int(_db.hero.max_hp)) | 混编行集 spawn 受理(正向)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 749 | 693 | "混编实例总数 = 各行数量求和(1+2=3——行集实例总数 ≤4 守卫内)" | mix_units.size() == 3 | 混编实例总数=各行数量求和(守卫内)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 750 | 694 | "混编生成序 = 文件行序(行序即契约——地精在前熊地精在后,handle_id 跨行连续派发)" | String(mix_units[0].get("enemy_id")) == "goblin" and String(mix_units[1].get("enemy_id")) == "bugbear" and String(mix_units[2].get("enemy_id")) == "bugbear" | 混编生成序=文件行序(行序即契约)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 751 | 695 | "混编各行行值独立投影(display_name/hp/atk 按各自行值——同场不同种不同值)" | String(mix_units[0].get("display_name")) == "地精" and int(mix_units[1].get("hp")) == 20 and int(mix_units[1].get("atk")) == 3 | 混编各行行值独立投影(同场不同种不同值)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 752 | 699 | "行集实例总数守卫:两行各定值 3(总 6 > 4)spawn 被拒(行集构成纪律③——R3 fail-fast 运行面)" | not over_sys.spawn(1, 6, int(_db.hero.max_hp)) | 两行各定值 3 总 6>4 的 spawn 被拒(行集纪律 fail-fast 运行面)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 753 | 704 | "混编断言段注入行全量还原(表回 28 行——迭代 35 生成图深度扩面覆盖批后的基线行数;首批混编投放基线 20 行+覆盖 8 行)" | int(_db.enemy.rows.size()) == 28 | (待填) | | | -| 754 | 711 | "可见性冒烟:L2 入口停点就位(defer_entry——无当前节点,锚层 0)" | vis_ok and vis_runner.awaiting_choice() and vis_runner.current_node_id() == -1 | L2 入口停点就位(defer_entry 无当前节点)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 755 | 717 | "可见性冒烟:入口停点候选集 ⊆ 类型可见集(下一跳恒可见)〔fv4〕" | vis_cand_in | 入口停点候选集⊆类型可见集(下一跳恒可见)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 756 | 722 | "可见性冒烟:L2 全部终点 Boss 句柄 %s 恒在可见集(终点不入雾)〔fv3〕" ↩ % str(_ids_of_type(nodes2, "boss")) | vis_boss_ok | (待填) | | | -| 757 | 724 | "可见性冒烟:N 读数据表 = %d(provisional 2——代码零硬编码)〔fv6〕" ↩ % vis_n | vis_n == 2 | 可见层数 N 读数据表(代码零硬编码)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 758 | 746 | "可见性冒烟:入口锚层 0 时层距 ≤ %d 的节点全在可见集(扫描面 %d 颗·违例 0)〔fv1〕" ↩ % [vis_n, near_count] | near_count >= 1 and near_ok | (待填) | | | -| 759 | 747 | "可见性冒烟:入口锚层 0 时层距 > %d 的非终点节点全入雾(扫描面 %d 颗·违例 0)〔fv1〕" ↩ % [vis_n, fog_count] | fog_count >= 1 and fog_ok | (待填) | | | - -## S99 _smoke_gp_wallet(行 784–820 · 9 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 760 | 784 | "GP 到账:L1 启动(普通战档)" | runner.start_level(1, 42) | GP 到账场景 L1 启动受理。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 761 | 794 | "L1 首战胜利(battle_finished gp_dropped 载荷在案)" | fin.has("p") and String(fin["p"].get("result", "")) == "victory" | L1 首战胜利(完成信号 gp_dropped 载荷在案)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 762 | 797 | "L1 普通战 gp_dropped − escrow ∈ [2+0, 4+0](掷定+关级加值 %d,escrow %+d)" ↩ % [gp_dropped - escrow_sum, escrow_sum] | gp_dropped - escrow_sum >= 2 and gp_dropped - escrow_sum <= 4 | L1 普通战 gp_dropped 减 escrow 落在掷定+关级加值窗口。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 763 | 802 | "钱包恰入账一次(gain 事件 ×%d)" ↩ % gains.size() | gains.size() == 1 | 钱包恰入账一次。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 764 | 804 | "入账金额 = gp_dropped 载荷(%d)" ↩ % gp_dropped | int(gains[0]["amount"]) == gp_dropped | 入账金额=gp_dropped 载荷。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 765 | 805 | "钱包读数 = 入账后投影(gp=%d,破财负值场景经 clamp ≥0)" ↩ % runner.gp | runner.gp == maxi(0, gp_dropped) and runner.gp == int(gains[0]["gp"]) | 钱包读数=入账后投影(破财负值经钳制≥0)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 766 | 812 | "L2 Boss 战启动((2,9)——Boss 档掷定 [6,10] + 关级加值 1)" | boss.begin_battle({"level_index": 2, "node_depth": 9, "node_type": "boss", "hero_hp": int(_db.hero.max_hp)}) | L2 Boss 战启动受理(Boss 档掷定+关级加值)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 767 | 817 | "L2 Boss 战终局(%s——gp_dropped 于战斗末掷定上抛)" ↩ % String(boss.battle_result) | fin_b.has("p") | L2 Boss 战终局到达(gp_dropped 战斗末掷定上抛)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 768 | 820 | "L2 Boss 战 gp_dropped − escrow ∈ [6+1, 10+1](掷定+关级加值 %d,escrow %+d)" ↩ % [gp_b - escrow_b, escrow_b] | gp_b - escrow_b >= 7 and gp_b - escrow_b <= 11 | L2 Boss gp_dropped 减 escrow 落在 Boss 档窗口。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | - -## S100 _smoke_gp_escrow(行 858–922 · 12 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 769 | 858 | "GP 袋(gp 型安全格)可挖掘局种子可达(seed=%s)" ↩ % str(scan.get("seed", -1)) | not scan.is_empty() | GP 袋可挖掘局种子可达。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 770 | 871 | "GP局战斗终局(先手 999 收束)" | settlement2.battle_result == "victory" | GP 局战斗终局(先手收束)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 771 | 876 | "GP 袋兑付行在案(GP 袋 +3 → 战斗内缓冲,origin 指回来源格)" | gain_ok | GP 袋兑付行在案(+3 入战斗内缓冲,来源指回格)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 772 | 877 | "escrow 累计缓冲 ≥3(实测 %d——战斗域中间态,不入钱包不入增益容器)" ↩ % int(settlement2.escrow) | int(settlement2.escrow) >= 3 | escrow 累计缓冲≥3(战斗域中间态不入钱包)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 773 | 879 | "格面兑付计入 gp_dropped(%d − escrow %d ∈ [2,4]——战斗末一次到账,随上抛即弃)" ↩ % [gp_d, int(settlement2.escrow)] | gp_d - int(settlement2.escrow) >= 2 and gp_d - int(settlement2.escrow) <= 4 | 格面兑付计入 gp_dropped(战斗末一次到账随上抛即弃)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 774 | 885 | "破财直验战斗启动" | settle_l.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "hero_atk": 999, "node_type": "boss"}) | 破财直验战斗启动受理。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 775 | 889 | "破财 ×3 → escrow −6(负向缓冲记账,零钱包触达)" | int(settle_l.escrow) == -6 | 破财 ×3 → escrow −6(负向缓冲记账零钱包触达)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 776 | 894 | "破财压制 gp_dropped 负值上抛(%d + 6 = %d ∈ Boss 档掷定 [%d,%d] + 关级加值 %d——掷定不受破财污染)" ↩ % [ gp_l, gp_l + 6, int(boss_drop.get("roll_min", 0)), int(boss_drop.get("roll_max", 0)), boss_bonus1] | gp_l + 6 >= int(boss_drop.get("roll_min", 0)) + boss_bonus1 and gp_l + 6 <= int(boss_drop.get("roll_max", 0)) + boss_bonus1 | 破财压制 gp_dropped 负值上抛(掷定本身不受污染)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 777 | 904 | "clamp 场景:L1 启动(冒险钱包开局 0)" | runner.start_level(1, 4242) | clamp 场景 L1 启动(冒险钱包开局 0)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 778 | 916 | "破财压制入账载荷为负(gp_dropped=%d)" ↩ % gp3 | gp3 < 0 | 破财压制入账载荷为负。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 779 | 921 | "破财入账恰一次且投影读数 0(amount=%d,clamp 后 gp=0)" ↩ % gp3 | gain3.size() == 1 and int(gain3[0]["amount"]) == gp3 and int(gain3[0]["gp"]) == 0 | 破财入账恰一次且投影读数 0(钳制后归零)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 780 | 922 | "钱包全程不透支(gp=%d,事件投影最小读数 %d ≥ 0)" ↩ % [runner.gp, int(ev_box["min_gp"])] | runner.gp == 0 and int(ev_box["min_gp"]) >= 0 | 钱包全程不透支(事件投影最小读数≥0)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | - -## S101 _smoke_shop_room(行 988–1074 · 25 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 781 | 988 | "商店房到达种子可达(seed=%s——进店 GP ≥ 16)" ↩ % str(shop_run.get("seed", -1)) | not shop_run.is_empty() | 商店房到达种子可达(进店 GP≥16)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 782 | 1001 | "L2 行进到商店房:当前节点型别 = shop 且出边恰一条指终点 Boss(句柄 %d·出边 %s)" ↩ % [shop_id, str(shop_outs)] | String(runner.current_node().get("type", "")) == "shop" and shop_outs.size() == 1 \\ and String(s_byid.get(int(shop_outs[0]), {}).get("type", "")) == "boss" \\ and (s_byid.get(int(shop_outs[0]), {}).get("next", []) as Array).is_empty() | (待填) | | | -| 783 | 1005 | "L2 到店路径穿商店恰 1 家(已到句柄 %s 型别计数——必经恰一店的行进面)" ↩ % str(runner.visited_ids()) | _path_shop_count(s_nodes, runner.visited_ids()) == 1 | (待填) | | | -| 784 | 1007 | "L2 商店节点(句柄 %d)入边恰一条(店位归终点的直接前驱——行进面复核)" ↩ % shop_id | shop_preds == 1 | (待填) | | | -| 785 | 1009 | "到店商店节点无出边(夹具停摆——去终点方向无法从图解析)" | false | (待填) | | | -| 786 | 1011 | "到店开店态(覆盖层数据源就位——离开前不出选路口,零自动推进)" | not runner.awaiting_choice() and runner.next_candidate_ids().is_empty() | 到店开店态(离开前不出选路口)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 787 | 1014 | "三商品全陈列(atk_up/hp_up/armor_up——无刷新无摇号)" | prods.size() == 3 | 三商品全陈列(无刷新无摇号)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 788 | 1016 | "L2 首购现价 = 基价 + 关级加值 2(atk_up 6 / hp_up 4 / armor_up 6)" | int(by_id["atk_up"]["price"]) == 6 and int(by_id["hp_up"]["price"]) == 4 and int(by_id["armor_up"]["price"]) == 6 | L2 首购现价=基价+关级加值。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 789 | 1019 | "购买 atk_up 受理(现价 6)" | runner.buy_product("atk_up") | 购买 atk_up 受理。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 790 | 1020 | "钱包扣现价 6(%d → %d)" ↩ % [gp0, runner.gp] | runner.gp == gp0 - 6 | 钱包扣现价 6。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 791 | 1022 | "统一冒险加算区 atk +1(bonus={atk,max_hp,armor_class} 账本)" | int(runner.run_ledger.bonus.get("atk", -1)) == 1 and int(runner.run_ledger.bonus.get("max_hp", -1)) == 0 and int(runner.run_ledger.bonus.get("armor_class", -1)) == 0 | 统一冒险加算区 atk +1(RunLedger 账本)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 792 | 1024 | "atk_up 二购现价 = 4+2+2=8(步长 2 × 已购 1 档,现价现算)" | int(by_id["atk_up"]["price"]) == 8 and int(by_id["atk_up"]["purchased"]) == 1 | atk_up 二购现价=步长×已购档(现价现算)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 793 | 1031 | "购买 hp_up 受理(现价 4)" | runner.buy_product("hp_up") | 购买 hp_up 受理。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 794 | 1032 | "钱包扣 4(%d)" ↩ % runner.gp | runner.gp == gp0 - 6 - 4 | 钱包扣 4。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 795 | 1033 | "生命上限 +3(%d → %d——加算区 max_hp 轴)" ↩ % [mhp_before, runner.effective_max_hp()] | runner.effective_max_hp() == mhp_before + 3 | 生命上限 +3(加算区 max_hp 轴)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 796 | 1034 | "当前生命同步 +3 封顶新上限(%d → %d)" ↩ % [hp_before, runner.hero_hp] | runner.hero_hp == mini(hp_before + 3, runner.effective_max_hp()) | 当前生命同步 +3 封顶新上限。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 797 | 1036 | "购买 armor_up 受理(现价 6)" | runner.buy_product("armor_up") | 购买 armor_up 受理。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 798 | 1037 | "AC = 10+1(armor_class 加算区轴,无百分比通道)" | runner.effective_ac() == int(_db.hero.armor_class) + 1 | AC=10+1(armor_class 加算区轴无百分比通道)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 799 | 1052 | "余额不足购买被拒(gp=%d < 现价 %d——零扣费零档位变动)" ↩ % [gp_dry, hp_dry] | not runner.buy_product("hp_up") and runner.gp == gp_dry and runner.purchased_tier("hp_up") == tier_dry | 余额不足购买被拒(零扣费零档位变动)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 800 | 1054 | "离开商店 → 回选路口(候选 = 店出边 %s——零自动推进收口)" ↩ % str(shop_outs) | runner.continue_after_node() and runner.awaiting_choice() and _sorted_ids(runner.next_candidate_ids()) == _sorted_ids(shop_outs) | (待填) | | | -| 801 | 1057 | "主角攻击生效值 = 2+1(基础表值 + 加算区)" | runner.effective_atk() == int(_db.hero.atk) + 1 | 主角攻击生效值=基础表值+加算区。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 802 | 1058 | "选终点 Boss(句柄 %d)开战" ↩ % int(shop_outs[0]) | runner.pick_next_node(int(shop_outs[0])) and runner.current_battle() != null | (待填) | | | -| 803 | 1062 | "战斗开始参数 = 基础表值 + 加算区(atk %d = 2+1 / max_hp %d = %d+%d×3)" ↩ % [int(snap["hero"].get("atk", -1)), int(snap["hero"].get("max_hp", -1)), int(_db.hero.max_hp), hp_buys] | int(snap["hero"].get("atk", -1)) == int(_db.hero.atk) + 1 and int(snap["hero"].get("max_hp", -1)) == int(_db.hero.max_hp) + 3 * hp_buys | 战斗开始参数=基础表值+加算区(按实际购买笔数推导)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 804 | 1073 | "冒险收束(Boss 战终局:%s)" ↩ % String(fin_end.get("p", {}).get("result", "")) | fin_end.has("p") and String(fin_end["p"].get("result", "")) in ["victory", "defeat"] | 冒险收束到达(Boss 战终局)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 805 | 1074 | "冒险收束钱包清零(局内货币生命周期终点——不入存档不入摘要)" | runner.gp == 0 | 冒险收束钱包清零(局内货币生命周期终点)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | - -## S102 _smoke_shop_graph(行 1096–1153 · 9 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 806 | 1096 | "L%d 参数档缺行(商店编排断言无源可扫)" ↩ % L | false | (待填) | | | -| 807 | 1101 | "L%d 种子 %d 生成图为空(编排断言扫描面空转)" ↩ % [L, seed_val] | false | (待填) | | | -| 808 | 1127 | "编排断言扫描面非空(三关参数档 × 种子 %d–%d=图 %d 张·终点 %d·路径 %d)〔v5〕" ↩ % [SWEEP_SEED_FROM, SWEEP_SEED_TO - 1, graph_total, term_total, path_total] | graph_total == 3 * (SWEEP_SEED_TO - SWEEP_SEED_FROM) and term_total >= 3 and path_total >= 3 | (待填) | | | -| 809 | 1129 | "性质① 每个终点的直接前驱恰一家商店(入边一条且源型别=shop·违例 %d/%d 终点)〔v5〕" ↩ % [pred_bad, term_total] | pred_bad == 0 | (待填) | | | -| 810 | 1130 | "性质② 每图商店节点数 = 终点数(违例 %d/%d 图)〔v5〕" ↩ % [count_bad, graph_total] | count_bad == 0 | (待填) | | | -| 811 | 1131 | "性质③ 每条入口→终点路径恰穿一家商店(违例 %d/%d 条路径)〔v5〕" ↩ % [path_bad, path_total] | path_bad == 0 | (待填) | | | -| 812 | 1137 | "L%d 现行图终点 %d 个 = 商店 %d 个·入口→终点路径 %d 条全穿恰一店" ↩ % [L, cur_terms.size(), cur_shops.size(), _all_entry_terminal_paths(cur_nodes).size()] | cur_terms.size() == cur_shops.size() and not cur_terms.is_empty() and _all_entry_terminal_paths(cur_nodes).size() >= cur_terms.size() and _paths_all_one_shop(cur_nodes) | (待填) | | | -| 813 | 1149 | "L%d 现行图终点深度 %s 逐点 Boss 行集解析命中且落带 ≥ 终点深度" ↩ % [L, str(tdepths)] | hit_ok | (待填) | | | -| 814 | 1152 | "L1 现行图跨表覆盖(战斗/Boss 节点 %d 颗非空·敌侧违例 %d·板侧违例 %d)" ↩ % [int(l1_cov["nodes"]), int(l1_cov["enemy_bad"]), int(l1_cov["board_bad"])] | int(l1_cov["nodes"]) >= 1 and int(l1_cov["enemy_bad"]) == 0 and int(l1_cov["board_bad"]) == 0 | (待填) | | | - -## S103 _smoke_multi_terminal_settlement(行 1161–1208 · 5 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 815 | 1161 | "L2 现行图为多终点(Boss 终点 %s 两颗以上——大岔口承载面)" ↩ % str(boss_ids) | boss_ids.size() >= 2 | (待填) | | | -| 816 | 1173 | "L2 min/max 两路各打进各自终点 Boss(末句柄 %d / %d 互异且都在图 Boss 集 %s 内;两路 result=%s/%s)" ↩ % [min_last, max_last, str(boss_ids), String(run_min.get("result", "")), String(run_max.get("result", ""))] | String(run_min.get("result", "")) == "victory" and String(run_max.get("result", "")) == "victory" and boss_ids.has(min_last) and boss_ids.has(max_last) and min_last != max_last | (待填) | | | -| 817 | 1177 | "min 路未到 max 终点 %d、max 路未到 min 终点 %d(区域隔离——走 A 区不误兑付 B 区 Boss)" ↩ % [max_last, min_last] | not min_vis.has(max_last) and not max_vis.has(min_last) | (待填) | | | -| 818 | 1184 | "L2 全程驱动启动受理" | runner.start_level(2, 42) | (待填) | | | -| 819 | 1206 | "任一 Boss 胜即本关收束恰一次(count=%d·载荷 result=%s·node_id=%s 在图 Boss 集 %s 内)" ↩ % [int(fin["count"]), String(payload.get("result", "")), str(payload.get("node_id", -1)), str(boss_ids)] | int(fin["count"]) == 1 and String(payload.get("result", "")) == "victory" and boss_ids.has(int(payload.get("node_id", -1))) | (待填) | | | - -## S104 _smoke_shop_pool(行 1243–1373 · 28 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 820 | 1243 | "池模式商店房场景可达(seed=%s)" ↩ % str(scen.get("s", -1)) | not scen.is_empty() | 池模式商店房场景种子可达。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 821 | 1251 | "进店抽 3(货架 %d 卡——同构交互)" ↩ % first_shelf_a.size() | first_shelf_a.size() == 3 | 进店抽 3(货架 3 卡同构交互)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 822 | 1257 | "刷新首价=基价(%d)" ↩ % cost0 | cost0 == int(_db.adventure.shop_refresh_cost_base) | 刷新首价=基价。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 823 | 1258 | "基础免费 0 次(Q2——天赋增益位后续添加)" | r8.shop_refresh_free_left() == 0 | 基础免费 0 次。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 824 | 1260 | "GP 刷新受理(gp=%d cost=%d)" ↩ % [gp0, cost0] | r8.shop_refresh() | GP 刷新受理。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 825 | 1261 | "GP 刷新扣费(%d→%d)" ↩ % [gp0, int(r8.gp)] | int(r8.gp) == gp0 - cost0 | GP 刷新扣费。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 826 | 1262 | "刷新爬价 ×curve(%d)" ↩ % int(r8.shop_refresh_cost()) | int(r8.shop_refresh_cost()) == int(ceil(float(cost0) * float(_db.adventure.shop_refresh_cost_curve))) | 刷新爬价×curve 现算。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 827 | 1270 | "同种子同货架逐位(确定性重放承载——断点货架续玩语义)" | same_shelf | 同种子同货架逐位(断点货架续玩=确定性重放)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 828 | 1282 | "货架 kind 在封闭词表或保底 relic 卡(%s)" ↩ % ckind | is_relic or shelf_kinds.has(ckind) | 货架 kind 在封闭词表或保底 relic 卡。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 829 | 1289 | "首购现价 = ceil(基价×b^(关序−1))(%s: %d = %d 推导)" ↩ % [ckind, int(c.get("price", -1)), expect_p] | int(c.get("price", -1)) == expect_p | 首购现价=ceil(基价×关序曲线)(核心现算呈现零复算)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 830 | 1296 | "可购卡在架(GP %d——种子扫描保证首购空间)" ↩ % int(r8.gp) | not buy_c.is_empty() | 可购卡在架(种子扫描保证首购空间)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 831 | 1304 | "购买受理(pool 口——%s\|%s)" ↩ % [kind, tier] | r8.buy_pool_item(kind) | 购买受理(pool 口)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 832 | 1305 | "钱包扣现价(%d → %d)" ↩ % [gp0, int(r8.gp)] | int(r8.gp) == gp0 - int(buy_c.get("price", 0)) | 钱包扣现价。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 833 | 1306 | "加算区 +令牌档位件值(池件=奖励件本体——%s\|%s %+d)" ↩ % [kind, tier, int(_db.adventure.curio_value(kind, tier))] | int(a8.run_ledger.bonus.get(String(val_map[kind]), -999)) == val0 + int(_db.adventure.curio_value(kind, tier)) | 加算区+令牌档位件值(池件=奖励件本体)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 834 | 1307 | "共享池账本 -1(跨渠道单点——taken %d → %d)" ↩ % [taken0, int(a8._curio_taken.get(kind, 0))] | int(a8._curio_taken.get(kind, 0)) == taken0 + 1 | 共享池账本 −1(跨渠道单点)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 835 | 1319 | "A 链爬价 = ceil(base×b^(关序−1)×A^1)(进店第 2 购——%d)" ↩ % expect_p2 | int(second.get("price", -1)) == expect_p2 | A 链爬价=公式含 A^1(进店第 2 购)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 836 | 1320 | "同 visit 第二购受理(A 链软闸在价不在限)" | r8.buy_pool_item(String(second.get("kind_id", ""))) | 同 visit 第二购受理(A 链软闸在价不在限)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 837 | 1322 | "A 链爬价场景未构造(抬 GP 后货架仍无可购第二卡)——场景绑定失败" | false | 场景绑定失败显式红(核代码增补)——白盒抬 GP 后货架必无可购第二卡时红。 | 核代码增补(tests/unit/core/level_shop_economy_test.gd) | | -| 838 | 1330 | "切边件零入店(40 次重抽零 gp/aux——负交易切边+三轴 doctrine)" | not cut_seen | 切边件零入店(40 次重抽零 GP 袋/aux)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 839 | 1341 | "库存尽剔牌库(attack 令牌耗尽——池消耗即上限跨渠道共库 reward→shop 向)" | not attack_seen | 库存尽剔牌库(池消耗即上限跨渠道共库)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 840 | 1343 | "池空消费拒绝(handler 零副作用——买光即枯竭硬闸)" | not a8._consume_pool_purchase("attack", "common") and a8.pool_stock("attack") == stock_before | 池空消费拒绝(handler 零副作用买光即枯竭)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 841 | 1346 | "商店消费口受理(_apply_curio_pick 同口——真单点库)" | a8._consume_pool_purchase("attack", "common") | 商店消费口受理(真单点库)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 842 | 1352 | "商店购买进奖励牌库摘牌(买一件=奖励面少一件——shop→reward 向共库)" | not reward_attack_seen | 商店购买进奖励牌库摘牌(买一件=奖励面少一件)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 843 | 1357 | "离开商店 → 回选路口(货架态清零落档——零自动推进收口)" | r8.continue_after_node() and r8.awaiting_choice() | 离开商店回选路口(货架态清零落档)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 844 | 1359 | "断点扩键 shop_shelf 在案(离开店=空架合法态——P2 甲扩列)" | bp.has("shop_shelf") and (bp["shop_shelf"] as Array).is_empty() | 断点扩键 shop_shelf 在案(离开店=空架合法态)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 845 | 1360 | "断点扩键 shop_visit_buys 在案(离开店清零——A 链输入不入档语义)" | bp.has("shop_visit_buys") and int(bp["shop_visit_buys"]) == 0 | 断点扩键 shop_visit_buys 在案(离开店清零)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 846 | 1365 | "终局池读数六键出讫(run_adventure pool 面——四池种+T5 专长凭证+T6 heal 行,读数只锚一次载体)" | ps.size() == 6 | 终局池读数六键出讫。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | -| 847 | 1373 | "终局池余量在域 [0,配额](%s)" ↩ % str(ps) | ps_ok | 终局池余量在 [0,配额] 域内。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | - -## S105 _test_positive(行 46–56 · 3 条 · 源 game/tests/unit/core/level_table_validate_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 848 | 46 | "四检正例:多入口多终点图零错误〔v1〕" | _errs(multi).is_empty() | (待填) | | | -| 849 | 53 | "四检正例:含岔口单终点图零错误〔v1〕" | _errs(forked).is_empty() | (待填) | | | -| 850 | 56 | "四检正例:单链退化图零错误(不设必有岔口)〔v1〕" | _errs(chain).is_empty() | (待填) | | | - -## S106 _test_negative(行 63–89 · 11 条 · 源 game/tests/unit/core/level_table_validate_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 851 | 63 | "负例:终点非 Boss 命中「终点恒为 Boss」〔v1〕" | _has(_errs(non_boss_end), "structural", "终点 1 非 Boss") | (待填) | | | -| 852 | 66 | "负例:Boss 带出边被拒〔v1〕" | _has(_errs(boss_out), "structural", "Boss 节点 1 带出边") | (待填) | | | -| 853 | 68 | "负例:回边(目标深度不严格大于源)命中前进方向判据〔v1〕" | _has(_errs(back_edge), "structural", "违反前进方向") | (待填) | | | -| 854 | 71 | "负例:环(2→1 回边)被前进方向拦下〔v1〕" | _has(_errs(cycle), "structural", "违反前进方向") | (待填) | | | -| 855 | 77 | "判据②正证:两独立链(双入口双终点)不误伤·全员可达〔v1〕" | _errs(two_components).is_empty() | (待填) | | | -| 856 | 79 | "判据②兜底面:自环先被前进方向拦下(④ 与 ② 互斥的实证)〔v1〕" | _has(_errs(self_loop), "structural", "违反前进方向") | (待填) | | | -| 857 | 82 | "负例:悬空出边报 dangling_next〔v1〕" | _has(_errs(dangling), "dangling_next") | (待填) | | | -| 858 | 84 | "负例:句柄重复报 dup_key〔v1〕" | _has(_errs(dup), "dup_key") | (待填) | | | -| 859 | 86 | "负例:battle 节点缺深度被良构基底拒〔v1〕" | _has(_errs(no_depth), "structural", "缺深度") | (待填) | | | -| 860 | 87 | "负例:空节点集被拒〔v1〕" | _has(_errs([]), "structural", "图节点集为空") | (待填) | | | -| 861 | 89 | "负例:类型出域报 enum〔v1〕" | _has(_errs(bad_type), "enum") | (待填) | | | - -## S107 _test_scope_not_extended(行 97–102 · 3 条 · 源 game/tests/unit/core/level_table_validate_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 862 | 97 | "范围反证:无商店图不在加载校验内报错(商店位由冒烟断言把关)〔v1〕" | _errs(no_shop).is_empty() | (待填) | | | -| 863 | 99 | "范围反证:双入口合法(入口唯一判据已随模板池退役,个数归产出侧)〔v1〕" | _errs(two_entries).is_empty() | (待填) | | | -| 864 | 102 | "范围反证:节点缺 next 字段仍被良构基底拒〔v1〕" | _has(_errs(missing_key), "structural", "缺字段 next") | (待填) | | | - -## S108 _meta_pricing_and_buy(行 138–151 · 4 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 865 | 138 | "树购买受理(Lv 0→1、余额 %d−%d)" ↩ % [bal0, price0] | mp.tree_buy("sp_hp") and sv.tree_level("sp_hp") == 1 and sv.keystone_balance() == bal0 - price0 | 树购买受理(Lv0→1、余额扣表价)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 866 | 141 | "购买持久(reload 后级与余额不变)" | sv_r.tree_level("sp_hp") == 1 and sv_r.keystone_balance() == bal0 - price0 | 购买持久(重载后级与余额不变)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 867 | 148 | "余额不足 false 零副作用(0 余额购买拒绝——三域零变化〔天赋树批 V3〕)" | not mp_dry.tree_buy("sp_hp") and sv_dry.keystone_balance() == 0 and sv_dry.tree_nodes() == tn_before and sv_dry.fork_sides() == fs_before | 余额不足 false 零副作用(三域零变化)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 868 | 151 | "未知节点 false 零副作用(词表外出域)" | not mp_dry.tree_buy("no_such_node") | 未知节点 false 零副作用(词表外出域)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | - -## S109 _meta_settle_and_workshop(行 168–341 · 42 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 869 | 168 | "新档首通 L1:boss_drops[0] {first:true, amount=first_kill}(验收5——明细随摘要归档)" | bool(drop1.get("first", false)) and int(drop1.get("amount", 0)) == int(row1.get("first_kill", -1)) and int(drop1.get("level", 0)) == 1 | 新档首通 L1:掉落明细 {first:true, 金额=首杀值}。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 870 | 171 | "明细 total = keystone_total(逐杀掉落全源——无二次派生加账)" | int(d1.get("total", 0)) == int(row1.get("first_kill", 0)) | 明细 total=keystone_total(无二次派生加账)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 871 | 173 | "余额 = 累计入账(逐杀掉落合计——无重复加账)" | sv.keystone_balance() == int(row1.get("first_kill", 0)) | 余额=累计入账(无重复加账)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 872 | 181 | "再通 L1:{first:false, amount=repeat_kill}(首杀标记置位后转小额)" | not bool(drop2.get("first", true)) and int(drop2.get("amount", 0)) == int(row1.get("repeat_kill", -1)) | 再通 L1:{first:false, 金额=重复杀值}(标记置位后转小额)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 873 | 187 | "重复杀第三轮恒定不递减(验收5——无衰减曲线)" | not bool(d3["boss_drops"][0].get("first", true)) and int(d3["boss_drops"][0].get("amount", 0)) == int(row1.get("repeat_kill", -1)) | 重复杀第三轮恒定不递减(无衰减曲线)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 874 | 195 | "两关通关逐关归因:L1 重复档 + L2 首杀档(first_kill 金额)" | not bool(d4["boss_drops"][0].get("first", true)) and bool(d4["boss_drops"][1].get("first", false)) and int(d4["boss_drops"][1].get("amount", 0)) == int(row2.get("first_kill", -1)) | 两关通关逐关归因(重复档+首杀档)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 875 | 203 | "败北零通关摘要:零入账零标记(钥石只增不减——验收1;败北局 clear_counts 不变)" | dz["boss_drops"].is_empty() and sv.keystone_balance() == bal_before and sv.clear_count(1) == cc1_before | 败北零通关摘要:零入账零标记、通关次数不变。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 876 | 210 | "条件件误购拒绝(购买件专属——契约 8 分配面)〔秘宝批 V3 反例〕" | not mp_u.buy_treasure_unlock("sky_eye") and not sv_u.item_unlocked("sky_eye") and not mp_u.buy_treasure_unlock("ranged_pick") | 条件件误购拒绝(购买件专属)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 877 | 213 | "余额不足购买拒绝且零副作用(余额 10 < 价 25)〔秘宝批 V3〕" | not mp_u.buy_treasure_unlock("twin_loader") and sv_u.keystone_balance() == 10 | 余额不足购买拒绝且零副作用。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 878 | 215 | "钥石购买解锁:余额足→解锁态落档+余额扣减正确(价 10)〔秘宝批 V3〕" | mp_u.buy_treasure_unlock("morning_order") and sv_u.item_unlocked("morning_order") and sv_u.keystone_balance() == 0 | 钥石购买解锁:解锁态落档+余额扣减。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 879 | 218 | "已解锁重复购买拒绝〔秘宝批 V3〕" | not mp_u.buy_treasure_unlock("morning_order") | 已解锁重复购买拒绝。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 880 | 220 | "条件未达不解锁(远程镐=通关 1 次)〔秘宝批 V4〕" | mp_u.refresh_treasure_unlocks().is_empty() and not sv_u.item_unlocked("ranged_pick") | 条件未达不解锁。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 881 | 223 | "远程镐=adventure_cleared≥1 达标恰解锁一次+重复刷新幂等(C6:通关=打通整局冒险)〔秘宝批 V4〕" | (mp_u.refresh_treasure_unlocks() as Array).has("ranged_pick") and sv_u.item_unlocked("ranged_pick") and mp_u.refresh_treasure_unlocks().is_empty() | 达标恰解锁一次+重复刷新幂等。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 882 | 227 | "刷新执照=boss_kill_count≥3(阈值读 unlock_requires 行值不硬编)〔秘宝批 V4〕" | (mp_u.refresh_treasure_unlocks() as Array).has("refresh_license") | 刷新执照=Boss 击杀数达标(阈值读行值不硬编)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 883 | 230 | "天眼单条件不足不解锁(AND 语义——cleared 3 达 boss 4 未达)〔秘宝批 V4〕" | not sv_u.item_unlocked("sky_eye") | 天眼单条件不足不解锁(AND 语义)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 884 | 233 | "天眼双条件 AND(通关≥3 且 Boss≥4)达标解锁〔秘宝批 V4〕" | (mp_u.refresh_treasure_unlocks() as Array).has("sky_eye") | 天眼双条件 AND 达标解锁。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 885 | 236 | "信标网络=真视类工具累计使用≥10(行为计数新域——use_treasure 受理成功累计)〔秘宝批 V4〕" | (mp_u.refresh_treasure_unlocks() as Array).has("beacon_network") | 信标网络=真视类工具累计使用达标解锁(行为计数新域)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 886 | 241 | "解锁态持久投影一致(reload 面同源读数;5 件=购买 1+条件 4)〔秘宝批 V4〕" | r_n == 5 | 解锁态持久投影一致(5 件=购买 1+条件 4)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 887 | 244 | "摘要最新在前 + 元素含掉落明细副本(复盘可归因——验收5)" | rs.size() >= 5 and String(rs[0].get("result", "")) != "" and rs[0].has("boss_drops") and (rs[1] as Dictionary).has("keystone_drops") and int(rs[0].get("master_seed", -1)) == 15 | 摘要最新在前+元素含掉落明细副本。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 888 | 253 | "摘要 cap 10 且最新在前(12 连 settle 截断后 seed 11 居首)" | sv_c.recent_summaries().size() == 10 and int(sv_c.recent_summaries()[0].get("master_seed", -1)) == 11 | 摘要上限 10 且最新在前(12 连收束截断)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 889 | 258 | "白板档同调位求值 = 0(0 起步裁决)且 Y=0(未购无刷新能力——P1 甲初始 0 同构)" | Attunement.slots(_db, sv_a) == 0 and int(mp_a.attune_camp_status()["refresh"]["charges"]) == 0 | 白板档同调位求值 0 且刷新次数 Y=0。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 890 | 260 | "满位禁选(Slots 0 时装配拒绝;兼未解锁拒绝前置——直置解锁后下方装配)〔秘宝批 V5〕" | mp_a.toggle_attunement("morning_order") == false | 满位禁选(Slots 0 装配拒绝)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 891 | 265 | "Y 升级购买入账(Lv1 → 每冒险 +1 次刷新)" | mp_a.buy_attune_refresh() and sv_a.attune_refresh_level() == 1 and int(mp_a.attune_camp_status()["refresh"]["charges"]) == 1 | Y 升级购买入账(Lv1→+1 次刷新)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 892 | 270 | "通关 1 关 → Slots 恒 0(旧「按击杀 Boss 次数自动涨」条件已删——槽位唯一来源=树点亮)且 Boss 击杀总数=1(计数域保留)〔天赋树批 V14〕" | Attunement.slots(_db, sv_a) == 0 and sv_a.boss_kill_count() == 1 | 通关 1 关 Slots 恒 0(槽位唯一来源=树点亮)且 Boss 击杀计数=1。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 893 | 273 | "树上点亮同调位 1 → Slots=1(槽位收编树节点——Q2 裁甲)" | Attunement.slots(_db, sv_a) == 1 | 树上点亮同调位→Slots=1。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 894 | 275 | "稀有件入普通格拒绝(件档 rank 3 > 装入位序 1——契约 2 档位格校验)〔秘宝批 V5〕" | not mp_a.toggle_attunement("sky_eye") | 稀有件入普通格拒绝(档位格校验)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 895 | 277 | "装配持久写入 + 满位禁选(Slots 1 装第二件拒绝)〔秘宝批 V5 换主语〕" | mp_a.toggle_attunement("morning_order") and mp_a.toggle_attunement("twin_loader") == false | 装配持久写入+满位禁选。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 896 | 279 | "卸下恒可再装配(自卸自装——营地换装=冒险开始前合法面,装配不可变只锁运行中)〔秘宝批换主语〕" | mp_a.toggle_attunement("morning_order") and mp_a.toggle_attunement("morning_order") | 卸下恒可再装配(自卸自装合法面)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 897 | 284 | "装配关系读回持久(reload 后投影一致——写新件 id)〔秘宝批 V15〕" | (mp_a2.attune_camp_status()["equipped"] as Array).has("morning_order") | 装配关系读回持久(重载投影一致)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 898 | 288 | "充能重置场景:装配冒险启动(seed 12)〔秘宝批 V8 换主语〕" | a12.start_adventure(12, {"attunement": ["incense_stick", "sky_eye"]}) | 充能重置场景装配冒险启动。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 899 | 290 | "充能账本按装备 active_charged 件重置(新目录表值:线香 battle 级 1 次/天眼 level 级 1 次——周期重置点归 V8 逐形态面)" | int(a12.run_ledger.attune_charges.get("incense_stick", -1)) == 1 and int(a12.run_ledger.attune_charges.get("sky_eye", -1)) == 1 | 充能账本按装备件重置(表值周期)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 900 | 296 | "刷新双计数器——X 优先消耗(x1y2 → x0y2)" | a12.refresh_curio() and a12._attune_refresh_x == 0 and a12._attune_refresh_y == 2 | 刷新双计数器 X 优先消耗。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 901 | 298 | "刷新双计数器——X 耗尽转 Y 池、池竭拒绝(x0y2 → y0 → 第四次 false 零副作用)" | a12.refresh_curio() and a12.refresh_curio() and a12._attune_refresh_y == 0 and a12.refresh_curio() == false | 刷新双计数器 X 耗尽转 Y 池、池竭拒绝。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 902 | 302 | "刷新 X 每碰三选一重置(不结转;新目录无 bonus_refresh 行源——X 机制保留恒 0 空转,存量加仓走 _refresh_stock=刷新练习 V8)〔秘宝批换代〕" | a12._attune_refresh_x == 0 and a12.attune_refresh_left() == 0 | 刷新 X 每碰三选一重置(不结转)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 903 | 313 | "开局传参夹具前置:Fork 侧 a 激活 rule 键非空 + Y=Lv2 行表值 2(防断言空转)" | rules_e.has("posture_first_strike") and mp_e.attune_refresh_charges() == 2 | 开局传参夹具前置(Fork 激活+Y 档表值防空转)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 904 | 317 | "主菜单开新冒险参数面:同调装配 = 存档装配集(营地装了什么开局就带什么)" | (p_new.get("attunement", []) as Array) == (sv_e.attunement_equipped() as Array) | 开新冒险参数面:同调装配=存档装配集。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 905 | 319 | "主菜单开新冒险参数面:Y 刷新池 = 当前等级行表值(营地升几档开局就给几次)" | int(p_new.get("attune_refresh_y", -1)) == mp_e.attune_refresh_charges() | 开新冒险参数面:Y 刷新池=当前等级行表值。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 906 | 321 | "主菜单开新冒险参数面:树 rule 键集 = Fork 激活侧 effects(点哪侧带哪侧)" | (p_new.get("tree_rules", []) as Array) == rules_e | 开新冒险参数面:树 rule 键集=Fork 激活侧。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 907 | 323 | "主菜单开新冒险参数面:入口特有键(treasure_unlocked/shop_mode)随 extra 原样并入" | bool((p_new.get("treasure_unlocked", {}) as Dictionary).get("wide_scout_band", false)) and String(p_new.get("shop_mode", "")) == "pool" | (待填) | | | -| 908 | 327 | "主菜单继续上局参数面:三键与开新冒险同源同值(续玩不吃白板开局)" | (p_resume.get("attunement", []) as Array) == (sv_e.attunement_equipped() as Array) and int(p_resume.get("attune_refresh_y", -1)) == mp_e.attune_refresh_charges() and (p_resume.get("tree_rules", []) as Array) == rules_e | 续玩参数面三键与开新冒险同源同值。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 909 | 338 | "继续上局受理面:resume_adventure 受理三键参数面(尾裁微批b 受理闭环——受理不再吃白板)" | adr_res.resume_adventure(bp_min, p_resume) | 续玩受理面:resume_adventure 受理三键参数面。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 910 | 340 | "继续上局受理面:tree_rules 随参数面进 runner._tree_rules(消费面 set_tree_rules 下传现成)" | (adr_res._tree_rules as Array) == rules_e | tree_rules 随参数面进 runner(消费面下传)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | - -## S110 _meta_injection(行 353–383 · 10 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 911 | 353 | "生命淬炼 2 级 → hero_override().max_hp == Σ 节点 effects×等级(验收4 换代〔天赋树批 V12〕)" | int(mp.hero_override().get("max_hp", -1)) == 2 * int(_db.tree_node("sp_hp").get("effects", {}).get("max_hp", 0)) | 生命淬炼 2 级 → 永久强化 max_hp=Σ节点效果×等级。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 912 | 355 | "攻/AC 树零来源(契约 9——树表零 atk/armor_class 行,override 二轴恒 0+预留位 0)" | int(mp.hero_override().get("atk", -1)) == 0 and int(mp.hero_override().get("armor_class", -1)) == 0 | 攻/AC 树零来源(override 二轴恒 0)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 913 | 357 | "命中线在账未注入(契约 6——override 无 hit 字段〔天赋树批 V13〕)" | not mp.hero_override().has("hit") | 命中线在账未注入(override 无 hit 字段)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 914 | 360 | "注入参数冒险启动(start_adventure params 通道)" | adr.start_adventure(920251, {"hero_override": mp.hero_override()}) | 注入参数冒险启动受理。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 915 | 364 | "首战 hero_atk = 表值 + 永久强化 flat(begin_battle 同表达式——下一局能用到强化)" | int(r.effective_atk()) == int(_db.hero.atk) + int(ov.get("atk", 0)) | 首战 hero_atk=表值+永久强化 flat。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 916 | 366 | "首战 hero_max_hp 与开局生命账本 = 表值 + 注入档" | int(r.effective_max_hp()) == int(_db.hero.max_hp) + int(ov.get("max_hp", 0)) and int(adr.ledger["hero_hp"]) == int(_db.hero.max_hp) + int(ov.get("max_hp", 0)) | 首战上限与开局生命账本=表值+注入档。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 917 | 371 | "白板冒险启动(无 params 实参——B1 空参数路径)" | adr_w.start_adventure(920251) | 白板冒险启动(无参数路径)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 918 | 374 | "零强化参数冒险启动(新档 hero_override 全零)" | adr_z.start_adventure(920251, {"hero_override": load("res://core/meta_progression.gd").new(_db, sv_white).hero_override()}) | 零强化参数冒险启动(新档全零)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 919 | 378 | "白板成立:空参数 = 零强化参数 = 纯表值(atk/max_hp/ac 三轴逐位一致——验收 2/3)" | int(rw.effective_atk()) == int(_db.hero.atk) and int(rw.effective_max_hp()) == int(_db.hero.max_hp) and int(rz.effective_atk()) == int(rw.effective_atk()) and int(rz.effective_max_hp()) == int(rw.effective_max_hp()) and int(rz.effective_ac()) == int(rw.effective_ac()) | 白板成立:空参数=零强化参数=纯表值(三轴逐位)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 920 | 382 | "ac 轴:白板恒表值、未换强化时注入零位移" | int(rw.effective_ac()) == int(_db.hero.armor_class) and int(r.effective_ac()) == int(_db.hero.armor_class) | ac 轴:白板恒表值、注入零位移。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | - -## S111 _smoke_forge_axes(行 392–540 · 33 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 921 | 392 | "hero.tres 无 attack_type/armor_type 字段(契约 2——静态表两轴删除)〔锻造工坊批 V5〕" | not ("attack_type" in _db.hero) and not ("armor_type" in _db.hero) | 主角静态表无两类型轴字段(轴所有权移交锻造配置)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 922 | 395 | "锻造配置默认值 attack_type=normal / armor_type=light(常驻持有)〔锻造工坊批 V5〕" | _db.forge_default_attack_type() == "normal" and _db.forge_default_armor_type() == "light" | 锻造配置默认值 normal/light(常驻持有与解锁解耦)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 923 | 399 | "spawn 受理(三参形态——两轴缺省回退面)" | uc.spawn(1, 0, int(_db.hero.max_hp)) | spawn 三参缺省形态受理(回退面)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 924 | 400 | "主角单位快照两轴 = 锻造配置默认值 normal/light(零注入回退——行为连续)〔锻造工坊批 V5〕" | String(uc.hero_unit().get("attack_type", "")) == "normal" and String(uc.hero_unit().get("armor_type", "")) == "light" | 主角单位快照两轴=锻造配置默认值。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 925 | 410 | "长剑锻造首购:扣 20 钥石、等级 0→1(Q1——第一级=获得装备=首个 +1)〔锻造工坊批 V2〕" | mp2.forge_buy_level("sword") and sv2.forge_route_level("sword") == 1 and sv2.keystone_balance() == 80 | 长剑首购:扣 20 钥石、等级 0→1。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 926 | 414 | "购买持久(reload 后等级与余额不变——即时写盘)〔锻造工坊批 V2〕" | sv2r.forge_route_level("sword") == 1 and sv2r.keystone_balance() == 80 | 购买持久(重载后等级与余额不变)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 927 | 417 | "长剑二级购:扣 30、等级→2;轻甲独立零扰动(六路线各练各的)〔锻造工坊批 V2〕" | mp2.forge_buy_level("sword") and sv2.forge_route_level("sword") == 2 and sv2.keystone_balance() == 50 and sv2.forge_route_level("light") == 0 | 长剑二级购扣 30;轻甲独立零扰动。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 928 | 424 | "余额不足购买拒绝且零副作用(0 余额——三域零变化)〔锻造工坊批 V2〕" | not mp2d.forge_buy_level("sword") and sv2d.keystone_balance() == 0 and sv2d.forge_routes_snapshot() == fr_dry | 余额不足购买拒绝且零副作用(三域零变化)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 929 | 429 | "已满级拒购/未解锁拒购(cap 闸+解锁闸——零副作用)〔锻造工坊批 V2〕" | not mp2.forge_buy_level("sword") and not mp2d.forge_buy_level("spear") | 已满级拒购/未解锁拒购(上限闸+解锁闸)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 930 | 432 | "通道一:长剑练满→长枪/法杖派生解锁;轻甲未练满→重甲/魔法护甲仍锁〔锻造工坊批 V3〕" | mp2.forge_route_unlocked("spear") and mp2.forge_route_unlocked("staff") and not mp2.forge_route_unlocked("heavy") and not mp2.forge_route_unlocked("ward") | 通道一:长剑练满派生解锁长枪/法杖;轻甲未满重甲/魔法甲仍锁。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 931 | 435 | "派生解锁后长枪可购(首购 20——余额 50−20)〔锻造工坊批 V3〕" | mp2.forge_buy_level("spear") and sv2.forge_route_level("spear") == 1 and sv2.keystone_balance() == 30 | 派生解锁后长枪可购(首购 20)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 932 | 442 | "通道二:魔法护甲一次性解锁价 200 直购开(轻甲未练满也可买)〔锻造工坊批 V3〕" | not mp3.forge_route_unlocked("ward") and mp3.forge_buy_unlock("ward") and sv3.keystone_balance() == 300 and mp3.forge_route_unlocked("ward") | 通道二:一次性解锁价 200 直购开(未练满也可买)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 933 | 447 | "直购后练满不重复收(已解锁=拒绝零收费——两通道互不影响)〔锻造工坊批 V3〕" | mp3.forge_route_unlocked("ward") and not mp3.forge_buy_unlock("ward") and sv3.keystone_balance() == 300 | 直购后练满不重复收(已解锁再直购拒绝零收费)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 934 | 452 | "不设串联门槛(法杖直购开——长枪零状态不阻)〔锻造工坊批 V3〕" | mp2d.forge_buy_unlock("staff") and mp2d.forge_route_unlocked("staff") and sv2d.forge_route_level("spear") == 0 and not mp2d.forge_route_unlocked("spear") | 不设串联门槛(法杖直购开长枪零状态不阻)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 935 | 457 | "两币分工:购买只扣钥石(余额差=当级价 20——GP 零触碰、无材料计数)〔锻造工坊批 V4〕" | mp3.forge_buy_level("ward") and sv3.keystone_balance() == ks4 - 20 and sv3.forge_route_level("ward") == 1 | 两币分工:购买只扣钥石(GP 零触碰)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 936 | 462 | "选中切换免费(余额零动——写存档域即盘)〔锻造工坊批 V6〕" | mp3.forge_select_armor("ward") and sv3.forge_armor() == "ward" and sv3.keystone_balance() == ks6 | 选中切换免费(余额零动写存档即盘)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 937 | 465 | "切换后旧路线练度保留(轻甲 5 级不丢——回家待命只不生效)〔锻造工坊批 V6〕" | sv3.forge_route_level("light") == 5 | 切换后旧路线练度保留(回家待命只不生效)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 938 | 468 | "未解锁路线拒选零副作用〔锻造工坊批 V6〕" | not mp2d.forge_select_weapon("spear") and sv2d.forge_weapon() == "sword" | 未解锁路线拒选零副作用。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 939 | 471 | "类型轴当前生效值随选中走(ward;未解锁档回退默认 normal/light)〔锻造工坊批 V5/V6〕" | mp3.hero_armor_type() == "ward" and mp2d.hero_attack_type() == "normal" and mp2d.hero_armor_type() == "light" | 类型轴当前生效值随选中走(未解锁档回退默认)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 940 | 480 | "build_adventure_params 注入默认值 normal/light(树钥匙未点亮——新档初始持有态)〔锻造工坊批 V5〕" | String(bp_params.get("attack_type", "")) == "normal" and String(bp_params.get("armor_type", "")) == "light" | 开局参数构造注入默认值(树钥匙未点亮新档)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 941 | 484 | "长枪直购解锁+首购+选中(注入面夹具——200+20 钥石)" | mp4.forge_buy_unlock("spear") and mp4.forge_buy_level("spear") and mp4.forge_select_weapon("spear") | 长枪直购解锁+首购+选中(夹具)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 942 | 487 | "build_adventure_params 读存档选中覆盖(选中长枪→pierce)〔锻造工坊批 V5〕" | String(bp_sel.get("attack_type", "")) == "pierce" and String(bp_sel.get("armor_type", "")) == "light" | 开局参数读存档选中覆盖(选中长枪→穿刺)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 943 | 491 | "hero_override=锻造路线加值(选中长枪 Lv1→atk +1;甲 0 级→AC +0;树零分量;无 hit 键)〔锻造工坊批 V7〕" | int(mp4.hero_override().get("atk", -1)) == 1 and int(mp4.hero_override().get("max_hp", -1)) == 0 and int(mp4.hero_override().get("armor_class", -1)) == 0 and not mp4.hero_override().has("hit") | hero_override=锻造路线加值(只认选中、树零分量、无命中键)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 944 | 497 | "换当前武器→攻击加值随切换(长剑 3 级待命不生效——只认选中)〔锻造工坊批 V7〕" | int(mp4.hero_override().get("atk", -1)) == 1 | 换当前武器→攻击加值随切换(待命练度不生效)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 945 | 499 | "切回长剑→加值=3 级量(旧路线练度保留且生效)〔锻造工坊批 V7〕" | mp4.forge_select_weapon("sword") and int(mp4.hero_override().get("atk", -1)) == 3 | 切回长剑→加值=3 级量(旧练度保留且生效)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 946 | 505 | "重甲直购解锁+首购+选中(三层 AC 夹具)" | mp5.forge_buy_unlock("heavy") and mp5.forge_buy_level("heavy") and mp5.forge_select_armor("heavy") | 重甲直购解锁+首购+选中(三层 AC 夹具)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 947 | 509 | "注入参数冒险启动(两轴+override 同面)" | adr5.start_adventure(77, p5) | 注入参数冒险启动受理。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 948 | 511 | "入战 AC 三层:甲型基础 12(重甲)+路线加值 1+局内 0〔锻造工坊批 V7〕" | int(r5.effective_ac()) == 12 + 1 | 入战 AC 三层:甲型基础 12+路线加值 1+局内 0。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 949 | 513 | "参数面快照入 runner(attack_type=normal/armor_type=heavy)〔锻造工坊批 V6〕" | adr5.hero_attack_type == "normal" and adr5.hero_armor_type == "heavy" | 参数面快照入 runner(两轴正确)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 950 | 516 | "冒险开始后锁死:改存档选中不影响参数面快照与生效值(契约 8)〔锻造工坊批 V6〕" | mp5.forge_select_armor("light") and adr5.hero_armor_type == "heavy" and int(r5.effective_ac()) == 12 + 1 | 冒险开始后锁死:改存档选中不影响快照与生效值。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 951 | 525 | "enc 两轴直达 spawn 英雄快照(hero_attack 行携带 pierce 键——注入链末端)〔锻造工坊批 V5〕" | pierce_rows >= 1 | enc 两轴直达 spawn 英雄快照(攻击行携带选中类型键)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 952 | 529 | "spawn 显式两轴注入(magic/heavy 直入英雄快照)〔锻造工坊批 V5〕" | uc2.spawn(1, 0, int(_db.hero.max_hp), [], -1, -1, [], "magic", "heavy") and String(uc2.hero_unit().get("attack_type", "")) == "magic" and String(uc2.hero_unit().get("armor_type", "")) == "heavy" | spawn 显式两轴注入(直入英雄快照)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 953 | 539 | "树零攻防来源复核(12 行 effects 键集无 atk/armor_class——契约 1)〔锻造工坊批 V10〕" | not tree_atk_ac | 树零攻防来源复核(12 行效果键无攻/护)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | - -## S112 _smoke_tree(行 548–621 · 25 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 954 | 548 | "meta_tree 已加载(12 节点=脊柱 2〔生命/命中淬炼一线一节点 cap4〕+锻造 2〔钥匙+彩蛋——彩蛋含在锻造 2 内非第 13 行〕+钥石 2〔秘宝匣/刷新升级〕+同调 6〔socket 3+Fork 3 双态〕——v0.8 终态)〔天赋树批 V1〕" | _db.meta_tree != null and (_db.meta_tree.rows as Array).size() == 12 | 天赋树表已加载恰 12 节点。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 955 | 550 | "MetaTreeTable.validate 零违例(脉/类型/币词表出域即拒/Fork 三组每组恰一且持 a/b 双效果键/彩蛋 ≤3/effects 键封闭/零 archetype)" | _db.meta_tree.validate().is_empty() | 天赋树表自校验零违例(词表/Fork 结构/彩蛋上限/effects 封闭)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 956 | 558 | "easter_ley effects 键集 ⊆ {max_hp, hit}(攻/护分量已摘除——契约 9)〔天赋树批 V8 落点〕" | ley_ok | 彩蛋行效果键集⊆{max_hp,hit}(攻/护分量已摘除)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 957 | 566 | "schema 反例:vein 出域被拒" | not t2.validate().is_empty() | schema 反例:脉出域被拒。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 958 | 571 | "schema 反例:fork_group 出域(fg_mastery 已退役组)被拒" | not t2.validate().is_empty() | schema 反例:Fork 组出域被拒。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 959 | 576 | "schema 反例:effects.archetype 加载致命(🔴 层级纪律护栏面)〔天赋树批 V4〕" | not t2.validate().is_empty() | schema 反例:effects.archetype 加载致命(层级纪律护栏)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 960 | 581 | "schema 反例:requires 悬空被拒" | not t2.validate().is_empty() | schema 反例:前置引用悬空被拒。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 961 | 586 | "树购买受理(脊柱生命 L1——树全钥石单币)" | mp.tree_buy("sp_hp") and sv.tree_level("sp_hp") == 1 | 树购买受理(脊柱生命 L1 全钥石单币)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 962 | 588 | "行门槛:第 2 级不可购(总等级 1 < 4——一线一节点主语,买第 N 级需全树总投入 ≥4×(N−1))〔天赋树批 V2〕" | not mp.tree_can_buy("sp_hp") | 行门槛:总等级不足时第 2 级不可购。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 963 | 590 | "命中 L1/锻造钥匙(free 无前置=初始零购零币可点亮)/同调位 1(根节点无前置)受理(总等级 1→4)" | mp.tree_buy("sp_hit") and mp.tree_buy("forge_workshop") and mp.tree_buy("att_socket1") | 命中/锻造钥匙/同调位 1 三行受理(总等级 1→4)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 964 | 592 | "锻造工坊钥匙零币(free)" | int(_db.tree_node("forge_workshop").get("price", -1)) == 0 | 锻造工坊钥匙零币(free)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 965 | 593 | "行门槛恰达开放(总等级 4 ≥ 4×1)" | mp.tree_can_buy("sp_hp") | 行门槛恰达当刻开放(总等级 4)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 966 | 594 | "第 2 级购买受理(门槛恰达当刻开放)" | mp.tree_buy("sp_hp") and sv.tree_level("sp_hp") == 2 | 第 2 级购买受理。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 967 | 598 | "Fork 购买=解锁+默认 A 态(一节点双态——单行承载)" | mp.tree_buy_fork("fork_posture") and sv.tree_level("fork_posture") == 1 and sv.fork_side("fg_posture") == "a" | Fork 购买=解锁+默认 A 态(一节点双态)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 968 | 600 | "已解锁 Fork 重复购买拒绝(cap 1)" | not mp.tree_can_buy("fork_posture") | 已解锁 Fork 重复购买拒绝。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 969 | 601 | "同态重复切换零操作(false)" | not mp.tree_toggle_fork("fg_posture", "a") | 同态重复切换零操作。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 970 | 602 | "未解锁 Fork 切换拒绝(解锁唯一入口=购买面)" | not mp.tree_toggle_fork("fg_sense", "b") | 未解锁 Fork 切换拒绝。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 971 | 604 | "零币切态 b(购买解锁后 A/B 可切)" | mp.tree_toggle_fork("fg_posture", "b") and sv.fork_side("fg_posture") == "b" | 零币切态 b(解锁后 A/B 可切)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 972 | 606 | "切态零消费(钥石余额差分不动)" | int(sv.keystone_balance()) == bal_pre_toggle | 切态零消费(余额不动)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 973 | 607 | "规则键投影=激活态效果键(B 态稳健拓荒——tree_rule_keys 随切态换键集)" | (mp.tree_rule_keys() as Array).has("posture_bulwark") and not (mp.tree_rule_keys() as Array).has("posture_first_strike") | 规则键投影=激活态效果键(随切态换键集)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 974 | 609 | "切回 A 态键集随换(六键词表在册——V11 校验面归单元段)" | mp.tree_toggle_fork("fg_posture", "a") and (mp.tree_rule_keys() as Array).has("posture_first_strike") | 切回 A 态键集随换。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 975 | 612 | "彩蛋不经购买(手动购亮=红——觉醒唯一入口=特化判定)" | not mp.tree_can_buy("easter_ley") | 彩蛋不经购买(觉醒唯一入口=特化判定)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 976 | 613 | "觉醒前置未满足不亮(两线未拉满)" | sv.tree_level("easter_ley") == 0 | 觉醒前置未满足不亮。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 977 | 618 | "两线拉满+同调位全解锁 → 地脉共鸣自动点亮(零币特化判定)" | sv.tree_level("easter_ley") == 1 | 两线拉满+同调位全解锁→彩蛋自动点亮(零币特化判定)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 978 | 621 | "觉醒零消费(钥石余额不动)" | int(sv.keystone_balance()) == bal_now | 觉醒零消费(余额不动)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 676 | 188 | "N 读表:N=1 时节点 4(层距 2)出窗〔fv6〕" | not r.type_visible_ids().has(4) | (待填) | | | +| 677 | 190 | "N 读表:N=3 时节点 5(层距 3)入窗〔fv6〕" | r.type_visible_ids().has(5) | (待填) | | | +| 678 | 196 | "N 读表:N ≥ 全图层高 → 全部非豁免节点可见〔fv6〕" | all_in | (待填) | | | -## S113 _smoke_tree_rules(行 633–757 · 21 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) +## S99 _test_resume_rebuild(行 234–235 · 1 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 979 | 633 | "先手压制:每关首战首轮窗命中(battle_index=1 且 round=1 → grant_ap queue)〔天赋树批 V10〕" | (fx.dispatch(ctx_p, "round_start", {"battle_index": 1, "round_index": 1}, Callable()) as Array).size() == 1 | 先手压制:每关首战首轮窗命中。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 980 | 635 | "先手压制:首轮窗外地不触发(第二轮/第二关首战首轮——「每战首轮」旧语义换代件)" | (fx.dispatch(ctx_p, "round_start", {"battle_index": 1, "round_index": 2}, Callable()) as Array).is_empty() and (fx.dispatch(ctx_p, "round_start", {"battle_index": 2, "round_index": 1}, Callable()) as Array).is_empty() | 先手压制:首轮窗外地不触发(第二轮/第二关首战)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 981 | 638 | "稳健拓荒:首轮窗内受击出盾、窗外不出(shield damage_taken 同窗条件)" | (fx.dispatch(ctx_p, "damage_taken", {"battle_index": 1, "round_index": 1}, Callable()) as Array).size() == 1 and (fx.dispatch(ctx_p, "damage_taken", {"battle_index": 2, "round_index": 1}, Callable()) as Array).is_empty() | 稳健拓荒:首轮窗内受击出盾、窗外不出。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 982 | 654 | "锋:踩雷减半命中+限次记账(counter 0<2 闸开——damage_mult+counter_add 双行同预评估态)" | got_mult and got_add | 锋:踩雷减半命中+限次记账(闸开双行同态)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 983 | 657 | "锋:限次耗尽全额(counter 2 不<2 闸关——每关限次 provisional 2)〔无状态化 V5〕" | (fz.dispatch(ctx_fz, "mine_hit", {}, Callable()) as Array).is_empty() | 锋:限次耗尽全额(闸关)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 984 | 663 | "锋:限次跨战持久(level 桶共享引用——替代旧副本搬运链)〔无状态化 V4〕" | (fz2.dispatch(ctx_fz2, "mine_hit", {}, Callable()) as Array).is_empty() | 锋:限次跨战持久(共享桶引用)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 985 | 685 | "锋:惊巢反制 mine_hit 减半分支受理(wa12 前提——halve_pct=50 效果行落 step_row)〔wa12〕" | nc_hit != "" | (待填) | | | -| 986 | 687 | "惊巢反制效果行==「惊巢反制——危险格挖掘惩罚减半(6→3)」零「挖雷」(u13 换代)〔wa12〕" | nc_hit == "惊巢反制——危险格挖掘惩罚减半(6→3)" and not nc_hit.contains("挖雷") | (待填) | | | -| 987 | 691 | "守侧 rig:战斗编排启动(tree_rules=sense_guard 携带)" | orch.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {}, "feats": [], "tree_rules": ["sense_guard"], "battle_index": 1, "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {}, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle"}) | 守侧 rig 战斗启动(携带守侧规则)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 988 | 703 | "守侧 rig:安全格定位(非危险格=标错负样本)" | safe.x >= 0 | 守侧 rig 安全格定位(非危险格=标错负样本)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 989 | 704 | "守侧 rig:首轮后 queue 干净(开局 posture queue 已兑现)" | int(orch.action_economy.pending_ap_delta()) == 0 | 守侧 rig 首轮后队列干净。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 990 | 708 | "守①:当回合标错且封印在场 → 下轮 AP+1(queue 排队——本轮 AP 逐位零变化)" | int(orch.action_economy.pending_ap_delta()) == 1 | 守①:当回合标错且封印在场→下轮 AP+1(排队)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 991 | 713 | "守③④:旧封印重判零触发+同格最多一次(_seal_miss_seen 去重闸=「当回合标错动作」判别器)" | int(orch.action_economy.pending_ap_delta()) == 1 | 守③④:旧封印重判零触发+同格最多一次(去重闸判别器)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 992 | 717 | "守②负样本:标中/被挖封印零触发(标错事件只在 miss 分支发射——发射面过滤承载)" | int(orch.action_economy.pending_ap_delta()) == 1 | 守②负样本:标中/被挖封印零触发(发射面过滤)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 993 | 721 | "守⑤ rig:次战编排启动(新战新队列实例)" | orch2.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {}, "feats": [], "tree_rules": ["sense_guard"], "battle_index": 1, "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {}, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle"}) | 守⑤ rig:次战编排启动(新战新队列实例)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 994 | 727 | "守⑤:末轮判出不返(queue 只在 notify_round_start 兑现、战亡即弃——无跨战泄漏)" | int(orch2.action_economy.pending_ap_delta()) == 0 | 守⑤:末轮判出不返(战亡即弃无跨战泄漏)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 995 | 733 | "散未激活:商品标价原价(无 economy_spend 键)" | int(lr._spend_discounted(100)) == 100 | 散未激活:商品标价原价。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 996 | 735 | "散激活:商品购买价 ×80%(provisional 折率——floor 偏向玩家、下限 1;刷新价不折=另一计价点零触碰)" | int(lr._spend_discounted(100)) == 80 and int(lr._spend_discounted(1)) == 1 | 散激活:商品购买价×80%(floor 偏向玩家下限 1)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 997 | 739 | "聚 rig:基线编排启动(无增益侧)" | orch3.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {}, "feats": [], "tree_rules": [], "battle_index": 1, "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {}, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle"}) | 聚 rig:基线编排启动(无增益侧)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 998 | 748 | "聚 rig:增益编排启动(tree_rules=economy_hoard)" | orch4.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {}, "feats": [], "tree_rules": ["economy_hoard"], "battle_index": 1, "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {}, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle"}) | 聚 rig:增益编排启动。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | -| 999 | 756 | "聚:战斗掉落乘算 ×1.5(provisional——同种子配对 %d→%d,入账前=结算框显示实得)" | gp_hoard == int(round(float(gp_base) * 1.5)) and gp_hoard >= gp_base | 聚:战斗掉落乘算×1.5(同种子配对对拍)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 679 | 234 | "续玩重建同值:resume 路径与同停点直入 type_visible_ids() 逐位一致〔fv7〕" | resumed.type_visible_ids() == direct.type_visible_ids() | (待填) | | | -## S114 _init(行 22–30 · 2 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) +## S100 _smoke_table_migration(行 262–269 · 4 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1000 | 22 | "数值表加载" | false | (待填) | | | -| 1001 | 30 | "生成返回棋盘(前置)" | false | (待填) | | | +| 680 | 262 | "shop.relic_base_price 表值 == 迁移前字面量(15.0)〔wd11〕" | _db.shop.relic_base_price == 15.0 | (待填) | | | +| 681 | 263 | "shop.spend_discount 表值 == 迁移前字面量(0.8)〔wd11〕" | _db.shop.spend_discount == 0.8 | (待填) | | | +| 682 | 264 | "hero.display_name 表值 == 迁移前字面量(探险者)〔wd16〕" | _db.hero.display_name == "探险者" | (待填) | | | +| 683 | 268 | "spawn 后英雄单位 display_name 读表同值(探险者)〔wd16〕" | spawned and String(units.hero_unit().get("display_name", "")) == String(_db.hero.display_name) | (待填) | | | -## S115 _test_install_invariants(行 48–56 · 4 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) +## S101 _smoke_generated_coverage(行 283–320 · 6 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1002 | 48 | "装板后 current_board 即所装棋盘" | _rules.current_board() == _board | (待填) | | | -| 1003 | 52 | "装板带初始扩张揭示(%d 格,非零且未铺满安全格 %d)" ↩ % [opened, safe_total] | opened > 0 and opened < safe_total | (待填) | | | -| 1004 | 54 | "安全格余量 = 安全格总数 − 已揭示数(%d − %d)" ↩ % [safe_total, opened] | _rules.safe_remaining() == safe_total - opened | (待填) | | | -| 1005 | 56 | "装板后仍有未揭示格" | _rules.has_unrevealed() | (待填) | | | +| 684 | 283 | "L%d 参数档缺行(覆盖断言无源可扫)〔v11〕" ↩ % L | false | (待填) | | | +| 685 | 290 | "L%d 种子 %d 生成图为空(覆盖扫描面空转)〔v11〕" ↩ % [L, seed_val] | false | (待填) | | | +| 686 | 317 | "生成图覆盖扫描面非空(三关参数档×种子 %d–%d 共 %d 个节点读数)〔v11〕" ↩ % [SWEEP_SEED_FROM, SWEEP_SEED_TO - 1, scanned] | scanned > 0 | (待填) | | | +| 687 | 318 | "生成图每个 battle/boss 节点深度的敌侧解析落带 ≥ 请求深度(band_of 非 -1·顶 clamp 消解禁)〔v11〕" | enemy_ok | (待填) | | | +| 688 | 319 | "生成图每个节点深度在 board_gen 精确命中(非空且 node_depth 等值)〔v11〕" | board_ok | (待填) | | | +| 689 | 320 | "多终点 Boss 各终点解析带键两两互异(L2/L3×20 种子)〔v11〕" | boss_band_ok | (待填) | | | -## S116 _test_dig_face_rules(行 73–80 · 2 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) +## S102 _smoke_level(行 331–387 · 17 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1006 | 73 | "挖已揭示格被拒(board_face=revealed,禁重复挖)——载荷 %s" ↩ % str(o.get("sub_reason", "")) | String(o.get("outcome", "")) == "rejected" and String(o.get("sub_reason", "")) == "target_revealed" | (待填) | | | -| 1007 | 79 | "挖非相邻格被拒(邻接规则)——载荷 %s" ↩ % str(o2.get("sub_reason", "")) | String(o2.get("outcome", "")) == "rejected" | (待填) | | | +| 690 | 331 | "L1 现行起关图取数非空(种子 %d × 参数档 → 节点 %d 颗)" ↩ % [ACTIVE_GRAPH_SEED, nodes1.size()] | not nodes1.is_empty() | (待填) | | | +| 691 | 334 | "关卡·满配(atk+200/hp+100)通关(实测:%s @节点 %d)" ↩ % [win.result, win.defeat_node_index] | win.result == "victory" | (待填) | | | +| 692 | 342 | "L1 通关路径逐跳为图出边(入口起步·末节点无出边——句柄轴行进自洽)" | _path_in_graph(nodes1, win.visited_ids) | (待填) | | | +| 693 | 343 | "L1 通关路径战斗/Boss 节点数 = 行进 battles 读数(路径 %d 节点·战斗 %d 颗·battles %d)" ↩ % [win.visited_ids.size(), path_battles, int(win.battles)] | path_battles == int(win.battles) and path_battles >= 2 | (待填) | | | +| 694 | 353 | "L1 终点 Boss 深度 = 参数档层高 %d 且通关路径末节点落该深度(实测终点深度 %d·末句柄 %d)" ↩ % [int(params1.get("layers", -1)), boss_depth1, last_id] | boss_depth1 == int(params1.get("layers", -1)) and int(by_id1.get(last_id, {}).get("depth", -1)) == boss_depth1 | (待填) | | | +| 695 | 355 | "L1 终点恰一个(大岔口自 L2 起的图侧投影)" | _terminal_ids(nodes1).size() == 1 and _ids_of_type(nodes1, "boss").size() == 1 | (待填) | | | +| 696 | 356 | "L1 生成图含岔口(出度 ≥2 节点 %d 处)" ↩ % _fork_ids(nodes1).size() | _fork_ids(nodes1).size() >= 1 | (待填) | | | +| 697 | 357 | "进 Boss HP 携带在域内(%d)" ↩ % win.hp_at_boss | win.hp_at_boss >= 1 and win.hp_at_boss <= int(_db.hero.max_hp) * 3 | (待填) | | | +| 698 | 363 | "恢复点公式无源(现行图入口后一位无可直达的恢复候选)" | false | (待填) | | | +| 699 | 366 | "恢复点公式·缺口充分档:回复 = floor(生效max_hp×%d%%) = %d(生效 max %d,实测 %d)" ↩ % [pct1, expect_pct, int(rest_wide["max"]), int(rest_wide["healed"])] | int(rest_wide["healed"]) == expect_pct and expect_pct > 0 | (待填) | | | +| 700 | 370 | "恢复点公式缺口封顶档无源(同图二次驱动未达恢复候选)" | false | (待填) | | | +| 701 | 372 | "恢复点公式·缺口不足档:回复封顶到缺口 2(实测 %d,比例项 %d)" ↩ % [int(rest_narrow["healed"]), expect_pct] | int(rest_narrow["healed"]) == mini(expect_pct, 2) and expect_pct > 2 | (待填) | | | +| 702 | 376 | "关卡·裸装(atk≈1/hp≈10)败北路径可达(实测:%s)" ↩ % lose.result | lose.result == "defeat" | (待填) | | | +| 703 | 377 | "败北节点记录在域内(节点 %d,图节点数 %d)" ↩ % [lose.defeat_node_index, nodes1.size()] | lose.defeat_node_index >= 0 and lose.defeat_node_index < nodes1.size() | (待填) | | | +| 704 | 382 | "关卡回放断言:同种子两遍结算步骤流逐行相同" | JSON.stringify(rep1.rows) == JSON.stringify(rep2.rows) | (待填) | | | +| 705 | 383 | "关卡步骤流非空(%d 行)" ↩ % rep1.rows.size() | rep1.rows.size() > 0 | (待填) | | | +| 706 | 385 | "模拟后 hero 表恢复基线(atk=%d hp=%d ac=%d——对配置快照)" ↩ % [int(_db.hero.atk), int(_db.hero.max_hp), int(_db.hero.armor_class)] | int(_db.hero.atk) == int(hero_base.atk) and int(_db.hero.max_hp) == int(hero_base.hp) \\ and int(_db.hero.armor_class) == int(hero_base.ac) | (待填) | | | -## S117 _test_reveal_advances(行 86–93 · 4 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) +## S103 _smoke_level_graph(行 442–762 · 70 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1008 | 86 | "存在未揭示安全格(揭示推进前置)" | false | (待填) | | | -| 1009 | 90 | "专长揭示受理(fx_reveal 不受邻接约束)" | ok | (待填) | | | -| 1010 | 91 | "fx_reveal 后该格已揭示" | _rules.is_revealed(c) | (待填) | | | -| 1011 | 92 | "fx_reveal 后安全格余量递减(%d → %d)" ↩ % [before, _rules.safe_remaining()] | _rules.safe_remaining() < before | (待填) | | | +| 707 | 442 | "LevelTable 行级校验零错误(图合法性移交 validate_graph)" | _db.level.validate().is_empty() | (待填) | | | +| 708 | 446 | "L2 现行起关图取数非空(种子 %d × 参数档 → 节点 %d 颗)" ↩ % [ACTIVE_GRAPH_SEED, nodes2.size()] | not nodes2.is_empty() | (待填) | | | +| 709 | 449 | "L2 节点数 %d ≥ 层高 %d+1 且 = 句柄去重读数 %d(全员句柄唯一)" ↩ % [nodes2.size(), int(params2.get("layers", -1)), _nodes_by_id(nodes2).size()] | nodes2.size() >= int(params2.get("layers", -1)) + 1 and _nodes_by_id(nodes2).size() == nodes2.size() | (待填) | | | +| 710 | 451 | "L2 五型计数合计 = 节点数(battle %d + boss %d + reward %d + rest %d + shop %d = %d)" ↩ % [tcount["battle"], tcount["boss"], tcount["reward"], tcount["rest"], tcount["shop"], nodes2.size()] | int(tcount["battle"]) + int(tcount["boss"]) + int(tcount["reward"]) + int(tcount["rest"]) + int(tcount["shop"]) == nodes2.size() | (待填) | | | +| 711 | 453 | "L2 战斗节点数 %d ≥ 参数档战斗保底 %d 且 Boss 节点数 %d = 参数档出口数 %d" ↩ % [tcount["battle"], int(params2.get("battle_floor", -1)), tcount["boss"], int(params2.get("exit_count", -1))] | int(tcount["battle"]) >= int(params2.get("battle_floor", -1)) and int(tcount["boss"]) == int(params2.get("exit_count", -1)) | (待填) | | | +| 712 | 455 | "L2 恢复/奖励/商店节点各 ≥1(实测 rest %d · reward %d · shop %d——计数由逐节点扫描同一读数)" ↩ % [tcount["rest"], tcount["reward"], tcount["shop"]] | int(tcount["rest"]) >= 1 and int(tcount["reward"]) >= 1 and int(tcount["shop"]) >= 1 | (待填) | | | +| 713 | 457 | "L2 岔口(出度 ≥2 的节点)= %d 处(≥1)" ↩ % branch | branch >= 1 | (待填) | | | +| 714 | 467 | "图校验正例:两岔合流小图零错误〔v1〕" | LT.validate_graph(good).is_empty() | (待填) | | | +| 715 | 470 | "图校验拒绝:句柄重复(图内唯一句柄)〔v1〕" | _has_err_category(LT.validate_graph(dup), "dup_key") | (待填) | | | +| 716 | 473 | "图校验正例:多入口合法(入口唯一判据随模板池退役)〔v1〕" | LT.validate_graph(dual).is_empty() | (待填) | | | +| 717 | 476 | "图校验拒绝:Boss 带出边(终点不可外出)〔v1〕" | _has_err_category(LT.validate_graph(boss_out), "structural") | (待填) | | | +| 718 | 479 | "图校验拒绝:回边 1→0(目标深度须严格大于源)〔v1〕" | _has_err_category(LT.validate_graph(cyc), "structural") | (待填) | | | +| 719 | 482 | "图校验拒绝:出边指向不存在句柄(悬空致命)〔v1〕" | _has_err_category(LT.validate_graph(dang), "dangling_next") | (待填) | | | +| 720 | 488 | "图校验拒绝:非 Boss 终点(终点恒为 Boss)〔v1〕" | _has_err_category(LT.validate_graph(orphan), "structural") | (待填) | | | +| 721 | 491 | "L2·满配 min 路径通关(实测:%s)" ↩ % win_min.result | win_min.result == "victory" | (待填) | | | +| 722 | 496 | "L2 min 路径逐跳为图出边·末节点为 Boss(实测路径 %s·末句柄型别 %s)" ↩ % [str(win_min.visited_ids), n2_last_type] | _path_in_graph(nodes2, win_min.visited_ids) and n2_last_type == "boss" | (待填) | | | +| 723 | 498 | "L2 min 路径终点深度 = 参数档层高 %d(行进读数 %d——终点落最长路径步数)" ↩ % [int(params2.get("layers", -1)), int(win_min.boss_depth)] | int(win_min.boss_depth) == int(params2.get("layers", -1)) | (待填) | | | +| 724 | 500 | "L2 min 路径穿商店恰 1 家(路径句柄 %s 型别计数 = 1——必经恰一店的行进面)" ↩ % str(win_min.visited_ids) | _path_shop_count(nodes2, win_min.visited_ids) == 1 | (待填) | | | +| 725 | 502 | "选路口发生 %d 次(每节点兑付后——零自动推进)" ↩ % win_min.choices | win_min.choices >= 3 | (待填) | | | +| 726 | 504 | "L2·满配 max 路径(候选取最大句柄)通关(实测:%s·路径 %s)" ↩ % [win_max.result, str(win_max.visited_ids)] | win_max.result == "victory" | (待填) | | | +| 727 | 509 | "L2 max 路径逐跳为图出边·末节点为 Boss·穿商店恰 1 家" | _path_in_graph(nodes2, win_max.visited_ids) and n2_last_max_type == "boss" and _path_shop_count(nodes2, win_max.visited_ids) == 1 | (待填) | | | +| 728 | 512 | "L2 max 与 min 路径分岔(方向轴生效——两路终点句柄 %d / %d 不同)" ↩ % [n2_last, n2_last_max] | n2_last != n2_last_max and win_max.visited_ids != win_min.visited_ids | (待填) | | | +| 729 | 519 | "手动驱动:L2 启动(入口战斗直接开打——入口无前置兑付;图 = 参数档 × 种子 %d)" ↩ % ACTIVE_GRAPH_SEED | runner.start_level(2, ACTIVE_GRAPH_SEED) | (待填) | | | +| 730 | 526 | "首战胜利后:settlement 释放 + 进选路态(战斗胜利不再自动推进——接口漂移清偿)" | String(res_box.get("result", "")) == "victory" and runner.current_battle() == null and runner.awaiting_choice() | (待填) | | | +| 731 | 529 | "runner 消费面图 = 生成面图(节点数 %d/%d 且句柄集逐位相同)" ↩ % [rnodes.size(), nodes2.size()] | rnodes.size() == nodes2.size() and _ids_of_type(rnodes, "battle").size() + _ids_of_type(rnodes, "boss").size() + _ids_of_type(rnodes, "reward").size() \\ + _ids_of_type(rnodes, "rest").size() + _ids_of_type(rnodes, "shop").size() == nodes2.size() and _nodes_by_id(rnodes) == _nodes_by_id(nodes2) | (待填) | | | +| 732 | 537 | "入口选路口候选非空且全在图内(候选 %s·类型 %s)" ↩ % [str(cand), str(cand_types)] | not cand.is_empty() and not cand_types.has("<不在图>") | (待填) | | | +| 733 | 538 | "候选下一跳类型两两互异(岔口去路类型不重复契约——实测 %s)" ↩ % str(cand_types) | cand_types.size() == _uniq(cand_types).size() | (待填) | | | +| 734 | 545 | "候选外句柄 %d 拒绝且仍待选(拒绝语义)" ↩ % outside_id | outside_id >= 0 and not runner.pick_next_node(outside_id) and runner.awaiting_choice() | (待填) | | | +| 735 | 555 | "入口岔口含奖励位与战斗位(句柄 %d/%d——候选型别可定位)" ↩ % [reward_cand, battle_cand] | reward_cand >= 0 and battle_cand >= 0 | (待填) | | | +| 736 | 556 | "选奖励候选 %d 受理" ↩ % reward_cand | runner.pick_next_node(reward_cand) | (待填) | | | +| 737 | 557 | "到达奖励点:直通兑付(确认前不出选路口)" | String(runner.current_node().get("type", "")) == "reward" and not runner.awaiting_choice() | (待填) | | | +| 738 | 559 | "兑付确认后才出现选路口(continue_after_node 泛化语义)候选 = 该点图出边 %s" ↩ % str(reward_outs) | runner.continue_after_node() and runner.awaiting_choice() and _sorted_ids(runner.next_candidate_ids()) == _sorted_ids(reward_outs) | (待填) | | | +| 739 | 566 | "选下一跳战斗候选 %d 开战" ↩ % next_battle | next_battle >= 0 and runner.pick_next_node(next_battle) and runner.current_battle() != null | (待填) | | | +| 740 | 591 | "首遇位零踩雷:完美档 12 种子(节点 0 熊地精)恒零踩雷" | green_ok | (待填) | | | +| 741 | 592 | "首遇战胜局 ≥7/12(实际 %d——阶段拔高后锚行退出绿档,多数胜口径)" ↩ % green_wins | green_wins >= 7 | (待填) | | | +| 742 | 593 | "首遇战胜后 HP 下探读数 %d(>0——绿档记录)" ↩ % green_min_hp | green_min_hp > 0 | (待填) | | | +| 743 | 597 | "跨表覆盖扫描面非空:L2 现行图战斗/Boss 节点 %d 颗" ↩ % int(cov2["nodes"]) | int(cov2["nodes"]) >= 1 | (待填) | | | +| 744 | 598 | "L2 现行图每战斗/Boss 节点深度敌侧解析落带 ≥ 请求深度(违例 %d 处)" ↩ % int(cov2["enemy_bad"]) | int(cov2["enemy_bad"]) == 0 | (待填) | | | +| 745 | 599 | "L2 现行图每节点深度 board_gen 精确命中(非空且 node_depth 等值·违例 %d 处)" ↩ % int(cov2["board_bad"]) | int(cov2["board_bad"]) == 0 | (待填) | | | +| 746 | 607 | "巨蜘蛛首遇带(2,battle,%d——扫表取最低带)行集 = {giant_spider} 恰一行且 unit_count=2 无 unit_count_max(固定 2 只——首遇教学位不加骰)〔fv22〕" ↩ % l2_spider_band | l2_spider_band >= 0 and l2_spider_ids.has("giant_spider") and l2_spider_ids.size() == 1 and l2_spider_set.size() == 1 \\ and int(l2_spider_set[0].get("unit_count", -1)) == 2 and not l2_spider_set[0].has("unit_count_max") | (待填) | | | +| 747 | 617 | "L2 全部终点深度 %s 的 Boss 行集均 = {drider} 恰一行且首行魔法甲(护甲类型重配批:关卡 2 唯一非轻甲;P4 乙 Boss 行集多行合法——零内容期仍单行)〔fv24〕" ↩ % str(l2_terms) | l2_boss_ok | (待填) | | | +| 748 | 624 | "首批混编行集 (2,battle,5) = 双行且行序 [bugbear 重击手 → giant_spider 巢群](R6 行序契约——文件序 = 声明序 = 生成序,首行默认目标 = 前排坦克;迭代 18 卡⑤投放)〔fv18〕" | mix_shape_ok | (待填) | | | +| 749 | 628 | "首批混编行集 (2,battle,5) 静态最坏骰和 = Σ(unit_count_max,固定行缺省=unit_count) = %d ≤ 4(行集实例总数守卫同口径——重击手固定 1 + 巢群 [2,3])〔fv19〕" ↩ % mix_sum_max | mix_sum_max <= 4 | (待填) | | | +| 750 | 629 | "首批混编新行 (2,battle,5,giant_spider) 行值 = 复制巢群行(P4 甲零数值新增——2-3 只/hp 26/atk [1,2]/light/ac 2;既有 19 行现值零变更)" | int(l2_mix_set[1].get("unit_count", 0)) == 2 and int(l2_mix_set[1].get("unit_count_max", 0)) == 3 \\ and int(l2_mix_set[1].get("hp", 0)) == 26 and int(l2_mix_set[1].get("armor_class", -1)) == 2 | (待填) | | | +| 751 | 636 | "L3 现行起关图取数非空(种子 %d × 参数档 → 节点 %d 颗)" ↩ % [ACTIVE_GRAPH_SEED, nodes3.size()] | not nodes3.is_empty() | (待填) | | | +| 752 | 639 | "L3 节点数 %d ≥ 层高 %d+1 且 = 句柄去重读数 %d(全员句柄唯一)" ↩ % [nodes3.size(), int(params3.get("layers", -1)), _nodes_by_id(nodes3).size()] | nodes3.size() >= int(params3.get("layers", -1)) + 1 and _nodes_by_id(nodes3).size() == nodes3.size() | (待填) | | | +| 753 | 641 | "L3 五型计数合计 = 节点数(battle %d + boss %d + reward %d + rest %d + shop %d = %d)" ↩ % [tcount3["battle"], tcount3["boss"], tcount3["reward"], tcount3["rest"], tcount3["shop"], nodes3.size()] | int(tcount3["battle"]) + int(tcount3["boss"]) + int(tcount3["reward"]) + int(tcount3["rest"]) + int(tcount3["shop"]) == nodes3.size() | (待填) | | | +| 754 | 643 | "L3 战斗节点数 %d ≥ 参数档战斗保底 %d 且 Boss 节点数 %d = 参数档出口数 %d" ↩ % [tcount3["battle"], int(params3.get("battle_floor", -1)), tcount3["boss"], int(params3.get("exit_count", -1))] | int(tcount3["battle"]) >= int(params3.get("battle_floor", -1)) and int(tcount3["boss"]) == int(params3.get("exit_count", -1)) | (待填) | | | +| 755 | 645 | "L3 恢复/奖励/商店节点各 ≥1(实测 rest %d · reward %d · shop %d——计数由逐节点扫描同一读数)" ↩ % [tcount3["rest"], tcount3["reward"], tcount3["shop"]] | int(tcount3["rest"]) >= 1 and int(tcount3["reward"]) >= 1 and int(tcount3["shop"]) >= 1 | (待填) | | | +| 756 | 647 | "L3 岔口(出度 ≥2 的节点)= %d 处(≥1)" ↩ % branch3 | branch3 >= 1 | (待填) | | | +| 757 | 648 | "L3 入口数 %d = 参数档 entry_count · 终点数 %d = exit_count ≥2(多终点大岔口自 L2 起)" ↩ % [_entry_ids(nodes3).size(), _terminal_ids(nodes3).size()] | _entry_ids(nodes3).size() == int(params3.get("entry_count", -1)) and _terminal_ids(nodes3).size() == int(params3.get("exit_count", -1)) and _terminal_ids(nodes3).size() >= 2 | (待填) | | | +| 758 | 652 | "跨表覆盖扫描面非空:L3 现行图战斗/Boss 节点 %d 颗" ↩ % int(cov3["nodes"]) | int(cov3["nodes"]) >= 1 | (待填) | | | +| 759 | 653 | "L3 现行图每战斗/Boss 节点深度敌侧解析落带 ≥ 请求深度(违例 %d 处)" ↩ % int(cov3["enemy_bad"]) | int(cov3["enemy_bad"]) == 0 | (待填) | | | +| 760 | 654 | "L3 现行图每节点深度 board_gen 精确命中(非空且 node_depth 等值·违例 %d 处)" ↩ % int(cov3["board_bad"]) | int(cov3["board_bad"]) == 0 | (待填) | | | +| 761 | 661 | "灰矮人首遇带(3,battle,%d——扫表取最低带)行集 = {duergar} 恰一行、首行重甲、固定 1 只不加骰〔fv23〕" ↩ % l3_lead_band | l3_lead_band >= 0 and l3_lead_ids.has("duergar") and l3_lead_ids.size() == 1 \\ and String(l3_lead_set[0].get("armor_type", "")) == "heavy" \\ and int(l3_lead_set[0].get("unit_count", -1)) == 1 and not l3_lead_set[0].has("unit_count_max") | (待填) | | | +| 762 | 672 | "L3 全部终点深度 %s 的 Boss 行集均 = {duergar_king} 恰一行且首行重甲(重甲收束「轻甲→魔法甲→重甲」教学梯度)〔fv24〕" ↩ % str(l3_terms) | l3_boss_ok | (待填) | | | +| 763 | 695 | "L3 首遇位绿档:成长代理档口径(atk_pct=200/hp_pct=100)完美档 12 种子首遇战(节点 0 灰矮人)全胜且零踩雷" | green3_ok | (待填) | | | +| 764 | 696 | "L3 首遇战胜后 HP 下探读数 %d(>0——绿档记录)" ↩ % green3_min_hp | green3_min_hp > 0 | (待填) | | | +| 765 | 706 | "混编行集 spawn 受理((1,battle,5) 双行 地精1+熊地精2——定值无区间路径,行集构成纪律放开面正向)" | mix_sys.spawn(1, 5, int(_db.hero.max_hp)) | (待填) | | | +| 766 | 708 | "混编实例总数 = 各行数量求和(1+2=3——行集实例总数 ≤4 守卫内)" | mix_units.size() == 3 | (待填) | | | +| 767 | 709 | "混编生成序 = 文件行序(行序即契约——地精在前熊地精在后,handle_id 跨行连续派发)" | String(mix_units[0].get("enemy_id")) == "goblin" and String(mix_units[1].get("enemy_id")) == "bugbear" and String(mix_units[2].get("enemy_id")) == "bugbear" | (待填) | | | +| 768 | 710 | "混编各行行值独立投影(display_name/hp/atk 按各自行值——同场不同种不同值)" | String(mix_units[0].get("display_name")) == "地精" and int(mix_units[1].get("hp")) == 20 and int(mix_units[1].get("atk")) == 3 | (待填) | | | +| 769 | 714 | "行集实例总数守卫:两行各定值 3(总 6 > 4)spawn 被拒(行集构成纪律③——R3 fail-fast 运行面)" | not over_sys.spawn(1, 6, int(_db.hero.max_hp)) | (待填) | | | +| 770 | 719 | "混编断言段注入行全量还原(表回 28 行——迭代 35 生成图深度扩面覆盖批后的基线行数;首批混编投放基线 20 行+覆盖 8 行)" | int(_db.enemy.rows.size()) == 28 | (待填) | | | +| 771 | 726 | "可见性冒烟:L2 入口停点就位(defer_entry——无当前节点,锚层 0)" | vis_ok and vis_runner.awaiting_choice() and vis_runner.current_node_id() == -1 | (待填) | | | +| 772 | 732 | "可见性冒烟:入口停点候选集 ⊆ 类型可见集(下一跳恒可见)〔fv4〕" | vis_cand_in | (待填) | | | +| 773 | 737 | "可见性冒烟:L2 全部终点 Boss 句柄 %s 恒在可见集(终点不入雾)〔fv3〕" ↩ % str(_ids_of_type(nodes2, "boss")) | vis_boss_ok | (待填) | | | +| 774 | 739 | "可见性冒烟:N 读数据表 = %d(provisional 2——代码零硬编码)〔fv6〕" ↩ % vis_n | vis_n == 2 | (待填) | | | +| 775 | 761 | "可见性冒烟:入口锚层 0 时层距 ≤ %d 的节点全在可见集(扫描面 %d 颗·违例 0)〔fv1〕" ↩ % [vis_n, near_count] | near_count >= 1 and near_ok | (待填) | | | +| 776 | 762 | "可见性冒烟:入口锚层 0 时层距 > %d 的非终点节点全入雾(扫描面 %d 颗·违例 0)〔fv1〕" ↩ % [vis_n, fog_count] | fog_count >= 1 and fog_ok | (待填) | | | + +## S104 _smoke_gp_wallet(行 799–835 · 9 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 777 | 799 | "GP 到账:L1 启动(普通战档)" | runner.start_level(1, 42) | (待填) | | | +| 778 | 809 | "L1 首战胜利(battle_finished gp_dropped 载荷在案)" | fin.has("p") and String(fin["p"].get("result", "")) == "victory" | (待填) | | | +| 779 | 812 | "L1 普通战 gp_dropped − escrow ∈ [2+0, 4+0](掷定+关级加值 %d,escrow %+d)" ↩ % [gp_dropped - escrow_sum, escrow_sum] | gp_dropped - escrow_sum >= 2 and gp_dropped - escrow_sum <= 4 | (待填) | | | +| 780 | 817 | "钱包恰入账一次(gain 事件 ×%d)" ↩ % gains.size() | gains.size() == 1 | (待填) | | | +| 781 | 819 | "入账金额 = gp_dropped 载荷(%d)" ↩ % gp_dropped | int(gains[0]["amount"]) == gp_dropped | (待填) | | | +| 782 | 820 | "钱包读数 = 入账后投影(gp=%d,破财负值场景经 clamp ≥0)" ↩ % runner.gp | runner.gp == maxi(0, gp_dropped) and runner.gp == int(gains[0]["gp"]) | (待填) | | | +| 783 | 827 | "L2 Boss 战启动((2,9)——Boss 档掷定 [6,10] + 关级加值 1)" | boss.begin_battle({"level_index": 2, "node_depth": 9, "node_type": "boss", "hero_hp": int(_db.hero.max_hp)}) | (待填) | | | +| 784 | 832 | "L2 Boss 战终局(%s——gp_dropped 于战斗末掷定上抛)" ↩ % String(boss.battle_result) | fin_b.has("p") | (待填) | | | +| 785 | 835 | "L2 Boss 战 gp_dropped − escrow ∈ [6+1, 10+1](掷定+关级加值 %d,escrow %+d)" ↩ % [gp_b - escrow_b, escrow_b] | gp_b - escrow_b >= 7 and gp_b - escrow_b <= 11 | (待填) | | | + +## S105 _smoke_gp_escrow(行 873–937 · 12 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 786 | 873 | "GP 袋(gp 型安全格)可挖掘局种子可达(seed=%s)" ↩ % str(scan.get("seed", -1)) | not scan.is_empty() | (待填) | | | +| 787 | 886 | "GP局战斗终局(先手 999 收束)" | settlement2.battle_result == "victory" | (待填) | | | +| 788 | 891 | "GP 袋兑付行在案(GP 袋 +3 → 战斗内缓冲,origin 指回来源格)" | gain_ok | (待填) | | | +| 789 | 892 | "escrow 累计缓冲 ≥3(实测 %d——战斗域中间态,不入钱包不入增益容器)" ↩ % int(settlement2.escrow) | int(settlement2.escrow) >= 3 | (待填) | | | +| 790 | 894 | "格面兑付计入 gp_dropped(%d − escrow %d ∈ [2,4]——战斗末一次到账,随上抛即弃)" ↩ % [gp_d, int(settlement2.escrow)] | gp_d - int(settlement2.escrow) >= 2 and gp_d - int(settlement2.escrow) <= 4 | (待填) | | | +| 791 | 900 | "破财直验战斗启动" | settle_l.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "hero_atk": 999, "node_type": "boss"}) | (待填) | | | +| 792 | 904 | "破财 ×3 → escrow −6(负向缓冲记账,零钱包触达)" | int(settle_l.escrow) == -6 | (待填) | | | +| 793 | 909 | "破财压制 gp_dropped 负值上抛(%d + 6 = %d ∈ Boss 档掷定 [%d,%d] + 关级加值 %d——掷定不受破财污染)" ↩ % [ gp_l, gp_l + 6, int(boss_drop.get("roll_min", 0)), int(boss_drop.get("roll_max", 0)), boss_bonus1] | gp_l + 6 >= int(boss_drop.get("roll_min", 0)) + boss_bonus1 and gp_l + 6 <= int(boss_drop.get("roll_max", 0)) + boss_bonus1 | (待填) | | | +| 794 | 919 | "clamp 场景:L1 启动(冒险钱包开局 0)" | runner.start_level(1, 4242) | (待填) | | | +| 795 | 931 | "破财压制入账载荷为负(gp_dropped=%d)" ↩ % gp3 | gp3 < 0 | (待填) | | | +| 796 | 936 | "破财入账恰一次且投影读数 0(amount=%d,clamp 后 gp=0)" ↩ % gp3 | gain3.size() == 1 and int(gain3[0]["amount"]) == gp3 and int(gain3[0]["gp"]) == 0 | (待填) | | | +| 797 | 937 | "钱包全程不透支(gp=%d,事件投影最小读数 %d ≥ 0)" ↩ % [runner.gp, int(ev_box["min_gp"])] | runner.gp == 0 and int(ev_box["min_gp"]) >= 0 | (待填) | | | + +## S106 _smoke_shop_room(行 1003–1089 · 25 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 798 | 1003 | "商店房到达种子可达(seed=%s——进店 GP ≥ 16)" ↩ % str(shop_run.get("seed", -1)) | not shop_run.is_empty() | (待填) | | | +| 799 | 1016 | "L2 行进到商店房:当前节点型别 = shop 且出边恰一条指终点 Boss(句柄 %d·出边 %s)" ↩ % [shop_id, str(shop_outs)] | String(runner.current_node().get("type", "")) == "shop" and shop_outs.size() == 1 \\ and String(s_byid.get(int(shop_outs[0]), {}).get("type", "")) == "boss" \\ and (s_byid.get(int(shop_outs[0]), {}).get("next", []) as Array).is_empty() | (待填) | | | +| 800 | 1020 | "L2 到店路径穿商店恰 1 家(已到句柄 %s 型别计数——必经恰一店的行进面)" ↩ % str(runner.visited_ids()) | _path_shop_count(s_nodes, runner.visited_ids()) == 1 | (待填) | | | +| 801 | 1022 | "L2 商店节点(句柄 %d)入边恰一条(店位归终点的直接前驱——行进面复核)" ↩ % shop_id | shop_preds == 1 | (待填) | | | +| 802 | 1024 | "到店商店节点无出边(夹具停摆——去终点方向无法从图解析)" | false | (待填) | | | +| 803 | 1026 | "到店开店态(覆盖层数据源就位——离开前不出选路口,零自动推进)" | not runner.awaiting_choice() and runner.next_candidate_ids().is_empty() | (待填) | | | +| 804 | 1029 | "三商品全陈列(atk_up/hp_up/armor_up——无刷新无摇号)" | prods.size() == 3 | (待填) | | | +| 805 | 1031 | "L2 首购现价 = 基价 + 关级加值 2(atk_up 6 / hp_up 4 / armor_up 6)" | int(by_id["atk_up"]["price"]) == 6 and int(by_id["hp_up"]["price"]) == 4 and int(by_id["armor_up"]["price"]) == 6 | (待填) | | | +| 806 | 1034 | "购买 atk_up 受理(现价 6)" | runner.buy_product("atk_up") | (待填) | | | +| 807 | 1035 | "钱包扣现价 6(%d → %d)" ↩ % [gp0, runner.gp] | runner.gp == gp0 - 6 | (待填) | | | +| 808 | 1037 | "统一冒险加算区 atk +1(bonus={atk,max_hp,armor_class} 账本)" | int(runner.run_ledger.bonus.get("atk", -1)) == 1 and int(runner.run_ledger.bonus.get("max_hp", -1)) == 0 and int(runner.run_ledger.bonus.get("armor_class", -1)) == 0 | (待填) | | | +| 809 | 1039 | "atk_up 二购现价 = 4+2+2=8(步长 2 × 已购 1 档,现价现算)" | int(by_id["atk_up"]["price"]) == 8 and int(by_id["atk_up"]["purchased"]) == 1 | (待填) | | | +| 810 | 1046 | "购买 hp_up 受理(现价 4)" | runner.buy_product("hp_up") | (待填) | | | +| 811 | 1047 | "钱包扣 4(%d)" ↩ % runner.gp | runner.gp == gp0 - 6 - 4 | (待填) | | | +| 812 | 1048 | "生命上限 +3(%d → %d——加算区 max_hp 轴)" ↩ % [mhp_before, runner.effective_max_hp()] | runner.effective_max_hp() == mhp_before + 3 | (待填) | | | +| 813 | 1049 | "当前生命同步 +3 封顶新上限(%d → %d)" ↩ % [hp_before, runner.hero_hp] | runner.hero_hp == mini(hp_before + 3, runner.effective_max_hp()) | (待填) | | | +| 814 | 1051 | "购买 armor_up 受理(现价 6)" | runner.buy_product("armor_up") | (待填) | | | +| 815 | 1052 | "AC = 10+1(armor_class 加算区轴,无百分比通道)" | runner.effective_ac() == int(_db.hero.armor_class) + 1 | (待填) | | | +| 816 | 1067 | "余额不足购买被拒(gp=%d < 现价 %d——零扣费零档位变动)" ↩ % [gp_dry, hp_dry] | not runner.buy_product("hp_up") and runner.gp == gp_dry and runner.purchased_tier("hp_up") == tier_dry | (待填) | | | +| 817 | 1069 | "离开商店 → 回选路口(候选 = 店出边 %s——零自动推进收口)" ↩ % str(shop_outs) | runner.continue_after_node() and runner.awaiting_choice() and _sorted_ids(runner.next_candidate_ids()) == _sorted_ids(shop_outs) | (待填) | | | +| 818 | 1072 | "主角攻击生效值 = 2+1(基础表值 + 加算区)" | runner.effective_atk() == int(_db.hero.atk) + 1 | (待填) | | | +| 819 | 1073 | "选终点 Boss(句柄 %d)开战" ↩ % int(shop_outs[0]) | runner.pick_next_node(int(shop_outs[0])) and runner.current_battle() != null | (待填) | | | +| 820 | 1077 | "战斗开始参数 = 基础表值 + 加算区(atk %d = 2+1 / max_hp %d = %d+%d×3)" ↩ % [int(snap["hero"].get("atk", -1)), int(snap["hero"].get("max_hp", -1)), int(_db.hero.max_hp), hp_buys] | int(snap["hero"].get("atk", -1)) == int(_db.hero.atk) + 1 and int(snap["hero"].get("max_hp", -1)) == int(_db.hero.max_hp) + 3 * hp_buys | (待填) | | | +| 821 | 1088 | "冒险收束(Boss 战终局:%s)" ↩ % String(fin_end.get("p", {}).get("result", "")) | fin_end.has("p") and String(fin_end["p"].get("result", "")) in ["victory", "defeat"] | (待填) | | | +| 822 | 1089 | "冒险收束钱包清零(局内货币生命周期终点——不入存档不入摘要)" | runner.gp == 0 | (待填) | | | + +## S107 _smoke_shop_graph(行 1111–1168 · 9 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 823 | 1111 | "L%d 参数档缺行(商店编排断言无源可扫)" ↩ % L | false | (待填) | | | +| 824 | 1116 | "L%d 种子 %d 生成图为空(编排断言扫描面空转)" ↩ % [L, seed_val] | false | (待填) | | | +| 825 | 1142 | "编排断言扫描面非空(三关参数档 × 种子 %d–%d=图 %d 张·终点 %d·路径 %d)〔v5〕" ↩ % [SWEEP_SEED_FROM, SWEEP_SEED_TO - 1, graph_total, term_total, path_total] | graph_total == 3 * (SWEEP_SEED_TO - SWEEP_SEED_FROM) and term_total >= 3 and path_total >= 3 | (待填) | | | +| 826 | 1144 | "性质① 每个终点的直接前驱恰一家商店(入边一条且源型别=shop·违例 %d/%d 终点)〔v5〕" ↩ % [pred_bad, term_total] | pred_bad == 0 | (待填) | | | +| 827 | 1145 | "性质② 每图商店节点数 = 终点数(违例 %d/%d 图)〔v5〕" ↩ % [count_bad, graph_total] | count_bad == 0 | (待填) | | | +| 828 | 1146 | "性质③ 每条入口→终点路径恰穿一家商店(违例 %d/%d 条路径)〔v5〕" ↩ % [path_bad, path_total] | path_bad == 0 | (待填) | | | +| 829 | 1152 | "L%d 现行图终点 %d 个 = 商店 %d 个·入口→终点路径 %d 条全穿恰一店" ↩ % [L, cur_terms.size(), cur_shops.size(), _all_entry_terminal_paths(cur_nodes).size()] | cur_terms.size() == cur_shops.size() and not cur_terms.is_empty() and _all_entry_terminal_paths(cur_nodes).size() >= cur_terms.size() and _paths_all_one_shop(cur_nodes) | (待填) | | | +| 830 | 1164 | "L%d 现行图终点深度 %s 逐点 Boss 行集解析命中且落带 ≥ 终点深度" ↩ % [L, str(tdepths)] | hit_ok | (待填) | | | +| 831 | 1167 | "L1 现行图跨表覆盖(战斗/Boss 节点 %d 颗非空·敌侧违例 %d·板侧违例 %d)" ↩ % [int(l1_cov["nodes"]), int(l1_cov["enemy_bad"]), int(l1_cov["board_bad"])] | int(l1_cov["nodes"]) >= 1 and int(l1_cov["enemy_bad"]) == 0 and int(l1_cov["board_bad"]) == 0 | (待填) | | | + +## S108 _smoke_multi_terminal_settlement(行 1176–1223 · 5 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 832 | 1176 | "L2 现行图为多终点(Boss 终点 %s 两颗以上——大岔口承载面)" ↩ % str(boss_ids) | boss_ids.size() >= 2 | (待填) | | | +| 833 | 1188 | "L2 min/max 两路各打进各自终点 Boss(末句柄 %d / %d 互异且都在图 Boss 集 %s 内;两路 result=%s/%s)" ↩ % [min_last, max_last, str(boss_ids), String(run_min.get("result", "")), String(run_max.get("result", ""))] | String(run_min.get("result", "")) == "victory" and String(run_max.get("result", "")) == "victory" and boss_ids.has(min_last) and boss_ids.has(max_last) and min_last != max_last | (待填) | | | +| 834 | 1192 | "min 路未到 max 终点 %d、max 路未到 min 终点 %d(区域隔离——走 A 区不误兑付 B 区 Boss)" ↩ % [max_last, min_last] | not min_vis.has(max_last) and not max_vis.has(min_last) | (待填) | | | +| 835 | 1199 | "L2 全程驱动启动受理" | runner.start_level(2, 42) | (待填) | | | +| 836 | 1221 | "任一 Boss 胜即本关收束恰一次(count=%d·载荷 result=%s·node_id=%s 在图 Boss 集 %s 内)" ↩ % [int(fin["count"]), String(payload.get("result", "")), str(payload.get("node_id", -1)), str(boss_ids)] | int(fin["count"]) == 1 and String(payload.get("result", "")) == "victory" and boss_ids.has(int(payload.get("node_id", -1))) | (待填) | | | + +## S109 _smoke_shop_pool(行 1258–1392 · 29 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 837 | 1258 | "池模式商店房场景可达(seed=%s)" ↩ % str(scen.get("s", -1)) | not scen.is_empty() | (待填) | | | +| 838 | 1266 | "进店抽 3(货架 %d 卡——同构交互)" ↩ % first_shelf_a.size() | first_shelf_a.size() == 3 | (待填) | | | +| 839 | 1272 | "刷新首价=基价(%d)" ↩ % cost0 | cost0 == int(_db.adventure.shop_refresh_cost_base) | (待填) | | | +| 840 | 1273 | "基础免费 0 次(Q2——天赋增益位后续添加)" | r8.shop_refresh_free_left() == 0 | (待填) | | | +| 841 | 1275 | "GP 刷新受理(gp=%d cost=%d)" ↩ % [gp0, cost0] | r8.shop_refresh() | (待填) | | | +| 842 | 1276 | "GP 刷新扣费(%d→%d)" ↩ % [gp0, int(r8.gp)] | int(r8.gp) == gp0 - cost0 | (待填) | | | +| 843 | 1277 | "刷新爬价 ×curve(%d)" ↩ % int(r8.shop_refresh_cost()) | int(r8.shop_refresh_cost()) == int(ceil(float(cost0) * float(_db.adventure.shop_refresh_cost_curve))) | (待填) | | | +| 844 | 1285 | "同种子同货架逐位(确定性重放承载——断点货架续玩语义)" | same_shelf | (待填) | | | +| 845 | 1295 | "relic 现价 = ceil(15.0×b^(关序−1))(读表与迁移前字面量逐位一致,%d)〔wd11〕" ↩ % relic_price_now | relic_price_now == relic_price_pre | (待填) | | | +| 846 | 1301 | "货架 kind 在封闭词表或保底 relic 卡(%s)" ↩ % ckind | is_relic or shelf_kinds.has(ckind) | (待填) | | | +| 847 | 1308 | "首购现价 = ceil(基价×b^(关序−1))(%s: %d = %d 推导)" ↩ % [ckind, int(c.get("price", -1)), expect_p] | int(c.get("price", -1)) == expect_p | (待填) | | | +| 848 | 1315 | "可购卡在架(GP %d——种子扫描保证首购空间)" ↩ % int(r8.gp) | not buy_c.is_empty() | (待填) | | | +| 849 | 1323 | "购买受理(pool 口——%s\|%s)" ↩ % [kind, tier] | r8.buy_pool_item(kind) | (待填) | | | +| 850 | 1324 | "钱包扣现价(%d → %d)" ↩ % [gp0, int(r8.gp)] | int(r8.gp) == gp0 - int(buy_c.get("price", 0)) | (待填) | | | +| 851 | 1325 | "加算区 +令牌档位件值(池件=奖励件本体——%s\|%s %+d)" ↩ % [kind, tier, int(_db.adventure.curio_value(kind, tier))] | int(a8.run_ledger.bonus.get(String(val_map[kind]), -999)) == val0 + int(_db.adventure.curio_value(kind, tier)) | (待填) | | | +| 852 | 1326 | "共享池账本 -1(跨渠道单点——taken %d → %d)" ↩ % [taken0, int(a8._curio_taken.get(kind, 0))] | int(a8._curio_taken.get(kind, 0)) == taken0 + 1 | (待填) | | | +| 853 | 1338 | "A 链爬价 = ceil(base×b^(关序−1)×A^1)(进店第 2 购——%d)" ↩ % expect_p2 | int(second.get("price", -1)) == expect_p2 | (待填) | | | +| 854 | 1339 | "同 visit 第二购受理(A 链软闸在价不在限)" | r8.buy_pool_item(String(second.get("kind_id", ""))) | (待填) | | | +| 855 | 1341 | "A 链爬价场景未构造(抬 GP 后货架仍无可购第二卡)——场景绑定失败" | false | (待填) | | | +| 856 | 1349 | "切边件零入店(40 次重抽零 gp/aux——负交易切边+三轴 doctrine)" | not cut_seen | (待填) | | | +| 857 | 1360 | "库存尽剔牌库(attack 令牌耗尽——池消耗即上限跨渠道共库 reward→shop 向)" | not attack_seen | (待填) | | | +| 858 | 1362 | "池空消费拒绝(handler 零副作用——买光即枯竭硬闸)" | not a8._consume_pool_purchase("attack", "common") and a8.pool_stock("attack") == stock_before | (待填) | | | +| 859 | 1365 | "商店消费口受理(_apply_curio_pick 同口——真单点库)" | a8._consume_pool_purchase("attack", "common") | (待填) | | | +| 860 | 1371 | "商店购买进奖励牌库摘牌(买一件=奖励面少一件——shop→reward 向共库)" | not reward_attack_seen | (待填) | | | +| 861 | 1376 | "离开商店 → 回选路口(货架态清零落档——零自动推进收口)" | r8.continue_after_node() and r8.awaiting_choice() | (待填) | | | +| 862 | 1378 | "断点扩键 shop_shelf 在案(离开店=空架合法态——P2 甲扩列)" | bp.has("shop_shelf") and (bp["shop_shelf"] as Array).is_empty() | (待填) | | | +| 863 | 1379 | "断点扩键 shop_visit_buys 在案(离开店清零——A 链输入不入档语义)" | bp.has("shop_visit_buys") and int(bp["shop_visit_buys"]) == 0 | (待填) | | | +| 864 | 1384 | "终局池读数六键出讫(run_adventure pool 面——四池种+T5 专长凭证+T6 heal 行,读数只锚一次载体)" | ps.size() == 6 | (待填) | | | +| 865 | 1392 | "终局池余量在域 [0,配额](%s)" ↩ % str(ps) | ps_ok | (待填) | | | + +## S110 _test_positive(行 46–56 · 3 条 · 源 game/tests/unit/core/level_table_validate_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 866 | 46 | "四检正例:多入口多终点图零错误〔v1〕" | _errs(multi).is_empty() | (待填) | | | +| 867 | 53 | "四检正例:含岔口单终点图零错误〔v1〕" | _errs(forked).is_empty() | (待填) | | | +| 868 | 56 | "四检正例:单链退化图零错误(不设必有岔口)〔v1〕" | _errs(chain).is_empty() | (待填) | | | + +## S111 _test_negative(行 63–89 · 11 条 · 源 game/tests/unit/core/level_table_validate_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 869 | 63 | "负例:终点非 Boss 命中「终点恒为 Boss」〔v1〕" | _has(_errs(non_boss_end), "structural", "终点 1 非 Boss") | (待填) | | | +| 870 | 66 | "负例:Boss 带出边被拒〔v1〕" | _has(_errs(boss_out), "structural", "Boss 节点 1 带出边") | (待填) | | | +| 871 | 68 | "负例:回边(目标深度不严格大于源)命中前进方向判据〔v1〕" | _has(_errs(back_edge), "structural", "违反前进方向") | (待填) | | | +| 872 | 71 | "负例:环(2→1 回边)被前进方向拦下〔v1〕" | _has(_errs(cycle), "structural", "违反前进方向") | (待填) | | | +| 873 | 77 | "判据②正证:两独立链(双入口双终点)不误伤·全员可达〔v1〕" | _errs(two_components).is_empty() | (待填) | | | +| 874 | 79 | "判据②兜底面:自环先被前进方向拦下(④ 与 ② 互斥的实证)〔v1〕" | _has(_errs(self_loop), "structural", "违反前进方向") | (待填) | | | +| 875 | 82 | "负例:悬空出边报 dangling_next〔v1〕" | _has(_errs(dangling), "dangling_next") | (待填) | | | +| 876 | 84 | "负例:句柄重复报 dup_key〔v1〕" | _has(_errs(dup), "dup_key") | (待填) | | | +| 877 | 86 | "负例:battle 节点缺深度被良构基底拒〔v1〕" | _has(_errs(no_depth), "structural", "缺深度") | (待填) | | | +| 878 | 87 | "负例:空节点集被拒〔v1〕" | _has(_errs([]), "structural", "图节点集为空") | (待填) | | | +| 879 | 89 | "负例:类型出域报 enum〔v1〕" | _has(_errs(bad_type), "enum") | (待填) | | | + +## S112 _test_scope_not_extended(行 97–102 · 3 条 · 源 game/tests/unit/core/level_table_validate_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 880 | 97 | "范围反证:无商店图不在加载校验内报错(商店位由冒烟断言把关)〔v1〕" | _errs(no_shop).is_empty() | (待填) | | | +| 881 | 99 | "范围反证:双入口合法(入口唯一判据已随模板池退役,个数归产出侧)〔v1〕" | _errs(two_entries).is_empty() | (待填) | | | +| 882 | 102 | "范围反证:节点缺 next 字段仍被良构基底拒〔v1〕" | _has(_errs(missing_key), "structural", "缺字段 next") | (待填) | | | + +## S113 _meta_pricing_and_buy(行 138–151 · 4 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 883 | 138 | "树购买受理(Lv 0→1、余额 %d−%d)" ↩ % [bal0, price0] | mp.tree_buy("sp_hp") and sv.tree_level("sp_hp") == 1 and sv.keystone_balance() == bal0 - price0 | (待填) | | | +| 884 | 141 | "购买持久(reload 后级与余额不变)" | sv_r.tree_level("sp_hp") == 1 and sv_r.keystone_balance() == bal0 - price0 | (待填) | | | +| 885 | 148 | "余额不足 false 零副作用(0 余额购买拒绝——三域零变化〔天赋树批 V3〕)" | not mp_dry.tree_buy("sp_hp") and sv_dry.keystone_balance() == 0 and sv_dry.tree_nodes() == tn_before and sv_dry.fork_sides() == fs_before | (待填) | | | +| 886 | 151 | "未知节点 false 零副作用(词表外出域)" | not mp_dry.tree_buy("no_such_node") | (待填) | | | + +## S114 _meta_settle_and_workshop(行 168–341 · 42 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 887 | 168 | "新档首通 L1:boss_drops[0] {first:true, amount=first_kill}(验收5——明细随摘要归档)" | bool(drop1.get("first", false)) and int(drop1.get("amount", 0)) == int(row1.get("first_kill", -1)) and int(drop1.get("level", 0)) == 1 | (待填) | | | +| 888 | 171 | "明细 total = keystone_total(逐杀掉落全源——无二次派生加账)" | int(d1.get("total", 0)) == int(row1.get("first_kill", 0)) | (待填) | | | +| 889 | 173 | "余额 = 累计入账(逐杀掉落合计——无重复加账)" | sv.keystone_balance() == int(row1.get("first_kill", 0)) | (待填) | | | +| 890 | 181 | "再通 L1:{first:false, amount=repeat_kill}(首杀标记置位后转小额)" | not bool(drop2.get("first", true)) and int(drop2.get("amount", 0)) == int(row1.get("repeat_kill", -1)) | (待填) | | | +| 891 | 187 | "重复杀第三轮恒定不递减(验收5——无衰减曲线)" | not bool(d3["boss_drops"][0].get("first", true)) and int(d3["boss_drops"][0].get("amount", 0)) == int(row1.get("repeat_kill", -1)) | (待填) | | | +| 892 | 195 | "两关通关逐关归因:L1 重复档 + L2 首杀档(first_kill 金额)" | not bool(d4["boss_drops"][0].get("first", true)) and bool(d4["boss_drops"][1].get("first", false)) and int(d4["boss_drops"][1].get("amount", 0)) == int(row2.get("first_kill", -1)) | (待填) | | | +| 893 | 203 | "败北零通关摘要:零入账零标记(钥石只增不减——验收1;败北局 clear_counts 不变)" | dz["boss_drops"].is_empty() and sv.keystone_balance() == bal_before and sv.clear_count(1) == cc1_before | (待填) | | | +| 894 | 210 | "条件件误购拒绝(购买件专属——契约 8 分配面)〔秘宝批 V3 反例〕" | not mp_u.buy_treasure_unlock("sky_eye") and not sv_u.item_unlocked("sky_eye") and not mp_u.buy_treasure_unlock("ranged_pick") | (待填) | | | +| 895 | 213 | "余额不足购买拒绝且零副作用(余额 10 < 价 25)〔秘宝批 V3〕" | not mp_u.buy_treasure_unlock("twin_loader") and sv_u.keystone_balance() == 10 | (待填) | | | +| 896 | 215 | "钥石购买解锁:余额足→解锁态落档+余额扣减正确(价 10)〔秘宝批 V3〕" | mp_u.buy_treasure_unlock("morning_order") and sv_u.item_unlocked("morning_order") and sv_u.keystone_balance() == 0 | (待填) | | | +| 897 | 218 | "已解锁重复购买拒绝〔秘宝批 V3〕" | not mp_u.buy_treasure_unlock("morning_order") | (待填) | | | +| 898 | 220 | "条件未达不解锁(远程镐=通关 1 次)〔秘宝批 V4〕" | mp_u.refresh_treasure_unlocks().is_empty() and not sv_u.item_unlocked("ranged_pick") | (待填) | | | +| 899 | 223 | "远程镐=adventure_cleared≥1 达标恰解锁一次+重复刷新幂等(C6:通关=打通整局冒险)〔秘宝批 V4〕" | (mp_u.refresh_treasure_unlocks() as Array).has("ranged_pick") and sv_u.item_unlocked("ranged_pick") and mp_u.refresh_treasure_unlocks().is_empty() | (待填) | | | +| 900 | 227 | "刷新执照=boss_kill_count≥3(阈值读 unlock_requires 行值不硬编)〔秘宝批 V4〕" | (mp_u.refresh_treasure_unlocks() as Array).has("refresh_license") | (待填) | | | +| 901 | 230 | "天眼单条件不足不解锁(AND 语义——cleared 3 达 boss 4 未达)〔秘宝批 V4〕" | not sv_u.item_unlocked("sky_eye") | (待填) | | | +| 902 | 233 | "天眼双条件 AND(通关≥3 且 Boss≥4)达标解锁〔秘宝批 V4〕" | (mp_u.refresh_treasure_unlocks() as Array).has("sky_eye") | (待填) | | | +| 903 | 236 | "信标网络=真视类工具累计使用≥10(行为计数新域——use_treasure 受理成功累计)〔秘宝批 V4〕" | (mp_u.refresh_treasure_unlocks() as Array).has("beacon_network") | (待填) | | | +| 904 | 241 | "解锁态持久投影一致(reload 面同源读数;5 件=购买 1+条件 4)〔秘宝批 V4〕" | r_n == 5 | (待填) | | | +| 905 | 244 | "摘要最新在前 + 元素含掉落明细副本(复盘可归因——验收5)" | rs.size() >= 5 and String(rs[0].get("result", "")) != "" and rs[0].has("boss_drops") and (rs[1] as Dictionary).has("keystone_drops") and int(rs[0].get("master_seed", -1)) == 15 | (待填) | | | +| 906 | 253 | "摘要 cap 10 且最新在前(12 连 settle 截断后 seed 11 居首)" | sv_c.recent_summaries().size() == 10 and int(sv_c.recent_summaries()[0].get("master_seed", -1)) == 11 | (待填) | | | +| 907 | 258 | "白板档同调位求值 = 0(0 起步裁决)且 Y=0(未购无刷新能力——P1 甲初始 0 同构)" | Attunement.slots(_db, sv_a) == 0 and int(mp_a.attune_camp_status()["refresh"]["charges"]) == 0 | (待填) | | | +| 908 | 260 | "满位禁选(Slots 0 时装配拒绝;兼未解锁拒绝前置——直置解锁后下方装配)〔秘宝批 V5〕" | mp_a.toggle_attunement("morning_order") == false | (待填) | | | +| 909 | 265 | "Y 升级购买入账(Lv1 → 每冒险 +1 次刷新)" | mp_a.buy_attune_refresh() and sv_a.attune_refresh_level() == 1 and int(mp_a.attune_camp_status()["refresh"]["charges"]) == 1 | (待填) | | | +| 910 | 270 | "通关 1 关 → Slots 恒 0(旧「按击杀 Boss 次数自动涨」条件已删——槽位唯一来源=树点亮)且 Boss 击杀总数=1(计数域保留)〔天赋树批 V14〕" | Attunement.slots(_db, sv_a) == 0 and sv_a.boss_kill_count() == 1 | (待填) | | | +| 911 | 273 | "树上点亮同调位 1 → Slots=1(槽位收编树节点——Q2 裁甲)" | Attunement.slots(_db, sv_a) == 1 | (待填) | | | +| 912 | 275 | "稀有件入普通格拒绝(件档 rank 3 > 装入位序 1——契约 2 档位格校验)〔秘宝批 V5〕" | not mp_a.toggle_attunement("sky_eye") | (待填) | | | +| 913 | 277 | "装配持久写入 + 满位禁选(Slots 1 装第二件拒绝)〔秘宝批 V5 换主语〕" | mp_a.toggle_attunement("morning_order") and mp_a.toggle_attunement("twin_loader") == false | (待填) | | | +| 914 | 279 | "卸下恒可再装配(自卸自装——营地换装=冒险开始前合法面,装配不可变只锁运行中)〔秘宝批换主语〕" | mp_a.toggle_attunement("morning_order") and mp_a.toggle_attunement("morning_order") | (待填) | | | +| 915 | 284 | "装配关系读回持久(reload 后投影一致——写新件 id)〔秘宝批 V15〕" | (mp_a2.attune_camp_status()["equipped"] as Array).has("morning_order") | (待填) | | | +| 916 | 288 | "充能重置场景:装配冒险启动(seed 12)〔秘宝批 V8 换主语〕" | a12.start_adventure(12, {"attunement": ["incense_stick", "sky_eye"]}) | (待填) | | | +| 917 | 290 | "充能账本按装备 active_charged 件重置(新目录表值:线香 battle 级 1 次/天眼 level 级 1 次——周期重置点归 V8 逐形态面)" | int(a12.run_ledger.attune_charges.get("incense_stick", -1)) == 1 and int(a12.run_ledger.attune_charges.get("sky_eye", -1)) == 1 | (待填) | | | +| 918 | 296 | "刷新双计数器——X 优先消耗(x1y2 → x0y2)" | a12.refresh_curio() and a12._attune_refresh_x == 0 and a12._attune_refresh_y == 2 | (待填) | | | +| 919 | 298 | "刷新双计数器——X 耗尽转 Y 池、池竭拒绝(x0y2 → y0 → 第四次 false 零副作用)" | a12.refresh_curio() and a12.refresh_curio() and a12._attune_refresh_y == 0 and a12.refresh_curio() == false | (待填) | | | +| 920 | 302 | "刷新 X 每碰三选一重置(不结转;新目录无 bonus_refresh 行源——X 机制保留恒 0 空转,存量加仓走 _refresh_stock=刷新练习 V8)〔秘宝批换代〕" | a12._attune_refresh_x == 0 and a12.attune_refresh_left() == 0 | (待填) | | | +| 921 | 313 | "开局传参夹具前置:Fork 侧 a 激活 rule 键非空 + Y=Lv2 行表值 2(防断言空转)" | rules_e.has("posture_first_strike") and mp_e.attune_refresh_charges() == 2 | (待填) | | | +| 922 | 317 | "主菜单开新冒险参数面:同调装配 = 存档装配集(营地装了什么开局就带什么)" | (p_new.get("attunement", []) as Array) == (sv_e.attunement_equipped() as Array) | (待填) | | | +| 923 | 319 | "主菜单开新冒险参数面:Y 刷新池 = 当前等级行表值(营地升几档开局就给几次)" | int(p_new.get("attune_refresh_y", -1)) == mp_e.attune_refresh_charges() | (待填) | | | +| 924 | 321 | "主菜单开新冒险参数面:树 rule 键集 = Fork 激活侧 effects(点哪侧带哪侧)" | (p_new.get("tree_rules", []) as Array) == rules_e | (待填) | | | +| 925 | 323 | "主菜单开新冒险参数面:入口特有键(treasure_unlocked/shop_mode)随 extra 原样并入" | bool((p_new.get("treasure_unlocked", {}) as Dictionary).get("wide_scout_band", false)) and String(p_new.get("shop_mode", "")) == "pool" | (待填) | | | +| 926 | 327 | "主菜单继续上局参数面:三键与开新冒险同源同值(续玩不吃白板开局)" | (p_resume.get("attunement", []) as Array) == (sv_e.attunement_equipped() as Array) and int(p_resume.get("attune_refresh_y", -1)) == mp_e.attune_refresh_charges() and (p_resume.get("tree_rules", []) as Array) == rules_e | (待填) | | | +| 927 | 338 | "继续上局受理面:resume_adventure 受理三键参数面(尾裁微批b 受理闭环——受理不再吃白板)" | adr_res.resume_adventure(bp_min, p_resume) | (待填) | | | +| 928 | 340 | "继续上局受理面:tree_rules 随参数面进 runner._tree_rules(消费面 set_tree_rules 下传现成)" | (adr_res._tree_rules as Array) == rules_e | (待填) | | | + +## S115 _meta_injection(行 353–383 · 10 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 929 | 353 | "生命淬炼 2 级 → hero_override().max_hp == Σ 节点 effects×等级(验收4 换代〔天赋树批 V12〕)" | int(mp.hero_override().get("max_hp", -1)) == 2 * int(_db.tree_node("sp_hp").get("effects", {}).get("max_hp", 0)) | (待填) | | | +| 930 | 355 | "攻/AC 树零来源(契约 9——树表零 atk/armor_class 行,override 二轴恒 0+预留位 0)" | int(mp.hero_override().get("atk", -1)) == 0 and int(mp.hero_override().get("armor_class", -1)) == 0 | (待填) | | | +| 931 | 357 | "命中线在账未注入(契约 6——override 无 hit 字段〔天赋树批 V13〕)" | not mp.hero_override().has("hit") | (待填) | | | +| 932 | 360 | "注入参数冒险启动(start_adventure params 通道)" | adr.start_adventure(920251, {"hero_override": mp.hero_override()}) | (待填) | | | +| 933 | 364 | "首战 hero_atk = 表值 + 永久强化 flat(begin_battle 同表达式——下一局能用到强化)" | int(r.effective_atk()) == int(_db.hero.atk) + int(ov.get("atk", 0)) | (待填) | | | +| 934 | 366 | "首战 hero_max_hp 与开局生命账本 = 表值 + 注入档" | int(r.effective_max_hp()) == int(_db.hero.max_hp) + int(ov.get("max_hp", 0)) and int(adr.ledger["hero_hp"]) == int(_db.hero.max_hp) + int(ov.get("max_hp", 0)) | (待填) | | | +| 935 | 371 | "白板冒险启动(无 params 实参——B1 空参数路径)" | adr_w.start_adventure(920251) | (待填) | | | +| 936 | 374 | "零强化参数冒险启动(新档 hero_override 全零)" | adr_z.start_adventure(920251, {"hero_override": load("res://core/meta_progression.gd").new(_db, sv_white).hero_override()}) | (待填) | | | +| 937 | 378 | "白板成立:空参数 = 零强化参数 = 纯表值(atk/max_hp/ac 三轴逐位一致——验收 2/3)" | int(rw.effective_atk()) == int(_db.hero.atk) and int(rw.effective_max_hp()) == int(_db.hero.max_hp) and int(rz.effective_atk()) == int(rw.effective_atk()) and int(rz.effective_max_hp()) == int(rw.effective_max_hp()) and int(rz.effective_ac()) == int(rw.effective_ac()) | (待填) | | | +| 938 | 382 | "ac 轴:白板恒表值、未换强化时注入零位移" | int(rw.effective_ac()) == int(_db.hero.armor_class) and int(r.effective_ac()) == int(_db.hero.armor_class) | (待填) | | | + +## S116 _smoke_forge_axes(行 392–540 · 33 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 939 | 392 | "hero.tres 无 attack_type/armor_type 字段(契约 2——静态表两轴删除)〔锻造工坊批 V5〕" | not ("attack_type" in _db.hero) and not ("armor_type" in _db.hero) | (待填) | | | +| 940 | 395 | "锻造配置默认值 attack_type=normal / armor_type=light(常驻持有)〔锻造工坊批 V5〕" | _db.forge_default_attack_type() == "normal" and _db.forge_default_armor_type() == "light" | (待填) | | | +| 941 | 399 | "spawn 受理(三参形态——两轴缺省回退面)" | uc.spawn(1, 0, int(_db.hero.max_hp)) | (待填) | | | +| 942 | 400 | "主角单位快照两轴 = 锻造配置默认值 normal/light(零注入回退——行为连续)〔锻造工坊批 V5〕" | String(uc.hero_unit().get("attack_type", "")) == "normal" and String(uc.hero_unit().get("armor_type", "")) == "light" | (待填) | | | +| 943 | 410 | "长剑锻造首购:扣 20 钥石、等级 0→1(Q1——第一级=获得装备=首个 +1)〔锻造工坊批 V2〕" | mp2.forge_buy_level("sword") and sv2.forge_route_level("sword") == 1 and sv2.keystone_balance() == 80 | (待填) | | | +| 944 | 414 | "购买持久(reload 后等级与余额不变——即时写盘)〔锻造工坊批 V2〕" | sv2r.forge_route_level("sword") == 1 and sv2r.keystone_balance() == 80 | (待填) | | | +| 945 | 417 | "长剑二级购:扣 30、等级→2;轻甲独立零扰动(六路线各练各的)〔锻造工坊批 V2〕" | mp2.forge_buy_level("sword") and sv2.forge_route_level("sword") == 2 and sv2.keystone_balance() == 50 and sv2.forge_route_level("light") == 0 | (待填) | | | +| 946 | 424 | "余额不足购买拒绝且零副作用(0 余额——三域零变化)〔锻造工坊批 V2〕" | not mp2d.forge_buy_level("sword") and sv2d.keystone_balance() == 0 and sv2d.forge_routes_snapshot() == fr_dry | (待填) | | | +| 947 | 429 | "已满级拒购/未解锁拒购(cap 闸+解锁闸——零副作用)〔锻造工坊批 V2〕" | not mp2.forge_buy_level("sword") and not mp2d.forge_buy_level("spear") | (待填) | | | +| 948 | 432 | "通道一:长剑练满→长枪/法杖派生解锁;轻甲未练满→重甲/魔法护甲仍锁〔锻造工坊批 V3〕" | mp2.forge_route_unlocked("spear") and mp2.forge_route_unlocked("staff") and not mp2.forge_route_unlocked("heavy") and not mp2.forge_route_unlocked("ward") | (待填) | | | +| 949 | 435 | "派生解锁后长枪可购(首购 20——余额 50−20)〔锻造工坊批 V3〕" | mp2.forge_buy_level("spear") and sv2.forge_route_level("spear") == 1 and sv2.keystone_balance() == 30 | (待填) | | | +| 950 | 442 | "通道二:魔法护甲一次性解锁价 200 直购开(轻甲未练满也可买)〔锻造工坊批 V3〕" | not mp3.forge_route_unlocked("ward") and mp3.forge_buy_unlock("ward") and sv3.keystone_balance() == 300 and mp3.forge_route_unlocked("ward") | (待填) | | | +| 951 | 447 | "直购后练满不重复收(已解锁=拒绝零收费——两通道互不影响)〔锻造工坊批 V3〕" | mp3.forge_route_unlocked("ward") and not mp3.forge_buy_unlock("ward") and sv3.keystone_balance() == 300 | (待填) | | | +| 952 | 452 | "不设串联门槛(法杖直购开——长枪零状态不阻)〔锻造工坊批 V3〕" | mp2d.forge_buy_unlock("staff") and mp2d.forge_route_unlocked("staff") and sv2d.forge_route_level("spear") == 0 and not mp2d.forge_route_unlocked("spear") | (待填) | | | +| 953 | 457 | "两币分工:购买只扣钥石(余额差=当级价 20——GP 零触碰、无材料计数)〔锻造工坊批 V4〕" | mp3.forge_buy_level("ward") and sv3.keystone_balance() == ks4 - 20 and sv3.forge_route_level("ward") == 1 | (待填) | | | +| 954 | 462 | "选中切换免费(余额零动——写存档域即盘)〔锻造工坊批 V6〕" | mp3.forge_select_armor("ward") and sv3.forge_armor() == "ward" and sv3.keystone_balance() == ks6 | (待填) | | | +| 955 | 465 | "切换后旧路线练度保留(轻甲 5 级不丢——回家待命只不生效)〔锻造工坊批 V6〕" | sv3.forge_route_level("light") == 5 | (待填) | | | +| 956 | 468 | "未解锁路线拒选零副作用〔锻造工坊批 V6〕" | not mp2d.forge_select_weapon("spear") and sv2d.forge_weapon() == "sword" | (待填) | | | +| 957 | 471 | "类型轴当前生效值随选中走(ward;未解锁档回退默认 normal/light)〔锻造工坊批 V5/V6〕" | mp3.hero_armor_type() == "ward" and mp2d.hero_attack_type() == "normal" and mp2d.hero_armor_type() == "light" | (待填) | | | +| 958 | 480 | "build_adventure_params 注入默认值 normal/light(树钥匙未点亮——新档初始持有态)〔锻造工坊批 V5〕" | String(bp_params.get("attack_type", "")) == "normal" and String(bp_params.get("armor_type", "")) == "light" | (待填) | | | +| 959 | 484 | "长枪直购解锁+首购+选中(注入面夹具——200+20 钥石)" | mp4.forge_buy_unlock("spear") and mp4.forge_buy_level("spear") and mp4.forge_select_weapon("spear") | (待填) | | | +| 960 | 487 | "build_adventure_params 读存档选中覆盖(选中长枪→pierce)〔锻造工坊批 V5〕" | String(bp_sel.get("attack_type", "")) == "pierce" and String(bp_sel.get("armor_type", "")) == "light" | (待填) | | | +| 961 | 491 | "hero_override=锻造路线加值(选中长枪 Lv1→atk +1;甲 0 级→AC +0;树零分量;无 hit 键)〔锻造工坊批 V7〕" | int(mp4.hero_override().get("atk", -1)) == 1 and int(mp4.hero_override().get("max_hp", -1)) == 0 and int(mp4.hero_override().get("armor_class", -1)) == 0 and not mp4.hero_override().has("hit") | (待填) | | | +| 962 | 497 | "换当前武器→攻击加值随切换(长剑 3 级待命不生效——只认选中)〔锻造工坊批 V7〕" | int(mp4.hero_override().get("atk", -1)) == 1 | (待填) | | | +| 963 | 499 | "切回长剑→加值=3 级量(旧路线练度保留且生效)〔锻造工坊批 V7〕" | mp4.forge_select_weapon("sword") and int(mp4.hero_override().get("atk", -1)) == 3 | (待填) | | | +| 964 | 505 | "重甲直购解锁+首购+选中(三层 AC 夹具)" | mp5.forge_buy_unlock("heavy") and mp5.forge_buy_level("heavy") and mp5.forge_select_armor("heavy") | (待填) | | | +| 965 | 509 | "注入参数冒险启动(两轴+override 同面)" | adr5.start_adventure(77, p5) | (待填) | | | +| 966 | 511 | "入战 AC 三层:甲型基础 12(重甲)+路线加值 1+局内 0〔锻造工坊批 V7〕" | int(r5.effective_ac()) == 12 + 1 | (待填) | | | +| 967 | 513 | "参数面快照入 runner(attack_type=normal/armor_type=heavy)〔锻造工坊批 V6〕" | adr5.hero_attack_type == "normal" and adr5.hero_armor_type == "heavy" | (待填) | | | +| 968 | 516 | "冒险开始后锁死:改存档选中不影响参数面快照与生效值(契约 8)〔锻造工坊批 V6〕" | mp5.forge_select_armor("light") and adr5.hero_armor_type == "heavy" and int(r5.effective_ac()) == 12 + 1 | (待填) | | | +| 969 | 525 | "enc 两轴直达 spawn 英雄快照(hero_attack 行携带 pierce 键——注入链末端)〔锻造工坊批 V5〕" | pierce_rows >= 1 | (待填) | | | +| 970 | 529 | "spawn 显式两轴注入(magic/heavy 直入英雄快照)〔锻造工坊批 V5〕" | uc2.spawn(1, 0, int(_db.hero.max_hp), [], -1, -1, [], "magic", "heavy") and String(uc2.hero_unit().get("attack_type", "")) == "magic" and String(uc2.hero_unit().get("armor_type", "")) == "heavy" | (待填) | | | +| 971 | 539 | "树零攻防来源复核(12 行 effects 键集无 atk/armor_class——契约 1)〔锻造工坊批 V10〕" | not tree_atk_ac | (待填) | | | + +## S117 _smoke_tree(行 548–621 · 25 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 972 | 548 | "meta_tree 已加载(12 节点=脊柱 2〔生命/命中淬炼一线一节点 cap4〕+锻造 2〔钥匙+彩蛋——彩蛋含在锻造 2 内非第 13 行〕+钥石 2〔秘宝匣/刷新升级〕+同调 6〔socket 3+Fork 3 双态〕——v0.8 终态)〔天赋树批 V1〕" | _db.meta_tree != null and (_db.meta_tree.rows as Array).size() == 12 | (待填) | | | +| 973 | 550 | "MetaTreeTable.validate 零违例(脉/类型/币词表出域即拒/Fork 三组每组恰一且持 a/b 双效果键/彩蛋 ≤3/effects 键封闭/零 archetype)" | _db.meta_tree.validate().is_empty() | (待填) | | | +| 974 | 558 | "easter_ley effects 键集 ⊆ {max_hp, hit}(攻/护分量已摘除——契约 9)〔天赋树批 V8 落点〕" | ley_ok | (待填) | | | +| 975 | 566 | "schema 反例:vein 出域被拒" | not t2.validate().is_empty() | (待填) | | | +| 976 | 571 | "schema 反例:fork_group 出域(fg_mastery 已退役组)被拒" | not t2.validate().is_empty() | (待填) | | | +| 977 | 576 | "schema 反例:effects.archetype 加载致命(🔴 层级纪律护栏面)〔天赋树批 V4〕" | not t2.validate().is_empty() | (待填) | | | +| 978 | 581 | "schema 反例:requires 悬空被拒" | not t2.validate().is_empty() | (待填) | | | +| 979 | 586 | "树购买受理(脊柱生命 L1——树全钥石单币)" | mp.tree_buy("sp_hp") and sv.tree_level("sp_hp") == 1 | (待填) | | | +| 980 | 588 | "行门槛:第 2 级不可购(总等级 1 < 4——一线一节点主语,买第 N 级需全树总投入 ≥4×(N−1))〔天赋树批 V2〕" | not mp.tree_can_buy("sp_hp") | (待填) | | | +| 981 | 590 | "命中 L1/锻造钥匙(free 无前置=初始零购零币可点亮)/同调位 1(根节点无前置)受理(总等级 1→4)" | mp.tree_buy("sp_hit") and mp.tree_buy("forge_workshop") and mp.tree_buy("att_socket1") | (待填) | | | +| 982 | 592 | "锻造工坊钥匙零币(free)" | int(_db.tree_node("forge_workshop").get("price", -1)) == 0 | (待填) | | | +| 983 | 593 | "行门槛恰达开放(总等级 4 ≥ 4×1)" | mp.tree_can_buy("sp_hp") | (待填) | | | +| 984 | 594 | "第 2 级购买受理(门槛恰达当刻开放)" | mp.tree_buy("sp_hp") and sv.tree_level("sp_hp") == 2 | (待填) | | | +| 985 | 598 | "Fork 购买=解锁+默认 A 态(一节点双态——单行承载)" | mp.tree_buy_fork("fork_posture") and sv.tree_level("fork_posture") == 1 and sv.fork_side("fg_posture") == "a" | (待填) | | | +| 986 | 600 | "已解锁 Fork 重复购买拒绝(cap 1)" | not mp.tree_can_buy("fork_posture") | (待填) | | | +| 987 | 601 | "同态重复切换零操作(false)" | not mp.tree_toggle_fork("fg_posture", "a") | (待填) | | | +| 988 | 602 | "未解锁 Fork 切换拒绝(解锁唯一入口=购买面)" | not mp.tree_toggle_fork("fg_sense", "b") | (待填) | | | +| 989 | 604 | "零币切态 b(购买解锁后 A/B 可切)" | mp.tree_toggle_fork("fg_posture", "b") and sv.fork_side("fg_posture") == "b" | (待填) | | | +| 990 | 606 | "切态零消费(钥石余额差分不动)" | int(sv.keystone_balance()) == bal_pre_toggle | (待填) | | | +| 991 | 607 | "规则键投影=激活态效果键(B 态稳健拓荒——tree_rule_keys 随切态换键集)" | (mp.tree_rule_keys() as Array).has("posture_bulwark") and not (mp.tree_rule_keys() as Array).has("posture_first_strike") | (待填) | | | +| 992 | 609 | "切回 A 态键集随换(六键词表在册——V11 校验面归单元段)" | mp.tree_toggle_fork("fg_posture", "a") and (mp.tree_rule_keys() as Array).has("posture_first_strike") | (待填) | | | +| 993 | 612 | "彩蛋不经购买(手动购亮=红——觉醒唯一入口=特化判定)" | not mp.tree_can_buy("easter_ley") | (待填) | | | +| 994 | 613 | "觉醒前置未满足不亮(两线未拉满)" | sv.tree_level("easter_ley") == 0 | (待填) | | | +| 995 | 618 | "两线拉满+同调位全解锁 → 地脉共鸣自动点亮(零币特化判定)" | sv.tree_level("easter_ley") == 1 | (待填) | | | +| 996 | 621 | "觉醒零消费(钥石余额不动)" | int(sv.keystone_balance()) == bal_now | (待填) | | | -## S118 _test_seal_place(行 99–105 · 3 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) +## S118 _smoke_tree_rules(行 633–757 · 21 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1012 | 99 | "存在未揭示安全格(封印前置)" | false | (待填) | | | -| 1013 | 104 | "封印后该格在封印态" | _rules.has_seal(c) | (待填) | | | -| 1014 | 105 | "封印数计数 = 1" | _rules.seal_count() == 1 | (待填) | | | +| 997 | 633 | "先手压制:每关首战首轮窗命中(battle_index=1 且 round=1 → grant_ap queue)〔天赋树批 V10〕" | (fx.dispatch(ctx_p, "round_start", {"battle_index": 1, "round_index": 1}, Callable()) as Array).size() == 1 | (待填) | | | +| 998 | 635 | "先手压制:首轮窗外地不触发(第二轮/第二关首战首轮——「每战首轮」旧语义换代件)" | (fx.dispatch(ctx_p, "round_start", {"battle_index": 1, "round_index": 2}, Callable()) as Array).is_empty() and (fx.dispatch(ctx_p, "round_start", {"battle_index": 2, "round_index": 1}, Callable()) as Array).is_empty() | (待填) | | | +| 999 | 638 | "稳健拓荒:首轮窗内受击出盾、窗外不出(shield damage_taken 同窗条件)" | (fx.dispatch(ctx_p, "damage_taken", {"battle_index": 1, "round_index": 1}, Callable()) as Array).size() == 1 and (fx.dispatch(ctx_p, "damage_taken", {"battle_index": 2, "round_index": 1}, Callable()) as Array).is_empty() | (待填) | | | +| 1000 | 654 | "锋:踩雷减半命中+限次记账(counter 0<2 闸开——damage_mult+counter_add 双行同预评估态)" | got_mult and got_add | (待填) | | | +| 1001 | 657 | "锋:限次耗尽全额(counter 2 不<2 闸关——每关限次 provisional 2)〔无状态化 V5〕" | (fz.dispatch(ctx_fz, "mine_hit", {}, Callable()) as Array).is_empty() | (待填) | | | +| 1002 | 663 | "锋:限次跨战持久(level 桶共享引用——替代旧副本搬运链)〔无状态化 V4〕" | (fz2.dispatch(ctx_fz2, "mine_hit", {}, Callable()) as Array).is_empty() | (待填) | | | +| 1003 | 685 | "锋:惊巢反制 mine_hit 减半分支受理(wa12 前提——halve_pct=50 效果行落 step_row)〔wa12〕" | nc_hit != "" | (待填) | | | +| 1004 | 687 | "惊巢反制效果行==「惊巢反制——危险格挖掘惩罚减半(6→3)」零「挖雷」(u13 换代)〔wa12〕" | nc_hit == "惊巢反制——危险格挖掘惩罚减半(6→3)" and not nc_hit.contains("挖雷") | (待填) | | | +| 1005 | 691 | "守侧 rig:战斗编排启动(tree_rules=sense_guard 携带)" | orch.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {}, "feats": [], "tree_rules": ["sense_guard"], "battle_index": 1, "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {}, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle"}) | (待填) | | | +| 1006 | 703 | "守侧 rig:安全格定位(非危险格=标错负样本)" | safe.x >= 0 | (待填) | | | +| 1007 | 704 | "守侧 rig:首轮后 queue 干净(开局 posture queue 已兑现)" | int(orch.action_economy.pending_ap_delta()) == 0 | (待填) | | | +| 1008 | 708 | "守①:当回合标错且封印在场 → 下轮 AP+1(queue 排队——本轮 AP 逐位零变化)" | int(orch.action_economy.pending_ap_delta()) == 1 | (待填) | | | +| 1009 | 713 | "守③④:旧封印重判零触发+同格最多一次(_seal_miss_seen 去重闸=「当回合标错动作」判别器)" | int(orch.action_economy.pending_ap_delta()) == 1 | (待填) | | | +| 1010 | 717 | "守②负样本:标中/被挖封印零触发(标错事件只在 miss 分支发射——发射面过滤承载)" | int(orch.action_economy.pending_ap_delta()) == 1 | (待填) | | | +| 1011 | 721 | "守⑤ rig:次战编排启动(新战新队列实例)" | orch2.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {}, "feats": [], "tree_rules": ["sense_guard"], "battle_index": 1, "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {}, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle"}) | (待填) | | | +| 1012 | 727 | "守⑤:末轮判出不返(queue 只在 notify_round_start 兑现、战亡即弃——无跨战泄漏)" | int(orch2.action_economy.pending_ap_delta()) == 0 | (待填) | | | +| 1013 | 733 | "散未激活:商品标价原价(无 economy_spend 键)" | int(lr._spend_discounted(100)) == 100 | (待填) | | | +| 1014 | 735 | "散激活:商品购买价 ×80%(provisional 折率——floor 偏向玩家、下限 1;刷新价不折=另一计价点零触碰)" | int(lr._spend_discounted(100)) == 80 and int(lr._spend_discounted(1)) == 1 | (待填) | | | +| 1015 | 739 | "聚 rig:基线编排启动(无增益侧)" | orch3.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {}, "feats": [], "tree_rules": [], "battle_index": 1, "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {}, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle"}) | (待填) | | | +| 1016 | 748 | "聚 rig:增益编排启动(tree_rules=economy_hoard)" | orch4.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {}, "feats": [], "tree_rules": ["economy_hoard"], "battle_index": 1, "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {}, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle"}) | (待填) | | | +| 1017 | 756 | "聚:战斗掉落乘算 ×1.5(provisional——同种子配对 %d→%d,入账前=结算框显示实得)" | gp_hoard == int(round(float(gp_base) * 1.5)) and gp_hoard >= gp_base | (待填) | | | -## S119 _test_reject_out_of_catalog(行 111–112 · 1 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) +## S119 _init(行 22–30 · 2 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1015 | 111 | "目录外意图被拒(不静默吞掉)——实际载荷 %s" ↩ % str(o).substr(0, 80) | out.size() == 1 and String(o.get("outcome", o.get("result", ""))) == "rejected" | (待填) | | | +| 1018 | 22 | "数值表加载" | false | (待填) | | | +| 1019 | 30 | "生成返回棋盘(前置)" | false | (待填) | | | -## S120 _test_output_ok(行 88–89 · 2 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) +## S120 _test_install_invariants(行 48–56 · 4 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1016 | 88 | "三档×20 种子产出图过产出约束五条+结构铁律(自证零错误)〔fv1〕" | all_ok | (待填) | | | -| 1017 | 89 | "入口数/终点数 = 参数档 entry_count/exit_count〔fv1〕" | entry_exit_ok | (待填) | | | +| 1020 | 48 | "装板后 current_board 即所装棋盘" | _rules.current_board() == _board | (待填) | | | +| 1021 | 52 | "装板带初始扩张揭示(%d 格,非零且未铺满安全格 %d)" ↩ % [opened, safe_total] | opened > 0 and opened < safe_total | (待填) | | | +| 1022 | 54 | "安全格余量 = 安全格总数 − 已揭示数(%d − %d)" ↩ % [safe_total, opened] | _rules.safe_remaining() == safe_total - opened | (待填) | | | +| 1023 | 56 | "装板后仍有未揭示格" | _rules.has_unrevealed() | (待填) | | | -## S121 _test_reproducible(行 99–119 · 3 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) +## S121 _test_dig_face_rules(行 73–80 · 2 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1018 | 99 | "同(种子,参数档)两次产图逐位同图〔fv3〕" | same | (待填) | | | -| 1019 | 109 | "counter 无关(不同号位态同种子同图)〔fv3〕" | _sig(gen_a.generate(_params(2), 42)) == _sig(gen_b.generate(_params(2), 42)) and _sig(gen_a.generate(_params(3), 7)) == _sig(gen_b.generate(_params(3), 7)) | (待填) | | | -| 1020 | 119 | "同参数档不同种子产出异图(10 种子去重数 ≥ 8)〔fv3〕" | distinct >= 8 | (待填) | | | +| 1024 | 73 | "挖已揭示格被拒(board_face=revealed,禁重复挖)——载荷 %s" ↩ % str(o.get("sub_reason", "")) | String(o.get("outcome", "")) == "rejected" and String(o.get("sub_reason", "")) == "target_revealed" | (待填) | | | +| 1025 | 79 | "挖非相邻格被拒(邻接规则)——载荷 %s" ↩ % str(o2.get("sub_reason", "")) | String(o2.get("outcome", "")) == "rejected" | (待填) | | | -## S122 _test_fork_rules(行 163–164 · 2 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) +## S122 _test_reveal_advances(行 86–93 · 4 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1021 | 163 | "任意岔口各去路下一节点类型互不相同〔fv5〕" | next_hop_ok | (待填) | | | -| 1022 | 164 | "任意岔口各支路类型构成互异且各有独有类型〔fv5〕" | comp_ok | (待填) | | | +| 1026 | 86 | "存在未揭示安全格(揭示推进前置)" | false | (待填) | | | +| 1027 | 90 | "专长揭示受理(fx_reveal 不受邻接约束)" | ok | (待填) | | | +| 1028 | 91 | "fx_reveal 后该格已揭示" | _rules.is_revealed(c) | (待填) | | | +| 1029 | 92 | "fx_reveal 后安全格余量递减(%d → %d)" ↩ % [before, _rules.safe_remaining()] | _rules.safe_remaining() < before | (待填) | | | -## S123 _test_shop_and_attribution(行 228–230 · 3 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) +## S123 _test_seal_place(行 99–105 · 3 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1023 | 228 | "任意入口→终点路径恰过一家店〔fv6〕" | paths_ok | (待填) | | | -| 1024 | 229 | "每终点前一位恰一店+商店总数=终点数(归属锁定)〔fv6〕" | lock_ok | (待填) | | | -| 1025 | 230 | "多终点落在互异节点深度(L2/L3)〔fv6〕" | depth_ok | (待填) | | | +| 1030 | 99 | "存在未揭示安全格(封印前置)" | false | (待填) | | | +| 1031 | 104 | "封印后该格在封印态" | _rules.has_seal(c) | (待填) | | | +| 1032 | 105 | "封印数计数 = 1" | _rules.seal_count() == 1 | (待填) | | | -## S124 _test_battle_floor(行 257–257 · 1 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) +## S124 _test_reject_out_of_catalog(行 111–112 · 1 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1026 | 257 | "任意合法产出战斗节点数 ≥ battle_floor〔fv7〕" | ok | (待填) | | | +| 1033 | 111 | "目录外意图被拒(不静默吞掉)——实际载荷 %s" ↩ % str(o).substr(0, 80) | out.size() == 1 and String(o.get("outcome", o.get("result", ""))) == "rejected" | (待填) | | | -## S125 _test_exit_structure(行 285–287 · 3 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) +## S125 _test_output_ok(行 88–89 · 2 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1027 | 285 | "三关参数档生成图各含 ≥1 岔口节点(第一关即含)〔fv9〕" | fork_all | (待填) | | | -| 1028 | 286 | "L1 生成图终点恰 1(出口恒 1)〔fv9〕" | l1_single | (待填) | | | -| 1029 | 287 | "L2/L3 含分区域大岔口(一节点可达 ≥2 终点且去路数=出口数)〔fv9〕" | big_fork_ok | (待填) | | | +| 1034 | 88 | "三档×20 种子产出图过产出约束五条+结构铁律(自证零错误)〔fv1〕" | all_ok | (待填) | | | +| 1035 | 89 | "入口数/终点数 = 参数档 entry_count/exit_count〔fv1〕" | entry_exit_ok | (待填) | | | -## S126 _test_rng_single_point(行 313–318 · 3 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) +## S126 _test_reproducible(行 99–119 · 3 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1030 | 313 | "pure_stream 不推全局计数器(产图前后 counter 同值)〔fv10〕" | svc.counter() == before | (待填) | | | -| 1031 | 316 | "pure_stream 同 (seed,scope) 同流〔fv10〕" | a.randf() == b.randf() and a.randi() == b.randi() | (待填) | | | -| 1032 | 317 | "pure_stream 不推计数器且产图非空〔fv10〕" | svc.counter() == before and (g["nodes"] as Array).size() > 0 | (待填) | | | +| 1036 | 99 | "同(种子,参数档)两次产图逐位同图〔fv3〕" | same | (待填) | | | +| 1037 | 109 | "counter 无关(不同号位态同种子同图)〔fv3〕" | _sig(gen_a.generate(_params(2), 42)) == _sig(gen_b.generate(_params(2), 42)) and _sig(gen_a.generate(_params(3), 7)) == _sig(gen_b.generate(_params(3), 7)) | (待填) | | | +| 1038 | 119 | "同参数档不同种子产出异图(10 种子去重数 ≥ 8)〔fv3〕" | distinct >= 8 | (待填) | | | -## S127 _test_negative_fixtures(行 328–412 · 13 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) +## S127 _test_fork_rules(行 163–164 · 2 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1033 | 328 | "句柄重复夹具报 dup_key〔fv1〕" | _has_cat(_gen.validate_output(dup), "dup_key") | (待填) | | | -| 1034 | 332 | "悬空出边夹具报 dangling_next〔fv1〕" | _has_cat(_gen.validate_output(dang), "dangling_next") | (待填) | | | -| 1035 | 336 | "类型出域夹具报 enum〔fv1〕" | _has_cat(_gen.validate_output(badty), "enum") | (待填) | | | -| 1036 | 343 | "战斗节点低于 battle_floor 的合法形状夹具命中「低于保底」判据〔fv7〕" | _has_msg(_gen.validate_output(thin_battle, {"battle_floor": 4}), "低于保底") | (待填) | | | -| 1037 | 345 | "同夹具在保底 1 下自证零错误(判据不误伤)〔fv7〕" | _gen.validate_output(thin_battle, {"battle_floor": 1}).is_empty() | (待填) | | | -| 1038 | 350 | "非 Boss 终点夹具命中「终点(无出边)非 Boss」判据〔fv1〕" | _has_msg(_gen.validate_output(noBoss), "终点(无出边)非 Boss") | (待填) | | | -| 1039 | 361 | "合法合流图(第二入口直插主链,深度取最长路 3)自证零错误〔fv1〕" | _gen.validate_output(conf_legal).is_empty() | (待填) | | | -| 1040 | 370 | "声明深度高于最长路径步数被拒(深度=自入口最长路径步数)〔fv1〕" | _has_msg(_gen.validate_output(conf_high), "自入口最长路径步数") | (待填) | | | -| 1041 | 377 | "回边夹具报前进方向违例(structural)〔fv1〕" | _has_cat(_gen.validate_output(loop), "structural") | (待填) | | | -| 1042 | 387 | "多终点同深度夹具报 structural〔fv6〕" | _has_cat(_gen.validate_output(twin), "structural") | (待填) | | | -| 1043 | 397 | "双终点异深+区域类型互异合法夹具自证零错误〔fv6〕" | _gen.validate_output(twin_ok).is_empty() | (待填) | | | -| 1044 | 404 | "终点前驱非店夹具报 structural(必经恰一店/商店数)〔fv6〕" | _has_cat(cats, "structural") | (待填) | | | -| 1045 | 412 | "岔口同类型下一跳夹具报 structural〔fv5〕" | _has_cat(_gen.validate_output(sameHop), "structural") | (待填) | | | +| 1039 | 163 | "任意岔口各去路下一节点类型互不相同〔fv5〕" | next_hop_ok | (待填) | | | +| 1040 | 164 | "任意岔口各支路类型构成互异且各有独有类型〔fv5〕" | comp_ok | (待填) | | | -## S128 _test_growth(行 57–58 · 2 条 · 源 game/tests/unit/core/node_map_growth_test.gd · 去向:待分类回填) +## S128 _test_shop_and_attribution(行 228–230 · 3 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1046 | 57 | "生长递增:三档最长路径步数逐关变大〔fv8〕" | steps_ok | (待填) | | | -| 1047 | 58 | "生长递增:节点总数逐关变大且增幅变大〔fv8〕" | count_ok | (待填) | | | +| 1041 | 228 | "任意入口→终点路径恰过一家店〔fv6〕" | paths_ok | (待填) | | | +| 1042 | 229 | "每终点前一位恰一店+商店总数=终点数(归属锁定)〔fv6〕" | lock_ok | (待填) | | | +| 1043 | 230 | "多终点落在互异节点深度(L2/L3)〔fv6〕" | depth_ok | (待填) | | | -## S129 _test_fail_fast_static(行 64–74 · 3 条 · 源 game/tests/unit/core/node_map_growth_test.gd · 去向:待分类回填) +## S129 _test_battle_floor(行 257–257 · 1 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1048 | 64 | "生产三档参数档可行性判定零错误〔fv4〕" | NodeMapGenerator.check_params(db.level_gen_params_row(1)).is_empty() and NodeMapGenerator.check_params(db.level_gen_params_row(3)).is_empty() | (待填) | | | -| 1049 | 70 | "层深 6 放三出口无解 → check_params 拦截〔fv4〕" | not NodeMapGenerator.check_params(unsolvable).is_empty() | (待填) | | | -| 1050 | 74 | "战斗保底超可放数无解 → check_params 拦截〔fv4〕" | not NodeMapGenerator.check_params(overfloor).is_empty() | (待填) | | | +| 1044 | 257 | "任意合法产出战斗节点数 ≥ battle_floor〔fv7〕" | ok | (待填) | | | -## S130 _test_fatal_subprocess(行 82–91 · 2 条 · 源 game/tests/unit/core/node_map_growth_test.gd · 去向:待分类回填) +## S130 _test_exit_structure(行 285–287 · 3 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1051 | 82 | "未找到引擎二进制,致命子进程通道未跑(如实标注)〔fv4〕" | false | (待填) | | | -| 1052 | 91 | "无解参数档置致命封印并由组合根停机(退出码恰 1,无残图交付)〔fv4〕" | code == 1 | (待填) | | | +| 1045 | 285 | "三关参数档生成图各含 ≥1 岔口节点(第一关即含)〔fv9〕" | fork_all | (待填) | | | +| 1046 | 286 | "L1 生成图终点恰 1(出口恒 1)〔fv9〕" | l1_single | (待填) | | | +| 1047 | 287 | "L2/L3 含分区域大岔口(一节点可达 ≥2 终点且去路数=出口数)〔fv9〕" | big_fork_ok | (待填) | | | -## S131 _smoke_save(行 81–329 · 60 条 · 源 game/tests/unit/core/save_manager_domain_test.gd · 去向:待分类回填) +## S131 _test_rng_single_point(行 313–318 · 3 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1053 | 81 | "apply 原语入账(钥石 7+3、通关次数 1/1/0)" | w.keystone_balance() == 10 and w.clear_count(1) == 1 and w.clear_count(2) == 1 and w.clear_count(3) == 0 | 收束原语入账(钥石+通关次数按摘要)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1054 | 83 | "首杀标记随通关关卡置位(1/2 有、3 无)" | w.boss_first_kill_marked(1) and w.boss_first_kill_marked(2) and not w.boss_first_kill_marked(3) | 首杀标记随通关关卡置位。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1055 | 85 | "生涯终身计数随 apply 自增恰一(中止局:总局数 1、通关局数 0——迭代 18 卡④)" | w.adventure_total() == 1 and w.adventure_cleared() == 0 | 生涯终身计数随收束自增恰一(中止局口径)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1056 | 87 | "写盘成功" | w.write_to_disk() | 写盘成功。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1057 | 89 | "读回成功" | r.load_from_disk() | 读回成功。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1058 | 90 | "钥石余额往返相等(10)" | r.keystone_balance() == 10 | 钥石余额往返相等。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1059 | 91 | "树域往返相等(tree_nodes/fork_sides——迭代 21 换代承载)" | r.tree_level("sp_hp") == 2 and r.tree_level("sp_hit") == 1 and r.fork_side("fg_posture") == "b" | 树域往返相等(节点/分叉侧)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1060 | 93 | "锻造工坊域往返相等(levels/unlocked/weapon/armor——迭代 23 T5)〔锻造工坊批 V8〕" | r.forge_route_level("spear") == 3 and r.forge_route_level("sword") == 0 and r.forge_unlocked_purchases() == ["spear"] and r.forge_weapon() == "spear" and r.forge_armor() == "ward" | 锻造域往返相等(等级/直购/选中)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1061 | 97 | "道具解锁往返相等" | r.item_unlocked("probe_compass") and not r.item_unlocked("rope") | 道具解锁往返相等。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1062 | 98 | "通关次数表往返相等" | r.clear_count(1) == 1 and r.clear_count(2) == 1 and r.clear_count(3) == 0 | 通关次数表往返相等。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1063 | 99 | "首杀标记往返相等" | r.boss_first_kill_marked(1) and r.boss_first_kill_marked(2) and not r.boss_first_kill_marked(3) | 首杀标记往返相等。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1064 | 101 | "生涯终身计数往返相等(1/0)" | r.adventure_total() == 1 and r.adventure_cleared() == 0 | 生涯终身计数往返相等。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1065 | 103 | "冒险摘要往返相等(1 条,seed 99)" | r.recent_summaries().size() == 1 and int(s0.get("master_seed", -1)) == 99 and int(s0.get("keystone_total", -1)) == 3 | 冒险摘要往返相等(1 条种子 99)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1066 | 108 | "新档合法态(load true + 全零默认)" | f2.load_from_disk() and f2.keystone_balance() == 0 and f2.tree_level("sp_hp") == 0 and not f2.item_unlocked("anything") and f2.clear_count(1) == 0 and f2.recent_summaries().is_empty() | 新档合法态(读入 true+全零默认)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1067 | 111 | "新档生涯终身计数缺省全零(迭代 18 卡④两新域——白板存档完整可玩)" | f2.adventure_total() == 0 and f2.adventure_cleared() == 0 | 新档生涯计数缺省全零(白板完整可玩)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1068 | 117 | "余额 5 花 8 拒绝且余额不动" | not f3.spend_keystone(8) and f3.keystone_balance() == 5 | 余额不足花钥石拒绝且余额不动。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1069 | 118 | "余额内消费成功(5→2)" | f3.spend_keystone(3) and f3.keystone_balance() == 2 | 余额内消费成功。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1070 | 126 | "高版本拒读(load false)" | not f5.load_from_disk() | 高版本档拒读(读入 false)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1071 | 127 | "拒读后原文件字节不动" | FileAccess.open(paths[3], FileAccess.READ).get_as_text() == hv_text | 拒读后原文件字节不动。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1072 | 128 | "拒读态写盘拒绝且文件仍字节不动" | not f5.write_to_disk() and FileAccess.open(paths[3], FileAccess.READ).get_as_text() == hv_text | 拒读态写盘拒绝且文件仍字节不动。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1073 | 137 | "损坏档兜底(默认数据继续)" | f6.load_from_disk() and f6.keystone_balance() == 0 and f6.recent_summaries().is_empty() | 损坏档兜底(默认数据继续)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1074 | 139 | "坏档备份 .corrupt.bak 存在" | FileAccess.file_exists(paths[4] + ".corrupt.bak") | 坏档备份文件存在。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1075 | 140 | "备份内容 = 原坏档字节" | FileAccess.open(paths[4] + ".corrupt.bak", FileAccess.READ).get_as_text() == junk | 备份内容=原坏档字节。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1076 | 151 | "v0 旧档经迁移链入账(keystone_old 12 → 余额 12)" | f7.load_from_disk() and f7.keystone_balance() == 12 | v0 旧档经迁移链入账。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1077 | 153 | "迁移链 0→1(测试钩)→7(生产) 写盘 version == 7(v7=锻造工坊域,迭代 23 T5——v6=生涯终身计数域迭代 18 卡④)" | f7.write_to_disk() and int(JSON.parse_string(FileAccess.open(paths[5], FileAccess.READ).get_as_text()).get("version", -1)) == 7 | 迁移链写盘版本=7(链式升到现行)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1078 | 165 | "域界干净——meta 域无局内资源键(发现 %s)" ↩ % str(leaked) | leaked.is_empty() | 域界干净——meta 域无局内资源键。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1079 | 173 | "摘要 cap 10(12 份 → 10 条)" | arr.size() == 10 | 摘要上限 10(12 份截断)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1080 | 174 | "最新在前(头 seed 11 / 尾 seed 2)" | int(arr[0].get("master_seed", -1)) == 11 and int(arr[9].get("master_seed", -1)) == 2 | 摘要最新在前。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1081 | 182 | "断点:新档无断点(合法空态——「继续上局」隐藏)" | not fbp.has_breakpoint() | 断点:新档无断点(合法空态)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1082 | 193 | "断点:写入+写盘成功" | fbp.has_breakpoint() and fbp.write_to_disk() | 断点写入+写盘成功。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1083 | 195 | "断点:读回在档" | rb.load_from_disk() and rb.has_breakpoint() | 断点读回在档。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1084 | 197 | "断点七字段往返:关卡序号/节点位/生命" | int(bp.get("level_index", -1)) == 2 and int(bp.get("node_id", -1)) == 5 and int(bp.get("hero_hp", -1)) == 13 | 断点往返:关卡序号/节点位/生命。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1085 | 201 | "断点七字段往返:属性加成区" | int(bp_bonus.get("atk", -1)) == 2 and int(bp_bonus.get("max_hp", -1)) == 5 and int(bp_bonus.get("armor_class", -1)) == 1 | 断点往返:属性加成区。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1086 | 204 | "断点七字段往返:GP 钱包" | int(bp.get("gp", -1)) == 17 | 断点往返:GP 钱包。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1087 | 206 | "断点七字段往返:商店已购档" | int(bp_shop.get("hp_small", -1)) == 2 and int(bp_shop.get("atk_small", -1)) == 1 | 断点往返:商店已购档。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1088 | 208 | "断点七字段往返:生成图代差标记键" | int(bp.get("graph_gen_v2", 0)) == 1 | (待填) | | | -| 1089 | 209 | "断点附随件往返:主种子/计数器/钥石累计" | int(bp.get("master_seed", -1)) == 424242 and int(bp.get("rng_counter", -1)) == 38 and int(bp.get("keystone", -1)) == 6 | 断点附随件往返:主种子/计数器/钥石累计。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1090 | 213 | "断点附随件往返:已到节点序" | bp_visited.size() == 3 and int(bp_visited[0]) == 0 and int(bp_visited[2]) == 5 | 断点附随件往返:已到节点序。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1091 | 216 | "断点附随件往返:道具库存" | bp_treasure.size() == 1 and String(bp_treasure[0].get("item_id", "")) == "aux_scout" | 断点附随件往返:道具库存。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1092 | 219 | "断点读面值拷贝(改读面不渗档)" | int((rb.breakpoint_snapshot() as Dictionary).get("hero_hp", -1)) == 13 | 断点读面值拷贝(改读面不渗档)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1093 | 222 | "断点:清除+写盘(三结局收束口径)" | not rb.has_breakpoint() and rb.write_to_disk() | 断点清除+写盘。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1094 | 224 | "断点:清除后读回无断点" | rc.load_from_disk() and not rc.has_breakpoint() | 断点清除后读回无断点。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1095 | 229 | "v1 旧档迁移 v7:断点域补空(合法空态,无断点域不炸)" | fo.load_from_disk() and fo.keystone_balance() == 4 and not fo.has_breakpoint() | v1 旧档迁移:断点域补空(合法空态不炸)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1096 | 231 | "v1 旧档迁移后写盘 version == 7(v1→v7 链式——锻造工坊域 2026-10-12)" | fo.write_to_disk() and int(JSON.parse_string(FileAccess.open(paths[9], FileAccess.READ).get_as_text()).get("version", -1)) == 7 | v1 旧档迁移后写盘版本=7(链式)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1097 | 243 | "v2 旧档迁移 v3:断点域与 meta 域原样保留(缺新附随件=合法空态)" | fv2.load_from_disk() and fv2.has_breakpoint() and fv2.keystone_balance() == 7 | v2 旧档迁移:断点域与 meta 域原样保留。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1098 | 245 | "v2 旧断点字段零丢失(向后兼容——breakpoint 内容不因 schema 升版重写)" | int((fv2.breakpoint_snapshot() as Dictionary).get("master_seed", 0)) == 777 | v2 旧断点字段零丢失(向后兼容)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1099 | 248 | "v2 旧断点续玩受理(缺 bonus 附随件走常规重建路径——g 裁 semver 口径)" | adr_v2.resume_adventure(fv2.breakpoint_snapshot(), {"hero_override": {}}) | v2 旧断点续玩受理(缺附随件走常规重建)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1100 | 257 | "v5 旧档升 v7:计数域缺省补零且既有余额原样(迁移只增字段不改值——v5→v6→v7 链式)" | f13.load_from_disk() and f13.adventure_total() == 0 and f13.adventure_cleared() == 0 and f13.keystone_balance() == 9 | v5 旧档升版:计数域缺省补零且既有余额原样(只增不改)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1101 | 260 | "升档幂等:v7 档写盘再载零漂移(重复迁移零副作用——锻造工坊域同批补齐)" | f13.write_to_disk() and f13.load_from_disk() and int(JSON.parse_string(FileAccess.open(paths[11], FileAccess.READ).get_as_text()).get("version", -1)) == 7 and f13.adventure_total() == 0 and f13.adventure_cleared() == 0 | 升档幂等:写盘再载零漂移。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1102 | 268 | "收束自增恰一·通关局(三关全通 cleared=true:总局数+1 且通关局数+1)" | f13b.adventure_total() == 1 and f13b.adventure_cleared() == 1 | 收束自增恰一·通关局(总局数+1 且通关局数+1)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1103 | 272 | "收束自增恰一·中止局(放弃局:仅总局数+1,通关局数不动)" | f13b.adventure_total() == 2 and f13b.adventure_cleared() == 1 | 收束自增恰一·中止局(仅总局数+1)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1104 | 283 | "换代前结构存档:树域废弃重置(旧节点 id/fork 侧/forge_levels 旧域残留清退)、钥石余额零折算零变动" | f15.load_from_disk() and f15.keystone_balance() == 33 and f15.tree_nodes().is_empty() and f15.fork_sides().is_empty() | 换代前结构存档:树域废弃重置(旧残留清退、余额零折算)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1105 | 288 | "废弃重置后后续购买正常受理(无残留脏节点)" | mp16.tree_buy("sp_hp") and f15.tree_level("sp_hp") == 1 | 废弃重置后后续购买正常受理(无残留脏节点)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1106 | 297 | "v6 旧档升 v7:既有域逐位不动(钥石/树/生涯计数零折算——迁移只增不改)〔锻造工坊批 V8〕" | f16.load_from_disk() and f16.keystone_balance() == 21 and f16.tree_level("sp_hp") == 1 and f16.adventure_total() == 4 and f16.adventure_cleared() == 1 | v6 旧档升版:既有域逐位不动(迁移只增不改)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1107 | 300 | "v6 旧档缺键补齐=初始持有态(六路线 0 级/直购清单空/选中=sword+light)〔锻造工坊批 V8〕" | f16.forge_route_level("sword") == 0 and f16.forge_route_level("spear") == 0 and f16.forge_route_level("light") == 0 and f16.forge_route_level("ward") == 0 and f16.forge_unlocked_purchases().is_empty() and f16.forge_weapon() == "sword" and f16.forge_armor() == "light" | v6 旧档缺键补齐=初始持有态(六路线 0 级/选中默认)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1108 | 307 | "forge_routes 域键集恰四项(无特效域——满级特效本期不做,契约 7)〔锻造工坊批 V8/V11〕" | fr_keys == ["armor", "levels", "unlocked", "weapon"] | 锻造域键集恰四项(无特效域)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1109 | 311 | "levels 六路线键集恰全(sword/spear/staff/light/heavy/ward)〔锻造工坊批 V8〕" | lv_keys == ["heavy", "light", "spear", "staff", "sword", "ward"] | levels 六路线键集恰全。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1110 | 316 | "forge_routes_snapshot 值拷贝出户(改快照不渗档)〔锻造工坊批 V8〕" | f16.forge_route_level("sword") == 0 | 锻造快照值拷贝出户(改快照不渗档)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1111 | 324 | "收束链自增·中止局(settle_adventure 真实挂点:总局数+1、通关局数不动)" | f13b.adventure_total() == 3 and f13b.adventure_cleared() == 1 | 收束链自增·中止局(真实挂点全链恰一)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1112 | 328 | "收束链自增·通关局(三关全通:总局数+1 且通关局数+1——全链恰一)" | f13b.adventure_total() == 4 and f13b.adventure_cleared() == 2 | 收束链自增·通关局(全链恰一)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | - -## S132 _smoke_save_slots(行 359–499 · 31 条 · 源 game/tests/unit/core/save_manager_domain_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 1113 | 359 | "sv5 进档受理·槽 %d(激活+读盘)〔卡① sv5〕" ↩ % i | t.activate_slot(i) | 进档受理(激活+读盘)逐槽。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1114 | 362 | "sv3 激活态写盘受理·槽 %d〔卡① sv1〕" ↩ % i | t.write_to_disk() | 激活态写盘受理逐槽。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1115 | 366 | "sv1 三槽往返一致·槽 %d(异值按位读回)〔卡① sv1〕" ↩ % i | t2.activate_slot(i) and t2.keystone_balance() == 100 * i and t2.adventure_total() == 1 and t2.adventure_cleared() == (1 if i == 1 else 0) | 三槽往返一致(异值按位读回)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1116 | 372 | "sv3 写盘受理·槽 1 加值后〔卡① sv1〕" | t.write_to_disk() | 槽 1 加值后写盘受理。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1117 | 373 | "sv1 位间隔离:改槽 1 槽 2 字节不动〔卡① sv1〕" | FileAccess.get_file_as_bytes(slot_tpl % 2) == slot2_before | 位间隔离:改槽 1 槽 2 字节不动。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1118 | 377 | "sv5 切激活至槽 3(删激活槽夹具前置)〔卡① sv5〕" | t.activate_slot(3) | 切激活至槽 3(删档夹具前置)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1119 | 378 | "sv6 删档受理·激活槽 3〔卡① sv6〕" | t.delete_slot(3) | 删激活槽受理。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1120 | 379 | "sv6 删后文件不在盘+无 .tmp 残留〔卡① sv6〕" | not FileAccess.file_exists(slot_tpl % 3) and not FileAccess.file_exists(slot_tpl % 3 + ".tmp") | 删后文件不在盘+无临时残留。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1121 | 381 | "sv6 删激活槽激活指针连清除(该槽不再可写)〔卡① sv6〕" | t.active_slot() == 0 and not t.has_write_target() | 删激活槽激活指针连清除(该槽不再可写)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1122 | 385 | "sv6 删非激活槽受理·槽 2〔卡① sv6〕" | t.delete_slot(2) | 删非激活槽受理。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1123 | 386 | "sv6 删非激活槽:文件不在盘+激活指针保持〔卡① sv6〕" | not FileAccess.file_exists(slot_tpl % 2) and t.active_slot() == 1 | 删非激活槽:文件不在盘+激活指针保持。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1124 | 388 | "sv6 删槽 2 槽 1 字节不动(位间互隔离)〔卡① sv6〕" | FileAccess.get_file_as_bytes(slot_tpl % 1) == slot1_before | 删槽 2 槽 1 字节不动(位间互隔离)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1125 | 397 | "sv4 新档受理·槽 3(创建即激活)〔卡① sv4〕" | t.create_slot(3) | 新档受理(创建即激活)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1126 | 398 | "sv4 新档=全零可玩合法白板态(旧内容 999/5 零带入——钥石/总局数/断点全零)〔卡① sv4〕" | t.active_slot() == 3 and t.keystone_balance() == 0 and not t.has_breakpoint() and t.adventure_total() == 0 | 新档=全零可玩合法白板态(旧内容零带入)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1127 | 402 | "sv4 新档落盘=白板内容(盘面核验非 999 旧值)〔卡① sv4〕" | int((created.get("meta", {}) as Dictionary).get("keystone_balance", -1)) == 0 | 新档落盘=白板内容(盘面核验非旧值)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1128 | 411 | "sv5 进档槽 1 读回(107)〔卡① sv10〕" | hv.activate_slot(1) and hv.keystone_balance() == 107 | 进档槽 1 读回(高版本夹具前置)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1129 | 414 | "sv9 高版本槽进档拒绝(拒读禁写链按槽)〔卡① sv9〕" | not hv.activate_slot(2) | 高版本槽进档拒绝(拒读禁写链按槽)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1130 | 415 | "sv10 激活失败恢复切换前三态(路径/激活号/数据归槽 1)〔卡① sv10〕" | hv.active_slot() == 1 and hv.keystone_balance() == 107 | 激活失败恢复切换前三态(路径/激活号/数据)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1131 | 418 | "sv10 恢复后写盘落激活槽 1(高版本槽 2 零误写)〔卡① sv10〕" | hv.write_to_disk() and FileAccess.get_file_as_bytes(slot_tpl % 2) == slot2_hv | 恢复后写盘落激活槽(高版本槽零误写)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1132 | 429 | "sv9 迁移链按槽成立·槽 %d(v5→v7 各槽独立+写盘 version=7)〔卡① sv9〕" ↩ % i | mi.activate_slot(i) and mi.keystone_balance() == i * 11 and mi.write_to_disk() | 迁移链按槽成立(各槽独立+写盘版本=7)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1133 | 444 | "sv7 窥视三项逐槽正确·槽 %d〔卡① sv7〕" ↩ % i | bool(ps.get("readable", false)) and int(ps.get("keystone", -1)) == 200 * i and int(ps.get("cleared", -1)) == i and int(ps.get("total", -1)) == 5 * i | 窥视三项逐槽正确(余额/通关/总局数)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1134 | 447 | "sv6 删槽 3 备空槽窥视〔卡① sv6〕" | pv.delete_slot(3) | 删槽 3 备空槽窥视。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1135 | 449 | "sv7 空槽窥视=零值合法态〔卡① sv7〕" | bool(pe.get("readable", false)) and int(pe.get("keystone", -1)) == 0 and int(pe.get("cleared", -1)) == 0 and int(pe.get("total", -1)) == 0 | 空槽窥视=零值合法态。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1136 | 458 | "sv8 坏槽窥视=显式损坏标记〔卡① sv8〕" | not bool(pb.get("readable", true)) | 坏槽窥视=显式损坏标记。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1137 | 459 | "sv8 坏槽窥视零副作用(字节+mtime 双比对不变+零 .corrupt.bak 改名)〔卡① sv8〕" | FileAccess.get_file_as_bytes(slot_tpl % 1) == bad_bytes and FileAccess.get_modified_time(slot_tpl % 1) == bad_mtime and not FileAccess.file_exists(slot_tpl % 1 + ".corrupt.bak") | 坏槽窥视零副作用(字节+mtime 双比对+零改名)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1138 | 469 | "sv8 高版本槽窥视=不可读标记〔卡① sv8〕" | not bool(ph.get("readable", true)) | 高版本槽窥视=不可读标记。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1139 | 470 | "sv8 高版本窥视零副作用(字节+mtime 不变+零改名)〔卡① sv8〕" | FileAccess.get_file_as_bytes(slot_tpl % 2) == hv_bytes and FileAccess.get_modified_time(slot_tpl % 2) == hv_mtime | 高版本窥视零副作用(字节+mtime 不变)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1140 | 483 | "sv12 旧档字节迁入槽 1(逐域相等——余额 1234)〔卡① sv12〕" | int((migrated.get("meta", {}) as Dictionary).get("keystone_balance", -1)) == 1234 | 旧档字节迁入槽 1(逐域相等)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1141 | 485 | "sv12 迁移后源改名留痕(.migrated.bak 在盘、原路径不在)〔卡① sv12〕" | not FileAccess.file_exists(legacy_src) and FileAccess.file_exists(legacy_src + ".migrated.bak") | 迁移后源改名留痕(备份在盘原路径不在)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1142 | 494 | "sv12 幂等:槽 1 已存在不重迁(预置 777 零覆盖、源保留)〔卡① sv12〕" | int((slot1_now.get("meta", {}) as Dictionary).get("keystone_balance", -1)) == 1234 and FileAccess.file_exists(legacy_src) | 幂等:槽已存在不重迁(零覆盖源保留)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | -| 1143 | 497 | "sv12 迁移他槽零触碰(槽 2/3 字节不动)〔卡① sv12〕" | FileAccess.get_file_as_bytes(slot_tpl % 2) == s2_before and FileAccess.get_file_as_bytes(slot_tpl % 3) == s3_before | 迁移他槽零触碰(槽 2/3 字节不动)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | - -## S133 _init(行 14–15 · 2 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 1144 | 14 | "全部表加载 + 校验通过(含跨表护栏零 error 诊断)〔fv6〕" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | -| 1145 | 15 | "is_ready 通道随加载置位" | db.is_ready | (待填) | | | - -## S134 _test_progress_resolution(行 55–83 · 7 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) +| 1048 | 313 | "pure_stream 不推全局计数器(产图前后 counter 同值)〔fv10〕" | svc.counter() == before | (待填) | | | +| 1049 | 316 | "pure_stream 同 (seed,scope) 同流〔fv10〕" | a.randf() == b.randf() and a.randi() == b.randi() | (待填) | | | +| 1050 | 317 | "pure_stream 不推计数器且产图非空〔fv10〕" | svc.counter() == before and (g["nodes"] as Array).size() > 0 | (待填) | | | + +## S132 _test_negative_fixtures(行 328–412 · 13 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 1051 | 328 | "句柄重复夹具报 dup_key〔fv1〕" | _has_cat(_gen.validate_output(dup), "dup_key") | (待填) | | | +| 1052 | 332 | "悬空出边夹具报 dangling_next〔fv1〕" | _has_cat(_gen.validate_output(dang), "dangling_next") | (待填) | | | +| 1053 | 336 | "类型出域夹具报 enum〔fv1〕" | _has_cat(_gen.validate_output(badty), "enum") | (待填) | | | +| 1054 | 343 | "战斗节点低于 battle_floor 的合法形状夹具命中「低于保底」判据〔fv7〕" | _has_msg(_gen.validate_output(thin_battle, {"battle_floor": 4}), "低于保底") | (待填) | | | +| 1055 | 345 | "同夹具在保底 1 下自证零错误(判据不误伤)〔fv7〕" | _gen.validate_output(thin_battle, {"battle_floor": 1}).is_empty() | (待填) | | | +| 1056 | 350 | "非 Boss 终点夹具命中「终点(无出边)非 Boss」判据〔fv1〕" | _has_msg(_gen.validate_output(noBoss), "终点(无出边)非 Boss") | (待填) | | | +| 1057 | 361 | "合法合流图(第二入口直插主链,深度取最长路 3)自证零错误〔fv1〕" | _gen.validate_output(conf_legal).is_empty() | (待填) | | | +| 1058 | 370 | "声明深度高于最长路径步数被拒(深度=自入口最长路径步数)〔fv1〕" | _has_msg(_gen.validate_output(conf_high), "自入口最长路径步数") | (待填) | | | +| 1059 | 377 | "回边夹具报前进方向违例(structural)〔fv1〕" | _has_cat(_gen.validate_output(loop), "structural") | (待填) | | | +| 1060 | 387 | "多终点同深度夹具报 structural〔fv6〕" | _has_cat(_gen.validate_output(twin), "structural") | (待填) | | | +| 1061 | 397 | "双终点异深+区域类型互异合法夹具自证零错误〔fv6〕" | _gen.validate_output(twin_ok).is_empty() | (待填) | | | +| 1062 | 404 | "终点前驱非店夹具报 structural(必经恰一店/商店数)〔fv6〕" | _has_cat(cats, "structural") | (待填) | | | +| 1063 | 412 | "岔口同类型下一跳夹具报 structural〔fv5〕" | _has_cat(_gen.validate_output(sameHop), "structural") | (待填) | | | + +## S133 _test_growth(行 57–58 · 2 条 · 源 game/tests/unit/core/node_map_growth_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 1064 | 57 | "生长递增:三档最长路径步数逐关变大〔fv8〕" | steps_ok | (待填) | | | +| 1065 | 58 | "生长递增:节点总数逐关变大且增幅变大〔fv8〕" | count_ok | (待填) | | | + +## S134 _test_fail_fast_static(行 64–74 · 3 条 · 源 game/tests/unit/core/node_map_growth_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 1066 | 64 | "生产三档参数档可行性判定零错误〔fv4〕" | NodeMapGenerator.check_params(db.level_gen_params_row(1)).is_empty() and NodeMapGenerator.check_params(db.level_gen_params_row(3)).is_empty() | (待填) | | | +| 1067 | 70 | "层深 6 放三出口无解 → check_params 拦截〔fv4〕" | not NodeMapGenerator.check_params(unsolvable).is_empty() | (待填) | | | +| 1068 | 74 | "战斗保底超可放数无解 → check_params 拦截〔fv4〕" | not NodeMapGenerator.check_params(overfloor).is_empty() | (待填) | | | + +## S135 _test_fatal_subprocess(行 82–91 · 2 条 · 源 game/tests/unit/core/node_map_growth_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 1069 | 82 | "未找到引擎二进制,致命子进程通道未跑(如实标注)〔fv4〕" | false | (待填) | | | +| 1070 | 91 | "无解参数档置致命封印并由组合根停机(退出码恰 1,无残图交付)〔fv4〕" | code == 1 | (待填) | | | + +## S136 _smoke_save(行 81–329 · 60 条 · 源 game/tests/unit/core/save_manager_domain_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 1071 | 81 | "apply 原语入账(钥石 7+3、通关次数 1/1/0)" | w.keystone_balance() == 10 and w.clear_count(1) == 1 and w.clear_count(2) == 1 and w.clear_count(3) == 0 | (待填) | | | +| 1072 | 83 | "首杀标记随通关关卡置位(1/2 有、3 无)" | w.boss_first_kill_marked(1) and w.boss_first_kill_marked(2) and not w.boss_first_kill_marked(3) | (待填) | | | +| 1073 | 85 | "生涯终身计数随 apply 自增恰一(中止局:总局数 1、通关局数 0——迭代 18 卡④)" | w.adventure_total() == 1 and w.adventure_cleared() == 0 | (待填) | | | +| 1074 | 87 | "写盘成功" | w.write_to_disk() | (待填) | | | +| 1075 | 89 | "读回成功" | r.load_from_disk() | (待填) | | | +| 1076 | 90 | "钥石余额往返相等(10)" | r.keystone_balance() == 10 | (待填) | | | +| 1077 | 91 | "树域往返相等(tree_nodes/fork_sides——迭代 21 换代承载)" | r.tree_level("sp_hp") == 2 and r.tree_level("sp_hit") == 1 and r.fork_side("fg_posture") == "b" | (待填) | | | +| 1078 | 93 | "锻造工坊域往返相等(levels/unlocked/weapon/armor——迭代 23 T5)〔锻造工坊批 V8〕" | r.forge_route_level("spear") == 3 and r.forge_route_level("sword") == 0 and r.forge_unlocked_purchases() == ["spear"] and r.forge_weapon() == "spear" and r.forge_armor() == "ward" | (待填) | | | +| 1079 | 97 | "道具解锁往返相等" | r.item_unlocked("probe_compass") and not r.item_unlocked("rope") | (待填) | | | +| 1080 | 98 | "通关次数表往返相等" | r.clear_count(1) == 1 and r.clear_count(2) == 1 and r.clear_count(3) == 0 | (待填) | | | +| 1081 | 99 | "首杀标记往返相等" | r.boss_first_kill_marked(1) and r.boss_first_kill_marked(2) and not r.boss_first_kill_marked(3) | (待填) | | | +| 1082 | 101 | "生涯终身计数往返相等(1/0)" | r.adventure_total() == 1 and r.adventure_cleared() == 0 | (待填) | | | +| 1083 | 103 | "冒险摘要往返相等(1 条,seed 99)" | r.recent_summaries().size() == 1 and int(s0.get("master_seed", -1)) == 99 and int(s0.get("keystone_total", -1)) == 3 | (待填) | | | +| 1084 | 108 | "新档合法态(load true + 全零默认)" | f2.load_from_disk() and f2.keystone_balance() == 0 and f2.tree_level("sp_hp") == 0 and not f2.item_unlocked("anything") and f2.clear_count(1) == 0 and f2.recent_summaries().is_empty() | (待填) | | | +| 1085 | 111 | "新档生涯终身计数缺省全零(迭代 18 卡④两新域——白板存档完整可玩)" | f2.adventure_total() == 0 and f2.adventure_cleared() == 0 | (待填) | | | +| 1086 | 117 | "余额 5 花 8 拒绝且余额不动" | not f3.spend_keystone(8) and f3.keystone_balance() == 5 | (待填) | | | +| 1087 | 118 | "余额内消费成功(5→2)" | f3.spend_keystone(3) and f3.keystone_balance() == 2 | (待填) | | | +| 1088 | 126 | "高版本拒读(load false)" | not f5.load_from_disk() | (待填) | | | +| 1089 | 127 | "拒读后原文件字节不动" | FileAccess.open(paths[3], FileAccess.READ).get_as_text() == hv_text | (待填) | | | +| 1090 | 128 | "拒读态写盘拒绝且文件仍字节不动" | not f5.write_to_disk() and FileAccess.open(paths[3], FileAccess.READ).get_as_text() == hv_text | (待填) | | | +| 1091 | 137 | "损坏档兜底(默认数据继续)" | f6.load_from_disk() and f6.keystone_balance() == 0 and f6.recent_summaries().is_empty() | (待填) | | | +| 1092 | 139 | "坏档备份 .corrupt.bak 存在" | FileAccess.file_exists(paths[4] + ".corrupt.bak") | (待填) | | | +| 1093 | 140 | "备份内容 = 原坏档字节" | FileAccess.open(paths[4] + ".corrupt.bak", FileAccess.READ).get_as_text() == junk | (待填) | | | +| 1094 | 151 | "v0 旧档经迁移链入账(keystone_old 12 → 余额 12)" | f7.load_from_disk() and f7.keystone_balance() == 12 | (待填) | | | +| 1095 | 153 | "迁移链 0→1(测试钩)→7(生产) 写盘 version == 7(v7=锻造工坊域,迭代 23 T5——v6=生涯终身计数域迭代 18 卡④)" | f7.write_to_disk() and int(JSON.parse_string(FileAccess.open(paths[5], FileAccess.READ).get_as_text()).get("version", -1)) == 7 | (待填) | | | +| 1096 | 165 | "域界干净——meta 域无局内资源键(发现 %s)" ↩ % str(leaked) | leaked.is_empty() | (待填) | | | +| 1097 | 173 | "摘要 cap 10(12 份 → 10 条)" | arr.size() == 10 | (待填) | | | +| 1098 | 174 | "最新在前(头 seed 11 / 尾 seed 2)" | int(arr[0].get("master_seed", -1)) == 11 and int(arr[9].get("master_seed", -1)) == 2 | (待填) | | | +| 1099 | 182 | "断点:新档无断点(合法空态——「继续上局」隐藏)" | not fbp.has_breakpoint() | (待填) | | | +| 1100 | 193 | "断点:写入+写盘成功" | fbp.has_breakpoint() and fbp.write_to_disk() | (待填) | | | +| 1101 | 195 | "断点:读回在档" | rb.load_from_disk() and rb.has_breakpoint() | (待填) | | | +| 1102 | 197 | "断点七字段往返:关卡序号/节点位/生命" | int(bp.get("level_index", -1)) == 2 and int(bp.get("node_id", -1)) == 5 and int(bp.get("hero_hp", -1)) == 13 | (待填) | | | +| 1103 | 201 | "断点七字段往返:属性加成区" | int(bp_bonus.get("atk", -1)) == 2 and int(bp_bonus.get("max_hp", -1)) == 5 and int(bp_bonus.get("armor_class", -1)) == 1 | (待填) | | | +| 1104 | 204 | "断点七字段往返:GP 钱包" | int(bp.get("gp", -1)) == 17 | (待填) | | | +| 1105 | 206 | "断点七字段往返:商店已购档" | int(bp_shop.get("hp_small", -1)) == 2 and int(bp_shop.get("atk_small", -1)) == 1 | (待填) | | | +| 1106 | 208 | "断点七字段往返:生成图代差标记键" | int(bp.get("graph_gen_v2", 0)) == 1 | (待填) | | | +| 1107 | 209 | "断点附随件往返:主种子/计数器/钥石累计" | int(bp.get("master_seed", -1)) == 424242 and int(bp.get("rng_counter", -1)) == 38 and int(bp.get("keystone", -1)) == 6 | (待填) | | | +| 1108 | 213 | "断点附随件往返:已到节点序" | bp_visited.size() == 3 and int(bp_visited[0]) == 0 and int(bp_visited[2]) == 5 | (待填) | | | +| 1109 | 216 | "断点附随件往返:道具库存" | bp_treasure.size() == 1 and String(bp_treasure[0].get("item_id", "")) == "aux_scout" | (待填) | | | +| 1110 | 219 | "断点读面值拷贝(改读面不渗档)" | int((rb.breakpoint_snapshot() as Dictionary).get("hero_hp", -1)) == 13 | (待填) | | | +| 1111 | 222 | "断点:清除+写盘(三结局收束口径)" | not rb.has_breakpoint() and rb.write_to_disk() | (待填) | | | +| 1112 | 224 | "断点:清除后读回无断点" | rc.load_from_disk() and not rc.has_breakpoint() | (待填) | | | +| 1113 | 229 | "v1 旧档迁移 v7:断点域补空(合法空态,无断点域不炸)" | fo.load_from_disk() and fo.keystone_balance() == 4 and not fo.has_breakpoint() | (待填) | | | +| 1114 | 231 | "v1 旧档迁移后写盘 version == 7(v1→v7 链式——锻造工坊域 2026-10-12)" | fo.write_to_disk() and int(JSON.parse_string(FileAccess.open(paths[9], FileAccess.READ).get_as_text()).get("version", -1)) == 7 | (待填) | | | +| 1115 | 243 | "v2 旧档迁移 v3:断点域与 meta 域原样保留(缺新附随件=合法空态)" | fv2.load_from_disk() and fv2.has_breakpoint() and fv2.keystone_balance() == 7 | (待填) | | | +| 1116 | 245 | "v2 旧断点字段零丢失(向后兼容——breakpoint 内容不因 schema 升版重写)" | int((fv2.breakpoint_snapshot() as Dictionary).get("master_seed", 0)) == 777 | (待填) | | | +| 1117 | 248 | "v2 旧断点续玩受理(缺 bonus 附随件走常规重建路径——g 裁 semver 口径)" | adr_v2.resume_adventure(fv2.breakpoint_snapshot(), {"hero_override": {}}) | (待填) | | | +| 1118 | 257 | "v5 旧档升 v7:计数域缺省补零且既有余额原样(迁移只增字段不改值——v5→v6→v7 链式)" | f13.load_from_disk() and f13.adventure_total() == 0 and f13.adventure_cleared() == 0 and f13.keystone_balance() == 9 | (待填) | | | +| 1119 | 260 | "升档幂等:v7 档写盘再载零漂移(重复迁移零副作用——锻造工坊域同批补齐)" | f13.write_to_disk() and f13.load_from_disk() and int(JSON.parse_string(FileAccess.open(paths[11], FileAccess.READ).get_as_text()).get("version", -1)) == 7 and f13.adventure_total() == 0 and f13.adventure_cleared() == 0 | (待填) | | | +| 1120 | 268 | "收束自增恰一·通关局(三关全通 cleared=true:总局数+1 且通关局数+1)" | f13b.adventure_total() == 1 and f13b.adventure_cleared() == 1 | (待填) | | | +| 1121 | 272 | "收束自增恰一·中止局(放弃局:仅总局数+1,通关局数不动)" | f13b.adventure_total() == 2 and f13b.adventure_cleared() == 1 | (待填) | | | +| 1122 | 283 | "换代前结构存档:树域废弃重置(旧节点 id/fork 侧/forge_levels 旧域残留清退)、钥石余额零折算零变动" | f15.load_from_disk() and f15.keystone_balance() == 33 and f15.tree_nodes().is_empty() and f15.fork_sides().is_empty() | (待填) | | | +| 1123 | 288 | "废弃重置后后续购买正常受理(无残留脏节点)" | mp16.tree_buy("sp_hp") and f15.tree_level("sp_hp") == 1 | (待填) | | | +| 1124 | 297 | "v6 旧档升 v7:既有域逐位不动(钥石/树/生涯计数零折算——迁移只增不改)〔锻造工坊批 V8〕" | f16.load_from_disk() and f16.keystone_balance() == 21 and f16.tree_level("sp_hp") == 1 and f16.adventure_total() == 4 and f16.adventure_cleared() == 1 | (待填) | | | +| 1125 | 300 | "v6 旧档缺键补齐=初始持有态(六路线 0 级/直购清单空/选中=sword+light)〔锻造工坊批 V8〕" | f16.forge_route_level("sword") == 0 and f16.forge_route_level("spear") == 0 and f16.forge_route_level("light") == 0 and f16.forge_route_level("ward") == 0 and f16.forge_unlocked_purchases().is_empty() and f16.forge_weapon() == "sword" and f16.forge_armor() == "light" | (待填) | | | +| 1126 | 307 | "forge_routes 域键集恰四项(无特效域——满级特效本期不做,契约 7)〔锻造工坊批 V8/V11〕" | fr_keys == ["armor", "levels", "unlocked", "weapon"] | (待填) | | | +| 1127 | 311 | "levels 六路线键集恰全(sword/spear/staff/light/heavy/ward)〔锻造工坊批 V8〕" | lv_keys == ["heavy", "light", "spear", "staff", "sword", "ward"] | (待填) | | | +| 1128 | 316 | "forge_routes_snapshot 值拷贝出户(改快照不渗档)〔锻造工坊批 V8〕" | f16.forge_route_level("sword") == 0 | (待填) | | | +| 1129 | 324 | "收束链自增·中止局(settle_adventure 真实挂点:总局数+1、通关局数不动)" | f13b.adventure_total() == 3 and f13b.adventure_cleared() == 1 | (待填) | | | +| 1130 | 328 | "收束链自增·通关局(三关全通:总局数+1 且通关局数+1——全链恰一)" | f13b.adventure_total() == 4 and f13b.adventure_cleared() == 2 | (待填) | | | + +## S137 _smoke_save_slots(行 359–499 · 31 条 · 源 game/tests/unit/core/save_manager_domain_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 1131 | 359 | "sv5 进档受理·槽 %d(激活+读盘)〔卡① sv5〕" ↩ % i | t.activate_slot(i) | (待填) | | | +| 1132 | 362 | "sv3 激活态写盘受理·槽 %d〔卡① sv1〕" ↩ % i | t.write_to_disk() | (待填) | | | +| 1133 | 366 | "sv1 三槽往返一致·槽 %d(异值按位读回)〔卡① sv1〕" ↩ % i | t2.activate_slot(i) and t2.keystone_balance() == 100 * i and t2.adventure_total() == 1 and t2.adventure_cleared() == (1 if i == 1 else 0) | (待填) | | | +| 1134 | 372 | "sv3 写盘受理·槽 1 加值后〔卡① sv1〕" | t.write_to_disk() | (待填) | | | +| 1135 | 373 | "sv1 位间隔离:改槽 1 槽 2 字节不动〔卡① sv1〕" | FileAccess.get_file_as_bytes(slot_tpl % 2) == slot2_before | (待填) | | | +| 1136 | 377 | "sv5 切激活至槽 3(删激活槽夹具前置)〔卡① sv5〕" | t.activate_slot(3) | (待填) | | | +| 1137 | 378 | "sv6 删档受理·激活槽 3〔卡① sv6〕" | t.delete_slot(3) | (待填) | | | +| 1138 | 379 | "sv6 删后文件不在盘+无 .tmp 残留〔卡① sv6〕" | not FileAccess.file_exists(slot_tpl % 3) and not FileAccess.file_exists(slot_tpl % 3 + ".tmp") | (待填) | | | +| 1139 | 381 | "sv6 删激活槽激活指针连清除(该槽不再可写)〔卡① sv6〕" | t.active_slot() == 0 and not t.has_write_target() | (待填) | | | +| 1140 | 385 | "sv6 删非激活槽受理·槽 2〔卡① sv6〕" | t.delete_slot(2) | (待填) | | | +| 1141 | 386 | "sv6 删非激活槽:文件不在盘+激活指针保持〔卡① sv6〕" | not FileAccess.file_exists(slot_tpl % 2) and t.active_slot() == 1 | (待填) | | | +| 1142 | 388 | "sv6 删槽 2 槽 1 字节不动(位间互隔离)〔卡① sv6〕" | FileAccess.get_file_as_bytes(slot_tpl % 1) == slot1_before | (待填) | | | +| 1143 | 397 | "sv4 新档受理·槽 3(创建即激活)〔卡① sv4〕" | t.create_slot(3) | (待填) | | | +| 1144 | 398 | "sv4 新档=全零可玩合法白板态(旧内容 999/5 零带入——钥石/总局数/断点全零)〔卡① sv4〕" | t.active_slot() == 3 and t.keystone_balance() == 0 and not t.has_breakpoint() and t.adventure_total() == 0 | (待填) | | | +| 1145 | 402 | "sv4 新档落盘=白板内容(盘面核验非 999 旧值)〔卡① sv4〕" | int((created.get("meta", {}) as Dictionary).get("keystone_balance", -1)) == 0 | (待填) | | | +| 1146 | 411 | "sv5 进档槽 1 读回(107)〔卡① sv10〕" | hv.activate_slot(1) and hv.keystone_balance() == 107 | (待填) | | | +| 1147 | 414 | "sv9 高版本槽进档拒绝(拒读禁写链按槽)〔卡① sv9〕" | not hv.activate_slot(2) | (待填) | | | +| 1148 | 415 | "sv10 激活失败恢复切换前三态(路径/激活号/数据归槽 1)〔卡① sv10〕" | hv.active_slot() == 1 and hv.keystone_balance() == 107 | (待填) | | | +| 1149 | 418 | "sv10 恢复后写盘落激活槽 1(高版本槽 2 零误写)〔卡① sv10〕" | hv.write_to_disk() and FileAccess.get_file_as_bytes(slot_tpl % 2) == slot2_hv | (待填) | | | +| 1150 | 429 | "sv9 迁移链按槽成立·槽 %d(v5→v7 各槽独立+写盘 version=7)〔卡① sv9〕" ↩ % i | mi.activate_slot(i) and mi.keystone_balance() == i * 11 and mi.write_to_disk() | (待填) | | | +| 1151 | 444 | "sv7 窥视三项逐槽正确·槽 %d〔卡① sv7〕" ↩ % i | bool(ps.get("readable", false)) and int(ps.get("keystone", -1)) == 200 * i and int(ps.get("cleared", -1)) == i and int(ps.get("total", -1)) == 5 * i | (待填) | | | +| 1152 | 447 | "sv6 删槽 3 备空槽窥视〔卡① sv6〕" | pv.delete_slot(3) | (待填) | | | +| 1153 | 449 | "sv7 空槽窥视=零值合法态〔卡① sv7〕" | bool(pe.get("readable", false)) and int(pe.get("keystone", -1)) == 0 and int(pe.get("cleared", -1)) == 0 and int(pe.get("total", -1)) == 0 | (待填) | | | +| 1154 | 458 | "sv8 坏槽窥视=显式损坏标记〔卡① sv8〕" | not bool(pb.get("readable", true)) | (待填) | | | +| 1155 | 459 | "sv8 坏槽窥视零副作用(字节+mtime 双比对不变+零 .corrupt.bak 改名)〔卡① sv8〕" | FileAccess.get_file_as_bytes(slot_tpl % 1) == bad_bytes and FileAccess.get_modified_time(slot_tpl % 1) == bad_mtime and not FileAccess.file_exists(slot_tpl % 1 + ".corrupt.bak") | (待填) | | | +| 1156 | 469 | "sv8 高版本槽窥视=不可读标记〔卡① sv8〕" | not bool(ph.get("readable", true)) | (待填) | | | +| 1157 | 470 | "sv8 高版本窥视零副作用(字节+mtime 不变+零改名)〔卡① sv8〕" | FileAccess.get_file_as_bytes(slot_tpl % 2) == hv_bytes and FileAccess.get_modified_time(slot_tpl % 2) == hv_mtime | (待填) | | | +| 1158 | 483 | "sv12 旧档字节迁入槽 1(逐域相等——余额 1234)〔卡① sv12〕" | int((migrated.get("meta", {}) as Dictionary).get("keystone_balance", -1)) == 1234 | (待填) | | | +| 1159 | 485 | "sv12 迁移后源改名留痕(.migrated.bak 在盘、原路径不在)〔卡① sv12〕" | not FileAccess.file_exists(legacy_src) and FileAccess.file_exists(legacy_src + ".migrated.bak") | (待填) | | | +| 1160 | 494 | "sv12 幂等:槽 1 已存在不重迁(预置 777 零覆盖、源保留)〔卡① sv12〕" | int((slot1_now.get("meta", {}) as Dictionary).get("keystone_balance", -1)) == 1234 and FileAccess.file_exists(legacy_src) | (待填) | | | +| 1161 | 497 | "sv12 迁移他槽零触碰(槽 2/3 字节不动)〔卡① sv12〕" | FileAccess.get_file_as_bytes(slot_tpl % 2) == s2_before and FileAccess.get_file_as_bytes(slot_tpl % 3) == s3_before | (待填) | | | + +## S138 _init(行 14–15 · 2 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 1162 | 14 | "全部表加载 + 校验通过(含跨表护栏零 error 诊断)〔fv6〕" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | +| 1163 | 15 | "is_ready 通道随加载置位" | db.is_ready | (待填) | | | + +## S139 _test_progress_resolution(行 55–83 · 7 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 1164 | 55 | "进度解析精确命中返回原行(board_gen 首行)" | int(got.get("level_index", -99)) == int(probe_row.level_index) and int(got.get("node_depth", -99)) == int(probe_row.node_depth) and String(got.get("board_type", "")) == String(probe_row.get("board_type", "")) | (待填) | | | +| 1165 | 63 | "enemy 行集同坐标解析非空(双表精确行契约——battle 行类)" | not en_got.is_empty() | (待填) | | | +| 1166 | 68 | "深度越最大行 clamp 取该关最大深度行" | int(deep.get("level_index", -99)) == min_l and int(deep.get("node_depth", -99)) == int(min_depths[-1]) | (待填) | | | +| 1167 | 70 | "clamp 解析发警告诊断(resolve_depth_clamp)" | db.diagnostics_count("warning") >= 1 | (待填) | | | +| 1168 | 75 | "警告类别 = resolve_depth_clamp" | found_clamp | (待填) | | | +| 1169 | 78 | "同键重复请求警告去重(四元组注册表)" | db.diagnostics_count("warning") == warn_before | (待填) | | | +| 1170 | 83 | "关卡越最大行 clamp 取最大关卡行" | int(high.get("level_index", -99)) == max_l | (待填) | | | + +## S140 _test_variant_pool_gate(行 108–118 · 6 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1146 | 55 | "进度解析精确命中返回原行(board_gen 首行)" | int(got.get("level_index", -99)) == int(probe_row.level_index) and int(got.get("node_depth", -99)) == int(probe_row.node_depth) and String(got.get("board_type", "")) == String(probe_row.get("board_type", "")) | (待填) | | | -| 1147 | 63 | "enemy 行集同坐标解析非空(双表精确行契约——battle 行类)" | not en_got.is_empty() | (待填) | | | -| 1148 | 68 | "深度越最大行 clamp 取该关最大深度行" | int(deep.get("level_index", -99)) == min_l and int(deep.get("node_depth", -99)) == int(min_depths[-1]) | (待填) | | | -| 1149 | 70 | "clamp 解析发警告诊断(resolve_depth_clamp)" | db.diagnostics_count("warning") >= 1 | (待填) | | | -| 1150 | 75 | "警告类别 = resolve_depth_clamp" | found_clamp | (待填) | | | -| 1151 | 78 | "同键重复请求警告去重(四元组注册表)" | db.diagnostics_count("warning") == warn_before | (待填) | | | -| 1152 | 83 | "关卡越最大行 clamp 取最大关卡行" | int(high.get("level_index", -99)) == max_l | (待填) | | | +| 1171 | 108 | "变异池 unlock 前不含未解锁行(level=unlock−1)" | not _rows_contain(before, gated_row) | (待填) | | | +| 1172 | 109 | "变异池 unlock 当级含该行" | _rows_contain(after, gated_row) | (待填) | | | +| 1173 | 113 | "∅ board_types 行通配(任一 board_type 命中)" | _rows_contain(any_pool, wildcard_row) | (待填) | | | +| 1174 | 116 | "global 行 boss 节点入池(board_types ∅ 通配)" | _rows_contain(db.variant_pool(u3, "nonexistent_board_type_tag", "boss"), global_row) | (待填) | | | +| 1175 | 117 | "global 行缺省双参调用跳过(既有调用口径零扰动)" | not _rows_contain(db.variant_pool(u3, "nonexistent_board_type_tag"), global_row) | (待填) | | | +| 1176 | 118 | "global 行 battle 节点静默跳过(门控兜底零报错)" | not _rows_contain(db.variant_pool(u3, "nonexistent_board_type_tag", "battle"), global_row) | (待填) | | | -## S135 _test_variant_pool_gate(行 108–118 · 6 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) +## S141 _test_shop_accessors(行 133–152 · 8 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1153 | 108 | "变异池 unlock 前不含未解锁行(level=unlock−1)" | not _rows_contain(before, gated_row) | (待填) | | | -| 1154 | 109 | "变异池 unlock 当级含该行" | _rows_contain(after, gated_row) | (待填) | | | -| 1155 | 113 | "∅ board_types 行通配(任一 board_type 命中)" | _rows_contain(any_pool, wildcard_row) | (待填) | | | -| 1156 | 116 | "global 行 boss 节点入池(board_types ∅ 通配)" | _rows_contain(db.variant_pool(u3, "nonexistent_board_type_tag", "boss"), global_row) | (待填) | | | -| 1157 | 117 | "global 行缺省双参调用跳过(既有调用口径零扰动)" | not _rows_contain(db.variant_pool(u3, "nonexistent_board_type_tag"), global_row) | (待填) | | | -| 1158 | 118 | "global 行 battle 节点静默跳过(门控兜底零报错)" | not _rows_contain(db.variant_pool(u3, "nonexistent_board_type_tag", "battle"), global_row) | (待填) | | | +| 1177 | 133 | "ShopTable products 行族非空(表已已入库前提)" | false | (待填) | | | +| 1178 | 135 | "ShopTable products 行族非空" | true | (待填) | | | +| 1179 | 138 | "shop_product_row 按 product_id 精确命中首行" | got == first | (待填) | | | +| 1180 | 139 | "shop_product_row 未知 id 返回空字典(容错通道)" | db.shop_product_row("no_such_product_id__").is_empty() | (待填) | | | +| 1181 | 143 | "shop_gp_drop_row 按 tier 命中" | not db.shop_gp_drop_row(tier0).is_empty() | (待填) | | | +| 1182 | 147 | "shop_level_gp_bonus 按关卡序号命中行值" | db.shop_level_gp_bonus(blv) == int(bonus_rows[0].bonus) | (待填) | | | +| 1183 | 148 | "shop_level_gp_bonus 未登记关卡返回 0(兜底通道)" | db.shop_level_gp_bonus(-1) == 0 | (待填) | | | +| 1184 | 152 | "shop_price_level_bonus 按关卡序号命中行值" | db.shop_price_level_bonus(plv) == int(price_rows[0].bonus) | (待填) | | | -## S136 _test_shop_accessors(行 133–152 · 8 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) +## S142 _test_meta_accessors(行 162–166 · 2 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1159 | 133 | "ShopTable products 行族非空(表已已入库前提)" | false | (待填) | | | -| 1160 | 135 | "ShopTable products 行族非空" | true | (待填) | | | -| 1161 | 138 | "shop_product_row 按 product_id 精确命中首行" | got == first | (待填) | | | -| 1162 | 139 | "shop_product_row 未知 id 返回空字典(容错通道)" | db.shop_product_row("no_such_product_id__").is_empty() | (待填) | | | -| 1163 | 143 | "shop_gp_drop_row 按 tier 命中" | not db.shop_gp_drop_row(tier0).is_empty() | (待填) | | | -| 1164 | 147 | "shop_level_gp_bonus 按关卡序号命中行值" | db.shop_level_gp_bonus(blv) == int(bonus_rows[0].bonus) | (待填) | | | -| 1165 | 148 | "shop_level_gp_bonus 未登记关卡返回 0(兜底通道)" | db.shop_level_gp_bonus(-1) == 0 | (待填) | | | -| 1166 | 152 | "shop_price_level_bonus 按关卡序号命中行值" | db.shop_price_level_bonus(plv) == int(price_rows[0].bonus) | (待填) | | | +| 1185 | 162 | "meta_boss_keystone_row 按关卡序号命中" | db.meta_boss_keystone_row(klv) == key_rows[0] | (待填) | | | +| 1186 | 166 | "meta_conversion_row 按 attr 命中" | db.meta_conversion_row(cattr) == conv_rows[0] | (待填) | | | -## S137 _test_meta_accessors(行 162–166 · 2 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) +## S143 _test_manifest_dir_sync(行 188–188 · 1 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1167 | 162 | "meta_boss_keystone_row 按关卡序号命中" | db.meta_boss_keystone_row(klv) == key_rows[0] | (待填) | | | -| 1168 | 166 | "meta_conversion_row 按 attr 命中" | db.meta_conversion_row(cattr) == conv_rows[0] | (待填) | | | +| 1187 | 188 | "TABLE_MANIFEST 与 data/tables 目录逐文件同步(无缺登/无悬登)" | synced | (待填) | | | -## S138 _test_manifest_dir_sync(行 188–188 · 1 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) +## S144 _test_rowset_discipline(行 208–273 · 8 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1169 | 188 | "TABLE_MANIFEST 与 data/tables 目录逐文件同步(无缺登/无悬登)" | synced | (待填) | | | +| 1188 | 208 | "混编行集加载通过(同带键两行不同敌种相邻——行集恰一敌种校验退役的正向面)" | errs.is_empty() | (待填) | | | +| 1189 | 214 | "Boss 带混编行集加载通过(P4 乙——Boss 多行合法,无硬闸)" | errs.is_empty() | (待填) | | | +| 1190 | 225 | "行序相邻校验:同带键行被隔断报 rowset_rule 且定位带键〔fv9〕〔fv20〕" | gap_hit | (待填) | | | +| 1191 | 235 | "同带键同敌种二行被 dup_key 新键拦(纪律①——enemy_id 唯一)〔fv8〕" | dup_hit | (待填) | | | +| 1192 | 246 | "单行数量域 [1,3] 域闸不回归(UNIT_COUNT_MAX 语义 = 单种数)" | dom_hit | (待填) | | | +| 1193 | 256 | "非法 row_kind 被拒(封闭枚举 {battle, boss})〔fv7〕" | kind_hit | (待填) | | | +| 1194 | 265 | "缺 (battle, band 0) 锚行报 anchor〔fv10〕" | no_anchor_hit | (待填) | | | +| 1195 | 273 | "缺 Boss 行集报 anchor(每关 Boss 行集 ≥1)〔fv10〕" | no_boss_hit | (待填) | | | -## S139 _test_rowset_discipline(行 208–273 · 8 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) +## S145 _init(行 17–17 · 1 条 · 源 game/tests/unit/data_config/forge_table_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1170 | 208 | "混编行集加载通过(同带键两行不同敌种相邻——行集恰一敌种校验退役的正向面)" | errs.is_empty() | (待填) | | | -| 1171 | 214 | "Boss 带混编行集加载通过(P4 乙——Boss 多行合法,无硬闸)" | errs.is_empty() | (待填) | | | -| 1172 | 225 | "行序相邻校验:同带键行被隔断报 rowset_rule 且定位带键〔fv9〕〔fv20〕" | gap_hit | (待填) | | | -| 1173 | 235 | "同带键同敌种二行被 dup_key 新键拦(纪律①——enemy_id 唯一)〔fv8〕" | dup_hit | (待填) | | | -| 1174 | 246 | "单行数量域 [1,3] 域闸不回归(UNIT_COUNT_MAX 语义 = 单种数)" | dom_hit | (待填) | | | -| 1175 | 256 | "非法 row_kind 被拒(封闭枚举 {battle, boss})〔fv7〕" | kind_hit | (待填) | | | -| 1176 | 265 | "缺 (battle, band 0) 锚行报 anchor〔fv10〕" | no_anchor_hit | (待填) | | | -| 1177 | 273 | "缺 Boss 行集报 anchor(每关 Boss 行集 ≥1)〔fv10〕" | no_boss_hit | (待填) | | | +| 1196 | 17 | "生产 forge.tres 随全表加载 + validate 零违例〔锻造工坊批 V1〕" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | -## S140 _init(行 17–17 · 1 条 · 源 game/tests/unit/data_config/forge_table_test.gd · 去向:待分类回填) +## S146 _test_production_rows(行 30–81 · 13 条 · 源 game/tests/unit/data_config/forge_table_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1178 | 17 | "生产 forge.tres 随全表加载 + validate 零违例〔锻造工坊批 V1〕" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | +| 1197 | 30 | "六路线行族规模恰 6(三武器+三甲)〔锻造工坊批 V1〕" | rows.size() == 6 | (待填) | | | +| 1198 | 35 | "路线 id 全集 = {sword, spear, staff, light, heavy, ward}〔锻造工坊批 V1〕" | ids == ["heavy", "light", "spear", "staff", "sword", "ward"] | (待填) | | | +| 1199 | 46 | "六行字段键集恰 = ROW_KEYS 闭集(无特效参数位)〔锻造工坊批 V11〕" | keys_closed | (待填) | | | +| 1200 | 53 | "初始持有恰 sword+light〔锻造工坊批 V1〕" | initial == ["light", "sword"] | (待填) | | | +| 1201 | 55 | "默认攻击类型轴 = normal(常驻持有)〔锻造工坊批 V1〕" | db.forge_default_attack_type() == "normal" | (待填) | | | +| 1202 | 56 | "默认甲型轴 = light(常驻持有)〔锻造工坊批 V1〕" | db.forge_default_armor_type() == "light" | (待填) | | | +| 1203 | 58 | "长枪/法杖 ← 长剑练满(并联双通道①)〔锻造工坊批 V1〕" | String(db.forge_route("spear").get("prereq_route", "")) == "sword" and String(db.forge_route("staff").get("prereq_route", "")) == "sword" | (待填) | | | +| 1204 | 61 | "重甲/魔法护甲 ← 轻甲练满(并联双通道①)〔锻造工坊批 V1〕" | String(db.forge_route("heavy").get("prereq_route", "")) == "light" and String(db.forge_route("ward").get("prereq_route", "")) == "light" | (待填) | | | +| 1205 | 64 | "四待解锁路线一次性解锁价 = 200〔锻造工坊批 V1〕" | int(db.forge_route("spear").get("unlock_price", 0)) == 200 and int(db.forge_route("staff").get("unlock_price", 0)) == 200 and int(db.forge_route("heavy").get("unlock_price", 0)) == 200 and int(db.forge_route("ward").get("unlock_price", 0)) == 200 | (待填) | | | +| 1206 | 70 | "甲型基础 AC = 轻 10 / 重 12 / 魔法护甲 9〔锻造工坊批 V1〕" | int(db.forge_route("light").get("base_ac", 0)) == 10 and int(db.forge_route("heavy").get("base_ac", 0)) == 12 and int(db.forge_route("ward").get("base_ac", 0)) == 9 | (待填) | | | +| 1207 | 75 | "forge_armor_route_by_type(ward) = 魔法护甲锻造〔锻造工坊批 V1〕" | String(db.forge_armor_route_by_type("ward").get("route_id", "")) == "ward" | (待填) | | | +| 1208 | 77 | "forge_weapon_route_by_type(pierce) = 长枪锻造〔锻造工坊批 V1〕" | String(db.forge_weapon_route_by_type("pierce").get("route_id", "")) == "spear" | (待填) | | | +| 1209 | 80 | "未知 route_id / 未知类型轴 = 空字典〔锻造工坊批 V1〕" | db.forge_route("blade").is_empty() and db.forge_armor_route_by_type("plate").is_empty() | (待填) | | | -## S141 _test_production_rows(行 30–81 · 13 条 · 源 game/tests/unit/data_config/forge_table_test.gd · 去向:待分类回填) +## S147 _test_price_ladder(行 91–94 · 2 条 · 源 game/tests/unit/data_config/forge_table_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1179 | 30 | "六路线行族规模恰 6(三武器+三甲)〔锻造工坊批 V1〕" | rows.size() == 6 | (待填) | | | -| 1180 | 35 | "路线 id 全集 = {sword, spear, staff, light, heavy, ward}〔锻造工坊批 V1〕" | ids == ["heavy", "light", "spear", "staff", "sword", "ward"] | (待填) | | | -| 1181 | 46 | "六行字段键集恰 = ROW_KEYS 闭集(无特效参数位)〔锻造工坊批 V11〕" | keys_closed | (待填) | | | -| 1182 | 53 | "初始持有恰 sword+light〔锻造工坊批 V1〕" | initial == ["light", "sword"] | (待填) | | | -| 1183 | 55 | "默认攻击类型轴 = normal(常驻持有)〔锻造工坊批 V1〕" | db.forge_default_attack_type() == "normal" | (待填) | | | -| 1184 | 56 | "默认甲型轴 = light(常驻持有)〔锻造工坊批 V1〕" | db.forge_default_armor_type() == "light" | (待填) | | | -| 1185 | 58 | "长枪/法杖 ← 长剑练满(并联双通道①)〔锻造工坊批 V1〕" | String(db.forge_route("spear").get("prereq_route", "")) == "sword" and String(db.forge_route("staff").get("prereq_route", "")) == "sword" | (待填) | | | -| 1186 | 61 | "重甲/魔法护甲 ← 轻甲练满(并联双通道①)〔锻造工坊批 V1〕" | String(db.forge_route("heavy").get("prereq_route", "")) == "light" and String(db.forge_route("ward").get("prereq_route", "")) == "light" | (待填) | | | -| 1187 | 64 | "四待解锁路线一次性解锁价 = 200〔锻造工坊批 V1〕" | int(db.forge_route("spear").get("unlock_price", 0)) == 200 and int(db.forge_route("staff").get("unlock_price", 0)) == 200 and int(db.forge_route("heavy").get("unlock_price", 0)) == 200 and int(db.forge_route("ward").get("unlock_price", 0)) == 200 | (待填) | | | -| 1188 | 70 | "甲型基础 AC = 轻 10 / 重 12 / 魔法护甲 9〔锻造工坊批 V1〕" | int(db.forge_route("light").get("base_ac", 0)) == 10 and int(db.forge_route("heavy").get("base_ac", 0)) == 12 and int(db.forge_route("ward").get("base_ac", 0)) == 9 | (待填) | | | -| 1189 | 75 | "forge_armor_route_by_type(ward) = 魔法护甲锻造〔锻造工坊批 V1〕" | String(db.forge_armor_route_by_type("ward").get("route_id", "")) == "ward" | (待填) | | | -| 1190 | 77 | "forge_weapon_route_by_type(pierce) = 长枪锻造〔锻造工坊批 V1〕" | String(db.forge_weapon_route_by_type("pierce").get("route_id", "")) == "spear" | (待填) | | | -| 1191 | 80 | "未知 route_id / 未知类型轴 = 空字典〔锻造工坊批 V1〕" | db.forge_route("blade").is_empty() and db.forge_armor_route_by_type("plate").is_empty() | (待填) | | | +| 1210 | 91 | "长剑各级价格 = 20/30/45/67/101(基础 20×150%^已购级数)〔锻造工坊批 V2〕" | prices == [20, 30, 45, 67, 101] | (待填) | | | +| 1211 | 93 | "已满级(owned=5)再购价照算 151(cap 闸归写口,价格函数纯几何)〔锻造工坊批 V2〕" | ForgeTableScript.level_price(row, 5) == 151 | (待填) | | | -## S142 _test_price_ladder(行 91–94 · 2 条 · 源 game/tests/unit/data_config/forge_table_test.gd · 去向:待分类回填) +## S148 _test_negative_cases(行 133–189 · 2 条 · 源 game/tests/unit/data_config/forge_table_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1192 | 91 | "长剑各级价格 = 20/30/45/67/101(基础 20×150%^已购级数)〔锻造工坊批 V2〕" | prices == [20, 30, 45, 67, 101] | (待填) | | | -| 1193 | 93 | "已满级(owned=5)再购价照算 151(cap 闸归写口,价格函数纯几何)〔锻造工坊批 V2〕" | ForgeTableScript.level_price(row, 5) == 151 | (待填) | | | +| 1212 | 133 | "负向夹具(合法六行)validate 零违例〔锻造工坊批 V1〕" | t_ok.validate().is_empty() | (待填) | | | +| 1213 | 189 | "锻造 schema 负向:%s〔锻造工坊批 V1〕" ↩ % label | _has_cat(errs, expect_cat) | (待填) | | | -## S143 _test_negative_cases(行 133–189 · 2 条 · 源 game/tests/unit/data_config/forge_table_test.gd · 去向:待分类回填) +## S149 _test_effect_kinds_closed(行 197–200 · 2 条 · 源 game/tests/unit/data_config/forge_table_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1194 | 133 | "负向夹具(合法六行)validate 零违例〔锻造工坊批 V1〕" | t_ok.validate().is_empty() | (待填) | | | -| 1195 | 189 | "锻造 schema 负向:%s〔锻造工坊批 V1〕" ↩ % label | _has_cat(errs, expect_cat) | (待填) | | | +| 1214 | 197 | "效果引擎效果类型闭集 32 种(锻造工坊批零新增口径随迭代 31 卡② 扩签 forgive_seal_miss 换代)〔锻造工坊批 V11〕" | EffectEngineScript.EFFECT_KINDS.size() == 32 | (待填) | | | +| 1215 | 199 | "效果引擎事件词表在册 20 种(本批零扩签参照面)〔锻造工坊批 V11〕" | EffectEngineScript.EVENTS.size() == 20 | (待填) | | | -## S144 _test_effect_kinds_closed(行 197–200 · 2 条 · 源 game/tests/unit/data_config/forge_table_test.gd · 去向:待分类回填) +## S150 _init(行 16–17 · 1 条 · 源 game/tests/unit/data_config/level_gen_params_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1196 | 197 | "效果引擎效果类型闭集 32 种(锻造工坊批零新增口径随迭代 31 卡② 扩签 forgive_seal_miss 换代)〔锻造工坊批 V11〕" | EffectEngineScript.EFFECT_KINDS.size() == 32 | (待填) | | | -| 1197 | 199 | "效果引擎事件词表在册 20 种(本批零扩签参照面)〔锻造工坊批 V11〕" | EffectEngineScript.EVENTS.size() == 20 | (待填) | | | +| 1216 | 16 | "生产 level_gen_params.tres 随全表加载 + validate 零违例〔fv2〕" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | -## S145 _init(行 16–17 · 1 条 · 源 game/tests/unit/data_config/level_gen_params_test.gd · 去向:待分类回填) +## S151 _test_production(行 47–55 · 4 条 · 源 game/tests/unit/data_config/level_gen_params_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1198 | 16 | "生产 level_gen_params.tres 随全表加载 + validate 零违例〔fv2〕" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | +| 1217 | 47 | "访问器读回 L1 行五旋钮齐备〔fv2〕" | int(r1.get("layers", 0)) == 6 and is_equal_approx(float(r1.get("branch_density", 0.0)), 0.3) and int(r1.get("exit_count", 0)) == 1 and int(r1.get("entry_count", 0)) == 1 | (待填) | | | +| 1218 | 50 | "访问器读回 L2/L3 行层深 9/13 递增〔fv2〕" | int(r2.get("layers", 0)) == 9 and int(r3.get("layers", 0)) == 13 | (待填) | | | +| 1219 | 52 | "三行出口 = 1/2/3、战斗保底 = 4/6/8 与 §8.1 表对上〔fv2〕" | int(r1.get("battle_floor", 0)) == 4 and int(r2.get("exit_count", 0)) == 2 and int(r3.get("exit_count", 0)) == 3 and int(r3.get("battle_floor", 0)) == 8 | (待填) | | | +| 1220 | 55 | "生产表 validate 零错误〔fv2〕" | db.level_gen_params.validate().is_empty() | (待填) | | | -## S146 _test_production(行 47–55 · 4 条 · 源 game/tests/unit/data_config/level_gen_params_test.gd · 去向:待分类回填) +## S152 _test_accessor(行 62–62 · 1 条 · 源 game/tests/unit/data_config/level_gen_params_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1199 | 47 | "访问器读回 L1 行五旋钮齐备〔fv2〕" | int(r1.get("layers", 0)) == 6 and is_equal_approx(float(r1.get("branch_density", 0.0)), 0.3) and int(r1.get("exit_count", 0)) == 1 and int(r1.get("entry_count", 0)) == 1 | (待填) | | | -| 1200 | 50 | "访问器读回 L2/L3 行层深 9/13 递增〔fv2〕" | int(r2.get("layers", 0)) == 9 and int(r3.get("layers", 0)) == 13 | (待填) | | | -| 1201 | 52 | "三行出口 = 1/2/3、战斗保底 = 4/6/8 与 §8.1 表对上〔fv2〕" | int(r1.get("battle_floor", 0)) == 4 and int(r2.get("exit_count", 0)) == 2 and int(r3.get("exit_count", 0)) == 3 and int(r3.get("battle_floor", 0)) == 8 | (待填) | | | -| 1202 | 55 | "生产表 validate 零错误〔fv2〕" | db.level_gen_params.validate().is_empty() | (待填) | | | +| 1221 | 62 | "row_by_level_index 缺行返回空字典〔fv2〕" | t.row_by_level_index(9).is_empty() | (待填) | | | -## S147 _test_accessor(行 62–62 · 1 条 · 源 game/tests/unit/data_config/level_gen_params_test.gd · 去向:待分类回填) +## S153 _test_negative(行 72–105 · 11 条 · 源 game/tests/unit/data_config/level_gen_params_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1203 | 62 | "row_by_level_index 缺行返回空字典〔fv2〕" | t.row_by_level_index(9).is_empty() | (待填) | | | +| 1222 | 72 | "合法三行域校验零错误(夹具自证)〔fv2〕" | _table(base).validate().is_empty() | (待填) | | | +| 1223 | 75 | "L1 出口=2 被拒(出口 L1 恒 1 已锁)〔fv2〕" | not _table(bad_l1_exit).validate().is_empty() | (待填) | | | +| 1224 | 78 | "配比合计 0.5 被拒(四类合计 1 已锁)〔fv2〕" | not _table(bad_ratio).validate().is_empty() | (待填) | | | +| 1225 | 81 | "层深 5<6 被拒(层深下限已锁)〔fv2〕" | not _table(bad_layers).validate().is_empty() | (待填) | | | +| 1226 | 85 | "层深增幅倒挂被拒(递增且增幅变大)〔fv2〕" | not _table(bad_growth).validate().is_empty() | (待填) | | | +| 1227 | 88 | "出口 4 越域 [1,3] 被拒〔fv2〕" | not _table(bad_exit).validate().is_empty() | (待填) | | | +| 1228 | 92 | "岔口去路数 (2,4) 被拒(恒 2/3 已锁)〔fv2〕" | not _table([bad_fork_row, base[1], base[2]]).validate().is_empty() | (待填) | | | +| 1229 | 97 | "字段键集非 ROW_KEYS 闭集被拒〔fv2〕" | not _table([bad_keys_row, base[1], base[2]]).validate().is_empty() | (待填) | | | +| 1230 | 100 | "键重复行被拒〔fv2〕" | not _table([base[0], base[0]]).validate().is_empty() | (待填) | | | +| 1231 | 102 | "战斗保底 -1 被拒(≥0)〔fv2〕" | not _table([_row(1, 6, 0.3, [0.5, 0.2, 0.2, 0.1], 1, 1, -1), base[1], base[2]]).validate().is_empty() | (待填) | | | +| 1232 | 105 | "空表域校验零错误(合法态)〔fv2〕" | _table([]).validate().is_empty() | (待填) | | | -## S148 _test_negative(行 72–105 · 11 条 · 源 game/tests/unit/data_config/level_gen_params_test.gd · 去向:待分类回填) +## S154 _test_derive_determinism(行 36–40 · 2 条 · 源 game/tests/unit/data_config/rng_service_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1204 | 72 | "合法三行域校验零错误(夹具自证)〔fv2〕" | _table(base).validate().is_empty() | (待填) | | | -| 1205 | 75 | "L1 出口=2 被拒(出口 L1 恒 1 已锁)〔fv2〕" | not _table(bad_l1_exit).validate().is_empty() | (待填) | | | -| 1206 | 78 | "配比合计 0.5 被拒(四类合计 1 已锁)〔fv2〕" | not _table(bad_ratio).validate().is_empty() | (待填) | | | -| 1207 | 81 | "层深 5<6 被拒(层深下限已锁)〔fv2〕" | not _table(bad_layers).validate().is_empty() | (待填) | | | -| 1208 | 85 | "层深增幅倒挂被拒(递增且增幅变大)〔fv2〕" | not _table(bad_growth).validate().is_empty() | (待填) | | | -| 1209 | 88 | "出口 4 越域 [1,3] 被拒〔fv2〕" | not _table(bad_exit).validate().is_empty() | (待填) | | | -| 1210 | 92 | "岔口去路数 (2,4) 被拒(恒 2/3 已锁)〔fv2〕" | not _table([bad_fork_row, base[1], base[2]]).validate().is_empty() | (待填) | | | -| 1211 | 97 | "字段键集非 ROW_KEYS 闭集被拒〔fv2〕" | not _table([bad_keys_row, base[1], base[2]]).validate().is_empty() | (待填) | | | -| 1212 | 100 | "键重复行被拒〔fv2〕" | not _table([base[0], base[0]]).validate().is_empty() | (待填) | | | -| 1213 | 102 | "战斗保底 -1 被拒(≥0)〔fv2〕" | not _table([_row(1, 6, 0.3, [0.5, 0.2, 0.2, 0.1], 1, 1, -1), base[1], base[2]]).validate().is_empty() | (待填) | | | -| 1214 | 105 | "空表域校验零错误(合法态)〔fv2〕" | _table([]).validate().is_empty() | (待填) | | | +| 1233 | 36 | "同种子两实例 derive_at(0..7) 逐位一致" | same | (待填) | | | +| 1234 | 40 | "derive_at 为纯函数不推计数器" | a2.counter() == before | (待填) | | | -## S149 _test_derive_determinism(行 36–40 · 2 条 · 源 game/tests/unit/data_config/rng_service_test.gd · 去向:待分类回填) +## S155 _test_counter_ledger(行 51–57 · 2 条 · 源 game/tests/unit/data_config/rng_service_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1215 | 36 | "同种子两实例 derive_at(0..7) 逐位一致" | same | (待填) | | | -| 1216 | 40 | "derive_at 为纯函数不推计数器" | a2.counter() == before | (待填) | | | +| 1235 | 51 | "open_stream 逐次推进全局计数器(+1/次)" | c1 == c0 + 1 and c2 == c0 + 2 | (待填) | | | +| 1236 | 57 | "同 scope 异用途标签派生种子一致(标签不参与派生)" | s1 == s2 | (待填) | | | -## S150 _test_counter_ledger(行 51–57 · 2 条 · 源 game/tests/unit/data_config/rng_service_test.gd · 去向:待分类回填) +## S156 _test_stream_draw_determinism(行 71–71 · 1 条 · 源 game/tests/unit/data_config/rng_service_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1217 | 51 | "open_stream 逐次推进全局计数器(+1/次)" | c1 == c0 + 1 and c2 == c0 + 2 | (待填) | | | -| 1218 | 57 | "同 scope 异用途标签派生种子一致(标签不参与派生)" | s1 == s2 | (待填) | | | +| 1237 | 71 | "同种子两实例同标签流取数 12 次逐位一致" | same | (待填) | | | -## S151 _test_stream_draw_determinism(行 71–71 · 1 条 · 源 game/tests/unit/data_config/rng_service_test.gd · 去向:待分类回填) +## S157 _test_negative_seed_normalization(行 79–79 · 1 条 · 源 game/tests/unit/data_config/rng_service_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1219 | 71 | "同种子两实例同标签流取数 12 次逐位一致" | same | (待填) | | | +| 1238 | 79 | "负种子 abs 归一化后派生与正种子一致" | pos.derive_at(0) == neg.derive_at(0) | (待填) | | | -## S152 _test_negative_seed_normalization(行 79–79 · 1 条 · 源 game/tests/unit/data_config/rng_service_test.gd · 去向:待分类回填) +## S158 _smoke_initialize(行 28–53 · 13 条 · 源 game/tests/unit/data_config/rng_stream_boot_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1220 | 79 | "负种子 abs 归一化后派生与正种子一致" | pos.derive_at(0) == neg.derive_at(0) | (待填) | | | +| 1239 | 28 | "全部表加载 + 校验通过(含跨表护栏)" | load_ok | (待填) | | | +| 1240 | 37 | "统一流向量 m=42 scope=0(=旧 board_gen scope0 连续性锚)" | rng.derive_at(0) == 8672514112661456564 | (待填) | | | +| 1241 | 38 | "统一流向量 m=42 scope=1" | rng.derive_at(1) == 8834291164406037215 | (待填) | | | +| 1242 | 39 | "统一流向量 m=42 scope=2" | rng.derive_at(2) == 7714373262647006282 | (待填) | | | +| 1243 | 40 | "统一流向量 m=42 scope=3" | rng.derive_at(3) == 1280102845971471347 | (待填) | | | +| 1244 | 41 | "统一流向量 m=42 scope=4" | rng.derive_at(4) == 82585501446482399 | (待填) | | | +| 1245 | 42 | "统一流向量 m=42 scope=5" | rng.derive_at(5) == 8078924421474470922 | (待填) | | | +| 1246 | 43 | "统一流向量 m=42 scope=6" | rng.derive_at(6) == 6638052740430622424 | (待填) | | | +| 1247 | 44 | "统一流向量 m=42 scope=7" | rng.derive_at(7) == 8264196488417721294 | (待填) | | | +| 1248 | 45 | "derive_at 纯函数恒等" | rng.derive_at(0) == rng.derive_at(0) | (待填) | | | +| 1249 | 49 | "open_stream 全局计数器单调 +1 ×2" | rng.counter() == 2 | (待填) | | | +| 1250 | 50 | "首个取号 = scope 0(顺序确定)" | s0.seed == rng.derive_at(0) and s1.seed == rng.derive_at(1) | (待填) | | | +| 1251 | 53 | "new_adventure 重置全局计数器" | rng.counter() == 1 and s2.seed == rng.derive_at(0) | (待填) | | | -## S153 _smoke_initialize(行 28–53 · 13 条 · 源 game/tests/unit/data_config/rng_stream_boot_test.gd · 去向:待分类回填) +## S159 _smoke_adventure_table(行 92–203 · 18 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1221 | 28 | "全部表加载 + 校验通过(含跨表护栏)" | load_ok | 全部数据表能加载且跨表校验零错误——表损坏或引用悬空在此红。 | 迁单元测试(tests/unit/data_config/rng_stream_boot_test.gd) | | -| 1222 | 37 | "统一流向量 m=42 scope=0(=旧 board_gen scope0 连续性锚)" | rng.derive_at(0) == 8672514112661456564 | 主种子 42 下统一流 scope 0 的派生种子必须等于此黄金值(与旧棋盘生成向量逐位连续的锚)。 | 迁单元测试(tests/unit/data_config/rng_stream_boot_test.gd) | | -| 1223 | 38 | "统一流向量 m=42 scope=1" | rng.derive_at(1) == 8834291164406037215 | 同上,scope 1 黄金向量。 | 迁单元测试(tests/unit/data_config/rng_stream_boot_test.gd) | | -| 1224 | 39 | "统一流向量 m=42 scope=2" | rng.derive_at(2) == 7714373262647006282 | 同上,scope 2 黄金向量。 | 迁单元测试(tests/unit/data_config/rng_stream_boot_test.gd) | | -| 1225 | 40 | "统一流向量 m=42 scope=3" | rng.derive_at(3) == 1280102845971471347 | 同上,scope 3 黄金向量。 | 迁单元测试(tests/unit/data_config/rng_stream_boot_test.gd) | | -| 1226 | 41 | "统一流向量 m=42 scope=4" | rng.derive_at(4) == 82585501446482399 | 同上,scope 4 黄金向量。 | 迁单元测试(tests/unit/data_config/rng_stream_boot_test.gd) | | -| 1227 | 42 | "统一流向量 m=42 scope=5" | rng.derive_at(5) == 8078924421474470922 | 同上,scope 5 黄金向量。 | 迁单元测试(tests/unit/data_config/rng_stream_boot_test.gd) | | -| 1228 | 43 | "统一流向量 m=42 scope=6" | rng.derive_at(6) == 6638052740430622424 | 同上,scope 6 黄金向量。 | 迁单元测试(tests/unit/data_config/rng_stream_boot_test.gd) | | -| 1229 | 44 | "统一流向量 m=42 scope=7" | rng.derive_at(7) == 8264196488417721294 | 同上,scope 7 黄金向量。 | 迁单元测试(tests/unit/data_config/rng_stream_boot_test.gd) | | -| 1230 | 45 | "derive_at 纯函数恒等" | rng.derive_at(0) == rng.derive_at(0) | derive_at 同参数两次调用结果恒等(纯函数、无隐藏状态)。 | 迁单元测试(tests/unit/data_config/rng_stream_boot_test.gd) | | -| 1231 | 49 | "open_stream 全局计数器单调 +1 ×2" | rng.counter() == 2 | 开两条流后全局取号计数器恰为 2(单调加一,不跳不漏)。 | 迁单元测试(tests/unit/data_config/rng_stream_boot_test.gd) | | -| 1232 | 50 | "首个取号 = scope 0(顺序确定)" | s0.seed == rng.derive_at(0) and s1.seed == rng.derive_at(1) | 首个与次个 open_stream 依次取全局 scope 0、1 的派生种子(取号顺序确定)。 | 迁单元测试(tests/unit/data_config/rng_stream_boot_test.gd) | | -| 1233 | 53 | "new_adventure 重置全局计数器" | rng.counter() == 1 and s2.seed == rng.derive_at(0) | new_adventure 后计数器归 1 且新流首号=scope 0(会话重置语义)。 | 迁单元测试(tests/unit/data_config/rng_stream_boot_test.gd) | | +| 1252 | 92 | "db.adventure 已加载(adventure.tres 入 manifest)" | _db.adventure != null | (待填) | | | +| 1253 | 95 | "AdventureTable.validate() 零违例(全键域检+档位配额结构锚在案)" | _db.adventure.validate().is_empty() | (待填) | | | +| 1254 | 96 | "clear_heal_pct ∈ [0,100](%d——通关部分回血比例)" ↩ % int(_db.adventure.clear_heal_pct) | int(_db.adventure.clear_heal_pct) >= 0 and int(_db.adventure.clear_heal_pct) <= 100 | (待填) | | | +| 1255 | 99 | "档位配额结构锚(四级 70/25/7/0——迭代 31 卡② 净删两件投放):%s = %d 张" ↩ % [tier, int(_db.adventure.TIER_QUOTAS[tier])] | int(_db.adventure.tier_total(tier)) == int(_db.adventure.TIER_QUOTAS[tier]) | (待填) | | | +| 1256 | 101 | "令牌池总量 = 102 张(四级 普通 70/优秀 25/稀有 7/传说 0——迭代 31 卡② 净删两件投放 104→102)〔凭证裁削批 V5〕" | int(_db.adventure.deck_total()) == 102 | (待填) | | | +| 1257 | 102 | "数值对(四级换代承袭):小GP 20/大GP 50 · 小增伤 +1/大 +2 · 小生命 +5/大 +10 · 小护甲 +1/大 +2" | int(_db.adventure.curio_value("gp", "common")) == 20 and int(_db.adventure.curio_value("gp", "excellent")) == 50 and int(_db.adventure.curio_value("attack", "common")) == 1 and int(_db.adventure.curio_value("attack", "excellent")) == 2 and int(_db.adventure.curio_value("max_hp", "common")) == 5 and int(_db.adventure.curio_value("max_hp", "excellent")) == 10 and int(_db.adventure.curio_value("armor", "common")) == 1 and int(_db.adventure.curio_value("armor", "excellent")) == 2 | (待填) | | | +| 1258 | 107 | "恢复件 +6 当即回血(优秀档承袭)· AP +1(稀有档承袭)〔专长批 V14 换代〕" | int(_db.adventure.curio_value("heal", "excellent")) == 6 and int(_db.adventure.curio_value("ap", "rare")) == 1 | (待填) | | | +| 1259 | 109 | "cap 占位在域(案②提前退役——99=实质无上限,实数归调参批)〔专长批 V8 形态面〕" | int(_db.adventure.curio_cap("gp")) == 99 and int(_db.adventure.curio_cap("attack")) == 99 | (待填) | | | +| 1260 | 111 | "同调位读数 null 档早退分支求值 0(迭代 21 T4 槽位收编——谓词表已退役)〔天赋树批 V14·核代码增补〕" | Attunement.slots(_db, null) == 0 | (待填) | | | +| 1261 | 113 | "同调位读数源=树同调位节点点亮数(非空档——socket1 点亮 1 件计 1)〔天赋树批 V14·核代码增补〕" | Attunement.slots(_db, _TreeSaveMock.new()) == 1 | (待填) | | | +| 1262 | 120 | "新目录 12 件全量落表(旧 12 行删除;装配词表=活行 12 件零重复)〔秘宝批 V1〕" | trows.size() == 12 and tid_set.size() == 12 | (待填) | | | +| 1263 | 131 | "命名锁定:12 件 item_id 与 display_name 逐件=正式名(C2 代号即名)〔秘宝批 V2 初筛〕" | names_ok | (待填) | | | +| 1264 | 159 | "逐行解锁方式与契约 8 分配一致(钥石 8 件带价格、条件 4 件带谓词行互斥)+三档 7/3/2〔秘宝批 V1〕" | alloc_ok and tier_map["common"] == 7 and tier_map["excellent"] == 3 and tier_map["rare"] == 2 | (待填) | | | +| 1265 | 162 | "装配→战斗贯通:enc.attunement=[morning_order] 起战 attune_has=true(注入面+只读出口)〔秘宝批换主语〕" | orch_l.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(_db.hero.max_hp), "attunement": ["morning_order"]}) and orch_l.attune_has("morning_order") | (待填) | | | +| 1266 | 166 | "空装配基线:不带 attunement 起战 attune_has=false(off 态零扰动)〔秘宝批换主语〕" | orch_e.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(_db.hero.max_hp)}) and not orch_e.attune_has("morning_order") | (待填) | | | +| 1267 | 181 | "新 12 行效果词零棋盘改写类(危险格/安全格分布同池冻结——真视/开格动作外无写面)〔秘宝批 V9〕" | v9_ok | (待填) | | | +| 1268 | 188 | "效果三方向覆盖在册(信息/行动资源/操作能力=plan·抉择刷新——12 件目录结构对齐设计权威)〔秘宝批 V10 覆盖半〕" | tag_set.has("info") and tag_set.has("action") and tag_set.has("plan") | (待填) | | | +| 1269 | 202 | "装配面空目录合法态:白板档 12 件全未解锁且 mode 分派正确(空目录=合法可玩态)〔秘宝批 V18〕" | v18_ok and locked_n == 12 | (待填) | | | -## S154 _smoke_adventure_table(行 92–203 · 18 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) +## S160 _smoke_enemy_bands(行 229–261 · 4 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1234 | 92 | "db.adventure 已加载(adventure.tres 入 manifest)" | _db.adventure != null | adventure 表已加载进配置宿主。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1235 | 95 | "AdventureTable.validate() 零违例(全键域检+档位配额结构锚在案)" | _db.adventure.validate().is_empty() | AdventureTable 自校验零违例(全键域检+档位配额结构锚)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1236 | 96 | "clear_heal_pct ∈ [0,100](%d——通关部分回血比例)" ↩ % int(_db.adventure.clear_heal_pct) | int(_db.adventure.clear_heal_pct) >= 0 and int(_db.adventure.clear_heal_pct) <= 100 | 通关回血比例在 [0,100] 域内。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1237 | 99 | "档位配额结构锚(四级 70/25/7/0——迭代 31 卡② 净删两件投放):%s = %d 张" ↩ % [tier, int(_db.adventure.TIER_QUOTAS[tier])] | int(_db.adventure.tier_total(tier)) == int(_db.adventure.TIER_QUOTAS[tier]) | 四档令牌配额结构=锚值(70/25/7/0)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1238 | 101 | "令牌池总量 = 102 张(四级 普通 70/优秀 25/稀有 7/传说 0——迭代 31 卡② 净删两件投放 104→102)〔凭证裁削批 V5〕" | int(_db.adventure.deck_total()) == 102 | 令牌池总量恰 102 张(迭代 31 卡② 净删后口径)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1239 | 102 | "数值对(四级换代承袭):小GP 20/大GP 50 · 小增伤 +1/大 +2 · 小生命 +5/大 +10 · 小护甲 +1/大 +2" | int(_db.adventure.curio_value("gp", "common")) == 20 and int(_db.adventure.curio_value("gp", "excellent")) == 50 and int(_db.adventure.curio_value("attack", "common")) == 1 and int(_db.adventure.curio_value("attack", "excellent")) == 2 and int(_db.adventure.curio_value("max_hp", "common")) == 5 and int(_db.adventure.curio_value("max_hp", "excellent")) == 10 and int(_db.adventure.curio_value("armor", "common")) == 1 and int(_db.adventure.curio_value("armor", "excellent")) == 2 | 八类令牌件值对=正式承袭值(GP 20/50、增伤 1/2、生命 5/10、护甲 1/2)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1240 | 107 | "恢复件 +6 当即回血(优秀档承袭)· AP +1(稀有档承袭)〔专长批 V14 换代〕" | int(_db.adventure.curio_value("heal", "excellent")) == 6 and int(_db.adventure.curio_value("ap", "rare")) == 1 | 恢复件优秀档 +6、AP 稀有档 +1(换代承袭值)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1241 | 109 | "cap 占位在域(案②提前退役——99=实质无上限,实数归调参批)〔专长批 V8 形态面〕" | int(_db.adventure.curio_cap("gp")) == 99 and int(_db.adventure.curio_cap("attack")) == 99 | GP/攻击 cap 占位 99 在域(实质无上限口径)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1242 | 111 | "同调位读数 null 档早退分支求值 0(迭代 21 T4 槽位收编——谓词表已退役)〔天赋树批 V14·核代码增补〕" | Attunement.slots(_db, null) == 0 | 同调位读数=树同调位节点点亮数(空档求值 0,谓词表已退役)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1243 | 113 | "同调位读数源=树同调位节点点亮数(非空档——socket1 点亮 1 件计 1)〔天赋树批 V14·核代码增补〕" | Attunement.slots(_db, _TreeSaveMock.new()) == 1 | 同调位读数=非空档下树点亮同调位数(恒真对象引用比较已废,白盒定位补验)。 | 核代码增补(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1244 | 120 | "新目录 12 件全量落表(旧 12 行删除;装配词表=活行 12 件零重复)〔秘宝批 V1〕" | trows.size() == 12 and tid_set.size() == 12 | 同调目录 12 件全量落表且 id 零重复。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1245 | 131 | "命名锁定:12 件 item_id 与 display_name 逐件=正式名(C2 代号即名)〔秘宝批 V2 初筛〕" | names_ok | 12 件 item_id 与显示名逐件=正式定名(C2 代号即名)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1246 | 159 | "逐行解锁方式与契约 8 分配一致(钥石 8 件带价格、条件 4 件带谓词行互斥)+三档 7/3/2〔秘宝批 V1〕" | alloc_ok and tier_map["common"] == 7 and tier_map["excellent"] == 3 and tier_map["rare"] == 2 | 逐行解锁方式与契约分配一致:8 件钥石价、4 件条件谓词互斥,档位 7/3/2。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1247 | 162 | "装配→战斗贯通:enc.attunement=[morning_order] 起战 attune_has=true(注入面+只读出口)〔秘宝批换主语〕" | orch_l.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(_db.hero.max_hp), "attunement": ["morning_order"]}) and orch_l.attune_has("morning_order") | 带晨操令装配起战,战斗侧 attune_has=true(装配→战斗贯通)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1248 | 166 | "空装配基线:不带 attunement 起战 attune_has=false(off 态零扰动)〔秘宝批换主语〕" | orch_e.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(_db.hero.max_hp)}) and not orch_e.attune_has("morning_order") | 不带装配起战 attune_has=false(off 态零扰动基线)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1249 | 181 | "新 12 行效果词零棋盘改写类(危险格/安全格分布同池冻结——真视/开格动作外无写面)〔秘宝批 V9〕" | v9_ok | 12 行效果词零棋盘改写类(rewrite_board 族零引用——棋盘事实不可变契约)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1250 | 188 | "效果三方向覆盖在册(信息/行动资源/操作能力=plan·抉择刷新——12 件目录结构对齐设计权威)〔秘宝批 V10 覆盖半〕" | tag_set.has("info") and tag_set.has("action") and tag_set.has("plan") | 效果三方向覆盖标签齐备(信息/行动资源/抉择刷新)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1251 | 202 | "装配面空目录合法态:白板档 12 件全未解锁且 mode 分派正确(空目录=合法可玩态)〔秘宝批 V18〕" | v18_ok and locked_n == 12 | 白板档 12 件全未解锁且解锁模式分派正确(空目录=合法可玩态)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 1270 | 229 | "敌人表 28 行且键序 = 覆盖表序((L,row_kind,depth_band,enemy_id) 逐行比对——文件行序不变)〔fv11〕" | _db.enemy.rows.size() == 28 and got_keys == expect_keys | (待填) | | | +| 1271 | 236 | "hobgoblin 行在 (1,battle,3) 留任随迁(缺口行不删不补——补齐归敌种内容批)〔fv12〕〔fv25〕" | int(hob.get("level_index", -1)) == 1 and String(hob.get("row_kind", "")) == "battle" \\ and int(hob.get("depth_band", -1)) == 3 | (待填) | | | +| 1272 | 239 | "hobgoblin 缺口登记随行(表文件注释在位——缺口不静默)〔fv25〕" | FileAccess.get_file_as_string("res://data/tables/enemy.tres").contains("hobgoblin 行为缺口行") | (待填) | | | +| 1273 | 260 | "三关 Boss 行集带键 = 覆盖表(模板终点 4/9/9 在册+生成图终点 6/8/9/11/12/13 扩带)%s〔fv13〕" ↩ % boss_detail | boss_ok | (待填) | | | -## S155 _smoke_enemy_bands(行 229–261 · 4 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) +## S161 _smoke_ac_swap_guard(行 272–380 · 13 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1252 | 229 | "敌人表 28 行且键序 = 覆盖表序((L,row_kind,depth_band,enemy_id) 逐行比对——文件行序不变)〔fv11〕" | _db.enemy.rows.size() == 28 and got_keys == expect_keys | (待填) | | | -| 1253 | 236 | "hobgoblin 行在 (1,battle,3) 留任随迁(缺口行不删不补——补齐归敌种内容批)〔fv12〕〔fv25〕" | int(hob.get("level_index", -1)) == 1 and String(hob.get("row_kind", "")) == "battle" \\ and int(hob.get("depth_band", -1)) == 3 | hobgoblin 缺口行在 (1,battle,3) 留任随迁(不删不补)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1254 | 239 | "hobgoblin 缺口登记随行(表文件注释在位——缺口不静默)〔fv25〕" | FileAccess.get_file_as_string("res://data/tables/enemy.tres").contains("hobgoblin 行为缺口行") | 缺口登记随行:表文件注释在位(缺口不静默)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1255 | 260 | "三关 Boss 行集带键 = 覆盖表(模板终点 4/9/9 在册+生成图终点 6/8/9/11/12/13 扩带)%s〔fv13〕" ↩ % boss_detail | boss_ok | (待填) | | | +| 1274 | 272 | "状态表 defense_up 行已退役(无双轨)" | _db.status_effect_row("defense_up").is_empty() | (待填) | | | +| 1275 | 274 | "ac_up 行在位(hero · +1 ac · 只续时不叠层——2026-09-20 主角增益续时批)" | not ac_row.is_empty() and String(ac_row.get("target_side", "")) == "hero" \\ and String(ac_row.get("unit", "")) == "ac" and int(ac_row.get("magnitude", 0)) == 1 \\ and String(ac_row.get("stack_policy", "")) == "refresh" | (待填) | | | +| 1276 | 282 | "奖励表 defense 正向行 effect_id = ac_up(显示名盾牌加护,权重/tier 不变)" | String(ac_reward.get("effect_id", "")) == "ac_up" and String(ac_reward.get("display_name", "")) == "盾牌加护" | (待填) | | | +| 1277 | 284 | "effect_pool_map 无 defense_up/ac_up 键(减伤不入池——AC 不经乘区)" | not _db.settlement.effect_pool_map.has("defense_up") and not _db.settlement.effect_pool_map.has("ac_up") | (待填) | | | +| 1278 | 299 | "hero_defense 池 percent 行叠满不可触池底(墙最深%s > clamp_lo %d——单效果归零结构性不可达)" ↩ % [ wall_detail if wall_detail != "" else "(池内现无负向行)", int(_db.settlement.pool_clamp_lo)] | wall_ok | (待填) | | | +| 1279 | 305 | "hero_defense 池映射数==0(绊线钉——红=已加映射,去核上方护栏是否已同步激活)" | hd_maps == 0 | (待填) | | | +| 1280 | 307 | "主角 armor_class 在域内(%d——暂定基准随表走)" ↩ % int(_db.hero.armor_class) | int(_db.hero.armor_class) >= 0 and int(_db.hero.armor_class) <= 30 | (待填) | | | +| 1281 | 324 | "敌方 AC 行值在已裁带宽内(杂鱼 0~5·重击手/重装手 8~9·Boss 8~12——迭代 5 返工批 v1 重投,越带行%s)" ↩ % [ ac_detail if ac_detail != "" else "无"] | ac_band | (待填) | | | +| 1282 | 332 | "Boss AC 关卡台阶严格递增(%d < %d < %d)" ↩ % [int(boss_ac.get(1, 0)), int(boss_ac.get(2, 0)), int(boss_ac.get(3, 0))] | int(boss_ac.get(1, 0)) < int(boss_ac.get(2, 0)) and int(boss_ac.get(2, 0)) < int(boss_ac.get(3, 0)) | (待填) | | | +| 1283 | 348 | "熊地精首遇带(1,battle,%d)降档锚定(hp 22 · ac 2 · 池 attack6/charge2/heavy2 零改动——迭代 24 N1;首遇带数据驱动定位)〔fv21〕" ↩ % l12_band | l12_band >= 0 and not l12_row.is_empty() and int(l12_row.get("hp", -1)) == 22 \\ and int(l12_row.get("armor_class", -1)) == 2 \\ and l12_pool == {"attack": 6, "charge": 2, "heavy": 2} | (待填) | | | +| 1284 | 370 | "第一关相邻深度带总量比 ≤ 2.5(12/10/22/35/72——迭代 24 N1 曲线护栏,带键升序;越带%s)" ↩ % [ curve_detail if curve_detail != "" else "无"] | curve_ok | (待填) | | | +| 1285 | 375 | "掷点参数拍板值在位(d20 / nat20 暴击 ×200%% / 下限 1 / attack_bonus 0 / armor_roll 流)" | int(_db.settlement.roll_faces) == 20 and int(_db.settlement.crit_roll) == 20 \\ and int(_db.settlement.crit_multiplier) == 200 and int(_db.settlement.damage_floor) == 1 \\ and int(_db.settlement.attack_bonus) == 0 and String(_db.settlement.armor_roll_stream) == "armor_roll" | (待填) | | | +| 1286 | 379 | "档位表拍板值在位(缺口 1-3 → ×0.75 · 4-7 → ×0.5 · 8+ → ×0.25——无零档)" | bounds == [3, 7] and discounts == [75, 50, 25] | (待填) | | | -## S156 _smoke_ac_swap_guard(行 272–380 · 13 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) +## S162 _smoke_variation_framework(行 387–415 · 9 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1256 | 272 | "状态表 defense_up 行已退役(无双轨)" | _db.status_effect_row("defense_up").is_empty() | 状态表 defense_up 行已退役(无双轨)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1257 | 274 | "ac_up 行在位(hero · +1 ac · 只续时不叠层——2026-09-20 主角增益续时批)" | not ac_row.is_empty() and String(ac_row.get("target_side", "")) == "hero" \\ and String(ac_row.get("unit", "")) == "ac" and int(ac_row.get("magnitude", 0)) == 1 \\ and String(ac_row.get("stack_policy", "")) == "refresh" | ac_up 行在位:主角侧+1AC 只续时不叠层。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1258 | 282 | "奖励表 defense 正向行 effect_id = ac_up(显示名盾牌加护,权重/tier 不变)" | String(ac_reward.get("effect_id", "")) == "ac_up" and String(ac_reward.get("display_name", "")) == "盾牌加护" | 奖励表 defense 正向行 effect_id=ac_up(盾牌加护)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1259 | 284 | "effect_pool_map 无 defense_up/ac_up 键(减伤不入池——AC 不经乘区)" | not _db.settlement.effect_pool_map.has("defense_up") and not _db.settlement.effect_pool_map.has("ac_up") | 效果池映射无 defense_up/ac_up 键(AC 不经乘区)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1260 | 299 | "hero_defense 池 percent 行叠满不可触池底(墙最深%s > clamp_lo %d——单效果归零结构性不可达)" ↩ % [ wall_detail if wall_detail != "" else "(池内现无负向行)", int(_db.settlement.pool_clamp_lo)] | wall_ok | 免伤墙归零护栏:percent 行叠满不可触池底(BUG-0003 免疫类防回归)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1261 | 305 | "hero_defense 池映射数==0(绊线钉——红=已加映射,去核上方护栏是否已同步激活)" | hd_maps == 0 | 钉住 hero_defense 池空集现态(核代码增补)——有人加映射先红,提醒护栏转激活需核验。 | 核代码增补(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1262 | 307 | "主角 armor_class 在域内(%d——暂定基准随表走)" ↩ % int(_db.hero.armor_class) | int(_db.hero.armor_class) >= 0 and int(_db.hero.armor_class) <= 30 | 主角 AC 在 [0,30] 域内(暂定基准随表走)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1263 | 324 | "敌方 AC 行值在已裁带宽内(杂鱼 0~5·重击手/重装手 8~9·Boss 8~12——迭代 5 返工批 v1 重投,越带行%s)" ↩ % [ ac_detail if ac_detail != "" else "无"] | ac_band | 敌方 AC 行值在裁带宽内(杂鱼 0~5/重击手 8~9/Boss 8~12)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1264 | 332 | "Boss AC 关卡台阶严格递增(%d < %d < %d)" ↩ % [int(boss_ac.get(1, 0)), int(boss_ac.get(2, 0)), int(boss_ac.get(3, 0))] | int(boss_ac.get(1, 0)) < int(boss_ac.get(2, 0)) and int(boss_ac.get(2, 0)) < int(boss_ac.get(3, 0)) | Boss AC 关卡台阶严格递增(L1= 0 and not l12_row.is_empty() and int(l12_row.get("hp", -1)) == 22 \\ and int(l12_row.get("armor_class", -1)) == 2 \\ and l12_pool == {"attack": 6, "charge": 2, "heavy": 2} | 熊地精首遇带降档锚定:hp22/ac2/池 attack6-charge2-heavy2 零改动。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1266 | 370 | "第一关相邻深度带总量比 ≤ 2.5(12/10/22/35/72——迭代 24 N1 曲线护栏,带键升序;越带%s)" ↩ % [ curve_detail if curve_detail != "" else "无"] | curve_ok | 第一关相邻深度带总量比≤2.5(难度曲线护栏)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1267 | 375 | "掷点参数拍板值在位(d20 / nat20 暴击 ×200%% / 下限 1 / attack_bonus 0 / armor_roll 流)" | int(_db.settlement.roll_faces) == 20 and int(_db.settlement.crit_roll) == 20 \\ and int(_db.settlement.crit_multiplier) == 200 and int(_db.settlement.damage_floor) == 1 \\ and int(_db.settlement.attack_bonus) == 0 and String(_db.settlement.armor_roll_stream) == "armor_roll" | 掷点参数拍板值在位(d20/nat20×2/下限 1/加成 0/专流)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1268 | 379 | "档位表拍板值在位(缺口 1-3 → ×0.75 · 4-7 → ×0.5 · 8+ → ×0.25——无零档)" | bounds == [3, 7] and discounts == [75, 50, 25] | 折扣档表拍板值在位(3 档边界与折扣无零档)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 1287 | 387 | "变异清单 6 行(荆棘丛/藤蔓缠绕/岩浆/烬覆 + 落石/矿尘——首批类型定稿批 + 第三关内容批;贫矿脉 2026-10-16 用户令屏蔽删行)" | _db.variation.rows.size() == 6 | (待填) | | | +| 1288 | 389 | "变异表校验零错误(6 行全过域与跨字段互锁——含 scope=global 三互锁负样本)" | _db.variation.validate().is_empty() | (待填) | | | +| 1289 | 394 | "L1 dungeon 候选池 = 荆棘丛+藤蔓缠绕(unlock + board_types 双闸)" | pool1_ids == ["thorn", "vine"] | (待填) | | | +| 1290 | 398 | "L2 underdark 候选池 = 岩浆+烬覆(第二关解锁)" | pool2_ids == ["magma", "ash"] | (待填) | | | +| 1291 | 399 | "unlock 门控:L1 不进 underdark 行(岩浆/烬覆第二关解锁)" | _db.variant_pool(1, "underdark").is_empty() | (待填) | | | +| 1292 | 403 | "board_types 门控:L2 dungeon 板池 = 荆棘丛+藤蔓缠绕(解锁累积;岩浆/烬覆被 board_types ∅ 交集排除)" | pool2d_ids == ["thorn", "vine"] | (待填) | | | +| 1293 | 405 | "清单外 variant_id 返回空(读取通道在位)" | _db.variation.row_by_id("nonexistent").is_empty() | (待填) | | | +| 1294 | 411 | "跨字段互锁负样本①:flat_damage + placement=any 被拒(只出现在未打开的格子)" | _errs_has_cross_field(vt.validate()) | (待填) | | | +| 1295 | 415 | "跨字段互锁负样本②:hide_number + trigger≠on_number_shown 被拒" | _errs_has_cross_field(vt.validate()) | (待填) | | | -## S157 _smoke_variation_framework(行 387–415 · 9 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) +## S163 _smoke_difficulty_table(行 422–444 · 8 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1269 | 387 | "变异清单 6 行(荆棘丛/藤蔓缠绕/岩浆/烬覆 + 落石/矿尘——首批类型定稿批 + 第三关内容批;贫矿脉 2026-10-16 用户令屏蔽删行)" | _db.variation.rows.size() == 6 | 变异清单恰 6 行(首批四类型+落石/矿尘)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1270 | 389 | "变异表校验零错误(6 行全过域与跨字段互锁——含 scope=global 三互锁负样本)" | _db.variation.validate().is_empty() | 变异表自校验零错误(含 global 三互锁负样本)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1271 | 394 | "L1 dungeon 候选池 = 荆棘丛+藤蔓缠绕(unlock + board_types 双闸)" | pool1_ids == ["thorn", "vine"] | L1 地牢候选池=荆棘丛+藤蔓缠绕(解锁+板型双闸)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1272 | 398 | "L2 underdark 候选池 = 岩浆+烬覆(第二关解锁)" | pool2_ids == ["magma", "ash"] | L2 幽暗地域池=岩浆+烬覆。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1273 | 399 | "unlock 门控:L1 不进 underdark 行(岩浆/烬覆第二关解锁)" | _db.variant_pool(1, "underdark").is_empty() | 解锁门控:L1 不进幽暗地域行。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1274 | 403 | "board_types 门控:L2 dungeon 板池 = 荆棘丛+藤蔓缠绕(解锁累积;岩浆/烬覆被 board_types ∅ 交集排除)" | pool2d_ids == ["thorn", "vine"] | 板型门控:L2 地牢池被 board_types 交集排除岩浆/烬覆。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1275 | 405 | "清单外 variant_id 返回空(读取通道在位)" | _db.variation.row_by_id("nonexistent").is_empty() | 清单外 variant_id 返回空(读取通道在位)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1276 | 411 | "跨字段互锁负样本①:flat_damage + placement=any 被拒(只出现在未打开的格子)" | _errs_has_cross_field(vt.validate()) | 互锁负样本①:flat_damage+placement=any 被拒。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1277 | 415 | "跨字段互锁负样本②:hide_number + trigger≠on_number_shown 被拒" | _errs_has_cross_field(vt.validate()) | 互锁负样本②:hide_number+trigger≠on_number_shown 被拒。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 1296 | 422 | "DifficultyTable MVP 空表加载(结构位)" | _db.difficulty != null and _db.difficulty.rows.is_empty() | (待填) | | | +| 1297 | 425 | "spawn 受理" | enemy_system.spawn(1, 0, int(_db.hero.max_hp)) | (待填) | | | +| 1298 | 428 | "空表零修饰(hp=%d atk=%d = 行值 %d/%d)" ↩ % [int(unit.hp), int(unit.atk), int(d0.hp), int(d0.atk)] | int(unit.hp) == int(d0.hp) and int(unit.atk) == int(d0.atk) | (待填) | | | +| 1299 | 433 | "修饰后 spawn 受理" | enemy_system_b.spawn(1, 0, int(_db.hero.max_hp)) | (待填) | | | +| 1300 | 435 | "hp 修饰 ×1.5(%d→%d,max_hp 同步)" ↩ % [int(d0.hp), int(g2.hp)] | int(g2.hp) == int(round(float(d0.hp) * 1.5)) and int(g2.max_hp) == int(g2.hp) | (待填) | | | +| 1301 | 437 | "atk 修饰 ×2.0(%d→%d)" ↩ % [int(d0.atk), int(g2.atk)] | int(g2.atk) == int(d0.atk) * 2 | (待填) | | | +| 1302 | 442 | "越域修饰 spawn 受理" | enemy_system_c.spawn(1, 0, int(_db.hero.max_hp)) | (待填) | | | +| 1303 | 443 | "越域乘数 clamp 至 10(%d→%d)" ↩ % [int(d0.hp), int(enemy_system_c.all_enemies()[0].hp)] | int(enemy_system_c.all_enemies()[0].hp) == int(d0.hp) * 10 | (待填) | | | -## S158 _smoke_difficulty_table(行 422–444 · 8 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) +## S164 _smoke_nine_types_gp_stream(行 452–494 · 6 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1278 | 422 | "DifficultyTable MVP 空表加载(结构位)" | _db.difficulty != null and _db.difficulty.rows.is_empty() | 难度表 MVP 空表加载(结构位)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1279 | 425 | "spawn 受理" | enemy_system.spawn(1, 0, int(_db.hero.max_hp)) | 空表下 spawn 受理。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1280 | 428 | "空表零修饰(hp=%d atk=%d = 行值 %d/%d)" ↩ % [int(unit.hp), int(unit.atk), int(d0.hp), int(d0.atk)] | int(unit.hp) == int(d0.hp) and int(unit.atk) == int(d0.atk) | 空表零修饰:敌值=行值原样。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1281 | 433 | "修饰后 spawn 受理" | enemy_system_b.spawn(1, 0, int(_db.hero.max_hp)) | 修饰行注入后 spawn 受理。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1282 | 435 | "hp 修饰 ×1.5(%d→%d,max_hp 同步)" ↩ % [int(d0.hp), int(g2.hp)] | int(g2.hp) == int(round(float(d0.hp) * 1.5)) and int(g2.max_hp) == int(g2.hp) | hp 修饰 ×1.5 且 max_hp 同步。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1283 | 437 | "atk 修饰 ×2.0(%d→%d)" ↩ % [int(d0.atk), int(g2.atk)] | int(g2.atk) == int(d0.atk) * 2 | atk 修饰 ×2。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1284 | 442 | "越域修饰 spawn 受理" | enemy_system_c.spawn(1, 0, int(_db.hero.max_hp)) | 越域修饰 spawn 受理。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1285 | 443 | "越域乘数 clamp 至 10(%d→%d)" ↩ % [int(d0.hp), int(enemy_system_c.all_enemies()[0].hp)] | int(enemy_system_c.all_enemies()[0].hp) == int(d0.hp) * 10 | 越域乘数 clamp 至 10。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 1304 | 452 | "格面资源十型封闭(第九型 gp 九型批 2026-09-20 / 第十型 treasure_item 秘宝内容批 2026-09-29——词表 v1.2)" | RewardTable.CELL_TYPES.size() == 10 and RewardTable.CELL_TYPES.has("gp") and RewardTable.CELL_TYPES.has("treasure_item") | (待填) | | | +| 1305 | 467 | "奖励字典 18 行(九型×正负 18 格——aux 挖格投放四行已退役 2026-10-01 用户拍板甲:战斗/挖格不掉道具,秘宝唯一来路=三选一共享池〔节点/通关〕;清图槽独立批尾不计)" | cell_rows == 18 | (待填) | | | +| 1306 | 474 | "危险格负向直伤 %s power=%d(卡⑧投值)" ↩ % [rid, int(mine_power[rid])] | int(row.get("power", -1)) == int(mine_power[rid]) | (待填) | | | +| 1307 | 487 | "正向 cell 行权重 ≥1 全在池 / 负向行权重恒 0 不入池(正向合计 %d——配置推导)" ↩ % pos_weight | pos_all_in_pool and neg_all_zero | (待填) | | | +| 1308 | 489 | "gp 型两行(GP 袋 gp_gain 正向 weight 2 / 破财 gp_loss 负向 weight 0 不入池)" | gp_pos == 2 and gp_neg == 0 | (待填) | | | +| 1309 | 493 | "derive_at 纯函数不推计数器(统一流全局计数器恒 0——并流后纯函数语义不变,gp_drop 专用向量随分盐作废、统一向量中央断言)" | rng.counter() == 0 | (待填) | | | -## S159 _smoke_nine_types_gp_stream(行 452–494 · 6 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) +## S165 _smoke_meta(行 502–515 · 4 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1286 | 452 | "格面资源十型封闭(第九型 gp 九型批 2026-09-20 / 第十型 treasure_item 秘宝内容批 2026-09-29——词表 v1.2)" | RewardTable.CELL_TYPES.size() == 10 and RewardTable.CELL_TYPES.has("gp") and RewardTable.CELL_TYPES.has("treasure_item") | 格面资源十型封闭(gp 与秘宝件在册)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1287 | 467 | "奖励字典 18 行(九型×正负 18 格——aux 挖格投放四行已退役 2026-10-01 用户拍板甲:战斗/挖格不掉道具,秘宝唯一来路=三选一共享池〔节点/通关〕;清图槽独立批尾不计)" | cell_rows == 18 | 奖励字典恰 18 行(九型×正负)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1288 | 474 | "危险格负向直伤 %s power=%d(卡⑧投值)" ↩ % [rid, int(mine_power[rid])] | int(row.get("power", -1)) == int(mine_power[rid]) | 危险格负向直伤行 power=投值(三行逐行)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1289 | 487 | "正向 cell 行权重 ≥1 全在池 / 负向行权重恒 0 不入池(正向合计 %d——配置推导)" ↩ % pos_weight | pos_all_in_pool and neg_all_zero | 正向行权重≥1 全在池、负向行权重恒 0 不入池。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1290 | 489 | "gp 型两行(GP 袋 gp_gain 正向 weight 2 / 破财 gp_loss 负向 weight 0 不入池)" | gp_pos == 2 and gp_neg == 0 | GP 型两行权重(正向 2 入池/负向 0 不入池)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1291 | 493 | "derive_at 纯函数不推计数器(统一流全局计数器恒 0——并流后纯函数语义不变,gp_drop 专用向量随分盐作废、统一向量中央断言)" | rng.counter() == 0 | derive_at 纯函数不推计数器(全局计数恒 0)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 1310 | 502 | "MetaTable 非空 + validate 零错(三行族词表封闭/唯一/连续/域界/first≥repeat/ratio 全过)" | meta_tbl != null and meta_tbl.validate().is_empty() | (待填) | | | +| 1311 | 504 | "MetaTable 行族规模 3/3/2(Boss/换算恰三/Y 升级两档——forge_products 迭代 21 退役、workshop_items 迭代 22 退役)" | meta_tbl.boss_keystone.size() == 3 and meta_tbl.conversion.size() == 3 and meta_tbl.attune_refresh_upgrade.size() == 2 | (待填) | | | +| 1312 | 510 | "conversion attr 词表封闭恰全(三属性轴——meta_* 访问器可达)" | attrs_ok | (待填) | | | +| 1313 | 515 | "boss_keystone L1–L3 行连续可达且 first_kill ≥ repeat_kill 全行(首杀全量/重复小额)" | boss_ok | (待填) | | | -## S160 _smoke_meta(行 502–515 · 4 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) +## S166 _init(行 13–14 · 2 条 · 源 game/tests/unit/example_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1292 | 502 | "MetaTable 非空 + validate 零错(三行族词表封闭/唯一/连续/域界/first≥repeat/ratio 全过)" | meta_tbl != null and meta_tbl.validate().is_empty() | 元进度表非空且自校验零错。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1293 | 504 | "MetaTable 行族规模 3/3/2(Boss/换算恰三/Y 升级两档——forge_products 迭代 21 退役、workshop_items 迭代 22 退役)" | meta_tbl.boss_keystone.size() == 3 and meta_tbl.conversion.size() == 3 and meta_tbl.attune_refresh_upgrade.size() == 2 | 行族规模 3/3/2(Boss/换算/同调刷新升级)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1294 | 510 | "conversion attr 词表封闭恰全(三属性轴——meta_* 访问器可达)" | attrs_ok | 换算 attr 词表封闭恰全(三属性轴)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | -| 1295 | 515 | "boss_keystone L1–L3 行连续可达且 first_kill ≥ repeat_kill 全行(首杀全量/重复小额)" | boss_ok | Boss 钥石 L1–L3 行连续且首杀≥重复杀。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 1314 | 13 | "headless SceneTree 协议可用(quit(code) 通道)" | _test_headless_protocol() | (待填) | | | +| 1315 | 14 | "命名规范可执行:test_[scenario]_[expected]" | _test_naming_convention_followed() | (待填) | | | -## S161 _init(行 13–14 · 2 条 · 源 game/tests/unit/example_test.gd · 去向:待分类回填) +## S167 _test_numeric_lines_carry_values(行 30–35 · 3 条 · 源 game/tests/unit/presentation/battle_narrator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1296 | 13 | "headless SceneTree 协议可用(quit(code) 通道)" | _test_headless_protocol() | (待填) | | | -| 1297 | 14 | "命名规范可执行:test_[scenario]_[expected]" | _test_naming_convention_followed() | (待填) | | | +| 1316 | 30 | "休整叙述带入回复量与当前血量(实际「%s」)" ↩ % rest | rest.contains("12") and rest.contains("30") and rest.contains("40") | (待填) | | | +| 1317 | 33 | "商店叙述带入 GP 数(实际「%s」)" ↩ % shop | shop.contains("77") | (待填) | | | +| 1318 | 34 | "奖励行与休整行互异(不是同一句套所有场景)" | NarratorScript.reward_line() != rest | (待填) | | | -## S162 _test_numeric_lines_carry_values(行 30–35 · 3 条 · 源 game/tests/unit/presentation/battle_narrator_test.gd · 去向:待分类回填) +## S168 _test_end_text_differs_by_result(行 41–47 · 4 条 · 源 game/tests/unit/presentation/battle_narrator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1298 | 30 | "休整叙述带入回复量与当前血量(实际「%s」)" ↩ % rest | rest.contains("12") and rest.contains("30") and rest.contains("40") | (待填) | | | -| 1299 | 33 | "商店叙述带入 GP 数(实际「%s」)" ↩ % shop | shop.contains("77") | (待填) | | | -| 1300 | 34 | "奖励行与休整行互异(不是同一句套所有场景)" | NarratorScript.reward_line() != rest | (待填) | | | +| 1319 | 41 | "关卡收束态恒为「关卡通关!」(实际「%s」)" ↩ % level | level == "关卡通关!" | (待填) | | | +| 1320 | 44 | "单场胜负文案互异(胜「%s」/负「%s」)" ↩ % [win, lose] | win != lose | (待填) | | | +| 1321 | 45 | "胜利文案非空(实际「%s」)" ↩ % win | win != "" | (待填) | | | +| 1322 | 47 | "未知结果有兜底文案不吐空串(实际「%s」)" ↩ % unknown | unknown != "" | (待填) | | | -## S163 _test_end_text_differs_by_result(行 41–47 · 4 条 · 源 game/tests/unit/presentation/battle_narrator_test.gd · 去向:待分类回填) +## S169 _test_phase_text_nonempty(行 52–54 · 2 条 · 源 game/tests/unit/presentation/battle_narrator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1301 | 41 | "关卡收束态恒为「关卡通关!」(实际「%s」)" ↩ % level | level == "关卡通关!" | (待填) | | | -| 1302 | 44 | "单场胜负文案互异(胜「%s」/负「%s」)" ↩ % [win, lose] | win != lose | (待填) | | | -| 1303 | 45 | "胜利文案非空(实际「%s」)" ↩ % win | win != "" | (待填) | | | -| 1304 | 47 | "未知结果有兜底文案不吐空串(实际「%s」)" ↩ % unknown | unknown != "" | (待填) | | | +| 1323 | 52 | "相位叙述非空(实际「%s」)" ↩ % t | t != "" | (待填) | | | +| 1324 | 54 | "Boss 在场时叙述带入 Boss 名(实际「%s」)" ↩ % boss | boss.contains("灰矮人石君王") | (待填) | | | -## S164 _test_phase_text_nonempty(行 52–54 · 2 条 · 源 game/tests/unit/presentation/battle_narrator_test.gd · 去向:待分类回填) +## S170 _test_boss_name_uses_boss_rowset(行 81–82 · 2 条 · 源 game/tests/unit/presentation/battle_narrator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1305 | 52 | "相位叙述非空(实际「%s」)" ↩ % t | t != "" | (待填) | | | -| 1306 | 54 | "Boss 在场时叙述带入 Boss 名(实际「%s」)" ↩ % boss | boss.contains("灰矮人石君王") | (待填) | | | +| 1325 | 81 | "Boss 名取自 Boss 行集首行(期望「%s」实际「%s」)〔fv24〕" ↩ % [boss_expect, got] | got == boss_expect | (待填) | | | +| 1326 | 82 | "Boss 名与同深度战斗行集名不同(期望「%s」≠「%s」——行类路由可判别)〔fv24〕" ↩ % [boss_expect, battle_name] | boss_expect != battle_name | (待填) | | | -## S165 _test_boss_name_uses_boss_rowset(行 81–82 · 2 条 · 源 game/tests/unit/presentation/battle_narrator_test.gd · 去向:待分类回填) +## S171 _run(行 27–102 · 13 条 · 源 game/tests/unit/presentation/forge_workbench_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1307 | 81 | "Boss 名取自 Boss 行集首行(期望「%s」实际「%s」)〔fv24〕" ↩ % [boss_expect, got] | got == boss_expect | (待填) | | | -| 1308 | 82 | "Boss 名与同深度战斗行集名不同(期望「%s」≠「%s」——行类路由可判别)〔fv24〕" ↩ % [boss_expect, battle_name] | boss_expect != battle_name | (待填) | | | +| 1327 | 27 | "ConfigDb 加载(工作间测试夹具前提)" | false | (待填) | | | +| 1328 | 44 | "锁定态:工作间不可见(forge_workshop 钥匙未点亮)〔锻造工坊批 V13〕" | not ov._forge_workbench.visible and ov._forge_lock_l.visible | (待填) | | | +| 1329 | 46 | "锁定态:两区零行板(占位纪律——零购买路径)〔锻造工坊批 V13〕" | ov._forge_zone_weapon.get_child_count() == 0 and ov._forge_zone_armor.get_child_count() == 0 | (待填) | | | +| 1330 | 52 | "解锁态:工作间可见(钥匙点亮展开)〔锻造工坊批 V13〕" | ov._forge_workbench.visible | (待填) | | | +| 1331 | 53 | "两区各三路线(武器锻造区 3 + 护甲锻造区 3)〔锻造工坊批 V13〕" | ov._forge_zone_weapon.get_child_count() == 3 and ov._forge_zone_armor.get_child_count() == 3 | (待填) | | | +| 1332 | 56 | "六路线行册恰全(sword/spear/staff/light/heavy/ward)〔锻造工坊批 V13〕" | rows.has("sword") and rows.has("spear") and rows.has("staff") and rows.has("light") and rows.has("heavy") and rows.has("ward") | (待填) | | | +| 1333 | 63 | "长剑行:购买钮「锻造 20」(0 级位几何价)+选中钮「当前」禁用〔锻造工坊批 V13〕" | rsw.get("buy_btn") != null and String(rsw.buy_btn.text) == "锻造 20" and not rsw.buy_btn.disabled and rsw.get("select_btn") != null and String(rsw.select_btn.text) == "当前" and rsw.select_btn.disabled | (待填) | | | +| 1334 | 66 | "轻甲行:购买钮「锻造 20」+选中钮「当前」禁用〔锻造工坊批 V13〕" | rli.get("buy_btn") != null and String(rli.buy_btn.text) == "锻造 20" and String(rli.select_btn.text) == "当前" and rli.select_btn.disabled | (待填) | | | +| 1335 | 73 | "长枪行:解锁钮「解锁 200」+无购买钮+无选中钮〔锻造工坊批 V13〕" | rsp.get("unlock_btn") != null and String(rsp.unlock_btn.text) == "解锁 200" and not rsp.unlock_btn.disabled and rsp.get("buy_btn") == null and rsp.get("select_btn") == null | (待填) | | | +| 1336 | 76 | "魔法护甲行:解锁钮「解锁 200」+状态含并联双通道文案(练满轻甲锻造 或 200 钥石直购)〔锻造工坊批 V13〕" | rwa.get("unlock_btn") != null and String(rwa.status_l.text).contains("练满轻甲锻造") and String(rwa.status_l.text).contains("200") | (待填) | | | +| 1337 | 84 | "余额 0:长剑购买钮禁用+长枪解锁钮禁用(余额联动常态防线)〔锻造工坊批 V13〕" | rows.sword.buy_btn.disabled and rows.spear.unlock_btn.disabled | (待填) | | | +| 1338 | 92 | "长剑满级:无购买钮+状态「已满级」+长枪派生解锁出解锁钮消失(练满通道)〔锻造工坊批 V13〕" | rows.sword.get("buy_btn") == null and String(rows.sword.status_l.text) == "已满级" and rows.spear.get("unlock_btn") == null and rows.spear.get("buy_btn") != null | (待填) | | | +| 1339 | 101 | "三意图信号外发(buy=spear/unlock=heavy/select=spear——UI 只发意图)〔锻造工坊批 V13〕" | buys == ["spear"] and unlocks == ["heavy"] and selects == ["spear"] | (待填) | | | -## S166 _run(行 27–102 · 13 条 · 源 game/tests/unit/presentation/forge_workbench_test.gd · 去向:待分类回填) +## S172 _test_accepted_text(行 20–20 · 1 条 · 源 game/tests/unit/presentation/hud_presenter_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1309 | 27 | "ConfigDb 加载(工作间测试夹具前提)" | false | (待填) | | | -| 1310 | 44 | "锁定态:工作间不可见(forge_workshop 钥匙未点亮)〔锻造工坊批 V13〕" | not ov._forge_workbench.visible and ov._forge_lock_l.visible | (待填) | | | -| 1311 | 46 | "锁定态:两区零行板(占位纪律——零购买路径)〔锻造工坊批 V13〕" | ov._forge_zone_weapon.get_child_count() == 0 and ov._forge_zone_armor.get_child_count() == 0 | (待填) | | | -| 1312 | 52 | "解锁态:工作间可见(钥匙点亮展开)〔锻造工坊批 V13〕" | ov._forge_workbench.visible | (待填) | | | -| 1313 | 53 | "两区各三路线(武器锻造区 3 + 护甲锻造区 3)〔锻造工坊批 V13〕" | ov._forge_zone_weapon.get_child_count() == 3 and ov._forge_zone_armor.get_child_count() == 3 | (待填) | | | -| 1314 | 56 | "六路线行册恰全(sword/spear/staff/light/heavy/ward)〔锻造工坊批 V13〕" | rows.has("sword") and rows.has("spear") and rows.has("staff") and rows.has("light") and rows.has("heavy") and rows.has("ward") | (待填) | | | -| 1315 | 63 | "长剑行:购买钮「锻造 20」(0 级位几何价)+选中钮「当前」禁用〔锻造工坊批 V13〕" | rsw.get("buy_btn") != null and String(rsw.buy_btn.text) == "锻造 20" and not rsw.buy_btn.disabled and rsw.get("select_btn") != null and String(rsw.select_btn.text) == "当前" and rsw.select_btn.disabled | (待填) | | | -| 1316 | 66 | "轻甲行:购买钮「锻造 20」+选中钮「当前」禁用〔锻造工坊批 V13〕" | rli.get("buy_btn") != null and String(rli.buy_btn.text) == "锻造 20" and String(rli.select_btn.text) == "当前" and rli.select_btn.disabled | (待填) | | | -| 1317 | 73 | "长枪行:解锁钮「解锁 200」+无购买钮+无选中钮〔锻造工坊批 V13〕" | rsp.get("unlock_btn") != null and String(rsp.unlock_btn.text) == "解锁 200" and not rsp.unlock_btn.disabled and rsp.get("buy_btn") == null and rsp.get("select_btn") == null | (待填) | | | -| 1318 | 76 | "魔法护甲行:解锁钮「解锁 200」+状态含并联双通道文案(练满轻甲锻造 或 200 钥石直购)〔锻造工坊批 V13〕" | rwa.get("unlock_btn") != null and String(rwa.status_l.text).contains("练满轻甲锻造") and String(rwa.status_l.text).contains("200") | (待填) | | | -| 1319 | 84 | "余额 0:长剑购买钮禁用+长枪解锁钮禁用(余额联动常态防线)〔锻造工坊批 V13〕" | rows.sword.buy_btn.disabled and rows.spear.unlock_btn.disabled | (待填) | | | -| 1320 | 92 | "长剑满级:无购买钮+状态「已满级」+长枪派生解锁出解锁钮消失(练满通道)〔锻造工坊批 V13〕" | rows.sword.get("buy_btn") == null and String(rows.sword.status_l.text) == "已满级" and rows.spear.get("unlock_btn") == null and rows.spear.get("buy_btn") != null | (待填) | | | -| 1321 | 101 | "三意图信号外发(buy=spear/unlock=heavy/select=spear——UI 只发意图)〔锻造工坊批 V13〕" | buys == ["spear"] and unlocks == ["heavy"] and selects == ["spear"] | (待填) | | | +| 1340 | 20 | "受理态文案固定为「执行完成。」(实际「%s」)" ↩ % t | t == "执行完成。" | (待填) | | | -## S167 _test_accepted_text(行 20–20 · 1 条 · 源 game/tests/unit/presentation/hud_presenter_test.gd · 去向:待分类回填) +## S173 _test_reject_text_by_reason(行 36–38 · 2 条 · 源 game/tests/unit/presentation/hud_presenter_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1322 | 20 | "受理态文案固定为「执行完成。」(实际「%s」)" ↩ % t | t == "执行完成。" | (待填) | | | +| 1341 | 36 | "棋盘面各拒绝原因文案逐类正确(错 %d 项 %s)" ↩ % [bad.size(), str(bad)] | bad.is_empty() | (待填) | | | +| 1342 | 38 | "资源面拒绝文案为「AP 不足。」(实际「%s」)" ↩ % ap | ap == "AP 不足。" | (待填) | | | -## S168 _test_reject_text_by_reason(行 36–38 · 2 条 · 源 game/tests/unit/presentation/hud_presenter_test.gd · 去向:待分类回填) +## S174 _test_unknown_reason_fallback(行 43–47 · 2 条 · 源 game/tests/unit/presentation/hud_presenter_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1323 | 36 | "棋盘面各拒绝原因文案逐类正确(错 %d 项 %s)" ↩ % [bad.size(), str(bad)] | bad.is_empty() | (待填) | | | -| 1324 | 38 | "资源面拒绝文案为「AP 不足。」(实际「%s」)" ↩ % ap | ap == "AP 不足。" | (待填) | | | +| 1343 | 43 | "未知拒绝原因兜底为「动作被拒。」而非空串(实际「%s」)" ↩ % t | t == "动作被拒。" | (待填) | | | +| 1344 | 47 | "棋盘面未知子因兜底带前缀可诊断(实际「%s」)" ↩ % sub | sub.begins_with("棋盘面拒绝: ") | (待填) | | | -## S169 _test_unknown_reason_fallback(行 43–47 · 2 条 · 源 game/tests/unit/presentation/hud_presenter_test.gd · 去向:待分类回填) +## S175 _test_fog_hides_type(行 50–55 · 3 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1325 | 43 | "未知拒绝原因兜底为「动作被拒。」而非空串(实际「%s」)" ↩ % t | t == "动作被拒。" | (待填) | | | -| 1326 | 47 | "棋盘面未知子因兜底带前缀可诊断(实际「%s」)" ↩ % sub | sub.begins_with("棋盘面拒绝: ") | (待填) | | | +| 1345 | 50 | "可见集内节点:图标/类型文字均绘制〔fv2〕" | bool(shown["draw_icon"]) and bool(shown["draw_label"]) | (待填) | | | +| 1346 | 53 | "雾下节点:图标与类型文字均不绘制(显隐语义,非灰化)〔fv2〕" | not bool(fogged["draw_icon"]) and not bool(fogged["draw_label"]) | (待填) | | | +| 1347 | 55 | "雾下非 Boss 节点无 Boss 描边〔fv2〕" | not bool(fogged["boss_border"]) | (待填) | | | -## S170 _test_fog_hides_type(行 50–55 · 3 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) +## S176 _test_boss_exempt_and_multi_boss(行 64–72 · 4 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1327 | 50 | "可见集内节点:图标/类型文字均绘制〔fv2〕" | bool(shown["draw_icon"]) and bool(shown["draw_label"]) | (待填) | | | -| 1328 | 53 | "雾下节点:图标与类型文字均不绘制(显隐语义,非灰化)〔fv2〕" | not bool(fogged["draw_icon"]) and not bool(fogged["draw_label"]) | (待填) | | | -| 1329 | 55 | "雾下非 Boss 节点无 Boss 描边〔fv2〕" | not bool(fogged["boss_border"]) | (待填) | | | +| 1348 | 64 | "多终点:两个 Boss 节点均带 Boss 描边〔fv3〕" | bool(boss_a["boss_border"]) and bool(boss_b["boss_border"]) | (待填) | | | +| 1349 | 66 | "Boss 不入雾:可见集与图标/文字三通道均在〔fv3〕" | bool(boss_a["draw_icon"]) and bool(boss_a["draw_label"]) and bool(boss_b["draw_icon"]) | (待填) | | | +| 1350 | 69 | "Boss 豁免独立于可见集:不在 type_vis 的 Boss 仍出示类型〔fv3〕" | bool(far_boss["draw_icon"]) and bool(far_boss["draw_label"]) | (待填) | | | +| 1351 | 72 | "Boss 豁免不扩散:雾下非 Boss 仍隐类型〔fv3〕" | not bool(fogged_battle["draw_icon"]) | (待填) | | | -## S171 _test_boss_exempt_and_multi_boss(行 64–72 · 4 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) +## S177 _test_details_never_shown(行 86–88 · 2 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1330 | 64 | "多终点:两个 Boss 节点均带 Boss 描边〔fv3〕" | bool(boss_a["boss_border"]) and bool(boss_b["boss_border"]) | (待填) | | | -| 1331 | 66 | "Boss 不入雾:可见集与图标/文字三通道均在〔fv3〕" | bool(boss_a["draw_icon"]) and bool(boss_a["draw_label"]) and bool(boss_b["draw_icon"]) | (待填) | | | -| 1332 | 69 | "Boss 豁免独立于可见集:不在 type_vis 的 Boss 仍出示类型〔fv3〕" | bool(far_boss["draw_icon"]) and bool(far_boss["draw_label"]) | (待填) | | | -| 1333 | 72 | "Boss 豁免不扩散:雾下非 Boss 仍隐类型〔fv3〕" | not bool(fogged_battle["draw_icon"]) | (待填) | | | +| 1352 | 86 | "决策面不消费内容键:携带 enemy_id/reward_id 与不带者输出逐键相等〔fv5〕" | same | (待填) | | | +| 1353 | 87 | "label_text 不含内容值(实际「%s」)〔fv5〕" ↩ % String(b["label_text"]) | not String(b["label_text"]).contains("goblin_secret") and not String(b["label_text"]).contains("reward_secret") | (待填) | | | -## S172 _test_details_never_shown(行 86–88 · 2 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) +## S178 _test_label_three_states(行 95–100 · 3 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1334 | 86 | "决策面不消费内容键:携带 enemy_id/reward_id 与不带者输出逐键相等〔fv5〕" | same | (待填) | | | -| 1335 | 87 | "label_text 不含内容值(实际「%s」)〔fv5〕" ↩ % String(b["label_text"]) | not String(b["label_text"]).contains("goblin_secret") and not String(b["label_text"]).contains("reward_secret") | (待填) | | | +| 1354 | 95 | "当前位文案=「当前位置」〔fv2〕" | String(ScreenScript.node_display(node, 3, [3], true, false)["label_text"]) == "当前位置" | (待填) | | | +| 1355 | 97 | "候选文案=「战斗 · 可选」〔fv2〕" | String(ScreenScript.node_display(node, 3, [3], false, true)["label_text"]) == "战斗 · 可选" | (待填) | | | +| 1356 | 99 | "其余文案=类型名「战斗」〔fv2〕" | String(ScreenScript.node_display(node, 3, [3], false, false)["label_text"]) == "战斗" | (待填) | | | -## S173 _test_label_three_states(行 95–100 · 3 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) +## S179 _test_big_graph_fit(行 164–190 · 6 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1336 | 95 | "当前位文案=「当前位置」〔fv2〕" | String(ScreenScript.node_display(node, 3, [3], true, false)["label_text"]) == "当前位置" | (待填) | | | -| 1337 | 97 | "候选文案=「战斗 · 可选」〔fv2〕" | String(ScreenScript.node_display(node, 3, [3], false, true)["label_text"]) == "战斗 · 可选" | (待填) | | | -| 1338 | 99 | "其余文案=类型名「战斗」〔fv2〕" | String(ScreenScript.node_display(node, 3, [3], false, false)["label_text"]) == "战斗" | (待填) | | | +| 1357 | 164 | "大图夹具规模:≥11 层 × ≥6 槽(实测最大层 %d/最大槽 %d)〔fv8〕" ↩ % [maxl, maxslot] | maxl >= 10 and maxslot >= 5 | (待填) | | | +| 1358 | 170 | "大图拟合:画布 min.x 覆盖最深节点(%.1f ≥ %.1f)〔fv8〕" ↩ % [min_size.x, need_w] | min_size.x >= need_w - 0.01 | (待填) | | | +| 1359 | 172 | "大图拟合:画布 min.y 覆盖最宽层底节点(%.1f ≥ %.1f)〔fv8〕" ↩ % [min_size.y, need_h] | min_size.y >= need_h - 0.01 | (待填) | | | +| 1360 | 180 | "大图布局:全节点方块落在拟合画布内(origin=(%.1f,%.1f) size=(%.1f,%.1f))〔fv8〕" ↩ % [origin.x, origin.y, min_size.x, min_size.y] | all_in | (待填) | | | +| 1361 | 187 | "大图呈现:两个终点 Boss 均有描边标记〔fv8〕" | disp.has(boss_ids[0]) and disp.has(boss_ids[1]) and bool(disp[boss_ids[0]]["boss_border"]) and bool(disp[boss_ids[1]]["boss_border"]) | (待填) | | | +| 1362 | 190 | "大图呈现:入口停点远处战斗节点(句柄 32,层 6)类型入雾〔fv8〕" | not bool(disp[32]["draw_icon"]) | (待填) | | | -## S174 _test_big_graph_fit(行 164–190 · 6 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) +## S180 _init(行 105–140 · 9 条 · 源 game/tests/unit/presentation/pacing_queue_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1339 | 164 | "大图夹具规模:≥11 层 × ≥6 槽(实测最大层 %d/最大槽 %d)〔fv8〕" ↩ % [maxl, maxslot] | maxl >= 10 and maxslot >= 5 | (待填) | | | -| 1340 | 170 | "大图拟合:画布 min.x 覆盖最深节点(%.1f ≥ %.1f)〔fv8〕" ↩ % [min_size.x, need_w] | min_size.x >= need_w - 0.01 | (待填) | | | -| 1341 | 172 | "大图拟合:画布 min.y 覆盖最宽层底节点(%.1f ≥ %.1f)〔fv8〕" ↩ % [min_size.y, need_h] | min_size.y >= need_h - 0.01 | (待填) | | | -| 1342 | 180 | "大图布局:全节点方块落在拟合画布内(origin=(%.1f,%.1f) size=(%.1f,%.1f))〔fv8〕" ↩ % [origin.x, origin.y, min_size.x, min_size.y] | all_in | (待填) | | | -| 1343 | 187 | "大图呈现:两个终点 Boss 均有描边标记〔fv8〕" | disp.has(boss_ids[0]) and disp.has(boss_ids[1]) and bool(disp[boss_ids[0]]["boss_border"]) and bool(disp[boss_ids[1]]["boss_border"]) | (待填) | | | -| 1344 | 190 | "大图呈现:入口停点远处战斗节点(句柄 32,层 6)类型入雾〔fv8〕" | not bool(disp[32]["draw_icon"]) | (待填) | | | +| 1363 | 105 | "排空前后核心诊断快照逐位一致(AC⑤ 演出关停零分叉)" | snap_before == snap_after | (待填) | | | +| 1364 | 107 | "结算行已产生(断言前提)" | rows_seen.size() > 0 | (待填) | | | +| 1365 | 108 | "排空后队列恒空(flush 完整性)" | main._row_queue.is_empty() | (待填) | | | +| 1366 | 117 | "末信号行原文在明细带(排空不吞行:『%s』)" ↩ % last_text | last_text != "" and main.log_view.parsed_text().contains(last_text) | (待填) | | | +| 1367 | 123 | "常态段间延迟 >0 且 ≥ 段内(%f/%f)" ↩ % [d_on, d_same] | d_on > 0.0 and d_on >= d_same | (待填) | | | +| 1368 | 125 | "减动效段间延迟归零(行不跳过——同帧直落快路)" | main.pace_delay_for("damage", "heal") == 0.0 | (待填) | | | +| 1369 | 135 | "结算标出按行 target_id 解析站桩(id 与名同传——d12)〔wd12〕" | fstage.calls.size() == 1 and fstage.calls[0] == ["goblin_b", "哥布林"] | (待填) | | | +| 1370 | 138 | "跨区弹道按行 target_id 解析站桩(id 与名同传——d12)〔wd12〕" | fstage.calls.size() == 2 and fstage.calls[1] == ["goblin_b", "哥布林"] | (待填) | | | +| 1371 | 140 | "两路解析成功后演出层各接一拍(记参面非空)〔wd12〕" | ffx.calls.size() == 2 | (待填) | | | -## S175 _init(行 79–99 · 6 条 · 源 game/tests/unit/presentation/pacing_queue_test.gd · 去向:待分类回填) +## S181 _sweep_hero_panel(行 47–50 · 2 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1345 | 79 | "排空前后核心诊断快照逐位一致(AC⑤ 演出关停零分叉)" | snap_before == snap_after | (待填) | | | -| 1346 | 81 | "结算行已产生(断言前提)" | rows_seen.size() > 0 | (待填) | | | -| 1347 | 82 | "排空后队列恒空(flush 完整性)" | main._row_queue.is_empty() | (待填) | | | -| 1348 | 91 | "末信号行原文在明细带(排空不吞行:『%s』)" ↩ % last_text | last_text != "" and main.log_view.parsed_text().contains(last_text) | (待填) | | | -| 1349 | 97 | "常态段间延迟 >0 且 ≥ 段内(%f/%f)" ↩ % [d_on, d_same] | d_on > 0.0 and d_on >= d_same | (待填) | | | -| 1350 | 99 | "减动效段间延迟归零(行不跳过——同帧直落快路)" | main.pace_delay_for("damage", "heal") == 0.0 | (待填) | | | +| 1372 | 47 | "C 区专长段头==「专长」(u1 换代——段头文案钉值)〔wa1〕" | feat_head != null and feat_head.text == "专长" | (待填) | | | +| 1373 | 49 | "C 区属性值提升段头==「属性值提升」(u2 换代——同屏两段同标撞车消除)〔wa1〕" | curio_head != null and curio_head.text == "属性值提升" | (待填) | | | -## S176 _sweep_hero_panel(行 47–50 · 2 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) +## S182 _sweep_narrator(行 57–64 · 7 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1351 | 47 | "C 区专长段头==「专长」(u1 换代——段头文案钉值)〔wa1〕" | feat_head != null and feat_head.text == "专长" | (待填) | | | -| 1352 | 49 | "C 区属性值提升段头==「属性值提升」(u2 换代——同屏两段同标撞车消除)〔wa1〕" | curio_head != null and curio_head.text == "属性值提升" | (待填) | | | +| 1374 | 57 | "奖励点头部含「冒险牌库」(u3 机制事实①同库)〔wa4〕" | head.contains("冒险牌库") | (待填) | | | +| 1375 | 58 | "奖励点头部含「属性值提升与补给」(u3 机制事实②渠道分流)〔wa4〕" | head.contains("属性值提升与补给") | (待填) | | | +| 1376 | 59 | "奖励点头部含「同库分流」(u3 机制事实②措辞)〔wa4〕" | head.contains("同库分流") | (待填) | | | +| 1377 | 60 | "奖励点头部零「同权重」旧话(u3 清零)〔wa4〕" | not head.contains("同权重") | (待填) | | | +| 1378 | 61 | "奖励点头部零「同上限」旧话(u3 清零)〔wa4〕" | not head.contains("同上限") | (待填) | | | +| 1379 | 62 | "奖励点头部零「提升池」旧话(u3 清零)〔wa4〕" | not head.contains("提升池") | (待填) | | | +| 1380 | 63 | "专长卡兜底==「专长\\n???」(u11 换代——数据缺行真实兜底路径)〔wa3〕" | String(narrator.feat_card_text({})) == "专长\\n???" | (待填) | | | -## S177 _sweep_narrator(行 57–64 · 7 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) +## S183 _sweep_legend(行 75–79 · 5 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1353 | 57 | "奖励点头部含「冒险牌库」(u3 机制事实①同库)〔wa4〕" | head.contains("冒险牌库") | (待填) | | | -| 1354 | 58 | "奖励点头部含「属性值提升与补给」(u3 机制事实②渠道分流)〔wa4〕" | head.contains("属性值提升与补给") | (待填) | | | -| 1355 | 59 | "奖励点头部含「同库分流」(u3 机制事实②措辞)〔wa4〕" | head.contains("同库分流") | (待填) | | | -| 1356 | 60 | "奖励点头部零「同权重」旧话(u3 清零)〔wa4〕" | not head.contains("同权重") | (待填) | | | -| 1357 | 61 | "奖励点头部零「同上限」旧话(u3 清零)〔wa4〕" | not head.contains("同上限") | (待填) | | | -| 1358 | 62 | "奖励点头部零「提升池」旧话(u3 清零)〔wa4〕" | not head.contains("提升池") | (待填) | | | -| 1359 | 63 | "专长卡兜底==「专长\\n???」(u11 换代——数据缺行真实兜底路径)〔wa3〕" | String(narrator.feat_card_text({})) == "专长\\n???" | (待填) | | | +| 1381 | 75 | "图例封印行存在(wa5 夹具前提)〔wa5〕" | not seal_row.is_empty() | (待填) | | | +| 1382 | 76 | "图例封印行含「标空」(u4——与「标中」成对)〔wa5〕" | String(seal_row[2] if seal_row.size() > 2 else "").contains("标空") | (待填) | | | +| 1383 | 77 | "图例封印行含「受挖掘惩罚」(u4 雷字清零换代)〔wa5〕" | String(seal_row[2] if seal_row.size() > 2 else "").contains("受挖掘惩罚") | (待填) | | | +| 1384 | 78 | "图例封印行零「插错」(u4 旗族遗留词干清零)〔wa5〕" | not String(seal_row[2] if seal_row.size() > 2 else "").contains("插错") | (待填) | | | +| 1385 | 79 | "图例封印行零「挖雷」(u4 雷字清零)〔wa5〕" | not String(seal_row[2] if seal_row.size() > 2 else "").contains("挖雷") | (待填) | | | -## S178 _sweep_legend(行 75–79 · 5 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) +## S184 _sweep_source_names(行 92–104 · 4 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1360 | 75 | "图例封印行存在(wa5 夹具前提)〔wa5〕" | not seal_row.is_empty() | (待填) | | | -| 1361 | 76 | "图例封印行含「标空」(u4——与「标中」成对)〔wa5〕" | String(seal_row[2] if seal_row.size() > 2 else "").contains("标空") | (待填) | | | -| 1362 | 77 | "图例封印行含「受挖掘惩罚」(u4 雷字清零换代)〔wa5〕" | String(seal_row[2] if seal_row.size() > 2 else "").contains("受挖掘惩罚") | (待填) | | | -| 1363 | 78 | "图例封印行零「插错」(u4 旗族遗留词干清零)〔wa5〕" | not String(seal_row[2] if seal_row.size() > 2 else "").contains("插错") | (待填) | | | -| 1364 | 79 | "图例封印行零「挖雷」(u4 雷字清零)〔wa5〕" | not String(seal_row[2] if seal_row.size() > 2 else "").contains("挖雷") | (待填) | | | +| 1386 | 92 | "状态槽 mine_hit 效果行在座(wa6 夹具前提)〔wa6〕" | slot != null | (待填) | | | +| 1387 | 95 | "状态槽来源注记==「危险格」(u5 换代——meta tooltip 消费半)〔wa6〕" | row_view._tip_label.text.contains("来源:危险格") | (待填) | | | +| 1388 | 98 | "结算表 source_label_map[mine_hit]==「危险格」(u6 换代——ConfigDB 真装载)〔wa7〕" | String(_db.settlement.source_label_map.get("mine_hit", "")) == "危险格" | (待填) | | | +| 1389 | 103 | "战斗日志 mine_hit 来源行含「(危险格)」(u6 消费半——_format_row 生产路径)〔wa7〕" | row_txt.contains("(危险格)") | (待填) | | | -## S179 _sweep_source_names(行 92–104 · 4 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) +## S185 _sweep_board_glyph(行 133–142 · 4 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1365 | 92 | "状态槽 mine_hit 效果行在座(wa6 夹具前提)〔wa6〕" | slot != null | (待填) | | | -| 1366 | 95 | "状态槽来源注记==「危险格」(u5 换代——meta tooltip 消费半)〔wa6〕" | row_view._tip_label.text.contains("来源:危险格") | (待填) | | | -| 1367 | 98 | "结算表 source_label_map[mine_hit]==「危险格」(u6 换代——ConfigDB 真装载)〔wa7〕" | String(_db.settlement.source_label_map.get("mine_hit", "")) == "危险格" | (待填) | | | -| 1368 | 103 | "战斗日志 mine_hit 来源行含「(危险格)」(u6 消费半——_format_row 生产路径)〔wa7〕" | row_txt.contains("(危险格)") | (待填) | | | +| 1390 | 133 | "棋盘夹具含挖掘射程内未开危险格(wa8 前提——实测 %d 步)〔wa8〕" ↩ % glow_steps | mine_cell.x >= 0 | (待填) | | | +| 1391 | 139 | "已开危险格 glyph==「危」(u7 换代)〔wa8〕" | u.glyph == "危" | (待填) | | | +| 1392 | 140 | "已开危险格 plate==true(u9 牌载哨兵包含项随动)〔wa8〕" | u.plate == true | (待填) | | | +| 1393 | 141 | "已开危险格 glyph_color==RED_MINE(u7 墨色不随字形换代漂移)〔wa8〕" | u.glyph_color == UiKit.RED_MINE | (待填) | | | -## S180 _sweep_board_glyph(行 133–142 · 4 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) +## S186 _sweep_token_scans(行 148–158 · 2 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1369 | 133 | "棋盘夹具含挖掘射程内未开危险格(wa8 前提——实测 %d 步)〔wa8〕" ↩ % glow_steps | mine_cell.x >= 0 | (待填) | | | -| 1370 | 139 | "已开危险格 glyph==「危」(u7 换代)〔wa8〕" | u.glyph == "危" | (待填) | | | -| 1371 | 140 | "已开危险格 plate==true(u9 牌载哨兵包含项随动)〔wa8〕" | u.plate == true | (待填) | | | -| 1372 | 141 | "已开危险格 glyph_color==RED_MINE(u7 墨色不随字形换代漂移)〔wa8〕" | u.glyph_color == UiKit.RED_MINE | (待填) | | | +| 1394 | 148 | "board_view.gd 源零 \\"雷\\" 字面量(u7/u8/u9 哨兵族统一钉住)〔wa8〕" | board_src.count("\\"雷\\"") == 0 | (待填) | | | +| 1395 | 157 | "battle_orchestrator.gd 玩家面字符串字面量零「挖雷」(u13——注释保留面不入域)〔wa12〕" | orch_hits == 0 | (待填) | | | -## S181 _sweep_token_scans(行 148–158 · 2 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) +## S187 _init(行 75–132 · 18 条 · 源 game/tests/unit/presentation/standee_fx_sweep_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1373 | 148 | "board_view.gd 源零 \\"雷\\" 字面量(u7/u8/u9 哨兵族统一钉住)〔wa8〕" | board_src.count("\\"雷\\"") == 0 | (待填) | | | -| 1374 | 157 | "battle_orchestrator.gd 玩家面字符串字面量零「挖雷」(u13——注释保留面不入域)〔wa12〕" | orch_hits == 0 | (待填) | | | +| 1396 | 75 | "站桩 enemy_id 访问器读装载值〔wd12〕" | s1.enemy_id() == "goblin_a" and s2.enemy_id() == "goblin_b" | (待填) | | | +| 1397 | 76 | "同名敌按 id 精确解析(不做名子串撞车)〔wd12〕" | stage._card_by_id("goblin_b") == s2 and stage._card_by_id("goblin_a") == s1 | (待填) | | | +| 1398 | 78 | "空 id / 未登记 id 恒不命中(名兜底归调用点)〔wd12〕" | stage._card_by_id("") == null and stage._card_by_id("no_such") == null | (待填) | | | +| 1399 | 83 | "伤害行按 target_id 命中正确站桩(闪白落点)〔wd12〕" | s2._flash_count == 1 and s1._flash_count == 0 | (待填) | | | +| 1400 | 88 | "弹道落点按 target_id 取站桩中心〔wd12〕" | c_b != null and c_b == c_b_expect | (待填) | | | +| 1401 | 91 | "无 target_id 时按名兜底同旧行为(首命中)〔wd12〕" | c_fallback != null and c_fallback == c_fallback_expect | (待填) | | | +| 1402 | 95 | "蓄力技能(heavy)置 _intent_charge=true〔wd2〕" | s1._intent_charge | (待填) | | | +| 1403 | 97 | "非蓄力攻击技(goblin_slash)零置位〔wd2〕" | not s1._intent_charge | (待填) | | | +| 1404 | 99 | "增益系零置位〔wd2〕" | not s1._intent_charge | (待填) | | | +| 1405 | 101 | "普攻零表示路径复位 _intent_charge〔wd2〕" | not s1._intent_charge | (待填) | | | +| 1406 | 108 | "首开胜利画面创建庆祝句柄〔wd4〕" | first != null and first.is_valid() | (待填) | | | +| 1407 | 111 | "重开胜利画面:旧句柄已 kill、新句柄在位(至多一个活跃)〔wd4〕" | first != null and not first.is_valid() and second != null and second.is_valid() | (待填) | | | +| 1408 | 114 | "close 必杀庆祝句柄(句柄清空)〔wd4〕" | ov.get_meta(&"celebrate_tw", null) == null | (待填) | | | +| 1409 | 119 | "模拟运行期调试入口钮同置灰〔wd5〕" | dbg._dbg_btn.disabled | (待填) | | | +| 1410 | 121 | "模拟收尾调试入口钮恢复(未锁定)〔wd5〕" | not dbg._dbg_btn.disabled | (待填) | | | +| 1411 | 130 | "控制器运行中 is_running()=true(驱动回调内读数)〔wd8〕" | drv.seen == [true, true] | (待填) | | | +| 1412 | 131 | "控制器收尾 is_running()=false〔wd8〕" | not ctrl.is_running() and not panel.running | (待填) | | | +| 1413 | 132 | "两局报告全文注入面板(run 收尾链路完整)〔wd8〕" | String(panel.report) != "" | (待填) | | | -## S182 _init(行 24–34 · 2 条 · 源 game/tests/unit/presentation/terrain_tile_sets_test.gd · 去向:待分类回填) +## S188 _init(行 24–34 · 2 条 · 源 game/tests/unit/presentation/terrain_tile_sets_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1375 | 24 | "前置:表加载零 error" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | -| 1376 | 34 | "前置:board_gen 词表非空(%d 型)" ↩ % types.size() | types.size() > 0 | (待填) | | | +| 1414 | 24 | "前置:表加载零 error" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | +| 1415 | 34 | "前置:board_gen 词表非空(%d 型)" ↩ % types.size() | types.size() > 0 | (待填) | | | -## S183 _test_set_completeness(行 56–56 · 1 条 · 源 game/tests/unit/presentation/terrain_tile_sets_test.gd · 去向:待分类回填) +## S189 _test_set_completeness(行 56–56 · 1 条 · 源 game/tests/unit/presentation/terrain_tile_sets_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1377 | 56 | "图型 %s 瓦片套完整(16 墙 + %d 地面,缺=%s)" ↩ % [String(bt), variants, str(missing)] | missing.is_empty() | (待填) | | | +| 1416 | 56 | "图型 %s 瓦片套完整(16 墙 + %d 地面,缺=%s)" ↩ % [String(bt), variants, str(missing)] | missing.is_empty() | (待填) | | | -## S184 _test_hue_domains(行 72–82 · 4 条 · 源 game/tests/unit/presentation/terrain_tile_sets_test.gd · 去向:待分类回填) +## S190 _test_hue_domains(行 72–82 · 4 条 · 源 game/tests/unit/presentation/terrain_tile_sets_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1378 | 72 | "图型 %s 抽检样本可读" ↩ % String(bt) | false | (待填) | | | -| 1379 | 77 | "图型 %s 墙顶均值色相在带 %s(实测 %.3f)" ↩ % [String(bt), str(band), float(wall["h"])] | float(wall["h"]) >= float(band[0]) and float(wall["h"]) <= float(band[1]) | (待填) | | | -| 1380 | 79 | "图型 %s 图底分离(墙顶明度 %.3f > 地面 %.3f)" ↩ % [String(bt), wall_v[bt], floor_v[bt]] | wall_v[bt] > floor_v[bt] | (待填) | | | -| 1381 | 82 | "deep_underdark 墙顶更暗于 underdark(更深蓝黑方向)" | float(wall_v["deep_underdark"]) < float(wall_v["underdark"]) | (待填) | | | +| 1417 | 72 | "图型 %s 抽检样本可读" ↩ % String(bt) | false | (待填) | | | +| 1418 | 77 | "图型 %s 墙顶均值色相在带 %s(实测 %.3f)" ↩ % [String(bt), str(band), float(wall["h"])] | float(wall["h"]) >= float(band[0]) and float(wall["h"]) <= float(band[1]) | (待填) | | | +| 1419 | 79 | "图型 %s 图底分离(墙顶明度 %.3f > 地面 %.3f)" ↩ % [String(bt), wall_v[bt], floor_v[bt]] | wall_v[bt] > floor_v[bt] | (待填) | | | +| 1420 | 82 | "deep_underdark 墙顶更暗于 underdark(更深蓝黑方向)" | float(wall_v["deep_underdark"]) < float(wall_v["underdark"]) | (待填) | | | -## S185 _test_digit_ink_modes(行 137–159 · 5 条 · 源 game/tests/unit/presentation/terrain_tile_sets_test.gd · 去向:待分类回填) +## S191 _test_digit_ink_modes(行 137–159 · 5 条 · 源 game/tests/unit/presentation/terrain_tile_sets_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1382 | 137 | "图型 %s 数字墨族断言样本可读" ↩ % String(bt) | false | (待填) | | | -| 1383 | 144 | "图型 %s 双峰纹理落牌(亮族 p90 %.2f/均值 %.2f,深族 p90 %.2f/均值 %.2f 均未过门)" ↩ % [ String(bt), float(mode["cr_p90_bright"]), float(mode["cr_mean_bright"]), float(mode["cr_p90_dark"]), float(mode["cr_mean_dark"])] | float(mode["cr_mean_dark"]) < float(BoardViewScript.DIGIT_INK_CR_MEAN_DARK) | (待填) | | | -| 1384 | 151 | "图型 %s 所选%s 对 p90 ≥%.1f(实测 %.2f)" ↩ % [String(bt), mode_name, float(BoardViewScript.DIGIT_INK_CR_P90), cr_p90] | cr_p90 >= float(BoardViewScript.DIGIT_INK_CR_P90) | (待填) | | | -| 1385 | 155 | "图型 %s 所选%s 对均值 ≥%.1f(实测 %.2f)" ↩ % [String(bt), mode_name, mean_gate, cr_mean] | cr_mean >= mean_gate | (待填) | | | -| 1386 | 158 | "图型 %s 呈现模式期望=%s(实测=%s)" ↩ % [String(bt), "色牌" if bool(expect_plate[bt]) else "直画", mode_name] | plate == bool(expect_plate[bt]) | (待填) | | | +| 1421 | 137 | "图型 %s 数字墨族断言样本可读" ↩ % String(bt) | false | (待填) | | | +| 1422 | 144 | "图型 %s 双峰纹理落牌(亮族 p90 %.2f/均值 %.2f,深族 p90 %.2f/均值 %.2f 均未过门)" ↩ % [ String(bt), float(mode["cr_p90_bright"]), float(mode["cr_mean_bright"]), float(mode["cr_p90_dark"]), float(mode["cr_mean_dark"])] | float(mode["cr_mean_dark"]) < float(TilePaletteScript.DIGIT_INK_CR_MEAN_DARK) | (待填) | | | +| 1423 | 151 | "图型 %s 所选%s 对 p90 ≥%.1f(实测 %.2f)" ↩ % [String(bt), mode_name, float(TilePaletteScript.DIGIT_INK_CR_P90), cr_p90] | cr_p90 >= float(TilePaletteScript.DIGIT_INK_CR_P90) | (待填) | | | +| 1424 | 155 | "图型 %s 所选%s 对均值 ≥%.1f(实测 %.2f)" ↩ % [String(bt), mode_name, mean_gate, cr_mean] | cr_mean >= mean_gate | (待填) | | | +| 1425 | 158 | "图型 %s 呈现模式期望=%s(实测=%s)" ↩ % [String(bt), "色牌" if bool(expect_plate[bt]) else "直画", mode_name] | plate == bool(expect_plate[bt]) | (待填) | | | -## S186 _smoke_type_glow(行 214–374 · 24 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S192 _smoke_type_glow(行 214–374 · 24 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1387 | 214 | "V1 微光默认开(Q4 丙案——呈现态布尔缺省 true,生产写入面零显式开)" | bool(snap.get("enabled", false)) | 类型微光默认开启(呈现态布尔缺省 true)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1388 | 216 | "V1 夹具全集合法九型 ⇒ 清单外登记为空(禁静默空光通道零误报)" | (snap.get("errors", []) as Array).is_empty() | 夹具全集合法九型时清单外登记为空(禁静默空光零误报)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1389 | 219 | "V1 作用集恒等:微光格集合 == _diggable ∩ 身份可查(期望 %d,实测 %d)" ↩ % [expect.size(), (snap.get("cells", {}) as Dictionary).size()] | _glow_key_set_equal(snap.get("cells", {}), expect) | 微光格集合与独立重算期望集恒等(可挖∩身份可查)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1390 | 221 | "V1 夹具前提:英雄未开邻格 ≥5(实测 %d——novice %d 步)" ↩ % [expect.size(), steps] | expect.size() >= 5 | 夹具前提:英雄未开邻格≥5(驱动步数在册)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1391 | 231 | "V1 已开格/r0 出生展开区零微光(S1)%s" ↩ % ("" if opened_leak == "" else "违例:" + opened_leak) | opened_leak == "" | 已开格与出生展开区零微光。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1392 | 248 | "V2 夹具前提:未开邻格派定 ≥5 型(实测 %d)" ↩ % mapped | mapped >= 5 | 夹具前提:未开邻格派定≥5 型。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1393 | 257 | "V2 九型→五组逐格映射(attack/defense/action/gp→增益/heal→治疗/pierce→穿刺/blast→群伤/magic→魔法)%s" ↩ % ("" if map_bad == "" else "违例:" + map_bad) | map_bad == "" | 九型→五组逐格映射正确(增益/治疗/穿刺/群伤/魔法)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1394 | 271 | "V2 清单外 cell_type 显式登记(errors=%s)" ↩ % str(errs) | errs.has("keystone") | 清单外 cell_type 显式登记进 errors(不静默)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1395 | 272 | "V2 清单外格零微光(无静默空光——%s 不在作用集)" ↩ % str(bogus_cell) | not (snap.get("cells", {}) as Dictionary).has(bogus_cell) | 清单外格零微光。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1396 | 274 | "V2 清单外格剔除后作用集 = 派定数 −1(%d→%d)" ↩ % [mapped, (snap.get("cells", {}) as Dictionary).size()] | (snap.get("cells", {}) as Dictionary).size() == mapped - 1 | 清单外格剔除后作用集=派定数−1。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1397 | 287 | "V1 封锁格零微光(query null ⇒ 天然惰性——%s)" ↩ % str(blocked_cell) | not (snap.get("cells", {}) as Dictionary).has(blocked_cell) | 封锁格(查询 null)零微光(天然惰性)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1398 | 289 | "V1 封锁格零登记(errors 仍仅清单外一条——%s)" ↩ % str(snap.get("errors", [])) | (snap.get("errors", []) as Array).size() == 1 | 封锁格零登记(errors 仍仅清单外一条)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1399 | 291 | "V1 封锁格剔除后作用集 = 派定数 −1(实测 %d)" ↩ % (snap.get("cells", {}) as Dictionary).size() | (snap.get("cells", {}) as Dictionary).size() == mapped - 1 | 封锁格剔除后作用集=派定数−1。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1400 | 297 | "V1 封锁解除后作用集复位恒等(期望 %d,实测 %d)" ↩ % [expect.size(), (snap.get("cells", {}) as Dictionary).size()] | _glow_key_set_equal(snap.get("cells", {}), expect) | 封锁解除后作用集复位恒等(进出圈复亮走回)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1401 | 303 | "V7 off 态开关读数=false" | not bool(snap.get("enabled", true)) | off 态开关读数=false。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1402 | 304 | "V7 off 态作用集为空(基线保护面)" | (snap.get("cells", {}) as Dictionary).is_empty() | off 态作用集为空(基线保护面)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1403 | 307 | "V7 on 态开关读数=true(复现)" | bool(snap_on.get("enabled", false)) | on 态开关读数=true(复现)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1404 | 308 | "V7 off→on 作用集逐位复现(关前 %d 格/开后 %d 格,组号逐位一致)" ↩ % [ (snap_before_off.get("cells", {}) as Dictionary).size(), (snap_on.get("cells", {}) as Dictionary).size()] | _glow_map_equal(snap_on.get("cells", {}), snap_before_off.get("cells", {})) | off→on 作用集逐位复现(组号一致)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1405 | 311 | "V7 off→on 作用集与独立重算期望集恒等(%d 格)" ↩ % (snap_on.get("cells", {}) as Dictionary).size() | _glow_key_set_equal(snap_on.get("cells", {}), expect) | off→on 作用集与独立重算期望集恒等。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1406 | 315 | "V7 微光开关零玩法写入(已开集/英雄位/封印数三面对照逐位一致)" | revealed_before == revealed_after and hero_before == ms.hero_pos() and seals_before == ms.seal_count() | 微光开关零玩法写入(已开集/英雄位/封印数三面零扰动)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1407 | 328 | "V3 换图 rebuild 后作用集按新板重算(期望 %d,实测 %d——零旧板残留)" ↩ % [expect2.size(), (snap2.get("cells", {}) as Dictionary).size()] | _glow_key_set_equal(snap2.get("cells", {}), expect2) | 换图 rebuild 后作用集按新板重算(零旧板残留)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1408 | 330 | "V3 换图清清单外登记(rebuild 清零)" | (snap2.get("errors", []) as Array).is_empty() | 换图清清单外登记(rebuild 清零)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1409 | 371 | "契约 1 同板同序列 on/off 两态结算逐位一致(%d 步对照——A 开态/B 关态)%s" ↩ % [seq_steps, diverge] | diverge == "" | 同板同序列 on/off 双实例结算逐位一致(微光派生零写板态)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1410 | 374 | "契约 1 对照前提:同序列 ≥5 步(实测 %d)" ↩ % seq_steps | seq_steps >= 5 | 对照前提:同序列≥5 步(非空转)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 1426 | 214 | "V1 微光默认开(Q4 丙案——呈现态布尔缺省 true,生产写入面零显式开)" | bool(snap.get("enabled", false)) | (待填) | | | +| 1427 | 216 | "V1 夹具全集合法九型 ⇒ 清单外登记为空(禁静默空光通道零误报)" | (snap.get("errors", []) as Array).is_empty() | (待填) | | | +| 1428 | 219 | "V1 作用集恒等:微光格集合 == _diggable ∩ 身份可查(期望 %d,实测 %d)" ↩ % [expect.size(), (snap.get("cells", {}) as Dictionary).size()] | _glow_key_set_equal(snap.get("cells", {}), expect) | (待填) | | | +| 1429 | 221 | "V1 夹具前提:英雄未开邻格 ≥5(实测 %d——novice %d 步)" ↩ % [expect.size(), steps] | expect.size() >= 5 | (待填) | | | +| 1430 | 231 | "V1 已开格/r0 出生展开区零微光(S1)%s" ↩ % ("" if opened_leak == "" else "违例:" + opened_leak) | opened_leak == "" | (待填) | | | +| 1431 | 248 | "V2 夹具前提:未开邻格派定 ≥5 型(实测 %d)" ↩ % mapped | mapped >= 5 | (待填) | | | +| 1432 | 257 | "V2 九型→五组逐格映射(attack/defense/action/gp→增益/heal→治疗/pierce→穿刺/blast→群伤/magic→魔法)%s" ↩ % ("" if map_bad == "" else "违例:" + map_bad) | map_bad == "" | (待填) | | | +| 1433 | 271 | "V2 清单外 cell_type 显式登记(errors=%s)" ↩ % str(errs) | errs.has("keystone") | (待填) | | | +| 1434 | 272 | "V2 清单外格零微光(无静默空光——%s 不在作用集)" ↩ % str(bogus_cell) | not (snap.get("cells", {}) as Dictionary).has(bogus_cell) | (待填) | | | +| 1435 | 274 | "V2 清单外格剔除后作用集 = 派定数 −1(%d→%d)" ↩ % [mapped, (snap.get("cells", {}) as Dictionary).size()] | (snap.get("cells", {}) as Dictionary).size() == mapped - 1 | (待填) | | | +| 1436 | 287 | "V1 封锁格零微光(query null ⇒ 天然惰性——%s)" ↩ % str(blocked_cell) | not (snap.get("cells", {}) as Dictionary).has(blocked_cell) | (待填) | | | +| 1437 | 289 | "V1 封锁格零登记(errors 仍仅清单外一条——%s)" ↩ % str(snap.get("errors", [])) | (snap.get("errors", []) as Array).size() == 1 | (待填) | | | +| 1438 | 291 | "V1 封锁格剔除后作用集 = 派定数 −1(实测 %d)" ↩ % (snap.get("cells", {}) as Dictionary).size() | (snap.get("cells", {}) as Dictionary).size() == mapped - 1 | (待填) | | | +| 1439 | 297 | "V1 封锁解除后作用集复位恒等(期望 %d,实测 %d)" ↩ % [expect.size(), (snap.get("cells", {}) as Dictionary).size()] | _glow_key_set_equal(snap.get("cells", {}), expect) | (待填) | | | +| 1440 | 303 | "V7 off 态开关读数=false" | not bool(snap.get("enabled", true)) | (待填) | | | +| 1441 | 304 | "V7 off 态作用集为空(基线保护面)" | (snap.get("cells", {}) as Dictionary).is_empty() | (待填) | | | +| 1442 | 307 | "V7 on 态开关读数=true(复现)" | bool(snap_on.get("enabled", false)) | (待填) | | | +| 1443 | 308 | "V7 off→on 作用集逐位复现(关前 %d 格/开后 %d 格,组号逐位一致)" ↩ % [ (snap_before_off.get("cells", {}) as Dictionary).size(), (snap_on.get("cells", {}) as Dictionary).size()] | _glow_map_equal(snap_on.get("cells", {}), snap_before_off.get("cells", {})) | (待填) | | | +| 1444 | 311 | "V7 off→on 作用集与独立重算期望集恒等(%d 格)" ↩ % (snap_on.get("cells", {}) as Dictionary).size() | _glow_key_set_equal(snap_on.get("cells", {}), expect) | (待填) | | | +| 1445 | 315 | "V7 微光开关零玩法写入(已开集/英雄位/封印数三面对照逐位一致)" | revealed_before == revealed_after and hero_before == ms.hero_pos() and seals_before == ms.seal_count() | (待填) | | | +| 1446 | 328 | "V3 换图 rebuild 后作用集按新板重算(期望 %d,实测 %d——零旧板残留)" ↩ % [expect2.size(), (snap2.get("cells", {}) as Dictionary).size()] | _glow_key_set_equal(snap2.get("cells", {}), expect2) | (待填) | | | +| 1447 | 330 | "V3 换图清清单外登记(rebuild 清零)" | (snap2.get("errors", []) as Array).is_empty() | (待填) | | | +| 1448 | 371 | "契约 1 同板同序列 on/off 两态结算逐位一致(%d 步对照——A 开态/B 关态)%s" ↩ % [seq_steps, diverge] | diverge == "" | (待填) | | | +| 1449 | 374 | "契约 1 对照前提:同序列 ≥5 步(实测 %d)" ↩ % seq_steps | seq_steps >= 5 | (待填) | | | -## S187 _smoke_battle_log_view(行 391–422 · 6 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S193 _smoke_battle_log_view(行 391–422 · 6 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1411 | 391 | "敌方伤害行格式化(baseline 行不读退役 attach)" | enemy_txt.contains("撕咬") and enemy_txt.contains("〔掷 15 vs 护甲 12") | 敌方伤害行格式化(不读退役字段——卡死 bug 回归)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1412 | 393 | "退役伏击/基线拆行文案不再出现" | not enemy_txt.contains("伏击") and not enemy_txt.contains("基线") | 退役伏击/基线拆行文案不再出现。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1413 | 400 | "主角穿刺型暴击行金框(单分量 payload 不触发三分量拆行)" | hero_txt.contains("暴击×") and hero_txt.contains("⚔击杀") and hero_txt.contains("■") | 主角穿刺暴击行金框(单分量 payload 不触发拆行)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1414 | 402 | "退役三分量拆行文案不再出现" | not hero_txt.contains(" + 穿刺 ") | 退役三分量拆行文案不再出现。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1415 | 408 | "未生效行聚合计数 ×2 呈现" | agg_txt.contains("⚠") and agg_txt.contains("×2") | 未生效行聚合计数×2 呈现(不逐行刷屏)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1416 | 421 | "行型 %s 格式化落地(解析文本增长)" ↩ % String(row_type) | view.parsed_text().length() > before | 其余行型逐型格式化落地(解析文本增长——脚本错误静默吞断言转可见 FAIL)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 1450 | 391 | "敌方伤害行格式化(baseline 行不读退役 attach)" | enemy_txt.contains("撕咬") and enemy_txt.contains("〔掷 15 vs 护甲 12") | (待填) | | | +| 1451 | 393 | "退役伏击/基线拆行文案不再出现" | not enemy_txt.contains("伏击") and not enemy_txt.contains("基线") | (待填) | | | +| 1452 | 400 | "主角穿刺型暴击行金框(单分量 payload 不触发三分量拆行)" | hero_txt.contains("暴击×") and hero_txt.contains("⚔击杀") and hero_txt.contains("■") | (待填) | | | +| 1453 | 402 | "退役三分量拆行文案不再出现" | not hero_txt.contains(" + 穿刺 ") | (待填) | | | +| 1454 | 408 | "未生效行聚合计数 ×2 呈现" | agg_txt.contains("⚠") and agg_txt.contains("×2") | (待填) | | | +| 1455 | 421 | "行型 %s 格式化落地(解析文本增长)" ↩ % String(row_type) | view.parsed_text().length() > before | (待填) | | | -## S188 _smoke_level_open_line(行 429–435 · 3 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S194 _smoke_level_open_line(行 429–435 · 3 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1417 | 429 | "开场行 boosted=true 尾接「(含面板加成)」〔wc1〕" | String(NarratorScript.level_open_line("第一层", 42, 5, 30, true)).contains("(含面板加成)") | (待填) | | | -| 1418 | 431 | "开场行 boosted=false 零后缀(白板局不带)〔wc1〕" | not String(NarratorScript.level_open_line("第一层", 42, 5, 30, false)).contains("(含面板加成)") | (待填) | | | -| 1419 | 433 | "开场行种子/两值带入不受后缀位影响(数值带入回归)〔wc1〕" | String(NarratorScript.level_open_line("第一层", 42, 5, 30, true)).contains("种子 42") and String(NarratorScript.level_open_line("第一层", 42, 5, 30, true)).contains("攻击 5 / 生命 30") | (待填) | | | +| 1456 | 429 | "开场行 boosted=true 尾接「(含面板加成)」〔wc1〕" | String(NarratorScript.level_open_line("第一层", 42, 5, 30, true)).contains("(含面板加成)") | (待填) | | | +| 1457 | 431 | "开场行 boosted=false 零后缀(白板局不带)〔wc1〕" | not String(NarratorScript.level_open_line("第一层", 42, 5, 30, false)).contains("(含面板加成)") | (待填) | | | +| 1458 | 433 | "开场行种子/两值带入不受后缀位影响(数值带入回归)〔wc1〕" | String(NarratorScript.level_open_line("第一层", 42, 5, 30, true)).contains("种子 42") and String(NarratorScript.level_open_line("第一层", 42, 5, 30, true)).contains("攻击 5 / 生命 30") | (待填) | | | -## S189 _smoke_global_theme(行 444–539 · 22 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S195 _smoke_global_theme(行 444–539 · 22 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1420 | 444 | "V1 项目主题挂载(project.godot gui/theme/custom → main_theme.tres)" | th != null | 项目主题已挂载(工程设置指向主主题)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1421 | 453 | "V1 主题类型覆盖 %s" ↩ % t | type_set.has(t) | 主题类型覆盖在册(15 类逐类型——含弹层预盖)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1422 | 455 | "V1 红线 Button 类型词条零接触" | not type_set.has("Button") | Button 类型词条零接触(五态矩阵归按钮工厂)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1423 | 463 | "V1 红线 focus 族词条零定义%s" ↩ % ("" if focus_hits == "" else "(违例:" + focus_hits + ")") | focus_hits == "" | focus 族词条零定义(红线扫描)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1424 | 467 | "V1 滚动条槽/柄三态词条在位" | vsb.has_theme_stylebox("scroll") and vsb.has_theme_stylebox("grabber") and vsb.has_theme_stylebox("grabber_highlight") and vsb.has_theme_stylebox("grabber_pressed") | 滚动条槽/柄三态词条在位。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1425 | 470 | "V1 滚动条 scroll_focus 未定义(红线)" | not th.has_stylebox("scroll_focus", "VScrollBar") and not th.has_stylebox("scroll_focus", "HScrollBar") | 滚动条 scroll_focus 未定义(红线)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1426 | 473 | "V1 滑轨槽/填充/柄词条在位" | hsl.has_theme_stylebox("slider") and hsl.has_theme_stylebox("grabber_area") and hsl.has_theme_stylebox("grabber_area_highlight") and hsl.has_theme_icon("grabber") and hsl.has_theme_icon("grabber_highlight") and hsl.has_theme_icon("grabber_disabled") | 滑轨槽/填充/柄词条在位。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1427 | 479 | "V1 LineEdit 词条在位(含占位色 font_placeholder_color)" | le.has_theme_stylebox("normal") and le.has_theme_color("font_color") and le.has_theme_color("font_placeholder_color") | 输入框词条在位(含占位色)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1428 | 483 | "V1 SpinBox 箭头词条在位(updown 合并槽)" | sp.has_theme_icon("updown") | 微调框箭头词条在位。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1429 | 486 | "V1 分隔线词条在位" | hsep.has_theme_stylebox("separator") and vsep.has_theme_stylebox("separator") | 分隔线词条在位。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1430 | 490 | "V1 TooltipPanel 词条在位" | tpanel.has_theme_stylebox("panel") | 提示面板词条在位。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1431 | 491 | "V1 TooltipLabel 词条在位" | tlabel.has_theme_color("font_color") and tlabel.has_theme_font_size("font_size") | 提示文字词条在位。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1432 | 499 | "V2 冻结值锚定(已填充段=BG_ROOT #3A2E22,槽=暖石 #B8A582)" | fill_c.is_equal_approx(Color8(58, 46, 34)) and track_c.is_equal_approx(Color8(184, 165, 130)) | 滑轨冻结值锚定(已填充段与槽色=拍板色值)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1433 | 502 | "V2 滑轨两段对比 %.2f:1 ≥4.5:1(拖到中点一眼可辨)" ↩ % ratio | ratio >= 4.5 | 滑轨两段对比度≥4.5:1(拖到中点一眼可辨)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1434 | 504 | "V2 滑轨 stylebox 类型均为 StyleBoxFlat" | false | 滑轨样式盒类型均为扁平(否则显式红)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1435 | 507 | "V3 CheckBox 四态图标非空" | cbx.get_theme_icon("checked") != null and cbx.get_theme_icon("unchecked") != null and cbx.get_theme_icon("checked_disabled") != null and cbx.get_theme_icon("unchecked_disabled") != null | 复选框四态图标非空。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1436 | 511 | "V3 CheckButton 四态图标非空(与 CheckBox 同套 checked/unchecked 名,4.7 源码实证)" | cbt.get_theme_icon("checked") != null and cbt.get_theme_icon("unchecked") != null and cbt.get_theme_icon("checked_disabled") != null and cbt.get_theme_icon("unchecked_disabled") != null | 开关钮四态图标非空。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1437 | 515 | "V3 PopupMenu 词条存在(弹层预盖)" | pm.has_theme_stylebox("panel") and pm.has_theme_stylebox("hover") and pm.has_theme_font_size("font_size") | 弹出菜单词条存在(弹层预盖)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1438 | 519 | "V3 OptionButton 词条存在(预盖)" | ob.has_theme_stylebox("normal") and ob.has_theme_stylebox("hover") and ob.has_theme_stylebox("pressed") and ob.has_theme_color("font_color") | 选项按钮词条存在(预盖)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1439 | 533 | "V4 退役实现零残留(%s)%s" ↩ % [path, "" if bad == "" else "违例:" + bad] | bad == "" | 四文件退役实现零残留(剥注释行扫旧符号)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1440 | 536 | "V6 主题 default_font 非空(挂主题顶掉 custom_font 也不丢中文字形)" | th.default_font != null | 主题默认字体非空(挂主题不丢中文字形)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1441 | 539 | "V6 Label 实取字体非空(中文字形防御实证)" | lbl.get_theme_font("font") != null | Label 实取字体非空(中文字形防御实证)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 1459 | 444 | "V1 项目主题挂载(project.godot gui/theme/custom → main_theme.tres)" | th != null | (待填) | | | +| 1460 | 453 | "V1 主题类型覆盖 %s" ↩ % t | type_set.has(t) | (待填) | | | +| 1461 | 455 | "V1 红线 Button 类型词条零接触" | not type_set.has("Button") | (待填) | | | +| 1462 | 463 | "V1 红线 focus 族词条零定义%s" ↩ % ("" if focus_hits == "" else "(违例:" + focus_hits + ")") | focus_hits == "" | (待填) | | | +| 1463 | 467 | "V1 滚动条槽/柄三态词条在位" | vsb.has_theme_stylebox("scroll") and vsb.has_theme_stylebox("grabber") and vsb.has_theme_stylebox("grabber_highlight") and vsb.has_theme_stylebox("grabber_pressed") | (待填) | | | +| 1464 | 470 | "V1 滚动条 scroll_focus 未定义(红线)" | not th.has_stylebox("scroll_focus", "VScrollBar") and not th.has_stylebox("scroll_focus", "HScrollBar") | (待填) | | | +| 1465 | 473 | "V1 滑轨槽/填充/柄词条在位" | hsl.has_theme_stylebox("slider") and hsl.has_theme_stylebox("grabber_area") and hsl.has_theme_stylebox("grabber_area_highlight") and hsl.has_theme_icon("grabber") and hsl.has_theme_icon("grabber_highlight") and hsl.has_theme_icon("grabber_disabled") | (待填) | | | +| 1466 | 479 | "V1 LineEdit 词条在位(含占位色 font_placeholder_color)" | le.has_theme_stylebox("normal") and le.has_theme_color("font_color") and le.has_theme_color("font_placeholder_color") | (待填) | | | +| 1467 | 483 | "V1 SpinBox 箭头词条在位(updown 合并槽)" | sp.has_theme_icon("updown") | (待填) | | | +| 1468 | 486 | "V1 分隔线词条在位" | hsep.has_theme_stylebox("separator") and vsep.has_theme_stylebox("separator") | (待填) | | | +| 1469 | 490 | "V1 TooltipPanel 词条在位" | tpanel.has_theme_stylebox("panel") | (待填) | | | +| 1470 | 491 | "V1 TooltipLabel 词条在位" | tlabel.has_theme_color("font_color") and tlabel.has_theme_font_size("font_size") | (待填) | | | +| 1471 | 499 | "V2 冻结值锚定(已填充段=BG_ROOT #3A2E22,槽=暖石 #B8A582)" | fill_c.is_equal_approx(Color8(58, 46, 34)) and track_c.is_equal_approx(Color8(184, 165, 130)) | (待填) | | | +| 1472 | 502 | "V2 滑轨两段对比 %.2f:1 ≥4.5:1(拖到中点一眼可辨)" ↩ % ratio | ratio >= 4.5 | (待填) | | | +| 1473 | 504 | "V2 滑轨 stylebox 类型均为 StyleBoxFlat" | false | (待填) | | | +| 1474 | 507 | "V3 CheckBox 四态图标非空" | cbx.get_theme_icon("checked") != null and cbx.get_theme_icon("unchecked") != null and cbx.get_theme_icon("checked_disabled") != null and cbx.get_theme_icon("unchecked_disabled") != null | (待填) | | | +| 1475 | 511 | "V3 CheckButton 四态图标非空(与 CheckBox 同套 checked/unchecked 名,4.7 源码实证)" | cbt.get_theme_icon("checked") != null and cbt.get_theme_icon("unchecked") != null and cbt.get_theme_icon("checked_disabled") != null and cbt.get_theme_icon("unchecked_disabled") != null | (待填) | | | +| 1476 | 515 | "V3 PopupMenu 词条存在(弹层预盖)" | pm.has_theme_stylebox("panel") and pm.has_theme_stylebox("hover") and pm.has_theme_font_size("font_size") | (待填) | | | +| 1477 | 519 | "V3 OptionButton 词条存在(预盖)" | ob.has_theme_stylebox("normal") and ob.has_theme_stylebox("hover") and ob.has_theme_stylebox("pressed") and ob.has_theme_color("font_color") | (待填) | | | +| 1478 | 533 | "V4 退役实现零残留(%s)%s" ↩ % [path, "" if bad == "" else "违例:" + bad] | bad == "" | (待填) | | | +| 1479 | 536 | "V6 主题 default_font 非空(挂主题顶掉 custom_font 也不丢中文字形)" | th.default_font != null | (待填) | | | +| 1480 | 539 | "V6 Label 实取字体非空(中文字形防御实证)" | lbl.get_theme_font("font") != null | (待填) | | | -## S190 _smoke_button_five_states(行 552–624 · 23 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S196 _smoke_button_five_states(行 552–624 · 23 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1442 | 552 | "V1 普通钮 %s 样式盒非空" ↩ % st | b.has_theme_stylebox_override(st) | 普通钮五态样式盒逐态非空(循环 5 态)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1443 | 558 | "V1 普通钮 normal/hover/disabled 均为石纹九宫格贴图样式盒" | n_sb is StyleBoxTexture and h_sb is StyleBoxTexture and d_sb is StyleBoxTexture | 普通钮三态为石纹九宫格贴图样式盒。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1444 | 562 | "V1 普通钮 hover=modulate 提亮(乙案,异于常态 WHITE)" | h_mod != Color.WHITE and h_mod.r > 1.0 | 普通钮悬停=提亮(乙案,异于常态白)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1445 | 566 | "V1 普通钮 disabled=本族底 α0.45 幽灵化(甲案)" | is_equal_approx(d_mod.a, 0.45) and d_mod.r == 1.0 and d_mod.g == 1.0 | 普通钮禁用=本族底 α0.45 幽灵化(甲案)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1446 | 569 | "V1 普通钮 disabled≠pressed(旧错配已修正)" | _sb_content_differs(d_sb, p_sb) | 普通钮禁用≠按压(旧错配已修正)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1447 | 571 | "V1 普通钮 focus 样式盒为扁平环(StyleBoxFlat)" | f_sb is StyleBoxFlat | 普通钮焦点样式盒为扁平环。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1448 | 573 | "V1 焦点环色=INK(与 font_focus_color 同源)" | fsb.border_color == uikit_s.INK | 焦点环色=INK(与焦点字色同源)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1449 | 574 | "V1 焦点环宽=2px" | is_equal_approx(fsb.border_width_left, 2.0) and is_equal_approx(fsb.border_width_right, 2.0) and is_equal_approx(fsb.border_width_top, 2.0) and is_equal_approx(fsb.border_width_bottom, 2.0) | 焦点环宽=2px(四边)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1450 | 577 | "V1 焦点环内缩=1px" | is_equal_approx(fsb.expand_margin_left, -1.0) and is_equal_approx(fsb.expand_margin_top, -1.0) and is_equal_approx(fsb.expand_margin_right, -1.0) and is_equal_approx(fsb.expand_margin_bottom, -1.0) | 焦点环内缩=1px(四边)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1451 | 580 | "V1 焦点环无底(叠加态不遮钮底)" | not fsb.draw_center and fsb.bg_color.a < 0.001 | 焦点环无底(叠加态不遮钮底)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1452 | 585 | "V1 主钮 %s 样式盒非空" ↩ % st | pb.has_theme_stylebox_override(st) | 主钮五态样式盒逐态非空。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1453 | 589 | "V1 主钮 pressed/disabled=本族平涂(非石纹)" | ppn is StyleBoxFlat and pdd is StyleBoxFlat | 主钮按压/禁用=本族平涂(非石纹)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1454 | 591 | "V1 主钮 pressed=烛光黄深档(现行值不动)" | (ppn as StyleBoxFlat).bg_color == Color8(214, 172, 88) | 主钮按压=烛光黄深档(现行值)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1455 | 593 | "V1 主钮 disabled=本族底 α0.45 幽灵化" | is_equal_approx((pdd as StyleBoxFlat).bg_color.a, 0.45) | 主钮禁用=本族底 α0.45 幽灵化。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1456 | 595 | "V1 主钮 disabled≠pressed" | _sb_content_differs(ppn, pdd) | 主钮禁用≠按压。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1457 | 596 | "V1 主钮 hover=提亮档(承现行不动)" | (phv as StyleBoxFlat).bg_color == uikit_s.CANDLE.lightened(0.06) | 主钮悬停=提亮档(承现行)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1458 | 602 | "V1 危险钮 %s 样式盒非空" ↩ % st | db.has_theme_stylebox_override(st) | 危险钮五态样式盒逐态非空。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1459 | 605 | "V1 危险钮 pressed/disabled=本族平涂(非石纹,旧错配已修正)" | dpr is StyleBoxFlat and dds is StyleBoxFlat | 危险钮按压/禁用=本族平涂。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1460 | 607 | "V1 危险钮 pressed=深红深档(暗于常态)" | (dpr as StyleBoxFlat).bg_color.r < 180.0 / 255.0 | 危险钮按压=深红深档(暗于常态)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1461 | 609 | "V1 危险钮 disabled=本族底 α0.45 幽灵化" | is_equal_approx((dds as StyleBoxFlat).bg_color.a, 0.45) | 危险钮禁用=α0.45 幽灵化。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1462 | 611 | "V1 危险钮 disabled≠pressed" | _sb_content_differs(dpr, dds) | 危险钮禁用≠按压。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1463 | 616 | "V2 红线维持 Button 类型词条零接触" | not th.get_type_list().has("Button") | 主题红线维持:Button 类型词条零接触。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1464 | 623 | "V2 红线维持 focus 族词条零定义%s" ↩ % ("" if focus_hits == "" else "(违例:" + focus_hits + ")") | focus_hits == "" | 主题红线维持:focus 族词条零定义。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 1481 | 552 | "V1 普通钮 %s 样式盒非空" ↩ % st | b.has_theme_stylebox_override(st) | (待填) | | | +| 1482 | 558 | "V1 普通钮 normal/hover/disabled 均为石纹九宫格贴图样式盒" | n_sb is StyleBoxTexture and h_sb is StyleBoxTexture and d_sb is StyleBoxTexture | (待填) | | | +| 1483 | 562 | "V1 普通钮 hover=modulate 提亮(乙案,异于常态 WHITE)" | h_mod != Color.WHITE and h_mod.r > 1.0 | (待填) | | | +| 1484 | 566 | "V1 普通钮 disabled=本族底 α0.45 幽灵化(甲案)" | is_equal_approx(d_mod.a, 0.45) and d_mod.r == 1.0 and d_mod.g == 1.0 | (待填) | | | +| 1485 | 569 | "V1 普通钮 disabled≠pressed(旧错配已修正)" | _sb_content_differs(d_sb, p_sb) | (待填) | | | +| 1486 | 571 | "V1 普通钮 focus 样式盒为扁平环(StyleBoxFlat)" | f_sb is StyleBoxFlat | (待填) | | | +| 1487 | 573 | "V1 焦点环色=INK(与 font_focus_color 同源)" | fsb.border_color == uikit_s.INK | (待填) | | | +| 1488 | 574 | "V1 焦点环宽=2px" | is_equal_approx(fsb.border_width_left, 2.0) and is_equal_approx(fsb.border_width_right, 2.0) and is_equal_approx(fsb.border_width_top, 2.0) and is_equal_approx(fsb.border_width_bottom, 2.0) | (待填) | | | +| 1489 | 577 | "V1 焦点环内缩=1px" | is_equal_approx(fsb.expand_margin_left, -1.0) and is_equal_approx(fsb.expand_margin_top, -1.0) and is_equal_approx(fsb.expand_margin_right, -1.0) and is_equal_approx(fsb.expand_margin_bottom, -1.0) | (待填) | | | +| 1490 | 580 | "V1 焦点环无底(叠加态不遮钮底)" | not fsb.draw_center and fsb.bg_color.a < 0.001 | (待填) | | | +| 1491 | 585 | "V1 主钮 %s 样式盒非空" ↩ % st | pb.has_theme_stylebox_override(st) | (待填) | | | +| 1492 | 589 | "V1 主钮 pressed/disabled=本族平涂(非石纹)" | ppn is StyleBoxFlat and pdd is StyleBoxFlat | (待填) | | | +| 1493 | 591 | "V1 主钮 pressed=烛光黄深档(现行值不动)" | (ppn as StyleBoxFlat).bg_color == Color8(214, 172, 88) | (待填) | | | +| 1494 | 593 | "V1 主钮 disabled=本族底 α0.45 幽灵化" | is_equal_approx((pdd as StyleBoxFlat).bg_color.a, 0.45) | (待填) | | | +| 1495 | 595 | "V1 主钮 disabled≠pressed" | _sb_content_differs(ppn, pdd) | (待填) | | | +| 1496 | 596 | "V1 主钮 hover=提亮档(承现行不动)" | (phv as StyleBoxFlat).bg_color == uikit_s.CANDLE.lightened(0.06) | (待填) | | | +| 1497 | 602 | "V1 危险钮 %s 样式盒非空" ↩ % st | db.has_theme_stylebox_override(st) | (待填) | | | +| 1498 | 605 | "V1 危险钮 pressed/disabled=本族平涂(非石纹,旧错配已修正)" | dpr is StyleBoxFlat and dds is StyleBoxFlat | (待填) | | | +| 1499 | 607 | "V1 危险钮 pressed=深红深档(暗于常态)" | (dpr as StyleBoxFlat).bg_color.r < 180.0 / 255.0 | (待填) | | | +| 1500 | 609 | "V1 危险钮 disabled=本族底 α0.45 幽灵化" | is_equal_approx((dds as StyleBoxFlat).bg_color.a, 0.45) | (待填) | | | +| 1501 | 611 | "V1 危险钮 disabled≠pressed" | _sb_content_differs(dpr, dds) | (待填) | | | +| 1502 | 616 | "V2 红线维持 Button 类型词条零接触" | not th.get_type_list().has("Button") | (待填) | | | +| 1503 | 623 | "V2 红线维持 focus 族词条零定义%s" ↩ % ("" if focus_hits == "" else "(违例:" + focus_hits + ")") | focus_hits == "" | (待填) | | | -## S191 _smoke_texture_filter(行 631–673 · 9 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 1465 | 631 | "V3 工程设置 default_texture_filter=0(Nearest,翻转面=立绘/瓦片/九宫格 crisp)" | v != null and int(v) == 0 | 工程默认纹理过滤=Nearest(像素 crisp)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1466 | 635 | "V3 白名单在位:暗角 vignette 显式 LINEAR" | vg.texture_filter == CanvasItem.TEXTURE_FILTER_LINEAR | 白名单:暗角显式 LINEAR。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1467 | 639 | "V3 白名单在位:光柱 beams 显式 LINEAR" | bm.texture_filter == CanvasItem.TEXTURE_FILTER_LINEAR | 白名单:光柱显式 LINEAR。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1468 | 657 | "T4 光柱带心峰值 α≈0.05(8bit 截断量化下限 12/255,实测 %.5f)" ↩ % max_a | max_a >= 12.0 / 255.0 and max_a <= 0.05 | 光柱带心峰值 α≈0.05(量化下限内)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1469 | 659 | "T4 光柱带内连续 alpha 过渡像素 %d 个(旧零过渡硬边已修)" ↩ % mid_a | mid_a > 100 | 光柱带内连续过渡像素>100(旧零过渡硬边已修)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1470 | 660 | "T4 光柱纵向衰减生效(上半亮度 %.0f > 下半 %.0f 的 2 倍——上亮下暗)" ↩ % [top_sum, bot_sum] | top_sum > bot_sum * 2.0 | 光柱纵向衰减生效(上亮下暗 2 倍)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1471 | 664 | "V3 白名单在位:地图渐变 gradient_base 显式 LINEAR" | gb.texture_filter == CanvasItem.TEXTURE_FILTER_LINEAR | 白名单:地图渐变显式 LINEAR。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1472 | 669 | "V3 脚影显式 LINEAR 不丢(board_world.gd)" | bw_src.contains("shadow.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR") | 脚影显式 LINEAR 不丢(源码锚定)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1473 | 672 | "V3 状态图标显式 LINEAR 不丢(status_slot_row.gd)" | ss_src.contains("icon.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR") | 状态图标显式 LINEAR 不丢(源码锚定)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +## S197 _smoke_texture_filter(行 631–673 · 9 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 1504 | 631 | "V3 工程设置 default_texture_filter=0(Nearest,翻转面=立绘/瓦片/九宫格 crisp)" | v != null and int(v) == 0 | (待填) | | | +| 1505 | 635 | "V3 白名单在位:暗角 vignette 显式 LINEAR" | vg.texture_filter == CanvasItem.TEXTURE_FILTER_LINEAR | (待填) | | | +| 1506 | 639 | "V3 白名单在位:光柱 beams 显式 LINEAR" | bm.texture_filter == CanvasItem.TEXTURE_FILTER_LINEAR | (待填) | | | +| 1507 | 657 | "T4 光柱带心峰值 α≈0.05(8bit 截断量化下限 12/255,实测 %.5f)" ↩ % max_a | max_a >= 12.0 / 255.0 and max_a <= 0.05 | (待填) | | | +| 1508 | 659 | "T4 光柱带内连续 alpha 过渡像素 %d 个(旧零过渡硬边已修)" ↩ % mid_a | mid_a > 100 | (待填) | | | +| 1509 | 660 | "T4 光柱纵向衰减生效(上半亮度 %.0f > 下半 %.0f 的 2 倍——上亮下暗)" ↩ % [top_sum, bot_sum] | top_sum > bot_sum * 2.0 | (待填) | | | +| 1510 | 664 | "V3 白名单在位:地图渐变 gradient_base 显式 LINEAR" | gb.texture_filter == CanvasItem.TEXTURE_FILTER_LINEAR | (待填) | | | +| 1511 | 669 | "V3 脚影显式 LINEAR 不丢(board_world.gd)" | bw_src.contains("shadow.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR") | (待填) | | | +| 1512 | 672 | "V3 状态图标显式 LINEAR 不丢(status_slot_row.gd)" | ss_src.contains("icon.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR") | (待填) | | | -## S192 _smoke_contrast_inks(行 682–698 · 9 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S198 _smoke_contrast_inks(行 682–698 · 9 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1474 | 682 | "V4 顺位徽标/重甲徽标 GOLD_BRIGHT 对舞台底 %.2f:1 ≥4.5:1" ↩ % r_order | r_order >= 4.5 | 顺位徽标亮墨对舞台底对比≥4.5:1。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1475 | 684 | "V4 魔法护甲徽标 HERO_BLUE_BRIGHT 对舞台底 %.2f:1 ≥4.5:1" ↩ % r_ward | r_ward >= 4.5 | 魔法护甲徽标亮墨对比≥4.5:1。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1476 | 686 | "V4 RAP 反馈行 STONE_DEEP 对数据板 %.2f:1 ≥4.5:1" ↩ % r_rap | r_rap >= 4.5 | RAP 反馈行深墨对数据板对比≥4.5:1。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1477 | 689 | "V4 顺位徽标①②③ 墨=GOLD_BRIGHT(13px 行原位)" | sb_src.contains("HORIZONTAL_ALIGNMENT_LEFT, -1, 13, UiKit.GOLD_BRIGHT") | 顺位徽标墨=亮金(源码锚定原位)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1478 | 691 | "V4 魔法护甲徽标墨=HERO_BLUE_BRIGHT" | sb_src.contains("col = UiKit.HERO_BLUE_BRIGHT") | 魔法护甲徽标墨=亮蓝。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1479 | 692 | "V4 重甲徽标墨=GOLD_BRIGHT" | sb_src.contains("col = UiKit.GOLD_BRIGHT") | 重甲徽标墨=亮金。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1480 | 693 | "V4 GOLD_DEEP 语义岗不动(standee:199 意图动画判据)" | sb_src.contains("if _intent_col == UiKit.GOLD_DEEP:") | 深金语义岗不动(意图动画判据)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1481 | 696 | "V4 RAP 反馈行墨=STONE_DEEP(原位行锚定)" | ap_src.contains('UiKit.label("",UiKit.FS_CAPTION, false, UiKit.STONE_DEEP)') | RAP 反馈行墨=深石墨(原位行锚定)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1482 | 698 | "V4 RAP 反馈行旧墨 STONE_FAINT 清零(含注记面)" | not ap_src.contains("STONE_FAINT") | RAP 反馈行旧墨零残留(含注记面)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 1513 | 682 | "V4 顺位徽标/重甲徽标 GOLD_BRIGHT 对舞台底 %.2f:1 ≥4.5:1" ↩ % r_order | r_order >= 4.5 | (待填) | | | +| 1514 | 684 | "V4 魔法护甲徽标 HERO_BLUE_BRIGHT 对舞台底 %.2f:1 ≥4.5:1" ↩ % r_ward | r_ward >= 4.5 | (待填) | | | +| 1515 | 686 | "V4 RAP 反馈行 STONE_DEEP 对数据板 %.2f:1 ≥4.5:1" ↩ % r_rap | r_rap >= 4.5 | (待填) | | | +| 1516 | 689 | "V4 顺位徽标①②③ 墨=GOLD_BRIGHT(13px 行原位)" | sb_src.contains("HORIZONTAL_ALIGNMENT_LEFT, -1, 13, UiKit.GOLD_BRIGHT") | (待填) | | | +| 1517 | 691 | "V4 魔法护甲徽标墨=HERO_BLUE_BRIGHT" | sb_src.contains("col = UiKit.HERO_BLUE_BRIGHT") | (待填) | | | +| 1518 | 692 | "V4 重甲徽标墨=GOLD_BRIGHT" | sb_src.contains("col = UiKit.GOLD_BRIGHT") | (待填) | | | +| 1519 | 693 | "V4 意图蓄力档判据=分档布尔(standee_body:179 CHARGE_SKILL_IDS 判定;旧 GOLD_DEEP 恒不可达判据清零)〔wd2〕" | sb_src.contains("_intent_charge = pid in CHARGE_SKILL_IDS") and not sb_src.contains("if _intent_col == UiKit.GOLD_DEEP:") | (待填) | | | +| 1520 | 696 | "V4 RAP 反馈行墨=STONE_DEEP(原位行锚定)" | ap_src.contains('UiKit.label("",UiKit.FS_CAPTION, false, UiKit.STONE_DEEP)') | (待填) | | | +| 1521 | 698 | "V4 RAP 反馈行旧墨 STONE_FAINT 清零(含注记面)" | not ap_src.contains("STONE_FAINT") | (待填) | | | -## S193 _smoke_element_details(行 707–726 · 8 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S199 _smoke_element_details(行 707–726 · 8 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1483 | 707 | "T5② BCard 图标槽白图标对比 %.2f:1 ≥3:1(硬约束)" ↩ % r_white | r_white >= 3.0 | BCard 图标槽白图标对比≥3:1(图形硬约束)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1484 | 710 | "T5② 明度差降档生效(槽对卡面 %.2f:1 < 旧 %.2f:1)" ↩ % [r_new, r_old] | r_new < r_old | 图标槽明度差降档生效(新槽<旧槽对比)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1485 | 712 | "T5② BCard 图标槽落柔和档(SLOT_BACKDROP_SOFT)" | uk_src.contains("UiKit.panel_flat(SLOT_BACKDROP_SOFT, PANEL_BORDER, 0, 1)") | 图标槽落柔和档(源码锚定)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1486 | 715 | "T5② B 区状态图标槽仍用深档(SLOT_BACKDROP,未误伤)" | ss_src.contains("UiKit.SLOT_BACKDROP") | B 区状态图标槽仍用深档(未误伤)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1487 | 719 | "T5③ 商店卡片区垂直居中(ALIGNMENT_CENTER)" | shop_src.contains("_cards_box.alignment = BoxContainer.ALIGNMENT_CENTER") | 商店卡片区垂直居中(源码锚定)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1488 | 723 | "T5③ 营地三 holder 统一 min 高 336(实测 %d 处)" ↩ % camp_min | camp_min == 3 | 营地三 holder 统一最小高 336。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1489 | 725 | "T5③ 营地四栏面板纵向 FILL 等高(实测 %d 处)" ↩ % camp_fill | camp_fill == 4 | 营地四栏面板纵向等高填充。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1490 | 726 | "T5③ 三处锁文垂直居中在位" | camp_src.count("SIZE_SHRINK_CENTER") >= 3 | 三处锁文垂直居中在位。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 1522 | 707 | "T5② BCard 图标槽白图标对比 %.2f:1 ≥3:1(硬约束)" ↩ % r_white | r_white >= 3.0 | (待填) | | | +| 1523 | 710 | "T5② 明度差降档生效(槽对卡面 %.2f:1 < 旧 %.2f:1)" ↩ % [r_new, r_old] | r_new < r_old | (待填) | | | +| 1524 | 712 | "T5② BCard 图标槽落柔和档(SLOT_BACKDROP_SOFT)" | uk_src.contains("UiKit.panel_flat(SLOT_BACKDROP_SOFT, PANEL_BORDER, 0, 1)") | (待填) | | | +| 1525 | 715 | "T5② B 区状态图标槽仍用深档(SLOT_BACKDROP,未误伤)" | ss_src.contains("UiKit.SLOT_BACKDROP") | (待填) | | | +| 1526 | 719 | "T5③ 商店卡片区垂直居中(ALIGNMENT_CENTER)" | shop_src.contains("_cards_box.alignment = BoxContainer.ALIGNMENT_CENTER") | (待填) | | | +| 1527 | 723 | "T5③ 营地三 holder 统一 min 高 336(实测 %d 处)" ↩ % camp_min | camp_min == 3 | (待填) | | | +| 1528 | 725 | "T5③ 营地四栏面板纵向 FILL 等高(实测 %d 处)" ↩ % camp_fill | camp_fill == 4 | (待填) | | | +| 1529 | 726 | "T5③ 三处锁文垂直居中在位" | camp_src.count("SIZE_SHRINK_CENTER") >= 3 | (待填) | | | -## S194 _smoke_diag_button_retired(行 733–746 · 7 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S200 _smoke_diag_button_retired(行 733–746 · 7 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1491 | 733 | "V6 主区无 build_diag_button 调用残留(旧实现同批清理)" | not main_src.contains("build_diag_button") | 主区无旧诊断钮工厂调用残留。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1492 | 735 | "V6 转储入口连接随迁(组合根接浮窗 diagnose_requested 信号)" | main_src.contains("debug_overlay.diagnose_requested.connect(_on_diagnose_pressed)") | 转储入口连接随迁(组合根接浮窗信号)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1493 | 737 | "V6 转储编排承接函数在位(dump_diagnosis 卡死取证保留)" | main_src.contains("func _on_diagnose_pressed()") | 转储编排承接函数在位(卡死取证保留)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1494 | 740 | "V6 build_diag_button 工厂已退役(ui_assembler 零残留)" | not asm_src.contains("build_diag_button") | 旧诊断钮工厂已退役(装配器零残留)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1495 | 743 | "V6 调试浮窗 diagnose_requested 信号在位" | dbg_src.contains("signal diagnose_requested") | 调试浮窗转储信号在位。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1496 | 744 | "V6 浮窗转储钮外发方法在位(纯视图边界)" | dbg_src.contains("_emit_diagnose_requested") | 浮窗转储钮外发方法在位(纯视图边界)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1497 | 745 | "V6 浮窗诊断转储钮在位(玩家画面角落钮清零后唯一入口)" | dbg_src.contains('UiKit.button("转储")') | 浮窗诊断转储钮在位(唯一入口)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 1530 | 733 | "V6 主区无 build_diag_button 调用残留(旧实现同批清理)" | not main_src.contains("build_diag_button") | (待填) | | | +| 1531 | 735 | "V6 转储入口连接随迁(组合根接浮窗 diagnose_requested 信号)" | main_src.contains("debug_overlay.diagnose_requested.connect(_on_diagnose_pressed)") | (待填) | | | +| 1532 | 737 | "V6 转储编排承接函数在位(dump_diagnosis 卡死取证保留)" | main_src.contains("func _on_diagnose_pressed()") | (待填) | | | +| 1533 | 740 | "V6 build_diag_button 工厂已退役(ui_assembler 零残留)" | not asm_src.contains("build_diag_button") | (待填) | | | +| 1534 | 743 | "V6 调试浮窗 diagnose_requested 信号在位" | dbg_src.contains("signal diagnose_requested") | (待填) | | | +| 1535 | 744 | "V6 浮窗转储钮外发方法在位(纯视图边界)" | dbg_src.contains("_emit_diagnose_requested") | (待填) | | | +| 1536 | 745 | "V6 浮窗诊断转储钮在位(玩家画面角落钮清零后唯一入口)" | dbg_src.contains('UiKit.button("转储")') | (待填) | | | -## S195 _status_panel_projection(行 762–792 · 10 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S201 _status_panel_projection(行 762–792 · 10 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1498 | 762 | "状态面板开层可见(open_status 直开——P4 甲零动效路径)" | po.visible | 状态面板开层可见。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1499 | 763 | "余额徽投影 = 快照 balance 读数(sv4 回归锚)〔sv4〕" | String(po._balance_badge.value_label.text) == "钥石 77" | (待填) | | | -| 1500 | 765 | "属性行全值投影 = 契约4 钉值格式逐字(生命/上限/攻击/AC/GP 五现值)〔sv1〕" | String(po._attrs_label.text) == "生命 13/29 · 攻击 7 · 护甲 AC 11 · GP 41" | (待填) | | | -| 1501 | 767 | "秘宝行投影 = 受理序 join+已用件「(已用)」尾缀〔sv2〕" | String(po._curios_label.text) == "青苔密卷 · 锈蚀怀表(已用)" | (待填) | | | -| 1502 | 769 | "专长行投影 = 行串 join〔sv3〕" | String(po._feats_label.text) == "疼痛记忆 ×2 · 双管镐 ×1" | (待填) | | | -| 1503 | 776 | "面板子树零「道具工坊/天赋树」文案(跨关概览退役零残留)〔sv5〕" | stale == "" | (待填) | | | -| 1504 | 777 | "旧段成员退役(_workshop_holder/_tree_label 零残留——契约2 数据/成员层面清零)〔sv5〕" | po.get("_workshop_holder") == null and po.get("_tree_label") == null | (待填) | | | -| 1505 | 784 | "空秘宝态句=「未携带秘宝」〔sv2〕" | String(po2._curios_label.text) == "未携带秘宝" | (待填) | | | -| 1506 | 785 | "空专长态句=「未获专长」〔sv3〕" | String(po2._feats_label.text) == "未获专长" | (待填) | | | -| 1507 | 791 | "提示行=「查看本局冒险进度(只读)」(新定位换代)〔sv8〕" | hint_txt == "查看本局冒险进度(只读)" | (待填) | | | +| 1537 | 762 | "状态面板开层可见(open_status 直开——P4 甲零动效路径)" | po.visible | (待填) | | | +| 1538 | 763 | "余额徽投影 = 快照 balance 读数(sv4 回归锚)〔sv4〕" | String(po._balance_badge.value_label.text) == "钥石 77" | (待填) | | | +| 1539 | 765 | "属性行全值投影 = 契约4 钉值格式逐字(生命/上限/攻击/AC/GP 五现值)〔sv1〕" | String(po._attrs_label.text) == "生命 13/29 · 攻击 7 · 护甲 AC 11 · GP 41" | (待填) | | | +| 1540 | 767 | "秘宝行投影 = 受理序 join+已用件「(已用)」尾缀〔sv2〕" | String(po._curios_label.text) == "青苔密卷 · 锈蚀怀表(已用)" | (待填) | | | +| 1541 | 769 | "专长行投影 = 行串 join〔sv3〕" | String(po._feats_label.text) == "疼痛记忆 ×2 · 双管镐 ×1" | (待填) | | | +| 1542 | 776 | "面板子树零「道具工坊/天赋树」文案(跨关概览退役零残留)〔sv5〕" | stale == "" | (待填) | | | +| 1543 | 777 | "旧段成员退役(_workshop_holder/_tree_label 零残留——契约2 数据/成员层面清零)〔sv5〕" | po.get("_workshop_holder") == null and po.get("_tree_label") == null | (待填) | | | +| 1544 | 784 | "空秘宝态句=「未携带秘宝」〔sv2〕" | String(po2._curios_label.text) == "未携带秘宝" | (待填) | | | +| 1545 | 785 | "空专长态句=「未获专长」〔sv3〕" | String(po2._feats_label.text) == "未获专长" | (待填) | | | +| 1546 | 791 | "提示行=「查看本局冒险进度(只读)」(新定位换代)〔sv8〕" | hint_txt == "查看本局冒险进度(只读)" | (待填) | | | -## S196 _career_overlay_projection(行 837–872 · 8 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S202 _career_overlay_projection(行 837–872 · 8 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1508 | 837 | "生涯页开层可见(open_career 直开——零动效路径)" | co.visible | 生涯页开层可见。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1509 | 838 | "总局数投影 = adventure_total() 读数(%s)" ↩ % co._total_label.text | String(co._total_label.text) == "2 局" | 总局数投影=访问器读数。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1510 | 840 | "通关局数投影 = adventure_cleared() 读数(%s)" ↩ % co._cleared_label.text | String(co._cleared_label.text) == "1 局(三关全通)" | 通关局数投影=访问器读数。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1511 | 845 | "各关通关次数投影 = clear_count() 逐关读数(%s)" ↩ % co._clears_label.text | String(co._clears_label.text) == " · ".join(expect_clears) | 各关通关次数投影=逐关读数。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1512 | 847 | "Boss 首杀/总杀投影 = 首杀标记计数 + boss_kill_count()(%s / %s)" ↩ % [co._boss_first_label.text, co._boss_kills_label.text] | String(co._boss_first_label.text) == "3/3 关" and String(co._boss_kills_label.text) == "4 次" | Boss 首杀/总杀投影=标记计数+访问器。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1513 | 849 | "钥石余额徽投影 = keystone_balance() 读数(%s)" ↩ % co._keystone_badge.value_label.text | String(co._keystone_badge.value_label.text) == "钥石 %d" % mp.keystone_balance() | 钥石余额徽投影=访问器读数。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1514 | 858 | "v5 现役档升 v6 载入成功(计数域缺省补零)" | v5.load_from_disk() and v5.adventure_total() == 0 and v5.adventure_cleared() == 0 | v5 现役档升版载入成功(计数域缺省补零)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | -| 1515 | 869 | "旧档载入后生涯页正常显示(v5 升 v6:总局数/通关局数显示 0,既有钥石余额原样呈现)" | co_v5.visible and String(co_v5._total_label.text) == "0 局" \\ and String(co_v5._cleared_label.text) == "0 局(三关全通)" \\ and String(co_v5._keystone_badge.value_label.text) == "钥石 3" | 旧档载入后生涯页正常显示(0 值呈现+余额原样)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 1547 | 837 | "生涯页开层可见(open_career 直开——零动效路径)" | co.visible | (待填) | | | +| 1548 | 838 | "总局数投影 = adventure_total() 读数(%s)" ↩ % co._total_label.text | String(co._total_label.text) == "2 局" | (待填) | | | +| 1549 | 840 | "通关局数投影 = adventure_cleared() 读数(%s)" ↩ % co._cleared_label.text | String(co._cleared_label.text) == "1 局(三关全通)" | (待填) | | | +| 1550 | 845 | "各关通关次数投影 = clear_count() 逐关读数(%s)" ↩ % co._clears_label.text | String(co._clears_label.text) == " · ".join(expect_clears) | (待填) | | | +| 1551 | 847 | "Boss 首杀/总杀投影 = 首杀标记计数 + boss_kill_count()(%s / %s)" ↩ % [co._boss_first_label.text, co._boss_kills_label.text] | String(co._boss_first_label.text) == "3/3 关" and String(co._boss_kills_label.text) == "4 次" | (待填) | | | +| 1552 | 849 | "钥石余额徽投影 = keystone_balance() 读数(%s)" ↩ % co._keystone_badge.value_label.text | String(co._keystone_badge.value_label.text) == "钥石 %d" % mp.keystone_balance() | (待填) | | | +| 1553 | 858 | "v5 现役档升 v6 载入成功(计数域缺省补零)" | v5.load_from_disk() and v5.adventure_total() == 0 and v5.adventure_cleared() == 0 | (待填) | | | +| 1554 | 869 | "旧档载入后生涯页正常显示(v5 升 v6:总局数/通关局数显示 0,既有钥石余额原样呈现)" | co_v5.visible and String(co_v5._total_label.text) == "0 局" \\ and String(co_v5._cleared_label.text) == "0 局(三关全通)" \\ and String(co_v5._keystone_badge.value_label.text) == "钥石 3" | (待填) | | | -## S197 _test_grouping_closed(行 61–79 · 7 条 · 源 game/tests/unit/presentation/type_glow_test.gd · 去向:待分类回填) +## S203 _test_grouping_closed(行 61–79 · 7 条 · 源 game/tests/unit/presentation/type_glow_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1516 | 61 | "九型封闭:%s → 组%d(期望组%d)" ↩ % [t, got, int(TYPE_EXPECT[t])] | got == int(TYPE_EXPECT[t]) | (待填) | | | -| 1517 | 65 | "清单外值「%s」→ GLOW_NONE(禁静默空光)" ↩ % bad | UiKitScript.glow_group_for_cell_type(bad) == UiKitScript.GLOW_NONE | (待填) | | | -| 1518 | 70 | "两带并存锚定:卡顶带 blast→TG_MAGIC(E5 回滚既存行为未动)" | band_blast == UiKitScript.TG_MAGIC | (待填) | | | -| 1519 | 71 | "两带并存锚定:格面微光 blast→群伤组(与卡顶带刻意不同)" | UiKitScript.glow_group_for_cell_type("blast") == UiKitScript.GLOW_AOE | (待填) | | | -| 1520 | 74 | "glow_color_for_group 群伤组 = TG_GLOW_AOE" | UiKitScript.glow_color_for_group(UiKitScript.GLOW_AOE) == UiKitScript.TG_GLOW_AOE | (待填) | | | -| 1521 | 76 | "glow_color_for_group 无组(GLOW_NONE)→ 全透明" | UiKitScript.glow_color_for_group(UiKitScript.GLOW_NONE) == Color(0, 0, 0, 0) | (待填) | | | -| 1522 | 78 | "glow_color_for_group 越界组号 → 全透明" | UiKitScript.glow_color_for_group(99) == Color(0, 0, 0, 0) | (待填) | | | +| 1555 | 61 | "九型封闭:%s → 组%d(期望组%d)" ↩ % [t, got, int(TYPE_EXPECT[t])] | got == int(TYPE_EXPECT[t]) | (待填) | | | +| 1556 | 65 | "清单外值「%s」→ GLOW_NONE(禁静默空光)" ↩ % bad | UiKitScript.glow_group_for_cell_type(bad) == UiKitScript.GLOW_NONE | (待填) | | | +| 1557 | 70 | "两带并存锚定:卡顶带 blast→TG_MAGIC(E5 回滚既存行为未动)" | band_blast == UiKitScript.TG_MAGIC | (待填) | | | +| 1558 | 71 | "两带并存锚定:格面微光 blast→群伤组(与卡顶带刻意不同)" | UiKitScript.glow_group_for_cell_type("blast") == UiKitScript.GLOW_AOE | (待填) | | | +| 1559 | 74 | "glow_color_for_group 群伤组 = TG_GLOW_AOE" | UiKitScript.glow_color_for_group(UiKitScript.GLOW_AOE) == UiKitScript.TG_GLOW_AOE | (待填) | | | +| 1560 | 76 | "glow_color_for_group 无组(GLOW_NONE)→ 全透明" | UiKitScript.glow_color_for_group(UiKitScript.GLOW_NONE) == Color(0, 0, 0, 0) | (待填) | | | +| 1561 | 78 | "glow_color_for_group 越界组号 → 全透明" | UiKitScript.glow_color_for_group(99) == Color(0, 0, 0, 0) | (待填) | | | -## S198 _test_frozen_hue_face(行 88–102 · 5 条 · 源 game/tests/unit/presentation/type_glow_test.gd · 去向:待分类回填) +## S204 _test_frozen_hue_face(行 88–102 · 5 条 · 源 game/tests/unit/presentation/type_glow_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1523 | 88 | "实现值锚定:%s = #%02X%02X%02X(色值单点 ui_kit,逐位钉死)" ↩ % [CONST_NAME[g], int(m.r8), int(m.g8), int(m.b8)] | impl_const == m | (待填) | | | -| 1524 | 93 | "冻结色相保持:%s ΔH=%.3f° ≤0.2° 且 ΔS=%.4f ≤0.01(只动明度档)" ↩ % [g, dh * 360.0, ds] | dh <= 0.2 / 360.0 and ds <= 0.01 | (待填) | | | -| 1525 | 98 | "群伤组专项:H 与冻结 #FF2D55 差 %.4f°(零移动)" ↩ % (absf(aoe_m.h - aoe_f.h) * 360.0) | absf(aoe_m.h - aoe_f.h) < 0.001 | (待填) | | | -| 1526 | 100 | "群伤组专项:S 与冻结差 %.4f ≤0.01(8bit 量化噪声级)" ↩ % absf(aoe_m.s - aoe_f.s) | absf(aoe_m.s - aoe_f.s) <= 0.01 | (待填) | | | -| 1527 | 102 | "群伤组专项:V=0.65 明度档( provisional 域内)" | absf(aoe_m.v - 166.0 / 255.0) < 0.002 | (待填) | | | +| 1562 | 88 | "实现值锚定:%s = #%02X%02X%02X(色值单点 ui_kit,逐位钉死)" ↩ % [CONST_NAME[g], int(m.r8), int(m.g8), int(m.b8)] | impl_const == m | (待填) | | | +| 1563 | 93 | "冻结色相保持:%s ΔH=%.3f° ≤0.2° 且 ΔS=%.4f ≤0.01(只动明度档)" ↩ % [g, dh * 360.0, ds] | dh <= 0.2 / 360.0 and ds <= 0.01 | (待填) | | | +| 1564 | 98 | "群伤组专项:H 与冻结 #FF2D55 差 %.4f°(零移动)" ↩ % (absf(aoe_m.h - aoe_f.h) * 360.0) | absf(aoe_m.h - aoe_f.h) < 0.001 | (待填) | | | +| 1565 | 100 | "群伤组专项:S 与冻结差 %.4f ≤0.01(8bit 量化噪声级)" ↩ % absf(aoe_m.s - aoe_f.s) | absf(aoe_m.s - aoe_f.s) <= 0.01 | (待填) | | | +| 1566 | 102 | "群伤组专项:V=0.65 明度档( provisional 域内)" | absf(aoe_m.v - 166.0 / 255.0) < 0.002 | (待填) | | | -## S199 _test_render_params(行 107–113 · 3 条 · 源 game/tests/unit/presentation/type_glow_test.gd · 去向:待分类回填) +## S205 _test_render_params(行 107–113 · 3 条 · 源 game/tests/unit/presentation/type_glow_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1528 | 107 | "泛光强度 α = 170/255(v2 呈现参数冻结)" | is_equal_approx(UiKitScript.TYPE_GLOW_ALPHA, 170.0 / 255.0) | (待填) | | | -| 1529 | 109 | "带宽 = cell÷8(TYPE_GLOW_BAND_FRAC=0.125)" | is_equal_approx(UiKitScript.TYPE_GLOW_BAND_FRAC, 0.125) | (待填) | | | -| 1530 | 111 | "组号常量域连续(GLOW_NONE=0…GLOW_MAGIC=5)" | UiKitScript.GLOW_NONE == 0 and UiKitScript.GLOW_BUFF == 1 and UiKitScript.GLOW_HEAL == 2 and UiKitScript.GLOW_PIERCE == 3 and UiKitScript.GLOW_AOE == 4 and UiKitScript.GLOW_MAGIC == 5 | (待填) | | | +| 1567 | 107 | "泛光强度 α = 170/255(v2 呈现参数冻结)" | is_equal_approx(UiKitScript.TYPE_GLOW_ALPHA, 170.0 / 255.0) | (待填) | | | +| 1568 | 109 | "带宽 = cell÷8(TYPE_GLOW_BAND_FRAC=0.125)" | is_equal_approx(UiKitScript.TYPE_GLOW_BAND_FRAC, 0.125) | (待填) | | | +| 1569 | 111 | "组号常量域连续(GLOW_NONE=0…GLOW_MAGIC=5)" | UiKitScript.GLOW_NONE == 0 and UiKitScript.GLOW_BUFF == 1 and UiKitScript.GLOW_HEAL == 2 and UiKitScript.GLOW_PIERCE == 3 and UiKitScript.GLOW_AOE == 4 and UiKitScript.GLOW_MAGIC == 5 | (待填) | | | -## S200 _test_gamma_ladder(行 128–139 · 3 条 · 源 game/tests/unit/presentation/type_glow_test.gd · 去向:待分类回填) +## S206 _test_gamma_ladder(行 128–139 · 3 条 · 源 game/tests/unit/presentation/type_glow_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1531 | 128 | "阶梯单调:%s 合成亮度 %.4f < 上一档 %.4f(伽马合成空间)" ↩ % [g, lum, prev_lum] | lum < prev_lum | (待填) | | | -| 1532 | 134 | "相邻组模型 CR:%s/%s = %.3f ≥1.20(色弱互辨预判)" ↩ % [order[i], order[i + 1], cr] | cr >= 1.20 | (待填) | | | -| 1533 | 138 | "群伤/魔法线性亮度序:群伤 %.4f < 魔法 %.4f(首版 CIE L* 排反事故的守门)" ↩ % [aoe_lin, magic_lin] | aoe_lin < magic_lin | (待填) | | | +| 1570 | 128 | "阶梯单调:%s 合成亮度 %.4f < 上一档 %.4f(伽马合成空间)" ↩ % [g, lum, prev_lum] | lum < prev_lum | (待填) | | | +| 1571 | 134 | "相邻组模型 CR:%s/%s = %.3f ≥1.20(色弱互辨预判)" ↩ % [order[i], order[i + 1], cr] | cr >= 1.20 | (待填) | | | +| 1572 | 138 | "群伤/魔法线性亮度序:群伤 %.4f < 魔法 %.4f(首版 CIE L* 排反事故的守门)" ↩ % [aoe_lin, magic_lin] | aoe_lin < magic_lin | (待填) | | | -## S201 _test_probe_api_shape(行 146–153 · 5 条 · 源 game/tests/unit/presentation/type_glow_test.gd · 去向:待分类回填) +## S207 _test_probe_api_shape(行 146–153 · 5 条 · 源 game/tests/unit/presentation/type_glow_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1534 | 146 | "probe 键集 = {enabled, cells, errors}" | snap.has("enabled") and snap.has("cells") and snap.has("errors") and snap.size() == 3 | (待填) | | | -| 1535 | 148 | "probe 默认态:enabled=true(Q4 丙案生产默认开)" | bool(snap.get("enabled", false)) | (待填) | | | -| 1536 | 149 | "probe 默认态:作用集空(无板未派生)" | (snap.get("cells", {}) as Dictionary).is_empty() | (待填) | | | -| 1537 | 150 | "probe 默认态:清单外登记空" | (snap.get("errors", []) as Array).is_empty() | (待填) | | | -| 1538 | 152 | "set_type_glow_enabled(false) → probe enabled=false(无板不崩)" | not bool(view.type_glow_probe().get("enabled", true)) | (待填) | | | +| 1573 | 146 | "probe 键集 = {enabled, cells, errors}" | snap.has("enabled") and snap.has("cells") and snap.has("errors") and snap.size() == 3 | (待填) | | | +| 1574 | 148 | "probe 默认态:enabled=true(Q4 丙案生产默认开)" | bool(snap.get("enabled", false)) | (待填) | | | +| 1575 | 149 | "probe 默认态:作用集空(无板未派生)" | (snap.get("cells", {}) as Dictionary).is_empty() | (待填) | | | +| 1576 | 150 | "probe 默认态:清单外登记空" | (snap.get("errors", []) as Array).is_empty() | (待填) | | | +| 1577 | 152 | "set_type_glow_enabled(false) → probe enabled=false(无板不崩)" | not bool(view.type_glow_probe().get("enabled", true)) | (待填) | | | -## S202 _init(行 16–28 · 6 条 · 源 game/tests/unit/presentation/ui_font_asset_test.gd · 去向:待分类回填) +## S208 _init(行 16–28 · 6 条 · 源 game/tests/unit/presentation/ui_font_asset_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1539 | 16 | "随包字体守卫装载非空(res://assets/fonts/ui_font_fusion_pixel_zh_hans.ttf)" | f != null | (待填) | | | -| 1540 | 18 | "中文字形在集(攻/雷/旗/封)" | f.has_char("攻".unicode_at(0)) and f.has_char("雷".unicode_at(0)) \\ and f.has_char("旗".unicode_at(0)) and f.has_char("封".unicode_at(0)) | (待填) | | | -| 1541 | 20 | "中文字形在集(危——已开危险格字形换代前置,迭代36 u12/wa9)" | f.has_char("危".unicode_at(0)) | (待填) | | | -| 1542 | 21 | "ASCII/数字字形在集(A/0/%)" | f.has_char("A".unicode_at(0)) and f.has_char("0".unicode_at(0)) and f.has_char("%".unicode_at(0)) | (待填) | | | -| 1543 | 25 | "中文采样文本宽度 > 0(C6 headless 判据)" | w_cn > 0.0 and w_label > 0.0 | (待填) | | | -| 1544 | 28 | "字体缺失时后续断言跳过(缺位=回归,此行仅占位计数)" | false | (待填) | | | +| 1578 | 16 | "随包字体守卫装载非空(res://assets/fonts/ui_font_fusion_pixel_zh_hans.ttf)" | f != null | (待填) | | | +| 1579 | 18 | "中文字形在集(攻/雷/旗/封)" | f.has_char("攻".unicode_at(0)) and f.has_char("雷".unicode_at(0)) \\ and f.has_char("旗".unicode_at(0)) and f.has_char("封".unicode_at(0)) | (待填) | | | +| 1580 | 20 | "中文字形在集(危——已开危险格字形换代前置,迭代36 u12/wa9)" | f.has_char("危".unicode_at(0)) | (待填) | | | +| 1581 | 21 | "ASCII/数字字形在集(A/0/%)" | f.has_char("A".unicode_at(0)) and f.has_char("0".unicode_at(0)) and f.has_char("%".unicode_at(0)) | (待填) | | | +| 1582 | 25 | "中文采样文本宽度 > 0(C6 headless 判据)" | w_cn > 0.0 and w_label > 0.0 | (待填) | | | +| 1583 | 28 | "字体缺失时后续断言跳过(缺位=回归,此行仅占位计数)" | false | (待填) | | | --- -全源共 202 章、1544 条(静态调用点口径)。 +全源共 208 章、1583 条(静态调用点口径)。 diff --git a/reports/iter37-代拍板-2026-10-25.md b/reports/iter37-代拍板-2026-10-25.md deleted file mode 100644 index dc699163..00000000 --- a/reports/iter37-代拍板-2026-10-25.md +++ /dev/null @@ -1,31 +0,0 @@ -# iter37 代拍板追认单(2026-10-25) - -迭代 37 实现批(分支 `iter37`,四卡:①行为缺陷定点修 ②main 判离 ③boardview 拆件 ④runner 拆件与断点 schema 单点)批内由会话替你拍的决定,等你**追认或推翻**。授权发生在卡片审问阶段的只列「备案」;卡片没写清、会话自己拍的列「待追认」;会话拍错了的列「认错」。 - -## 一、待追认(卡片未写清,会话自拍) - -| # | 决定 | 依据与后果 | 落点 | -|---|---|---|---| -| B-1 | **计数守恒装置:三处宿主文件共 25 条转发属性/常量别名**(board_view 13 条 :82-122、adventure_runner 4 条 :60-77、level_runner 8 条 :50-64),件内状态以宿主同名属性露出,只为让 S13 基线消息里的「类变量数」逐字不变 | 用户令「只改代码不改基线」+卡片契约 1「公开面零变化」+旧基线键含计数 ⇒ 数学上只剩"持平/达标/停卡"三途,会话选了持平。**已判定为处置错误**:本应停卡呈报(实现段零裁断)。且 master `37625317` 基线键治本后(改小/持平不红),该装置的存在理由已消失 | 已立 P0 待办条目「AdventureRunner 职责真拆分」(待办清单.md:5,2026-10-25 登记) | -| B-2 | 卡② **范围扩展**:r1-1~r1-4 之外多做 `_build_ui` 八步拆分 → `main_ui_build`,冒险/浮层/调试三流程族 → `main_adventure_flow`/`main_overlay_flow`/`main_debug_flow` | 卡片只列四族,但 main.gd 不降到 999 行内就冒新红;属"只改代码"令的直接推论。逐字搬运,成员访问改 root 前缀 | commit `b0d92524` | -| B-3 | 卡④ **件接口形态**:`CurioDeck._init(db_ref, rng_ref)`/`bind_handlers`/`reset()`;`ShopSession.bind(refs, hooks)`/`open_pool()`/`leave()`、件内状态全部写成公有 var(`deck`/`taken`/`shelf`/`visit_buys`…) | 卡片接口块只给 `_init(db)` 与 `set_shop_pool` 四参沿用;实际形态由"零新造协议+逐字可核"反推。公有 var 是为让宿主转发属性与既有 Callable 注入面零成本对接——代价是件自身无封装 | commit `e5ee4d04` | -| B-4 | 卡④ **登记项只含策略标签,不含"写侧取值器"**:`BREAKPOINT_FIELDS` 值=`{"strategy": ...}`,写侧取值由 `_bp_parts()` 的键名字典承载(未登记键即 `parts[key]` 越界 fail-fast) | 卡片 §104 写的是「键→{写侧取值键路径+读侧回退策略标签}」;GDScript 里 27 个取值 callable 只能写 lambda(S6 禁捕获 self)或字符串反射,两者都比现状差。读侧两处(`resume_adventure` 与 `_start_current_level` 二次消费)已同源派生 | [game/core/adventure_runner.gd:857](file:///D:/code/g-plan2-iter37/game/core/adventure_runner.gd#L857) | -| B-5 | 卡④ `CurioDeck.reset()` **不清凭证三计数**(`ticket_pity`/`ticket_sold`/`ticket_appeared`) | 宿主原 `_reset_curio_state` 就只重置池与 kind 计数;清零会改限购/保语义=行为变更。等价性优先于"看起来更合理" | curio_deck.gd `reset()` | -| B-6 | 卡① d18 参数注解形态:卡片字面 `lumas_out: Array[Variant] = null` 在 Godot 4.7 解析报错(typed array 不可默认 null),改用 `: Variant = null` | 唯一合法注法,零行为变化 | commit `0529e169` | -| B-7 | 卡① T1 三处代拍板:`target_mine_count` 签名(照接口块)、信号契约措辞与载荷键并集(按发射位实查)、`int()` 读侧包裹写法 | 卡片白名单内预授权面 | commit `1b9736f0` | -| B-8 | 卡③ r2-1 的**瓦片查表族与墨族判据改为表驱动重构**(16 掩码查表+`_family_contrasts`/`_mean_of` 辅助式),非逐字迁移 | 独立验收审计 F-3 抓出本批漏报;动机=把 `wall_tile_for`/`wall_center_tile`/`digit_ink_mode` 从 S13 函数级超标(分支 14/返回 15/局部 12)压回阈值内,兑现「只改代码不改基线」;等价性由审计穷尽验证(掩码/档位/三态真值表零差)+golden 三图 0.00%。**违卡③ 契约 1「禁顺手改算法」字面,未违语义**——请追认;不回退 | commit `5c9c5f80`+实现报告 §3.1b | - -## 二、认错(会话处置错误,已补账) - -| # | 事实 | 已做的补救 | -|---|---|---| -| E-1 | 卡④ 遇到「卡片冻结公开面」×「S13 基线键含计数」互斥时,**没停卡呈报,自造了第三条路**(转发属性糊数)。按五段流水线实现段零裁断,这属上游缺陷该退回编排/写卡段 | 已在待办清单登记 P0 条目(含撤除依据与四件待裁缺口);本批按用户令「先按现状收口」不返工 | -| E-2 | P0 条目把同类装置只写成 12 条(adventure_runner 4+level_runner 8),**漏了 board_view 的 13 条**(卡③ 同款),覆盖面偏窄 | 已在本批随登记更正为「三宿主共 25 条」并补第⑤件缺口(待办清单.md:5,pipeline_gate 单件自查 exit=0);反哺点第 4 条改记为已执行 | - -## 三、备案(用户当场裁决,非代拍板) - -1. 「全部都改代码,不允许改基线」——批内基线零改动贯穿全批(`tools/gdstyle_baseline.txt` 未动;commit `2ed81668` 九条漂移全用代码压回)。 -2. golden 棋盘三图判失效 → 用户裁「重制三张基线」(成因 `2f3a87e9` 与本批零因果),复闸 changed=0.00%。 -3. 「行为验收只走撤销证明,禁注入造红」(P0 令)——本批撤销证明全部为"回退本批 hunk → 断言红 → 还原复绿",无变异开关、无改值造红。 -4. 本批直写根 `待办清单.md` 登记 P0 条目——用户本轮明示授权,越批性质禁写面的豁免仅限这一条登记。 -5. 「本批按现状收口,真拆分另立 P0 卡」——用户裁。 diff --git a/reports/iter37-反哺点-2026-10-25.md b/reports/iter37-反哺点-2026-10-25.md deleted file mode 100644 index 84008e2d..00000000 --- a/reports/iter37-反哺点-2026-10-25.md +++ /dev/null @@ -1,61 +0,0 @@ -# iter37 反哺点清单(2026-10-25) - -迭代 37 实现批发现、需要写进 `design/` 权威文档或规约的点。**本批不落笔**(批性质禁令),逐条写清「哪个文件哪一节 → 改成什么 → 依据」。用户在会话里裁决后归文档批(doc-design 技能)落文,落完本条即删。 - -## 1. architecture 模块归属表 —— 登记本批十一个新件 - -- **文件/节**:[design/engineering/architecture/architecture.md](file:///D:/code/g-plan2/design/engineering/architecture/architecture.md) 核心层/功能层模块归属表 -- **改成什么**:新增行——`core/curio_deck.gd`(CurioDeck:令牌多重集/抽签/cap/专长凭证三计数与判定)、`core/shop_session.gd`(ShopSession:商店双模式货架/定价/购买/刷新)、`presentation/views/tile_palette.gd`、`presentation/views/hero_sprite.gd`、`presentation/views/board_world.gd`、`presentation/board_resolvers.gd`、`presentation/main_ui_build.gd`、`presentation/main_adventure_flow.gd`、`presentation/main_overlay_flow.gd`、`presentation/main_debug_flow.gd`;并在 HudPresenter 既有行补「数据投影+文案拼装静态族」。 -- **依据**:本批 commits `b0d92524`/`5c9c5f80`/`e5ee4d04` 的 diff。与待办清单「架构与规范对齐批」条目④(模块归属对齐)**并批落文**,避免两次改同一张表。 - -## 2. 存档序列化文档 —— 断点键登记载体址变化 - -- **文件/节**:[design/gdd/systems/表现与支撑/存档序列化.md](file:///D:/code/g-plan2/design/gdd/systems/表现与支撑/存档序列化.md) 断点域(SCHEMA_VERSION+快照锚)节 -- **改成什么**:加注一句「断点 27 顶层键的登记单源=`core/adventure_runner.gd` 内 `const BREAKPOINT_FIELDS`(键→回退策略标签),写侧由 `_bp_parts()` 拼装、读侧由 `_bp_read`/`_bp_read_int` 两处派生」。若该文档逐键列了清单,逐键表本身**不改**(键名/缺省值/回退策略零变化)。 -- **依据**:卡④ r3-3 单源化;wr2 断言(`breakpoint_resume_test` P8)已把「登记键集==写侧实拼键集」钉进回归面。 - -## 3. 秘宝/商店相关 GDD —— 实现载体址注记(机制零变化) - -- **文件/节**:[design/gdd/systems/跨局循环/秘宝.md](file:///D:/code/g-plan2/design/gdd/systems/跨局循环/秘宝.md)(若记令牌池结构)与商店/经济相关篇目的实现落点段 -- **改成什么**:只把「池与判定」逻辑的载体从 `adventure_runner` 私有族改注为 `CurioDeck`、「货架/定价/购买/刷新」载体改注为 `ShopSession`;四口径契约、池消耗即上限、A 链定价等**规则文字一字不动**。 -- **依据**:卡④ 契约 1 零行为变更;等价三证之逐字迁移核对表(实现报告 §3)。 - -## 4. 待办清单 P0 条目覆盖面补记(已执行,留痕用) - -- **文件/节**:[待办清单.md](file:///D:/code/g-plan2/待办清单.md):5 「AdventureRunner 职责真拆分(优先级=P0)」条目的**实况**属性 -- **改成什么**:已在本批随登记更正(commit 待填)——标题与实况改为「三宿主共 25 条同类装置」(board_view 13 条 :82-122+adventure_runner 4 条+level_runner 8 条),读数补 level_runner 38/54、board_view 45/27 同状,缺口加第⑤件「卡③ 拆件件与 board_view 的字段归属重撤面」。**本条不需下游再动**,留作落文凭据;`python tools/pipeline_gate.py` 单件自查 exit=0。 -- **依据**:本批盘点读数(登记时只数了卡④ 两文件,漏了卡③——见代拍板单 E-2)。 - -## 5. 验证集清单(断言索引)陈旧 - -- **文件/节**:[game/tests/验证集清单.md](file:///D:/code/g-plan2/game/tests/验证集清单.md) -- **改成什么**:由 `python tools/extract_checks.py` 重生成一次(本批新增带尾缀断言=〔wd1/2/4/5/6/7/8/9/11/12/14/16〕四十五行+〔wr2〕三行,均不在册;`wc` 族系迭代 36 旧族,本批零新增)。 -- **依据**:该文件只由脚本手工重生成、不受任何闸覆盖(在册已知盲区),换代批后必陈旧。 - -## 6. gdstyle 基线陈旧键清理(禁自动刷) - -- **文件/节**:`tools/gdstyle_baseline.txt` -- **改成什么**:下次**人工**发起 `--refresh` 时一并清掉已不命中盘面的条目——`adventure_runner` 的 `_draw_curio_candidates`(85 行/26 局部/11 分支)、`resume_adventure`(108 行/13 局部/11 分支)、`_start_current_level`(53 行);`level_runner` 迁出函数名对应条目;`board_view` 已压回阈值内函数对应条目。 -- **依据**:本批基线零改动令;新键形态(文件|规则|函数名)下陈旧键无害但属噪声。骤降守卫在 refresh 路径已内建(`37625317`)。 - -## 7. 规约反哺:重构卡必须显式裁"计数守恒装置" - -- **文件/节**:[design/engineering/code-conventions.md](file:///D:/code/g-plan2/design/engineering/code-conventions.md) §1(一文件一职责)或 [design/engineering/test-conventions.md](file:///D:/code/g-plan2/design/engineering/test-conventions.md) 撤销证明段+项目技能 `.qoder/skills/req-card/SKILL.md` -- **改成什么**:加一条判据——「重构类需求卡片必须写明:**禁止为迎合机检消息文本(含计数)而保留或新增无效成员/别名**;遇机检键与卡片契约互斥,实现段一律停卡呈报,不得自造第三条路」。 -- **依据**:本批 E-1 实证——卡片契约 1「公开面零变化」×旧基线键含计数=数学上只剩"持平/达标/停卡",实现段自拍糊数即违实现段零裁断。规则修订须先在真实任务上评估(本条已评估:本批三处踩同一坑)。 - -## 9. 单点读取口径需核一次:行访问器直读是否等同「ConfigDB 单点」 - -- **文件/节**:[design/engineering/code-conventions.md](file:///D:/code/g-plan2/design/engineering/code-conventions.md) 数值单点纪律条(+AGENTS.md 同一条红线) -- **改成什么**:明确「经 `db.shop.relic_base_price` 这类**表行访问器**直读」是否满足「玩法数值只存 .tres 经 ConfigDB 单点读取」。本批 `2ed81668` 退役了 `ConfigDB.shop_relic_base_price()`/`shop_spend_discount()`/`hero_display_name()` 三个包装访问器,消费面改直读行访问器(理由=配置值写死进代码再运行时追加=双重真值);独立审计 F-4 指出该改法与卡片钉值表文本不一致,需口径裁断而非默认放行。 -- **依据**:审计 F-4;commit `2ed81668`;代拍板单 F-4 行。 - -## 8. 观感/交互类无(本批零行为变更) - -本批四卡全部为缺陷定点修+等价重构,无 GDD 数值与玩法语义变化;数值表新增两 knob(`relic_base_price`/`spend_discount`,卡① d11)与 `display_name`(d16a)已随卡① 提交入库,其**表值即事实源**,GDD 若已列同值则无需改动——文档批按现行 GDD 核一次即可,未核前不落笔。 - -## 10. 重锚批的义务清单(本批实测新凭据,须并入待办清单:118 那条) - -- **文件/节**:`待办清单.md:118` 「旧行为 MC 行为锚全数作废——重锚归数值批」条目的**实况**属性;以及 `sim-data/README.md` 的 MC 采集档位表说明 -- **改成什么**:给该条目补一手凭据与新增义务——①2026-10-25 迭代 37 实测:`parity_anchor.py check` 失败 9 项,其中 3 票 RUNSUM 与 JSONL 行数面属锚早于迭代 35 卡② `9ee393a1`(MC 出数字段 `level_templates`→`level_maps`)的历史失配,对 master 同样成立;②另 6 票=迭代 37 卡① d12 新增明细行键 `target_id`(双树原始面实测:行数全同、零值差、唯一差异=该新键),**重锚时必须把 `target_id` 纳入锚的 JSONL 口径**,否则重锚后仍报 6 票;③锚重制属数值标定窗口动作,禁在行为重构批内顺手做(本批即按此未重锚)。 -- **依据**:实现报告附录 E(含原始面留盘位置 `sim-data/parity-master`、`sim-data/parity-iter37`,gitignored);commit `0170071d`/`9ee393a1`;待办清单:118 现行条文。 diff --git a/reports/iter37-实现报告-2026-10-25.md b/reports/iter37-实现报告-2026-10-25.md deleted file mode 100644 index 01a5d37e..00000000 --- a/reports/iter37-实现报告-2026-10-25.md +++ /dev/null @@ -1,281 +0,0 @@ -# iter37 实现报告(2026-10-25) - -迭代 37=**代码实现批**(分支 `iter37`,工作区 `D:/code/g-plan2-iter37`),四卡: - -| 卡 | 需求卡片 | 收口 commit | 状态 | -|---|---|---|---| -| ① 行为缺陷定点修(19 单元 d1–d19) | reports/需求卡片-game行为缺陷定点修批-2026-10-24.md | `1b9736f0`/`0529e169`/`7389618f` | 收口 | -| ② main 组合根判离(r1-1–r1-4+范围扩展) | reports/需求卡片-重构main判离批-2026-10-24.md | `b0d92524`+`c99c23fb`(.uid 边车) | 收口 | -| ③ boardview 拆件(r2-1–r2-3) | reports/需求卡片-重构boardview拆件批-2026-10-24.md | `5c9c5f80` | 收口 | -| ④ runner 拆件与断点 schema 单点(r3-1–r3-3) | reports/需求卡片-重构runner拆件与断点schema单点批-2026-10-24.md | `e5ee4d04`+件核 `48248b21` | 收口 | - -批内另两笔:`2ed81668`(九条 S13 基线外漂移全部以代码压回,基线零改动)、`84dc3e4b`(P0 重构条目登记,用户授权)。合回前 master 已并入(`d2c19791`,带入 S13 基线键治本 `37625317`)。 - -## 1. 完成定义读数(卡片「读数项非停机闸」如实申报) - -| 件 | master | 本批 HEAD | 卡片成文读数预期(非停机闸) | 达成判定 | -|---|---|---|---|---| -| `main.gd` | 1506 | **946** | ≈1370–1380(卡② §1 成文读数预期) | **超出卡面 424 行**(代拍板 B-2 扩面所致,非 r1 清单量;卡② 自注「读数项非停机闸,判离量以函数清单为准」);S13 文件线 ≤1000 另达成 | -| `views/board_view.gd` | 1219 | **937** | ≈880(卡③ §1 成文) | **未达成**(超 57 行=13 条转发属性+常量别名回填 ≈70 行);S13 文件线 ≤1000 达成 | -| `core/adventure_runner.gd` | 1050 | **989** | ≈850±10 | **未达成**(−61;委托层注释、`BREAKPOINT_FIELDS` 登记段与六小函数回填 ≈150 行,抵掉迁出 ≈183 行。公开面冻结+停点编排留宿主使迁出面本就 ≈183 行,卡片 850 预期系按 v1.0 旧边界算,读数项非停机闸) | -| `core/level_runner.gd` | 999 | **857** | ≈780 | **未达成**(−142;17 个同签名委托+2 个私有委托+8 条转发属性回填 ≈70 行) | -| 新件 `core/curio_deck.gd` | — | 259 | — | 净增 | -| 新件 `core/shop_session.gd` | — | 339 | — | 净增 | -| 新件(卡②③)`tile_palette` 261/`hero_sprite` 152/`board_resolvers` 67/`main_ui_build` 238/`main_adventure_flow` 213/`main_overlay_flow` 155/`main_debug_flow` 105 | — | 合计 1191 | — | 净增 | - -四宿主文件合计 4774→3729 行(−1045),逻辑迁入 9 个新件;**行数不是本批的验收判据,等价与单源才是**(下两节)。 - -## 2. 改动—验收对照表(实际形态) - -列义:落点=HEAD 盘面实际位置;真实入口=从生产入口进(非下层直调);撤销证明=回退本批 hunk → 断言红 → 还原复绿,等价类单元如实申报「撤不红属预期」并以等价断言承载。 - -### 卡① 行为缺陷定点修(d1–d19) - -| 单元 | 落点(实际) | 真实入口断言 | 撤销证明/等价申报 | -|---|---|---|---| -| d1 单据归属 `==`→`is_same` | core/damage_controller.gd 来源敌作废判定 | damage_controller_test 双敌两断言〔wd1〕 | 回退该 hunk → 同容异实例断言红(A 单据被 B 死亡误废),还原复绿 | -| d2 蓄力档分档 | presentation/views/standee_body.gd `_intent_charge` | standee_fx_sweep_test(新增 18 项)+theme_projection_test V4 | 回退分档 → 三档判据红;V4 断言随卡① 换代补齐 | -| d3 注释订正 | presentation/views/ui_kit.gd:28(注释面,非行为改动) | 走查 | 不适用(随 d2 复核) | -| d4 庆祝 tween 句柄 kill | battle_end_overlay.gd:281-287/156-161 | 断言②〔wd4〕standee_fx_sweep_test(`open_victory`/`close` 真实开合链) | 回退 kill 段 → 红(T2④) | -| d5–d8 互斥四口 | presentation/views/debug_overlay.gd `_sync_sim_button`+main 两守卫+`is_running()` | system_test〔wd6〕〔wd7〕 | 回退守卫 → system_test 对应行红 | -| d9 ModalStack fail-fast+遮挡账合并 | presentation/modal_stack.gd | modal_flow_test V6-5(5 项〔wd9〕) | 回退 fail-fast → V6-5 红;V6-1~4 原样同绿 | -| d10 两信号契约成文 | core/minesweeper_rules.gd 头注+全库 28 处 `#`→`##` | 契约在位扫描(信号注释载体机检) | 回退契约段 → `undocumented=2` 红,还原归零 | -| d11 数值入表(relic 基价/散折) | data/schemas/shop_table.gd:15-16 字段+:132-135 域检+data/tables/shop.tres:95-96;消费面直读 `db.shop.*`(ConfigDB 两 getter 后由 `2ed81668` 退役,读点现随 r3-2 落在 shop_session.gd:62/:192) | level_shop_economy_test 三表值断言+现价逐位等价〔wd11〕 | 等价申报:原值迁移,撤销读表 → 〔wd11〕红(−1 读数) | -| d12 target_id 产发+站桩按 id 解析 | damage_controller 四处产发/standee_body 两口 | damage_controller_test+pacing_queue_test〔wd12〕 | 回退产发 → target_id 断言红 | -| d14 目标雷数公式单点 | core/board_generator.gd `target_mine_count`+两调用点、legality 去 db 参 | board_generator_test/board_gen_reward_test〔wd14〕 | 等价申报(同式单点),撤销 → 公式断言红 | -| d15 常量化(蓄力层上限) | core/action_economy.gd:185/191-192 `CHARGE_LAYER_CAP`(两处字面量+注释随动) | 全量闸回归线〔wd15〕(finish_round_stop 真实结算链既有套件覆盖) | 等价申报(同 d14 性质) | -| d16a HeroTable.display_name 入表 | data/schemas/hero_table.gd:11+:22-23 非空域检+hero.tres:10;unit_container.gd:55 直读 `db.hero.display_name`(`hero_display_name()` 访问器已由 `2ed81668` 退役) | spawn 真实链断言〔wd16〕 | 撤销读表 → 断言红 | -| d16b 六处显式 int() 包裹 | unit_container/status_container 读侧 | 既有系统测试覆盖 | 等价申报 | -| d13 孤儿注释 | core/treasure_items.gd:55-62(注释面,非行为改动) | 走查 | 不适用(非行为改动) | -| d17 await 守卫 | presentation/views/settlement_fx_view.gd:94-95 | 走查+惯例一致性〔wd17〕(如实申报:无独立断言) | 申报豁免(T2 标注) | -| d18 参数类型注解 | `lumas_out: Variant = null` 现址 views/tile_palette.gd:134(卡① 时在 board_view:998,卡③ 随函数迁件) | S9 类型闸 `audit_type_hints.py` 该行零命中〔wd18〕 | 回退类型 → S9 R3 命中红 | -| d19 产出物 | 本批三份 reports/ 产出物(实现报告/代拍板/反哺点) | 件核对〔wd19〕 | 删一落点 → 件核红 | - -### 卡② main 组合根判离(r1-1–r1-4+范围扩展) - -| 单元 | 落点(实际) | 真实入口断言 | 撤销证明 | -|---|---|---|---| -| r1-1 数据投影五函数出根 | presentation/hud_presenter.gd:230/:245/:257/:280/:293 静态族(status_data/status_curios/career_data/save_select_data/camp_data) | **真实入口=探针开层链**:ui_struct_probe.gd:60/:94 与 ui_click_probe.gd:789 经 main 薄委托(main.gd:317/main_overlay_flow.gd:25/:39/:57/:133 直引 HudPresenter.*)。**如实更正(审计 F-1):hud_presenter_test 零引用这五个静态件,`career_data`/`save_select_data`/`bonus_summary_text` 至今无单元级断言**,建回归断言属下一批 | 删承接体 → Parse Error 红→还原复绿 | -| r1-2 只读解析回调四函数 | **新件** presentation/board_resolvers.gd(db+浮窗+结算三参注入) | ui_click_probe/system_test 棋盘读数面 | 撤销注入 → 解析 Callable 断言红 | -| r1-3 文案拼装三件 | hud_presenter.gd(gp_summary_text/bonus_summary_text/phase_text) | pacing_queue_test/player_copy_sweep | 删 `gp_summary_text` 承接体 → Parse Error+error_scan exit 1 红,还原复绿(**在案那条**) | -| r1-4 探针直引零改动 | main.gd `_camp_data`/`_status_data`/`_gp_summary_text` 三薄委托 | 探针原签名直调 | 等价申报(委托层零语义) | -| 范围扩展:`_build_ui` 八步+三流程族 | main_ui_build/main_adventure_flow/main_overlay_flow/main_debug_flow | 冒烟 17/17+probe --click --full | 逐字搬运等价;诊断转储 source 扫描断言零改动 | - -### 卡③ boardview 拆件(r2-1–r2-3) - -| 单元 | 落点(实际) | 真实入口断言 | 撤销证明 | -|---|---|---|---| -| r2-1 瓦片族出根 | **新件** views/tile_palette.gd(261 行:排版+装载+墨族判据+查表) | terrain_tile_sets_test 19 检查+ui_contrast_probe | 测试改引回退旧址符号 → `SCRIPT ERROR: Static function "_normalize_floor_luma()" not found`+error_scan exit 1,还原后 19 检查复绿 | -| r2-2 英雄族出根 | **新件** views/hero_sprite.gd(152 行,Node2D) | ui_input_probe 英雄落位/朝向/缩格重排断言 | probe --click 十八断言全绿为等价证据;撤销装配 → 落位断言红 | -| r2-3 board_view 改引 | board_view.gd 937 行(13 条转发属性+2 件引用压回 45 变量/27 公有方法) | render_gate 棋盘三图+菜单 golden changed=0.00% | 逐像素等价(golden 换代后复闸 0.00%) | - -### 卡④ runner 拆件与断点 schema 单点(r3-1–r3-3) - -| 单元 | 落点(实际) | 真实入口断言 | 撤销证明 | -|---|---|---|---| -| r3-1 令牌池与判定出根 | **新件** core/curio_deck.gd(259 行:`build/reset/token_identity/find_token/pool_stock/stock_snapshot/consume_deck_token/consume_pool_purchase/cap_ok/feat_ticket_consider/draw_candidates`);宿主 8 个同签名委托(adventure_runner 实址 :357/:444/:456/:461/:466/:635/:640/:645)+4 条转发属性 getter(:61/:64/:73/:76) | adventure_feats_test 138 项/pool_case_d 17 项/feat_vouchers 49 项/curio_refresh_paid 21 项/level_shop_economy 186 项(全部经 `start_adventure`→真链进入) | **P-C**:`_find_token` 委托体回退成 `return {}` → adventure_feats_test 成片红(「通关三选一恰呈现 2 次、恰兑付 2 次」等 6 条),还原复绿 | -| r3-2 商店族出根 | **新件** core/shop_session.gd(339 行,21 函数同形);level_runner 17 公开委托+2 私有委托(`_spend_discounted` :801、`_shop_draw_shelf` :812,测试直引面保号) | level_shop_economy_test/meta_progression_forge_test(`lr._spend_discounted(100)==80` 走件内折价) | **P-A**:`pool_price` 委托体回退成 `return -1` → `[FAIL] relic 现价 = ceil(15.0×b^(关序−1))…〔wd11〕` 红,`git checkout --` 还原后 186 检查复绿 | -| r3-3 断点字段登记单源 | adventure_runner :864 `const BREAKPOINT_FIELDS`(27 键)+写侧 `_bp_parts()` :907 +读侧 `_bp_read`/`_bp_strategy`/`_bp_read_int`;`resume_adventure` 由 108 行巨型体拆成六小函数(`_resume_ledger`/`_resume_curio_state`/`_resume_params`/`_resume_counters`/`_resume_pending` 及主体 36 行);二次消费面(`node_id`/`visited_ids`/`aux_items`)改走 `_bp_read` | **wr2 新增断言**:breakpoint_resume_test:191/:199/:206 三行——登记键集==写侧实拼键集、键集==冻结 27 键清单、策略标签全落六类词表〔wr2〕。**承力实测(审计):三条里只有「键集==冻结 27 键」在增删键时变红**,另两条因写侧与登记同源派生,删键时同步变化而不红;主线回归=breakpoint_resume_test 59 项+iter36 wc2 续玩断言(ui_click_probe「续玩重建开场行带后缀 bonus.atk=5」) | **P-B**:从登记里删 `"refresh_stock"` 一项 → `[breakpoint_resume_test] FAIL — P8 键集与冻结清单逐位一致(27 键契约;增删键即红)〔wr2〕`(1/59 失败),还原复绿 | -| 契约 5 必改接线行 | adventure_runner :745 `_runner.set_shop_pool(_curio_deck, _consume_pool_purchase, _feat_ticket_consider, _curio_cap_ok)` 四标识符改指委托/件读面,参数序不变 | **真链**=level_shop_economy_test.gd:1251(经 start_adventure→关卡推进进店);pool_case_d_test:45 自构 LevelRunner 直调 `set_shop_pool`,属下层直调,**不算本行验收入口**(审计更正) | 在 r3-1/r3-2 撤销证明覆盖面内 | - -**卡④ 撤销证明共三条**(P-A/P-B/P-C,各覆盖一单元),满足并超出卡片 T3「至少两条」。三条全为「回退本批 hunk → 断言红 → 还原复绿」,无注入式变异开关(用户 P0 令口径)。 - -## 3. 等价三证(卡④ 完成定义 2) - -### 3.1 逐字迁移核对表(两拆件族) - -- **CurioDeck**:11 个方法体逐字对齐迁出前宿主实现,仅接收者化(`_curio_deck`→`deck`、`_curio_taken`→`taken`、`_feat_ticket_*`→`ticket_*`)与 `db.adventure`/`db.feat_*` 访问面不变。**RNG 消费序逐位核对**:`draw_candidates` 内 `rng.open_stream("reward_pick")` 取流点、`_draw_exposure` 两次 `_shuffle`(全库→优秀子池)顺序、`_collect_one` 续取、`feat_ticket_consider` 的 `randi_range(1,100)` 与 pity 记账先后,全部与迁出前同一顺序同一分支;`_draw_weighted`(Boss 渠道)的 `_pool_weight`/`_weighted_index` 掷点次数与剔牌重掷路径同序。 -- **ShopSession**:21 函数同形;`set_shop_pool` 四参签名与缺省值 `Callable()` 逐字对齐(卡片 §103)。钱包与加算区经 `bind(refs, hooks)` 持**宿主对象引用**:`wallet`(`_ledger`)、`run_ledger`(账类,件内每次重读 `.bonus`,防宿主 `run_ledger.bonus = {...}` 换代失真)、`level_row`(原地 `assign`)、`tree_rules`/`attunement`;五个换代点全部重绑——level_runner :165/:205/:646/:670/:688 调 `_bind_shop()`。 -- **未迁面**:停点编排与兑付推进留宿主(`skip_empty_curio`/`pick_curio`/`_apply_curio_pick`/`_finish_curio_pick`/`present_node_curio`/`apply_node_curio`/refresh 族/`_redeem_feat_ticket`),断点五时点集零增减零迁移(A4 核:`:506/:634/:803` 现址仍在同三个函数体内)。 - -### 3.1b 卡②③ 逐字迁移核对(交付物补位,审计 F-5/F-3) - -- **交付物指认**:卡②③ 的「逐函数:原行界→新落点→逐字差异」核对结果由独立验收审计机检补上,本批不再重复制表——见 [iter37-验收审计-四卡-2026-10-25.md](file:///D:/code/g-plan2-iter37/reports/iter37-%E9%AA%8C%E6%94%B6%E5%AE%A1%E8%AE%A1-%E5%9B%9B%E5%8D%A1-2026-10-25.md) §2.2(卡② 逐函数 diff 结论)、§2.3(卡③)、§5.5(查表族与墨族真值表穷尽验证)。 -- **须申报的偏差(审计 F-3,本批此前漏报)**:卡③ r2-1 的**瓦片查表族与墨族判据不是逐字迁移,而是表驱动化重构**——`wall_tile_for`/`wall_center_tile` 改 16 掩码查表、`digit_ink_mode` 改 `_family_contrasts`/`_mean_of` 辅助式。动机=把这三个函数从 S13 函数级超标(分支 14/返回 15/局部 12)压回阈值内,属"改代码不改基线"令的推论。**等价性**:16 掩码逐位、60/20/20 档、三态真值表由审计穷尽验证零差,棋盘 golden 三图 changed=0.00%。**处置**:不回退,补代拍板 B-8 追认(卡片契约 1 字面写「禁顺手改算法」,此项越字面但未越语义)。 - -### 3.2 断点 27 键单源对照表(改前读侧实义 → 改后策略标签,逐键一致) - -| 键 | 标签 | 改前读侧实义(原 `resume_adventure`/二次消费) | 一致 | -|---|---|---|---| -| level_index | plain | `int(bp.get("level_index", 1))` | ✓ | -| node_id | plain | 二次消费 `int(_resume_bp.get("node_id", -1))` | ✓(改走 `_bp_read`) | -| hero_hp | clamp | `int(bp.get(...,0))` 入 ledger,随后越界钳到生效上限 | ✓ | -| bonus | clamp | 四子键各 `maxi(0, int(...))` | ✓ | -| gp / shop_purchased / shop_shelf / shop_visit_buys | plain | `int(...)`/`as Dictionary` duplicate/`shop_bp_*()` 取数 | ✓ | -| attunement_charges | keep_on_null | `bp.get(k, null)` + `is Dictionary` 才覆盖 | ✓ | -| graph_gen_v2 | plain | 缺键≠1 显式拒绝(代差检查) | ✓ | -| master_seed / rng_counter | plain | 直取 + `rng.restore_adventure(种子, 计数器)` | ✓ | -| keystone / visited_ids / aux_items / relics | plain | 直取/`as Array`/`restore_treasure_items(...)`/for-append | ✓ | -| bonus_taken | dual_merge | 既有 kind 键按快照清零回填+快照身份维键全量还原 | ✓ | -| bonus_deck | keep_on_null | `is Array` 才还原+relic 令牌 `feat_id` 随迁 | ✓ | -| bonus_pending | token_migration | 候选字符串→普通档令牌防御归一(relic 三段身份键随迁) | ✓ | -| feat_ticket_pity / feat_ticket_sold | plain | `int(bp.get(...,0))`(旧档缺键缺省 0) | ✓ | -| adventure_counters | keep_on_null | `is Dictionary` 才覆盖 | ✓ | -| vision_counter_used / refresh_stock | clamp | `maxi(0, int(...))` | ✓ | -| attune_refresh_y | params_fallback | `maxi(0, int(bp.get(k, int(params.get(k, 0)))))` | ✓ | -| bonus_result / keystone_drops | plain | `as Dictionary` 分支/`duplicate(true)` | ✓ | - -键序=写侧拼装序=`_bp_parts()` 键集=wr2 冻结清单,三者同源(`_snapshot_breakpoint` 按登记顺序取值,未登记键 `parts[key]` 越界 fail-fast)。 - -### 3.3 读数对拍与闸 - -见 §5。 - -## 4. 披露(本批不体面之处,逐条如实写) - -1. **计数守恒装置 25 条**:board_view 13+adventure_runner 4+level_runner 8 条转发属性,唯一作用是让 S13 基线消息里的类变量数逐字不变。**master `37625317` 基线键治本后(改小/持平不红)其存在理由已消失**,本批按用户令「先按现状收口」未返工;已立 P0 待办条目(登记时只算 12 条,漏 board_view 13 条——反哺点第 4 条补记)。判定:**处置错误**,遇「卡片契约×机检键」互斥应停卡呈报而非自拍第三条路(详见代拍板单 B-1/E-1)。 -2. **卡的行数预期未达**(adventure_runner 850±10→989、level_runner 780→857):预期按 v1.0 旧边界算,实际委托层与登记段回填 ≈220 行。卡片自注「读数项非停机闸」,不构成停机条件,但差距必须留痕。 -3. **件封装是名义的**:测试仍从宿主转发面直改件内多重集(adventure_feats_test :390-393、feat_vouchers_test :92-93、level_shop_economy_test :1351-1353)。真拆分卡片的验收面必须包含这三处直引改法。 -4. **件↔宿主方法 Callable 注入成环**(`bind_handlers`/`bind(refs,hooks)`):沿用批前既有形态(原 `set_shop_pool` 即如此),RefCounted 环不报 fatal,但件生命周期由宿主持有,属已知欠账。 -5. **golden 基线换代**(用户裁「重制三张」):成因 `2f3a87e9` 与本批零因果,判失效依据与复闸读数在 commit `7389618f`。 -6. **待办清单.md 由本批直写**:属越批性质禁写面,用户本轮明示授权且仅限这一条登记(commit `84dc3e4b`)。 -7. **等价类单元撤销不红**(卡① d11/d14/d15/d16、卡②③④ 全部委托层):属预期,以等价断言+读数对拍承载,已逐行标注。 -8. **断点「单源」的覆盖面比标称窄(审计发现,此前未披露)**:27 键里读侧真正经 `BREAKPOINT_FIELDS` 策略派生的只有 9 键(keep_on_null 三键+clamp 四键中的 `vision_counter_used`/`refresh_stock`+params_fallback 一键+二次消费三键走 `_bp_read` 默认通道)。`hero_hp` 与 `bonus` 四子键**登记为 clamp 但实际钳制写在 `_resume_ledger`/`_resume_params` 体内**(bonus 子键就地 `maxi(0,…)`、hero_hp 越界钳制依赖 `effective_max_hp` 时机),未从登记派生;`keep_on_null` 的类型守卫也部分靠调用点自身 `is Dictionary`/`is Array`。语义逐键与改前一致(审计逐表达式对拍零差),但"改登记即改行为"这条只在 9/27 上成立——拆卡重裁 BreakpointSchema 时按此读数定边界。 -9. **双写面比此前申报的多**:除 §4 第 3 条列的三处测试直引外,**生产续玩链也经转发属性写件内状态**(`_resume_curio_state` 里 `_curio_taken[id]=…` 与 `_curio_deck = restored`)。它是单点存储(属性 setter 指回件内唯一份),非双源,但"件封装名义化"的范围因此比原报更大。 -10. **wr2 三条断言只有一条承力**(见 §2 卡④ r3-3 行内实测):键名增删由「键集==冻结 27 键清单」拦住;「登记键集==写侧实拼键集」与「策略标签落六类词表」结构上与登记同源,删键不红。契约 3 的「单源键集断言」实质由冻结清单承担。 - -## 5. 实机目检总清单(真人检测只登记不阻塞) - -本批四卡零玩法语义变化,但动了呈现装配与商店/三选一接线,需真人试玩确认的项(**不写进根体验验证清单——该件属本批禁写面,是否入册由用户裁**): - -1. 棋盘观感逐像素:普通/优秀/稀有档瓦片、封印纹、数字墨族、英雄待机/行走/撞击/挖掘四态与朝向、缩格重排与缺位回退(卡③ 拆件后同链)。 -2. 商店:进店货架绘制、刷新(免费额度/付费价)、购买扣款与文案、离店再进店(货架入断点)。 -3. 三选一停点:关底通关三选一呈现、刷新钮、空池跳过、专长凭证替换、确认后回血与加算区读数。 -4. 断点续玩:中途关退出→「继续上局」→停在原节点、GP/生命/道具库存/特性/凭证限购读数一致。 -5. 卡① 指定目检两项(卡片 §8.2 末段明文要求写进本清单):蓄力敌(heavy 技能敌)意图三档观感;同名敌受击闪白与弹道落点是否落对个体。 -6. 蓄力三档观感分档、战斗结束庆祝浮动不叠加、批量模拟与调试浮窗互斥(连点不并发)。 -7. 模态层重复 open 不再吞旧账(多层叠加关闭顺序)。 - -## 6. 遗留读数义务 - -- 全量域闸(两域档)回执 `game/tests/.gates/receipt.json`:读数见 §7 与验收审计报告。 -- 本批**未跑 MC**(零数值改动,除卡① d11/d16a 入表为等值迁移),按红线「无数值改动零跑」不补跑;parity/对拍档位未动。 -- `game/tests/验证集清单.md` 断言索引未重生成(不受闸覆盖)→ 反哺点第 5 条。 -- gdstyle 基线陈旧键待人工 refresh 清理 → 反哺点第 6 条。 - -## 附录 A:断言尾缀索引(只读取证代理自 `git diff master...HEAD -- game/tests game/probe` 抽出的**本批新增行**) - -尾缀族核实:卡①=`〔wdN〕`;卡②③④ 共用 `〔wrN〕` 族,其中卡面对照表里的 wr1/wr3/wr4 是**行号标识**(非行为改动/等价行),盘面新增的带尾缀断言只有 `〔wr2〕` 三条;`wc` 族系迭代 36 旧族,本批零新增。 - -| 尾缀 | 文件:行 | 套件 | 测什么 | -|---|---|---|---| -| wd1 | unit/battle_settlement/damage_controller_test.gd:243/:253 | damage_controller_test | 双敌不误废单据/自身死亡作废对照 | -| wd2 | unit/presentation/standee_fx_sweep_test.gd:95/:97/:99/:101 | standee_fx_sweep | heavy 置蓄力档/非蓄力技零置位/增益系零置位/普攻路径复位 | -| wd2 | unit/presentation/theme_projection_test.gd:693 | theme_projection | 分档布尔判据(换代后) | -| wd4 | standee_fx_sweep_test.gd:108/:111/:114 | 同上 | 首开建句柄/重开旧句柄已 kill/close 必杀 | -| wd5 | 同上:119/:121 | 同上 | 模拟期调试钮灰/收尾恢复 | -| wd6 | tests/system_test.gd:290 | system_test | 模拟态调试守卫早退 | -| wd7 | tests/system_test.gd:300 | system_test | 关间态模拟守卫早退 | -| wd8 | standee_fx_sweep_test.gd:130/:131/:132 | 同上 | 运行中 is_running/收尾 is_running/报告注入链路 | -| wd9 | unit/presentation/modal_flow_test.gd:126/:129/:132/:134/:136 | modal_flow | open/close 未登记 fail-fast+重复 open 遮挡账三半 | -| wd11 | unit/core/level_shop_economy_test.gd:262/:263/:1295 | level_shop_economy | relic 基价表值/散折表值/现价逐位等价 | -| wd12 | damage_controller_test.gd:271/:276 | damage_controller_test | fx 载荷携 target_id/伤害行携 target_id | -| wd12 | standee_fx_sweep_test.gd:75/:76/:78/:83/:88/:91 | standee_fx_sweep | enemy_id 访问器/同名敌按 id 命中/空 id 恒不命中/闪白落点/弹道落点/无 id 名兜底 | -| wd12 | unit/presentation/pacing_queue_test.gd:136/:139/:140 | pacing_queue | 标出传 id/弹道传 id/两路各接一拍 | -| wd14 | unit/core/board_generator_test.gd:75/:81 | board_generator | 公式单点同值对拍/metrics 读数同源 | -| wd16 | level_shop_economy_test.gd:264/:268 | level_shop_economy | 英雄名表值/spawn 读表同值 | -| **wr2** | unit/core/breakpoint_resume_test.gd:191/:199/:206 | breakpoint_resume | 单源键集==写侧实拼键集/键集==27 键冻结清单/策略标签落六类词表 | - -无落码断言的卡面单元(与卡① §8.2 分行申报一致):wd3/wd10/wd13/wd15/wd17/wd18/wd19(走查、grep 静态扫描、全量闸回归线、件核对四类)。 -接线行无尾缀:tests/run_all.gd:62(standee_fx_sweep 套件登记)。注释级标记:system_test.gd:271/283/292(wd6/wd7 段头注)、adventure_feats_test.gd:218(退役符号注记,commit `48248b21`)。 - -## 附录 B:撤销证明原始读数(`[FAIL]`/`SCRIPT ERROR` 原行+还原复绿) - -| 单元 | 撤销动作 | 原始红读数(原样) | 还原复绿 | -|---|---|---|---| -| d1 | 回退 `is_same` hunk | `[FAIL] 同内容异实例敌死亡不误废他人单据〔wd1〕`+`[damage_controller_test] 1/44 项失败` | `damage_controller_test] all green (44 checks)` | -| d2 | 回退布尔分档 | `SCRIPT ERROR: Parse Error: Identifier "_intent_charge" not declared in the current scope.`(standee_body:161/166/178/179/196)+`Failed to load script ...standee_fx_sweep_test.gd with error "Compilation failed"` | `[standee_fx_sweep] all green (18 checks)` | -| d4 | 回退 kill 段 | `[FAIL] 首开胜利画面创建庆祝句柄〔wd4〕`+`[FAIL] 重开胜利画面:旧句柄已 kill、新句柄在位(至多一个活跃)〔wd4〕`+`1/18 项失败` | 同上 | -| d5 | 回退 disabled 半 | `[FAIL] 模拟运行期调试入口钮同置灰〔wd5〕`+`1/18 项失败` | 同上 | -| d8 | 回退 `is_running()` | `SCRIPT ERROR: Invalid call. Nonexistent function 'is_running' in base 'RefCounted (tune_sim_controller.gd)'.`(两次)+`[FAIL] 控制器运行中 is_running()=true(驱动回调内读数)〔wd8〕` | 同上 | -| d9 | 回退合并段 | `[modal_flow] FAIL — V6 重复 open 不吞遮挡账(close 低层恢复 mb——合并非覆盖)〔wd9〕`+`1/24 项失败` | `[modal_flow] all green (24 checks)` | -| d12 | 回退站桩按 id 半 | `[FAIL] 伤害行按 target_id 命中正确站桩(闪白落点)〔wd12〕`+`[FAIL] 弹道落点按 target_id 取站桩中心〔wd12〕`+`2/18 项失败` | 同上 | -| d10 | 回退信号契约段 | 契约在位扫描 `undocumented=2` 红 | 还原归零 | -| 卡③ r2-3 | 测试改引回退旧址符号 | `SCRIPT ERROR: Parse Error: Static function "_normalize_floor_luma()" not found in base "BoardView".` | `[terrain_tile_sets_test] all green (19 checks)` | -| 卡② r1-3 | 删 `HudPresenter.gp_summary_text` 承接体 | `SCRIPT ERROR: Parse Error: Static function "gp_summary_text()" not found in base "HudPresenter".`(main.gd:493)+冒烟 `[run_smoke] FAIL—— 退出码 = 1/冒烟失败 8 项 / 共 17 项` | **缺该撤销的复绿日志**——复绿以同批后续绿记录指认(冒烟 17/17、probe --click/--full 全绿在 commit `b0d92524` 读数段),如实申报为取证缺口 | -| 卡④ r3-2(P-A) | `pool_price` 委托体改 `return -1` | `[FAIL] relic 现价 = ceil(15.0×b^(关序−1))(读表与迁移前字面量逐位一致,-1)〔wd11〕` | `git checkout --` 后 `[level_shop_economy_test] all green (186 checks)` | -| 卡④ r3-3(P-B) | 登记里删 `"refresh_stock"` 一项 | `[breakpoint_resume_test] FAIL — P8 键集与冻结清单逐位一致(27 键契约;增删键即红)〔wr2〕`(1/59 失败) | 还原后 `PASS — …字段单源 全绿` | -| 卡④ r3-1(P-C) | `_find_token` 委托体改 `return {}` | 六条成片红:`[FAIL] 满配通关…`、`[FAIL] 通关几关 = 3`、`[FAIL] 钥石合计 = ΣBoss 首杀行值`、`[FAIL] 逐杀掉落明细恰 3 笔`、`[FAIL] 摘要携带主种子(42)`、`[FAIL] 通关三选一恰呈现 2 次、恰兑付 2 次` | 还原后 `[adventure_feats_test] all green (138 checks)` | - -## 附录 C:合回前闸况变化与修法(本次全量档首跑 84dc3e4b,四条新红已全部处置并留痕) - -| 闸 | 首跑读数 | 成因归属 | 处置 | -|---|---|---|---| -| 09 静态扫描 S11 | 禁用词命中 2 处=minesweeper_rules.gd:9/:10「雷格」 | **本批新增**(卡① d10 补信号契约注释时用词违术语表退役词对照 :143「雷格/惩罚格 → 危险格」) | 两处改「危险格」,`static_scan.sh` 单跑 `S11 ok(扫描面 237 个文件,词表 19 词)` | -| 02 单元与集成测试 | `[FAIL] V8 字符串 Callable 产品码零命中〔迭代27 T6〕`(source_guard_test 1/8) | **本批新增**(卡④ `_bind_shop()` 用三处 `Callable(self, "…")` 字符串形态,违迭代 27 T6 方法引用换装闸) | 三处改裸方法引用(Godot 4 方法名即值),source_guard_test 8/8 绿+level_shop_economy 186/curio_refresh_paid 21 绿 | -| 10 熵增闸 | 图像越界 11 个,全在 `.iter37-logs/golden_diag/*.png` | **本批在途件**(黄金图诊断临时件落仓库根临时目录,未入库) | 处置完删除整个 `.iter37-logs/` 临时目录(取证读数已转录本附录) | -| 08d 引用可核 | 红项 1=`design/gdd/systems/跨局循环/生涯.md:55` 指 `adventure_runner.gd:1007-1018`,目标现 989 行 | **本批引起**(adventure_runner 1050→989 使文档行号引用超界),但修复位在被批性质禁写的 `design/` | **待用户裁**:授权本批同步该行号(一处)/或由文档批落文(则本批无法清红)。其余八处同类漂移因同句带日期/退役说明自动降为建议级 | -| 07 doc_link | 基线红 → 现绿 | 本批无关(合并 master 带入) | 记为改善,无需处置 | - -## 附录 D:独立验收审计八项处置(审计文件=`reports/iter37-验收审计-四卡-2026-10-25.md`,判定「可以合并」) - -| # | 审计事项 | 本批处置 | 落点 | -|---|---|---|---| -| F-1 | 卡② r1-1 真实入口指认不实(hud_presenter_test 零引用五静态件) | 已更正为探针开层链+组合根直引,并记「三件至今无单元级断言」 | §2 卡② 行 | -| F-2 | 卡④ 委托/const 行号指针 8 处不准;契约 5 入口用下层直调冒充 | 委托行号订正为实址八条+四条 getter;`:857→:864`、`:909→:907`;契约 5 改指 `level_shop_economy_test.gd:1251` 真链,`pool_case_d:45` 降级为非验收入口 | §2 卡④ 行 | -| F-3 | 卡③ 查表族/墨族表驱动化重构未申报(违契约 1 字面) | 补 §3.1b 逐字偏差说明+代拍板 B-8 追认;等价性采信审计穷尽验证与 golden 0.00%,不回退 | §3.1b/代拍板 B-8 | -| F-4 | 卡① d11/d16a「ConfigDB 三访问器」被 `2ed81668` 撤销改直读行访问器 | 括注已在案(`2ed81668` 退役理由=运行时追加=双重真值);「行访问器直读是否等同单点口径」列反哺点第 9 条待文档批核 | §2 卡① 行/反哺点 9 | -| F-5 | 卡②③ 缺逐字迁移核对表交付物 | 交付物指回审计 §2.2/§2.3/§5.5(本批不重复制表),并补 §3.1b | §3.1b | -| F-6 | 读数表用机检线冒充卡片预期 | 列名改「卡片成文读数预期(非停机闸)」,main 946 判「超出卡面 424 行」、board_view 937 判「未达成(超 57 行)」 | §1 | -| F-7 | 卡① §8.3「唯一既有测试改动」与 d2×契约 5 互斥(卡片自身 spec-AC 矛盾) | 不属本批可修:V4 换代只增强不放宽(审计证实);矛盾退回写卡段,列反哺点第 7 条 | 反哺点 7 | -| F-8 | 回执钉 `9e3e2667` 落后 HEAD(纯 reports/ 追加) | 随合并 master 后的全量档复跑消解(复跑原因=master 推进,合法复跑况②) | §5 闸读数 | -| 用户裁 | 11 号回放确定性对拍 | **已获批准**:复跑带 `PARITY_APPROVED=1` 实跑三变体×20 局同种子逐字节比 | 本次复跑 | - -## 附录 E:11 号回放确定性对拍的根因定性(用户批准发起,2026-10-25 19:06–20:43 实测) - -你批准发起后跑了 `tools/parity_anchor.py check`:三票 RUNSUM 漂移+六票 JSONL 逐位漂移=**失败 9 项**,回执因此非绿。为分清"锚本来就错/需求改了没配套/实现写坏了",按脚本自带取证法(头注 15 行)做了双树原始面比对: - -| 步骤 | 读数 | -|---|---| -| A:master 树跑同参批落原始面 | `sim-data/parity-master/`(9 件,gitignored) | -| B:iter37 树跑同参批落原始面 | `sim-data/parity-iter37/`(9 件) | -| C:A vs B 逐字节比 runsum | **三份 runsum 全同** | -| D:A vs B 比 JSONL | 六份文件**行数两两全同**(9671/62766/9217/60775/7125/59436);差异行合计 17222,**全部是 `kind:"step"` 行**;差异形态只有一种——B 侧多一个键 `target_id`;**删键 0、值差 0** | - -定性结论(三类各判一次): - -1. **不是"实现不正确"**:若实现有偏差,表现为数值差或行数差。实测两树零值差、零行数差,runsum 逐字节同——本批(含卡④ 拆件与断点单源、卡②③ 判离拆件)行为侧零漂移,这比审计的静态逐表达式对拍更强一层。工具确定性也一并验过:同种子两树各跑,无任何顺序抖动。 -2. **是"原本就有问题"(历史欠账,占 3 票 RUNSUM+行数面)**:锚最后重制是 `0170071d`(2026-10-19),其 RUNSUM 行仍含 `level_templates`、不含 `level_maps`——该字段改名出自**迭代 35 卡② `9ee393a1`**(提交信息④明写「MC 出数字段 `level_templates`→`level_maps`」)。待办清单:118「旧行为 MC 行为锚全数作废——重锚归数值批」(2026-10-06 登记,状态=待文档设计)已明文规定"重锚不在改动批执行、重锚前旧读数禁止与新行为逐项比对"。⇒ 这个红对 master 一样成立,与本批无因果。 -3. **也含"需求改了出数面、锚与口径没配套"(本批侧,占 6 票 JSONL)**:17222 行差异 100% 是卡① d12 按设计新增的 `target_id` 键(damage_controller 四处产发)。卡① §8.2 只要求单元级断言(已在:damage_controller_test:271/:276 等〔wd12〕),未要求也不该在缺陷修批内重锚——但**重锚批必须把 `target_id` 纳入锚面**,否则下一次 `check` 还会因同一形态报 6 票。已列反哺点第 10 条。 - -处置建议(待你选一,见对话):A=按在册条目闭合(重跑全量档不带 PARITY_APPROVED,回执回 NEED-APPROVAL 形态,本附录与合回提交信息留归因凭据);B=批准本批重锚(`freeze` 重写 tracked 锚文件,与待办条文"重锚不在改动批执行"相违)。 - -**用户裁=B(批准本批重锚),已执行**:`python tools/parity_anchor.py freeze` 以现行行为重写 `game/tests/evidence/parity/anchor.json`。重锚后盘面对旧锚的实测差: - -| 项 | 旧锚(0170071d/2026-10-19) | 新锚(本批重制) | -|---|---|---| -| RUNSUM 出数字段 | 含 `level_templates` | 含 `level_maps`(迭代 35 卡② 改名的追认) | -| JSONL 行数面 base | normal 8945 / perfect 39811 | normal 9216 / perfect 60774 | -| JSONL 行数面 attune | normal 9620 / perfect 38435 | normal 9670 / perfect 62765 | -| JSONL 行数面 shop | normal 7499 / perfect 45132 | normal 7124 / perfect 59435 | -| 与新锚 sha 关系 | — | 六项全部与旧锚不同(`target_id` 已入锚口径) | - -新锚的行数面与附录 E 步骤 B(iter37 树原始面)逐项吻合,说明重锚取自同一确定性盘面。待办清单:118「旧行为 MC 行为锚全数作废」条已补记本次实测凭据与出账一项(对拍锚部分;T6 四带复算义务不变)。重跑全量档时 11 号将对新锚做 `check`。 - -## 附录 F:合回前终局读数(回执 commit `eb8d6073`,domain=full,all_green=true,dirty_game=false,时间 2026-10-25 21:45:47) - -| 闸 | 退出码 | -|---|---| -| 00 冒烟/01 系统测试/02 单元与集成 | 0 / 0 / 0 | -| 03 探针 --click/04 探针 --full/05 渲染闸 | 0 / 0 / 0 | -| 06 gdd_lint/06b 变更记录节/07 doc_link/08 doc_term | 0 / 0 / 0 / 0 | -| 08c 流水线闸/08d 引用可核 | 0 / 0 | -| 09 静态扫描(含合并带入的新增 S6/S8 段)/10 熵增闸 | 0 / 0 | -| **11 回放确定性对拍** | **0(✓ 全同:base/attune/shop 三变体 RUNSUM+JSONL 双面逐位一致)** | - -闸况变化记录:`07 doc_link` 基线红→现绿(合并带入,非本批)、`11 对拍` 基线红→现绿(重锚后转绿)。 - -### 收口期三次并源与一次白跑(如实留痕) - -| 时点 | 事件 | 处置 | -|---|---|---| -| 17:55 | 首次全量档(回执 `9e3e2667`) | 四条新红(S11 退役词/V8 字符串 Callable/熵增临时图/08d 行号超界),三条改码+一条经用户授权改文档 | -| 18:18 | 第二次全量档(回执 `9e3e2667` 复算) | 两域全绿,11 号未批准故 NEED-APPROVAL | -| 19:06 | 用户批准实跑 11 号 | 失败 9 项 → 归因为锚陈旧(历史)+卡① d12 新键未入锚,双树原始面实测排除实现不正确(附录 E) | -| 20:45–21:04 | 用户裁 B=本批重锚 | `freeze` 重写锚(含 `level_maps`+`target_id`),commit `bd71917a` | -| 21:16–21:20 | **白跑一次**:另一会话合入 S6/S8 闸批使 master 推进,我先误将旧跑批外壳 kill(子进程仍在)→ 两轮并发写同一 `.gates/` 盘面 | 清掉两轮共 6 个本工作区进程(另一会话进程未碰),并源 `eb8d6073` 后起唯一一轮 | -| 21:20–21:45 | 终局全量档 | 15 闸全绿,11 号对新锚逐位全同 | - -域判定依据(供合回闸核):本批 diff 同时触及 `game/`(代码域)与 `design/`·根流程清单·`reports/`(文档域),故取**全量档**(两域全跑)回执,domain=full。 - -两处越批性质禁写面均已获用户当场授权并在提交信息留痕:`待办清单.md`(P0 条目登记与更正,commits `84dc3e4b`/`947664b6`/`bd71917a`)、`design/gdd/systems/跨局循环/生涯.md`(一处行号同步,commit `9e3e2667`)。 diff --git a/reports/iter37-拆分卡缺口-2026-10-25.md b/reports/iter37-拆分卡缺口-2026-10-25.md deleted file mode 100644 index 15b01328..00000000 --- a/reports/iter37-拆分卡缺口-2026-10-25.md +++ /dev/null @@ -1,47 +0,0 @@ -# iter37 → P0 拆分卡缺口补记(2026-10-25) - -给写卡段(`req-card`)的输入。**不是需求卡片**,也**不是本批返工清单**——本批四卡已按用户令收口,这份只把 P0 条目「AdventureRunner 职责真拆分」的四件缺口补成可写卡形态。落点读数以 HEAD `9e3e2667` 盘面为准。 - -## 1. 现状读数(为什么不能只删转发属性) - -| 面 | 现值 | S13 阈值 | 只删转发属性后 | -|---|---|---|---| -| `adventure_runner` 类变量 | 42 | ≤15(超出走基线登记) | 38(−4)——**仍远高于阈值**,红不红取决于登记值,债没还 | -| `adventure_runner` 公开方法 | 23 | ≤20 | 23(**一件没减**,委托层全是公开/私有方法) | -| `level_runner` 类变量 | 38 | ≤15 | 30 | -| `level_runner` 公开方法 | 54 | ≤20 | 54 | -| `board_view` 类变量 | 45 | ≤15 | 32(board_view 侧同类转发属性 13 条,卡③ 产物,同一债种) | - -结论:**撤装置=还一半债(计数改善,新键形态不判红),但职责混合一点没解**。真拆分才可能压到阈值内。 - -## 2. 按变化原因的件边界(待裁案的起点,非结论) - -| 件 | 应收的成员(现宿主址) | 应收的方法 | 变化原因 | -|---|---|---|---| -| `AdventureFlow`(留名 adventure_runner) | `state`/`level_index`/`levels_total`/`_runner`/`_pending_next_level`/`_last_level_payload` | `start_adventure`/`resume_adventure`/`_start_current_level`/`_bind_level_runner`/`_inject_level_params`/`_present_level_entry`/`_on_level_finished`/`_settle`/`continue_to_next_level`/`abandon`/`runner` | 关序推进与结算受理一次 | -| `RewardStop` | `_curio_pending`/`_curio_result`/`_node_curio_pending`/`_attune_refresh_x`/`_refresh_stock`/`_pending_rap`/`_time_sand_queue`/`_curio_paid_refreshes` | `pick_curio`/`apply_node_curio`/`present_node_curio`/`node_curio_candidates`/`skip_empty_curio`/`_apply_curio_pick`/`_finish_curio_pick`/`_finish_node_bonus`/`_redeem_feat_ticket`/refresh 族五件/`_feat_payload_keys` | 奖励怎么给、怎么刷、怎么确认 | -| `CurioDeck`(已建) | 池与凭证计数(已入件) | 已入件 | 池规则 | -| `HeroLedger` | `ledger`/`run_ledger`/`_base_override`/`_override`/`purchased`/`keystone`/`_feats` | `effective_max_hp`/`apply_rest`/`set_hero_override`/`feats_held`/`_seed_attune_charges` | 数值叠加与生命口径 | -| `BreakpointSchema` | `BREAKPOINT_FIELDS`(const 可留宿主,派生段出根) | `_snapshot_breakpoint`/`_bp_parts`/`_bp_read`/`_bp_read_int`/`_bp_strategy`/六个 `_resume_*` | 存档格式 | -| 呈现接线 | 无 | `_forward_*` 六件+信号面 | UI 事件形状(候选下沉装配层,见缺口④) | - -## 3. 写卡前必须裁的五件 - -1. **RewardStop ↔ CurioDeck 分界**:停点三态归 RewardStop 还是 CurioDeck?`_redeem_feat_ticket`(兑付推进,且体内有断点时点)在谁侧——现方案是宿主用 `bind_handlers` 把 `_apply_curio_pick`/`_redeem_feat_ticket` 反向插进 CurioDeck,件与宿主双向拉扯,必须定一个方向。 -2. **HeroLedger 与既有 `RunLedger` 的关系**:`run_ledger` 已存在且被 LevelRunner 共享引用(`set_run_ledger` 下传)。是把 `ledger`(hero_hp/gp)并进 RunLedger,还是新立 HeroLedger 包两只?并=共享引用面重划;新立=多一层。 -3. **BreakpointSchema 独立成件的取舍要重裁**:卡④ §11 以「27 键多数取宿主态、开洞面>收益」淘汰过独立件。真拆分后键值来源收敛到三个件(Flow/RewardStop/Ledger),该论据部分失效——重裁时须给「开洞面」的新读数。 -4. **公开面收缩与调用方改动一并算进验收**:main.gd(`start_adventure`/`resume_adventure`/`continue_to_next_level`/`pick_curio`/`apply_node_curio`/refresh 面/`runner`/`feats_held`)、`camp_shop_presenter`、`curio_interact_controller`、`ui_click_probe`(iter36 wc2 续玩断言)、五套测试的直引点(含从转发面直改件内状态的三处:`adventure_feats_test.gd:390-393`、`feat_vouchers_test.gd:92-93`、`level_shop_economy_test.gd:1351-1353`)。 -5. **撤装置必须与拆件同批**:单独撤 25 条转发属性=测试成片红(它们靠转发面写件内状态)。卡片需把「测试改法」列为改动单元并各配真实入口断言,否则该批无法闭合。 - -## 4. 验收面预案(供卡片 §8.2 直接展开) - -- 每新件至少一条真实入口断言:`RewardStop`=走 `start_adventure`→通关三选一→选一件→断言加算区/池余量/停点态清零;`HeroLedger`=rest 与 heal 封顶走真链;`BreakpointSchema`=已有〔wr2〕三条+续玩真链(`ui_click_probe` wc2 段)。 -- 撤销证明:每单元至少一条「回退本批 hunk → 断言红 → 还原复绿」;禁注入式变异(P0 令)。 -- 等价面:断点 27 键键名/缺省/回退策略逐键冻结;RNG 流名与消费序(`reward_pick`)零漂移;MC/11 对拍是否发起由用户裁(触及存档/RNG/结算面)。 -- 基线:`tools/gdstyle_baseline.txt` 禁改;计数下降属改善,新键形态下不判红(`37625317` 已治本)。 - -## 5. 本批随批留痕的相关条目 - -- 待办清单 P0 条目(`84dc3e4b`)——覆盖面待补 board_view 13 条(反哺点第 4 条)。 -- 代拍板单 B-1/E-1——转发属性装置的成因与认错。 -- 实现报告 §4 第 1/3 条——装置清单与「件封装是名义的」实证。 diff --git a/reports/iter37-验收审计-四卡-2026-10-25.md b/reports/iter37-验收审计-四卡-2026-10-25.md deleted file mode 100644 index 91e02f3b..00000000 --- a/reports/iter37-验收审计-四卡-2026-10-25.md +++ /dev/null @@ -1,273 +0,0 @@ -# 验收审计:iter37 四卡(独立审计方,2026-10-25) - -> **性质**:合回前独立验收审计(test-conventions 撤销证明与验收闭合计口径;卡① §8.3/卡②③④ §8 末段规定的合并前置件)。审计方未参与实现。 -> **被审对象**:worktree `D:/code/g-plan2-iter37`(分支 `iter37`),`git diff master...HEAD`=15 commits/75 文件。开工指令记为 14 commits/74 文件;审计期间并行会话追加 `356029d0`(只写 `reports/iter37-拆分卡缺口-2026-10-25.md`,零代码面)。 -> **输入**:四张需求卡片+`reports/iter37-实现报告-2026-10-25.md`(§2 对照表/§3 等价三证/附录 A/B/C)+代拍板单+反哺点清单+闸回执 `game/tests/.gates/receipt.json`(commit `9e3e2667`,domain=full,all_green=true,dirty_game=false)。 -> **方式**:全部结论自己取证——逐行 grep 盘面、`git show master:` 逐函数对拍(含 python 机检对键序/取值表达式/掩码表穷尽比对)、单件直跑 5 个套件+一次自跑撤销证明。报告里写「绿」不采信。零全量闸重跑、零生产码改动。 - ---- - -## 一、总判定 - -**可以合并**——四卡验收面闭合:卡① 19 单元全部在盘且有断言,卡②③④ 等价迁移经本审计独立证实(含 RNG 调用序与断点 27 键逐表达式对拍),零测试削弱,卡④ P-B 撤销证明本审计实跑复现成功且还原复绿。 - -合回前三件事必须做(都不动生产码): - -1. **回执与 HEAD 不同位**:回执钉 `9e3e2667`,HEAD 现为 `356029d0`(追加的 reports/ 文档件)。合回前按域重跑所选档一次,或由 guard 明确接受「回执 commit 落后一个纯文档提交」。 -2. **实现报告三处指认须更正/补申报**:见第六节 F-1/F-2/F-3(一处真实入口指认不实、一处机检线冒充卡片预期、一处未申报的查表算法重构需追加代拍板追认)。 -3. **呈报 11 号闸(回放确定性对拍)是否发起**:它是卡④「RNG 消费序不漂」的最强独立判据,回执记 NEED-APPROVAL 未跑;本审计用静态对拍替代(结论=同序),但档位发起权归用户。 - ---- - -## 二、检查一:改动—验收对照表逐行核对 - -### 2.1 卡① d1–d19(实现报告 §2 表 + 附录 A 尾缀索引) - -判定列=盘面实据是否存在+入口是否真链。行号一律给 HEAD 实测值。 - -| 单元 | 盘面实据 | 判定 | -|---|---|---| -| d1 `is_same` | `game/core/damage_controller.gd:216` 判定符已改;断言 `game/tests/unit/battle_settlement/damage_controller_test.gd:243`/`:253`〔wd1〕,链路 `open_ticket→submit→units.unit_died.emit→flush→outcome()` 全真链 | 通过 | -| d2 蓄力分档 | `game/presentation/views/standee_body.gd:33`(`_intent_charge` 成员)/`:178-179`(`_intent_charge = pid in CHARGE_SKILL_IDS`)/`:196`(`if _intent_charge:`);断言 `standee_fx_sweep_test.gd:95/97/99/101`+`theme_projection_test.gd:693` | 通过(入口深度见 4.3) | -| d3 注释订正 | `game/presentation/views/ui_kit.gd:28` 失真引述已整段删除 | 通过(注释面,无断言属预期) | -| d4 庆祝句柄 | `battle_end_overlay.gd:161-164`(close kill+remove_meta)/`:288-292`(先 kill 后建);断言 `standee_fx_sweep_test.gd:108/111/114`〔wd4〕绑 Tween 实例 `is_valid()` | 通过(报告记 `:281-287/156-161`,现址 `:285-295/158-164`=指针漂移) | -| d5–d8 互斥四口 | `debug_overlay.gd:104`(`_dbg_btn.disabled` 并入 `_sync_sim_button`,且 `:84` 原直写已删);`tune_sim_controller.gd:14/32-33/36/70`(`_running`+`is_running()`+run 首尾置位);两守卫在 `main_debug_flow.gd:84`(d6 补 `is_running()`)与 `:72-79`(d7 补 `is_adventure`),main 留同签名委托 `main.gd:423-424/928-929`;断言 `standee_fx_sweep_test.gd:119/121`〔wd5〕`:130/131/132`〔wd8〕+`system_test.gd:290`〔wd6〕`:300`〔wd7〕 | 通过。system_test 从 `main._start_debug_levels()`/`main._on_tune_simulate(3)` 进入=信号实际接线的入口(`main_ui_build.gd:196-197`),非下层直调 | -| d9 模态 fail-fast+合并 | `modal_stack.gd:22-25`(open 未登记 push_error+return)/`:28-38`(旧账∪新隐合并)/`:41-44`(close 未登记守卫);断言 `modal_flow_test.gd:126/129/132/134/136`〔wd9〕;V6-1~4 在役序列 `:40-48` 原样保留(diff 零触碰该段) | 通过 | -| d10 信号契约 | `minesweeper_rules.gd:8-16` 两契约按 §2.1 三要素成文;全库扫描复现:`grep -rnP "^\s*signal\s+\w+\(.*\)\s*#(?!#)" game/` =**零命中** | 通过 | -| d11 数值入表 | `data/schemas/shop_table.gd:15-16` 两 knob+`:132-135` 两域检(报告记 `:132-135`=精确);`data/tables/shop.tres:95-96` 值 15.0/0.8;消费点 `core/shop_session.gd:62`(spend_discount)/`:192`(relic_base_price),报告注「读点随 r3-2 落在 shop_session:62/:192」=精确 | 通过(ConfigDB 三访问器被 `2ed81668` 退役=卡片 d11 改法偏离,见 F-4) | -| d12 target_id | 产发四处 `damage_controller.gd:169/371/401/505`;两口解析 `battle_stage_view.gd:308-310`(on_step_row_fx,id 先名兜底)/`:357-358`(enemy_standee_center 改签名);`row_pacer.gd:111/116/137/145`;`standee_body.gd:70` `enemy_id()`;断言三宿主共 11 行〔wd12〕 | 通过(报告/卡片记 :168/370/400/504/:342 全系 ±1~15 漂移) | -| d13 孤儿注释 | `treasure_items.gd:51-56` 现仅 `inventory_count()` 与 `_usable_now` 头注,master :55-62 三段孤儿注释(持有数/同调位守卫谓词/持有件记录/丢弃)已删 | 通过 | -| d14 公式单点 | `board_generator.gd:14` `static func target_mine_count`;两调用点 `:102`/`:339`;`:286 legality(b, params)` 去 db 参;调用点两处同批改(`:53` 内部+`board_gen_reward_test.gd:160`) | 通过(卡片记 test:145=master 行号,现 160) | -| d15 常量化 | `action_economy.gd:14` `const CHARGE_LAYER_CAP := 3`;`:192/193` 两处改引;`:186` 注释随动 | 通过 | -| d16a 英雄名入表 | `hero_table.gd:11` 字段+`:22-23` 非空域检;`hero.tres:10`;消费 `unit_container.gd:55` 直读 `String(db.hero.display_name)` | 通过 | -| d16b int() 包裹 | `unit_container.gd`/`status_container.gd` 读侧包裹在位,由全量闸回归线承载(等价申报) | 通过(等价类,卡片即如此申报) | -| d17 await 守卫 | `settlement_fx_view.gd:95-96` | 通过(无独立断言=卡片 §8.2 已申报豁免) | -| d18 参数类型 | `tile_palette.gd:134 lumas_out: Variant = null`(报告记现址 :134=精确);本审计实跑 `python tools/audit_type_hints.py` → `[audit_type_hints] PASS——产品码类型闸归零(R1/R2/R3)` | 通过 | -| d19 产出物 | `reports/` 三件在盘(实现报告/代拍板/反哺点) | 通过 | - -### 2.2 卡② r1-1–r1-4+范围扩展 - -| 单元 | 盘面实据 | 判定 | -|---|---|---| -| r1-1 投影五函数 | `hud_presenter.gd:230/245/257/280/293` 五静态函数在盘;逐行对拍 master `main.gd` 原函数体=除接收者/参数化外**逐字一致**(本审计机检逐函数 diff:`status_curios` 仅成员→参、`career_data`/`save_select_data` 仅签名行变化、`status_data` 仅 `run_scope.runner`→`runner` 等 7 处参数化) | 断言入口不实:报告记「hud_presenter_test+ui_struct_probe 直调面」,`tests/unit/presentation/hud_presenter_test.gd` 全文零引用这五个新静态件(只测 outcome_text/board_variant_lines)。真实承载面=`probe/ui_click_probe.gd:789`(sv7 `_status_data()` 八键对拍)+`probe/ui_struct_probe.gd:60/94`(`_open_career`/`_open_save_select` 真开层链)+冒烟/系统测试。判 **通过(附更正)**,见 F-1 | -| r1-2 解析回调四函数 | 新件 `presentation/board_resolvers.gd`(67 行)四函数齐(`debug_hero_override`/`identity_lookup`/`variant_mask_lookup`/`variant_mark_lookup`);对拍 master `main.gd:753-797`=除 `run_scope.settlement`→`_settlement_getter.call()` 外逐字一致;注入点 `main_ui_build.gd:200-205`(三参注入,与卡片 v1.2 接口块一致) | 通过 | -| r1-3 文案三件 | `hud_presenter.gd:302/310/325`;逐字对拍同上(`phase_text` 仅四成员改参);main 消费点 `main.gd:493/470/476/858` | 通过 | -| r1-4 探针直引零改动 | `main.gd:492`(`_gp_summary_text`→HudPresenter)、`:659`(`_status_data`→**MainOverlayFlow**)、`:726`(`_camp_data`→**MainOverlayFlow**);五处探针直引原行号原样在盘(ui_click_probe:74/101/112/789、ui_struct_probe:244),两探针文件本批零改动(不在 diff 内) | 通过(形态偏差:卡片接口块写「三件薄委托零参转发 HudPresenter」,实为两跳委托 main→main_overlay_flow→HudPresenter;语义等价,卡片代拍板白名单外但 commit b0d92524 与 §2 已记「三薄委托」,判为措辞不精确) | -| 范围扩展四件 | `main_ui_build.gd` 238/`main_adventure_flow.gd` 213/`main_overlay_flow.gd` 155/`main_debug_flow.gd` 105 行;main 1506→946;冒烟 17/17 与 probe --click/--full 读数在 commit `b0d92524` 消息在案,回执 00/03/04 号闸 green | 通过(属代拍板 B-2,卡片 §5「不做」明写 `_build_ui` 留驻——超出卡片范围已由代拍板单申报待追认,本审计不判越权) | - -### 2.3 卡③ r2-1–r2-3 - -| 单元 | 盘面实据 | 判定 | -|---|---|---| -| r2-1 瓦片族出根 | `views/tile_palette.gd`(261 行):装载缓存 7 成员(`:45-53`)+六 static(`flip_tex:123`/`normalize_floor_luma:134`/`srgb_lin:160`/`digit_ink_mode:170`/`contrast_y:198`/`ink_y:203`)+查表族(`is_wall:219`/`wall_tile_for:242`/`wall_center_tile:250`/`floor_tile_for:260`)+棋盘态注入 `bind_board:60`;`terrain_tile_sets_test.gd` 19 检查本审计实跑绿;`ui_contrast_probe.gd:140-150` 改引 `TilePalette.*` | 通过,**附未申报的算法重构**(见 5.5 与 F-3) | -| r2-2 英雄族出根 | `views/hero_sprite.gd`(152 行,`extends Node2D` 符合卡片契约 4 硬约束)八函数齐(`update_hero:52`/`place_hero:75`/`frame_flash:99`/`notify_move:116`/`play_dig_bounce:122`/`feet:139`/`hand_point:146`/`sprite_scale:151`);board_view `:158-160` bind+`add_child`,`:397/403/409/414/426/431` 同签名委托;真实入口断言在 `probe/ui_input_probe.gd:293-352`(case⑦ 落位 ≤0.5px/case⑧ 材质链/case⑨ 纹理朝向/case⑩ 缩格重排与复位),该文件本批零改动 | 通过 | -| r2-3 board_view 改引 | `board_view.gd` 937 行;13 条转发属性实测在 `:84-124`(7 条指 `_palette`、6 条指 `_hero_sprite`);`HERO_SPRITE_SCALE:44`/`HERO_GROUND_FRAC:45` 为指向件常量的**别名**(非重复字面值,无双源);旧址符号 `_normalize_floor_luma`/`_digit_ink_mode`/`_wall_center_tile`/`_hero_feet` 在 board_view 已不存在(本审计 grep 证实)→ 卡③ T2 规定的撤销形态成立 | 通过 | - -### 2.4 卡④ r3-1–r3-3+契约 5 - -| 单元 | 盘面实据 | 判定 | -|---|---|---| -| r3-1 令牌池出根 | `core/curio_deck.gd` 259 行,卡片点名的 11 方法全在盘(`build:39`/`reset:31`/`token_identity:53`/`find_token:60`/`pool_stock:73`/`stock_snapshot:83`/`consume_deck_token:92`/`consume_pool_purchase:107`/`cap_ok:118`/`feat_ticket_consider:132`/`draw_candidates:150`)+四个内部helper;宿主委托实测 8 处:`adventure_runner.gd:356/443/455/460/465/634/639/644`(报告记 `:355/:443/:457/:465/:471/:641/:647/:654`,其中 :471 指向 `_finish_curio_pick`=非委托,:457/:641/:647 为空行,:654 在 `_redeem_feat_ticket` 体内);套件实跑 `adventure_feats_test`=138 checks 全绿,入口为 `start_adventure` 真链 | 通过(委托行号指针不实,见 F-2) | -| r3-2 商店族出根 | `core/shop_session.gd` 339 行 21 函数同形;宿主委托 17 公开+2 私有=`level_runner.gd:780/784/788/792/796/801/807/812/816/820/824/828/832/836/840/844/848/852/856`(报告记 `_spend_discounted:801`、`_shop_draw_shelf:812`=精确);`set_shop_pool` 四参签名与缺省 `Callable()` 逐字对齐;实跑 `level_shop_economy_test`=186 checks 全绿;`meta_progression_forge_test.gd:733/736` 经宿主私有委托走件内折价(`=80`/`=1` 下限)在盘 | 通过 | -| r3-3 断点单源 | `adventure_runner.gd:864 const BREAKPOINT_FIELDS`(27 键)+`_bp_parts():907`+`_bp_read():951`/`_bp_strategy():961`/`_bp_read_int():966`;`resume_adventure` 现 36 行(`:149-184`)+五小函数(`:188/204/230/252/264`);报告记 `:857`/`:909`=漂移 7/2 行。二次消费面 `:705-707` 与 `:762` 改走 `_bp_read`(node_id/visited_ids/aux_items)在盘。断言 `breakpoint_resume_test.gd:191/199/206`〔wr2〕三行在盘 | 通过(单源覆盖面见 5.2) | -| 契约 5 接线行 | `adventure_runner.gd:745` `_runner.set_shop_pool(_curio_deck, _consume_pool_purchase, _feat_ticket_consider, _curio_cap_ok)`——报告记 `:745`=精确,四标识符全指委托/转发面,参数序不变 | 通过(入口标签不精确:报告写「pool_case_d_test 直调 set_shop_pool」,该处 `tests/unit/core/pool_case_d_test.gd:42-44` 自构 `LevelRunner` 直调=下层直调;接线行的真链覆盖在 `tests/unit/core/level_shop_economy_test.gd:1251 start_adventure(seed, {"shop_mode":"pool"})`。判 **通过(附更正)**) | - -### 2.5 附录 A 断言尾缀索引核对 - -本审计用 `git diff master...HEAD -- game/tests game/probe` 抽出全部新增尾缀,与附录 A 逐行对: - -- 新增带尾缀行计数=wd1×2/wd2×5/wd4×3/wd5×2/wd6×1/wd7×1/wd8×3/wd9×5/wd11×3/wd12×11/wd14×2/wd16×2/wr2×3,共 44 行;**附录 A 的 44 个 `文件:行` 指针全部命中,无一空指**(盘面 grep 复算见第二节各行)。 -- 删除行中的尾缀计数=**0**(`git diff ... | grep "^-" | grep 〔w*〕` 空输出)→ 无断言被删。 -- 「盘面新增带尾缀断言只有〔wr2〕三条」=属实(全库 grep 〔wr1〕/〔wr3〕/〔wr4〕 零命中)。推论:卡②③④ §8 表里挂在 wr1/wr3/wr4 行的「验收测试」从未以尾缀断言落码,由闸读数与件核承载——报告如实说明其语义,但卡片把闸读数写成尾缀断言属上游口径混用,记入第七节未覆盖面旁的观察。 - ---- - -## 三、检查二:撤销证明抽查(四条) - -### 3.1 卡① d1(证据复核) - -commit `1b9736f0` 消息明文记录「回退 d1 hunk → 同容异实例断言红(A 单据被 B 死亡误废),还原复绿」,与实现报告附录 B 首行读数(`[FAIL] …〔wd1〕`+`1/44 项失败`→`all green (44 checks)`)一致。可核性独立成立:判定符现值在 `damage_controller.gd:216`,回退即回到 `==`;夹具断言的判别力由 `damage_controller_test.gd:231` 前置检查(`a == b and not is_same(a, b)`)保证扫描面非空——非空转绿结构成立。 - -**判定:通过(取证级,本审计未重跑)。** - -### 3.2 卡③ r2-3(证据复核+结构可核性) - -commit `5c9c5f80` 消息逐字记录了与报告附录 B 相同的红行与复绿读数(19 检查复绿+error_scan exit 0)。本审计独立核其结构前提:`BoardView` 现盘面**不存在** `_normalize_floor_luma` 静态函数(grep 证实),故「测试改引回退旧址」必然产生 `Parse Error: Static function "_normalize_floor_luma()" not found in base "BoardView"` → 该红不由注入造成,属回退本批 hunk 的自然后果。 - -**判定:通过。** 性质提示:它证的是「旧址符号已消亡」,等价性判据本体在 render_gate(棋盘三图 changed=0.00%,且基线由卡① T3 `7389618f` 换代、早于卡②③——所以卡③ 的 0.00% 读数是相对自身改动量的有效对拍)。 - -### 3.3 卡② r1-3(证据复核+取证缺口属实) - -结构前提本审计已核:`main.gd:493` 直呼 `HudPresenter.gp_summary_text(...)`,删承接体必致 Parse Error(报告记红行指向 main.gd:493=精确)。commit `b0d92524` 消息把撤销证明写作「→Parse Error 红→还原复绿」在案,而报告附录 B 该行自报「**缺该撤销的复绿日志**」。两处口径不一致,报告的下调比 commit 消息诚实。 - -缺口现由批级回执补上一半:回执 `00 冒烟`/`03 探针 --click`/`04 探针 --full` 在 HEAD 全绿,可作「还原后盘面复绿」的指认,但不是逐 hunk 复绿原始日志。 - -**判定:通过(附取证缺口申报,报告已自报)。** - -### 3.4 卡④ P-B(本审计实跑复现) - -动作:删 `game/core/adventure_runner.gd` 第 888 行 `"refresh_stock": {"strategy": "clamp"},` 一行 → 单件直跑 → `git checkout --` 还原 → 再跑。 - -变异后原始输出(红行原样,未加未减): - -``` -[breakpoint_resume_test] FAIL — P8 键集与冻结清单逐位一致(27 键契约;增删键即红)〔wr2〕 -ERROR: [breakpoint_resume_test] FAIL — P8 键集与冻结清单逐位一致(27 键契约;增删键即红)〔wr2〕 -ERROR: [breakpoint_resume_test] 1/59 项失败 -``` - -还原后(同命令再跑): - -``` -[breakpoint_resume_test] PASS — 断点往返/续玩确定性/幂等/退出清断点/装配存档回归/生成图换代/字段单源 全绿 -``` - -`git status --short` 输出为空(本审计自身零残留)。还原前变异态未提交,还原用 `git checkout --` 只回退该一个文件,未触碰其他改动。 - -读数与报告附录 B 记的红行/计数(1/59)**逐字一致**,且形态合规:回退本批 hunk、非注入式开关。 - -附带观察(不判缺陷,供写卡段参考):三条 wr2 断言里只有「键集==冻结 27 清单」承力。删一个登记项时,「登记键集==写侧实拼键集」不红——因为 `bp[key]` 由遍历 `BREAKPOINT_FIELDS` 生成,两侧同源,该断言结构上只能发现「登记了但 `_bp_parts` 没拼」的单向偏差;「策略标签落词表」同理对已登记键恒真。 - -**判定:通过(实跑复现)。** - ---- - -## 四、检查三:测试削弱审查 - -### 4.1 删除行全清单(`git diff master...HEAD -- game/tests game/probe`,24 行删除逐条归位) - -| 文件 | 删行 | 性质 | 判定 | -|---|---|---|---| -| `game/probe/ui_contrast_probe.gd` | 4 | `BoardView.*`→`TilePalette.*` 换址(常量×2+注释+静态调用),判据零变 | 非削弱 | -| `tests/unit/core/adventure_feats_test.gd` | 1 | 注释级退役符号注记(`_token_identity`→`CurioDeck.token_identity`),commit `48248b21` 申报 | 非削弱 | -| `tests/unit/core/board_gen_reward_test.gd` | 1 | `legality(b1, row, _db)`→`legality(b1, row)`,卡片 §8.3 申报在案 | 非削弱 | -| `tests/unit/core/breakpoint_resume_test.gd` | 1 | `Kit.finish` 的 PASS 消息串追加「字段单源」四字 | 非削弱 | -| `tests/unit/core/level_shop_economy_test.gd` | 2 | ①注释「15 固定锚」→「表 knob」;②`expect_p = int(ceil(15.0*…))`→`int(ceil(float(_db.shop.relic_base_price)*…))` | 判据口径改动,**非削弱**:同文件新增 `:1293-1295` 用硬编码 15.0 反算 `relic_price_pre` 并与件内 `pool_price("relic")` 逐位对拍〔wd11〕+`:262` 钉表值==15.0,硬锚未丢 | -| `tests/unit/presentation/terrain_tile_sets_test.gd` | 11 | 全部是 `BoardViewScript.`→`TilePaletteScript.` 换址(含 `TILE_*`/`DIGIT_INK_CR_*` 常量直读与两次静态调用),断言表达式逐字不变 | 非削弱 | -| `tests/unit/presentation/theme_projection_test.gd` | 4 | ①`:693` V4 断言判据换代;②`:810/:825` 两处注释同构引用改址 | 见 4.2 | -| 三张 `golden/board_L*.png` | 二进制 | 基线换代:成因 `2f3a87e9`(与本批零因果)+用户裁「重制三张」+`tests/golden/README.md` 追加判定记录(6 行,卡① 契约 6 要求的显式呈报在案) | 非削弱(申报齐) | - -阈值/扫描面类改动零处:`run_all.gd` 只加一行套件登记(`:62`);`error_scan`/`audit_type_hints`/`gdstyle_baseline.txt`/`tools/` 全目录零改动(`git diff --name-only master...HEAD -- tools/` 空输出)。 - -### 4.2 卡① §8.3 那句话的属实性 - -卡片原文:「零削弱;唯一既有测试改动=board_gen_reward_test:145/board_generator_test 的 `legality` 调用签名随 d14 同批改;既有断言零删除零放宽」。 - -- 「零削弱」「既有断言零删除」——**属实**(见 4.1 全清单+删除行尾缀计数 0)。 -- 「唯一既有测试改动」——**不成立**。卡① 自己触发的既有断言改动还有一处:`theme_projection_test.gd:693` V4 由「断言旧 GOLD_DEEP 判据在位」换成「断言分档布尔在位**且**旧判据消失」。该改动在 commit `2ed81668` 消息与实现报告 §2 d2 行(「V4 断言随卡① 换代补齐」)有申报,但不在卡片 §8.3 的申报句里。 -- 深层原因是卡片自相矛盾:d2 改法明写「颜色比较判据删除」,契约 5 又写「既有断言零删除零放宽」,而 V4 断言的正是那条待删判据的存在——两条约束在同一个输入上给不出同一个答案(agents.md「spec-AC 一致性」红线)。实现段的处理(判据换代、只增强不放宽、留痕)是这一矛盾下唯一不破红线的走法。 - -**未申报的其余改动:无。** 除上表 8 个文件面外,`game/tests`/`game/probe` 无其他既有件被触碰。 - -### 4.3 `standee_fx_sweep_test.gd` 断言绑面审查 - -| 断言族 | 绑的面 | 判定 | -|---|---|---| -| wd12 站桩两口(`:81-91`) | 经真实解析口 `stage.on_step_row_fx(...)`/`stage.enemy_standee_center(...)` 驱动,判据=**被闪白的站桩是哪一枚**(`s2._flash_count==1 and s1._flash_count==0`)与中心点等值(`world_to_shell(s2.position+s2.box*0.5)`)。`_flash_count` 是 master 既有的探针出口计数(`standee_body.gd:260`,非本批植入的观测桩) | 绑提交面——兜底路径(名匹配)若胜出,闪白必落 s1,断言即红 | -| wd2 蓄力四断言(`:95-101`) | 读 `set_pending` 写入的判定标志位 `_intent_charge` | 绑决策落点,**未绑运动学后果**(③蓄力缓推分支实际入 tween 未取证);配合 `theme_projection_test:693` 的源码文本扫描,d2 的行为面缺一条可观测断言。卡片 §8.2 wd2 规定的是「真实调用链+回退红」,实现按卡执行——记为上游判据设计面,非实现段削弱 | -| wd4 句柄三断言(`:108-114`) | 绑真 Tween 实例生命周期(首建 `is_valid()`/重开后旧句柄 `not is_valid()`/close 后元数据清空),经真实 `open_victory`/`close` 开合链 | 绑提交面,判别力强 | -| wd5/wd8 互斥断言(`:119-121`、`:130-132`) | wd5 读真控件属性 `_dbg_btn.disabled`;wd8 在注入 driver 的回调内取 `is_running()` 读数(`drv.seen == [true, true]`)+收尾面板态 | 绑提交面;driver 注入位是 master 既有面(`tune_sim_controller.gd:_init(..., driver = null)`),非本批造桩 | - ---- - -## 五、检查四:卡④ 等价性专查(本批最大风险面) - -### 5.1 两拆件族逐字对比(对 `git show master:` 原实现) - -- **CurioDeck**:11 个迁出方法与 master `adventure_runner.gd` 同族实现**逐表达式一致**(接收者化:`_curio_deck`→`deck`、`_curio_taken`→`taken`、`_feat_ticket_*`→`ticket_*`)。`consume_pool_purchase` 的未知 kind 拒绝分支(`curio_deck.gd:110-112` push_error+false)与 master `:478-480` 逐字同;`feat_ticket_consider`(`:132-143`)与 master `:775-786` 同——限购先返→配额先返→`pity >= step or rg.randi_range(1,100) <= chance`(短路保留:保底命中时**不掷点**)→命中清零 pity 并 `appeared += 1`/否则 `pity += 1`,记账先后零变。 -- **`reset()` 不清凭证三计数**:属实且正确——master `_reset_curio_state`(`:250-265`)本身就不清 `_feat_ticket_pity/_sold/_appeared`,`_feat_ticket_appeared` 在 master 亦不入断点、不在任何处复位;改后由 `CurioDeck.ticket_appeared` 同样只由 `_init` 归零。宿主 `_reset_curio_state`(`adventure_runner.gd:282-292`)与 master 除首两行改 `_curio.reset()` 外逐字同。**代拍板 B-5 的申报成立。** -- **RNG 调用次数与顺序**(非 Boss 渠道):`_candidate_pool`(过滤,零掷点)→ `rng.open_stream("reward_pick")` 取流点同位 → `_shuffle(pool)`=`pool.size()` 次 `randi_range(i, size-1)`(与 master 内联循环同式同次)→ 由已洗的 pool 生成 sub(tier_rank≥excellent)→ `_shuffle(sub)`=`sub.size()` 次 → `_draw_exposure` 收集循环零掷点 → `feat_ticket_consider` 至多一次 `randi_range`。Boss 渠道:取流后直接 `_draw_weighted`,每轮 1 次 `randi_range(1, total)`,剔牌重掷走同一次数(`continue` 回循环再掷),与 master 内联加权段逐次同。**逐位核对通过。** -- **ShopSession**:21 函数同形,成员→`_refs/_hooks` 读取(`_gp/_set_gp` 落 `wallet`=宿主 `_ledger` 同一字典对象,`level_runner.gd:33-35` 的 `gp` 属性也读写 `_ledger["gp"]`,无双账本)。`buy_product` 的出账→加算区→购血 hook→计数→emit 次序与 master 逐行同;`_emit` 取的是** mutation 后的 `_gp()`**=master 的 `wallet_changed.emit("buy", gp, …)` 同值。`_draw_shelf` 的内联洗牌抽出为 `_shuffle`,次数同;`pool_ticket.call(stream)` 仍复用同一 `shop_draw` stream(零新 scope)。 -- **换代重绑面核实**:`_bind_shop()` 五处(`:165/:205/:646/:670/:688`)恰好覆盖宿主引用被**整体替换**的全部位点——`_ledger` 重赋值(`:151`、`:202`)、`_attunement` 重赋值(`:645`)、`_tree_rules` 重赋值(`:669`)、`run_ledger` 重赋值(`:687`);`level_row` 为原地 `assign`(`:329`)故不需重绑,件内 `_level_index()` 每次现读。`:162 gp = 0` 落在 `_bind_shop()` 之前,但窗口内无件读点(件读点在 `_enter_node` 之后),无失真。**核对通过。** -- **一处需知情的既有怪癖(非本批引入)**:`set_shop_pool` 捕获的是数组**引用**,之后 `_curio_deck = restored`(续玩)与 `reset()` 都是整数组替换,会使 `ShopSession.pool_deck` 指向旧数组——master 的 `_pool_deck` 同样如此,改后逐位不变(本批未「顺手修」=正确)。 - -### 5.2 `BREAKPOINT_FIELDS` 27 键逐键核(机检+代码验,未照抄报告 §3.2) - -机检结果(本审计 python 对拍,别名词表归一后): - -- 写侧键**序**:master `_snapshot_breakpoint` 字面量 27 键 = HEAD `_bp_parts()` 27 键 = HEAD `BREAKPOINT_FIELDS` 27 键,**三者序列完全相同**(`mk==hk: True`/`reg==hk: True`,对称差集为空)。 -- 写侧**取值表达式**:27 键逐键字符串比对(把四个转发属性名归一后)**零不一致**——含 `bonus` 四子键、`shop_shelf`/`shop_visit_buys` 的 `_runner != null` 三元式、`rng_counter: rng.counter()`、`graph_gen_v2: 1`。 -- 读侧覆盖面:master 读 25 键(另 2 键 `shop_shelf`/`shop_visit_buys` 写侧专用、两侧都不读,改后同);HEAD=16 键仍由 `_resume_*` 段内手写 `bp.get(...)`+9 键改走 `_bp_read`/`_bp_read_int`(attunement_charges/bonus_deck/adventure_counters/attune_refresh_y/refresh_stock/vision_counter_used/node_id/visited_ids/aux_items),两集合并集=master 的 25 键,**无增无减**。 - -逐键语义判定(与报告 §3.2 对照表交叉): - -| 键类 | 判定 | -|---|---| -| keep_on_null 三键(`attunement_charges`/`bonus_deck`/`adventure_counters`) | **等价成立,但靠调用点守卫而非派生段**。`_bp_read:955` 的类型判别写作 `not (v is Dictionary or v is Array)`——比改前各站单一守卫**宽**(Dictionary 值喂给 `is Array` 站、Array 值喂给 `is Dictionary` 站,改前都会被拒、改后 `_bp_read` 都原样返回)。三处调用点各自保留了原 `is Dictionary`/`is Array` 判定(`:181`、`:214`、`:257`),净行为逐位不变。若日后某调用点删掉自身守卫,宽判别就成漏洞——登记标签与派生段职责边界不清,属可修的实现级技术债(不阻塞合回) | -| clamp 四处 | `vision_counter_used`(`:260`)与 `refresh_stock`(`:259`)经 `_bp_read_int` → `maxi(0, int(bp.get(k, 0)))`=master 逐字同 ✓。**`hero_hp` 与 `bonus` 四子键不经派生段**:`:189` 仍写 `int(bp.get("hero_hp", 0))`(master 同)、`:191-194` 仍写四行 `maxi(0, int(...))`(master 同)。改前改后同义 ✓,但 `hero_hp` 的 `clamp` 标签名不副实(其 clamp 实为 `_resume_params:239` 的 `mini` 上限钳制,非 `maxi(0,…)`),且两键未真正单源化。**报告 §3.2 的 ✓ 列在「语义一致」义上成立,在「由登记派生」义上仅 9/25 键成立**——「单源」是半单的,报告未披露这一层 | -| params_fallback 一处(`attune_refresh_y`) | `_bp_read_int("attune_refresh_y", bp, 0, params)` → `maxi(0, int(bp.get(k, int(params.get(k, 0)))))` = master `:205` 那行**逐字同式** ✓ | -| plain 类(余 18 键) | 语义逐键同义;报告把 `shop_shelf`/`shop_visit_buys` 的「读侧实义」写成「`shop_bp_*()` 取数」——那是写侧取数,两键两侧都无读点(轻微表述错置,不影响结论) | -| `dual_merge`/`token_migration` | `bonus_taken` 双向清零回填+身份维键(`:206-210`)与 master 逐字同;`bonus_pending` 候选字符串→普通档令牌归一+relic 三段键(`:264-279`)逐字同 ✓ | -| 未登记键 fail-fast | `_bp_read:952-953` 对未登记键 `push_error` 后**继续按 plain 读**,`_bp_strategy` 缺省回 `"plain"`——注释称「fail-fast 不静默按 plain 兜」,码实为「报错+软兜」。行为无差(wr2 键集断言保证不可达),但注释与码不符,且与红线「fail-fast=致命中止无软回退」的口径有张力 | -| `resume_adventure` 拆分 | 段序核实:ledger→curio 态→参数→计数器→pending/result→`_resume_bp`+attunement_charges→`_start_current_level`。与 master 唯一差异=`bp_pending` 的**读取点**从计数器段之前挪到之后(`:167`),其间无对 `bp` 的写点,纯局部变量取值时点移动,零行为差 ✓ | - -### 5.3 断点五时点零增减零迁移 - -`git grep -n _snapshot_breakpoint` 两侧逐一对所属函数: - -| master | 所属函数 | HEAD | 所属函数 | -|---|---|---|---| -| :509 | `_finish_curio_pick` | :483 | `_finish_curio_pick` ✓ | -| :637 | `_refresh_curio_common` | :611 | `_refresh_curio_common` ✓ | -| :806 | `_redeem_feat_ticket` | :665 | `_redeem_feat_ticket` ✓ | -| :934 | `_on_level_finished` | :806 | `_on_level_finished` ✓ | -| :983 | `_forward_choices_presented` | :855 | `_forward_choices_presented` ✓ | - -计数 5↔5,所属函数一一对应,且 `_finish_curio_pick`/`_refresh_curio_common` 两函数体与 master 逐字节相同(`diff` rc=0);全仓无第六处调用(`shop_session.gd:303` 只是注释提及)。**契约 3 后半成立。** - -### 5.4 转发属性读写面与双写面 - -| 宿主 | 条数 | 只读转发 | 可写回件内 | -|---|---|---|---| -| `adventure_runner.gd:60-77` | 4 | 0 | **4**(`_curio_taken`、`_curio_deck`、`_feat_ticket_pity`、`_feat_ticket_sold` 全带 set) | -| `level_runner.gd:50-66` | 8 | **7** | 1(`_shop_purchased`;其 set 用于 `:164/:204` 两处把冒险级 `purchased` 字典别名进来,功能必需) | -| `board_view.gd:84-124` | 13 | 11 | 2(`_hero_tex_idle`、`_hero_last`——探针与 battle_stage_view 的既有写点保号,`board_view.gd:164-165` 装载时即写) | - -双写面核查:报告 §4 第 3 条「测试仍从宿主转发面直改件内多重集」三处指认**逐处属实**——`adventure_feats_test.gd:390-393`(`a11._curio_deck.remove_at(di)`,经 getter 拿到活数组下标写回)、`feat_vouchers_test.gd:92/98/108`(`a._curio_deck = [...]`,走 setter)、`level_shop_economy_test.gd:1351-1353`(`a8._curio_deck.remove_at(...)`)。 - -但该条**披露不完整**:生产码同样经宿主面写件内状态——`adventure_runner.gd:207-208`(`_curio_taken[id] = …`)、`:210`、`:222`(`_curio_deck = restored` 整数组替换)、`:253-254`(`_feat_ticket_pity/_sold` 赋值)、`:652-657`(`_redeem_feat_ticket` 内 `+=`/`-=`);`level_runner.gd:164/204`。即「件封装是名义的」不止测试面,续玩与兑付主链也是。存储仍是单点(件内一份,无副本),所以**不构成双源**,但「谁写件内状态」的写者数在宿主侧,P0 待办条目应据实扩面。 - -一处须留证的疑点(本审计实跑消解):`board_view.gd:165` 对**只读**转发属性 `_hero_tex_walk`(`:108-110` 仅 get)赋值。若该写不落件内,英雄行走帧永不切换。实跑 `res://probe/ui_input_probe.tscn` → 十八断言全绿、零 `SCRIPT ERROR`/零 setter 报错,其中 case⑨ 断言「移动后 `sp.texture != bv._hero_tex_idle`」——判据成立说明写点确实落进了 `HeroSprite.tex_walk`,行为无异常。留此记录供后续批知悉:只读转发属性上存在宿主写点,属计数守恒装置的连带面(P0 条目扩面时一并裁)。 - -### 5.5 卡③ 查表族与墨族被重构(非改名级变化) - -对 master `board_view.gd` 逐函数比对,四个函数不是搬运而是改写: - -- `_wall_tile_for`:master 18 段 if-链 → `WALL_MASK_TILE[16]` 查表+`_neighbor_mask` 位掩码(`:226-246`)。 -- `_wall_center_tile`:master 四连 if+三段阈值 → `WALL_DIAG_DIRS/WALL_DIAG_TILES/WALL_CENTER_PICK` 表驱动(`:250-255`)。 -- `_digit_ink_mode`:master 内联均值+四对比度+标志位 if/elif/else → 抽出 `_mean_of`/`_family_contrasts`(Vector4 四元组)+布尔重导出(`:170-195`)。 -- `load_board_tiles`:镜像件翻转六行去掉 `as Texture2D` 冗余转换,`var t := {}` → `Dictionary[String, Texture2D]`。 - -等价性本审计穷尽验证:16 个掩码组合逐一对拍 master if-链输出,**零不一致**(位序 `nf<<3|ef<<2|sf<<1|wf` 与表下标吻合;0/1 两块都落 `wall_w`,与 master 收尾分支同);中心三变体 `WALL_CENTER_PICK` 分布=6/2/2,与 master `m<6`/`m<8`/else 的 60/20/20 同;`crs.x/y/z/w` 与 master 的 p90_bright/p90_dark/mean_bright/mean_dark 及返回字典键一一对应,`dark`/`plate` 三态重导出真值表与 if/elif/else 同。 - -**但卡片契约 1 写的是「禁顺手改算法、禁合并/删函数、装载器/查表/英雄族除接收者化与缓存成员随迁外逐字一致」,代拍板白名单只给到「命名与函数名前缀取舍」**——新增三张 const 表与两个 helper 属白名单外的结构裁断,实现报告与代拍板单均未申报(grep 全套 reports/ 无掩码表/查表重构字样)。等价性成立 ⇒ 不必回退代码,但**须补追认**。 - ---- - -## 六、未通过项与例外清单 - -| # | 事项 | 缺什么 | 归属 | 建议处置(非裁断,供用户决) | -|---|---|---|---|---| -| F-1 | 实现报告 §2 卡② r1-1 行「真实入口=hud_presenter_test」 | `hud_presenter_test.gd` 零引用五个新迁静态件;真实承载面是 `ui_click_probe.gd:789`/`ui_struct_probe.gd:60/94` | **实现段可修**(只改 reports/ 一句) | 把该格改为指认实际承载面;顺带记:`career_data`/`save_select_data`/`bonus_summary_text` 至今无单元级断言,只有探针开层链与冒烟,若要为「投影」建回归断言属下一批 | -| F-2 | 卡④ r3-1 委托行号 8 处中 5 处不指委托(`:471` 指 `_finish_curio_pick`、`:457/:641/:647` 为空行、`:654` 在 `_redeem_feat_ticket` 体内);r3-3 记 `const` 在 `:857`(实 864)、`_bp_parts` 在 `:909`(实 907);卡① d4/d12/d15/d17 行号漂移 1~15 行 | 取证指针精度——实体全部在盘,签名与语义本审计已逐个核实 | **实现段可修**(指针订正,零代码面) | 附录 A 的 44 个指针零错,可只校 §2 生产面指针 | -| F-3 | 卡③ r2-1 查表族/墨族四处**算法重构**未在报告/代拍板/反哺点任一处申报 | 违卡③ 契约 1「禁顺手改算法、禁合并/删函数」字面;等价性本审计穷尽证实(16 掩码+60/20/20+三态真值表零差) | **实现段可修**(补代拍板追认行+实现报告逐字迁移核对表补注) | 追加 B-8 记「为压 S13 函数阈值而表驱动化,判据零变」,请用户追认;不建议回退 | -| F-4 | 卡① d11/d16a 改法「ConfigDB 新增三访问器」被 `2ed81668` 撤销,消费面改直读 `db.shop.*`/`db.hero.*` | 卡片钉值表与盘面不符(契约 1「钉值表逐字」被后续批内提交推翻);红线「数值只经 ConfigDB 单点读取」义上仍守(`db` 即 ConfigDB,无自建 `load()` 表路径);已在报告 §2 两行括注+commit 消息申报 | **实现段已修(申报在案)**,卡片文本属历史件不改 | 记入反哺点第 8 条随文档批核一次「行访问器直读」是否等同「ConfigDB 单点」 | -| F-5 | 卡②③ 缺「逐字迁移核对表(逐函数:原行界→新落点)」交付物 | 两卡各自完成定义 2 证①明文要求;报告 §3.1 只给卡④ 两拆件族。本审计已用机检补上证据(§2.2/2.3 的逐函数 diff 结论) | **实现段可修**(补一节表,或按吸收即死并入本审计) | 不必重做,把本审计 §2.2/2.3/5.5 的比对结果作为交付物指回本文件即可 | -| F-6 | 实现报告 §1 读数表「卡片预期」列混记机检线 | 卡② 成文预期是 ≈1370–1380、卡③ 是 ≈880,表中分别填「≤999(S13 文件线)」「≤1000」并判「达成」;按卡片口径 main 946 是**超出**预期 424 行(因代拍板 B-2 扩面)、board_view 937 是**超出** 57 行 | **实现段可修**(改列名或加一行如实注记) | 与卡④ 两行「未达成」的写法对齐,同一把尺 | -| F-7 | 卡① §8.3「唯一既有测试改动」与 V4 换代冲突(卡片自身 d2 改法×契约 5 互斥) | 上游卡片 spec-AC 不一致;实现处理正确且已留痕 | **须退回上游**(写卡段反哺,不在本批修) | 反哺点第 7 条同源:卡片钉「删除某判据」时必须同时列「该判据的在役断言如何换代」 | -| F-8 | 闸回执 commit(`9e3e2667`)落后 HEAD(`356029d0`)一个纯文档提交 | 域覆盖:追加件只在 `reports/`,代码域零变化;但 guard 按 diff 推域校验回执时可能对不上位 | **工程/流程面**(非四卡缺陷) | 合回前重跑一次所选域档,或确认 guard 接受文档件后追加 | - -无一项属「断言缺失/阈值放宽/入口造假」级;F-1 与 F-2 是取证指针不实,F-3 是白名单外结构裁断未申报(等价已证),其余为口径与交付物完整度。 - ---- - -## 七、本审计未覆盖的面(如实列出) - -1. **未重跑全量闸**——遵指令。09 静态扫描(S11/S13)、10 熵增闸、06/07/08/08c/08d 文档域各闸、05 渲染闸、00-04 冒烟/系统/单元/probe,均**只采信回执**+卡③/卡② commit 消息读数。例外:本审计自跑了 `tools/audit_type_hints.py`(PASS)与六件单跑——`breakpoint_resume`(59,含变异复现两次)/`level_shop_economy`(186)/`adventure_feats`(138)/`standee_fx_sweep`(18)/`terrain_tile_sets`(19)/`theme_projection`(162)+探针单件 `ui_input_probe`(十八断言)——全部与报告读数一致、零 `SCRIPT ERROR`。系统测试/冒烟/渲染闸/其余探针段未自跑。 -2. **11 号回放确定性对拍未跑**(回执 NEED-APPROVAL)。它恰是卡④「拆件后 RNG 消费序不漂」最强的独立判据;本审计的替代证据是静态逐调用点序比对(§5.1)+同种子既有断言,**未做改前改后回放逐位对拍**。发起与否归用户。 -3. **未跑 MC/未动 `sim-data/` 锚**——遵「无数值改动零跑」;因此卡④ 的数值面等价只到「现算公式与读表值同义」这一层。 -4. **撤销证明只实跑了卡④ P-B 一条**。卡① d1/d2/d4/d5/d8/d9/d10/d12、卡② r1-3、卡③ r2-3、卡④ P-A/P-C 共 11 条=取证复核(commit 消息+报告附录 B 原文+结构前提独立核实),未逐条重演。原始日志目录 `.iter37-logs/` 已由 `6b23bc46` 删除,取证只能靠转录读数。 -5. **人工目检未做**(真人试玩项登记在实现报告 §5 七条,按红线只登记不阻塞)。呈现层拆件的观感等价证据止于 golden 逐像素 0.00% 与 probe 断言,未看实拍图。 -6. **未审计非本批文件**:`battle_orchestrator.gd`(卡④ 契约 5 明写不碰,diff 亦无该文件)、`save_manager.gd`(卡④ 论定 save 侧零键登记,diff 亦无)——两者本批零改动,未复核卡片论断本身。 -7. **未核 `game/tests/验证集清单.md` 陈旧度**(反哺点第 5 条在册,不受闸覆盖);未核 gdstyle 基线陈旧键(反哺点第 6 条)。 -8. **`iter37-拆分卡缺口-2026-10-25.md`(`356029d0`)与 P0 待办条目的内容一致性未审**——属另一会话产出,不在四卡验收面内;只核了待办条目里「adventure_runner :60-77 四条/level_runner :50-64 八条」的行号指认(属实)。 diff --git a/reports/需求卡片-game行为缺陷定点修批-2026-10-24.md b/reports/需求卡片-game行为缺陷定点修批-2026-10-24.md deleted file mode 100644 index ef159bc3..00000000 --- a/reports/需求卡片-game行为缺陷定点修批-2026-10-24.md +++ /dev/null @@ -1,313 +0,0 @@ -# 需求卡片:game 行为缺陷定点修批(代码结构审查 §二 行为缺陷+定点小件) - -- 版本:**v1.2**(2026-10-24 v1.0 成文→2026-10-25 grilling 第一轮修订 v1.1〔三阻断+九应修折入〕→第二轮复核**通过**,四条建议级修正折入定稿;复核结论见 §12) -- 来源:[待办清单](../待办清单.md)「game 代码结构审查批——行为缺陷定点修+结构重构候选」(2026-10-24 登记,来源=审计发现,设计面=工程直入,状态=待写卡+已写卡)(唯一事实源:工程直入证据指认=审计报告 [reports/审计-game代码结构审查-2026-10-24.md](审计-game代码结构审查-2026-10-24.md) §二+本写卡会话逐处 file:line 实查〔本卡 §3,含对审计三处读数的订正〕;规约依据=[code-conventions](../design/engineering/code-conventions.md) §2.1/§4+[test-conventions](../design/engineering/test-conventions.md) §9.4+2026-10-24 用户 P0 令〔行为改动验收只走撤销证明,禁注入造红实验〕) -- 归属:`game/core/`(damage_controller / minesweeper_rules / level_runner / board_generator / action_economy / unit_container / status_container / treasure_items)+ `game/presentation/`(main.gd / debug_overlay / tune_sim_controller / modal_stack / views 下 standee_body / battle_end_overlay / battle_stage_view / settlement_fx_view / board_view / ui_kit)+ `game/data/`(shop 表与 hero 表扩列)+ 断言面(四件既有单测扩展+一件新单测+system_test 两断言+run_all 清单行) -- 排期:未编入(卡片不预占迭代号;本卡估时 ≈3–3.5 工作日,低于编排硬下限 5 工作日,须与其他卡同批编入——归 iter-plan 段四查③) -- 开工门:U0(用户发令)+ grilling 复核门过(§12)+前置:本卡先于 R1/R2/R3 重构批落地(§0) - -## 0. 前置依赖与并行性 - -- **第〇步核对(2026-10-24 写卡会话)**:来源条目设计面=工程直入、状态=待写卡,条目现状带行号级证据(审计报告 §二+本会话逐处实查复核)→ **放行写卡**。 -- **条目拆分(2026-10-24 用户裁)**:条目原含「结构重构候选(报告 §三)」——五件已裁**全部另立**为 R1(main.gd 判离)/R2(board_view 拆件)/R3(runner 拆件+断点 schema 单点化)三卡,本卡缺陷修**先行落地**后重构再动。缺陷九件中的**⑦明细行枚举漂移划归条目④**(「架构与规范对齐批」,词表归 13 还是 15 是④的裁点,本卡不自答、不碰 `battle_orchestrator.gd:15-19` 头注与架构文档 B1→D1 行)——本卡实收 = 八件缺陷+定点小件七件。 -- **与卡②(S9 类型闸正则修复批,同批写卡)**:两批零 hunk 交叠(卡②改正则+补注解,本卡改缺陷点;grilling 实查确认:main.gd:565 vs :712/:1486、level_runner:632 vs :779/:895、unit_container:275 vs :55/:257/:270 皆同文件不同 hunk),先后皆可;本卡 d18 给 `board_view.gd:998` 的 `lumas_out` 补类型=替卡②消掉一件存量红,卡② T0 存量清点以其落位时盘面为准。本卡**新增代码全类型**(接口块签名已钉全类型——含返回类型)。 -- **与迭代 36(已于 2026-10-24 实现入库,commit 9468c70e 等)**:文案清扫批 u7–u9 触点 `board_view.gd:730/:778/:782` 在本卡两触点(:998 与信号载体行)带外、`battle_orchestrator.gd:797`(u13)与⑦头注 :15-19 不同 hunk、开场后缀批触点 :608-616 与本卡 :712/:1486 不同 hunk、状态面板瘦身卡触点 status_panel/main._status_data 与本卡零重叠——grilling 实查「同文件不同 hunk」成立;d10 信号载体 28 处含 `camp_overlay_view.gd`/`board_view.gd`(iter36 触过的文件),按**现行盘面**(iter36 已并入)T0 复核行号。 -- **与 gdstyle S13 闸(2026-10-24 已引入,8cdeb180)**:本批对产品码的改动落在 S13 扫描面(core/data/presentation 100 个 .gd)——改动禁产生基线外新违规(S13 差集判红)。 - -- **卡后换代注记(2026-10-25 补)**:迭代 35 卡②(5499eb9c)在本卡成文后合入——`level_runner.gd` 行号整体漂移(`_spend_discounted` :777→:764、`pool_price` :891→:878、relic 15.0 :895→:882,**字面量与散折单点确认仍在**)、`main.gd` :712→:710/::1486→:1484;缺陷身份与修法零变化,T0 复核按现行盘面重采行号。 -- **回放确定性对拍触面**:本批 diff 触 `game/data/tables/`(d11/d16 扩列)与 `game/core/damage_controller.gd`(d1)——11 号闸将判「触面、须用户批准发起」;本批无数值调整(表值=原字面量原样迁移)、MC 零跑,对拍是否发起归用户,闸以 NEED-APPROVAL 留痕不判红。 - -## 1. 目标与完成定义 - -**目标**:代码结构审查 §二 的八件行为缺陷(C6–C10+定点清单三件)+定点小件七件全部定点修复,每件带断言与撤销证明;数值字面量两处与英雄名一处迁入数据表(值原样);全库信号注释载体统一 `##` 且两条零契约核心信号补写成文。 - -**改动单元清单**(钉值表,🔴 契约 1): - -| # | 位置 | 现状(实查) | 改法 | 来源 | -|---|---|---|---|---| -| d1 | `game/core/damage_controller.gd:215` | `if t.source_unit == u:`——Godot 4 Dictionary `==` 是逐键内容比较。**生产不可达实证**(grilling 订正):`unit_container.gd:98` 每敌单位带自增 `handle_id`(:77/:114),任意两敌内容必异,真实 spawn 路径不会触发误作废;该行属**语义加固件**(引用同一性判定才是 `_on_unit_died` 的正确语义,且防御未来单位字典形态演化) | 判定改 `is_same(t.source_unit, u)`;定性=防御性语义加固(非行为修复——撤销不红属预期不适用,见 wd1 证明设计) | 审计 §二 C6(前提读数经本会话订正——审计所述「内容全同双敌」被 handle_id 排除) | -| d2 | `game/presentation/views/standee_body.gd:171-172/188` | `set_pending` 蓄力档落 `GOLD_INK`(:172),`_play_intent_motion` 却判 `GOLD_DEEP`(:188)——③蓄力缓推分支恒不可达死代码 | 判据改布尔:新增成员 `_intent_charge := false`,`pid in CHARGE_SKILL_IDS` 分支内置 true,`_play_intent_motion` 判 `_intent_charge` 分档;颜色比较判据删除 | 审计 §二 C7 | -| d3 | `game/presentation/views/ui_kit.gd:28` | `GOLD_BRIGHT` 注释宣称「GOLD_DEEP 语义岗〔standee:199 意图判据/board_view:35/level_map:237〕在役不动」——**三处引述全部失真**(grilling 实查:board_view:35 实为 `DEPTH_PX`、level_map:237 在 abandon 段、standee 意图判据即 d2 死代码,两文件 GOLD_DEEP/GOLD_BRIGHT 引用 grep 零命中) | 该括号引述整段删除(不留条件保留),意图蓄力墨改引 `GOLD_INK` | 审计 §二 C7 连带 | -| d4 | `game/presentation/views/battle_end_overlay.gd:281-287/156-161` | `_play_celebrate` 建 `set_loops()` 无限 tween,`close()` 只隐藏不 kill——每次胜利叠加一个永不停的 tween | 新增成员 `_celebrate_tw: Tween`;`_play_celebrate` 开头 kill 旧句柄再建新;`close()` kill+置空(全库先 kill 后建惯例=board_view:362-364 先例) | 审计 §二 C8 | -| d5 | `game/presentation/views/debug_overlay.gd:84` | `_dbg_btn.disabled = _ctx_locked`——调试钮不看 `_sim_running`(模拟钮 :101 看两态) | 调试钮 disabled 并入 `_sim_running`(`set_sim_running` 同步刷调试钮) | 审计 §二 C9 | -| d6 | `game/presentation/main.gd:712-717` | `_start_debug_levels` 守卫只查 `is_adventure or settlement != null`,漏批量模拟运行态;:717 `reset_sliders()` 会作废在跑测量的滑条读数 | 守卫补 `_tune_sim.is_running()` 即返(模拟运行期调试钮已由 d5 置灰,此处为信号绕过 disabled 时的第二道闸) | 审计 §二 C9 | -| d7 | `game/presentation/main.gd:1486-1491` | `_on_tune_simulate` 守卫只查 `settlement != null`,漏 `is_adventure` | 守卫补 `or run_scope.is_adventure` | 审计 §二 C9 | -| d8 | `game/presentation/tune_sim_controller.gd` | 无运行态查询面——`run()` :37 起 `set_sim_running(true)`、:62 收 `false`,main 侧无从互查 | 新增 `var _running := false`+`func is_running() -> bool`;`run()` 首行置 true、收尾置 false(run 无早退分支) | 审计 §二 C9 | -| d9 | `game/presentation/modal_stack.gd:21-38` | `open()` 对未登记层(`_list.find(m)`=-1)静默隐藏全部已开层;同层重复 open 时第二次 `hidden` 为空表(上方已隐)→ `_occluded[m]` 被空表覆盖,原被隐藏层 close 后永不恢复 | fail-fast+遮挡账**合并非覆盖**:`open()` 首行 find<0 即 `push_error` 且 return;重复 open 仍重扫上方可见层(V6 遮挡语义不变——modal_flow_test :40-48 在役断言依赖之),`_occluded[m]` 改**旧账∪新隐去重合并**(吞账缺陷修复);`close()` 对未登记层 `push_error` | 审计 §二 C10(修法经 grilling A-2 订正:幂等直返方案与在役断言冲突,改合并制) | -| d10 | `game/core/minesweeper_rules.gd:8-9`+全库 28 处 | 两条核心信号零契约成文(违 code-conventions §2.1);全库信号注释载体 28 处 `#` 非 `##`(不进编辑器 F1 帮助;grilling 复算 28/51 精确吻合) | :8/:9 按 §2.1 三要素(参数语义〔载荷键集按发射位并集:cell_revealed 四处 :129/:303/:310/:345、seal_judged :476〕/发送时点/消费方依赖)补 `##` 契约;全库信号声明行的 `#` 注释逐处换 `##`(注释内容逐字保留) | 审计 §二 定点清单① | -| d11 | `game/core/level_runner.gd:779/895`+`game/data/schemas/shop_table.gd`+`game/data/tables/shop.tres`+`game/core/config_db.gd` | relic 基价 `15.0`(:895)与散折 `0.8`(:779)是玩法数值字面量(自注 provisional)——违数值单一事实源 | ShopTable 新增两个 knob:`relic_base_price: float = 15.0`(域 [1.0,99.0])+`spend_discount: float = 0.8`(域 (0.0,1.0]),`validate()` 补两域检;ConfigDB 新增 `shop_relic_base_price() -> float`/`shop_spend_discount() -> float`(:337-341 同式);level_runner 两处改读表;过时注释随动订正 | 审计 §二 定点清单② | -| d12 | 产发端 `game/core/damage_controller.gd:168/370/400/504`+解析端**两口** `game/presentation/views/battle_stage_view.gd:308(on_step_row_fx——闪白/火花/击杀灰化)与 :342-346(enemy_standee_center——弹道/标出)**+`game/presentation/row_pacer.gd:110-115/135-143`+`game/presentation/views/standee_body.gd` | 站桩解析两口都以 display_name 子串匹配(`_card_by_name` 的 `contains`,消费点 :308 与 :343——grilling A-3 订正:审计点名的「弹道/闪白/击杀」三症分住两口,v1.0 漏了 :308)——同名敌或名字互为前缀错位 | 产发端四处行内/载荷内**增键** `"target_id": <敌 enemy_id>`("target" 文本值保留——日志行文不变;:168/:400 属 fx 载荷面,其键是否随 fx 行下发到 :308 解析口=T0 核类);standee_body 新增 `func enemy_id() -> String`;battle_stage_view 新增 `_card_by_id(enemy_id)`(相等匹配),`enemy_standee_center` 改签名(id 先、display_name 兜底)、`on_step_row_fx` 解析同改(id 先、名兜底);row_pacer 取 `row.get("target_id","")` 传入 | 审计 §二 定点清单③ | -| d13 | `game/core/treasure_items.gd:55-62` | 三段孤儿注释(持有数/持有件记录/丢弃)指向已删函数(连空壳都不在) | 三段孤儿注释删除 | 审计 §二 定点清单④ | -| d14 | `game/core/board_generator.gd:97/334/281` | 目标雷数公式两处手抄(:97 与 :334 同式);`legality(b, params, db)` 的 db 参数未用 | 公式单点化(`static func` 一处、两调用点改引);`legality` 去 db 参数(调用点两处:board_generator.gd:53+tests/unit/core/board_gen_reward_test.gd:145,同批改) | 审计 §二 定点清单⑤ | -| d15 | `game/core/action_economy.gd:191-192`(注释 :185 随动) | 蓄力层上限 `3` 硬编码两处字面量(`mini(3,…)`/`>= 3`);:185 注释「上限 3」同源 | 模块常量 `const CHARGE_LAYER_CAP := 3`(机制常量非表值——结构常量先例 STANDEE_FOOT_MARGIN 同型);两处+注释随动改引 | 审计 §二 定点清单⑥ | -| d16a | `game/core/unit_container.gd:55`+`game/data/schemas/hero_table.gd`+`game/data/tables/hero.tres`+`game/core/config_db.gd` | 英雄名「探险者」硬编码绕开 HeroTable(敌人名走 row.display_name) | HeroTable 新增 `display_name: String = "探险者"`(非空域检);ConfigDB 新增 `hero_display_name() -> String`;unit_container:55 改读表 | 审计 §二 定点清单⑦ | -| d16b | `game/core/unit_container.gd:257/270`+`game/core/status_container.gd:58-61/102/114` | Dictionary 取值 Variant 裸算术(`u.hp - n`/`u.hp += actual`/`inst.layers …`)漏包 int() | 逐处算术前显式 `int()` 包裹(写回路径保持既有语义) | 审计 §二 定点清单⑧ | -| d17 | `game/presentation/views/settlement_fx_view.gd:94-95` | `await get_tree().process_frame` 后无 `is_instance_valid(lab)` 守卫(全库守卫惯例唯一漏网处) | await 后补 `if not is_instance_valid(lab): return` | 审计 §二 定点清单⑨ | -| d18 | `game/presentation/views/board_view.gd:998` | `_normalize_floor_luma` 的 `lumas_out` 参数缺类型且无豁免注记(S9 R3 面存量;调用点三处:board_view:957 传 `Array[Variant]`、terrain_tile_sets_test:135、ui_contrast_probe:150 传 null) | 参数补类型 `lumas_out: Array[Variant] = null` | 审计 §二 定点清单⑩ | - -**完成定义**(全部满足才算本批完成): - -1. 钉值表 19 行落地(d16 拆 a/b 两行计);d11/d16a 表值与原字面量逐位一致(15.0/0.8/「探险者」),无数值调整、MC 零跑; -2. §8.2 对照表逐行断言绿;**行为改动单元撤销证明双证齐**(回退该单元 hunk → 对应断言红 → 还原复绿——只撤销本批 hunk,禁注入/改值造红);**等价类单元如实申报**(d1 语义加固/d11/d16a 原值迁移/d13–d16/d18 等价重构——撤销不红属预期,以等价断言与读数对拍承载;d17 无独立断言以惯例一致性走查申报——见 §8.2 分行); -3. d10 载体统一后,`grep -rnP "^\s*signal\s+\w+\(.*\)\s*#(?!#)"` 对 `game/` 全域零命中; -4. 批末按 `tools/run_all_gates.sh` 域口径选档(本批只碰 `game/`,域划分唯一出处=脚本头注 :10-15)全绿(含 S13 基线零新增)+改前改后读数对拍;三份 `reports/` 产出物写齐(T3)。 - -## 2. 设计事实源 - -| # | 出处 | 取用条款 | -|---|---|---| -| D1 | 工程直入证据指认=审计报告 §二(C6–C10+已核实定点清单)+本写卡会话逐处实查(§3 F1–F21,含对审计三处读数的订正:C6 可达性前提/d12 解析口单复/路径) | 19 个改动单元的 file:line 现状与成因定性 | -| D2 | [code-conventions](../design/engineering/code-conventions.md) §2.1 | 每个跨类信号:参数语义、发送时点、消费方依赖必须在发送方 `##` 文档注释成文(d10 依据) | -| D3 | [code-conventions](../design/engineering/code-conventions.md) §4+AGENTS.md 数值单一事实源红线 | 玩法数值只存 `data/tables/`(.tres)经 ConfigDB 单点读取(d11/d16 依据;d15 判机制常量的边界=结构常量先例 STANDEE_FOOT_MARGIN) | -| D4 | [architecture](../design/engineering/architecture/architecture.md) 架构原则 5(fail-fast) | d9 push_error 早退依据(「层必须先 register 方可 open/close」已写在 modal_stack 头注,机器强制缺口本批补) | -| D5 | [test-conventions](../design/engineering/test-conventions.md) §9.4+2026-10-24 用户 P0 令(原记录于迭代 36 实现报告,报告已退役〔git 历史可考〕) | 行为改动验收只走撤销证明(回退本批 hunk→断言红),禁注入/改值造红实验;新断言判别力随所属改动的撤销证明一并自证,证不了的一律记「拦截能力未证」 | - -## 3. 现状事实(2026-10-24 写卡会话实查+2026-10-25 grilling 独立补查;开工日 T0 复核行号) - -| # | 事实 | 出处 | -|---|---|---| -| F1 | `_on_unit_died` 遍历 `_queue` 按 `t.source_unit == u` 置 `cancelled`——Dictionary `==` 在 Godot 4 为逐键内容比较。**可达性(grilling 订正)**:`unit_container.gd:98` 敌单位行带 `"handle_id": handle`,handle 随实例自增(:77/:114)——任意两敌单位字典内容必异,真实 spawn 路径下 `==` 只对同一字典为真,误作废不可达;该行是语义隐患(判定语义应为引用同一性)而非现行行为缺陷 | `game/core/damage_controller.gd:210-216`;`game/core/unit_container.gd:77/98/114` | -| F2 | 蓄力档判定链:`set_pending(ptype, pid)` :154;`pid in CHARGE_SKILL_IDS`(:7 `["heavy"]`)时 `col = UiKit.GOLD_INK`(:172,Color8(116,80,11));`_play_intent_motion` :182-194 判 `if _intent_col == UiKit.GOLD_DEEP`(:188,Color8(176,122,20))——常量错配,③蓄力缓推+定格微顿+回落分支 :189-191 恒不可达 | `game/presentation/views/standee_body.gd:7/154/171-172/182-194` | -| F3 | `GOLD_BRIGHT` 注释含「GOLD_DEEP 语义岗〔standee:199 意图判据/board_view:35/level_map:237〕在役不动」——grilling 实查三处引述全失真(board_view:35=`DEPTH_PX`;level_map:237 在 abandon 确认段;两文件对 GOLD_DEEP/GOLD_BRIGHT 引用 grep 零命中) | `game/presentation/views/ui_kit.gd:28` | -| F4 | `_play_celebrate` :281-287 `_cele.create_tween()`+`set_loops()`(无限)无句柄持有;`close()` :156-161 只置 visible=false+`_clear_crest_bricks()`,零 kill;`open_victory` :131 每调必 `_play_celebrate()`。全库先 kill 惯例先例=board_view:362-364 | `game/presentation/views/battle_end_overlay.gd:116-139/156-161/281-287` | -| F5 | `set_context_locked` :82-85 只置 `_dbg_btn.disabled = _ctx_locked`;`_sync_sim_button` :100-103 模拟钮看 `_sim_running or _ctx_locked`——同屏两钮判据不对称;`is_running()` :71-72 已有(面板态) | `game/presentation/views/debug_overlay.gd:71-103` | -| F6 | `_start_debug_levels` :712-717 守卫 `run_scope.is_adventure or run_scope.settlement != null`,:717 `debug_overlay.reset_sliders()`;批量模拟(tune_sim_controller.run 逐帧 await :58)与调试对局并发=演出扭曲+测量作废(autoplay 自建局部 RngService=`game/sim/level_autoplay.gd:44`,无全局 RNG 竞态) | `game/presentation/main.gd:712-724` | -| F7 | `_on_tune_simulate` :1486-1491 守卫只 `run_scope.settlement != null`;`_tune_sim` 构造点 :393(无 driver 注入) | `game/presentation/main.gd:393/1486-1491` | -| F8 | `TuneSimController` 零运行态查询面:`run(n)` :30-62 唯一执行体(`panel.set_sim_running(true)` :37 ↔ `:62`);RefCounted,`_init(db_ref, panel_ref, base_hp: int, driver = null)` 有 driver 注入位 | `game/presentation/tune_sim_controller.gd:16-62` | -| F9 | `open()` :21-30:`idx := _list.find(m)` 为 -1 时 `range(idx+1, size)` 隐藏**全部**已开层后 `_occluded[m] = hidden`、`m.visible = true`;同层重复 open 时第二次 `hidden` 为空表(上方已隐)→ `_occluded[m]` 被空表覆盖,原被隐藏层 close 后永不恢复;`close()` :33-38 对未登记层无防护。**在役断言约束(grilling 查出)**:`modal_flow_test.gd:40-48` V6 序列 open(a)→open(map)→open(a) 断言 `not map.visible`——依赖「重复 open 仍重扫上方」,幂等直返方案会使该断言变红(d9 采合并制的依据) | `game/presentation/modal_stack.gd:21-38`;`game/tests/unit/presentation/modal_flow_test.gd:40-48` | -| F10 | `cell_revealed`(:8)与 `seal_judged`(:9)零注释;同文件 `round_end_board`(:10)带 `#` 注释;全库信号声明行携 `#`(非 `##`)注释 28 处/声明总数 51(本会话与 grilling 双重 `grep -P` 复算一致);发射位:cell_revealed ×4(:129/:303/:310/:345)、seal_judged ×1(:476 `{"cell": c, "hit": …}`) | `game/core/minesweeper_rules.gd:8-10`;全库 grep 实数 | -| F11 | 散折单点 `_spend_discounted` :777-780 `maxi(1, int(floor(float(price) * 0.8)))`;relic 基价 :895 `int(ceil(15.0 * pow(...)))`。ShopTable 标量 knob 先例:`price_a`/`price_b`(:13-14,域检 :101-104);`SHELF_KINDS` 封闭词表 :18 不含 relic(relic=池令牌非货架种);ConfigDB shop 访问器族 :304-341 | `game/core/level_runner.gd:769-780/888-900`;`game/data/schemas/shop_table.gd:13-18/101-104`;`game/core/config_db.gd:304-341` | -| F12 | 站桩解析**两口**(grilling 订正):`on_step_row_fx` :308 用 `_card_by_name(target)` 解析受击站桩(闪白 :311→`_schedule_arrival_fx` :318-325 火花/击杀灰化)+`enemy_standee_center` :342-346(row_pacer :110-115 结算标出、:135-143 跨区弹道消费);`_card_by_name` :332-338 以 `contains` 子串匹配。产发端四处携 display_name:damage_controller :168(attack_commit fx **载荷**)、:370(fx 载荷)、:400(kill hits 载荷)、:504(damage **行**);哨兵 `"hero"`(:470/:527、orchestrator:885)与 `"all_enemies"`(:519)不经站桩解析;单位字典带 `enemy_id`(unit_container:99 敌/:55 英雄 `"hero"`);standee 有 `display_name()`(:65)与 `_enemy_id`(:44/:89 装载),**无 enemy_id 访问器** | `game/presentation/views/battle_stage_view.gd:308-346`;`game/presentation/row_pacer.gd:106-150`;`game/core/damage_controller.gd:168/370/400/470/504/519/527`;`game/core/unit_container.gd:55/99`;`game/presentation/views/standee_body.gd:44/65/89` | -| F13 | treasure_items :55-58「持有数」注释段与 :61-62「持有件记录/丢弃」注释段之间零函数体(:66 起才是 `_usable_now`) | `game/core/treasure_items.gd:51-66` | -| F14 | 雷数公式 :97(生成期)与 :334(产物断言期)同式手抄;`legality(b, params, db)` :281-283 db 参数未用(行内带 `# type-hint: ignore` 豁免),调用点两处 | `game/core/board_generator.gd:53/97/281-283/334`;`game/tests/unit/core/board_gen_reward_test.gd:145` | -| F15 | `finish_round_stop` :188-196 内 `mini(3, _charge_layers + _last_stop_ap)`(:191)与 `_charge_layers >= 3`(:192);:185 注释含「上限 3」 | `game/core/action_economy.gd:185-196` | -| F16 | 英雄单位构造硬编码 `"display_name": "探险者"`;HeroTable 现仅三数值轴(零文案字段);敌名走 `row.display_name` | `game/core/unit_container.gd:55`;`game/data/schemas/hero_table.gd:8-10` | -| F17 | Variant 裸算术:unit_container :257(`u.hp - n`)、:270(`u.hp += actual`);status_container :58-61(`inst.layers` 比较/自增)、:102(`inst.layers * inst.snap_m`)、:114(`inst.remaining -= 1`) | `game/core/unit_container.gd:250-271`;`game/core/status_container.gd:56-64/100-119` | -| F18 | `play_damage_number` :94 `await get_tree().process_frame` 后 :95 直取 `lab.size`——lab 在等待帧间释放即错;全库 await 后守卫惯例(如 `_schedule_arrival_fx` :318-329) | `game/presentation/views/settlement_fx_view.gd:85-99` | -| F19 | `_normalize_floor_luma` static,`lumas_out = null` 无类型;调用点三处(board_view:957 传 `Array[Variant]`、terrain_tile_sets_test:135、ui_contrast_probe:150 传 null) | `game/presentation/views/board_view.gd:950-998`;`game/tests/unit/presentation/terrain_tile_sets_test.gd:135`;`game/probe/ui_contrast_probe.gd:150` | -| F20 | 断言宿主在役实况:damage_controller_test(直构 ConfigDB/RngService/UnitContainer/StatusContainer/DamageController,已覆盖「死亡作废」面);modal_flow_test(直构 ModalStack+哑 Control,:40-48 V6 序列在役);level_shop_economy_test(商店域);pacing_queue_test(row_pacer 排播域);system_test(真实玩法路径宿主);单测入闸协议=run_all.gd 套件清单显式登记行。**脚本错判定面**:单件直跑退出码不含运行期脚本错——判红须经 `tools/error_scan.py` 扫直跑输出(run_all.gd:136 同款机制) | `game/tests/unit/battle_settlement/damage_controller_test.gd:9-30`;`game/tests/unit/presentation/modal_flow_test.gd:1-48`;`game/tests/run_all.gd:91/:101/:136` | -| F21 | 改前读数基线=开工日 T0 采集(写卡会话不跑测——测试执行两入口铁规),本卡不预写通过数 | — | - -## 4. 设计契约(红线,违反 = L3 停机呈报) - -🔴 **契约 1 · 钉值表逐字**:§1 钉值表 19 行逐字落地;d11/d16a 为**原值迁移**(15.0/0.8/「探险者」),禁顺带调值禁增减字段语义;d2 是修成 [战斗表现](../design/gdd/systems/战斗循环/战斗表现.md) §4.6.4 已成文的三档运动签名(激活死代码=对齐设计);d12 行内/载荷内**只增键不改值**("target" 的 display_name 文本保留——日志行文零变化)。 - -🔴 **契约 2 · core 触碰面封闭清单**:core 文件改动全部为钉值表所列定点(damage_controller 一处判定符+四处增键、minesweeper 两行契约、level_runner 读表替换+注释订正、board_generator 公式单点+去参、action_economy 常量化+注释随动、unit_container 读表+int() 包裹、status_container int() 包裹、treasure_items 注释删)——**battle_orchestrator 零触碰**(⑦头注归条目④;orchestrator:718 treasure_used 行的 target 形态 T0 核类,若为敌 display_name 则同批增键,属 d12 产发端清单的 T0 扩面非新单元);零编排逻辑、零结算序、零随机流改动。 - -🔴 **契约 3 · 表扩列纪律**:ShopTable/HeroTable 新字段全部带 `validate()` 域检(F11/F16 既有条目同式);消费面一律经 ConfigDB 新访问器;跨表校验器(`config_db_cross_validator.gd`)是否需登记新字段=T0 核(其只校键集的域不涉新标量则零改动)。 - -🔴 **契约 4 · 验收闭合与证明纪律(2026-10-24 P0 令)**:行为改动单元(d2/d4/d5/d6/d7/d8/d9/d12/d10-契约半)撤销证明=**只回退本批该单元 hunk** → 最窄闸对应断言红 → 还原复绿;**禁注入造红、禁改值造红**(等价类单元 d1/d11/d13–d18 撤销不红属预期,如实申报、以等价断言与读数对拍承载);断言判别力证不了的面一律记「拦截能力未证」,禁硬凑。 - -🔴 **契约 5 · 断言面零未申报削弱**:既有断言零删除零放宽;**d9 修法与 modal_flow_test :40-48 V6 在役断言必须同绿**(合并制设计的硬约束);d14 改 board_gen_reward_test:145 调用签名属随签名同批改(非削弱,申报在案);新增断言全列 §8.2。 - -🔴 **契约 6 · golden 判定先于动作**:d2/d12 是行为修正,渲染闸棋盘三图可能 changed(蓄力敌局/同名敌局的意图与弹道演出变化=修对的证据而非回归);本批不预重制,按 [golden README](../game/tests/golden/README.md) 双态判定(判有效加注;判失效显式呈报后重制+复闸);菜单基线预期无涉。 - -🔴 **契约 7 · d10 注释逐字保留**:28 处信号注释只换载体 `#`→`##`,注释内容逐字不动(含旧词——用语清扫另有其批);minesweeper 两契约新写成文按 §2.1 三要素,载荷键集以发射位实查并集为准(T0 复核四处发射位键集后落笔)。 - -## 5. 范围边界 - -**做**:§1 钉值表 19 行;四件既有单测扩展+一件新单测+run_all 清单行+system_test 两断言;两表扩列与访问器;渲染闸 golden 判定动作;反哺点清单。 - -**不做**(明示去向): -- ⑦明细行枚举漂移(词表 13/15)——归条目④「架构与规范对齐批」裁决,④落文前 battle_orchestrator 头注不动; -- S9 正则修复与其余存量注解归零——归卡②(本卡仅 d18 一件顺带补类型); -- 结构重构五件——R1/R2/R3 三卡(本卡先行); -- 报告 §三「其余小件」(level_map_screen 视图直读与 BFS 重算、main.gd 死变量与 confirm 旁路、settle_batch 空判、row_pacer 常驻 flush、`_fatal` assert 剥离、rng_service open_stream early-return)——**未入本卡**(改善级非缺陷级、审计未标写卡面),留审计报告在册待用户后续点名或随重构批收编; -- 意图动画重设计、庆祝演出换装——零设计面(本卡只修可达性与句柄纪律)。 - -## 6. 关联文档同步面 - -> 本批是代码实现批,禁写 `design/` 整目录与根规则文件,唯一可写目录=`reports/`。同步义务一律执行为:逐条列进 `reports/iter-反哺点-<完成日>.md`,用户会话裁决后归文档批落文。 - -| 文档 | 关联点 | 同步义务 | -|---|---|---| -| [战斗结算](../design/gdd/systems/战斗循环/战斗结算.md) 明细行协议节 | d12 行内增 `target_id` 键=行协议字段集扩列(T0 查该节是否逐键登记行字段,列则补一行) | 反哺点 · 列清单(①增键说明+依据=本卡 d12;呈现消费语义不变) | -| [存档序列化](../design/gdd/systems/表现与支撑/存档序列化.md)/[行动经济](../design/gdd/systems/排雷循环/行动经济.md)/[战斗表现](../design/gdd/systems/战斗循环/战斗表现.md)/shop 相关 GDD | d11/d15/d16 值未变仅迁源、d2 对齐已成文设计——写卡会话检索(散折/基价/蓄力层/英雄名关键词)零陈述被本批改变 | 无关联文档(查讫) | -| `待办清单.md` | 来源条目批后按拆分结论退役/换代(归 batch-closure) | 反哺点 · 列清单(②批末退役笔) | -| `roadmap.md` | 迭代行编入笔(归 iter-plan 段发令) | 反哺点 · 列清单(③) | -| `game/data/tables/shop.tres`/`hero.tres`、`game/tests/`、探针与单测文件 | 实施本体与断言面 | 实施本体(可写) | - -## 7. 任务卡 - -> 估时按 Claude 工作日口径;每卡出口自跑轻闸;开工日 T0 复核本卡全部行号。回归措辞模板:里程碑步(T 段)出口跑一次全套闸;波内修复只做出红最小单元的最窄确认;新增测试先行孤立验证再并入全套。 - -### 卡前 · 文件结构图 - -| 文件 | 职责 | 行为改动 | -|---|---|---| -| `game/core/damage_controller.gd` | d1 判定符+d12 四处增键(复用既有·修改) | d1 非行为改动(语义加固)→ wd1;d12 行为改动 → wd12 | -| `game/presentation/views/standee_body.gd` | d2 `_intent_charge` 布尔分档+d12 `enemy_id()` 访问器(复用既有·修改) | 行为改动 → wd2/wd12 | -| `game/presentation/views/ui_kit.gd` | d3 注释订正一行(复用既有·修改) | 非行为改动(注释)→ 随 wd2 复核 | -| `game/presentation/views/battle_end_overlay.gd` | d4 tween 句柄 kill 纪律(复用既有·修改) | 行为改动 → wd4 | -| `game/presentation/views/debug_overlay.gd` | d5 调试钮判据并入模拟运行态(复用既有·修改) | 行为改动 → wd5 | -| `game/presentation/main.gd` | d6/d7 两守卫补口(复用既有·修改) | 行为改动 → wd6/wd7 | -| `game/presentation/tune_sim_controller.gd` | d8 `is_running()` 运行态查询面(复用既有·增成员) | 行为改动 → wd8 | -| `game/presentation/modal_stack.gd` | d9 fail-fast+遮挡账合并制(复用既有·修改) | 行为改动 → wd9 | -| `game/core/minesweeper_rules.gd`+全库 28 信号行 | d10 两契约成文+载体统一(复用既有·修改) | wd10(契约成文=行为改动·注释面;载体统一=非行为改动) | -| `game/data/schemas/shop_table.gd`+`game/data/tables/shop.tres`+`game/core/config_db.gd` | d11 两 knob+域检+访问器(复用既有·扩列) | 等价类(读数源迁移)→ wd11 | -| `game/core/level_runner.gd` | d11 两处读表+注释订正(复用既有·修改) | 等价类 → wd11 | -| `game/presentation/views/battle_stage_view.gd`+`game/presentation/row_pacer.gd` | d12 两口按 id 解析+target_id 取用(复用既有·修改) | 行为改动 → wd12 | -| `game/core/treasure_items.gd` | d13 孤儿注释删(复用既有·删注释) | 非行为改动 → wd13 | -| `game/core/board_generator.gd` | d14 公式单点+legality 去参(复用既有·修改) | 非行为改动(等价重构)→ wd14 | -| `game/core/action_economy.gd` | d15 常量化+注释随动(复用既有·修改) | 非行为改动 → wd15 | -| `game/data/schemas/hero_table.gd`+`game/data/tables/hero.tres`+`game/core/config_db.gd`+`game/core/unit_container.gd` | d16a 英雄名入表(扩列);d16b int() 包裹 | wd16 等价类/非行为改动 | -| `game/core/status_container.gd` | d16b int() 包裹四处(复用既有·修改) | 非行为改动 → 随 wd16 | -| `game/presentation/views/settlement_fx_view.gd` | d17 await 后守卫(复用既有·修改) | 行为改动(健态)→ wd17 | -| `game/presentation/views/board_view.gd` | d18 参数类型(复用既有·修改) | 非行为改动 → wd18 | -| `game/tests/unit/battle_settlement/damage_controller_test.gd` | d1 语义断言+d12 产发断言(复用既有·增断言) | 断言面 → wd1/wd12 | -| `game/tests/unit/presentation/standee_fx_sweep_test.gd` | **新增**:d2 意图档分档+d4 句柄 kill+d12 按 id 解析三断言族(形制=SceneTree+test_kit,同 modal_flow_test;新增·新文件) | 断言面 → wd2/wd4/wd12 呈现半 | -| `game/tests/unit/presentation/modal_flow_test.gd` | d9 三早退+合并制断言扩展(复用既有·增断言;:40-48 V6 序列原样保留同绿) | 断言面 → wd9 | -| `game/tests/unit/core/level_shop_economy_test.gd` | d11 断言扩展(复用既有·增断言) | 断言面 → wd11 | -| `game/tests/unit/presentation/pacing_queue_test.gd` | d12 排播链 target_id 优先断言(复用既有·增断言;夹具形态 T0 复核) | 断言面 → wd12 排播半 | -| `game/tests/unit/core/board_generator_test.gd` | d14 同值断言+调用签名同批改(复用既有·增/改断言——签名变更已申报契约 5) | 断言面 → wd14 | -| `game/tests/system_test.gd` | d6/d7 守卫两断言(复用既有·增断言) | 断言面 → wd6/wd7 | -| `game/tests/run_all.gd` | 新套件清单登记行(复用既有·增行) | 断言面接线 | -| `reports/iter-实现报告/代拍板追认单/反哺点`(三份,批末写) | 产出物落位(含 golden 判定读数) | 非行为改动 → wd19 | - -### 接口块(全卡共用) - -- 新成员/新函数签名(断言与撤销证明引用以此为准;**全类型含返回类型**): - - `standee_body.gd`:`var _intent_charge := false`;`func enemy_id() -> String`。 - - `battle_end_overlay.gd`:`var _celebrate_tw: Tween`。 - - `tune_sim_controller.gd`:`var _running := false`;`func is_running() -> bool`。 - - `battle_stage_view.gd`:`func _card_by_id(enemy_id: String) -> Node2D`(`_cards` 内 `c.enemy_id() == enemy_id` 相等匹配);`func enemy_standee_center(target_id: String, display_name: String) -> Variant`(id 先、名兜底;现行同位函数本无返回类型——新签名补 `-> Variant`,卡②落地后 S9 R2 面零新增)。 - - `config_db.gd`:`func shop_relic_base_price() -> float`/`func shop_spend_discount() -> float`/`func hero_display_name() -> String`。 - - `shop_table.gd`:`@export var relic_base_price: float = 15.0`+`@export var spend_discount: float = 0.8`(validate 域检两条);`hero_table.gd`:`@export var display_name: String = "探险者"`(非空域检)。 - - `board_generator.gd`:`static func target_mine_count(params: Dictionary) -> int`(命名 L1 可调);`legality(b: BoardData, params: Dictionary) -> bool`(去 db 参)。 - - `action_economy.gd`:`const CHARGE_LAYER_CAP := 3`。 -- row_pacer 两处取用改:`var target_id := String(row.get("target_id", ""))`+`stage.enemy_standee_center(target_id, target)`。 -- 新单测形制:`standee_fx_sweep_test.gd`=`extends SceneTree`+`const Kit := preload("res://tests/unit/test_kit.gd")`+`_init()` 逐断言+`quit(Kit.finish(...))`(modal_flow_test 同构);run_all 清单行 `{"path": "res://tests/unit/presentation/standee_fx_sweep_test.gd", "expect": 0}`。 -- 最窄闸(撤销证明跑法与判红口径): - - 单测族=`D:\code\godot\Godot_v4.7.2-stable_win64_console.exe --headless --path game --script res://tests/unit/<相对路径>.gd` 单件直跑;**运行期脚本错判红=直跑输出经 `tools/error_scan.py` 扫出 SCRIPT ERROR 标记**(F20——单件退出码不含脚本错,d2/d8 撤销半依赖此口径); - - 系统测试=`python tools/run_system_test.py`; - - 信号载体扫描=完成定义 3 的 `grep -rnP` 命令零命中; - - 类型面=`python tools/audit_type_hints.py`(判据=d18 行零命中+新增码零新增红)。 - -### 落位与可复用性预检(本批新增件逐条三问) - -- **`standee_fx_sweep_test.gd`(新单测文件)**——复用面:d9/d11/d14 各归域既有宿主(modal_flow/level_shop_economy/board_generator_test,F20);d1 归 damage_controller_test;d6/d7 归 system_test;d12 排播半归 pacing_queue_test;d2/d4/d12-站桩解析半三件同属「战斗舞台呈现域」且全库无 standee/battle_end/stage 宿主(实查 tests/ 树)——并入任一现役文件都域错位,新立=该域唯一断言宿主。唯一面:与 type_glow_test(墨色投影域)扫域不交。分层与交互面:纯呈现层直建(StandeeBody/BattleEndOverlay/BattleStageView 均 `preload().new()` 直构、零场景文件依赖——theme_projection_test:200 BoardView 直建先例同构)。 -- **ConfigDB 三个新访问器+两表三字段**——复用面:标量 knob+域检+访问器三件套是 ShopTable.price_a/price_b 既有形态(F11),照式扩展即复用;为什么不并入他表:relic 基价与散折都是商店域 knob、英雄名是英雄基线面——落点各归其主无第二候选。唯一面:三值各只此一处表内定义,level_runner/unit_container 字面量随批消亡。分层:数值只经 ConfigDB 单点读取(D3)。 -- **产品码 18 处修改**——零新增文件;新增函数仅访问器×3+常量×1+`_card_by_id`(为什么新增:按键解析与按名解析是两条判据,合一则旧路无兜底;与 `_card_by_name` 同文件同职责域无散落)。 -- **d14 `target_mine_count` static 函数**——为什么新增:两处手抄公式的唯一收口点;不能复用现成:全库该式仅此两处无第三处共享面。 - -### T0 事实坐实(≈0.25d) - -- [ ] 复核 §3 全部 file:line(重点:d12 产发端四址+orchestrator:718 treasure_used 行 target 值形态核类+:168/:400 fx 载荷键是否下发到 :308 解析口——判 target_id 增键的必要位点;pacing_queue_test 夹具形态;28 处信号载体清单逐处过目确认零误匹配)。可核查结果:行号漂移清单(零漂移则声明)进实现报告素材。 -- [ ] 采集改前读数基线(单测/系统测试通过数)。可核查结果:基线读数在案。 - -### T1 core 域缺陷修+断言(≈1–1.25d) - -- [ ] 实装 d1(is_same)+damage_controller_test 扩展语义断言:**手工构造**内容全同双敌字典 A/B(复刻单位字典键集但不含 handle_id——生产不可达的语义面用夹具钉住),A 开单(source_unit=A)→ B 死亡事件 → 断言 A 单据未作废;A 死亡 → A 单据作废(正向对照)。可核查结果:断言绿。 -- [ ] 实装 d11(两 knob+域检+访问器+level_runner 读表+注释订正)+level_shop_economy_test 扩展断言:`db.shop_relic_base_price() == 15.0`、`db.shop_spend_discount() == 0.8`(经 ConfigDB 真装载)+pool_price("relic") 同种子同输入现价与改前逐位一致(等价证明)。可核查结果:断言绿。 -- [ ] 实装 d16a(HeroTable.display_name+域检+访问器+unit_container:55 读表)+断言:`db.hero_display_name() == "探险者"`+spawn 后英雄单位 display_name 取值同。可核查结果:断言绿。 -- [ ] 实装 d15(常量化两处+注释随动)+d16b(int() 包裹六处)+d14(公式单点+legality 去参+两调用点改)+board_generator_test 同值断言(`target_mine_count` 与原式同参同值)+调用签名同批改。可核查结果:单测绿。 -- [ ] 实装 d10(minesweeper 两契约+全库 28 处载体统一)。可核查结果:完成定义 3 grep 读数零。 -- [ ] 实装 d13(孤儿注释删)。可核查结果::51-66 段注释与函数体对齐。 -- [ ] 撤销证明(**只回退本批 hunk**→最窄闸红→精确还原→记录;最窄闸=接口块单测直跑+error_scan 判红口径):①回退 d1 hunk(is_same 回 `==`)→ 双敌夹具断言红(B 死亡误废 A 单据);②回退 d10 契约 hunk → grep 命中 2 处红。等价类申报:d11/d16a/d14/d15/d16b/d13 撤销不红属预期(值等价/等价重构),以等价断言+改前改后读数对拍承载,记录表如实标注。记录表进实现报告。 -- [ ] 验收步骤:T 段出口跑一次全套闸(按域选档,本批只碰 `game/`),期望全绿(含 S13 基线零新增)+11 号闸 NEED-APPROVAL 留痕(触 face=data/tables+damage_controller,§0)。 - -### T2 呈现域缺陷修+断言(≈1–1.25d) - -- [ ] 实装 d2(`_intent_charge` 分档)+d3(ui_kit:28 括号引述整段删)+standee_fx_sweep_test 断言①:`set_pending("damage", "heavy")` → `_intent_charge == true`;`set_pending("damage", "非蓄力id")` → false;`set_pending("buff", …)` → false。可核查结果:断言绿。 -- [ ] 实装 d4(句柄 kill)+断言②:直建 BattleEndOverlay 入树 → `open_victory(...)` 两次 → 断言任一时刻活跃 celebrate tween 至多一个(旧句柄非 running/已 kill)→ `close()` 后 `_celebrate_tw` 无效或空。可核查结果:断言绿。 -- [ ] 实装 d5/d6/d7/d8(互斥四口)+断言③:直建 DebugOverlay → `set_sim_running(true)` → `_dbg_btn.disabled == true`;TuneSimController 假 panel+假 driver(:16 注入位)跑 `run(2)`,driver 回调内记录 `is_running()` → 断言运行中 true、收尾 false。system_test 两断言:**wd7**=真实冒险**关间态**(is_adventure=true 且 settlement==null——选路停点态,非战斗中;战斗中调用则守卫首条件已拦、断言空转)直调 `_on_tune_simulate(3)` → `_tune_sim.is_running()` 保持 false(d7 守卫早退;回退 d7 hunk 后 is_running 变 true 即红——调用后立即置回 `_tune_sim._running = false` 清理);**wd6**=闲时态白盒置态 `main._tune_sim._running = true` → 直调 `_start_debug_levels()` → 断言 `run_scope.settlement == null` 且开始层未被收起(守卫早退)→ 清理置态(回退 d6 hunk 后守卫漏模拟态即红)。可核查结果:断言绿。 -- [ ] 实装 d9(fail-fast+合并制)+modal_flow_test 扩展断言④:未登记层 open → 已开层 visible 全不变+该层不可见;A/B 双层开 A(B 被隐)→ 重复 open A → 断言 B 仍隐(V6 语义)→ close A → **B 恢复可见(合并制吞账修复)**;未登记层 close → 零副作用;:40-48 V6 在役序列原样绿。可核查结果:断言绿。 -- [ ] 实装 d12(产发端增键+standee 访问器+`_card_by_id`+`enemy_standee_center` 改签名+`:308` on_step_row_fx 解析同改+row_pacer 取用)+断言⑤:两 StandeeBody 同 `display_name`(如「哥布林」)异 `enemy_id` 挂 `_cards` → 按 id 各自命中(两口各断言一次);id 空时按名兜底行为同旧;damage_controller_test 扩:开单后行/载荷 `"target_id"` == 目标敌 enemy_id;pacing_queue_test 扩:row 携 target_id 时解析调用传 id(假 stage 记参断言)。可核查结果:断言绿。 -- [ ] 实装 d17(await 后守卫)+d18(lumas_out 类型)。可核查结果:`python tools/audit_type_hints.py` 对 board_view:998 零命中。 -- [ ] 撤销证明:③回退 d2 hunk → 断言①直跑输出经 error_scan 扫出 SCRIPT ERROR 红(`_intent_charge` 未定义);④回退 d4 kill 段 → 断言②活跃句柄数红;⑤回退 d5 hunk → 断言③ disabled 半红;⑥回退 d8 hunk → 断言③ is_running 半 error_scan 红;⑦回退 d9 合并段 hunk → 断言④ B 恢复半红(吞账复现);⑧回退 d12 站桩半 hunk → 断言⑤红。等价类申报:d3/d13/d17/d18 撤销不红属预期(注释/守卫惯例/类型面,如实标注——d17 以走查承载)。记录表进实现报告。 -- [ ] 验收步骤:T 段出口跑一次全套闸(按域选档),期望全绿。 - -### T3 渲染闸 golden 判定+产出物落位卡(≈0.5d,末卡) - -- [ ] golden 判定(契约 6):跑渲染闸读棋盘三图+菜单 changed 读数×4,按 README 双态判定;changed>0 落棋盘区=修对的证据,显式呈报后定重制+复闸。可核查结果:读数与判定结论进实现报告。 -- [ ] 轻闸全绿后、合并前写齐三份:`reports/iter-实现报告-<完成日>.md`(各单元状态+撤销证明记录表〔含等价类申报行〕+golden 判定读数+实机目检总清单〔蓄力敌意图三档观感/同名敌受击闪白与弹道落点两条〕+遗留读数义务)/`reports/iter-代拍板追认单-<完成日>.md`(§10 白名单内代拍项留痕)/`reports/iter-反哺点-<完成日>.md`(§6 各条有落点)。可核查结果:三文件在 `reports/` 且逐条有落点。 -- [ ] 合并前置:独立验收审计完成且报告 `reports/iter-验收审计-<完成日>.md` 逐行「通过」——未通过不得合并(审计入口=未参与实现的会话/子代理,只读本卡、实现报告对照表、diff 与测试件,自跑最窄抽查,不采信「已全绿」)。 - -**估时合计 ≈3–3.5d**(T0 0.25+T1 1–1.25+T2 1–1.25+T3 0.5)。切分预案:T1/T2 各含一次全套闸出口,超软盒时 d12(跨 core/presentation/排播三面)可独立成波。**编排注记**:本卡先行于 R1–R3 重构批落地(§0);与卡②先后皆可、d18 已替其消一件存量红;估时低于 iter-plan 四查③硬下限,与同会话他卡同批编入。 - -## 8. 验证闸 - -### 8.1 逐需求点验收清单 - -| # | 需求点 | 测什么 | 落点 | -|---|---|---|---| -| V1 | 同内容双敌单据不作废(d1,语义加固) | 夹具双敌:B 死亡不废 A 单据;A 死亡废 A 单据 | damage_controller_test 扩 | -| V2 | 蓄力档可达(d2/d3) | `_intent_charge` 两态;非蓄力零置位 | standee_fx_sweep 断言① | -| V3 | 庆祝 tween 不叠加(d4) | 活跃句柄至多一;close 即 kill | standee_fx_sweep 断言② | -| V4 | 批量模拟互斥闭合(d5–d8) | 调试钮随模拟置灰;控制器运行态可查;两守卫早退 | standee_fx_sweep 断言③+system_test | -| V5 | 模态栈 fail-fast+吞账修复(d9) | 未登记层零副作用;重复 open 不吞遮挡账;V6 在役断言同绿 | modal_flow_test 扩 | -| V6 | 信号契约成文(d10) | 两契约在位;全库载体 grep 零 `#` 命中 | grep+走查 | -| V7 | 数值入表(d11/d16a) | 表值==原字面量;消费端读表;现价逐位等价 | level_shop_economy_test 扩+单测 | -| V8 | 站桩按键解析(d12,两口) | 同名异 id 两口各自命中;行携 target_id;排播链传 id | standee_fx_sweep 断言⑤+pacing_queue_test 扩+damage_controller_test 扩 | -| V9 | 等价重构不破坏(d13–d18) | 公式单点同值;调用点改签名后套件绿;int() 包裹后套件绿;守卫/类型补齐 | board_generator_test+全量闸+S9 | -| V10 | golden 判定 | changed 读数×4+判定结论 | 渲染闸+README 记录 | -| V11 | 反哺点清单 | §6 各条有落点 | 件核对(T3 出口) | - -### 8.2 改动—验收对照表 - -> 断言 desc 统一尾缀〔wd#〕;撤销证明逐条对应 T1/T2 撤销步;**证明纪律=2026-10-24 P0 令(只回退本批 hunk,禁注入造红)**。 - -| 需求点 | 改动 | 验收测试(尾缀) | 层级 | 真实入口 | 撤销证明 | -|---|---|---|---|---|---| -| wd1 双敌单据(语义加固) | `damage_controller.gd:215` 判定符 | 夹具双敌两断言〔wd1〕damage_controller_test 扩 | 单元 | 控制器真实构造(ConfigDB/RngService/UnitContainer 全链,F20 先例)+`unit_died.emit` 真实事件;夹具=生产不可达语义面的显式钉住(F1 定性如实注记) | 回退 d1 hunk → 夹具断言红(T1①)——生产盘面撤销不红属预期,如实申报 | -| wd2 蓄力档 | `standee_body.gd:171-194` 布尔分档 | 断言①〔wd2〕standee_fx_sweep_test | 单元 | `set_pending` 真实调用链(快照投影 pid) | 回退 d2 → error_scan 红(T2③) | -| wd3 注释订正 | `ui_kit.gd:28` | 走查(注释面) | — | — | 不适用(随 wd2 复核) | -| wd4 庆祝句柄 | `battle_end_overlay.gd:281-287/156-161` | 断言②〔wd4〕standee_fx_sweep_test | 单元 | `open_victory`/`close` 真实开合链 | 回退 kill 段 → 红(T2④) | -| wd5 调试钮 | `debug_overlay.gd:84` | 断言③ disabled 半〔wd5〕 | 单元 | `set_sim_running` 真实信号路径 | 回退 d5 → 红(T2⑤) | -| wd6 调试开局守卫 | `main.gd:712-717` | system_test 白盒置态断言〔wd6〕 | 系统 | 守卫入口直调+真实闲时态(`_tune_sim._running` 白盒置态即真实运行态位面) | 回退 d6 → 守卫漏模拟态 → settlement 非 null 红(T2②wd6) | -| wd7 模拟守卫 | `main.gd:1489` | system_test〔wd7〕 | 系统 | 真实冒险关间态(is_adventure 且 settlement==null)直调 `_on_tune_simulate` | 回退 d7 → is_running 变 true 红(T2②wd7) | -| wd8 运行态面 | `tune_sim_controller.gd` | 断言③ is_running 半〔wd8〕 | 单元 | `run()` 真实执行体+driver 注入位 | 回退 d8 → error_scan 红(T2⑥) | -| wd9 模态 fail-fast+合并制 | `modal_stack.gd:21-38` | 断言④四半〔wd9〕modal_flow_test 扩+:40-48 V6 同绿 | 单元 | ModalStack 直构+哑 Control(宿主先例) | 回退合并段 → B 恢复半红(T2⑦) | -| wd10 信号契约 | `minesweeper_rules.gd:8-9`+28 行 | grep 零命中+契约走查〔wd10〕 | 静态扫描+走查 | 发射位实查(:129/:303/:310/:345/:476) | 回退 d10 契约 → grep 红(T1②) | -| wd11 数值入表 | shop/hero 表+config_db+level_runner/unit_container | 表读断言+等价断言〔wd11〕level_shop_economy_test 扩+单测 | 单元 | ConfigDB 真装载+pool_price/英雄构造真实消费链 | 等价类申报:撤销不红属预期(原值迁移)——等价断言+读数对拍承载 | -| wd12 按 id 解析(两口) | `damage_controller:168/370/400/504`+`standee_body`+`battle_stage_view:308/:342`+`row_pacer` | 断言⑤(两口)+产发断言+排播断言〔wd12〕三宿主 | 单元 | 站桩装载链(set_data enemy_id)+fx/排播真实行驱动 | 回退站桩半 → 断言⑤红(T2⑧) | -| wd13 孤儿注释 | `treasure_items.gd:55-62` | 走查(注释面) | — | — | 不适用(非行为改动) | -| wd14 公式单点 | `board_generator.gd:97/334/281` | 同值断言+套件绿〔wd14〕board_generator_test | 单元 | 生成期/断言期两真实调用点 | 等价类申报:同值断言为判据,撤销不红属预期 | -| wd15 常量化 | `action_economy.gd:185/191-192` | 全量闸回归线〔wd15〕 | 集成 | finish_round_stop 真实结算链(既有套件覆盖) | 同 wd14 性质(等价申报) | -| wd16 英雄名/Variant | `unit_container:55/257/270`+`status_container` 四处+hero 表 | 表读断言+套件绿〔wd16〕 | 单元 | spawn/apply_damage/heal/apply_effect/decay 真实链(既有套件覆盖) | 等价类申报(同 wd11) | -| wd17 await 守卫 | `settlement_fx_view.gd:94-95` | 走查+惯例一致性〔wd17〕(申报:无独立断言) | 走查 | play_damage_number 真实演出链 | 申报豁免(T2 如实标注) | -| wd18 参数类型 | `board_view.gd:998` | `python tools/audit_type_hints.py` 该行零命中〔wd18〕 | 静态扫描 | 三调用点真实形态(F19) | 回退类型 → S9 R3 命中红 | -| wd19 产出物 | T3 三份 | 件核对〔wd19〕 | — | — | 删一落点 → 件核红 | - -**测试方式两分法**:V1–V9 全机检;能力验证经真实构造/事件链驱动(调试接口零用)。**用户目检项**(登记不阻塞):蓄力敌(heavy 技能敌)意图三档观感+同名敌受击闪白与弹道落点两条,写进实现报告目检总清单。 - -**闸口径**:轻闸每任务卡出口自跑;批末重闸=`tools/run_all_gates.sh` 按改动域选档(本批只碰 `game/`;域划分唯一出处=脚本头注)全绿才合并+改前改后读数对拍;11 号闸触面 NEED-APPROVAL 留痕不判红(§0);S13 gdstyle 基线零新增(§0)。回归措辞:里程碑步出口一次全套;波内修复只做最窄确认;新增测试先行孤立验证再并入。 - -### 8.3 测试削弱申报(test-conventions §9.5) - -零削弱;唯一既有测试改动=board_gen_reward_test:145/board_generator_test 的 `legality` 调用签名随 d14 同批改(签名变更非削弱,申报在案);既有断言零删除零放宽——**modal_flow_test :40-48 V6 序列原样保留且必须同绿(契约 5)**。 - -**独立验收审计(test-conventions §9.7)**:合并前由未参与实现的会话/子代理审计——只读本卡、实现报告对照表、diff 与测试件,自跑最窄抽查;报告落 `reports/iter-验收审计-<完成日>.md`,未通过不得合并。 - -## 9. 异常分级 - -- **L1(自救)**:`target_mine_count`/`_card_by_id`/`standee_fx_sweep_test` 命名与签名细节(接口块签名语义不变前提);d10 契约措辞(三要素不变);断言夹具构造细节。 -- **L2(记录继续)**:d9 合并制残边注记——上层「其间被重新 open 又被显式 close」的 stale 遮挡记录会在 close 下层时复活,现调用面与 V6 流不触及、接受为域外(第二轮复核注记在案);orchestrator:718 treasure_used 行 target 形态扩入 d12 产发端清单;:168/:400 fx 载荷键下发面核类结果;pacing_queue_test 夹具与假 stage 形态适配;跨表校验器新字段登记与否;wd6 白盒置态的清理写法。 -- **L3(停机呈报)**:夹具双敌字典构造不出(单位键集复刻受阻);golden 判失效且重制面涉及非本批像素;发现钉值表现状与 §3 不符(含 28 处清单漂移);任一既有套件出非本批存量红;d11/d16a 等价断言不成立(表迁移后现价/构造逐位不一致——数值面意外变化);modal_flow_test V6 序列与 d9 修法无法同绿(合并制失效——回呈修法裁决);任何要求顺带改数值/编排逻辑/⑦头注的诉求。 - -## 10. 代拍板白名单(超清单必呈报) - -1. `target_mine_count`/`_card_by_id`/`standee_fx_sweep_test` 命名与签名细节(接口块签名语义不变前提)。 -2. d10 契约成文措辞与载荷键集并集的取舍(以实查为准)。 -3. d12 兜底路径形态(id 优先+名兜底 vs 纯 id——倾向前者,兜底承 fx 行类)。 -4. d16b int() 包裹的写法(读侧包 vs 写侧包——语义等价取一贯)。 -5. 提交切分与 worktree 分支命名。 - -## 11. 待裁点 - -**零待裁点,开工门=U0。** - -五处灰色地带已按设计事实推导定值(用户可否决,随 T3 反哺点清单逐条列示呈报): -- **d1 定性为语义加固而非行为修复**——依据=handle_id 生产不可达实证(F1)+iter36 P0 纪律下「生产盘面撤销不红」须如实申报;备选「按审计原述定性为行为缺陷」淘汰依据=审计前提读数被 unit_container:98 排除(本卡 §3 已订正回呈)。 -- **d12 用 enemy_id 增键而非槽位 index**——依据=单位字典自带 enemy_id(F12)零新造键语义、槽位随编排重排会漂;备选「槽位 index」淘汰依据=时序耦合。 -- **d11 走标量 knob 不扩 SHELF_KINDS 词表**——依据=relic 是池令牌非货架种(F11 注释自证)、扩词表属数值批设计裁;备选「pool_shelf 增 relic 行」淘汰依据=词表封闭性+现价公式 relic 专枝已存在(:894)。 -- **d15 蓄力上限走模块常量不入表**——依据=机制结构常量先例(STANDEE_FOOT_MARGIN 同型);备选「入 action_economy.tres」淘汰依据=审计未判其为玩法数值、无调参诉求。 -- **d16a 英雄名入 HeroTable**——依据=敌人名对称先例(row.display_name)+文案值表内单源先例(settlement.tres source_label_map);备选「保留字面量」淘汰依据=审计在册缺陷定性。 - -## 12. 自审六查与 grilling 复核记录 - -**自审六查(2026-10-24 写卡会话)**:①规格覆盖——19 单元逐条挂 T1/T2/T3 并指到 wd1–wd19;②步骤扫描——一步一动作一可核查结果,零函数体;③类型一致性——接口块签名与 F1–F19 实查逐字一致(v1.1 补全返回类型与参数类型);④审查焦点+改动覆盖——补挂:同内容双敌(wd1 正反两断言+定性申报)、两口解析(wd12 两半)、等价断言(wd11/wd14/wd16)、golden 修对证据面(契约 6)、d17 无断言如实申报;⑤篇幅比例——钉值表与 T 卡≈1:1,零代码块;⑥落位与复用——唯一新增文件 standee_fx_sweep_test 的域归属与既有宿主分工已答(预检节),两表三字段各归其主。 - -**grilling 第一轮复核记录(2026-10-25,独立子代理对抗复核)**:判回炉三阻断——A-1 d1 缺陷前提生产不可达(unit_container:98 handle_id——验收链「真实构造+撤销」自相矛盾);A-2 d9 幂等直返与 modal_flow_test :40-48 V6 在役断言冲突;A-3 d12 漏 `battle_stage_view.gd:308` on_step_row_fx 第二解析口(审计「闪白/击杀」半不修)。随同九应修(A-4 iter36 已入库陈述过时+第三卡漏记;A-5 接口块两处缺类型;A-6 改 .tres 值造红违 P0 令;A-7 脚本错红判出口径缺 error_scan;A-8 wd6 无断言支撑;A-9 d3 两引用址实为失真;A-10 grep 命令 -P 订正;A-11 fx 载荷与行的措辞;A-12 d15 注释随动)+两建议。v1.1 全部折入:d1 重定性(语义加固+夹具断言+生产盘面撤销申报)、d9 改合并制(V6 同绿硬约束入契约 5)、d12 扩两口、§0 换代(iter36 入库/S13 gdstyle/P0 令出处)、接口块全类型、撤销步全改「只回退本批 hunk」形态、wd6/wd7 补真断言、F1/F3/F9/F10/F12/F20 六条事实按 grilling 补查订正。复核结论:三阻断闭合、九应修落位(v1.1)。 - -**grilling 第二轮复核记录(2026-10-25,同代理续核)**:总判**通过**——d1 重定性自洽(夹具钉语义+生产盘面撤销申报三件自洽)、d9 合并制经 modal_flow_test :40-60 全序列推演与 V6 同绿、d12 两口齐、九应修全折入。拟议的「d9 push_error 是否误触 error_scan」经在库反证消解(config_db_fatal_oob_test 成功路径必经 push_error 而长期绿——本引擎 push_error 输出不含 SCRIPT ERROR 标记,非问题)。余四条建议级:分类口径统一(完成定义 2 d17 单列)、wd7 时点钉死关间态、d9 残边注记(L2)、wa8→u7–u9 与应修计数订正——v1.2 已全部折入。零阻断零应修,卡进可编排态。 diff --git a/reports/需求卡片-重构boardview拆件批-2026-10-24.md b/reports/需求卡片-重构boardview拆件批-2026-10-24.md deleted file mode 100644 index 3ebfb978..00000000 --- a/reports/需求卡片-重构boardview拆件批-2026-10-24.md +++ /dev/null @@ -1,155 +0,0 @@ -# 需求卡片:重构 boardview 拆件批(瓦片件 TilePalette+英雄精灵件 HeroSprite 拆出) - -- 版本:**v1.2**(2026-10-24 v1.0 成文→2026-10-25 grilling 第一轮修订 v1.1〔三阻断+四应修折入〕→第二轮复核「通过(附应修)」,两条应修(查表族棋盘态依赖/TILE_ 常量直读归属)折入定稿;复核结论见 §12) -- 来源:[待办清单](../待办清单.md)「game 代码结构审查批——行为缺陷定点修+结构重构候选」条目之结构重构候选(2026-10-24 用户裁**五件全部立项**,本卡为 board_view 拆件件)(唯一事实源:工程直入证据指认=审计报告 [reports/审计-game代码结构审查-2026-10-24.md](审计-game代码结构审查-2026-10-24.md) §三 大文件表 board_view 行+本写卡会话结构实查〔本卡 §3——审计「瓦片算法 ~230 行」经实查须并入查表族与缓存态方成立,§12 订正回呈〕) -- 归属:`game/presentation/views/board_view.gd`(拆出源)+ `game/presentation/views/tile_palette.gd`(**新增**·瓦片件——静态算法+装载缓存+查表一体)+ `game/presentation/views/hero_sprite.gd`(**新增**·英雄精灵件,Node2D)+ 调用点改引 -- 排期:未编入(迭代批卡片;估时 ≈1.5 工作日);**前置=卡①先行落地**(其 d18 触点 :998 在瓦片族内);与 R1/R3 零文件交叠 -- 开工门:U0+grilling 复核门过(§12) - -## 0. 前置依赖与并行性 - -- **第〇步核对(2026-10-24 写卡会话)**:来源条目设计面=工程直入、状态=待写卡;重构候选 2026-10-24 用户裁全部立项——工程直入重构类,凭审计 §三行号级证据放行。 -- **顺序硬约束**:卡①先行(d18 lumas_out 类型补齐后随族迁移,等价搬运不回退);卡② S9 注解若先行,board_view 位点注解随迁。R1/R3 零文件交叠可并行波。 -- **与迭代 36(已实现入库)**:文案清扫批 wa8 触点 `board_view.gd:730/:778/:782`(字形与哨兵)与 iter36 wc5 的 `ui_contrast_probe.gd:150` 直引(grilling 实查仍在原址未漂)——本批直引改引后探针调用同步改址(B2 清单),零 hunk 交叠实测成立。 -- **gdstyle S13(已落地)**:两新件落在 S13 扫描面——基线零新增。 -- 棋盘 golden 基线由 `board_shot_probe`+auto_player novice 档生成(文案卡 F7 同源事实)——本批等价拆件,基线像素必须逐位不变。 - -## 1. 目标与完成定义 - -**目标**:board_view.gd(1219 行,五职责混装)的「最划算两刀」落地——①瓦片族(装载/翻转/明度归一/墨族模式/对比度/哈希查表,静态算法+装载缓存一体)拆出为 `tile_palette.gd`;②英雄精灵族(~103 行)拆出为 `hero_sprite.gd`(Node2D 件——`create_tween()` 为 Node 方法,硬约束)——board_view 收在 **≈880 行**(实码重算:净拆出 ≈341 行),退回「格面渲染+输入翻译+探针面」三职责;零行为变更,棋盘 golden 三图逐像素不变。 - -**改动单元清单**(🔴 契约 1): - -| # | 拆出块(实查行界与净行数) | 去向 | 依据 | -|---|---|---|---| -| r2-1 | 瓦片族全带:`_load_board_tiles`(:932,**持七成员装载缓存** :970-978:`_tiles`/`_floor_tiles`/`_tiles_board_type`/`_tiles_ok`/`_digit_dark_ink`/`_digit_plate`/`_seal_tex`——grilling 订正「无状态装载器」失实)/`_request_dungeon_fallback`(:980)/`_flip_tex`(:987)/`_normalize_floor_luma`(:998)/`_srgb_lin`(:1024)/`_digit_ink_mode`(:1040)/`_contrast_y`(:1069)/`_ink_y`(:1074,`_contrast_y` 唯一消费——随迁防跨件反调)/`_cell_hash`(:1081,查表族消费)+**查表族** `_is_wall`(:1089)/`_wall_tile_for`(:1098)/`_wall_center_tile`(:1136)/`_floor_tile_for`(:1155)——:932-1155 净 ≈238 行 | 新件 `tile_palette.gd`=**瓦片件**(装载缓存七成员随迁持于件内+六 static 算法+查表族经**棋盘态注入**——第二轮复核订正:`_is_wall`(:1089-1093) 读 `_board.width/height`+`_minesweeper.is_revealed(c)`、`_wall_tile_for` 四邻判定全经 `_is_wall`,件内拿不到棋盘态——查表族以件持 board/minesweeper 引用实现(rebuild 换板时随 `load_board_tiles` 同步更新),board_view 持 `tile_palette` 实例成员;v1.0「纯静态装配器」形态经 grilling 订正——装载器持缓存态,件形态定案为「持缓存装配器」) | 审计 §三「瓦片静态算法+哈希查表(~230 行)」——查表族并入方达该行数口径(§12 订正回呈) | -| r2-2 | 英雄精灵族:`_update_hero`(:387)/`_place_hero`(:410)/`_hero_frame_flash`(:434)/`notify_hero_move`(:451)/`play_hero_dig_bounce`(:457)/`_hero_feet`(:480)/`hero_hand_point`(:487)/`hero_sprite_scale`(:493)——净 ≈103 行(`cell_origin` :472-479 留驻) | 新件 `hero_sprite.gd`=**Node2D 件**(形态定案依据:`create_tween()` 是 Node 方法,英雄族三处调用——RefCounted 无此 API;grilling 订正 v1.0 两态待定);依赖面=grilling 实测清单:`_cell`/`SEP`/`_svp_container.position` 三几何量+`_world`+三纹理+状态量+tween 句柄(`_hero_feet` 自算坐标不调 `cell_origin`——B3) | 审计 §三「英雄精灵状态机(~110 行)」 | -| r2-3 | 调用点改引:board_view 内部消费点+外部直引三处(`terrain_tile_sets_test.gd:135` `_normalize_floor_luma`+`:139` `_digit_ink_mode`——grilling 补查、`ui_contrast_probe.gd:150` `_normalize_floor_luma`) | 直引改 `TilePalette.` 前缀同形调用(断言判据零变化);board_view 内部改经件成员/类前缀 | 探针/测试缝 | - -**完成定义**: - -1. 三单元落地;**行数读数记录**(board_view 预期 ≈880、两新件各自净行数)——读数项非停机闸(拆出量以函数清单为准); -2. **等价三证**齐:①逐字迁移核对表(六 static 逐字搬运;装载器/查表/英雄族除接收者化与缓存成员随迁外逐字一致);②全套闸绿+改前改后读数对拍;③**棋盘三图 golden 逐像素 changed==0**(瓦片归一/墨族/英雄渲染全在像素路径上——本卡最强等价判据)+菜单 golden 同判; -3. 两新件合规(S9 全类型/S10 头注/S11 零禁用词/S13 基线零新增); -4. 至少一条撤销证明记录(T2);三份产出物写齐(T3)。 - -## 2. 设计事实源 - -| # | 出处 | 取用条款 | -|---|---|---| -| D1 | 工程直入证据指认=审计 §三 大文件表 board_view 行(1219 行;「最划算两刀」指认)+本会话结构实查(§3——查表族与缓存态归属经 grilling 订正,~230 行口径以并入查表族成立) | 拆出块清单与行界 | -| D2 | [code-conventions](../design/engineering/code-conventions.md) §1 结构纪律 | 一文件一职责;迁移禁顺手改写 | -| D3 | [architecture](../design/engineering/architecture/architecture.md) 呈现层 D1 行 | BoardView=Control 壳+SubViewport+Node2D 世界的探针契约面不变 | -| D4 | 2026-10-24 用户 P0 令 | 行为验收只走撤销证明,禁注入造红 | - -## 3. 现状事实(2026-10-24 写卡会话实查+2026-10-25 grilling 实测;开工日 T0 复核行号) - -| # | 事实 | 出处 | -|---|---|---| -| B1 | board_view.gd 1219 行;瓦片族全带 :932-1155(六 static+装载器+fallback+`_ink_y`+`_cell_hash`+查表四函数);**装载器持七成员缓存** :970-978(`_tiles`/`_floor_tiles`/`_tiles_board_type`/`_tiles_ok`/`_digit_dark_ink`/`_digit_plate`/`_seal_tex`)——「实例无状态」陈述失实(grilling 订正) | `game/presentation/views/board_view.gd:932-1155` | -| B2 | 外部直引三处:`terrain_tile_sets_test.gd:135`(`BoardViewScript._normalize_floor_luma(tex, 0.10, lumas)`)+`:139`(`BoardViewScript._digit_ink_mode(lumas)`)+`ui_contrast_probe.gd:150`(`BoardView._normalize_floor_luma(tex, 0.10, null)`——iter36 wc5 后仍在原址);其余函数直引面 T0 复扫 | 上列 file:line | -| B3 | 英雄族依赖面(grilling 实测):`_hero_feet`(:480) 自算 `Vector2(c)*(_cell+SEP)` **不调 cell_origin**;实际耦合=`_cell`/`SEP`/`_svp_container.position`(`hero_hand_point` :487 读)三几何量+`_world`+三纹理+状态量+三 tween 句柄;**board_view 无 HeroState 枚举**(全文件 grep 零命中——英雄态=纹理切换+布尔+tween 句柄,v1.0「HeroState 迁移与否 T0 定」悬空点撤销) | `game/presentation/views/board_view.gd:387-497` | -| B4 | 瓦片族消费链:`_load_board_tiles`←`rebuild`(:258)/`set_desaturated`;`_digit_ink_mode`←`_paint_cell`(:708) 墨族模式;查表族←`_paint_cell` 装配路径(:1145/:1156 消费 `_cell_hash`)——全部在棋盘像素路径上(golden 铁证依据)。**查表族棋盘态依赖(第二轮复核)**:`_is_wall` 读 `_board` 尺寸+`_minesweeper.is_revealed`——件需棋盘引用注入且随换板更新;**TILE_ 常量族**(`TILE_WALL_NAMES`/`TILE_FLOOR_VARIANTS`/`TILE_FLOOR_FMT`,装载器 :936-937 注释自证被测试直读)外部直读五处:`terrain_tile_sets_test.gd:44/45/125`、`ui_contrast_probe.gd:146/147`——常量随件迁(瓦片域唯一宿主),五处直读同批改引 | `game/presentation/views/board_view.gd:258/708/932-1155`;`game/tests/unit/presentation/terrain_tile_sets_test.gd:44/45/125`;`game/probe/ui_contrast_probe.gd:146/147` | `game/presentation/views/board_view.gd:258/708/932-1155` | -| B5 | 卡① d18 触点 `lumas_out`(:998) 在瓦片族内(前置落地后随族迁移);卡① d2/d12 触点在 standee/stage/row_pacer 零交叠;iter36 wa8 触点 :730/:778/:782 在两族带外 | 卡① §1;§0 | - -## 4. 设计契约(红线,违反 = L3 停机呈报) - -🔴 **契约 1 · 零行为变更**:六 static **逐字搬运**(签名含默认参逐字一致,仅类宿主变);装载器/查表/英雄族搬运除接收者化(self→件)与缓存成员随迁外逐字一致;禁顺手改算法、禁改默认参值、禁合并/删函数;装载缓存语义不变(同板型复用/换板重载时序零变化)。 - -🔴 **契约 2 · golden 逐像素铁证**:棋盘三图+菜单 golden changed==0;任何非 0 即 L3(瓦片族与英雄渲染在像素路径上,非 0=等价性破)。 - -🔴 **契约 3 · 探针/测试缝**:探针对 BoardView 的公开面与快照断言零变化;B2 三处直引同批改引(`BoardViewScript._normalize_floor_luma`→`TilePalette.normalize_floor_luma` 同形)且改引后套件绿、断言判据零变化。 - -🔴 **契约 4 · 新件形态定案**:`tile_palette.gd`=**持缓存装配器**(七成员装载缓存+查表读缓存+六 static 算法一体;「纯静态零成员」形态经 grilling 否决);`hero_sprite.gd`=Node2D 件(`create_tween` 硬约束);两件头注 S10 形制+S9 全类型+S11 零禁用词+S13 基线零新增。 - -## 5. 范围边界 - -**做**:r2-1~r2-3+等价三证+撤销证明+产出物。 - -**不做**:board_view 其余三职责(格面渲染/输入翻译/探针面)的再拆分;瓦片算法本身的重写/优化(明度归一带与 Bible 冻结值不动);§三「其余小件」中 level_map_screen 事项;R1/R3 面。 - -## 6. 关联文档同步面 - -| 文档 | 关联点 | 同步义务 | -|---|---|---| -| [architecture](../design/engineering/architecture/architecture.md) 呈现层 D1 行/装配节 | 两新拆件登记(views 层件清单变化) | 反哺点 · 列清单(①) | -| [HUD框架](../design/gdd/systems/表现与支撑/HUD框架.md)(若逐件登记 views 清单) | 新件两行(T0 核是否列件清单) | 反哺点 · 列清单(②) | -| `待办清单.md`/`roadmap.md` | 条目拆分注记与迭代行编入笔 | 反哺点 · 列清单(③④) | -| `game/tests/`、`game/probe/` 引用点 | 实施本体 | 实施本体(可写) | - -## 7. 任务卡 - -### 卡前 · 文件结构图 - -| 文件 | 职责 | 行为改动 | -|---|---|---| -| `game/presentation/views/board_view.gd` | 两族拆出+件成员持用+调用点改引(复用既有·修改) | 非行为改动(等价迁移)→ wr1 | -| `game/presentation/views/tile_palette.gd` | **新增**:瓦片件(装载缓存+查表+六 static)(新增·新文件) | 非行为改动 → wr1 | -| `game/presentation/views/hero_sprite.gd` | **新增**:英雄精灵件 Node2D(新增·新文件) | 非行为改动 → wr1 | -| tests/probe 直引三处 | 改引(复用既有·修改) | 非行为改动 → wr1 | - -### 接口块 - -- `tile_palette.gd`:`extends RefCounted`;件内七成员装载缓存(随迁原名);`load_board_tiles(board_type: String)`(装载器——缓存语义不变;rebuild 换板时同步更新件内 board/minesweeper 引用)+查表四函数(读件内缓存+**件持棋盘引用**——`_is_wall` 依赖面,签名同形去下划线 L1)+件内 `const TILE_WALL_NAMES/TILE_FLOOR_VARIANTS/TILE_FLOOR_FMT`(常量随迁——瓦片词表唯一宿主,五处外部直读改引)+六 static(`flip_tex`/`normalize_floor_luma`/`srgb_lin`/`digit_ink_mode`/`contrast_y`/`ink_y`——签名含默认参逐字一致);board_view 持 `var _palette` 实例成员。 -- `hero_sprite.gd`:`extends Node2D`(定案——`create_tween()` Node 硬约束);持英雄精灵与状态;`update_hero/place_hero/frame_flash/notify_move/play_dig_bounce/feet/hand_point/sprite_scale`(原签名同形、接收者化;几何三量经构造注入)。 -- 最窄闸:`terrain_tile_sets_test` 单件直跑+`tools/error_scan.py` 判红口径;探针 `probe_gate.sh` 段单跑;渲染 `render_gate.sh`(golden 判据主战场)。 - -### 落位与可复用性预检 - -- **`tile_palette.gd`**——复用面:为什么不并入 ui_kit(既有 848 行静态宿主):ui_kit 是全 UI 共用色彩/字体/控件装配器,瓦片算法+装载缓存+查表是棋盘域私有——并入=跨域混装再增重;为什么留在 board_view 不可:五职责混装即本批拆因。唯一面:瓦片装载/明度算法/查表唯一宿主(B4 单源)。分层:presentation 层件零新依赖(纯 Image/Texture 计算+自有缓存)。 -- **`hero_sprite.gd`**——复用面:全库无第二英雄小人实现(standee=B 区站桩世界精灵,域与形态都不同);为什么 Node2D:`create_tween` 硬约束(B3)。唯一面:英雄小人状态机唯一宿主。分层:views 层件,几何依赖经注入。 -- **零新增算法**:全部为既有函数搬运。 - -### T0 事实坐实(≈0.25d) - -- [ ] 复核 B1-B5+外部直引复扫(grep tests/probe/sim 对瓦片全带**与英雄族八函数名与 TILE_ 三常量名**)+装载缓存成员与消费点对照(:970-978 七成员的读写点全列+查表族棋盘态读写点)。可核查结果:直引与缓存消费点清单进实现报告素材。 -- [ ] 改前读数(行数、套件通过数、golden 基线指纹)。可核查结果:基线表在案。 - -### T1 两族拆出+改引(≈1d) - -- [ ] 实装 r2-1(tile_palette 新件:装载缓存随迁+查表+六 static;board_view 件成员+调用点改引+B2 三直引点改引)→ r2-2(hero_sprite 新件:接收者化迁移+几何注入)→ r2-3(直引复扫改引)。可核查结果:board_view ≈880 行读数记录;逐字迁移核对表逐函数落位。 -- [ ] 验收步骤:全套闸按域选档全绿+棋盘三图与菜单 golden changed==0(契约 2——非 0 即停)。 - -### T2 撤销证明+产出物落位卡(≈0.25d,末卡) - -- [ ] 撤销证明一条(P0 令形态):回退 `terrain_tile_sets_test.gd:135` 改引 hunk(回指 `BoardViewScript._normalize_floor_luma` 旧址)→ 单测直跑经 error_scan 扫出 SCRIPT ERROR 红(旧址符号已随迁移消亡)→ 精确还原复绿。记录进实现报告。 -- [ ] 写齐三份产出物(实现报告含逐字迁移核对表+行数与 golden 读数+实机目检总清单〔三关瓦片明度/数字墨色/英雄移动与挖掘弹跳抽查〕/代拍板追认单/反哺点)。 -- [ ] 合并前置:独立验收审计(同卡①口径)逐行「通过」。 - -**估时合计 ≈1.5d**。编排注记:前置卡①;与 R1/R3 可同迭代分波;golden 判据重(渲染闸必跑)。 - -## 8. 验证闸 - -### 改动—验收对照表 - -| 需求点 | 改动 | 验收测试(尾缀) | 层级 | 真实入口 | 撤销证明 | -|---|---|---|---|---|---| -| wr1 两族拆出等价 | board_view 两族迁出+改引 | 全套闸绿+golden changed==0+逐字迁移核对表〔wr1〕 | 集成+渲染 | 棋盘真实渲染路径(board_shot_probe/golden)+探针 | 撤销证明一条(T2);等价三证替代逐单元撤销(等价重构申报) | -| wr2 新件合规 | 两新件 | S9/S10/S11/S13 四闸零命中〔wr2〕 | 静态扫描 | 闸真实跑批 | 回退类型/头注 → 对应闸红 | -| wr3 产出物 | T2 三份 | 件核对〔wr3〕 | — | — | 缺项红 | - -**测试削弱申报**:零(terrain_tile_sets_test 改引仅换址,断言判据零变化)。**独立验收审计**:同卡① §8.3 口径。 - -## 9. 异常分级 - -- **L1(自救)**:两新件命名与函数名前缀取舍(签名语义不变前提);几何注入面最小化写法。 -- **L2(记录继续)**:直引清单个别点改引适配;行数读数与 ≈880 的正常偏差。 -- **L3(停机呈报)**:golden changed>0(等价性破);英雄族依赖面比 B3 清单宽(接收者化不可行——判离指认失真回呈);任一套件非本批存量红;装载缓存语义无法等价随迁(消费点比 T0 清单宽)。 - -## 10. 代拍板白名单 - -1. 两新件命名与函数名前缀取舍(签名语义不变前提)。 -2. hero_sprite 几何注入的具体形态。 -3. 提交切分与 worktree 分支命名。 - -## 11. 待裁点 - -**零待裁点,开工门=U0。** 两处灰色地带推导定值(呈报可否决,随 T2 反哺点):①瓦片族落**独立 tile_palette**而非并入 ui_kit——依据=全 UI 共用宿主与棋盘私有算法+缓存的域界;备选「并入 ui_kit」淘汰依据=跨域混装+该件已 848 行再增重。②tile_palette 定形态为**持缓存装配器**(非纯静态)——依据=装载器七成员缓存的实存(B1);备选「缓存留宿主、件纯静态」淘汰依据=缓存与查表同源、拆两处即双写。hero_sprite 的 Node2D 形态非灰色地带(API 硬约束)。 - -## 12. 自审六查与 grilling 复核记录 - -**自审六查(2026-10-24 写卡会话)**:①规格覆盖——r2-1~r2-3→T1、合规/撤销/产出物→T2,指到 wr1–wr3;②步骤扫描——一步一动作一可核查结果;③类型一致性——接口块签名与 B1-B4 实查一致(v1.1 撤销 HeroState 悬空点、形态定案);④审查焦点——golden 逐像素铁证(契约 2)、B2 三直引判据不变、缓存消费点 T0 全列义务;⑤篇幅比例——零实现体;⑥落位预检——两新件不能复用既有件的理由与唯一落点已答。 - -**grilling 第一轮复核记录(2026-10-25,独立子代理对抗复核)**:判回炉三阻断——R2-A1 行数目标与清单算术断裂(v1.0 七函数 ≈156+103 行拆出后 ≈959>900,须并查表族方达 ≈887);R2-A2 `_load_board_tiles` 实持七成员缓存(:970-978),「无状态装载器」失实且与「纯静态」契约互斥;R2-A3 T2 接线变异违 P0 令。随同四应修(F1 漏 `_ink_y`/`_cell_hash`;F2 HeroState 枚举不存在于 board_view;F3 直引三处实测+iter36 陈述;F4 hero_sprite 形态缺决定性依据〔create_tween 系 Node 方法〕)。v1.1 全部折入:查表族+`_ink_y`+`_cell_hash` 并入 r2-1(≈238 行、board_view ≈880 可达)、tile_palette 定形态持缓存装配器(契约 4 重立)、hero_sprite 定 Node2D、HeroState 悬空点撤销、B3 依赖面按 grilling 清单写实、证明改撤销形态(回退改引 hunk→error_scan 红)、iter36 关系实测陈述。复核结论:三阻断闭合、四应修落位(v1.1)。 - -**grilling 第二轮复核记录(2026-10-25,同代理续核)**:总判**通过(附应修)**——三阻断真闭合(行数账自洽 1219−341≈878、缓存态契约重立、撤销证明合规)。新发现两应修:①查表族棋盘态依赖缺列(`_is_wall` 读 board 尺寸+is_revealed——接口块「读件内缓存」覆盖不了,件需棋盘引用注入);②TILE_ 常量族外部直读五处在册未列(terrain_tile_sets_test:44/45/125+ui_contrast_probe:146/147,T0 函数名 grep 兜不住)。v1.2 折入:B4 补两依赖面、接口块补棋盘引用注入与常量随迁、r2-3 直引清单+5、T0 复扫口径加常量名。零待裁点成立,卡进可编排态。 diff --git a/reports/需求卡片-重构main判离批-2026-10-24.md b/reports/需求卡片-重构main判离批-2026-10-24.md deleted file mode 100644 index cf7e804e..00000000 --- a/reports/需求卡片-重构main判离批-2026-10-24.md +++ /dev/null @@ -1,169 +0,0 @@ -# 需求卡片:重构 main 判离批(数据投影/只读解析回调/文案拼装三族出组合根) - -- 版本:**v1.2**(2026-10-24 v1.0 成文→2026-10-25 grilling 第一轮修订 v1.1→第二轮复核「通过(附应修)」,5 条小刀修正折入定稿;复核结论见 §12) -- 来源:[待办清单](../待办清单.md)「game 代码结构审查批——行为缺陷定点修+结构重构候选」条目之结构重构候选(2026-10-24 用户裁**五件全部立项**,本卡为 main.gd 判离件)(唯一事实源:工程直入证据指认=审计报告 [reports/审计-game代码结构审查-2026-10-24.md](审计-game代码结构审查-2026-10-24.md) §三 大文件表 main.gd 行+本写卡会话结构实查〔本卡 §3;审计「~440 行可判离/拆后 ~1070 行」读数经实查**不成立**,本卡按实码重算——§12 订正回呈〕) -- 归属:`game/presentation/main.gd`(判离源)+ `game/presentation/hud_presenter.gd`(投影/文案承接件·复用既有)+ `game/presentation/board_resolvers.gd`(**新增**·解析回调承接件) -- 排期:未编入(迭代批卡片;估时 ≈1–1.5 工作日);**前置=卡①(game 行为缺陷定点修批)先行落地**(§0) -- 开工门:U0+grilling 复核门过(§12) - -## 0. 前置依赖与并行性 - -- **第〇步核对(2026-10-24 写卡会话)**:来源条目设计面=工程直入、状态=待写卡;重构候选经 2026-10-24 用户裁全部立项另立三卡(R1/R2/R3)——工程直入重构类,凭审计 §三行号级证据放行。 -- **顺序硬约束**:卡①先落地(其 d6/d7 守卫触点在本文件 :712-717/:1486-1491);卡②(S9 注解批)若先行,其 main.gd:565 位点的注解**不随判离迁移**(:565 `build_adventure_params` 不在任何判离块——grilling 订正 v1.0 误述)。 -- **与迭代 36(已实现入库,9468c70e 等)**:iter36 卡③静态扫描归零提交含「main/_status_curios 容器参数化」——恰落在 r1-1 五函数之一的 `_status_curios` 上;**迁移基线=当前码**(iter36 已并入,无并行冲突),T0 复核现状行号。 -- 与 R2(board_view 拆件)/R3(runner 拆件)零文件交叠,可同迭代不同波或分迭代。 -- **gdstyle S13(已落地)**:新件 `board_resolvers.gd` 落在 S13 扫描面——基线零新增。 - -- **卡后换代注记(2026-10-25 补)**:迭代 35 卡②(5499eb9c)在本卡成文后合入——`main.gd` 1508→1507 行(判离块行界微漂);T0 复核按现行盘面重采,行数读数预期随动 ±5 行。 -- 探针缝约束(architecture「三屏壳与组合根句柄面」节):**探针挂载假设 `main.X` 一级句柄路径零迁移**——本卡判离的是私有函数族非句柄成员,句柄面零触碰;探针对判离函数的**五处直引**(M6)经三件薄委托保零改动(§1 r1-4)。 - -## 1. 目标与完成定义 - -**目标**:main.gd(1508 行)的三族可判离块迁出组合根——①数据投影族(五函数)②只读解析回调族(四函数)③文案拼装三件——HudPresenter 承接投影与文案、新件 board_resolvers 承接解析回调;探针直引面经薄委托零改动;判离=逐字等价迁移(零行为变更)。**行数读数(实码重算,非审计 ~440 原数)**:净判离≈140 行+三件薄委托回填≈12 行,main 收在 ≈1370–1380——审计「拆后回 ~1070」经实查不成立(其判离量读数无法复算,§12 回呈),1100 行上帝对象线的进一步下探不属本卡范围。 - -**改动单元清单**(🔴 契约 1): - -| # | 判离块(实查行界与净行数) | 去向 | 依据 | -|---|---|---|---| -| r1-1 | 数据投影族:`_status_data`(:1076-1090,15 行)/`_status_curios`(:1091-1098,8)/`_career_data`(:1126-1149,24)/`_save_select_data`(:1164-1177,14)/`_camp_data`(:1238-1241,4)——净 ≈65 行 | `hud_presenter.gd` 静态函数族(构造注入参数显式化——db/save/meta/run_scope 取值由调用侧传参);`_camp_data`/`_status_data`/`_status_curios` 三件在 main 留**薄委托**(探针直引零改动,r1-4) | 审计 §三「数据投影族」指认+HudPresenter 既有静态投影先例(feat_holdings_lines/tune_sim_report);行数=grilling 实测 | -| r1-2 | 只读解析回调族:`_identity_lookup`(:762)/`_variant_mask_lookup`(:773)/`_variant_mark_lookup`(:788)+`_debug_hero_override`(:755)——:755-798 净 ≈44 行 | 新件 `board_resolvers.gd`(构造注入 **db+debug_overlay+settlement 取值 Callable**——第二轮复核订正依赖面:`_identity_lookup` 三处读 `run_scope.settlement`〔:765 `minesweeper.current_board()`/:767 `presentation_snapshot()`/:769 `query_reward_identity()`〕、`_variant_*` 读 `db.variation.row_by_id`、`_debug_hero_override` 读 `db.hero`+调试滑条,无 meta);产出三个绑定解析 Callable+override 拼装;main :253-256 注入点改引 | 审计 §三「只读解析回调 L755-798」;解析器自成一族、依赖面窄 | -| r1-3 | 文案拼装三件:`_gp_summary_text`(:845-852)/`_bonus_summary_text`(:853-866)/`_phase_text`(:1393-1401)——净 ≈31 行 | `hud_presenter.gd` 静态函数(纯拼装零状态);`_gp_summary_text` 在 main 留**薄委托**(探针直引,r1-4) | 审计 §三「文案拼装三件」;HudPresenter 即文案拼装宿主 | -| r1-4 | 引用点:main 内部调用点改引+**探针直引五处经薄委托零改动**(`ui_click_probe.gd:74/101/112` 直调 `main._camp_data()`×3、`:789` 直调 `main._status_data()`〔iter36 sv7 断言(卡③状态面板瘦身批前缀)〕、`ui_struct_probe.gd:244` 直调 `main._gp_summary_text`——grilling 实查在册,theme_projection_test:810/825 两处注释级引用随动改写) | 三件薄委托保留(零参签名原样转发 HudPresenter 对应静态件);其余直迁 | 探针缝纪律+直引实测清单 | - -**完成定义**: - -1. 四单元落地;**行数读数记录**(main 收在 ≈1370–1380、HudPresenter/board_resolvers 各自净增行数)——读数项非停机闸(判离量以函数清单为准,§12 grilling 订正); -2. **等价三证**齐(🔴 契约 2):①逐字迁移核对表(逐函数:原行界→新落点,迁移体除接收者/参数化外逐字一致);②全套闸绿(按域选档,本批只碰 `game/`)+改前改后读数对拍;③渲染闸棋盘三图+菜单 golden 逐像素 changed==0——**resolver 族经 :253-256 注入正是棋盘徽章/数字遮蔽/角标的数据源,在像素路径上**(grilling 归因订正),等价则像素不变; -3. 新件合规:`board_resolvers.gd` 头注库内形制(S10)+全类型(S9)+零禁用词(S11)+S13 基线零新增; -4. 至少一条撤销证明记录(§7 T2——回退本批 hunk 形态);三份产出物写齐(T3)。 - -## 2. 设计事实源 - -| # | 出处 | 取用条款 | -|---|---|---| -| D1 | 工程直入证据指认=审计 §三 大文件表 main.gd 行+本会话结构实查(§3——行数与净判离量按实码重算,审计 ~440 读数不复算成立,订正回呈) | 判离块清单与行界 | -| D2 | [architecture](../design/engineering/architecture/architecture.md)「呈现层装配」节 | 组合根职责=接线(构建归屏壳/UiAssembler);探针 `main.X` 句柄路径零迁移;LAYER_CONTRACT 不动 | -| D3 | [code-conventions](../design/engineering/code-conventions.md) §1(结构纪律) | 一个职责一个文件;跨文件迁移禁顺手改写 | -| D4 | 2026-10-24 用户 P0 令 | 行为验收只走撤销证明(回退本批 hunk→红),禁注入造红 | - -## 3. 现状事实(2026-10-24 写卡会话实查+2026-10-25 grilling 实测;开工日 T0 复核行号) - -| # | 事实 | 出处 | -|---|---|---| -| M1 | main.gd 1508 行;三族行界——解析回调族 :755-798(四函数连续,净 ≈44 行)、文案两件 :845-866、投影族五函数 :1076-1241(净 ≈65 行,间隔开窗/关闭处理函数)、`_phase_text` :1393-1401;**净判离合计 ≈140 行**(grilling 逐函数实测) | `game/presentation/main.gd` 结构图谱 | -| M2 | resolver 注入点:`board_view.set_identity_resolver(_identity_lookup)`(:253)/`set_variant_mask_resolver`(:255)/`set_variant_mark_resolver`(:256)——`_build_ui` 内一次性接线;resolver 三函数经此注入是棋盘每格渲染数据源(徽章/数字遮蔽/角标) | `game/presentation/main.gd:253-256` | -| M3 | 解析回调族读态面(两轮复核订正):`_identity_lookup`(:762-772) 三处读 `run_scope.settlement`(:765 `minesweeper.current_board()`/:767 `presentation_snapshot()`/:769 `query_reward_identity()`);`_variant_mask/mark_lookup` 读 `db.variation.row_by_id`;`_debug_hero_override`(:755) 读 `db.hero`+`debug_overlay` 滑条,无 meta | `game/presentation/main.gd:755-798` | -| M4 | HudPresenter 现役形态:static 函数族(`refresh` :12 起、`feat_holdings_lines`/`tune_sim_report` 等),纯投影/拼装零成员态——承接 r1-1/r1-3 的先例形态 | `game/presentation/hud_presenter.gd` | -| M5 | 投影族消费面:`_status_data`→status_panel、`_career_data`→career_overlay、`_save_select_data`→save_select_overlay、`_camp_data`→camp_overlay、`_status_curios`→`_status_data` 内部 | `game/presentation/main.gd:1076-1241` | -| M6 | **探针/测试直引五处在册**(grilling 实查):`ui_click_probe.gd:74/101/112`(`main._camp_data()`×3)、`:789`(`main._status_data()` sv7 直调八键断言,iter36 wc7 刚落)、`ui_struct_probe.gd:244`(`main._gp_summary_text`);另 `theme_projection_test.gd:810/825` 两处注释级同构引用 | 上列 file:line | -| M7 | 卡①触点 :712-717/:1486-1491(先落地);`_tune_sim` 构造 :393;卡②触点 :565 **不在判离块** | §0 | - -## 4. 设计契约(红线,违反 = L3 停机呈报) - -🔴 **契约 1 · 零行为变更**:判离=逐字等价迁移——函数体除「成员访问改显式参数/接收者」外**逐字一致**;禁顺手重构、禁改名(承接件内函数名保持原名,静态化所需参数化最小化);`LAYER_CONTRACT` 与一切句柄成员零触碰;三件薄委托签名与现 main 私有函数逐字同形(`_gp_summary_text` 带参逐字转发——第二轮复核措辞订正)。 - -🔴 **契约 2 · 等价三证**:逐字迁移核对表+全套闸绿+golden(棋盘三图+菜单)逐像素 changed==0,三证缺一不算完成;changed>0 即 L3(resolver 在像素路径上——归因见完成定义 2)。 - -🔴 **契约 3 · 探针缝零迁移**:`main.X` 一级句柄路径与探针既有断言零变化;M6 五处直引经薄委托零改动(探针文件本批零改动);theme_projection_test 两处注释级引用随动改写(注释面非断言)。 - -🔴 **契约 4 · 新件合规**:`board_resolvers.gd` 一文件一职责(S10 头注+库内 VERTICAL SLICE 首行形制、S9 全类型、S11 零禁用词、S13 基线零新增);禁把判离块散落多处(r1-2 整族一个文件)。 - -🔴 **契约 5 · LAYER_CONTRACT 与模态登记零触碰**:判离块不含层序/登记逻辑;`_apply_layer_contract` 不动。 - -## 5. 范围边界 - -**做**:r1-1~r1-4 四单元+等价三证+撤销证明一条+产出物。 - -**不做**:main.gd 其余部分的继续拆分(`_build_ui`/信号处理族/开窗关闭处理族留驻——审计仅指认三族;1100 行线的进一步下探待后续批);R2/R3 各自文件;卡①缺陷面;§三「其余小件」中 main.gd 死变量/confirm 旁路/settle_batch 空判(未立项,留审计在册)。 - -## 6. 关联文档同步面 - -> 迭代批禁写 `design/` 与根规则文件;同步义务=列进 `reports/iter-反哺点-<完成日>.md` 归文档批。 - -| 文档 | 关联点 | 同步义务 | -|---|---|---| -| [architecture](../design/engineering/architecture/architecture.md) 呈现层 D1 行/呈现层装配节 | 新增 `board_resolvers.gd` 拆件+HudPresenter 职责扩列(投影/文案承接)=模块清单变化;架构文档 :149/151「17 项」计数漂移属条目④机械对齐件(零触碰、反哺点并呈) | 反哺点 · 列清单(①新件登记+HudPresenter 职责句扩列) | -| [HUD框架](../design/gdd/systems/表现与支撑/HUD框架.md)(若逐件登记呈现层清单) | 新件一行(T0 核该文档是否列件清单) | 反哺点 · 列清单(②) | -| `待办清单.md`/`roadmap.md` | 条目拆分注记与迭代行编入笔 | 反哺点 · 列清单(③④) | -| `game/` 探针/测试引用点 | 实施本体(本批预期探针零改动,注释级两处随动) | 实施本体(可写) | - -## 7. 任务卡 - -> 回归措辞:里程碑步出口一次全套闸;波内修复只做最窄确认。 - -### 卡前 · 文件结构图 - -| 文件 | 职责 | 行为改动 | -|---|---|---| -| `game/presentation/main.gd` | 三族判离+三件薄委托+调用点改引(复用既有·修改) | 非行为改动(等价迁移)→ wr1 | -| `game/presentation/hud_presenter.gd` | r1-1/r1-3 承接(复用既有·扩静态族) | 非行为改动 → wr1 | -| `game/presentation/board_resolvers.gd` | **新增**:r1-2 解析回调族承接(新增·新文件) | 非行为改动 → wr1 | -| `game/tests/unit/presentation/theme_projection_test.gd` | 两处注释级引用随动改写(复用既有·改注释) | 非行为改动 → wr1 | - -### 接口块 - -- `board_resolvers.gd` 形态(命名 L1 可调):`extends RefCounted`;`_init(db_ref, debug_overlay_ref, settlement_getter: Callable)`(依赖面=M3 实查——db+调试滑条+settlement 活态取值〔`_identity_lookup` 三读 settlement〕;无 meta);产出 `identity_lookup(c: Vector2i) -> Variant`/`variant_mask_lookup(c: Vector2i) -> bool`/`variant_mark_lookup(c: Vector2i) -> String`/`debug_hero_override() -> Dictionary`(签名与现 main 私有函数逐字同形,返回类型按现体实态补全)。 -- HudPresenter 承接函数命名:保持原名去下划线入静态族(`status_data`/`status_curios`/`career_data`/`save_select_data`/`camp_data`/`gp_summary_text`/`bonus_summary_text`/`phase_text`),参数表显式化(取值面=M5 消费链:db/save/meta/runner 状态由调用侧传参);命名差异属 L1。 -- 三件薄委托(main 内保留,签名逐字同形零参转发):`_camp_data()`/`_status_data()`/`_gp_summary_text()`——探针直引零改动的承载面;`_status_curios` 为 `_status_data` 内部调用,随委托链走(grilling 行数账注:委托回填 ≈12 行)。 -- 最窄闸:探针族=`tools/probe_gate.sh` 对应段单跑;单测=`--script` 单件直跑+`tools/error_scan.py` 判红口径;渲染=`tools/render_gate.sh`。 - -### 落位与可复用性预检 - -- **`board_resolvers.gd`(新文件)**——复用面:为什么不并入 HudPresenter:解析回调是**有态绑定件**(构造注入 db/滑条、产出绑定 Callable),HudPresenter 是无态静态族——混装破坏两者形态纪律;为什么不留在 main:审计判离指认+组合根只接线原则。唯一面:board_view 三个 resolver 的唯一供给源(注入点 :253-256 单点)。分层与交互面:presentation 层内部件,零 core 依赖(读 db 与调试滑条),零新跨层出口。 -- **HudPresenter 扩列**——复用面:既有静态投影/拼装宿主(M4),扩列即复用不新立;唯一面:投影/文案承接后仍是唯一宿主(与 board_resolvers 职责互斥:静态投影 vs 有态绑定)。 -- **三件薄委托**——为什么留:探针五处直引(M6)零改动的最小承载;备选「探针改引」淘汰依据=探针缝纪律(探针文件零改动优先)。 -- **零新增函数体**:全部迁移体为既有函数逐字搬运(薄委托为三行转发体)。 - -### T0 事实坐实(≈0.25d) - -- [ ] 复核 M1-M7(重点:M3 `_identity_lookup` 取值链实读;M6 五处直引现址复核;行号漂移)。可核查结果:直引清单+漂移声明进实现报告素材。 -- [ ] 改前读数(main.gd 行数、探针/单测/golden 通过数)。可核查结果:基线表在案。 - -### T1 三族判离+改引(≈0.75d) - -- [ ] 实装 r1-2(board_resolvers 新件+:253-256 注入改引)→ r1-1/r1-3(HudPresenter 扩列+main 调用点改引)→ r1-4(三件薄委托+theme_projection_test 注释随动)。可核查结果:`git diff` 迁移体逐字一致(核对表逐函数行界对);行数读数记录(预期 ≈1370–1380)。 -- [ ] 验收步骤:全套闸按域选档跑一次全绿+棋盘三图与菜单 golden changed==0(契约 2——非 0 即 L3)+探针全绿(M6 五处直引零改动即绿)。 - -### T2 撤销证明+产出物落位卡(≈0.25d,末卡) - -- [ ] 撤销证明一条(P0 令形态——只回退本批 hunk):回退 HudPresenter 承接件任一函数 hunk(如 `gp_summary_text` 承接体)→ main 委托/调用点引用未定义符号 → 单测/探针直跑输出经 `tools/error_scan.py` 扫出 SCRIPT ERROR 红 → 精确还原复绿。记录进实现报告。 -- [ ] 写齐三份:`reports/iter-实现报告-<完成日>.md`(逐字迁移核对表+行数读数+golden 读数+实机目检总清单〔三屏开合与状态面板/生涯/选档/营地投喂抽查〕)/代拍板追认单/反哺点(§6 各条落点)。 -- [ ] 合并前置:独立验收审计(未参与实现的会话/子代理,只读本卡/diff/测试件+抽查迁移体逐字一致性)报告逐行「通过」。 - -**估时合计 ≈1.25d**。编排注记:前置卡①落地后开工;估时低于 iter-plan 硬下限,与 R2/R3 或他卡同批编入。 - -## 8. 验证闸 - -### 改动—验收对照表 - -| 需求点 | 改动 | 验收测试(尾缀) | 层级 | 真实入口 | 撤销证明 | -|---|---|---|---|---|---| -| wr1 三族判离等价 | main.gd 三族迁出+薄委托+改引 | 全套闸绿+golden changed==0+逐字迁移核对表〔wr1〕 | 集成+渲染 | 探针真实驱动路径(五直引零改动)+面板真实投喂链 | 撤销证明一条(T2——回退承接 hunk→error_scan 红);等价三证替代逐单元撤销(等价重构申报) | -| wr2 新件合规 | board_resolvers.gd | S9/S10/S11/S13 四闸零命中〔wr2〕 | 静态扫描 | 闸真实跑批 | 回退头注/类型 → 对应闸红 | -| wr3 产出物 | T2 三份 | 件核对〔wr3〕 | — | — | 缺项红 | - -**测试削弱申报**:零(既有断言零改动——theme_projection_test 两处为注释级随动)。**独立验收审计**:同卡① §8.3 口径。 - -## 9. 异常分级 - -- **L1(自救)**:承接件命名(去下划线入静态族等);参数化的最小形态。 -- **L2(记录继续)**:行数读数与 ≈1370–1380 预期的正常偏差(判离量以函数清单为准)。 -- **L3(停机呈报)**:迁移体无法逐字等价(依赖面比 M3 宽——判离指认失真,回呈审计结论);golden changed>0;任一探针/套件出非本批存量红(含 M6 直引处变红)。 - -## 10. 代拍板白名单 - -1. board_resolvers.gd 与承接函数命名(接口块签名语义不变前提)。 -2. 参数化取值的具体形态。 -3. 提交切分与 worktree 分支命名。 - -## 11. 待裁点 - -**零待裁点,开工门=U0。** 一处灰色地带推导定值:解析回调族落**新件**而非 HudPresenter——依据=有态绑定件与无态静态族的形态纪律(预检节);备选「并入 HudPresenter」淘汰依据=形态混装。另一处:探针直引面走**薄委托**而非探针改引——依据=探针缝纪律(探针零改动优先);备选「探针改引」淘汰依据=改探针引入额外验证面。两处均呈报可否决(随 T2 反哺点)。 - -## 12. 自审六查与 grilling 复核记录 - -**自审六查(2026-10-24 写卡会话)**:①规格覆盖——r1-1~r1-4→T1、合规/撤销/产出物→T2,指到 wr1–wr3;②步骤扫描——一步一动作一可核查结果;③类型一致性——接口块签名与 M1-M6 实查一致;④审查焦点——探针直引五处(M6→薄委托)、golden 归因(resolver 在像素路径——契约 2)、iter36 迁移基线换代;⑤篇幅比例——重构卡零实现体;⑥落位预检——新件唯一性、HudPresenter 复用、薄委托理由已答。 - -**grilling 第一轮复核记录(2026-10-25,独立子代理对抗复核)**:判回炉两阻断——R1-A1 行数目标与判离清单算术断裂(实查净判离 ≈140 行,≤1100 差约 260 行不可达,审计 ~440/1070 读数自身不成立);R1-A2 T2「接线变异」违 2026-10-24 P0 令。随同五应修(F1 M6 直引五处实测在册;F2 依赖面错位〔meta→实为 db〕;F3 golden 归因失真〔resolver 在像素路径〕;F4 :565 不随迁误述;F5 iter36 已入库且 `_status_curios` 参数化交叠)。v1.1 全部折入:行数账按实码重算(≈1370–1380 读数记录、1100 线下探移出范围、审计读数订正回呈)、证明改撤销形态(回退承接 hunk→error_scan 红)、依赖面/golden 归因/:516 直引清单/iter36 基线换代全部订正、薄委托方案定案(§11 呈报)。复核结论:两阻断闭合、五应修落位。 - -**grilling 第二轮复核记录(2026-10-25,同代理续核)**:总判**通过(附应修)**——第一轮两阻断真闭合、行数账自洽、证明形态合规、薄委托方案成立。新发现一条应修(接口块 v1.1 修订时把 v1.0 原本正确的 settlement 注入弄丢——`_identity_lookup` 三处读 `run_scope.settlement`,两参注入面无法支撑)+两条建议(M6「wc7」实为 sv7;「零参转发」对带参 `_gp_summary_text` 不精确)。v1.2 折入:接口块/M3/r1-2 恢复 settlement 取值面(三参注入)、sv7 订正、「签名逐字同形转发」措辞。零待裁点成立,卡进可编排态。 diff --git a/reports/需求卡片-重构runner拆件与断点schema单点批-2026-10-24.md b/reports/需求卡片-重构runner拆件与断点schema单点批-2026-10-24.md deleted file mode 100644 index 9076635e..00000000 --- a/reports/需求卡片-重构runner拆件与断点schema单点批-2026-10-24.md +++ /dev/null @@ -1,173 +0,0 @@ -# 需求卡片:重构 runner 拆件与断点 schema 单点批(CurioDeck+商店件+断点字段单源) - -- 版本:**v1.2**(2026-10-24 v1.0 成文→2026-10-25 grilling 第一轮修订 v1.1〔两阻断+五应修折入〕→第二轮复核「通过(附应修)」,两条应修(行数读数按新边界重算/契约 5 接线行例外)折入定稿;复核结论见 §12) -- 来源:[待办清单](../待办清单.md)「game 代码结构审查批——行为缺陷定点修+结构重构候选」条目之结构重构候选(2026-10-24 用户裁**五件全部立项**,本卡收 adventure_runner 令牌池拆件+level_runner 商店拆件+断点 schema 单点化三件)(唯一事实源:工程直入证据指认=审计报告 [reports/审计-game代码结构审查-2026-10-24.md](审计-game代码结构审查-2026-10-24.md) §三 大文件表 adventure_runner/level_runner 行+断点 schema 段+本写卡会话结构实查〔本卡 §3——审计「断点三处重复登记」经实查**save 侧零键登记**,「三处」订正为写/读两侧,§12 回呈〕) -- 归属:`game/core/adventure_runner.gd`(令牌池拆出源)+ `game/core/level_runner.gd`(商店拆出源)+ `game/core/curio_deck.gd`(**新增**·令牌池件)+ `game/core/shop_session.gd`(**新增**·商店件,命名 L1 可调)+ 断点字段单源(宿主内 const,§11 定值)+ 断点域测试面 -- 排期:未编入(迭代批卡片;估时 ≈2–2.5 工作日);**前置=卡①先行落地**(其 d11 表读触点在商店族内) -- 开工门:U0+grilling 复核门过(§12) - -## 0. 前置依赖与并行性 - -- **第〇步核对(2026-10-24 写卡会话)**:来源条目设计面=工程直入、状态=待写卡;重构候选 2026-10-24 用户裁全部立项——工程直入重构类,凭审计 §三行号级证据放行。 -- **顺序硬约束**:卡①先行(d11 表读替换落在 `_spend_discounted`/`pool_price` 内——先修后迁,迁移体带上修好的表读形态);卡②注解若先行随迁。R1/R2 零文件交叠,可并行波。 -- **与迭代 36(已实现入库)**:iter36 卡C wc2 落「断点快照喂入→`resume_adventure` :151-154 真链回填、run_ledger.bonus.atk==5」续玩断言(ui_click_probe)——**现役最强断点行为回归,纳入 T0 基线与本批回归面**;本批触 `resume_adventure` 读写侧,该断言必须全程绿。 - -- **卡后换代注记(2026-10-25 补)**:迭代 35 卡②(5499eb9c)在本卡成文后合入,触两卡面:①断点键集换代——写侧 `template_pick` 键退役、换代差标记 `graph_gen_v2: 1`(adventure_runner:1014),`resume_adventure` 增缺键显式拒绝(:146-149);**顶层键数仍 27、键清单以 T0 现行盘面重采为准**(本卡 A3 清单按此换代,读侧策略词表不变——graph_gen_v2 归 plain 类缺省);`_start_current_level` 二次消费面相应变化(template_pick 出列,T0 重列);②`level_runner.gd` 行号漂移(商店族整体 −12 行上下,函数清单不变);③`resume_adventure` 104→108 行(代差检查四行入读侧——r3-3 单源化覆盖面含它)。 -- **断点兼容硬约束**:断点单点化**不改任何键名/缺省值/回退语义**——旧档读取行为逐位不变;`breakpoint_resume_test`+iter36 wc2 断言是回归主线。 -- **gdstyle S13(已落地)**:两新 core 件落在 S13 扫描面——基线零新增。 - -## 1. 目标与完成定义 - -**目标**:①adventure_runner(1049 行)的**池与判定面**(令牌多重集/抽签/cap/凭证判定/库存查询,≈340 行)拆出为 `curio_deck.gd`——**停点编排与兑付推进面留宿主**(边界见 r3-1);②level_runner(1012 行)的商店族(21 函数,≈260 行)拆出为 `shop_session.gd`;③断点 schema 写/读两侧重复登记单点化为字段登记单源——三件全零行为变更,断点档与续玩行为逐位不变。 - -**改动单元清单**(🔴 契约 1): - -| # | 单元(实查) | 改法 | 依据 | -|---|---|---|---| -| r3-1 | **迁出面**(池与判定):成员 `_curio_deck`(:60)/`_curio_taken`(:59)/`_feat_ticket_pity`(:67)/`_feat_ticket_sold`(:68)/`_feat_ticket_appeared`(:69)+函数 `_build_curio_deck`(:265)/`_token_identity`(:279)/`_find_token`(:346)/`pool_stock`(:461)/`_consume_pool_purchase`(:473)/`_pool_stock_snapshot`(:484)/`_consume_deck_token`(:440-452,grilling 补入——纯池摘牌,不迁则件内池被宿主直改=双写)/`_draw_curio_candidates`(:669)/`_curio_cap_ok`(:757)/`_feat_ticket_consider`(:772)+`_reset_curio_state`(:247) 的池半(件内 `reset()` 承载,宿主复位停点态)。**留宿主面**(停点编排与兑付推进):`skip_empty_curio`(:334)/`pick_curio`(:361)/`_feat_payload_keys`(:381)/`_apply_curio_pick`(:393)/`_finish_curio_pick`(:494)/`node_curio_candidates`(:513)/`present_node_curio`(:518)/`apply_node_curio`(:525)/refresh 族(:556-641,读写 `_attune_refresh_x`/`_refresh_stock`/`_node_curio_pending` 停点态——**断点时点 :634 住在 `_refresh_curio_common` 体内**)/`_finish_node_bonus`(:643)/`_redeem_feat_ticket`(:789,兑付推进——**断点时点 :803 住其体内**);另 `feats_held`(:453) 读宿主 `_feats` 留驻(持有集查询,非池面) | 拆出 `curio_deck.gd`:令牌多重集+kind 计数+凭证三计数+抽签/cap/凭证判定入件;宿主公开面委托保持原签名(外部调用面零变化);AdventureRunner 行数读数预期 **≈850±10**(第二轮按 v1.1 边界清单重算:迁出 ≈211 行+委托与 Callable 绑定回填 ≈15–25——第一轮 709 系「只留 refresh 族」旧口径,随边界扩到兑付推进族作废;**读数项非停机闸**) | 审计 §三「令牌池子系统(构建/抽签/cap/凭证,~500 行)」指认——**边界经 grilling R3-A1 重划**(全族含 refresh 迁则断点时点被扰动、违零变更;拆「池与判定」不拆「停点编排」) | -| r3-2 | 商店族 21 函数(`shop_products`(:753)/`current_price`(:769)/`_spend_discounted`(:777)/`purchased_tier`(:783)/`buy_product`(:789)/`player_request_refresh`(:812)/`set_shop_pool`(:823)/`_shop_open_pool`(:832)/`_shop_draw_shelf`(:843)/`pool_stock`(:880)/`pool_price`(:891)/`shop_shelf`(:905)/`shop_refresh_free_left`(:932)/`_attune_shop_refresh_bonus`(:939)/`shop_refresh_cost`(:948)/`shop_refresh`(:953)/`spend_gp`(:967)/`buy_pool_item`(:977)/`is_shop_pool`(:1002)/`shop_bp_shelf`(:1007)/`shop_bp_visit_buys`(:1011))+成员 `_shop_*`(:37/:49-56)——**以函数清单为准**(:715-1011 区间内 `grant_curio_gp`/`continue_after_node` 等非商店函数不迁) | 拆出 `shop_session.gd`:双模式货架/定价/购买/刷新入件(db 注入+`set_shop_pool` 既有四参 Callable 面沿用,缺省值 `Callable()` 逐字对齐 :823);LevelRunner 公开面委托保持原签名;钱包原语 `_gain_wallet`/`close_wallet` 留宿主(`spend_gp` 随件迁、经回调落宿主账本——§11 定值);level_runner 读数预期 ≈780 | 审计 §三「商店子系统(legacy+pool 双模式,~260 行)可拆」 | -| r3-3 | 断点 schema **写/读两侧**(grilling 订正:审计「三处」的 save 侧经实查**零键登记**——:218-219/:298 只置空字典、:595-607 四口值拷贝,无字段枚举=空派生不动):写侧 `_snapshot_breakpoint`(:990-1034,**27 顶层键**——bonus 含 4 子键;v1.0「28 键」计数订正);读侧两消费位=`resume_adventure`(:140-246)+`_start_current_level` 二次消费(`node_id`/`visited_ids`/`template_pick`/`aux_items` 四键经 `_resume_bp` 在 :854-860/:875-877,grilling 补查——v1.0 漏记) | 单源化:宿主内 `const BREAKPOINT_FIELDS`(键→登记项:写侧取值器+读侧回退策略标签)驱动写侧拼装与**两处**读侧回退;**读侧回退策略按实语义逐键归类**(grilling R3-F2 五类:plain 缺省/null 类型守卫保持既有态〔bonus_deck/adventure_counters/attunement_charges :174/:223/:241〕/双向合并〔bonus_taken :162-165〕/钳制〔bonus 子键 maxi(0)、hero_hp :192-193〕/参数面回退〔attune_refresh_y :205〕/令牌化迁移〔bonus_pending.candidates :230-240〕)——`{default, deep}` 双字段表达不了的面按策略标签登记;save 侧经核零改动 | 审计 §三「断点 schema 三处重复登记,每加一字段改三处」——写/读两侧单源化即消散弹面;save 侧论断不成立(§12 回呈) | - -**完成定义**: - -1. 三单元落地;**行数读数记录**(adventure_runner ≈710–730/level_runner ≈780/两新件净行数——读数项非停机闸,判离量以函数清单为准); -2. 等价三证齐:①逐字迁移核对表(两拆件族)+断点单源对照表(**27 顶层键逐键:缺省值/回退策略改前改后一致**);②全套闸绿+读数对拍;③断点专项:`breakpoint_resume_test` 绿+iter36 wc2 续玩断言(ui_click_probe)绿+新增断言「单源键集==写侧字典键集」(wr2——判据从写侧实拼字典推导); -3. 两新件+单源合规(S9/S10/S11/S13+core 域 S1-S5:零 UI 零 await 零裸随机零表直读); -4. 至少两条撤销证明记录(T3);三份产出物写齐(T3)。 - -## 2. 设计事实源 - -| # | 出处 | 取用条款 | -|---|---|---| -| D1 | 工程直入证据指认=审计 §三 adventure_runner/level_runner 行+断点 schema 段+本会话结构实查(§3——边界经 grilling 重划、save 侧论断订正) | 三单元清单与边界 | -| D2 | [architecture](../design/engineering/architecture/architecture.md) 核心层纪律+「战斗域装配现状」节 RunLedger 先例 | core 件零引擎依赖;共享账经显式类/引用不隐式——r3-1 宿主记账回调化的依据 | -| D3 | [code-conventions](../design/engineering/code-conventions.md) §1/§2.4 | 一文件一职责;状态字段语义显式(断点单源=语义显式化) | -| D4 | [存档序列化](../design/gdd/systems/表现与支撑/存档序列化.md) | 断点域契约(SCHEMA_VERSION+快照锚)——键集冻结的规范面 | -| D5 | 2026-10-24 用户 P0 令 | 行为验收只走撤销证明,禁注入造红 | - -## 3. 现状事实(2026-10-24 写卡会话实查+2026-10-25 grilling 实测;开工日 T0 复核行号) - -| # | 事实 | 出处 | -|---|---|---| -| A1 | adventure_runner 1049 行;池与判定面(r3-1 迁出清单)与停点编排面(留宿主清单)的分界依据:refresh 族读写 `_attune_refresh_x`(:49)/`_refresh_stock`(:53)/`_node_curio_pending`(:63) 停点态、`_finish_curio_pick`/`_finish_node_bonus` 写 run_ledger.bonus 与 ledger、`_redeem_feat_ticket` 兑付推进——**断点五时点之三住在留宿主函数体内**(:506 在 `_finish_curio_pick`、:634 在 `_refresh_curio_common`、:803 在 `_redeem_feat_ticket`) | `game/core/adventure_runner.gd:49/53/63/247-810/990-1034` | -| A2 | level_runner 1012 行;商店族 21 函数清单(r3-2,函数清单为准);`set_shop_pool`(:823) 四参注入面(deck 引用+consume/ticket/cap 三 Callable,后两参缺省 `Callable()`)=拆件回调面的现成形态;钱包边界=`_gain_wallet`(:738)/`close_wallet`(:745) 宿主留、`spend_gp`(:967) 只服务商店扣款 | `game/core/level_runner.gd:37/49-56/715-1011` | -| A3 | 断点写侧 **27 顶层键**(:1001-1030 逐键实查:level_index/node_id/hero_hp/bonus{atk,max_hp,armor_class,ap 四子键}/gp/shop_purchased/shop_shelf/shop_visit_buys/attunement_charges/template_pick/master_seed/rng_counter/keystone/visited_ids/aux_items/bonus_taken/bonus_deck/relics/bonus_pending/feat_ticket_pity/feat_ticket_sold/adventure_counters/vision_counter_used/refresh_stock/attune_refresh_y/bonus_result/keystone_drops);读侧消费**两处**:`resume_adventure`(:140-246——五类回退策略实语义见 r3-3)+`_start_current_level` 经 `_resume_bp` 四键二次消费(:854-860/:875-877);save 侧 :218-219/:298/:595-607 **零键登记**(空字典与值拷贝) | `game/core/adventure_runner.gd:140-246/990-1034`;`game/core/save_manager.gd:218-219/298/595-607` | -| A4 | 断点五时点集::506/:634/:803/:933/:982——**全部住在留宿主函数体内**(A1),拆件不扰动时点形态 | `game/core/adventure_runner.gd:506/634/803/933/982` | -| A5 | 外部调用面:LevelRunner 商店/池面消费方=main.gd+camp_shop_presenter/curio_interact_controller(呈现侧)+level_shop_economy_test/curio_refresh_paid_test/pool_case_d_test 等;AdventureRunner 令牌面消费方=main/控制器+断点域测试——公开签名零变化=外部零改动的依据;iter36 wc2 断点续玩断言在 `ui_click_probe`(断点快照喂入→resume 真链→run_ledger.bonus.atk==5) | T0 直引清单化;`game/probe/ui_click_probe.gd`(iter36 wc2 段) | -| A6 | 卡① d11 触点 `pool_price`(:891)/`_spend_discounted`(:777) 在商店族内(先修后迁);`shop_bp_shelf`/`shop_bp_visit_buys`(:1007/:1011) 是断点附随取数面(随商店件迁、断点写侧经其公开面取数不变) | 卡① §1 d11;`game/core/level_runner.gd:1007-1011` | - -## 4. 设计契约(红线,违反 = L3 停机呈报) - -🔴 **契约 1 · 零行为变更+公开面零变化**:两拆件族的**公开函数签名逐字保持**(宿主委托层承载);迁移体逐字一致(接收者化/参数化最小);断点 **27 顶层键**的键名/缺省值/回退策略逐键冻结(对照表落实现报告,读侧五类策略逐键归类登记);禁顺手改任何判定/缺省/顺序。 - -🔴 **契约 2 · core 域纪律**:两新件 core 合规全过——零 UI 引用(S4)、零 await、随机只经注入参数面(S1/S2——`_feat_ticket_consider(rg: RandomNumberGenerator)` 现形态 :772 随迁不改)、零表直读(数值经注入 db,S3/S5);头注 S10 形制+S13 基线零新增。 - -🔴 **契约 3 · 断点专项回归**:`breakpoint_resume_test` 全绿+iter36 wc2 续玩断言全程绿+新增「单源键集==写侧键集」断言一条(wr2);五时点集(A4)零增减零迁移。 - -🔴 **契约 4 · 等价三证**(§1 完成定义 2)+撤销证明至少两条(T3——P0 令形态)。 - -🔴 **契约 5 · 范围收口**:只拆两族+单源化+**一条必改接线行**——`adventure_runner.gd:883-884` `_runner.set_shop_pool(_curio_deck, _consume_pool_purchase, _feat_ticket_consider, _curio_cap_ok)` 四标识符全在迁出面,该行随拆件改引(经宿主委托/件成员取值,参数序不变);除此与 `_settle`/`_start_current_level`/节点推进等其余面不动;`_place_variants` 搬迁(审计 §三附带小件,battle_orchestrator 面)不在本卡。 - -## 5. 范围边界 - -**做**:r3-1~r3-3+断点键集断言+等价三证+撤销证明+产出物。 - -**不做**:battle_orchestrator 的段类再拆与 `_place_variants` 搬迁(未立项,留审计在册);present 层三控制器写调用归属(条目④裁点);R1/R2 面;卡①缺陷面。 - -## 6. 关联文档同步面 - -| 文档 | 关联点 | 同步义务 | -|---|---|---| -| [architecture](../design/engineering/architecture/architecture.md) 核心层/功能层模块归属表 | 两新 core 件登记(CurioDeck/ShopSession 归属行)——与条目④「模块归属对齐」并批落文 | 反哺点 · 列清单(①:登记行草案,落文归④批统一裁决) | -| [存档序列化](../design/gdd/systems/表现与支撑/存档序列化.md) | 断点键集零变化——单源化不改契约;T0 核其是否逐键列清单(列则注记单源载体址) | 反哺点 · 列清单(②,预期零改动注记) | -| [秘宝](../design/gdd/systems/跨局循环/秘宝.md)(若记令牌池结构)/商店相关 GDD | 结构载体址变化、机制语义零变化(T0 核) | 反哺点 · 列清单(③,预期载体址注记) | -| `待办清单.md`/`roadmap.md` | 条目拆分注记与迭代行编入笔 | 反哺点 · 列清单(④⑤) | - -## 7. 任务卡 - -### 卡前 · 文件结构图 - -| 文件 | 职责 | 行为改动 | -|---|---|---| -| `game/core/adventure_runner.gd` | 池与判定面拆出+委托层+断点写侧改单源驱动(复用既有·修改) | 非行为改动 → wr1 | -| `game/core/level_runner.gd` | 商店族拆出+委托层(复用既有·修改) | 非行为改动 → wr1 | -| `game/core/curio_deck.gd` | **新增**:令牌池件(新增·新文件) | 非行为改动 → wr1 | -| `game/core/shop_session.gd` | **新增**:商店件(新增·新文件) | 非行为改动 → wr1 | -| 断点字段单源(adventure_runner 内 `const BREAKPOINT_FIELDS`——§11 定值) | 27 键登记单源+写/读派生(新增·常量) | 非行为改动 → wr1 | -| `game/tests/unit/core/breakpoint_resume_test.gd` | 键集断言一条(复用既有·增断言) | 断言面 → wr2 | -| 外部直引点(A5 清单) | 零改动预期(公开签名不变)——T0 核 | — | - -### 接口块 - -- `curio_deck.gd`:`extends RefCounted`;`_init(db: ConfigDb)`;持令牌多重集+`_curio_taken`+凭证三计数;方法=迁出清单去下划线同形(`build/token_identity/find_token/pool_stock/consume_pool_purchase/stock_snapshot/consume_deck_token/draw_candidates/cap_ok/feat_ticket_consider/reset`);`feat_ticket_consider(rg: RandomNumberGenerator) -> bool` 形态随迁不改;宿主记账经回调注入(形态 L1)。 -- `shop_session.gd`:`extends RefCounted`;`_init(db: ConfigDb)`;`set_pool(deck_ref: Array, consume_handler: Callable, ticket_consider: Callable = Callable(), cap_check: Callable = Callable())`(与 :823 逐字对齐含缺省值);方法=商店 21 函数清单同形(`spend_gp` 随迁、出账经回调落宿主 `_gain_wallet`)。 -- 断点单源:`const BREAKPOINT_FIELDS: Dictionary`(键→{写侧取值键路径+读侧策略标签〔plain/keep_on_null/dual_merge/clamp/params_fallback/token_migration 六类〕})+写侧拼装/读侧回退两派生段。 -- 最窄闸:`breakpoint_resume_test`/`level_shop_economy_test`/`curio_refresh_paid_test`/`pool_case_d_test` 单件直跑+`tools/error_scan.py` 判红口径;系统测试 `python tools/run_system_test.py`;探针 `ui_click_probe`(iter36 wc2 断言)。 - -### 落位与可复用性预检 - -- **`curio_deck.gd`**——复用面:全库令牌池逻辑唯一宿主即 adventure_runner 私有族(grep 实查),无第二实现;为什么不留在宿主:审计指认自成一体+1049 行超线。唯一面:令牌多重集与凭证计数唯一持有者(断点 `bonus_deck`/`bonus_taken`/`feat_ticket_pity`/`feat_ticket_sold` 数据源单点)。分层:core 件零引擎依赖;db 注入;随机经注入 rg 参数面(现状形态)。 -- **`shop_session.gd`**——复用面:`set_shop_pool` 既有 Callable 注入面即拆件接口的现成形态(零新造协议);无第二商店实现。唯一面:货架/定价/购买/刷新唯一宿主(断点 `shop_shelf`/`shop_visit_buys` 数据源)。分层:core 件;钱包原语留宿主(账本单点,`spend_gp` 经回调)。 -- **断点单源 const 随宿主**——复用面:为什么不上独立件:27 键多数取宿主态(20+ 键取宿主成员/嵌套),独立件要把宿主全量开洞——const 随宿主+两派生段最小;备选「独立 schema 件」淘汰依据=开洞面>收益。唯一面:键登记唯一处(写侧+两读侧三消费全派生)。 -- **零新增逻辑**:全部为搬运+登记单源化。 - -### T0 事实坐实(≈0.25d) - -- [ ] 复核 A1-A6(重点:A3 27 键缺省/策略表逐键摘录;A5 外部直引清单;r3-1 边界函数逐一确认归属——尤其 `pick_curio`/`skip_empty_curio` 停点面归属复核)。可核查结果:边界判定表+27 键策略表进实现报告素材。 -- [ ] 改前读数(行数、断点/商店/令牌域套件通过数、ui_click_probe iter36 wc2 段绿读数)。可核查结果:基线表在案。 - -### T1 商店拆件+断点单源(≈0.75–1d) - -- [ ] 实装 r3-2(shop_session 拆出+委托层)+r3-3(单源 const+写侧/两读侧改派生)+键集断言(wr2)。可核查结果:level_runner ≈780 读数记录;27 键对照表逐键一致;断点域套件+iter36 wc2 断言绿。 -- [ ] 验收步骤:全套闸按域选档跑一次全绿。 - -### T2 令牌池拆件(≈0.75d) - -- [ ] 实装 r3-1(curio_deck 拆出+委托层+停点面留宿主边界落地)。可核查结果:adventure_runner ≈850±10 读数记录;逐字迁移核对表逐函数落位;令牌/商店域全部既有套件绿。 -- [ ] 验收步骤:全套闸全绿+改前改后读数对拍。 - -### T3 撤销证明+产出物落位卡(≈0.25d,末卡) - -- [ ] 撤销证明两条(P0 令形态——只回退本批 hunk):①回退 shop_session 承接件任一函数 hunk(如 `pool_price` 承接体)→ 宿主委托/消费面引用未定义符号 → 套件直跑经 `tools/error_scan.py` 扫出 SCRIPT ERROR 红 → 还原复绿;②回退单源 const hunk → 键集断言直跑 error_scan 红(常量未定义)→ 还原复绿。记录进实现报告。 -- [ ] 写齐三份产出物(实现报告含两核对表+行数读数+27 键对照表+实机目检总清单〔商店购买/刷新/三选一/续玩断点抽查〕/代拍板追认单/反哺点)。 -- [ ] 合并前置:独立验收审计(同卡①口径——抽查重点=27 键对照表与逐字迁移核对表)逐行「通过」。 - -**估时合计 ≈2–2.5d**。编排注记:前置卡①;与 R1/R2 可同迭代分波;断点面重(键集断言+五时点+iter36 wc2 回归)。 - -## 8. 验证闸 - -### 改动—验收对照表 - -| 需求点 | 改动 | 验收测试(尾缀) | 层级 | 真实入口 | 撤销证明 | -|---|---|---|---|---|---| -| wr1 三单元等价 | 两 runner 拆件+断点单源 | 全套闸绿+两核对表+27 键对照表〔wr1〕 | 集成 | 断点/商店/令牌域真实套件+系统测试+ui_click_probe(iter36 wc2) | 撤销证明两条(T3);等价三证替代逐单元撤销(等价重构申报) | -| wr2 断点键集断言 | breakpoint_resume_test 增一条 | 单源键集==写侧字典键集〔wr2〕 | 单元 | `_snapshot_breakpoint` 真实拼装路径 | 回退单源 const hunk → error_scan 红(T3②) | -| wr3 新件合规 | 两新件+单源 | S1-S5/S9/S10/S11/S13 全零命中〔wr3〕 | 静态扫描 | 闸真实跑批 | 回退任一合规面 → 对应闸红 | -| wr4 产出物 | T3 三份 | 件核对〔wr4〕 | — | — | 缺项红 | - -**测试削弱申报**:零(既有断言零改动;增断言一条)。**独立验收审计**:同卡① §8.3 口径。 - -## 9. 异常分级 - -- **L1(自救)**:两新件命名;委托层薄厚;单源 const 形态与策略标签命名;宿主记账回调具体形态。 -- **L2(记录继续)**:A5 直引清单个别适配;行数读数与预期的正常偏差。 -- **L3(停机呈报)**:迁移体无法逐字等价(耦合面比 A1/A2 判定宽——判离指认失真回呈);断点对照表任一键不一致;任一断点/商店/令牌域套件或 iter36 wc2 断言非本批存量红;单源化需动 save_manager 结构(超 r3-3 范围);r3-1 边界函数在实装中被迫跨侧(停点面与池面无法分离)。 - -## 10. 代拍板白名单 - -1. 两新件命名与去下划线取舍(签名语义不变前提)。 -2. 宿主记账回调的具体形态。 -3. `spend_gp` 随迁的回调落账写法。 -4. 提交切分与 worktree 分支命名。 - -## 11. 待裁点 - -**零待裁点,开工门=U0。** 三处灰色地带推导定值(呈报可否决,随 T3 反哺点):①断点单源落**宿主内 const** 而非独立件——依据=27 键多数取宿主态、独立件开洞面大(预检节);②**拆池不拆停点**的边界(refresh 族/兑付推进族留宿主)——依据=停点态与断点五时点强耦合宿主流程,全拆=行为面扩大违零变更契约(grilling R3-A1 订正 v1.0 全族迁移方案);③`spend_gp` 随商店件迁、经回调落宿主账本——依据=它只服务商店扣款而账本原语单点在宿主。备选淘汰依据各见预检/契约。 - -## 12. 自审六查与 grilling 复核记录 - -**自审六查(2026-10-24 写卡会话)**:①规格覆盖——r3-1~r3-3→T1/T2、断言/合规/撤销/产出物→T1/T3,指到 wr1–wr4;②步骤扫描——一步一动作一可核查结果;③类型一致性——接口块与 A1-A6 实查一致(v1.1 边界重划后逐函数归属两侧清单化);④审查焦点——断点 27 键对照表(契约 1/3)、键集断言(wr2)、五时点全留宿主(契约 3)、A6 交点、iter36 wc2 回归纳入;⑤篇幅比例——零实现体;⑥落位预检——两新件唯一性、单源随宿主、账本留宿主边界均已答。 - -**grilling 第一轮复核记录(2026-10-25,独立子代理对抗复核)**:判回炉两阻断——R3-A1 r3-1 清单圈入 refresh 族/兑付面与「拆池不拆停点」边界直接互斥(断点时点 :634/:803 住在 refresh/兑付函数体内,随迁即扰动五时点;行数账两头堵);R3-A2 T3 两条变异违 2026-10-24 P0 令。随同五应修(F1 断点实为 27 顶层键非 28;F2 读侧回退语义远超 {default,deep} 双字段——五类策略实语义;F3 读侧漏 `_start_current_level` 四键二次消费;F4 save 侧零键登记、「三处」论断不成立;F5 iter36 wc2 断点断言未纳入+`_consume_deck_token` 漏迁+两函数归属未交代)+两建议(A2 区间收窄;set_pool 缺省值对齐)。v1.1 全部折入:r3-1 边界重划(迁出/留宿主两侧函数清单化、五时点全留宿主、`_consume_deck_token` 补入、`_feat_payload_keys`/`_apply_curio_pick` 归属留宿主)、27 键订正、读侧两消费位+五类策略登记、save 侧空派生订正(§12 回呈审计)、证明改撤销形态两条、iter36 wc2 纳入 T0 基线与回归面、区间收窄为函数清单、set_pool 逐字对齐。复核结论:两阻断闭合、五应修落位(v1.1)。 - -**grilling 第二轮复核记录(2026-10-25,同代理续核)**:总判**通过(附应修)**——A1 边界重划真闭合(两侧清单逐项对账无重叠无遗漏、五时点全留宿主实锤)、A2 撤销证明合规、F1–F5 全落。新发现两应修:①行数读数 710–730 系第一轮旧口径未随边界扩面重算(按 v1.1 清单实算迁出 ≈211 行→收 ≈850±10);②契约 5「其余面不动」与 :883-884 接线行互斥(四标识符全在迁出面,不改该行无法编译)。v1.2 折入:读数改 ≈850±10、契约 5 增接线行例外并列入 r3-1 改动面、策略词表统一六类、feats_held 留驻一行明示。零待裁点成立,卡进可编排态。 diff --git a/roadmap.md b/roadmap.md index 4e5a6cf8..5742dfa0 100644 --- a/roadmap.md +++ b/roadmap.md @@ -5,8 +5,8 @@ ## 当前位(只回答「现在该做什么」;三行内随批次换代,禁止在此扩写成状态快照) - **阶段**:M3——Alpha 版本的完整实现(2026-10-09 立规:未明令宣布完成前停留 M3,迭代只排 M3 需求,M4 不排期;根目录 stage.txt 为旧阶段开关仅存档)。 -- **迭代位**:迭代 1–36 全部已完成——迭代 35 两卡于 2026-10-25 合回并完成批后收尾;迭代 36 三卡已合回并于 2026-10-25 完成批后收尾;迭代 37 已编排(四卡,2026-10-25),待开工。 -- **下一步**:迭代 37 开工由用户发令(迭代实现段入口)。待编池:营地交互重做卡⑤(排卡④之后)、卡④(已退回美术设计段,美术设计闭合后重立卡)、冒险前资源配比预设(冻结备选)、迭代 30 原卡②③(2026-10-17 已剔出迭代 30 规划、退回待设计,待美术/设计定稿后重新立卡)、奖励节点三选一实装(2026-10-25 新登记)、结算明细日志行序重锚(2026-10-21 授权发起、时点另定)。 +- **迭代位**:迭代 1–37 全部已完成——迭代 37 四卡 2026-10-25 合回并完成批后收尾。 +- **下一步**:无已编排迭代;P0「AdventureRunner 职责真拆分」五件写卡前待裁(待办清单),裁后走写卡段由用户发令。待编池:营地交互重做卡⑤(排卡④之后)、卡④(已退回美术设计段,美术设计闭合后重立卡)、冒险前资源配比预设(冻结备选)、迭代 30 原卡②③(2026-10-17 已剔出迭代 30 规划、退回待设计,待美术/设计定稿后重新立卡)、奖励节点三选一实装(2026-10-25 新登记)、结算明细日志行序重锚(2026-10-21 授权发起、时点另定)。 ## 里程碑时间线 @@ -70,7 +70,7 @@ | 迭代 34 | 已完成(2026-10-22) | 已退役(git 历史可考) | 2026-10-21 | ≈11 个工作日〔用户特批 2026-10-21 超 10 个工作日〕 | 已退役(git 历史可考) | | 迭代 35 | 已完成(2026-10-25) | 已退役(git 历史可考) | 2026-10-21 | ≈15.25 个工作日〔用户特批 2026-10-21 超 10 个工作日〕 | 已退役(git 历史可考) | | 迭代 36 | 已完成(2026-10-25) | 已退役(git 历史可考) | 2026-10-24 | ≈5.25 个工作日 | 已退役(git 历史可考) | -| 迭代 37 | 待开工 | reports/需求卡片-game行为缺陷定点修批-2026-10-24.md
reports/需求卡片-重构main判离批-2026-10-24.md
reports/需求卡片-重构boardview拆件批-2026-10-24.md
reports/需求卡片-重构runner拆件与断点schema单点批-2026-10-24.md | 2026-10-25 | ≈8.25 个工作日 | — | +| 迭代 37 | 已完成(2026-10-25) | 已退役(git 历史可考) | 2026-10-25 | ≈8.25 个工作日 | 已退役(git 历史可考) | - 历史行的「—」= 该列事实不可考:迭代 1~8 早于需求卡片制;编排时点与预计周期不再回溯,细节查 git 提交史。 - 「已退役(git 历史可考)」= 需求卡片或实现报告曾存在,已按「证据只留最新」退役;已完成迭代的实现报告列只填 reports/ 现存路径或本保留字,不填「—」。 diff --git a/体验验证清单.md b/体验验证清单.md index c6cfea44..bca7b1a3 100644 --- a/体验验证清单.md +++ b/体验验证清单.md @@ -394,3 +394,21 @@ | 5 | 状态面板观感(卡③) | 面板只留本局信息五件(带的秘宝/已获专长/当前属性/现有 GP/钥石总量),层级与文案通顺、空态句得体,零跨关解锁段 | - **红灯信号**:后缀在不该出现时出现或该出现时缺失;营地灰字糊到难读;「危」字糊或与旧字形误读;图例仍见旧词;面板仍出现秘宝匣/天赋树跨关概览段或五件缺失。 - **结论回流**:实现缺陷归待办清单登记新条目;观感问题归呈现面后续批。 + +--- + +## ㉟ 迭代 37——缺陷定点修+结构重构四卡实机目检总清单(2026-10-25 随批后收尾登记) + +> 四卡=卡① 行为缺陷定点修+卡②③④ main 判离/boardview 拆件/runner 拆件与断点 schema 单点(等价重构),玩法语义零变化,但动了呈现装配与商店/三选一接线。机检已覆盖撤销证明、断点 27 键逐键对拍与双树原始面逐字节比对;真机上「看着是否与改前一致」仍要人眼看。登记不阻塞(2026-10-12 真人检测立规);总收口时点=迭代 37 实现报告(已退役,git 可考)。 + +| # | 目检项 | 判据 | +|---|---|---| +| 1 | 棋盘观感逐像素(卡③) | 普通/优秀/稀有档瓦片、封印纹、数字墨族、英雄待机/行走/撞击/挖掘四态与朝向、缩格重排与缺位回退——拆件后同链,与改前零漂移 | +| 2 | 商店(卡④) | 进店货架绘制、刷新(免费额度/付费价)、购买扣款与文案、离店再进店(货架入断点)与改前一致 | +| 3 | 三选一停点(卡④) | 关底通关三选一呈现、刷新钮、空池跳过、专长凭证替换、确认后回血与加算区读数与改前一致 | +| 4 | 断点续玩(卡④) | 中途关退出→「继续上局」→停在原节点,GP/生命/道具库存/特性/凭证限购读数一致 | +| 5 | 卡① 指定两项 | 蓄力敌(heavy 技能敌)意图三档观感;同名敌受击闪白与弹道落点落对个体 | +| 6 | 卡① 其余行为面 | 蓄力三档观感分档、战斗结束庆祝浮动不叠加、批量模拟与调试浮窗互斥(连点不并发) | +| 7 | 模态层(卡①) | 模态层重复 open 不再吞旧账——多层叠加时关闭顺序正确 | +- **红灯信号**:本批为等价重构+定点修,任何屏观感或交互读数与改前不一致即报缺陷(第 1–4 项对「改前」、第 5–7 项对卡片设计的修复预期)。 +- **结论回流**:实现缺陷归待办清单登记新条目;不在试玩现场改数。 diff --git a/待办清单.md b/待办清单.md index 59623b99..d803ae61 100644 --- a/待办清单.md +++ b/待办清单.md @@ -7,9 +7,11 @@ **实况**: - 迎合装置本体:迭代 37 卡④ 实现(commit e5ee4d04)在 game/core/adventure_runner.gd:60-77 留 4 条、game/core/level_runner.gd:50-64 留 8 条转发属性(get/set 直指 CurioDeck/ShopSession 件内同名状态);卡③ 在 game/presentation/views/board_view.gd:82-122 留 13 条同类件(直指 TilePalette/HeroSprite 件内纹理与 tween 句柄)。三处同债种,唯一理由=让 S13 基线消息里的类变量数逐字保持 42/38/45;实现报告已按「指标迎合债」登记。 - 存在理由已消失:S13 基线键治本已入库(master 37625317,键=文件|规则|函数名,判定=改小/持平不红、仅较登记值恶化判红)⇒ 撤除这 25 条属纯代码改动、不碰基线(但须与拆件同批——单撤会让三处测试直引与探针直读面成片红,见缺口⑤)。 +- 撤装置后的实测计数(迭代 37 批内逐卡缺口盘点):只撤 25 条转发属性=adventure_runner 类变量 42→38、公开方法 23 不减;level_runner 类变量 38→30、公开方法 54 不减;board_view 类变量 45→32。结论=单撤装置只还计数债、职责混合零解,撤除必须与拆件同批。 - 本体问题:adventure_runner 现 42 成员/23 公开方法/65 函数/989 行(level_runner 38/54、board_view 45/27 同状——只撤装置到不了阈值),按变化原因数混着七件事(生命周期与状态机/英雄账本与生命/三选一停点兑付/刷新 X 与 Y 双源/断点 schema 写读/呈现事件转发/商店池装配接线),属 God Class+Data Clump;卡④ 只搬数据未搬决策,件的封装是名义的——测试仍从宿主面直改件内多重集(game/tests/unit/core/adventure_feats_test.gd:390-393、feat_vouchers_test.gd:92-93、level_shop_economy_test.gd:1351-1353)。 -- 目标切法(待裁,非结论):AdventureFlow(状态机+关序推进+结算受理一次)/RewardStop(停点态+兑付推进+刷新双源)/CurioDeck(已在册)/HeroLedger(ledger+run_ledger+effective_max_hp+rest 公式)/BreakpointSchema(27 键单源+写读两派生);呈现转名下沉装配层。判据=件拥有状态与决策、宿主不留同名数据槽、件→宿主只传值不回调宿主私有族。 -- 相关文档:design/engineering/architecture/architecture.md(模块归属表须随拆分登记新件)· design/gdd/systems/表现与支撑/存档序列化.md(断点域契约,27 键冻结面)· reports/需求卡片-重构runner拆件与断点schema单点批-2026-10-24.md(卡④ 契约 1 冻结公开面,正是本条无法在批内完成的原因) +- 验收面预案要点(迭代 37 批内成文,写卡输入):每新件至少一条真实入口断言(RewardStop 走 start_adventure→通关三选一→选件→断言加算区/池余量/停点态清零;HeroLedger 的 rest 与 heal 封顶走真链;BreakpointSchema 承〔wr2〕三条+ui_click_probe wc2 续玩真链);撤销证明逐单元「回退本批 hunk→断言红→还原复绿」、禁注入式变异;断点 27 键键名/缺省/回退策略逐键冻结;RNG 流名(reward_pick)与消费序零漂移;`tools/gdstyle_baseline.txt` 禁改、计数下降属改善不判红;MC/对拍是否发起由用户裁(触及存档/RNG/结算面)。 +- 目标切法(待裁,非结论;各件收编范围=迭代 37 批内按变化原因划界的成文要点):AdventureFlow(留名 adventure_runner;收 state/level_index/levels_total/_runner/_pending_next_level/_last_level_payload 与 start/resume/关序推进/结算受理方法族=状态机+关序推进+结算受理一次)/RewardStop(收停点三态 _curio_pending/_curio_result/_node_curio_pending、刷新双源 _attune_refresh_x/_refresh_stock 与 pick_curio/refresh 族/兑付推进方法族)/CurioDeck(已在册:池与凭证计数已入件)/HeroLedger(收 ledger/run_ledger/_base_override/_override/purchased/keystone/_feats 与 effective_max_hp/apply_rest 等=数值叠加与生命口径)/BreakpointSchema(收 BREAKPOINT_FIELDS 27 键登记的派生段与 _snapshot_breakpoint/_bp_read/`_resume_*` 六小函数=存档格式);呈现转名(`_forward_*` 六件+信号面)下沉装配层。判据=件拥有状态与决策、宿主不留同名数据槽、件→宿主只传值不回调宿主私有族。 +- 相关文档:design/engineering/architecture/architecture.md(模块归属表须随拆分登记新件)· design/gdd/systems/表现与支撑/存档序列化.md(断点域契约,27 键冻结面)· 迭代 37 卡④ 契约 1 冻结公开面(commit e5ee4d04),正是本条无法在批内完成的原因(需求卡片已退役,口径见 git 历史) **缺口**:拆分边界未裁四件——①RewardStop 与 CurioDeck 的分界(停点态 `_curio_pending`/`_curio_result`/`_node_curio_pending` 归谁、凭证兑付 `_redeem_feat_ticket` 在哪侧);②HeroLedger 与既有 RunLedger 的关系(合并还是分层,RunLedger 已被 LevelRunner 共享引用);③断点 schema 是否独立成件(卡④ §11 以「27 键多数取宿主态、开洞面>收益」淘汰过独立件,真拆分后该论据失效,需重裁);④公开面收缩后 main.gd/camp_shop_presenter/curio_interact_controller 与三处测试直引点的改法与验收面;⑤board_view 13 条同类装置撤除时探针面与组合根直读面的改法(terrain_tile_sets_test/ui_contrast_probe/ui_input_probe 直引在案)。裁完才进写卡段。 @@ -29,19 +31,21 @@ **状态**:待文档设计 -#### game 代码结构审查批——行为缺陷定点修+结构重构候选(2026-10-24 登记,来源=审计发现) +#### 结构审查未立项小件候审(2026-10-25 登记,来源=批次移交) -**实况**:审计报告=reports/审计-game代码结构审查-2026-10-24.md(2026-10-24,全目录只读审查,零测试执行、结论全部静态阅读)。缺陷证据(报告 §二 Critical 行为缺陷段+定点清单,含行号)九件:①core/damage_controller.gd:215 用 `==` 比较单位字典(Godot 4 语义=逐键内容比较)——内容全同双敌其一死亡误作废对方单据,应 `is_same()`;②presentation/views/standee_body.gd:172/188 蓄力意图档常量错配(GOLD_INK vs GOLD_DEEP)死代码,三档运动签名设计失效且无测试覆盖;③presentation/views/battle_end_overlay.gd:281-287 庆祝 tween set_loops 无限叠加泄漏(close 只隐藏不 kill);④批量模拟×两层调试并发互斥缺口(presentation/views/debug_overlay.gd:84+presentation/main.gd:712-715/1489+presentation/tune_sim_controller.gd:46-58——用户两击可并触发,演出扭曲+测量作废);⑤presentation/modal_stack.gd:23-29 未登记层/重复开层静默降级(违 fail-fast,当前调用面无违规者属埋雷);⑥core/minesweeper_rules.gd:8-9 两条核心信号零契约成文(违 code-conventions §2.1)+全库信号注释载体普遍 `#` 非 `##`;⑦明细行类型封闭枚举漂移(core 实发 13 类 vs battle_orchestrator.gd 头注 9 类);⑧core/level_runner.gd:895/779 玩法数值字面量(relic 基价 15.0、散折 0.8,自注 provisional);⑨presentation/views/battle_stage_view.gd:332-338 以 display_name 子串匹配解析受击目标(同名敌错位,应携带 enemy_id)。另有定点小件与结构重构候选(报告 §三):main.gd 1508 行可判离块 ~440 行/board_view.gd 1219 行瓦片算法+英雄精灵可拆/adventure_runner 令牌池子系统 ~500 行可拆 CurioDeck/level_runner 商店子系统 ~260 行可拆/断点 schema 三处重复登记可单点化——重构候选非缺陷,是否立项由写卡段呈报用户裁决。**2026-10-24 写卡会话收口**:用户裁五件重构候选全部立项(卡①缺陷修先行落地后重构再动);缺陷⑦明细行枚举漂移划归「架构与规范对齐批」条目(词表 13/15 归该批裁决,缺陷卡不碰头注);报告 §三「其余小件」(level_map 视图直读/BFS、main 死变量与 confirm 旁路、settle_batch 空判、row_pacer 常驻 flush、_fatal assert 剥离、rng_service early-return)未立项,留审计报告在册待后续点名。切卡输入=审计报告 §二/§三,四卡均零待裁点。 - -关联文档清单: -- 需求卡片 reports/需求卡片-game行为缺陷定点修批-2026-10-24.md(2026-10-24 v1.2(grilling 两轮复核通过),缺陷修主卡·迭代批,八件缺陷+定点小件七件,零待裁点) -- 需求卡片 reports/需求卡片-重构main判离批-2026-10-24.md(2026-10-24 v1.2(grilling 两轮复核通过),重构卡 R1·迭代批,零待裁点) -- 需求卡片 reports/需求卡片-重构boardview拆件批-2026-10-24.md(2026-10-24 v1.2(grilling 两轮复核通过),重构卡 R2·迭代批,零待裁点) -- 需求卡片 reports/需求卡片-重构runner拆件与断点schema单点批-2026-10-24.md(2026-10-24 v1.2(grilling 两轮复核通过),重构卡 R3·迭代批,零待裁点) +**实况**:证据=reports/审计-game代码结构审查-2026-10-24.md §三「其余小件」清单(2026-10-24 写卡会话未立项,随迭代 37 批后收尾移交本条)。六件:①level_map 视图直读/BFS;②main 死变量与 confirm 旁路;③settle_batch 空判;④row_pacer 常驻 flush;⑤_fatal assert 剥离;⑥rng_service early-return。逐件论证与行号址见该审计报告 §三(报告留档在册)。是否立项与点名时点待用户裁决;立项者按改动四类走需求改动通道(写卡+编批)。 **改动面**:游戏代码 -**状态**:待写卡+已写卡+已编迭代 37 +**状态**:待裁决 + +#### gdstyle 基线陈旧键待人工清理(2026-10-25 登记,来源=批次移交) + +**实况**:证据=`tools/gdstyle_baseline.txt` 存量条目有不命中盘面的旧键——adventure_runner 的 `_draw_curio_candidates`/`resume_adventure`/`_start_current_level` 与 `_resume` 前族、level_runner 迁出函数名对应条目、board_view 已压回阈值内函数对应条目(迭代 37 卡②③④ 重构后函数已迁出或压回)。新键形态(文件|规则|函数名)下陈旧键无害,但属噪声。处置=下次人工发起 `--refresh` 时一并清掉(骤降守卫已内建于 refresh 路径,commit `37625317`)。发起时点由用户定。 + +**改动面**:工具代码 + +**状态**:待执行 #### 效果引擎校验文案计数错误——封闭事实词表数写死「9 项」实为 18(2026-10-25 登记,来源=审计发现)