feat(迭代27 T1): 波一 场景拆分+局生命周期物化——逐位一致对拍全同
P0-1 三屏场景化(契约2 只拆不重写): - menu_screen/camp_screen/adventure_screen 三 .tscn 屏壳+壳脚本(视图代码原样构建, 40+ views 零 .tscn 化);adventure 屏壳内含 level_map/battle 两容器 - main.gd _build_ui 解体=三屏实例化+模态装配+接线;组合根句柄全部保留(探针挂载 假设 main.X 一级路径零迁移);树序=屏壳在前全模态之下(视觉共存对逐对验算) - 屏壳根 mouse_filter=IGNORE(ui_input 实证:全屏 STOP 壳吃下层模态鼠标输入) P0-2 会话物化(裁点①甲 autoload+手工清单消亡): - run_scope.gd 节点持全部局态(adventure/runner/settlement/7+ 局态旗/账本); _begin_session 建会话、终局整节点 queue_free()——_reset_run_scope 三项+ _on_battle_end_continue 置空四件手工清单删除 - SaveManager/MetaProgression 升格 autoload 3/4 号位(类名加 Core 后缀沿 RngService 先例;--script 模式编译器不解析裸 autoload 标识符——组合根留读视图属性 get_node 形态) - 探针隔离改 attach_path 原地重置(set_master_seed 先例);10 探针+pacing 测试迁移 验收(V1 核心=逐位一致对拍): - parity_anchor 三变体 base/attune/shop×40 局 RUNSUM+JSONL 双面逐位全同(零行为漂移) - 冒烟 1055→1077/0(+V2 屏壳独立实例化 11 断言+V3 两连局复位 11 断言) - run_all 25 套+probe click 全绿+render_gate golden 0.00%×3
This commit is contained in:
1 parent
fc7fee03bd
commit
7a0d57ddb0
30 files changed
+683
-364
No files matched your search
@@ -14,18 +14,29 @@
|
||||
# 参数面显式传参)。
|
||||
# 纪律:数值零字面量——价格/档位/阈值/两档金额只读 db.meta_* 访问器;存档只经 SaveManager 访问器,
|
||||
# 禁读内部结构;行集解析禁自扫表(一律走 ConfigDB 访问器)。
|
||||
class_name MetaProgression
|
||||
extends RefCounted
|
||||
class_name MetaProgressionCore
|
||||
extends Node
|
||||
|
||||
var db: ConfigDb
|
||||
var save: SaveManager
|
||||
var save: SaveManagerCore
|
||||
|
||||
|
||||
func _init(db_ref: ConfigDb, save_ref: SaveManager) -> void:
|
||||
# 迭代 27 T1 裁点①——MetaProgression 升格 autoload(RngService 先例:类名加 Core 后缀、
|
||||
# autoload 占净名)。_init 参数可缺省兼容双形态:autoload 实例化走 .new() 零参 + _ready 从
|
||||
# autoload 链取依赖(工程序 ConfigDB→RngService→SaveManager→MetaProgression,_ready 时序
|
||||
# 已就绪);单测/工具直呼 .new(db, save) 形态不变(不入树零 _ready)。
|
||||
func _init(db_ref: ConfigDb = null, save_ref: SaveManagerCore = null) -> void:
|
||||
db = db_ref
|
||||
save = save_ref
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if db == null:
|
||||
db = get_node("/root/ConfigDB")
|
||||
if save == null:
|
||||
save = get_node("/root/SaveManager")
|
||||
|
||||
|
||||
func keystone_balance() -> int:
|
||||
return save.keystone_balance()
|
||||
|
||||
|
||||
@@ -47,8 +47,8 @@
|
||||
#
|
||||
# JSON 键纪律:字典键一律 String——JSON 键非字符串的坑,int 键(关卡序号)读写两侧都要 str() 换算,
|
||||
# 混用必出静默 miss。
|
||||
class_name SaveManager
|
||||
extends RefCounted
|
||||
class_name SaveManagerCore
|
||||
extends Node
|
||||
|
||||
const SAVE_FORMAT_VERSION := 7 # registry schema_version:v7=锻造工坊域(迭代 23 T5——meta 新域 forge_routes〔六路线等级 levels+直购解锁清单 unlocked+当前武器/甲选中标记 weapon/armor;无特效域——裁 6 满级特效本期不做〕,旧档缺键由 _normalize 按默认补齐=初始持有态〔sword/light 在档余四锁、选中=初始路线〕,v6→v7 空迁移=缺键补齐模式——v3→v4 先例);v6=生涯终身计数域(迭代 18 卡④——meta 两新域 adventure_total〔冒险总局数,开局即计含放弃与失败〕/adventure_cleared〔通关局数,三关全通才计 1——判定字段 summary.cleared〕,旧档缺键由 _normalize 按默认补零,收束自增唯一入账=apply_adventure_summary);v5=天赋树域(迭代 12 T9——meta 三新域 tree_nodes〔node_id→已购等级〕/fork_sides〔fork_group→a|b〕/migration_note〔三体系迁移补偿注记〕——M-4 迭代 12 批次二;需求卡片「v3→v4」系同调批未落时行文,v4 已被同调批占用顺延 v5 披露);v4=同调批扩域(meta 三新域 boss_kill_count/attunement_equipped/attune_refresh_level——M-4 迭代 11 第①批 T2;断点充能余量随 T6 接线,旧档缺键 = 合法空态);v3=断点附随件扩列+摘要 keystone_drops(M-3 迭代 10 奖励重构);v2=断点域出生(M-3 迭代 8 卡①);v1=首版;高版本拒读 owner=存档序列化
|
||||
const SUMMARY_CAP := 10 # 最近冒险摘要保留条数(最新在前)
|
||||
@@ -74,6 +74,22 @@ func _init(path: String = "user://save.json") -> void:
|
||||
register_migration(6, _migrate_v6_v7)
|
||||
|
||||
|
||||
# autoload 生命周期(迭代 27 T1 裁点①——SaveManager 升格跨场景持有者,RngService 先例:
|
||||
# 类名加 Core 后缀、autoload 占净名;读盘挂 _ready=autoload 先于主场景就绪,时序与旧
|
||||
# main._ready 读盘等价)。独立 .new() 实例(单测/工具)不入树零 _ready,读盘仍由调用方显式触发。
|
||||
func _ready() -> void:
|
||||
load_from_disk()
|
||||
|
||||
|
||||
# 原地重置(迭代 27 T1——探针隔离面,沿 RngService.set_master_seed 单例原地重置先例:
|
||||
# 换路径+回默认数据+重读盘;对象引用恒定,MetaProgression 持有的 save 引用零失效)
|
||||
func attach_path(path: String) -> void:
|
||||
_path = path
|
||||
_read_only = false
|
||||
_data = _default_data()
|
||||
load_from_disk()
|
||||
|
||||
|
||||
# v1→v2:断点域出生(旧档无断点 = 合法空态——「继续上局」隐藏,非损坏非债务)
|
||||
func _migrate_v1_v2(data: Dictionary) -> Dictionary:
|
||||
if not data.has("breakpoint"):
|
||||
|
||||
+266
-250
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,24 @@
|
||||
class_name RunScope
|
||||
extends Node
|
||||
# 一局会话状态承载(迭代 27 T1——P0-2「局生命周期物化」修复波)。
|
||||
# 会话 = 一次冒险(开新/续玩)或一次调试两层对局;组合根 _begin_session 创建本节点挂树、
|
||||
# 会话更替/终局整节点 queue_free()——旧手工复位清单(原 main._reset_run_scope 三项 +
|
||||
# _on_battle_end_continue 置空四件)随节点释放机制消亡,此后新增跨局字段零新增泄漏点
|
||||
#(泄漏=低频难复现脏状态的手工契约病灶,审计 P0-2)。
|
||||
# 纪律:本节点零信号连接、零视图构建、零核心写调用(ADR-0001——信号连接只在组合根):
|
||||
# 纯状态承载,组合根经 run_scope.X 读写。
|
||||
var adventure = null # 冒险结构编排器(调试两层恒 null)
|
||||
var runner = null # 关卡推进器(冒险每关重取 adventure.runner();调试两层恒 null)
|
||||
var settlement = null # 当前战斗编排器(冒险与调试两层统一读它)
|
||||
var is_adventure := false # 冒险会话标记(inter 层确认分派与终局分派)
|
||||
var level_active := false # 对局激活(状态面板入口点亮/刷新受理分派)
|
||||
var level_screen_active := false # 关卡独立屏激活状态(屏级互斥——形态升格批语义)
|
||||
var refresh_used_round := 0 # 本轮已受理刷新次数(遥测 round_refresh_index——每轮 round_started 归零)
|
||||
var boss_active := false # Boss 遭遇变体(阶段指示附 Boss 名)
|
||||
var was_settling := false # 结算相位上升沿检测(结算轮光带触发——演出链批)
|
||||
var hints_shown := {} # 一次性非模态提示去重(每 run 一次:chord/no_adjacent)
|
||||
var settlement_stop_timer := ArmedTimer.new() # 卡⑥ 结算框挂起计时(T3/D3 P4 乙——窗口 0=挂起到显式回落)
|
||||
var pending_candidates: Array = [] # 结算框挂起期的选路候选暂存(choices_presented 载荷)
|
||||
var locked_click := {} # 锁期点击缓存(T17 v3.1——最后一次;演播完边沿重放)
|
||||
var last_gain_amount := 0 # 最近一笔钱包入账(卡⑥ GP 增量取数面——wallet_changed gain 载荷)
|
||||
var gp_ledger = null # GP 冒险钱包影子账(gp_shadow_ledger.gd 实例——wallet 事件投喂;T3 账本收口波将退役)
|
||||
@@ -0,0 +1 @@
|
||||
uid://d05dkfqnbqdlx
|
||||
@@ -0,0 +1,37 @@
|
||||
class_name AdventureScreen
|
||||
extends Control
|
||||
# 冒险屏壳(迭代 27 T1 场景拆分——审计 P0-1 第一步;内含关卡地图屏与战斗屏两容器)。
|
||||
# 两容器视图原样构建(build_battle_ui 归 ui_assembler 纪律不变、LevelMapScreen 类视图继续
|
||||
# 代码构建、零 .tscn 化改造——契约 2);战斗信号接线归组合根 main.gd(ADR-0001——信号连接
|
||||
# 只在组合根)。树序语义:本屏壳挂 main 首子位(全模态之下)——屏内 level_screen 与
|
||||
# battle_root 无同时可见态(屏级互斥+模态栈遮挡),层内先后序零视觉差;
|
||||
# 模态登记序不变(level_screen 实例仍按节点层与三选一层之间登记)。
|
||||
# 可独立实例化(F6/headless 挂载)——V2 探针断言载体。
|
||||
|
||||
var battle_root: Control # 战斗屏宿主(B/C/D 三区容器——原 main._battle_root 句柄指向成员)
|
||||
var stage: BattleStageView # B 区(含 log_view 明细带)
|
||||
var log_view: BattleLogView
|
||||
var info_panel: HeroInfoPanelView # C 区
|
||||
var board_zone: BoardZoneView # D 区(含 ap_bar/board_view/toast/fx)
|
||||
var board_view: BoardView # 探针兼容别名(= board_zone.board_view)
|
||||
var ap_bar: ApBarView # 探针兼容别名(= board_zone.ap_bar)
|
||||
var settlement_fx: SettlementFxView # 结算演出覆盖层
|
||||
var level_screen: LevelMapScreen # 关卡画面(独立屏)
|
||||
|
||||
const UiAssembler := preload("res://presentation/ui_assembler.gd") # 构建归装配器(规范 1.1——非全局类,preload 引用)
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE # 屏壳=纯容器(ui_input 实证:全屏 STOP 壳会吃掉下层模态鼠标输入)
|
||||
battle_root = VBoxContainer.new()
|
||||
add_child(battle_root)
|
||||
var battle_ui: Dictionary = UiAssembler.build_battle_ui(battle_root, get_node("/root/ConfigDB"))
|
||||
stage = battle_ui.stage
|
||||
log_view = battle_ui.log_view
|
||||
info_panel = battle_ui.info_panel
|
||||
board_zone = battle_ui.board_zone
|
||||
board_view = battle_ui.board_view
|
||||
ap_bar = battle_ui.ap_bar
|
||||
settlement_fx = battle_ui.settlement_fx
|
||||
level_screen = LevelMapScreen.new()
|
||||
add_child(level_screen)
|
||||
@@ -0,0 +1 @@
|
||||
uid://csw5cl1xa7qu7
|
||||
@@ -0,0 +1,12 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://slice0advn0screen"]
|
||||
|
||||
[ext_resource type="Script" path="res://presentation/scenes/adventure_screen.gd" id="1_advn"]
|
||||
|
||||
[node name="AdventureScreen" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_advn")
|
||||
@@ -0,0 +1,14 @@
|
||||
class_name CampScreen
|
||||
extends Control
|
||||
# 营地屏壳(迭代 27 T1 场景拆分——审计 P0-1 第一步)。
|
||||
# 营地覆盖层视图原样构建(CampOverlay 类视图继续代码构建、零 .tscn 化改造——契约 2);
|
||||
# 购买/开合接线归组合根经 CampShopPresenter(ADR-0001——信号连接只在组合根)。
|
||||
# 可独立实例化(F6/headless 挂载)——V2 探针断言载体。
|
||||
|
||||
var camp_overlay: CampOverlay # 营地覆盖层(原 build_overlays 营地段迁入——main 组合根句柄指向本屏成员)
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE # 屏壳=纯容器(ui_input 实证:全屏 STOP 壳会吃掉下层模态鼠标输入)
|
||||
camp_overlay = CampOverlay.new()
|
||||
add_child(camp_overlay)
|
||||
@@ -0,0 +1 @@
|
||||
uid://dw1k6rn5w8gso
|
||||
@@ -0,0 +1,12 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://slice0camp0screen"]
|
||||
|
||||
[ext_resource type="Script" path="res://presentation/scenes/camp_screen.gd" id="1_camp"]
|
||||
|
||||
[node name="CampScreen" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_camp")
|
||||
@@ -0,0 +1,31 @@
|
||||
class_name MenuScreen
|
||||
extends Control
|
||||
# 开始菜单屏壳(迭代 27 T1 场景拆分——审计 P0-1「场景作为组合单元/生命周期单元」第一步)。
|
||||
# 视图代码原样构建(节点构建归 ui_assembler.gd 纪律不变——本屏壳只持有与暴露句柄);
|
||||
# 按钮接线归组合根 main.gd(ADR-0001——信号连接只在组合根)。
|
||||
# 可独立实例化(F6/headless 挂载)——V2「各新屏场景可独立实例化」探针断言载体。
|
||||
# main 组合根保留 _start_overlay 等句柄指向本屏成员——可见性开关与探针挂载假设零迁移。
|
||||
|
||||
var start_overlay: Control # 全屏菜单层(原 main._start_overlay——组合根可见性开关句柄不变)
|
||||
var box: VBoxContainer
|
||||
var lvbtn: Button
|
||||
var resumebtn: Button
|
||||
var campbtn: Button
|
||||
var careerbtn: Button
|
||||
var settingsbtn: Button
|
||||
var quitbtn: Button
|
||||
|
||||
const UiAssembler := preload("res://presentation/ui_assembler.gd") # 构建归装配器(规范 1.1——非全局类,preload 引用)
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE # 屏壳=纯容器(ui_input 实证:全屏 STOP 壳会吃掉下层模态鼠标输入)
|
||||
var start: Dictionary = UiAssembler.build_start_overlay(self, get_node("/root/ConfigDB"))
|
||||
start_overlay = start.overlay
|
||||
box = start.box
|
||||
lvbtn = start.lvbtn
|
||||
resumebtn = start.resumebtn
|
||||
campbtn = start.campbtn
|
||||
careerbtn = start.careerbtn
|
||||
settingsbtn = start.settingsbtn
|
||||
quitbtn = start.quitbtn
|
||||
@@ -0,0 +1 @@
|
||||
uid://dqijp10rs1kaj
|
||||
@@ -0,0 +1,12 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://slice0menu0screen"]
|
||||
|
||||
[ext_resource type="Script" path="res://presentation/scenes/menu_screen.gd" id="1_menu"]
|
||||
|
||||
[node name="MenuScreen" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_menu")
|
||||
@@ -62,9 +62,10 @@ static func build_battle_ui(root: Control, db) -> Dictionary:
|
||||
|
||||
|
||||
# 模态覆盖层组(z 序 = 登记序:图例 < 节点 < 终局 < 商店)。
|
||||
# 返回 {legend_overlay, node_overlay, end_overlay, shop_overlay, camp_overlay};调用方按本序 addChild。
|
||||
# 返回 {legend_overlay, node_overlay, end_overlay, shop_overlay};调用方按本序 addChild。
|
||||
# (调整板已随迭代 14.5 调试整合批退役——调试浮窗由组合根直构挂树序最末,见 main._build_ui;
|
||||
# 明细回看 overlay 已随 M9/P-C7 退役 2026-10-04——冒险中回看归「生涯」06 登记)
|
||||
# 明细回看 overlay 已随 M9/P-C7 退役 2026-10-04——冒险中回看归「生涯」06 登记;
|
||||
# 营地覆盖层已随迭代 27 T1 场景拆分迁 camp_screen 屏壳——本组不再构建)
|
||||
static func build_overlays(host: Control, db) -> Dictionary:
|
||||
var legend_overlay := LegendOverlay.new(int(db.action_economy.rap_refresh_cost)) # 打磨包 2026-09-29——RAP 行表值注入
|
||||
host.add_child(legend_overlay)
|
||||
@@ -79,16 +80,11 @@ static func build_overlays(host: Control, db) -> Dictionary:
|
||||
var shop_overlay := ShopOverlay.new()
|
||||
host.add_child(shop_overlay)
|
||||
|
||||
# 营地覆盖层(元进度实现批 2026-09-21——主菜单营地入口兑付呈现;开合与开始层互斥仿设置面先例)
|
||||
var camp_overlay := CampOverlay.new()
|
||||
host.add_child(camp_overlay)
|
||||
|
||||
return {
|
||||
"legend_overlay": legend_overlay,
|
||||
"node_overlay": node_overlay,
|
||||
"end_overlay": end_overlay,
|
||||
"shop_overlay": shop_overlay,
|
||||
"camp_overlay": camp_overlay,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://dd071t8g8cjb3
|
||||
@@ -18,6 +18,8 @@ config/features=PackedStringArray("4.7")
|
||||
|
||||
ConfigDB="*res://core/config_db.gd"
|
||||
RngService="*res://core/rng_service.gd"
|
||||
SaveManager="*res://core/save_manager.gd"
|
||||
MetaProgression="*res://core/meta_progression.gd"
|
||||
|
||||
[display]
|
||||
|
||||
|
||||
@@ -51,11 +51,11 @@ func _ready() -> void:
|
||||
main.level_screen.press_candidate(0)
|
||||
for i in 20:
|
||||
await get_tree().process_frame
|
||||
var st = main.settlement
|
||||
var st = main.run_scope.settlement
|
||||
if st == null:
|
||||
_fail("首战未开(settlement 空)")
|
||||
return
|
||||
var rules = main.settlement.minesweeper
|
||||
var rules = main.run_scope.settlement.minesweeper
|
||||
var board = rules.current_board()
|
||||
# 自有驱动(只挖/走——测试驱动可用板面数据挑安全格;新手策略档的插旗拒绝会耗尽预算):挖到 3 格停
|
||||
var dug := 0
|
||||
|
||||
@@ -90,8 +90,11 @@ static func _text_match(text: String, needle: String, mode: String) -> bool:
|
||||
# tag = 隔离档名(不带扩展名,如 "smoke_input_probe_save"——各探针后缀保持不变);先删后建防陈旧档泄入。
|
||||
# mark_first_done = 对齐真实档游玩史口径(模板抽签路径不变);with_settings = 连设置面换注
|
||||
#(隔离 cfg 名 = <tag>_settings.cfg,与被收口探针原文件名逐一同名);refresh_resume = 组合根
|
||||
# 「继续上局」按钮重刷;swap_save = 是否换注 main.save_manager(营地先行段只换 meta 不换 save_manager)。
|
||||
# 「继续上局」按钮重刷;swap_save = 是否换注全局 SaveManager(营地先行段只换 meta 侧不换 SaveManager)。
|
||||
# 返回隔离 SaveManager 实例(调用方常需后续直读余额/重读档)。
|
||||
# 迭代 27 T1——SaveManager/MetaProgression 升格 autoload 后隔离面改单例原地重置
|
||||
#(attach_path 沿 RngService.set_master_seed 先例;MetaProgression 持 save 引用透传,
|
||||
# swap_save=false 分支换 meta 侧引用=独立 iso 实例,语义同旧「只换 meta 不换 save_manager」)。
|
||||
static func isolate_save(main, tag: String, mark_first_done: bool = false, with_settings: bool = false,
|
||||
refresh_resume: bool = false, swap_save: bool = true):
|
||||
var save_path := "user://%s.json" % tag
|
||||
@@ -102,9 +105,15 @@ static func isolate_save(main, tag: String, mark_first_done: bool = false, with_
|
||||
iso.load_from_disk()
|
||||
if mark_first_done:
|
||||
iso.mark_first_adventure_done()
|
||||
# --script 模式无裸 autoload 标识符(run_all.gd 头注)——经 main 节点路径解析
|
||||
var sm = main.get_node("/root/SaveManager")
|
||||
if swap_save:
|
||||
main.save_manager = iso
|
||||
main.meta = MetaProgressionScript.new(main.db, iso)
|
||||
sm.attach_path(save_path)
|
||||
if mark_first_done:
|
||||
sm.mark_first_adventure_done()
|
||||
iso = sm
|
||||
else:
|
||||
main.get_node("/root/MetaProgression").save = iso # 只换 meta 侧(营地段语义)——SaveManager 全局不动
|
||||
if with_settings:
|
||||
main.settings_store = SettingsStoreScript.new("user://%s_settings.cfg" % tag)
|
||||
if refresh_resume:
|
||||
|
||||
@@ -73,10 +73,128 @@ func _initialize() -> void:
|
||||
if load_ok:
|
||||
_run_core_smoke(db, rng)
|
||||
|
||||
_split_tail() # 迭代 27 T1 V2/V3——场景拆分+会话物化断言(挂树帧推进型异步尾段;摘要与退出码随尾段落)
|
||||
|
||||
|
||||
# ---- 场景拆分/会话物化异步尾段(迭代 27 T1——autoload 挂载需过 _initialize 后首帧〔run_all.gd 头注时序〕,
|
||||
# 挂 main.tscn 的断言在帧后执行;摘要打印与 quit 从 _initialize 移本尾段)----
|
||||
func _split_tail() -> void:
|
||||
await process_frame
|
||||
await process_frame
|
||||
var autoload_ok: bool = get_root().get_node_or_null("/root/ConfigDB") != null and get_root().get_node_or_null("/root/SaveManager") != null
|
||||
if autoload_ok:
|
||||
await _smoke_scene_split() # 协程函数必须 await(无 await=发射后不管,首个 await 后即被 quit 截断)
|
||||
await _smoke_run_lifecycle()
|
||||
else:
|
||||
check(false, "V2/V3 前置:autoload 链未就绪(ConfigDB/SaveManager)")
|
||||
print("=== 冒烟完成:%d 项检查,%d 项失败 ===" % [_checks, _failures])
|
||||
quit(1 if _failures > 0 else 0)
|
||||
|
||||
|
||||
# ---- 三屏场景化断言(迭代 27 T1 V2——独立实例化+关键节点在位+组合根树序/z 序保序)----
|
||||
func _smoke_scene_split() -> void:
|
||||
print("--- 迭代27 T1 三屏场景化(V2:屏壳独立实例化+组合根装配保序)---")
|
||||
# ① 三屏壳独立实例化(F6 等价——headless 直挂;视图代码构建在屏壳 _ready)
|
||||
var menu: Node = load("res://presentation/scenes/menu_screen.tscn").instantiate()
|
||||
root.add_child(menu)
|
||||
await process_frame
|
||||
check(menu.start_overlay != null and menu.lvbtn != null and menu.resumebtn != null and menu.campbtn != null and menu.careerbtn != null and menu.settingsbtn != null and menu.quitbtn != null,
|
||||
"V2 菜单屏壳独立实例化(start_overlay+操作组六钮在位)")
|
||||
check(menu.start_overlay.visible, "V2 菜单层默认可见(非模态全屏态)")
|
||||
check(menu.mouse_filter == Control.MOUSE_FILTER_IGNORE, "V2 屏壳根零鼠标截获(IGNORE——全屏 STOP 壳吃输入病灶回归闸)")
|
||||
menu.queue_free()
|
||||
var camp: Node = load("res://presentation/scenes/camp_screen.tscn").instantiate()
|
||||
root.add_child(camp)
|
||||
await process_frame
|
||||
check(camp.camp_overlay != null, "V2 营地屏壳独立实例化(camp_overlay 在位)")
|
||||
check(not camp.camp_overlay.visible, "V2 营地层默认隐藏(模态纪律)")
|
||||
camp.queue_free()
|
||||
var adv: Node = load("res://presentation/scenes/adventure_screen.tscn").instantiate()
|
||||
root.add_child(adv)
|
||||
await process_frame
|
||||
check(adv.battle_root != null and adv.stage != null and adv.board_zone != null and adv.board_view != null and adv.ap_bar != null and adv.level_screen != null,
|
||||
"V2 冒险屏壳独立实例化(战斗宿主+四区句柄+关卡地图屏在位)")
|
||||
adv.queue_free()
|
||||
await process_frame
|
||||
# ② 组合根装配(树序=菜单屏@0+冒险屏@1 在全模态之下;营地屏壳夹商店与设置层之间——z 序保序)
|
||||
var main: Node = load("res://presentation/main.tscn").instantiate()
|
||||
root.add_child(main)
|
||||
await process_frame
|
||||
var kids: Array = main.get_children()
|
||||
check(kids.find(main._menu_screen) == 0 and kids.find(main._adventure_screen) == 1,
|
||||
"V2 组合根树序=屏壳在前(menu@0 adventure@1——视觉共存对全模态在其上)")
|
||||
var i_camp: int = kids.find(main._camp_screen)
|
||||
check(i_camp > kids.find(main.shop_overlay) and i_camp < kids.find(main.settings_overlay),
|
||||
"V2 营地屏壳树序夹商店层与设置层之间(登记序=树序=z 序纪律保序)")
|
||||
check(kids.find(main._adventure_screen) < kids.find(main.legend_overlay),
|
||||
"V2 冒险屏壳在全模态之下(图例战斗中开=其上——视觉共存对验算位)")
|
||||
# ③ 探针挂载假设句柄在座(main.X 一级路径——探针迁移面收口证明)
|
||||
check(main._start_overlay == main._menu_screen.start_overlay and main.camp_overlay == main._camp_screen.camp_overlay and main.level_screen == main._adventure_screen.level_screen and main._battle_root == main._adventure_screen.battle_root and main.board_view == main._adventure_screen.board_view,
|
||||
"V2 组合根句柄指向屏壳成员(探针挂载假设 main.X 零迁移)")
|
||||
check(main.run_scope != null and main.run_scope.gp_ledger != null and not main.run_scope.is_adventure,
|
||||
"V2 闲时会话自 _ready 即在(永非 null——探针注入面时序)")
|
||||
main.queue_free()
|
||||
await process_frame
|
||||
|
||||
|
||||
# ---- 会话物化断言(迭代 27 T1 V3——两连局复位/无脏状态残留;原手工复位清单消亡的行为级证明)----
|
||||
func _smoke_run_lifecycle() -> void:
|
||||
print("--- 迭代27 T1 会话物化(V3:两连局复位+脏状态不残留)---")
|
||||
var main: Node = load("res://presentation/main.tscn").instantiate()
|
||||
root.add_child(main)
|
||||
await process_frame
|
||||
# 存档隔离(autoload attach 原地重置——绝不碰真实档 user://save.json)
|
||||
var sm = main.get_node("/root/SaveManager")
|
||||
for p in ["user://smoke_run_lifecycle.json", "user://smoke_run_lifecycle.json.tmp"]:
|
||||
if FileAccess.file_exists(p):
|
||||
DirAccess.remove_absolute(p)
|
||||
sm.attach_path("user://smoke_run_lifecycle.json")
|
||||
sm.mark_first_adventure_done()
|
||||
# ---- 第一局:开局→注脏→放弃→终局出口→会话收束 ----
|
||||
main.debug_overlay.seed_edit.text = "42"
|
||||
main._start_adventure_from_menu()
|
||||
await process_frame
|
||||
check(main.run_scope != null and main.run_scope.is_adventure and main.run_scope.adventure != null,
|
||||
"V3 开局建会话(is_adventure+adventure 在座)")
|
||||
check(main.run_scope.hints_shown.is_empty() and main.run_scope.refresh_used_round == 0,
|
||||
"V3 新会话局态零值")
|
||||
# 注入脏状态(旧手工清单时代的典型泄漏源——新会话必须整体吞掉)
|
||||
main.run_scope.hints_shown["chord"] = true
|
||||
main.run_scope.refresh_used_round = 2
|
||||
main.run_scope.boss_active = true
|
||||
main.run_scope.last_gain_amount = 77
|
||||
var finished := {"done": false}
|
||||
main.run_scope.adventure.adventure_finished.connect(func(_s: Dictionary) -> void:
|
||||
finished.done = true, CONNECT_ONE_SHOT)
|
||||
check(main.run_scope.adventure.abandon(), "V3 放弃受理(入口停点天然非战斗期)")
|
||||
for i in range(8):
|
||||
if finished.done:
|
||||
break
|
||||
await process_frame
|
||||
check(finished.done, "V3 冒险终局到达(abandon→adventure_finished 同步链)")
|
||||
check(main._end_overlay.is_open(), "V3 终局面板弹起")
|
||||
main._on_battle_end_continue() # 终局出口(回菜单+会话收束——手工置空清单已消亡)
|
||||
await process_frame
|
||||
check(not main.run_scope.is_adventure and main.run_scope.adventure == null and main.run_scope.runner == null,
|
||||
"V3 会话收束(新闲时会话零冒险/关卡对象)")
|
||||
check(main.run_scope.hints_shown.is_empty() and main.run_scope.refresh_used_round == 0 and not main.run_scope.boss_active and main.run_scope.last_gain_amount == 0,
|
||||
"V3 局态旗/账本全复位(queue_free 物化——注入脏状态零残留)")
|
||||
check(main._start_overlay.visible, "V3 回开始菜单(终局出口路径)")
|
||||
# ---- 第二局:放弃终局无断点残留→直进开局→全新会话 ----
|
||||
check(not sm.has_breakpoint(), "V3 放弃终局清断点(无残留断点——续玩按钮态联动)")
|
||||
main.debug_overlay.seed_edit.text = "43"
|
||||
main._start_adventure_from_menu()
|
||||
await process_frame
|
||||
check(main.run_scope.is_adventure and main.run_scope.adventure != null and main.run_scope.hints_shown.is_empty() and main.run_scope.refresh_used_round == 0,
|
||||
"V3 第二连局全新会话(两连局零泄漏)")
|
||||
check(not main._start_overlay.visible, "V3 直进入局(菜单隐)")
|
||||
main.queue_free()
|
||||
await process_frame
|
||||
for p in ["user://smoke_run_lifecycle.json", "user://smoke_run_lifecycle.json.tmp"]:
|
||||
if FileAccess.file_exists(p):
|
||||
DirAccess.remove_absolute(p)
|
||||
|
||||
|
||||
# ---- Core 集成冒烟(表加载成功才执行)----
|
||||
var _db
|
||||
var _rng
|
||||
|
||||
@@ -45,32 +45,32 @@ func _ready() -> void:
|
||||
main.level_screen.press_candidate(0)
|
||||
for i in 20:
|
||||
await get_tree().process_frame
|
||||
if main.settlement == null:
|
||||
if main.run_scope.settlement == null:
|
||||
_fail("战斗未启动")
|
||||
return
|
||||
# 直挖至击杀胜利(settle_batch 真实管线——step_row 信号→排播队列,与点击路径同链)
|
||||
var ms = main.settlement.minesweeper
|
||||
var ms = main.run_scope.settlement.minesweeper
|
||||
var guard := 200
|
||||
var t_ap_out := -1 # AP 用完/停手帧
|
||||
var ap_clock := "" # 墙上时钟时间戳
|
||||
var settle_clock := ""
|
||||
while guard > 0 and String(main.settlement.battle_result) == "":
|
||||
while guard > 0 and String(main.run_scope.settlement.battle_result) == "":
|
||||
guard -= 1
|
||||
var plan = load("res://smoke/auto_player.gd").choose_dig(ms)
|
||||
if plan.is_empty():
|
||||
main.settlement.settle_batch([{"action": "stop", "cell": Vector2i(-1, -1)}])
|
||||
main.run_scope.settlement.settle_batch([{"action": "stop", "cell": Vector2i(-1, -1)}])
|
||||
await get_tree().process_frame
|
||||
continue
|
||||
if plan.hero_cell != ms.hero_pos():
|
||||
ms.settle_batch([{"action": "move", "cell": plan.hero_cell}])
|
||||
ms.settle_batch([{"action": "dig", "cell": plan.dig_cell}])
|
||||
if t_ap_out < 0 and main.settlement.action_economy.phase != "Active":
|
||||
if t_ap_out < 0 and main.run_scope.settlement.action_economy.phase != "Active":
|
||||
t_ap_out = Time.get_ticks_msec()
|
||||
ap_clock = _clock_ms()
|
||||
settle_clock = ap_clock # 触发结算与 AP 用完同帧(核心同步)——同一墙上时刻
|
||||
await get_tree().process_frame
|
||||
if String(main.settlement.battle_result) != "victory":
|
||||
_fail("战斗未胜利(result='%s')" % str(main.settlement.battle_result))
|
||||
if String(main.run_scope.settlement.battle_result) != "victory":
|
||||
_fail("战斗未胜利(result='%s')" % str(main.run_scope.settlement.battle_result))
|
||||
return
|
||||
var t_settle := Time.get_ticks_msec()
|
||||
var _rows_at_victory: int = main._row_pacer.pending_rows().size() # 时间戳①:结算入队完成帧(AP 耗尽/停手→五行段同步跑完→行全在队)
|
||||
|
||||
@@ -269,7 +269,7 @@ func _ready() -> void:
|
||||
_problems.append("三选一停点打开时输入未锁定")
|
||||
if main.curio_overlay._continue_btn.visible:
|
||||
main.curio_overlay.press_continue()
|
||||
elif main.adventure != null and not main.adventure._curio_pending.is_empty():
|
||||
elif main.run_scope.adventure != null and not main.run_scope.adventure._curio_pending.is_empty():
|
||||
main.curio_overlay.press_card(0)
|
||||
main.curio_overlay.press_confirm()
|
||||
else:
|
||||
@@ -319,7 +319,7 @@ func _ready() -> void:
|
||||
next_btn.pressed.emit()
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame
|
||||
if main.runner == null or int(main.runner.level_row.get("level_index", -1)) != 2:
|
||||
if main.run_scope.runner == null or int(main.run_scope.runner.level_row.get("level_index", -1)) != 2:
|
||||
_problems.append("大奖励确认后未推进到第二关(level_index != 2)")
|
||||
else:
|
||||
_done = true
|
||||
@@ -332,8 +332,8 @@ func _ready() -> void:
|
||||
if not main._input_locked():
|
||||
_problems.append("胜利结算框打开时输入未锁定")
|
||||
var gp_line: String = "%s %s" % [main._end_overlay._gp_name.text, main._end_overlay._gp_val.text] # 两列账行(审计差距④)——名/值列拼回整串断言
|
||||
if not gp_line.contains("GP +%d" % main._last_gain_amount) or not gp_line.contains("合计 %d" % int(main.runner.gp)):
|
||||
_problems.append("胜利结算框 GP 增量/总量与钱包实态不符(%s|实态 +%d/%d)" % [gp_line, main._last_gain_amount, int(main.runner.gp)])
|
||||
if not gp_line.contains("GP +%d" % main.run_scope.last_gain_amount) or not gp_line.contains("合计 %d" % int(main.run_scope.runner.gp)):
|
||||
_problems.append("胜利结算框 GP 增量/总量与钱包实态不符(%s|实态 +%d/%d)" % [gp_line, main.run_scope.last_gain_amount, int(main.run_scope.runner.gp)])
|
||||
if main._end_overlay.verdict_text() == "":
|
||||
_problems.append("胜利结算框判定行缺失")
|
||||
_settlement_confirms += 1
|
||||
@@ -350,8 +350,8 @@ func _ready() -> void:
|
||||
# T17 演播后排窗适配:真终局 vs 中途结算面判据=adventure.state(冒险结构三态——
|
||||
# 战斗胜利停点=running / 真终局(失败/放弃/打穿)=settling)。旧判据
|
||||
# (_adventure_mode)在真终局仍 true——会把真终局当中途面点掉返回键(实证)。
|
||||
var _adv_running: bool = main.adventure != null and String(main.adventure.state) == "running"
|
||||
if main._adventure_mode and _adv_running:
|
||||
var _adv_running: bool = main.run_scope.adventure != null and String(main.run_scope.adventure.state) == "running"
|
||||
if main.run_scope.is_adventure and _adv_running:
|
||||
main._end_overlay._btn.pressed.emit()
|
||||
continue
|
||||
if not main._input_locked():
|
||||
@@ -360,12 +360,12 @@ func _ready() -> void:
|
||||
print("[click_probe] 冒险终局:%s · 选路口点击 %d 次" % [
|
||||
main._end_overlay.verdict_text().replace("\n", " "), _route_clicks])
|
||||
break
|
||||
if main.settlement == null or main.runner == null:
|
||||
if main.run_scope.settlement == null or main.run_scope.runner == null:
|
||||
continue
|
||||
var diag: Dictionary = main.settlement.diagnostic_snapshot() # 诊断观测面(私有字段零直读)
|
||||
var diag: Dictionary = main.run_scope.settlement.diagnostic_snapshot() # 诊断观测面(私有字段零直读)
|
||||
var st := {
|
||||
"node": main.runner.node_index, "round": main.settlement.round_index,
|
||||
"phase": main.settlement.action_economy.phase, "ap": main.settlement.action_economy.ap,
|
||||
"node": main.run_scope.runner.node_index, "round": main.run_scope.settlement.round_index,
|
||||
"phase": main.run_scope.settlement.action_economy.phase, "ap": main.run_scope.settlement.action_economy.ap,
|
||||
"settling": diag.settling,
|
||||
}
|
||||
if JSON.stringify(st) == JSON.stringify(_last_state):
|
||||
@@ -384,7 +384,7 @@ func _ready() -> void:
|
||||
_ap_spent_this_round = 0
|
||||
_last_state = st
|
||||
_locktest_tick()
|
||||
if main.settlement.battle_result == "" and main.settlement.action_economy.phase == "Active":
|
||||
if main.run_scope.settlement.battle_result == "" and main.run_scope.settlement.action_economy.phase == "Active":
|
||||
# T1 D1——锁测在飞期(state 1)冻结探针动作面:排空帧 autoplay 的免费 move 会抢在
|
||||
# 帧末 deferred 重放之前改变英雄位(flag 拒收 not_adjacent 实证)——「注入→重放」
|
||||
# 窗口内局面零变化正是 v3.1 被测语义(演播期冻结),动作面恢复待断言收口。
|
||||
@@ -402,14 +402,14 @@ func _ready() -> void:
|
||||
await get_tree().process_frame
|
||||
if main._end_overlay.is_open():
|
||||
# 中途结算面(state=running)=点继续推进;真终局(settling)=断点段前提满足,收口。
|
||||
if main._adventure_mode and main.adventure != null and String(main.adventure.state) == "running":
|
||||
if main.run_scope.is_adventure and main.run_scope.adventure != null and String(main.run_scope.adventure.state) == "running":
|
||||
main._end_overlay._btn.pressed.emit()
|
||||
else:
|
||||
break
|
||||
elif main.curio_overlay.visible:
|
||||
if main.curio_overlay._continue_btn.visible:
|
||||
main.curio_overlay.press_continue()
|
||||
elif main.adventure != null and not main.adventure._curio_pending.is_empty():
|
||||
elif main.run_scope.adventure != null and not main.run_scope.adventure._curio_pending.is_empty():
|
||||
main.curio_overlay.press_card(0)
|
||||
main.curio_overlay.press_confirm()
|
||||
else:
|
||||
@@ -432,8 +432,8 @@ func _ready() -> void:
|
||||
for i in 10:
|
||||
await get_tree().process_frame
|
||||
break
|
||||
elif main.settlement != null and main.settlement.battle_result == "" \
|
||||
and main.settlement.action_economy.phase == "Active":
|
||||
elif main.run_scope.settlement != null and main.run_scope.settlement.battle_result == "" \
|
||||
and main.run_scope.settlement.action_economy.phase == "Active":
|
||||
_play_one_action()
|
||||
# 排播期/Waiting 等待态=自然流逝(演播秒级、锁随队列空解除)
|
||||
# T17 适配:战败或放弃二途皆达终局面(放弃=预算耗尽收口路径——见上),断点段前提不变。
|
||||
@@ -474,11 +474,11 @@ func _ready() -> void:
|
||||
iso_chk.load_from_disk()
|
||||
if not iso_chk.has_breakpoint():
|
||||
_problems.append("断点段:入口停点断点未写盘(卡①写面失守)")
|
||||
var cand_before: Array = main.runner.next_candidate_ids()
|
||||
var cand_before: Array = main.run_scope.runner.next_candidate_ids()
|
||||
# 合成「关游戏重开回菜单」(探针专用——真实路径无此入口;断点已在盘)
|
||||
main._exit_level_screen()
|
||||
main._start_overlay.visible = true
|
||||
main.adventure = null
|
||||
main.run_scope.adventure = null
|
||||
main._refresh_resume_button()
|
||||
await get_tree().process_frame
|
||||
if not resume_btn.visible:
|
||||
@@ -487,10 +487,10 @@ func _ready() -> void:
|
||||
resume_btn.pressed.emit() # 真实按钮信号路径 → _resume_from_menu → resume_adventure
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame
|
||||
if main.adventure == null or main.runner == null or not main.level_screen.visible:
|
||||
if main.run_scope.adventure == null or main.run_scope.runner == null or not main.level_screen.visible:
|
||||
_problems.append("断点段:续玩未恢复至停点(关卡画面未现)")
|
||||
else:
|
||||
var cand_after: Array = main.runner.next_candidate_ids()
|
||||
var cand_after: Array = main.run_scope.runner.next_candidate_ids()
|
||||
if cand_after.size() != cand_before.size() or (cand_after.size() > 0 and int(cand_after[0]) != int(cand_before[0])):
|
||||
_problems.append("断点段:续玩候选与中断前不一致(停点重建失准)")
|
||||
if not iso_chk.load_from_disk() or not iso_chk.has_breakpoint():
|
||||
@@ -498,12 +498,12 @@ func _ready() -> void:
|
||||
# ③ 互斥确认弹窗(二轮修改批 A):断点在档点开始冒险 → 弹确认框;取消=回菜单;开新冒险=提交
|
||||
main._exit_level_screen()
|
||||
main._start_overlay.visible = true
|
||||
main.adventure = null
|
||||
main.run_scope.adventure = null
|
||||
main._refresh_resume_button()
|
||||
await get_tree().process_frame
|
||||
lv_btn.pressed.emit()
|
||||
await get_tree().process_frame
|
||||
if not main.confirm_dialog.is_open() or main.adventure != null:
|
||||
if not main.confirm_dialog.is_open() or main.run_scope.adventure != null:
|
||||
_problems.append("断点段:有断点点开始冒险未弹确认框(二轮 A 失守)")
|
||||
var cancel_btn: Button = ProbeUiUtil.find_button(main.confirm_dialog, "取消", "contains")
|
||||
if cancel_btn == null:
|
||||
@@ -511,7 +511,7 @@ func _ready() -> void:
|
||||
else:
|
||||
cancel_btn.pressed.emit()
|
||||
await get_tree().process_frame
|
||||
if main.confirm_dialog.is_open() or main.adventure != null or not main._start_overlay.visible:
|
||||
if main.confirm_dialog.is_open() or main.run_scope.adventure != null or not main._start_overlay.visible:
|
||||
_problems.append("断点段:取消未回开始菜单(二轮 A)")
|
||||
lv_btn.pressed.emit()
|
||||
await get_tree().process_frame
|
||||
@@ -522,7 +522,7 @@ func _ready() -> void:
|
||||
confirm_btn.pressed.emit()
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame
|
||||
if main.adventure == null or main._start_overlay.visible or main.confirm_dialog.is_open():
|
||||
if main.run_scope.adventure == null or main._start_overlay.visible or main.confirm_dialog.is_open():
|
||||
_problems.append("断点段:确认开新冒险后未开局(二轮 A)")
|
||||
print("[click_probe] 循环退出(done=%s budget=%s)问题=%d" % [
|
||||
str(_done), str(action_budget), _problems.size()])
|
||||
@@ -610,24 +610,24 @@ func _run_modal_probes_once() -> void:
|
||||
# ④ 弃图刷新确认链(pattern 7 异位:确认 chips 与常规确认不同位 + 显式代价文案)
|
||||
if not _tested_refresh_chain and main.ap_bar.is_refresh_ready():
|
||||
_tested_refresh_chain = true
|
||||
var rap_before: int = int(main.settlement.action_economy.rap)
|
||||
var board_before = main.settlement.minesweeper.current_board()
|
||||
var rap_before: int = int(main.run_scope.settlement.action_economy.rap)
|
||||
var board_before = main.run_scope.settlement.minesweeper.current_board()
|
||||
main.ap_bar.press_refresh_for_probe()
|
||||
if not main.ap_bar.confirm_visible():
|
||||
_problems.append("弃图刷新确认 chips 未出现(异位确认缺失)")
|
||||
main.ap_bar.press_confirm_for_probe()
|
||||
if int(main.settlement.action_economy.rap) != rap_before - 8:
|
||||
_problems.append("弃图刷新未扣 RAP8(%d→%d)" % [rap_before, int(main.settlement.action_economy.rap)])
|
||||
if main.settlement.action_economy.phase != "Active":
|
||||
_problems.append("轮内刷新后相位丢失(%s)" % main.settlement.action_economy.phase)
|
||||
if main.settlement.minesweeper.current_board() == board_before:
|
||||
if int(main.run_scope.settlement.action_economy.rap) != rap_before - 8:
|
||||
_problems.append("弃图刷新未扣 RAP8(%d→%d)" % [rap_before, int(main.run_scope.settlement.action_economy.rap)])
|
||||
if main.run_scope.settlement.action_economy.phase != "Active":
|
||||
_problems.append("轮内刷新后相位丢失(%s)" % main.run_scope.settlement.action_economy.phase)
|
||||
if main.run_scope.settlement.minesweeper.current_board() == board_before:
|
||||
_problems.append("轮内刷新未当场换图")
|
||||
if main._refresh_used_round != 1:
|
||||
_problems.append("刷新轮内计数错误(%d)" % main._refresh_used_round)
|
||||
if main.run_scope.refresh_used_round != 1:
|
||||
_problems.append("刷新轮内计数错误(%d)" % main.run_scope.refresh_used_round)
|
||||
|
||||
|
||||
func _play_one_action() -> void:
|
||||
var settlement = main.settlement
|
||||
var settlement = main.run_scope.settlement
|
||||
if settlement == null:
|
||||
return # 结算竞态防抖:battle_finished 与本轮动作调度同帧到达时 settlement 可能已被置空(基线既有噪声——加固)
|
||||
if not main._row_queue.is_empty():
|
||||
@@ -696,7 +696,7 @@ func _click_stop() -> void:
|
||||
func _locktest_tick() -> void:
|
||||
if _locktest_state == 2:
|
||||
return
|
||||
var settlement = main.settlement
|
||||
var settlement = main.run_scope.settlement
|
||||
if settlement == null or settlement.minesweeper.current_board() == null:
|
||||
return
|
||||
if _locktest_state == 0:
|
||||
@@ -716,7 +716,7 @@ func _locktest_tick() -> void:
|
||||
_problems.append("锁期断言:演播期点击未被冻结(旗即时受理——v3 冻结失守)")
|
||||
_locktest_state = 2
|
||||
return
|
||||
if main._locked_click.is_empty() or main._locked_click.get("cell") != target:
|
||||
if main.run_scope.locked_click.is_empty() or main.run_scope.locked_click.get("cell") != target:
|
||||
_problems.append("锁期断言:演播期点击未入缓存(v3.1 缓存失守)")
|
||||
_locktest_state = 2
|
||||
return
|
||||
@@ -733,10 +733,10 @@ func _locktest_tick() -> void:
|
||||
if _locktest_frames < 3:
|
||||
return # 排空边沿的 deferred 重放(pump_ended 帧末执行)在途宽限
|
||||
if _locktest_frames > 90:
|
||||
_problems.append("重放断言:演播完边沿后缓存未清(%s)" % str(main._locked_click))
|
||||
_problems.append("重放断言:演播完边沿后缓存未清(%s)" % str(main.run_scope.locked_click))
|
||||
_locktest_state = 2
|
||||
return
|
||||
if main._locked_click.is_empty():
|
||||
if main.run_scope.locked_click.is_empty():
|
||||
if not settlement.minesweeper.has_flag(_locktest_cell):
|
||||
var b = settlement.minesweeper.current_board()
|
||||
print("[lockdiag] 重放后旗未置:br='%s' phase=%s ap=%d cell=%s revealed=%s board=%dx%d hero=%s" % [
|
||||
@@ -767,20 +767,20 @@ func _report_stall(st: Dictionary) -> void:
|
||||
why = "结算停滞:Waiting 相位无推进(结算应同步走完——窗口段已删除)"
|
||||
elif st.phase == "Active" and st.ap == 0:
|
||||
why = "Active 相位 AP=0(应在归零瞬间翻 Waiting)"
|
||||
elif st.phase == "Idle" and main.settlement.battle_result == "":
|
||||
elif st.phase == "Idle" and main.run_scope.settlement.battle_result == "":
|
||||
why = "Idle 相位但战斗未结束(begin 失败?)"
|
||||
else:
|
||||
why = "状态无推进(battle_result='%s')" % str(main.settlement.battle_result)
|
||||
why = "状态无推进(battle_result='%s')" % str(main.run_scope.settlement.battle_result)
|
||||
print("[click_probe] 🔴 卡死复现(PROBE_SEED=%s):%s" % [OS.get_environment("PROBE_SEED"), why])
|
||||
_dump_state()
|
||||
get_tree().quit(2)
|
||||
|
||||
|
||||
func _dump_state() -> void:
|
||||
var diag: Dictionary = main.settlement.diagnostic_snapshot() # 诊断观测面(私有字段零直读)
|
||||
var diag: Dictionary = main.run_scope.settlement.diagnostic_snapshot() # 诊断观测面(私有字段零直读)
|
||||
print(" node_index=%s round=%s phase=%s ap=%s rap=%s" % [
|
||||
str(main.runner.node_index), str(main.settlement.round_index),
|
||||
str(main.settlement.action_economy.phase), str(main.settlement.action_economy.ap), str(main.settlement.action_economy.rap)])
|
||||
str(main.run_scope.runner.node_index), str(main.run_scope.settlement.round_index),
|
||||
str(main.run_scope.settlement.action_economy.phase), str(main.run_scope.settlement.action_economy.ap), str(main.run_scope.settlement.action_economy.rap)])
|
||||
print(" battle_result='%s' settling=%s" % [
|
||||
str(diag.battle_result), str(diag.settling)])
|
||||
print(" board_installed=%s facts_path='%s' refresh_used=%s flags=%s" % [
|
||||
|
||||
@@ -40,11 +40,11 @@ func _ready() -> void:
|
||||
# 命中函数探针全绿、真鼠标点不中——本 case 封住输入管线,⑬封住几何拟合。
|
||||
if main.level_screen.visible:
|
||||
var ro = main.level_screen
|
||||
var cands: Array = main.runner.next_candidate_ids()
|
||||
var nds: Array = main.runner.nodes()
|
||||
var cands: Array = main.run_scope.runner.next_candidate_ids()
|
||||
var nds: Array = main.run_scope.runner.nodes()
|
||||
var tgt := {}
|
||||
for n in nds:
|
||||
if main.runner.node_id_of(n) == int(cands[0]):
|
||||
if main.run_scope.runner.node_id_of(n) == int(cands[0]):
|
||||
tgt = n
|
||||
var npos: Vector2 = ro.layout_probe(nds)[tgt]
|
||||
var gpos: Vector2 = ro.canvas_probe()["origin"] + npos
|
||||
@@ -63,30 +63,30 @@ func _ready() -> void:
|
||||
main.get_viewport().push_input(evc, true)
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame
|
||||
if main.level_screen.visible or main.runner.awaiting_choice():
|
||||
_pre_fail("图上真实点击未提交选路(overlay 可见=%s 选路态=%s)" % [str(main.level_screen.visible), str(main.runner.awaiting_choice())])
|
||||
if main.level_screen.visible or main.run_scope.runner.awaiting_choice():
|
||||
_pre_fail("图上真实点击未提交选路(overlay 可见=%s 选路态=%s)" % [str(main.level_screen.visible), str(main.run_scope.runner.awaiting_choice())])
|
||||
for i in range(3):
|
||||
await get_tree().process_frame
|
||||
# ---- ⑬ 画布随图拟合(同 bug 的几何面回归):复刻分层公式独立推导最深节点内容 x,
|
||||
# 断言画布 min 尺寸覆盖之(open_choices 每 stop 拟合——L1 单链 Boss x=70+6*122=802 曾越界)
|
||||
var ro2 = main.level_screen
|
||||
var nds2: Array = main.runner.nodes()
|
||||
var nds2: Array = main.run_scope.runner.nodes()
|
||||
var layer2 := {}
|
||||
var inbound2 := {}
|
||||
for n2 in nds2:
|
||||
for tid2 in main.runner.node_out_ids(n2):
|
||||
for tid2 in main.run_scope.runner.node_out_ids(n2):
|
||||
inbound2[tid2] = true
|
||||
var queue2: Array = []
|
||||
for n2 in nds2:
|
||||
if not inbound2.has(main.runner.node_id_of(n2)):
|
||||
if not inbound2.has(main.run_scope.runner.node_id_of(n2)):
|
||||
queue2.append(n2)
|
||||
layer2[n2] = 0
|
||||
while not queue2.is_empty():
|
||||
var cur2: Dictionary = queue2.pop_front()
|
||||
for tid2 in main.runner.node_out_ids(cur2):
|
||||
for tid2 in main.run_scope.runner.node_out_ids(cur2):
|
||||
var tg2 := {}
|
||||
for n2 in nds2:
|
||||
if main.runner.node_id_of(n2) == tid2:
|
||||
if main.run_scope.runner.node_id_of(n2) == tid2:
|
||||
tg2 = n2
|
||||
if tg2.is_empty() or layer2.has(tg2):
|
||||
continue
|
||||
@@ -102,9 +102,9 @@ func _ready() -> void:
|
||||
if float(cvmin["min_size"].x) < need_x2 - 0.5:
|
||||
_pre_fail("画布拟合不足(min.x=%.0f < 最深内容 %.0f——深层节点不可点回归)" % [cvmin["min_size"].x, need_x2])
|
||||
var bv = main.board_view
|
||||
var ms = main.settlement.minesweeper if main.settlement != null else null
|
||||
var ms = main.run_scope.settlement.minesweeper if main.run_scope.settlement != null else null
|
||||
if ms == null or bv.cells_probe().is_empty():
|
||||
_pre_fail("战斗未就绪(cells=%d settlement=%s)" % [bv.cells_probe().size(), str(main.settlement)])
|
||||
_pre_fail("战斗未就绪(cells=%d settlement=%s)" % [bv.cells_probe().size(), str(main.run_scope.settlement)])
|
||||
return
|
||||
bv.action_requested.connect(func(c: Vector2i, kind: String): _actions.append([c, kind]))
|
||||
bv.chord_attempted.connect(func(c: Vector2i): _chords.append(c))
|
||||
@@ -426,13 +426,13 @@ func _ready() -> void:
|
||||
var p_lantern: Dictionary = main.build_adventure_params(main.save_manager, main.meta)
|
||||
if not (p_lantern.get("attunement", []) as Array).has("wide_scout_band"):
|
||||
_problems.append("case⑮ 装配未进开局参数面(attunement=%s 应含 wide_scout_band)" % str(p_lantern.get("attunement", [])))
|
||||
main.settlement.attunement = ["wide_scout_band"]
|
||||
main.run_scope.settlement.attunement = ["wide_scout_band"]
|
||||
main._sync_treasure_glow()
|
||||
if int(bv.fog_params_probe()["lantern"]) != 4:
|
||||
_problems.append("case⑮ 装配后视野未扩(lantern 应=4——呈现桥未生效)")
|
||||
if int(bv.cells_probe()[ring_cell].fog_state) != 0:
|
||||
_problems.append("case⑮ 装配后 dist==R+1 格仍被遮(应转可见)")
|
||||
main.settlement.attunement = []
|
||||
main.run_scope.settlement.attunement = []
|
||||
main._sync_treasure_glow()
|
||||
if int(bv.fog_params_probe()["lantern"]) != 0:
|
||||
_problems.append("case⑮ 卸装后视野未复原(lantern 应=0——契约复原语义显式重算)")
|
||||
|
||||
@@ -43,7 +43,7 @@ func _ready() -> void:
|
||||
continue
|
||||
print("%-16s pos=%s size=%s min=%s hflag=%d vflag=%d" % [k, ctl.position, ctl.size,
|
||||
ctl.custom_minimum_size, ctl.size_flags_horizontal, ctl.size_flags_vertical])
|
||||
var root_box: Control = main.get_child(0)
|
||||
var root_box: Control = main._adventure_screen.battle_root # 迭代 27 T1——屏壳化后战斗四区根迁冒险屏壳(序数寻址漂移改名寻址)
|
||||
print("four-zone root: anchors=(%s,%s)-(%s,%s) offsets=(%s,%s,%s,%s) size=%s" % [
|
||||
root_box.anchor_left, root_box.anchor_top, root_box.anchor_right, root_box.anchor_bottom,
|
||||
root_box.offset_left, root_box.offset_top, root_box.offset_right, root_box.offset_bottom, root_box.size])
|
||||
|
||||
+12
-8
@@ -67,8 +67,8 @@ func _ready() -> void:
|
||||
problems.append("关卡开始未弹入口选路停点(level_screen 应可见)")
|
||||
elif main._battle_root.visible:
|
||||
problems.append("独立屏激活期战斗屏宿主未隐藏(零透出契约失守)")
|
||||
elif main.runner == null or not main.runner.awaiting_choice() \
|
||||
or main.runner.next_candidate_ids().size() != 1:
|
||||
elif main.run_scope.runner == null or not main.run_scope.runner.awaiting_choice() \
|
||||
or main.run_scope.runner.next_candidate_ids().size() != 1:
|
||||
problems.append("入口停点状态失守(选路态 + 入口单候选)")
|
||||
else:
|
||||
main.level_screen.press_candidate(0)
|
||||
@@ -82,7 +82,7 @@ func _ready() -> void:
|
||||
else:
|
||||
main.level_screen.abandon_btn.pressed.emit()
|
||||
await get_tree().process_frame
|
||||
if main.adventure == null or String(main.adventure.state) != "running":
|
||||
if main.run_scope.adventure == null or String(main.run_scope.adventure.state) != "running":
|
||||
problems.append("首战中放弃未被拒(战斗域单事务在飞——state 应保持 running)")
|
||||
# ---- 既有断言(回归基线;A 区进度条断言随 停点前移批 2026-09-23 退役——节点链进度由关卡画面独立屏承载)----
|
||||
if main.board_view.cells_probe().is_empty():
|
||||
@@ -91,7 +91,7 @@ func _ready() -> void:
|
||||
problems.append("board 未装载")
|
||||
if main.info_panel.attack_text().contains("—") or main.info_panel.armor_text().contains("—"):
|
||||
problems.append("C 区攻击/护甲未显示(%s | %s)" % [main.info_panel.attack_text(), main.info_panel.armor_text()])
|
||||
if main.settlement == null or main.settlement.battle_result != "":
|
||||
if main.run_scope.settlement == null or main.run_scope.settlement.battle_result != "":
|
||||
problems.append("首战未处于进行中")
|
||||
if not main.log_view.parsed_text().contains("节点 1/"):
|
||||
problems.append("节点头日志缺失")
|
||||
@@ -193,7 +193,7 @@ func _ready() -> void:
|
||||
# ---- L2 单场段(debug_session 单场原语 + 去饱和接口可用性;雾格机制已整体移除,不再断言变体呈现)----
|
||||
# 2026-09-13 单场用户入口已删——探针直调 debug_session 原语(battle_created/ready 仍经组合根接线;
|
||||
# _reset_run_scope 复现原 _start_single 的提示去重复位)
|
||||
main._reset_run_scope()
|
||||
main._begin_session(false)
|
||||
main._debug.start_single(2)
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame
|
||||
@@ -218,11 +218,15 @@ func _ready() -> void:
|
||||
main._on_wallet_changed("clear", 0, {})
|
||||
if int(main._adventure_gp_final) != 57 or int(main._adventure_gp_view) != 0:
|
||||
problems.append("GP 影子账清零转存失败(final=%s view=%s)" % [str(main._adventure_gp_final), str(main._adventure_gp_view)])
|
||||
var meta_saved = main.meta
|
||||
main.meta = null # 合成摘要不落结算(settle_adventure 会写隔离档→跨跑自污染),GP 行只验呈现
|
||||
var meta_saved = main.meta.save
|
||||
for p2 in ["user://smoke_ui_probe_settle_scratch.json", "user://smoke_ui_probe_settle_scratch.json.tmp"]:
|
||||
if FileAccess.file_exists(p2):
|
||||
DirAccess.remove_absolute(p2)
|
||||
main.meta.save = load("res://core/save_manager.gd").new("user://smoke_ui_probe_settle_scratch.json")
|
||||
# 合成摘要不落隔离档(settle_adventure 写盘改道 scratch——跨跑自污染断根),GP 行只验呈现
|
||||
main._on_adventure_finished({"result": "completed", "cleared": true, "levels_cleared": 3,
|
||||
"stopped_at": 0, "keystone_total": 30, "master_seed": 42})
|
||||
main.meta = meta_saved
|
||||
main.meta.save = meta_saved
|
||||
if not main._end_overlay.is_open():
|
||||
problems.append("冒险终局面板未开(合成摘要路径)")
|
||||
elif not main._end_overlay._gp_row.visible or not main._end_overlay._gp_val.text.contains("57"):
|
||||
|
||||
@@ -167,7 +167,7 @@ func _ready() -> void:
|
||||
ProbeUiUtil.isolate_save(main, "smoke_ui_struct_probe_resume", true)
|
||||
main.save_manager.set_breakpoint(_resume_bp_fixture())
|
||||
main._refresh_resume_button()
|
||||
main.runner = null # 全新启动等价态(真机=菜单直进续玩,无前置冒险——runner 从未赋值;探针此前推流会残留旧 runner)
|
||||
main.run_scope.runner = null # 全新启动等价态(真机=菜单直进续玩,无前置冒险——runner 从未赋值;探针此前推流会残留旧 runner)
|
||||
main._resume_from_menu()
|
||||
_settle(8)
|
||||
if not main._end_overlay.visible:
|
||||
@@ -181,7 +181,7 @@ func _ready() -> void:
|
||||
if not s.contains("合计 24"):
|
||||
problems.append("[resume] _gp_summary_text runner=null 守卫未取快照 gp(%s)" % s)
|
||||
# 恢复现场(resume 改写了 main 的 adventure/save 态——后续无屏依赖,仅为整洁)
|
||||
main._level_active = false
|
||||
main.run_scope.level_active = false
|
||||
|
||||
# ---- 判定 ----
|
||||
if problems.is_empty():
|
||||
|
||||
+13
-13
@@ -99,14 +99,14 @@ func _ready() -> void:
|
||||
# ⑧b 开冒险路径显式关浮窗(18卡⑦ T2 防护——DS_DEBUG 会话默认开态下开局不带窗,对局中经角标再开)
|
||||
if main.debug_overlay.visible:
|
||||
problems.append("开冒险路径未显式关浮窗(T2 防护失守)")
|
||||
if int(main.adventure.master_seed) != 42:
|
||||
problems.append("显式种子未达冒险(master_seed=%s ≠ 42——真实路径显式优先失守)" % str(main.adventure.master_seed))
|
||||
if int(main.run_scope.adventure.master_seed) != 42:
|
||||
problems.append("显式种子未达冒险(master_seed=%s ≠ 42——真实路径显式优先失守)" % str(main.run_scope.adventure.master_seed))
|
||||
# 停点前移批 2026-09-23:关卡开始 = 入口选路停点——真实按钮路径选入口候选进首战再读呈现快照
|
||||
if main.level_screen.visible:
|
||||
main.level_screen.press_candidate(0)
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame
|
||||
var hero: Dictionary = main.settlement.presentation_snapshot().hero
|
||||
var hero: Dictionary = main.run_scope.settlement.presentation_snapshot().hero
|
||||
if int(hero.get("atk", -1)) != boosted_atk or int(hero.get("max_hp", -1)) != boosted_hp:
|
||||
problems.append("战斗加成单次生效违例(atk=%s max_hp=%s,期望 %d/%d)" % [
|
||||
str(hero.get("atk", "?")), str(hero.get("max_hp", "?")), boosted_atk, boosted_hp])
|
||||
@@ -132,19 +132,19 @@ func _ready() -> void:
|
||||
problems.append("对局中批量模拟/两层调试按钮未禁用(T2 并存态不开放失守)")
|
||||
# 当局数据六项值对(T3——数值与只读访问器逐项一致;种子=adventure.master_seed 现值非 _seed_value 重算)
|
||||
var rd: Dictionary = main.debug_overlay.run_data_probe()
|
||||
var r = main.adventure.runner()
|
||||
var r = main.run_scope.adventure.runner()
|
||||
if String(rd.get("seed", "")) != "42":
|
||||
problems.append("当局数据种子 ≠ master_seed 现值(%s)" % str(rd.get("seed")))
|
||||
if int(rd.get("level", -1)) != int(main.adventure.level_index):
|
||||
problems.append("当局数据关卡失对(%s ≠ %s)" % [str(rd.get("level")), str(main.adventure.level_index)])
|
||||
if int(rd.get("level", -1)) != int(main.run_scope.adventure.level_index):
|
||||
problems.append("当局数据关卡失对(%s ≠ %s)" % [str(rd.get("level")), str(main.run_scope.adventure.level_index)])
|
||||
if int(rd.get("node", -999)) != int(r.node_index):
|
||||
problems.append("当局数据节点失对(%s ≠ %s)" % [str(rd.get("node")), str(r.node_index)])
|
||||
if String(rd.get("hp", "")) != "%d/%d" % [int(r.hero_hp), int(r.effective_max_hp())]:
|
||||
problems.append("当局数据 HP 失对(%s)" % str(rd.get("hp")))
|
||||
if int(rd.get("gp", -1)) != int(r.gp):
|
||||
problems.append("当局数据 GP 失对(%s ≠ %s)" % [str(rd.get("gp")), str(r.gp)])
|
||||
if int(rd.get("keystone", -1)) != int(main.adventure.keystone):
|
||||
problems.append("当局数据钥石失对(%s ≠ %s)" % [str(rd.get("keystone")), str(main.adventure.keystone)])
|
||||
if int(rd.get("keystone", -1)) != int(main.run_scope.adventure.keystone):
|
||||
problems.append("当局数据钥石失对(%s ≠ %s)" % [str(rd.get("keystone")), str(main.run_scope.adventure.keystone)])
|
||||
var close_btn: Button = ProbeUiUtil.find_button(main.debug_overlay, "关闭", "prefix")
|
||||
if close_btn == null:
|
||||
problems.append("浮窗关闭钮缺失(真实收窗路径中断)")
|
||||
@@ -161,14 +161,14 @@ func _ready() -> void:
|
||||
main.debug_overlay.seed_edit.text = "777"
|
||||
if main._seed_value() != 777:
|
||||
problems.append("显式种子输入未优先(_seed_value != 777——C1 ①失守)")
|
||||
var fresh_sm = load("res://core/save_manager.gd").new("user://smoke_tune_probe_seed.json")
|
||||
fresh_sm.load_from_disk() # 全新空档——first_adventure_done=false(首局态;新档缺省面)
|
||||
var keep_sm = main.save_manager
|
||||
main.save_manager = fresh_sm
|
||||
# 迭代 27 T1——SaveManager 升格 autoload 后换注改原地 attach(首局态挂载+恢复隔离档);
|
||||
# --script 模式无裸 autoload 标识符——经 main 节点路径解析
|
||||
var sm = main.get_node("/root/SaveManager")
|
||||
sm.attach_path("user://smoke_tune_probe_seed.json") # 全新空档——first_adventure_done=false(首局态;新档缺省面)
|
||||
main.debug_overlay.seed_edit.text = ""
|
||||
if main._seed_value() != 42:
|
||||
problems.append("首局空框种子 != 42(P5 甲失守——教学态可复现破约)")
|
||||
main.save_manager = keep_sm # 恢复隔离档(非首局态)
|
||||
sm.attach_path("user://smoke_tune_probe_save.json") # 恢复隔离档(非首局态——isolate_save 已建档置位)
|
||||
var draws: Array[int] = []
|
||||
for i in range(4):
|
||||
draws.append(main._seed_value())
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://dwsud4v5wh2av
|
||||
@@ -0,0 +1 @@
|
||||
uid://c61pxo55ok5ph
|
||||
@@ -19,19 +19,17 @@ 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")
|
||||
# 隔离换注(迭代 27 T1——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)
|
||||
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)
|
||||
var sm = main.get_node("/root/SaveManager")
|
||||
sm.attach_path("user://test_pacing_queue_save.json")
|
||||
sm.mark_first_adventure_done()
|
||||
main._refresh_resume_button()
|
||||
# 行信号计数器(真实信号路径旁挂计数——零触碰既有接线)
|
||||
var rows_seen: Array = []
|
||||
main.adventure = null
|
||||
main._start_adventure_from_menu()
|
||||
await process_frame
|
||||
# 入口停点选路进首战(真实停点链)
|
||||
@@ -39,11 +37,11 @@ func _init() -> void:
|
||||
_problems.append("入口停点未呈现(前置失守)")
|
||||
_finish()
|
||||
return
|
||||
main.runner.pick_next_node(int(main.runner.next_candidate_ids()[0]))
|
||||
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.runner.current_battle()
|
||||
var battle = main.run_scope.runner.current_battle()
|
||||
if battle == null:
|
||||
_problems.append("首战未开打(排播断言无载体)")
|
||||
_finish()
|
||||
|
||||
Reference in new issue
Block a user