Add emergency aftermath: evacuation, investigation, review copy
- 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.
This commit is contained in:
1 parent
71b0b53705
commit
921bab796e
25 files changed
+1019
-23
No files matched your search
@@ -0,0 +1,103 @@
|
||||
extends RefCounted
|
||||
## Emergency aftermath: responder outcomes reach the review, evacuation clears the
|
||||
## entrance and locks the door, and an unjustified call opens an investigation.
|
||||
|
||||
const Rules := preload("res://src/core/rules.gd")
|
||||
const RadioSystem := preload("res://src/core/radio_system.gd")
|
||||
const Coordinator := preload("res://src/core/shift_coordinator.gd")
|
||||
const Factory := preload("res://src/content/scenario_factory.gd")
|
||||
const Strings := preload("res://src/core/strings.gd")
|
||||
|
||||
func _coordinator() -> Object:
|
||||
var c := Coordinator.new()
|
||||
c.setup(Factory.hollywood_building(), 60.0, 4242)
|
||||
return c
|
||||
|
||||
func test_responder_outcomes_reach_the_review() -> bool:
|
||||
var c := _coordinator()
|
||||
c.responder_outcomes.append("arrest")
|
||||
c.responder_outcomes.append("command")
|
||||
var review: Dictionary = c.build_review()
|
||||
var outcomes: Array = review.get("responder_outcomes", [])
|
||||
var a := TestUtils.check_eq(outcomes, ["arrest", "command"], "responder outcomes are in the review")
|
||||
var b := TestUtils.check(review.has("investigations"), "review carries an investigations list")
|
||||
c = null
|
||||
return a and b
|
||||
|
||||
func test_outcomes_reset_with_a_new_shift() -> bool:
|
||||
var c := _coordinator()
|
||||
c.responder_outcomes.append("evacuation")
|
||||
c.investigations.append("note")
|
||||
c.setup(Factory.hollywood_building(), 60.0, 1)
|
||||
var a := TestUtils.check_eq(c.responder_outcomes.size(), 0, "outcomes reset on setup")
|
||||
var b := TestUtils.check_eq(c.investigations.size(), 0, "investigations reset on setup")
|
||||
c = null
|
||||
return a and b
|
||||
|
||||
func test_evacuation_is_the_emergency_default() -> bool:
|
||||
var call := {"type": "emergency", "report": {"danger_level": 2, "weapons_seen": false}}
|
||||
var a := TestUtils.check_eq(RadioSystem.responder_outcome(call), "restraint", "danger 2 restrains")
|
||||
var call2 := {"type": "emergency", "report": {"danger_level": 1, "weapons_seen": false}}
|
||||
var b := TestUtils.check_eq(RadioSystem.responder_outcome(call2), "evacuation", "low-danger emergency evacuates")
|
||||
var call3 := {"type": "assist", "report": {"danger_level": 1, "weapons_seen": false}}
|
||||
var c := TestUtils.check_eq(RadioSystem.responder_outcome(call3), "command", "assistance just commands")
|
||||
var call4 := {"type": "emergency", "report": {"danger_level": 3, "weapons_seen": true}}
|
||||
var d := TestUtils.check_eq(RadioSystem.responder_outcome(call4), "arrest", "weapons arrest")
|
||||
return a and b and c and d
|
||||
|
||||
func test_evacuation_wiring_locks_the_door_and_clears_npcs() -> bool:
|
||||
var src := FileAccess.get_file_as_string("res://src/game/game_manager.gd")
|
||||
var a := TestUtils.check(src.contains("func _evacuate_entrance"), "evacuation has a handler")
|
||||
var b := TestUtils.check(src.contains('if outcome == "evacuation":'), "arrival routes evacuation")
|
||||
var c := TestUtils.check(src.contains("door.set_locked(true)"), "evacuation locks the door")
|
||||
var d := TestUtils.check(src.contains("npc.start_flee(door_pos)"), "evacuation sends NPCs away")
|
||||
var e := TestUtils.check(src.contains("controller.leave_requested = true"), "evacuated NPCs are marked as leaving")
|
||||
return a and b and c and d and e
|
||||
|
||||
func test_unjustified_emergency_is_detected() -> bool:
|
||||
var src := FileAccess.get_file_as_string("res://src/game/game_manager.gd")
|
||||
var a := TestUtils.check(src.contains("func _is_unjustified_emergency"), "unjustified check exists")
|
||||
var b := TestUtils.check(src.contains("return not weapons and danger <= 1"), "no weapon and low danger is unjustified")
|
||||
var c := TestUtils.check(src.contains("coordinator.investigations.append"), "an investigation note is recorded")
|
||||
var d := TestUtils.check(src.contains('"type": "investigation_opened"'), "the incident log records the investigation")
|
||||
var e := TestUtils.check(src.contains('coordinator.reputation.apply("false_report"'), "the false-report hit applies")
|
||||
return a and b and c and d and e
|
||||
|
||||
func test_radio_arrival_reconnects_after_setup() -> bool:
|
||||
## coordinator.setup() builds a new RadioSystem, so the arrival handler must be
|
||||
## re-connected each shift or dispatched responders never resolve anything.
|
||||
var src := FileAccess.get_file_as_string("res://src/game/game_manager.gd")
|
||||
var a := TestUtils.check(src.contains("func _connect_radio"), "radio wiring has a helper")
|
||||
var b := TestUtils.check(src.contains("coordinator.radio.responder_arrived.connect(_on_responder_arrived)"),
|
||||
"the helper connects the arrival handler")
|
||||
var c := TestUtils.check(src.contains("_connect_radio()"), "shifts call the helper")
|
||||
return a and b and c
|
||||
|
||||
func test_review_strings_exist() -> bool:
|
||||
var a := TestUtils.check(Strings.lookup("review.responder") != "review.responder", "responder line exists")
|
||||
var b := TestUtils.check(Strings.lookup("review.investigations") != "review.investigations", "investigation line exists")
|
||||
var c := TestUtils.check(Strings.lookup("review.none") != "review.none", "empty placeholder exists")
|
||||
var d := TestUtils.check(Strings.lookup("responder.evacuated") != "responder.evacuated", "evacuation log line exists")
|
||||
var e := TestUtils.check(Strings.lookup("investigation.started") != "investigation.started", "investigation log line exists")
|
||||
return a and b and c and d and e
|
||||
|
||||
func test_review_renders_outcomes_and_investigations() -> bool:
|
||||
var Review := load("res://src/game/ui/shift_review.gd")
|
||||
var panel: Node = Review.new()
|
||||
var review := {
|
||||
"scenario_id": "hollywood_building",
|
||||
"reputation": {"rating": 61.0, "grade": "C", "modifiers": []},
|
||||
"incidents": {"incidents": []},
|
||||
"crowd": {"mood": 0.4, "panic": false},
|
||||
"radio": {"battery": 70.0, "emergency_cooldown_s": 0.0},
|
||||
"spawned_count": 3,
|
||||
"responder_outcomes": ["evacuation"],
|
||||
"investigations": ["Emergency call reviewed"],
|
||||
}
|
||||
panel.show_review(review, Factory.hollywood_building(), [])
|
||||
var text: String = panel.report_label.text
|
||||
var a := TestUtils.check(text.contains("evacuation"), "review names the responder outcome")
|
||||
var b := TestUtils.check(text.contains("Emergency call reviewed"), "review shows the investigation note")
|
||||
var c := TestUtils.check(text.contains("Investigations:"), "investigations line is labelled")
|
||||
panel.free()
|
||||
return a and b and c
|
||||
Reference in new issue
Block a user