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:
Takeshi
2026-10-11 15:58:00 +09:00
parent 39f6068461
commit b84bade045
235 changed files with 495831 additions and 312 deletions
+30
View File
@@ -17,6 +17,7 @@ func _run() -> void:
_test_connector_access_and_direction()
_test_cache_invalidation_and_revision()
_test_connector_only_snapshot()
_test_cross_layer_reason_and_live_changes()
if _failures == 0:
print("PASS: %d checks" % _checks)
@@ -173,6 +174,35 @@ func _test_connector_only_snapshot() -> void:
)
func _test_cross_layer_reason_and_live_changes() -> void:
var service = PathServiceScript.new()
var start := Vector3i(0, 0, 0)
var bridge_front := Vector3i(1, 0, 0)
var bridge_back := Vector3i(1, 0, 1)
var goal := Vector3i(2, 0, 1)
service.rebuild_graph([start,bridge_front,bridge_back,goal])
service.configure_connector(&"staff_door",bridge_front,bridge_back,120,false,&"staff")
_expect(service.find_path_report(start,goal)[&"reason"] == service.ROUTE_GOAL_UNREACHABLE,"guest cannot cross a staff-only layer door")
var forward: Array[Vector3i] = service.find_path(start,goal,null,[&"staff"])
_expect(forward == [start,bridge_front,bridge_back,goal],"staff retains the exact forward cross-layer path")
_expect(service.find_path_report(goal,start,null,[&"staff"])[&"reason"] == service.ROUTE_GOAL_UNREACHABLE,"one-way layer door remains unreachable in reverse")
service.set_connector_enabled(&"staff_door",false)
_expect(service.find_path_report(start,goal,null,[&"staff"])[&"reason"] == service.ROUTE_CONNECTOR_UNAVAILABLE,"disabled permitted crossing retains connector-unavailable diagnosis")
service.set_connector_enabled(&"staff_door",true)
service.set_cell_temporary_block(bridge_front,true)
_expect(service.find_path_report(start,goal,null,[&"staff"])[&"reason"] == service.ROUTE_TEMPORARILY_BLOCKED,"blocked permitted crossing retains temporary-block diagnosis")
service.set_cell_temporary_block(start,true)
_expect(service.find_path_report(start,goal,null,[&"staff"])[&"reason"] == service.ROUTE_START_BLOCKED,"blocked endpoint precedence is preserved")
_expect(service.find_path_report(start,goal,service.FRONT,[&"staff"])[&"reason"] == service.ROUTE_ACCESS_LAYER_DENIED,"layer restriction still precedes blocked endpoint")
service.set_cell_temporary_block(start,false)
service.set_cell_temporary_block(bridge_front,false)
_expect(service.find_path(start,goal,null,[&"staff"]) == forward,"reenabled and unblocked crossing restores exact path")
service.configure_connector(&"staff_door",bridge_front,bridge_back,120,true,&"staff")
_expect(service.find_path(goal,start,null,[&"staff"]) == [goal,bridge_back,bridge_front,start],"current bidirectional configuration restores the reverse path")
service.remove_connector(&"staff_door")
_expect(service.find_path_report(start,goal,null,[&"staff"])[&"reason"] == service.ROUTE_GOAL_UNREACHABLE,"connector removal immediately restores unreachable result")
func _expect(condition: bool, label: String) -> void:
_checks += 1
if condition:
@@ -44,5 +44,39 @@ func run() -> void:
var restored := PathService.new()
expect(restored.restore_snapshot(valid) and restored.rebuild_graph(cells), "save restores real passenger references")
expect(var_to_bytes(restored.create_snapshot()) == var_to_bytes(valid), "restored full save matches original")
_test_incomplete_runtime_path_save()
print("Path snapshot transit guard: %d checks, %d failures" % [checks, failures])
quit(1 if failures else 0)
func _test_incomplete_runtime_path_save() -> void:
var clock: Variant = load("res://autoload/game_clock.gd").new()
var state: Variant = load("res://autoload/game_state.gd").new()
var runtime: Variant = load("res://autoload/simulation_runtime.gd").new()
clock.reset(1, 6, 0); state.reset(); clock.set_paused(true)
expect(runtime.initialize(clock, state, 806042)[&"valid"], "save boundary Runtime initializes")
runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
var slot := "user://saves/path_transit_guard"
expect(runtime.save_to_path(slot)[&"ok"], "healthy formal save succeeds")
var members: Dictionary = {}
for name: String in ["manifest.json", "world.bin", "thumbnail.png", "journal.log"]:
members[name] = FileAccess.get_file_as_bytes(slot.path_join(name))
runtime.path_service._transit_actor_ids[123] = true
var raw_transit := var_to_bytes(runtime.path_service._transit_actor_ids)
var before := var_to_bytes(runtime.create_snapshot())
var result: Dictionary = runtime.save_to_path(slot)
expect(not result[&"ok"] and result[&"code"] == &"INVALID_SNAPSHOT", "incomplete Path refuses whole Runtime save")
expect(var_to_bytes(runtime.create_snapshot()) == before, "refusal preserves all typed Runtime fields and events")
expect(var_to_bytes(runtime.path_service._transit_actor_ids) == raw_transit, "refusal preserves corrupt IDs instead of repairing")
clock.set_paused(false)
var auto_before := var_to_bytes(runtime.create_snapshot())
var autosave: Dictionary = runtime._perform_checkpoint_save(slot, {}, true, &"autosave", 17)
expect(not autosave[&"ok"] and autosave[&"code"] == &"INVALID_SNAPSHOT", "shared autosave writer refuses incomplete Path")
expect(var_to_bytes(runtime.create_snapshot()) == auto_before and not clock.paused, "autosave refusal preserves running clock and every state/event field")
var unchanged := true
for name: String in members:
unchanged = unchanged and FileAccess.get_file_as_bytes(slot.path_join(name)) == members[name]
expect(unchanged, "refusal preserves every formal member")
expect(not DirAccess.dir_exists_absolute(slot + ".tmp") and not DirAccess.dir_exists_absolute(slot + ".bak"), "refusal creates no new temporary or backup package")
runtime.path_service._transit_actor_ids.erase(123)
expect(runtime.load_from_path(slot)[&"ok"], "original formal save remains loadable after refusal")
runtime.free(); clock.free(); state.free()