NPC Lifecycle¶
Status: 🚧 Partial — Lifecycle has a real spine — archetype/stage/activity enums, daily_schedule execution (Loop A), recruit->active, KIA+NPCDeathLog+succession … (impl audit 2026-06-16)
The unified framework that gives every named NPC in the universe a full-life workflow — patrol shifts, off-duty rotations, sleep cycles, career progression, retirement, succession. NPCs aren't 24/7 automata; they live full days like players do. They wake, commute, work, take meals, socialize, retire to rest, and (eventually) hand off their role to a successor when they retire or are killed in action.
This doc covers the lifecycle layer that sits above the npc-scheduler. The scheduler executes the lifecycle's plans — moving NPCs along their schedules, processing role transitions, and applying state changes. The data model is in ../DATA_MODELS/npcs.md. Specific NPC archetypes (police, faction patrols, station officials, traders) are listed below; archetype-specific behavior lives in their own feature docs (e.g., ../FEATURES/gameplay/police-forces.md for Marshals and Sentinels).
Design intent¶
A persistent named NPC should feel like a real person whose life intersects with the player's at specific times and places. That means:
- Schedules. A Federation Marshal works an 8-hour patrol shift, then goes off-duty, then sleeps. They aren't always at the same sector waiting to engage you — and they aren't anywhere at all during their off-hours.
- Careers. A Marshal can be a recruit who learns the ropes, an active officer who builds reputation, a senior officer who mentors recruits, and eventually a Captain or a retired veteran. The role they hold today isn't necessarily the role they'll hold in three months.
- Succession. When Marshal Cassandra Vance is killed in action, her seat doesn't immediately fill with a faceless replacement. There's a recruitment delay, a successor steps in with her own name and personality, and Vance becomes part of the region's history (player-visible KIA log; her successor may even be named "Marshal Reyna, Vance's apprentice").
- Generality. This framework doesn't apply only to police. Pirate captains have schedules (raiding hours vs. drinking-at-the-Fringe-port hours), faction leaders have career arcs (Admiral Chen retires to a Council seat), station masters have shift rotations. The lifecycle is the canonical mechanism for any persistent named NPC.
NPC archetypes¶
The universe carries NPCs across ten archetypes, each with default behavior templates:
| Archetype | Examples | Schedule pattern | Movement pattern |
|---|---|---|---|
LAW_ENFORCEMENT |
Federation Marshal, Nexus Sentinel | 8h patrol / 8h off-duty / 8h sleep | Patrol cycle through assigned sectors |
FACTION_PATROL |
Federation Navy, AM enforcer, Frontier militia | 8h patrol / 8h off-duty / 8h sleep | Patrol cycle within faction territory |
STATION_SECURITY |
Hired station guards, Guard-Captains | 12h shift / 12h off-station (1h handoff overlap) | Patrol the station's docking ring + host sector + 1-hop ring |
HOSTILE_RAIDER |
Named pirate captain, Cabal lieutenant | Variable raiding hours; off-duty at home base | Random or themed (raids high-traffic sectors during peak hours) |
FACTION_LEADER |
Admiral Chen, Council member, Pirate Lord | Mostly stationary at HQ sector; ceremonial appearances elsewhere | Rare moves; significant when they happen |
STATION_OFFICIAL |
Station master, dock manager, contract clerk | 12h shift / 12h off-station | Stationary at one station; off-duty location nearby |
MISSION_GIVER |
Quest issuer, faction-rep representative | Office hours (e.g., 09:00–17:00 station-time) | Stationary; players visit them |
TRADER |
NPC merchant captain | Travel route between 2–4 stations on a schedule | Multi-day route with rest stops |
RESEARCHER |
Nova Scientific surveyor | Field expedition cycles (8h scanning / 4h analysis / 12h rest) | Cycles through nebula clusters |
CIVILIAN |
Refugee, colonist-in-transit, background population | Schedule varies; often passive / non-interactive | Slow drift through fed/border zones |
STATION_SECURITY is distinct from LAW_ENFORCEMENT and FACTION_PATROL in two important ways: (1) station-funded — paid by the host station's defense budget, not the faction treasury; (2) station-jurisdictioned — patrols the host sector + 1-hop ring only, not a regional route. Used by hired security guards at Standard- and Premium-tier protected stations. See ../FEATURES/economy/station-protection.md.
Each archetype defines default schedule templates and default behavior trees. A specific NPC's schedule is derived from the archetype template plus per-NPC offsets (so 8 Marshals don't all sleep at the same time — staggered shifts give continuous coverage).
The NPCCharacter.archetype enum carries the value; the per-archetype default templates live in the NPCRoster.config JSONB and can be operator-tuned per region per archetype per role.
Schedule mechanism¶
Every NPC carries a daily_schedule JSONB on the NPCCharacter row (per the schema in ../DATA_MODELS/npcs.md, extended for the lifecycle layer):
{
"timezone": "UTC",
"shift_offset_hours": 0,
"blocks": [
{
"start_minute": 0,
"end_minute": 480,
"activity": "sleep",
"location_type": "home_sector",
"location_ref": "<sector_number_in_home_region>"
},
{
"start_minute": 480,
"end_minute": 510,
"activity": "commute",
"location_type": "transit",
"location_ref": null
},
{
"start_minute": 510,
"end_minute": 990,
"activity": "patrol",
"location_type": "patrol_route",
"location_ref": "<route_id>"
},
{
"start_minute": 990,
"end_minute": 1020,
"activity": "commute",
"location_type": "transit",
"location_ref": null
},
{
"start_minute": 1020,
"end_minute": 1320,
"activity": "socialize",
"location_type": "station",
"location_ref": "<station_id>"
},
{
"start_minute": 1320,
"end_minute": 1440,
"activity": "personal",
"location_type": "home_sector",
"location_ref": "<sector_number_in_home_region>"
}
]
}
start_minute and end_minute are minute-of-day in UTC. shift_offset_hours is added to the canonical archetype template so different NPCs in the same role run at different wall-clock times — Marshal Vance's 06:00 patrol start aligns with another Marshal's 14:00 patrol start, providing continuous coverage with handoff.
Activities¶
The canonical activity vocabulary (extends as new archetypes are added):
sleep— NPC is at their home sector / barracks; not interactive; not in any sector room. Other NPCs and players don't see them. Engagement-routing skips sleeping NPCs.commute— NPC is in transit between activities. Visible in any sector they pass through; not engagement-eligible.patrol— NPC is on assigned route, fully active. Engagement-eligible (e.g., Marshal pursues Wanted players).work_station— NPC is at a station performing job duties (issuing contracts, running market trades, etc.). Visible to players who dock; not engagement-eligible (mostly).socialize— NPC is at a station or sector socializing (off-duty Marshals at the bar, traders comparing notes). Visible; can be interacted with for flavor dialog; not engagement-eligible.dine— NPC is taking a meal at a station mess hall or on their ship. Not engagement-eligible.train— NPC is at a faction training facility honing skills. Not engagement-eligible.personal— NPC is performing private business (family, hobbies). Off-grid; not visible.raid— Hostile-archetype NPC is actively raiding. Engagement-eligible by definition (they engage anyone they encounter).survey— Researcher archetype is scanning a nebula / anomaly. Engagement-eligible only if Wanted (research is non-violent).engaged— Override state set by the scheduler when the NPC enters combat. Suspends the schedule until combat resolves.reassigned— Override state during a region transfer; NPC is in transit to a new home region.
Schedule scopes¶
Three nested schedule scopes:
- Daily template (the JSONB above). 24-hour cycle.
- Weekly variations. A Marshal may have one rest day per week (no patrol; full off-duty). The
daily_schedulecarries aweekly_overridesarray indexed by ISO weekday:{"weekday": 0, "blocks": […]}(where 0 = Monday). - Career-event overrides. Promotions, retirements, KIA, and special assignments override the schedule for a defined period. These are stored as
NPCScheduleOverriderows with start/end timestamps.
Schedule timezone¶
All schedules are UTC by default. A future expansion (📐 Design-only) supports per-region "local time" anchored to the region's timezone_offset config (so a Frontier-Coalition region might run on a different shift cadence than Federation regions). At launch, every NPC schedule is UTC.
Lifecycle states¶
Each NPC carries a lifecycle_stage enum on NPCCharacter:
recruit— Newly spawned; in training. Lasts 7 real-time days (📐 tunable per archetype). Stats reduced (combat effectiveness, scanner range, etc.); flavor cue ("Marshal Vance, Trainee" in display).active— Full duty. The dominant lifecycle stage; most NPCs are here.senior— Tenure ≥ 90 real-time days. Slight stat buffs (combat +5%, scanner range +1). Flavor cue ("Senior Marshal Vance"). Eligible for promotion to a higher role (e.g., Marshal-Captain).decorated— Earned an in-faction medal (e.g., killed N Wanted players, completed N successful patrols). Stat buff scales with medal count. Flavor cue ("Captain Vance, Decorated").retired— Voluntarily left active duty (administrative or story-driven). Persists in lore and KIA logs but does not appear in any sector. Cannot be reactivated (a returning veteran is a new NPC with reference to the retired predecessor).kia— Killed in action. Same persistence rules asretiredbut distinguishable in audit; the kill is logged inNPCDeathLogwithkilled_by_player_idset.reassigned— Transferred to a different region. Brief transient state during the transfer; flips back toactiveat arrival.
Stage transitions are driven by the scheduler:
recruit→active— automatic at end of training period (7 days post-spawn).active→senior— automatic at 90 days tenure.active/senior→decorated— on medal earned (combat stats, mission completions).active/senior/decorated→retired— operator-initiated or story-event-driven (📐).- Any →
kia— combat resolver fires_handle_npc_kia. active→reassigned— operator admin action.
Career progression¶
NPCs can hold multiple roles over their lifetime. Career history lives in the role_history JSONB on NPCCharacter:
{
"current_role": "marshal",
"role_history": [
{"role": "marshal_recruit", "started_at": "2026-04-01T00:00:00Z", "ended_at": "2026-04-08T00:00:00Z", "reason": "training_complete"},
{"role": "marshal", "started_at": "2026-04-08T00:00:00Z", "ended_at": null, "reason": "active"}
],
"promotion_eligible_at": "2026-07-08T00:00:00Z",
"decorations": [
{"medal_id": "<uuid>", "awarded_at": "2026-05-15T...", "reason": "100_wanted_kills"}
]
}
Promotion rules (📐 Design-only, archetype-specific): - Marshal → Marshal-Captain: 90+ days tenure, 50+ successful pursuits, no KIA marks against them. The senior Marshal is promoted when a Captain seat opens (KIA, retirement, transfer). - Captain → Regional Director: 1+ year tenure, no major incidents. Operator-tunable. - Trader → Senior Trader: 100+ successful trade routes, established faction reputation.
Retirement rules (📐): - Voluntary at any tenure ≥ 1 year (operator-tunable per archetype). - Forced retirement at 5 years tenure for archetypes with hard caps (e.g., field NPCs whose ship class is being phased out).
When an NPC is promoted to a new role, the role's archetype may change (e.g., Senior Marshal stays LAW_ENFORCEMENT; promotion to Federation Council member shifts to FACTION_LEADER). The schedule is regenerated from the new role's archetype template; the new role's stats and behavior tree apply.
Succession on death / retirement¶
When an NPC vacates their seat (KIA or retirement), the NPCRoster target slot becomes empty. The scheduler's Loop B (roster maintenance) detects this and spawns a successor:
- The new NPC's name is drawn from the
NPCRoster.name_pool. - The new NPC starts at
lifecycle_stage = recruitwith a 7-day training period. - The new NPC's
replaced_by_idlink points at them from the predecessor's row, forming a succession chain visible in player UI. - Optional: the new NPC's
backstory.mentor_idreferences their predecessor (especially for KIA — "Marshal Reyna, who took up Vance's badge after the Hephaestus Incident").
For high-tier roles (Marshal-Captain, Faction Leader), succession may go through internal promotion instead of new-recruit-spawn: a Senior Marshal is promoted to Captain, and a fresh recruit fills the Marshal seat. This is the "internal succession" path; the alternative "fresh succession" path is for archetypes with no internal promotion candidates (most Civilian and Trader archetypes).
Respawn vs recruit-stage timers¶
Per ADR-0063 N-D2, three timers/states are distinct:
- Respawn cooldown — same NPC identity returns. 15 minutes. Applies when the NPC's archetype permits respawn (most named pirates, some trader archetypes).
NPCCharacter.status = respawningduring the cooldown; the slot is vacant. After cooldown the NPC re-enters its existing slot at full stats — career and reputation persist. - Recruit lifecycle stage — fresh NPC entering the roster after KIA-without-respawn. 7 days at reduced stats (existing rule). The recruit is engagement-eligible during this window — they're a learner on duty, not a benchwarmer. Reduced combat effectiveness + scanner range; "Trainee" flavor cue in display.
trainactivity block — within a single day's schedule, the NPC may have atrainblock at a training facility. During that blockengagement_eligible = falsefor that NPC (regardless of lifecycle stage). Active veterans may have train blocks too.
The respawn-cooldown and recruit-stage timers are independent. Slot quorum counts the slot as filled the moment a recruit is spawned — even though they're at reduced effectiveness.
Barracks advisory and hard caps¶
Per ADR-0063 N-F4, barracks have two cap thresholds:
- Advisory cap — soft limit per barracks. Recruits added past advisory cap enter
delayed_trainstatus: they exist in the roster but their lifecycle-stage timer is paused until an active slot frees up. - Hard cap = 2 × advisory cap. Attempts to add a recruit past hard cap reject with
ERR_BARRACKS_FULL. Faction recruit-generation logic stops queueing for this barracks until the queue drains.
Per-class defaults:
| Barracks class | Advisory cap | Hard cap (computed) |
|---|---|---|
| Class-1 Frontier post | 4 | 8 |
| Class-3 Garrison | 8 | 16 |
| Class-5 Mega-barracks | 24 | 48 |
Operators tune advisory_cap per-barracks; hard_cap is computed at read-time as advisory_cap × 2 and is non-configurable (the 2× ratio is a deliberate ceiling against unbounded queue depth).
Genocide rapid-recovery¶
Per ADR-0063 N-V4, coordinated Marshal-tier kills trigger faction-led rapid recovery without a region-wide debuff:
- 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 the rapid-recovery window reach
activestage at half the normal duration (3.5 days instead of 7). - No region-wide debuff: patrol response, faction-rep grants, and policing all continue at normal rates. The region recovers naturally; players don't suffer ambient penalty for the wipe.
ARIA narrates: "Heavy losses among regional authorities — faction reinforcements arriving in waves." A npc.coordinated_genocide_detected realtime event logs to the operator dashboard, giving ops an early signal of coordinated griefing patterns even without an automatic gameplay penalty. If griefing patterns later prove the rapid-recovery flood insufficient, a follow-up ADR can promote the response to a region-state change without changing the detection signal.
Social affinity / rivals graph (future)¶
📐 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.
Player-like workflows¶
NPCs perform real workflows that interact with player-visible game state. The lifecycle layer enables this; specific behaviors are archetype-defined.
Movement¶
NPCs move between sectors using the warp graph the player uses. The scheduler picks routes based on the activity (patrol cycle, commute home, raid run) and pays the same WarpTunnel.turn_cost players pay. NPCs don't have player turn pools — the scheduler is the authoritative actor — but the time a movement takes is honored: a 3-turn long tunnel on a Marshal's commute means the Marshal isn't visible at either endpoint for ~4 minutes wall-clock at L1-equivalent regen pacing. The schedule's commute blocks budget time for this realistically.
Cross-region movement is rare and reserved for reassigned lifecycle transitions. A Marshal doesn't normally leave their home region; a Trader does, regularly, on their multi-region trade route.
Combat¶
NPCs use their assigned ship and engage per the combat-resolver pipeline. KIA fires the standard destruction handler with a special branch (_handle_npc_kia) that updates the lifecycle layer. NPCs can flee, surrender (📐 — high-stage decorated NPCs may surrender to higher-rank attackers under specific conditions), or be destroyed.
Trade¶
TRADER archetype NPCs literally trade at stations — buy commodities at one station, transit to another, sell at higher prices. The market-pricing service treats their transactions identically to player transactions (they affect supply/price). This means traders can be intercepted, robbed, or out-bid by players — they're full economic actors.
Contracts¶
MISSION_GIVER archetype NPCs issue contracts visible to players. The Contract.issuer_npc_id field (📐 Design-only — extension to the contract schema) references the named NPC. Players can develop relationships with specific issuers (e.g., "Quartermaster Hara always pays well; she's at Station Prime in sector 247 from 09:00 to 17:00 UTC").
Faction interactions¶
NPCs honor faction allegiances. Federation Marshals don't engage Nova scientists. AM enforcers patrol AM-claimed sectors and engage non-licensed miners. The faction-reputation rules apply to NPC-vs-player interactions identically to NPC-vs-NPC where both sides have factional allegiance.
Schema extensions¶
The lifecycle layer extends NPCCharacter with the following new fields (the DATA_MODELS/npcs.md doc carries the full table):
| Field | Type | Purpose |
|---|---|---|
archetype |
Enum npc_archetype |
One of the 9 archetypes above. |
lifecycle_stage |
Enum npc_lifecycle_stage |
recruit / active / senior / decorated / retired / kia / reassigned. |
daily_schedule |
JSONB | Schedule blocks; supersedes the simpler patrol_route from the original v1. |
role_history |
JSONB | Career arc; current_role, role_history array, promotion_eligible_at, decorations. |
mentor_id |
UUID FK npc_characters.id, nullable | The predecessor or trainer. Player-visible succession lore. |
current_activity |
Enum npc_activity |
Live-state denormalization of "what is this NPC doing right now" — derived from schedule + overrides; cached for fast reads. |
The legacy patrol_route field becomes a kind of schedule block (activity = patrol, location_ref = <route_id>); existing rows migrate by wrapping the route into a single patrol block in the new daily_schedule JSONB.
New runtime services¶
Two new services join the npc-scheduler family:
Schedule executor¶
Runs every 5 minutes. For each NPCCharacter where lifecycle_stage is not retired/kia:
- Compute
current_minute_of_day_utc. - Find the matching schedule block (or weekly override or career override).
- Compare to
current_activity; if different, transition. - Update
current_sector_idper the new block'slocation_ref(or set NULL forsleep/personal). - Update
Sector.defenses.patrol_shipsJSONB on both the prior and new sectors to keepnpc_character_idscoherent. - Emit appropriate realtime events (
npc_off_duty,npc_began_patrol,npc_arrived_home).
Career processor¶
Runs daily. For each NPCCharacter:
- Check
lifecycle_stagetransitions: tenure-based (recruit→activeat 7 days;active→seniorat 90 days), decoration-based (medal-earned triggers). - Check promotion eligibility for archetypes with internal promotion paths.
- If eligible and a higher role's seat is open, fire promotion event: update
role_history, changearchetypeif applicable, regeneratedaily_schedulefrom the new role's template. - Emit
npc_promotedrealtime event (visible in faction news feed).
Example — Federation Marshal Cassandra Vance¶
A worked example of a Marshal's life across one week.
Day 0 (spawn): Loop B detects an open Marshal slot in Region X. Spawns "Marshal Cassandra Vance" with archetype = LAW_ENFORCEMENT, lifecycle_stage = recruit, daily_schedule from the Federation Marshal recruit template (heavy training blocks, light patrol). current_role = marshal_recruit.
Day 7: Career processor flips lifecycle_stage = active, current_role = marshal. New schedule template: 06:00–14:00 patrol on assigned route (sectors 12, 34, 47, 89, 102, 156), 14:00–18:00 socializing at the Capital station, 18:00–22:00 dining + personal, 22:00–06:00 sleep at the Capital station's barracks.
Days 7–60: Vance executes her schedule. Players in sector 12 between 06:00 and 08:00 may encounter her. Players in sector 47 between 10:00 and 12:00 may encounter her. Players entering during her sleep block see no Marshal.
Day 30: Player attacks an innocent in sector 47 at 10:30 UTC. Schedule executor reports Vance is patrolling sector 47 right now. Engagement routing dispatches her. She wins; offender flees. Returns to schedule.
Day 75: Player kills another player on Vance's beat. Vance pursues, attacks, and is destroyed in the engagement. Scheduler fires _handle_npc_kia(vance.id, killer_player_id, sector_id). Vance's lifecycle_stage = kia, current_sector_id = NULL, destroyed_at = now, respawn_eligible_at = now + 7 days. NPCDeathLog row inserted. Squad row at sector 47 updated to remove Vance's id. Realtime broadcast: "Marshal Cassandra Vance has been killed." The killer takes a −250 Federation rep hit per police-forces.md rules.
Day 82: Loop B detects the open Marshal slot. Spawns "Marshal Astrid Reyna" with archetype = LAW_ENFORCEMENT, lifecycle_stage = recruit, mentor_id = vance.id, backstory.origin = "took up Vance's badge after the Hephaestus Incident". The succession chain is now vance.replaced_by_id = reyna.id.
Day 89: Reyna flips to active. The region is fully staffed again.
The player who killed Vance now sees, on their next patrol-encounter screen, Marshal Reyna with the lore line about Vance. The universe has a memory.
Physical realism — ship parking, lodging, and shift transitions¶
The schedule mechanism above defines when an NPC is doing what; this section spells out where their ship is parked, where they physically sleep, and how shifts hand over realistically. NPCs aren't 24/7 automata — they need places to put their ships, beds to sleep in, and orderly transitions when one shift ends and the next begins.
Per-archetype lodging mapping¶
Every NPC has a home_barracks_id FK (or home_outlaw_base_id for hostile factions) pointing at their canonical sleep location. Per archetype:
| Archetype | Lodging model | Location |
|---|---|---|
LAW_ENFORCEMENT (Marshals) |
Shared NPCBarracks at host station |
Capital station; capacity 9 (8 Marshals + 1 Captain) |
LAW_ENFORCEMENT (Sentinels) |
Shared NPCBarracks in dedicated sector |
Gateway Plaza cluster, dedicated barracks sector; capacity 28 |
FACTION_PATROL (Federation Navy / AM enforcers / Frontier militia) |
Shared NPCBarracks at faction-flagged station |
Per-faction; capacity 12 |
FACTION_LEADER (Admiral, Council member, Pirate Lord) |
Two NPCBarracks rows — faction_hq (office) + private_residence (sleep) |
Each capacity 1; distinct sectors |
STATION_OFFICIAL |
Shared NPCBarracks at the station they work at |
Capacity 6 |
MISSION_GIVER |
Shared NPCBarracks at the station they work at |
Capacity 4 |
TRADER |
On-ship lodging during transit + home-station NPCBarracks for rest days |
Capacity 1 (per trader) |
RESEARCHER (Nova surveyor) |
Shared NPCBarracks at a Nova-flagged Class-11 station |
Capacity 6 |
HOSTILE_RAIDER (pirates, Cabal) |
Shared OutlawBase at hidden sector |
Capacity 12; gated by is_player_discoverable |
CIVILIAN |
None — ghost presence; current_sector_id = NULL during sleep |
n/a |
Lodging entities are first-class — see ../DATA_MODELS/npc-lodging.md.
Ship parking¶
Every NPC has a permanent 1:1 ship assignment. Ships are not shared between shifts — each NPC has their own hull for the lifetime of their lifecycle (KIA destroys both NPC and ship via the canonical destruction handler; spawn of a successor creates a new hull). This avoids the complexity of inter-shift handoff protocols at the ship level.
When the schedule executor (Loop A) transitions an NPC from active duty to off-duty / sleep:
- The NPC's ship
Ship.statusflips toDOCKED. - The ship's
Ship.sector_idis set to the host sector of the NPC'shome_barracks(orhome_outlaw_basefor hostile factions). - An entry is added to
Sector.defenses.docked_npc_shipsJSONB array:{npc_id, ship_id, status: "docked_off_duty", docked_at: now}. - The combat-resolver checks
docked_npc_ships: ships docked withstatus = "docked_off_duty"at a barracks-flagged station/sector are shielded from player attack (rejection errorERR_NPC_SHIP_AT_BARRACKS).
When the schedule transitions back to active duty:
Ship.statusflips toIN_SPACE.- The
docked_npc_shipsentry is removed. - The ship is back in regular sector circulation; player attacks are permitted (subject to standard combat rules).
Off-duty ship vulnerability rules. A ship docked at a public outlaw station (where pirates rest) is NOT at a barracks; the docked_npc_ships entry uses status: "docked_standby" and is not shielded — players who infiltrate a Fringe-Alliance port can attack the resting pirate's ship. Off-duty ships at Federation barracks stations are protected; off-duty ships at non-barracks docking facilities are not.
Auto-maintenance during sleep. Loop A's sleep transition queues a free auto-repair order at the host barracks (📐 Design-only): if the NPC's ship maintenance.current_integrity < 75% × max_integrity, the maintenance ticks up to 90% during the sleep block. Pirates have a 10–20% degradation multiplier — outlaws don't maintain as rigorously.
Trader cargo persistence. When a TRADER archetype NPC docks for off-duty rest, their Ship.cargo JSONB is preserved across the docking — partially-sold cargo carries over to the next active block. Players can observe the same trader with the same partial cargo across multiple sessions, enabling emergent storytelling ("I've been watching this trader's cargo for three days; they keep getting outbid").
Shift overlap and handoff briefing¶
Hard handoffs at exactly 14:00 lose intelligence. The design includes a 30-minute overlap window for any role that runs shifted patrol coverage:
- Marshal Vance's 06:00–14:00 shift and Marshal Singh's 14:00–22:00 shift overlap from 13:30 to 14:00. During that 30-min window, both are simultaneously on the patrol route in the
shift_handoffactivity (a new value in thenpc_activityenum). - Vance shares her threat notes with Singh via a
shift_handoff_stateJSONB on the squad row inSector.defenses.patrol_ships:
{
"shift_handoff_state": {
"outgoing_npc_id": "<vance-uuid>",
"incoming_npc_id": "<singh-uuid>",
"overlap_started_at": "<iso8601>",
"threat_notes": [
{"player_id": "<uuid>", "sector_id": 47, "note": "encountered with Wanted Status; fled north", "timestamp": "<iso8601>"}
],
"engaged_player_ids": ["<player-uuid-of-active-pursuit>"]
}
}
- The schedule executor reads
shift_handoff_stateon Singh's transition to active duty and flags him to any threats Vance passed. - After Vance's 14:00 transition out, the
shift_handoff_stateis cleared and the squad row reflects only Singh.
The schedule encoder represents overlap via a new overlap_start_minute field on each block:
{
"start_minute": 360,
"end_minute": 840,
"overlap_start_minute": 810,
"activity": "patrol",
"location_type": "patrol_route",
"location_ref": "<route_id>"
}
The overlap_start_minute (840 − 30 = 810) tells the executor "begin handoff 30 minutes before block end." The incoming NPC's block has a corresponding overlap_start_minute 30 min before their start_minute so they can pre-arrive.
Player-visible: realtime bus emits npc_handoff_started and npc_handoff_completed events with both NPC names; the player sees "Marshal Vance is briefing Marshal Singh on patrol" in the sector log.
Late or absent relief¶
If the relieving NPC isn't available at handoff time (status = engaged elsewhere, KIA, off-duty, or unspawned), the design cascades through fallback options:
- Cascade-hold (15 min). Outgoing NPC extends their patrol end-time by +15 min; the schedule executor recomputes downstream blocks. Vance's 14:00–22:00 off-duty becomes 14:15–22:15.
- Secondary recall. If still no relief at +15 min, the scheduler recalls the next-most-senior off-duty NPC of the same role from their off-duty / personal block. Their schedule is preempted; they take a 15-min commute to the patrol post.
- Coverage gap. If no secondary is available (everyone is engaged / KIA / on rest day), the route runs uncovered. The bus emits
npc_coverage_gap(player-visible in region news). The next free NPC is dispatched when one becomes available; the offender's response-grace window per the engagement-routing fallback applies.
shift_handoff_state carries an additional coverage_gap_started_at field when the gap is active.
KIA mid-handoff¶
If Vance is KIA at 13:55 (in the middle of her overlap window with Singh), the destruction handler's _handle_npc_kia calls _reroute_incoming_relief(vance.id):
- Detect Singh is the pending relief (he's in the
shift_handoffactivity). - Set Singh's
current_activity = shift_reroute(new enum value). - Teleport Singh's
current_sector_idto the patrol route's start sector. - Extend Singh's shift
end_minuteby 30 min to cover Vance's lost overlap window. - Emit
npc_emergency_coveragewith both NPC names: "Marshal Singh assumes coverage of sectors 12–47 following Marshal Vance's KIA." - The succession chain (per WR1) fires Loop B's recruit spawn at cooldown expiry.
Engagement preemption¶
If Vance is engaged (in combat) at her shift end-time:
- Set
NPCCharacter.shift_preempt_pending = true. - Singh's relief begins on schedule but the squad row carries
preempted_prior_shift_npc_id = vance.id. - When Vance's combat resolves (win, flee, or KIA), the scheduler atomically:
- If Vance survives: transitions her to
off_duty(taking her overdue rest); clearsshift_preempt_pending; clears Singh'spreempted_prior_shift_npc_id. - If Vance is KIA: handles the KIA flow per above.
- Bus emits
npc_preempt_coverage_startandnpc_preempt_coverage_endevents.
Commute feasibility¶
The schedule encodes commute durations (typically 30 min in the example template), but actual sector-traversal time depends on the warp graph between source and destination sectors. Loop A validates feasibility before transitioning to a commute block:
- Query the warp graph for the path from current sector → destination sector.
- Sum
WarpTunnel.turn_costfor each edge. - Compute wall-clock duration at standard regen pacing (~86 sec/turn at L1 ARIA).
- If
actual_duration > block.end_minute − block.start_minute, dynamically extend the block'send_minuteand cascade downstream blocks. - If route is impossible (graph disconnected, all warps one-way against the direction of travel), mark the NPC's
current_activity = error_strandedand emitnpc_commute_impossibleto the ops bus. (This shouldn't happen in normal operation; if it does, ops manually reassigns or destroys the NPC.)
Promotion handoff¶
When the career processor (daily) detects promotion eligibility, it doesn't preempt the NPC mid-shift. Instead:
- Set
NPCCharacter.promotion_pending_at = end_of_current_block. - Loop A checks this on each block transition.
- At the next block boundary, the new role's schedule template is generated and replaces the existing
daily_schedule. - If the new role uses a different barracks (e.g., Senior Marshal → Marshal-Captain who lives in private quarters instead of the shared barracks),
home_barracks_idis updated. - Bus emits
npc_promoted(public).
Reassignment travel¶
A region transfer is a multi-step transit. When an admin triggers a reassignment from Region A → Region B:
- Set
NPCCharacter.lifecycle_stage = reassigned. - Compute warp-graph path from current sector → destination region's home sector (typically via the Nexus).
- Insert a synthetic
reassignedactivity block with calculated duration (sum of warp tunnel turn costs translated to wall-clock). - The NPC is non-engagement-eligible and not visible in any sector room during the transit (akin to
commute). - On arrival: flip
home_region_idto Region B, setlifecycle_stage = active, regeneratedaily_schedulefrom the destination roster's template, sethome_barracks_idto the destination's barracks. - Bus emits
npc_reassignedwith before/after region names.
Off-duty social proximity (📐 future enhancement)¶
For lore-rich gameplay, the design supports an optional social-affinity layer (📐 Design-only). NPCCharacter.npc_social_affinity JSONB carries {"allies": [npc_id, …], "rivals": [npc_id, …]}. When multiple off-duty NPCs schedule the same station's socialize block, the scheduler can colocate allies and separate rivals. Bus emits npc_socialize_together / npc_socialize_apart for flavor narration.
This is non-blocking for launch — the affinity field defaults to empty, social blocks place NPCs at their default station's sector regardless.
Humanized realtime events¶
Instead of bare npc_arrived / npc_departed payloads, the realtime bus extends NPC events with narrative metadata:
{
"event_type": "npc_departed",
"npc_id": "<vance-uuid>",
"npc_name": "Marshal Cassandra Vance",
"prior_activity": "patrol",
"new_activity": "commute",
"reason": "shift_end",
"estimated_return_at": "<iso8601>",
"narrative": "Marshal Vance is heading back to the Capital, shift ending"
}
Player-client renders the narrative in the sector event log. The lore service generates the narrative string from the structured payload via a small templating layer (📐 Design-only).
Engagement-gap visibility¶
When the engagement-routing fallback fires (no Marshal available in-region), the bus emits npc_response_delay:
{
"event_type": "npc_response_delay",
"region_id": "<uuid>",
"reason": "all_marshals_engaged | all_marshals_kia | roster_short",
"estimated_response_at": "<iso8601>",
"grace_duration_minutes": 12
}
Player UI surfaces "Law enforcement response delayed (estimated 12 min)" — emergent universe state, visible to players.
Schema impact summary¶
| Field / table | Status | Notes |
|---|---|---|
NPCBarracks table |
New (see ../DATA_MODELS/npc-lodging.md) |
Lawful-faction lodging |
OutlawBase table |
New | Hostile-faction lodging |
NPCCharacter.home_barracks_id UUID FK |
New | NPC's lodging assignment |
NPCCharacter.shift_preempt_pending Boolean |
New | Engagement-preemption marker |
NPCCharacter.preempted_prior_shift_npc_id UUID FK |
New | Coverage-cascade marker |
NPCCharacter.promotion_pending_at DateTime |
New | Promotion deferral marker |
NPCCharacter.npc_social_affinity JSONB |
New, optional | Allies/rivals for social colocation |
NPCRoster.default_lodging_id UUID + default_lodging_type Enum |
New | Spawn-time lodging resolution |
Sector.defenses.docked_npc_ships JSONB array |
New | Off-duty ship parking + player-attack shielding |
Sector.is_outlaw_zone Boolean |
New | Pirate base sector flag |
Sector.is_npc_barracks_sector Boolean |
New | Sentinel-style barracks sector flag |
daily_schedule.blocks[].overlap_start_minute Integer |
New | Shift handoff window |
daily_schedule.blocks[].location_type Enum |
Extended | Add barracks, outlaw_base, ship, faction_hq, private_residence, transit, error_stranded |
npc_activity enum |
Extended | Add shift_handoff, shift_reroute, error_stranded |
Squad.shift_handoff_state JSONB |
New | Inter-shift intel passing |
Status¶
📐 Design-only. The lifecycle framework, schedule mechanism, archetype enum, lifecycle stages, career processor, and schedule executor are all design targets. The simpler v1 npc-scheduler Loop A (patrol movement) is a strict subset of the schedule executor described here; this doc generalizes that loop to handle non-patrol activities. The
daily_scheduleJSONB extends the legacypatrol_routefield; legacy rows migrate cleanly by wrapping their route in a singlepatrolblock.
Cross-references¶
./npc-scheduler.md— original (police-specific) scheduler design that this lifecycle generalizes.../DATA_MODELS/npcs.md—NPCCharacter,NPCRoster,NPCDeathLogschema; lifecycle fields added per this doc.../FEATURES/gameplay/police-forces.md— police archetype as one specific instance of the wider lifecycle.../FEATURES/gameplay/faction-lore.md— faction-specific archetypes and lore that informs name pools and personality.../FEATURES/economy/contracts.md—Contract.issuer_npc_idextension forMISSION_GIVERarchetype NPCs../market-pricing.md—TRADERarchetype NPCs participating as full market actors../combat-resolver.md—_handle_npc_kiabranch in the destruction handler.