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
@@ -21,6 +21,20 @@ var leave_requested := false
|
||||
var reaction: int = Rules.Reaction.COMPLY
|
||||
var reaction_set := false
|
||||
|
||||
## Returning-trespasser state: an NPC directed away may circle back later.
|
||||
var will_return := false
|
||||
var return_delay_s := 0.0
|
||||
var return_elapsed_s := 0.0
|
||||
|
||||
## Perception pass: NPCs notice the guard's proximity and react (PLAN.md:
|
||||
## Level 0 Observation). Proximity bands in meters.
|
||||
const AWARE_RADIUS := 6.0
|
||||
const NEAR_RADIUS := 2.0
|
||||
var guard_noticed := false
|
||||
|
||||
## Whether this NPC has passed the handheld wand screening (The Joke Shop).
|
||||
var screened := false
|
||||
|
||||
func _init() -> void:
|
||||
fsm = FSM.new(self)
|
||||
|
||||
@@ -89,6 +103,7 @@ func _update_state_for_reaction(r: int) -> void:
|
||||
Rules.Reaction.COMPLY, Rules.Reaction.LEAVE:
|
||||
leave_requested = true
|
||||
fsm.transition_to("LEAVING")
|
||||
_maybe_schedule_return()
|
||||
Rules.Reaction.ARGUE, Rules.Reaction.RECORD:
|
||||
fsm.transition_to("ARGUING")
|
||||
Rules.Reaction.THREATEN:
|
||||
@@ -97,12 +112,68 @@ func _update_state_for_reaction(r: int) -> void:
|
||||
fsm.transition_to("FORCING_ENTRY")
|
||||
Rules.Reaction.RETURN_LATER:
|
||||
fsm.transition_to("LOITERING_DISTANT")
|
||||
will_return = true
|
||||
return_delay_s = _roll_return_delay()
|
||||
_:
|
||||
pass
|
||||
|
||||
## Decide whether a directed-away NPC will circle back, and after how long.
|
||||
## Deterministic via the NPC's seeded RNG. Only trespassers and people in
|
||||
## crisis have a nonzero default return probability (set on their profiles).
|
||||
func _maybe_schedule_return() -> void:
|
||||
if profile == null or profile.return_probability <= 0.0:
|
||||
return
|
||||
if rng.randf() < profile.return_probability:
|
||||
will_return = true
|
||||
return_delay_s = _roll_return_delay()
|
||||
# Move to the loitering state so the return transition is reachable.
|
||||
fsm.transition_to("LOITERING_DISTANT")
|
||||
|
||||
func _roll_return_delay() -> float:
|
||||
# 20-60 seconds before circling back.
|
||||
return 20.0 + rng.randf() * 40.0
|
||||
|
||||
## Advance the return timer. When it elapses, the NPC transitions from
|
||||
## LOITERING_DISTANT back to APPROACH. Returns true when a return just fired.
|
||||
func tick_return(delta: float) -> bool:
|
||||
if not will_return:
|
||||
return false
|
||||
return_elapsed_s += delta
|
||||
if return_elapsed_s >= return_delay_s:
|
||||
will_return = false
|
||||
return_elapsed_s = 0.0
|
||||
leave_requested = false
|
||||
fsm.transition_to("APPROACH")
|
||||
return true
|
||||
return false
|
||||
|
||||
func decide_leave() -> bool:
|
||||
return leave_requested
|
||||
|
||||
## Perception pass: given the guard's distance (meters), return a perception
|
||||
## result describing how the NPC reacts to the guard's presence. Pure and
|
||||
## headless-testable; the presentation layer feeds it the real distance.
|
||||
## Returns { noticed: bool, band: String, reaction: int }.
|
||||
func perceive_guard(distance: float) -> Dictionary:
|
||||
var band := "unaware"
|
||||
if distance <= NEAR_RADIUS:
|
||||
band = "near"
|
||||
elif distance <= AWARE_RADIUS:
|
||||
band = "aware"
|
||||
var noticed := band != "unaware"
|
||||
if noticed and not guard_noticed:
|
||||
guard_noticed = true
|
||||
var reaction := Rules.Reaction.IGNORE
|
||||
if band == "near":
|
||||
# Close proximity makes calm NPCs uneasy and hot-headed ones hostile.
|
||||
if profile != null and profile.temperament >= 0.6:
|
||||
reaction = Rules.Reaction.ARGUE
|
||||
else:
|
||||
reaction = Rules.Reaction.COMPLY
|
||||
elif band == "aware":
|
||||
reaction = Rules.Reaction.IGNORE
|
||||
return {"noticed": noticed, "band": band, "reaction": reaction}
|
||||
|
||||
func to_dict() -> Dictionary:
|
||||
return {
|
||||
"id": npc_id,
|
||||
|
||||
Reference in new issue
Block a user