Skip to content

Galaxy Generator — Design Overview

Status: 🚧 Partial — The 14-phase orchestrated generator with a Phase-13 validation gate, SCC re-verification, anchored guarantees and faction-influence seeding does NOT exist as designed … · ⚠︎ contains code↔spec divergence (impl audit 2026-06-16)

Where it runs: this design is implemented by the external sw2102-bang sidecar; the gameserver validates and persists its output via BangImportService. Source-file citations to in-gameserver generation functions denote the design's target shape, not committed gameserver code. See ../OPERATIONS/bang-integration.md.

Architectural blueprint for the galaxy-generation pipeline. Pulls together every constraint scattered across ADRs, DECISIONS resolutions, and feature docs into one place so a future implementer can build a single coherent generator that honors all the rules. The implementation-side spec (which functions exist, which Alembic migration adds which column) lives in ./galaxy-generation.md; this doc is the design layer above it.

Purpose

Build a region's content — sectors, clusters, zones, warp graph, stations, planets, formations, factional influence — from a small input bundle (region row, size tier, optional seed) such that:

  1. Every relevant ADR and DECISIONS resolution holds at the end of generation.
  2. The output is deterministic given a seed (replayable from sw2102-bang previews).
  3. Generation is idempotent at the region level (re-runnable with force=true without leaving partial state).
  4. The pipeline composes with the multi-regional architecture: a player-region generation triggers when a Region Owner subscription activates, attaches the new region to the Central Nexus, and never modifies any other region.

Non-goals

  • No in-place region expansion. The galaxy grows only via region attachment (ADR-0006). The generator never resizes an existing region.
  • No procedural new-sector generation at runtime. Once a region is built, its sector count is fixed for the region's lifetime.
  • No global sector numbering. Sector identity is the compound (Region.id, Cluster.id, sector_number) (ADR-0005). The generator uses region-local 1..N numbering and never coordinates with other regions for disjointness.
  • No cross-region warp graph rewiring. Cross-region travel goes through the Central Nexus; the generator places exactly one natural warp per player region landing in the Frontier outer reaches and stops there (per ADR-0043).

The constraint surface

Every rule the generator must honor. Each row cites the canonical source.

