iter8-T9 卡⑨ 断言批:pacing_queue_test 第 15 套入 run_all manifest(卡⑦ AC⑤ 逻辑先行断言=排空前后核心诊断快照逐位一致〔演出关停零分叉〕+零丢行结构判据〔排空后队列恒空+末信号行原文在带〕+段间延迟双态〔pace_delay_for 常态>0/减动效=0〕);断点往返/续玩幂等/退出清断点=T1 第 14 套、无断点按钮隐藏/结算框呈现=T2/T5 click 探针段、节奏双态=T6 fx_probe ④b——卡⑨清单六件全数在册;坑册:多行 lambda sort 静默失效(普通循环替代)+轮末才 emit step_row 行族(stop 触发)——闸:run_all 15 套全绿

This commit is contained in:
DKChan committed 2026-09-29 20:40:02 +08:00
1 parent 79427d0f98
commit 4918bf3c67
3 files changed
+247

No files matched your search

@@ -0,0 +1,122 @@
# VERTICAL SLICE - NOT FOR PRODUCTION
# 轮间结算节奏化断言批(迭代 8 卡⑦ AC⑤/T9——逻辑先行断言面):
# ① 演出队列排空前后核心逻辑状态逐位一致(演出关停不产生分叉——排播队列零核心触碰的结构证明);
# ② 零丢行:step_row 信号总数 = 明细带实收行数(排播/排空两路都不吞行);
# ③ 段间延迟取数面双态(常态 >0 / 减动效 =0 归零不跳过——pace_delay_for 直读)。
# 载体:main.tscn 实例(同 ui_click_probe 形态——组合根全链在座),真实冒险驱动至首战结算后断言。
# 运行:godot --headless --path game --script res://tests/unit/presentation/pacing_queue_test.gd(expect 0)
extends SceneTree
var _problems: Array = []
func check(cond: bool, msg: String) -> void:
if not cond:
_problems.append(msg)
func _init() -> void:
var main: Node = load("res://presentation/main.tscn").instantiate()
root.add_child(main)
await process_frame
var iso = load("res://core/save_manager.gd").new("user://test_pacing_queue_save.json")
for p in ["user://test_pacing_queue_save.json", "user://test_pacing_queue_save.json.tmp"]:
if FileAccess.file_exists(p):
DirAccess.remove_absolute(p)
iso = load("res://core/save_manager.gd").new("user://test_pacing_queue_save.json")
iso.load_from_disk()
iso.mark_first_adventure_done()
main.save_manager = iso
main.meta = load("res://core/meta_progression.gd").new(main.db, iso)
main._refresh_resume_button()
# 行信号计数器(真实信号路径旁挂计数——零触碰既有接线)
var rows_seen: Array = []
main.adventure = null
main._start_adventure_from_menu()
await process_frame
# 入口停点选路进首战(真实停点链)
if not main.level_screen.visible:
_problems.append("入口停点未呈现(前置失守)")
_finish()
return
main.runner.pick_next_node(int(main.runner.next_candidate_ids()[0]))
await process_frame
# 等首战开打(board_changed 同步链——两帧余量)
await process_frame
var battle = main.runner.current_battle()
if battle == null:
_problems.append("首战未开打(排播断言无载体)")
_finish()
return
battle.step_row.connect(_count_row.bind(rows_seen))
# 理性策略挖到触发结算(≥1 行入队即达断言前提——AP 一次消耗;挖格须邻英雄——邻接优先排序)
var ms = battle.minesweeper
# 理性策略驱动(auto_player 单一实现复用——damage_probe 同款两步消费:先 move 到 hero_cell 再 dig)
var ap = load("res://smoke/auto_player.gd")
var acted := false
for guard in range(40):
var plan: Dictionary = ap.choose_dig(ms)
if plan.is_empty():
break
if plan.hero_cell != ms.hero_pos():
ms.settle_batch([{"action": "move", "cell": plan.hero_cell}])
if battle.action_economy.phase != "Active":
break
var outcomes: Array = ms.settle_batch([{"action": "dig", "cell": plan.dig_cell}])
acted = not outcomes.is_empty() and bool(outcomes[0].accepted)
if acted:
break
# 轮末结算触发(step_row 行族只在轮末 emit——停手即结,damage_probe 同款)
if battle.action_economy.phase == "Active":
ms.settle_batch([{"action": "stop"}])
if not acted:
_problems.append("首战无受理动作(队列断言无行)")
_finish()
return
await process_frame
# ---- 断言①:排空前后核心状态逐位一致(演出关停零分叉)----
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()
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 完整性)")
var last_text := ""
for i in range(rows_seen.size() - 1, -1, -1):
var cand_row: Dictionary = rows_seen[i]
last_text = String(cand_row.get("text", ""))
if last_text == "":
last_text = String(cand_row.get("display_name", ""))
if last_text != "":
break
check(last_text != "" and main.log_view.parsed_text().contains(last_text),
"末信号行原文在明细带(排空不吞行:『%s』)" % last_text)
# ---- 断言③:段间延迟取数面双态 ----
UiKit.motion_scale = 1.0
var d_on: float = main.pace_delay_for("damage", "heal")
var d_same: float = main.pace_delay_for("damage", "damage")
check(d_on > 0.0 and d_on >= d_same, "常态段间延迟 >0 且 ≥ 段内(%f/%f)" % [d_on, d_same])
UiKit.motion_scale = 0.5
check(main.pace_delay_for("damage", "heal") == 0.0, "减动效段间延迟归零(行不跳过——同帧直落快路)")
UiKit.motion_scale = 1.0
_finish()
func _count_row(row: Dictionary, arr: Array) -> void:
arr.append(row.duplicate(true))
func _finish() -> void:
for p in ["user://test_pacing_queue_save.json", "user://test_pacing_queue_save.json.tmp"]:
if FileAccess.file_exists(p):
DirAccess.remove_absolute(p)
if _problems.is_empty():
print("[pacing_queue_test] PASS — 排空零分叉/零丢行/延迟双态 全绿")
quit(0)
else:
for p in _problems:
push_error("[pacing_queue_test] FAIL — " + p)
print("[pacing_queue_test] FAIL — " + p)
quit(1)