feat(测试迁移): 批 B 波 0 基建——断言静态提取器+验证集清单冻结+迁移账本
tools/extract_checks.py:smoke_all.gd 静态 check( 调用点提取器(括号配平归集可跨行调用, 码位掩码排字符串/注释内字样)——--extract 生成清单(已存在拒写防误覆)、--diff 逐条对账 (消失/新增/成对字面变更三类报告,批 B 出口机检唯一工具)、--list-funcs 编账辅助。 基点实测调用点 1065;行级 grep=1067 系另一口径(多 2 行=func check 定义行+一处注释行), 清单头注已写明。对刚生成清单 --diff 自测零差异;释义/处置列回填后复跑仍零差异。 game/tests/验证集清单.md(冻结参照物·常驻):77 章 1065 条,标签原文与 cond 摘要逐字 保真(cond 跨行折叠单行口径),每条人工白话释义+处置列(去向)已回填;章节含行段/条数/ 去向/施工组。过 static_scan S11(205 文件零命中,唯一退役词字面标签与「去重闸」同行被 既有豁免覆盖,未新增豁免条款)。 reports/批B-断言迁移账本-2026-10-22.md:77 套分类账(系统 6 套 49 条/集成 1 套 14 条/ 单元 70 套 1002 条)+12 施工组+套级变异策略+宿主落位计划(11 个新宿主预定路径与 run_all.gd manifest/system_test.gd 挂载方式,本波零建文件)+歧义套 8 项留主会话裁决 +引用普查附录(合同在案必改点逐条核实+合同外新发现 18 类,含产品码 tune_sim_controller.gd preload smoke 库件一处)。 smoke_all.gd 零改动(git diff 空);三个去向既有测试文件零改动;本波零游戏测试。
This commit is contained in:
1 parent
1aea93972b
commit
771f871527
3 files changed
+2175
No files matched your search
+1467
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,214 @@
|
||||
# 批 B 断言迁移账本(波 0 基建产出)
|
||||
|
||||
> 状态:波 0 基建完成 · 2026-10-22 · 主刀代理(波 1 迁移)的施工输入合同
|
||||
> 基点 commit:`1aea93972b5ff3bc51de1814081e69a79f74a735`(smoke_all.gd 在此基点冻结)
|
||||
> 合同:`reports/提案-测试术语与层级重构方案-2026-10-22.md` §四/§五/§七
|
||||
> 冻结参照物:`game/tests/验证集清单.md`(机器生成+逐条白话释义+处置列已回填;出口对账=`python tools/extract_checks.py --diff game/tests/验证集清单.md`)
|
||||
> 波 0 纪律遵守:`game/smoke/smoke_all.gd` 零改动(git diff 空);三个去向既有测试文件零改动;未跑任何游戏测试
|
||||
|
||||
## 一、提取对账(先读这一节)
|
||||
|
||||
- **静态调用点 = 1065 处**(`tools/extract_checks.py` 括号配平提取,含跨行调用归集)。
|
||||
- **与合同 §一「1067」的差**:1067 是行级 `grep -c "check("` 口径,多出的 2 行=`func check(` 定义行(:14)+一处注释行(:3452「禁恒真 check(true)」),均非调用点。清单头注已写明两口径,**迁移对账一律以 1065 为准**。
|
||||
- **--diff 自测**:对刚生成的清单跑 `--diff`=零差异(计数 1065/1065 对齐、标签/cond 逐字对齐);释义与处置列回填后复跑仍零差异(回填不触冻结列)。
|
||||
- 清单结构:77 章(套)×1065 条;每条含序号/基点行号/标签原文(逐字)/cond 摘要(跨行折叠单行)/白话释义/处置/quiet(全源仅 1 处 quiet=true,:3448)。
|
||||
|
||||
## 二、分类账总表(77 套)
|
||||
|
||||
去向判据(tests/README「分层判据」):`--script` 不加载 autoload 能验完=单元;跨系统且走落盘=集成;需完整项目引导与场景树=系统测试。套=smoke_all.gd 的断言函数;行段=基点行号。
|
||||
|
||||
| 套 | 行段 | 条数 | 去向 | 新宿主 | 守的生产模块(变异改坏点落点) | 组 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| _initialize | 44–70 | 14 | 单元 | unit/data_config/rng_stream_boot_test.gd | data/tables 加载链+core/rng_service.gd(统一流黄金向量/取号/事件日志) | G12 |
|
||||
| _split_tail | 80–94 | 1 | 系统 | tests/system_test.gd | autoload 装配链就绪护栏(main.tscn 引导) | G1 |
|
||||
| _smoke_type_gate | 99–105 | 2 | 单元 | unit/contracts/source_guard_test.gd | project.godot 警告配置+tools/audit_type_hints.py 闸接线 | G12 |
|
||||
| _smoke_hygiene_gates | 109–153 | 6 | 单元 | unit/contracts/source_guard_test.gd | 产品码源码卫生(core/presentation/data 三目录死代码/影子账/层序单口) | G12 |
|
||||
| _smoke_scene_split | 156–216 | 11 | 系统 | tests/system_test.gd | presentation 三屏壳+main.tscn 层序合同(LAYER_CONTRACT) | G1 |
|
||||
| _smoke_boot_chain | 242–312 | 15 | 系统 | tests/system_test.gd | main.gd boot 链(设置应用/续玩钮/启动态/调试浮窗)+SaveManager 启动态 | G1 |
|
||||
| _smoke_lifecycle_matrix | 319–492 | 10 | 系统 | tests/system_test.gd | main.gd 九阶段生命周期(引用面/模态开合/两连局/续玩断点) | G1 |
|
||||
| _stage_faces | 527–579 | 1 | 系统 | tests/system_test.gd | 呈现层引用面快照协议(现值回调与 run_scope 同源) | G1 |
|
||||
| _smoke_run_lifecycle | 592–645 | 11 | 系统 | tests/system_test.gd | main.gd 会话物化(脏态复位/断点清理/两连局零泄漏) | G1 |
|
||||
| _smoke_node_map_templates | 716–784 | 14 | 集成 | integration/node_map_templates_test.gd | level_runner+adventure_runner+save_manager+meta_progression(模板池跨系统+首冒险置位落盘) | G12 |
|
||||
| _smoke_adventure_table | 804–915 | 17 | 单元 | unit/data_config/table_structure_guard_test.gd | data/tables/adventure.tres+同调目录 12 件+orchestrator 装配贯通 | G2 |
|
||||
| _smoke_adventure_api | 919–955 | 11 | 单元 | unit/core/adventure_feats_test.gd | core/adventure_runner.gd 状态机受理面(重复拒/停点序/放弃两态) | G7 |
|
||||
| _smoke_adventure | 974–1287 | 54 | 单元 | unit/core/adventure_feats_test.gd | adventure_runner 冒险结构(三关/大奖励/账本封闭/败北线/恢复数学/共享池) | G7 |
|
||||
| _smoke_feats | 1292–1637 | 71 | 单元 | unit/core/adventure_feats_test.gd | data/tables/feats.tres+core/effect_engine.gd 逐件订阅+凭证保底 | G7 |
|
||||
| _smoke_layout_cell_formula | 1691–1705 | 2 | 单元 | unit/core/board_gen_reward_test.gd | presentation/views/board_view.gd 格边长公式(下缘预留回归) | G3 |
|
||||
| _smoke_type_glow | 1767–1955 | 24 | 单元 | unit/presentation/theme_projection_test.gd | board_view 微光数据面(作用集恒等/九型归组/零玩法写入) | G11 |
|
||||
| _smoke_reward_identity_unit | 1977–2019 | 10 | 单元 | unit/core/board_gen_reward_test.gd | core/reward_economy.gd 身份面(roll 恰一次/五型封闭/幂等) | G3 |
|
||||
| _smoke_seal_ap_exhausted | 2027–2058 | 5 | 单元 | unit/battle_settlement/seal_polarity_test.gd | core/action_economy.gd 末点 AP 封印轮末结算(playtest 缺陷②回归) | G5 |
|
||||
| _smoke_level | 2063–2094 | 11 | 单元 | unit/core/level_shop_economy_test.gd | level_runner 线性推进(经 level_autoplay:满配/败北/回放/恢复公式) | G8 |
|
||||
| _smoke_enemy_bands | 2099–2143 | 4 | 单元 | unit/data_config/table_structure_guard_test.gd | data/tables/enemy.tres 带键结构(20 行键序/hobgoblin 留任/Boss 行) | G2 |
|
||||
| _smoke_enemy_attack_type | 2151–2205 | 4 | 单元 | unit/battle_settlement/roll_multiplier_test.gd | 敌 attack_type 轴+克制矩阵消费(±1 位移证词) | G4 |
|
||||
| _smoke_ac_swap_guard | 2215–2320 | 12 | 单元 | unit/data_config/table_structure_guard_test.gd | status_effect/reward/settlement 表(AC 单轨/免伤墙护栏/掷点参数/敌 AC 带宽) | G2 |
|
||||
| _smoke_armor_roll | 2325–2400 | 15 | 单元 | unit/battle_settlement/roll_multiplier_test.gd | core/damage_math.gd 查档+orchestrator 掷点行为(nat20/下限/续时) | G4 |
|
||||
| _smoke_pool_wiring | 2474–2487 | 4 | 单元 | unit/battle_settlement/roll_multiplier_test.gd | orchestrator 乘区接线(×1.5/×0.75/硬化折半) | G4 |
|
||||
| _smoke_board_generator | 2509–2564 | 22 | 单元 | unit/core/board_gen_reward_test.gd | core/board_generator.gd(可清性/合法性/出图验收/确定性/r0 闸) | G3 |
|
||||
| _smoke_variation_framework | 2576–2603 | 9 | 单元 | unit/data_config/table_structure_guard_test.gd | data/tables/variation.tres+config_db 池门控+跨字段互锁 | G2 |
|
||||
| _smoke_global_variant | 2634–2749 | 30 | 单元 | unit/battle_settlement/variant_placement_test.gd | orchestrator 全局变异兜底回落+reward_economy 偏斜投影 | G6 |
|
||||
| _smoke_variant_placement | 2795–2873 | 9 | 单元 | unit/battle_settlement/variant_placement_test.gd | orchestrator 战斗初始化布置段(门控/布置域/数量公式/连通片) | G6 |
|
||||
| _smoke_full_battle | 2896–2928 | 7 | 单元 | unit/battle_settlement/round_flow_skill_pool_test.gd | orchestrator 全战斗回放(双流逐位/判定行/先手必兑) | G6 |
|
||||
| _run_battle | 2931–2962 | 1 | 单元 | unit/battle_settlement/round_flow_skill_pool_test.gd | orchestrator 收束相位回 Idle(随 S29 迁) | G6 |
|
||||
| _smoke_power_interval | 2988–3055 | 5 | 单元 | unit/core/board_gen_reward_test.gd | reward_economy 区间行(中点锚/流动/定值不受扰) | G3 |
|
||||
| _smoke_active_refresh | 3069–3144 | 17 | 单元 | unit/battle_settlement/round_flow_skill_pool_test.gd | action_economy 轮内刷新(上限/双账/V5 快照单源/回放) | G6 |
|
||||
| _smoke_difficulty_table | 3171–3193 | 8 | 单元 | unit/data_config/table_structure_guard_test.gd | data/tables/difficulty.tres+unit_container 修饰(空表/套乘/clamp) | G2 |
|
||||
| _smoke_damage_types | 3198–3229 | 10 | 单元 | unit/battle_settlement/roll_multiplier_test.gd | unit_container 护甲类型+克制矩阵表值+标错/标中兑付 | G4 |
|
||||
| _smoke_seal_irreversible_quadrants | 3286–3388 | 21 | 单元 | unit/battle_settlement/seal_polarity_test.gd | minesweeper_rules 封印不可逆四情况+拒绝面 | G5 |
|
||||
| _smoke_playtest_logger | 3392–3436 | 8 | 单元 | unit/presentation/theme_projection_test.gd | presentation/playtest_logger.gd(快照 JSONL/诊断转储) | G11 |
|
||||
| _smoke_encounter_roll | 3441–3506 | 8 | 单元 | unit/battle_settlement/round_flow_skill_pool_test.gd | orchestrator 遭遇区间骰(域内/回放/首遇带固定/空遭遇不可达) | G6 |
|
||||
| _smoke_attack_chain_fresh_pool | 3566–3590 | 3 | 单元 | unit/battle_settlement/roll_multiplier_test.gd | damage_controller fresh 池攻击链(Bug#2 掷点口径) | G4 |
|
||||
| _smoke_clear_reward_chain | 3611–3631 | 5 | 单元 | unit/battle_settlement/seal_polarity_test.gd | orchestrator 清图奖励双份兑付(真实流完美封印) | G5 |
|
||||
| _smoke_clear_reward_zero_paths | 3636–3690 | 11 | 单元 | unit/battle_settlement/seal_polarity_test.gd | 清图奖励弱形式分支(零兑付/高光守卫/第二份拒) | G5 |
|
||||
| _smoke_refresh_provenance_guard | 3694–3709 | 5 | 单元 | unit/battle_settlement/seal_polarity_test.gd | orchestrator 刷新图 provenance 守卫 | G5 |
|
||||
| _smoke_polarity_routing | 3740–3853 | 12 | 单元 | unit/battle_settlement/seal_polarity_test.gd | orchestrator 极性路由+封印溢价 125% | G5 |
|
||||
| _smoke_negative_dictionary | 3912–3998 | 8 | 单元 | unit/battle_settlement/seal_polarity_test.gd | 负向字典逐型兑付形态(效果行/直伤/警觉/行动点排队) | G5 |
|
||||
| _smoke_resource_surface | 4003–4044 | 5 | 单元 | unit/battle_settlement/seal_polarity_test.gd | 揭示事件资源标记+封锁格身份查询面 | G5 |
|
||||
| _smoke_settlement_segments | 4048–4115 | 5 | 单元 | unit/battle_settlement/round_flow_skill_pool_test.gd | settlement 五段全序+行型封闭+案C 段内序 | G6 |
|
||||
| _smoke_skill_pool_data | 4232–4406 | 27 | 单元 | unit/battle_settlement/round_flow_skill_pool_test.gd | data/tables/skill.tres+enemy.tres 池模板+解锁技编排集成 | G6 |
|
||||
| _smoke_skill_pool_replay_independence | 4412–4426 | 2 | 单元 | unit/battle_settlement/round_flow_skill_pool_test.gd | SkillLottery 同种子回放 | G6 |
|
||||
| _smoke_skill_pool_ev_anchor | 4452–4500 | 1 | 单元 | unit/battle_settlement/round_flow_skill_pool_test.gd | SkillLottery EV 带宽锚(含保底闸速率) | G6 |
|
||||
| _smoke_skill_pool_death_void | 4504–4538 | 5 | 单元 | unit/battle_settlement/round_flow_skill_pool_test.gd | 编排死亡打断作废(不动手不进账) | G6 |
|
||||
| _smoke_skill_pool_validation_reject | 4542–4587 | 13 | 单元 | unit/battle_settlement/round_flow_skill_pool_test.gd | skill/enemy 表加载校验拒绝族 | G6 |
|
||||
| _smoke_debuff_rider | 4600–4662 | 3 | 单元 | unit/battle_settlement/round_flow_skill_pool_test.gd | 妨害技落点+毒爆必中附加逐拍复算 | G6 |
|
||||
| _smoke_level_graph | 4667–4943 | 54 | 单元 | unit/core/level_shop_economy_test.gd | level.tres 图校验+level_runner 推进+enemy 行集定点+类型可见性 | G8 |
|
||||
| _smoke_gp_wallet | 4960–5009 | 9 | 单元 | unit/core/level_shop_economy_test.gd | level_runner 钱包到账(掷定窗口/关级加值) | G8 |
|
||||
| _smoke_gp_escrow | 5023–5108 | 12 | 单元 | unit/core/level_shop_economy_test.gd | orchestrator escrow 缓冲+钱包钳制不透支 | G8 |
|
||||
| _smoke_shop_room | 5133–5244 | 22 | 单元 | unit/core/level_shop_economy_test.gd | level_runner 商店三收口(现价/加算区/拒购/离开/清零) | G8 |
|
||||
| _smoke_shop_graph | 5256–5292 | 6 | 单元 | unit/core/level_shop_economy_test.gd | level.tres 商店房图不变式(必经位/跨表行集迁移) | G8 |
|
||||
| _smoke_shop_pool | 5297–5450 | 27 | 单元 | unit/core/level_shop_economy_test.gd | adventure_runner 商店共享池(抽 3 买 1/乘法价/共库/断点扩键) | G8 |
|
||||
| _smoke_nine_types_gp_stream | 5484–5524 | 6 | 单元 | unit/data_config/table_structure_guard_test.gd | reward.tres 十型封闭+18 行字典+gp 流权重 | G2 |
|
||||
| _smoke_meta | 5529–5545 | 4 | 单元 | unit/data_config/table_structure_guard_test.gd | data/tables/meta.tres 结构(行族/词表/first≥repeat) | G2 |
|
||||
| _smoke_forge_axes | 5556–5707 | 33 | 单元 | unit/core/meta_progression_forge_test.gd | meta_progression 锻造域+main.gd build_adventure_params+unit_container 两轴 | G9 |
|
||||
| _status_panel_projection | 5713–5765 | 4 | 单元 | unit/presentation/theme_projection_test.gd | presentation/views/status_panel.gd 只读投影 | G11 |
|
||||
| _career_overlay_projection | 5772–5833 | 8 | 单元 | unit/presentation/theme_projection_test.gd | presentation/views/career_overlay.gd 投影+v5 旧档显示 | G11 |
|
||||
| _meta_pricing_and_buy | 5862–5883 | 4 | 单元 | unit/core/meta_progression_forge_test.gd | meta_progression 树购买(受理/持久/拒绝零副作用) | G9 |
|
||||
| _meta_injection | 5887–5924 | 10 | 单元 | unit/core/meta_progression_forge_test.gd | meta_progression hero_override+adventure_runner 注入/白板 | G9 |
|
||||
| _meta_settle_and_workshop | 5928–6110 | 42 | 单元 | unit/core/meta_progression_forge_test.gd | meta_progression 收束入账/工坊解锁/同调位/开局传参面 | G9 |
|
||||
| _smoke_tree | 6115–6188 | 25 | 单元 | unit/core/meta_progression_forge_test.gd | meta_tree 表+购买面/Fork 双态/彩蛋觉醒 | G9 |
|
||||
| _smoke_counter_buckets | 6193–6241 | 6 | 单元 | unit/battle_settlement/round_flow_skill_pool_test.gd | effect_engine 四桶+run_ledger 跨战持久 | G6 |
|
||||
| _smoke_tree_rules | 6244–6347 | 19 | 单元 | unit/core/meta_progression_forge_test.gd | effect_engine 树六键+level_runner 计价+编排守侧 queue | G9 |
|
||||
| _smoke_battle_log_view | 6354–6389 | 6 | 单元 | unit/presentation/theme_projection_test.gd | presentation/views/battle_log_view.gd 行协议同步闸 | G11 |
|
||||
| _smoke_global_theme | 6395–6488 | 22 | 单元 | unit/presentation/theme_projection_test.gd | main_theme.tres+ThemeDB 挂载(词条/滑轨对比度/字体防御) | G11 |
|
||||
| _smoke_button_five_states | 6492–6562 | 23 | 单元 | unit/presentation/theme_projection_test.gd | ui_kit 按钮四工厂五态矩阵 | G11 |
|
||||
| _smoke_texture_filter | 6566–6606 | 9 | 单元 | unit/presentation/theme_projection_test.gd | project.godot 纹理过滤+ui_kit 白名单+光柱结构 | G11 |
|
||||
| _smoke_contrast_inks | 6610–6631 | 9 | 单元 | unit/presentation/theme_projection_test.gd | ui_kit 墨色对比度+standee/ap_bar 源码锚 | G11 |
|
||||
| _smoke_element_details | 6635–6658 | 8 | 单元 | unit/presentation/theme_projection_test.gd | ui_kit 槽色降档+商店/营地布局源码锚 | G11 |
|
||||
| _smoke_diag_button_retired | 6662–6674 | 7 | 单元 | unit/presentation/theme_projection_test.gd | main/ui_assembler/debug_overlay 诊断钮退役源码契约 | G11 |
|
||||
| _smoke_save | 6680–6982 | 70 | 单元 | unit/core/save_manager_domain_test.gd | core/save_manager.gd 读写/迁移链/兜底/域界/摘要/断点域 | G10 |
|
||||
| _smoke_save_slots | 6988–7151 | 31 | 单元 | unit/core/save_manager_domain_test.gd | save_manager 三槽位(往返/删档/窥视/迁移/旧单档) | G10 |
|
||||
|
||||
计数核对:套 6+1+70=77;条 49(系统)+14(集成)+1002(单元:data_config 74+contracts 8+core 550+battle_settlement 242+presentation 128)=1065。
|
||||
|
||||
随套迁移的无断言 helper(63 个,与主套同 commit 剪切):`check/_theme_rel_luminance/_theme_contrast/_topmost_visible_layer/_face_snapshot/_face_liveness/_run_core_smoke/_smoke_rest_stub/_smoke_find_token/_api_mark_*/_adv_drive_to_shop/_adv_st/_glow_*/_ms_digest/_first_hero_attack_rows/_weaken_refresh_probe/_ac11_tier_samples/_hero_attack_row_with/_mk_stream/_errs_has_cross_field/_arr_str_eq/_rows_weights_equal/_rows_weight_map/_board_resource_dump/_variant_dump/_board_dump/_auto_play_round/_row_power/_active_refresh_replay/_run_seal_backlash_round/_find_seal_target/_row_kind_of/_boss_depth/_first_encounter_band/_level_active_nodes/_level_battle_nodes/_encounter_replay/_polarity_probe_accepts/_run_polarity_probe/_rows_at/_row_phase/_run_full_clear/_drive_clear/_rows_of_type/_has_source_row/_has_effect_id/_battle_st/_smoke_skill_pool(调度壳)/_record_pool_trajectories/_has_err_category/_fresh_rng/_escrow_sum_of/_find_gp_dig/_shop_products_by_id/_all_entry_boss_paths/_meta_smoke_cleanup/_meta_new_save`。归属:`check` 归各宿主自持(新宿主已有 Kit.check 或宿主 check,迁移时按各宿主协议换通道——**这是唯一的非逐字面**,见 §五注意事项 4);其余 helper 一对一随首用套迁移,共享 helper(`_fresh_rng/_find_seal_target/_auto_play_round/_run_battle` 等)落其首要消费者宿主、他人 preload。
|
||||
|
||||
## 三、施工组(12 组,供逐组原子提交)
|
||||
|
||||
| 组 | 套 | 条数 | 宿主 |
|
||||
|---|---|---|---|
|
||||
| G1 系统·启动链尾段 | _split_tail、_smoke_scene_split、_smoke_boot_chain、_smoke_run_lifecycle、_smoke_lifecycle_matrix、_stage_faces(+_topmost_visible_layer/_face_snapshot/_face_liveness) | 49 | system_test.gd |
|
||||
| G2 单元·表结构族 | _smoke_adventure_table、_smoke_enemy_bands、_smoke_ac_swap_guard、_smoke_variation_framework、_smoke_nine_types_gp_stream、_smoke_meta、_smoke_difficulty_table | 60 | unit/data_config/table_structure_guard_test.gd |
|
||||
| G3 单元·棋盘生成与资源 | _smoke_board_generator、_smoke_layout_cell_formula、_smoke_reward_identity_unit、_smoke_power_interval | 39 | unit/core/board_gen_reward_test.gd |
|
||||
| G4 单元·掷点与乘区 | _smoke_armor_roll、_smoke_pool_wiring、_smoke_enemy_attack_type、_smoke_attack_chain_fresh_pool、_smoke_damage_types | 36 | unit/battle_settlement/roll_multiplier_test.gd |
|
||||
| G5 单元·封印与极性 | _smoke_seal_ap_exhausted、_smoke_seal_irreversible_quadrants、_smoke_polarity_routing、_smoke_negative_dictionary、_smoke_resource_surface、_smoke_clear_reward_chain、_smoke_clear_reward_zero_paths、_smoke_refresh_provenance_guard | 72 | unit/battle_settlement/seal_polarity_test.gd |
|
||||
| G6 单元·轮内流与技能池+变异 | _smoke_active_refresh、_smoke_encounter_roll、_smoke_settlement_segments、_smoke_full_battle+_run_battle、_smoke_skill_pool_*(5 件)、_smoke_debuff_rider、_smoke_counter_buckets、_smoke_global_variant、_smoke_variant_placement | 134 | unit/battle_settlement/round_flow_skill_pool_test.gd+variant_placement_test.gd(双宿主) |
|
||||
| G7 单元·冒险结构与专长 | _smoke_adventure_api、_smoke_adventure、_smoke_feats | 136 | unit/core/adventure_feats_test.gd |
|
||||
| G8 单元·关卡图与 GP 商店 | _smoke_level、_smoke_level_graph、_smoke_gp_wallet、_smoke_gp_escrow、_smoke_shop_room、_smoke_shop_graph、_smoke_shop_pool | 141 | unit/core/level_shop_economy_test.gd |
|
||||
| G9 单元·元进度与锻造 | _smoke_tree、_smoke_tree_rules、_meta_pricing_and_buy、_meta_injection、_meta_settle_and_workshop、_smoke_forge_axes | 133 | unit/core/meta_progression_forge_test.gd |
|
||||
| G10 单元·存档域 | _smoke_save、_smoke_save_slots | 101 | unit/core/save_manager_domain_test.gd |
|
||||
| G11 单元·呈现投影与主题 | _smoke_type_glow、_status_panel_projection、_career_overlay_projection、_smoke_battle_log_view、_smoke_global_theme、_smoke_button_five_states、_smoke_texture_filter、_smoke_contrast_inks、_smoke_element_details、_smoke_diag_button_retired、_smoke_playtest_logger | 128 | unit/presentation/theme_projection_test.gd |
|
||||
| G12 单元·源码契约+集成 | _initialize、_smoke_type_gate、_smoke_hygiene_gates、_smoke_node_map_templates | 36 | unit/data_config/rng_stream_boot_test.gd+unit/contracts/source_guard_test.gd+integration/node_map_templates_test.gd |
|
||||
|
||||
组内顺序建议:先小后大热身(G3/G4 → G2/G12 → G5/G6 → G1 → G8/G9 → G7 → G10 → G11);每组一个原子 commit(新宿主落函数+smoke_all.gd 同 commit 删函数)。
|
||||
|
||||
## 四、变异策略(到套级,按条执行细则照 test-conventions §6)
|
||||
|
||||
标准改坏形态按生产模块族分四类,每套守的模块见 §二末列:
|
||||
|
||||
1. **表结构类(G2/G12 表半/技能池数据面)**:改坏 `game/data/tables/*.tres` 一行值(如改 adventure.tres 档位配额、enemy.tres 一行 hp、meta.tres 首杀值)→ 跑该套断言验红 → `git checkout` 还原。
|
||||
2. **公式/纯逻辑类(G3/G4)**:改 core 公式一行(如 damage_math.gd 查档边界、board_generator.gd 尺寸公式、reward_economy.gd 中点取整)→ 验红还原。
|
||||
3. **状态机/编排行为类(G5/G6/G7/G8/G9 存档外半)**:改编排器/runner 分支(如删一处拒绝分支、翻转一个标志、改 action_economy 上限常量)→ 验红还原。
|
||||
4. **呈现/源码契约类(G11/G12 卫生面)**:改 ui_kit 常量或视图字段验红;源码契约面(hygiene/diag_button)的变异=往被扫产品码临时加一个违例符号(如一行 `Callable(self, "x")` 或 `build_diag_button` 调用)→ 验红还原。
|
||||
5. **系统类(G1)**:改 main.gd 装配序/层序合同一行或 settings 默认值 → 验红还原(跑 `run_system_test.py --script res://tests/system_test.gd`)。
|
||||
|
||||
注意:单元宿主断言通道是 `Kit.check("描述", cond)`(参序与 smoke 的 `check(cond, label)` 相反)——「只剪切禁改写」约束的是**标签字符串与 cond 表达式逐字**,调用通道换成宿主协议属宿主接线,不属改写;出口 `--diff` 比对的正是标签+cond 本身,不受通道影响。
|
||||
|
||||
## 五、宿主落位计划(本波不建任何宿主文件)
|
||||
|
||||
**为什么本波不建**:空壳测试文件会触发单元运行器「断言数为零即红」(test_kit.gd `finish()` 零断言返回退出码 2),且 system_test.gd 现状零断言跑绿是批 A 有意的过渡态。宿主文件只随波 1 各组迁移 commit 出现。
|
||||
|
||||
**新宿主文件(11 个,随首组迁移创建)**:
|
||||
- `game/tests/unit/data_config/rng_stream_boot_test.gd`(G12:_initialize 14 条)
|
||||
- `game/tests/unit/data_config/table_structure_guard_test.gd`(G2)
|
||||
- `game/tests/unit/contracts/source_guard_test.gd`(G12:type_gate+hygiene)
|
||||
- `game/tests/integration/node_map_templates_test.gd`(G12:首个集成新增件)
|
||||
- `game/tests/unit/core/board_gen_reward_test.gd`(G3)
|
||||
- `game/tests/unit/battle_settlement/roll_multiplier_test.gd`(G4)
|
||||
- `game/tests/unit/battle_settlement/seal_polarity_test.gd`(G5)
|
||||
- `game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd`(G6 主宿主)
|
||||
- `game/tests/unit/battle_settlement/variant_placement_test.gd`(G6 第二宿主)
|
||||
- `game/tests/unit/core/adventure_feats_test.gd`(G7);`level_shop_economy_test.gd`(G8);`meta_progression_forge_test.gd`(G9);`save_manager_domain_test.gd`(G10);`unit/presentation/theme_projection_test.gd`(G11)——core 四件与 presentation 一件共 5 个。
|
||||
|
||||
**挂载方式**:
|
||||
- **unit/integration**:新文件在 `game/tests/run_all.gd` `_suites()` manifest 追加 `{"path": "res://tests/unit/<域>/<名>_test.gd", "expect": 0}`(集成件同样进此 manifest,README 明示「与单元测试同一入口、同一协议」)。命名照 `[system]_[feature]_test.gd`;协议照 example_test.gd(extends SceneTree+Kit preload+quit(Kit.finish(...)));随组迁移的同批登记,禁出现「文件在 manifest 不在」的空窗(manifest 数即闸 02 计数口径)。
|
||||
- **system_test.gd**:断言函数直接追加进既有薄壳(宿主已自持 `check(cond, label)` 通道与 `_failures/_checks` 计数,摘要行 `[system_test] 摘要:N 项检查,M 项失败` 已被 run_system_test.py 三闸识别)。迁移期单跑验证:`python tools/run_system_test.py --script res://tests/system_test.gd`。**闸 01 缺省宿主在迁移期不动**(仍吃 smoke_all.gd),全部迁完的收尾批才把 `tools/run_system_test.py` 缺省 `--script` 改指 `res://tests/system_test.gd`(其头注既定程序)。
|
||||
- **夹具前提**:新宿主须自建 `_db/_rng` 等实例成员(smoke_all 的 `var _db/_rng` 由 `_initialize` 赋值)——照单元惯例显式实例化 ConfigDB/RngService 构造注入(rng_stream_boot_test 即 S01 本体的夹具形态);`AutoPlayer`/`level_autoplay` 等 smoke 库件迁移期继续按现路径 `res://smoke/...` preload,目录收尾批改名时同 commit 改 preload 路径。
|
||||
|
||||
## 六、歧义套(留主会话裁决,波 0 未定夺)
|
||||
|
||||
| 套 | 波 0 倾向 | 拿不准的理由 |
|
||||
|---|---|---|
|
||||
| _initialize | 单元 → data_config | 13 条(表加载+RNG 向量/流语义)是标准单元域且现役 rng_service_test.gd 同域;但 :70 事件日志落盘一条带落盘味;且它是入口编排函数,编排壳(_run_core_smoke/_split_tail 调用序)不随迁而随源文件退役——「整套迁 data_config 一个宿主」还是「按域拆两宿主」留裁。 |
|
||||
| _smoke_feats | 单元 → core | 主体 68 条=效果引擎逐件直测(纯逻辑);但 1420–1426 三条断点往返跨 SaveManager 落盘(集成味)。套级定单元、三条子块拆不拆留裁。 |
|
||||
| _smoke_save | 单元 → core | 与现役 unit/core/save_*_test 同惯例(临时档隔离);但 6807–6831 摘要集成块驱动真实 run_adventure 全局再入账(adventure×save 两系统落盘)——该子块是否升集成留裁。 |
|
||||
| _smoke_global_theme | 单元 → presentation | 依赖 ThemeDB 项目主题在座;`--script` 模式下 `ThemeDB.get_project_theme()` 能否取到项目主题未实跑验证(波 0 禁跑游戏测试),若取不到则此套须降级系统测试。主刀迁 G11 时先单跑验证。 |
|
||||
| _smoke_type_gate / _smoke_hygiene_gates | 单元 → contracts | 断言对象是仓库自身文件(project.godot/static_scan.sh/产品码源码),归「契约」语义最贴;但现役 unit/contracts/ 定位是「系统间接口契约」——新设 source_guard 宿主还是并进别处留裁。 |
|
||||
| _smoke_node_map_templates | 集成 | 跨四系统且走落盘判据明确;但前半 8 条(RNG 词表/模板池结构/抽签确定性)纯表结构可单元——整套集成还是拆套留裁。 |
|
||||
| _smoke_playtest_logger | 单元 → presentation | logger 自持临时档;但它挂在编排器真实结算流上(跨系统)——单测/集成两可,倾向单测(被测主体唯一)。 |
|
||||
| _smoke_type_glow | 单元 → presentation | 现役 unit/presentation/type_glow_test.gd 已覆盖同域微光——新宿主文件与它的分工边界(并列新文件还是后续合并)留主刀代理按「既有文件零改动」红线执行时定。 |
|
||||
|
||||
## 七、附录 A:引用普查清单(全仓 grep `game/smoke`/`res://smoke`/`smoke_all`,reports/ 历史件与本账本自身除外)
|
||||
|
||||
**合同在案必改点核实(§七批 B 第 4 条清单逐条)**:
|
||||
|
||||
| 在案点 | 实况 | 核实 |
|
||||
|---|---|---|
|
||||
| run_all_gates.sh PARITY_TOUCH_FACES | :200–210 定义块,`game/smoke/adventure_monte_carlo.gd` 在 :209(合同写 :206——行号已漂 3 行,重核时以符号搜为准) | 在案 ✓ |
|
||||
| AGENTS.md 在册入口 | :20(run_system_test 过渡宿主+探针路径)、:21(调参批量两件)、:22(verifier_batch 采集端) | 在案 ✓ |
|
||||
| tools/parity_anchor.py | :55 `res://smoke/adventure_monte_carlo.gd` | 在案 ✓ |
|
||||
| tools/balance_verifier.py | :54 `SCRIPT = "res://smoke/verifier_batch.gd"` | 在案 ✓ |
|
||||
| sim-data/analyze.py | **零命中**(仅 :182 注释提「smoke/calibrate_boards.gd」字样)——合同虚列或在案即此注释行,收尾批顺手核 | 在案但弱 ✎ |
|
||||
| architecture.md | :189(run_system_test 过渡宿主)、:241(三层承载表)、:242(UI 探针路径)、:315(启动后置条件判据事实源指针) | 在案 ✓ |
|
||||
| design/gdd/术语表.md 豁免行 | :128「被检查的禁字不算使用」节引 smoke_all.gd contains("旗") 例 | 在案 ✓ |
|
||||
| 08d 在册行号指针 | 08d=doc_ref_audit(run_all_gates.sh:192);其校验的行号指针散在设计文档——实测三处:锻造工坊.md:131(引「smoke_all.gd:1199 断言同步」史录)、数据配置.md:95(黄金向量断言指针)、体验验证清单.md:83(引函数名 `_smoke_flag_irreversible_quadrants`——**已滞后**,现名 `_smoke_seal_irreversible_quadrants`) | 在案 ✓(+1 处滞后名) |
|
||||
|
||||
**合同清单之外的新发现(迁移/收尾批必改或须知)**:
|
||||
|
||||
| 文件:行 | 性质 |
|
||||
|---|---|
|
||||
| game/presentation/tune_sim_controller.gd:27 | **产品码** preload `res://smoke/level_autoplay.gd`——smoke 目录改名时必须同批改,否则产品运行红 |
|
||||
| game/tests/unit/presentation/pacing_queue_test.gd:53 | 现役单测 preload `res://smoke/auto_player.gd`——同上 |
|
||||
| tools/probe_gate.sh:89–148(10 处) | 探针场景路径 `res://smoke/*.tscn`(合同 §四「批 B 随目录改名改路径」覆盖,列出防漏) |
|
||||
| tools/render_gate.sh:56/72/96(3 处) | 同上(渲染探针路径) |
|
||||
| tools/golden_baseline_refresh.sh:39/64(2 处) | **新发现**——golden 基线刷新脚本的探针路径,与 render_gate 同面 |
|
||||
| tools/t3_lever_scan.py:74 | **新发现**——调参扫描脚本调 `res://smoke/adventure_monte_carlo.gd` |
|
||||
| sim-data/README.md:36 | **新发现**——MC 采集档位表引 `game/smoke/adventure_monte_carlo.gd` 路径 |
|
||||
| game/tests/unit/README.md:6 | 参考实现指针「`game/smoke/smoke_all.gd`(752+ 条断言…)」——迁移后改指新宿主并顺带订正滞后读数 |
|
||||
| game/tests/unit/{rng_service_test:4, level_map_type_vis_test:4, curio_refresh_paid_test:26} | 注释提及 smoke_all(黄金向量锚分工/白盒先例)——收尾批顺手改提法 |
|
||||
| game/tests/static_scan.sh:100 | S11 去重闸头注提 smoke_all.gd(批 A 加的豁免说明——smoke_all 删除后改提新宿主) |
|
||||
| game/tests/.gates/baseline.json note | 历史记事文本含 smoke_all 字样(记事性引用,非路径依赖;随闸况重生成自然换代) |
|
||||
| game/tests/README.md(11 处) | 过渡期口径句(层级表/游离台账/运行示例)——收尾批统一换代 |
|
||||
| game/README.md:8/21/27/33/41(5 处) | **新发现**——工程 README 的运行命令全是 smoke_all 与 res://smoke 探针路径 |
|
||||
| 待办清单.md:570/656/658、体验验证清单.md:83 | 流程清单条目与滞后函数名(体验验证清单:83 函数名已滞后,见上) |
|
||||
| design/engineering/test-conventions.md:3/36 | 约束范围句与执行载体清单提 smoke_all.gd/smoke/ |
|
||||
| design/gdd/{锻造工坊:131, 数据配置:95} | 设计文档指针(锻造工坊为史录句;数据配置为活性指针须改指) |
|
||||
| game/smoke/*.gd 自引(各文件头注 res://smoke 路径,约 30 处) | 目录收尾改名时同批改;非闸件的自引属文件自身头注 |
|
||||
| tools/run_all_gates.sh:166 | 注释「过渡期宿主=game/smoke/smoke_all.gd」——收尾批同步 |
|
||||
|
||||
普查口径:`grep -rnE "game/smoke|res://smoke|smoke_all"` 限 .py/.sh/.gd/.md/.godot/.cfg/.json,排除 reports/、archive/、.git/ 与本账本/清单自身;总命中文件 63 个(上表归并后 18 类)。
|
||||
|
||||
## 八、其他需主会话注意
|
||||
|
||||
1. **清单入库面**:`game/tests/验证集清单.md` 已过 static_scan S11(205 文件词表扫描零命中)——唯一含退役词字面的标签(:6304「重判」行)与「去重闸」同行被既有豁免覆盖,**未新增任何豁免条款**。
|
||||
2. **工具面**:`tools/extract_checks.py` 为常驻对账工具(非本批中间件);其熵增闸归属=tools/ 现有类别,无新目录。它依赖码位掩码解析,smoke_all.gd 冻结期内源文件变动(不该发生)会直接反映在 diff。
|
||||
3. **收尾批提示**:smoke_all.gd 拆空删除时,`game/tests/smoke/` 子目录(critical-paths.md 人工闸清单)与 `game/smoke/` 目录改名是两件事,别漏前者;baseline.json 01 键 note 在收尾批换宿主时随批更新。
|
||||
4. **释义列的性质**:白话释义由波 0 逐条人工撰写(非机器生成),是对冻结面的可读注解;后续维护只增改释义不算改清单冻结面(--diff 不比对释义/处置列)。
|
||||
@@ -0,0 +1,494 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
r"""extract_checks.py — smoke_all.gd 断言静态提取器(验证集清单生成与出口对账)
|
||||
|
||||
机器提取 game/smoke/smoke_all.gd 的全部静态 check( 调用点(每个调用点记录:所在函数、
|
||||
行号、标签字符串字面量原文、cond 表达式原文),提供两种模式:
|
||||
|
||||
用法:
|
||||
python tools/extract_checks.py --extract [--src <源文件>] [--out <清单文件>] [--force]
|
||||
生成验证集清单(已存在时拒写,除非 --force——冻结参照物防误覆)
|
||||
python tools/extract_checks.py --diff <清单文件> [--src <源文件>]
|
||||
对当前源码再提取,与清单逐条比对——批 B 出口对账唯一工具
|
||||
python tools/extract_checks.py --list-funcs [--src <源文件>]
|
||||
只打印函数清单与各函数条数(编账本辅助,不写文件)
|
||||
|
||||
提取口径:
|
||||
- 静态 check( 调用点。与行级 grep(grep -c "check(")的差:grep 数含 func check 定义行
|
||||
与一处注释行(「禁恒真 check(true)」),行级 grep=1067、真实调用点=1065(2026-10-22 基点实测)。
|
||||
- 调用点可跨行:从 `check(` 起括号配平归集完整实参文本(() [] {} 三种括号都计深度);
|
||||
字符串字面量内的括号/逗号/井号、行尾注释、转义序列均不参与结构判定(码位掩码单趟预扫)。
|
||||
- cond 摘要=cond 原文跨行折叠为单行(连续空白并一空格);标签原文=第二个实参里的字符串
|
||||
字面量逐字保真(含引号与转义序列原样,禁改写);标签实参若带格式化/拼接后缀
|
||||
(`"..." % [...]`),后缀折叠后以「↩」接排在标签列。
|
||||
- md 表格单元格内 `|` 转义为 `\|`、`\` 转义为 `\\`(源码串内无这两序列的歧义形态,读回可还原)。
|
||||
|
||||
--diff 对账规则:只比对「标签+cond+计数」三要素(合同 §五出口机检);行号随迁移漂移、
|
||||
序号重排、白话释义/处置列由维护回填,均不参与比对。对不上的条目分三类报告:
|
||||
消失(清单有现码无)、新增(现码有清单无)、成对字面变更(cond 同标签异=标签变更;
|
||||
标签同 cond 异=cond 变更——一条「消失」与一条「新增」同因一字之改时按成对呈现)。
|
||||
退出码:0=全对齐;1=有差异;2=用法错或文件不可读。
|
||||
"""
|
||||
import argparse
|
||||
import bisect
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
DEFAULT_SRC = ROOT / "game" / "smoke" / "smoke_all.gd"
|
||||
DEFAULT_OUT = ROOT / "game" / "tests" / "验证集清单.md"
|
||||
|
||||
CALL_RE = re.compile(r"check\(")
|
||||
DEF_RE = re.compile(r"^\s*(static\s+)?func\s+check\s*\(")
|
||||
FUNC_RE = re.compile(r"^(static\s+)?func\s+([A-Za-z_][A-Za-z0-9_]*)")
|
||||
LITERAL_RE = re.compile(r'"((?:[^"\\]|\\.)*)"')
|
||||
OPENERS = "([{"
|
||||
CLOSERS = ")]}"
|
||||
CLOSER_OF = {")": "(", "]": "[", "}": "{"}
|
||||
|
||||
|
||||
def fold(text: str) -> str:
|
||||
"""跨行折叠:连续空白(含换行/制表/回车)并为一空格。对账归一口径。"""
|
||||
return re.sub(r"\s+", " ", text).strip()
|
||||
|
||||
|
||||
def esc_cell(text: str) -> str:
|
||||
return text.replace("\\", "\\\\").replace("|", "\\|")
|
||||
|
||||
|
||||
def unesc_cell(text: str) -> str:
|
||||
"""esc_cell 的逆。源码串内无 `\\|`/`\\\\` 歧义形态(提取口径节),还原无歧义。"""
|
||||
out = []
|
||||
i = 0
|
||||
while i < len(text):
|
||||
if text[i] == "\\" and i + 1 < len(text):
|
||||
out.append(text[i + 1])
|
||||
i += 2
|
||||
else:
|
||||
out.append(text[i])
|
||||
i += 1
|
||||
return "".join(out)
|
||||
|
||||
|
||||
def code_mask(joined: str) -> bytearray:
|
||||
"""单趟预扫:1=码位(非字符串内部、非注释)。字符串与行尾注释位置置 0。"""
|
||||
mask = bytearray(len(joined))
|
||||
in_str = False
|
||||
i = 0
|
||||
n = len(joined)
|
||||
while i < n:
|
||||
ch = joined[i]
|
||||
if in_str:
|
||||
mask[i] = 0
|
||||
if ch == "\\" and i + 1 < n:
|
||||
mask[i + 1] = 0
|
||||
i += 2
|
||||
continue
|
||||
if ch == '"':
|
||||
in_str = False
|
||||
i += 1
|
||||
continue
|
||||
if ch == '"':
|
||||
in_str = True
|
||||
mask[i] = 0
|
||||
i += 1
|
||||
continue
|
||||
if ch == "#":
|
||||
nl = joined.find("\n", i)
|
||||
end = n if nl == -1 else nl
|
||||
for j in range(i, end):
|
||||
mask[j] = 0
|
||||
i = end
|
||||
continue
|
||||
mask[i] = 1
|
||||
i += 1
|
||||
return mask
|
||||
|
||||
|
||||
def func_table(lines):
|
||||
"""顶层函数表:[(name, start_line_1based)]。lambda 归宿主函数。"""
|
||||
funcs = []
|
||||
for i, line in enumerate(lines, 1):
|
||||
m = FUNC_RE.match(line)
|
||||
if m:
|
||||
funcs.append((m.group(2), i))
|
||||
return funcs
|
||||
|
||||
|
||||
def func_at(funcs, lineno):
|
||||
name = "<module>"
|
||||
for fn, start in funcs:
|
||||
if start <= lineno:
|
||||
name = fn
|
||||
else:
|
||||
break
|
||||
return name
|
||||
|
||||
|
||||
def split_args(body):
|
||||
"""按括号配平拆实参(顶层逗号分隔)。body=check( 与最外层 ) 之间的文本(换行已并空格)。"""
|
||||
args = []
|
||||
depth = {"(": 0, "[": 0, "{": 0}
|
||||
cur = []
|
||||
in_str = False
|
||||
i = 0
|
||||
while i < len(body):
|
||||
ch = body[i]
|
||||
if in_str:
|
||||
if ch == "\\" and i + 1 < len(body):
|
||||
cur.append(body[i])
|
||||
cur.append(body[i + 1]) # 转义序列原样入账
|
||||
i += 2
|
||||
continue
|
||||
if ch == '"':
|
||||
in_str = False
|
||||
cur.append(ch)
|
||||
elif ch == '"':
|
||||
in_str = True
|
||||
cur.append(ch)
|
||||
elif ch in depth:
|
||||
depth[ch] += 1
|
||||
cur.append(ch)
|
||||
elif ch in CLOSERS:
|
||||
depth[CLOSER_OF[ch]] -= 1
|
||||
cur.append(ch)
|
||||
elif ch == "," and depth["("] == 0 and depth["["] == 0 and depth["{"] == 0:
|
||||
args.append("".join(cur))
|
||||
cur = []
|
||||
else:
|
||||
cur.append(ch)
|
||||
i += 1
|
||||
args.append("".join(cur))
|
||||
return args
|
||||
|
||||
|
||||
def collect_call(joined, start):
|
||||
"""从 check( 的 start 起括号配平归集调用。返回 (body, end_pos);不配平返回 None。"""
|
||||
i = start + len("check(")
|
||||
depth = 1
|
||||
in_str = False
|
||||
parts = []
|
||||
n = len(joined)
|
||||
while i < n:
|
||||
ch = joined[i]
|
||||
if in_str:
|
||||
if ch == "\\":
|
||||
parts.append(joined[i:i + 2])
|
||||
i += 2
|
||||
continue
|
||||
if ch == '"':
|
||||
in_str = False
|
||||
parts.append(ch)
|
||||
elif ch == '"':
|
||||
in_str = True
|
||||
parts.append(ch)
|
||||
elif ch in OPENERS:
|
||||
depth += 1
|
||||
parts.append(ch)
|
||||
elif ch in CLOSERS:
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return "".join(parts), i
|
||||
parts.append(ch)
|
||||
elif ch == "#":
|
||||
nl = joined.find("\n", i)
|
||||
i = n if nl == -1 else nl # 行尾注释不进 body(并一空格)
|
||||
parts.append(" ")
|
||||
continue
|
||||
else:
|
||||
parts.append(ch if ch != "\n" else " ")
|
||||
i += 1
|
||||
return None, i
|
||||
|
||||
|
||||
def extract(src_path: Path):
|
||||
"""提取全部静态 check( 调用点。返回(条目列表, 函数表),条目保持源码顺序。"""
|
||||
src = src_path.read_text(encoding="utf-8")
|
||||
lines = src.split("\n")
|
||||
funcs = func_table(lines)
|
||||
offsets = []
|
||||
total = 0
|
||||
for line in lines:
|
||||
offsets.append(total)
|
||||
total += len(line) + 1
|
||||
joined = "\n".join(lines)
|
||||
mask = code_mask(joined)
|
||||
|
||||
def line_of(pos):
|
||||
i = bisect.bisect_right(offsets, pos) - 1
|
||||
return i + 1
|
||||
|
||||
entries = []
|
||||
for m in CALL_RE.finditer(joined):
|
||||
if not mask[m.start()]:
|
||||
continue # 字符串字面量内 / 注释里的 check( 字样
|
||||
ln = line_of(m.start())
|
||||
if DEF_RE.match(lines[ln - 1]):
|
||||
continue # func check 定义行
|
||||
body, end_pos = collect_call(joined, m.start())
|
||||
if body is None:
|
||||
raise SystemExit("[extract_checks] FAIL:行 %d 起的 check( 括号不配平到文件尾——先查解析缺口" % ln)
|
||||
args = split_args(body)
|
||||
if len(args) not in (2, 3):
|
||||
raise SystemExit(
|
||||
"[extract_checks] FAIL:行 %d 的 check( 拆出 %d 个实参(预期 2/3)——解析缺口,先查再跑"
|
||||
% (ln, len(args)))
|
||||
cond = args[0]
|
||||
label_expr = args[1]
|
||||
quiet = args[2] if len(args) == 3 else ""
|
||||
lm = LITERAL_RE.search(label_expr)
|
||||
if lm is None:
|
||||
raise SystemExit(
|
||||
"[extract_checks] FAIL:行 %d 的标签实参不含字符串字面量——解析缺口,先查再跑" % ln)
|
||||
entries.append({
|
||||
"line": ln,
|
||||
"end_line": line_of(min(end_pos, len(joined) - 1)),
|
||||
"func": func_at(funcs, ln),
|
||||
"label_literal": lm.group(0), # 含引号,原文逐字
|
||||
"label_suffix": label_expr[lm.end():], # 格式化/拼接后缀(通常为空或 % ...)
|
||||
"cond": cond,
|
||||
"quiet": quiet,
|
||||
})
|
||||
return entries, funcs
|
||||
|
||||
|
||||
def label_cell(e):
|
||||
"""标签列:字面量 ↩ 后缀(后缀空则只有字面量)。"""
|
||||
sfx = fold(e["label_suffix"])
|
||||
return e["label_literal"] + (" ↩ " + sfx if sfx else "")
|
||||
|
||||
|
||||
def entry_key(e):
|
||||
"""对账指纹:cond 折叠 + 标签字面量(逐字)+ 后缀折叠 + quiet。"""
|
||||
return (fold(e["cond"]), e["label_literal"], fold(e["label_suffix"]), fold(e["quiet"]))
|
||||
|
||||
|
||||
def pad3(n):
|
||||
return "S%02d" % n if n < 100 else "S%03d" % n
|
||||
|
||||
|
||||
def base_commit():
|
||||
try:
|
||||
r = subprocess.run(["git", "rev-parse", "HEAD"], cwd=str(ROOT),
|
||||
capture_output=True, text=True, encoding="utf-8")
|
||||
if r.returncode == 0:
|
||||
return r.stdout.strip()
|
||||
except OSError:
|
||||
pass
|
||||
return "<git 不可用,未取得基点 commit>"
|
||||
|
||||
|
||||
def render_list(entries, funcs, src_rel: str) -> str:
|
||||
"""生成验证集清单骨架。白话释义/处置两列留待回填(回填不动冻结列)。"""
|
||||
out = []
|
||||
out.append("# 验证集清单(迁移参照物·冻结)")
|
||||
out.append("")
|
||||
out.append("> 生成日期:2026-10-22 · 基点 commit:`%s` · 源:`%s`" % (base_commit(), src_rel))
|
||||
out.append("> 提取口径:静态 `check(` 调用点,括号配平归集(可跨行)。基点实测调用点 1065 处;")
|
||||
out.append("> 行级 `grep -c \"check(\"` = 1067 是另一口径——多出的 2 行为 `func check(` 定义行与")
|
||||
out.append("> 一处注释行(「禁恒真 check(true)」),非调用点。运行时计数大于静态计数=循环展开条目,")
|
||||
out.append("> 两口径各自浮动,禁互相换算(tests/README「断言计数双口径」)。")
|
||||
out.append("> 生成命令:`python tools/extract_checks.py --extract`(可复现;重新生成须 --force)。")
|
||||
out.append("> 冻结纪律:每条的「标签原文」「cond 摘要」「quiet」三列为机器提取逐字保真,禁任何改写;")
|
||||
out.append("> cond 摘要=原文跨行折叠单行(连续空白并一空格),单元格内 `|`→`\\|`、`\\`→`\\\\` 转义,读回还原。")
|
||||
out.append("> 「白话释义」「处置」两列由维护回填,不属冻结面。")
|
||||
out.append("> 出口对账:`python tools/extract_checks.py --diff game/tests/验证集清单.md`")
|
||||
out.append("> ——标签/cond/计数三对齐才算迁移完(合同 §五出口机检)。")
|
||||
out.append("> 维护义务:此后断言新增/删除/改判必须同步本清单(test-conventions §9.3 锚)。")
|
||||
out.append("")
|
||||
seq = 0
|
||||
by_func = []
|
||||
for e in entries:
|
||||
if by_func and by_func[-1][0] == e["func"]:
|
||||
by_func[-1][1].append(e)
|
||||
else:
|
||||
by_func.append((e["func"], [e]))
|
||||
for idx, (fn, es) in enumerate(by_func, 1):
|
||||
start, end = es[0]["line"], es[-1]["end_line"]
|
||||
out.append("## %s %s(行 %d–%d · %d 条 · 去向:待分类回填)" % (pad3(idx), fn, start, end, len(es)))
|
||||
out.append("")
|
||||
out.append("| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet |")
|
||||
out.append("|---|---|---|---|---|---|---|")
|
||||
for e in es:
|
||||
seq += 1
|
||||
out.append("| %d | %d | %s | %s | (待填) | | %s |" % (
|
||||
seq, e["line"], esc_cell(label_cell(e)), esc_cell(fold(e["cond"])), esc_cell(fold(e["quiet"]))))
|
||||
out.append("")
|
||||
out.append("---")
|
||||
out.append("全源共 %d 章、%d 条(静态调用点口径)。" % (len(by_func), seq))
|
||||
return "\n".join(out) + "\n"
|
||||
|
||||
|
||||
CELL_SPLIT_RE = re.compile(r"(?<!\\)\|")
|
||||
|
||||
|
||||
def parse_list(path: Path):
|
||||
"""读回清单:返回条目列表(键与 entry_key 同构;section 供报告归组)。"""
|
||||
entries = []
|
||||
cur_section = None
|
||||
for i, line in enumerate(path.read_text(encoding="utf-8").split("\n"), 1):
|
||||
if line.startswith("## "):
|
||||
m = re.match(r"## \S+ (\S+)(行", line)
|
||||
cur_section = m.group(1) if m else line[3:].strip()
|
||||
continue
|
||||
if not line.startswith("|"):
|
||||
continue
|
||||
cells = [c.strip() for c in CELL_SPLIT_RE.split(line)[1:-1]]
|
||||
if len(cells) != 7 or not cells[0].isdigit() or cells[0] == "序号":
|
||||
continue # 表头/分隔行
|
||||
label_raw = unesc_cell(cells[2])
|
||||
cond = unesc_cell(cells[3])
|
||||
quiet = unesc_cell(cells[6])
|
||||
lit_m = LITERAL_RE.match(label_raw)
|
||||
literal = lit_m.group(0) if lit_m else label_raw
|
||||
suffix = label_raw[lit_m.end():].strip() if lit_m else ""
|
||||
suffix = suffix[1:].strip() if suffix.startswith("↩") else suffix
|
||||
entries.append({
|
||||
"md_line": i,
|
||||
"section": cur_section,
|
||||
"label_literal": literal,
|
||||
"label_suffix": suffix,
|
||||
"cond": cond,
|
||||
"quiet": quiet,
|
||||
})
|
||||
return entries
|
||||
|
||||
|
||||
def _short(s, n=80):
|
||||
s = s.replace("\n", " ")
|
||||
return s if len(s) <= n else s[:n] + "…"
|
||||
|
||||
|
||||
def diff_report(list_path: Path, src_path: Path) -> int:
|
||||
cur, _funcs = extract(src_path)
|
||||
frozen = parse_list(list_path)
|
||||
cur_keys = {}
|
||||
for e in cur:
|
||||
cur_keys.setdefault(entry_key(e), []).append(e)
|
||||
frozen_keys = {}
|
||||
for e in frozen:
|
||||
frozen_keys.setdefault(entry_key(e), []).append(e)
|
||||
missing = [k for k in frozen_keys if k not in cur_keys]
|
||||
added = [k for k in cur_keys if k not in frozen_keys]
|
||||
# 成对归类:cond 同→标签变更;标签同→cond 变更
|
||||
label_changes, cond_changes = [], []
|
||||
used_added = set()
|
||||
rest_missing = []
|
||||
for k in missing:
|
||||
cond, lit, sfx, _q = k
|
||||
pair = next((ka for ka in added if ka not in used_added and ka[0] == cond), None)
|
||||
if pair is not None:
|
||||
used_added.add(pair)
|
||||
label_changes.append((k, pair))
|
||||
continue
|
||||
pair = next((ka for ka in added if ka not in used_added and ka[1] == lit and ka[2] == sfx), None)
|
||||
if pair is not None:
|
||||
used_added.add(pair)
|
||||
cond_changes.append((k, pair))
|
||||
continue
|
||||
rest_missing.append(k)
|
||||
rest_added = [k for k in added if k not in used_added]
|
||||
ok = True
|
||||
print("[extract_checks] 对账:%s vs 现提取 %s" % (list_path.name, src_path.name))
|
||||
print(" 清单条数=%d 现提取条数=%d %s" % (
|
||||
len(frozen), len(cur), "(计数对齐)" if len(frozen) == len(cur) else "(计数不齐)"))
|
||||
|
||||
def show(k, tag):
|
||||
cond, lit, sfx, q = k
|
||||
s = " %s cond=%s label=%s" % (tag, _short(cond), _short(lit))
|
||||
if sfx:
|
||||
s += " suffix=%s" % _short(fold(sfx))
|
||||
if q:
|
||||
s += " quiet=%s" % fold(q)
|
||||
print(s)
|
||||
|
||||
if label_changes:
|
||||
ok = False
|
||||
print(" 标签字面变更 %d 对(cond 同、标签异):" % len(label_changes))
|
||||
for old, new in label_changes:
|
||||
show(old, "-")
|
||||
show(new, "+")
|
||||
if cond_changes:
|
||||
ok = False
|
||||
print(" cond 字面变更 %d 对(标签同、cond 异):" % len(cond_changes))
|
||||
for old, new in cond_changes:
|
||||
show(old, "-")
|
||||
show(new, "+")
|
||||
if rest_missing:
|
||||
ok = False
|
||||
print(" 消失 %d 条(清单有、现码无):" % len(rest_missing))
|
||||
for k in rest_missing:
|
||||
print(" - [%s]" % frozen_keys[k][0].get("section", "?"), end="")
|
||||
show(k, "")
|
||||
if rest_added:
|
||||
ok = False
|
||||
print(" 新增 %d 条(现码有、清单无):" % len(rest_added))
|
||||
for k in rest_added:
|
||||
es = cur_keys[k][0]
|
||||
print(" + [func %s @%d]" % (es["func"], es["line"]), end="")
|
||||
show(k, "")
|
||||
if ok:
|
||||
print("[extract_checks] PASS——标签/cond/计数三对齐,零差异")
|
||||
return 0
|
||||
print("[extract_checks] FAIL——见上差异清单")
|
||||
return 1
|
||||
|
||||
|
||||
def list_funcs(entries, funcs):
|
||||
by_func = []
|
||||
for e in entries:
|
||||
if by_func and by_func[-1][0] == e["func"]:
|
||||
by_func[-1][1] = by_func[-1][1] + 1
|
||||
else:
|
||||
by_func.append([e["func"], 1])
|
||||
checked = {f for f, _ in by_func}
|
||||
for fn, n in by_func:
|
||||
print("%s\t%d" % (fn, n))
|
||||
print("--- 无断言函数(helper,随套迁移)---")
|
||||
for fn, ln in funcs:
|
||||
if fn not in checked:
|
||||
print("%s\t@%d" % (fn, ln))
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if hasattr(sys.stdout, "reconfigure"):
|
||||
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
||||
ap = argparse.ArgumentParser(description="smoke_all.gd 断言静态提取器(清单生成/对账)")
|
||||
ap.add_argument("--extract", action="store_true", help="生成验证集清单")
|
||||
ap.add_argument("--diff", metavar="清单文件", help="与清单逐条对账(批 B 出口机检)")
|
||||
ap.add_argument("--list-funcs", action="store_true", help="打印函数清单与条数(编账本辅助)")
|
||||
ap.add_argument("--src", default=str(DEFAULT_SRC), help="断言源文件")
|
||||
ap.add_argument("--out", default=str(DEFAULT_OUT), help="--extract 输出路径")
|
||||
ap.add_argument("--force", action="store_true", help="输出文件已存在时允许覆盖")
|
||||
args = ap.parse_args()
|
||||
if sum(bool(x) for x in (args.extract, args.diff, args.list_funcs)) != 1:
|
||||
print("[extract_checks] 用法:--extract / --diff <清单文件> / --list-funcs 三选一")
|
||||
return 2
|
||||
src_path = Path(args.src)
|
||||
if not src_path.is_file():
|
||||
print("[extract_checks] FAIL:源文件不可读:%s" % src_path)
|
||||
return 2
|
||||
if args.extract:
|
||||
out = Path(args.out)
|
||||
if out.exists() and not args.force:
|
||||
print("[extract_checks] FAIL:清单已存在(%s)——冻结参照物防误覆,重生成须加 --force" % out)
|
||||
return 2
|
||||
entries, _funcs = extract(src_path)
|
||||
out.write_text(render_list(entries, _funcs, src_path.name), encoding="utf-8", newline="\n")
|
||||
print("[extract_checks] 已生成 %s(%d 个调用点)" % (out, len(entries)))
|
||||
return 0
|
||||
if args.diff:
|
||||
lp = Path(args.diff)
|
||||
if not lp.is_file():
|
||||
print("[extract_checks] FAIL:清单不可读:%s" % lp)
|
||||
return 2
|
||||
return diff_report(lp, src_path)
|
||||
entries, funcs = extract(src_path)
|
||||
list_funcs(entries, funcs)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user