extends SceneTree const TEST_PREFIX := "user://saves/s6_runtime_lifecycle" const PRODUCTION_AUTOSAVE_PATH := "user://saves/autosave" var _checks := 0 var _failures := 0 var _clock: Variant var _state: Variant var _runtime: Variant func _initialize() -> void: call_deferred("_run") func _run() -> void: print("S6: runtime save lifecycle and recovery safety") await _test_autoload_isolation_and_main_opt_in() _test_disabled_policy_and_path_timing() _test_queued_checkpoint_at_tick_boundary() _test_daily_checkpoint_dedup_generations_and_failure_preservation() _test_exact_round_trip_recovery_pause_and_performance() _test_domain_invalid_formal_falls_back_to_backup() _test_multiple_domain_invalid_candidates_fall_back_to_autosave() _test_all_domain_invalid_candidates_preserve_complete_live_state() _test_content_filter_cannot_be_weakened() _test_whitelisted_recovery_repairs() _test_non_whitelisted_corruption_is_rejected_atomically() _cleanup_runtime() _cleanup_test_saves() if _failures == 0: print("PASS: %d checks" % _checks) else: push_error("FAIL: %d of %d checks failed" % [_failures, _checks]) quit(_failures) func _test_autoload_isolation_and_main_opt_in() -> void: var app_clock := root.get_node("GameClock") var app_state := root.get_node("GameState") var app_runtime := root.get_node("SimulationRuntime") app_runtime.configure_save_policy({&"enabled": false}) var default_generation := SaveService.get_autosave_path( PRODUCTION_AUTOSAVE_PATH, 0 ) var before := _package_fingerprint(default_generation) app_clock.reset(2, 5, 59) app_state.reset() app_runtime.initialize(app_clock, app_state, 60_001) _expect(not app_runtime.get_save_policy()[&"enabled"], "autoload test runtime starts with disk autosave disabled") app_clock.advance_one_minute() _expect(_package_fingerprint(default_generation) == before, "disabled autoload reaches 06:00 without touching the production autosave") var main := load("res://scenes/main/main.tscn").instantiate() as Control root.add_child(main) await process_frame _expect( app_runtime.get_save_policy()[&"enabled"] and app_runtime.get_save_policy()[&"autosave_path"] == PRODUCTION_AUTOSAVE_PATH, "playable main scene explicitly opts into the production autosave policy" ) app_runtime.configure_save_policy({&"enabled": false}) main.queue_free() await process_frame func _test_disabled_policy_and_path_timing() -> void: _reset_runtime(60_002, 1, 5, 59) var path := TEST_PREFIX + "_disabled" SaveService.delete_save(path) _expect( not _runtime.get_save_policy()[&"enabled"] and _runtime.get_save_policy()[&"autosave_path"] == PRODUCTION_AUTOSAVE_PATH, "direct Runtime instances remain isolated until explicitly enabled" ) _clock.advance_one_minute() _expect( not DirAccess.dir_exists_absolute(ProjectSettings.globalize_path(SaveService.get_autosave_path(path, 0))), "disabled policy writes no implicit autosave" ) var before_count := int( _clock.performance_budget.get_report(&"path_query")[&"lifetime_sample_count"] ) _runtime.path_service.find_path( Vector3i(-2, 0, WorldService.LAYER_FRONT), Vector3i(2, 0, WorldService.LAYER_BACK), [WorldService.LAYER_FRONT, WorldService.LAYER_BACK], [&"staff"] ) var after_count := int( _clock.performance_budget.get_report(&"path_query")[&"lifetime_sample_count"] ) _expect(after_count == before_count + 1, "one public path query records one Runtime performance sample") var snapshot = _runtime.create_snapshot() _expect(_runtime.restore_snapshot(snapshot), "Runtime restores before reconnecting the Path timing boundary") before_count = int(_clock.performance_budget.get_report(&"path_query")[&"lifetime_sample_count"]) _runtime.path_service.find_path(Vector3i(-1, 0, 0), Vector3i(1, 0, 0)) after_count = int(_clock.performance_budget.get_report(&"path_query")[&"lifetime_sample_count"]) _expect(after_count == before_count + 1, "restore reconnects Path timing without duplicate signal delivery") func _test_queued_checkpoint_at_tick_boundary() -> void: _reset_runtime(60_003) var path := TEST_PREFIX + "_queued" SaveService.delete_save(path) var queued = _runtime.queue_manual_checkpoint(path, {&"display_name": "Queued Runtime"}) _expect(queued[&"ok"] and _runtime.get_pending_checkpoint_count() == 1, "manual checkpoint is queued for the next fixed tick boundary") _expect(not DirAccess.dir_exists_absolute(ProjectSettings.globalize_path(path)), "queued checkpoint does not write before its boundary") _clock.advance_one_minute() var loaded := SaveService.load_snapshot(path) _expect(loaded[&"ok"] and _runtime.get_pending_checkpoint_count() == 0, "queued checkpoint writes exactly once at PUBLISH_SNAPSHOT") _expect(int(loaded[&"snapshot"][&"clock"][&"absolute_minute"]) == 361, "queued package captures the completed minute boundary") _expect(_has_domain_event(&"SAVE_CHECKPOINT_SUCCEEDED"), "successful manual checkpoint publishes a DomainEvent") SaveService.delete_save(path) func _test_daily_checkpoint_dedup_generations_and_failure_preservation() -> void: _reset_runtime(60_004, 1, 5, 59) var path := TEST_PREFIX + "_daily" SaveService.delete_save(path) var configured = _runtime.configure_save_policy({ &"enabled": true, &"daily_checkpoint_enabled": true, &"autosave_path": path, &"autosave_generations": 3, }) _expect(configured[&"ok"], "daily Runtime save policy accepts a safe explicit slot") _clock.advance_one_minute() _expect(_runtime.get_last_daily_checkpoint_day() == 1, "day 1 checkpoint runs at exactly 06:00") var generation_zero := SaveService.load_snapshot(SaveService.get_autosave_path(path, 0)) _expect(generation_zero[&"ok"] and int(generation_zero[&"snapshot"][&"clock"][&"absolute_minute"]) == 360, "first daily checkpoint becomes autosave generation zero") _runtime.call("_process_save_checkpoints", 360) _expect(not DirAccess.dir_exists_absolute(ProjectSettings.globalize_path(SaveService.get_autosave_path(path, 1))), "same-day 06:00 callback is deduplicated") _clock.advance_minutes(SimulationKernel.MINUTES_PER_DAY) _clock.advance_minutes(SimulationKernel.MINUTES_PER_DAY) var expected_minutes := [3240, 1800, 360] var all_generations_match := true for generation in range(3): var loaded := SaveService.load_snapshot(SaveService.get_autosave_path(path, generation)) all_generations_match = all_generations_match \ and loaded[&"ok"] \ and int(loaded[&"snapshot"][&"clock"][&"absolute_minute"]) == expected_minutes[generation] _expect(all_generations_match, "daily checkpoints rotate three deterministic newest-first generations") var newest_world := ProjectSettings.globalize_path( SaveService.get_autosave_path(path, 0) ).path_join(SaveService.WORLD_FILE) var newest_before := FileAccess.get_md5(newest_world) var pending_path := ProjectSettings.globalize_path(path + ".autosave.pending") DirAccess.make_dir_recursive_absolute(pending_path) var sentinel := FileAccess.open(pending_path.path_join("not_a_package.txt"), FileAccess.WRITE) sentinel.store_string("block promotion") sentinel = null _clock.advance_minutes(SimulationKernel.MINUTES_PER_DAY) _expect( not _runtime.latest_save_operation_result[&"ok"] and _runtime.get_last_daily_checkpoint_day() == 3, "failed day 4 checkpoint remains eligible for retry instead of being marked complete" ) _expect(FileAccess.get_md5(newest_world) == newest_before, "failed autosave leaves the previous newest generation byte-identical") _expect(_has_domain_event(&"AUTOSAVE_CHECKPOINT_FAILED"), "autosave failure publishes a structured DomainEvent") DirAccess.remove_absolute(pending_path.path_join("not_a_package.txt")) DirAccess.remove_absolute(pending_path) SaveService.delete_save(path) func _test_exact_round_trip_recovery_pause_and_performance() -> void: _reset_runtime(60_005, 3, 7, 15) var path := TEST_PREFIX + "_round_trip" SaveService.delete_save(path) _clock.set_speed(3) var expected_snapshot = _runtime.create_snapshot() var expected_digest := SimulationDigest.compute(expected_snapshot) var first_save = _runtime.save_to_path(path, {&"display_name": "Runtime Exact"}) _expect(first_save[&"ok"] and not _clock.paused, "save pauses I/O temporarily and restores the running clock") _state.add_resource(&"faith", 77) _clock.advance_minutes(5) var loaded = _runtime.load_from_path(path) _expect(loaded[&"ok"] and _runtime.latest_digest == expected_digest, "ordinary Runtime load restores the exact persisted snapshot") _expect(_clock.absolute_minute == int(expected_snapshot[&"clock"][&"absolute_minute"]) and not _clock.paused, "load preserves the saved clock minute and pause state") var minute_before_process = _clock.absolute_minute _expect(_clock.process_real_delta(10.0) == 0 and _clock.absolute_minute == minute_before_process, "first process frame after load never advances an extra minute") var second_save = _runtime.save_to_path(path, {&"display_name": "Runtime New Formal"}) _expect(second_save[&"ok"], "second formal Runtime save creates a verified prior-generation backup") _corrupt_world(path) _state.add_resource(&"faith", 5) var inventory = _runtime.inspect_recovery_candidates(path) _expect(inventory[&"ok"] and inventory[&"selected"][&"id"] == &"backup", "explicit candidate inventory identifies the verified backup") var recovered = _runtime.load_with_recovery(path) _expect(recovered[&"ok"] and recovered[&"selected"][&"id"] == &"backup", "Runtime recovery skips a corrupt formal package") _expect(_runtime.latest_digest == expected_digest, "recovery loads the exact prior formal snapshot without promotion") _expect(recovered[&"recovery_report"][&"restore_committed"] and _has_domain_event(&"SAVE_RECOVERY_LOADED"), "successful recovery returns a structured report and publishes a DomainEvent") var explicit = _runtime.load_recovery_candidate(path, &"backup") _expect(explicit[&"ok"] and explicit[&"read_only"], "caller can explicitly load one named recovery candidate") _expect(int(_clock.performance_budget.get_report(&"save")[&"lifetime_sample_count"]) >= 2, "Runtime records actual save operation timing") _expect(int(_clock.performance_budget.get_report(&"load")[&"lifetime_sample_count"]) >= 3, "Runtime records load, inventory and recovery timing") SaveService.delete_save(path) func _test_domain_invalid_formal_falls_back_to_backup() -> void: _reset_runtime(60_009, 2, 8, 0) var path := TEST_PREFIX + "_domain_backup" SaveService.delete_save(path) var backup_snapshot: Dictionary = _runtime.create_snapshot() var backup_digest := SimulationDigest.compute(backup_snapshot) _expect( SaveService.save_snapshot(path, backup_snapshot)[&"ok"], "domain fallback fixture writes a valid prior formal package" ) var invalid_formal: Dictionary = backup_snapshot.duplicate(true) invalid_formal[&"game_state"][&"resources"][&"gold"] = int( invalid_formal[&"game_state"][&"resources"][&"gold"] ) + 1 _expect( SaveService.save_snapshot(path, invalid_formal)[&"ok"], "checksum-valid but ledger-invalid formal package is preserved as a package" ) _state.add_resource(&"faith", 17) var strict_guard := _authoritative_live_guard() var strict_load: Dictionary = _runtime.load_from_path(path) _expect( not strict_load[&"ok"] and strict_load[&"code"] == &"RESTORE_FAILED", "ordinary load remains strict and does not fall through to a valid backup" ) _expect( _authoritative_live_guard() == strict_guard, "strict ordinary-load rejection preserves authoritative gameplay state" ) var explicit_formal: Dictionary = _runtime.load_recovery_candidate(path, &"formal") _expect( not explicit_formal[&"ok"] and explicit_formal[&"code"] == &"RESTORE_FAILED", "explicit candidate load remains strict to the requested domain-invalid package" ) var recovered: Dictionary = _runtime.load_with_recovery(path) _expect( recovered[&"ok"] and recovered[&"selected"][&"id"] == &"backup", "Runtime rejects a domain-invalid formal package and automatically selects its valid backup" ) _expect( recovered[&"candidates"][0][&"package_status"] == &"VALID" and recovered[&"candidates"][0][&"domain_status"] == &"REJECTED" and recovered[&"candidates"][0][&"domain_rejection_code"] == &"DOMAIN_OR_CROSSLINK_VALIDATION_FAILED" and recovered[&"candidates"][0][&"failed_attempt_live_state_preserved"], "formal candidate report separates successful package verification from domain rejection" ) _expect( recovered[&"candidates"][1][&"domain_status"] == &"VALID" and recovered[&"candidates"][1][&"selected"], "backup candidate report identifies the first domain-valid committed candidate" ) _expect( _runtime.latest_digest == backup_digest, "domain fallback commits the exact backup snapshot" ) _expect( recovered[&"package_candidate_report"][&"selected"][&"id"] == &"formal" and recovered[&"package_recovery_report"][&"source"][&"candidate_id"] == &"backup" and recovered[&"restore_safety_report"][&"recovery_mode"], "result preserves package-level selection evidence and final Runtime restore-safety evidence" ) SaveService.delete_save(path) func _test_multiple_domain_invalid_candidates_fall_back_to_autosave() -> void: _reset_runtime(60_010, 2, 9, 0) var path := TEST_PREFIX + "_domain_autosave" SaveService.delete_save(path) var valid_autosave: Dictionary = _runtime.create_snapshot() var valid_digest := SimulationDigest.compute(valid_autosave) var invalid_backup: Dictionary = valid_autosave.duplicate(true) invalid_backup[&"game_state"][&"resources"][&"gold"] = int( invalid_backup[&"game_state"][&"resources"][&"gold"] ) + 1 var invalid_formal: Dictionary = valid_autosave.duplicate(true) invalid_formal[&"game_state"][&"resources"][&"gold"] = int( invalid_formal[&"game_state"][&"resources"][&"gold"] ) + 2 _expect( SaveService.save_snapshot(path, invalid_backup)[&"ok"] and SaveService.save_snapshot(path, invalid_formal)[&"ok"], "multiple domain-invalid formal generations remain checksum-valid recovery candidates" ) _expect( SaveService.save_autosave(path, valid_autosave, {}, 3)[&"ok"], "valid autosave is written behind both invalid formal generations" ) var package_guard := { &"formal": _package_fingerprint(path), &"backup": _package_fingerprint(path + ".bak"), &"autosave": _package_fingerprint(SaveService.get_autosave_path(path, 0)), } _state.add_resource(&"faith", 23) var recovered: Dictionary = _runtime.load_with_recovery(path) _expect( recovered[&"ok"] and recovered[&"selected"][&"id"] == &"autosave_0", "Runtime walks past two domain-invalid candidates to the newest valid autosave" ) _expect( recovered[&"candidates"][0][&"domain_status"] == &"REJECTED" and recovered[&"candidates"][1][&"domain_status"] == &"REJECTED" and recovered[&"candidates"][0][&"failed_attempt_live_state_preserved"] and recovered[&"candidates"][1][&"failed_attempt_live_state_preserved"] and recovered[&"candidates"][4][&"domain_status"] == &"VALID", "candidate attempts retain both ordered domain rejections and the autosave success" ) _expect( recovered[&"recovery_report"][&"domain_rejections"].size() == 2 and recovered[&"recovery_report"][&"selected_candidate_id"] == &"autosave_0", "combined recovery report records every rejected domain candidate and final selection" ) _expect(_runtime.latest_digest == valid_digest, "autosave fallback restores the exact valid snapshot") _expect( package_guard == { &"formal": _package_fingerprint(path), &"backup": _package_fingerprint(path + ".bak"), &"autosave": _package_fingerprint(SaveService.get_autosave_path(path, 0)), }, "domain fallback remains read-only and never promotes or rewrites any package" ) SaveService.delete_save(path) func _test_all_domain_invalid_candidates_preserve_complete_live_state() -> void: _reset_runtime(60_011, 2, 10, 0) var path := TEST_PREFIX + "_all_domain_invalid" SaveService.delete_save(path) var base: Dictionary = _runtime.create_snapshot() var invalid_backup: Dictionary = base.duplicate(true) invalid_backup[&"game_state"][&"resources"][&"gold"] = int( invalid_backup[&"game_state"][&"resources"][&"gold"] ) + 1 var invalid_formal: Dictionary = base.duplicate(true) invalid_formal[&"game_state"][&"resources"][&"gold"] = int( invalid_formal[&"game_state"][&"resources"][&"gold"] ) + 2 var invalid_autosave: Dictionary = base.duplicate(true) invalid_autosave[&"game_state"][&"resources"][&"gold"] = int( invalid_autosave[&"game_state"][&"resources"][&"gold"] ) + 3 _expect( SaveService.save_snapshot(path, invalid_backup)[&"ok"] and SaveService.save_snapshot(path, invalid_formal)[&"ok"] and SaveService.save_autosave(path, invalid_autosave, {}, 3)[&"ok"], "all-invalid fixture exposes three checksum-valid but domain-invalid candidates" ) _state.add_resource(&"faith", 31) _runtime.event_service.configure_content_filter( EventDefinition.FilterBehavior.DISABLE, EventDefinition.ContentRating.GENERAL, [&"local_atomic_filter"] ) _clock.set_speed(3) _clock.set_paused(true) _expect( _runtime.queue_manual_checkpoint(TEST_PREFIX + "_never_written")[&"ok"], "all-invalid atomicity fixture includes a pending Runtime checkpoint" ) var live_guard := _complete_live_guard() var rejected: Dictionary = _runtime.load_with_recovery(path) _expect( not rejected[&"ok"] and rejected[&"code"] == &"RESTORE_FAILED" and rejected[&"recovery_report"][&"rejection_reason"] == &"NO_DOMAIN_VALID_RECOVERY_CANDIDATE", "all domain-invalid candidates return one explicit terminal recovery failure" ) _expect( rejected[&"recovery_report"][&"domain_rejections"].size() == 3 and rejected[&"selected"].is_empty(), "terminal failure reports all three domain rejections and no selected candidate" ) _expect( _complete_live_guard() == live_guard and rejected[&"live_state_preserved"], "all-candidate failure preserves the complete live snapshot, filter, pause, policy, and pending checkpoint" ) _expect( rejected[&"restore_safety_report"] == rejected[&"recovery_report"] and rejected[&"package_candidate_report"].has(&"candidates"), "terminal failure preserves both package inventory and Runtime restore-safety reports" ) SaveService.delete_save(path) func _test_content_filter_cannot_be_weakened() -> void: _reset_runtime(60_006) var weak_path := TEST_PREFIX + "_weak_filter" var exact_path := TEST_PREFIX + "_exact_filter" SaveService.delete_save(weak_path) SaveService.delete_save(exact_path) _runtime.event_service.configure_content_filter( EventDefinition.FilterBehavior.NONE, EventDefinition.ContentRating.RESTRICTED, [&"saved_tag"] ) _expect(_runtime.save_to_path(weak_path)[&"ok"], "weaker saved content profile fixture is valid") _runtime.event_service.configure_content_filter( EventDefinition.FilterBehavior.DISABLE, EventDefinition.ContentRating.GENERAL, [&"local_tag"] ) var loaded = _runtime.load_from_path(weak_path) var profile = _runtime.event_service.get_content_filter_profile() _expect( loaded[&"ok"] and profile[&"filter_mode"] == EventDefinition.FilterBehavior.DISABLE and profile[&"maximum_content_rating"] == EventDefinition.ContentRating.GENERAL, "loading a weaker save cannot reduce behavior severity or raise the rating ceiling" ) _expect( profile[&"disabled_content_tags"] == [&"local_tag", &"saved_tag"], "load safety unions local and saved disabled content tags" ) var filter_report: Dictionary = loaded[&"restore_safety_report"][&"content_filter"] _expect(filter_report[&"saved_profile_was_weaker"] and filter_report[&"strict_profile_preserved"], "load result explains that the stricter local filter won") var exact_snapshot = _runtime.create_snapshot() var exact_digest := SimulationDigest.compute(exact_snapshot) _expect(_runtime.save_to_path(exact_path)[&"ok"], "effective strict profile saves normally") _runtime.event_service.configure_content_filter( EventDefinition.FilterBehavior.NONE, EventDefinition.ContentRating.RESTRICTED, [] ) var exact_loaded = _runtime.load_from_path(exact_path) _expect(exact_loaded[&"ok"] and _runtime.latest_digest == exact_digest, "ordinary content-profile round trip remains snapshot-exact") SaveService.delete_save(weak_path) SaveService.delete_save(exact_path) func _test_whitelisted_recovery_repairs() -> void: _reset_runtime(60_007) var path := TEST_PREFIX + "_allowed_repairs" SaveService.delete_save(path) var snapshot = _runtime.create_snapshot() var runtime_data: Dictionary = snapshot[&"systems"][&"runtime"] var staff := StaffRuntime.new().configure(&"repair_staff", &"worker", { &"state": StaffRuntime.State.MOVING_TO_TASK, &"current_task_id": &"repair_task", }) var task := StaffTask.new().configure(&"repair_task", &"cleanup", 50, _clock.absolute_minute, { &"state": StaffTask.State.RESERVED, &"assigned_staff_id": &"missing_staff", }) runtime_data[&"staff"][&"staff"].append(staff.create_snapshot()) runtime_data[&"staff"][&"tasks"].append(task.create_snapshot()) _append_staff_path_crosslink(runtime_data, &"repair_staff", Vector3i.ZERO) var terminal := EventInstance.new().configure( &"terminal_foreground", &"relationship.threshold", &"relationship.threshold", [&"player"], 100, _clock.absolute_minute, -1, true, &"" ) terminal.state = EventInstance.State.COMPLETED runtime_data[&"events"][&"instances"].append(terminal.create_snapshot()) runtime_data[&"events"][&"foreground_instance_id"] = "terminal_foreground" runtime_data[&"events"][&"actor_locks"] = {"player": "terminal_foreground"} _expect(SaveService.save_snapshot(path, snapshot)[&"ok"], "checksum-valid orphan repair fixture writes as a package") var recovered = _runtime.load_with_recovery(path) _expect(recovered[&"ok"], "recovery accepts only the whitelisted Task and Event Lock damage") var repaired_task = _runtime.staff_service.get_task(&"repair_task") var repaired_staff = _runtime.staff_service.get_staff(&"repair_staff") _expect( repaired_task != null and repaired_task.state == StaffTask.State.OPEN and repaired_task.assigned_staff_id.is_empty() and repaired_staff != null and repaired_staff.current_task_id.is_empty(), "StaffService audit reopens the orphan Task and releases its stale Staff pointer" ) _expect(_runtime.event_service.get_actor_locks().is_empty() and _runtime.event_service.get_foreground_instance() == null, "terminal foreground recovery releases only its stale Event Locks") var report: Dictionary = recovered[&"recovery_report"] _expect( report[&"repairs"][&"orphan_tasks"][&"reopened_task_ids"] == [&"repair_task"] and report[&"repairs"][&"orphan_tasks"][&"released_staff_ids"] == [&"repair_staff"], "repair report names every repaired Task and Staff exactly" ) _expect( report[&"repairs"][&"orphan_event_locks"][&"released_actor_ids"] == [&"player"] and report[&"repairs"][&"orphan_event_locks"][&"released_foreground_instance_id"] == &"terminal_foreground", "repair report names the released Actor Lock and terminal foreground exactly" ) _expect(report[&"automatic_repairs_applied"] and not report[&"forbidden_repairs_applied"], "recovery report confirms its authoritative whitelist boundary") SaveService.delete_save(path) func _test_non_whitelisted_corruption_is_rejected_atomically() -> void: _reset_runtime(60_008) var built = _runtime.submit_command( &"build_facility", {&"definition_id": &"reception", &"origin": Vector3i(-6, 0, 0), &"rotation": 0}, &"player", &"", 100, -1, &"s6.invalid.fixture.facility" ) _expect(built.is_success(), "invalid recovery fixtures start from a valid constructed facility") _clock.advance_minutes(120) var base = _runtime.create_snapshot() var missing_facility = base.duplicate(true) missing_facility[&"systems"][&"runtime"][&"facilities"][&"instances"] = [] _test_rejected_candidate( TEST_PREFIX + "_reject_missing_facility", missing_facility, "recovery never deletes or invents a missing operational facility" ) var ledger_mutation = base.duplicate(true) ledger_mutation[&"game_state"][&"resources"][&"gold"] = int( ledger_mutation[&"game_state"][&"resources"][&"gold"] ) + 1 _test_rejected_candidate( TEST_PREFIX + "_reject_ledger", ledger_mutation, "recovery never synthesizes Gold or rewrites the ledger projection" ) var route_mismatch = base.duplicate(true) var invalid_relationship := RelationshipState.new().configure( &"invalid_route", &"actor_a", &"actor_b", { &"route_lock": RelationshipState.RouteLock.EXCLUSIVE_LOCKED, &"route_id": &"", } ) route_mismatch[&"systems"][&"runtime"][&"relationships"][&"relationships"].append( invalid_relationship.create_snapshot() ) _test_rejected_candidate( TEST_PREFIX + "_reject_route", route_mismatch, "recovery never guesses an exclusive Relationship route lock" ) var unreleasable = base.duplicate(true) var bad_staff := StaffRuntime.new().configure(&"blocked_staff", &"worker", { &"state": StaffRuntime.State.MOVING_TO_TASK, &"current_task_id": &"blocked_task", }) var bad_task := StaffTask.new().configure(&"blocked_task", &"cleanup", 50, _clock.absolute_minute, { &"state": StaffTask.State.RESERVED, &"assigned_staff_id": &"missing_staff", &"reservation_ids": [&"missing_reservation"], }) unreleasable[&"systems"][&"runtime"][&"staff"][&"staff"].append(bad_staff.create_snapshot()) unreleasable[&"systems"][&"runtime"][&"staff"][&"tasks"].append(bad_task.create_snapshot()) _append_staff_path_crosslink( unreleasable[&"systems"][&"runtime"], &"blocked_staff", Vector3i.ZERO ) _test_rejected_candidate( TEST_PREFIX + "_reject_reservation", unreleasable, "recovery rejects an orphan Task whose inventory Reservation cannot be released" ) func _test_rejected_candidate(path: String, snapshot: Dictionary, label: String) -> void: SaveService.delete_save(path) _expect(SaveService.save_snapshot(path, snapshot)[&"ok"], "%s fixture is checksum-valid" % label) var guard := _authoritative_live_guard() var result = _runtime.load_with_recovery(path) _expect(not result[&"ok"] and result[&"code"] == &"RESTORE_FAILED", label) _expect(_authoritative_live_guard() == guard, "%s and leaves authoritative live state unchanged" % label) _expect( result[&"recovery_report"][&"restore_committed"] == false and not result[&"recovery_report"][&"forbidden_repairs_applied"], "%s reports atomic rejection without forbidden repair" % label ) SaveService.delete_save(path) func _append_staff_path_crosslink( runtime_data: Dictionary, staff_id: StringName, cell: Vector3i ) -> void: var actor_id := StringName("staff:%s" % staff_id) runtime_data[&"path"][&"actor_cells"].append({ &"actor_id": actor_id, &"cell": cell, }) runtime_data[&"path"][&"actor_progress"].append({ &"actor_id": actor_id, &"last_cell": cell, &"last_progress_at": _clock.absolute_minute, &"last_observed_at": _clock.absolute_minute, &"stuck_reported_at": -1, }) runtime_data[&"movement_credits"].append({ &"actor_id": String(actor_id), &"credit": 0, &"updated_at": _clock.absolute_minute, }) for traffic: Dictionary in runtime_data[&"path"][&"cell_traffic"]: if traffic[&"cell"] == cell: traffic[&"current_density"] = int(traffic[&"current_density"]) + 1 break func _authoritative_live_guard() -> Dictionary: return { &"clock": _clock.clock_snapshot(), &"game_state": _state.create_snapshot(), &"economy": _runtime.economy_service.create_snapshot(), &"inventory": _runtime.inventory_service.create_snapshot(), &"facilities": _runtime.facility_service.create_snapshot(), &"construction": _runtime.construction_service.create_snapshot(), &"staff": _runtime.staff_service.create_snapshot(), &"relationships": _runtime.relationship_service.create_snapshot(), &"events": _runtime.event_service.create_snapshot(), } func _complete_live_guard() -> Dictionary: return { &"snapshot": _runtime.create_snapshot(), &"clock_paused": _clock.paused, &"save_policy": _runtime.get_save_policy(), &"pending_checkpoint_count": _runtime.get_pending_checkpoint_count(), &"last_daily_checkpoint_day": _runtime.get_last_daily_checkpoint_day(), &"content_filter": _runtime.event_service.get_content_filter_profile(), } func _reset_runtime(seed: int, day := 1, hour := 6, minute := 0) -> void: _cleanup_runtime() _clock = load("res://autoload/game_clock.gd").new() _state = load("res://autoload/game_state.gd").new() _runtime = load("res://autoload/simulation_runtime.gd").new() _clock.reset(day, hour, minute) _state.reset() var report = _runtime.initialize(_clock, _state, seed) _expect(report[&"valid"], "Runtime fixture initializes deterministic definitions") func _cleanup_runtime() -> void: if _runtime != null: _runtime.free() if _clock != null: _clock.free() if _state != null: _state.free() _runtime = null _clock = null _state = null func _cleanup_test_saves() -> void: for suffix: String in [ "_disabled", "_queued", "_daily", "_round_trip", "_weak_filter", "_exact_filter", "_allowed_repairs", "_reject_missing_facility", "_reject_ledger", "_reject_route", "_reject_reservation", "_domain_backup", "_domain_autosave", "_all_domain_invalid", "_never_written", ]: SaveService.delete_save(TEST_PREFIX + suffix) func _has_domain_event(event_type: StringName) -> bool: for event: SimulationDomainEvent in _runtime.domain_events.recent_events(): if event.event_type == event_type: return true return false func _package_fingerprint(save_path: String) -> Dictionary: var absolute_path := ProjectSettings.globalize_path(save_path) var result := {&"directory_exists": DirAccess.dir_exists_absolute(absolute_path)} for file_name: String in [SaveService.MANIFEST_FILE, SaveService.WORLD_FILE, SaveService.THUMBNAIL_FILE]: var file_path := absolute_path.path_join(file_name) result[file_name] = FileAccess.get_md5(file_path) if FileAccess.file_exists(file_path) else "" return result func _corrupt_world(save_path: String) -> void: var world_path := ProjectSettings.globalize_path(save_path).path_join(SaveService.WORLD_FILE) var bytes := FileAccess.get_file_as_bytes(world_path) if bytes.is_empty(): return bytes[bytes.size() - 1] = bytes[bytes.size() - 1] ^ 0x5a var file := FileAccess.open(world_path, FileAccess.WRITE) file.store_buffer(bytes) func _expect(condition: bool, label: String) -> void: _checks += 1 if condition: print(" ✓ %s" % label) return _failures += 1 push_error(" ✗ %s" % label)