Files
settled-reach/server/data/systems-schema.sql
T
jpmschweitzer d21c690293 fix(tooling): address PR #129 review — atlas generator, schema, Makefile
- ON DELETE CASCADE added to every atlas_* foreign key (atlas_body_grids,
  atlas_cities, atlas_roads, atlas_railroads, atlas_pois, atlas_rivers,
  atlas_oceans, atlas_mountain_ranges). Previously, deleting a body from
  the bodies table or NULL-ing its terrain_reference would leave orphan
  atlas rows forever — sync_markers_to_db only cleans up for bodies it
  re-processes. The existing atlas tables in systems.db were dropped and
  recreated with the new constraint; FK list now reports CASCADE.

- Atlas DDL deduplicated. systems-schema.sql is now the single source of
  truth, bracketed by `-- BEGIN ATLAS INDEX` / `-- END ATLAS INDEX`
  markers. generate_atlas.py reads that block via `_load_atlas_schema()`
  and applies it at runtime, so there is no second copy of the DDL to
  keep in sync. Adding a column requires one edit, not two.

- Uniqueness guard on city coordinates. `_enforce_unique_city_coords`
  runs at the end of `place_cities` and deterministically perturbs any
  duplicate (row, col) via a fixed spiral walk to the first free
  walkable land cell. Rare in practice but the MST collapses to a
  zero-distance edge otherwise, producing an empty A* path and silently
  dropping the road.

- Grid header validation. `load_markers` now raises `AtlasGridMismatch`
  if the loaded `grid: {w, h}` header does not match `GRID_W`/`GRID_H`.
  Both the incremental-skip path and the regenerate path route through
  this loader, so a hand-authored template shipping a different grid
  size fails loud with a per-body error rather than silently producing
  half-scale coordinates.

- Unused `seed_rng` parameter removed from `_analyse_terrain`. The
  function is RNG-free (continent flood-fill, habitability scoring,
  river-mouth dedup, cost grid — all pure functions of terrain). The
  false API contract made it look like terrain analysis consumed RNG
  state and had to be sequenced with downstream RNG use.

- `_score_capital_sites` river-mouth bonus now builds one sparse
  accumulator with all mouth points set at once and runs a single
  `gaussian_filter` call, instead of O(n_mouths) filter calls over
  single-point images.

- `binary_dilation(analysis["land_mask"] == False)` replaced with the
  idiomatic `~analysis["land_mask"]`, matching the convention used
  elsewhere in the file.

- `atlas-generate` Makefile target now guards on
  `SELECT COUNT(*) FROM bodies WHERE terrain_reference IS NOT NULL`.
  On a fresh DB that count is 0 and the generator previously exited
  "success" after processing zero bodies. The target now fails loud
  with a pointer to `populate_terrain_reference.py`.

- `main.rs` SimRng defensive re-insertion gains a long comment
  explaining the exact plugin-ordering hazard it guards against, so
  future readers don't treat the line as dead code. Tied to #826.
2026-04-15 09:25:46 +02:00

