Files
gamedev-the-steward/tests/pantry_crisis_demo_test.gd
T
2026-07-16 10:58:48 +02:00

186 lines
6.1 KiB
GDScript

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 manager := main_scene.get_node("SimulationManager")
var world_view := main_scene.get_node("WorldViewManager")
var demo := main_scene.get_node("DemoController")
var caption := main_scene.get_node("CrisisCaptionLayer/CrisisCaption") as Label
manager.set_process(false)
var staged: Dictionary = demo.stage_pantry_crisis()
_check(not staged.is_empty(), "The repeatable pantry crisis should stage from a fresh scene")
if staged.is_empty():
_finish()
return
var helper: SimNPC = staged["helper"]
var interested: SimNPC = staged["interested"]
var shortage_actor: SimNPC = staged["shortage_actor"]
var opportunity: OpportunityStateRecord = staged["opportunity"]
var pantry: StorageStateRecord = manager.get_pantry()
var helper_visual: Node3D = world_view.active_npc_visuals.get(helper.id)
var interested_visual: Node3D = world_view.active_npc_visuals.get(interested.id)
_check(
is_equal_approx(pantry.get_amount(SimulationIds.RESOURCE_FOOD), 0.0),
"The demo shortage should empty the authoritative pantry"
)
_check(
is_equal_approx(shortage_actor.get_inventory_amount(SimulationIds.RESOURCE_FOOD), 0.0),
"The final pantry unit should be consumed through the real economy before the food run"
)
_check(
(
opportunity.get_interested_npc_id() == interested.id
and interested_visual.get_node("OpportunityConcernRoot").visible
and interested_visual.current_target == Vector3.INF
),
"The informed hungry villager should own and visibly express the open need"
)
_check(
not caption.visible,
"The cinematic caption should remain hidden until the full sequence starts"
)
var derived_helper: OpportunityHelperResult = manager.get_active_opportunity_helper()
_check(
(
derived_helper != null
and derived_helper.helper_npc_id == helper.id
and derived_helper.action_id == SimulationIds.ACTION_GATHER_FOOD
and derived_helper.resource_id == SimulationIds.RESOURCE_FOOD
and derived_helper.source_id.is_empty()
and derived_helper.available_source_count > 0
and not derived_helper.uses_inventory
),
"The open need should read-only derive the staged trusted helper and a finite food route"
)
_check(demo.begin_pantry_restock(), "A trusted helper should begin the real food run")
var decision: ActionSelectionResult = manager.get_latest_decision(helper.id)
_check(
(
decision != null
and decision.action_id == SimulationIds.ACTION_GATHER_FOOD
and interested.npc_name in decision.reason
),
"The helper's selected action should be caused by the starving trusted villager"
)
_check(
(
helper.task_state == SimNPC.TASK_STATE_TRAVELING
and helper.has_travel_target
and not helper.target_id.is_empty()
),
"The helper should reserve and travel to a finite food ResourceNode"
)
var resource_state: ResourceStateRecord = manager.get_resource_state(helper.target_id)
_check(
(
resource_state != null
and resource_state.get_resource_id() == SimulationIds.RESOURCE_FOOD
and resource_state.get_reserved_by() == helper.id
),
"The demo trip should use the normal finite-resource reservation contract"
)
helper_visual.global_position = helper.travel_target_position
manager.synchronize_npc_position(helper.id, helper_visual.global_position)
manager.notify_npc_arrived(helper.id)
for _tick in int(ceil(helper.task_duration)):
manager.simulate_tick()
_check(
(
helper.get_inventory_amount(SimulationIds.RESOURCE_FOOD) > 0.0
and helper_visual.get_node("CarriedFood").visible
),
"Completing the real gather action should create visible carried food"
)
manager.simulate_tick()
var deposit_decision: ActionSelectionResult = manager.get_latest_decision(helper.id)
_check(
(
helper.current_task == SimulationIds.ACTION_DEPOSIT_FOOD
and helper.target_id == SimulationIds.STORAGE_VILLAGE_PANTRY
and helper.has_travel_target
and deposit_decision != null
and "Responding to village need" in deposit_decision.reason
),
"The re-derived helper should autonomously choose the real pantry deposit"
)
helper_visual.global_position = helper.travel_target_position
manager.synchronize_npc_position(helper.id, helper_visual.global_position)
manager.notify_npc_arrived(helper.id)
manager.simulate_tick()
var pantry_visual := (
main_scene.get_node("JajceWorld/WorldObjects/StorageSites/VillagePantry/PantryPresentation")
as PantryStockVisual
)
var resolved: OpportunityStateRecord = manager.opportunity_system.get_latest()
_check(
(
manager.get_active_opportunity() == null
and resolved != null
and resolved.get_status() == SimulationIds.OPPORTUNITY_STATUS_RESOLVED
and resolved.get_resolution_event_id() > resolved.get_trigger_event_id()
),
"The exact later deposit event should resolve the same opportunity record"
)
_check(
(
pantry.get_amount(SimulationIds.RESOURCE_FOOD) > 0.0
and pantry_visual.get_node("RefillResponse").visible
and not interested_visual.get_node("OpportunityConcernRoot").visible
),
"The deposit should physically restock the pantry and clear the concern cue"
)
_check(
(
_has_event(manager.economic_events, SimulationIds.EVENT_RESOURCE_EXTRACTED, helper.id)
and _has_event(
manager.economic_events, SimulationIds.EVENT_STORAGE_DEPOSITED, helper.id
)
),
"The sequence should be traceable through real extraction and deposit events"
)
_finish()
func _has_event(events: Array[EconomicEventRecord], event_type: StringName, actor_id: int) -> bool:
for event in events:
if (
StringName(event.data["event_type"]) == event_type
and int(event.data["actor_id"]) == actor_id
):
return true
return false
func _check(condition: bool, message: String) -> void:
if not condition:
failures.append(message)
func _finish() -> void:
if failures.is_empty():
print("[TEST] Pantry crisis demo scenario passed")
quit(0)
return
for failure in failures:
push_error("[TEST] " + failure)
quit(1)