feat(迭代28 T3): 生命周期存活性矩阵冒烟段(_smoke_lifecycle_matrix 九阶段×五引用面)
驱动复用:V3 debug seed 开局路径+_auto_play_round 活战斗模式(phase/round 守卫+choose_dig)+ui_struct resume 夹具模式,不另起点击链。阶段=闲时/开局停点(地图选路停点态合并)/战斗中/结算后(胜利停点)/选路页/终局面/回菜单收束/各模态开合后(菜单域四模态)/两连局/续玩断点。五引用面=_treasure_ui 2/_bonus 6/_camp_pres 3/_modal_flow 4/_row_pacer 5 句柄+2 Callable+run_scope 13 成员;判据=恒活面有效+会话三面阶段存活态(alive/null/both)+现值回调与 run_scope 成员同源(回调链无悬挂)+收束/续玩换会话无真空窗+旧局 queue_free 消亡。三处判据订正实录:隔离后刷新续玩钮定基/战斗收场判 is_battle_live(胜利停点 settlement 陈旧有效非 null)/调试浮窗开不隐菜单。冒烟 1097→1125/0
This commit is contained in:
1 parent
a35581e441
commit
77075e6e7a
1 file changed
+277
@@ -88,6 +88,7 @@ func _split_tail() -> void:
|
||||
await _smoke_scene_split() # 协程函数必须 await(无 await=发射后不管,首个 await 后即被 quit 截断)
|
||||
await _smoke_boot_chain()
|
||||
await _smoke_run_lifecycle()
|
||||
await _smoke_lifecycle_matrix()
|
||||
else:
|
||||
check(false, "V2/V3 前置:autoload 链未就绪(ConfigDB/SaveManager)")
|
||||
print("=== 冒烟完成:%d 项检查,%d 项失败 ===" % [_checks, _failures])
|
||||
@@ -302,6 +303,282 @@ func _smoke_boot_chain() -> void:
|
||||
DirAccess.remove_absolute(p)
|
||||
|
||||
|
||||
# ---- 生命周期存活性矩阵(迭代 28 T3——九阶段×五引用面;驱动复用 V3 debug seed 开局路径+
|
||||
# _auto_play_round 活战斗模式+ui_struct resume 夹具模式,不另起点击链)----
|
||||
# 阶段映射说明:计划书十阶段中「开局停点」与「地图选路」在本现行流程同为关卡画面停点态
|
||||
#(level_started→choices_presented 同步到达,无可观测中间态)——合并为 V3b 一处断言。
|
||||
func _smoke_lifecycle_matrix() -> void:
|
||||
print("--- 迭代28 T3 生命周期存活性矩阵(V3:九阶段×五引用面)---")
|
||||
var main: Node = load("res://presentation/main.tscn").instantiate()
|
||||
root.add_child(main)
|
||||
await process_frame
|
||||
await process_frame
|
||||
var sm = main.get_node("/root/SaveManager")
|
||||
for p in ["user://smoke_lifecycle.json", "user://smoke_lifecycle.json.tmp"]:
|
||||
if FileAccess.file_exists(p):
|
||||
DirAccess.remove_absolute(p)
|
||||
sm.attach_path("user://smoke_lifecycle.json")
|
||||
main._refresh_resume_button() # 隔离后重同步(boot 时按钮态反映的是真实档——T2 已断 boot 关系,此处按隔离档定基)
|
||||
main.debug_overlay.seed_edit.text = "42"
|
||||
# 阶段①闲时
|
||||
await _stage_faces(main, "V3a 闲时", "null", "null", "null",
|
||||
{"mf_start": true, "mf_inter": false, "mf_rows_empty": true, "start_vis": true, "resume_vis": false})
|
||||
# 阶段②开局停点(地图选路停点态)
|
||||
main._start_adventure_from_menu()
|
||||
for i in range(6):
|
||||
await process_frame
|
||||
await _stage_faces(main, "V3b 开局停点", "alive", "alive", "null",
|
||||
{"mf_start": false, "level_vis": true})
|
||||
# 阶段③地图选路→战斗中(真实选路提交 press_candidate)
|
||||
main.level_screen.press_candidate(0)
|
||||
for i in range(10):
|
||||
await process_frame
|
||||
await _stage_faces(main, "V3c 战斗中", "alive", "alive", "alive", {"mf_start": false})
|
||||
# 阶段④结算后/终局面(AutoPlayer 活战斗驱动到终局——_auto_play_round 同款 phase/round 守卫)
|
||||
var finished := {"done": false}
|
||||
main.run_scope.adventure.adventure_finished.connect(func(_s: Dictionary) -> void:
|
||||
finished.done = true, CONNECT_ONE_SHOT)
|
||||
var guard := 0
|
||||
while main.run_scope.settlement != null and main.run_scope.settlement.is_battle_live() \
|
||||
and not finished.done and guard < 600:
|
||||
var st = main.run_scope.settlement
|
||||
var ms = st.minesweeper
|
||||
var ae = st.action_economy
|
||||
var start_round: int = st.round_index
|
||||
var round_guard := 400
|
||||
while ae.phase == "Active" and st.round_index == start_round and round_guard > 0 \
|
||||
and main.run_scope.settlement != null:
|
||||
round_guard -= 1
|
||||
var plan: Dictionary = AutoPlayer.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 ae.phase != "Active" or st.round_index != start_round:
|
||||
break
|
||||
ms.settle_batch([{"action": "dig", "cell": plan.dig_cell}])
|
||||
if main.run_scope.settlement == null or not main.run_scope.settlement.is_battle_live():
|
||||
break
|
||||
if ae.phase == "Active" and st.round_index == start_round:
|
||||
ms.settle_batch([{"action": "stop"}])
|
||||
guard += 1
|
||||
check(main.run_scope.settlement == null or not main.run_scope.settlement.is_battle_live(),
|
||||
"V3d 活战斗驱动完毕(战斗已收场——%d 轮,finished=%s)〔装配生命批 V3〕" % [guard, str(finished.done)])
|
||||
main._flush_row_queue()
|
||||
for i in range(12):
|
||||
if main._end_overlay.is_open():
|
||||
break
|
||||
await process_frame
|
||||
# 分支甲:战斗胜利停点(结算后)→确认进选路→放弃→终局面
|
||||
if not finished.done:
|
||||
await _stage_faces(main, "V3d 结算后(胜利停点)", "alive", "alive", "both", {"mf_start": false, "end_open": true})
|
||||
main._on_battle_end_continue()
|
||||
for i in range(8):
|
||||
await process_frame
|
||||
await _stage_faces(main, "V3d2 选路页(停点后)", "alive", "alive", "both", {"mf_start": false, "level_vis": true})
|
||||
check(main.run_scope.adventure.abandon(), "V3d2 放弃受理(选路停点天然非战斗期)〔装配生命批 V3〕")
|
||||
for i in range(12):
|
||||
if finished.done:
|
||||
break
|
||||
await process_frame
|
||||
main._flush_row_queue()
|
||||
for i in range(8):
|
||||
if main._end_overlay.is_open():
|
||||
break
|
||||
await process_frame
|
||||
# 阶段⑤终局面(败北/放弃皆达)
|
||||
check(finished.done and main._end_overlay.is_open(),
|
||||
"V3e 终局面到达(adventure_finished+终局面板开)〔装配生命批 V3〕")
|
||||
await _stage_faces(main, "V3e 终局面", "alive", "alive", "null", {"mf_start": false, "end_open": true})
|
||||
# 阶段⑥回菜单收束(旧局消亡+新会话即驻+无真空窗)
|
||||
var old_scope = main.run_scope
|
||||
main._on_battle_end_continue()
|
||||
check(main.run_scope != old_scope and main.run_scope.is_inside_tree(),
|
||||
"V3f 收束换会话无真空窗(新闲时会话即刻在树)〔装配生命批 V3〕")
|
||||
await process_frame
|
||||
check(not is_instance_valid(old_scope),
|
||||
"V3f 收束后旧局经 queue_free 消亡(is_instance_valid=false)〔装配生命批 V3〕")
|
||||
await _stage_faces(main, "V3f 回菜单收束", "null", "null", "null",
|
||||
{"mf_start": true, "start_vis": true, "end_open": false})
|
||||
# 阶段⑦各模态开合后(菜单域四模态真实路径开合——会话面零扰动)
|
||||
for modal_name in ["settings", "career", "camp", "debug"]:
|
||||
if modal_name == "settings":
|
||||
main._open_settings()
|
||||
elif modal_name == "career":
|
||||
main._open_career()
|
||||
elif modal_name == "camp":
|
||||
var campbtn: Button = main._menu_screen.campbtn
|
||||
if campbtn == null:
|
||||
check(false, "V3g 营地入口缺失(驱动中断)〔装配生命批 V3〕")
|
||||
continue
|
||||
campbtn.pressed.emit()
|
||||
else:
|
||||
main._show_debug_overlay()
|
||||
for i in range(4):
|
||||
await process_frame
|
||||
var open_extra: Dictionary = {"mf_start": false}
|
||||
if modal_name == "debug":
|
||||
open_extra = {"mf_start": true} # 调试浮窗=浮于菜单之上的 Dialog(不隐藏开始层——迭代 18 卡⑦形态)
|
||||
await _stage_faces(main, "V3g 模态开·%s" % modal_name, "null", "null", "null", open_extra)
|
||||
if modal_name == "settings":
|
||||
main._on_settings_closed()
|
||||
elif modal_name == "career":
|
||||
main._on_career_closed()
|
||||
elif modal_name == "camp":
|
||||
main._on_camp_closed()
|
||||
else:
|
||||
main._close_debug_overlay()
|
||||
for i in range(4):
|
||||
await process_frame
|
||||
await _stage_faces(main, "V3g 模态合·%s" % modal_name, "null", "null", "null",
|
||||
{"mf_start": true, "start_vis": true})
|
||||
# 阶段⑧两连局(第二局全新会话——旧局对象不复用、脏态零残留)
|
||||
var first_adv = main.run_scope.adventure
|
||||
main.debug_overlay.seed_edit.text = "43"
|
||||
main._start_adventure_from_menu()
|
||||
for i in range(6):
|
||||
await process_frame
|
||||
check(main.run_scope.adventure != null and main.run_scope.adventure != first_adv,
|
||||
"V3h 两连局第二会话全新对象(与第一局不复用)〔装配生命批 V3〕")
|
||||
await _stage_faces(main, "V3h 两连局·开局停点", "alive", "alive", "null", {"mf_start": false, "level_vis": true})
|
||||
check(main.run_scope.adventure.abandon(), "V3h 放弃受理(第二局)〔装配生命批 V3〕")
|
||||
for i in range(12):
|
||||
if main._end_overlay.is_open():
|
||||
break
|
||||
await process_frame
|
||||
main._flush_row_queue()
|
||||
for i in range(8):
|
||||
if main._end_overlay.is_open():
|
||||
break
|
||||
await process_frame
|
||||
await _stage_faces(main, "V3h 两连局·终局面", "alive", "alive", "null", {"mf_start": false, "end_open": true})
|
||||
main._on_battle_end_continue()
|
||||
await process_frame
|
||||
await _stage_faces(main, "V3h 两连局·收束", "null", "null", "null", {"mf_start": true, "start_vis": true})
|
||||
# 阶段⑨续玩断点(fresh 隔离档+最小合法断点夹具——ui_struct resume 夹具同构;换会话无真空窗)
|
||||
for p in ["user://smoke_lifecycle_resume.json", "user://smoke_lifecycle_resume.json.tmp"]:
|
||||
if FileAccess.file_exists(p):
|
||||
DirAccess.remove_absolute(p)
|
||||
sm.attach_path("user://smoke_lifecycle_resume.json")
|
||||
sm.set_breakpoint({
|
||||
"master_seed": 42, "rng_counter": 0, "hero_hp": 20, "gp": 24, "keystone": 0,
|
||||
"level_index": 1, "bonus": {}, "shop_purchased": {}, "relics": [],
|
||||
"keystone_drops": [], "bonus_taken": {},
|
||||
"bonus_pending": {"candidates": [{"kind": "heal", "tier": "common"}], "payload": {"gp_dropped": 0}},
|
||||
})
|
||||
main._refresh_resume_button()
|
||||
check(main._resume_btn.visible, "V3i 续玩钮·断点在档可见〔装配生命批 V3〕")
|
||||
var idle_scope = main.run_scope
|
||||
main._resume_from_menu()
|
||||
check(main.run_scope != idle_scope and main.run_scope.is_inside_tree(),
|
||||
"V3i 续玩换会话无真空窗(新冒险会话即刻在树)〔装配生命批 V3〕")
|
||||
for i in range(10):
|
||||
await process_frame
|
||||
await _stage_faces(main, "V3i 续玩断点", "alive", "both", "both", {"mf_start": false})
|
||||
main.queue_free()
|
||||
await process_frame
|
||||
for p in ["user://smoke_lifecycle.json", "user://smoke_lifecycle.json.tmp",
|
||||
"user://smoke_lifecycle_resume.json", "user://smoke_lifecycle_resume.json.tmp"]:
|
||||
if FileAccess.file_exists(p):
|
||||
DirAccess.remove_absolute(p)
|
||||
|
||||
|
||||
# 引用面快照(五承载物的会话相关面+恒活面——逐个调用现值回调,与 run_scope 成员并采)
|
||||
func _face_snapshot(main: Node) -> Dictionary:
|
||||
var tu = main._treasure_ui
|
||||
var bo = main._bonus
|
||||
var cp = main._camp_pres
|
||||
var mf = main._modal_flow
|
||||
var rp = main._row_pacer
|
||||
return {
|
||||
"treasure_settlement": tu._settlement_get.call(),
|
||||
"treasure_runner": tu._runner_get.call(),
|
||||
"bonus_meta": bo._meta_get.call(),
|
||||
"bonus_save": bo._save_get.call(),
|
||||
"bonus_adventure": bo._adventure_get.call(),
|
||||
"bonus_runner": bo._runner_get.call(),
|
||||
"bonus_mode": bo._adventure_mode_get.call(),
|
||||
"camp_meta": cp._meta_get.call(),
|
||||
"camp_save": cp._save_get.call(),
|
||||
"camp_runner": cp._runner_get.call(),
|
||||
"mf_start": mf.start_visible_get.call(),
|
||||
"mf_inter": mf.inter_visible_get.call(),
|
||||
"mf_settlement": mf.settlement_get.call(),
|
||||
"mf_rows_empty": mf.rows_empty_get.call(),
|
||||
"rp_log": rp.log_view, "rp_stage": rp.stage, "rp_board_zone": rp.board_zone,
|
||||
"rp_fx": rp.settlement_fx, "rp_board": rp.board_view,
|
||||
"rp_refresh": rp.refresh_request, "rp_pump": rp.pump_ended,
|
||||
"rs_adventure": main.run_scope.adventure, "rs_runner": main.run_scope.runner,
|
||||
"rs_settlement": main.run_scope.settlement, "rs_is_adventure": main.run_scope.is_adventure,
|
||||
}
|
||||
|
||||
|
||||
# 阶段面断言:adv/run/set=alive|null|both(会话三面存活态);恒活面每阶段必查;
|
||||
# 现值回调与 run_scope 成员必须同源(回调链无悬挂铁证);extra=阶段附加判据。
|
||||
func _stage_faces(main: Node, tag: String, adv: String, run: String, set: String, extra: Dictionary = {}) -> void:
|
||||
var snap := _face_snapshot(main)
|
||||
var bad := ""
|
||||
for k in ["bonus_meta", "bonus_save", "camp_meta", "camp_save", "rp_log", "rp_stage", "rp_board_zone", "rp_fx", "rp_board"]:
|
||||
if not is_instance_valid(snap[k]):
|
||||
bad += " %s=失效" % k
|
||||
for k in ["rp_refresh", "rp_pump"]:
|
||||
var cb: Variant = snap[k]
|
||||
if not (cb is Callable) or not (cb as Callable).is_valid():
|
||||
bad += " %s=非有效Callable" % k
|
||||
if not main.run_scope.is_inside_tree():
|
||||
bad += " run_scope=不在树"
|
||||
bad += _face_liveness("adventure", snap["rs_adventure"], adv)
|
||||
bad += _face_liveness("runner", snap["rs_runner"], run)
|
||||
bad += _face_liveness("settlement", snap["rs_settlement"], set)
|
||||
if snap["treasure_settlement"] != snap["rs_settlement"]:
|
||||
bad += " treasure._settlement_get≠run_scope.settlement(回调悬挂)"
|
||||
if snap["treasure_runner"] != snap["rs_runner"]:
|
||||
bad += " treasure._runner_get≠run_scope.runner(回调悬挂)"
|
||||
if snap["bonus_adventure"] != snap["rs_adventure"]:
|
||||
bad += " bonus._adventure_get≠run_scope.adventure(回调悬挂)"
|
||||
if snap["bonus_runner"] != snap["rs_runner"]:
|
||||
bad += " bonus._runner_get≠run_scope.runner(回调悬挂)"
|
||||
if snap["camp_runner"] != snap["rs_runner"]:
|
||||
bad += " camp._runner_get≠run_scope.runner(回调悬挂)"
|
||||
if snap["mf_settlement"] != snap["rs_settlement"]:
|
||||
bad += " modal_flow.settlement_get≠run_scope.settlement(回调悬挂)"
|
||||
if bool(snap["bonus_mode"]) != bool(snap["rs_is_adventure"]):
|
||||
bad += " bonus._adventure_mode_get≠run_scope.is_adventure(回调悬挂)"
|
||||
for k in extra:
|
||||
match String(k):
|
||||
"mf_start":
|
||||
if bool(snap["mf_start"]) != bool(extra[k]):
|
||||
bad += " modal_flow.start_visible_get 应=%s" % str(extra[k])
|
||||
"mf_inter":
|
||||
if bool(snap["mf_inter"]) != bool(extra[k]):
|
||||
bad += " modal_flow.inter_visible_get 应=%s" % str(extra[k])
|
||||
"mf_rows_empty":
|
||||
if bool(snap["mf_rows_empty"]) != bool(extra[k]):
|
||||
bad += " modal_flow.rows_empty_get 应=%s" % str(extra[k])
|
||||
"level_vis":
|
||||
if main.level_screen.visible != bool(extra[k]):
|
||||
bad += " level_screen.visible 应=%s" % str(extra[k])
|
||||
"end_open":
|
||||
if main._end_overlay.is_open() != bool(extra[k]):
|
||||
bad += " _end_overlay.is_open 应=%s" % str(extra[k])
|
||||
"resume_vis":
|
||||
if main._resume_btn.visible != bool(extra[k]):
|
||||
bad += " _resume_btn.visible 应=%s" % str(extra[k])
|
||||
"start_vis":
|
||||
if main._start_overlay.visible != bool(extra[k]):
|
||||
bad += " _start_overlay.visible 应=%s" % str(extra[k])
|
||||
check(bad.is_empty(), "%s(引用面违例:%s)〔装配生命批 V3〕" % [tag, bad])
|
||||
|
||||
|
||||
# 单面存活态判据:want=null=必须缺位/alive=必须有效非空/both=两可但非空必有效
|
||||
func _face_liveness(name: String, v, want: String) -> String:
|
||||
if want == "null":
|
||||
return "" if v == null else " %s 应缺位(实测非空)" % name
|
||||
if v != null and not is_instance_valid(v):
|
||||
return " %s 悬挂(失效对象)" % name
|
||||
return ""
|
||||
|
||||
|
||||
# ---- 会话物化断言(迭代 27 T1 V3——两连局复位/无脏状态残留;原手工复位清单消亡的行为级证明)----
|
||||
func _smoke_run_lifecycle() -> void:
|
||||
print("--- 迭代27 T1 会话物化(V3:两连局复位+脏状态不残留)---")
|
||||
|
||||
Reference in new issue
Block a user