# Round 1 — Burnelli-Sheldon ## System Economic-Specialization Workshop *Lens: economic credibility + field→commodity mapping + weighting model + integration pipeline + CI guardrails.* --- ## Q1 — The Field **Proposed field: `dominant_commodity_id TEXT REFERENCES commodities(commodity_id)`** A single foreign-key column on `system_economy` pointing directly into the `commodities` table. One per system. Authored lore-first; the heuristic is the fallback for unspecified systems. ### Why a commodity ID and not a category? The field needs to resolve to `dominant_bulk_class × dominant_production_ubiquity` for D-233. The only clean source of that mapping is the commodity catalog itself — `bulk_class` and `production_ubiquity` are already on every commodity record. A separate "specialization category" field would require a second mapping table and would drift from the catalog over time. The FK is the mapping. ### The BulkClass × ProductionUbiquity projection D-233 (refined 2026-05-26) already defines the 8→5 `BulkClass` projection from commodities.toml: | `commodities.bulk_class` | → `BulkClass` (D-233) | |---|---| | `bulk` | `BulkSolid` | | `standard` | `BulkSolid` | | `compact` | `BulkSolid` | | `oversized` | `BulkSolid` | | `precision` | `PrecisionDense` | | `liquid` | `BulkLiquid` | | `perishable` | `Perishable` | | `non_physical` | `NonPhysical` | And the 5→4 `ProductionUbiquity` projection from commodities.toml: | `commodities.production_ubiquity` | → `ProductionUbiquity` (D-233) | |---|---| | `ubiquitous` | `Ubiquitous` | | `common` | `Common` | | `regional` | `Common` (treated as common for built-form purposes) | | `concentrated` | `Specialist` | | `monopolistic` | `MonopolySource` | `regional` collapses to `Common` because the built-form distinction is between broadly distributed infrastructure (Ubiquitous/Common) and concentrated infrastructure (Specialist/MonopolySource). Regional production produces the same physical plant pattern as common — dispersed operations, no single dominant facility. ### What this field is *not* Services (`non_physical`) ARE a valid `dominant_commodity_id` choice. A financial center like Groombridge should pin to `financial_services`. This gives `BulkClass = NonPhysical`, which D-233 correctly resolves to dense office towers and civic halls (high coverage, 0.85–0.95 roofed fraction) with no operations surface. A service economy *looks* different from an extraction economy, as it should. ### Relationship to `economic_base_primary` / `economic_base_secondary` **Do not replace. Coexist.** The existing `economic_base_primary/secondary` fields are free-text narrative prose — "grain agriculture and aquaculture" (Ran), "Commission clearing house and financial arbitration" (Groombridge). They belong to the GTTR voice and are not machine-readable. Keep them. `dominant_commodity_id` is the machine-readable counterpart — one FK, vocabulary-constrained, CI-validated. The two fields serve different consumers (humans vs. the generator) and should stay separate. A mismatch between the prose and the FK is detectable by CI (W-SES-02, below) and flags inconsistency for human review. ### The vocabulary: all 36 commodities are valid The complete vocabulary is the `commodity_id` column of `commodities`. Any of the 36 entries is valid: **Raw materials** — meaningful for extraction-dominated systems that operate at raw scale (rare in practice; most systems export intermediates, but a frontier mining outpost can legitimately pin to `metallic_ore` or `lattice_grade_material`). **Intermediates and finals** — the primary choice for most named systems. Ran → `agricultural_produce` or `processed_food` (both defensible; the question is whether Ran is mostly a raw exporter or a processed food exporter; the GTTR implies raw-to-intermediate, so `agricultural_produce`). **Services** — valid for financial, legal, medical, and mixed-service systems. Groombridge → `financial_services`. The Sirius capital system → `commission_certification` or `legal_services`. **The no-pin option:** `dominant_commodity_id = NULL` is permitted for uninhabited systems. Inhabited systems that are `NULL` trigger a CI hard error (V-SES-01). --- ## Q2 — Within-System Weighted Variation The system `dominant_commodity_id` sets the prior. Bodies deviate from it through a weighted deterministic draw. The algorithm is static: `seed + static data only`. No PressureState, no tâtonnement, no runtime query. ### The candidate set (not a 36-commodity draw) The draw operates over a small candidate set — usually 3–6 commodities — not all 36. Drawing over all 36 would produce economically incoherent results (why does a high-tech research moon in an agricultural system randomly get commission_certification?). The candidate set is: 1. **System anchor** — always included: `system.dominant_commodity_id` 2. **Role preferred** — 1–2 commodities per `economic_role` (table below) 3. **Planet-class preferred** — 0–2 commodities based on `bodies.planet_class` 4. **Corp HQ commodity** — 0–1 commodity if a corporation is HQ'd here, via `corp_presence.primary_operation` → commodity lookup ### Weight assignment (integer, D-010 compliant) All weights are integer basis points. No floats in the derivation path. | Signal | Weight added | To which commodity | |---|---|---| | System anchor | `W_SYS = 10_000` | `system.dominant_commodity_id` | | Body `economic_role` preferred | `W_ROLE = 5_000` | Role's preferred commodity (table below) | | Body `economic_role` secondary | `W_ROLE_SEC = 2_000` | Role's secondary commodity if any | | Body `planet_class` preferred | `W_PLANET = 3_000` | Planet-class preferred commodity | | Body `planet_class` secondary | `W_PLANET_SEC = 1_000` | Planet-class secondary | | Corp HQ present | `W_CORP = 4_000` | Corp's `primary_operation` commodity | **Role → preferred commodity mapping (fallback heuristic):** | `economic_role` | Primary preferred | Secondary preferred | |---|---|---| | `agricultural` | `agricultural_produce` | `processed_food` | | `extraction` | `metallic_ore` | `rare_minerals` | | `manufacturing` | `refined_metals` | `advanced_alloys` | | `financial` | `financial_services` | `insurance` | | `service_mixed` | `legal_services` | `medical_services` | | `research` | `electronics` | `lattice_substrate` | | `transit_hub` | `fusion_fuel` | `refined_metals` | | `institutional` | `commission_certification` | `legal_services` | | `military` | `heavy_equipment` | `advanced_alloys` | | `residential` | `processed_food` | `agricultural_produce` | **Planet class → preferred commodity mapping (fallback heuristic):** | `planet_class` | Primary preferred | Secondary preferred | |---|---|---| | `terrestrial` | `agricultural_produce` | `metallic_ore` | | `rocky` / `barren` | `metallic_ore` | `stone` | | `icy` / `ice_giant` | `water` | `fusion_fuel` | | `gas_giant` | `fusion_fuel` | — | | `ocean` | `agricultural_produce` | `organic_compounds` | | `volcanic` | `metallic_ore` | `rare_minerals` | | `desert` | `stone` | `metallic_ore` | | (station/orbital) | *(no planet bonus)* | — | These are heuristics — they apply only when `dominant_commodity_id` is not pinned to override. ### Weighted draw algorithm (deterministic) ``` candidate_weights: HashMap = {} // Add system anchor candidate_weights[system.dominant_commodity_id] += W_SYS // Add role-preferred role_preferred = ROLE_MAP[body.economic_role] candidate_weights[role_preferred.primary] += W_ROLE if role_preferred.secondary: candidate_weights[role_preferred.secondary] += W_ROLE_SEC // Add planet class planet_preferred = PLANET_MAP[body.planet_class] if planet_preferred.primary: candidate_weights[planet_preferred.primary] += W_PLANET if planet_preferred.secondary: candidate_weights[planet_preferred.secondary] += W_PLANET_SEC // Add corp HQ if present if corp_hq_commodity != NULL: candidate_weights[corp_hq_commodity] += W_CORP // Weighted draw (integer arithmetic only — D-010) total_weight = sum(candidate_weights.values()) seed = SeedChain(world_seed) .derive(SeedDomain::Layer3Settlement, fnv1a_64(body_id)) .seed() rng = AtlasRng::new(splitmix64(seed)) draw = rng.next_u64() % total_weight // Walk the candidate list (sorted by commodity_id for determinism) // to find the winner ``` The resulting commodity is looked up in the commodity catalog for `bulk_class` → `BulkClass` and `production_ubiquity` → `ProductionUbiquity`. ### The farming moon in a foundry system (worked example) System: `dominant_commodity_id = advanced_alloys` (BulkSolid, Specialist — an alloying hub). Moon: `economic_role = agricultural`, `planet_class = terrestrial`, no corp HQ. Weight allocation: - `advanced_alloys` ← 10_000 (system anchor) - `agricultural_produce` ← 5_000 (role primary) + 3_000 (planet class) = 8_000 - `processed_food` ← 2_000 (role secondary) - `metallic_ore` ← 1_000 (planet class secondary) Total weight: 21_000. Draw probabilities: `advanced_alloys` 47.6%, `agricultural_produce` 38.1%, `processed_food` 9.5%, `metallic_ore` 4.8%. The moon **is** a farming moon about 38% of seeds. The system still reads as a foundry system because most bodies (those without strongly competing role signals) resolve to `advanced_alloys`. The farming moon exists; it is not the dominant read. ### Why W_SYS = 10_000 specifically This ratio is calibrated so that a body with *two strong competing signals* (both role AND planet class pointing away from the system anchor) still resolves to the system anchor ~33% of the time. That threshold keeps system identity legible: a 5-body system will usually have 3–4 bodies expressing the system character. If W_SYS were lower (e.g., 5_000), too many bodies would deviate, destroying the authored read. If higher (e.g., 15_000), the minority body (the farming moon) becomes nearly impossible, which is also wrong. These ratios can be tuned in a single named-constant block without touching algorithm logic. They are NOT authored per-system. --- ## Q3 — `economy_size`? **Out. Don't add it.** This is the first Q where I'll be direct: `economy_size` doesn't earn its slot because it is substantially covered by fields that already exist. ### What economy_size would modify vs. what already exists The proposed modifications for `economy_size` are: 1. **Coverage density** — already modulated by `density_class` (D-220), which is derived from `population` and `economic_role`. 2. **Count of specialized blocks** — already modulated by settlement footprint size, which is derived from `population` and `prosperity_baseline` (D-197). 3. **Labor draw for residential follow-on** — already set by `BulkClass` extraction multiplier (D-233). Every function that `economy_size` would provide is already covered. Adding it introduces a third authoritative source for the same signal, which creates inconsistency problems: - If `economy_size = major` but `population = 50_000`, which wins? - If `economy_size = minor` but `prosperity_baseline = 0.85` (high prosperity), do we compress the settlement? The answers require tiebreaker rules, which are complexity with no payoff. ### The genuine case it might address The one scenario where `economy_size` might seem necessary: a low-population monopoly extraction site that should have massive industrial infrastructure. A lattice_grade_material mine with 10,000 workers that ships to the entire galaxy. This is already handled: - `production_ubiquity = monopolistic` → `MonopolySource` → D-233 concentration rule: "MonopolySource concentrates contiguous block groups (the mine *is* the settlement, everything else is support)." The infrastructure is there. - `prosperity_baseline` for an extraction site with corp HQ presence will be elevated. - `density_class` for an extraction chokepoint with economic pull is Dense (D-220: "extraction chokepoints... pull people in faster"). A 10,000-person monopoly mine reads correctly under the existing system: low population, high density (terrain-constrained + economic pull), MonopolySource concentration, elevated prosperity (corp-funded infrastructure). No `economy_size` field required. ### Verdict `economy_size` is redundant with `population × prosperity_baseline`. If those two values are incorrectly authored for a system (e.g., the system wiki says it's a major economic hub but the population is set to 50K), that's an authoring error to fix — not a signal to add a third field that masks it. --- ## Q4 — The Content Pass: Field→Commodity Mapping + CI Guardrails + Integration ### Source location New TOML file: **`wiki/economics/system_specialization.toml`** One entry per system that has been authored. Uninhabited and unspecified systems have no entry (CI will hard-error if an inhabited system has no entry after the pass is complete). ```toml # wiki/economics/system_specialization.toml # Source of truth for authored dominant commodity per star system. # Compiled to system_economy.dominant_commodity_id via import_economics.py. # FK: must exist in wiki/economics/commodities.toml # # Decisions: D-233, D-184, this workshop (re-amends D-233) [GJ-144] # Ran — core agricultural breadbasket dominant_commodity_id = "agricultural_produce" [GJ-273] # Groombridge — Assembly clearing house dominant_commodity_id = "financial_services" [GJ-15A] # Sirius — Commission/Assembly capital dominant_commodity_id = "commission_certification" # ... ~300 systems total ``` One line per system. The comment is optional documentation for human reviewers. The TOML key is `system_id` (matching the `star_systems.system_id` column). ### Database integration Add one column to `system_economy` in `systems-schema.sql`: ```sql ALTER TABLE system_economy ADD COLUMN dominant_commodity_id TEXT REFERENCES commodities(commodity_id); ``` Add to `COLUMN_MIGRATIONS` in `import_economics.py` (idempotent ALTER TABLE). New import step in `import_economics.py`: ```python def import_system_specialization(conn, dry_run=False): """Read wiki/economics/system_specialization.toml and upsert dominant_commodity_id into system_economy for authored systems.""" # Read TOML # Validate FK against commodity catalog (V-SES-01, V-SES-02) # UPSERT into system_economy # Log coverage report ``` Source file added to `IMPORT_ECONOMICS_SOURCES` for meta stamp tracking. ### CI guardrails **Hard errors (build aborts):** | ID | Rule | Rationale | |---|---|---| | V-SES-01 | Every system with `bodies.inhabited > 0` AND `system_economy.population > 0` MUST have `dominant_commodity_id` after the content pass is marked complete. Before the pass is complete: warn only. | Guarantees generator has authored data for every city it generates. | | V-SES-02 | `dominant_commodity_id` must exist in `commodities(commodity_id)` | FK integrity — typos produce silent fallbacks otherwise. | | V-SES-03 | Systems where `dominant_commodity_id` maps to a service commodity (`tier IN ('service_professional', 'service_luxury')`) but whose `bodies` are all non-inhabited or uninhabited MUST be flagged | Services require population to be plausible. A services-dominant system with no bodies makes no sense. | **Soft warnings (build succeeds, prints):** | ID | Rule | Rationale | |---|---|---| | W-SES-01 | `production_ubiquity = 'monopolistic'` systems: print list for human review | Monopoly pins are load-bearing and easy to over-assign. Lore must support the monopoly claim (D-177). | | W-SES-02 | Systems where `economic_base_primary` (prose) contains words that don't match the commodity name/description of `dominant_commodity_id` | Contradiction detection between narrative prose and the machine field. Not a hard error — authors may intentionally be more specific in prose. | | W-SES-03 | `dominant_commodity_id = water OR agricultural_produce` for named, non-frontier systems | Water and agricultural produce are ubiquitous. A named core system claiming water as its identity is probably wrong (every system produces water). Exception: a system whose entire identity is regional food export may legitimately pin to `agricultural_produce`. Flag for review, don't abort. | | W-SES-04 | More than 20% of inhabited systems pinned to the same `dominant_commodity_id` | A balanced economy needs diversity. If 60 systems all claim `refined_metals`, the catalog entry is being used as a generic default, not an authored identity. | ### Coverage report (new output from import_economics.py) After the import, print: ``` system_specialization coverage: authored: NNN / MMM inhabited systems (NN%) by BulkClass: BulkSolid=NN, BulkLiquid=NN, Perishable=NN, PrecisionDense=NN, NonPhysical=NN by ProductionUbiquity: Ubiquitous=NN, Common=NN, Specialist=NN, MonopolySource=NN MonopolySource systems: [list] ``` This gives the content team a live view of market structure as they author. A healthy distribution avoids over-concentration in any one BulkClass or ubiquity tier. ### Who authors what (the 300-system content pass) **Miri**: Canonical commodity for every named/hero system from GTTR lore. The economic_base_primary prose is the input signal; Miri determines which of the 36 commodities best represents the system's authored identity. Source of truth on Ran (agricultural_produce), Groombridge (financial_services), the east reach lattice systems (lattice_grade_material), etc. **Paula**: Faction homeworlds, Compact systems, and political capitals. A Compact homeworld's dominant commodity is not just economic — it's a political statement. The Mark-primary zone currency choice and the shadow economy intensity are correlated with the commodity choice (Compact: tends toward energy independence → fusion_fuel, or organic-based industries independent of Commission certification routes). **Burnelli-Sheldon (me)**: Economic credibility review of all ~300 entries. Specifically: 1. **Monopoly-class check**: Does the lore actually support a `monopolistic` production_ubiquity pin? (D-177 constraints are the test — Kvitfjell marble, brach fiber, Calloway terroir, VGV varietals are the canon examples of genuine monopoly-class production. Assign monopolistic sparingly.) 2. **Distribution balance**: Check that the final distribution of commodity pins across 300 systems is plausible. An economy where 80% of systems are NonPhysical services is not credible. 3. **Field→BulkClass sanity**: Does the built form make sense? A financial-services dominant system should not look like a mine. If a pin produces an unexpected BulkClass (e.g., `water` → BulkLiquid for a city), flag for reconsideration. 4. **Heuristic fallback validation**: For uninhabited/unnamed systems that get the fallback heuristic (not authored), spot-check that the role+planet_class heuristic produces plausible results for representative cases. ### Authoring tool expectation The content pass is a text editor + TOML file workflow. No specialized tool needed. The `make regen-db` build validates every entry against the commodity catalog. A companion query helps authors check consistency: ```bash tooling/db/sqlite-query " SELECT se.system_id, se.dominant_commodity_id, c.bulk_class, c.production_ubiquity, se.economic_base_primary FROM system_economy se JOIN commodities c ON c.commodity_id = se.dominant_commodity_id WHERE c.production_ubiquity = 'monopolistic' ORDER BY se.system_id" ``` --- ## Q5 — Additional Identity Fields The question is: while touching all ~300 systems by hand once, what other 1–2 fields meaningfully flesh out identity at marginal additional authoring cost? The guard is explicit: every field is authoring burden ×300 and a maintenance surface. ### My recommendation: ONE additional field **`atlas_body_trait_bias` hero pins** (D-232) — this is the architecture-flavor anchor for hero bodies (~30–40). It is technically a per-body field, not a per-system field, but it is authored during the same pass since the hero body is the system's primary reading body. This earns its slot on one condition: **it is authored ONLY for hero bodies, not for all 300 systems.** The ~270 non-hero bodies run the algorithm with no bias (D-232 is explicit: "Non-hero bodies run the identical algorithm with no bias"). So the authoring burden is ~30–40 entries (one per hero body), not 300. From my economic-credibility lens, the trait pin is justified because: - D-232's hard-gate economics (bulk_class, prosperity, production_ubiquity) already constrain the template pool. The pin selects within the economically-valid set, not outside it. - The wrong architectural read on a canonical system is expensive — it cannot be corrected by a post-launch patch without breaking determinism (D-010). - The trait catalog is designed with pin support (`pin` counts toward K, K=1 for Minimal complexity tier). **However**: the actual `atlas_body_trait_bias` pins are Miri + Araminta territory (cultural meaning + visual bundle). I'll defer to them on which ~30–40 bodies get a pin. My role is to confirm that the economic eligibility rules don't need to be overridden to support the intended templates. ### Fields I recommend against **`economy_size`** — eliminated in Q3. **`trade_orientation` (export/import/self-sufficient)** — Derivable from `dominant_commodity_id` × `production_ubiquity`. A `MonopolySource` system is obviously export-dominant. A `NonPhysical` services system is self-contained (services don't traverse gates). An `Ubiquitous` raw material system may import more than it exports (since ubiquitous goods have thin margins and don't drive trade specialization). Authoring this separately introduces a second signal that can contradict the FK. Don't add it. **`political_register`** — Paula's domain. Plausibly valuable for faction-political characterization, but I should not advocate for fields outside my lane. I will say only that from an economic integration perspective, the political register would not touch the commodity-building pipeline at all — it would be consumed by NPC generation and dialogue, not by the generator. That makes it zero authoring burden overlap with the commodity pass. **`shadow_economy_intensity`** — D-174 explicitly defines this as derived from inputs (Commission presence, Compact membership, hop distance, gate topology), not authored per-system. The derivation model is correct. Don't replace it with an authored field; the emergent calculation is the point. ### Summary of recommended field set | Field | Scope | Authoring burden | Earns slot? | |---|---|---|---| | `dominant_commodity_id` | Per system (system_economy) | ~300 entries | **YES — primary deliverable** | | `atlas_body_trait_bias` pins | Per hero body (~30–40) | ~30–40 entries | **YES — but limited to hero bodies only** | | `economy_size` | Per system | ~300 entries | **NO — redundant** | | `trade_orientation` | Per system | ~300 entries | **NO — derivable** | | `political_register` | Per system | ~300 entries | **OUT OF LANE — Paula opines** | --- ## Integration summary The proposed change to the existing pipeline is minimal: 1. **`wiki/economics/system_specialization.toml`** — new authored source file. One `dominant_commodity_id` per inhabited system. 2. **`systems-schema.sql`** — add `dominant_commodity_id TEXT REFERENCES commodities(commodity_id)` to `system_economy`. 3. **`import_economics.py`** — add `import_system_specialization()` step, five new CI validation rules, coverage report output. 4. **`city_context_reader.rs`** — replace the two `#982 design-blocked` stub fields with a DB query that reads the commodity catalog join, runs the weighted draw algorithm, and populates `dominant_bulk_class` + `dominant_production_ubiquity`. 5. **`MIGRATION_SQL`** in `import_economics.py` — `ALTER TABLE system_economy ADD COLUMN dominant_commodity_id TEXT`. The existing `economic_base_primary/secondary` fields, the `economic_role` field on `bodies`, and the `system_factions` data are **all preserved and unchanged**. The new field plugs into existing signals; it does not replace them. Everything is static: read at build time into `systems.db`, resolved at generation-dispatch time by `CityContextReader`, no PressureState, no tâtonnement, no save-state. The per-body weighted draw uses `SeedChain(world_seed, body_id)` — deterministic, D-010 compliant, re-derivable from seed + static inputs alone.