test(验证): 批 B 按条变异验证引擎交付——单进程内存内变异(table 表改写/inject 源码注入编译路径接管/state 状态篡改+树形模板)三武器 189 条全跑:验红 131/异常 58(异常全数归因入账:磁盘读取面 2/schema 常量面 4/幂等等值面 2/预算收刀与改坏面缺形 50/基线未复绿 1 套如实亮灯);复验单轮中位 122ms;--adhoc 零配置宿主/--excl-func/--excl-line 毒候选隔离阀/--snap 快照物化/--ledger 台账机生成;seal 提交快照留底 .gates/mut_snapshots 供 --snap 物化;统一台账落 reports/批B-变异统一台账.md(主会话统一验证用 --adhoc 跑剩余未提交宿主)

This commit is contained in:
DKChan committed 2026-10-23 13:35:49 +08:00
1 parent 5ed1ec99af
commit 8fbd2dd875
4 files changed
+2697

No files matched your search

@@ -0,0 +1,74 @@
# tests/unit/battle_settlement/seal_polarity_test.gd — 封印与极性单元测试宿主
# 断言面 = 八套(批 B 断言迁移自 smoke_all.gd,G5 施工组;八套按源编排序分八笔原子提交迁入):
# 末点 AP 封印轮末结算(playtest 缺陷②回归——action_economy 末点花费与轮终结)
# 封印不可逆四情况(禁撤封印 + 挖封印格兑付改判——minesweeper_rules 拒绝面)
# 清图奖励双份兑付链(真实流完美封印——orchestrator grant_clear 身份兑付)
# 清图奖励弱形式分支(零兑付 / 高光守卫 / 第二份拒——直调编排私有面构造)
# 轮内刷新 provenance 守卫(刷新图清图不发清图奖励)
# 极性路由 + 封印溢价 125%(挖安全格正向 / 挖危险格负向 / 标中标空兑付极性)
# 负向字典逐型直验(效果行 / heal 直伤 / blast 警觉 / action 行动点排队)
# 格面资源资格面(揭示事件 resource 标记 + 封锁格身份查询)
# 协议照单元侧惯例:extends SceneTree+Kit.check 主通道;--script 模式无 autoload,
# ConfigDB 显式实例化构造注入(各套自建 RngService——源套掷点隔离形态,宿主不持统一流);
# 表加载失败不中止(沿被迁断言面的源语义——诊断在此展开,表值变异的红由具体断言落,承 G2/G3/G4 同款)。
# 运行:godot --headless --path game --script res://tests/unit/battle_settlement/seal_polarity_test.gd
extends SceneTree
const Kit := preload("res://tests/unit/test_kit.gd")
const ConfigDBScript := preload("res://core/config_db.gd")
const AutoPlayer := preload("res://smoke/auto_player.gd") # 自动玩家策略单一实现(迁移期按现路径 preload——账本 §五夹具前提)
var _db
func _init() -> void:
_db = ConfigDBScript.new()
# 表加载失败不中止(承 G2/G3/G4 同款:表损坏的红落在具体断言上——
# 诊断在此展开,后续断言按坏表实况逐条验红;collect_only=true 下成员照常赋值可评)
var load_ok: bool = _db.load_tables("res://data/tables", true)
if not load_ok:
push_error("[seal_polarity_test] 表加载返回失败——诊断如下(红由具体断言落)")
for d in _db.diagnostics():
if d.severity == "error" or d.severity == "fatal":
push_error(" [%s] %s: %s" % [d.severity, d.category, d.message])
# 套执行序沿源编排序
_smoke_seal_ap_exhausted()
quit(Kit.finish("seal_polarity_test"))
# ---- 末点 AP 封印轮末结算(playtest 缺陷②回归:末点 AP 花在封印必须终结轮次进入下一轮)----
func _smoke_seal_ap_exhausted() -> void:
print("--- 排雷玩法·封印动作耗尽末点 AP 回归 ---")
var rng = load("res://core/rng_service.gd").new()
rng.set_master_seed(20260911)
var settlement = load("res://core/battle_orchestrator.gd").new(_db, rng)
var rounds: Array = []
settlement.round_started.connect(func(ri): rounds.append(ri))
Kit.check("回归战斗启动(Boss 行——D0 20HP 级会被盲挖粉尘秒杀,无法承载封印回归场景)",
settlement.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}))
var minesweeper = settlement.minesweeper
# 恰花 (AP-1) 点挖掘,留末点给封印(表驱动——AP 从表读不写死)
var ap_total: int = int(_db.action_economy.ap_per_round)
for i in range(ap_total - 1):
if settlement.action_economy.phase != "Active" or settlement.action_economy.ap == 0:
break
var plan = AutoPlayer.choose_dig(minesweeper)
if plan.is_empty():
break
if plan.hero_cell != minesweeper.hero_pos():
minesweeper.settle_batch([{"action": "move", "cell": plan.hero_cell}])
minesweeper.settle_batch([{"action": "dig", "cell": plan.dig_cell}])
Kit.check("%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)
# 末点 AP 花在封印——轮次必须终结并结算进入第 2 轮
# 封印目标 = 挖掘策略的下一目标格(构造保证:未开、与小人邻接——先把小人移到其邻接已开格)
var plan2 = AutoPlayer.choose_dig(minesweeper)
Kit.check("仍存在可挖目标(封印目标来源)", not plan2.is_empty())
var out: Array = []
if not plan2.is_empty():
if plan2.hero_cell != minesweeper.hero_pos():
minesweeper.settle_batch([{"action": "move", "cell": plan2.hero_cell}])
out = minesweeper.settle_batch([{"action": "seal_place", "cell": plan2.dig_cell}])
Kit.check("末点 AP 封印被受理", not out.is_empty() and bool(out[0].accepted))
Kit.check("封印动作耗尽 AP 触发轮末结算并进入第 2 轮(rounds=%s)" % str(rounds), rounds.size() >= 2)
File diff suppressed because it is too large. Load diff
+159
View File
@@ -0,0 +1,159 @@
# tests/mutation/mutation_plan.gd — 变异验证引擎的计划配置(数据件,无逻辑)
# 宿主×套×变异策略一览:套→守的生产模块(账本 §二末列)+武器优先序+运行参数。
# src 指「被变换编译的宿主源码」:稳定宿主直接读工作区文件;迁移中的宿主(如 seal)
# 用驱动命令物化的快照(user:// 下,仓库外)——物化命令见套配置旁注与台账头部。
# 武器代号:table=表/资源改写(内存字段写读还原)· inject=注入编译(生产源码内存变换
# reload 原地替换·零触盘)· state=状态篡改(静态变量/成员初值/autoload 实例与树状态)。
# budget=每套改坏点试跑上限(轮)· cap_ms=每套墙钟上限 · count=静态断言点数(对账用)。
extends RefCounted
const GLOBAL_WATCHDOG_MS := 14400000 # 引擎全局看门狗(防挂死占闸;外层 bash 超时为最终兜底——4h,慢套全程靠它)
const SUITE_CAP_MS_DEFAULT := 600000 # 单套墙钟上限缺省(宿主级 cap_ms 可覆盖)
const BUDGET_DEFAULT := 160 # 单套试跑轮数缺省
static func hosts() -> Array:
return [
{
"id": "system_test",
"src": "res://tests/system_test.gd",
"channel": "self", # 断言通道 = 宿主自有 check(cond, label)
"async": true, # 套函数为协程(await process_frame)
"budget": 240,
"cap_ms": 2400000, # 慢套墙钟上限(boot 链/活战斗驱动单轮 20–60s)
"state_autoloads": ["ConfigDB", "SaveManager"],
"suites": {
"_split_tail": {"count": 1, "weapons": ["state"], "modules": [], "tables": [],
"strip_siblings": true, "funcs": {}},
"_smoke_scene_split": {"count": 11, "weapons": ["inject", "state"], "tables": [],
"modules": ["res://presentation/scenes/menu_screen.gd", "res://presentation/scenes/camp_screen.gd",
"res://presentation/scenes/adventure_screen.gd", "res://presentation/main.gd"],
"funcs": {"menu_screen.gd": ["_ready", "_build_ui"], "camp_screen.gd": ["_ready", "_build_ui"],
"adventure_screen.gd": ["_ready", "_build_ui"], "main.gd": ["_apply_layer_contract", "_build_ui", "_ready"]}},
"_smoke_boot_chain": {"count": 15, "weapons": ["inject", "state"], "tables": [],
"modules": ["res://presentation/settings_store.gd", "res://core/save_manager.gd",
"res://presentation/main.gd", "res://presentation/views/ui_kit.gd", "res://core/config_db.gd"],
"funcs": {"settings_store.gd": ["*"], "save_manager.gd": ["active_slot", "keystone_balance",
"write_to_disk", "has_breakpoint", "set_breakpoint", "clear_breakpoint", "slot_path", "adventure_total", "has_write_target"],
"main.gd": ["_ready", "_build_ui", "_refresh_resume_button", "_begin_session"]}},
"_smoke_run_lifecycle": {"count": 11, "weapons": ["inject", "state"], "tables": [],
"modules": ["res://presentation/run_scope.gd", "res://presentation/views/battle_end_overlay.gd",
"res://presentation/main.gd", "res://core/adventure_runner.gd"],
"funcs": {"run_scope.gd": ["*"], "battle_end_overlay.gd": ["is_open", "open", "close"],
"main.gd": ["_start_adventure_from_menu", "_begin_session", "_on_battle_end_continue", "_refresh_resume_button"],
"adventure_runner.gd": ["abandon", "clear_breakpoint"]}},
"_smoke_lifecycle_matrix": {"count": 10, "weapons": ["inject", "state"], "tables": [],
"modules": ["res://presentation/scenes/menu_screen.gd", "res://core/battle_orchestrator.gd",
"res://presentation/views/battle_end_overlay.gd", "res://presentation/main.gd", "res://core/adventure_runner.gd"],
"funcs": {"menu_screen.gd": ["_build_ui"], "battle_orchestrator.gd": ["is_battle_live", "begin_battle"],
"battle_end_overlay.gd": ["is_open"], "main.gd": ["_begin_session", "_on_battle_end_continue",
"_resume_from_menu", "_open_settings", "_open_career", "_show_debug_overlay"],
"adventure_runner.gd": ["abandon", "clear_breakpoint"]}},
"_stage_faces": {"count": 1, "run_fn": "_smoke_lifecycle_matrix", "weapons": ["inject", "state"],
"tables": [], "modules": ["res://presentation/modal_flow.gd", "res://presentation/main.gd"],
"funcs": {"modal_flow.gd": ["*"], "main.gd": ["_setup_modal_flow", "_build_ui"]}},
},
},
{
"id": "table_structure_guard",
"src": "res://tests/unit/data_config/table_structure_guard_test.gd",
"channel": "kit",
"async": false,
"budget": 3000,
"cap_ms": 1200000,
"state_autoloads": [],
"suites": {
"_smoke_ac_swap_guard": {"count": 12, "weapons": ["table", "inject"],
"tables": ["enemy", "status_effects", "reward", "settlement", "hero"],
"modules": ["res://core/unit_container.gd", "res://core/config_db_resolver.gd"], "funcs": {}},
"_smoke_enemy_bands": {"count": 4, "weapons": ["table", "inject"],
"tables": ["enemy"], "modules": ["res://core/config_db_resolver.gd"], "funcs": {}},
"_smoke_variation_framework": {"count": 9, "weapons": ["table", "inject"],
"tables": ["variation"], "modules": ["res://data/schemas/variation_table.gd"], "funcs": {}},
"_smoke_difficulty_table": {"count": 8, "weapons": ["table", "inject"],
"tables": ["difficulty"], "modules": ["res://core/unit_container.gd", "res://core/config_db_resolver.gd"], "funcs": {}},
"_smoke_nine_types_gp_stream": {"count": 6, "weapons": ["table", "inject"],
"tables": ["reward"], "modules": ["res://data/schemas/reward_table.gd", "res://core/rng_service.gd"], "funcs": {}},
"_smoke_adventure_table": {"count": 17, "weapons": ["table", "inject"],
"tables": ["adventure", "treasure_items", "meta"],
"modules": ["res://data/schemas/adventure_table.gd", "res://core/attunement.gd",
"res://data/schemas/treasure_item_table.gd", "res://core/battle_orchestrator.gd", "res://core/meta_progression.gd",
"res://core/config_db.gd"], "funcs": {}},
"_smoke_meta": {"count": 4, "weapons": ["table", "inject"],
"tables": ["meta"], "modules": ["res://core/meta_progression.gd"], "funcs": {}},
},
},
{
"id": "board_gen_reward",
"src": "res://tests/unit/core/board_gen_reward_test.gd",
"channel": "kit",
"async": false,
"budget": 800,
"state_autoloads": [],
"suites": {
"_smoke_layout_cell_formula": {"count": 2, "weapons": ["inject", "state"], "tables": [],
"modules": ["res://presentation/views/board_view.gd"], "funcs": {}},
"_smoke_reward_identity_unit": {"count": 10, "weapons": ["inject", "state"], "tables": [],
"modules": ["res://core/reward_economy.gd"], "funcs": {}},
"_smoke_board_generator": {"count": 22, "weapons": ["inject", "table"],
"tables": ["board_gen"],
"modules": ["res://core/board_generator.gd", "res://core/config_db_resolver.gd"], "funcs": {}},
"_smoke_power_interval": {"count": 5, "weapons": ["table", "inject"], "tables": ["reward"],
"modules": ["res://core/reward_economy.gd"], "funcs": {}},
},
},
{
"id": "roll_multiplier",
"src": "res://tests/unit/battle_settlement/roll_multiplier_test.gd",
"channel": "kit",
"async": false,
"budget": 400,
"cap_ms": 1800000,
"state_autoloads": [],
"suites": {
"_smoke_enemy_attack_type": {"count": 4, "weapons": ["table", "inject"],
"tables": ["enemy"], "modules": ["res://core/unit_container.gd", "res://core/damage_controller.gd"], "funcs": {}},
"_smoke_armor_roll": {"count": 15, "weapons": ["table", "inject"], "tables": ["settlement"],
"modules": ["res://core/damage_math.gd", "res://core/damage_controller.gd", "res://core/status_container.gd"], "funcs": {}},
"_smoke_pool_wiring": {"count": 4, "weapons": ["table", "inject"], "tables": ["status_effects"],
"modules": ["res://core/damage_controller.gd"], "funcs": {}},
"_smoke_damage_types": {"count": 10, "weapons": ["table", "inject"], "tables": ["enemy", "settlement"],
"modules": ["res://core/settlement/grant_segment.gd", "res://core/damage_controller.gd"], "funcs": {}},
"_smoke_attack_chain_fresh_pool": {"count": 3, "weapons": ["inject", "state"], "tables": [],
"modules": ["res://core/battle_orchestrator.gd", "res://core/damage_controller.gd"], "funcs": {}},
},
},
{
"id": "seal_polarity",
# 迁移中宿主:读驱动命令物化的提交快照(5ed1ec99 版=套1 五条在册形态),不读 WIP 工作区
"src": "user://mutation_work/seal_polarity_test.gd",
"channel": "kit",
"async": false,
"budget": 600,
"state_autoloads": [],
"suites": {
"_smoke_seal_ap_exhausted": {"count": 5, "weapons": ["state", "table", "inject"],
"tables": ["action_economy"],
"modules": ["res://core/battle_orchestrator.gd", "res://core/action_economy.gd", "res://core/minesweeper_rules.gd"],
# funcs 提示=账本 §九 G5 套1 旧法变异点所在函数(只作试跑排序,不作唯一改坏面)
"funcs": {"battle_orchestrator.gd": ["begin_battle"],
"minesweeper_rules.gd": ["revealed_cells", "settle_batch"],
"action_economy.gd": ["_cost", "try_spend", "_spend_cost_and_arbitrate", "_spend_and_arbitrate"]}},
},
},
]
static func host_by_id(id: String) -> Dictionary:
for h in hosts():
if h["id"] == id:
return h
return {}
static func total_static_checks() -> int:
var n := 0
for h in hosts():
for s in h["suites"].values():
n += int(s["count"])
return n