extends RefCounted ## PLAN verbs added after the radial pass: eye contact, recognition as a player ## decision, the body-camera flag, blocking station, and requesting a reason. const Rules := preload("res://src/core/rules.gd") const ActionRadial := preload("res://src/game/ui/action_radial.gd") const IncidentRecorder := preload("res://src/core/incident_recorder.gd") const NPCController := preload("res://src/core/npc_controller.gd") const NpcProfile := preload("res://src/core/npc_profile.gd") const AccessLoadout := preload("res://src/core/access_loadout.gd") const AccessArtifact := preload("res://src/core/access_artifact.gd") func _slice_index(id: String) -> int: for i in ActionRadial.SLICE_DEFS.size(): if str(ActionRadial.SLICE_DEFS[i]["id"]) == id: return i return -1 func test_radial_has_new_verbs() -> bool: var a := TestUtils.check(_slice_index("eye_contact") >= 0, "eye contact slice exists") var b := TestUtils.check(_slice_index("reason") >= 0, "request-reason slice exists") var c := TestUtils.check(_slice_index("recognize") >= 0, "recognize slice exists") return a and b and c func test_eye_contact_is_level_one() -> bool: var def: Dictionary = ActionRadial.SLICE_DEFS[_slice_index("eye_contact")] var ok := TestUtils.check_eq(int(def.get("level", -1)), Rules.Level.SIMPLE_DETERRENCE, "eye contact is a Level 1 action") var action_ok := TestUtils.check_eq(str(def.get("action", "")), "eye contact", "logs as eye contact") return ok and action_ok func test_every_slice_has_a_string_key() -> bool: var strings = load("res://src/core/strings.gd") var ok := true for def in ActionRadial.SLICE_DEFS: var key: String = str(def.get("label_key", "")) if key == "": ok = false continue if strings.lookup(key) == key: ok = false return TestUtils.check(ok, "every radial slice resolves a localized label") func _profile(name: String, lines: Array[String] = []) -> NpcProfile: var p := NpcProfile.new() p.id = "test" p.display_name = name p.dialogue_lines = lines return p func test_request_reason_uses_dialogue_lines() -> bool: var p := _profile("Visitor", ["I have a ticket for tonight.", "I am meeting a friend."]) var npc := NPCController.new() npc.setup("npc_1", p, "enter", 4242) var a := TestUtils.check_eq(npc.request_reason(), npc.last_reason_reply, "reply is stored") var b := TestUtils.check_eq(npc.last_reason_reply, p.dialogue_lines[0], "seeded pick is deterministic") var c := TestUtils.check(not npc.last_reason_reply.is_empty(), "reply is non-empty") npc = null return a and b and c func test_request_reason_silent_without_lines() -> bool: var npc := NPCController.new() npc.setup("npc_2", _profile("Silent"), "enter", 7) var reply: String = npc.request_reason() var ok := TestUtils.check_eq(reply, "", "no dialogue lines means silence") npc = null return ok func test_player_recognition_is_not_default() -> bool: var p := _profile("Staffer") p.npc_type = Rules.NpcType.AUTHORIZED_GUEST var npc := NPCController.new() npc.setup("npc_3", p, "enter", 11) var a := TestUtils.check(not npc.player_recognized, "guests are not pre-recognized") var override := {} var artifacts := AccessLoadout.build(p, null, override) var has_recognition := false for artifact in artifacts: if artifact is AccessArtifact and artifact.kind == Rules.AccessKind.RECOGNITION: has_recognition = true var b := TestUtils.check(not has_recognition, "guest artifact set has no recognition") npc = null return a and b func test_recorder_starts_and_closes_incidents() -> bool: var recorder := IncidentRecorder.new() var a := TestUtils.check(not recorder.has_open_incident("npc_a"), "no incident before start") recorder.start_incident("npc_a", "player_focus") var b := TestUtils.check(recorder.has_open_incident("npc_a"), "incident opens") recorder.add_event("npc_a", {"type": "recognition_decision", "recognized": true}) var c := TestUtils.check_eq(recorder.incidents.size(), 0, "open incident is not yet in history") var closed: int = recorder.close_all_open("unresolved") var d := TestUtils.check_eq(closed, 1, "close_all_open closes the open incident") var e := TestUtils.check_eq(recorder.incidents.size(), 1, "closed incident lands in history") var events: Array = recorder.incidents[0].get("events", []) var f := TestUtils.check_eq(events.size(), 1, "recorded event survived the close") recorder = null return a and b and c and d and e and f func test_shift_end_closes_open_incidents_into_review() -> bool: var Coordinator := load("res://src/core/shift_coordinator.gd") var Factory := load("res://src/content/scenario_factory.gd") var coordinator = Coordinator.new() coordinator.setup(Factory.hollywood_building(), 60.0, 99) coordinator.start() coordinator.recorder.start_incident("npc_x", "player_action") coordinator.recorder.add_event("npc_x", {"type": "reason_requested", "reply": ""}) coordinator.call("_finish_shift") var review: Dictionary = coordinator.build_review() var incidents: Dictionary = review.get("incidents", {}) var list: Array = incidents.get("incidents", []) var a := TestUtils.check_eq(list.size(), 1, "unresolved incident appears in the review") var b := TestUtils.check_eq(str(list[0].get("outcome", "")), "unresolved", "outcome is unresolved") var open := TestUtils.check_eq(int(incidents.get("open_count", -1)), 0, "nothing left open") coordinator = null return a and b and open func test_body_camera_not_on_by_default() -> bool: var src := FileAccess.get_file_as_string("res://src/game/game_manager.gd") var a := TestUtils.check(src.contains("func _set_body_camera"), "body camera has a setter") var b := TestUtils.check(src.contains("KEY_B"), "B toggles the body camera") var c := TestUtils.check( src.contains("level == Rules.Level.FORMAL_WARNING and not body_camera_on"), "a formal warning turns the body camera on") return a and b and c func test_blocking_stations_the_guard() -> bool: var src := FileAccess.get_file_as_string("res://src/game/game_manager.gd") var a := TestUtils.check(src.contains("func _station_between"), "blocking has a stationing step") var b := TestUtils.check(src.contains("_station_between(controller"), "blocking calls it") var c := TestUtils.check(src.contains("to_door.normalized() * 0.9"), "station point is between NPC and door") return a and b and c