Implement NPC perception, returning trespassers, responder dispatch, wand screening, and accessibility polish
CI / headless-tests (push) Successful in 2m11s

This commit is contained in:
zwitschi committed 2026-10-31 12:31:13 +01:00
1 parent 439e0a4281
commit 81b5d5ce5a
22 files changed
+879 -73

No files matched your search

+56
View File
@@ -81,3 +81,59 @@ func test_fsm_transitions() -> bool:
npc.apply_player_action(Rules.Level.SIMPLE_DETERRENCE, Rules.Tone.FRIENDLY)
var left := TestUtils.check_eq(npc.fsm.current_state, "LEAVING", "complies and leaves")
return started and left
func test_returning_trespasser_circles_back() -> bool:
## A trespasser with return_probability 1.0 will always circle back after
## being directed away. The return timer fires and the FSM re-approaches.
var p := _profile_with_defaults("trespasser", Rules.NpcType.TRESPASSER, 0.35)
p.return_probability = 1.0
var npc := NPCController.new()
npc.setup("rt1", p, "trespass", 4242, "AT_ENTRANCE")
npc.apply_player_action(Rules.Level.SIMPLE_DETERRENCE, Rules.Tone.PROFESSIONAL)
var left := TestUtils.check_eq(npc.fsm.current_state, "LOITERING_DISTANT", "directed away loiters before returning")
var scheduled := TestUtils.check(npc.will_return, "return scheduled")
# Advance past the return delay (max 60s).
var returned := false
for i in 120:
if npc.tick_return(1.0):
returned = true
break
var came_back := TestUtils.check(returned, "NPC circles back within delay window")
var reapproaching := TestUtils.check_eq(npc.fsm.current_state, "APPROACH", "FSM returns to APPROACH")
var not_leaving := TestUtils.check_eq(npc.decide_leave(), false, "leave flag cleared on return")
return left and scheduled and came_back and reapproaching and not_leaving
func test_tourist_never_returns() -> bool:
## A tourist (return_probability 0) never schedules a return.
var p := _profile_with_defaults("tourist", Rules.NpcType.TOURIST, 0.9)
var npc := NPCController.new()
npc.setup("t2", p, "photo", 7, "AT_ENTRANCE")
npc.apply_player_action(Rules.Level.SIMPLE_DETERRENCE, Rules.Tone.FRIENDLY)
return TestUtils.check_eq(npc.will_return, false, "tourist does not schedule a return")
func test_perception_pass_bands() -> bool:
## NPCs notice the guard within the aware radius and react to close
## proximity based on temperament.
var calm := _profile_with_defaults("tourist", Rules.NpcType.TOURIST, 0.9)
calm.temperament = 0.1
var npc := NPCController.new()
npc.setup("p1", calm, "photo", 11, "AT_ENTRANCE")
var far := npc.perceive_guard(20.0)
var a := TestUtils.check_eq(far["band"], "unaware", "far guard is unnoticed")
var b := TestUtils.check_eq(far["noticed"], false, "far guard not noticed")
var aware := npc.perceive_guard(4.0)
var c := TestUtils.check_eq(aware["band"], "aware", "within aware radius")
var d := TestUtils.check_eq(aware["noticed"], true, "guard noticed")
var near := npc.perceive_guard(1.0)
var e := TestUtils.check_eq(near["band"], "near", "within near radius")
var f := TestUtils.check_eq(near["reaction"], Rules.Reaction.COMPLY, "calm NPC complies when guard is near")
return a and b and c and d and e and f
func test_perception_hotheaded_reacts_to_proximity() -> bool:
## A hot-headed NPC argues when the guard stands close.
var hot := _profile_with_defaults("trespasser", Rules.NpcType.TRESPASSER, 0.35)
hot.temperament = 0.8
var npc := NPCController.new()
npc.setup("p2", hot, "trespass", 22, "AT_ENTRANCE")
var near := npc.perceive_guard(1.5)
return TestUtils.check_eq(near["reaction"], Rules.Reaction.ARGUE, "hot-headed NPC argues at close range")