feat(迭代23-T5): 存档域 forge_routes v7——六路线等级+直购解锁清单+选中标记(无特效域)+旧档缺键补齐=初始持有态
〔执行序调整:T5 先行于 T3——写口族硬依赖存档域,台账记录〕
- SAVE_FORMAT_VERSION 6→7 + _migrate_v6_v7 空迁移(缺键补齐模式,v3→v4 先例;幂等零漂移)
- _default_data 新域 forge_routes={levels 六键/unlocked 直购清单/weapon+armor 选中};_normalize 经默认模型自动补齐(旧档=初始持有态 sword/light 在档余四锁)+退役键白名单自动收口
- 访问器组:forge_route_level/set_forge_route_level/forge_unlocked_purchases/add_forge_unlock/forge_weapon/set_forge_weapon/forge_armor/set_forge_armor/forge_routes_snapshot(值拷贝出户)
- 解锁态派生不落档:初始持有(表 initial_owned)∨直购(unlocked)∨练满前一档(levels[prereq]≥cap)——派生单点归 T3 MetaProgression
- 冒烟 V8:往返逐域相等+v6 旧档既有域零折算/缺键补齐初始态+域键集恰四项(无特效域)+levels 六键恰全+快照值拷贝;迁移链版本断言 6→7 三处换代
- 轻闸:冒烟 908/0(902 基线 +6)+run_all 23 套全绿
This commit is contained in:
1 parent
00d98b6f74
commit
6def3544a0
2 files changed
+124
-10
No files matched your search
@@ -21,6 +21,10 @@
|
||||
# tree_nodes / fork_sides / tree_struct —— 内容 owner = 天赋树.md(迭代 12 T9 建域、迭代 21 T5
|
||||
# 换代承载:tree_nodes=node_id→已购等级;fork_sides=fork_group→激活态;tree_struct=树结构
|
||||
# 版本标记〔缺失=换代前结构→加载时树域破坏性废弃重置——开发阶段无迁移,Q3 用户裁〕)
|
||||
# forge_routes —— 内容 owner = 锻造工坊.md(迭代 23 T5:levels 六路线等级+unlocked 直购解锁清单
|
||||
# +weapon/armor 当前选中标记;**无特效域**(裁 6——满级特效本期不做);解锁态派生不落档:
|
||||
# 初始持有〔表 initial_owned〕∨ 直购〔unlocked〕∨ 练满前一档〔levels[prereq]≥cap〕——派生单点
|
||||
# 在 MetaProgression.forge_route_unlocked;旧档缺键补齐=初始持有态,开发阶段禁迁移禁折算)
|
||||
# breakpoint.attunement_charges(主动件充能余量)——每冒险重置语义的断点瞬态附随件,内容装配归
|
||||
# AdventureRunner(随 T6 接线入断点),本类照单承载;契约 2 域界注记同断点域豁免先例
|
||||
# recent_summaries(冒险摘要五件事副本)—— 内容 owner = 冒险结构.md(摘要五件事口径)
|
||||
@@ -46,7 +50,7 @@
|
||||
class_name SaveManager
|
||||
extends RefCounted
|
||||
|
||||
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 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 # 最近冒险摘要保留条数(最新在前)
|
||||
|
||||
const TREE_STRUCT_VERSION := 2 # 树结构版本标记(迭代 21 T5——12 节点 v0.8 结构;加载检测缺失
|
||||
@@ -67,6 +71,7 @@ func _init(path: String = "user://save.json") -> void:
|
||||
register_migration(3, _migrate_v3_v4)
|
||||
register_migration(4, _migrate_v4_v5)
|
||||
register_migration(5, _migrate_v5_v6)
|
||||
register_migration(6, _migrate_v6_v7)
|
||||
|
||||
|
||||
# v1→v2:断点域出生(旧档无断点 = 合法空态——「继续上局」隐藏,非损坏非债务)
|
||||
@@ -114,6 +119,14 @@ func _migrate_v5_v6(data: Dictionary) -> Dictionary:
|
||||
return data
|
||||
|
||||
|
||||
# v6→v7:锻造工坊域(迭代 23 T5——meta 新域 forge_routes,旧档缺键由 _normalize 按默认补齐=
|
||||
# 初始持有态〔开发阶段无迁移禁折算——契约 4〕,无结构迁移体;版本号推进仅为 schema_version 对齐
|
||||
# ——v3→v4 先例。幂等性:重复迁移只覆写 version,既有值零改动;加载后 _normalize 补齐缺省——旧档可载可玩)
|
||||
func _migrate_v6_v7(data: Dictionary) -> Dictionary:
|
||||
data["version"] = SAVE_FORMAT_VERSION
|
||||
return data
|
||||
|
||||
|
||||
# ---- 默认数据模型(新档合法态——全零;白板存档 = 完整可玩〔元进度 单局循环零依赖〕)----
|
||||
|
||||
func _default_data() -> Dictionary[String, Variant]:
|
||||
@@ -136,6 +149,12 @@ func _default_data() -> Dictionary[String, Variant]:
|
||||
"adventure_cleared": 0, # 生涯终身计数·通关局数(三关全通才计 1——判定字段 summary.cleared)
|
||||
"vision_used": 0, # 生涯终身计数·真视类工具累计使用次数(迭代 22 T2——信标网络解锁行为条件;
|
||||
# 受理成功唯一计数点=battle 层经冒险层聚合、收束入账;缺键补零不升版本——迭代 21 先例)
|
||||
"forge_routes": { # 锻造工坊域(迭代 23 T5——六路线等级+直购解锁清单+当前选中标记;无特效域)
|
||||
"levels": {"sword": 0, "spear": 0, "staff": 0, "light": 0, "heavy": 0, "ward": 0},
|
||||
"unlocked": [], # 直购解锁路线 id 清单(初始持有与练满前一档=派生解锁不落档)
|
||||
"weapon": "sword", # 当前选中武器路线(初始持有=sword)
|
||||
"armor": "light", # 当前选中甲路线(初始持有=light)
|
||||
},
|
||||
},
|
||||
"breakpoint": {}, # 冒险断点快照(空 = 无断点合法态——「继续上局」隐藏;迭代 8 卡①)
|
||||
}
|
||||
@@ -314,6 +333,65 @@ func set_fork_side(group: String, side: String) -> void:
|
||||
_data["meta"]["fork_sides"] = fs
|
||||
|
||||
|
||||
# ---- 锻造工坊域 forge_routes(迭代 23 T5——MetaProgression 锻造写口读写面;无特效域)----
|
||||
# 解锁态不落档(派生):初始持有=表 initial_owned、练满前一档=levels[prereq]≥cap——判定单点在
|
||||
# MetaProgression.forge_route_unlocked(读表+读档);本类只承载直购清单 unlocked 与选中标记。
|
||||
# 域结构:{levels:{六路线 id→等级}, unlocked:[直购路线 id], weapon:当前武器路线 id, armor:当前甲路线 id}
|
||||
|
||||
func _forge_domain() -> Dictionary:
|
||||
return _data["meta"].get("forge_routes", {}) as Dictionary
|
||||
|
||||
|
||||
func forge_routes_snapshot() -> Dictionary:
|
||||
return _forge_domain().duplicate(true) # 值拷贝出户(本类契约「禁碰内部结构」——消费方直改不得渗入存档内存态)
|
||||
|
||||
|
||||
func forge_route_level(route_id: String) -> int:
|
||||
var lv: Dictionary = _forge_domain().get("levels", {}) as Dictionary
|
||||
return int(lv.get(route_id, 0))
|
||||
|
||||
|
||||
func set_forge_route_level(route_id: String, level: int) -> void:
|
||||
var dom: Dictionary = _forge_domain()
|
||||
var lv: Dictionary = dom.get("levels", {}) as Dictionary
|
||||
lv[route_id] = maxi(0, level)
|
||||
dom["levels"] = lv
|
||||
_data["meta"]["forge_routes"] = dom
|
||||
|
||||
|
||||
func forge_unlocked_purchases() -> Array:
|
||||
return (_forge_domain().get("unlocked", []) as Array).duplicate()
|
||||
|
||||
|
||||
func add_forge_unlock(route_id: String) -> void:
|
||||
var dom: Dictionary = _forge_domain()
|
||||
var un: Array = dom.get("unlocked", []) as Array
|
||||
if not un.has(route_id):
|
||||
un.append(route_id)
|
||||
dom["unlocked"] = un
|
||||
_data["meta"]["forge_routes"] = dom
|
||||
|
||||
|
||||
func forge_weapon() -> String:
|
||||
return String(_forge_domain().get("weapon", "sword"))
|
||||
|
||||
|
||||
func set_forge_weapon(route_id: String) -> void:
|
||||
var dom: Dictionary = _forge_domain()
|
||||
dom["weapon"] = route_id
|
||||
_data["meta"]["forge_routes"] = dom
|
||||
|
||||
|
||||
func forge_armor() -> String:
|
||||
return String(_forge_domain().get("armor", "light"))
|
||||
|
||||
|
||||
func set_forge_armor(route_id: String) -> void:
|
||||
var dom: Dictionary = _forge_domain()
|
||||
dom["armor"] = route_id
|
||||
_data["meta"]["forge_routes"] = dom
|
||||
|
||||
|
||||
# 装配关系(同调目录 item_id 有序数组——装配序即呈现序;跨冒险持久,冒险中途不可换装〔底稿 §8.5〕)。
|
||||
# 局外进度事实(元进度域同族),非契约 2 反面清单的「局内道具库存」——存档序列化.md 契约 2 随批注记。
|
||||
func attunement_equipped() -> Array:
|
||||
|
||||
+45
-9
@@ -5077,7 +5077,8 @@ func _smoke_save() -> void:
|
||||
"user://smoke_save_domain.json", "user://smoke_save_cap.json",
|
||||
"user://smoke_save_integration.json", "user://smoke_save_breakpoint.json",
|
||||
"user://smoke_save_v2compat.json", "user://smoke_save_v5upgrade.json",
|
||||
"user://smoke_save_v5legacy.json", "user://smoke_save_v16discard.json"]
|
||||
"user://smoke_save_v5legacy.json", "user://smoke_save_v16discard.json",
|
||||
"user://smoke_save_forge_v6legacy.json"]
|
||||
var stale: Array = []
|
||||
for p in paths:
|
||||
stale.append(p)
|
||||
@@ -5093,6 +5094,10 @@ func _smoke_save() -> void:
|
||||
w.set_tree_level("sp_hp", 2)
|
||||
w.set_tree_level("sp_hit", 1)
|
||||
w.set_fork_side("fg_posture", "b") # 树域写入(属性锻造域已退役——forge_levels 域清退换代件)
|
||||
w.set_forge_route_level("spear", 3) # 锻造工坊域写入(迭代 23 T5——等级+直购解锁+选中标记)
|
||||
w.add_forge_unlock("spear")
|
||||
w.set_forge_weapon("spear")
|
||||
w.set_forge_armor("ward")
|
||||
w.unlock_item("probe_compass")
|
||||
w.apply_adventure_summary({"result": "failed", "cleared": false, "levels_cleared": 2,
|
||||
"stopped_at": 3, "keystone_total": 3, "master_seed": 99})
|
||||
@@ -5108,6 +5113,10 @@ func _smoke_save() -> void:
|
||||
check(r.keystone_balance() == 10, "钥石余额往返相等(10)")
|
||||
check(r.tree_level("sp_hp") == 2 and r.tree_level("sp_hit") == 1 and r.fork_side("fg_posture") == "b",
|
||||
"树域往返相等(tree_nodes/fork_sides——迭代 21 换代承载)")
|
||||
check(r.forge_route_level("spear") == 3 and r.forge_route_level("sword") == 0
|
||||
and r.forge_unlocked_purchases() == ["spear"]
|
||||
and r.forge_weapon() == "spear" and r.forge_armor() == "ward",
|
||||
"锻造工坊域往返相等(levels/unlocked/weapon/armor——迭代 23 T5)〔锻造工坊批 V8〕")
|
||||
check(r.item_unlocked("probe_compass") and not r.item_unlocked("rope"), "道具解锁往返相等")
|
||||
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),
|
||||
@@ -5162,8 +5171,8 @@ func _smoke_save() -> void:
|
||||
fm.store_string(JSON.stringify({"version": 0, "keystone_old": 12}))
|
||||
fm.close()
|
||||
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)) == 6,
|
||||
"迁移链 0→1(测试钩)→6(生产) 写盘 version == 6(v6=生涯终身计数域,迭代 18 卡④——v5=天赋树域迭代 12 T9)")
|
||||
check(f7.write_to_disk() and int(JSON.parse_string(FileAccess.open(paths[5], FileAccess.READ).get_as_text()).get("version", -1)) == 7,
|
||||
"迁移链 0→1(测试钩)→7(生产) 写盘 version == 7(v7=锻造工坊域,迭代 23 T5——v6=生涯终身计数域迭代 18 卡④)")
|
||||
|
||||
# ⑧ 域界干净:meta 永久进度域不含局内资源键(行动点/RAP/GP/道具库存/生命账本不入档——契约 2;
|
||||
# 迭代 8 卡①修订:扫描收窄到 meta 域——断点域=冒险进行中瞬态快照,生命/GP 按拍板面入域豁免)
|
||||
@@ -5259,9 +5268,9 @@ func _smoke_save() -> void:
|
||||
fold.store_string("{\"version\": 1, \"meta\": {\"keystone_balance\": 4}}")
|
||||
fold.close()
|
||||
check(fo.load_from_disk() and fo.keystone_balance() == 4 and not fo.has_breakpoint(),
|
||||
"v1 旧档迁移 v4:断点域补空(合法空态,无断点域不炸)")
|
||||
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 == 6(v1→v6 链式——生涯终身计数域 2026-10-07)")
|
||||
"v1 旧档迁移 v7:断点域补空(合法空态,无断点域不炸)")
|
||||
check(fo.write_to_disk() and int(JSON.parse_string(FileAccess.open(paths[9], FileAccess.READ).get_as_text()).get("version", -1)) == 7,
|
||||
"v1 旧档迁移后写盘 version == 7(v1→v7 链式——锻造工坊域 2026-10-12)")
|
||||
|
||||
# ⑫ v2 旧档→v3 兼容(迭代 10 g 裁——semver 向后兼容:旧断点零丢失、新代码可续玩)
|
||||
var fv2 = sm_script.new(paths[10])
|
||||
@@ -5288,11 +5297,11 @@ func _smoke_save() -> void:
|
||||
fv5u.close()
|
||||
check(f13.load_from_disk() and f13.adventure_total() == 0 and f13.adventure_cleared() == 0
|
||||
and f13.keystone_balance() == 9,
|
||||
"v5 旧档升 v6:计数域缺省补零且既有余额原样(迁移只增字段不改值)")
|
||||
"v5 旧档升 v7:计数域缺省补零且既有余额原样(迁移只增字段不改值——v5→v6→v7 链式)")
|
||||
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 int(JSON.parse_string(FileAccess.open(paths[11], FileAccess.READ).get_as_text()).get("version", -1)) == 7
|
||||
and f13.adventure_total() == 0 and f13.adventure_cleared() == 0,
|
||||
"升档幂等:v6 档写盘再载零漂移(重复迁移零副作用)")
|
||||
"升档幂等:v7 档写盘再载零漂移(重复迁移零副作用——锻造工坊域同批补齐)")
|
||||
var f13b = sm_script.new(paths[12])
|
||||
f13b.load_from_disk()
|
||||
f13b.apply_adventure_summary({"result": "completed", "cleared": true, "levels_cleared": 3,
|
||||
@@ -5319,6 +5328,33 @@ func _smoke_save() -> void:
|
||||
f15.add_keystone(50)
|
||||
check(mp16.tree_buy("sp_hp") and f15.tree_level("sp_hp") == 1, "废弃重置后后续购买正常受理(无残留脏节点)")
|
||||
|
||||
# ⑯ 锻造工坊域 forge_routes(迭代 23 T5——V8):v6 旧档缺键补齐=初始持有态+无特效域+域界干净
|
||||
var f16 = sm_script.new(paths[14])
|
||||
var fv6 := FileAccess.open(paths[14], FileAccess.WRITE)
|
||||
fv6.store_string(JSON.stringify({"version": 6, "meta": {"keystone_balance": 21,
|
||||
"tree_nodes": {"sp_hp": 1}, "tree_struct": 2, "adventure_total": 4, "adventure_cleared": 1}}))
|
||||
fv6.close()
|
||||
check(f16.load_from_disk() and f16.keystone_balance() == 21
|
||||
and f16.tree_level("sp_hp") == 1 and f16.adventure_total() == 4 and f16.adventure_cleared() == 1,
|
||||
"v6 旧档升 v7:既有域逐位不动(钥石/树/生涯计数零折算——迁移只增不改)〔锻造工坊批 V8〕")
|
||||
check(f16.forge_route_level("sword") == 0 and f16.forge_route_level("spear") == 0
|
||||
and f16.forge_route_level("light") == 0 and f16.forge_route_level("ward") == 0
|
||||
and f16.forge_unlocked_purchases().is_empty()
|
||||
and f16.forge_weapon() == "sword" and f16.forge_armor() == "light",
|
||||
"v6 旧档缺键补齐=初始持有态(六路线 0 级/直购清单空/选中=sword+light)〔锻造工坊批 V8〕")
|
||||
var fr_keys: Array = (f16.forge_routes_snapshot() as Dictionary).keys()
|
||||
fr_keys.sort()
|
||||
check(fr_keys == ["armor", "levels", "unlocked", "weapon"],
|
||||
"forge_routes 域键集恰四项(无特效域——满级特效本期不做,契约 7)〔锻造工坊批 V8/V11〕")
|
||||
var lv_keys: Array = ((f16.forge_routes_snapshot() as Dictionary).get("levels", {}) as Dictionary).keys()
|
||||
lv_keys.sort()
|
||||
check(lv_keys == ["heavy", "light", "spear", "staff", "sword", "ward"],
|
||||
"levels 六路线键集恰全(sword/spear/staff/light/heavy/ward)〔锻造工坊批 V8〕")
|
||||
# 值拷贝出户(改快照不渗档——本类契约)
|
||||
var snap: Dictionary = f16.forge_routes_snapshot()
|
||||
(snap.get("levels", {}) as Dictionary)["sword"] = 99
|
||||
check(f16.forge_route_level("sword") == 0, "forge_routes_snapshot 值拷贝出户(改快照不渗档)〔锻造工坊批 V8〕")
|
||||
|
||||
# ⑭ 收束链入账(迭代 18 卡④ T2——真实收束挂点全链恰一:主流程 adventure_finished →
|
||||
# MetaProgression.settle_adventure → apply_adventure_summary 单口;两夹具经同链入账)
|
||||
var mp13 = load("res://core/meta_progression.gd").new(_db, f13b)
|
||||
|
||||
Reference in new issue
Block a user