Skip to content

Bang integration

Status: 🚧 Partial — Path A (admin job) and the core translator are fully wired; Path B bootstrap script is absent, version versioning logic is simplified, Phase 13 invariants are not enforced … · ⚠︎ contains code↔spec divergence (impl audit 2026-06-16)

Status. The sw2102-bang sidecar is the live generator path: the admin-ui's "Bang a New Galaxy!" form drives a bang generation job (Path A), and the gameserver's BangImportService translates its output and persists it. The in-process POST /admin/galaxy/generate endpoint returns HTTP 410. The contract documented below is the canonical bang↔gameserver boundary; the fully standalone-service split, the complete Universe JSON envelope, and the bang.* PostgreSQL schema remain launch-target. See ../SYSTEMS/galaxy-generation.md.

sw2102-bang is the standalone universe generator that produces a fresh galaxy. The gameserver consumes its output to bootstrap (or rebuild) the world. This page documents the contract between the two repos.

The generator lives at https://github.com/.../sw2102-bang. The gameserver is services/gameserver/ inside the Sectorwars2102 repo. The two communicate by writing to and reading from the shared PostgreSQL database; there is no HTTP coupling.

Two execution modes

sw2102-bang supports two entry points. Both produce the same Universe shape internally; they differ in where the result goes.

CLI mode — JSON to disk or stdout

bigbang [options]

The CLI writes the generated universe as JSON to stdout, an output file, or as a human summary. Use this for review, reproducibility checks, and offline tooling.

Source: sw2102-bang/src/cli.ts

Flag Default Meaning
--sectors, -s <n> 1000 Total sectors (clamped 20-20000)
--seed <n> random RNG seed; same seed + same config = same universe
--density <n> 20 Max course length, controls overall density (3-50). Recorded on Universe.config; enforcement semantics are pinned in a follow-up doc PR alongside bang's first enforcement implementation.
--two-way-warps <n> 30 Two-way warp percentage (5-100). Drives the FORCED bidirectional bucket size; the total observed bidirectional fraction also includes natural k-NN symmetry from the bulge density gradient and is reported in --summary.
--one-way-warps <n> 5 One-way warp percentage (0-25). Drives the FORCED one-way bucket size; the remainder of pairs preserves natural k-NN symmetry.
--max-warps <n> 6 Max warps per sector (2-10). Strictly enforced via post-categorization drop; bidirectional warps count toward both endpoints' degree, one-way warps only toward the source.
--port-percent <n> 50 Sectors with ports (0-80)
--planet-percent <n> 20 Sectors with planets (0-60)
--nebula-percent <n> 5 Sectors in nebulae (0-30)
--fedspace <n> 10 Federation-protected sectors (2-20)
--stardock <n> 0 (auto) Stardock sector ID; 0 means auto-pick
--output, -o <file> stdout Write JSON to file
--summary off Print a human-readable summary to stderr
--render off Print an ASCII map of the galaxy to stderr (3D → 2D projection with chalk colors). Density-binned grid; fedspace highlighted; cell-color gradient by sector count.
--render-axis <axis> top Projection axis for --render: top (XY plane), side (XZ plane), or iso (isometric).
--no-color off Disable chalk color codes in --render output. Useful for non-tty redirects, CI logs, and snapshot tests.
--compact off Compact JSON (no formatting)
--quiet, -q off Suppress progress output on stderr

The same options resolve through resolveConfig() (sw2102-bang/src/config.ts), which clamps inputs and auto-picks a seed when seed === 0.

Service mode — write directly to PostgreSQL

The generator can also run as a one-shot container that writes to a database and exits. This is the integration path with the gameserver.

docker compose up generator

Source: sw2102-bang/src/db-writer.ts, sw2102-bang/Dockerfile.generator, sw2102-bang/docker-compose.yml

Configuration is via environment variables:

Env var Default Meaning
DATABASE_URL postgresql://tw2102:tw2102@localhost:5432/tw2102 Connection string
UNIVERSE_NAME default Identifier; reuse to overwrite
TW_SECTORS 1000 Total sectors
TW_SEED random RNG seed
TW_PORT_PERCENT 50 Port density
TW_PLANET_PERCENT 20 Planet density

The container waits for the database to accept connections, generates a universe, deletes any existing universe with the same name, and writes the new one in a single transaction.

Output shape — JSON

