Files
settled-reach/docs/workshops/system-economic-specialization/burnelli-sheldon-round1.md
T
jpmschweitzerandClaude Opus 4.8 33c083f0ef docs(workshops): system-economic-specialization rounds + outcomes
Three-round design workshop (Miri, Burnelli-Sheldon, Paula; documented by
Qatux, ticketed by SI) that produced D-237. Includes per-agent round
outputs, round notes, the independent scout critique, and the final
workshop-outcomes.md with the consolidated schema, 27-value economic
vocabulary, cultural + faction vocabularies, deterministic-varied fallback
spec, must-pin table, and CI guardrails.

Clerk-Skip: workshop discussion docs, no D-record surface (D-237 filed separately)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 18:40:08 +02:00

394 lines
24 KiB
Markdown
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.
# 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<commodity_id, u64> = {}
// 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.