docs(meta): H3 — correct D-242 verification wording to recompute-every-run behavior
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1821,14 +1821,14 @@ Technical foundation decisions that constrain implementation: engine, client-ser
|
||||
- **Implementation:** corp-HQ model + specialization map + standalone-HQ settlements, remove the cross-ref insert, resolve `headquarters_body` (**T-1074**); per-settlement population/settlement_class baking (**T-1075**); and the gated road-graph hub refinement — scaled-cap hubs + co-location collapse + hybrid minor-settlement attach + the deferred `RailHeadFacing` junction pass (which itself needs a [D-213](#d-213) amendment) — (**T-1076**, blocked by T-1074 + T-1075). All under Phase 4 (epic T-750). The T-1038 road-graph **core** (MST + A\* + MaintenanceAuthority + waypoints + junction detection) ships independently of this record — it routes over whatever settlement list it is handed.
|
||||
- **Implementation status (T-1074/T-1075, 2026-07-16): done.** Both stories landed together, in order.
|
||||
- **`corp_type` was already known degenerate** (158 `corporation` / 7 `combine`) — confirmed the key is `corporations.specialization`, not `corp_type`. That free-text `specialization` column is legacy prose (only 20/165 rows ever populated, by the orphaned `tooling/populate-corporations.sh` — **retired/deleted**, T-1074 point 6) and is left alone (`generate_corporations`'s fuzzy brand-relevance match still reads it). The HQ-placement key is a **new, separate column**: `corporations.corp_specialization`.
|
||||
- **Vocabulary choice — reused AND extended, per this record's own instruction to prefer reuse.** `corp_specialization` reuses the [D-237](#d-237) `specialization_vocabulary` id-space directly rather than a parallel corp-only taxonomy — every value already carries `bulk_class_projected` (`NonPhysical` vs everything else), which *is* the CityTenant/Standalone signal this record calls for. Backfilling all 165 corps (155 with a wiki page; the categorization pass read every one) found the 27 system-authored values genuinely under-cover corp-shaped identities: ~10% of corps do **trade/logistics distribution**, **hospitality/tourism**, or **general professional/advisory** work — moving or selling what someone else made, not producing it — which none of the 27 values name. Three values were **added** to `specialization_vocabulary.toml` (all `NonPhysical` → `CityTenant`): `trade_distribution`, `hospitality_hub`, `professional_services`. The vocabulary is now 30 values, shared by two authored axes: `system_economy.economic_specialization` ([D-237](#d-237), system-scale) and `corporations.corp_specialization` (this record, corp-scale) — nothing stops a future system from adopting the 3 new values too. Per-corp authoring lives in wiki frontmatter (`wiki/corporations/*.md`, key `corp_specialization`) — the natural per-corp source, parsed by `_parse_corp_frontmatter`/`load_wiki_corps` (extended) — not a per-corp TOML stanza file (unlike D-237's per-*system* TOML, 165 corps would make one file unwieldy; one wiki page per corp already exists and is the authoritative source for everything else about it).
|
||||
- **The placement map is authored explicitly, not computed from `bulk_class_projected` at import time** — `wiki/economics/corp_hq_placement.toml`, one `hq_placement` stanza per vocabulary value (all 30), defaulting to the `NonPhysical` rule but reviewable/overridable per value without a code change. Standalone stanzas also carry `standalone_economic_role`, mapping the 30-value specialization onto the pre-existing 10-value `economic_role` vocabulary ([D-195](#d-195)/[D-197](#d-197)) the emitted settlement row needs (`match_cities`/`CompatibilityMatrix` index by the 10-value set, not the 30-value one).
|
||||
- **`headquarters_body` backfill reused the lifted heuristic, with one fix found live.** `most_populated_body_in_system` (lifted from the retired cross-reference function into `atlas.py`, reused by both the backfill and the Standalone-settlement emission) needed a third tiebreak: when every body in a system ties at `population = 0` (found live — GJ 702B/Prometheus, all 12 bodies), a population-then-`body_id`-ascending sort can land on an `asteroid_belt`/`oort_cloud` (its naming convention's `-` sorts before letters) over a `planet` sibling — a type that structurally cannot host a settlement. A `body_type` preference (`planet` < `moon` < `gas_giant` < `asteroid_belt`/`oort_cloud`) was added between the two, engaging **only** among population ties.
|
||||
- **Vocabulary choice — reused AND extended, per this record's own instruction to prefer reuse.** `corp_specialization` reuses the [D-237](#d-237) `specialization_vocabulary` id-space directly rather than a parallel corp-only taxonomy — every value already carries `bulk_class_projected` (`NonPhysical` vs everything else), which *is* the CityTenant/Standalone signal this record calls for. Backfilling all 165 corps (155 with a wiki page; the categorization pass read every one) found the 27 system-authored values genuinely under-cover corp-shaped identities: ~10% of corps do **trade/logistics distribution**, **hospitality/tourism**, **general professional/advisory**, or **licensed multi-site clinical** work — moving or selling what someone else made, or delivering certified services, not producing goods — which none of the 27 values name. Four values were **added** to `specialization_vocabulary.toml` (all `NonPhysical` → `CityTenant`): `trade_distribution`, `hospitality_hub`, `professional_services`, and `licensed_clinical_services` (the fourth via PR #177 review M1 — Somatic Futures is a 40+-clinic *licensed network*, one of several certified operators: neither generic consulting nor `longevity_monopoly`, which is Prometheus, THE sole facility). The vocabulary is now 31 values, shared by two authored axes: `system_economy.economic_specialization` ([D-237](#d-237), system-scale) and `corporations.corp_specialization` (this record, corp-scale) — nothing stops a future system from adopting the 4 new values too. Per-corp authoring lives in wiki frontmatter (`wiki/corporations/*.md`, key `corp_specialization`) — the natural per-corp source, parsed by `_parse_corp_frontmatter`/`load_wiki_corps` (extended) — not a per-corp TOML stanza file (unlike D-237's per-*system* TOML, 165 corps would make one file unwieldy; one wiki page per corp already exists and is the authoritative source for everything else about it). The corp pages are part of the `import_economics` meta-stamp source set (PR #177 T1): a frontmatter edit without a regen trips the stamp check like any TOML edit.
|
||||
- **The placement map is authored explicitly, not computed from `bulk_class_projected` at import time** — `wiki/economics/corp_hq_placement.toml`, one `hq_placement` stanza per vocabulary value (all 31), defaulting to the `NonPhysical` rule but reviewable/overridable per value without a code change. Standalone stanzas also carry `standalone_economic_role`, mapping the 31-value specialization onto the pre-existing 10-value `economic_role` vocabulary ([D-195](#d-195)/[D-197](#d-197)) the emitted settlement row needs (`match_cities`/`CompatibilityMatrix` index by the 10-value set, not the 31-value one).
|
||||
- **`headquarters_body` is recomputed from source on every run — never kept from prior DB state (PR #177 H1/H2).** The importer-owned `corporations` columns (`corp_specialization`, `hq_placement`, `headquarters_body`) are NULL-reset and re-derived each run: `corporations` is append-only, so a keep-if-set guard would freeze the first run's output forever and silently promote DB state to source (exactly the asset-pipeline golden-rule inversion — the review caught the original one-shot backfill doing precisely that). An authored `headquarters_body:` frontmatter override wins where present (hard-validated: body must exist and sit in the corp's HQ system; none authored yet); otherwise `most_populated_body_in_system` (`atlas.py`, descended from the retired cross-reference function's heuristic) derives it with a four-stage deterministic tiebreak: **population DESC → city-bearing body (has `atlas_city_names` rows) → body-type rank (`planet` < `moon` < `gas_giant` < belt/oort) → `body_id`**. The city-presence tier is what the review's H2 added — the first draft ranked body-type directly and rested on a false premise ("belts can't host settlements"): belts in this world *do* host them (GJ845-belt: Orkney Ceramics; GJ268-belt: Jeju Lattice; GJ222A-belt: two Standalone HQs), so presence keeps settlements where they are, and the type rank only fires in fully cityless, fully unpopulated systems (GJ 702B — 12 bodies, zero population, zero city rows → planet `GJ702Bb` instead of the belt the old alphabetical sort picked). The presence set is read at step 7b, *before* the run's pool rebuild — i.e. the previous run's settled state, a deliberate, documented hysteresis (settlement continuity across regens) whose fixpoint is stable: each run's output reproduces the presence distribution it read.
|
||||
- **CityTenant is a new schema link, not a repurposed one.** `corporations.headquarters_city_id` (nullable FK → `atlas_city_names.id`) is the "corp→city link" this record calls for — the OLD `atlas_city_names.corp_id` column is left in place (additive-safe, no `DROP COLUMN`) but is now permanently `NULL`: nothing writes it once the cross-reference insert is gone, and nothing ever read it downstream (confirmed: zero references in `city_context_reader.rs`/`attractor_matching.rs` — the Rust consumption side never depended on it).
|
||||
- **The merge is free.** Standalone-HQ settlements are inserted as ordinary `atlas_city_names` rows (`populate_standalone_hq_settlements`, Phase B) — same schema, no corp-linkage marker — so `read_body_settlements` → `match_cities` (the "natural seam") needed **zero Rust changes**; a Standalone HQ competes for attractors purely on population/`settlement_class`, exactly like any pooled city (verified live: `cascade_for_body("GJ251c")` places "The Gate Corporation" alongside "Tributarium"/"Ruhr" through the unmodified pipeline). Two new Rust unit tests lock this invariant in (`city_context_reader.rs`, `attractor_matching.rs`).
|
||||
- **Verified against a real, repeated `make regen-db`:** 0 duplicate `(body_id, name)` groups (was the whole bug); 155/155 wiki corps get `corp_specialization`/`hq_placement`; 154/155 get `headquarters_body` (the 1 gap is a pre-existing broken `headquarters system` reference on `sova-station-works.md`, unrelated to this record); 95 `Standalone` corps → 94 settlements emitted (same 1 gap) + 60 `CityTenant` corps → 58 links (2 unmatched: `prometheus-labs`/`kovalev-freight`, both on GJ702B-belt, which — even after the tiebreak fix — has **zero** authored city names anywhere in its system, a wiki content gap, not an import bug); deterministic across 3 consecutive runs. The 10 legacy DB-only corps seeded by the now-deleted `populate-corporations.sh` (`mvg`, `adams-ford`, `dsmc`, and the 7 Cygni-B "combines") have no wiki page to author `corp_specialization` from and are left with `corp_specialization`/`hq_placement`/`headquarters_body` NULL — a logged, non-fatal gap (they are load-bearing for 2,468 `brand_products` rows via `generate_brands`, which reads `corporations` directly and doesn't care about wiki-page existence, so their rows are kept, not deleted).
|
||||
- **Verified against real, repeated `make regen-db` runs — each run a full recompute, not a no-op (wording corrected per PR #177 H3; the pre-review text described the one-shot backfill being stably inert):** 0 duplicate `(body_id, name)` groups (was the whole bug); 155/155 wiki corps get `corp_specialization`/`hq_placement`; **154/155 `headquarters_body` re-derived on every run** (the 1 gap is a pre-existing broken `headquarters system` reference on `sova-station-works.md`, unrelated to this record), with two consecutive runs producing **zero differences** across all 165 corps' placement columns and tenancy targets; against the prior committed snapshot the recompute changes **exactly 2 placements** — `prometheus-labs`/`kovalev-freight`, `GJ702B-belt` → `GJ702Bb` (the intended H2 outcome) — and **zero tenancy targets** (`calluna-wellness` → (GJ845-belt, Orkney Ceramics) and `namsan-collective` → (GJ268-belt, Jeju Lattice) preserved via the city-presence tier). 95 `Standalone` corps → 94 settlements emitted (same 1 `sova` gap) + 60 `CityTenant` corps → 58 links (2 unmatched: the GJ 702B pair — now correctly on planet `GJ702Bb`, which still has zero authored city names, like every body in that system: a wiki content gap [T-1115], logged, not an import error). A poisoned-copy test confirmed no DB state survives the reset: hand-written garbage in all three columns is wiped and re-derived (wiki corp) or reverted to NULL (legacy corp with no authored source — the removed-key case). The 10 legacy DB-only corps seeded by the now-deleted `populate-corporations.sh` (`mvg`, `adams-ford`, `dsmc`, and the 7 Cygni-B "combines") have no wiki page to author `corp_specialization` from and are reset to NULL across the board each run — a logged, non-fatal gap (they are load-bearing for 2,468 `brand_products` rows via `generate_brands`, which reads `corporations` directly and doesn't care about wiki-page existence, so their rows are kept, not deleted).
|
||||
- **Amendment (T-1075, 2026-07-16 — population/class derivation clarified):** the phrase above, "the per-settlement spread is **carried down into** the city/settlement source data," is superseded by **derivation-at-import, not hand-authoring**: `import_economics` computes the per-city population spread from the authored `bodies.population` on **every** `regen-db` run, via a documented integer rank-size (Zipf, exponent 1) curve (`atlas.py::_zipf_population_spread`, constants named + commented — tuning the curve is a deliberate code change, not a data edit). There is no per-city population *source file* — only `bodies.population` (already-baked) is authored; the per-settlement split is 100% derived. The spread runs over the **corrected, merged pool** (post-T-1074: Standalone-HQ settlements are ordinary rows by the time this runs, so they get a real share of their body's population like any city, not a bolted-on afterthought). `settlement_class` defaults every pooled row to `PopulationBudget` (D-196); a small authored override TOML (`wiki/economics/settlement_name_locked.toml`, `[[hero]]` stanzas keyed on `(body_id, name)` — not the volatile numeric id) pins 8 hero settlements to `NameLocked`. **EconomicTriggered and OrganicGrowth stay simulation-time** (D-196) — out of bake scope; so is the D-196 *generation-time* consumer that reads the baked population against the >=50k-active/<5k-ghost thresholds to decide skeleton depth (confirmed live: no such consumer exists yet anywhere in `server/src/atlas` — this record's bake only writes the values a future ticket will read).
|
||||
- **Cross-reference:** [D-207](#d-207-fully-generative-placement--markersjson-stripped-to-topographic-features) (corp-HQ cross-ref — the insert path is removed here), [D-223](#d-223) (names-only pool — the corp-HQ-stays + pop/class-deferred notes are superseded here), [D-211](#d-211) (placement — consumes the merged list; only position is seed-derived), [D-213](#d-213) (`FoundingOrientation` — a `RailHeadFacing` variant is the gated follow-on), [D-237](#d-237) (corp `specialization` vocabulary — the HQ-placement key, extended here from 27 to 30 values), [D-195](#d-195)/[D-197](#d-197) (the 10-value `economic_role` vocabulary `standalone_economic_role` maps onto), [D-196](#d-196) (`SettlementClass` — the bake target), [D-010](#d-010) (determinism — only position is seed-derived).
|
||||
- **Cross-reference:** [D-207](#d-207-fully-generative-placement--markersjson-stripped-to-topographic-features) (corp-HQ cross-ref — the insert path is removed here), [D-223](#d-223) (names-only pool — the corp-HQ-stays + pop/class-deferred notes are superseded here), [D-211](#d-211) (placement — consumes the merged list; only position is seed-derived), [D-213](#d-213) (`FoundingOrientation` — a `RailHeadFacing` variant is the gated follow-on), [D-237](#d-237) (corp `specialization` vocabulary — the HQ-placement key, extended here from 27 to 31 values), [D-195](#d-195)/[D-197](#d-197) (the 10-value `economic_role` vocabulary `standalone_economic_role` maps onto), [D-196](#d-196) (`SettlementClass` — the bake target), [D-010](#d-010) (determinism — only position is seed-derived).
|
||||
- **Dissent:** None
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user