The CLI emits the Universe interface from sw2102-bang/src/types.ts, with the internal sectors map serialized as a keyed object. Per ADR-0069, bang emits a fully-formed region snapshot: the topology graph plus the named clusters, special formations, station and planet inventories, citadel content, and NPC rosters that go with it. Faction-influence values, the Region row, cross-region warp wiring, and runtime state remain gameserver-side.

{
  "version": "1.1.0",
  "seed": 42,
  "totalSectors": 1000,
  "sectors": {
    "1": {
      "id": 1,
      "position": {"x": 0, "y": 0, "z": 0},
      "warps": [2, 3, 7],
      "port": {
        "name": "Capital Station",
        "class": 1,
        "commodities": {
          "fuel_ore":  {"action": "B", "quantity": 1000, "capacity": 5000, "regenRate": 100},
          "organics":  {"action": "B", "quantity": 800,  "capacity": 3000, "regenRate": 80},
          "equipment": {"action": "S", "quantity": 500,  "capacity": 2000, "regenRate": 50}
        }
      },
      "planets": [
        {
          "name": "Terra",
          "type": "earth",
          "owner": null,
          "fuelOre": 0,
          "organics": 0,
          "equipment": 0,
          "colonists": 0,
          "citadel": {
            "level": 1,
            "droneCapacity": 100,
            "safeContents": {
              "credits": 0,
              "items": []
            }
          }
        }
      ],
      "navHazards": [
        {"type": "mine", "owner": null, "quantity": 4}
      ],
      "nebula": null,
      "beacon": null,
      "explored": true
    }
  },
  "warps": [
    {"from": 1, "to": 2, "oneWay": false}
  ],
  "clusters": [
    {
      "id": 1,
      "name": "Gateway Plaza",
      "sectorRangeStart": 1,
      "sectorRangeEnd": 250,
      "type": "TRADE_HUB"
    }
  ],
  "specialFormations": [
    {
      "id": "f-001",
      "type": "BUBBLE",
      "anchorSectorId": 412,
      "interiorSectorIds": [413, 414, 415, 416],
      "properties": {
        "interiorSize": 4,
        "linkTunnelDepth": 0
      }
    },
    {
      "id": "f-002",
      "type": "LOST_SECTOR",
      "anchorSectorId": 731,
      "interiorSectorIds": [731],
      "properties": {
        "exitWarp": null
      }
    },
    {
      "id": "f-003",
      "type": "LOST_CLUSTER",
      "anchorSectorId": 812,
      "interiorSectorIds": [812, 813, 814, "...", 833],
      "properties": {
        "interiorClusterId": 7,
        "exitWarp": { "sourceSectorId": 820, "destinationSectorId": 504 }
      }
    },
    {
      "id": "f-004",
      "type": "ARCHIPELAGO",
      "anchorSectorId": 850,
      "interiorSectorIds": [850, 851, "...", 949],
      "properties": {
        "memberClusterIds": [8, 9, 10],
        "crossWarpCount": 5,
        "exitWarps": [
          { "sourceSectorId": 905, "destinationSectorId": 612 }
        ]
      }
    }
  ],
  "npcRosters": [
    {
      "kind": "federation_marshal",
      "factionCode": "terran_federation",
      "targetCount": 9,
      "hostSectorId": 5,
      "namePool": ["..."]
    }
  ],
  "specialLocations": [
    {"type": "terra",      "sectorId": 1},
    {"type": "stardock",   "sectorId": 173},
    {"type": "rylan",      "sectorId": 401},
    {"type": "alpha_centauri", "sectorId": 9},
    {"type": "fringe_homeworld", "sectorId": 600}
  ],
  "fedspaceSectors": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
  "config": { "...": "the BigBangConfig used to generate this universe" },
  "createdAt": "2026-04-30T18:00:00.000Z"
}

