④T1: 存档 v6 生涯终身计数域——SAVE_FORMAT_VERSION 5→6(版本史注释随批换代)+meta 两新域 adventure_total〔总局数,开局即计含放弃与失败〕/adventure_cleared〔通关局数,三关全通才计 1——判定字段 summary.cleared 现成直用〕+迁移函数 _migrate_v5_v6(沿现役迁移链 _init 自注册;旧档缺键 _normalize 按默认补零、只增不改=幂等,v3→v4 先例)+只读访问器 adventure_total()/adventure_cleared()+写入单口=apply_adventure_summary 收束口头部自增(三结局同经本口恰计一次,禁第二写入点——契约 2;迁移链陈旧注释 v1→v3/v1→v6 顺手校正)+域清单 owner 注记(生涯.md)+registry save_file 条目 v4→v6 换代(v5 批漏账补齐)+冒烟随批:硬断言「写盘 version==5」两处改 6(迁移链段+v1 旧档段)+①往返段收束自增恰一/往返相等两断言+②新档段缺省全零断言+⑬新增段(v5 旧档升 v6 缺省补零且既有余额原样/升档幂等写盘再载零漂移/通关局 cleared+1 夹具)——冒烟 821/0(基线 814+7)
This commit is contained in:
1 parent
cf2aa03f87
commit
669c1c7d01
3 files changed
+75
-12
No files matched your search
@@ -69,8 +69,8 @@ entities:
|
|||||||
attributes:
|
attributes:
|
||||||
path: user://save.json(单档 MVP——多存档槽位 P2 保留)
|
path: user://save.json(单档 MVP——多存档槽位 P2 保留)
|
||||||
format: JSON 单档(人可读可调试;字典键一律 String)
|
format: JSON 单档(人可读可调试;字典键一律 String)
|
||||||
version: SAVE_FORMAT_VERSION=4(registry schema_version 同源;v4=同调批扩域 2026-10-03 迭代 11 第①批——meta 三新域 boss_kill_count/attunement_equipped/attune_refresh_level + 断点附随件 attunement_charges,旧档缺键由 _normalize 按默认补齐;v3=断点附随件扩列+摘要 keystone_drops 2026-10-01 迭代 10;v2=断点域出生 2026-09-29 迭代 8 卡①;生产迁移链 v1→v4 链式补空;高版本拒读 + 禁写盘 + 内存默认新档运行;低版本读档时链式迁移 register_migration,任一步失败按损坏兜底)
|
version: SAVE_FORMAT_VERSION=6(registry schema_version 同源;v6=生涯终身计数域 2026-10-07 迭代 18 卡④——meta 两新域 adventure_total〔冒险总局数,开局即计含放弃与失败〕/adventure_cleared〔通关局数,三关全通才计 1——判定字段 summary.cleared〕,旧档缺键由 _normalize 按默认补零,收束自增唯一入账=apply_adventure_summary;v5=天赋树域 2026-10-04 迭代 12 T9——meta 三新域 tree_nodes/fork_sides/migration_note;v4=同调批扩域 2026-10-03 迭代 11 第①批——meta 三新域 boss_kill_count/attunement_equipped/attune_refresh_level + 断点附随件 attunement_charges,旧档缺键由 _normalize 按默认补齐;v3=断点附随件扩列+摘要 keystone_drops 2026-10-01 迭代 10;v2=断点域出生 2026-09-29 迭代 8 卡①;生产迁移链 v1→v6 链式补空;高版本拒读 + 禁写盘 + 内存默认新档运行;低版本读档时链式迁移 register_migration,任一步失败按损坏兜底)
|
||||||
domains: meta 域九件——keystone_balance(元进度)/ forge_levels 三档(元进度)/ item_unlocks(元进度)/ level_clear_counts(元进度)/ boss_first_kill(元进度)/ boss_kill_count(秘宝——同调位里程碑计数源,Boss 恒唯一终点故收束入账=levels_cleared)/ attunement_equipped(秘宝——装配关系有序数组,局外进度事实非局内道具库存,装配序即呈现序)/ attune_refresh_level(秘宝——Y 刷新升级购买等级)/ recent_summaries cap 10(冒险结构,元素附 boss_drops 副本);+ breakpoint 域(断点快照:七字段拍板面+附随件〔主种子/取数计数器/钥石累计/已到节点序/道具库存/bonus_* 附随件/attunement_charges 充能余量——迭代 11 第①批 T6〕,空字典=无断点合法态,三结局清除)
|
domains: meta 域十一件——keystone_balance(元进度)/ forge_levels 三档(元进度)/ item_unlocks(元进度)/ level_clear_counts(元进度)/ boss_first_kill(元进度)/ boss_kill_count(秘宝——同调位里程碑计数源,Boss 恒唯一终点故收束入账=levels_cleared)/ attunement_equipped(秘宝——装配关系有序数组,局外进度事实非局内道具库存,装配序即呈现序)/ attune_refresh_level(秘宝——Y 刷新升级购买等级)/ tree_nodes+fork_sides(天赋树——迭代 12 T9 树域)/ adventure_total+adventure_cleared(生涯——终身计数域,迭代 18 卡④,写入唯一经 apply_adventure_summary)/ recent_summaries cap 10(冒险结构,元素附 boss_drops 副本);+ breakpoint 域(断点快照:七字段拍板面+附随件〔主种子/取数计数器/钥石累计/已到节点序/道具库存/bonus_* 附随件/attunement_charges 充能余量——迭代 11 第①批 T6〕,空字典=无断点合法态,三结局清除)
|
||||||
write_timing: 启动读盘;正式冒险收束(adventure_finished → apply_adventure_summary)写盘;营地每笔钥石变更后即时写(元进度实现批接线);冒险选路停点断点快照写盘 + 三结局清断点写盘(迭代 8 卡①);调试两层与单关测试路径(LevelRunner.start_level,仅 QA 工具)不写档
|
write_timing: 启动读盘;正式冒险收束(adventure_finished → apply_adventure_summary)写盘;营地每笔钥石变更后即时写(元进度实现批接线);冒险选路停点断点快照写盘 + 三结局清断点写盘(迭代 8 卡①);调试两层与单关测试路径(LevelRunner.start_level,仅 QA 工具)不写档
|
||||||
atomic_write: tmp + rename 原子写(写一半断电不留烂档——钥石不丢判据承载)
|
atomic_write: tmp + rename 原子写(写一半断电不留烂档——钥石不丢判据承载)
|
||||||
corruption_policy: 坏档改名 <path>.corrupt.bak(已存在覆盖)+ 默认新档继续 + 警告留痕(不静默丢档不 crash)
|
corruption_policy: 坏档改名 <path>.corrupt.bak(已存在覆盖)+ 默认新档继续 + 警告留痕(不静默丢档不 crash)
|
||||||
|
|||||||
@@ -17,6 +17,9 @@
|
|||||||
# breakpoint.attunement_charges(主动件充能余量)——每冒险重置语义的断点瞬态附随件,内容装配归
|
# breakpoint.attunement_charges(主动件充能余量)——每冒险重置语义的断点瞬态附随件,内容装配归
|
||||||
# AdventureRunner(随 T6 接线入断点),本类照单承载;契约 2 域界注记同断点域豁免先例
|
# AdventureRunner(随 T6 接线入断点),本类照单承载;契约 2 域界注记同断点域豁免先例
|
||||||
# recent_summaries(冒险摘要五件事副本)—— 内容 owner = 冒险结构.md(摘要五件事口径)
|
# recent_summaries(冒险摘要五件事副本)—— 内容 owner = 冒险结构.md(摘要五件事口径)
|
||||||
|
# adventure_total / adventure_cleared(生涯终身计数域)—— 内容 owner = 生涯.md(跨局汇总页
|
||||||
|
# 数据源:总局数开局即计〔三结局同经收束恰计一次,含放弃与失败〕/通关局数三关全通才计 1
|
||||||
|
# ——判定字段 summary.cleared;写入唯一经 apply_adventure_summary,迭代 18 卡④)
|
||||||
# breakpoint(冒险断点快照——停点粒度续玩域)—— 内容 owner = 冒险结构.md(七字段拍板面
|
# breakpoint(冒险断点快照——停点粒度续玩域)—— 内容 owner = 冒险结构.md(七字段拍板面
|
||||||
# +恢复必需附随件:主种子/取数计数器/钥石累计/已到节点序/道具库存);契约 2 域界反面清单
|
# +恢复必需附随件:主种子/取数计数器/钥石累计/已到节点序/道具库存);契约 2 域界反面清单
|
||||||
# 只约束 meta 永久进度域——断点域=冒险进行中瞬态快照,生命/GP/道具库存按拍板面入域
|
# 只约束 meta 永久进度域——断点域=冒险进行中瞬态快照,生命/GP/道具库存按拍板面入域
|
||||||
@@ -36,7 +39,7 @@
|
|||||||
class_name SaveManager
|
class_name SaveManager
|
||||||
extends RefCounted
|
extends RefCounted
|
||||||
|
|
||||||
const SAVE_FORMAT_VERSION := 5 # registry schema_version: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 SAVE_FORMAT_VERSION := 6 # registry schema_version: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 # 最近冒险摘要保留条数(最新在前)
|
const SUMMARY_CAP := 10 # 最近冒险摘要保留条数(最新在前)
|
||||||
|
|
||||||
const FORGE_ATTRS := ["max_hp", "atk", "armor_class"] # 属性锻造三档合法键(元进度.md 属性锻造柜台)
|
const FORGE_ATTRS := ["max_hp", "atk", "armor_class"] # 属性锻造三档合法键(元进度.md 属性锻造柜台)
|
||||||
@@ -44,7 +47,7 @@ const FORGE_ATTRS := ["max_hp", "atk", "armor_class"] # 属性锻造三档合
|
|||||||
var _path: String # 存档文件路径(默认 user://save.json;冒烟注入隔离路径)
|
var _path: String # 存档文件路径(默认 user://save.json;冒烟注入隔离路径)
|
||||||
var _read_only := false # 高版本拒读置位——write_to_disk 一律拒绝(契约 4 禁写)
|
var _read_only := false # 高版本拒读置位——write_to_disk 一律拒绝(契约 4 禁写)
|
||||||
var _data: Dictionary[String, Variant] = {} # 内存数据模型(_init 即内置默认;load 后为档案内容)
|
var _data: Dictionary[String, Variant] = {} # 内存数据模型(_init 即内置默认;load 后为档案内容)
|
||||||
var _migrations: Dictionary[int, Callable] = {} # from_version → Callable(data)->data(升一版;v1→v3 生产迁移链现行——_init 自注册)
|
var _migrations: Dictionary[int, Callable] = {} # from_version → Callable(data)->data(升一版;v1→v6 生产迁移链现行——_init 自注册)
|
||||||
|
|
||||||
|
|
||||||
func _init(path: String = "user://save.json") -> void:
|
func _init(path: String = "user://save.json") -> void:
|
||||||
@@ -55,6 +58,7 @@ func _init(path: String = "user://save.json") -> void:
|
|||||||
register_migration(2, _migrate_v2_v3)
|
register_migration(2, _migrate_v2_v3)
|
||||||
register_migration(3, _migrate_v3_v4)
|
register_migration(3, _migrate_v3_v4)
|
||||||
register_migration(4, _migrate_v4_v5)
|
register_migration(4, _migrate_v4_v5)
|
||||||
|
register_migration(5, _migrate_v5_v6)
|
||||||
|
|
||||||
|
|
||||||
# v1→v2:断点域出生(旧档无断点 = 合法空态——「继续上局」隐藏,非损坏非债务)
|
# v1→v2:断点域出生(旧档无断点 = 合法空态——「继续上局」隐藏,非损坏非债务)
|
||||||
@@ -97,6 +101,14 @@ func _migrate_v4_v5(data: Dictionary) -> Dictionary:
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
# v5→v6:生涯终身计数域(迭代 18 卡④——meta 两新域 adventure_total/adventure_cleared,旧档缺键
|
||||||
|
# 由 _normalize 按默认补零,无结构迁移体;版本号推进仅为 schema_version 对齐——v3→v4 先例。
|
||||||
|
# 幂等性:重复迁移只覆写 version,既有值零改动;加载后 _normalize 补齐缺省——旧档可载可玩)
|
||||||
|
func _migrate_v5_v6(data: Dictionary) -> Dictionary:
|
||||||
|
data["version"] = SAVE_FORMAT_VERSION
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
# ---- 默认数据模型(新档合法态——全零;白板存档 = 完整可玩〔元进度 单局循环零依赖〕)----
|
# ---- 默认数据模型(新档合法态——全零;白板存档 = 完整可玩〔元进度 单局循环零依赖〕)----
|
||||||
|
|
||||||
func _default_data() -> Dictionary[String, Variant]:
|
func _default_data() -> Dictionary[String, Variant]:
|
||||||
@@ -115,6 +127,8 @@ func _default_data() -> Dictionary[String, Variant]:
|
|||||||
"fork_sides": {}, # A/B 激活侧(fork_group→"a"|"b"——营地免费切换承载)
|
"fork_sides": {}, # A/B 激活侧(fork_group→"a"|"b"——营地免费切换承载)
|
||||||
"recent_summaries": [],
|
"recent_summaries": [],
|
||||||
"first_adventure_done": false,
|
"first_adventure_done": false,
|
||||||
|
"adventure_total": 0, # 生涯终身计数·冒险总局数(迭代 18 卡④——开局即计含放弃与失败,收束唯一入账)
|
||||||
|
"adventure_cleared": 0, # 生涯终身计数·通关局数(三关全通才计 1——判定字段 summary.cleared)
|
||||||
},
|
},
|
||||||
"breakpoint": {}, # 冒险断点快照(空 = 无断点合法态——「继续上局」隐藏;迭代 8 卡①)
|
"breakpoint": {}, # 冒险断点快照(空 = 无断点合法态——「继续上局」隐藏;迭代 8 卡①)
|
||||||
}
|
}
|
||||||
@@ -191,7 +205,7 @@ func write_to_disk() -> bool:
|
|||||||
|
|
||||||
|
|
||||||
# 迁移注册口:from_version → from_version+1 的升级函数(读档时链式调用)。fn 契约:
|
# 迁移注册口:from_version → from_version+1 的升级函数(读档时链式调用)。fn 契约:
|
||||||
# Dictionary → Dictionary,须写回 version = from_version + 1。生产迁移链现行(v1→v3,_init 自注册),
|
# Dictionary → Dictionary,须写回 version = from_version + 1。生产迁移链现行(v1→v6,_init 自注册),
|
||||||
# 冒烟经本口验证链式升级。
|
# 冒烟经本口验证链式升级。
|
||||||
func register_migration(from_version: int, fn: Callable) -> void:
|
func register_migration(from_version: int, fn: Callable) -> void:
|
||||||
_migrations[from_version] = fn
|
_migrations[from_version] = fn
|
||||||
@@ -251,6 +265,20 @@ func boss_kill_count() -> int:
|
|||||||
return int(_data["meta"].get("boss_kill_count", 0))
|
return int(_data["meta"].get("boss_kill_count", 0))
|
||||||
|
|
||||||
|
|
||||||
|
# ---- 生涯终身计数域(迭代 18 卡④——跨局汇总页数据源;只读面经访问器,
|
||||||
|
# 写入唯一经 apply_adventure_summary 收束口,禁第二写入点)----
|
||||||
|
|
||||||
|
# 冒险总局数(口径=开局即计:三结局 completed/failed/abandoned 同经收束恰计一次,含放弃与失败)
|
||||||
|
func adventure_total() -> int:
|
||||||
|
return int(_data["meta"].get("adventure_total", 0))
|
||||||
|
|
||||||
|
|
||||||
|
# 通关局数(口径=三关全通才计 1——判定字段 summary.cleared,adventure_runner「completed」
|
||||||
|
# 唯一出口=末关通关)
|
||||||
|
func adventure_cleared() -> int:
|
||||||
|
return int(_data["meta"].get("adventure_cleared", 0))
|
||||||
|
|
||||||
|
|
||||||
# ---- 天赋树域(迭代 12 T9——MetaProgression 树段读写面)----
|
# ---- 天赋树域(迭代 12 T9——MetaProgression 树段读写面)----
|
||||||
|
|
||||||
func tree_nodes() -> Dictionary:
|
func tree_nodes() -> Dictionary:
|
||||||
@@ -321,8 +349,13 @@ func recent_summaries() -> Array:
|
|||||||
# 钥石入账(summary.keystone_total——迭代 10 起逐杀掉落已在冒险内计入,boss_drops 参数降级为
|
# 钥石入账(summary.keystone_total——迭代 10 起逐杀掉落已在冒险内计入,boss_drops 参数降级为
|
||||||
# 摘要归档副本不重复入账)+ 逐通关关次 clear_count +1 与 Boss 首杀标记 + Boss 击杀总数累计
|
# 摘要归档副本不重复入账)+ 逐通关关次 clear_count +1 与 Boss 首杀标记 + Boss 击杀总数累计
|
||||||
# (迭代 11 第①批 T2——同调位里程碑计数源)+ 摘要头部插入(cap 10,最新在前,元素附
|
# (迭代 11 第①批 T2——同调位里程碑计数源)+ 摘要头部插入(cap 10,最新在前,元素附
|
||||||
# keystone_drops 副本——复盘可归因)。
|
# keystone_drops 副本——复盘可归因)+ 生涯终身计数自增(迭代 18 卡④——总局数开局即计:
|
||||||
|
# 三结局同经本口恰计一次;通关局数按 summary.cleared 判定三关全通才计 1。终身计数写入
|
||||||
|
# 单口=本函数,禁第二写入点)。
|
||||||
func apply_adventure_summary(summary: Dictionary, boss_drops: Array = []) -> void:
|
func apply_adventure_summary(summary: Dictionary, boss_drops: Array = []) -> void:
|
||||||
|
_data["meta"]["adventure_total"] = adventure_total() + 1
|
||||||
|
if bool(summary.get("cleared", false)):
|
||||||
|
_data["meta"]["adventure_cleared"] = adventure_cleared() + 1
|
||||||
add_keystone(int(summary.get("keystone_total", 0)))
|
add_keystone(int(summary.get("keystone_total", 0)))
|
||||||
var cleared := int(summary.get("levels_cleared", 0))
|
var cleared := int(summary.get("levels_cleared", 0))
|
||||||
for li in range(1, cleared + 1):
|
for li in range(1, cleared + 1):
|
||||||
|
|||||||
+36
-6
@@ -4655,7 +4655,7 @@ func _smoke_battle_log_view() -> void:
|
|||||||
|
|
||||||
|
|
||||||
# ---- 存档序列化冒烟(SaveManager——读写往返/新档合法/余额拒花/锻造出域拒/高版本拒读禁写/
|
# ---- 存档序列化冒烟(SaveManager——读写往返/新档合法/余额拒花/锻造出域拒/高版本拒读禁写/
|
||||||
# 损坏兜底/迁移链/域界干净/摘要 cap/摘要集成)。测试文件统一 user://smoke_save_*.json 隔离,
|
# 损坏兜底/迁移链/域界干净/摘要 cap/摘要集成/生涯终身计数域 v6)。测试文件统一 user://smoke_save_*.json 隔离,
|
||||||
# 段首清场+段尾清理——绝不碰 user://save.json(红线)。
|
# 段首清场+段尾清理——绝不碰 user://save.json(红线)。
|
||||||
func _smoke_save() -> void:
|
func _smoke_save() -> void:
|
||||||
print("--- SaveManager 存档读写/迁移/兜底 ---")
|
print("--- SaveManager 存档读写/迁移/兜底 ---")
|
||||||
@@ -4665,7 +4665,8 @@ func _smoke_save() -> void:
|
|||||||
"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", "user://smoke_save_breakpoint.json",
|
"user://smoke_save_integration.json", "user://smoke_save_breakpoint.json",
|
||||||
"user://smoke_save_v2compat.json"]
|
"user://smoke_save_v2compat.json", "user://smoke_save_v5upgrade.json",
|
||||||
|
"user://smoke_save_v5legacy.json"]
|
||||||
var stale: Array = []
|
var stale: Array = []
|
||||||
for p in paths:
|
for p in paths:
|
||||||
stale.append(p)
|
stale.append(p)
|
||||||
@@ -4687,6 +4688,8 @@ func _smoke_save() -> void:
|
|||||||
"apply 原语入账(钥石 7+3、通关次数 1/1/0)")
|
"apply 原语入账(钥石 7+3、通关次数 1/1/0)")
|
||||||
check(w.boss_first_kill_marked(1) and w.boss_first_kill_marked(2) and not w.boss_first_kill_marked(3),
|
check(w.boss_first_kill_marked(1) and w.boss_first_kill_marked(2) and not w.boss_first_kill_marked(3),
|
||||||
"首杀标记随通关关卡置位(1/2 有、3 无)")
|
"首杀标记随通关关卡置位(1/2 有、3 无)")
|
||||||
|
check(w.adventure_total() == 1 and w.adventure_cleared() == 0,
|
||||||
|
"生涯终身计数随 apply 自增恰一(中止局:总局数 1、通关局数 0——迭代 18 卡④)")
|
||||||
check(w.write_to_disk(), "写盘成功")
|
check(w.write_to_disk(), "写盘成功")
|
||||||
var r = sm_script.new(paths[0])
|
var r = sm_script.new(paths[0])
|
||||||
check(r.load_from_disk(), "读回成功")
|
check(r.load_from_disk(), "读回成功")
|
||||||
@@ -4697,6 +4700,7 @@ func _smoke_save() -> void:
|
|||||||
check(r.clear_count(1) == 1 and r.clear_count(2) == 1 and r.clear_count(3) == 0, "通关次数表往返相等")
|
check(r.clear_count(1) == 1 and r.clear_count(2) == 1 and r.clear_count(3) == 0, "通关次数表往返相等")
|
||||||
check(r.boss_first_kill_marked(1) and r.boss_first_kill_marked(2) and not r.boss_first_kill_marked(3),
|
check(r.boss_first_kill_marked(1) and r.boss_first_kill_marked(2) and not r.boss_first_kill_marked(3),
|
||||||
"首杀标记往返相等")
|
"首杀标记往返相等")
|
||||||
|
check(r.adventure_total() == 1 and r.adventure_cleared() == 0, "生涯终身计数往返相等(1/0)")
|
||||||
var s0: Dictionary = r.recent_summaries()[0]
|
var s0: Dictionary = r.recent_summaries()[0]
|
||||||
check(r.recent_summaries().size() == 1 and int(s0.get("master_seed", -1)) == 99 and int(s0.get("keystone_total", -1)) == 3,
|
check(r.recent_summaries().size() == 1 and int(s0.get("master_seed", -1)) == 99 and int(s0.get("keystone_total", -1)) == 3,
|
||||||
"冒险摘要往返相等(1 条,seed 99)")
|
"冒险摘要往返相等(1 条,seed 99)")
|
||||||
@@ -4706,6 +4710,8 @@ func _smoke_save() -> void:
|
|||||||
check(f2.load_from_disk() and f2.keystone_balance() == 0 and f2.forge_level("atk") == 0
|
check(f2.load_from_disk() and f2.keystone_balance() == 0 and f2.forge_level("atk") == 0
|
||||||
and not f2.item_unlocked("anything") and f2.clear_count(1) == 0 and f2.recent_summaries().is_empty(),
|
and not f2.item_unlocked("anything") and f2.clear_count(1) == 0 and f2.recent_summaries().is_empty(),
|
||||||
"新档合法态(load true + 全零默认)")
|
"新档合法态(load true + 全零默认)")
|
||||||
|
check(f2.adventure_total() == 0 and f2.adventure_cleared() == 0,
|
||||||
|
"新档生涯终身计数缺省全零(迭代 18 卡④两新域——白板存档完整可玩)")
|
||||||
|
|
||||||
# ③ spend 拒绝:余额不足 false 且零副作用(钥石只增不减——消费除外)
|
# ③ spend 拒绝:余额不足 false 且零副作用(钥石只增不减——消费除外)
|
||||||
var f3 = sm_script.new(paths[2])
|
var f3 = sm_script.new(paths[2])
|
||||||
@@ -4748,8 +4754,8 @@ func _smoke_save() -> void:
|
|||||||
fm.store_string(JSON.stringify({"version": 0, "keystone_old": 12}))
|
fm.store_string(JSON.stringify({"version": 0, "keystone_old": 12}))
|
||||||
fm.close()
|
fm.close()
|
||||||
check(f7.load_from_disk() and f7.keystone_balance() == 12, "v0 旧档经迁移链入账(keystone_old 12 → 余额 12)")
|
check(f7.load_from_disk() and f7.keystone_balance() == 12, "v0 旧档经迁移链入账(keystone_old 12 → 余额 12)")
|
||||||
check(f7.write_to_disk() and int(JSON.parse_string(FileAccess.open(paths[5], FileAccess.READ).get_as_text()).get("version", -1)) == 5,
|
check(f7.write_to_disk() and int(JSON.parse_string(FileAccess.open(paths[5], FileAccess.READ).get_as_text()).get("version", -1)) == 6,
|
||||||
"迁移链 0→1(测试钩)→5(生产) 写盘 version == 5(v5=天赋树域,迭代 12 T9——v4 被同调批占用顺延披露)")
|
"迁移链 0→1(测试钩)→6(生产) 写盘 version == 6(v6=生涯终身计数域,迭代 18 卡④——v5=天赋树域迭代 12 T9)")
|
||||||
|
|
||||||
# ⑧ 域界干净:meta 永久进度域不含局内资源键(行动点/RAP/GP/道具库存/生命账本不入档——契约 2;
|
# ⑧ 域界干净:meta 永久进度域不含局内资源键(行动点/RAP/GP/道具库存/生命账本不入档——契约 2;
|
||||||
# 迭代 8 卡①修订:扫描收窄到 meta 域——断点域=冒险进行中瞬态快照,生命/GP 按拍板面入域豁免)
|
# 迭代 8 卡①修订:扫描收窄到 meta 域——断点域=冒险进行中瞬态快照,生命/GP 按拍板面入域豁免)
|
||||||
@@ -4846,8 +4852,8 @@ func _smoke_save() -> void:
|
|||||||
fold.close()
|
fold.close()
|
||||||
check(fo.load_from_disk() and fo.keystone_balance() == 4 and not fo.has_breakpoint(),
|
check(fo.load_from_disk() and fo.keystone_balance() == 4 and not fo.has_breakpoint(),
|
||||||
"v1 旧档迁移 v4:断点域补空(合法空态,无断点域不炸)")
|
"v1 旧档迁移 v4:断点域补空(合法空态,无断点域不炸)")
|
||||||
check(fo.write_to_disk() and int(JSON.parse_string(FileAccess.open(paths[9], FileAccess.READ).get_as_text()).get("version", -1)) == 5,
|
check(fo.write_to_disk() and int(JSON.parse_string(FileAccess.open(paths[9], FileAccess.READ).get_as_text()).get("version", -1)) == 6,
|
||||||
"v1 旧档迁移后写盘 version == 5(v1→v5 链式——天赋树域 2026-10-04)")
|
"v1 旧档迁移后写盘 version == 6(v1→v6 链式——生涯终身计数域 2026-10-07)")
|
||||||
|
|
||||||
# ⑫ v2 旧档→v3 兼容(迭代 10 g 裁——semver 向后兼容:旧断点零丢失、新代码可续玩)
|
# ⑫ v2 旧档→v3 兼容(迭代 10 g 裁——semver 向后兼容:旧断点零丢失、新代码可续玩)
|
||||||
var fv2 = sm_script.new(paths[10])
|
var fv2 = sm_script.new(paths[10])
|
||||||
@@ -4866,6 +4872,30 @@ func _smoke_save() -> void:
|
|||||||
check(adr_v2.resume_adventure(fv2.breakpoint_snapshot(), {"hero_override": {}}),
|
check(adr_v2.resume_adventure(fv2.breakpoint_snapshot(), {"hero_override": {}}),
|
||||||
"v2 旧断点续玩受理(缺 bonus 附随件走常规重建路径——g 裁 semver 口径)")
|
"v2 旧断点续玩受理(缺 bonus 附随件走常规重建路径——g 裁 semver 口径)")
|
||||||
|
|
||||||
|
# ⑬ 生涯终身计数域(迭代 18 卡④):v5 旧档升 v6 缺省补零 + 升档幂等 + 收束自增恰一
|
||||||
|
#(真实旧档形态=计数域两键不存在;升档只增不改——既有 meta 值零触碰,存档红线 S2 断言面)
|
||||||
|
var f13 = sm_script.new(paths[11])
|
||||||
|
var fv5u := FileAccess.open(paths[11], FileAccess.WRITE)
|
||||||
|
fv5u.store_string(JSON.stringify({"version": 5, "meta": {"keystone_balance": 9}}))
|
||||||
|
fv5u.close()
|
||||||
|
check(f13.load_from_disk() and f13.adventure_total() == 0 and f13.adventure_cleared() == 0
|
||||||
|
and f13.keystone_balance() == 9,
|
||||||
|
"v5 旧档升 v6:计数域缺省补零且既有余额原样(迁移只增字段不改值)")
|
||||||
|
check(f13.write_to_disk() and f13.load_from_disk()
|
||||||
|
and int(JSON.parse_string(FileAccess.open(paths[11], FileAccess.READ).get_as_text()).get("version", -1)) == 6
|
||||||
|
and f13.adventure_total() == 0 and f13.adventure_cleared() == 0,
|
||||||
|
"升档幂等:v6 档写盘再载零漂移(重复迁移零副作用)")
|
||||||
|
var f13b = sm_script.new(paths[12])
|
||||||
|
f13b.load_from_disk()
|
||||||
|
f13b.apply_adventure_summary({"result": "completed", "cleared": true, "levels_cleared": 3,
|
||||||
|
"stopped_at": 0, "keystone_total": 0, "master_seed": 1})
|
||||||
|
check(f13b.adventure_total() == 1 and f13b.adventure_cleared() == 1,
|
||||||
|
"收束自增恰一·通关局(三关全通 cleared=true:总局数+1 且通关局数+1)")
|
||||||
|
f13b.apply_adventure_summary({"result": "abandoned", "cleared": false, "levels_cleared": 1,
|
||||||
|
"stopped_at": 2, "keystone_total": 0, "master_seed": 2})
|
||||||
|
check(f13b.adventure_total() == 2 and f13b.adventure_cleared() == 1,
|
||||||
|
"收束自增恰一·中止局(放弃局:仅总局数+1,通关局数不动)")
|
||||||
|
|
||||||
# 段尾清理(测试文件绝不残留)
|
# 段尾清理(测试文件绝不残留)
|
||||||
for p in stale:
|
for p in stale:
|
||||||
if FileAccess.file_exists(p):
|
if FileAccess.file_exists(p):
|
||||||
|
|||||||
Reference in new issue
Block a user