fix(db): PR #177 review round — H1/H2/T1/T2/M1

H1+H2: headquarters_body is reset+derived every run (DB is never source); optional authored frontmatter override, hard-validated; tiebreak now population DESC -> city-bearing body -> type rank -> body_id, preserving belt tenancies and fixing GJ702B to GJ702Bb. T2: NULL-reset pass for corp_specialization/hq_placement before authored re-apply (poison-tested). T1: wiki/corporations/*.md globbed into IMPORT_ECONOMICS_SOURCES. M1: licensed_clinical_services vocabulary value (31st, NonPhysical->CityTenant) + somatic-futures retag. Fixpoint verified stable across 4 consecutive regens (0 diffs); regen systems.db.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 13:00:03 +02:00
co-authored by Claude Fable 5
parent 0c8a270471
commit e63caeb620
10 changed files with 275 additions and 128 deletions
Binary file not shown.
+44 -26
View File
@@ -164,17 +164,18 @@ def populate_atlas_city_names(conn: sqlite3.Connection, dry_run: bool) -> int:
return len(rows)
# body_type preference for the most_populated_body_in_system tiebreak — lower
# sorts first. Only breaks a population TIE (real population always wins);
# this exists because a plain population-then-body_id sort can otherwise land
# on an asteroid_belt/oort_cloud sibling purely because '-' (the body_id
# naming convention for these types, e.g. "GJ702B-belt") sorts before letters
# — a body type that structurally cannot host a settlement (found live in
# T-1074/T-1075 regen: GJ 702B's 12 bodies are ALL population=0, and the old
# alphabetical-only tiebreak picked "GJ702B-belt" over 6 sibling planets,
# stranding prometheus-labs/kovalev-freight's CityTenant resolution with a
# city-less body). planet/moon/gas_giant can plausibly carry cities or
# stations; asteroid_belt/oort_cloud are geometry, never named settlements.
# body_type rank for the most_populated_body_in_system LAST-RESORT tiebreak —
# lower sorts first. This is NOT "belts can't host settlements" (they can and
# DO in this world: GJ845-belt hosts Orkney Ceramics, GJ268-belt hosts Jeju
# Lattice, GJ222A-belt hosts two Standalone-HQ settlements — PR #177 review
# H2 corrected the earlier false premise). It only engages when a system's
# bodies tie on BOTH population and city-presence — i.e. a fully cityless,
# fully unpopulated system, where NOTHING in the data distinguishes the
# siblings (found live: GJ 702B — 12 bodies, all population=0, zero city rows
# anywhere). There, a bare body_id sort lands on the belt purely because '-'
# (the naming convention for belts/oort clouds, e.g. "GJ702B-belt") sorts
# before letters; among equally blank siblings a planet is the saner default
# host for a future settlement than orbital rubble.
_BODY_TYPE_SETTLEMENT_PREFERENCE: dict[str, int] = {
"planet": 0,
"moon": 1,
@@ -184,23 +185,39 @@ _BODY_TYPE_SETTLEMENT_PREFERENCE: dict[str, int] = {
}
def most_populated_body_in_system(conn: sqlite3.Connection, system_id: str) -> str | None:
"""Return the most-populated body_id in `system_id`, or None if the system
has no bodies.
def most_populated_body_in_system(
conn: sqlite3.Connection, system_id: str, city_bearing_bodies: set[str]
) -> str | None:
"""Return the body_id in `system_id` that should host a corp HQ, or None
if the system has no bodies.
D-242, T-1074: this is the heuristic the retired
D-242, T-1074: descended from the heuristic the retired
`populate_atlas_city_names_corps` (D-207, #909) used to attach a corp-HQ
row to a body — lifted out here (rather than duplicated) so
`populate_corp_specialization` (economy_import/corporations.py) can reuse
it to backfill `corporations.headquarters_body` for Standalone HQs.
row to a body; used by `populate_corp_specialization`
(economy_import/corporations.py) to resolve `corporations.headquarters_body`
on every run (PR #177 H1 — recompute, never treat prior DB output as
authored).
Deterministic tiebreak, in order (D-010 #4): (1) population descending —
real population always wins regardless of type; (2) body_type preference
(planet < moon < gas_giant < asteroid_belt/oort_cloud — see
`_BODY_TYPE_SETTLEMENT_PREFERENCE`) — only engages among population TIES,
so it never overrides an authored population signal, it only picks a
saner body among zero-population siblings; (3) body_id ascending, the
final tiebreak when population AND type both tie.
Deterministic tiebreak, in order (D-010 #4; PR #177 H2):
(1) population descending — an authored population signal always wins;
(2) city-presence — a body in `city_bearing_bodies` (has atlas_city_names
rows) beats a cityless sibling. This is what keeps settlements where
they already are: a belt that hosts settlements stays the system's HQ
anchor (GJ845-belt/GJ268-belt tenancies), while a genuinely cityless
system falls through to (3);
(3) body_type rank (planet < moon < gas_giant < belt/oort — see
`_BODY_TYPE_SETTLEMENT_PREFERENCE`) — last resort among fully blank
siblings (GJ 702B → planet GJ702Bb);
(4) body_id ascending — final deterministic tiebreak.
`city_bearing_bodies` is the caller's snapshot of `SELECT DISTINCT body_id
FROM atlas_city_names` taken BEFORE this run's pool rebuild (step 7b runs
before step 12's clear) — i.e. the PREVIOUS run's settled state. That is a
deliberate hysteresis: established settlements anchor future HQ placement
(settlement continuity across regens), and the fixpoint is stable — a
run's output presence distribution reproduces itself, so consecutive
regens compute identical values. See populate_corp_specialization's
docstring for the one-run-lag / fresh-DB caveats.
"""
rows = conn.execute(
"SELECT body_id, body_type, COALESCE(population, 0) FROM bodies WHERE system_id = ?",
@@ -211,7 +228,8 @@ def most_populated_body_in_system(conn: sqlite3.Connection, system_id: str) -> s
rows.sort(
key=lambda r: (
-r[2], # population descending
_BODY_TYPE_SETTLEMENT_PREFERENCE.get(r[1], 3), # body_type preference
0 if r[0] in city_bearing_bodies else 1, # city-bearing body wins
_BODY_TYPE_SETTLEMENT_PREFERENCE.get(r[1], 3), # body_type rank
r[0], # body_id ascending
)
)
+134 -65
View File
@@ -57,9 +57,16 @@ def load_wiki_corps() -> list[dict]:
system_id = m.group(1) if m else None
# D-242, T-1074: authored corp_specialization frontmatter key — the
# HQ-placement key (reuses the D-237 specialization_vocabulary
# id-space, extended from 27 to 30 values for corp coverage; see
# id-space, extended from 27 to 31 values for corp coverage; see
# wiki/economics/corp_hq_placement.toml). Optional — None until a
# corp's page is authored with the key.
#
# headquarters_body (PR #177 H1): optional authored body_id override.
# When present it pins corporations.headquarters_body directly (hard-
# validated at import: the body must exist AND sit in the corp's
# headquarters system); absent, the deterministic heuristic derives it
# on every run. Authored overrides live HERE (source), never in the DB
# — the asset-pipeline golden rule. No page authors it yet.
corps.append({
"corp_id": fm["slug"],
"proper_name": fm["title"],
@@ -67,6 +74,7 @@ def load_wiki_corps() -> list[dict]:
"tags": fm.get("tags", []),
"scope": fm.get("scope", ""),
"corp_specialization": fm.get("corp_specialization") or None,
"headquarters_body": fm.get("headquarters_body") or None,
})
return corps
@@ -191,42 +199,65 @@ def _resolve_hq_location(
def populate_corp_specialization(
conn: sqlite3.Connection, wiki_corps: list[dict], dry_run: bool
) -> dict:
"""Phase A of the D-242 corp-HQ model (T-1074).
"""Phase A of the D-242 corp-HQ model (T-1074; reworked per PR #177 H1/H2/T2).
For every wiki corp: validate its authored `corp_specialization` (frontmatter
key, reuses the D-237 `specialization_vocabulary` id-space — see
wiki/economics/corp_hq_placement.toml header) against the vocabulary table
(must run AFTER `import_system_specialization`, which populates
RESET + DERIVE, every run. The three importer-owned columns on
`corporations` — `corp_specialization`, `hq_placement`,
`headquarters_body` — are NULLed for ALL corps first, then re-derived from
source. Nothing in those columns is ever treated as authored: `corporations`
is append-only (never cleared), so any keep-if-set guard would freeze the
FIRST run's output forever and silently promote DB state to source — the
exact inversion of the asset-pipeline golden rule (PR #177 H1: the old
fill-only-when-NULL backfill made the heuristic a permanent one-shot; its
own earlier output blocked every later fix from applying). The reset also
guarantees a frontmatter key REMOVED from a corp's page reverts that corp
to NULL instead of leaving a stale baked value (T2 — mirrors the
settlement_class reset in atlas.populate_settlement_population_class).
The fourth derived column, `headquarters_city_id`, follows the same
discipline at steps 12/13 (NULLed in populate_atlas_city_names, re-derived
in populate_standalone_hq_settlements).
For every wiki corp: validate its authored `corp_specialization`
(frontmatter key, reuses the D-237 `specialization_vocabulary` id-space —
see wiki/economics/corp_hq_placement.toml header) against the vocabulary
table (must run AFTER `import_system_specialization`, which populates
`specialization_vocabulary`); resolve `hq_placement` ('CityTenant' |
'Standalone') from `corp_hq_placement.toml`; and backfill
`corporations.headquarters_body` via the most-populated-body-in-system
heuristic (`atlas.most_populated_body_in_system` — lifted from the retired
`populate_atlas_city_names_corps`, D-207/#909) for any corp that doesn't
already have one authored.
'Standalone') from `corp_hq_placement.toml`; and resolve
`headquarters_body`: an authored `headquarters_body:` frontmatter override
wins (hard-validated: the body must exist and sit in the corp's
headquarters system; no page authors one yet), else the deterministic
heuristic (`atlas.most_populated_body_in_system`) derives it with the
PR #177 H2 tiebreak — population DESC, then city-bearing body (has
atlas_city_names rows), then body-type rank, then body_id.
**City-presence timing (H2):** the presence set is read here, at step 7b —
BEFORE step 12 clears/rebuilds the pool — so it reflects the PREVIOUS
run's settled state (pool cities + Standalone-HQ settlements). Deliberate
hysteresis: established settlements anchor HQ placement across regens
(GJ845-belt keeps Orkney Ceramics + calluna-wellness's tenancy), and the
fixpoint is stable — each run's output reproduces the presence
distribution it read, so consecutive regens derive identical values. Two
documented consequences: a brand-new Standalone corp's settlement only
influences OTHER corps' placement from the run after it first lands
(one-run lag, converges immediately); and a hypothetical from-empty DB
rebuild would see an inert presence tier on its first run (regen-db always
evolves the committed snapshot, so this does not arise in practice — if
the table is empty a loud warning prints rather than silently degrading).
Must run AFTER `sync_corporations` (corp rows must exist) and BEFORE
`import_corp_presence` (which reads `headquarters_body` back off
`corporations` to resolve corp_presence locations, #909's existing
resolution order) — so `import_corp_presence` picks up the backfilled
value for free, with no change to its own logic.
`corporations`) and BEFORE step 12 (see presence timing above).
Does NOT touch `atlas_city_names` — this is Phase A. Phase B
(`populate_standalone_hq_settlements`) runs after `populate_atlas_city_names`
and emits Standalone-HQ settlement rows + resolves CityTenant
`headquarters_city_id` links, since both need the city pool to exist.
HARD error (raises ImportAborted) on: a `corp_specialization` value not in
the vocabulary; a vocabulary id with no placement stanza; an invalid
placement value; or an authored `headquarters_body` override that names a
missing body or a body outside the corp's headquarters system. A corp with
NO authored `corp_specialization` is a soft warning (stays NULL across the
board — the 10 legacy DB-only corps have no wiki page); all 155 wiki corps
are authored as of T-1074.
HARD error (raises ImportAborted) if any corp's authored
`corp_specialization` is non-empty but not a valid vocabulary id, OR if
any corp's specialization does not resolve to a placement value in
`corp_hq_placement.toml` (a vocabulary/placement-map drift — every
vocabulary id must have a placement stanza). A corp with NO authored
`corp_specialization` is a soft warning (defers headquarters_body
backfill and Phase B placement for that corp) — not fatal, so an
un-authored future corp doesn't block regen-db; but ALL 165 corps in the
live wiki are authored as of T-1074, so this path is not expected to fire
on the shipped data.
Returns a coverage dict: {"total", "specialized", "placed", "backfilled_hq"}.
Returns a coverage dict:
{"total", "specialized", "hq_resolved", "hq_overridden"}.
"""
with open(CORP_HQ_PLACEMENT_TOML, "rb") as f:
placement_map: dict[str, dict] = tomllib.load(f)
@@ -234,12 +265,26 @@ def populate_corp_specialization(
valid_spec_ids = {
r[0] for r in conn.execute("SELECT specialization_id FROM specialization_vocabulary").fetchall()
}
valid_body_system = {
r[0]: r[1]
for r in conn.execute("SELECT body_id, system_id FROM bodies").fetchall()
}
# H2 presence set — previous run's settled state (see docstring timing note).
city_bearing_bodies = {
r[0] for r in conn.execute("SELECT DISTINCT body_id FROM atlas_city_names").fetchall()
}
if not city_bearing_bodies:
print(
" warning: atlas_city_names is empty — the city-presence "
"tiebreak tier is inert this run (placements fall through to "
"body-type rank; they converge on the next run)"
)
errors: list[str] = []
unauthored: list[str] = []
# (corp_id, corp_specialization, hq_placement) rows to write.
spec_rows: list[tuple[str, str, str]] = []
# (corp_id, corp_specialization, hq_placement, authored_hq_body_or_None).
spec_rows: list[tuple[str, str, str, str | None]] = []
for corp in wiki_corps:
corp_id = corp["corp_id"]
@@ -267,7 +312,25 @@ def populate_corp_specialization(
f"(expected 'CityTenant' or 'Standalone')"
)
continue
spec_rows.append((corp_id, spec, placement))
# Authored headquarters_body override (H1) — hard-validate: typos in
# freshly-authored data must fail loudly, not silently fall back.
authored_body = corp.get("headquarters_body")
if authored_body is not None:
if authored_body not in valid_body_system:
errors.append(
f"{corp_id}: authored headquarters_body '{authored_body}' is not a "
f"known bodies.body_id"
)
continue
corp_system = corp.get("system_id")
if corp_system and valid_body_system[authored_body] != corp_system:
errors.append(
f"{corp_id}: authored headquarters_body '{authored_body}' sits in "
f"system '{valid_body_system[authored_body]}', not the corp's "
f"headquarters system '{corp_system}'"
)
continue
spec_rows.append((corp_id, spec, placement, authored_body))
if errors:
print(f" CORP SPECIALIZATION ERRORS ({len(errors)}):")
@@ -278,45 +341,51 @@ def populate_corp_specialization(
if unauthored:
print(
f" warning: {len(unauthored)} corp(s) have no authored "
f"corp_specialization (headquarters_body/placement skipped): {unauthored[:5]}"
f"corp_specialization (stay NULL; no HQ placement): {unauthored[:5]}"
)
n_backfilled = 0
if not dry_run:
for corp_id, spec, placement in spec_rows:
conn.execute(
"UPDATE corporations SET corp_specialization = ?, hq_placement = ? "
"WHERE corp_id = ?",
(spec, placement, corp_id),
)
# Backfill headquarters_body — only for rows that don't already have one
# (never clobber an authored value; today none are authored, but the
# column exists precisely so a future ticket CAN author one directly).
for corp_id, _spec, _placement in spec_rows:
row = conn.execute(
"SELECT headquarters_system, headquarters_body FROM corporations WHERE corp_id = ?",
# Pre-compute the resolution so dry-run reports the same numbers a real
# run writes (write is gated on dry_run; the derivation is not).
n_resolved = 0
n_overridden = 0
resolved_rows: list[tuple[str, str, str, str | None]] = []
for corp_id, spec, placement, authored_body in spec_rows:
if authored_body is not None:
resolved = authored_body
n_overridden += 1
else:
hq_system_row = conn.execute(
"SELECT headquarters_system FROM corporations WHERE corp_id = ?",
(corp_id,),
).fetchone()
if row is None:
continue
hq_system, hq_body = row
if hq_body:
continue
if not hq_system:
continue
resolved = most_populated_body_in_system(conn, hq_system)
if resolved:
conn.execute(
"UPDATE corporations SET headquarters_body = ? WHERE corp_id = ?",
(resolved, corp_id),
)
n_backfilled += 1
hq_system = hq_system_row[0] if hq_system_row else None
resolved = (
most_populated_body_in_system(conn, hq_system, city_bearing_bodies)
if hq_system
else None
)
if resolved is not None:
n_resolved += 1
resolved_rows.append((corp_id, spec, placement, resolved))
if not dry_run:
# T2/H1 reset: importer-owned columns are never source — wipe, then derive.
conn.execute(
"UPDATE corporations SET corp_specialization = NULL, "
"hq_placement = NULL, headquarters_body = NULL"
)
for corp_id, spec, placement, resolved in resolved_rows:
conn.execute(
"UPDATE corporations SET corp_specialization = ?, hq_placement = ?, "
"headquarters_body = ? WHERE corp_id = ?",
(spec, placement, resolved, corp_id),
)
return {
"total": len(wiki_corps),
"specialized": len(spec_rows),
"placed": len(spec_rows),
"backfilled_hq": n_backfilled,
"hq_resolved": n_resolved,
"hq_overridden": n_overridden,
}
@@ -403,7 +472,7 @@ def populate_standalone_hq_settlements(conn: sqlite3.Connection, dry_run: bool)
settlement named after the corp (`proper_name`), on `headquarters_body`,
with `economic_role` from `corp_hq_placement.toml`'s
`standalone_economic_role` (the existing 10-value D-195/D-197 vocabulary
— NOT the 30-value specialization_vocabulary, which `match_cities`/
— NOT the 31-value specialization_vocabulary, which `match_cities`/
`CompatibilityMatrix` do not index). `population = 0` — T-1075 bakes the
per-settlement population spread over the corrected pool (this row
included) in a later step; this function only creates the row.
+4 -1
View File
@@ -14,6 +14,7 @@ from generator_sources import (
ARCHITECTURE_ZONE_BIAS_TOML,
COLOR_REGISTER_BANDS_TOML,
CORP_HQ_PLACEMENT_TOML,
CORPORATIONS_DIR,
GENERATE_BRANDS_WRAPPER,
OBJECT_TAG_VOCABULARY_TOML,
REPO_ROOT,
@@ -52,7 +53,9 @@ COMMODITIES_TOML: Path = REPO_ROOT / "wiki" / "economics" / "commodities.toml"
CHAINS_TOML: Path = REPO_ROOT / "wiki" / "economics" / "production_chains.toml"
CURRENCY_ZONES_TOML: Path = REPO_ROOT / "wiki" / "economics" / "currency_zones.toml"
SCHEMA_SQL: Path = REPO_ROOT / "server" / "data" / "systems-schema.sql"
CORPORATIONS_DIR: Path = REPO_ROOT / "wiki" / "corporations"
# CORPORATIONS_DIR moved to generator_sources.py (T1, PR #177): the corp pages
# are now part of the stamped source set, and stamped paths are defined once
# in the registry — re-exported via the import block above.
WIKI_STAR_SYSTEMS: Path = REPO_ROOT / "wiki" / "star-systems"
BRANDS_TOML: Path = REPO_ROOT / "wiki" / "economics" / "corporations" / "brands.toml"
GENERATED_BRANDS_TOML: Path = (
+9 -3
View File
@@ -183,13 +183,19 @@ def main() -> None:
print(f" {n_db_corps} corporations in DB ({len(wiki_corps)} from wiki)")
# 7b. Corp specialization + HQ placement (D-242, T-1074) — Phase A.
# Must run after corp sync (rows must exist) and before corp_presence
# (which reads headquarters_body back off corporations).
# Reset + derive on every run (PR #177 H1/T2): the importer-owned
# corporations columns are NULLed and re-derived from source, never
# kept from a prior run. Must run after corp sync (rows must exist),
# before corp_presence (which reads headquarters_body back off
# corporations), and before step 12 (the H2 city-presence tiebreak
# reads atlas_city_names BEFORE this run's clear/rebuild — the
# previous run's settled state, by design).
print(" [7b/10] Importing corp specialization + HQ placement (D-242)...")
corp_spec = corporations.populate_corp_specialization(conn, wiki_corps, args.dry_run)
print(
f" {corp_spec['specialized']}/{corp_spec['total']} corps specialized, "
f"{corp_spec['backfilled_hq']} headquarters_body backfilled"
f"{corp_spec['hq_resolved']} headquarters_body resolved (recomputed every run; "
f"{corp_spec['hq_overridden']} authored overrides)"
)
# 8. Corp presence from wiki headquarters data
+33 -1
View File
@@ -79,6 +79,14 @@ SYSTEM_SPECIALIZATION_TOML: Path = (
CORP_HQ_PLACEMENT_TOML: Path = (
REPO_ROOT / "wiki" / "economics" / "corp_hq_placement.toml"
)
# The corp wiki pages themselves (PR #177 review T1): their frontmatter
# (corp_specialization, headquarters, optional headquarters_body) now shapes
# the DB — specialization drives hq_placement, headquarters_body, and the
# Standalone-HQ settlement rows in atlas_city_names — so a page edit without a
# regen must trip the stamp check exactly like a TOML edit does. Defined here
# (not in economy_import/paths.py) per the stamped-paths-live-here convention;
# paths.py re-exports it.
CORPORATIONS_DIR: Path = REPO_ROOT / "wiki" / "corporations"
# D-242 population/settlement_class bake (T-1075): the small authored
# NameLocked hero-city override list.
SETTLEMENT_NAME_LOCKED_TOML: Path = (
@@ -127,14 +135,38 @@ def _economy_import_modules() -> tuple[Path, ...]:
return modules
def _corporation_pages() -> tuple[Path, ...]:
"""All corp wiki pages the importer reads, collected by glob (T1, PR #177).
`load_wiki_corps` (economy_import/corporations.py) reads every
wiki/corporations/*.md EXCEPT index.md — the frontmatter feeds
corp_specialization/hq_placement/headquarters_body and the Standalone-HQ
settlement rows, so the read-set is stamped with the same glob-not-list +
fail-closed discipline as `_economy_import_modules`. index.md is excluded
because the importer skips it by name: stamping it would flag a false
stale on edits that cannot change DB output.
"""
pages = tuple(
sorted(p for p in CORPORATIONS_DIR.glob("*.md") if p.name != "index.md")
)
if not pages:
raise RuntimeError(
f"corporation pages not found at {CORPORATIONS_DIR}"
"the import_economics stamp source set would be incomplete"
)
return pages
# Canonical source set for import_economics' meta stamp. Covers the Python
# entrypoint and its module package, the Rust binary it invokes (generate_brands
# main.rs + names.rs + surname corpus + wrapper script), the shared schema
# version constant, the authored data TOMLs, and this registry itself.
# version constant, the authored data TOMLs, the corp wiki pages whose
# frontmatter the importer bakes (T1, PR #177), and this registry itself.
IMPORT_ECONOMICS_SOURCES: tuple[Path, ...] = (
Path(__file__).resolve(), # registry changes must stale the stamp
IMPORT_ECONOMICS_ENTRYPOINT,
*_economy_import_modules(),
*_corporation_pages(),
GENERATE_BRANDS_RS,
GENERATE_BRANDS_NAMES_RS,
GENERATE_BRANDS_SURNAMES_RS,
+1 -1
View File
@@ -9,7 +9,7 @@ updated: 2026-04-05
scope: reach-wide
faction_type: economic
headquarters: Kallast (GJ 144)
corp_specialization: professional_services
corp_specialization: licensed_clinical_services
tags: [medical_re-embodiment, medical_goods, longevity, tractus, assembly, ran]
decision_refs: [D-175, D-174]
cross_refs: []
+13 -9
View File
@@ -25,11 +25,12 @@
# 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
# system-authored values plus 4 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
# professional_services, licensed_clinical_services — real gaps the T-1074
# corp categorization pass found: ~10% of corps do logistics/hospitality/
# consulting/licensed-clinical work no original value covered; the fourth
# landed via PR #177 M1). 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
@@ -178,11 +179,11 @@ hq_placement = "CityTenant"
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).
# Services extension (4, all NonPhysical) — added alongside the vocabulary
# extension in specialization_vocabulary.toml (D-242/T-1074; the fourth via
# PR #177 M1). Same rule as the rest of Services: NonPhysical -> CityTenant
# (an office/depot/clinic, not a factory — this is exactly what made these
# corps hard to place in the original 27).
# -------------------------------------------------------------------------
[trade_distribution]
hq_placement = "CityTenant"
@@ -192,3 +193,6 @@ hq_placement = "CityTenant"
[professional_services]
hq_placement = "CityTenant"
[licensed_clinical_services]
hq_placement = "CityTenant"
+14 -8
View File
@@ -28,14 +28,20 @@
# 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).
# markers.json pool. Every entry below is verified against real, repeated
# `make regen-db` runs — see the note on each stanza for its actual source
# row. Note (PR #177 H1/H3): corporations.headquarters_body is RECOMPUTED
# from source on every run now, so the three corp-named Standalone-HQ pins
# below (Cygni Combines, The Gate Corporation, Mastroianni Vehicle Group)
# hold because their HQ bodies carry real authored population — the
# population-first tiebreak re-derives the same body every run (verified:
# two consecutive regens, all 8 pins applied, zero placement drift) — not
# because a stale first-run value is being kept. 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 — T-1115
# authors the missing names; the bake 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
+23 -14
View File
@@ -15,8 +15,8 @@
# 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
# The 4-value "Services extension" block near the end of the Services
# section was added for corp_specialization coverage (corp-only gaps 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.
#
@@ -266,21 +266,23 @@ 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.
# Services extension (4) — added D-242/T-1074 for corp_specialization reuse
# (first three), extended with licensed_clinical_services per PR #177 M1.
#
# The categorization pass that backfilled corp_specialization for all 165
# corporations (T-1074) found 16/155 wiki corps (~10%) whose actual business
# corporations (T-1074) found ~10% of wiki corps 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).
# properties, general professional advisory, or licensed multi-site
# clinical care — had no honest fit among the original 27 system-authored
# values above. Per D-242's amendment ("prefer reusing/extending the
# existing vocabulary over inventing a parallel taxonomy"), these values
# extend the shared vocabulary rather than spawn a corp-only one. All four
# 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"
@@ -302,3 +304,10 @@ 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)."
[licensed_clinical_services]
commodity_id = "medical_services"
production_ubiquity_override = "concentrated"
bulk_class_projected = "NonPhysical"
production_ubiquity_projected = "Specialist"
description = "Licensed multi-site clinical network — Assembly-certified medical/re-embodiment operators, one of several (Somatic Futures, 40+ clinics); the non-monopoly counterpart to longevity_monopoly (Prometheus, THE sole facility)."