Files
gamedev-the-steward/tests/food_storage_loop_test.gd
T
admin 8a1913c5ae feat: structured narrative event feed with per-NPC inspector timeline
Add narrative event types (task_started, npc_slept, npc_died, resource_depleted) alongside economic transfers. EconomicEventRecord gains create_narrative factory and human-readable description method. SimulationManager records task-start and sleep/death events, exposes get_npc_events() for per-NPC history queries. Inspector now shows a Recent timeline of the last 5 events for the selected NPC, plus displays carried wood count. Relax EconomicEventRecord validation to accept non-economic events. Update roadmap with completed milestones.
2026-07-08 19:02:11 +02:00

182 lines
5.3 KiB
GDScript

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)
manager.clock.elapsed_ticks = 100
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,
"safety_risk": 0.0,
"comfort_distance": 18.0,
"discovery_priority": 0.0
}
)
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
]
var transfer_events: Array[EconomicEventRecord] = []
for event in manager.economic_events:
if float(event.data["amount"]) > 0.0:
transfer_events.append(event)
_check(
transfer_events.size() == expected_types.size(),
"Food transfer loop should emit one structured event per transfer"
)
if transfer_events.size() != expected_types.size():
return
for index in expected_types.size():
var event: EconomicEventRecord = transfer_events[index]
_check(
(
int(event.data["event_id"]) >= 0
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(transfer_events[0].data["amount"]), 2.0)
and is_equal_approx(float(transfer_events[1].data["amount"]), 2.0)
and is_equal_approx(float(transfer_events[2].data["amount"]), 1.0)
and is_equal_approx(float(transfer_events[3].data["amount"]), 1.0)
),
"Economic events should record exact transferred quantities"
)