feat: chunk regional simulation state

This commit is contained in:
Rijad Zuzo
2026-08-12 22:02:40 +02:00
parent acffe6aa0f
commit e85ee27395
4 changed files with 1230 additions and 0 deletions
@@ -0,0 +1,281 @@
extends GutTest
const WORLD_ID := &"regional_world"
const ORIGIN_LOCATION_ID := &"location_origin"
const DESTINATION_LOCATION_ID := &"location_destination"
const ORIGIN_SETTLEMENT_ID := &"settlement_origin"
const DESTINATION_SETTLEMENT_ID := &"settlement_destination"
const ROUTE_ID := &"route_trade"
const DEPARTURE_TICK := 10
const ARRIVAL_TICK := 15
func test_mid_route_round_trip_preserves_exact_envelope_and_continuation() -> void:
var service := _fixture_service(1)
assert_true(_depart_group(service, 0))
var expected_envelope := service.to_dictionary()
var archive := RegionalChunkedPersistence.capture(service, 1)
assert_not_null(archive)
assert_eq(archive.get_manifest()["envelope_checksum"], service.checksum())
assert_eq(archive.get_chunk_ids_for_type(RegionalChunkedPersistence.CHUNK_EVENTS).size(), 2)
var restored := archive.restore_service()
assert_not_null(restored)
assert_eq(restored.to_dictionary(), expected_envelope)
assert_eq(restored.checksum(), service.checksum())
assert_eq(service.process_due(ARRIVAL_TICK, 1).size(), 1)
assert_eq(restored.process_due(ARRIVAL_TICK, 1).size(), 1)
assert_eq(restored.to_dictionary(), service.to_dictionary())
func test_active_location_metadata_does_not_hydrate_or_discard_unloaded_authority() -> void:
var service := _fixture_service(2)
var archive := RegionalChunkedPersistence.capture(service)
assert_not_null(archive)
var archive_before := archive.to_dictionary()
var partial := archive.load_active_location_metadata(ORIGIN_LOCATION_ID)
assert_false(partial.is_empty())
assert_eq(
partial["loaded_chunk_ids"],
[
RegionalChunkedPersistence.GLOBAL_CHUNK_ID,
"location:%s" % ORIGIN_LOCATION_ID,
]
)
assert_eq(partial["mobile_group_refs"].size(), 2)
assert_true("scheduler:regional" in partial["unloaded_chunk_ids"])
assert_true("mobile_group:group_00" in partial["unloaded_chunk_ids"])
assert_false((partial["global_index"] as Dictionary).has("mobile_groups"))
(partial["active_location"] as Dictionary)["settlements"] = []
(partial["unloaded_chunk_ids"] as Array).clear()
assert_eq(archive.to_dictionary(), archive_before)
assert_eq(archive.restore_service().checksum(), service.checksum())
func test_missing_duplicate_tampered_and_noncanonical_chunks_are_rejected() -> void:
var archive := RegionalChunkedPersistence.capture(_fixture_service(1))
assert_not_null(archive)
var missing := archive.to_dictionary()
(missing["chunks"] as Array).pop_back()
assert_null(RegionalChunkedPersistence.from_dictionary(missing))
var duplicate := archive.to_dictionary()
var duplicated_chunk: Dictionary = (duplicate["chunks"] as Array)[-1].duplicate(true)
var duplicated_descriptor: Dictionary = (
((duplicate["manifest"] as Dictionary)["chunks"] as Array)[-1].duplicate(true)
)
(duplicate["chunks"] as Array).append(duplicated_chunk)
((duplicate["manifest"] as Dictionary)["chunks"] as Array).append(duplicated_descriptor)
(duplicate["manifest"] as Dictionary)["chunk_count"] = ((duplicate["chunks"] as Array).size())
assert_null(RegionalChunkedPersistence.from_dictionary(duplicate))
var tampered_payload := archive.to_dictionary()
var origin_chunk := _find_chunk(tampered_payload, "location:%s" % ORIGIN_LOCATION_ID)
(origin_chunk["payload"] as Dictionary)["location_id"] = "location_forged"
assert_null(RegionalChunkedPersistence.from_dictionary(tampered_payload))
var tampered_checksum := archive.to_dictionary()
var descriptors: Array = (tampered_checksum["manifest"] as Dictionary)["chunks"]
descriptors[0]["checksum"] = "forged"
assert_null(RegionalChunkedPersistence.from_dictionary(tampered_checksum))
var self_consistent_but_noncanonical := archive.to_dictionary()
var noncanonical_chunk := _find_chunk(
self_consistent_but_noncanonical, "location:%s" % ORIGIN_LOCATION_ID
)
(noncanonical_chunk["payload"] as Dictionary)["ignored_authority"] = true
_refresh_chunk_descriptor(self_consistent_but_noncanonical, noncanonical_chunk)
assert_null(RegionalChunkedPersistence.from_dictionary(self_consistent_but_noncanonical))
var noncanonical := archive.to_dictionary()
var chunks: Array = noncanonical["chunks"]
var noncanonical_descriptors: Array = (noncanonical["manifest"] as Dictionary)["chunks"]
var first_chunk: Variant = chunks[0]
chunks[0] = chunks[1]
chunks[1] = first_chunk
var first_descriptor: Variant = noncanonical_descriptors[0]
noncanonical_descriptors[0] = noncanonical_descriptors[1]
noncanonical_descriptors[1] = first_descriptor
assert_null(RegionalChunkedPersistence.from_dictionary(noncanonical))
func test_payload_contract_rejects_nodes_resources_and_node_paths() -> void:
var archive := RegionalChunkedPersistence.capture(_fixture_service(1))
assert_not_null(archive)
assert_true(_contains_only_primitive_values(archive.to_dictionary()))
var node_bundle := archive.to_dictionary()
var node := Node.new()
(_first_chunk_payload(node_bundle))["forbidden"] = node
assert_null(RegionalChunkedPersistence.from_dictionary(node_bundle))
node.free()
var resource_bundle := archive.to_dictionary()
(_first_chunk_payload(resource_bundle))["forbidden"] = Resource.new()
assert_null(RegionalChunkedPersistence.from_dictionary(resource_bundle))
var path_bundle := archive.to_dictionary()
(_first_chunk_payload(path_bundle))["forbidden"] = NodePath("World/Caravan")
assert_null(RegionalChunkedPersistence.from_dictionary(path_bundle))
func test_twenty_caravans_remain_structurally_chunked_and_bounded() -> void:
var service := _fixture_service(20)
for index in range(20):
assert_true(_depart_group(service, index))
var archive := RegionalChunkedPersistence.capture(service, 8)
assert_not_null(archive)
assert_eq(
archive.get_chunk_ids_for_type(RegionalChunkedPersistence.CHUNK_MOBILE_GROUP).size(), 20
)
assert_eq(archive.get_chunk_ids_for_type(RegionalChunkedPersistence.CHUNK_EVENTS).size(), 5)
assert_eq(archive.get_manifest()["chunk_count"], 29)
for event_chunk_id: String in archive.get_chunk_ids_for_type(
RegionalChunkedPersistence.CHUNK_EVENTS
):
assert_true(
(
(archive.get_chunk(StringName(event_chunk_id))["payload"]["events"] as Array).size()
<= 8
)
)
var encoded_size := JSON.stringify(archive.to_dictionary()).to_utf8_buffer().size()
assert_lt(encoded_size, 250_000)
assert_eq(archive.restore_service().checksum(), service.checksum())
func _fixture_service(group_count: int) -> RegionalCaravanService:
var origin_location := LocationStateRecord.create(
ORIGIN_LOCATION_ID, WORLD_ID, &"settlement", "Origin", _address(ORIGIN_LOCATION_ID)
)
var destination_location := LocationStateRecord.create(
DESTINATION_LOCATION_ID,
WORLD_ID,
&"settlement",
"Destination",
_address(DESTINATION_LOCATION_ID)
)
var origin := SettlementStateRecord.create(
ORIGIN_SETTLEMENT_ID,
ORIGIN_LOCATION_ID,
"Origin",
&"polity_origin",
1,
&"founders_origin",
0,
{&"food": float(group_count * 2 + 10)}
)
var destination := SettlementStateRecord.create(
DESTINATION_SETTLEMENT_ID,
DESTINATION_LOCATION_ID,
"Destination",
&"polity_destination",
1,
&"founders_destination",
0,
{&"food": 4.0}
)
var route := RouteStateRecord.create(ROUTE_ID, ORIGIN_LOCATION_ID, DESTINATION_LOCATION_ID, 5)
var groups: Array[MobileGroupStateRecord] = []
for index in range(group_count):
groups.append(
MobileGroupStateRecord.create_at_location(
_group_id(index),
&"caravan",
"Caravan %02d" % index,
&"polity_origin",
_address(ORIGIN_LOCATION_ID),
[],
{},
{},
2.0
)
)
var origin_polity := PolityStateRecord.create(
&"polity_origin", "Origin polity", ORIGIN_LOCATION_ID, [ORIGIN_SETTLEMENT_ID]
)
var destination_polity := PolityStateRecord.create(
&"polity_destination",
"Destination polity",
DESTINATION_LOCATION_ID,
[DESTINATION_SETTLEMENT_ID]
)
var locations: Array[LocationStateRecord] = [destination_location, origin_location]
var settlements: Array[SettlementStateRecord] = [destination, origin]
var routes: Array[RouteStateRecord] = [route]
var persons: Array[PersonStateRecord] = []
var cohorts: Array[PopulationCohortRecord] = []
var polities: Array[PolityStateRecord] = [destination_polity, origin_polity]
var relations: Array[DiplomaticRelationRecord] = []
var world_state := RegionalWorldState.create(
WORLD_ID, locations, settlements, routes, groups, persons, cohorts, polities, relations
)
return RegionalCaravanService.create(
world_state, RegionalJobScheduler.new(), WorldEventStore.new()
)
func _depart_group(service: RegionalCaravanService, index: int) -> bool:
return service.depart(
_group_id(index),
ORIGIN_SETTLEMENT_ID,
DESTINATION_SETTLEMENT_ID,
ROUTE_ID,
{&"food": 1.0},
DEPARTURE_TICK
)
func _group_id(index: int) -> StringName:
return StringName("group_%02d" % index)
func _address(location_id: StringName) -> SpatialAddress:
return SpatialAddress.create(WORLD_ID, location_id, Vector3.ZERO)
func _find_chunk(bundle: Dictionary, chunk_id: String) -> Dictionary:
for raw_chunk: Variant in bundle["chunks"]:
if raw_chunk is Dictionary and String(raw_chunk.get("chunk_id", "")) == chunk_id:
return raw_chunk
return {}
func _first_chunk_payload(bundle: Dictionary) -> Dictionary:
return ((bundle["chunks"] as Array)[0] as Dictionary)["payload"]
func _refresh_chunk_descriptor(bundle: Dictionary, chunk: Dictionary) -> void:
var serialized := JSON.stringify(chunk)
for raw_descriptor: Variant in (bundle["manifest"] as Dictionary)["chunks"]:
if (
raw_descriptor is Dictionary
and String(raw_descriptor["chunk_id"]) == String(chunk["chunk_id"])
):
raw_descriptor["byte_size"] = serialized.to_utf8_buffer().size()
raw_descriptor["checksum"] = serialized.sha256_text()
return
func _contains_only_primitive_values(value: Variant) -> bool:
match typeof(value):
TYPE_NIL, TYPE_BOOL, TYPE_INT, TYPE_STRING:
return true
TYPE_FLOAT:
return is_finite(float(value))
TYPE_ARRAY:
for item: Variant in value:
if not _contains_only_primitive_values(item):
return false
return true
TYPE_DICTIONARY:
for key: Variant in value:
if not key is String or not _contains_only_primitive_values(value[key]):
return false
return true
return false
@@ -0,0 +1 @@
uid://bmxqfpf0jl4um