extends SceneTree var failures: Array[String] = [] func _initialize() -> void: call_deferred("_run") func _run() -> void: var manager := _create_manager() var contributor: SimNPC = manager.npcs[0] var nearby_observer: SimNPC = manager.npcs[1] var distant_observer: SimNPC = manager.npcs[2] contributor.position = Vector3.ZERO nearby_observer.position = Vector3(4.0, 0.0, 0.0) distant_observer.position = Vector3(20.0, 0.0, 0.0) nearby_observer.hunger = 85.0 distant_observer.hunger = 85.0 contributor.add_inventory(SimulationIds.RESOURCE_FOOD, 2.0) _check( is_equal_approx( manager.economy.deposit_inventory(contributor, SimulationIds.RESOURCE_FOOD), 2.0 ), "The witnessed fact should originate from a real successful food deposit" ) var deposit_events: Array = manager.get_npc_events(contributor.id, 3) var deposit_event: EconomicEventRecord for event in deposit_events: if StringName(event.data["event_type"]) == SimulationIds.EVENT_STORAGE_DEPOSITED: deposit_event = event break _check(deposit_event != null, "The objective event stream should contain the deposit fact") if deposit_event == null: manager.free() _finish() return var event_id := int(deposit_event.data["event_id"]) _check( ( manager.npc_knows_event(contributor.id, event_id) and manager.npc_knows_event(nearby_observer.id, event_id) and not manager.npc_knows_event(distant_observer.id, event_id) ), "Nearby and distant familiar NPCs should retain different evidence about one event" ) var nearby_relationship: RelationshipStateRecord = manager.relationship_system.get_relationship( nearby_observer.id, contributor.id ) var distant_relationship: RelationshipStateRecord = ( manager.relationship_system.get_relationship(distant_observer.id, contributor.id) ) _check( ( nearby_relationship != null and is_equal_approx(nearby_relationship.get_trust(), 0.65) and nearby_relationship.get_last_trust_cause_event_id() == event_id ), "Witnessed food aid should raise directed trust with the known fact as its cause" ) _check( ( distant_relationship != null and is_equal_approx( distant_relationship.get_trust(), RelationshipStateRecord.NEUTRAL_TRUST ) and ( distant_relationship.get_last_trust_cause_event_id() == RelationshipStateRecord.NO_CAUSE_EVENT ) ), "Familiarity alone should not grant trust without evidence" ) _prepare_choice_divergence(manager, contributor, nearby_observer, distant_observer) var nearby_choice: ActionSelectionResult = manager.action_selector.select_action( nearby_observer, manager.village, 0.5, manager.npcs ) var distant_choice: ActionSelectionResult = manager.action_selector.select_action( distant_observer, manager.village, 0.5, manager.npcs ) _check( ( nearby_choice.action_id == SimulationIds.ACTION_GATHER_FOOD and contributor.npc_name in nearby_choice.reason ), "The informed trusted observer should leave ordinary work to help" ) _check( distant_choice.action_id == SimulationIds.ACTION_PATROL, "The uninformed neutral observer should continue ordinary guard work" ) var saved_json: String = manager.serialize_state() var restored := _create_manager(999) _check( restored.restore_state_from_json(saved_json), "Witnessed knowledge should restore through the versioned world schema" ) _check( ( restored.npc_knows_event(nearby_observer.id, event_id) and not restored.npc_knows_event(distant_observer.id, event_id) and restored.get_known_events(nearby_observer.id, 1).size() == 1 ), "Save/load should preserve different per-NPC knowledge of the same objective event" ) _check( restored.get_state_checksum() == manager.get_state_checksum(), "Restored knowledge should retain the complete deterministic checksum" ) var current_nearby_choice: ActionSelectionResult = manager.action_selector.select_action( nearby_observer, manager.village, 0.5, manager.npcs ) var restored_nearby: SimNPC = restored.npcs[nearby_observer.id] var restored_nearby_choice: ActionSelectionResult = restored.action_selector.select_action( restored_nearby, restored.village, 0.5, restored.npcs ) _check( ( current_nearby_choice.action_id == SimulationIds.ACTION_GATHER_FOOD and restored_nearby_choice.action_id == SimulationIds.ACTION_GATHER_FOOD and contributor.npc_name in current_nearby_choice.reason and contributor.npc_name in restored_nearby_choice.reason ), "The restored informed NPC should retain the named helping branch" ) var current_future_choice: ActionSelectionResult = manager.action_selector.select_action( distant_observer, manager.village, 0.5, manager.npcs ) var restored_distant: SimNPC = restored.npcs[distant_observer.id] var restored_future_choice: ActionSelectionResult = restored.action_selector.select_action( restored_distant, restored.village, 0.5, restored.npcs ) _check( current_future_choice.action_id == restored_future_choice.action_id, "Knowledge restoration should preserve the next deterministic decision" ) _check( restored.get_state_checksum() == manager.get_state_checksum(), "Equivalent future decisions should advance restored RNG state identically" ) manager.free() restored.free() _finish() func _prepare_choice_divergence( manager: Node, contributor: SimNPC, nearby: SimNPC, distant: SimNPC ) -> void: var pantry: StorageStateRecord = manager.get_pantry() pantry.withdraw( SimulationIds.RESOURCE_FOOD, maxf( ( pantry.get_amount(SimulationIds.RESOURCE_FOOD) - (ActionSelectionSystem.RELATIONSHIP_AID_PANTRY_THRESHOLD - 5.0) ), 0.0 ) ) manager.economy.sync_resource(SimulationIds.RESOURCE_FOOD) manager.economy.deposit_resource(SimulationIds.RESOURCE_WOOD, 10.0) manager.village.safety = 50.0 manager.village.knowledge = 20.0 manager.village.update_priorities() contributor.hunger = 95.0 contributor.is_starving = true for observer in [nearby, distant]: observer.profession = SimulationIds.PROFESSION_GUARD observer.hunger = 20.0 observer.energy = 80.0 observer.inventory.clear() observer.last_task = &"" func _create_manager(seed_value: int = 811) -> Node: var manager: Node = load("res://simulation/SimulationManager.gd").new() manager.simulation_seed = seed_value manager.debug_logs = false var home_positions: Array[Vector3] = [ Vector3(0.0, 0.0, 0.0), Vector3(1.0, 0.0, 0.0), Vector3(2.5, 0.0, 0.0), Vector3(20.0, 0.0, 20.0), Vector3(30.0, 0.0, 30.0), Vector3(40.0, 0.0, 40.0), ] manager.home_positions = home_positions root.add_child(manager) manager.set_process(false) return manager func _finish() -> void: if failures.is_empty(): print("[TEST] Witnessed knowledge passed: evidence -> trust -> divergent choices") quit(0) return for failure in failures: push_error("[TEST] " + failure) quit(1) func _check(condition: bool, message: String) -> void: if not condition: failures.append(message)