Save The Tree implementation and acceptance work in progress
Preserve production code, regression fixtures, audio assets and provenance, test infrastructure, and the original-completion plan and progress ledger. Final game and Windows acceptance remain pending; excluded local artifacts and native experiments are not part of this snapshot.
This commit is contained in:
+205
-11
@@ -2,6 +2,10 @@ class_name SaveService
|
||||
extends RefCounted
|
||||
|
||||
const FORMAT_VERSION := 1
|
||||
const MIMI_PURCHASE_FORMAT_VERSION := 2
|
||||
const SUPPORTED_CURRENT_FORMAT := -2
|
||||
const PurchaseQuoteV2 := preload("res://scripts/supply/supply_purchase_quote_v2.gd")
|
||||
const PurchaseQuotePlansV2 := preload("res://scripts/supply/supply_auto_purchase_quote_plans.gd")
|
||||
const MAGIC := "TTWB"
|
||||
const WORLD_FILE := "world.bin"
|
||||
const MANIFEST_FILE := "manifest.json"
|
||||
@@ -27,12 +31,15 @@ static func save_snapshot(save_path: String, snapshot: Dictionary, metadata := {
|
||||
var resolved := _resolve_safe_save_path(save_path)
|
||||
if not resolved[&"ok"]:
|
||||
return _complete_timed_operation(resolved, &"save", started_at_us)
|
||||
var selected_format := _snapshot_package_format(snapshot)
|
||||
if selected_format < 0:
|
||||
return _complete_timed_operation(_failure(&"INVALID_SNAPSHOT", "unsupported purchase Runtime/domain"), &"save", started_at_us)
|
||||
var result := _save_snapshot_absolute(
|
||||
resolved[&"path"],
|
||||
save_path,
|
||||
snapshot,
|
||||
metadata,
|
||||
FORMAT_VERSION,
|
||||
selected_format,
|
||||
resolved[&"path"],
|
||||
resolved[&"path"]
|
||||
)
|
||||
@@ -47,7 +54,7 @@ static func load_snapshot(save_path: String) -> Dictionary:
|
||||
var reconcile := _reconcile_interrupted_formal_save(resolved[&"path"])
|
||||
if not reconcile[&"ok"]:
|
||||
return _complete_timed_operation(reconcile, &"load", started_at_us)
|
||||
var result := _load_snapshot_absolute(resolved[&"path"], FORMAT_VERSION)
|
||||
var result := _load_snapshot_absolute(resolved[&"path"], SUPPORTED_CURRENT_FORMAT)
|
||||
result[&"interrupted_save_reconciled"] = bool(reconcile.get(&"reconciled", false))
|
||||
return _complete_timed_operation(result, &"load", started_at_us)
|
||||
|
||||
@@ -60,7 +67,7 @@ static func inspect_snapshot(save_path: String) -> Dictionary:
|
||||
var resolved := _resolve_safe_save_path(save_path)
|
||||
var result: Dictionary = resolved
|
||||
if resolved[&"ok"]:
|
||||
result = _load_snapshot_absolute(resolved[&"path"], FORMAT_VERSION)
|
||||
result = _load_snapshot_absolute(resolved[&"path"], SUPPORTED_CURRENT_FORMAT)
|
||||
result[&"read_only"] = true
|
||||
return _complete_timed_operation(result, &"load", started_at_us)
|
||||
|
||||
@@ -98,6 +105,9 @@ static func save_autosave(
|
||||
if not resolved[&"ok"]:
|
||||
return _complete_timed_operation(resolved, &"save", started_at_us)
|
||||
var target_path: String = resolved[&"path"]
|
||||
var selected_format := _snapshot_package_format(snapshot)
|
||||
if selected_format < 0:
|
||||
return _complete_timed_operation(_failure(&"INVALID_SNAPSHOT", "unsupported purchase Runtime/domain"), &"save", started_at_us)
|
||||
var pending_path := target_path + ".autosave.pending"
|
||||
var reconcile := _reconcile_autosave_rotation(target_path, pending_path, generation_count)
|
||||
if not reconcile[&"ok"]:
|
||||
@@ -137,7 +147,7 @@ static func save_autosave(
|
||||
get_autosave_path(save_path, 0),
|
||||
snapshot,
|
||||
candidate_metadata,
|
||||
FORMAT_VERSION,
|
||||
selected_format,
|
||||
target_path + AUTOSAVE_SUFFIX + "0",
|
||||
target_path,
|
||||
false
|
||||
@@ -201,7 +211,11 @@ static func inspect_recovery_candidates(
|
||||
var formal_reconcile := _reconcile_interrupted_formal_save(target_path)
|
||||
if not formal_reconcile[&"ok"]:
|
||||
return formal_reconcile
|
||||
var migration_reconcile := _reconcile_interrupted_migration(target_path)
|
||||
var migration_reconcile: Dictionary = (
|
||||
formal_reconcile[&"migration_reconciliation"]
|
||||
if formal_reconcile.has(&"migration_reconciliation")
|
||||
else _reconcile_interrupted_migration(target_path)
|
||||
)
|
||||
if not migration_reconcile[&"ok"]:
|
||||
return migration_reconcile
|
||||
var autosave_reconcile := _reconcile_autosave_rotation(
|
||||
@@ -235,7 +249,7 @@ static func inspect_recovery_candidates(
|
||||
var current_schema_valid := false
|
||||
var current_format := false
|
||||
if package_valid:
|
||||
current_format = int(loaded[&"manifest"].get("format_version", -1)) == FORMAT_VERSION
|
||||
current_format = _package_domain_matches(loaded)
|
||||
current_schema_valid = bool(SimulationSnapshot.validate(loaded[&"snapshot"])[&"ok"])
|
||||
var loadable := package_valid and current_format and current_schema_valid
|
||||
var candidate_code: StringName = StringName(loaded[&"code"])
|
||||
@@ -359,7 +373,7 @@ static func _load_recovery_candidate_impl(
|
||||
candidate_path = target_path + AUTOSAVE_SUFFIX + str(generation)
|
||||
else:
|
||||
return _failure(&"UNKNOWN_RECOVERY_CANDIDATE", str(candidate_id))
|
||||
var result := _load_snapshot_absolute(candidate_path, FORMAT_VERSION)
|
||||
var result := _load_snapshot_absolute(candidate_path, SUPPORTED_CURRENT_FORMAT)
|
||||
if not result[&"ok"]:
|
||||
return result
|
||||
result[&"candidate_id"] = candidate_id
|
||||
@@ -396,7 +410,9 @@ static func replace_snapshot_with_backup(save_path: String, expected: Dictionary
|
||||
if not validation[&"ok"]:
|
||||
return _failure(&"MIGRATION_INVALID_SNAPSHOT", ", ".join(validation[&"errors"]))
|
||||
var started := Time.get_ticks_usec()
|
||||
return _complete_timed_operation(_migrate_save_impl(save_path, null, FORMAT_VERSION, expected, candidate), &"save", started)
|
||||
var selected_format := _snapshot_package_format(candidate)
|
||||
if selected_format < 0: return _failure(&"MIGRATION_INVALID_SNAPSHOT", "unsupported purchase Runtime/domain")
|
||||
return _complete_timed_operation(_migrate_save_impl(save_path, null, selected_format, expected, candidate), &"save", started)
|
||||
|
||||
|
||||
static func _migrate_save_impl(
|
||||
@@ -664,6 +680,8 @@ static func _save_snapshot_absolute(
|
||||
identity_source_path := "",
|
||||
use_transaction_marker := true
|
||||
) -> Dictionary:
|
||||
if _snapshot_package_format(snapshot) != format_version:
|
||||
return _failure(&"INVALID_SNAPSHOT", "package/domain format differs")
|
||||
var validation := SimulationSnapshot.validate(snapshot)
|
||||
if not validation[&"ok"]:
|
||||
return _failure(&"INVALID_SNAPSHOT", ", ".join(validation[&"errors"]))
|
||||
@@ -674,6 +692,9 @@ static func _save_snapshot_absolute(
|
||||
return _failure(&"UNSAFE_PATH", "save path contains a symbolic link")
|
||||
var interrupted_reconcile := {&"ok": true, &"reconciled": false, &"action": &"none"}
|
||||
if use_transaction_marker:
|
||||
var format_guard := _guard_ordinary_save_format(target_path, format_version)
|
||||
if not format_guard[&"ok"]:
|
||||
return format_guard
|
||||
interrupted_reconcile = _reconcile_interrupted_formal_save(target_path)
|
||||
if not interrupted_reconcile[&"ok"]:
|
||||
return interrupted_reconcile
|
||||
@@ -754,6 +775,85 @@ static func _save_snapshot_absolute(
|
||||
}
|
||||
|
||||
|
||||
## Ordinary Save must not act as an implicit physical-format migration.
|
||||
## This preflight only reads the formal package and the candidate which the
|
||||
## existing normal-save reconciliation rules could promote. Explicit migration
|
||||
## APIs and autosave rotation keep their own unchanged contracts.
|
||||
static func _guard_ordinary_save_format(target_path: String, requested_format: int) -> Dictionary:
|
||||
var formal_guard := _guard_identified_formal_format(target_path)
|
||||
if not formal_guard[&"ok"]:
|
||||
return formal_guard
|
||||
var marker_path := target_path + SAVE_TRANSACTION_SUFFIX
|
||||
var marker_exists := FileAccess.file_exists(marker_path) or DirAccess.dir_exists_absolute(marker_path)
|
||||
if marker_exists and not _formal_transaction_marker_is_valid(marker_path):
|
||||
return _failure(&"TRANSACTION_MARKER_CONFLICT", marker_path)
|
||||
var identified := _inspect_identified_save_formats(target_path)
|
||||
if not identified[&"ok"]:
|
||||
return identified
|
||||
for existing_format: int in identified[&"format_versions"]:
|
||||
if existing_format != requested_format:
|
||||
return _ordinary_format_migration_required(target_path, existing_format, requested_format)
|
||||
var target_status := _package_status(target_path)
|
||||
if target_status[&"current_loadable"] or (target_status[&"exists"] and not marker_exists):
|
||||
return {&"ok": true, &"code": &"OK"}
|
||||
if target_status[&"exists"] and not _looks_like_save_package(target_path):
|
||||
return _failure(&"FORMAL_RECOVERY_TARGET_CONFLICT", target_path)
|
||||
# Reconciliation chooses a verified .tmp first, then a verified .bak. A
|
||||
# missing/invalid formal does not make a retained old-format source a new slot.
|
||||
for candidate_path: String in [target_path + ".tmp", target_path + ".bak"]:
|
||||
var status := _package_status(candidate_path)
|
||||
if not status[&"current_loadable"]:
|
||||
continue
|
||||
var existing_format := int(status[&"format_version"])
|
||||
if existing_format != requested_format:
|
||||
return _ordinary_format_migration_required(candidate_path, existing_format, requested_format)
|
||||
break
|
||||
return {&"ok": true, &"code": &"OK"}
|
||||
|
||||
|
||||
## A corrupt format1 checksum/manifest still identifies a retained format when
|
||||
## either its bounded manifest number or fixed TTWB header says so. Never use a
|
||||
## schema-looking nested string or coerce a non-integral manifest value.
|
||||
static func _inspect_identified_save_formats(target_path: String) -> Dictionary:
|
||||
var versions: Array[int] = []
|
||||
var manifest_path := target_path.path_join(MANIFEST_FILE)
|
||||
if FileAccess.file_exists(manifest_path):
|
||||
if not _package_file_is_safe_for_read(manifest_path):
|
||||
return _failure(&"UNSAFE_PATH", manifest_path)
|
||||
var manifest_file := FileAccess.open(manifest_path, FileAccess.READ)
|
||||
if manifest_file == null:
|
||||
return _failure(&"INVALID_MANIFEST", error_string(FileAccess.get_open_error()))
|
||||
if manifest_file.get_length() <= MAX_MANIFEST_BYTES:
|
||||
var parser := JSON.new()
|
||||
if parser.parse(manifest_file.get_as_text()) == OK and parser.data is Dictionary:
|
||||
var value: Variant = parser.data.get("format_version", null)
|
||||
if _is_integral_json_number(value, 0, 0xffffffff):
|
||||
versions.append(int(value))
|
||||
manifest_file = null
|
||||
var world_path := target_path.path_join(WORLD_FILE)
|
||||
if FileAccess.file_exists(world_path):
|
||||
if not _package_file_is_safe_for_read(world_path):
|
||||
return _failure(&"UNSAFE_PATH", world_path)
|
||||
var world_file := FileAccess.open(world_path, FileAccess.READ)
|
||||
if world_file == null:
|
||||
return _failure(&"READ_WORLD_FAILED", error_string(FileAccess.get_open_error()))
|
||||
if world_file.get_length() >= 8 and world_file.get_buffer(4).get_string_from_ascii() == MAGIC:
|
||||
var version := world_file.get_32()
|
||||
if version not in versions:
|
||||
versions.append(version)
|
||||
world_file = null
|
||||
return {&"ok": true, &"code": &"OK", &"format_versions": versions}
|
||||
|
||||
|
||||
static func _ordinary_format_migration_required(source_path: String, existing_format: int, requested_format: int) -> Dictionary:
|
||||
return _with_context(_failure(&"FORMAT_MIGRATION_REQUIRED", "%d -> %d requires explicit migration or a new slot" % [existing_format, requested_format]), {
|
||||
&"existing_format_version": existing_format,
|
||||
&"requested_format_version": requested_format,
|
||||
&"migration_source_path": source_path,
|
||||
&"formal_and_artifacts_preserved": true,
|
||||
})
|
||||
|
||||
|
||||
static func _write_package_directory(
|
||||
package_path: String,
|
||||
snapshot: Dictionary,
|
||||
@@ -761,6 +861,8 @@ static func _write_package_directory(
|
||||
format_version: int,
|
||||
logical_target_path := ""
|
||||
) -> Dictionary:
|
||||
if format_version in [FORMAT_VERSION, MIMI_PURCHASE_FORMAT_VERSION] and _snapshot_package_format(snapshot) != format_version:
|
||||
return _failure(&"INVALID_SNAPSHOT", "package/domain format differs")
|
||||
if not _operation_paths_are_safe([package_path]):
|
||||
return _failure(&"UNSAFE_PATH", "package path contains a symbolic link")
|
||||
if not _path_is_missing_or_package(package_path):
|
||||
@@ -812,6 +914,8 @@ static func _write_package_directory(
|
||||
&"checksum_world": checksum,
|
||||
&"uncompressed_size": raw_world.size(),
|
||||
}
|
||||
if format_version == MIMI_PURCHASE_FORMAT_VERSION:
|
||||
manifest[&"runtime_schema_version"] = 15
|
||||
for key: Variant in metadata:
|
||||
if key in [&"thumbnail_png", &"thumbnail_path", &"_thumbnail_png"]:
|
||||
continue
|
||||
@@ -839,7 +943,7 @@ static func _write_package_directory(
|
||||
return {&"ok": true, &"code": &"OK", &"manifest": manifest}
|
||||
|
||||
|
||||
static func _load_snapshot_absolute(
|
||||
static func _load_snapshot_absolute_v1(
|
||||
target_path: String,
|
||||
expected_format_version: int,
|
||||
validate_snapshot := true
|
||||
@@ -932,6 +1036,87 @@ static func _load_snapshot_absolute(
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
## Format choice comes only from the decoded owned Runtime, never from a
|
||||
## recursively found schema-looking string or the duplicate manifest field.
|
||||
static func _snapshot_package_format(snapshot: Dictionary) -> int:
|
||||
var systems: Variant = snapshot.get(&"systems", {})
|
||||
if not systems is Dictionary: return FORMAT_VERSION # existing generic extra payload
|
||||
var runtime_data: Variant = systems.get(&"runtime", null)
|
||||
if runtime_data == null: return FORMAT_VERSION # existing generic envelope
|
||||
if not runtime_data is Dictionary: return FORMAT_VERSION # no owned Runtime authority
|
||||
var version: Variant = runtime_data.get(&"runtime_schema_version", 1)
|
||||
if typeof(version) != TYPE_INT: return -1
|
||||
if version >= 1 and version <= 14:
|
||||
return -1 if PurchaseQuoteV2.has_history(runtime_data) else FORMAT_VERSION
|
||||
if version != 15: return -1
|
||||
if not _canonical_owned_key(snapshot, &"systems") or not _canonical_owned_key(systems, &"runtime"): return -1
|
||||
var clock: Variant = snapshot.get(&"clock")
|
||||
if not clock is Dictionary or typeof(clock.get(&"absolute_minute")) != TYPE_INT: return -1
|
||||
return MIMI_PURCHASE_FORMAT_VERSION if PurchaseQuotePlansV2.valid_runtime_format(runtime_data, clock[&"absolute_minute"]) else -1
|
||||
|
||||
|
||||
static func _canonical_owned_key(owner: Dictionary, key: StringName) -> bool:
|
||||
for raw: Variant in owner:
|
||||
if raw == key: return typeof(raw) == TYPE_STRING_NAME
|
||||
return false
|
||||
|
||||
|
||||
static func _package_domain_matches(loaded: Dictionary) -> bool:
|
||||
if not bool(loaded.get(&"ok", false)): return false
|
||||
var manifest: Dictionary = loaded[&"manifest"]
|
||||
var format_version := int(manifest.get("format_version", -1))
|
||||
if format_version not in [FORMAT_VERSION, MIMI_PURCHASE_FORMAT_VERSION]: return false
|
||||
if format_version != _snapshot_package_format(loaded[&"snapshot"]): return false
|
||||
if format_version == MIMI_PURCHASE_FORMAT_VERSION:
|
||||
var duplicate_version: Variant = manifest.get("runtime_schema_version")
|
||||
return _is_integral_json_number(duplicate_version, 15, 15)
|
||||
return true
|
||||
|
||||
|
||||
static func _load_snapshot_absolute(target_path: String, expected_format_version: int,
|
||||
validate_snapshot := true) -> Dictionary:
|
||||
# -2 means either supported current format; -1 still decodes historical
|
||||
# physical packages for explicit migration/reporting. Positive verification
|
||||
# requests keep their exact original physical-format check.
|
||||
if expected_format_version == SUPPORTED_CURRENT_FORMAT:
|
||||
var guard := _guard_identified_formal_format(target_path)
|
||||
if not guard[&"ok"]: return guard
|
||||
var expected := -1 if expected_format_version == SUPPORTED_CURRENT_FORMAT else expected_format_version
|
||||
var loaded := _load_snapshot_absolute_v1(target_path, expected, validate_snapshot)
|
||||
if not loaded[&"ok"]: return loaded
|
||||
var version := int(loaded[&"manifest"].get("format_version", -1))
|
||||
if version in [FORMAT_VERSION, MIMI_PURCHASE_FORMAT_VERSION]:
|
||||
if not _package_domain_matches(loaded):
|
||||
return _with_context(_failure(&"UNSUPPORTED_FORMAT", "package/domain crosslink differs"), {&"formal_and_artifacts_preserved": true})
|
||||
elif expected_format_version == SUPPORTED_CURRENT_FORMAT:
|
||||
return _failure(&"UNSUPPORTED_FORMAT", str(version))
|
||||
return loaded
|
||||
|
||||
|
||||
static func _guard_identified_formal_format(target_path: String) -> Dictionary:
|
||||
var original := _guard_identified_formal_format_v1(target_path)
|
||||
if not original[&"ok"]:
|
||||
# A checksum-damaged future format remains protected by the original
|
||||
# prefix/manifest probe. Only one coherent, fully decoded known2 passes.
|
||||
if original[&"code"] != &"UNSUPPORTED_FORMAT" \
|
||||
or original.get(&"identified_format_version") != MIMI_PURCHASE_FORMAT_VERSION \
|
||||
or original.get(&"identified_format_versions") != [MIMI_PURCHASE_FORMAT_VERSION]: return original
|
||||
var known := _load_snapshot_absolute_v1(target_path, MIMI_PURCHASE_FORMAT_VERSION, true)
|
||||
return {&"ok": true, &"code": &"OK"} if _package_domain_matches(known) else original
|
||||
# Existing malformed/checksum-broken format1 recovery remains allowed. A
|
||||
# successfully decoded Runtime15 hidden in format1 may not be overwritten.
|
||||
var known := _load_snapshot_absolute_v1(target_path, FORMAT_VERSION, false)
|
||||
if known[&"ok"] and not _package_domain_matches(known):
|
||||
var invalid := _failure(&"UNSUPPORTED_FORMAT", "format1 contains reserved/unsupported Runtime history")
|
||||
invalid[&"identified_format_version"] = FORMAT_VERSION
|
||||
invalid[&"identified_format_versions"] = [FORMAT_VERSION]
|
||||
invalid[&"formal_and_artifacts_preserved"] = true
|
||||
return invalid
|
||||
return original
|
||||
|
||||
|
||||
static func _is_integral_json_number(value: Variant, minimum: int, maximum: int) -> bool:
|
||||
if typeof(value) == TYPE_INT:
|
||||
return value >= minimum and value <= maximum
|
||||
@@ -947,7 +1132,7 @@ static func _world_size_is_supported(size: int) -> bool:
|
||||
## Probe only bounded metadata and the fixed binary prefix. An identifiable
|
||||
## unsupported formal format must be rejected before any transaction artifact
|
||||
## is reconciled, even when its compressed payload/checksum is damaged.
|
||||
static func _guard_identified_formal_format(target_path: String) -> Dictionary:
|
||||
static func _guard_identified_formal_format_v1(target_path: String) -> Dictionary:
|
||||
var versions: Array[int] = []
|
||||
var manifest_path := target_path.path_join(MANIFEST_FILE)
|
||||
if FileAccess.file_exists(manifest_path):
|
||||
@@ -1001,6 +1186,15 @@ static func _reconcile_interrupted_formal_save(target_path: String) -> Dictionar
|
||||
if marker_exists and not _formal_transaction_marker_is_valid(marker_path):
|
||||
return _failure(&"TRANSACTION_MARKER_CONFLICT", marker_path)
|
||||
|
||||
# A held migration source owns the absent/formally promoted target. Normal
|
||||
# .bak is an older ordinary-save generation and must not take that name.
|
||||
# Verify migration artifacts before allowing normal-save reconciliation.
|
||||
if DirAccess.dir_exists_absolute(target_path + ".migration.original"):
|
||||
if marker_exists:
|
||||
return _failure(&"MIGRATION_FORMAL_TRANSACTION_CONFLICT", target_path)
|
||||
var migration := _reconcile_interrupted_migration(target_path)
|
||||
return _with_context(migration, {&"migration_reconciliation": migration.duplicate(true)})
|
||||
|
||||
var target_status := _package_status(target_path)
|
||||
var temporary_status := _package_status(temporary_path)
|
||||
var backup_status := _package_status(backup_path)
|
||||
@@ -1425,7 +1619,7 @@ static func _package_status(path: String) -> Dictionary:
|
||||
&"current_loadable": false,
|
||||
&"code": loaded[&"code"],
|
||||
}
|
||||
var current_format := int(loaded[&"manifest"].get("format_version", -1)) == FORMAT_VERSION
|
||||
var current_format := _package_domain_matches(loaded)
|
||||
var schema_valid := bool(SimulationSnapshot.validate(loaded[&"snapshot"])[&"ok"])
|
||||
return {
|
||||
&"exists": true,
|
||||
|
||||
Reference in New Issue
Block a user