Bang Import Pipeline¶
Purpose¶
The bang import pipeline translates a sw2102-bang Universe JSON (or its equivalent rows in the bang PostgreSQL schema) into a fully-scaffolded gameserver multi-region galaxy. It is the canonical bridge between the world generator and the live game: every path that materializes generator output into the gameserver — admin-triggered import, first-time bootstrap, Alembic data fixture — invokes this pipeline. The pipeline is deterministic for a given input and target-region context, idempotent on re-run, and writes its output in a single transaction so a partial failure never leaves the gameserver schema in a half-imported state.
Inputs¶
The pipeline accepts one of two equivalent input shapes:
UniverseJSON as emitted bysw2102-bang/src/serialize.ts:universeToJSON. Thesectorsmap is rehydrated from the keyed object on read.bangschema rows — abang.universesrow plus its dependentbang.sectors,bang.warps,bang.ports,bang.planets,bang.nebulae,bang.nav_hazards,bang.special_locationsrows. Used by Path B (bootstrap script) when the generator has run in service mode.
In addition to the bang payload, the caller supplies:
- Target region context. Which gameserver
Regionis being created from this Universe. One of: central_nexus— bootstrap of the universal hub. Forces 5,000 sectors, singleEXPANSEzone, district overlay.terran_space— bootstrap of the starter region. Forces 300 sectors, three zones (Fed/Border/Frontier), governance autocracy at 2% tax.player_owned— region purchased by a player. Sector count and tier come from the subscription; the owner'sUser.idmust be supplied.- Optional admin overrides. A small dict of overrides applied after the per-region defaults: custom
faction_influenceweights, customgovernance_type, customtax_rate, customspecial_features. Overrides go through the same validation as the defaults. - Idempotency key. A stable identifier for this import attempt. Default:
Universe.seedcombined withUniverse.confighash. Used to detect re-runs of the same import.
The pipeline does not read live game state (no players, no transactions, no live presence); it only reads the bang payload, the Region row, and the optional overrides.
Process¶
The pipeline runs as a single database transaction with sixteen numbered steps. Each step's preconditions are validated before the next step begins.
1. Validate input¶
Before any write, the validator (services/gameserver/src/services/bang_schema.py — target) checks:
Universe.versionis in the supported version allowlist. Unknown versions abort with a structured error; no rows are written. The allowlist is a constant in the validator module and is bumped in lockstep with bang releases.Universe.totalSectors === Object.keys(Universe.sectors).length— the declared count matches the actual sector map size. Drift aborts the import.- Every
Warp.fromandWarp.toexists inUniverse.sectors. Orphaned warps are logged and dropped (non-fatal). Universe.fedspaceSectorsis a sorted contiguous range starting at sector 1. Length equalsconfig.fedspaceSize.Universe.specialLocationsreferences only sector IDs present inUniverse.sectors.
Validation failures that abort the import never produce partial writes; the transaction is never opened.
2. Create or upsert the Galaxy row¶
The Galaxy is a singleton; the pipeline reads-or-creates by name. The row stores the bang Universe.seed and Universe.config in Galaxy.density.resource_distribution and a translator-specific bang_universe_id extension on Galaxy.statistics for idempotency tracking.
namefrom caller; default"Sectorwars 2102"for the singleton.max_sectorsset to the sum of all regions the operator plans to import (caller hint; default 10,000).expansion_enabled = true,resources_regenerate = true,warp_shifts_enabled = true,default_turns_per_day = 1000.statistics,density,state,events,combat_penalties,economic_modifiersJSONBs initialized fromservices/gameserver/src/core/galaxy_defaults.py(target).statistics.bang_universe_idset to the import's idempotency key. If a Galaxy row already exists with the same key and identicalconfig, the pipeline returns early as a no-op.
3. Create the Region row¶
Exactly one Region is created per pipeline invocation. The region_type, governance defaults, and starter ship come from the target region context:
| Context | region_type |
governance_type |
tax_rate |
total_sectors |
subscription_tier |
|---|---|---|---|---|---|
central_nexus |
central_nexus |
autocracy |
0.00 |
5000 | n/a (no owner) |
terran_space |
terran_space |
autocracy |
0.02 |
300 | n/a (no owner) |
player_owned |
player_owned |
from override or autocracy |
from override or 0.10 |
from subscription tier | from subscription tier |
The Region's language_pack, aesthetic_theme, traditions, social_hierarchy, trade_bonuses JSONBs default to empty objects and are populated post-import by the regional governance flow.
For initial bootstrap, the operator runs the pipeline twice: once with central_nexus against a 5,000-sector bang Universe, then again with terran_space against a 300-sector bang Universe. The two imports produce distinct Region rows under the same Galaxy.
4. Partition zones¶
Zone partitioning is driven entirely by the target region context, not by anything in the bang payload (bang has no zone concept):
central_nexus— oneZonerow:zone_type = EXPANSE,name = "The Expanse",start_sector = 1,end_sector = 5000,policing_level = 3,danger_rating = 6.terran_spaceandplayer_owned— threeZonerows split by sector number:FEDERATION— first 33% of sectors —policing_level = 9,danger_rating = 1.BORDER— middle 34% —policing_level = 5,danger_rating = 4.FRONTIER— last 33% —policing_level = 2,danger_rating = 8.
The split-percentage rounding favours FEDERATION on the low end and FRONTIER on the high end so that start_sector/end_sector ranges are contiguous and cover the full sector range with no gaps.
5. Create cluster rows¶
Clusters group sectors thematically inside a Region. The cluster count and density derive from the region's sector total:
central_nexus— ~250 sectors per cluster → ~20 clusters.terran_space— ~50 sectors per cluster → ~6 clusters.player_owned— ~50 sectors per cluster, scaled by sector total.
Each Cluster.type is sampled from the standard distribution:
| Cluster type | Standard weight | Notes |
|---|---|---|
STANDARD |
35 | balanced sector mix |
RESOURCE_RICH |
12 | high asteroid yield |
POPULATION_CENTER |
10 | habitable planets |
TRADE_HUB |
12 | many ports |
MILITARY_ZONE |
8 | faction-aligned |
FRONTIER_OUTPOST |
8 | edge of explored space |
CONTESTED |
8 | multiple factions |
SPECIAL_INTEREST |
7 | unique anomalies |
The Nexus distribution is overridden by district characteristics in step 6. Cluster stats JSONB is initialized with zeros and is recomputed in step 16. Each cluster is given coordinates (x, y, z) interpolated from a Hilbert-curve packing of its sector-number range so adjacent clusters tend to be spatially adjacent.
6. Apply Central Nexus district overlay (Nexus only)¶
For central_nexus only, the pipeline invokes the district overlay specified in ./central-nexus-districts.md. The overlay:
- Partitions the 5,000 Nexus sectors into ten contiguous districts by the per-district
sectorscount inDistrictConfiguration. The running-sum check (sum == 5000) is performed before any write. - Biases each cluster's
economic_focusandcontrolling_factionby its district'scharacteristicslist. - Constrains each sector's
security_level,development_level,traffic_levelto the district's[min, max]ranges. - Tags every Sector and Cluster row with its
districtslug.
Districts are orthogonal to zones: every Nexus sector still belongs to the single EXPANSE zone; the district affects per-sector levels, not zone-level policing.
7. Assign sectors to cluster + zone¶
Each bang Sector becomes a gameserver Sector row. The translator iterates sectors in numeric order and assigns:
sector_id(integer) ← bangSector.id.sector_number(nullable integer) ←Sector.idfor Nexus sectors so admin queries can use it; null elsewhere.name← bangSector.beaconif non-null, else generated as"Sector {id}".cluster_id← the cluster created in step 5 that owns the sector's number range.zone_id← the zone created in step 4 that contains the sector's number, ornullif no zone covers it.region_id← the Region created in step 3.security_level,development_level,traffic_level← drawn uniformly from the district range (Nexus) or the zone profile (Terran/player-owned). Zone defaults: FEDERATION (8/7/7), BORDER (5/5/5), FRONTIER (2/3/3).is_discovered← bangSector.explored(the bang generator marks fedspace and special-location sectors as discovered).(x_coord, y_coord, z_coord)← interpolated from the Hilbert packing used in step 5.radiation_level = 0.0,hazard_level = 0unless overridden by step 8.resources← default JSONB fromDATA_MODELS/jsonb-schema.md#sectorresources. Asteroid yields stay zero unless the cluster type isRESOURCE_RICH, in which case the translator rolls non-zeroore/precious_metals/radioactivesfrom the cluster'sresource_distribution.defenses,players_present,ships_present,active_events,nav_beacons,nav_hazards← defaults fromDATA_MODELS/jsonb-schema.md.controlling_faction = null.
8. Mark sector special types¶
Sector types are assigned in two passes:
- Nebula pass. Every bang
Sector.nebula != nullbecomes a gameserverSectorwithtype = NEBULA. The bang nebula'stypeanddensityare translated through the table in the appendix and written into the owningCluster.nebula_propertiesJSONB (the gameserver carries nebula classification at the cluster level for atmospheric/colour metadata; the per-sectorSector.type = NEBULAflag is what gates sensor/combat effects). - Special-type pass. From the remaining sectors, 15% are flagged as special types. Per-cluster regional profile drives the distribution:
RESOURCE_RICHclusters → 60%ASTEROID_FIELD, 40%STANDARD.MILITARY_ZONEclusters → 80%STANDARD, 10%RADIATION_ZONE, 10%WARP_STORM.FRONTIER_OUTPOSTclusters → 70%STANDARD, 15%BLACK_HOLE, 15%RADIATION_ZONE.- All others → 85%
STANDARD, 15% split evenly acrossASTEROID_FIELD,RADIATION_ZONE,WARP_STORM,BLACK_HOLE.
The special-type roll is seeded from Universe.seed and the sector number so re-imports produce identical assignments.
9. Identify isolated clusters¶
After all sectors are written but before warps are translated, the pipeline runs a graph traversal over the bang warp list to compute connected components. A cluster is is_isolated = true when none of its sectors is reachable from a fedspace sector via natural warps.
Target: 10–20% of clusters are isolated. If the natural traversal yields fewer isolated clusters, the translator does not synthesize isolation; the bang seed determines reachability and the gameserver inherits it. If the natural traversal yields more than 25%, a warning is logged but the import proceeds — the operator should re-roll the bang seed if isolation is undesirable.
Sectors in isolated clusters require a Warp Jumper to reach; the movement service reads Cluster.is_isolated to gate movement. (is_isolated is stored in Cluster.special_features as the string "isolated" since Cluster does not carry a dedicated boolean column.)
10. Translate warps¶
Each bang Warp{from, to, oneWay} becomes one row in the sector_warps association table:
source_sector_id← UUID offrom.destination_sector_id← UUID ofto.is_bidirectional = !oneWay.turn_cost = 1(default; bang has no turn-cost concept).warp_stability = 1.0(default; degraded stability is a runtime concern).
When a bang warp connects sectors more than N numbers apart (heuristic threshold: max(50, totalSectors / 20)), the pipeline additionally creates a WarpTunnel row:
type = STANDARD,status = ACTIVE,is_bidirectional = !oneWay.stability = 1.0,stability_enum = STABLE.propertiesJSONB initialized fromDATA_MODELS/jsonb-schema.md#warptunnelpropertieswithtraversal_cost = 1,discovered = true,affected_by_storms = false.source_endpoint/destination_endpointJSONBs populated with the source and destination sector/cluster/region IDs and coordinates.
The sector_warps row is the lightweight association used by routing; the WarpTunnel row carries the heavier metadata (lifecycle, decay, discovery). Both reference the same sector pair.
11. Translate ports → stations¶
Each bang Port becomes a gameserver Station with a co-created Market and per-commodity MarketPrice rows. Mapping:
name← bangPort.name.sector_uuid← UUID of the bang sector hosting the port.sector_id← integer bang sector ID (denormalized for read paths).station_class←StationClass.CLASS_<n>forn = 0..8. Bang class 0 (Stardock-only) maps to gameserverCLASS_0; bang classes 1–8 map directly. Gameserver classes 9–11 are reserved for higher-tier stations created post-import (Nova / Luxury / Advanced Tech Hub).type(enumStationType) ← derived from class viaservices/gameserver/src/core/station_class_map.py(target): bang class 1 →MINING, class 2 →INDUSTRIAL(agricultural), class 3 →INDUSTRIAL, class 4–7 →TRADING, class 8 →TRADINGwithis_quest_hub = true, class 0 →DIPLOMATIC.status = OPERATIONAL,size = 5,faction_affiliationfrom the cluster'scontrolling_faction.commoditiesJSONB ← see commodity expansion below.trader_personalityJSONB ← sampled from the default-distribution pool. Pool: 40%BORDER, 25%FEDERATION, 20%FRONTIER, 10%LUXURY, 5%BLACK_MARKET. Stations in FEDERATION zones bias towardFEDERATION; in FRONTIER zones bias towardFRONTIER.services,defenses,acquisition_requirementsJSONBs ← class-appropriate defaults fromservices/gameserver/src/core/station_defaults.py(target).
Commodity expansion (3 → 8). Bang's three classic commodities (fuel_ore, organics, equipment) are translated as follows:
ore← bangfuel_ore(rename: bang'sfuel_oreis the "raw mineral" classic commodity, mapped to gameserveroreperStation.commodities).organics← bangorganics(direct).equipment← bangequipment(direct).fuel,luxury_goods,gourmet_food,exotic_technology,colonists← seeded fromservices/gameserver/src/core/commodity_catalog.py(target) using the class-specific defaults already documented inStation.commodities. The bang generator does not produce stocks for these; they default to the catalog's class-appropriatequantity/capacity/base_pricevalues.
After the JSONB is written, the pipeline calls Station.update_commodity_trading_flags() and Station.update_commodity_stock_levels() to apply the buys/sells and stock pattern from get_trading_pattern() for the assigned StationClass.
A Market row is created per station with the standard volatility defaults; one MarketPrice row is inserted per commodity-station pair.
12. Translate planets¶
Each bang Planet becomes a gameserver Planet row in the same sector:
name← bangPlanet.name.sector_uuid/sector_id← bang sector hosting the planet.type← gameserverPlanetTypeper the appendix table.status = UNINHABITABLEunless the planet is at a special location (Terra →DEVELOPEDwith population hub).owner_id = null,population = 0,colonists = bang.colonists,fuel_ore/organics/equipmentfrom bang.resourcesJSONB initialized from the gameserver default; bang's planetary stockpiles seed the integer columns directly.productionJSONB ←{"fuel": 0, "organics": 0, "equipment": 0, "research": 0}.- Citadel: if bang
Planet.citadelis non-null, writecitadel_level = bang.citadel.level,citadel_drone_capacityfrom class defaults, and rollcitadel_safe_maxfrom the class defaults; otherwise leave defaults at 0.
13. Special locations¶
Each bang SpecialLocation is materialized into the gameserver via location-specific rules:
- Terra — reserved as Sector 1 in the target region (Terran Space or Nexus context). The host Sector gets
special_features = ["terra", "homeworld"]. APlanetrow of typeTERRANwithstatus = DEVELOPED, large population, max citadel level, and ownership defaulting to the Terran Federation faction is created. - Stardock — Class-0
Stationnamed "Stardock" withis_quest_hub = true,is_player_ownable = false, all services enabled. Hosting sector getsspecial_features = ["stardock"]. - Rylos / Alpha Centauri / Ferrengi — host Sector gets
special_featurescontaining the location slug; if the location is canonically a homeworld, an associatedPlanetof the appropriatePlanetTypeis created with faction ownership. - SpaceDock anchor — for Nexus imports, the pipeline ensures Sector 1 carries the SpaceDock special feature regardless of bang output. If bang did not place SpaceDock at sector 1, the translator upserts the SpaceDock station into sector 1 and demotes any conflicting bang-placed station to a normal port.
All special-location upserts are idempotent on (region_id, special_features contains <slug>).
14. Fedspace zone mapping¶
Bang's fedspaceSectors array (sector IDs 1 through config.fedspaceSize) drives Federation security in the gameserver:
- Each fedspace sector's
Sector.security_level = 10(overrides whatever was rolled in step 7). - Each fedspace sector is assigned to the FEDERATION zone for Terran/player-owned regions, or to the EXPANSE zone for Nexus.
- The sector's
controlling_factionis set to"terran_federation".
For the Nexus (which has only one zone), fedspace sectors retain EXPANSE zone but receive maximum security level — the EXPANSE zone's policing/danger ratings are unchanged.
15. Faction influence seeding¶
Galaxy.faction_influence and per-Cluster.faction_influence JSONBs are seeded from a per-zone-type default profile in services/gameserver/src/core/faction_profiles.py (target):
| Zone type | terran_federation | mercantile_guild | frontier_coalition | astral_mining_consortium | nova_scientific_institute | fringe_alliance |
|---|---|---|---|---|---|---|
| FEDERATION | 80 | 15 | 0 | 2 | 2 | 1 |
| BORDER | 30 | 30 | 20 | 10 | 5 | 5 |
| FRONTIER | 5 | 10 | 60 | 10 | 5 | 10 |
| EXPANSE | 25 | 25 | 15 | 15 | 10 | 10 |
Each row sums to 100. dominant_faction is computed as argmax. Admin overrides applied at step 3 are merged into the cluster-level influence post-seed (with renormalization to keep the sum at 100).
The Galaxy.faction_influence row is the area-weighted average of all clusters in the galaxy.
16. Recompute Galaxy aggregates¶
The final step calls Galaxy.update_statistics() (or its equivalent target service method) to recompute:
Galaxy.statistics.total_sectors,discovered_sectors,station_count,planet_count,warp_tunnel_count,genesis_count(last is 0).Galaxy.density.station_density,planet_density,one_way_warp_percentage,resource_distribution.Galaxy.state.exploration_percentage(≈ count ofis_discoveredsectors /total_sectors).- Per-
Cluster.statsJSONB values (total_sectors,populated_sectors,resource_value, etc.).
The transaction is committed at the end of step 16. If any earlier step raised, the entire transaction is rolled back and the gameserver schema returns to its pre-import state.
Outputs / state changes¶
A successful import inserts (or upserts):
- 1 Galaxy row (upserted by name; statistics recomputed).
- 1 Region row (always inserted; the same Universe imported into a different region context produces a different Region row).
- 1 to ~30 Zone rows depending on region context (1 for Nexus, 3 for Terran/player-owned, plus one per district overlay layer if the implementation chooses to materialize districts as zones — current target keeps districts in the cluster overlay only).
- ~6 to ~20 Cluster rows depending on region size (Nexus ≈ 20, Terran ≈ 6, player-owned scaled).
- N Sector rows where N =
Universe.totalSectors. - M
sector_warpsrows where M =Universe.warps.length. - 0 to M' WarpTunnel rows where M' is the count of long-distance warps (typically 5–15% of warp count).
- K Station rows where K = number of bang sectors with a non-null
port. - K Market rows (one per Station).
- K × 8 MarketPrice rows (one per Station per commodity).
- J Planet rows where J = sum of
bang.Sector.planets.lengthacross all sectors. - S
Sector.special_featuresupdates for each bangSpecialLocation.
Side effects:
Galaxy.statistics,Galaxy.density, and per-Cluster.statsJSONBs are recomputed.- No realtime-bus events are emitted during import. The import is a bulk operation; the bus is signalled once at completion (
galaxy.importedevent) so dashboards can refresh, but no per-row events fire. - No player/team/ship rows are touched.
- No reputation, market-transaction, or audit-log rows are written.
The transaction commits or aborts atomically.
Invariants¶
- Sector count match. Total sectors written equals
Universe.totalSectors. If the count drifts during step 7 (a sector failed to insert), the transaction aborts. - Cluster membership. Every Sector belongs to exactly one Cluster.
Sector.cluster_idis non-null and references a Cluster within the same Region. - Region membership. Every Sector belongs to exactly one Region.
Sector.region_idmatches the Region created in step 3 for every Sector inserted by this run. - Zone membership. Every Sector either belongs to one Zone whose sector range contains the sector's number, or
Sector.zone_idis null only when no zone covers the sector's range (which only happens if the operator passes a region-context that omits zones — not a default). - Nexus district sum. For Nexus imports, the per-district sector counts sum to exactly 5,000.
- Warp referential integrity. Every
sector_warpsrow references two existing sectors; orphaned warps are dropped at validation time, never written. - Schema version.
Universe.versionis in the supported allowlist; no row is written for an unknown version. - Idempotency. Re-running the pipeline on the same
Universe.seed + configagainst the same Region context is a no-op. Detection is viaGalaxy.statistics.bang_universe_id(or an equivalent translator-private extension). - Faction influence sums. Per-Cluster
faction_influencenumeric values sum to 100;dominant_factionmatchesargmax. - Determinism. Two runs with the same
Universe.seed,config, and target region context produce identical row contents. Norandom.random()calls outside seeded RNG. - Single transaction. All writes occur in one transaction; partial failure leaves zero rows.
Failure modes¶
- Version mismatch.
Universe.versionnot in the allowlist → abort at step 1; structured error includes the supplied version and the allowlist. No partial writes. - Sector count drift.
Universe.totalSectors !== Object.keys(sectors).length→ abort at step 1. - Orphaned warps. Warp references a missing sector → log + skip the warp; import continues. The skipped warp is reported in the import summary.
- Faction profile missing. Zone type has no entry in
faction_profiles.py→ fall back to the neutral profile (each faction at 16-17, sum to 100,dominant_faction = "contested"). - Cluster generation oversampling. Standard distribution rolls produce too few
STANDARDclusters to satisfy invariants → fall back toSTANDARDfor any cluster that would have been a special type. - Idempotency mismatch. A
Galaxy.statistics.bang_universe_idalready exists with a differentconfighash → abort with explicit"use --force-reimport to override"hint. The--force-reimportflag deletes the prior Galaxy / Region / Cluster / Sector cascade and re-runs. - District sum mismatch (Nexus only). District configuration sectors don't sum to 5,000 → abort at step 6.
- Special location collision. Two different special locations claim the same sector → abort with the conflicting location list.
- Database transaction failure. Any underlying constraint violation → entire transaction rolls back; the operator sees the underlying SQL error in the structured response.
- Long-running import. Imports of full 5,000-sector Nexus universes can take 60–120 seconds; the operator must run the pipeline outside the realtime request path (see the operations doc for CLI / async-job invocation modes).
Source map¶
| Concern | Path |
|---|---|
| Translator entry point | services/gameserver/scripts/bootstrap_from_bang.py (target) |
| Admin trigger | services/gameserver/src/api/routes/admin/galaxy.py:POST /admin/galaxy/import-bang (target) |
| Region/Zone/Cluster scaffolding | services/gameserver/src/services/galaxy_bootstrap_service.py (target) |
| District overlay | services/gameserver/src/services/nexus_generation_service.py |
| Commodity catalog defaults | services/gameserver/src/core/commodity_catalog.py (target) |
| Station class → type map | services/gameserver/src/core/station_class_map.py (target) |
| Station defaults (services, defenses, acquisition) | services/gameserver/src/core/station_defaults.py (target) |
| Faction influence profiles | services/gameserver/src/core/faction_profiles.py (target) |
| Galaxy / Region / Cluster JSONB defaults | services/gameserver/src/core/galaxy_defaults.py (target) |
| Bang JSON schema validator | services/gameserver/src/services/bang_schema.py (target) |
| Bang DB reader (Path B) | services/gameserver/src/services/bang_db_reader.py (target) |
| Idempotency tracker | services/gameserver/src/services/galaxy_bootstrap_service.py:_already_imported() (target) |
Generator-side reference paths (not written by this pipeline; cited for context):
sw2102-bang/src/types.ts—Universe,Sector,Warp,Port,Planet,Nebula,NavHazard,SpecialLocationinterfaces.sw2102-bang/src/config.ts—BigBangConfig,DEFAULT_CONFIG,resolveConfig()clamps.sw2102-bang/src/bigbang.ts— generator orchestrator.sw2102-bang/src/serialize.ts—universeToJSON/universeFromJSON.sw2102-bang/db/init.sql— bang-side DB schema (used by Path B).
Related¶
../OPERATIONS/bang-integration.md— operational view: where the script runs, when, who triggers it, environment variables for the three invocation paths../central-nexus-districts.md— district overlay invoked at step 6../galaxy-generation.md— broader generation pipeline that calls this translator.../DATA_MODELS/galaxy.md— target Galaxy / Region / Cluster / Zone / Sector / WarpTunnel shapes.../DATA_MODELS/jsonb-schema.md— JSONB key shapes forCluster.faction_influence,Sector.defenses,Station.commodities,WarpTunnel.properties, etc.../OPERATIONS/multi-regional.md— Region governance defaults consumed at step 3.
Appendix A — Bang Commodity → gameserver commodity¶
Bang carries three commodities in Port.commodities. The gameserver Station.commodities JSONB has eight keys. The 3 → 8 expansion is:
| Bang commodity | Gameserver key | Translation |
|---|---|---|
fuel_ore |
ore |
Direct rename. Bang's "fuel ore" is the classic raw mineral; the gameserver's ore JSONB key carries the same role. Quantity, capacity, regen rate copied. |
organics |
organics |
Direct. Quantity, capacity, regen rate copied. |
equipment |
equipment |
Direct. Quantity, capacity, regen rate copied. |
| (synthesized) | fuel |
Defaulted from commodity_catalog.py: quantity = 1500, capacity = 4000, base_price = 12, production_rate = 120, price_variance = 15. |
| (synthesized) | luxury_goods |
Defaulted: quantity = 200, capacity = 800, base_price = 100, production_rate = 20, price_variance = 40. |
| (synthesized) | gourmet_food |
Defaulted: quantity = 150, capacity = 600, base_price = 80, production_rate = 15, price_variance = 35. |
| (synthesized) | exotic_technology |
Defaulted: quantity = 50, capacity = 200, base_price = 250, production_rate = 5, price_variance = 50. |
| (synthesized) | colonists |
Defaulted: quantity = 100, capacity = 500, base_price = 50, production_rate = 10, price_variance = 10. |
The bang PortCommodityState.action (B/S) maps to buys/sells flags on the corresponding gameserver commodity. After the JSONB is written, Station.update_commodity_trading_flags() sets the per-class trading pattern (overriding the per-commodity flags for the five synthesized commodities so the station class drives the pattern, not the bang JSON).
The synthesized commodities' buys/sells flags are set entirely by get_trading_pattern() for the station's class. Stations may carry stock for commodities they neither buy nor sell — the catalog defaults reflect typical free-market presence.
Appendix B — Bang NebulaeType → gameserver nebula classification¶
Bang has two nebula types. The gameserver carries six named nebula classifications used for atmospheric and economic colour:
Bang NebulaeType |
Density range | Zone bias | Gameserver classification |
|---|---|---|---|
normal |
1 – 33 | FEDERATION / EXPANSE | crimson — light interference, mild combat advantage |
normal |
34 – 66 | BORDER | azure — moderate interference, scanner penalty |
normal |
67 – 100 | FRONTIER | emerald — heavy interference, sensor blind spots |
magnetic |
1 – 33 | FEDERATION / EXPANSE | violet — minor warp-stability dampening |
magnetic |
34 – 66 | BORDER | amber — moderate warp-stability disruption; long-range warps unstable |
magnetic |
67 – 100 | FRONTIER | obsidian — severe disruption; warp tunnels degraded; no scan possible |
Selection is by zone bias first, density-range tie-break second. A bang normal nebula in a FEDERATION-zone sector with density 25 becomes crimson. A bang magnetic nebula in a FRONTIER-zone sector with density 90 becomes obsidian.
The chosen classification is written into the owning Cluster.nebula_properties JSONB:
{
"primary_nebula": "<classification>",
"density": <bang_density>,
"sectors": [<sector_numbers>],
"effects": {
"sensor_penalty": <derived>,
"combat_modifier": <derived>,
"warp_stability_penalty": <derived>
}
}
The per-Sector flag is the binary Sector.type = NEBULA (carried in the existing sector_type enum). The classification colour does not appear on the Sector row; it is read from the owning Cluster's JSONB by the UI and combat resolver.
Appendix C — Bang PlanetType → gameserver PlanetType¶
Bang PlanetType |
Gameserver PlanetType |
|---|---|
barren |
BARREN |
earth |
TERRAN |
mountainous |
MOUNTAINOUS |
oceanic |
OCEANIC |
glacial |
ICE |
volcanic |
VOLCANIC |
Gameserver-only PlanetType values (DESERT, GAS_GIANT, JUNGLE, ARCTIC, TROPICAL, ARTIFICIAL) are not produced by the bang generator and are reserved for post-import gameplay (genesis devices, terraforming, special quests).
Translation defaults set status = UNINHABITABLE and habitability_score = 0 for all imported planets. The Terra special location is the one exception: its planet is upgraded to status = DEVELOPED, habitability_score = 100, population = 10_000_000_000, and full citadel during step 13.
Appendix D — Bang SpecialLocationType → gameserver Sector flags¶
Bang SpecialLocationType |
Sector special_features |
Additional state |
|---|---|---|
terra |
["terra", "homeworld"] |
Planet of type TERRAN, status = DEVELOPED; faction owner terran_federation; reserved at sector 1 |
stardock |
["stardock"] |
Station of class CLASS_0 named "Stardock"; is_quest_hub = true, is_player_ownable = false; all services enabled |
rylos |
["rylos", "homeworld", "alien"] |
Planet of type OCEANIC; faction owner nova_scientific_institute |
alpha_centauri |
["alpha_centauri", "stellar_landmark"] |
Planet of type TERRAN with reduced habitability; no fixed faction |
ferrengi |
["ferrengi", "homeworld", "hostile"] |
Planet of type BARREN; faction owner fringe_alliance; sector controlling_faction = "fringe_alliance" |
Special features are appended to the Sector's existing special_features ARRAY rather than replaced, so a sector that is both a Stardock host and inside the Ferrengi homeworld system would carry both slugs. (In practice, bang places these locations at distinct sectors.)
The SpaceDock anchor — described in step 13 — is not a bang SpecialLocationType. It is a gameserver-only convention for the Nexus Region, idempotently upserted into sector 1 (or the nearest available sector if 1 is taken by Terra).
Appendix E — Bang Port class → gameserver StationClass¶
| Bang Port class | Gameserver StationClass |
Gameserver StationType |
Trading pattern |
|---|---|---|---|
| 0 | CLASS_0 |
DIPLOMATIC |
special: buys/sells special_goods, sells colonists |
| 1 | CLASS_1 |
MINING |
buys ore; sells organics, equipment |
| 2 | CLASS_2 |
INDUSTRIAL |
buys organics; sells ore, equipment |
| 3 | CLASS_3 |
INDUSTRIAL |
buys equipment; sells ore, organics |
| 4 | CLASS_4 |
TRADING |
buys exotic_technology; sells ore, organics, equipment, fuel |
| 5 | CLASS_5 |
TRADING |
buys all four basics; sells luxury_goods |
| 6 | CLASS_6 |
TRADING |
buys ore, organics; sells equipment, fuel |
| 7 | CLASS_7 |
TRADING |
buys equipment, fuel; sells ore, organics |
| 8 | CLASS_8 |
TRADING (is_quest_hub = true) |
premium buyer: buys all four basics |
Gameserver classes 9–11 (CLASS_9 Nova premium seller, CLASS_10 Luxury Market, CLASS_11 Advanced Tech Hub) are reserved for higher-tier stations created post-import by the economy or quest systems; the bang generator does not produce them.
The bang Port.class === 0 case appears only at the Stardock special location; the translator treats Stardock as the single canonical CLASS_0 station per Region and rejects any additional CLASS_0 ports the bang payload may contain.