Field reference (from sw2102-bang/src/types.ts):

  • Sectors. Each sector has a position (integer 3D coordinates scaled by 10000, range roughly [-10000, 10000]), warps (an array of destination sector IDs), an optional port, an array of planets, an array of navHazards, an optional nebula, an optional beacon, and an explored flag.
  • Ports. Class 0 is reserved for special ports (Stardock); classes 1-8 encode buy/sell combinations across the three core commodities. Each commodity tracks action (B for buying, S for selling), quantity, capacity, and regenRate. Per ADR-0069, quantity is the initial seeded value produced by bang's deterministic seeding pass (the gameserver translator passes pricing and seeding constants in as inputs so balance tuning stays gameserver-controlled, but the seed run itself executes inside bang).
  • Planets. Six types (barren, earth, mountainous, oceanic, glacial, volcanic). Generator output carries the planet's initial inventory in the flat fuelOre, organics, equipment, and colonists fields (per ADR-0069, bang owns the seeded values) and, when a citadel is stamped, the citadel's level, droneCapacity, and safeContents (the safe's credits + item list — bang seeds an empty safe by default; populated safes are an operator-or-quest concern).
  • Citadels. Embedded inside each planet under citadel. level matches the existing field; droneCapacity follows the per-level table in ../FEATURES/planets/citadels.md; safeContents is the safe's worldgen contents (bang stamps an empty safe; named-safe quests populate this on the gameserver side).
  • Nav hazards. mine (Armid), limpet, or fighter (deployed); each carries an owner ID and quantity. Mine quantities per sector are bang's responsibility (already in scope; reaffirmed by ADR-0069).
  • Nebulae. normal (hides contents) or magnetic (disrupts navigation); 1-100 density.
  • Warps. A flat list paralleling each sector's warps array; each entry specifies from, to, and oneWay.
  • Clusters. One row per cluster in the region — name (AI-generated per ADR-0044), sectorRangeStart / sectorRangeEnd, and type (one of the standard cluster-type enum values). No faction-influence values — those depend on the gameserver's faction enum and stay gameserver-side, seeded post-import from the per-zone profile in ../SYSTEMS/bang-import-pipeline.md.
  • Special formations. One row per stamped formation — type (one of the 12 catalog values: BUBBLE, DEAD_END_BUBBLE, GOLD_BUBBLE, TUNNEL, DEAD_END, WARP_SINK, BACKDOOR, BLISTER, ESCAPE_HATCH, plus the lost-formation set LOST_SECTOR / LOST_CLUSTER / ARCHIPELAGO added in ADR-0070), anchorSectorId, interiorSectorIds, and a type-specific properties JSONB. Bang stamps lost formations first (Step 6.5a), then the rest of the catalog (Step 6.5b) between proximity-warp placement and long-distance-tunnel placement; long-distance tunnels (Step 7) skip lost-formation sector sets. Bang enforces invariants — notably ADR-0046's "no WARP_SINK inside a BUBBLE" and ADR-0070's "no external warps from a lost formation except the optional one-way exits" — internally so output is correct-by-construction. The gameserver's Phase 13 validation gate cross-checks the same invariants on import.
  • NPC rosters. One row per worldgen-stamped roster: kind (e.g., federation_marshal, nexus_sentinel, pirate_captain), factionCode, targetCount, hostSectorId, and a namePool for runtime spawning. Bang emits the roster shapes specified by ../FEATURES/gameplay/police-forces.md Phase 12.5a (region rosters) and Phase 12.5b (Nexus rosters). No NPCCharacter rows — runtime materialization (Phase 12.5c) stays gameserver-side via npc_scheduler.bootstrap_region(region_id) after the import transaction commits.
  • Special locations. Terra, Stardock, Rylan, Alpha Centauri, Fringe Alliance homeworld; the generator places each one once.
  • Fedspace. Sectors 1 through fedspaceSize; protected from PvP.

Output shape — PostgreSQL

When run in service mode, the generator writes to the bang schema defined in sw2102-bang/db/init.sql:

Table Purpose Key columns
universes One row per generated universe. id PK, name UNIQUE, version, seed, total_sectors, config (JSONB)
sectors One row per sector. id PK, universe_id FK, sector_number, beacon, explored
warps Connections between sectors. id PK, universe_id FK, from_sector, to_sector, one_way
clusters One row per cluster (named, sector-range bounded). id PK, universe_id FK, name, type, sector_range_start, sector_range_end
special_formations One row per stamped formation. id PK, universe_id FK, type, anchor_sector_id FK, interior_sector_ids (ARRAY), properties (JSONB)
ports One row per port. id PK, sector_id FK, name, class, fuel_ore (JSONB — quantity is the initial seeded value), organics (JSONB), equipment (JSONB)
planets One row per planet. id PK, sector_id FK, name, type, owner, commodity columns (fuel_ore/organics/equipment/colonists — initial seeded values), citadel_level, citadel_drone_capacity, citadel_safe_contents (JSONB)
nebulae One row per nebula sector. id PK, sector_id FK, type, density
nav_hazards One row per hazard placement. id PK, sector_id FK, type, quantity, owner
npc_rosters One row per worldgen-stamped roster. id PK, universe_id FK, kind, faction_code, target_count, host_sector_id FK, name_pool (JSONB)
special_locations Terra/Stardock/etc. id PK, universe_id FK, type, sector_number

The writer deletes any prior universes row with the same name (cascading to all dependent rows) and writes the new universe in a single BEGIN/COMMIT transaction with batched inserts (500 rows per batch).

Gameserver consumption

The bang schema does not match the gameserver's domain schema. The gameserver expects Galaxy, Region, Cluster, Zone, Sector, Planet, Station, WarpTunnel, SpecialFormation, NPCRoster rows with multi-region scaffolding (Central Nexus, Terran Space, player-owned regions), faction-influence values, security/development/traffic levels, and the Sectorwars-specific eight-commodity catalog.

Per ADR-0069, bang emits a fully-formed region snapshot — clusters, formations, station and planet inventories, citadel content, and NPC rosters all arrive in the payload. The gameserver translator's job is therefore narrower than a content-derivation pipeline: it validates the payload against Phase 13 invariants, scaffolds the rows that depend on per-customer state (the Region row plus its operator config: subscription tier, governance, tax rate, language pack, aesthetic theme), glues the imported entities into the multi-region world (cluster faction-influence percentages, cross-region warp-gate wiring, Nexus attachment), and translates the 3-commodity bang port output into the gameserver's 8-commodity catalog (a real translation, not a regeneration). It does not derive clusters, stamp formations, or seed inventories — those arrive ready in the payload.

The translation algorithm — numbered steps with explicit per-entity translation tables, JSONB defaults, idempotency rules, and failure modes — is specified in ../SYSTEMS/bang-import-pipeline.md. All three operational paths below invoke the same translator pipeline; they differ only in how the bang payload reaches the translator and where the trigger comes from.

Path A — admin-triggered import

The admin endpoint POST /admin/galaxy/import-bang (see Swagger at <api-host>/docs) is the operator-facing trigger. The handler:

  1. Receives the admin's parameters (target region context, sector count, seed, density, faction balance, optional overrides).
  2. Invokes the generator out-of-process (CLI mode with the requested params), capturing the JSON.
  3. Hands the captured JSON plus the target region context to the translator pipeline.
  4. Returns the new Galaxy.id and Region.id to the admin.

Used for: post-launch region creation, admin-driven re-imports, and player-region provisioning. Because the import takes 60–120 seconds for a 5,000-sector Nexus, the admin endpoint runs the translator as an async background job and returns a job ID; the admin UI polls the job until completion.

Path B — bootstrap script

For first-time environment setup, the generator runs as a one-shot container that writes to a separate bang database (see "Service mode" above). A bootstrap script then reads bang.universes plus dependent rows, hands them to the translator pipeline, and exits. The script lives at services/gameserver/scripts/bootstrap_from_bang.py (target). It accepts a bang connection URL, a target Galaxy.name, and the target region context, and runs once per region during environment bring-up — typically twice on a fresh install (central_nexus then terran_space).

Used for: initial environment provisioning, full-galaxy re-rolls during major releases.

Path C — Alembic data migration

For test fixtures and CI environments, the generator output is checked in as a JSON artifact under services/gameserver/tests/fixtures/bang/ (target) and replayed via an Alembic data migration. The migration calls the same translator pipeline with the JSON loaded from disk. Suitable for deterministic tests; unsuitable for production-sized galaxies due to file size and migration time.

Used for: integration test fixtures, deterministic CI seeds.

Per-entity translation

The full per-entity translation (Sector, Warp, Port → Station, Planet, Nebula → Cluster nebula classification, NavHazard, SpecialLocation, fedspaceSectors, Cluster → Cluster, SpecialFormation → SpecialFormation, NPCRoster → NPCRoster) is documented in the appendices of ../SYSTEMS/bang-import-pipeline.md:

  • Appendix A — bang Commodity (3) → gameserver commodity (8) with default values for the synthesized five.
  • Appendix B — bang NebulaeType (2) → gameserver nebula classification (6) by zone bias and density.
  • Appendix C — bang PlanetType (6) → gameserver PlanetType enum.
  • Appendix D — bang SpecialLocationType → gameserver Sector special_features and associated rows.
  • Appendix E — bang Port class 0–8 → gameserver StationClass and StationType.
  • Appendix F — bang Cluster (named, sector-range-bounded) → gameserver Cluster row with seeded faction-influence overlay.
  • Appendix G — bang SpecialFormation → gameserver SpecialFormation row (1:1 type-and-properties copy; gameserver validates).
  • Appendix H — bang NPCRoster → gameserver NPCRoster row, with Phase 12.5c bootstrap_region hook running post-commit.

This operations doc does not duplicate the appendices; consult the SYSTEMS doc when implementing any of the three paths.

Reproducibility

Both modes are deterministic given a seed and a BigBangConfig. The same seed always produces the same universe regardless of when or where the generator runs. Operators wanting to reproduce a specific galaxy should record the seed and the full config from the universe row.

Versioning

The version field in the Universe and universes row is the generator schema version. The gameserver's translator must check this version against its supported-version allowlist and refuse to import an unknown one. Per ADR-0069, the contract is load-bearing: bang and the gameserver release in lockstep on contract bumps. Adding a field, changing a field's shape, or relocating an invariant from one side of the boundary to the other is a contract change and bumps the version.

Supported versions:

Version Status Notes
1.0.0 Legacy Original shape; no per-sector position field. Translator at bang-import-pipeline.md step 7 synthesizes 3D coordinates from a Hilbert-curve packing of sector-number ranges.
1.1.0 (and 1.1.0-pre.x pre-releases) Current Adds Sector.position: {x, y, z} integer fields scaled by 10000. Translator step 7 consumes position directly; no Hilbert packing. Walking-skeleton releases of sw2102-bang emit 1.1.0-pre.0 to flag that content layers (ports, planets, nebulae, special locations) may be stubbed.

Pre-release suffixes (1.1.0-pre.0, 1.1.0-pre.1, …) parse as >= 1.1.0 for the position-consumption rule but do not satisfy gameserver Phase 13 invariants that depend on content layers (e.g., #8 one TERRA at Capital, #9 two SpaceDocks). Pre-release imports are operator-only and require --allow-incomplete-content on the import endpoint (target).

Known gaps

🐛 Commodity coverage — Bang emits 3 of 9 core commodities

The Bang generator's port-class system uses a binary 2^3 = 8 encoding across three commodities only:

  • fuel_ore (maps to gameserver ore)
  • organics
  • equipment

The gameserver spec defines nine core commodities for procedural trade per ../SYSTEMS/market-pricing.md COMMODITY_PRICE_RANGES. Per ADR-0062 E-D2, bang must cover the full set across appropriate station classes:

Commodity In Bang? Station class affinity
ore ✅ (fuel_ore) All classes 1+
organics All classes 1+
equipment Classes 2+
fuel All classes 1+
gourmet_food Classes 3+
precious_metals Classes 3+
exotic_technology Classes 4+
luxury_goods Classes 4+
colonists Class 5 only (mega-station)

Per-class commodity coverage (per ADR-0062 E-D2):

Station class Commodities (procedural seed)
Class 1 (Frontier outpost) ore, organics, fuel
Class 2 (Border trade post) ore, organics, fuel, equipment
Class 3 (Industrial hub) ore, organics, fuel, equipment, precious_metals, gourmet_food
Class 4 (Capital trade dock) All except colonists
Class 5 (Nexus mega-station) All 9

Bang validates coverage at generation time and fails the seed if any station class is missing a required commodity.

Effect: A Bang-bootstrapped universe has no procedural ports buying or selling 4 of the 7 core commodities. Players can only acquire those four through:

  • Special locations (Stardock near the Capital Sector, TradeDocks).
  • Refining or production from owned planets.
  • Random events.

This is not the intended Launch state.

Resolution paths:

  1. Expand Bang — extend sw2102-bang/src/types.ts Commodity enum to include all seven, and update the port-class generator to use a 2^7 = 128 encoding (or a curated subset). Higher fidelity but a real engineering effort.
  2. Post-process at import — keep Bang's 3-commodity output and inject the missing four during the bang-import-pipeline translator pass (see ../SYSTEMS/bang-import-pipeline.md). Cheaper, but risks divergence between Bang's deterministic output and the gameserver's post-injected state.

The design target is option (1); option (2) is the practical near-term workaround pending Bang refactor.

Tracking: This gap is also flagged at ../FEATURES/economy/trading.md under "Bang generator commodity gap". When resolved, update both docs.