feat: package pantry crisis demo
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
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"
|
||||
)
|
||||
|
||||
_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()
|
||||
_check(
|
||||
(
|
||||
helper.current_task == SimulationIds.ACTION_DEPOSIT_FOOD
|
||||
and helper.target_id == SimulationIds.STORAGE_VILLAGE_PANTRY
|
||||
and helper.has_travel_target
|
||||
),
|
||||
"The helper should autonomously choose the real pantry deposit after gathering"
|
||||
)
|
||||
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)
|
||||
Reference in New Issue
Block a user