486 lines
22 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- Star Systems Database Schema — server/data/systems.db
-- Source of truth for all structured star system data.
-- Authored via tooling pipeline; wiki pages are generated views.
-- Shipped with the game — read-only from the server's perspective.
--
-- Rebuild: tooling/process-wiki-system-changes --rebuild-db
PRAGMA journal_mode=WAL;
PRAGMA foreign_keys=ON;
-- Core identity, physical character, political character
CREATE TABLE IF NOT EXISTS star_systems (
system_id TEXT PRIMARY KEY, -- GJ catalog number (e.g., "GJ 244A")
proper_name TEXT,
system_name TEXT,
star_type TEXT,
spectral_class TEXT,
dist_ly REAL,
geographic_sector TEXT,
geographic_band TEXT,
political_zone TEXT,
-- Physical character
habitable_planet_count INTEGER,
inhabited_planet_count INTEGER,
asteroid_belt INTEGER, -- boolean 0/1
gas_giant INTEGER, -- boolean 0/1
habitability_profile TEXT,
-- Political character
earth_alignment TEXT,
earth_proximity TEXT,
earth_tension TEXT,
-- Content notes
stability_index INTEGER,
system_volatility TEXT,
cultural_corridor TEXT,
generation_priority TEXT,
-- Economics (D-172)
currency_zone TEXT DEFAULT 'TRACTUS_PRIMARY', -- TRACTUS_PRIMARY | MARK_PRIMARY | MIXED
-- Energy-over-gate (D-186)
-- Gate Corp energy service: on-grid nodes get ~0.3× fusion_fuel utility demand.
-- MARK_PRIMARY zones default false (Compact refused Gate Corp dependency).
gate_energy_connected INTEGER DEFAULT 1, -- boolean 0/1
updated_at TEXT DEFAULT (datetime('now'))
);
-- Gate infrastructure — queried independently for pathfinding/topology
CREATE TABLE IF NOT EXISTS system_gates (
system_id TEXT PRIMARY KEY REFERENCES star_systems(system_id),
horizon_station INTEGER, -- boolean 0/1
aperture_count INTEGER,
gate_connections INTEGER,
gate_topology TEXT,
hop_distance_from_gateway INTEGER,
span_gate_network TEXT
);
-- Settlement history
CREATE TABLE IF NOT EXISTS system_history (
system_id TEXT PRIMARY KEY REFERENCES star_systems(system_id),
settlement_wave TEXT,
founding_motivation TEXT,
founding_culture_primary TEXT,
founding_culture_secondary TEXT,
cultural_persistence TEXT,
religious_status TEXT,
religious_generation_count INTEGER
);
-- Historical events — proper 1:N child table
CREATE TABLE IF NOT EXISTS historical_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
system_id TEXT NOT NULL REFERENCES star_systems(system_id),
event_type TEXT NOT NULL,
age_years INTEGER,
sort_order INTEGER NOT NULL DEFAULT 0 -- 0 = most recent
);
-- Economic life
CREATE TABLE IF NOT EXISTS system_economy (
system_id TEXT PRIMARY KEY REFERENCES star_systems(system_id),
economic_tier INTEGER,
population INTEGER,
economic_base_primary TEXT,
economic_base_secondary TEXT,
distribution_index TEXT,
imprint_access TEXT
);
-- Faction presence
CREATE TABLE IF NOT EXISTS system_factions (
system_id TEXT PRIMARY KEY REFERENCES star_systems(system_id),
governance_type TEXT,
dominant_faction TEXT,
primary_fault_line TEXT,
secondary_fault_line TEXT,
assembly_presence TEXT,
commission_presence TEXT,
syndic_presence TEXT,
syndic_type TEXT,
separatist_presence TEXT,
separatist_type TEXT,
institute_presence TEXT,
guardians_presence TEXT
);
-- Cultural voice
CREATE TABLE IF NOT EXISTS system_culture (
system_id TEXT PRIMARY KEY REFERENCES star_systems(system_id),
cultural_register TEXT,
cultural_register_secondary TEXT,
ambient_anxiety TEXT,
local_pride TEXT,
atmospheric_tone TEXT,
atmospheric_tone_secondary TEXT,
active_situation TEXT,
silence_threshold TEXT,
primary_archetype TEXT,
secondary_archetype TEXT,
narrative_notable INTEGER -- boolean 0/1
);
-- Authored prose (supply_dependency, faction_notes, silence_topic,
-- narrative_hook, calibration_note) lives in wiki markdown files only.
-- No DB mirror — read the file if you need the text.
-- Celestial bodies — planets, moons, gas giants, asteroid belts, oort clouds
-- Naming convention:
-- GJ-{n}b/c/d... — planets (innermost first)
-- GJ-{n}Ab/c/d... — planets orbiting primary (binary systems)
-- GJ-{n}d-1 — first moon of third planet
-- GJ-{n}-oort — oort cloud region
-- GJ-{n}-belt — asteroid belt
CREATE TABLE IF NOT EXISTS bodies (
body_id TEXT PRIMARY KEY,
system_id TEXT NOT NULL REFERENCES star_systems(system_id),
parent_body_id TEXT REFERENCES bodies(body_id), -- NULL for planets; parent planet for moons
-- Classification
body_type TEXT NOT NULL, -- planet, moon, gas_giant, asteroid_belt, oort_cloud
orbit_index INTEGER, -- innermost = 1 (planets around star, moons around planet)
proper_name TEXT, -- "Xin Chengdu", NULL for unnamed
-- Physical
mass_class TEXT, -- terrestrial, super_earth, ice_giant, gas_giant, dwarf
atmosphere TEXT, -- breathable, thin, toxic, none, dense
surface_gravity REAL, -- in g (Earth = 1.0), NULL for gas giants
orbital_period_days REAL, -- orbital period in Earth days, NULL for oort/belt
rotation_period_hours REAL, -- rotation period in Earth hours (Earth = 24), NULL for tidally locked/gas giants
planet_class TEXT, -- temperate, arid, frozen, oceanic, volcanic, barren, etc.
hydrosphere TEXT, -- ocean, ice, rivers, none, subsurface
-- Settlement
inhabited INTEGER NOT NULL DEFAULT 0,
population INTEGER DEFAULT 0,
economic_role TEXT, -- manufacturing, agricultural, extraction, research, transit
founding_age_years INTEGER,
settlement_pattern TEXT, -- urban_concentrated, dispersed_rural, orbital_only, domed, cave
-- Cultural (inherited from system, can override)
cultural_corridor TEXT, -- override system corridor if different
industrial_corridor TEXT, -- MVG, Gate_Corp, DSMC, Prometheus, Agricultural_Syndic
-- Rendering
-- terrain_reference: repo-root-relative path to the body's heightmap PNG.
-- Convention (enforced by populate_terrain_reference.py and assumed by
-- generate_atlas.py and the Godot client's atlas scene loader):
-- wiki/star-systems/<system_slug>/bodies/<body_id>/heightmap.png
-- where <system_slug> = system_id with spaces replaced by hyphens
-- (e.g. "GJ 244A" → "GJ-244A"). NULL means no heightmap has been
-- generated for this body yet. The three downstream pipelines
-- (populate, atlas generator, client loader) all assume this format —
-- changing it requires updating all three sites together.
terrain_reference TEXT,
screenshot_path TEXT, -- planetary shader screenshot path
updated_at TEXT DEFAULT (datetime('now'))
);
-- Stations and orbital facilities
-- Naming: GJ-{n}b-S1 (first station orbiting planet b), GJ-{n}-oort-S1 (horizon station)
CREATE TABLE IF NOT EXISTS stations (
station_id TEXT PRIMARY KEY,
system_id TEXT NOT NULL REFERENCES star_systems(system_id),
orbits_body_id TEXT REFERENCES bodies(body_id),
-- Classification
station_type TEXT NOT NULL, -- horizon, commercial, military, research, industrial, agricultural, transit
proper_name TEXT, -- "Chengdu Orbital", "Horizon Station"
-- Settlement
population INTEGER DEFAULT 0,
economic_role TEXT,
governance_type TEXT, -- can differ from system governance
-- Infrastructure
docking_class TEXT, -- major, standard, restricted, none
has_gate_infrastructure INTEGER DEFAULT 0,
district_count INTEGER DEFAULT 1,
updated_at TEXT DEFAULT (datetime('now'))
);
-- Corporations, combines, and major economic entities
-- Source: wiki/corporations/ pages (canonical)
CREATE TABLE IF NOT EXISTS corporations (
corp_id TEXT PRIMARY KEY, -- slug from wiki (e.g., "gate-corporation", "vethara")
proper_name TEXT NOT NULL, -- display name
corp_type TEXT NOT NULL, -- corporation, combine, syndic, institution, independent
scope TEXT, -- reach-wide, sector, system, local
headquarters_system TEXT REFERENCES star_systems(system_id),
headquarters_body TEXT, -- body_id or station_id
specialization TEXT, -- primary product/service
parent_corp TEXT REFERENCES corporations(corp_id),
notes TEXT,
-- Economics (D-175)
behavioral_archetype TEXT, -- Monopolist|Distributor|Producer|Specialist|Cooperative|Intermediary
supply_chain_role TEXT,
shadow_economy_access INTEGER DEFAULT 0,
updated_at TEXT DEFAULT (datetime('now'))
);
-- Gate adjacency graph — bidirectional edges from star-map.json (D-178)
-- Transport cost computation: 512% per hop (gate edge), 13% (orbital edge)
CREATE TABLE IF NOT EXISTS gate_links (
from_system_id TEXT NOT NULL REFERENCES star_systems(system_id),
to_system_id TEXT NOT NULL REFERENCES star_systems(system_id),
PRIMARY KEY (from_system_id, to_system_id)
);
-- Commodity catalog — 36 types (D-184)
-- Source: wiki/economics/commodities.toml
CREATE TABLE IF NOT EXISTS commodities (
commodity_id TEXT PRIMARY KEY,
name TEXT NOT NULL,
tier TEXT NOT NULL, -- raw|intermediate|final|service_professional|service_luxury
elasticity TEXT NOT NULL, -- perfectly_inelastic|inelastic|unit_elastic|elastic
base_price REAL NOT NULL,
bulk_class TEXT, -- bulk|liquid|perishable|standard|compact|precision|oversized|non_physical
unit TEXT, -- tonnes|units|contracts
production_ubiquity TEXT, -- ubiquitous|common|regional|concentrated|monopolistic
demand_model TEXT, -- market|utility|compliance
commission_certifiable INTEGER DEFAULT 0, -- subject to certification in TRACTUS_PRIMARY zones
compact_contested INTEGER DEFAULT 0,
shadow_viable INTEGER DEFAULT 0,
panic_threshold_weeks INTEGER DEFAULT 0,
description TEXT,
updated_at TEXT DEFAULT (datetime('now'))
);
-- Leontief production chain recipes (D-178)
-- Source: wiki/economics/production_chains.toml
CREATE TABLE IF NOT EXISTS production_chains (
chain_id TEXT PRIMARY KEY,
output_commodity_id TEXT NOT NULL REFERENCES commodities(commodity_id),
output_quantity REAL NOT NULL DEFAULT 1.0,
location_bound INTEGER DEFAULT 0,
description TEXT,
updated_at TEXT DEFAULT (datetime('now'))
);
-- Input requirements per production chain
CREATE TABLE IF NOT EXISTS chain_inputs (
chain_id TEXT NOT NULL REFERENCES production_chains(chain_id),
input_commodity_id TEXT NOT NULL REFERENCES commodities(commodity_id),
quantity REAL NOT NULL,
PRIMARY KEY (chain_id, input_commodity_id)
);
-- Corporation operating locations (D-175)
-- Populated by corporation assignment pipeline, not by initial import
CREATE TABLE IF NOT EXISTS corp_presence (
corp_id TEXT NOT NULL REFERENCES corporations(corp_id),
location_id TEXT NOT NULL, -- body_id or station_id
location_type TEXT NOT NULL, -- body|station
primary_operation TEXT,
updated_at TEXT DEFAULT (datetime('now')),
PRIMARY KEY (corp_id, location_id)
);
-- Brand layer (D-189) — administered-price layer above commodity tâtonnement.
-- Brands consume commodities as demand nodes; they are NOT commodities (D-185).
CREATE TABLE IF NOT EXISTS brand_products (
brand_product_id TEXT PRIMARY KEY,
corp_id TEXT NOT NULL REFERENCES corporations(corp_id),
product_name TEXT NOT NULL,
brand_category TEXT NOT NULL, -- terroir|heritage_craft|tech_premium|cultural|service_premium|commodity_branded|design_heritage|platform_catalogue
value_trajectory TEXT NOT NULL, -- appreciating|depreciating|timeless
scarcity_class TEXT NOT NULL, -- capped|constrained|scalable|unlimited
product_subcategory TEXT,
base_premium_multiplier REAL NOT NULL DEFAULT 1.0,
premium_floor REAL NOT NULL DEFAULT 0.0,
origin_system TEXT REFERENCES star_systems(system_id),
terroir_locked INTEGER NOT NULL DEFAULT 0, -- boolean: production bound to origin_system
currency_denomination TEXT NOT NULL DEFAULT 'tractus', -- tractus|mark|mixed|sol_adjacent
shadow_viable INTEGER NOT NULL DEFAULT 0, -- boolean: circulates in shadow economy
brand_tier TEXT NOT NULL, -- halo|volume
halo_brand_id TEXT REFERENCES brand_products(brand_product_id), -- NULL for halo tier
updated_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS brand_inputs (
brand_product_id TEXT NOT NULL REFERENCES brand_products(brand_product_id),
commodity_id TEXT NOT NULL REFERENCES commodities(commodity_id),
quantity REAL NOT NULL,
PRIMARY KEY (brand_product_id, commodity_id)
);
-- Fiscal parameters per star system (D-189 section 5 + section 6)
CREATE TABLE IF NOT EXISTS system_fiscal (
system_id TEXT PRIMARY KEY REFERENCES star_systems(system_id),
corp_tax_rate REAL NOT NULL DEFAULT 0.22, -- 0.01.0
collection_efficiency REAL NOT NULL DEFAULT 1.0, -- derived: 1.0 - shadow_intensity × 0.6
updated_at TEXT DEFAULT (datetime('now'))
);
-- Phase 2: passive corp health tracking (D-189 section 8)
CREATE TABLE IF NOT EXISTS corp_financial_state (
corp_id TEXT PRIMARY KEY REFERENCES corporations(corp_id),
health_metric REAL NOT NULL DEFAULT 1.0, -- 0.0 (distressed) to 1.0 (healthy)
updated_at TEXT DEFAULT (datetime('now'))
);
-- Phase 3 lifecycle state machine stub (D-189 section 8) — schema correct, not driven yet
CREATE TABLE IF NOT EXISTS corp_lifecycle_events (
event_id INTEGER PRIMARY KEY AUTOINCREMENT,
corp_id TEXT NOT NULL REFERENCES corporations(corp_id),
event_type TEXT NOT NULL, -- Founded|Growing|Active|Distressed|Acquired|Dissolved
event_tick INTEGER NOT NULL DEFAULT 0,
event_data TEXT, -- JSON blob for future lifecycle detail
created_at TEXT DEFAULT (datetime('now'))
);
-- BEGIN ATLAS INDEX (D-191 §8, #832) -- DO NOT EDIT THE MARKER COMMENTS
-- Atlas index — scalar metadata mirror of markers.json files.
-- Source of truth is wiki/star-systems/.../markers.json (next to the heightmap);
-- these tables exist so the atlas implant app and development queries don't
-- have to scan hundreds of JSON files. Polyline geometry stays in the files —
-- the DB only stores scalar/filterable fields + `point_count` as a rough length
-- proxy. Populated and refreshed by tooling/planet-gen/generate_atlas.py, which
-- extracts this entire block (between BEGIN/END ATLAS INDEX markers) from this
-- file at runtime so the DDL lives in exactly one place.
-- Per-body grid dimensions (one row per body that has a markers.json).
-- Lets any query interpret the pixel coordinates below without touching disk.
CREATE TABLE IF NOT EXISTS atlas_body_grids (
body_id TEXT PRIMARY KEY REFERENCES bodies(body_id) ON DELETE CASCADE,
grid_w INTEGER NOT NULL,
grid_h INTEGER NOT NULL,
updated_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS atlas_cities (
city_id TEXT PRIMARY KEY, -- "<body_id>/<local_id>" e.g. "GJ380c/city_0"
body_id TEXT NOT NULL REFERENCES bodies(body_id) ON DELETE CASCADE,
local_id TEXT NOT NULL, -- "city_0", "city_1" — matches markers.json id
name TEXT NOT NULL DEFAULT '',
kind TEXT NOT NULL, -- capital|city
center_row INTEGER NOT NULL,
center_col INTEGER NOT NULL,
population INTEGER NOT NULL DEFAULT 0,
updated_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS atlas_roads (
road_id TEXT PRIMARY KEY, -- "<body_id>/<local_id>"
body_id TEXT NOT NULL REFERENCES bodies(body_id) ON DELETE CASCADE,
local_id TEXT NOT NULL,
name TEXT NOT NULL DEFAULT '',
kind TEXT NOT NULL, -- commercial|highway|rural|...
point_count INTEGER NOT NULL, -- path length proxy (full geometry in markers.json)
updated_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS atlas_railroads (
railroad_id TEXT PRIMARY KEY, -- "<body_id>/<local_id>"
body_id TEXT NOT NULL REFERENCES bodies(body_id) ON DELETE CASCADE,
local_id TEXT NOT NULL,
name TEXT NOT NULL DEFAULT '',
kind TEXT NOT NULL, -- passenger_freight|freight|maglev|...
point_count INTEGER NOT NULL,
updated_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS atlas_pois (
poi_id TEXT PRIMARY KEY, -- "<body_id>/<local_id>"
body_id TEXT NOT NULL REFERENCES bodies(body_id) ON DELETE CASCADE,
local_id TEXT NOT NULL,
name TEXT NOT NULL DEFAULT '',
kind TEXT NOT NULL, -- transit|institutional|cultural|...
center_row INTEGER NOT NULL,
center_col INTEGER NOT NULL,
updated_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS atlas_rivers (
river_id TEXT PRIMARY KEY, -- "<body_id>/<local_id>"
body_id TEXT NOT NULL REFERENCES bodies(body_id) ON DELETE CASCADE,
local_id TEXT NOT NULL,
name TEXT NOT NULL DEFAULT '', -- empty for untitled procedural rivers
point_count INTEGER NOT NULL,
updated_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS atlas_oceans (
water_id TEXT PRIMARY KEY, -- "<body_id>/<local_id>"
body_id TEXT NOT NULL REFERENCES bodies(body_id) ON DELETE CASCADE,
local_id TEXT NOT NULL,
name TEXT NOT NULL DEFAULT '',
kind TEXT NOT NULL, -- lake|sea|ocean
center_row INTEGER NOT NULL,
center_col INTEGER NOT NULL,
area_fraction REAL NOT NULL DEFAULT 0.0, -- fraction of heightmap area covered
updated_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS atlas_mountain_ranges (
range_id TEXT PRIMARY KEY, -- "<body_id>/<local_id>"
body_id TEXT NOT NULL REFERENCES bodies(body_id) ON DELETE CASCADE,
local_id TEXT NOT NULL,
name TEXT NOT NULL DEFAULT '',
center_row INTEGER NOT NULL,
center_col INTEGER NOT NULL,
peak_row INTEGER NOT NULL,
peak_col INTEGER NOT NULL,
area_cells INTEGER NOT NULL DEFAULT 0,
updated_at TEXT DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_atlas_cities_body ON atlas_cities(body_id);
CREATE INDEX IF NOT EXISTS idx_atlas_cities_kind ON atlas_cities(kind);
CREATE INDEX IF NOT EXISTS idx_atlas_cities_population ON atlas_cities(population);
CREATE INDEX IF NOT EXISTS idx_atlas_cities_name ON atlas_cities(name);
CREATE INDEX IF NOT EXISTS idx_atlas_roads_body ON atlas_roads(body_id);
CREATE INDEX IF NOT EXISTS idx_atlas_railroads_body ON atlas_railroads(body_id);
CREATE INDEX IF NOT EXISTS idx_atlas_pois_body ON atlas_pois(body_id);
CREATE INDEX IF NOT EXISTS idx_atlas_pois_kind ON atlas_pois(kind);
CREATE INDEX IF NOT EXISTS idx_atlas_rivers_body ON atlas_rivers(body_id);
CREATE INDEX IF NOT EXISTS idx_atlas_oceans_body ON atlas_oceans(body_id);
CREATE INDEX IF NOT EXISTS idx_atlas_mountain_ranges_body ON atlas_mountain_ranges(body_id);
-- END ATLAS INDEX (D-191 §8, #832)
-- Indexes
-- astronomical_id removed: system_id IS the GJ catalog number
CREATE INDEX IF NOT EXISTS idx_star_systems_sector ON star_systems(geographic_sector);
CREATE INDEX IF NOT EXISTS idx_star_systems_wave ON system_history(settlement_wave);
CREATE INDEX IF NOT EXISTS idx_star_systems_topology ON system_gates(gate_topology);
CREATE INDEX IF NOT EXISTS idx_star_systems_star_type ON star_systems(star_type);
CREATE INDEX IF NOT EXISTS idx_star_systems_volatility ON star_systems(system_volatility);
CREATE INDEX IF NOT EXISTS idx_historical_events_system ON historical_events(system_id);
CREATE INDEX IF NOT EXISTS idx_system_factions_dominant ON system_factions(dominant_faction);
CREATE INDEX IF NOT EXISTS idx_system_economy_tier ON system_economy(economic_tier);
CREATE INDEX IF NOT EXISTS idx_system_culture_archetype ON system_culture(primary_archetype);
CREATE INDEX IF NOT EXISTS idx_bodies_system ON bodies(system_id);
CREATE INDEX IF NOT EXISTS idx_bodies_parent ON bodies(parent_body_id);
CREATE INDEX IF NOT EXISTS idx_bodies_type ON bodies(body_type);
CREATE INDEX IF NOT EXISTS idx_bodies_inhabited ON bodies(inhabited);
CREATE INDEX IF NOT EXISTS idx_stations_system ON stations(system_id);
CREATE INDEX IF NOT EXISTS idx_stations_orbits ON stations(orbits_body_id);
CREATE INDEX IF NOT EXISTS idx_stations_type ON stations(station_type);
CREATE INDEX IF NOT EXISTS idx_corps_system ON corporations(headquarters_system);
CREATE INDEX IF NOT EXISTS idx_corps_type ON corporations(corp_type);
CREATE INDEX IF NOT EXISTS idx_star_systems_currency ON star_systems(currency_zone);
CREATE INDEX IF NOT EXISTS idx_gate_links_from ON gate_links(from_system_id);
CREATE INDEX IF NOT EXISTS idx_gate_links_to ON gate_links(to_system_id);
CREATE INDEX IF NOT EXISTS idx_commodities_tier ON commodities(tier);
CREATE INDEX IF NOT EXISTS idx_production_chains_output ON production_chains(output_commodity_id);
CREATE INDEX IF NOT EXISTS idx_chain_inputs_commodity ON chain_inputs(input_commodity_id);
CREATE INDEX IF NOT EXISTS idx_corp_presence_corp ON corp_presence(corp_id);
CREATE INDEX IF NOT EXISTS idx_corp_presence_location ON corp_presence(location_id);
-- Brand layer indexes (D-189 section 5 — composite for UI queries)
CREATE INDEX IF NOT EXISTS idx_brand_products_corp_category ON brand_products(corp_id, brand_category);
CREATE INDEX IF NOT EXISTS idx_brand_products_origin ON brand_products(origin_system);
CREATE INDEX IF NOT EXISTS idx_brand_products_tier ON brand_products(brand_tier);
CREATE INDEX IF NOT EXISTS idx_brand_inputs_commodity ON brand_inputs(commodity_id);
CREATE INDEX IF NOT EXISTS idx_corp_lifecycle_events_corp ON corp_lifecycle_events(corp_id);
-- Atlas index (D-191 §8, #832) — see BEGIN/END ATLAS INDEX block above.