NPC Lodging¶
Status: 🚧 Partial — Both NPCBarracks and OutlawBase models are design-only with no code; the doc itself acknowledges this; the NPCRoster lodging FK columns exist but are always null … (impl audit 2026-06-16)
Where NPCs sleep, dock their ships, take meals, and socialize when they're off-duty. Two first-class entities cover the canonical cases: NPCBarracks for lawful-faction NPCs (Federation Marshals, Nexus Sentinels, station officials, traders at waypoint stations) and OutlawBase for hostile-faction NPCs (pirates, Cabal lieutenants). Both have the same runtime interface — an NPC's daily_schedule references a lodging entity by UUID, and the scheduler resolves the type at runtime to figure out where the NPC physically is.
This solves a structural gap in the v1 lifecycle design where daily_schedule.blocks[].location_ref was a sector-number string with no backing entity. Lodging is now normalized: capacity is tracked, occupancy is observable, player interactions are gated.
Schema status¶
Per ADR-0066 D-V1, schema-level implementation status is consolidated here. Field descriptions describe the target schema.
Design-only: both NPCBarracks and OutlawBase are design-only at present — model classes target paths that do not yet exist. The "discovery and raid model" section is future work (PvE raid loop on hostile-faction lodging).
NPCBarracks¶
Source: services/gameserver/src/models/npc_barracks.py.
Purpose: Lawful-faction lodging — the place a Federation Marshal sleeps, dines, takes off-duty rest, and parks her ship between shifts. One row per barracks; capacity tracked; player-visible at the host station/sector but not raidable as a destructible entity (raids on the host station follow standard combat rules instead).
Fields:
| name | type | constraints | notes |
|---|---|---|---|
| id | UUID | PK | |
| name | String(100) | not null | Player-visible (e.g., "Capital Marshal Barracks", "Gateway Plaza Sentinel Barracks", "Nova Research Quarters"). |
| location_type | Enum npc_lodging_location |
not null | station or sector — drives which FK is used. |
| station_id | UUID FK stations.id | nullable, CASCADE | Set when location_type = station; the host station. NULL when sector-based. |
| sector_id | Integer | nullable | Set when location_type = sector; compound (home_region_id, sector_id) per the canonical sector identity. NULL when station-based. |
| home_region_id | UUID FK regions.id | not null, CASCADE | The region this barracks belongs to. CASCADE delete on region termination. |
| faction_code | String(50) | not null, indexed | Matches Faction.code (e.g., terran_federation, galactic_concord, nova_scientific_institute). One barracks serves one faction. |
| archetype | Enum npc_archetype |
not null | Which archetype lodges here (LAW_ENFORCEMENT, STATION_OFFICIAL, etc.). A station may host multiple barracks for different archetypes (Marshal barracks + station-staff quarters). |
| capacity | Integer | not null | Max sleeping NPCs at any time (e.g., 9 for Capital Marshals — 8 Marshals + 1 Captain; 28 for Gateway Sentinels). Advisory — overflow is logged but not hard-rejected. |
| current_occupants_count | Integer | default 0 | Denormalized live count for fast reads. Updated by the scheduler on sleep/wake transitions. |
| assigned_npc_ids | JSONB | default [] |
Live array of NPCCharacter.id UUIDs currently in residence (sleeping, off-duty, or on-station for non-shift activities). |
| amenities | JSONB | default {} |
Flavor metadata: {"quarters_type": "shared", "has_mess_hall": true, "has_training_room": true}. Player-visible station-info copy. |
| created_at | DateTime | not null | |
| updated_at | DateTime | not null |
Indexes:
- (home_region_id, faction_code, archetype) — fast lookup for "where do this region's Marshals sleep?"
- (station_id) and (sector_id) partial — host-side queries.
Relationships:
- home_region → Region (FK).
- station / sector — host (one or the other, not both).
- assigned_npcs → NPCCharacter (1:many; an NPC's home_barracks_id points back).
Per-archetype default lodging¶
The galaxy generator's Phase 12.5 (per ../SYSTEMS/galaxy-generator-design.md) creates one barracks per region per faction-with-NPCs at appropriate locations, populated into NPCRoster.default_barracks_id:
| Archetype + faction | Default barracks location | Capacity |
|---|---|---|
LAW_ENFORCEMENT + Terran Federation (Marshals) |
The region's Capital station (1 per region) | 9 (8 Marshals + 1 Captain) |
LAW_ENFORCEMENT + Galactic Concord (Sentinels) |
A dedicated sector in the Gateway Plaza cluster (1 in the Nexus) | 28 (24 Sentinels + 4 Captains) |
FACTION_PATROL (Federation Navy, AM enforcers, Frontier militia) |
A faction-flagged station in the appropriate zone | 12 |
STATION_OFFICIAL |
The station the NPC works at | 6 |
MISSION_GIVER |
The station the NPC works at | 4 |
TRADER |
Each trader's home station (one per trader; not shared) | 1 |
RESEARCHER (Nova) |
A Nova-flagged Class-11 station in the region | 6 |
CIVILIAN |
(none — civilians are ghost-presence; see Civilian section below) | — |
FACTION_LEADER archetype uses a different model — see "Faction-leader dual home" section below.
HOSTILE_RAIDER archetype uses OutlawBase, not NPCBarracks.
OutlawBase¶
Source: services/gameserver/src/models/outlaw_base.py.
Purpose: Hostile-faction lodging — pirate dens, Cabal war rooms, Shadow Syndicate shadow venues. Distinct from NPCBarracks because outlaw bases are (a) typically hidden / not at a public station, (b) gated by player discovery requirements, (c) raidable as endgame PvE content (future).
Fields:
| name | type | constraints | notes |
|---|---|---|---|
| id | UUID | PK | |
| name | String(100) | not null | "The Rogue's Nest", "Cabal War Room Alpha", "Syndicate Shadow Venue 7". |
| sector_id | Integer | not null | Hidden sector hosting the base. Compound (home_region_id, sector_id). |
| home_region_id | UUID FK regions.id | not null, CASCADE | |
| faction_code | String(50) | not null | pirates, cabal, shadow_syndicate, etc. — sentinel faction codes for hostile-only entities. |
| archetype | Enum npc_archetype |
not null | Always HOSTILE_RAIDER at launch. |
| capacity | Integer | not null | Max sleeping outlaw NPCs. |
| current_occupants_count | Integer | default 0 | Live count. |
| assigned_npc_ids | JSONB | default [] |
Resident NPC UUIDs. |
| is_player_discoverable | Boolean | default false | When true, players who meet discovery_requirements can warp to the sector and raid the base. When false, the base is operator-only / lore-only — invisible from player tools. |
| discovery_requirements | JSONB | nullable | Gate criteria, e.g., {"min_faction_intel_rep": 100, "requires_item": "<intel_item_id>", "requires_clue_count": 3}. NULL means "always discoverable" (operator-tunable). |
| defenses | JSONB | not null | Static defenses: turrets, drone counts, NPC-pirate counts at the base, special hazards. Distinct from player-placed Sector.defenses. |
| amenities | JSONB | default {} |
Flavor: {"has_smuggler_cache": true, "has_gambling_den": true}. |
| created_at | DateTime | not null | |
| updated_at | DateTime | not null |
Indexes:
- (home_region_id, faction_code) — per-region pirate-base lookup.
- (is_player_discoverable, discovery_requirements) partial — for the player-discovery service.
Relationships:
- home_region → Region (FK).
- sector — host sector (compound id resolution).
- assigned_npcs → NPCCharacter.
Discovery and raid model¶
A player who satisfies an OutlawBase's discovery_requirements can:
- See the base in their player tools (faction-intel screen, exploration log).
- Warp to the host sector (subject to standard warp-graph rules — outlaw bases are typically in Frontier / contested sectors and may be far off the main routes).
- Engage the base's defenses + the on-base NPCs in combat.
A successful raid (future): - Steals a portion of the base's loot inventory (operator-defined). - Reduces the faction's regional power (-influence). - KIAs any sleeping outlaws who were caught in the raid. - Triggers a 30-day cooldown before the base can be re-raided (the outlaw faction relocates to a new hidden sector).
This is endgame PvE content; not load-bearing for launch but the schema enables the future expansion.
NPCRoster integration¶
NPCRoster (per ./npcs.md) gains a default_lodging_id UUID + default_lodging_type enum (barracks / outlaw_base) so the scheduler can route NPC-spawn lodging assignments without ambiguity. At spawn time, Loop B reads the roster's lodging defaults and sets the new NPC's home_barracks_id (or home_outlaw_base_id) accordingly.
Faction-leader dual home¶
FACTION_LEADER archetype NPCs split their day between office (faction HQ) and residence (private quarters). Both are typically modeled as NPCBarracks rows pointing at different host sectors:
Admiral Chen's Office— the Federation HQ at sector X.archetype = FACTION_LEADER, capacity 1, served by the schedule'swork_stationactivity blocks atlocation_type = "faction_hq".Admiral Chen's Residence— a private residence at sector Y.archetype = FACTION_LEADER, capacity 1, served by the schedule'ssleepandpersonalactivity blocks atlocation_type = "private_residence".
The schedule references both UUIDs explicitly. The runtime treats them as parallel barracks rows; capacity-1 means each is a single-occupant facility.
Trader on-ship lodging¶
TRADER archetype NPCs sleep on their ships during multi-day routes. The schedule encodes this with a special location_type = "ship" and location_ref = <ship_uuid>:
{
"start_minute": 0,
"end_minute": 480,
"activity": "sleep",
"location_type": "ship",
"location_ref": "<ship_uuid>"
}
When the scheduler processes this block, it sets current_sector_id to the ship's current sector (which may move during the sleep block if the ship is in transit on a multi-day haul) and current_activity = sleep. No NPCBarracks row is referenced for these blocks. Traders also have a home_barracks_id for their home station (the route's anchor port), used for rest days and personal blocks.
Civilian ghost presence¶
CIVILIAN archetype NPCs (refugees, colonists-in-transit, background population) do not have lodging entities. Their home_barracks_id is NULL. During sleep activity blocks, current_sector_id = NULL and the NPC is effectively offline — not visible in any sector room, not engagement-eligible, not interactable. This is the "ghost presence" model: civilians exist for flavor population during their active hours and disappear during rest.
Sector schema impact¶
Sector schema gains two flags (per ./galaxy.md):
is_outlaw_zone— Boolean default false. Set on sectors that hostOutlawBaserows; the warp-gate validation layer rejects player gate construction in outlaw zones (a backstop similar tois_nexus_protected, since outlaw faction control would corrupt gate-mediated traversal).is_npc_barracks_sector— Boolean default false. Set when anNPCBarracksrow'slocation_type = sectorand points at this sector. Mostly informational; used by the player-info UI to surface "this is a Sentinel barracks sector."
Sector.defenses JSONB gains a docked_npc_ships array (per ./jsonb-schema.md) parallel to patrol_ships:
{
"docked_npc_ships": [
{
"npc_id": "<uuid>",
"ship_id": "<uuid>",
"status": "docked_off_duty | docked_maintenance | docked_standby",
"docked_at": "<iso8601>"
}
]
}
This holds the ships of off-duty NPCs whose pilots are currently sleeping or on-station for non-shift activities. The combat-resolver checks this array — ships listed as docked_off_duty at a Station flagged as the NPC's home barracks are shielded from player attack (ERR_NPC_SHIP_AT_BARRACKS).
Cross-references¶
./npcs.md—NPCCharacter,NPCRosterschema; gainshome_barracks_id(orhome_outlaw_base_id) FK.../SYSTEMS/npc-lifecycle.md— schedule mechanism that references lodging UUIDs indaily_schedule.blocks[].location_ref.../SYSTEMS/npc-scheduler.md— Loop A sleep/wake transitions that updateassigned_npc_idsarrays +current_occupants_count.../FEATURES/gameplay/police-forces.md— Federation Marshal Capital barracks + Nexus Sentinel barracks specifications../galaxy.md—Sector.is_outlaw_zoneandSector.is_npc_barracks_sectorflags../jsonb-schema.md—Sector.defenses.docked_npc_shipsarray shape.