Implement NPC perception, returning trespassers, responder dispatch, wand screening, and accessibility polish
CI / headless-tests (push) Successful in 2m11s
CI / headless-tests (push) Successful in 2m11s
This commit is contained in:
1 parent
439e0a4281
commit
81b5d5ce5a
22 files changed
+879
-73
No files matched your search
@@ -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")
|
||||
Reference in new issue
Block a user