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 var manager: Node = main_scene.get_node("SimulationManager") manager.set_process(false) for _frame in 3: await physics_frame var goat := main_scene.get_node("JajceWorld/WorldObjects/Animals/Dunja") as AnimalNode var player := main_scene.get_node("Player") as Node3D var pantry_node := StorageNode.get_by_id(SimulationIds.STORAGE_VILLAGE_PANTRY) as StorageNode var pantry: StorageStateRecord = manager.get_pantry() var state: AnimalStateRecord = manager.animal_care.get_state(SimulationIds.ANIMAL_DUNJA) _check(goat != null, "Jajce should load Dunja's named presentation") _check( AnimalNode.get_all().size() == 2 and ResourceNode.get_all().size() == 18, "Named goats should remain AnimalNodes without changing the finite-resource count" ) _check( not goat.is_in_group("resource_nodes") and ResourceNode.get_by_id(goat.animal_id) == null, "Dunja must not masquerade as a ResourceNode", ) _check( ( goat.animal_id == SimulationIds.ANIMAL_DUNJA and goat.display_name == "Dunja" and goat.species_id == SimulationIds.SPECIES_GOAT and state != null and goat.state == state ), "The loaded presentation should bind the stable named-goat identity" ) if state == null: _finish() return var authored_position := state.get_position() _check( goat.global_position.is_equal_approx(authored_position), "Animal state should own the loaded presentation position" ) state.set_position(authored_position + Vector3(0.25, 0.0, 0.0)) _check( goat.global_position.is_equal_approx(state.get_position()), "A simulation position change should move the bound goat presentation" ) state.set_position(authored_position) for index in range(1, manager.npcs.size()): _park_npc(manager.npcs[index]) var caretaker: SimNPC = manager.npcs[0] manager.release_npc_reservation(caretaker.id) caretaker.inventory.clear() caretaker.hunger = 20.0 caretaker.energy = 90.0 caretaker.current_task = SimulationIds.ACTION_IDLE caretaker.task_state = SimNPC.TASK_STATE_IDLE caretaker.task_complete = true caretaker.target_id = &"" caretaker.has_travel_target = false state.set_hunger(72.0) var pantry_before_npc := pantry.get_amount(SimulationIds.RESOURCE_FOOD) manager.simulate_tick() _check( caretaker.current_task == SimulationIds.ACTION_FEED_ANIMAL, "An available villager should autonomously choose to feed hungry Dunja" ) if caretaker.target_id.is_empty(): manager.resolve_npc_target(caretaker.id, caretaker.position) _check( ( caretaker.target_id == SimulationIds.ANIMAL_DUNJA and state.get_reserved_by() == caretaker.id and pantry_node != null and caretaker.travel_target_position.is_equal_approx( pantry_node.get_interaction_position() ) ), "Animal target resolution should retain Dunja's exact claim while routing to the pantry" ) manager.notify_npc_arrived(caretaker.id) _check( ( is_equal_approx(caretaker.get_inventory_amount(SimulationIds.RESOURCE_FOOD), 1.0) and caretaker.target_id == SimulationIds.ANIMAL_DUNJA and state.get_reserved_by() == caretaker.id and caretaker.task_state == SimNPC.TASK_STATE_TRAVELING and caretaker.travel_target_position.is_equal_approx(goat.get_interaction_position()) ), "Pantry arrival should carry one real food toward the same reserved Dunja", ) manager.notify_npc_arrived(caretaker.id) manager.simulate_tick() var hunger_before_completion := state.get_hunger() manager.simulate_tick() _check( is_equal_approx( state.get_hunger(), ( hunger_before_completion + AnimalStateRecord.HUNGER_PER_TICK - AnimalStateRecord.HUNGER_RELIEF ) ), "NPC feeding should apply the exact simulation-owned hunger relief" ) _check( is_equal_approx(pantry.get_amount(SimulationIds.RESOURCE_FOOD), pantry_before_npc - 1.0), "NPC feeding should withdraw exactly one real pantry food" ) _check( state.get_last_fed_tick() == manager.tick_count and state.get_reserved_by() == -1, "Completed feeding should retain its deterministic tick and release the claim" ) var npc_feed_event := _latest_feed_event(manager) _check( ( npc_feed_event != null and int(npc_feed_event.data["actor_id"]) == caretaker.id and ( StringName(npc_feed_event.data["source_id"]) == SimulationIds.npc_inventory_id(caretaker.id) ) and (StringName(npc_feed_event.data["destination_id"]) == SimulationIds.ANIMAL_DUNJA) and StringName(npc_feed_event.data["item_id"]) == SimulationIds.RESOURCE_FOOD and is_equal_approx(float(npc_feed_event.data["amount"]), 1.0) and npc_feed_event.get_world_position().is_equal_approx(state.get_position()) ), "NPC feeding should record one exact carried-inventory-to-animal transfer fact" ) _check( ( not goat.get_node("Visual/HungerCueRoot").visible and goat.get_node("Visual/FedResponseRoot").visible ), "Real feeding should clear the stable hunger cue and play one cozy response" ) var npc_feed_json: String = manager.serialize_state() var npc_feed_checksum: String = manager.get_state_checksum() var saved_hunger := state.get_hunger() var saved_fed_tick := state.get_last_fed_tick() _check( manager.restore_state_from_json(npc_feed_json), "NPC-fed animal state should pass the complete schema and restore" ) state = manager.animal_care.get_state(SimulationIds.ANIMAL_DUNJA) pantry = manager.get_pantry() _check( ( goat.state == state and state.get_position().is_equal_approx(authored_position) and is_equal_approx(state.get_hunger(), saved_hunger) and state.get_last_fed_tick() == saved_fed_tick and manager.get_state_checksum() == npc_feed_checksum ), "Animal position, hunger, feed tick, and checksum should round-trip deterministically" ) _check( not goat.get_node("Visual/FedResponseRoot").visible, "Restore should rebuild stable animal cues without replaying transient praise" ) state.set_hunger(72.0) var pantry_before_player := pantry.get_amount(SimulationIds.RESOURCE_FOOD) player.global_position = goat.get_interaction_position() player.call("try_interact") var player_feed_event := _latest_feed_event(manager) _check( ( is_equal_approx( pantry.get_amount(SimulationIds.RESOURCE_FOOD), pantry_before_player - 1.0 ) and is_equal_approx(state.get_hunger(), 72.0 - AnimalStateRecord.HUNGER_RELIEF) and player_feed_event != null and int(player_feed_event.data["actor_id"]) == -1 and (StringName(player_feed_event.data["destination_id"]) == SimulationIds.ANIMAL_DUNJA) ), "The player should use the same exact one-food animal mutation" ) var player_feed_json: String = manager.serialize_state() _check( manager.restore_state_from_json(player_feed_json), "Player-fed animal history should restore through the same schema" ) state = manager.animal_care.get_state(SimulationIds.ANIMAL_DUNJA) pantry = manager.get_pantry() _check( not goat.get_node("Visual/FedResponseRoot").visible, "Player feed feedback should also remain transient across restore" ) state.set_hunger(72.0) pantry.withdraw(SimulationIds.RESOURCE_FOOD, pantry.get_amount(SimulationIds.RESOURCE_FOOD)) manager.economy.sync_resource(SimulationIds.RESOURCE_FOOD) var events_before_failed_feed: int = manager.economic_events.size() player.global_position = goat.get_interaction_position() player.call("try_interact") _check( ( is_equal_approx(state.get_hunger(), 72.0) and is_equal_approx(pantry.get_amount(SimulationIds.RESOURCE_FOOD), 0.0) and manager.economic_events.size() == events_before_failed_feed ), "An empty pantry should block feeding without inventing food or animal relief" ) _finish() func _park_npc(npc: SimNPC) -> void: npc.set_task(SimulationIds.ACTION_WANDER, 1000.0) npc.start_working() func _latest_feed_event(manager: Node) -> EconomicEventRecord: for index in range(manager.economic_events.size() - 1, -1, -1): var event := manager.economic_events[index] as EconomicEventRecord if StringName(event.data["event_type"]) == SimulationIds.EVENT_ANIMAL_FED: return event return null func _check(condition: bool, message: String) -> void: if not condition: failures.append(message) func _finish() -> void: if failures.is_empty(): print("[TEST] Animal feeding vertical slice passed") quit(0) return for failure in failures: push_error("[TEST] " + failure) quit(1)