extends SceneTree var failures: Array[String] = [] func _initialize() -> void: call_deferred("_run") func _run() -> void: var main_scene: Node = load("res://main.tscn").instantiate() root.add_child(main_scene) await process_frame for _frame in 10: await physics_frame var simulation_manager: Node = main_scene.get_node("SimulationManager") simulation_manager.set_process(false) var saved_clock_ticks: int = simulation_manager.clock.elapsed_ticks simulation_manager.clock.elapsed_ticks = 0 var day_night_cycle := main_scene.get_node("JajceWorld/DayNightCycle") day_night_cycle.call("_process", 0.11) var environment: Environment = ( (main_scene.get_node("JajceWorld/WorldEnvironment") as WorldEnvironment).environment ) _check( is_equal_approx(environment.ambient_light_energy, 0.25), "Day/night presentation should follow the authoritative simulation clock" ) simulation_manager.clock.elapsed_ticks = saved_clock_ticks await process_frame _check(simulation_manager.npcs.size() == 6, "Baseline should create six NPCs") var runtime_player := main_scene.get_node("Player") as Node3D _check( runtime_player.is_in_group("grass_interactors"), "The real player transform should drive nearby grass response" ) var inspector_label := ( main_scene.get_node("UI/NpcInspectorPanel/MarginContainer/NpcInspectorLabel") as Label ) _check( ( "Relationship:" in inspector_label.text and "familiar 50%" in inspector_label.text and "trust 50%" in inspector_label.text ), "Runtime inspector should render authoritative directed relationship state" ) var contributor: SimNPC = simulation_manager.npcs[0] var witness: SimNPC = simulation_manager.npcs[1] var listener: SimNPC = simulation_manager.npcs[2] for npc in simulation_manager.npcs: npc.position = Vector3(20.0 + npc.id * 2.0, 0.0, 0.0) contributor.position = Vector3.ZERO witness.position = Vector3(4.0, 0.0, 0.0) witness.hunger = 85.0 listener.hunger = 20.0 contributor.add_inventory(SimulationIds.RESOURCE_FOOD, 1.0) _check( is_equal_approx( simulation_manager.economy.deposit_inventory(contributor, SimulationIds.RESOURCE_FOOD), 1.0 ), "Runtime knowledge UI check should use a completed simulation transaction" ) var world_view := main_scene.get_node("WorldViewManager") var all_npc_visuals_drive_grass := true for npc_visual in world_view.active_npc_visuals.values(): all_npc_visuals_drive_grass = ( all_npc_visuals_drive_grass and (npc_visual as Node3D).is_in_group("grass_interactors") ) _check( all_npc_visuals_drive_grass, "Every active villager visual should drive local grass response" ) var grass_material := ( main_scene.get_node("JajceWorld/TerrainRoot/Terrain3D/CozyGrassField").get( "mesh_material_override" ) as ShaderMaterial ) var grass_interactor_count := int(grass_material.get_shader_parameter("interactor_count")) _check( grass_interactor_count in range(1, 9), "Grass shader should receive a bounded set of real nearby actor transforms" ) var contributor_visual: Node3D = world_view.active_npc_visuals[contributor.id] var witness_visual: Node3D = world_view.active_npc_visuals[witness.id] var listener_visual: Node3D = world_view.active_npc_visuals[listener.id] _check( ( witness_visual.get_node("TrustReactionRoot").visible and not contributor_visual.get_node("TrustReactionRoot").visible ), "A real trust consequence should blossom only above the reacting observer" ) var reaction_checksum: String = simulation_manager.get_state_checksum() witness_visual.play_trust_gain_reaction() _check( simulation_manager.get_state_checksum() == reaction_checksum, "Restarting the transient trust blossom must not mutate simulation state" ) var village_ui := main_scene.get_node("UI") var village_whisper := ( main_scene.get_node("VillageWhisperLayer/VillageWhisper") as VillageWhisperHud ) var whisper_kicker := village_whisper.get_node("Copy/Kicker") as Label var whisper_message := village_whisper.get_node("Copy/Message") as Label _check( not village_whisper.visible, "The player-facing village whisper should begin quiet instead of replaying history" ) village_ui.selected_npc_index = witness.id village_ui.call("_refresh_npc_inspector") _check( ( "Memories 1 lasting · 0 recent" in inspector_label.text and ( "◆ lasting · %s stocked 1 food · witnessed" % contributor.npc_name in inspector_label.text ) and "Relationship: %s" % contributor.npc_name in inspector_label.text and "Because:" in inspector_label.text and "Own history" in inspector_label.text ), "NPC history should show the lasting witnessed fact and its relationship consequence" ) var guard_site := ( main_scene.get_node("JajceWorld/WorldObjects/ActivitySites/GuardPost") as ActivitySite ) witness.set_task(SimulationIds.ACTION_PATROL) witness.target_id = guard_site.site_id witness.position = guard_site.get_interaction_position() witness.start_working() listener.set_task(SimulationIds.ACTION_PATROL) listener.target_id = guard_site.site_id listener.position = witness.position + Vector3(1.0, 0.0, 0.0) simulation_manager.notify_npc_arrived(listener.id) village_ui.selected_npc_index = listener.id village_ui.call("_refresh_npc_inspector") _check( ( "Memories 0 lasting · 1 recent" in inspector_label.text and ( "• recent · %s stocked 1 food · heard %s" % [contributor.npc_name, witness.npc_name] in inspector_label.text ) ), "NPC history should identify who communicated a retained fact" ) _check( not listener_visual.get_node("TrustReactionRoot").visible, "Learning a fact without a relationship consequence should not show a trust blossom" ) var village_stats_label := ( main_scene.get_node("UI/VillagePanel/MarginContainer/VillageStatsLabel") as Label ) var pantry_state: StorageStateRecord = simulation_manager.get_pantry() var pantry_visual := ( main_scene.get_node("JajceWorld/WorldObjects/StorageSites/VillagePantry/PantryPresentation") as PantryStockVisual ) _check( ( pantry_visual.get_stock_level() == PantryStockVisual.StockLevel.STOCKED and pantry_visual.get_node("LowStock").visible and pantry_visual.get_node("FullStock").visible and not pantry_visual.get_node("EmptyStock").visible ), "A stocked authoritative pantry should show its physical sacks and berry baskets" ) contributor.hunger = 85.0 var withdrawn_for_need: float = simulation_manager.economy.withdraw_to_inventory( contributor, SimulationIds.RESOURCE_FOOD, pantry_state.get_amount(SimulationIds.RESOURCE_FOOD) ) _check(withdrawn_for_need > 0.0, "Runtime opportunity setup should empty the real pantry") var opportunity: OpportunityStateRecord = simulation_manager.get_active_opportunity() _check(opportunity != null, "Emptying the pantry should open one known shortage need") if pantry_visual.transition_tween != null: await pantry_visual.transition_tween.finished _check( ( pantry_visual.get_stock_level() == PantryStockVisual.StockLevel.EMPTY and pantry_visual.get_node("EmptyStock").visible and not pantry_visual.get_node("LowStock").visible and contributor_visual.get_node("OpportunityConcernRoot").visible and not listener_visual.get_node("OpportunityConcernRoot").visible ), "The real shortage should empty the pantry prop and concern only its interested villager" ) _check( ( "Village need" in village_stats_label.text and "◆ Restock the empty pantry" in village_stats_label.text and ( "%s is worried about the shortage" % contributor.npc_name in village_stats_label.text ) and "Food 0 / 1" in village_stats_label.text ), "An open opportunity should add one compact state-derived village need" ) village_ui.selected_npc_index = contributor.id village_ui.call("_refresh_npc_inspector") _check( ( "Open village need" in inspector_label.text and "◆ Restock the empty pantry" in inspector_label.text and "Food 0 / 1" in inspector_label.text ), "The interested villager should expose the open pantry need in their inspector" ) var field_note := ( main_scene.get_node("VillagerInspectionLayer/VillagerFieldNote") as VillagerFieldNoteHud ) contributor_visual.global_position = Vector3(-30.0, 0.0, -30.0) var note_player := main_scene.get_node("Player") as Node3D note_player.global_position = contributor_visual.global_position + Vector3(1.0, 0.0, 0.0) var note_context: VillagerInspectionResult = note_player.get_nearby_villager_inspection() _check( ( note_context != null and note_context.npc_id == contributor.id and note_context.has_open_need and note_context.need_type == SimulationIds.OPPORTUNITY_RESTOCK_EMPTY_PANTRY and note_context.need_progress == "Food 0 / 1" and not note_context.need_response.strip_edges().is_empty() ), "Field note should derive the inspected villager's exact open need and response" ) field_note.refresh_note(true) var need_label := field_note.get_node("Copy/Need") as Label _check( field_note.visible and need_label.visible and "Food 0 / 1" in need_label.text, "Field note HUD should render the surfaced need line for its interested villager" ) village_ui.selected_npc_index = listener.id village_ui.call("_refresh_npc_inspector") _check( "Open village need" not in inspector_label.text, "An unrelated selected villager should not inherit someone else's open need" ) _check( ( simulation_manager.economy.deposit_inventory(contributor, SimulationIds.RESOURCE_FOOD) > 0.0 ), "Opportunity presentation should resolve from a real pantry deposit" ) _check( ( pantry_visual.get_stock_level() == PantryStockVisual.StockLevel.STOCKED and pantry_visual.get_node("RefillResponse").visible and not contributor_visual.get_node("OpportunityConcernRoot").visible ), "Real supply should restore visible stock, play one refill response, and clear concern" ) _check( "Village need" not in village_stats_label.text, "Resolved opportunities should leave the compact village summary" ) village_ui.selected_npc_index = contributor.id village_ui.call("_refresh_npc_inspector") _check( ( "Resolved village need" in inspector_label.text and "◆ %s restocked the pantry" % contributor.npc_name in inspector_label.text and "Food target 1" in inspector_label.text ), "The interested villager should retain a resolved detail naming the real actor" ) village_ui.selected_npc_index = listener.id village_ui.call("_refresh_npc_inspector") _check( "Resolved village need" not in inspector_label.text, "Resolved opportunity detail should remain scoped to its interested villager" ) var player_need_withdrawal: float = simulation_manager.economy.withdraw_to_inventory( contributor, SimulationIds.RESOURCE_FOOD, pantry_state.get_amount(SimulationIds.RESOURCE_FOOD) ) _check(player_need_withdrawal > 0.0, "Player resolution setup should reopen a real need") var player_opportunity: OpportunityStateRecord = simulation_manager.get_active_opportunity() _check(player_opportunity != null, "A later shortage should open a new opportunity record") var active_need_json: String = simulation_manager.serialize_state() var old_contributor_visual := contributor_visual _check( simulation_manager.restore_state_from_json(active_need_json), "The visible shortage should survive a valid schema-v11 restore" ) await process_frame contributor = simulation_manager.npcs[contributor.id] witness = simulation_manager.npcs[witness.id] listener = simulation_manager.npcs[listener.id] contributor_visual = world_view.active_npc_visuals[contributor.id] player_opportunity = simulation_manager.get_active_opportunity() _check( ( contributor_visual != old_contributor_visual and contributor_visual.get_node("OpportunityConcernRoot").visible and pantry_visual.get_stock_level() == PantryStockVisual.StockLevel.EMPTY and pantry_visual.get_node("EmptyStock").visible and not pantry_visual.get_node("RefillResponse").visible and not village_whisper.visible ), "Restore should rebuild stable cues without replaying refill or player-HUD feedback" ) var player := main_scene.get_node("Player") as Node3D var opportunity_bush := ( main_scene.get_node("JajceWorld/WorldObjects/ResourceNodes/BerryBush_01") as ResourceNode ) var opportunity_bush_state: ResourceStateRecord = simulation_manager.get_resource_state( opportunity_bush.node_id ) opportunity_bush_state.set_amount_remaining(1.0) player.global_position = opportunity_bush.interaction_point.global_position player.call("try_interact") var carried_after_gather: float = simulation_manager.get_player_state().get_inventory_amount( SimulationIds.RESOURCE_FOOD ) _check( ( simulation_manager.get_player_state().get_inventory_amount(SimulationIds.RESOURCE_FOOD) >= 1.0 ), "Player gathering should first carry the finite yield" ) var player_pantry := ( main_scene.get_node("JajceWorld/WorldObjects/StorageSites/VillagePantry") as StorageNode ) player.global_position = player_pantry.get_interaction_position() player.call("try_interact") village_ui.selected_npc_index = contributor.id village_ui.call("_refresh_npc_inspector") _check( ( is_equal_approx(carried_after_gather, 1.0) and player_opportunity.get_status() == OpportunityStateRecord.STATUS_RESOLVED and "Resolved village need" in inspector_label.text and "◆ Player restocked the pantry" in inspector_label.text and whisper_kicker.text == "NEED MET" and "The player restocked the pantry" in whisper_message.text ), "Player gathering and depositing should resolve the restored need in debug detail and transient HUD" ) var woodpile_state: StorageStateRecord = simulation_manager.get_woodpile() woodpile_state.withdraw( SimulationIds.RESOURCE_WOOD, woodpile_state.get_amount(SimulationIds.RESOURCE_WOOD) ) simulation_manager.economy.sync_resource(SimulationIds.RESOURCE_WOOD) var study_site := ( main_scene.get_node("JajceWorld/WorldObjects/ActivitySites/StudyDesk") as ActivitySite ) contributor.position = study_site.get_interaction_position() for npc in simulation_manager.npcs: if npc.id != contributor.id: npc.position = contributor.position + Vector3(20.0 + npc.id * 2.0, 0.0, 0.0) witness.profession = SimulationIds.PROFESSION_WOODCUTTER contributor.set_task(SimulationIds.ACTION_STUDY, 1.0) contributor.target_id = study_site.site_id contributor.travel_target_position = contributor.position contributor.start_working() simulation_manager.simulate_tick() village_ui.selected_npc_index = contributor.id village_ui.call("_refresh_npc_inspector") var runtime_opportunity: OpportunityStateRecord = simulation_manager.get_active_opportunity() var runtime_trigger_id := ( runtime_opportunity.get_trigger_event_id() if runtime_opportunity != null else -1 ) var runtime_helper: OpportunityHelperResult = simulation_manager.get_active_opportunity_helper() var runtime_player_response: OpportunityPlayerResponseResult = ( simulation_manager.get_active_opportunity_player_response() ) _check( ( runtime_opportunity != null and runtime_helper == null and runtime_player_response != null and runtime_player_response.action_id == SimulationIds.ACTION_GATHER_WOOD and runtime_player_response.target_id == SimulationIds.STORAGE_VILLAGE_WOODPILE and runtime_player_response.available_source_count > 0 and not simulation_manager.npc_knows_event(witness.id, runtime_trigger_id) and whisper_kicker.text == "YOU CAN HELP" and ( "Harvest a tree; wood goes straight to the village woodpile." in whisper_message.text ) ), "An unassisted blocked-work need should surface its real player harvest route" ) _check( ( "◆ Supply wood for blocked work" in village_stats_label.text and "could not finish Study" in village_stats_label.text and ( "Possible helper: none informed, trusted, and able to supply" in village_stats_label.text ) and "Player route: Gather Wood → Village Woodpile" in village_stats_label.text and "◆ Find wood for Study" in inspector_label.text and not contributor_visual.get_node("OpportunityConcernRoot").visible ), "The missing-wood UI should honestly show that no capable informed helper exists yet" ) contributor.set_task(SimulationIds.ACTION_PATROL) contributor.target_id = guard_site.site_id contributor.position = guard_site.get_interaction_position() contributor.start_working() witness.set_task(SimulationIds.ACTION_PATROL) witness.target_id = guard_site.site_id witness.position = contributor.position + Vector3(1.0, 0.0, 0.0) simulation_manager.notify_npc_arrived(witness.id) var communicated_trigger: KnownEventStateRecord = simulation_manager.get_known_event_record( witness.id, runtime_trigger_id ) runtime_helper = simulation_manager.get_active_opportunity_helper() runtime_player_response = simulation_manager.get_active_opportunity_player_response() _check( ( communicated_trigger != null and ( communicated_trigger.get_acquisition_method() == SimulationIds.KNOWLEDGE_ACQUISITION_COMMUNICATED ) and communicated_trigger.get_source_npc_id() == contributor.id and runtime_helper != null and runtime_player_response == null and runtime_helper.helper_npc_id == witness.id and runtime_helper.action_id == SimulationIds.ACTION_GATHER_WOOD and whisper_kicker.text == "NEWS TRAVELS" and contributor.npc_name in whisper_message.text and witness.npc_name in whisper_message.text ), "A real shared activity should report the exact need and reveal the trusted helper" ) var runtime_helper_reason := ( "Knows the need; trust %.2f toward %s" % [runtime_helper.trust, contributor.npc_name] if runtime_helper != null else "missing helper" ) _check( ( "◆ Supply wood for blocked work" in village_stats_label.text and "could not finish Study" in village_stats_label.text and "Possible helper: %s — Gather Wood" % witness.npc_name in village_stats_label.text and runtime_helper_reason in village_stats_label.text and "◆ Find wood for Study" in inspector_label.text and not contributor_visual.get_node("OpportunityConcernRoot").visible ), "The compact summary should update from the newly communicated simulation fact" ) witness.current_task = SimulationIds.ACTION_IDLE witness.task_state = SimNPC.TASK_STATE_IDLE witness.task_complete = true witness.target_id = &"" witness.has_travel_target = false witness.hunger = 20.0 witness.energy = 80.0 witness.mourning_ticks = 0 simulation_manager.simulate_tick() var runtime_decision: ActionSelectionResult = simulation_manager.get_latest_decision(witness.id) var helper_resource: ResourceStateRecord = simulation_manager.get_resource_state( witness.target_id ) _check( ( runtime_decision != null and runtime_decision.action_id == SimulationIds.ACTION_GATHER_WOOD and "Responding to village need" in runtime_decision.reason and witness.task_state == SimNPC.TASK_STATE_TRAVELING and helper_resource != null and helper_resource.get_reserved_by() == witness.id and whisper_kicker.text == "HELP IS ON THE WAY" and witness.npc_name in whisper_message.text and "gather wood" in whisper_message.text ), "The informed helper and cozy HUD should reflect ordinary target resolution and reservation" ) var demo_controller := main_scene.get_node("DemoController") demo_controller.set_debug_overlay_visible(false) _check( not village_ui.visible and village_whisper.visible, "The transient player HUD should remain legible when development overlays are hidden" ) demo_controller.set_debug_overlay_visible(true) contributor.add_inventory(SimulationIds.RESOURCE_WOOD, 1.0) _check( is_equal_approx( simulation_manager.economy.deposit_inventory(contributor, SimulationIds.RESOURCE_WOOD), 1.0 ), "Runtime opportunity presentation should resolve from a real wood deposit" ) village_ui.call("_refresh_npc_inspector") _check( ( "Village need" not in village_stats_label.text and "◆ %s supplied the woodpile" % contributor.npc_name in inspector_label.text and whisper_kicker.text == "NEED MET" and "supplied the woodpile" in whisper_message.text ), "Resolution should name its supplier in both debug detail and the transient player HUD" ) _check( main_scene.has_node("JajceWorld/TerrainRoot/Terrain3D"), "Playable runtime should instance the Jajce Terrain3D world" ) var terrain: Terrain3D = main_scene.get_node("JajceWorld/TerrainRoot/Terrain3D") _check( terrain.get_camera() == main_scene.get_node("CameraRig/Camera3D"), "Runtime Terrain3D should bind to the gameplay camera" ) var camera_rig := main_scene.get_node("CameraRig") _check( camera_rig.has_method("apply_presentation_preset"), "Runtime camera should expose a presentation staging preset" ) var presentation_focus := Vector3(-2.0, 1.2, -0.5) camera_rig.apply_presentation_preset(presentation_focus, 128.0, 0.95) _check( camera_rig.global_position.distance_to(presentation_focus) > 10.0, "Presentation preset should pull the camera back from the village focus" ) _check(terrain.collision_mode != 0, "Runtime Terrain3D collision should be enabled") _check( ( main_scene.has_node("Player/Visual/Body") and main_scene.has_node("Player/Visual/Head") and main_scene.has_node("Player/Visual/Scarf") ), "Player should use the same readable multi-part visual language as villagers" ) _check( ( not main_scene.has_node("Player/MeshInstance3D") and not main_scene.has_node("Player/FaceMarker") ), "Runtime should not retain the placeholder player capsule presentation" ) _check( not main_scene.has_node("JajceWorld/NavigationRegion3D/GreyboxGround"), "Runtime should not keep the temporary greybox navigation ground" ) _check( ( not main_scene.has_node("World") and not main_scene.has_node("ResourceNodes") and not main_scene.has_node("ActivityMarkers") and not main_scene.has_node("JajceWorld/LegacyActivityMarkers") ), "Runtime should not retain duplicate flat-world objects" ) var runtime_resources := _collect_resource_nodes(main_scene.get_node("JajceWorld/WorldObjects")) _check( runtime_resources.size() == 18, "Runtime should contain eighteen Jajce ResourceNodes across authored clusters" ) _check( simulation_manager.resource_states.size() == 18, "Simulation authority should bind all eighteen Jajce resources" ) var village_root := main_scene.get_node("JajceWorld/VillageRoot") var path_strips := 0 for child in village_root.get_children(): if child.name.begins_with("Path_"): path_strips += 1 _check( path_strips >= 4, "Runtime should include authored path strips for first-read composition" ) _check( main_scene.has_node("JajceWorld/FortressBlockout/Keep/RidgeBanner"), "Runtime should include a readable ridge landmark banner" ) _check( main_scene.has_node("JajceWorld/WorldObjects/StorageSites/VillagePantry/PantrySign"), "Pantry storage should have a readable work-site silhouette" ) _check( main_scene.has_node("JajceWorld/WorldObjects/ActivitySites/GuardPost/GuardPennant"), "Guard site should have a readable work-site silhouette" ) _check( main_scene.has_node("JajceWorld/WorldObjects/ActivitySites/StudyDesk/OpenBook_A"), "Study site should have readable study props" ) _check( main_scene.has_node("JajceWorld/WorldObjects/ActivitySites/RestBench/RestCanopy"), "Rest site should have a readable rest silhouette" ) var navigation_map: RID = main_scene.get_world_3d().navigation_map var navigation_region := ( main_scene.get_node("JajceWorld/NavigationRegion3D") as NavigationRegion3D ) _check( ( navigation_region.navigation_mesh.resource_path == "res://world/jajce/JajceNavigationMesh.tres" ), "Runtime navigation should use the Terrain3D-derived baked mesh resource" ) await _wait_for_navigation_map(navigation_map) NavigationServer3D.map_force_update(navigation_map) var fixed_origins := [Vector3(-6.0, 0.0, -4.0), Vector3(0.0, 0.0, 0.0), Vector3(6.0, 0.0, 4.0)] var destinations: Array[Vector3] = [] _check( main_scene.has_node("JajceWorld/WorldObjects/StorageSites/VillagePantry"), "Runtime should expose a typed VillagePantry StorageNode" ) _check( not main_scene.has_node("JajceWorld/LegacyActivityMarkers/PantryMarker"), "Runtime should not keep the pantry as a legacy activity marker" ) var pantry := ( main_scene.get_node("JajceWorld/WorldObjects/StorageSites/VillagePantry") as StorageNode ) destinations.append(pantry.get_interaction_position()) _check( main_scene.has_node("JajceWorld/WorldObjects/StorageSites/VillageWoodpile"), "Runtime should expose a typed VillageWoodpile StorageNode" ) var woodpile := ( main_scene.get_node("JajceWorld/WorldObjects/StorageSites/VillageWoodpile") as StorageNode ) destinations.append(woodpile.get_interaction_position()) var activity_root := main_scene.get_node("JajceWorld/WorldObjects/ActivitySites") _check(activity_root.get_child_count() == 3, "Runtime should expose three typed activity sites") for site in activity_root.get_children(): destinations.append((site as ActivitySite).get_interaction_position()) for resource in runtime_resources: destinations.append(resource.interaction_point.global_position) for origin in fixed_origins: for destination in destinations: var path := NavigationServer3D.map_get_path(navigation_map, origin, destination, true) _check( not path.is_empty(), "Navigation path should exist from %s to %s" % [origin, destination] ) _check_path_tracks_terrain( terrain, path, "Runtime navigation path should track Terrain3D height" ) _check_path_reaches_destination( path, destination, "Runtime navigation path should reach its requested target" ) var village := SimVillage.new() var worker := SimNPC.new(100, "BaselineWorker", SimulationIds.PROFESSION_SCHOLAR, 5.0, 5.0) worker.set_task(SimulationIds.ACTION_STUDY, 2.0) worker.start_working() var executor := ActionExecutionSystem.new() executor.advance_npc(worker, village) executor.advance_npc(worker, village) _check(worker.task_complete, "A two-tick working task should complete") var starving := SimNPC.new(101, "BaselineStarving", SimulationIds.PROFESSION_WANDERER, 5.0, 5.0) starving.hunger = 100.0 starving.starvation_death_threshold = 1 executor.advance_npc(starving, village) _check(starving.is_dead, "Starvation threshold should still kill an NPC") var adapter := main_scene.get_node("ActiveWorldAdapter") as ActiveWorldAdapter var storage_target := adapter.get_activity_target(SimulationIds.ACTION_DEPOSIT_FOOD) _check( storage_target.get("target_id", "") == String(SimulationIds.STORAGE_VILLAGE_PANTRY), "Runtime adapter should route storage actions to village_pantry" ) var wood_target := adapter.get_activity_target(SimulationIds.ACTION_DEPOSIT_WOOD) _check( wood_target.get("target_id", "") == String(SimulationIds.STORAGE_VILLAGE_WOODPILE), "Runtime adapter should route wood deposit to village_woodpile" ) var study_target := adapter.get_activity_target(SimulationIds.ACTION_STUDY) _check( study_target.get("target_id", "") == "study_desk", "Runtime adapter should route study to the typed study desk" ) if failures.is_empty(): print("[TEST] Jajce runtime passed: 6 NPCs, 18 resources, typed sites") quit(0) return for failure in failures: push_error("[TEST] " + failure) quit(1) func _wait_for_navigation_map(navigation_map: RID) -> void: for attempt in 30: if ( NavigationServer3D.map_get_iteration_id(navigation_map) > 0 and not NavigationServer3D.map_get_regions(navigation_map).is_empty() ): await physics_frame return await physics_frame _check(false, "Navigation map did not synchronize within 30 physics frames") func _check(condition: bool, message: String) -> void: if not condition: failures.append(message) func _collect_resource_nodes(parent: Node) -> Array[ResourceNode]: var resources: Array[ResourceNode] = [] for child in parent.get_children(): if child is ResourceNode: resources.append(child) else: resources.append_array(_collect_resource_nodes(child)) return resources func _check_path_tracks_terrain( terrain: Terrain3D, path: PackedVector3Array, message: String ) -> void: if path.is_empty(): return for point in path: var terrain_height: float = terrain.data.get_height(point) if is_nan(terrain_height): failures.append("%s: missing terrain height at %s" % [message, point]) return if absf(point.y - terrain_height) > 0.85: failures.append( ( "%s: path point %s is too far from terrain height %.2f" % [message, point, terrain_height] ) ) return func _check_path_reaches_destination( path: PackedVector3Array, destination: Vector3, message: String ) -> void: if path.is_empty(): return var final_point := path[path.size() - 1] var final_xz := Vector2(final_point.x, final_point.z) var destination_xz := Vector2(destination.x, destination.z) if final_xz.distance_to(destination_xz) > 1.5: failures.append( "%s: final path point %s is too far from %s" % [message, final_point, destination] )