0063 — NPC scheduler and lifecycle hardening (Group H)¶
Status¶
Accepted.
Context¶
Eight audit findings on the NPC scheduler and lifecycle layer. One critical (coverage-gap grief), seven standard (correctness, default values, edge cases, social-graph future work). The cluster is mostly clarification work plus one structural change (zero-gap handoff after a Marshal KIA).
Decision¶
N-F1 — Zero-gap promotion after Marshal KIA¶
The "15-minute coverage gap" between a Marshal KIA and the next NPC stepping in was the time until backup-fill from the recruit pool. The closure: the secondary on-duty NPC promotes to primary role immediately on KIA detection. The recruit-pool fill happens for the now-vacant backup slot — a fresh recruit takes the slot immediately and serves at reduced stats throughout the 7-day recruit stage per ../SYSTEMS/npc-lifecycle.md. The (formerly-backup, now-promoted) primary covers normally during this period — there is always a designated authority on duty.
def handle_npc_kia(npc, sector):
role = npc.duty_role # 'primary_marshal' | 'backup_marshal' | 'enforcer' | ...
if role == 'primary_marshal':
backup = sector.npc_roster.find_by_role('backup_marshal', status='on_duty')
if backup:
backup.duty_role = 'primary_marshal'
backup.promotion_at = utcnow()
outbox.queue('npc.role_promoted', { ... })
sector.npc_roster.queue_recruit('backup_marshal') # 7d recruit stage (engagement-eligible at reduced stats)
else:
# No backup on duty — fall through to emergency spawn (rare; only on
# double-KIA or sectors with reduced rosters by design)
sector.npc_roster.queue_emergency_recruit('primary_marshal')
The dual-KIA edge case (both primary and backup down simultaneously) falls through to an emergency recruit; this is rare enough that the temporary gap is acceptable. The on-station rotation pattern is the load-bearing defence.
N-D2 — Respawn cooldown vs recruit training period¶
The audit confused two timers and one activity-block. They are distinct:
- Respawn cooldown — same NPC identity returns after KIA. 15 minutes. During this window
NPCCharacter.status = respawningand the slot is vacant. After cooldown the NPC re-enters its existing slot at full stats (its career and reputation persist). - Recruit lifecycle stage — a new NPC entering the roster (succession after KIA-without-respawn, slot expansion, post-genocide refill). 7 real-time days at
lifecycle_stage = recruitper the existingnpc-lifecycle.md(not changed by this ADR). During the recruit stage the NPC is on the duty roster and is engagement-eligible — with reduced combat effectiveness, scanner range, and a "Trainee" flavor cue. The faction commits to having an authority on duty even if it's a learner. trainactivity block — within a single day's schedule, the NPC may have atrainactivity block (at a training facility honing skills). During that block,engagement_eligible = falsefor that NPC. This is unrelated to the lifecycle stage; anactiveveteran NPC may still havetrainblocks in their schedule.
The respawn-cooldown timer and the recruit-stage timer are independent. A respawning NPC re-occupies its existing slot at the 15-minute mark; a fresh recruit takes a vacant slot immediately and serves throughout the 7-day recruit stage at reduced effectiveness, then transitions to active.
NPCCharacter.status enum gains a respawning value (per ../DATA_MODELS/npcs.md update) for the 15-minute cooldown window. The lifecycle-stage column already exists.
N-D3 — KIA reroute supersedes scheduled shift handoff¶
When a scheduled shift handoff is pending and a KIA fires, the KIA-driven reroute takes precedence. The pending handoff is cancelled and rebuilt from the post-KIA roster state. This avoids the "teleport redundancy" artifact where two NPCs would swap places at the same moment a third NPC's death triggered a different reroute.
Rule: any KIA event invalidates pending shift-handoff orders for the affected sector. The shift scheduler reads the post-KIA roster on its next tick.
N-F4 — NPC barracks "advisory cap" semantics¶
Barracks have two cap thresholds:
- Advisory cap — soft limit defined per barracks. Recruits added past advisory cap enter
delayed_trainstatus — they exist in the roster but their training timer is paused until an active slot frees up. Reflects "we're at capacity, please wait." - Hard cap = 2× advisory cap. Attempts to add a recruit past hard cap reject with
ERR_BARRACKS_FULL. The faction's recruit-generation logic stops queueing additional recruits for this barracks until the queue drains.
Existing barracks rows get advisory_cap populated from the per-class default (Class-3 garrison: 8 advisory / 16 hard; Class-5 mega-barracks: 24 advisory / 48 hard). Operators can tune advisory_cap per-barracks; hard_cap is computed.
N-I1 — Engagement-routing distance cap¶
Default 5 sectors warp-tunnel distance for NPC engagement routing. NPCs won't pursue threats past this radius from their assigned duty sector. Implementation reads the warp-tunnel graph; no Quantum-Jump pursuit (consistent with the Roving Fleet Camp reachability rule from ADR-0060 G-V3).
The cap is per-NPC-class tunable (e.g., Sector Marshals 5 hops; Faction Patrol Captains 8 hops; Pirate Lords 3 hops — tighter, they don't roam far from their stronghold). Defaults live in NPC_CLASS_ROUTING_DISTANCE per ../SYSTEMS/npc-scheduler.md.
N-I2 — Social affinity / rivals graph¶
📐 Design-only future work. A future iteration will add an NPCRelationship table for affinity / rivalry / mentor relationships between NPCs in the same faction or sector. Effects could include morale modifiers when NPCs work alongside friends or rivals, narrative beats when a rival NPC is killed, and ARIA-narrated faction politics.
Not Launch-blocking. The placeholder is documented in ../SYSTEMS/npc-lifecycle.md so future work has a known landing surface.
N-I3 — Patrol-squad coherence scan¶
A periodic service (per ADR-0053) verifies squad members are within 3 sectors of squad leader every 5 minutes. Out-of-coherence members receive a reroute order to rejoin. The scan also catches squads with KIA leaders and re-elects a new leader from the surviving members (highest-rank or longest-serving tiebreaker).
# patrol_squad_coherence_sweep — 5-minute cadence
for squad in active_patrol_squads():
if squad.leader_status == 'kia':
new_leader = elect_new_leader(squad)
squad.leader_id = new_leader.id
for member in squad.members:
distance = warp_tunnel_distance(member.sector, squad.leader.sector)
if distance > 3:
issue_reroute(member, target_sector=squad.leader.sector)
N-V4 — Genocide rapid-recovery (no region-wide penalty)¶
Per the user pick, coordinated Marshal wipes don't trigger a region-wide state change. Instead, the detection signal triggers a rapid-recovery flood:
- Trigger: ≥3 Marshal-tier NPC kills within a 30-minute window in the same region.
- Response: faction recruit-generation rate doubles for 1 hour in the affected region. Recruits spawned during this window reach
activelifecycle stage at half the normal duration (3.5 days instead of 7). - No region-wide debuff: patrol response, faction-rep, and policing all continue at normal rates. The region recovers naturally; players don't suffer ambient penalty for the wipe.
The lighter touch reflects a design preference for rapid, faction-led recovery rather than a region-state debuff that punishes the whole population for a coordinated team's actions. ARIA narrates: "Heavy losses among regional authorities — faction reinforcements arriving in waves." No mandatory tribute, no rep penalty on attackers, no Lawless Region state.
The detection itself logs a npc.coordinated_genocide_detected realtime event and surfaces in the operator dashboard, which gives ops an early signal of coordinated griefing patterns even though no automatic gameplay penalty fires.
Consequences¶
- N-F1 introduces an
npc.role_promotedrealtime event and theNPCCharacter.duty_rolefield becomes mutable mid-shift. Existing scheduler logic that assumes static duty assignment over a shift must read liveduty_role, not a snapshot from shift start. - N-D2 schema additions are forward-only; existing NPC rows get
engagement_eligible_at = created_at(i.e., immediately eligible) at migration, since their training period is presumed past. - N-D3 removes a class of teleport-redundancy bugs but requires the shift scheduler to re-read roster state on every tick rather than caching across handoff cycles.
- N-F4 advisory/hard cap distinction is operator-visible — admin UI surfaces both numbers per barracks. The training-pause semantics for
delayed_trainrecruits requires a tiny scheduler change but the schema is backwards-compatible. - N-I1 the 5-sector default may need tuning per-faction at Launch; the table-driven approach makes this a config change, not code.
- N-I3 patrol-squad coherence sweep is a small periodic job. Squad leader re-election logic is deterministic (rank then longest-serving) — no ambiguity in tiebreakers.
- N-V4 is a deliberately lighter touch than a full Lawless Region state. If griefing patterns emerge that the rapid-recovery flood doesn't deter, a follow-up ADR can promote the response to a region-state change without changing the detection signal.
Alternatives considered¶
- Emergency NPC spawn at reduced capability (N-F1). Rejected per user pick — weaker authority is still exploitable; zero-gap promote is structurally cleaner.
- Lawless Region state with mandatory tribute + 24-72h duration (N-V4). Considered (was the recommended pick); rejected per user pick. Region-wide debuff punishes the whole population for a coordinated team's actions; rapid-recovery flood targets the actual gap (NPC supply) without ambient cost.
- Per-sector advisory cap with no hard cap (N-F4). Rejected — unbounded queue creates training-time-pressure attacks where the queue depth itself becomes a faction debility.
- Live social-affinity graph at Launch (N-I2). Rejected — out of scope for Launch; the placeholder documents the future direction.
Related¶
- ADR-0053 — periodic-service surface for the patrol-squad coherence sweep.
- ADR-0060 G-V3 — warp-tunnel reachability rule (N-I1 follows the same constraint).
../SYSTEMS/npc-scheduler.md— engagement routing, shift handoff, KIA reroute, coverage-gap promotion.../SYSTEMS/npc-lifecycle.md— respawn cooldown, recruit training, social-graph placeholder, genocide rapid-recovery.../DATA_MODELS/npcs.md—NPCCharacter.status,engagement_eligible_at,duty_rolemutation.../SYSTEMS/realtime-bus.md—npc.role_promoted,npc.coordinated_genocide_detectedevents.