feat: integrate regional caravan persistence
This commit is contained in:
@@ -17,7 +17,13 @@ func _run() -> void:
|
||||
|
||||
for step in 8:
|
||||
manager.simulate_tick()
|
||||
_check(
|
||||
manager.request_regional_jajce_delivery(3.0),
|
||||
"The quicksave fixture should start one production regional delivery",
|
||||
)
|
||||
var saved_checksum: String = manager.get_state_checksum()
|
||||
var saved_regional_checksum: String = manager.get_regional_state_checksum()
|
||||
var saved_world_checksum: String = manager.get_world_state_checksum()
|
||||
_check(store.save(manager), "A valid simulation should save atomically")
|
||||
_check(store.has_slot(), "The validated quicksave should be discoverable")
|
||||
|
||||
@@ -27,11 +33,28 @@ func _run() -> void:
|
||||
manager.get_state_checksum() != saved_checksum,
|
||||
"The scenario should change after the save point"
|
||||
)
|
||||
var restore_notifications: Array[StringName] = []
|
||||
manager.village_changed.connect(
|
||||
func(_village: SimVillage) -> void: restore_notifications.append(&"village")
|
||||
)
|
||||
manager.state_restored.connect(func() -> void: restore_notifications.append(&"state"))
|
||||
_check(store.load_into(manager), "A valid quicksave should load")
|
||||
_check(
|
||||
restore_notifications == [&"village", &"state"],
|
||||
"A committed combined restore should publish its two notifications exactly once",
|
||||
)
|
||||
_check(
|
||||
manager.get_state_checksum() == saved_checksum,
|
||||
"Loading should restore the exact saved simulation"
|
||||
)
|
||||
_check(
|
||||
manager.get_regional_state_checksum() == saved_regional_checksum,
|
||||
"Loading should restore the exact mid-route regional authority",
|
||||
)
|
||||
_check(
|
||||
manager.get_world_state_checksum() == saved_world_checksum,
|
||||
"Loading should restore both authorities from the same manifest",
|
||||
)
|
||||
|
||||
var final_path := store.get_slot_path()
|
||||
var backup_path := final_path + ".bak"
|
||||
@@ -43,19 +66,187 @@ func _run() -> void:
|
||||
store.load_into(manager),
|
||||
"A preserved previous save should recover an interrupted replacement"
|
||||
)
|
||||
_check(
|
||||
manager.get_world_state_checksum() == saved_world_checksum,
|
||||
"Backup recovery should restore the complete combined manifest",
|
||||
)
|
||||
_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")
|
||||
var legacy_slot := "legacy_v16"
|
||||
var legacy_path := store.get_slot_path(legacy_slot)
|
||||
var legacy_local_checksum: String = manager.get_state_checksum()
|
||||
var legacy_file := FileAccess.open(legacy_path, FileAccess.WRITE)
|
||||
_check(legacy_file != null, "Test should create a raw legacy local save")
|
||||
if legacy_file != null:
|
||||
legacy_file.store_string(manager.serialize_state())
|
||||
legacy_file.close()
|
||||
manager.simulate_tick()
|
||||
_check(
|
||||
manager.get_state_checksum() == unchanged_checksum,
|
||||
"A rejected save must not partially mutate the simulation"
|
||||
store.load_into(manager, legacy_slot),
|
||||
"Existing raw v16 quicksaves should remain loadable",
|
||||
)
|
||||
_check(
|
||||
manager.get_state_checksum() == legacy_local_checksum,
|
||||
"A legacy quicksave should restore its exact local authority",
|
||||
)
|
||||
var legacy_regional := RegionalSimulationFacade.from_dictionary(manager.get_regional_snapshot())
|
||||
_check(legacy_regional != null, "Legacy load should bootstrap a valid regional facade")
|
||||
if legacy_regional != null:
|
||||
var legacy_service := legacy_regional.get_service()
|
||||
var legacy_caravan := legacy_service.get_world_state().get_mobile_group(
|
||||
SimulationIds.REGIONAL_CARAVAN_BLUE
|
||||
)
|
||||
_check(
|
||||
legacy_regional.get_last_processed_tick() == manager.tick_count,
|
||||
"Legacy regional bootstrap should align with the saved local tick",
|
||||
)
|
||||
_check(
|
||||
legacy_regional.get_world_seed() == manager.simulation_seed,
|
||||
"Legacy regional bootstrap should align with the saved world seed",
|
||||
)
|
||||
_check(
|
||||
(
|
||||
legacy_service.get_scheduler().is_empty()
|
||||
and legacy_service.get_event_store().is_empty()
|
||||
),
|
||||
"Legacy load must not invent a historical regional journey",
|
||||
)
|
||||
_check(
|
||||
(
|
||||
legacy_caravan != null
|
||||
and legacy_caravan.is_at_location()
|
||||
and (
|
||||
legacy_caravan.get_current_location_id()
|
||||
== SimulationIds.REGIONAL_LOCATION_TRAVNIK
|
||||
)
|
||||
),
|
||||
"Legacy load should place the idle authored caravan at Travnik",
|
||||
)
|
||||
store.delete_slot(legacy_slot)
|
||||
|
||||
var custom_adapter := ActiveWorldAdapter.new()
|
||||
_check(
|
||||
custom_adapter.configure_context_identity(
|
||||
&"save_test_context", &"save_test_world", &"save_test_location"
|
||||
),
|
||||
"The custom-scope fixture should configure an independent active context",
|
||||
)
|
||||
root.add_child(custom_adapter)
|
||||
var custom_manager: Node = load("res://simulation/SimulationManager.gd").new()
|
||||
custom_manager.debug_logs = false
|
||||
custom_manager.active_world_adapter = custom_adapter
|
||||
root.add_child(custom_manager)
|
||||
custom_manager.set_process(false)
|
||||
var custom_slot := "custom_scope"
|
||||
var custom_saved_local_checksum: String = custom_manager.get_state_checksum()
|
||||
var custom_saved_regional_checksum: String = custom_manager.get_regional_state_checksum()
|
||||
_check(
|
||||
not custom_manager.uses_combined_save_manifest(),
|
||||
"A non-Jajce adapter scope should keep the supported local-v16 save boundary",
|
||||
)
|
||||
_check(
|
||||
custom_manager.can_serialize_local_state_only(),
|
||||
"A custom scope should allow local-only saving while regional state is reconstructibly idle",
|
||||
)
|
||||
_check(
|
||||
(
|
||||
not custom_manager.request_regional_jajce_delivery(1.0)
|
||||
and custom_manager.get_regional_state_checksum() == custom_saved_regional_checksum
|
||||
),
|
||||
"A non-Jajce adapter scope must reject Jajce regional mutation without changing authority",
|
||||
)
|
||||
_check(
|
||||
store.save(custom_manager, custom_slot),
|
||||
"A supported non-Jajce adapter scope should still save through SaveSlotStore",
|
||||
)
|
||||
var custom_file := FileAccess.open(store.get_slot_path(custom_slot), FileAccess.READ)
|
||||
_check(custom_file != null, "The custom-scope slot should be readable")
|
||||
if custom_file != null:
|
||||
var custom_json := custom_file.get_as_text()
|
||||
custom_file.close()
|
||||
_check(
|
||||
(
|
||||
SimulationSaveManifest.from_json(custom_json) == null
|
||||
and SimulationStateRecord.from_json(custom_json) != null
|
||||
),
|
||||
"A non-Jajce adapter scope should persist the explicit raw local record",
|
||||
)
|
||||
custom_manager.simulate_tick()
|
||||
_check(
|
||||
store.load_into(custom_manager, custom_slot),
|
||||
"The custom-scope raw local record should remain loadable",
|
||||
)
|
||||
_check(
|
||||
custom_manager.get_state_checksum() == custom_saved_local_checksum,
|
||||
"Custom-scope loading should restore the exact local authority",
|
||||
)
|
||||
_check(
|
||||
custom_manager.get_regional_state_checksum() == custom_saved_regional_checksum,
|
||||
"Custom-scope loading should reconstruct the exact idle regional facade",
|
||||
)
|
||||
var rejected_restore_notifications: Array[StringName] = []
|
||||
custom_manager.village_changed.connect(
|
||||
func(_village: SimVillage) -> void: rejected_restore_notifications.append(&"village")
|
||||
)
|
||||
custom_manager.state_restored.connect(
|
||||
func() -> void: rejected_restore_notifications.append(&"state")
|
||||
)
|
||||
var custom_before_rejected_local: String = custom_manager.get_state_checksum()
|
||||
var custom_before_rejected_regional: String = custom_manager.get_regional_state_checksum()
|
||||
_check(
|
||||
not custom_manager.restore_save_manifest(manager.create_save_manifest()),
|
||||
"A Jajce manifest should be rejected by a different active adapter scope",
|
||||
)
|
||||
_check(
|
||||
rejected_restore_notifications.is_empty(),
|
||||
"A rejected combined restore and its rollback must publish no success notifications",
|
||||
)
|
||||
_check(
|
||||
(
|
||||
custom_manager.get_state_checksum() == custom_before_rejected_local
|
||||
and custom_manager.get_regional_state_checksum() == custom_before_rejected_regional
|
||||
),
|
||||
"A rejected combined restore should roll back both custom-scope authorities",
|
||||
)
|
||||
_check(
|
||||
custom_manager._regional_simulation.depart_jajce_delivery(1.0),
|
||||
"The fail-closed fixture should create otherwise unreachable live regional state",
|
||||
)
|
||||
var custom_live_regional_checksum: String = custom_manager.get_regional_state_checksum()
|
||||
_check(
|
||||
not custom_manager.can_serialize_local_state_only(),
|
||||
"Live regional state must not be treated as reconstructible local-only authority",
|
||||
)
|
||||
_check(
|
||||
not store.save(custom_manager, "custom_live"),
|
||||
"SaveSlotStore must reject a local-only save that would discard live regional state",
|
||||
)
|
||||
_check(
|
||||
custom_manager.get_regional_state_checksum() == custom_live_regional_checksum,
|
||||
"Rejecting the lossy custom-scope save must not mutate regional authority",
|
||||
)
|
||||
store.delete_slot(custom_slot)
|
||||
|
||||
var unchanged_world_checksum: String = manager.get_world_state_checksum()
|
||||
var rejected_manifest: Dictionary = manager.create_save_manifest().to_dictionary()
|
||||
rejected_manifest["regional_state"]["active_location_id"] = String(
|
||||
SimulationIds.REGIONAL_LOCATION_TRAVNIK
|
||||
)
|
||||
rejected_manifest["regional_checksum"] = SimulationSaveManifest._checksum_dictionary(
|
||||
rejected_manifest["regional_state"]
|
||||
)
|
||||
var file := FileAccess.open(final_path, FileAccess.WRITE)
|
||||
_check(file != null, "Test should open the primary save for corruption")
|
||||
if file != null:
|
||||
file.store_string(JSON.stringify(rejected_manifest))
|
||||
file.close()
|
||||
_check(not store.load_into(manager), "Invalid regional manifest data should be rejected")
|
||||
_check(
|
||||
manager.get_world_state_checksum() == unchanged_world_checksum,
|
||||
"A rejected save must not partially mutate either authority",
|
||||
)
|
||||
_check(not store.save(manager, "../escape"), "Slot names must not escape the save directory")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user