feat: add validated quicksave persistence
This commit is contained in:
@@ -99,6 +99,23 @@ func _run() -> void:
|
||||
"Active position callback should synchronize simulation state"
|
||||
)
|
||||
|
||||
var saved_state: String = manager.serialize_state()
|
||||
var visual_before_restore: Node3D = view.active_npc_visuals[npc.id]
|
||||
npc.hunger = 99.0
|
||||
_check(
|
||||
manager.restore_state_from_json(saved_state),
|
||||
"Visual lifecycle scenario should restore valid state"
|
||||
)
|
||||
var visual_after_restore: Node3D = view.active_npc_visuals[npc.id]
|
||||
_check(
|
||||
visual_after_restore != visual_before_restore,
|
||||
"State restoration should rebuild active NPC presentation"
|
||||
)
|
||||
_check(
|
||||
visual_after_restore.global_position.is_equal_approx(npc.position),
|
||||
"Rebuilt presentation should use the restored authoritative position"
|
||||
)
|
||||
|
||||
if failures.is_empty():
|
||||
print("[TEST] NPC visual lifecycle passed")
|
||||
quit(0)
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
extends SceneTree
|
||||
|
||||
var failures: Array[String] = []
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
func _run() -> void:
|
||||
var test_directory := "user://save_slot_test_%s" % Time.get_ticks_usec()
|
||||
var store := SaveSlotStore.new(test_directory)
|
||||
var manager: Node = load("res://simulation/SimulationManager.gd").new()
|
||||
manager.debug_logs = false
|
||||
root.add_child(manager)
|
||||
manager.set_process(false)
|
||||
|
||||
for step in 8:
|
||||
manager.simulate_tick()
|
||||
var saved_checksum: String = manager.get_state_checksum()
|
||||
_check(store.save(manager), "A valid simulation should save atomically")
|
||||
_check(store.has_slot(), "The validated quicksave should be discoverable")
|
||||
|
||||
for step in 5:
|
||||
manager.simulate_tick()
|
||||
_check(
|
||||
manager.get_state_checksum() != saved_checksum,
|
||||
"The scenario should change after the save point"
|
||||
)
|
||||
_check(store.load_into(manager), "A valid quicksave should load")
|
||||
_check(
|
||||
manager.get_state_checksum() == saved_checksum,
|
||||
"Loading should restore the exact saved simulation"
|
||||
)
|
||||
|
||||
var final_path := store.get_slot_path()
|
||||
var backup_path := final_path + ".bak"
|
||||
_check(
|
||||
DirAccess.rename_absolute(final_path, backup_path) == OK,
|
||||
"Test should reproduce the atomic replacement recovery window"
|
||||
)
|
||||
_check(
|
||||
store.load_into(manager),
|
||||
"A preserved previous save should recover an interrupted replacement"
|
||||
)
|
||||
_check(
|
||||
DirAccess.rename_absolute(backup_path, final_path) == OK,
|
||||
"Test should restore the primary save path"
|
||||
)
|
||||
|
||||
var unchanged_checksum: String = manager.get_state_checksum()
|
||||
var file := FileAccess.open(final_path, FileAccess.WRITE)
|
||||
file.store_string("{\"schema\":\"broken\"}")
|
||||
file.close()
|
||||
_check(not store.load_into(manager), "Invalid save data should be rejected")
|
||||
_check(
|
||||
manager.get_state_checksum() == unchanged_checksum,
|
||||
"A rejected save must not partially mutate the simulation"
|
||||
)
|
||||
_check(
|
||||
not store.save(manager, "../escape"),
|
||||
"Slot names must not escape the save directory"
|
||||
)
|
||||
|
||||
store.delete_slot()
|
||||
DirAccess.remove_absolute(ProjectSettings.globalize_path(test_directory))
|
||||
if failures.is_empty():
|
||||
print("[TEST] Save-slot persistence passed")
|
||||
quit(0)
|
||||
return
|
||||
for failure in failures:
|
||||
push_error("[TEST] " + failure)
|
||||
quit(1)
|
||||
|
||||
func _check(condition: bool, message: String) -> void:
|
||||
if not condition:
|
||||
failures.append(message)
|
||||
@@ -0,0 +1 @@
|
||||
uid://c7ow4dj42sfhl
|
||||
Reference in New Issue
Block a user