-- 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, -- Short narrative hook extracted from the wiki's gttr.md file — the -- first characterisation paragraph ("where the rules live", "forty -- years old and still in the draft", etc). Populated by -- tooling/db/populate_gttr_hook.py from wiki/star-systems//gttr.md. -- Used as compact cultural context in the Gemma 2 naming pipeline -- (#833) so per-system feel is grounded in the canonical identity -- rather than generic corridor labels. gttr_hook 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, -- D-237 authored specialization layer (build-time; NULL = heuristic fallback) economic_specialization TEXT, -- value in specialization_vocabulary.specialization_id cultural_specialization TEXT -- activity- or heritage-type value; drives D-232 template pool (distinct from system_culture.cultural_register) ); -- 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 biosphere_class TEXT, -- D-247: NativeCompatible | NativeMirror | TerraformedSterile | Airless (NULL until authored/defaulted) -- 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 -- Physical dimensions (D-204, #905) -- Mean radius in km. NULL until authoritative data is available; fallback -- derivation from planet_class is applied at query time by the generator. body_radius_km REAL, -- Axial tilt in degrees (T-1024, D-239 §2). Sourced from planet-gen body-def -- frontmatter (wiki/star-systems/*/bodies/*/index.md, orbit.axial_tilt_deg). -- NULL until populate_axial_tilt_deg runs. Used by district temperature -- derivation: tilt determines latitude-band insolation gradient. axial_tilt_deg REAL, -- Rendering -- terrain_reference: repo-root-relative path to the body's heightmap PNG. -- Convention (enforced by populate_terrain_reference.py and assumed by -- the atlas importers and the Godot client's atlas scene loader): -- wiki/star-systems//bodies//heightmap.png -- where = 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 (D-242: backfilled at import for -- every corp via the most-populated-body-in-system heuristic -- when not authored; see populate_corp_specialization) specialization TEXT, -- free-text primary product/service (legacy prose; read by -- generate_corporations for fuzzy brand relevance — left as-is) 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, -- D-242: controlled-vocabulary corp specialization (HQ-placement key) + its -- baked placement outcome. corp_specialization is the corp_specialization -- controlled vocabulary (reuses the D-237 27-value specialization_vocabulary -- id-space — see wiki/economics/corp_hq_placement.toml header). hq_placement -- is 'CityTenant' | 'Standalone', derived once at import from corp_specialization -- via the authored placement map; NULL until populate_corp_specialization runs. -- headquarters_city_id is the D-242 "corp -> city link" for CityTenant HQs -- (many corps may tenant one city); NULL for Standalone HQs (which instead -- get their own atlas_city_names row, cross-referenced by headquarters_body). corp_specialization TEXT, hq_placement TEXT, headquarters_city_id INTEGER REFERENCES atlas_city_names(id), updated_at TEXT DEFAULT (datetime('now')) ); -- Gate adjacency graph — bidirectional edges from star-map.json (D-178) -- Transport cost computation: 5–12% per hop (gate edge), 1–3% (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')) ); -- D-237 authored specialization layer: economic-specialization vocabulary. -- Each value maps to a commodity + projected (BulkClass, ProductionUbiquity) for D-233. -- Scale is encoded in the value (breadbasket vs estate_farming); no separate scale column. -- Compiled from wiki/economics/specialization_vocabulary.toml by import_economics.py. CREATE TABLE IF NOT EXISTS specialization_vocabulary ( specialization_id TEXT PRIMARY KEY, commodity_id TEXT NOT NULL REFERENCES commodities(commodity_id), production_ubiquity_override TEXT, -- NULL = use the commodity's catalog default bulk_class_projected TEXT NOT NULL, -- pre-computed BulkClass for D-233 production_ubiquity_projected TEXT NOT NULL, -- pre-computed ProductionUbiquity for D-233 description TEXT NOT NULL ); CREATE INDEX IF NOT EXISTS idx_spec_vocab_commodity ON specialization_vocabulary(commodity_id); -- 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.0–1.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. The geometry tables are populated by the server-side generation -- cascade (Phase 4, D-223) and start empty; tooling/economy-db/import_economics.py -- extracts this entire block (between BEGIN/END ATLAS INDEX markers) from this -- file at runtime and empties the geometry tables on regen, 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, -- "/" 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 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 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 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 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 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 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); -- Heightmap BLOB storage REMOVED (D-202 amended, #963): canonical elevation is -- now a per-body 16-bit grayscale heightmap.png file (sea_level in a tEXt -- chunk), not a systems.db BLOB. The atlas_body_heightmaps table is dropped via -- MIGRATION_SQL in import_economics.py. -- City name reservations — replaces authored city positions in markers.json (D-207, #902) -- Position is generated by the city placement algorithm; name is authored or LLM-generated. -- D-242: a Standalone corp HQ IS a settlement, so it gets an ordinary row here -- (populate_standalone_hq_settlements) — same shape as a wiki-pooled city, no -- special kind/flag. A CityTenant HQ is NOT a row here; its corp->city link -- lives on corporations.headquarters_city_id (the inverse FK) instead. CREATE TABLE IF NOT EXISTS atlas_city_names ( id INTEGER PRIMARY KEY AUTOINCREMENT, body_id TEXT NOT NULL REFERENCES bodies(body_id) ON DELETE CASCADE, name TEXT NOT NULL, kind TEXT NOT NULL DEFAULT 'city', -- 'capital' | 'city' economic_role TEXT NOT NULL, population INTEGER NOT NULL, settlement_class TEXT, -- D-196 SettlementClass variant; baked at import (D-242/T-1075) corp_id TEXT REFERENCES corporations(corp_id), -- SUPERSEDED (D-242): the D-207/D-223 corp-HQ -- cross-reference insert that populated this is -- removed (it produced duplicate co-named "cities" — -- one row per corp HQ, no UNIQUE(body_id, name)). -- Always NULL going forward; the corp<->settlement -- relationship now lives on corporations -- (headquarters_body / headquarters_city_id). Column -- kept (not dropped) — additive-safe, no live reader. reserved INTEGER NOT NULL DEFAULT 0, -- 1 = reserved for authored scenario use updated_at TEXT NOT NULL DEFAULT (datetime('now')) ); -- Geographic feature name reservations — rivers, mountains, passes (D-207 adjacent, #903) CREATE TABLE IF NOT EXISTS atlas_feature_names ( id INTEGER PRIMARY KEY AUTOINCREMENT, body_id TEXT NOT NULL REFERENCES bodies(body_id) ON DELETE CASCADE, name TEXT NOT NULL, feature_type TEXT NOT NULL, -- 'river' | 'mountain' | 'pass' | 'ocean' | 'region' priority INTEGER NOT NULL DEFAULT 0, -- higher = applied first during naming updated_at TEXT NOT NULL DEFAULT (datetime('now')) ); -- Province boundaries — watershed drainage basin polylines (D-205, #904) -- Pre-computed at build time from D8 drainage analysis. CREATE TABLE IF NOT EXISTS atlas_province_boundaries ( body_id TEXT NOT NULL REFERENCES bodies(body_id) ON DELETE CASCADE, basin_id INTEGER NOT NULL, path TEXT NOT NULL, -- JSON array [[row, col], ...] pixel-space polyline area_pct REAL NOT NULL, -- fraction of body surface area in this basin PRIMARY KEY (body_id, basin_id) ); -- City positions — attractor-matched placement output (D-211, #34) -- Written at build time by the attractor-matching pipeline. Each row maps one -- atlas_city_names entry to its terrain position and the attractor that placed it. CREATE TABLE IF NOT EXISTS atlas_city_positions ( city_names_id INTEGER PRIMARY KEY REFERENCES atlas_city_names(id) ON DELETE CASCADE, body_id TEXT NOT NULL REFERENCES bodies(body_id) ON DELETE CASCADE, row INTEGER NOT NULL, -- pixel row in heightmap grid [0, GRID_H) col INTEGER NOT NULL, -- pixel col in heightmap grid [0, GRID_W) attractor_type TEXT NOT NULL, -- AttractorType variant name score REAL NOT NULL -- match quality [0.0, 1.0] ); CREATE INDEX IF NOT EXISTS idx_atlas_city_names_body ON atlas_city_names(body_id); CREATE INDEX IF NOT EXISTS idx_atlas_city_names_kind ON atlas_city_names(kind); CREATE INDEX IF NOT EXISTS idx_atlas_city_names_corp ON atlas_city_names(corp_id); CREATE INDEX IF NOT EXISTS idx_atlas_feature_names_body ON atlas_feature_names(body_id); CREATE INDEX IF NOT EXISTS idx_atlas_province_boundaries_body ON atlas_province_boundaries(body_id); CREATE INDEX IF NOT EXISTS idx_atlas_city_positions_body ON atlas_city_positions(body_id); -- END ATLAS INDEX (D-191 §8, #832) -- ── Architecture-flavor trait templates (D-232, #993) ────────────────────── -- trait_templates: the shared catalog of holistic architecture-flavor template -- bundles (NOT per-axis traits). Baked from -- wiki/economics/architecture_trait_catalog.toml. List/map fields are JSON text -- (the generator parses). All numerics are integer basis-points (D-010). -- Catalog content is authored in #1005; this table is the mechanism (#993). CREATE TABLE IF NOT EXISTS trait_templates ( tag TEXT PRIMARY KEY, label TEXT NOT NULL, cultural_description TEXT, corridor_pool TEXT NOT NULL DEFAULT 'baseline', -- baseline | heritage | cross_corridor geographic_sector TEXT, -- corridor; NULL = shared/cross-corridor bulk_class_gate TEXT, -- JSON list of eligible BulkClass; NULL/[] = any production_ubiquity_gate TEXT, -- JSON list; NULL/[] = any min_prosperity_bps INTEGER NOT NULL DEFAULT 0, -- hard gate (basis points) base_weight INTEGER NOT NULL DEFAULT 10000, -- basis points weight_mods TEXT, -- JSON {dimension: {value: multiplier_bps}} zone_affinity TEXT, -- JSON {DistrictType: weight_bps} allow_tags TEXT, -- JSON list of ObjectTag block_tags TEXT, -- JSON list of ObjectTag era_scope TEXT, visual_bundle TEXT, -- JSON (D-235 bundle + fallback parents) updated_at TEXT NOT NULL DEFAULT (datetime('now')) ); CREATE INDEX IF NOT EXISTS idx_trait_templates_pool ON trait_templates(corridor_pool); CREATE INDEX IF NOT EXISTS idx_trait_templates_sector ON trait_templates(geographic_sector); -- atlas_body_trait_bias: sparse per-body bias on the catalog draw (hero bodies -- only, ~30-40). pin = mandatory (counts toward K); boost = weight up (<=3x); -- suppress = weight down (>=0.33x, never 0). Hero-pin content authored in #1017. CREATE TABLE IF NOT EXISTS atlas_body_trait_bias ( id INTEGER PRIMARY KEY AUTOINCREMENT, body_id TEXT NOT NULL REFERENCES bodies(body_id) ON DELETE CASCADE, template_tag TEXT NOT NULL REFERENCES trait_templates(tag) ON DELETE CASCADE, bias_kind TEXT NOT NULL, -- pin | boost | suppress weight_multiplier_bps INTEGER, -- boost 10001..30000; suppress 3300..9999; pin NULL note TEXT, updated_at TEXT NOT NULL DEFAULT (datetime('now')), UNIQUE (body_id, template_tag) ); CREATE INDEX IF NOT EXISTS idx_atlas_body_trait_bias_body ON atlas_body_trait_bias(body_id); CREATE INDEX IF NOT EXISTS idx_atlas_body_trait_bias_tag ON atlas_body_trait_bias(template_tag); -- architecture_zone_bias: D-235 step-2 sparse bias table (T-988). Per -- (template, zone_type) axis-token weight overrides within that template's -- own visual_bundle; unlisted (template, zone_type) pairs — and unlisted -- tokens within a listed entry — fall back to a uniform draw. Baked from -- wiki/economics/architecture_zone_bias.toml. Importer-validated (V-TT-06): -- every referenced token must already be in the template's visual_bundle. CREATE TABLE IF NOT EXISTS architecture_zone_bias ( template_tag TEXT NOT NULL REFERENCES trait_templates(tag) ON DELETE CASCADE, zone_type_id TEXT NOT NULL, -- D-142 zone-type id (BuildingPropertyTag.zone_type_id) bias TEXT NOT NULL, -- JSON {axis: {token: weight_bps}}, D-010 integer bps PRIMARY KEY (template_tag, zone_type_id) ); CREATE INDEX IF NOT EXISTS idx_architecture_zone_bias_template ON architecture_zone_bias(template_tag); -- color_register_bands: D-235 numeric HSV sampling band per trait-template -- `color_register` label (T-988). One band per register; the fill seed -- samples (hue, sat, val) uniformly within it per building. Baked from -- wiki/economics/color_register_bands.toml. Importer-validated (V-TT-07): -- every color_register referenced by trait_templates.visual_bundle must have -- a band here, and every band's ranges must be in-bounds with min < max. CREATE TABLE IF NOT EXISTS color_register_bands ( color_register TEXT PRIMARY KEY, hue_min INTEGER NOT NULL, -- centidegrees (degrees x 100), 0..36000 hue_max INTEGER NOT NULL, sat_min INTEGER NOT NULL, -- basis points, 0..10000 sat_max INTEGER NOT NULL, val_min INTEGER NOT NULL, -- basis points, 0..10000 val_max INTEGER NOT NULL ); -- END TRAIT TEMPLATES (D-232, #993) -- 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); -- Generator metadata stamp (#855, #856, #888) -- One row per generator, updated on each successful non-dry-run. -- schema_version: monotonic semver string (e.g. "1.0.0") — bump on backwards-incompatible changes. -- Orderable, enabling savegame migration lineage (Phase 5+). -- Defined as SCHEMA_VERSION constant in tooling/schema_version.py. -- schema_sha: SHA-1 hex of systems-schema.sql content at generation time (tamper detection). -- generator_sha: SHA-1 hex of the generator source file(s) content -- generated_at: ISO-8601 UTC timestamp of the run -- -- Used by: -- tooling/check-systems-db-stamp — verifies freshness before push (#857) -- .config/hooks/pre-push — rejects pushes with stale DB (#857) -- /pr-push skill — triggers make regen-db if stale (#858) CREATE TABLE IF NOT EXISTS meta ( generator_name TEXT PRIMARY KEY, -- 'import_economics' (generate_atlas retired #951) schema_version TEXT NOT NULL, -- monotonic semver string (e.g. "1.0.0") — see #888 schema_sha TEXT, -- SHA-1 hex of systems-schema.sql content (tamper detection) generator_sha TEXT NOT NULL, -- SHA-1 hex of generator source file(s) content generated_at TEXT NOT NULL DEFAULT (datetime('now')) ); 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.