- Responder outcomes now reach the shift review (ShiftCoordinator.responder_outcomes), and the review screen names them. - "evacuation" (emergency call, low danger, no weapon) actually clears the entrance: the door is locked, every NPC flees and is marked as leaving, and a security_failure reputation hit applies. - An emergency call the situation did not justify (no weapon, danger <= 1) appends an investigation note, records investigation_opened, and applies the existing false_report hit. The review lists the investigations. - Fix dispatch dying after the first shift: coordinator.setup() builds a new RadioSystem, so the responder_arrived connection made in _ready was lost on every venue switch/restart. _connect_radio() re-wires it beside _connect_crowd(). Tests/docs - Headless suite 105 -> 113 (test_emergency_aftermath.gd), new smoke_emergency.gd covering the investigation, evacuation aftermath, and review copy. - README controls/status/smoke checklist, TODO section moved to DONE, MEMORY notes. - Version 0.2.5.
95 lines
3.1 KiB
GDScript
95 lines
3.1 KiB
GDScript
extends SceneTree
|
|
## Emergency-aftermath smoke: places an unjustified emergency call, checks the
|
|
## investigation note and reputation hit, forces the responder to arrive, and
|
|
## verifies the evacuation outcome locks the door and clears the entrance.
|
|
##
|
|
## Usage:
|
|
## godot --headless --path . --script res://src/test/smoke_emergency.gd
|
|
## Exit code 0 = pass, 1 = failure.
|
|
|
|
const MAX_FRAMES := 900
|
|
|
|
var frame := 0
|
|
var gm: Node = null
|
|
var started := false
|
|
var step := 0
|
|
var failures: Array[String] = []
|
|
|
|
func _initialize() -> void:
|
|
root.size = Vector2i(1280, 720)
|
|
root.add_child(load("res://scenes/main.tscn").instantiate())
|
|
|
|
func _process(_delta: float) -> bool:
|
|
frame += 1
|
|
if gm == null:
|
|
gm = root.find_child("GameManager", true, false)
|
|
return false
|
|
if not started:
|
|
gm.call("_on_venue_selected", "hollywood_building")
|
|
started = true
|
|
return false
|
|
if frame > MAX_FRAMES:
|
|
_fail("timeout before the emergency aftermath ran")
|
|
return true
|
|
if frame < 200:
|
|
return false
|
|
if step == 0:
|
|
if gm.npcs.is_empty():
|
|
return false
|
|
gm.call("_on_guard_target", gm.npcs[0])
|
|
# A tourist: no weapon, low danger.
|
|
gm.current_npc_truth = {"weapons_seen": false, "danger_level": 1}
|
|
var rating_before: float = gm.coordinator.reputation.rating
|
|
gm.call("_on_radio_submitted", {
|
|
"npc_id": gm.current_incident_npc_id,
|
|
"location": "entrance",
|
|
"description": "tourist",
|
|
"behavior": "standing",
|
|
"weapons_seen": false,
|
|
"danger_level": 1,
|
|
}, true)
|
|
_check(gm.coordinator.investigations.size() == 1, "unjustified emergency opens one investigation")
|
|
_check(gm.coordinator.reputation.rating < rating_before, "unjustified emergency loses reputation")
|
|
_check(gm.coordinator.radio.pending_calls.size() == 1, "the emergency call is pending")
|
|
_check(not gm.door.locked, "the door is open before the responder arrives")
|
|
step = 1
|
|
if step == 1:
|
|
# Fast-forward the arrival delay.
|
|
gm.coordinator.radio.tick(30.0)
|
|
_check(gm.coordinator.radio.pending_calls.is_empty(), "the responder arrived")
|
|
_check(gm.coordinator.responder_outcomes.has("evacuation"), "low-danger emergency resolves as evacuation")
|
|
_check(gm.door.locked, "evacuation locks the door")
|
|
var clearing := 0
|
|
for npc in gm.npcs:
|
|
if is_instance_valid(npc) and (npc.reaction_mode == "flee" or npc.npc.leave_requested):
|
|
clearing += 1
|
|
_check(clearing > 0, "evacuation sends people away from the entrance (%d clearing)" % clearing)
|
|
var review: Dictionary = gm.coordinator.build_review()
|
|
_check((review.get("review", review).get("responder_outcomes", []) as Array).has("evacuation"),
|
|
"the review carries the responder outcome")
|
|
_check((review.get("investigations", []) as Array).size() == 1, "the review carries the investigation note")
|
|
step = 2
|
|
if step == 2:
|
|
_finish()
|
|
return true
|
|
return false
|
|
|
|
func _check(condition: bool, message: String) -> void:
|
|
if condition:
|
|
print("ok: " + message)
|
|
else:
|
|
_fail(message)
|
|
|
|
func _fail(message: String) -> void:
|
|
failures.append(message)
|
|
_finish()
|
|
|
|
func _finish() -> void:
|
|
if failures.is_empty():
|
|
print("SMOKE OK: emergency aftermath (investigation, evacuation, review)")
|
|
quit(0)
|
|
else:
|
|
for message in failures:
|
|
printerr("SMOKE FAIL: " + message)
|
|
quit(1)
|