Skip to content

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_regionRegion (FK). - station / sector — host (one or the other, not both). - assigned_npcsNPCCharacter (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_regionRegion (FK). - sector — host sector (compound id resolution). - assigned_npcsNPCCharacter.

Discovery and raid model

A player who satisfies an OutlawBase's discovery_requirements can:

  1. See the base in their player tools (faction-intel screen, exploration log).
  2. 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).
  3. 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's work_station activity blocks at location_type = "faction_hq".
  • Admiral Chen's Residence — a private residence at sector Y. archetype = FACTION_LEADER, capacity 1, served by the schedule's sleep and personal activity blocks at location_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 host OutlawBase rows; the warp-gate validation layer rejects player gate construction in outlaw zones (a backstop similar to is_nexus_protected, since outlaw faction control would corrupt gate-mediated traversal).
  • is_npc_barracks_sector — Boolean default false. Set when an NPCBarracks row's location_type = sector and 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