feat: add emergent world foundations
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
extends GutTest
|
||||
|
||||
|
||||
func test_value_objects_round_trip_and_reject_invalid_data() -> void:
|
||||
var entity_ref := WorldEntityRef.create(&"npc", &"7")
|
||||
var address := SpatialAddress.create(&"jajce", &"village_square", Vector3(2.0, 3.0, 4.0))
|
||||
|
||||
assert_not_null(entity_ref)
|
||||
assert_eq(
|
||||
WorldEntityRef.from_dictionary(entity_ref.to_dictionary()).to_dictionary(),
|
||||
entity_ref.to_dictionary()
|
||||
)
|
||||
assert_not_null(address)
|
||||
assert_eq(
|
||||
SpatialAddress.from_dictionary(address.to_dictionary()).to_dictionary(),
|
||||
address.to_dictionary()
|
||||
)
|
||||
assert_null(WorldEntityRef.create(&"npc", &""))
|
||||
assert_null(
|
||||
(
|
||||
SpatialAddress
|
||||
. from_dictionary(
|
||||
{
|
||||
"schema_version": SpatialAddress.SCHEMA_VERSION,
|
||||
"world_id": "jajce",
|
||||
"location_id": "square",
|
||||
"position": [0.0, 1.0],
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
func test_record_serialization_is_deterministic_and_defensive() -> void:
|
||||
var actor := WorldEntityRef.create(&"npc", &"7")
|
||||
var resource := WorldEntityRef.create(&"resource", &"berry_patch_a")
|
||||
var participants := {
|
||||
&"source": resource,
|
||||
&"actor": actor,
|
||||
}
|
||||
var payload := {
|
||||
&"zeta": 2,
|
||||
&"alpha": &"food",
|
||||
&"nested": {&"beta": 2, &"alpha": 1},
|
||||
}
|
||||
var event := WorldEventRecord.create(
|
||||
42,
|
||||
&"resource_extracted",
|
||||
12,
|
||||
participants,
|
||||
SpatialAddress.create(&"jajce", &"forest_edge", Vector3(4.0, 0.0, 8.0)),
|
||||
payload
|
||||
)
|
||||
|
||||
participants.clear()
|
||||
payload[&"zeta"] = 99
|
||||
var serialized := event.to_dictionary()
|
||||
var restored := WorldEventRecord.from_dictionary(serialized)
|
||||
assert_not_null(restored)
|
||||
assert_eq(restored.to_dictionary(), serialized)
|
||||
assert_eq(JSON.stringify(restored.to_dictionary()), JSON.stringify(serialized))
|
||||
assert_eq(restored.get_participant_roles(), [&"actor", &"source"])
|
||||
assert_eq(restored.get_payload()["zeta"], 2)
|
||||
assert_eq(restored.get_payload()["alpha"], "food")
|
||||
serialized["payload"]["zeta"] = 100
|
||||
assert_eq(restored.get_payload()["zeta"], 2)
|
||||
|
||||
|
||||
func test_store_indexes_id_type_participant_location_and_tick() -> void:
|
||||
var store := WorldEventStore.new()
|
||||
var actor_a := WorldEntityRef.create(&"npc", &"7")
|
||||
var actor_b := WorldEntityRef.create(&"npc", &"8")
|
||||
var pantry := WorldEntityRef.create(&"storage", &"village_pantry")
|
||||
var square := SpatialAddress.create(&"jajce", &"village_square", Vector3.ZERO)
|
||||
var forest := SpatialAddress.create(&"jajce", &"forest_edge", Vector3(12.0, 0.0, 4.0))
|
||||
var later_event := WorldEventRecord.create(
|
||||
20,
|
||||
&"storage_deposited",
|
||||
9,
|
||||
{&"actor": actor_a, &"destination": pantry},
|
||||
square,
|
||||
{"item_id": "food", "amount": 2.0}
|
||||
)
|
||||
var earlier_event := WorldEventRecord.create(
|
||||
7, &"resource_extracted", 4, {&"actor": actor_a}, forest, {"item_id": "food", "amount": 2.0}
|
||||
)
|
||||
var same_tick_event := WorldEventRecord.create(
|
||||
9,
|
||||
&"storage_deposited",
|
||||
9,
|
||||
{&"actor": actor_b, &"source": pantry},
|
||||
square,
|
||||
{"item_id": "food", "amount": 1.0}
|
||||
)
|
||||
|
||||
assert_true(store.append(later_event))
|
||||
assert_true(store.append(earlier_event))
|
||||
assert_true(store.append(same_tick_event))
|
||||
assert_false(store.append(later_event))
|
||||
assert_same(store.get_by_id(20), store.get_by_id(20))
|
||||
assert_eq(_event_ids(store.get_all()), [7, 9, 20])
|
||||
assert_eq(_event_ids(store.get_for_type(&"storage_deposited")), [9, 20])
|
||||
assert_eq(_event_ids(store.get_for_participant(actor_a)), [7, 20])
|
||||
assert_eq(_event_ids(store.get_for_participant(pantry)), [9, 20])
|
||||
assert_eq(_event_ids(store.get_for_location(square)), [9, 20])
|
||||
assert_eq(_event_ids(store.get_for_tick(9)), [9, 20])
|
||||
assert_eq(_event_ids(store.get_between_ticks(4, 9)), [7, 9, 20])
|
||||
assert_true(store.get_for_type(&"unknown").is_empty())
|
||||
|
||||
|
||||
func test_store_round_trip_rebuilds_all_indexes_transactionally() -> void:
|
||||
var original := WorldEventStore.new()
|
||||
var actor := WorldEntityRef.create(&"npc", &"3")
|
||||
var address := SpatialAddress.create(&"jajce", &"pasture", Vector3(1.0, 0.0, 2.0))
|
||||
assert_true(
|
||||
original.append(
|
||||
WorldEventRecord.create(
|
||||
5, &"animal_fed", 14, {&"actor": actor}, address, {"amount": 1.0}
|
||||
)
|
||||
)
|
||||
)
|
||||
var serialized := original.to_dictionary()
|
||||
var restored := WorldEventStore.from_dictionary(serialized)
|
||||
|
||||
assert_not_null(restored)
|
||||
assert_eq(restored.to_dictionary(), serialized)
|
||||
assert_eq(_event_ids(restored.get_for_type(&"animal_fed")), [5])
|
||||
assert_eq(_event_ids(restored.get_for_participant(actor)), [5])
|
||||
assert_eq(_event_ids(restored.get_for_location(address)), [5])
|
||||
assert_eq(_event_ids(restored.get_for_tick(14)), [5])
|
||||
|
||||
var invalid_restore := {
|
||||
"schema_version": WorldEventStore.SCHEMA_VERSION,
|
||||
"events": [serialized["events"][0], serialized["events"][0]],
|
||||
}
|
||||
assert_false(restored.restore_from_dictionary(invalid_restore))
|
||||
assert_eq(restored.to_dictionary(), serialized)
|
||||
|
||||
|
||||
func test_economic_event_compatibility_conversion_preserves_contract() -> void:
|
||||
var economic := EconomicEventRecord.create(
|
||||
12,
|
||||
&"storage_deposited",
|
||||
30,
|
||||
4,
|
||||
&"npc_inventory_4",
|
||||
&"village_pantry",
|
||||
&"food",
|
||||
2.5,
|
||||
Vector3(3.0, 0.0, 7.0)
|
||||
)
|
||||
economic.data["custom_fact"] = {"reason": "harvest"}
|
||||
var world_event := WorldEventRecord.from_economic_event(economic, &"jajce", &"village_pantry")
|
||||
var round_tripped := world_event.to_economic_event()
|
||||
|
||||
assert_not_null(world_event)
|
||||
assert_eq(world_event.get_event_id(), 12)
|
||||
assert_eq(world_event.get_participant(&"actor").get_entity_id(), &"4")
|
||||
assert_eq(world_event.get_participant(&"source").get_entity_id(), &"npc_inventory_4")
|
||||
assert_eq(world_event.get_participant(&"destination").get_entity_id(), &"village_pantry")
|
||||
assert_eq(round_tripped.to_dictionary(), economic.to_dictionary())
|
||||
|
||||
var store := WorldEventStore.new()
|
||||
assert_not_null(store.append_economic(economic, &"jajce", &"village_pantry"))
|
||||
assert_eq(store.get_by_id(12).to_economic_event().to_dictionary(), economic.to_dictionary())
|
||||
|
||||
|
||||
func _event_ids(events: Array[WorldEventRecord]) -> Array[int]:
|
||||
var ids: Array[int] = []
|
||||
for event in events:
|
||||
ids.append(event.get_event_id())
|
||||
return ids
|
||||
Reference in New Issue
Block a user