extends GutTest const MANAGER_SCRIPT := preload("res://simulation/SimulationManager.gd") var manager: Node func before_each() -> void: manager = MANAGER_SCRIPT.new() add_child_autofree(manager) func test_canonical_manifest_round_trips_defensively() -> void: var local_state: SimulationStateRecord = manager.create_state_record() var regional_facade := RegionalSimulationFacade.create_jajce_route() var manifest := SimulationSaveManifest.create(local_state, regional_facade) assert_not_null(manifest) var saved := manifest.to_dictionary() var expected_fields := [ "local_checksum", "local_state", "regional_checksum", "regional_state", "schema", "schema_version", ] var saved_fields := saved.keys() saved_fields.sort() assert_eq(saved_fields, expected_fields) assert_eq(saved["schema"], SimulationSaveManifest.SCHEMA_NAME) assert_eq(saved["schema_version"], SimulationSaveManifest.SCHEMA_VERSION) assert_eq( saved["local_checksum"], SimulationSaveManifest._checksum_dictionary(saved["local_state"]), ) assert_eq( saved["regional_checksum"], SimulationSaveManifest._checksum_dictionary(saved["regional_state"]), ) var restored := SimulationSaveManifest.from_dictionary(saved) var restored_json := SimulationSaveManifest.from_json(manifest.to_json()) assert_not_null(restored) assert_not_null(restored_json) assert_eq(restored.to_dictionary(), saved) assert_eq(restored.checksum(), manifest.checksum()) assert_eq(restored_json.checksum(), manifest.checksum()) assert_not_same(manifest.get_local_state(), local_state) assert_not_same(manifest.get_regional_facade(), regional_facade) local_state.simulation["tick_count"] = 99 var copied_local := manifest.get_local_state() copied_local.simulation["tick_count"] = 77 assert_eq(int(manifest.get_local_state().simulation["tick_count"]), 0) var copied_regional := manifest.get_regional_facade().to_dictionary() copied_regional["last_processed_tick"] = 88 assert_eq(manifest.get_regional_facade().get_last_processed_tick(), 0) func test_parser_rejects_missing_extra_and_wrong_manifest_fields() -> void: var valid := _valid_manifest_dictionary() for field: String in SimulationSaveManifest.MANIFEST_FIELDS: var missing := valid.duplicate(true) missing.erase(field) assert_null(SimulationSaveManifest.from_dictionary(missing), "Missing %s" % field) var extra := valid.duplicate(true) extra["unexpected"] = true assert_null(SimulationSaveManifest.from_dictionary(extra)) var wrong_schema := valid.duplicate(true) wrong_schema["schema"] = &"the_steward.save_manifest" assert_null(SimulationSaveManifest.from_dictionary(wrong_schema)) var wrong_version := valid.duplicate(true) wrong_version["schema_version"] = "1" assert_null(SimulationSaveManifest.from_dictionary(wrong_version)) var wrong_float_version := valid.duplicate(true) wrong_float_version["schema_version"] = 1.0 assert_null(SimulationSaveManifest.from_dictionary(wrong_float_version)) var wrong_local := valid.duplicate(true) wrong_local["local_state"] = [] assert_null(SimulationSaveManifest.from_dictionary(wrong_local)) var wrong_regional := valid.duplicate(true) wrong_regional["regional_state"] = "regional" assert_null(SimulationSaveManifest.from_dictionary(wrong_regional)) var wrong_local_checksum := valid.duplicate(true) wrong_local_checksum["local_checksum"] = 1 assert_null(SimulationSaveManifest.from_dictionary(wrong_local_checksum)) var wrong_regional_checksum := valid.duplicate(true) wrong_regional_checksum["regional_checksum"] = [] assert_null(SimulationSaveManifest.from_dictionary(wrong_regional_checksum)) assert_null(SimulationSaveManifest.from_json("[]")) assert_null(SimulationSaveManifest.from_json("not json")) func test_nested_tampering_and_checksum_mismatches_fail_closed() -> void: var valid := _valid_manifest_dictionary() var local_checksum_mismatch := valid.duplicate(true) local_checksum_mismatch["local_checksum"] = "0".repeat(64) assert_null(SimulationSaveManifest.from_dictionary(local_checksum_mismatch)) var regional_checksum_mismatch := valid.duplicate(true) regional_checksum_mismatch["regional_checksum"] = "f".repeat(64) assert_null(SimulationSaveManifest.from_dictionary(regional_checksum_mismatch)) var tampered_local := valid.duplicate(true) tampered_local["local_state"]["simulation"]["seed"] = 4004 assert_null(SimulationSaveManifest.from_dictionary(tampered_local)) var tampered_regional := valid.duplicate(true) tampered_regional["regional_state"]["last_processed_tick"] = 1 assert_null(SimulationSaveManifest.from_dictionary(tampered_regional)) var nested_extra := valid.duplicate(true) nested_extra["local_state"]["unexpected"] = true nested_extra["local_checksum"] = SimulationSaveManifest._checksum_dictionary( nested_extra["local_state"] ) assert_null(SimulationSaveManifest.from_dictionary(nested_extra)) assert_null(SimulationSaveManifest.from_json(JSON.stringify(nested_extra))) func test_local_seed_and_tick_require_exact_integer_types() -> void: var valid := _valid_manifest_dictionary() var invalid_boundaries: Array[Dictionary] = [ {"field": "seed", "value": "1337"}, {"field": "seed", "value": 1337.5}, {"field": "tick_count", "value": "0"}, {"field": "tick_count", "value": 0.5}, ] for invalid_boundary: Dictionary in invalid_boundaries: var field := String(invalid_boundary["field"]) var value: Variant = invalid_boundary["value"] var malformed := valid.duplicate(true) malformed["local_state"]["simulation"][field] = value malformed["local_checksum"] = SimulationSaveManifest._checksum_dictionary( malformed["local_state"] ) assert_null( SimulationSaveManifest.from_dictionary(malformed), "Dictionary parser should reject %s=%s" % [field, value], ) assert_null( SimulationSaveManifest.from_json(JSON.stringify(malformed)), "JSON parser should reject %s=%s" % [field, value], ) var local_state: SimulationStateRecord = manager.create_state_record() local_state.simulation[field] = value assert_null( SimulationSaveManifest.create( local_state, RegionalSimulationFacade.create_jajce_route() ), "Manifest creation should reject %s=%s" % [field, value], ) func test_json_parser_rejects_nonnumeric_regional_ledger_values() -> void: var string_ledger := _valid_manifest_dictionary() _set_regional_stockpile_value( string_ledger["regional_state"], RegionalSimulationFacade.SETTLEMENT_TRAVNIK, "4", ) assert_null(SimulationSaveManifest.from_json(JSON.stringify(string_ledger))) var boolean_ledger := _valid_manifest_dictionary() _set_regional_stockpile_value( boolean_ledger["regional_state"], RegionalSimulationFacade.SETTLEMENT_JAJCE, false, ) var canonical_regional_state: Dictionary = boolean_ledger["regional_state"].duplicate(true) _set_regional_stockpile_value( canonical_regional_state, RegionalSimulationFacade.SETTLEMENT_JAJCE, 0.0, ) boolean_ledger["regional_checksum"] = SimulationSaveManifest._checksum_dictionary( canonical_regional_state ) assert_null(SimulationSaveManifest.from_json(JSON.stringify(boolean_ledger))) func test_world_location_and_tick_must_match_the_regional_boundary() -> void: var valid := _valid_manifest_dictionary() var wrong_world := valid.duplicate(true) wrong_world["local_state"]["simulation"]["world_id"] = "regional_elsewhere" wrong_world["local_checksum"] = SimulationSaveManifest._checksum_dictionary( wrong_world["local_state"] ) assert_null(SimulationSaveManifest.from_dictionary(wrong_world)) var wrong_location := valid.duplicate(true) wrong_location["local_state"]["simulation"]["location_id"] = String( RegionalSimulationFacade.LOCATION_TRAVNIK ) wrong_location["local_checksum"] = SimulationSaveManifest._checksum_dictionary( wrong_location["local_state"] ) assert_null(SimulationSaveManifest.from_dictionary(wrong_location)) var wrong_seed := valid.duplicate(true) wrong_seed["local_state"]["simulation"]["seed"] = ( RegionalSimulationFacade.DEFAULT_WORLD_SEED + 1 ) wrong_seed["local_checksum"] = SimulationSaveManifest._checksum_dictionary( wrong_seed["local_state"] ) assert_null(SimulationSaveManifest.from_dictionary(wrong_seed)) var wrong_regional_seed := valid.duplicate(true) wrong_regional_seed["regional_state"]["world_seed"] = ( RegionalSimulationFacade.DEFAULT_WORLD_SEED + 1 ) wrong_regional_seed["regional_checksum"] = SimulationSaveManifest._checksum_dictionary( wrong_regional_seed["regional_state"] ) assert_null(SimulationSaveManifest.from_dictionary(wrong_regional_seed)) var wrong_tick := valid.duplicate(true) wrong_tick["local_state"]["simulation"]["tick_count"] = 1 wrong_tick["local_checksum"] = SimulationSaveManifest._checksum_dictionary( wrong_tick["local_state"] ) assert_null(SimulationSaveManifest.from_dictionary(wrong_tick)) assert_null( SimulationSaveManifest.create( manager.create_state_record(), RegionalSimulationFacade.create_jajce_route( RegionalSimulationFacade.DEFAULT_WORLD_SEED, 1 ) ) ) assert_null( SimulationSaveManifest.create( manager.create_state_record(), RegionalSimulationFacade.create_jajce_route( RegionalSimulationFacade.DEFAULT_WORLD_SEED + 1, 0 ) ) ) var negative_seed_local: SimulationStateRecord = manager.create_state_record() negative_seed_local.simulation["seed"] = -42 assert_not_null( SimulationSaveManifest.create( negative_seed_local, RegionalSimulationFacade.create_jajce_route(-42, 0) ) ) manager.simulate_tick() var synchronized := SimulationSaveManifest.create( manager.create_state_record(), RegionalSimulationFacade.create_jajce_route(RegionalSimulationFacade.DEFAULT_WORLD_SEED, 1) ) assert_not_null(synchronized) assert_eq(synchronized.get_regional_facade().get_last_processed_tick(), 1) assert_eq(int(synchronized.get_local_state().simulation["tick_count"]), 1) func _valid_manifest_dictionary() -> Dictionary: var manifest := SimulationSaveManifest.create( manager.create_state_record(), RegionalSimulationFacade.create_jajce_route() ) assert_not_null(manifest) return manifest.to_dictionary() func _set_regional_stockpile_value( regional_state: Dictionary, settlement_id: StringName, value: Variant ) -> void: for settlement: Dictionary in regional_state["caravan_service"]["world_state"]["settlements"]: if StringName(settlement["settlement_id"]) == settlement_id: settlement["stockpile_ledger"][String(RegionalSimulationFacade.TRADE_GOODS)] = value return assert_true(false, "Expected regional settlement '%s'" % settlement_id)