feat(db): corp-HQ settlement model + per-settlement population/class bake (T-1074/T-1075, D-242)

Remove the reserved=1 corp-HQ city-pool cross-reference (duplicate co-named cities); corp_specialization keyed on the D-237 vocabulary extended 27->30; authored corp_hq_placement.toml {CityTenant,Standalone} map; headquarters_body backfill via lifted most-populated-body heuristic + body-type tiebreak; standalone HQs emitted as ordinary settlement rows; Zipf rank-size population spread from bodies.population at import; settlement_class defaults PopulationBudget + settlement_name_locked.toml hero pins; retire orphaned populate-corporations.sh; regen systems.db.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 10:29:00 +02:00
co-authored by Claude Fable 5
parent 43a6d55180
commit 62bbf57009
12 changed files with 903 additions and 145 deletions
+194
View File
@@ -0,0 +1,194 @@
# ==========================================================================
# Settled Reach — Corp HQ Placement Map (D-242)
# Source of truth. Compiled into corporations.hq_placement by
# import_economics.py (populate_corp_specialization), and into the
# Standalone-HQ settlement rows atlas_city_names gains by
# populate_standalone_hq_settlements (both economy_import/corporations.py).
#
# D-242: a corporate headquarters is not a city. Each corp's HQ is either:
# CityTenant — a tenant of an EXISTING city; a corp -> city link
# (corporations.headquarters_city_id), NO new settlement.
# Many corps may tenant one city (a bank and a newspaper
# share a downtown).
# Standalone — its OWN settlement, emitted into the cascade settlement
# list with its own body + position (a mine or shipyard
# sits alone). Standalone stanzas carry a second field,
# standalone_economic_role, so the emitted atlas_city_names
# row has a real economic_role (the existing 10-value
# D-195/D-197 vocabulary — manufacturing | financial |
# agricultural | extraction | service_mixed | institutional |
# transit_hub | research | military | residential) instead
# of a generic fallback. CityTenant stanzas do not carry this
# field — no settlement is emitted for them, so it would be
# dead data.
#
# This file has exactly one stanza per corp_specialization value — the
# REUSED D-237 specialization_vocabulary id-space (see
# wiki/economics/specialization_vocabulary.toml): the original 27
# system-authored values plus 3 corp-only Services-extension values added
# alongside this file (trade_distribution, hospitality_hub,
# professional_services — a real gap the T-1074 corp categorization pass
# found: ~10% of corps do logistics/hospitality/consulting work no original
# value covered). D-242 asked for an authored "specialization -> HQ-placement
# map"; this vocabulary already exists and already carries the signal that
# decides placement (bulk_class_projected: NonPhysical vs everything else),
# so rather than invent a second, parallel corp-only taxonomy, the map below
# is this vocabulary keyed onto the two-value placement enum (D-242
# amendment, recorded 2026-07-16 alongside T-1074).
#
# The default follows bulk_class_projected exactly:
# NonPhysical (financial/institutional/media/legal/research services)
# -> CityTenant — an office; no physical plant needed.
# Everything else (BulkSolid/BulkLiquid/PrecisionDense/Perishable —
# farms, mines, refineries, shipyards, factories)
# -> Standalone — needs land/plant of its own.
#
# hq_placement is authored EXPLICITLY per value (not computed from
# bulk_class_projected at import time) so a future ticket can flip one
# value without touching import code — three values in the Services group
# (governance_center, transit_hub, research_station) are individually
# lore-ambiguous (a "transit hub" corp could plausibly BE its own
# spaceport-town, e.g. Gateway/Wolf 359 read as whole transit-identity
# systems in the vocabulary's own descriptions) but are pinned to the
# bulk_class_projected default here for consistency; revisit case-by-case
# if a specific corp's placement reads wrong once bodies are walkable.
#
# Decisions: D-242 (this map), D-237 (the reused vocabulary + the
# bulk_class_projected signal this defaults from). Ticket: T-1074.
# ==========================================================================
# -------------------------------------------------------------------------
# Agricultural — Standalone (farms/estates need land)
# -------------------------------------------------------------------------
[estate_farming]
hq_placement = "Standalone"
standalone_economic_role = "agricultural"
[breadbasket]
hq_placement = "Standalone"
standalone_economic_role = "agricultural"
[terroir_agriculture]
hq_placement = "Standalone"
standalone_economic_role = "agricultural"
[terroir_spirits]
hq_placement = "Standalone"
standalone_economic_role = "agricultural"
[terroir_organics]
hq_placement = "Standalone"
standalone_economic_role = "agricultural"
# -------------------------------------------------------------------------
# Energy — Standalone (extraction/refinery infrastructure)
# -------------------------------------------------------------------------
[fuel_production]
hq_placement = "Standalone"
standalone_economic_role = "extraction"
[geothermal_hub]
hq_placement = "Standalone"
standalone_economic_role = "extraction"
# -------------------------------------------------------------------------
# Extraction — Standalone (mine/quarry sits on the deposit)
# -------------------------------------------------------------------------
[ore_extraction]
hq_placement = "Standalone"
standalone_economic_role = "extraction"
[company_mining]
hq_placement = "Standalone"
standalone_economic_role = "extraction"
[marble_monopoly]
hq_placement = "Standalone"
standalone_economic_role = "extraction"
[rare_mineral_extraction]
hq_placement = "Standalone"
standalone_economic_role = "extraction"
[lattice_material_source]
hq_placement = "Standalone"
standalone_economic_role = "extraction"
# -------------------------------------------------------------------------
# Manufacturing — Standalone (factory/yard footprint)
# -------------------------------------------------------------------------
[general_industrial]
hq_placement = "Standalone"
standalone_economic_role = "manufacturing"
[shipbuilding]
hq_placement = "Standalone"
standalone_economic_role = "manufacturing"
[vehicle_production]
hq_placement = "Standalone"
standalone_economic_role = "manufacturing"
[consumer_goods_bazaar]
hq_placement = "Standalone"
standalone_economic_role = "manufacturing"
[gate_fabrication]
hq_placement = "Standalone"
standalone_economic_role = "manufacturing"
[military_industrial]
hq_placement = "Standalone"
standalone_economic_role = "military"
# -------------------------------------------------------------------------
# High-tech — Standalone (fabrication/aquaculture plant)
# -------------------------------------------------------------------------
[precision_tech]
hq_placement = "Standalone"
standalone_economic_role = "manufacturing"
[marine_farming]
hq_placement = "Standalone"
standalone_economic_role = "agricultural"
# -------------------------------------------------------------------------
# Services (all NonPhysical) — CityTenant (an office, no plant); no
# standalone_economic_role — no settlement is ever emitted for these.
# -------------------------------------------------------------------------
[financial_hub]
hq_placement = "CityTenant"
[governance_center]
hq_placement = "CityTenant"
[transit_hub]
hq_placement = "CityTenant"
[research_station]
hq_placement = "CityTenant"
[legal_archive]
hq_placement = "CityTenant"
[media_center]
hq_placement = "CityTenant"
[longevity_monopoly]
hq_placement = "CityTenant"
# -------------------------------------------------------------------------
# Services extension (3, all NonPhysical) — added alongside the vocabulary
# extension in specialization_vocabulary.toml (D-242/T-1074). Same rule as
# the rest of Services: NonPhysical -> CityTenant (an office/depot, not a
# factory — this is exactly what made these corps hard to place in the
# original 27).
# -------------------------------------------------------------------------
[trade_distribution]
hq_placement = "CityTenant"
[hospitality_hub]
hq_placement = "CityTenant"
[professional_services]
hq_placement = "CityTenant"
@@ -0,0 +1,88 @@
# ==========================================================================
# Settled Reach — Authored NameLocked Settlement Overrides (D-242, T-1075)
# Source of truth. Compiled by import_economics.py
# (atlas.populate_settlement_population_class) onto atlas_city_names.settlement_class.
#
# D-196 defines four SettlementClass variants. Bake scope (T-1075) only
# assigns two of them: every pooled city defaults to PopulationBudget (the
# D-196 population thresholds — >=50k active / <5k ghost — are read from
# this baked population by a later, not-yet-built GENERATION-time consumer;
# out of this bake's scope); this file is the small authored override list
# that PINS a settlement to NameLocked instead — "named in wiki; always
# active regardless of population" (D-196). EconomicTriggered and
# OrganicGrowth are simulation-time classes (D-196) — out of bake scope,
# never written here.
#
# Use sparingly. NameLocked exists for settlements whose narrative
# significance does not depend on (and must survive) whatever population
# number the rank-size bake happens to produce — capitals and the handful of
# systems with a singular landmark identity (D-237's must-pin hero systems:
# docs/workshops/system-economic-specialization/workshop-outcomes.md §7).
#
# REVISED 2026-07-16 after the first live make regen-db (T-1074+T-1075
# integration test): the first draft of this file picked names straight out
# of the PRE-T-1074 atlas_city_names table (Sirius, Groombridge, Cygni B,
# Renaissance, Parallax, Prometheus, Nova Roma) — those turned out to be
# corp-HQ cross-reference ARTIFACTS from the retired D-207
# populate_atlas_city_names_corps (the corp's authored HQ-city text
# frequently equalled its system's proper_name, e.g. Gate Corp's
# "headquarters: Renaissance (GJ 251)"). Once that insert path was removed
# (D-242's whole point), those names no longer exist in the pure
# markers.json pool. Every entry below was re-verified against a real
# `make regen-db` output — see the note on each stanza for its actual
# source row. Prometheus (GJ 702B) is DROPPED from this list — verified
# live: every one of its 12 bodies has zero authored markers.json city
# names AND zero population, so there is no settlement to pin at all (a
# genuine wiki content gap: the Reach's longevity-monopoly hero system has
# no named city anywhere in its system — flagged for the lead, not fixed
# here; population/settlement_class baking cannot invent a name to pin).
#
# Match key: (body_id, name) against atlas_city_names — NOT the numeric
# atlas_city_names.id, which is regen-volatile (AUTOINCREMENT, no
# UNIQUE(body_id, name) — see the atlas_city_names schema comment, D-242).
# A stanza with no matching row is a silent no-op (logged) rather than an
# import error, so a future markers.json rename doesn't hard-fail regen-db.
#
# Decisions: D-196 (SettlementClass), D-242 (population/class baking scope),
# D-237 (the hero-system set these are drawn from). Ticket: T-1075.
# ==========================================================================
[[hero]]
body_id = "GJ144d"
name = "Kallast"
note = "Kallast / GJ 144 (Ran) — THE breadbasket; lore-anchored plains body (D-239 §1). Pure markers.json pool name, unaffected by the corp-HQ-artifact issue."
[[hero]]
body_id = "GJ820Bc"
name = "Cygni Combines"
note = "Cygni B / GJ 820B — shipbuilding cluster, 7 competing combines (D-237). D-242 Standalone-HQ settlement (general_industrial corp_specialization) — the corp's own settlement IS the hero identity here; the pure pool has only 'Metropolis' (generic)."
[[hero]]
body_id = "GJ251c"
name = "The Gate Corporation"
note = "Renaissance / GJ 251 — Gate Corp fabrication monopoly (D-237). D-242 Standalone-HQ settlement (gate_fabrication, MonopolySource) — the pure pool has only 'Tributarium'/'Ruhr' (generic); the monopolist's own company town is the hero place."
[[hero]]
body_id = "GJ764d"
name = "Mastroianni Vehicle Group"
note = "Nova Roma / GJ 764 — MVG vehicle production (D-237). D-242 Standalone-HQ settlement (vehicle_production) — pure pool has only 'Mirafiori'/'Fiorino' (generic)."
[[hero]]
body_id = "GJ380c"
name = "Aldren"
note = "Groombridge / GJ 380 — financial cluster + GSH clearing-house landmark (D-237). D-237's hero identity is the SERVICES/finance character, not any one CityTenant corp's manufacturing HQ — pinned to the lowest-id pure-pool city (a CityTenant host) rather than a Standalone-HQ settlement, since Groombridge's Standalone HQs (Alcyone Tech, Arclamp, ...) are all general_industrial/precision_tech, not financial_hub."
[[hero]]
body_id = "GJ244Ad"
name = "Mandate"
note = "Sirius / GJ 244A — 'the capital' (system_specialization.toml). Lowest-id pure-pool city; the pure pool no longer contains 'Sirius' itself (that was a corp-HQ artifact)."
[[hero]]
body_id = "GJ280Ad"
name = "Strata"
note = "Parallax / GJ 280A — news syndicates / cultural production center (D-237). Lowest-id pure-pool city (CityTenant host for the media_center specialization)."
[[hero]]
body_id = "GJ338Bd"
name = "Viridarium"
note = "Arbour / GJ 338B — estate amenity agriculture (D-237). Lowest-id pure-pool city; reads as the manor/estate theme D-237 describes."
+51 -1
View File
@@ -1,5 +1,5 @@
# ==========================================================================
# Settled Reach — Economic Specialization Vocabulary (D-237)
# Settled Reach — Economic Specialization Vocabulary (D-237, extended D-242)
# Source of truth. Compiled to systems.db specialization_vocabulary by
# import_economics.py.
#
@@ -8,6 +8,18 @@
# SCALE is encoded in the value itself (breadbasket vs estate_farming vs
# terroir_agriculture) — there is no separate scale column.
#
# SHARED VOCABULARY (D-242, T-1074): this id-space is used by TWO axes —
# system_economy.economic_specialization (D-237, the original per-SYSTEM
# authored identity) and corporations.corp_specialization (D-242, the
# per-CORP HQ-placement key; see corp_hq_placement.toml). A corp's
# specialization is authored directly in its wiki-page frontmatter
# (wiki/corporations/*.md, key: corp_specialization), not in a separate
# per-corp TOML — one value in this table, two authored sources reading it.
# The 3-value "Services extension" block near the end of the Services
# section was added for corp_specialization coverage (a corp-only gap the
# original 27 system-scale values didn't need to cover); nothing stops a
# future system from adopting them too if the fit is right.
#
# Fields per entry:
# commodity_id FK -> commodities.commodity_id (the anchor
# commodity; also drives body-level draws)
@@ -252,3 +264,41 @@ production_ubiquity_override = "monopolistic"
bulk_class_projected = "NonPhysical"
production_ubiquity_projected = "MonopolySource"
description = "The single licensed longevity/re-embodiment facility; access-controlled (Prometheus)."
# -------------------------------------------------------------------------
# Services extension (3) — added D-242/T-1074 for corp_specialization reuse.
#
# The categorization pass that backfilled corp_specialization for all 165
# corporations (T-1074) found 16/155 wiki corps (~10%) whose actual business
# — moving/selling already-made goods, running hospitality/tourism
# properties, or general professional advisory — had no honest fit among
# the original 24 system-authored values above. Per D-242's amendment
# ("prefer reusing/extending the existing vocabulary over inventing a
# parallel taxonomy"), these three values extend the shared vocabulary
# rather than spawn a corp-only one. All three are NonPhysical (services;
# CityTenant HQ placement per corp_hq_placement.toml) even though
# trade_distribution's anchor commodity is itself physical — the
# SPECIALIZATION is the logistics/service activity, not manufacture, which
# is exactly the distinction that made these corps ungroupable in the first
# place (a distributor's HQ is an office/depot, not a factory).
# -------------------------------------------------------------------------
[trade_distribution]
commodity_id = "consumer_goods"
production_ubiquity_override = "" # catalog default (ubiquitous) — distribution touches everything
bulk_class_projected = "NonPhysical"
production_ubiquity_projected = "Common"
description = "Wholesale/retail trade and freight distribution — moves goods at scale, does not manufacture them (Mercado Travessia, Eastbay Trading)."
[hospitality_hub]
commodity_id = "hospitality"
production_ubiquity_override = "" # catalog default (regional)
bulk_class_projected = "NonPhysical"
production_ubiquity_projected = "Specialist"
description = "Lodging, resort, spa, and recovery-stay operators; tourism/hospitality is the settlement's economic identity (Alcazar Hospitality, Thalassa Resort Group)."
[professional_services]
commodity_id = "insurance"
production_ubiquity_override = "" # catalog default (regional)
bulk_class_projected = "NonPhysical"
production_ubiquity_projected = "Common"
description = "General business/strategy/operational advisory — risk, consulting, arbitration outside the dedicated legal_archive/financial_hub identities (Saigon Consulting, Lusaka Advisory)."