feat(save): 存档挂点接入组合根——启动读盘+冒险收束写盘+摘要集成断言⑩项(冒烟 518 全绿)

- main.gd:_ready 建 SaveManager+load_from_disk(拒读/损坏类内兜底不阻断);_on_adventure_finished
  尾部 apply_adventure_summary+write_to_disk(正式冒险收束唯一写档点);L2/L3 直入口与调试两层
  走 runner/debug 路径不经本处理器——契约 7 测试入口不写档结构性成立
- smoke ⑨ 摘要集成:run_adventure(42,200,100) 真局摘要→域读数(余额=3×keystone_per_level/次数全 1/
  首杀齐/recent seed42)+二局翻倍+写盘读回保持
This commit is contained in:
DKChan committed 2026-09-21 05:12:57 +08:00
1 parent ed75e0f10c
commit 96255ae198
2 files changed
+40 -5

No files matched your search

@@ -26,11 +26,13 @@ const Narrator := preload("res://presentation/battle_narrator.gd")
const ModalStack := preload("res://presentation/modal_stack.gd") const ModalStack := preload("res://presentation/modal_stack.gd")
const TuneSimController := preload("res://presentation/tune_sim_controller.gd") const TuneSimController := preload("res://presentation/tune_sim_controller.gd")
const DebugSessionScript := preload("res://presentation/debug_session.gd") const DebugSessionScript := preload("res://presentation/debug_session.gd")
const SaveManagerScript := preload("res://core/save_manager.gd") # 存档承载(preload 避开全局类缓存时序——与 DebugSessionScript 同款)
var db var db
var settlement = null # 当前战斗编排器(关卡与调试模式统一读它) var settlement = null # 当前战斗编排器(关卡与调试模式统一读它)
var runner = null # 关卡节点地图 图版推进器(完整关卡模式;冒险模式 = 每关重取 adventure.runner()) var runner = null # 关卡节点地图 图版推进器(完整关卡模式;冒险模式 = 每关重取 adventure.runner())
var adventure = null # 冒险结构编排器(冒险结构实现批——三关连探;转发信号照 runner 接线原样对接) var adventure = null # 冒险结构编排器(冒险结构实现批——三关连探;转发信号照 runner 接线原样对接)
var save_manager = null # 存档序列化承载(存档序列化批——启动读盘/冒险收束写盘;只经访问器与 apply 原语,禁读内部结构)
var _adventure_mode := false # 冒险模式标记(inter 层确认分派与终局分派) var _adventure_mode := false # 冒险模式标记(inter 层确认分派与终局分派)
var logger = null # 试玩日志器(JSONL 持久化 + 卡死诊断转储——呈现层组件) var logger = null # 试玩日志器(JSONL 持久化 + 卡死诊断转储——呈现层组件)
var _level_active := false var _level_active := false
@@ -76,6 +78,10 @@ func _ready() -> void:
if not ok: if not ok:
_fatal_screen("数值表加载失败——见输出错误清单(fail-fast)") _fatal_screen("数值表加载失败——见输出错误清单(fail-fast)")
return return
# 存档读盘(存档序列化批):拒读/损坏均已在 SaveManager 内部兜底,失败不阻断启动
# (fatal 只留给数值表——fail-fast 边界在表加载,不在档);高版本拒读态后续写盘被类内拒绝。
save_manager = SaveManagerScript.new()
save_manager.load_from_disk()
_debug = DebugSessionScript.new(db, get_node("/root/RngService"), _debug = DebugSessionScript.new(db, get_node("/root/RngService"),
Callable(self, "_debug_hero_override"), Callable(self, "_seed_value")) Callable(self, "_debug_hero_override"), Callable(self, "_seed_value"))
_debug.battle_created.connect(_on_debug_battle_created) _debug.battle_created.connect(_on_debug_battle_created)
@@ -349,6 +355,11 @@ func _on_adventure_finished(summary: Dictionary) -> void:
_end_overlay.open_defeat(Narrator.adventure_summary_text(summary), _end_overlay.open_defeat(Narrator.adventure_summary_text(summary),
"返回", board_view.get_global_rect()) "返回", board_view.get_global_rect())
logger.log_level_end(summary) # 试玩时间线:冒险终局(摘要随载荷) logger.log_level_end(summary) # 试玩时间线:冒险终局(摘要随载荷)
# 存档入账(存档序列化批):正式冒险收束唯一写档点——域更新原语 + 原子写盘。
# 单关测试入口与调试两层不经过本处理器,天然不写档(契约:测试模式不污染生涯数据)。
if save_manager != null:
save_manager.apply_adventure_summary(summary)
save_manager.write_to_disk()
if runner != null: if runner != null:
runner.restore_hero_base() runner.restore_hero_base()
strip_view.refresh(runner) strip_view.refresh(runner)
@@ -357,7 +368,7 @@ func _on_adventure_finished(summary: Dictionary) -> void:
func _on_battle_end_continue() -> void: func _on_battle_end_continue() -> void:
# 终局出口:返回开始界面(冒险摘要留档试玩日志——钥石持久化归存档/元进度批) # 终局出口:返回开始界面(冒险摘要留档试玩日志;钥石已在收束时入存档——存档序列化批)
_close_modal(_end_overlay) _close_modal(_end_overlay)
_end_overlay.close() _end_overlay.close()
_debug.reset() _debug.reset()
@@ -3106,16 +3106,16 @@ func _smoke_battle_log_view() -> void:
# ---- 存档序列化冒烟(SaveManager——读写往返/新档合法/余额拒花/锻造出域拒/高版本拒读禁写/ # ---- 存档序列化冒烟(SaveManager——读写往返/新档合法/余额拒花/锻造出域拒/高版本拒读禁写/
# 损坏兜底/迁移链/域界干净/摘要 cap)。测试文件统一 user://smoke_save_*.json 隔离,段首清场+ # 损坏兜底/迁移链/域界干净/摘要 cap/摘要集成)。测试文件统一 user://smoke_save_*.json 隔离,
# 段尾清理——绝不碰 user://save.json(红线)。摘要集成断言(run_adventure 驱动,B1 冒险结构批产物) # 段首清场+段尾清理——绝不碰 user://save.json(红线)。
# 待其落 master 后补段。
func _smoke_save() -> void: func _smoke_save() -> void:
print("--- SaveManager 存档读写/迁移/兜底 ---") print("--- SaveManager 存档读写/迁移/兜底 ---")
var sm_script = load("res://core/save_manager.gd") var sm_script = load("res://core/save_manager.gd")
var paths := ["user://smoke_save_roundtrip.json", "user://smoke_save_fresh.json", var paths := ["user://smoke_save_roundtrip.json", "user://smoke_save_fresh.json",
"user://smoke_save_spend.json", "user://smoke_save_highver.json", "user://smoke_save_spend.json", "user://smoke_save_highver.json",
"user://smoke_save_corrupt.json", "user://smoke_save_migrate.json", "user://smoke_save_corrupt.json", "user://smoke_save_migrate.json",
"user://smoke_save_domain.json", "user://smoke_save_cap.json"] "user://smoke_save_domain.json", "user://smoke_save_cap.json",
"user://smoke_save_integration.json"]
var stale: Array = [] var stale: Array = []
for p in paths: for p in paths:
stale.append(p) stale.append(p)
@@ -3220,6 +3220,30 @@ func _smoke_save() -> void:
check(int(arr[0].get("master_seed", -1)) == 11 and int(arr[9].get("master_seed", -1)) == 2, check(int(arr[0].get("master_seed", -1)) == 11 and int(arr[9].get("master_seed", -1)) == 2,
"最新在前(头 seed 11 / 尾 seed 2)") "最新在前(头 seed 11 / 尾 seed 2)")
# ⑨ 摘要集成(冒险结构批产物驱动):run_adventure 真局摘要 → apply 原语 → 域读数 → 写盘读回保持
var autoplay = load("res://smoke/level_autoplay.gd")
var st9: Dictionary = autoplay.run_adventure(_db, 42, 200, 100)
var f9 = sm_script.new(paths[8])
f9.apply_adventure_summary(st9.get("summary", {}))
var kpl := int(_db.adventure.keystone_per_level)
check(String(st9.get("result", "")) == "completed", "摘要集成:冒险驱动满配通关(seed 42)")
check(f9.keystone_balance() == 3 * kpl, "集成钥石余额 = 3 × keystone_per_level(%d)" % f9.keystone_balance())
check(f9.clear_count(1) == 1 and f9.clear_count(2) == 1 and f9.clear_count(3) == 1, "集成通关次数表全 1")
check(f9.boss_first_kill_marked(1) and f9.boss_first_kill_marked(2) and f9.boss_first_kill_marked(3),
"集成三关首杀标记齐")
check(f9.recent_summaries().size() == 1 and int(f9.recent_summaries()[0].get("master_seed", -1)) == 42,
"集成 recent 1 条且 seed 42")
var st9b: Dictionary = autoplay.run_adventure(_db, 42, 200, 100)
f9.apply_adventure_summary(st9b.get("summary", {}))
check(f9.clear_count(1) == 2 and f9.clear_count(2) == 2 and f9.clear_count(3) == 2, "第二局后通关次数全 2")
check(f9.keystone_balance() == 6 * kpl, "第二局后余额翻倍(6 × keystone_per_level)")
check(f9.recent_summaries().size() == 2, "集成 recent 长度 2")
check(f9.write_to_disk(), "集成写盘成功")
var r9 = sm_script.new(paths[8])
check(r9.load_from_disk() and r9.keystone_balance() == 6 * kpl and r9.clear_count(3) == 2
and r9.recent_summaries().size() == 2 and r9.boss_first_kill_marked(3),
"集成写盘读回全量保持")
# 段尾清理(测试文件绝不残留) # 段尾清理(测试文件绝不残留)
for p in stale: for p in stale:
if FileAccess.file_exists(p): if FileAccess.file_exists(p):