合并(迭代44): 组合根注册面批+战斗编排归约批+测速循环批v1.1三卡合回——S13零豁免三键真实清偿(main.gd 49→15/battle_orchestrator 38→15/tune_sim裸键消除)·批末全量域闸13/14绿(02/05/09三闸从基线红改善归绿·唯一红08c为并行写卡会话在飞件)·独立验收审计通过·卡③v1.1停卡重裁实况见实现报告§5
This commit is contained in:
commit
bee73db51b
54 files changed
+1833
-1226
No files matched your search
@@ -34,9 +34,31 @@ func _init() -> void:
|
||||
_smoke_pool_wiring()
|
||||
_smoke_damage_types()
|
||||
_smoke_attack_chain_fresh_pool()
|
||||
_smoke_hero_atk_derived()
|
||||
quit(Kit.finish("roll_multiplier_test"))
|
||||
|
||||
|
||||
# ---- 主角攻击基值派生读(注册面批同代编排归约 V4——_hero_atk 快照成员退役改派生读:
|
||||
# enc 携 hero_atk 键受理后经派生读供 spawn 与攻击单 base_value 两消费点;缺省回退表值——
|
||||
# 受理后不变量(encounter 受理后只读)。攻击单 base_value 贯通由 _smoke_damage_types 的
|
||||
# hero_atk_val=100 全链路行值承载(同夹具既有覆盖——派生读在攻击链上工作即行值随 override 走)----
|
||||
func _smoke_hero_atk_derived() -> void:
|
||||
var rng_a = load("res://core/rng_service.gd").new()
|
||||
rng_a.set_master_seed(4242)
|
||||
var s_a = load("res://core/battle_orchestrator.gd").new(_db, rng_a)
|
||||
Kit.check_quiet(s_a.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(_db.combat.hero.max_hp), "hero_atk": 100}),
|
||||
"起战受理(override 100)〔编排归约 V4〕")
|
||||
Kit.check_quiet(int(s_a._enemy_system.hero_unit().get("atk", -1)) == 100,
|
||||
"主角攻击单基值==enc 携带 override(_hero_atk 派生读贯通 spawn 消费点)〔编排归约 V4〕")
|
||||
var rng_b = load("res://core/rng_service.gd").new()
|
||||
rng_b.set_master_seed(4242)
|
||||
var s_b = load("res://core/battle_orchestrator.gd").new(_db, rng_b)
|
||||
Kit.check_quiet(s_b.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(_db.combat.hero.max_hp)}),
|
||||
"起战受理(缺省)〔编排归约 V4〕")
|
||||
Kit.check_quiet(int(s_b._enemy_system.hero_unit().get("atk", -1)) == int(_db.combat.hero.atk),
|
||||
"缺省主角攻击单基值==hero 表值(派生读回退面)〔编排归约 V4〕")
|
||||
|
||||
|
||||
# ---- 敌方攻击类型轴(表列/单位快照/单据载荷/矩阵命中证词)----
|
||||
# 克制矩阵命中证词 = 同种子三甲型配对实测(不是查表存在性证词):同种子下 armor_roll 流消费逐位一致、
|
||||
# 阈值(hero armor_class)不变,三甲型唯一差异 = 克制点数 cdelta(normal_vs_light 0 / heavy −1 / ward +1)
|
||||
|
||||
@@ -83,9 +83,10 @@ func _run_battle(L: int, D: int, axes: Dictionary = {}) -> Dictionary:
|
||||
var rng = load("res://core/rng_service.gd").new()
|
||||
rng.set_master_seed(42)
|
||||
var settlement = load("res://core/battle_orchestrator.gd").new(_db, rng)
|
||||
var out := {"stream": [], "events": [], "rounds": 0, "result": "", "terminated": false}
|
||||
var out := {"stream": [], "events": [], "rounds": 0, "result": "", "terminated": false, "econ_refresh_used": -1}
|
||||
settlement.step_row.connect(func(row): out.stream.append(row))
|
||||
settlement.battle_finished.connect(func(payload): out.result = payload.get("result", ""))
|
||||
settlement.settlement_snapshot.connect(func(snap): out.econ_refresh_used = int(snap.get("economy", {}).get("refresh_used", -1))) # 末次发射=终局轮快照
|
||||
settlement.minesweeper.cell_revealed.connect(func(evt): out.events.append(evt))
|
||||
settlement.minesweeper.seal_judged.connect(func(evt): out.events.append(evt))
|
||||
settlement.minesweeper.round_end_board.connect(func(p): out.events.append({"ev": "round_end_board", "path_tag": p.path_tag}))
|
||||
@@ -108,6 +109,10 @@ func _run_battle(L: int, D: int, axes: Dictionary = {}) -> Dictionary:
|
||||
# P0-3 回归断言:notify_combat_end 曾被并进行尾注释从不执行——
|
||||
# 战斗收束后相位必须回 Idle(RAP 清零),任何「相位卡 Waiting」回归在此被抓
|
||||
Kit.check("战斗收束后行动经济相位回 Idle(notify_combat_end 生效,实测=%s)" % String(action_economy.phase), action_economy.phase == "Idle")
|
||||
# 复位边界(注册面批同代编排归约 V2——BattleFacts.reset_round 不碰 refresh_used 的可观测提交面):
|
||||
# 终局序列含 facts.reset_round(),其后呈现快照的 refresh_used 必须等于末轮结算快照的 economy 值
|
||||
Kit.check("L%d·D%d 终局后呈现快照 refresh_used==末轮结算快照 economy 值(复位不碰 refresh_used)〔编排归约 V2〕" % [L, D],
|
||||
int(settlement.presentation_snapshot().get("refresh_used", -1)) == out.econ_refresh_used)
|
||||
return out
|
||||
|
||||
|
||||
@@ -1019,7 +1024,7 @@ func _smoke_counter_buckets() -> void:
|
||||
if not oA.minesweeper.current_board().is_mine(ca):
|
||||
safe_a = ca
|
||||
break
|
||||
oA._grant_seg._judge_seal({"cell": safe_a, "hit": false}, 1)
|
||||
oA._rounds._grant_seg._judge_seal({"cell": safe_a, "hit": false}, 1)
|
||||
Kit.check("V6 第1战标错落 battle 桶 insurance_used=1(_apply_counter_hit 静态写口经 ctx)〔无状态化 V6〕", EffectEngine.counter(oA.fx_host().counters_ctx, "insurance_used") == 1)
|
||||
# 卡④ wr2①——完美保险标错行=单一扩展行型(原 insurance_used 实名行型收敛)
|
||||
var v6_ins := {}
|
||||
|
||||
@@ -374,10 +374,10 @@ func _smoke_clear_reward_zero_paths() -> void:
|
||||
Kit.check("高光守卫构造战斗启动", s_b.begin_battle({"level_index": 1, "node_depth": CLEAR_SMOKE_DEPTH, "hero_hp": 80, "hero_max_hp": 80, "node_type": "boss"}))
|
||||
for u in s_b._enemy_system.all_enemies():
|
||||
s_b._enemy_system.apply_damage(u, int(u.hp))
|
||||
s_b._perfect_hit_true = 1 # 测试通道:构造完美谓词成立(真值在正常流由封印判定累加)
|
||||
s_b._perfect_hit_false = 0
|
||||
s_b._facts.perfect_hit_true = 1 # 测试通道:构造完美谓词成立(真值在正常流由封印判定累加)
|
||||
s_b._facts.perfect_hit_false = 0
|
||||
rows_b.clear()
|
||||
s_b.reward_grant().grant_clear_reward(1, s_b._board_from_midround_refresh, s_b.perfect_sweep_earned())
|
||||
s_b.reward_grant().grant_clear_reward(1, s_b._facts.board_from_midround, s_b.perfect_sweep_earned())
|
||||
Kit.check("② grant_clear_reward 守卫路径重走首份 → 未生效行仍在案", _has_source_row(rows_b, "reward_ineffective", "clear_reward", "clear_blast"))
|
||||
Kit.check("② 首份零兑付 → 高光行不发(不记名义值 2)", _rows_of_type(rows_b, "highlight").is_empty())
|
||||
# ③ 第二份被拒:唯一存活敌 HP ≤ 首份 aoe 量 → 首份击杀 → 第二份无存活敌(zero + 未生效行)
|
||||
@@ -389,10 +389,10 @@ func _smoke_clear_reward_zero_paths() -> void:
|
||||
Kit.check("第二份被拒构造战斗启动", s2.begin_battle({"level_index": 1, "node_depth": CLEAR_SMOKE_DEPTH, "hero_hp": 80, "hero_max_hp": 80, "node_type": "boss"}))
|
||||
var boss = s2._enemy_system.all_enemies()[0]
|
||||
boss.hp = 1 # 测试通道:压低至首份 aoe 必杀(弱形式——正常流 Boss 血量不可控)
|
||||
s2._perfect_hit_true = 1
|
||||
s2._perfect_hit_false = 0
|
||||
s2._facts.perfect_hit_true = 1
|
||||
s2._facts.perfect_hit_false = 0
|
||||
rows2.clear()
|
||||
s2.reward_grant().grant_clear_reward(1, s2._board_from_midround_refresh, s2.perfect_sweep_earned())
|
||||
s2.reward_grant().grant_clear_reward(1, s2._facts.board_from_midround, s2.perfect_sweep_earned())
|
||||
Kit.check("③ 首份 aoe 真实兑付(击杀唯一存活敌)", _has_source_row(rows2, "damage", "clear_reward"))
|
||||
Kit.check("③ 第二份被拒产出 reward_ineffective 行(零兑付)", _has_source_row(rows2, "reward_ineffective", "clear_reward", "clear_blast"))
|
||||
var hl2 := _rows_of_type(rows2, "highlight")
|
||||
|
||||
@@ -116,19 +116,19 @@ func _init() -> void:
|
||||
"feats": [], "tree_rules": [], "battle_index": 1, "voucher_rap": 0, "voucher_time_sand": 0,
|
||||
"node_type": "battle", "adv_bucket": {}}), "V6 前置:战斗受理")
|
||||
# facts 组包经真实摇号路径消费(mines 全零时条件技 sneak_ambush 不入集合——facts 四键在位的可观察面)
|
||||
Kit.check_quiet(int(orch._mines_hits["total"]) == 0 and int(orch._mines_hits["last"]) == 0 and int(orch._mines_hits["round"]) == 0,
|
||||
Kit.check_quiet(int(orch._rounds.mines_hits["total"]) == 0 and int(orch._rounds.mines_hits["last"]) == 0 and int(orch._rounds.mines_hits["round"]) == 0,
|
||||
"V6 开战清零:三计数全零起〔触发器批 V6〕")
|
||||
# 正向 blast(spark_aoe)不递增——负向计数守卫(flat 行应用面自编排器迁至奖励授予件
|
||||
# ——迭代40 卡① 拆件;计数经授予件 battle_hooks.negative_blast 回宿主 _mines_hits)
|
||||
var att_v6 := {"source_class": "reward", "cell": Vector2i(-1, -1), "ri": 1}
|
||||
orch.reward_grant()._apply_flat_row({"cell_type": "blast", "polarity": "positive", "power": 2,
|
||||
"display_name": "爆裂", "reward_id": "spark_aoe"}, att_v6)
|
||||
Kit.check_quiet(int(orch._mines_hits["total"]) == 0 and int(orch._mines_hits["round"]) == 0,
|
||||
Kit.check_quiet(int(orch._rounds.mines_hits["total"]) == 0 and int(orch._rounds.mines_hits["round"]) == 0,
|
||||
"V6 正向 blast(spark_aoe)不递增(负向守卫——清图/爆裂格零误计)〔触发器批 V6〕")
|
||||
# 负向 blast(雷)递增
|
||||
orch.reward_grant()._apply_flat_row({"cell_type": "blast", "polarity": "negative", "power": 2,
|
||||
"display_name": "警觉", "reward_id": "enemy_alert"}, att_v6)
|
||||
Kit.check_quiet(int(orch._mines_hits["total"]) == 1 and int(orch._mines_hits["round"]) == 1,
|
||||
Kit.check_quiet(int(orch._rounds.mines_hits["total"]) == 1 and int(orch._rounds.mines_hits["round"]) == 1,
|
||||
"V6 负向 blast(雷)递增恰一次〔触发器批 V6〕")
|
||||
|
||||
# ---- V7 池构成纪律(现行全表零违例+构造全条件池报错——跨表校验真实入口)----
|
||||
|
||||
@@ -66,12 +66,33 @@ func _smoke_hygiene_gates() -> void:
|
||||
var mv: String = FileAccess.get_file_as_string("res://presentation/main.gd")
|
||||
Kit.check("V8 GP 影子账残留零命中(main 源码面——文件已删+委托缝已拆)〔迭代27 T3〕",
|
||||
not mv.contains("gp_shadow_ledger") and not mv.contains("_gp_ledger"))
|
||||
# V1d 层序落位单口:main.gd 的 move_child 调用全部收在 _apply_layer_contract 内(合同装配单口——
|
||||
# 函数体之外零调用点;出现第二处落位=硬编码层序,须改合同表而非加调用)
|
||||
var fn_start: int = mv.find("func _apply_layer_contract")
|
||||
var fn_end: int = mv.find("\nfunc ", fn_start + 10)
|
||||
if fn_end < 0:
|
||||
fn_end = mv.length()
|
||||
var outside_calls: int = (mv.substr(0, fn_start) + mv.substr(fn_end)).count("move_child(")
|
||||
Kit.check("V1d 层序落位单口(_apply_layer_contract 之外 move_child 调用点=%d 应=0)〔装配生命批 V1〕" % outside_calls,
|
||||
outside_calls == 0)
|
||||
# V1d 层序落位单口:注册面建件后扫全 presentation——move_child 只允许出现在 ui_registry.gd 的
|
||||
# apply_layer_contract 函数体内(合同装配单口——出现第二处落位=硬编码层序,须改合同表而非加调用)
|
||||
var scanned: int = 0
|
||||
var outside_calls: int = 0
|
||||
for fn_path: String in _list_scripts("res://presentation"):
|
||||
var src: String = FileAccess.get_file_as_string(fn_path)
|
||||
scanned += 1
|
||||
var fn_start: int = src.find("func apply_layer_contract") if fn_path.contains("ui_registry.gd") else -1
|
||||
var fn_end: int = src.find("\nfunc ", fn_start + 10) if fn_start >= 0 else -1
|
||||
if fn_end < 0:
|
||||
fn_end = src.length()
|
||||
outside_calls += (src.substr(0, fn_start) + src.substr(fn_end)).count("move_child(")
|
||||
Kit.check("V1d 层序落位单口(presentation %d 文件——apply_layer_contract 之外 move_child 调用点=%d 应=0)〔注册面 V1〕" % [scanned, outside_calls],
|
||||
scanned > 0 and outside_calls == 0)
|
||||
|
||||
|
||||
# 目录 .gd 清单(DirAccess 平铺——presentation 无子目录;扫描面>0 前置由调用侧断言承载)
|
||||
static func _list_scripts(dir_path: String) -> PackedStringArray:
|
||||
var out: PackedStringArray = PackedStringArray()
|
||||
var dir := DirAccess.open(dir_path)
|
||||
if dir == null:
|
||||
return out
|
||||
dir.list_dir_begin()
|
||||
var fname := dir.get_next()
|
||||
while fname != "":
|
||||
if fname.ends_with(".gd"):
|
||||
out.append(dir_path + "/" + fname)
|
||||
fname = dir.get_next()
|
||||
dir.list_dir_end()
|
||||
return out
|
||||
@@ -850,7 +850,7 @@ func _drive_to_pref(autoplay, prefer: String) -> Dictionary:
|
||||
r.pick_next_node(int(hit.get("id", -1)))
|
||||
out["node"] = hit
|
||||
if prefer == "battle":
|
||||
out["enc"] = r.current_battle().encounter
|
||||
out["enc"] = r.current_battle()._facts.encounter
|
||||
else:
|
||||
var cands: Array = (adr.stop().node_curio_candidates() as Dictionary).get("candidates", []) as Array
|
||||
adr.stop().present_node_curio(cands)
|
||||
|
||||
@@ -178,10 +178,10 @@ func _init() -> void:
|
||||
Kit.check_quiet(safe_cells.size() == 2, "保险 rig:两枚安全格定位(去重闸按格实例——限次样本须异格)〔卡② R2〕")
|
||||
var rows_orch: Array[Dictionary] = []
|
||||
orch.step_row.connect(func(row): rows_orch.append(row)) # step_row 公开信号面(insurance_used 载荷/seal_miss highlight=豁免账与误标账观测面)
|
||||
Kit.check_quiet(int(orch._perfect_hit_false) == 0 and int(orch._perfect_forgiven) == 0,
|
||||
Kit.check_quiet(int(orch._facts.perfect_hit_false) == 0 and int(orch._facts.perfect_forgiven) == 0,
|
||||
"保险前置:误标账与豁免账双零起〔卡② R2〕")
|
||||
orch._grant_seg._judge_seal({"cell": safe_cells[0], "hit": false}, 1)
|
||||
Kit.check_quiet(int(orch._perfect_hit_false) == 1,
|
||||
orch._rounds._grant_seg._judge_seal({"cell": safe_cells[0], "hit": false}, 1)
|
||||
Kit.check_quiet(int(orch._facts.perfect_hit_false) == 1,
|
||||
"计数纯净:首次标错 _perfect_hit_false 落 1 未被回退改写(契约 2)〔卡② R2〕")
|
||||
var ins_row := {}
|
||||
for r in rows_orch:
|
||||
@@ -191,7 +191,7 @@ func _init() -> void:
|
||||
"豁免一次:首次标错 _perfect_forgiven=1(forgive_seal_miss 派发)〔卡② R2〕")
|
||||
Kit.check_quiet(int(orch.action_economy.pending_ap_delta()) == 1,
|
||||
"事件不吞:同一标错的 seal_missed 仍被危险直觉·守收到(queue AP+1)〔卡② R2〕")
|
||||
orch._grant_seg._judge_seal({"cell": safe_cells[1], "hit": false}, 2)
|
||||
orch._rounds._grant_seg._judge_seal({"cell": safe_cells[1], "hit": false}, 2)
|
||||
var ins_count := 0
|
||||
var miss_hl_last := ""
|
||||
for r in rows_orch:
|
||||
@@ -203,11 +203,11 @@ func _init() -> void:
|
||||
"限次:第二次标错不获豁免(forgiven 恒 1——when counter lt 1)〔卡② R2〕")
|
||||
Kit.check_quiet(not orch.perfect_sweep_earned(),
|
||||
"判定相减:误标 2 − 豁免 1 = 有效 1 → 完美不成立〔卡② R2 真值表〕")
|
||||
orch._perfect_hit_false = 1
|
||||
orch._perfect_hit_true = 1
|
||||
orch._facts.perfect_hit_false = 1
|
||||
orch._facts.perfect_hit_true = 1
|
||||
Kit.check_quiet(orch.perfect_sweep_earned(),
|
||||
"判定相减:误标1−豁免1=有效0且标中≥1→完美成立(保险每战限次一次迁移保绿)〔无状态化 V7〕")
|
||||
orch._perfect_hit_true = 0
|
||||
orch._facts.perfect_hit_true = 0
|
||||
Kit.check_quiet(not orch.perfect_sweep_earned(),
|
||||
"判定守卫:标中数 0 时零误标也不成立(整图 ≥1 标中条件零松动)〔卡② R2 真值表〕")
|
||||
var rig_rng2 = RngScript.new()
|
||||
@@ -222,7 +222,7 @@ func _init() -> void:
|
||||
var rows_orch2: Array[Dictionary] = []
|
||||
orch2.step_row.connect(func(row): rows_orch2.append(row)) # step_row 公开信号面(insurance_used 载荷=豁免账观测面)
|
||||
var sc2 = safe_cells[0]
|
||||
orch2._grant_seg._judge_seal({"cell": sc2, "hit": false}, 1)
|
||||
orch2._rounds._grant_seg._judge_seal({"cell": sc2, "hit": false}, 1)
|
||||
var ins_row2 := {}
|
||||
for r in rows_orch2:
|
||||
if String(r.get("effect_id", "")) == "insurance_used":
|
||||
@@ -249,7 +249,7 @@ func _init() -> void:
|
||||
if not o10.minesweeper.current_board().is_mine(c10):
|
||||
sc10 = c10
|
||||
break
|
||||
o10._grant_seg._judge_seal({"cell": sc10, "hit": false}, 1)
|
||||
o10._rounds._grant_seg._judge_seal({"cell": sc10, "hit": false}, 1)
|
||||
var sm10: Dictionary = {}
|
||||
for r10 in rows10:
|
||||
if String(r10.get("type", "")) == "seal_miss":
|
||||
@@ -276,14 +276,14 @@ func _init() -> void:
|
||||
if not o11.minesweeper.current_board().is_mine(c11):
|
||||
sc11 = c11
|
||||
break
|
||||
o11._grant_seg._judge_seal({"cell": sc11, "hit": false}, 1)
|
||||
o11._rounds._grant_seg._judge_seal({"cell": sc11, "hit": false}, 1)
|
||||
var sm11: Dictionary = {}
|
||||
for r11 in rows11:
|
||||
if String(r11.get("type", "")) == "seal_miss":
|
||||
sm11 = r11
|
||||
Kit.check_quiet(String(sm11.get("highlight", "")) == "",
|
||||
"A11 豁免:首次标错获豁免 → seal_miss 行 highlight==空串〔卡① N7〕")
|
||||
o11._perfect_hit_true = 1
|
||||
o11._facts.perfect_hit_true = 1
|
||||
Kit.check_quiet(o11.perfect_sweep_earned(),
|
||||
"A11 真值:误标 1 − 豁免 1 = 有效 0 且标中≥1 → 完美成立(与 highlight==空 一致)〔卡① N7〕")
|
||||
|
||||
|
||||
@@ -339,7 +339,7 @@ func _meta_settle_and_workshop() -> void:
|
||||
adr_res.resume_adventure(bp_min, p_resume))
|
||||
Kit.check("继续上局受理面:tree_rules 随参数面进 runner._tree_rules(消费面 set_tree_rules 下传现成)",
|
||||
adr_res.runner() != null and adr_res.runner().pick_next_node(int(adr_res.runner().next_candidate_ids()[0]))
|
||||
and ((adr_res.runner().current_battle().encounter.get("tree_rules", []) as Array) == rules_e))
|
||||
and ((adr_res.runner().current_battle()._facts.encounter.get("tree_rules", []) as Array) == rules_e))
|
||||
_meta_smoke_cleanup()
|
||||
|
||||
|
||||
@@ -705,16 +705,16 @@ func _smoke_tree_rules() -> void:
|
||||
Kit.check("守侧 rig:首轮后 queue 干净(开局 posture queue 已兑现)",
|
||||
int(orch.action_economy.pending_ap_delta()) == 0)
|
||||
# 场景①:当回合新封印、结算判标错且封印仍在场 → queue +1(本轮 AP 零变化=queue 承载、下轮 notify_round_start 兑现)
|
||||
orch._grant_seg._judge_seal({"cell": safe, "hit": false}, 1)
|
||||
orch._rounds._grant_seg._judge_seal({"cell": safe, "hit": false}, 1)
|
||||
Kit.check("守①:当回合标错且封印在场 → 下轮 AP+1(queue 排队——本轮 AP 逐位零变化)",
|
||||
int(orch.action_economy.pending_ap_delta()) == 1)
|
||||
# 场景③④:之前回合标错重判(封印在场状态)与同格重复——去重闸挡回,最多返还一次
|
||||
orch._grant_seg._judge_seal({"cell": safe, "hit": false}, 2)
|
||||
orch._grant_seg._judge_seal({"cell": safe, "hit": false}, 3)
|
||||
orch._rounds._grant_seg._judge_seal({"cell": safe, "hit": false}, 2)
|
||||
orch._rounds._grant_seg._judge_seal({"cell": safe, "hit": false}, 3)
|
||||
Kit.check("守③④:旧封印重判零触发+同格最多一次(_seal_miss_seen 去重闸=「当回合标错动作」判别器)",
|
||||
int(orch.action_economy.pending_ap_delta()) == 1)
|
||||
# 场景②负样本:封印命中(hit=true 正向兑付路径)零触发
|
||||
orch._grant_seg._judge_seal({"cell": safe, "hit": true}, 4)
|
||||
orch._rounds._grant_seg._judge_seal({"cell": safe, "hit": true}, 4)
|
||||
Kit.check("守②负样本:标中/被挖封印零触发(标错事件只在 miss 分支发射——发射面过滤承载)",
|
||||
int(orch.action_economy.pending_ap_delta()) == 1)
|
||||
# 场景⑤:末轮判出→无下一轮=queue 不兑现(编排器每战新建——队列实例随战亡,合法作废态)
|
||||
@@ -746,7 +746,7 @@ func _smoke_tree_rules() -> void:
|
||||
"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"}))
|
||||
orch3.encounter["tree_rules"] = []
|
||||
orch3._facts.encounter["tree_rules"] = []
|
||||
var gp_base: int = orch3._roll_gp_drop()
|
||||
var orch4 = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(777005))
|
||||
Kit.check("聚 rig:增益编排启动(tree_rules=economy_hoard)",
|
||||
@@ -755,7 +755,7 @@ func _smoke_tree_rules() -> void:
|
||||
"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"}))
|
||||
orch4.encounter["tree_rules"] = ["economy_hoard"]
|
||||
orch4._facts.encounter["tree_rules"] = ["economy_hoard"]
|
||||
var gp_hoard: int = orch4._roll_gp_drop()
|
||||
Kit.check("聚:战斗掉落乘算 ×1.5(provisional——同种子配对 %d→%d,入账前=结算框显示实得)",
|
||||
gp_hoard == int(round(float(gp_base) * 1.5)) and gp_hoard >= gp_base)
|
||||
|
||||
@@ -72,7 +72,7 @@ func _seal_propagation(db) -> bool:
|
||||
rng_a.set_master_seed(5)
|
||||
var oc_bg = OrchestratorScript.new(db, rng_a)
|
||||
var bi_bg = BoardInstallerScript.new({"db": db, "rng": rng_a, "minesweeper": oc_bg.minesweeper,
|
||||
"reward_economy": oc_bg._reward_economy, "encounter": oc_bg.encounter, "fatal_cb": oc_bg._fatal})
|
||||
"reward_economy": oc_bg._reward_economy, "encounter": oc_bg._facts.encounter, "fatal_cb": oc_bg._fatal})
|
||||
bi_bg.generator._fatal("测试违约①棋盘生成")
|
||||
if oc_bg.battle_result != "fatal_error":
|
||||
push_error("[run_time_fatal_test/wr5] 棋盘生成违约未传导封印(battle_result=%s)" % str(oc_bg.battle_result))
|
||||
|
||||
@@ -23,12 +23,15 @@ func _init() -> void:
|
||||
Kit.check_quiet(load("res://core/settlement/decay_segment.gd").new(oc) != null, "V4 DecaySegment 直接 new()〔迭代27 T2〕")
|
||||
Kit.check_quiet(load("res://core/settlement/verdict_segment.gd").new(oc) != null, "V4 VerdictSegment 直接 new()〔迭代27 T2〕")
|
||||
|
||||
# V4-2 编排器 _init 接线(段持宿主引用;段序编排面在位)
|
||||
Kit.check_quiet(oc._grant_seg != null and oc._grant_seg.oc == oc, "V4 编排器接线·授予段(宿主回指)〔迭代27 T2〕")
|
||||
Kit.check_quiet(oc._aura_seg != null and oc._aura_seg.oc == oc, "V4 编排器接线·增益/减益段〔迭代27 T2〕")
|
||||
Kit.check_quiet(oc._combat_seg != null and oc._combat_seg.oc == oc, "V4 编排器接线·战斗段〔迭代27 T2〕")
|
||||
Kit.check_quiet(oc._decay_seg != null and oc._decay_seg.oc == oc, "V4 编排器接线·衰减段〔迭代27 T2〕")
|
||||
Kit.check_quiet(oc._verdict_seg != null and oc._verdict_seg.oc == oc, "V4 编排器接线·判定段〔迭代27 T2〕")
|
||||
# V4-2 编排器 _init 接线(段持宿主引用;段序编排面在位)——注册面批同代归约 V1:
|
||||
# 段件宿主直持改经 _rounds(轮循环驱动件)承载,接线断言改指 _rounds 段族
|
||||
Kit.check_quiet(oc._rounds != null and oc._rounds.oc == oc, "V1 编排器接线·轮循环驱动件在座且宿主回指〔编排归约 V1〕")
|
||||
Kit.check_quiet(oc._facts != null, "V1 编排器接线·战斗事实件在座〔编排归约 V1〕")
|
||||
Kit.check_quiet(oc._rounds._grant_seg != null and oc._rounds._grant_seg.oc == oc, "V4 编排器接线·授予段(宿主回指)〔迭代27 T2〕")
|
||||
Kit.check_quiet(oc._rounds._aura_seg != null and oc._rounds._aura_seg.oc == oc, "V4 编排器接线·增益/减益段〔迭代27 T2〕")
|
||||
Kit.check_quiet(oc._rounds._combat_seg != null and oc._rounds._combat_seg.oc == oc, "V4 编排器接线·战斗段〔迭代27 T2〕")
|
||||
Kit.check_quiet(oc._rounds._decay_seg != null and oc._rounds._decay_seg.oc == oc, "V4 编排器接线·衰减段〔迭代27 T2〕")
|
||||
Kit.check_quiet(oc._rounds._verdict_seg != null and oc._rounds._verdict_seg.oc == oc, "V4 编排器接线·判定段〔迭代27 T2〕")
|
||||
# counter_add 写口真实路径(geologist 订阅行承载:公共命令链 begin_battle→move→dig→stop
|
||||
# 经授予段 counter_add 单点落 counters_ctx battle 桶——受理载荷+公开读数口 fx_host().counters_ctx)
|
||||
var rng_c = load("res://core/rng_service.gd").new()
|
||||
|
||||
@@ -162,13 +162,13 @@ func _init() -> void:
|
||||
"线香:装配起战(battle 级充能 1)")
|
||||
var inv8 = load("res://core/treasure_items.gd").new(db)
|
||||
var rec8: Dictionary = inv8.add_item("incense_stick", "attunement", Vector2i(-1, -1), 0)
|
||||
ob8.treasure_inventory = inv8
|
||||
ob8.reward_grant().treasure_inventory = inv8
|
||||
var h8: int = int(rec8.get("handle", -1)) # handle 取 add_item 返回(snapshot 投影无 handle 键)
|
||||
var tgt8: Vector2i = _first_closed_safe(ob8)
|
||||
Kit.check_quiet(tgt8 != Vector2i(-9, -9), "线香:夹具存在未开安全格")
|
||||
var v8: Dictionary = ob8.reward_grant().use_treasure(h8, tgt8)
|
||||
Kit.check_quiet(bool(v8.get("ok", false)), "线香正常:单格真视受理(cell 目标解析)")
|
||||
Kit.check_quiet(bool(ob8.treasure_inventory.query_overlays().get("mine_reveal", []).has(tgt8))
|
||||
Kit.check_quiet(bool(ob8.reward_grant().treasure_inventory.query_overlays().get("mine_reveal", []).has(tgt8))
|
||||
and ob8.minesweeper.is_revealed(tgt8) == false,
|
||||
"线香断言:目标格入真视叠加层且未被开格(真视≠开格——信息红线)")
|
||||
Kit.check_quiet(inv8.snapshot_inventory().is_empty() or bool(inv8.snapshot_inventory()[0].get("used", false)),
|
||||
@@ -240,7 +240,7 @@ func _init() -> void:
|
||||
"天眼:装配起战(level 级充能 1)")
|
||||
var inv11 = load("res://core/treasure_items.gd").new(db)
|
||||
var rec11: Dictionary = inv11.add_item("sky_eye", "attunement", Vector2i(-1, -1), 0)
|
||||
ob11.treasure_inventory = inv11
|
||||
ob11.reward_grant().treasure_inventory = inv11
|
||||
var h11: int = int(rec11.get("handle", -1))
|
||||
var tgt11: Vector2i = _first_closed_safe(ob11)
|
||||
var v11: Dictionary = ob11.reward_grant().use_treasure(h11, tgt11)
|
||||
@@ -254,7 +254,7 @@ func _init() -> void:
|
||||
if ob11.minesweeper.is_revealed(c):
|
||||
opened_safe = false
|
||||
Kit.check_quiet(mines_shown == 0 or opened_safe, "天眼反例①:危险格仅显示不被开格(棋盘事实不可变)")
|
||||
var overlay11: Array = ob11.treasure_inventory.query_overlays().get("mine_reveal", [])
|
||||
var overlay11: Array = ob11.reward_grant().treasure_inventory.query_overlays().get("mine_reveal", [])
|
||||
var overlay_ok := true
|
||||
for c_v in (v11.get("resolved", []) as Array):
|
||||
if not overlay11.has(c_v):
|
||||
@@ -289,7 +289,7 @@ func _init() -> void:
|
||||
"计数链:装配起战(GainJournal 携带)")
|
||||
var inv13 = load("res://core/treasure_items.gd").new(db)
|
||||
var rec13: Dictionary = inv13.add_item("incense_stick", "attunement", Vector2i(-1, -1), 0)
|
||||
ob13.treasure_inventory = inv13
|
||||
ob13.reward_grant().treasure_inventory = inv13
|
||||
var h13: int = int(rec13.get("handle", -1))
|
||||
var tgt13: Vector2i = _first_closed_safe(ob13)
|
||||
if tgt13 != Vector2i(-9, -9):
|
||||
@@ -306,8 +306,13 @@ func _init() -> void:
|
||||
"装配不可变:双件装配冒险启动")
|
||||
Kit.check_quiet(adr14.runner().pick_next_node(int(adr14.runner().next_candidate_ids()[0])), "装配不可变:进首战")
|
||||
var ob14 = adr14.runner().current_battle()
|
||||
Kit.check_quiet(ob14 != null and (ob14.attunement as Array) == ["morning_order", "wide_scout_band"],
|
||||
Kit.check_quiet(ob14 != null and (ob14._facts.attunement as Array) == ["morning_order", "wide_scout_band"],
|
||||
"装配不可变:战斗运行态装配集与冒险装配集逐位同源(运行零写口——快照对比)")
|
||||
# 注册面批同代编排归约 V5:库存注入改址(level_runner 注入改经 reward_grant() 出口直注)后
|
||||
# 引用同一性——战斗域经出口持有的库存与关卡域同一实例;use_treasure 受理链经新地址走通
|
||||
# 由本套件 ob8/ob11/ob13 三段既有受理链承载(注入面同批改址后全绿即证据)。
|
||||
Kit.check_quiet(ob14 != null and ob14.reward_grant().treasure_inventory == adr14.runner().treasure_inventory,
|
||||
"库存注入改址:战斗域经 reward_grant 出口持有的库存与关卡域同一引用〔编排归约 V5〕")
|
||||
|
||||
# ⑮ 扩展行收敛(迭代 38 卡④ wr2④⑤——套件首次 feats 注入,V6 形态自 round_flow_skill_pool_test 移植:
|
||||
# enc 带 feats 键+ledger,专长触发经 _judge_seal 真实封印判定管线)
|
||||
@@ -332,7 +337,7 @@ func _init() -> void:
|
||||
Kit.check_quiet(mines15.size() == 3, "封印匠:板上存在三枚危险格(断言前提)")
|
||||
if mines15.size() == 3:
|
||||
for mv in mines15:
|
||||
ob15._grant_seg._judge_seal({"cell": mv, "hit": true}, 1)
|
||||
ob15._rounds._grant_seg._judge_seal({"cell": mv, "hit": true}, 1)
|
||||
var smith_row := {}
|
||||
for r in rows15:
|
||||
if String(r.get("effect_id", "")) == "mine_neutralized":
|
||||
@@ -354,7 +359,7 @@ func _init() -> void:
|
||||
cell15b = cv
|
||||
break
|
||||
if cell15b != null:
|
||||
ob15b._grant_seg._judge_seal({"cell": cell15b, "hit": true}, 1)
|
||||
ob15b._rounds._grant_seg._judge_seal({"cell": cell15b, "hit": true}, 1)
|
||||
var beacon_row := {}
|
||||
for r in rows15b:
|
||||
if String(r.get("effect_id", "")) == "fx_preview_area":
|
||||
|
||||
@@ -58,7 +58,7 @@ func _init() -> void:
|
||||
main._start_adventure_from_menu()
|
||||
await process_frame
|
||||
# 入口停点选路进首战(真实停点链)
|
||||
if not main.level_screen.visible:
|
||||
if not main.ui.modals.level_screen.visible:
|
||||
_problems.append("入口停点未呈现(前置失守)")
|
||||
_finish()
|
||||
return
|
||||
@@ -100,12 +100,12 @@ func _init() -> void:
|
||||
# ---- 断言①:排空前后核心状态逐位一致(演出关停零分叉)----
|
||||
var snap_before: String = JSON.stringify(battle.diagnostic_snapshot())
|
||||
var hp_before: int = int(battle.action_economy.ap) + int(battle.action_economy.rap) * 0 # 观测面双录
|
||||
main._flush_row_queue()
|
||||
main.ui.screens.row_pacer.flush()
|
||||
var snap_after: String = JSON.stringify(battle.diagnostic_snapshot())
|
||||
check(snap_before == snap_after, "排空前后核心诊断快照逐位一致(AC⑤ 演出关停零分叉)")
|
||||
# ---- 断言②:零丢行(结构判据:排空后队列恒空 + 末信号行原文在带——append_row 直落不吞行)----
|
||||
check(rows_seen.size() > 0, "结算行已产生(断言前提)")
|
||||
check(main._row_queue.is_empty(), "排空后队列恒空(flush 完整性)")
|
||||
check(main.ui.screens.row_pacer.pending_rows().is_empty(), "排空后队列恒空(flush 完整性)")
|
||||
var last_text := ""
|
||||
for i in range(rows_seen.size() - 1, -1, -1):
|
||||
var cand_row: Dictionary = rows_seen[i]
|
||||
@@ -114,7 +114,7 @@ func _init() -> void:
|
||||
last_text = String(cand_row.get("display_name", ""))
|
||||
if last_text != "":
|
||||
break
|
||||
check(last_text != "" and main.log_view.parsed_text().contains(last_text),
|
||||
check(last_text != "" and main.ui.battle.log_view.parsed_text().contains(last_text),
|
||||
"末信号行原文在明细带(排空不吞行:『%s』)" % last_text)
|
||||
# ---- 断言③:段间延迟取数面双态 ----
|
||||
UiKit.motion_scale = 1.0
|
||||
@@ -129,7 +129,7 @@ func _init() -> void:
|
||||
var fstage := FakeStage.new(null)
|
||||
var ffx := FakeFx.new()
|
||||
rp.stage = fstage
|
||||
rp.board_view = main.board_view
|
||||
rp.board_view = main.ui.battle.board_zone.board_view
|
||||
rp.settlement_fx = ffx
|
||||
rp._play_settlement_mark({"type": "damage", "target": "地精", "target_id": "goblin_b", "value": 3})
|
||||
check(fstage.calls.size() == 1 and fstage.calls[0] == ["goblin_b", "地精"],
|
||||
@@ -144,9 +144,9 @@ func _init() -> void:
|
||||
UiKit.motion_scale = 1.0 # 常态入队(减动效快路同帧直落不入队——夹具须真落队列)
|
||||
main._on_step_row({"type": "damage", "target": "hero", "value": 1})
|
||||
main._on_step_row({"type": "heal", "target": "hero", "value": 2})
|
||||
check(main._row_queue.size() == 2, "会话更替夹具已入队(前提:%d 行)〔wr4〕" % main._row_queue.size())
|
||||
check(main.ui.screens.row_pacer.pending_rows().size() == 2, "会话更替夹具已入队(前提:%d 行)〔wr4〕" % main.ui.screens.row_pacer.pending_rows().size())
|
||||
main._begin_session(false)
|
||||
check(main._row_queue.is_empty(), "会话更替后待播队列清零(_begin_session 内机制 flush)〔wr4〕")
|
||||
check(main.ui.screens.row_pacer.pending_rows().is_empty(), "会话更替后待播队列清零(_begin_session 内机制 flush)〔wr4〕")
|
||||
_finish()
|
||||
|
||||
|
||||
|
||||
@@ -722,8 +722,8 @@ func _smoke_diag_button_retired() -> void:
|
||||
var main_src: String = FileAccess.get_file_as_string("res://presentation/main.gd")
|
||||
Kit.check("V6 主区无 build_diag_button 调用残留(旧实现同批清理)",
|
||||
not main_src.contains("build_diag_button"))
|
||||
Kit.check("V6 转储入口连接随迁(组合根接浮窗 diagnose_requested 信号)",
|
||||
main_src.contains("debug_overlay.diagnose_requested.connect(_on_diagnose_pressed)"))
|
||||
Kit.check("V6 转储入口连接随迁(组合根经注册面接浮窗 diagnose_requested 信号)〔注册面 V2〕",
|
||||
main_src.contains("ui.modals.debug_overlay.diagnose_requested.connect(_on_diagnose_pressed)"))
|
||||
Kit.check("V6 转储编排承接函数在位(dump_diagnosis 卡死取证保留)",
|
||||
main_src.contains("func _on_diagnose_pressed()"))
|
||||
var asm_src: String = FileAccess.get_file_as_string("res://presentation/ui_assembler.gd")
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
# 双实现对拍配对套件(注册面批同代测速循环批 v1.1——帧相位等价的执行化证据):
|
||||
# 产品码(process_frame 信号步进状态机)vs 夹具(现行 await 形态逐字副本——循环内 await+收尾四语句在 run() 尾)。
|
||||
# 五面对拍(帧索引序列/运行态序列/进度串序列/stats 六键/报告全文)逐位相等=
|
||||
# 每步恰一帧+相位同点+输出逐字的当轮真实执行证据(测量语义逐点等价四判据的落码面)。
|
||||
# 拓扑钉死:测试宿主先连 process_frame 帧计数、后启动两形态——两形态下「计数先于步进体」相对次序同构,序列可比。
|
||||
# n=3 常规案+n=0 退化案(同步收尾零帧——_mean/_p90 空守卫渲染面)。
|
||||
# 运行:godot --headless --path game --script res://tests/unit/presentation/tune_sim_batch_parity_test.gd(expect 0)
|
||||
extends SceneTree
|
||||
|
||||
const Kit := preload("res://tests/unit/test_kit.gd")
|
||||
const TuneSimScript := preload("res://presentation/tune_sim_controller.gd")
|
||||
|
||||
|
||||
# 假调试面板(wd8 同形 setter 面+进度/运行态/报告三记录数组供序列对拍)
|
||||
class FakePanel extends Node:
|
||||
var running := false
|
||||
var progress := ""
|
||||
var report := ""
|
||||
var atk := 0
|
||||
var hp := 0
|
||||
var progress_log: Array[String] = []
|
||||
var report_log: Array[String] = []
|
||||
var running_log: Array[bool] = []
|
||||
|
||||
func atk_pct() -> int:
|
||||
return atk
|
||||
|
||||
func hp_pct() -> int:
|
||||
return hp
|
||||
|
||||
func set_sim_running(r: bool) -> void:
|
||||
running = r
|
||||
running_log.append(r)
|
||||
|
||||
func set_sim_progress(t: String) -> void:
|
||||
progress = t
|
||||
progress_log.append(t)
|
||||
|
||||
func set_sim_report(t: String) -> void:
|
||||
report = t
|
||||
report_log.append(t)
|
||||
|
||||
|
||||
# 假自动策略(固定返回值两档 victory/defeat 交替——wd8 :53-55 同款取值,零随机;
|
||||
# 逐局记录帧索引与运行态读数——帧索引经宿主注入的计数 Callable 读取)
|
||||
class FakeDriver extends RefCounted:
|
||||
var ctrl
|
||||
var frame_counter: Callable # 宿主帧计数(0 参 Callable——拓扑钉死:计数先于步进体)
|
||||
var seen: Array[bool] = []
|
||||
var frames: Array[int] = []
|
||||
var calls := 0
|
||||
|
||||
func run_level(_db: Variant, _seed: int, _level: int, _atk: int, _hp: int, _other: bool, _logger: Variant) -> Dictionary:
|
||||
calls += 1
|
||||
seen.append(ctrl.is_running())
|
||||
frames.append(int(frame_counter.call()))
|
||||
if calls % 2 == 1:
|
||||
return {"hero_dmg": 12.0, "rounds": 4, "result": "victory", "hp_left": 3.0, "hp_at_boss": 5.0}
|
||||
return {"hero_dmg": 6.0, "rounds": 2, "result": "defeat",
|
||||
"defeat_node_index": 3, "defeat_node_type": "battle", "hp_left": 0.0, "hp_at_boss": 0.0}
|
||||
|
||||
|
||||
# 假配置库(TuneSimController 只读 db.run.action_economy.ap_per_round——wd8 同款最小域对象图)
|
||||
class FakeActionEconomy:
|
||||
var ap_per_round := 2
|
||||
|
||||
|
||||
class FakeRun:
|
||||
var action_economy := FakeActionEconomy.new()
|
||||
|
||||
|
||||
class FakeDB:
|
||||
var run := FakeRun.new()
|
||||
|
||||
|
||||
# 现行 await 形态逐字副本(tune_sim_controller.gd run(n) 改形前形态——循环内 await+收尾四语句在 run() 尾;
|
||||
# 全仓唯一在测夹具——产品码禁留第二实现,tests/ 不在 S13/S8/S9/S10 扫描面、夹具内循环 await 合法在册)。
|
||||
# 与产品码新形态的申报差异仅一处:批末把 stats 六键束存 out_stats 成员供对拍读取
|
||||
# (产品码 stats 经报告公开面观测——内部字段直查受控例外申报在卡 §17)。
|
||||
class AwaitLoopRunner extends RefCounted:
|
||||
const SEED_BASE := 4200
|
||||
const ParityLogger := preload("res://presentation/playtest_logger.gd")
|
||||
var db
|
||||
var panel
|
||||
var _base_hp := 0
|
||||
var _driver = null
|
||||
var _running := false
|
||||
var out_stats: Dictionary = {}
|
||||
|
||||
func _init(db_ref, panel_ref, base_hp: int, driver = null) -> void:
|
||||
db = db_ref
|
||||
panel = panel_ref
|
||||
_base_hp = base_hp
|
||||
_driver = driver
|
||||
|
||||
func is_running() -> bool:
|
||||
return _running
|
||||
|
||||
func run(n: int) -> void:
|
||||
_running = true
|
||||
var logger = ParityLogger.new()
|
||||
logger.ap_per_round = int(db.run.action_economy.ap_per_round)
|
||||
logger.start_session(SEED_BASE)
|
||||
panel.set_sim_running(true)
|
||||
panel.set_sim_progress("模拟中:0/%d …" % n)
|
||||
var stats := {"wins": 0, "dmg_all": [], "hp_left_win": [], "hp_boss": [], "rounds_all": [], "death_nodes": {}}
|
||||
var autoplay = _driver
|
||||
for i in range(n):
|
||||
_run_one_level(autoplay, stats, i, panel, logger)
|
||||
panel.set_sim_progress("模拟中:%d/%d …" % [i + 1, n])
|
||||
await panel.get_tree().process_frame
|
||||
logger.close_session()
|
||||
panel.set_sim_report(HudPresenter.tune_sim_report(n, panel.atk_pct(), panel.hp_pct(), stats, _base_hp))
|
||||
panel.set_sim_running(false)
|
||||
_running = false
|
||||
out_stats = stats
|
||||
|
||||
func _run_one_level(autoplay, stats: Dictionary, i: int, panel, logger) -> void: # type-hint: ignore(驱动/面板/日志件鸭子型注入——同产品码构造缝)
|
||||
var st = autoplay.run_level(db, SEED_BASE + i, 1, panel.atk_pct(), panel.hp_pct(), false, logger)
|
||||
(stats["dmg_all"] as Array).append(st.hero_dmg)
|
||||
(stats["rounds_all"] as Array).append(st.rounds)
|
||||
if st.result == "victory":
|
||||
stats["wins"] = int(stats["wins"]) + 1
|
||||
(stats["hp_left_win"] as Array).append(st.hp_left)
|
||||
(stats["hp_boss"] as Array).append(st.hp_at_boss)
|
||||
elif st.result == "defeat":
|
||||
var key := "%d.%s" % [st.defeat_node_index, st.defeat_node_type]
|
||||
stats["death_nodes"][key] = int(stats["death_nodes"].get(key, 0)) + 1
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
var frame_idx := {"i": -1} # 帧计数(宿主最先连——拓扑钉死「计数先于步进体」)
|
||||
process_frame.connect(func() -> void: frame_idx["i"] += 1)
|
||||
await process_frame # 树初始化就绪帧(SceneTree._init 时点 root 未挂树——面板 get_tree() 首帧后才有效;wd8 同款先例:panel 段前有 await 过帧)
|
||||
var r3 := await _run_pair(3, frame_idx)
|
||||
_check_n3(r3)
|
||||
var r0 := await _run_pair(0, frame_idx)
|
||||
_check_n0(r0)
|
||||
quit(Kit.finish("tune_sim_batch_parity"))
|
||||
|
||||
|
||||
# 对拍驱动:两形态各经 run(n) 真实入口(同参假面板/假驱动各自实例);先启动两形态、后分别 await——
|
||||
# 两形态在调用点同一同步段内先后挂起(并行于同一帧时钟),驱动协程返回 int 承接 void run 的可等待面
|
||||
func _run_pair(n: int, frame_idx: Dictionary) -> Dictionary:
|
||||
# 两形态串行驱动(各经 run(n) 真实入口 await 调用——GDScript 协程动态调用必须 await,
|
||||
# 无并行启动形态):帧序列各自记录、断言按相对首帧偏移归一后逐位比对——拓扑不变式
|
||||
# 「计数先连、启动后于计数、计数先于步进体」在两形态下相对次序同构,等价证据结构不变。
|
||||
var panel_a := FakePanel.new()
|
||||
root.add_child(panel_a)
|
||||
var drv_a := FakeDriver.new()
|
||||
var a: Variant = TuneSimScript.new(FakeDB.new(), panel_a, 100, drv_a)
|
||||
drv_a.ctrl = a
|
||||
drv_a.frame_counter = func() -> int: return frame_idx["i"]
|
||||
await a.run(n)
|
||||
var panel_b := FakePanel.new()
|
||||
root.add_child(panel_b)
|
||||
var drv_b := FakeDriver.new()
|
||||
var b: Variant = AwaitLoopRunner.new(FakeDB.new(), panel_b, 100, drv_b)
|
||||
drv_b.ctrl = b
|
||||
drv_b.frame_counter = func() -> int: return frame_idx["i"]
|
||||
await b.run(n)
|
||||
return {"a": a, "b": b, "pa": panel_a, "pb": panel_b, "da": drv_a, "db": drv_b}
|
||||
|
||||
|
||||
func _check_n3(r: Dictionary) -> void:
|
||||
var pa: FakePanel = r["pa"]
|
||||
var pb: FakePanel = r["pb"]
|
||||
var da: FakeDriver = r["da"]
|
||||
var drv_b: FakeDriver = r["db"]
|
||||
var a = r["a"]
|
||||
var b = r["b"]
|
||||
Kit.check("n=3 帧序列相对偏移逐位相等(%s vs %s)+相邻差恒一(每步恰一帧)〔测速 V1〕",
|
||||
str(_norm(da.frames)) == str(_norm(drv_b.frames)) and da.frames.size() == 3
|
||||
and _consecutive(da.frames) and _consecutive(drv_b.frames))
|
||||
Kit.check("n=3 运行态读数序列逐位相等+收尾双运行态假+面板置假序列同构〔测速 V2〕",
|
||||
str(da.seen) == str(drv_b.seen) and da.seen == [true, true, true]
|
||||
and not a.is_running() and not b.is_running()
|
||||
and not pa.running and not pb.running
|
||||
and str(pa.running_log) == str(pb.running_log))
|
||||
Kit.check("n=3 进度串序列逐位相等(%d 段)〔测速 V3〕",
|
||||
str(pa.progress_log) == str(pb.progress_log) and pa.progress_log.size() == 4)
|
||||
var got: Dictionary = r["b"].out_stats
|
||||
var deaths: Dictionary = got.get("death_nodes", {})
|
||||
Kit.check("n=3 夹具 stats 六键与按假驱动固定返回值重算的期望逐键相等〔测速 V4〕",
|
||||
int(got.get("wins", -1)) == 2
|
||||
and _arr_eq(got.get("dmg_all"), [12.0, 6.0, 12.0])
|
||||
and _arr_eq(got.get("hp_left_win"), [3.0, 3.0])
|
||||
and _arr_eq(got.get("hp_boss"), [5.0, 5.0])
|
||||
and _arr_eq(got.get("rounds_all"), [4, 2, 4])
|
||||
and deaths.size() == 1 and int(deaths.get("3.battle", -1)) == 1)
|
||||
Kit.check("n=3 报告全文逐字相等且含渲染面四关键串〔测速 V5〕",
|
||||
pa.report == pb.report and pa.report != ""
|
||||
and pa.report.contains("关卡完成率") and pa.report.contains("关卡完成剩余 HP 均")
|
||||
and pa.report.contains("败北节点") and pa.report.contains("N=3"))
|
||||
|
||||
|
||||
func _check_n0(r: Dictionary) -> void:
|
||||
var pa: FakePanel = r["pa"]
|
||||
var pb: FakePanel = r["pb"]
|
||||
var da: FakeDriver = r["da"]
|
||||
var drv_b: FakeDriver = r["db"]
|
||||
var a = r["a"]
|
||||
var b = r["b"]
|
||||
Kit.check("n=0 退化:两形态驱动零回调、帧索引序列空(同步收尾零帧)〔测速 V1〕",
|
||||
da.frames.is_empty() and drv_b.frames.is_empty() and da.calls == 0 and drv_b.calls == 0)
|
||||
Kit.check("n=0 运行态置假序列 [true, false] 双形态同构+收尾双假〔测速 V2〕",
|
||||
str(pa.running_log) == str(pb.running_log) and pa.running_log == [true, false]
|
||||
and not a.is_running() and not b.is_running())
|
||||
Kit.check("n=0 进度串单段逐字相等〔测速 V3〕",
|
||||
str(pa.progress_log) == str(pb.progress_log) and pa.progress_log == ["模拟中:0/0 …"])
|
||||
Kit.check("n=0 夹具 stats 六键零计〔测速 V4〕",
|
||||
int((r["b"].out_stats as Dictionary).get("wins", -1)) == 0
|
||||
and ((r["b"].out_stats as Dictionary).get("dmg_all") as Array).is_empty()
|
||||
and ((r["b"].out_stats as Dictionary).get("hp_left_win") as Array).is_empty()
|
||||
and ((r["b"].out_stats as Dictionary).get("hp_boss") as Array).is_empty()
|
||||
and ((r["b"].out_stats as Dictionary).get("rounds_all") as Array).is_empty()
|
||||
and ((r["b"].out_stats as Dictionary).get("death_nodes") as Dictionary).is_empty())
|
||||
Kit.check("n=0 报告全文逐字相等且非空(空 stats 渲染面)〔测速 V5〕",
|
||||
pa.report == pb.report and pa.report != "")
|
||||
|
||||
# 帧序列归一(相对首帧偏移——串行驱动下两形态帧段错位、结构可比性归一承载)
|
||||
func _norm(fr: Array) -> Array:
|
||||
var out: Array = []
|
||||
var base := int(fr[0]) if fr.size() > 0 else 0
|
||||
for i in fr.size():
|
||||
out.append(int(fr[i]) - base)
|
||||
return out
|
||||
|
||||
|
||||
# 相邻差恒一(每步恰占一帧——判据①的序列面证据)
|
||||
func _consecutive(fr: Array) -> bool:
|
||||
for i in range(1, fr.size()):
|
||||
if int(fr[i]) - int(fr[i - 1]) != 1:
|
||||
return false
|
||||
return true
|
||||
|
||||
# 数组逐元素浮点比对(stats 桶重算期望——类型化比较,消 str 形态歧义)
|
||||
func _arr_eq(a, b: Array) -> bool:
|
||||
var aa := a as Array
|
||||
if aa == null or aa.size() != b.size():
|
||||
return false
|
||||
for i in b.size():
|
||||
if absf(float(aa[i]) - float(b[i])) > 0.0001:
|
||||
return false
|
||||
return true
|
||||
Reference in new issue
Block a user