# Constraint Source
1 Sector identity is (Region.id, Cluster.id, sector_number); sector numbers are region-scoped 1..N ADR-0005
2 Galaxy evolution by region attachment only; no in-place expansion ADR-0006
3 Region.total_sectors Standard tier 800–1,200 (±20% from 1,000); CHECK 100–1,500 DECISIONS AU2
4 Capital sector: Terran fixed at 1; player-owned random in Federation Zone; Nexus at 2251 (Gateway Plaza, sector first of cluster 10) ADR-0005, DECISIONS AU2-9
5 Starter cluster = sectors 1..fedspaceSize (default 10) — the narrow pocket around the Capital, distinct from the broader Federation Zone DECISIONS AU3-9
6 Federation Zone = first 33% of region's sector numbers; policing 9, danger 1 ./galaxy-generation.md, ../DATA_MODELS/galaxy.md
7 Zone partitioning: Federation/Border/Frontier in thirds for non-Nexus regions; single EXPANSE for Nexus ./galaxy-generation.md
8 Cluster count: Nexus 20 (fixed names + sector ranges per ./central-nexus-clusters.md); Terran 6; player-owned max(2, total_sectors // 50) ./galaxy-generation.md
9 Sector type mix: 85% STANDARD, 15% special (NEBULA, ASTEROID_FIELD, BLACK_HOLE, RADIATION_ZONE, WARP_STORM, ANOMALY) DECISIONS AU10, ../DATA_MODELS/galaxy.md
10 Warp directionality lives on is_bidirectional; is_latent boolean exists on both sector_warps and WarpTunnel; no WarpTunnelType.ONE_WAY enum value ADR-0034
11 Ambient one-way rates: 5% of sector_warps rows, 15% of WarpTunnel rows; 80% marked / 20% latent ADR-0034
12 Starter cluster carries no natural one-ways (worldgen invariant); densely interconnected via ARTIFICIAL warps ADR-0034, DECISIONS AU3-9
13 Capital Sector SCC floor — the strongly-connected component containing the Capital holds at least ⌊0.75 × total_sectors⌋ sectors. The remainder may form graph-disconnected island formations (LOST_SECTOR, LOST_CLUSTER, ARCHIPELAGO) reachable only via Quantum Jump. Federation Zone sectors must remain in the Capital's SCC under all stamping outcomes. ADR-0034, ADR-0070
14 (superseded by ADR-0070; the weak "unconnected clusters" concept is replaced by explicit LOST_CLUSTER and ARCHIPELAGO formation types stamped in the same pass as the other GENERATED formations.) ADR-0070
15 Two SpaceDocks per non-Nexus region (is_spacedock=True, outside Class 0–11 trading enum): #1 starter-cluster anchor (capital_sector_number + 9), #2 frontier anchor (total_sectors − 5) DECISIONS AU4-4
16 Class-1 station at capital_sector_number + 1 for new-player commerce ./galaxy-generation.md
17 TERRA planet placed only at the Capital Sector (one per region, welcome planet); never Genesis-rolled ADR-0014
18 Special formations: 23-entry V1 catalog split GENERATED (16) / EMERGENT (7); per-region budget by tier; anchor selection respects zone bias. GENERATED-origin stamping is bang-stamped per ADR-0069 and arrives in the bang Universe payload; the gameserver validates and persists. ADR-0034, ./special-formations-generation.md, ADR-0069
19 One-way formations (BACKDOOR, WARP_SINK, long-distance ESCAPE_HATCH) write is_bidirectional = false on their warp rows; never on a type enum ADR-0034
20 Astral Mining claims a portion of RESOURCE_RICH clusters in Federation and Border zones; Sector.controlling_faction String at launch ADR-0033, DECISIONS AU2-17
21 Tax rate default 0.10, CHECK 0.0–0.25 DECISIONS AU3-10
22 max_population = habitability_score × 1,000 for every generated planet; max_colonists = 1,000 (L1 Outpost) ADR-0035
23 Generator must be idempotent at the region level (re-runnable with force=true) ./galaxy-generation.md
24 Generation must be deterministic given a seed ./bang-import-pipeline.md

Inputs

The generator consumes:

  • Region row — created separately by the regional service or admin tool. Carries id, region_type (central_nexus / terran_space / player_owned), subscription_tier, owner_id (nullable for special regions), aesthetic preferences. The generator never creates the Region row; it populates it.
  • Generation configtotal_sectors target, density modifiers, zone profile, optional seed, optional force flag.
  • Region size tier (player-owned only) — Standard at launch (800–1,200 sectors with ±20% variability around 1,000); higher tiers reserved for future expansion (📐 Design-only).
  • sw2102-bang dataset (optional) — precomputed sector layouts, planet seeds, port templates fed in as JSON for reproducible previews. See ./bang-import-pipeline.md.

Triggers

The generator runs in three contexts:

  1. Initial galaxy createPOST /api/v1/admin/galaxy/generate runs Phase 1 (Galaxy metadata) once, then runs the per-region pipeline for Central Nexus + Terran Space.
  2. Player region provisioning — Region Owner subscription activation triggers the per-region pipeline for the new region, plus Phase 13 (region attachment to Nexus).
  3. Region regeneratePOST /api/v1/admin/regions/{id}/generate-content?force=true drops the region's content and re-runs the pipeline. Per-region only; never touches other regions.

Idempotency & determinism

Determinism. The generator threads the seed through every RNG draw. Phase order is fixed; intra-phase ordering is deterministic (cluster N processed before cluster N+1, sector N before N+1, etc.). With the same seed and same inputs, two runs produce byte-identical output.

Idempotency. A region's content is owned by the region — every cluster, sector, station, planet, warp tunnel, and special-formation row carries region_id as a CASCADE FK. force=true regeneration runs DELETE FROM ... WHERE region_id = ? for each owned table in dependency order, then re-runs the pipeline. The Region row itself is preserved (preserves subscription_id, owner_id, billing trail).

Crash recovery. Each phase commits its writes in a single DB transaction. A crash mid-phase rolls back that phase only; partial state is impossible at phase boundaries. The region carries a generation_phase tracking column (📐 Design-only): on restart, the orchestrator resumes at the next phase. Phases 1–12 are pure DB writes; phase 13 (region attachment) emits a realtime event and must be handled with at-least-once delivery semantics.

Phased pipeline

The pipeline runs in 14 phases. Each phase has explicit inputs, outputs, and exit invariants. Phase boundaries are commit points.

Phase 0  ── Pre-generation validation
Phase 1  ── Galaxy metadata (universe init only)
Phase 2  ── Region geometry
Phase 3  ── Zones
Phase 4  ── Clusters
Phase 5  ── Sectors
Phase 6  ── Capital and starter-cluster anchoring
Phase 7  ── Warp graph (proximity)
Phase 8  ── Long-distance warp tunnels
Phase 9  ── Special formations (worldgen-stamped)
Phase 10   ── Stations and planets
Phase 10.5 ── Special formation stamping (Bubbles, Tunnels, Dead-Ends, etc.)
Phase 11   ── Anchored guarantees (Capital welcome, Class-1, two SpaceDocks)
Phase 12   ── Faction influence and AM claims
Phase 12.5 ── Police force pre-seeding (Federation Marshals, Nexus Sentinels)
Phase 12.6 ── Hostile-faction pre-seeding (Pirate Holdings: Camps + Outposts + Strongholds)
Phase 13   ── Validation gate (rollback or commit)
Phase 14   ── Region attachment + statistics rollup

Phase 0 — Pre-generation validation

Inputs: Region row, generation config.

Operations: - Verify Region.id exists and Region.status = 'pending'. - Verify total_sectors ∈ [100, 1,500] (the schema CHECK constraint range). - For player-owned regions, verify total_sectors is within ±20% of the tier target (Standard tier: 800–1,200). - Verify seed is set (random if not supplied). - Reserve resource budget: cluster count, special-formation budget, expected station/planet counts.

Exit invariants: All inputs validated; no DB writes yet. Generator may return ERR_INVALID_INPUT with reason; failed validation costs no DB state.

Phase 1 — Galaxy metadata (universe init only)

Runs once at universe creation. Skipped on subsequent region generations.

Operations: - Insert singleton Galaxy row with max_sectors (default 10,000 universe-wide cap), statistics counters initialized to zero, default_turns_per_day = 1000. - Initialize density JSONB with default station/planet/warp percentages. - Initialize combat_penalties, economic_modifiers, events JSONB with empty defaults.

Exit invariants: Exactly one Galaxy row exists. All counters zero. No region rows created.

Phase 2 — Region geometry

Operations: - For player-owned: roll total_sectors within tier band (e.g., 800 + uniform(0, 401) for Standard). - For Terran Space: total_sectors = 300 (fixed). - For Central Nexus: total_sectors = 5,000 (fixed). - Compute density_multiplier: 0.3 for Nexus, 1.0 for everything else. - Compute fedspaceSize (default 10) — the starter-cluster size constant. Stored in Region config (📐 Design-only column). - Compute capital_sector_number: - Terran Space: 1 (fixed). - Central Nexus: 2251 (first sector of the Gateway Plaza cluster — see ./central-nexus-clusters.md). - Player-owned: random(1, floor(0.33 × total_sectors)) (anywhere in the Federation Zone). - Persist these values on the Region row.

Exit invariants: Region.total_sectors, Region.capital_sector_number, fedspaceSize all set. Region.status remains pending.

Phase 3 — Zones

Operations: - Central Nexus: create one Zone row, type EXPANSE, sector range 1–5000, policing 3, danger 6. - Other regions: create three Zone rows partitioned in thirds: - FEDERATION — sector range [1, floor(0.33 × total_sectors)], policing 9, danger 1. - BORDER — sector range [floor(0.33 × total_sectors) + 1, floor(0.67 × total_sectors)], policing 5, danger 4. - FRONTIER — sector range [floor(0.67 × total_sectors) + 1, total_sectors], policing 2, danger 8.

Exit invariants: Zone rows created; sector-number ranges partition [1, total_sectors] exactly with no gaps or overlaps.

Phase 4 — Clusters

Operations: - Determine cluster count by region kind: - Central Nexus: 20 (fixed names and sector ranges per ./central-nexus-clusters.md). - Terran Space: 6. - Player-owned: max(2, total_sectors // 50). - Partition the region's sector budget across clusters with ±33% size variability. - Assign cluster types weighted by region kind from {STANDARD, RESOURCE_RICH, POPULATION_CENTER, TRADE_HUB, MILITARY_ZONE, FRONTIER_OUTPOST, CONTESTED, SPECIAL_INTEREST}. - ~20% of clusters get a nebula property — defines the nebula color (Crimson, Azure, Emerald, Violet, Amber, Obsidian per the canonical 6-color taxonomy in ../FEATURES/galaxy/quantum-resources.md) and field strength. - Anchor the starter cluster (sectors 1..fedspaceSize) at the region's coordinate origin; cascade subsequent clusters outward in roughly sector-number order.

Exit invariants: Every sector-number in the region maps to exactly one cluster. Starter cluster is anchored at coordinate origin.

Phase 5 — Sectors

Operations: - Iterate cluster-by-cluster, sector-by-sector. - For each sector: assign type — 85% STANDARD, 15% rolled from cluster's allowed special types (NEBULA inherits the cluster's nebula color; ASTEROID_FIELD, BLACK_HOLE, RADIATION_ZONE, WARP_STORM, ANOMALY). - Compute 3D coordinates from cluster anchor coordinates plus per-sector offset. - Set both zone_id (sector-number range within region) and cluster_id (creation grouping). - Seed resources, hazard level, radiation level per sector type.

Exit invariants: Region.total_sectors rows created. Compound (Region.id, Cluster.id, sector_number) unique. All FKs resolve.

Phase 6 — Capital and starter-cluster anchoring

Operations: - Mark the Capital sector (Region.capital_sector_number from Phase 2). The Capital sector flag is a model column (📐 Design-only) on Sector. - For Nexus: Capital sits in Gateway Plaza cluster (cluster 10, sector 2251). - For Terran: Capital is sector 1. - For player-owned: Capital is somewhere in the Federation Zone, pre-determined in Phase 2.

Exit invariants: Exactly one Capital sector per region. The Capital sector's cluster is the starter cluster.

Phase 7 — Warp graph (proximity)

Operations: - For each sector, compute proximity neighbors (2–4 per sector). - Insert sector_warps rows with default is_bidirectional = true, turn_cost = 1, warp_stability derived from hazard. - For sectors in the starter cluster (sector_number ∈ [1, fedspaceSize]): force is_bidirectional = true on every warp into or out (worldgen invariant). - For other sectors: roll one-way assignment per ADR-0034: 5% of warp rows flip is_bidirectional = false. Of those, 80% are marked (is_latent = false); 20% are latent (is_latent = true).

Exit invariants: Every sector has 2–4 warp connections. Starter cluster contains zero one-ways. The 5%/80%-20% targets hold to within ±1 percentage point on a population-level audit.

Phase 8 — Long-distance warp tunnels

Operations: - Run _create_warp_tunnels_enhanced with the region's density_multiplier. - For the starter cluster: stamp ARTIFICIAL (generator-placed) tunnels to ensure dense interconnection regardless of random natural-tunnel layout. created_by_player_id IS NULL on these (player-built gates also use ARTIFICIAL but with the column populated). - For everywhere else: stamp NATURAL tunnels. - One-way assignment per ADR-0034: 15% of WarpTunnel rows flip is_bidirectional = false. Of those, 80% marked / 20% latent. - Federation Zone (broader 33% band): WarpTunnel one-way rate respects the overall 15% target but the starter cluster sub-band carries zero one-ways (the invariant from phase 7 extends here). - Long-distance tunnels respect island-formation boundaries per ADR-0070: the tunnel pass never stamps an edge into or out of a LOST_SECTOR, LOST_CLUSTER, or ARCHIPELAGO. The island pass (in Phase 9, bang-stamped per ADR-0069) runs before this phase so that island interior-sector lists are available as exclusions here.

Exit invariants: WarpTunnelType.ONE_WAY enum value is never written. Starter cluster has zero natural one-way warp tunnels into or out of it. No tunnel crosses an island-formation boundary. Capital sector remains in the strongly-connected component containing the bulk of the region (validated in Phase 8.5 below, before Phase 9 formation work).

Phase 8.5 — SCC re-verification (Capital majority component)

Per ADR-0064 G-F3. Long-distance tunnels (Phase 8) can introduce edges that split the graph in ways that put the Capital sector into a non-majority strongly-connected component. Phase 8.5 catches this before Phase 9's formation work begins.

Operations: - Compute the SCC partition of the post-Phase-8 warp graph. - Identify the SCC containing the Capital sector. - If the Capital is not in the majority component (≥50% of total sectors), reject the Phase 8 tunnel set and run a remediation pass: rebuild the long-distance tunnel set with the additional constraint that the Capital must remain in the majority SCC. - After remediation (or if the Capital was already in the majority), proceed to Phase 9.

Exit invariants: Capital sector is in the majority SCC of the post-Phase-8 graph, and that SCC holds at least ⌊0.75 × total_sectors⌋ sectors per constraint #13. The minority components — graph-disconnected island formations per ADR-0070 — do not contain the Capital or any Federation Zone sector. Phase 9 formation work runs on a verified-SCC graph.

The alternative — re-running Phase 9 after Phase 8 — was rejected because Phase 9 does formation-aware checks that don't fit a verify-and-rollback loop; a dedicated SCC-only re-verification is cheaper and clearer.

Phase 9 — Special formations (validate + persist as imported)

Per ADR-0069, formation stamping for GENERATED-origin formations runs inside bang, between bang's proximity-warp placement and long-distance-tunnel placement (the same pipeline slot the in-process generator uses). The bang Universe payload arrives with SpecialFormation rows already filled in. This phase validates and persists them; it does not run the stamping algorithm.

See ./special-formations-generation.md for the stamping algorithm itself (unchanged; only the execution location moved) and ./bang-import-pipeline.md#6-import-special-formations for the per-row import semantics.

Operations: - Validate that Universe.specialFormations honors the per-region budget profile (per the budget table in ./special-formations-generation.md). - Validate per-formation invariants: anchor_sector_id and interior_sector_ids resolve to imported sectors in the same region; ADR-0046 ("no WARP_SINK inside a BUBBLE") holds; no formation has reduced the Capital-containing SCC. - Persist SpecialFormation rows with origin = GENERATED and the bang-emitted properties JSONB. - EMERGENT-origin formations are not part of bang's payload; they materialize post-launch via the periodic detector pass (per ADR-0053 WR7).

Exit invariants: All persisted formations are GENERATED origin. No formation has reduced the Capital-containing SCC. EMERGENT formations are not stamped here.

Performance budget: per ADR-0051 SK26, the formation-bearing import phase completes in < 30 seconds on launch infrastructure (single Standard-region regen). Bang's stamping pass and the gameserver's validate-and-persist pass together stay within this budget; profile commitment at staging during launch validation.

Phase 10 — Stations and planets

Operations: - For each sector, roll station placement against _populate_sectors_with_ports(probability, region_id). On hit: create Station of region/zone-appropriate class (0–11), initialize commodities JSONB. - Initialize MarketPrice rows for each created station. - For each sector, roll planet placement against _populate_sectors_with_planets(probability, region_id). On hit: create Planet row. - Planet type assignment honors the natural-source distribution per ADR-0014: natural worldgen can produce TERRA, BARREN, or the live planet types reached only via natural sources (GAS_GIANT, JUNGLE, ARCTIC, TROPICAL) — JUNGLE and TROPICAL are colonizable worlds, ARCTIC is a colonizable world, and GAS_GIANT is a real body that cannot be landed on or claimed. TERRA is reserved for the Capital welcome planet only (placed in phase 11), so worldgen rolls exclude TERRA here. ARTIFICIAL is a reserved enum value with no generation path and is not rolled. - Habitability rolled per type and zone. max_population = habitability_score × 1,000 per ADR-0035. max_colonists = 1,000 (L1 Outpost cap).

Exit invariants: Station / planet counts match expected probability bands. No TERRA planet exists outside the Capital sector (which is anchored in phase 11). Every planet has the dual-ceiling fields set per ADR-0035.

Phase 11 — Anchored guarantees

Operations: - Capital welcome planet. Place a TERRA planet at capital_sector_number. This is the only TERRA planet in the region. Hosts the population-hub planet whose Pioneer Office issues migration contracts (per ../FEATURES/planets/colonization.md). - Class-1 commerce station. Place a StationClass.CLASS_1 station at capital_sector_number + 1 (or nearest available sector if that slot is occupied). Initialize with mining-pattern inventory (buys ore + organics, sells equipment). - SpaceDock #1 — starter-cluster anchor. Place a SpaceDock (carries is_spacedock = True, lives outside Class 0–11 trading enum per DECISIONS AU4-4) at capital_sector_number + 9 (or fallback inside the starter cluster). Full service portfolio: genesis_dealer, mine_dealer, drone_shop, refining_facility, luxury_amenities. is_quest_hub = True, is_faction_headquarters = True. - SpaceDock #2 — frontier anchor. Place a SpaceDock at total_sectors − 5 (or fallback in the Frontier-zone outer reaches — distinct location near the region edge, not just "anywhere in Frontier zone" per ADR-0064 G-D2). Identical service portfolio to #1.

Exit invariants: Capital sector has exactly one TERRA planet. Class-1 station exists in or adjacent to the Capital. Two SpaceDocks per non-Nexus region. Nexus has its own Starport Prime station at the Capital instead of these regional anchors.

Phase 12 — Faction influence and AM claims

Operations: - For each RESOURCE_RICH cluster in Federation or Border zones: roll AM claim (per ADR-0033, Faction.faction_type = MINING). - On claim: set Sector.controlling_faction = "astral_mining_consortium" (String at launch per DECISIONS AU2-17) on member sectors. - Initialize Cluster.faction_influence JSONB and per-sector SectorFactionInfluence rows (📐 Design-only) per ADR-0021. - Apply the territory taxonomy thresholds: Core (100%), Controlled (≥75%), Contested (40–60%), Uncontrolled (0%).

Exit invariants: Faction-influence rows reference live clusters / sectors. AM claims respect the canonical AM-controlling-clusters target share.

Phase 12.5 — Police force pre-seeding (validate + persist as imported)

Pre-seeds the standing patrol presence for both Police Forces. Per ADR-0069, Phases 12.5a (region rosters) and 12.5b (Nexus rosters) are bang-stamped — bang emits NPCRoster rows in the Universe payload, including the host-sector entries that anchor the standing squads. The gameserver validates and persists. Phase 12.5c (npc_scheduler.bootstrap_region materializing NPCCharacter rows) stays gameserver-side.

Operations: - Phase 12.5a — Region rosters (validate + persist). For each non-Nexus region, persist the bang-emitted NPCRoster rows for federation_marshal (target counts per ../FEATURES/gameplay/police-forces.md: 8 Marshals + 1 Marshal-Captain). Validate host_sector_id resolves to a Federation-Zone sector. - Phase 12.5b — Nexus rosters (validate + persist). For Central Nexus, persist the bang-emitted NPCRoster rows for nexus_sentinel. Set Sector.is_nexus_protected = true on the Capital sector (2251) and all Gateway Plaza cluster sectors (2251–2500). This is the schema flag the warp-gate Phase 1 deploy-beacon endpoint reads to reject construction with ERR_NEXUS_PROTECTED_SECTOR. Operator-tunable list of additional Nexus clusters to flag (e.g., Commerce Central Hub, Diplomatic Quarter) is post-launch tuning, not generator-time. - Phase 12.5c — Initial NPC bootstrap (post-commit hook, gameserver-side). After the import transaction commits, fire npc_scheduler.bootstrap_region(region_id) to spawn the initial roster of named NPCCharacter rows from each NPCRoster.name_pool. Runs outside the worldgen transaction so a roster-spawn failure does not block region creation.

Exit invariants: Every non-Nexus region carries at least one standing Federation Marshal NPCRoster in its Federation Zone. The Central Nexus carries at least 4 standing Sentinel NPCRoster rows in the Gateway Plaza cluster. is_nexus_protected = true on at least the Capital sector and the Gateway Plaza cluster's 250 sectors.

Phase 12.6 — Hostile-faction pre-seeding

Adjacent to Phase 12.5 (police pre-seeding) but separate. Pre-seeds Pirate Holdings — Camps, Outposts, and Strongholds — across the region's Frontier and (for fallback) Border zones per ADR-0047. Skipped for Central Nexus (the Nexus has its own Sentinel-protected invariant).

Operations:

  1. Determine the per-region tier budget from the table in ADR-0047: Standard tier 800–1,200 sectors yields 5–10 Camps + 3–5 Outposts + 1–2 Strongholds. Smaller regions scale down proportionally.

  2. Stronghold pass first. Pick eligible Bubble or Dead-End Bubble formations in Frontier (fallback Border) that don't already host a holding. For each, roll the Stronghold composition profile (fortress_complex 50% / citadel_state 30% / trade_nexus 20%) and create:

  3. OutlawBase row anchoring the stronghold (sleeping sector inside the Bubble interior).
  4. PirateHolding row with tier = stronghold, the rolled composition, AI-generated name (per ADR-0044), outlaw_base_id and special_formation_id set.
  5. Pirate-owned stations and planets per the composition profile (Class 3–5 stations; L3–L5 citadels with orbital platforms + rail guns where the profile calls for them).
  6. Sector defenses (drones, mines, parked patrol ships) in interior_sector_ids.
  7. NPCRoster rows for pirate_lord (1), pirate_captain (2–3), pirate_enforcer (8) with default_lodging_id = outlaw_base.id.

  8. Outpost pass second. Pick remaining eligible formations (Tunnel, smaller Bubble, Blister, Escape Hatch) in Frontier or Border. Roll the Outpost composition profile (fortress_planet 30% / pirate_dock_cluster 25% / mixed_outpost 25% / defended_formation 20%). Same per-holding operations as Strongholds, with smaller infrastructure (1–2 stations / 1 planet at most / L2–L3 citadels) and pirate_captain (1) + pirate_enforcer (4–6) roster.

  9. Camp pass third. Pick open Frontier sectors (no formation backing required) satisfying eligibility (no Capital, no starter cluster, no Phase-11 anchor, not already a holding's interior). For each, roll the Camp composition profile (hideout 25% / trading_post 20% / haven_planet 20% / combined_small 15% / roving_fleet 10% / fortified_empty 10%). Create the holding without a special_formation_id; populate per the rolled profile with 1 captain + 2–3 enforcers.

  10. Update Sector.defenses with pirate_patrol_ships, deployed_drones, and mines for each holding's sectors. Set Sector.is_outlaw_zone = true on holding interior sectors (existing flag; per npc-lodging.md).

  11. Cabal HQ pre-seeding (per ADR-0053 WR4). For regions with a special_interest = cabal_hq cluster (per ../FEATURES/gameplay/faction-lore.md), create one OutlawBase at the cluster anchor with a small named-NPC roster: 1 Cabal Lieutenant + 4 enforcers. Faction code: the_cabal. Distinct from pirate holdings — the Cabal base does not produce daughter sites and is not part of the pirate ecosystem (ADR-0048). It is a fixed worldgen anchor for Cabal lore content.

The actual NPCCharacter rows for individual pirates are spawned at runtime by NPC scheduler Loop B (per ./npc-scheduler.md) — Phase 12.6 only creates the rosters, lodging, and structural infrastructure.

Exit invariants: Region carries the budgeted number of holdings across the three tiers. All holdings honor the worldgen invariants enforced by Phase 13 below. No holding overlaps with a Phase-11 anchor or a starter-cluster sector. Every holding has a non-null outlaw_base_id and non-null anchor_sector_id.

Phase 13 — Validation gate

The validation gate runs every cross-cutting invariant in one pass. Failure rolls back the entire generation (transactional) or marks the region degraded with an audit log entry, depending on the operator's policy.

Checks:

  1. Region.total_sectors matches the count of created Sector rows.
  2. Sector-number ranges across clusters partition [1, total_sectors] exactly with no gaps or overlaps.
  3. Compound (Region.id, Cluster.id, sector_number) is unique across all created sectors.
  4. Every Sector.zone_id and Sector.cluster_id resolves to a live row in this region.
  5. Starter-cluster invariant: every sector_warps and WarpTunnel row touching a starter-cluster sector has is_bidirectional = true.
  6. Capital reachability: the strongly-connected component of the warp graph containing the Capital sector includes at least ⌊0.75 × total_sectors⌋ sectors. The remainder is graph-disconnected island content (LOST_SECTOR, LOST_CLUSTER, ARCHIPELAGO) per ADR-0070; every Federation Zone sector is inside the Capital's SCC; every island anchor is within Quantum-Jump committed range of at least one Capital-SCC sector.
  7. One-way rate audit: sector_warps one-way rate within ±1pp of 5%; WarpTunnel one-way rate within ±1pp of 15%; latent rate within ±1pp of 20% of one-ways.
  8. Capital welcome planet: exactly one TERRA planet in the region, in the Capital sector.
  9. SpaceDock placement: two SpaceDocks per non-Nexus region (or one Starport Prime for Nexus).
  10. No WarpTunnelType.ONE_WAY rows.
  11. Region attachment fields: for player-owned regions, nexus_warp_sector is set (from phase 14, which runs after this gate but the field is reserved here).
  12. All planet rows have max_population = habitability_score × 1,000 and max_colonists = 1,000.
  13. Pirate holding placement (per ADR-0047): every PirateHolding row's interior_sector_ids lie within the same region; no holding overlaps the Capital, the starter cluster, or any Phase-11 anchor (Class-1 station, SpaceDocks); no holding is in a Federation Zone sector; every Stronghold-tier holding has special_formation_id set; every holding's outlaw_base.sector_id is in its interior_sector_ids (or equal to its anchor_sector_id); every holding's NPC roster defaults reference valid OutlawBase rows.
  14. Tax rate: Region.tax_rate ∈ [0.0, 0.25].
  15. PlanetType column values are drawn only from the canonical 12-value biome-name enum (TERRA, OCEANIC, DESERT, ICE, VOLCANIC, MOUNTAINOUS, BARREN, GAS_GIANT, JUNGLE, ARCTIC, TROPICAL, ARTIFICIAL); no other values are accepted.
  16. WARP_SINK-inside-BUBBLE invariant (per ADR-0064 G-F4): no SpecialFormation of type WARP_SINK intersects a BUBBLE formation. Phase 9 enforces this internally during placement, but Phase 13's cross-check catches anything introduced by Phases 10–12 (anchor placement, named-entity injection, bang-import). Failure surfaces the violating (sink_id, bubble_id) pair.
  17. Bang-import verification (per ADR-0064 G-I3): for every bang-emitted entity (Sector, Warp, Port → Station, Planet, Nebula → Cluster), confirm the gameserver schema's required fields are populated per the per-entity translation tables in ./bang-import-pipeline.md. For entities with synthesized fields (e.g., commodity coverage per ADR-0062 E-D2), confirm synthesis ran. For nexus_warp_sector per ADR-0043, confirm the warp marker is placed and warp-knowledge-database hooks are wired. Failure surfaces the offending entity ID and missing fields.

Exit invariants: All 16 checks pass. Region.status set to active on success. Phase 13 fails fast — bang-import errors and invariant violations prevent the universe from going live.

Phase 14 — Region attachment + statistics rollup

For player-owned regions, the new region must be linked to the Central Nexus by a single natural warp in the region's Frontier outer reaches, per ADR-0043. The attachment is a discoverable destination, not a constructed gate near the Capital.

Operations: - Pick the Frontier landing sector. Eligibility filter: - zone_type = FRONTIER, AND - graph distance to the Capital ≥ 60% of the region's diameter (a "you can't bump into it on the way to your starter cluster" guarantee), AND - prefer sectors with no station, no planet, and incident-warp count in the bottom 25th percentile of the region (sparseness preference), AND - prefer sectors not already touched by a Special Formation (no Bubble interior, no Tunnel mouth, etc.). - Among eligible sectors, pick the one farthest from the Capital by graph distance. Ties broken by lowest (x_coord, y_coord, z_coord) for determinism. - Fallback ladder (per SK7): if no Frontier sector meets the sparseness + no-formation preference, widen to all Frontier sectors. If still none, widen to Border. If still none, fail with ERR_NO_NEXUS_LANDING_SECTOR and roll back the region per Phase 13's strict policy. - Pick the Nexus-side endpoint by hashing Region.id to a deterministic Gateway Plaza cluster sector — different regions land on different Nexus sectors so the Plaza cluster doesn't funnel into one bottleneck. - Set Region.nexus_warp_sector to the chosen Frontier sector number. - Create a WarpTunnel row of type = NATURAL, created_by_player_id IS NULL, is_bidirectional = true, is_latent = true (per ADR-0043 — the Nexus warp is hidden until a Warp Jumper scan reveals it for an individual player; per-player visibility lives in player_warp_knowledge per ADR-0045). - For Central Nexus and Terran Space: skip — these are operator-managed and don't attach. - Update Galaxy.statistics: increment total_sectors, station_count, planet_count, warp_tunnel_count from the region's contribution. - Emit region_attached event on the realtime bus. The event payload includes the chosen Frontier sector number for ops debugging but is not surfaced to the player client (the Nexus warp is a player-discovered feature).

Exit invariants: New region is reachable from the Central Nexus via exactly one bidirectional NATURAL warp tunnel landing in a Frontier-zone sector. Galaxy statistics counters reflect aggregate state across all attached regions.

Cross-cutting invariants

Must hold at every phase boundary, not just at the final validation gate. The orchestrator can run a fast-path subset of these between phases as a sanity check.

  • Compound sector identity is unique across the region: (Region.id, Cluster.id, sector_number).
  • All FKs resolve to live rows in the same region (no cross-region references except phase 14's Galaxy rollup and the region-attachment warp tunnel).
  • No WarpTunnelType.ONE_WAY row ever exists at any phase boundary.
  • The starter-cluster invariant (no natural one-ways in sectors 1..fedspaceSize) holds from phase 7 onward.
  • Capital sector exists from phase 6 onward and never moves.
  • Region.status is pending until phase 13 commits, then active.

Tradeoffs the design makes

Per-region atomicity over global consistency. The generator commits per-region; if a player provisioning fails mid-generation, the region rolls back independently — no global lock, no cross-region cascade. The cost: galaxy statistics counters are eventually consistent across regions, not strict-serializable. This is acceptable because counters are advisory (admin dashboards, leaderboards), not load-bearing for gameplay.

Sector identity is region-scoped, not global. Choosing region-scoped sector numbers per ADR-0005 means a Region A sector 47 and Region B sector 47 are distinct — UI and APIs always carry the compound tuple, and the database has a composite UNIQUE constraint. Tradeoff: every cross-region query joins on (region_id, sector_number) rather than a flat sector ID, but the win is that the universe can grow without coordinating sector-number ranges across regions.

Determinism via thread-the-seed, not snapshot-and-replay. The seed flows through every RNG draw rather than recording every draw to a log. Reproducibility relies on phase order being fixed and intra-phase ordering being deterministic. The cost: changing the implementation of any phase changes the output for the same seed (no replay-from-old-seed compatibility across versions). The win: simpler implementation and no log-storage cost.

Worldgen formations stamped, EMERGENT formations detected. Phase 9 only stamps GENERATED formations; the 7 EMERGENT formations (MINEFIELD_LATTICE, CITADEL_CROWN_RING, GENESIS_BLOOM_RING, DRONE_HIVE, TRADEDOCK_OVERFLOW, SAFE_VAULT_NEXUS, CONTESTED_FRONT) materialize post-launch via a periodic detector pass (per ADR-0053 WR7). Tradeoff: EMERGENT formations don't appear in fresh regions until players have built enough to trigger detection — a few real-time days for the most active regions. The win: the catalog supports both worldgen-stamped and player-built formations through the same schema with origin discriminator.

Idempotent regeneration drops content; preserves the Region row. force=true drops every region-scoped row but keeps the Region row itself (preserves PayPal subscription trail, owner, governance config). The cost: an operator who wants to fully reset a region must explicitly drop and recreate the Region row, which kills the subscription link. The win: routine re-generation (re-run with new seed for testing, fix a bad generation) doesn't disrupt billing state.

Failure modes

Mode Detection Recovery
Phase 0 input validation fails Pre-write validation Return error to caller; no DB writes; region stays pending
Phase mid-stream DB error Transaction rollback per phase Region.status remains pending; orchestrator retries the failed phase from the last committed boundary
Capital-SCC violation in phase 9 Per-formation reachability check Reject the formation placement; reroll anchor up to N times; on persistent failure drop the formation from the budget
Phase 13 validation failure Validation gate Two policies: strict = roll back the entire generation (drop region content, leave Region row), or soft = mark Region.status = degraded and emit an audit event for operator review. Default: strict
Phase 13 rollback failure Strict-rollback transaction (FK constraint, disk full, network blip) Region flips to Region.status = generation_corrupt per SK21 in ADR-0050. Ops alert fires (region_generation_corrupt event + email/Slack). Region removed from any provisioning surface. Operator decides per-case (manual recover / replace / hard delete).
Phase 14 region-attachment fails Realtime bus event delivery At-least-once retry with exponential backoff per SK22 in ADR-0050: 1s, 5s, 30s, 5m, 30m, 6h. Idempotency key on the region_attached event (region.id + attempt_n); the warp-tunnel insert is INSERT ... ON CONFLICT DO NOTHING. After 5 failed attempts: Region.status = attachment_pending; ops alert + ARIA narration to owner. Region remains functional (Phases 1–13 succeeded); only the cross-region Nexus warp is delayed.
Crash mid-phase Per-phase row-count sentinel mismatch Per SK17 in ADR-0050: each phase commits a row-count sentinel to Region.generation_phase_checksums JSONB at phase end. On generator restart at phase N+1, orchestrator re-verifies the count. Mismatch → roll back to the last verified phase, retry from there.
Re-generation race DB-level advisory lock keyed on Region.id force=true acquires the lock; concurrent re-generation requests block or fail-fast
sw2102-bang import disagrees with generator Phase-import comparison Generator runs the standard pipeline; bang-import runs alongside as a parallel reproducibility check (📐 Design-only)

Source map (target locations)

Phase Code site (target)
0 — Validation services/gameserver/src/services/galaxy_service.py:_validate_generation_inputs
1 — Galaxy metadata services/gameserver/src/services/galaxy_service.py:generate_galaxy
2 — Region geometry services/gameserver/src/services/galaxy_service.py:_compute_region_geometry
3 — Zones services/gameserver/src/services/galaxy_service.py:_generate_zones_for_region
4 — Clusters services/gameserver/src/services/galaxy_service.py:_create_clusters_for_region
5 — Sectors services/gameserver/src/services/galaxy_service.py:_create_sectors_for_cluster
6 — Capital anchoring services/gameserver/src/services/galaxy_service.py:_anchor_capital_sector
7 — Proximity warps services/gameserver/src/services/galaxy_service.py:_create_warps_between_sectors
8 — Long-distance tunnels services/gameserver/src/services/galaxy_service.py:_create_warp_tunnels_enhanced
9 — Special formations (validate + persist; bang-stamped) sw2102-bang/src/formations.ts (target — stamping); services/gameserver/src/services/special_formation_service.py:validate_and_persist_imported_formations (target — validate + persist)
10 — Stations / planets _populate_sectors_with_ports, _populate_sectors_with_planets
11 — Anchored guarantees _create_capital_welcome_planet, _create_starter_class1_station, _create_spacedocks_for_region
12 — Faction claims services/gameserver/src/services/faction_service.py:_seed_am_claims
13 — Validation gate services/gameserver/src/services/galaxy_service.py:_run_validation_gate
14 — Region attachment services/gameserver/src/services/regional_governance_service.py:attach_region_to_nexus

Cross-references