186 lines
6.1 KiB
GDScript
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)
|