feat: add provenance-aware npc communication

This commit is contained in:
Rijad Zuzo
2026-07-11 19:58:47 +02:00
parent 81409650df
commit 9a479ba182
18 changed files with 938 additions and 70 deletions
+152 -1
View File
@@ -16,6 +16,7 @@ func _run() -> void:
_test_previous_world_event_migration()
_test_previous_world_relationship_migration()
_test_previous_world_knowledge_migration()
_test_previous_world_provenance_migration()
_test_relationship_schema_rejection()
_test_schema_rejection()
@@ -266,7 +267,7 @@ func _test_previous_world_relationship_migration() -> void:
func _test_previous_world_knowledge_migration() -> void:
var manager := _create_manager(59)
var previous_data: Dictionary = manager.create_state_record().to_dictionary()
previous_data["schema_version"] = SimulationStateRecord.PREVIOUS_SCHEMA_VERSION
previous_data["schema_version"] = SimulationStateRecord.KNOWLEDGE_LEGACY_SCHEMA_VERSION
previous_data.erase("event_knowledge")
var deposit_event := EconomicEventRecord.create(
0,
@@ -291,12 +292,72 @@ func _test_previous_world_knowledge_migration() -> void:
migrated.event_knowledge.size() == 1
and migrated.event_knowledge[0].get_knower_id() == 0
and migrated.event_knowledge[0].get_event_id() == 0
and (
migrated.event_knowledge[0].get_acquisition_method()
== SimulationIds.KNOWLEDGE_ACQUISITION_LEGACY
)
),
"World v4 migration should preserve the fact implied by a relationship cause"
)
manager.free()
func _test_previous_world_provenance_migration() -> void:
var manager := _create_manager(60)
var previous_data: Dictionary = manager.create_state_record().to_dictionary()
previous_data["schema_version"] = SimulationStateRecord.PREVIOUS_SCHEMA_VERSION
var deposit_event := EconomicEventRecord.create(
0,
SimulationIds.EVENT_STORAGE_DEPOSITED,
12,
1,
SimulationIds.npc_inventory_id(1),
SimulationIds.STORAGE_VILLAGE_PANTRY,
SimulationIds.RESOURCE_FOOD,
2.0
)
previous_data["economic_events"] = [deposit_event.to_dictionary()]
previous_data["simulation"]["next_event_id"] = 1
previous_data["event_knowledge"] = [
{"schema_version": 1, "knower_id": 0, "event_id": 0},
{"schema_version": 1, "knower_id": 1, "event_id": 0},
]
previous_data["relationships"] = [
RelationshipStateRecord.create(0, 1, 0.5, 0.65, 0).to_dictionary()
]
var migrated := SimulationStateRecord.from_dictionary(previous_data)
_check(migrated != null, "World schema v5 should add honest acquisition provenance")
if migrated != null:
var observer_record: KnownEventStateRecord
var actor_record: KnownEventStateRecord
for known_event in migrated.event_knowledge:
if known_event.get_knower_id() == 0:
observer_record = known_event
elif known_event.get_knower_id() == 1:
actor_record = known_event
_check(
(
actor_record != null
and (
actor_record.get_acquisition_method()
== SimulationIds.KNOWLEDGE_ACQUISITION_PERFORMED
)
),
"World v5 migration can safely identify an actor's own completed fact"
)
_check(
(
observer_record != null
and (
observer_record.get_acquisition_method()
== SimulationIds.KNOWLEDGE_ACQUISITION_LEGACY
)
),
"World v5 migration must not invent witness or speaker provenance"
)
manager.free()
func _test_relationship_schema_rejection() -> void:
var manager := _create_manager(58)
var missing_cause_data: Dictionary = manager.create_state_record().to_dictionary()
@@ -391,6 +452,96 @@ func _test_relationship_schema_rejection() -> void:
SimulationStateRecord.from_dictionary(nonpositive_cause_data) == null,
"A trust cause must describe a successful positive food deposit"
)
var provenance_event := EconomicEventRecord.create(
0,
SimulationIds.EVENT_STORAGE_DEPOSITED,
1,
0,
SimulationIds.npc_inventory_id(0),
SimulationIds.STORAGE_VILLAGE_PANTRY,
SimulationIds.RESOURCE_FOOD,
1.0
)
var missing_source_data: Dictionary = manager.create_state_record().to_dictionary()
missing_source_data["economic_events"] = [provenance_event.to_dictionary()]
missing_source_data["simulation"]["next_event_id"] = 1
missing_source_data["event_knowledge"] = [
(
KnownEventStateRecord
. create(1, 0, SimulationIds.KNOWLEDGE_ACQUISITION_COMMUNICATED, 2)
. to_dictionary()
)
]
_check(
SimulationStateRecord.from_dictionary(missing_source_data) == null,
"Communicated knowledge must reference a source who knows the same event"
)
var wrong_actor_data: Dictionary = manager.create_state_record().to_dictionary()
wrong_actor_data["economic_events"] = [provenance_event.to_dictionary()]
wrong_actor_data["simulation"]["next_event_id"] = 1
wrong_actor_data["event_knowledge"] = [
(
KnownEventStateRecord
. create(1, 0, SimulationIds.KNOWLEDGE_ACQUISITION_PERFORMED)
. to_dictionary()
)
]
_check(
SimulationStateRecord.from_dictionary(wrong_actor_data) == null,
"Performed provenance must belong to the event actor"
)
var relayed_source_data: Dictionary = manager.create_state_record().to_dictionary()
relayed_source_data["economic_events"] = [provenance_event.to_dictionary()]
relayed_source_data["simulation"]["next_event_id"] = 1
relayed_source_data["event_knowledge"] = [
(
KnownEventStateRecord
. create(1, 0, SimulationIds.KNOWLEDGE_ACQUISITION_COMMUNICATED, 2)
. to_dictionary()
),
(
KnownEventStateRecord
. create(2, 0, SimulationIds.KNOWLEDGE_ACQUISITION_COMMUNICATED, 0)
. to_dictionary()
),
(
KnownEventStateRecord
. create(0, 0, SimulationIds.KNOWLEDGE_ACQUISITION_PERFORMED)
. to_dictionary()
),
]
_check(
SimulationStateRecord.from_dictionary(relayed_source_data) == null,
"Communicated facts must not form a second relay hop in this phase"
)
var order_independent_data: Dictionary = manager.create_state_record().to_dictionary()
order_independent_data["economic_events"] = [provenance_event.to_dictionary()]
order_independent_data["simulation"]["next_event_id"] = 1
order_independent_data["event_knowledge"] = [
(
KnownEventStateRecord
. create(1, 0, SimulationIds.KNOWLEDGE_ACQUISITION_COMMUNICATED, 2)
. to_dictionary()
),
(
KnownEventStateRecord
. create(0, 0, SimulationIds.KNOWLEDGE_ACQUISITION_PERFORMED)
. to_dictionary()
),
(
KnownEventStateRecord
. create(2, 0, SimulationIds.KNOWLEDGE_ACQUISITION_WITNESSED)
. to_dictionary()
),
]
_check(
SimulationStateRecord.from_dictionary(order_independent_data) != null,
"Provenance validation should not depend on knowledge array ordering"
)
manager.free()