Files
gplan-2/game/tests/unit/presentation/pacing_queue_test.gd
T
DKChan 3661689a56 iter33 卡② T1+T2: 主菜单三入口化(6 钮→3 钮)+F10 位点全量迁移
钮集:build_start_overlay 六钮两组帽→三钮(进入存档 primary/设置/退出——裁决 1;组帽双撤追认单留痕);
menu_screen 句柄面撤 4 留 3 增 entersavebtn;main.gd 接线撤四线接 _open_save_select(mn3 点击链闭环)。
_refresh_resume_button 净删除+消费点全清(main.gd 五处+camp ctx 载体+presenter 三处+probe_ui_util
参数+pacing_queue+V2d 族);断点确认链主菜单侧摘除(mn6——分派器 new_adventure 臂随迁退役,
机制保留卡④第三消费方)。_start_adventure_from_menu/_resume_from_menu/_open_career 保留为探针
直调中间态(mn10——卡④ 大门/笔记本换代点,已知中间态登记)。
F10 迁移:smoke V2 三钮/V2d 族废止三钮承接/V3g camp 经进档开层(槽模板隔离+恢复 attach 写盘目标)/
V3i 钮断言摘除+_stage_faces.resume_vis 键退役;ui_probe/ui_struct/screen_flow/shot/fx/t17 营地与
冒险驱动换代(open_camp_via_savesel 助手——槽模板清场防真实槽污染);ui_click 营地购买链换代
(swap=true 整链落槽文件+持久化核验同源)+断点段②③按 §7.2 申报删除移交卡④+生涯直调;
pacing_queue 钮依赖摘除;isolate_save 签名换代(refresh_resume→swap_save,全调用点核对)。
golden 基线随 T2 重制(mn12——三钮形态)。验证:冒烟 1212/0、单元 40 套件全绿、
struct/click/flow/ui_probe/tune/layout/input/t17 全绿。
2026-10-22 22:32:02 +08:00

120 lines
5.3 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# VERTICAL SLICE - NOT FOR PRODUCTION
# 轮间结算节奏化断言(逻辑先行断言面):
# ① 演出队列排空前后核心逻辑状态逐位一致(演出关停不产生分叉——排播队列零核心触碰的结构证明);
# ② 零丢行: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
# 隔离换注(SaveManager 是 autoload——原地 attach:先删后建防陈旧档泄入,
# 读盘在 attach 内完成;meta 经 save 引用透传零重建)
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)
var sm = main.get_node("/root/SaveManager")
sm.attach_path("user://test_pacing_queue_save.json")
sm.mark_first_adventure_done()
# 行信号计数器(真实信号路径旁挂计数——零触碰既有接线)
var rows_seen: Array = []
main._start_adventure_from_menu()
await process_frame
# 入口停点选路进首战(真实停点链)
if not main.level_screen.visible:
_problems.append("入口停点未呈现(前置失守)")
_finish()
return
main.run_scope.runner.pick_next_node(int(main.run_scope.runner.next_candidate_ids()[0]))
await process_frame
# 等首战开打(board_changed 同步链——两帧余量)
await process_frame
var battle = main.run_scope.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)