NPC Encounter Zones
Related: 19 — NPCs, 25 — NPC lifecycle, 34 — Aggro & leash, 43 — Boundary zones.
1. Summary
Wildlife circles (wildlife_spawns) remain always-on population. Encounter zones are a rectangular overlay: on-demand packs that appear while players occupy the box, fight with existing NPC behaviors, then vanish when ignored or wiped.
They are not a replacement for wildlife anchors. Same NPC defs (wolf, goblin, …) and predator FSM / aggro / leash.
2. Occupancy
One live pack per zone. Attempt a roll when there is at least one player in the zone, no alive pack, and cooldown elapsed. Evaluate on:
- player edge-enter
- periodic
check_interval_tickswhile occupied - cooldown expiry while still occupied (after a wipe or idle-despawn)
Hired workers and town NPCs do not occupy the zone.
3. Pool
Loot-table style: each row rolls its own chance. Then clamp to count_min…count_max (drop extras at random; pad shortfalls with weighted picks using chance as weight).
encounter_chance is an independent gate before the pool (default 1.0). Interval + cooldown pace density.
Pack members scatter in a disk (spawn_scatter_m) around a random occupying player, then clamp into the rectangle (same clear-position / collision path as wildlife).
4. Combat and idle despawn
Idle wander uses the zone AABB as territory. Chase and combat use the normal home leash (members may leave the rect); return-to-idle reclamps.
After idle_despawn_ticks with no combat and no interaction, despawn alive pack members. Corpses, carcasses, and ground loot stay (NPC lifecycle).
Activity that resets the idle timer (any pack member):
- Threat table non-empty, or AI state in
alert/chase/combat/telegraph - Player talk / trade / target on that NPC
- Damage dealt or taken involving the pack
Standing nearby without engaging does not reset the timer.
When no alive members remain, cooldown starts (corpses do not block the next pack). Encounter instances skip wildlife-anchor respawn (SpawnSource::Encounter; zone id on anchor_id).
5. Data model
Segment YAML (encounter_zones on WorldSegment):
| Field | Purpose |
|---|---|
id |
Zone id |
label |
Designer-facing name |
z_order |
Stack order |
rects / x0 y0 x1 y1 |
Axis-aligned rectangle (editor writes one rect) |
count_min / count_max |
Pack size clamp after pool rolls |
encounter_chance |
Chance a check becomes a pack |
check_interval_ticks |
Occupied re-check cadence |
cooldown_ticks |
Wait after pack ends |
idle_despawn_ticks |
Ignore timeout for alive members |
spawn_scatter_m |
Scatter disk around an occupying player |
pool[] |
npc_ref + chance rows |
Sim: crates/sim/src/encounter_zones.rs, ticked from the region loop with wildlife AI. Snapshot persists live instance ids and zone timers so a region bounce does not duplicate packs.
Protocol: EncounterZoneView on world/AOI (label + rects). Gfx debug overlay is optional (boundary zones are not drawn in play yet).
6. Admin
- Map layout tab: layer encounter_zones, Draw Encounter Zone, property panel (counts, chances, intervals, Add pool rows with catalog NPC labels).
- Draw / select / resize / lock / marquee / duplicate / delete match boundary zones.
- Save via the existing map layout API → segment YAML.
Native flatland-editor parity is not required.
7. Example
starter-plains — bog-ambush over the bog / woods (wolf + goblin pool). Existing wildlife circles stay.
Follow-ups
- Gfx client draw of
encounter_zonesfrom world view (debug/designer). - Interior room encounter volumes.
- Per-player instanced packs.
- Client HUD “you feel watched”.
- Stacking multiple live packs in one zone.