extends SceneTree var failures: Array[String] = [] func _initialize() -> void: call_deferred("_run") func _run() -> void: var manager: Node = load("res://simulation/SimulationManager.gd").new() manager.debug_logs = false root.add_child(manager) manager.set_process(false) var pantry: StorageStateRecord = manager.get_pantry() pantry.withdraw(SimulationIds.RESOURCE_FOOD, 1000.0) manager._sync_village_food() var resource := ResourceStateRecord.from_dictionary({ "schema_version": ResourceStateRecord.SCHEMA_VERSION, "node_id": "food_loop_bush", "action_id": String(SimulationIds.ACTION_GATHER_FOOD), "resource_id": String(SimulationIds.RESOURCE_FOOD), "amount_remaining": 10.0, "yield_per_action": 2.0, "reserved_by": -1, "enabled": true, "can_npcs_use": true, "can_player_use": true }) manager.resource_states[resource.get_node_id()] = resource var npc: SimNPC = manager.npcs[0] npc.hunger = 20.0 npc.energy = 100.0 npc.set_task(SimulationIds.ACTION_GATHER_FOOD, 1.0) npc.target_id = resource.get_node_id() resource.reserve(npc.id) npc.start_working() manager.simulate_tick() _check( is_equal_approx(resource.get_amount_remaining(), 8.0) and is_equal_approx( npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD), 2.0 ), "Gathering should move food from the source into NPC inventory" ) _check( is_equal_approx(pantry.get_amount(SimulationIds.RESOURCE_FOOD), 0.0), "Gathering should not teleport food into storage" ) manager.simulate_tick() _check( npc.current_task == SimulationIds.ACTION_DEPOSIT_FOOD, "Carried food should select the deposit action" ) manager.notify_npc_arrived(npc.id) manager.simulate_tick() _check( is_equal_approx(pantry.get_amount(SimulationIds.RESOURCE_FOOD), 2.0) and is_equal_approx( npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD), 0.0 ), "Deposit should transfer carried food into the pantry" ) npc.hunger = 80.0 manager.simulate_tick() _check( npc.current_task == SimulationIds.ACTION_WITHDRAW_FOOD, "Hungry NPC should retrieve food from storage" ) manager.notify_npc_arrived(npc.id) manager.simulate_tick() _check( is_equal_approx(pantry.get_amount(SimulationIds.RESOURCE_FOOD), 1.0) and is_equal_approx( npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD), 1.0 ), "Withdraw should transfer pantry food into NPC inventory" ) manager.simulate_tick() _check( npc.current_task == SimulationIds.ACTION_EAT, "NPC carrying food should select eat" ) manager.notify_npc_arrived(npc.id) var hunger_before_eating := npc.hunger manager.simulate_tick() manager.simulate_tick() _check( npc.hunger < hunger_before_eating and is_equal_approx( npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD), 0.0 ), "Eating should consume carried food and reduce hunger" ) var conserved_food := ( resource.get_amount_remaining() + pantry.get_amount(SimulationIds.RESOURCE_FOOD) + npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD) ) _check( is_equal_approx(conserved_food, 9.0), "Exactly one food unit should leave the world when eaten" ) _check_economic_event_chain(manager) var restored: Node = load("res://simulation/SimulationManager.gd").new() restored.debug_logs = false root.add_child(restored) restored.set_process(false) _check( restored.restore_state_from_json(manager.serialize_state()), "Economic events should restore with the simulation" ) _check( restored.economic_events.size() == manager.economic_events.size() and restored.next_event_id == manager.next_event_id, "Economic event history and its next ID should round-trip" ) if failures.is_empty(): print("[TEST] Food storage loop passed") 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) func _check_economic_event_chain(manager: Node) -> void: var expected_types: Array[StringName] = [ SimulationIds.EVENT_RESOURCE_EXTRACTED, SimulationIds.EVENT_STORAGE_DEPOSITED, SimulationIds.EVENT_STORAGE_WITHDRAWN, SimulationIds.EVENT_ITEM_CONSUMED ] _check( manager.economic_events.size() == expected_types.size(), "Food transfer loop should emit one structured event per transfer" ) if manager.economic_events.size() != expected_types.size(): return for index in expected_types.size(): var event: EconomicEventRecord = manager.economic_events[index] _check( int(event.data["event_id"]) == index and StringName(event.data["event_type"]) == expected_types[index] and StringName(event.data["item_id"]) == SimulationIds.RESOURCE_FOOD, "Economic event order, identity, and item should be deterministic" ) _check( is_equal_approx( float(manager.economic_events[0].data["amount"]), 2.0 ) and is_equal_approx( float(manager.economic_events[1].data["amount"]), 2.0 ) and is_equal_approx( float(manager.economic_events[2].data["amount"]), 1.0 ) and is_equal_approx( float(manager.economic_events[3].data["amount"]), 1.0 ), "Economic events should record exact transferred quantities" )