Commit Graph
2264 Commits
Author SHA1 Message Date
jpmschweitzerandClaude Opus 4.8 88d9be070e feat(meta): seed 1013 tickets into pql changelog (pql migration phase 2)
One-way ticket data migration from the legacy settledreach.db into pql's
git-tracked changelog. Establishes the T-N == #N id bijection (old #440 -> T-440)
so thousands of #NNN git-commit references stay a trivial mapping.

Migrated (verified by full `pql plan rebuild` from changelog):
  - 1013 tickets  (status + type distributions match source exactly)
  - 481  dependencies
  - 16   history rows (deterministic content hash so ON CONFLICT(hash) dedups)
  - 90   labels = 48 source + 42 milestone-derived
           (active "Phase 4" milestone -> phase:4; "Process Rewire" ->
            milestone:process-rewire; milestone_deps was empty/vestigial)

Transforms: ids T-prefixed, sprint_id dropped (legacy/archival), decision_ref
and the one 'server,client' comma-team (#575) kept verbatim, deleted_at NULL,
canonical_version 1. Seeded rows carry hash=NULL on tickets/deps/labels — proven
safe: PK-based ON CONFLICT, updated_at drives LWW, and replay is idempotent.

Key finding that dissolves pql-requirements item #1 ("Critical"): seeding via
direct-INSERT does NOT require explicit ticket ids from `ticket new`. The id
counter derives from max(id), so after seeding T-1..T-1021 the next native
`pql ticket new` mints T-1022 — no recycling, no collision. The blocker only
applied to the `ticket new --id` path we never use for bulk seeding.

Scaffolding: .pql/changelog/<table>/0000-schema.sql (canonical pql 1.6.2 schema,
byte-identical across the 4 table dirs) + .gitattributes union-merge driver so
changelog SQL never produces binary-style merge conflicts. Seed is reproducible
via tooling/pql-migrate/seed_tickets.py (read-only on the source, idempotent).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 12:32:50 +02:00
jpmschweitzerandClaude Opus 4.8 05d7bffd6f docs(meta): repoint decisions/ paths to governance/ tree (pql migration)
Phase 1 follow-up: update the active instruction layer (CLAUDE.md, project
structure rule, DECISIONS.md redirect, agent personalities, skill docs) to
reference governance/{decisions,questions,rejected}/<domain>.md instead of the
retired flat decisions/*.md layout.

Path references only — command-surface references (tooling/db/decision*,
decisions-sync, Makefile targets, clerk) are repointed to the pql CLI in the
Phase 4 consumer cutover. Historical archives (docs/sprints, docs/discussions,
docs/workshops) keep their point-in-time decisions/ paths; the separate
whatsinagame/ template distribution is untouched. Agent-memory is gitignored
and out of scope.

The agent/skill repath was applied by tooling/pql-migrate/repath_references.py
(ordered, meaning-preserving replacements; bare-dir rule uses a negative
lookbehind so it can't corrupt a freshly-created governance/decisions/ path),
committed for provenance. CLAUDE.md, project-structure.md, and DECISIONS.md
were hand-edited (structural tree/table changes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 12:18:49 +02:00
jpmschweitzerandClaude Opus 4.8 f936e503da docs(decisions): restructure into pql governance DQR tree
Phase 1 of the pql migration. Moves the flat decisions/*.md layout into
governance/{decisions,questions,rejected}/<domain>.md — the tree pql's
`decisions sync` parses natively (record type from subdir, domain from
filename stem). Proven against pql 1.6.2: sync reports 357 records
(237 D / 108 Q / 12 R), 1057 refs, broken: 0; validate ok.

- 6 D-domain files -> governance/decisions/ (git renames)
- 5 questions-<domain>.md -> governance/questions/<domain>.md (prefix dropped)
- rejected.md split by domain -> governance/rejected/{architecture(R-001..010),
  economics(R-011),perception(R-012)}.md
- decisions/README.md + questions.md index folded into governance/README.md;
  pql's `decisions sync` now auto-maintains the record index appended below
  the hand-written domain guidance (no more manual ID-list table upkeep).
- .pql/config.yaml: canonical vault config (tracked, not ignored).

Link rewrites are token-preserving: only the relative `foo.md` path portion
changes (e.g. `rejected.md#r-011` -> `../rejected/economics.md#r-011`); every
`[D-NNN]` bracket text and `#anchor` stays byte-identical, so pql's reference
extraction is unaffected. The one-shot transform is committed at
tooling/pql-migrate/restructure_decisions.py for provenance.

Codebase path references to decisions/ (CLAUDE.md, rules, skills, docs) are
updated in a follow-up commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 12:12:45 +02:00
jpmschweitzerandClaude Opus 4.8 5bb551b2e0 docs(decisions): resolve D-035 and R-011 id collisions (pql migration prep)
Phase 0 of the pql migration: the decision markdown had two genuine
canonical-ID integrity bugs that pql decisions sync silently LWW-collapses.

- D-035 was defined twice: a content.md tag-taxonomy record and a
  perception.md shadowcasting/LOS record, both dated the same day. The
  tag-taxonomy record keeps D-035; the shadowcasting record is renumbered
  to D-238 (freshly claimed) with a Renumbered note preserving the history.
  Inbound refs repointed in architecture.md (x4), questions-architecture.md,
  and the README perception index row.
- R-011 (single currency for Phase 2) was defined three times: once
  canonically in rejected.md and twice identically in economics.md. The two
  economics.md copies are removed; rejected.md absorbs the Proposal/Raised-by
  lines and gains a dedupe note. The economics.md dissent link now points at
  rejected.md and the footer count drops the rejected-alternative clause.

Verified: tooling/db/decision check-dupes reports 0 duplicates;
pql decisions validate returns ok.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 11:57:29 +02:00
jpmschweitzerandClaude Opus 4.8 caf8f5b754 chore(meta): narrow .pql gitignore to track changelog (pql migration phase 0)
The blanket `.pql/` ignore would have hidden `.pql/changelog/` — pql's durable,
git-versioned planning state — making the whole migration's output invisible to
git. Narrow to ignore only the rebuildable DBs (index/pql .db + WAL/SHM) and
track config.yaml + changelog/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 11:40:49 +02:00
jpmschweitzerandClaude Opus 4.8 07e5ba005d docs(meta): pql migration plan + requirements for the pql team
Deep analysis of migrating decisions + ticketing from the SQLite CLI to pql:
- pql-migration.md: phased branch-only plan (decisions→DQR tree, ticket changelog
  seed with T-N≡#N, #N→T-N find-replace, big-bang consumer cutover, docs/workshops/
  wiki fold-in, SQLite retirement) + verification gate + benefits.
- pql-requirements.md: 9 surfaced gaps for the pql team (critical: seeded ticket
  ids + counter-advance; high: dup-id detection, core.hooksPath awareness, changelog
  seed-format docs).

Key finding: milestones are vestigial (milestone_deps empty, 42/1013 tickets
linked) → mapped to a label, dropped as an entity.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 11:37:52 +02:00
jpmschweitzerandClaude Opus 4.8 f880635b24 fix(simulation): decorrelate per-footprint tag fields + test gaps (#957)
PR #153 review (Hoshe H1): zone_type/era/floors/flavor all shared one fp_chain
and each took splitmix64(seed) of the same value, correlating the fields (era
tracking floor height across a city). Derive a distinct sub-chain per field so
they draw independent entropy. Block_tags aren't serialized, so no fixture
change; behavior stays deterministic.

Also (review): n=1 corridor test (empty, no panic), Frontier-density waterfront
test, and a comment on the reserved `_morphology` param (Tyre).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 10:55:07 +02:00
jpmschweitzerandClaude Opus 4.8 0fe3b15a2e docs(architecture): record D-234 street/footprint/waterfront implementation (#957)
Note the #957 implementation on D-234: typed AccessPoint/CorridorSpine/
ChunkLayout, morphology-gated arterials + per-block lattice, BSP footprint
subdivision with D-233 coverage, and the waterfront rule. Flag the remaining
cross-layer dependency: the waterfront rule's real input (per-settlement Coastal
orientation) needs the Layer-3 placement -> Layer-4 GenerateSkeleton dispatch,
since city_context_reader still stubs founding_orientation to Cardinal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 10:22:23 +02:00
jpmschweitzerandClaude Opus 4.8 b73b4e6d92 feat(simulation): Layer-1 water bearing + D-234 waterfront rule (#957)
Close the last D-234 piece — terrain water-direction extraction wired through
to founding orientation and the quarter waterfront rule:

- Layer 1: TerrainAnalysis::water_bearing — 8-octant integer bearing toward the
  nearest water from the water_dist gradient (D-010, no atan2). Stored on
  GeographicAttractor.water_bearing (360 = none).
- #956 founding orientation: coastal/river settlements now get a real
  water-facing bearing (the anchoring attractor's), replacing the 0 stub.
- #957 waterfront rule (D-234b): the water-facing quarter edge (from the
  settlement's Coastal founding orientation) drops its block setback to 0 so
  buildings present flush to the quay (dock-orthogonal). Typed Edge + coastal_edge
  + per-block gating.

Golden + atlas_response fixture rebaked (additive water_bearing field only).
8 new tests. All integer-deterministic (D-010).

Pending: the waterfront rule reads context.founding_orientation, which
city_context_reader still stubs to Cardinal — real per-settlement orientation
reaches quarter generation once the Layer-3 placement -> Layer-4 GenerateSkeleton
dispatch is wired (the remaining cross-layer integration).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 10:22:14 +02:00
jpmschweitzerandClaude Opus 4.8 4c6fe596d1 feat(simulation): quarter street network — typed corridors/access/lattice (#957)
Complete the Layer-4 quarter geometry with the street layer (D-234), replacing
the ChunkLayout/CorridorSpine/AccessPoint String stubs with real types:
- AccessPoint{position,kind} + AccessKind (QuarterEdge/ReservationGate/
  BlockJunction): the quarter's road nodes (from road_entry_directions octants +
  reservation gates), the node set the D-097 audit reads.
- CorridorSpine{from,to,path}: arterial trunk edges, ±45°-snapped polylines
  (D-096 cap). Topology gated by morphology (D-234a): Ribbon (fjord/canyon/
  mountain-pass), HubSpoke (delta/island/enclosed water), Mesh/Prim-MST
  (plains/meander/coastal). A spanning tree over the access nodes.
- ChunkLayout{spacing,offset,rotation_steps}: per-block local ±45° lattice,
  spacing from density, offset/rotation modulated by the D-096 Grid/Organic mode.

Wired into generate_quarter_skeleton (access_points + corridors + per-block
chunk_layout). All integer-deterministic (D-010). 7 new tests.

Remaining D-234 piece: the per-edge waterfront pier/quay rule needs Layer-1
terrain water-adjacency threaded to the skeleton (cross-layer plumbing).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 23:29:21 +02:00
jpmschweitzerandClaude Opus 4.8 728a27e1b4 feat(simulation): quarter footprint subdivision + block-tag assembly (#957)
Build the per-quarter building geometry + tags (absorbs #976's footprint
subdivision into #957 — one coherent walkable-quarter deliverable):

- subdivide_block_footprints: deterministic BSP of each 128x128-tile block into
  variable axis-aligned building plots (D-229 fast-path). Lot size + setback
  scale with D-220 density (Frontier -> few big lots/wide gaps; Compressed ->
  many small lots/shared walls); the D-233 BulkClass roofed-coverage fraction
  decides building-vs-interstitial. morphology is wired in for the D-234
  waterfront/street layer (needs Layer-1 terrain water-adjacency threaded up).
- assign_all_block_tags: subdivides + tags every non-reserved block's footprints
  via the #957 derivation helpers, populating QuarterWorldState.block_tags in the
  GenerateSkeleton plan-phase (D-230). Reserved blocks get no standard fill.
- Remove the write-only BlockSkeleton.era String stub - construction era now
  lives per-footprint on BuildingPropertyTag.era (typed ConstructionEra).

All integer-deterministic (D-010). 5 new tests. Doors stay Vec::new() (#979).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 23:08:19 +02:00
jpmschweitzerandClaude Opus 4.8 aaa94152d0 docs(architecture): amend D-229 with the zone-type selection table (#957)
Record the (ZoningType × economic_role × setting) → ZoneTypeId selection table
for the planetary cascade: base candidate slices per ZoningType refined by
economic_role, a setting tweaker (Maritime/Agricultural/Wilderness — planetary
variants only), and the gap-fill rules (entry-class U-curve at the D-217 Broken
band <2300bps; era per founding_age+prosperity+seed; FloorExtent from D-220
density midpoints; doors deferred to #979). Station-only zone ids are excluded —
they belong to a separate cascade (Q-109).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 22:15:56 +02:00
jpmschweitzerandClaude Opus 4.8 1909e15edb feat(simulation): building-property-tag derivation helpers (#957)
The pure per-footprint tag derivations for Layer-4 quarter generation (D-229),
ahead of the footprint-subdivision + assembly step:
- zone_type_for: the authored (ZoningType × economic_role × setting) → ZoneTypeId
  selection (D-229 amendment) — planetary variants only, setting as a tweaker,
  station-only ids never selected (Q-109); deterministic seed-pick, no empty slice.
- building_entry_class: zone × layout_mode × prosperity, with the D-217 Broken-band
  U-curve degrade (Commercial→BreachOnly) and the Grid/Organic credential fork.
- construction_era: founding_age + prosperity + seed (D-229); old+Broken → Derelict.
- floor_extent: D-220 density-class floor ranges from density_pct + seed-jitter.
- initial_condition: D-217 frozen-amber condition from prosperity + era cause.

All integer-deterministic (D-010). 9 unit tests incl. the no-station-ids invariant.
Footprint subdivision + assign_building_tags assembly + block_tags wiring follow
(needs the footprint-packing model decided).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 22:15:46 +02:00
jpmschweitzerandClaude Opus 4.8 43888f8a99 docs(architecture): file Q-109 — cascade generation-source dispatch
Surfaced during #957 zone-selection authoring: a body's setting/mode should
dispatch the cascade entry to planetary / station / mod-DLC-forked /
save-only(player_base) sources, distinct from setting's in-path tweaker role.
Captured as an open question so #957 stays scoped to the planetary path; this is
why #957's zone table excludes station-only zone ids.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 17:19:44 +02:00
jpmschweitzerandClaude Opus 4.8 21544cbb47 test(simulation): assert "mixed" faction → ContestedZone (#956)
PR #152 review (Hoshe): the faction→TerritorialStatus mapping test covered 7 of
the 8 D-237 values; add the missing "mixed" assertion (grouped with "disputed",
behavior already correct).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 15:05:40 +02:00
jpmschweitzerandClaude Opus 4.8 5293301190 chore(meta): update changelog
Add Layer-3 settlement political character (#956).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 14:57:54 +02:00
jpmschweitzerandClaude Opus 4.8 edace3341d docs(architecture): amend D-212 + implementation notes for #956
- D-212: add the AutonomistHeld variant (Compact of Westphalia) and record the
  dominant_faction → TerritorialStatus mapping used in place of the
  numeric faction-influence thresholds (which aren't in the data). Grounded in
  wiki/factions/. IndigenousHeld/Derelict noted as deferred.
- D-214: note the political_archetype derivation + per-settlement storage.
- D-215: note the ArrangementPattern enum + derivation land here; block-adjacency
  enforcement stays with the Quarter-skeleton generator (#957).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 14:57:26 +02:00
jpmschweitzerandClaude Opus 4.8 31d7870823 test(simulation): rebake golden + atlas fixture for Layer-3 enrichment (#956)
DrainageBasin gained a territorial_status field, so the Layer-1 golden and the
atlas_response msgpack fixture pick it up. Both changes are additive/
representational — basin/attractor/river selection is unchanged.

- cascade_golden.rs: pass None dominant_faction; golden rebaked (only
  "territorial_status": "FrontierUnclaimed" added per basin).
- gen_fixtures.rs: set the new field in the atlas_response generator; fixture
  regenerated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 14:57:18 +02:00
jpmschweitzerandClaude Opus 4.8 af60d85d94 feat(simulation): Layer-3 settlement spatial-character enrichment (#956)
Derive and store each placed settlement's spatial character on top of the
#955 placement (D-212/213/214/215). Derivation is pure + integer-deterministic
(D-010); the cascade stays DB-free (D-225) — the enqueuer pre-resolves the
body's system faction onto the work item, like #955's settlements.

- TerritorialStatus (D-212): mapped from the authored dominant_faction
  (territorial_status_from_faction). Adds an AutonomistHeld variant for the
  Compact of Westphalia (self-governing bloc that rejects Assembly authority —
  neither Commission, Corp, Contested, nor truly Frontier). Grounded in
  wiki/factions/. Stored per-province on DrainageBasin.territorial_status
  (uniform per body for now; forward-compatible for per-province faction data).
- PoliticalArchetype (D-214): political_archetype(status, role), status takes
  precedence over economic_role.
- ArrangementPattern (D-215): new 5-variant enum + arrangement_pattern(); the
  block-adjacency *enforcement* stays deferred to the Quarter-skeleton gen (#957)
  — this only derives + stores which pattern applies.
- FoundingOrientation (D-213): existing fn extended with a seed-derived Free
  bearing so pioneer/open-terrain grids vary per seed.

Wiring: dominant_faction threaded through CityContextReader
(read_body_dominant_faction) → atlas proxy (cache-miss read) → AnalyzeBody work
item → run_cascade → run_layer3/match_cities. Enrichment stored on CityPlacement
(political_archetype, arrangement_pattern, founding_orientation).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 14:57:09 +02:00
jpmschweitzerandClaude Opus 4.8 8b1005221d docs(simulation): finish district→quarter doc residue in gen_queue (#950)
PR #151 review (Hoshe/Tyre): four doc comments in gen_queue.rs still said
"district" next to the renamed quarter_id field / generate_quarter_skeleton.
Pure comment update, no code change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:15:36 +02:00
jpmschweitzerandClaude Opus 4.8 c71c26a1a6 refactor(simulation): rename DistrictSkeleton→QuarterSkeleton per D-222 (#950)
D-222 renamed the 512m generation cell from District to Quarter (District
is now a new 2048m tier above it). Align the generation skeleton code to
the canonical vocabulary. Pure naming — no behavior change; all 1296 lib
tests + integration tests pass unchanged.

Renamed (spatial-cell identifiers):
- DistrictSkeleton → QuarterSkeleton, DistrictWorldState → QuarterWorldState
- DistrictId → QuarterId, DistrictContext/DistrictBoundaries → Quarter*
- field district_id → quarter_id, district_type → quarter_type
- BodyWorldState.districts map → .quarters
- generate_skeleton → generate_quarter_skeleton

Deliberately left as-is (these name functional ZONING, not the spatial
tier — orthogonal to D-222): DistrictType, DistrictLayoutMode, the
district_mix module (DistrictMix/compute_district_mix), and the
GenWorkItem::GenerateSkeleton / GenCompletion::SkeletonGenerated variants.

Also aligned the perception "sim tile" → "subtile" vocabulary (D-222:
Subtile = 0.5m) in decisions/perception.md and the generation-cascade code
comments. Historical D-066/D-094/D-201/D-220 decision bodies keep their
existing D-222 amendment notes (not rewritten in place); the public
max_offset_sim_tiles fn name is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:06:44 +02:00
jpmschweitzerandClaude Opus 4.8 f7a21c6b8d chore(meta): gitignore local agent tooling caches (.clide, .pql)
Per-machine tool dirs (.clide/settings.yaml, .pql/pql.db) that appeared as
untracked noise; not shared, should never be committed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 23:39:42 +02:00
jpmschweitzerandClaude Opus 4.8 f1d52944af fix(simulation): correct crate name in log10_floor doctest
The doctest imported `server::bps::log10_floor`, but the library crate is
`settled_reach_server` — so `cargo test --doc` failed to compile the
example (unresolved module `server`). Broken since the helper landed in
#145; it only surfaces under `--doc`, not `--lib`, so the pre-push gate
(which runs `--lib`) never caught it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 17:44:06 +02:00
jpmschweitzerandClaude Opus 4.8 2d8367d3bc docs(simulation): correct stale cascade docs and pin layer order (#955)
Address the doc/test-correctness items from PR #149 review:

- cascade.rs: Layer 3 is RNG-free (pure fn of attractors+cities); it does
  not consume the carried SeedChain. Corrected the "first RNG-using layer"
  claims on the module doc and the Settlement variant (Hoshe H3).
- cascade.rs: layers_are_ordered now also asserts Topography < Settlement,
  pinning the invariant the up_to >= Settlement guards rely on (Tyre T2).
- features.rs: strength is an integer 0-100 (quantized at extraction), not
  f32, and consumers that rank by it use the integer value (Hoshe H2).
- layer_proxy.rs: AtlasLayerRequest.up_to is not yet honored — run_work_item
  runs through Settlement unconditionally; per-request depth deferred to
  #1021 (Tyre T1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 17:33:36 +02:00
jpmschweitzerandClaude Opus 4.8 9d8ff8b341 fix(simulation): fall back on unrecognized settlement_class (#955)
read_body_settlements propagated a parse error for any non-NULL but
unrecognized settlement_class, which the atlas proxy swallowed and then
enqueued the body with zero cities — one malformed row silently dropped
placement for the entire body. Treat an unknown variant like NULL: fall
back to PopulationBudget with a warning. parse_settlement_class stays
strict for the D-199 read_set path, which must abort on bad fields.

Found in PR #149 review (Hoshe H1). Adds a test covering the fallback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 17:33:24 +02:00
jpmschweitzerandClaude Opus 4.8 9fa06f3401 chore(meta): update changelog
Mark Layer-3 settlement placement (#955) as running in-game now that the
B/E/F plumbing (per-body settlement read → work item → cache-miss proxy)
is wired.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 17:14:43 +02:00
jpmschweitzerandClaude Opus 4.8 e2787fbfb1 feat(simulation): run Layer-3 settlement placement in the runtime cascade (#955)
Wire the existing D-211 attractor-matching engine into the live
generation cascade so settlements are placed in-game, not just in tests.

- CityContextReader::read_body_settlements reads a body's settlements
  from atlas_city_names (ordered by id for determinism). NULL
  settlement_class defaults to PopulationBudget, not NameLocked: the
  class is NULL until placement runs, and NameLocked would force every
  settlement Tier-A in match_cities and collapse population tiering
  (D-211). NULL economic_role falls back to residential.
- The AnalyzeBody work item carries the body's Vec<CityRecord>, and
  run_work_item now runs up_to Settlement (was Topography). A body with
  no settlements yields empty placements at negligible cost.
- The atlas layer proxy reads settlements on a cache miss and pins them
  onto the work item, keeping the Rayon task DB-free (D-225). A read
  failure is non-fatal: log and place no cities (Layer 1 still runs).
  Threaded through a new CityContextReaderResource Bevy resource opened
  in main.rs, mirroring BodySourceResolverResource.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 17:14:18 +02:00
jpmschweitzerandClaude Opus 4.8 f748b017cb chore(meta): update changelog
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 16:31:35 +02:00
jpmschweitzerandClaude Opus 4.8 adce12035a fix(simulation): route atlas_response fixtures in the serialize test
all_fixtures_deserialize panicked with "unknown fixture naming
convention: atlas_response_ready" — the test reads every .msgpack in
client/tests/fixtures/msgpack but had no branch for the atlas_response
prefix (the AtlasLayerResponse fixtures gen_fixtures emits, #969). A
pre-existing gap (present on main; the suite isn't gated by the pre-push
hook, which runs cargo-deny/ruff/JSON/stamp, not cargo test).

Add an atlas_response branch deserializing AtlasLayerResponse, and
regenerate atlas_response_ready.msgpack so its bytes match the now-integer
GeographicAttractor types (#955).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 16:31:19 +02:00
jpmschweitzerandClaude Opus 4.8 ab084269f3 feat(simulation): integer-deterministic Layer-3 settlement placement (#955)
Wire the existing attractor-matching engine (#919/#925) into the
generation cascade as Layer 3, and make the whole placement-scoring path
integer-deterministic.

Layer 3 (D-211):
- CascadeLayer::Settlement + Layer3Output (placements) on the snapshot;
  BodyWorldState gains a `placements` field (the D-203 hot cache).
- run_layer3 runs the five-phase match_cities against Layer-1 attractors
  via the authored D-195 compatibility matrix; pure function of
  (attractors, cities) — no RNG. cities are passed in by the caller so the
  cascade stays DB-free and testable. A `// cache seam` marks where a
  persistent cache wraps it later (#1021).
- gen_queue passes &[] for now (Topography needs no cities); the runtime
  settlement read (gen_queue/layer_proxy) is the #955 follow-on.

Integer determinism (D-010 / D-227 — D-195 amended):
- Wiring match_cities into the deterministic cascade made its f32 scoring
  a live cross-platform divergence risk (a near-tie comparison or the
  Hungarian's f32 reductions can round differently per platform → a
  different world from the same seed). Converted the entire path to
  integers: CompatibilityMatrix is a 0-100 affinity table; attractor
  strength is 0-100 and terrain cost is a percent (100 = baseline),
  quantized once at the Layer-1 feature boundary; cell_score, the
  Hungarian, and CityPlacement.score are i64. No f32 in any placement or
  ranking decision.
- Layer-1 golden fixture rebaked: confirmed selection/positions are
  unchanged (same 256 attractors, 93 river cells) — only the strength/cost
  representation changed.

Tests: lib green (1292); new settlement_layer_places_cities_deterministically
covers placement + determinism + propagation into BodyWorldState.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 16:30:57 +02:00
jpmschweitzerandClaude Opus 4.8 a4727bf73e fix(economics): address PR #148 review — dry-run guard + pin validation
Review fixes (Hoshe/Tyre, both APPROVE):

- populate_trait_templates / populate_atlas_body_trait_bias: the table
  DELETEs ran unconditionally (safe only via transaction rollback on
  dry-run, and divergent from every other populate_* function). Restructured
  so validation + guardrails always run (dry-run now actually surfaces the
  would-bake counts and catches errors) but mutations happen only under
  `if not dry_run:`. Verified: --dry-run reports 28 templates, writes nothing.
- atlas_body_trait_bias: reject `pin` entries that carry a
  weight_multiplier_bps (pin is mandatory, no multiplier) — closes a silent-
  accept gap before #1017 authors ~30-40 real pins.

Deferred (noted on tickets): visual_bundle fallback-map completeness
(Phase-5/Araminta), pin-count-vs-K bake check (#1017 acceptance),
geographic_sector pool-narrowing semantics (#977).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 11:36:37 +02:00
jpmschweitzerandClaude Opus 4.8 298c37b1ee chore(meta): update changelog
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 11:05:26 +02:00
jpmschweitzerandClaude Opus 4.8 375d04a29d data(content): core architecture-flavor trait-template catalog (#1005)
The D-232 core hand-curated catalog (28 templates) the architecture-flavor
draw reads from, baked into trait_templates (#993):

- Cross-corridor pool (10): economic-function templates gated by BulkClass
  (extraction/industrial/cold-chain/precision/information/civic) + universal
  baselines + the foreign-import swerve. Guarantees the CI floor.
- Per-corridor baseline (8): each corridor's default cohesive look — core
  cosmopolitan, north anglo-frontier, south lusophone, west germanic +
  compact-cooperative, east dense-utilitarian, deep-frontier surname +
  hardscrabble.
- Heritage sub-pools (10): deep-history callbacks tied to the D-237 heritage
  taxonomy (scottish highland, west-african compound, iberian hacienda,
  atlantic creole, nordic timber, central-european blok, east-asian temple,
  vietnamese water village, afrikaans kraal, arab oasis).

Each template = holistic bundle: two-tier eligibility (hard gates
bulk_class/ubiquity/prosperity_bps + soft weight mods), zone_affinity over
the real DistrictType enum, allow/block tags from a shared ObjectTag
palette, and a D-235 visual_bundle with generic fallback parents. All
numerics integer basis-points (D-010).

Adds the D-232 CI guardrails to the baker (V-TT-01: >=5 templates eligible
per BulkClass; V-TT-02: no template >60% of pool weight) — both pass with
margin (23-24 eligible/class, 5% max share). Catalog can grow via the
bounded Gemma pass (#992); hero pins are #1017.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 11:04:43 +02:00
jpmschweitzerandClaude Opus 4.8 a133b6416e feat(schema): trait_templates + atlas_body_trait_bias tables + bake (#993)
D-232 architecture-flavor storage mechanism (content is #1005/#1017):

- trait_templates: shared catalog of holistic template bundles — two-tier
  eligibility (hard gates bulk_class/prosperity_bps/ubiquity + soft weight
  mods), zone_affinity, allow/block tags, visual_bundle. List/map fields
  are JSON; all numerics integer basis-points (D-010).
- atlas_body_trait_bias: sparse per-body hero pins — pin/boost/suppress
  with basis-point multiplier ranges (boost ≤3×, suppress ≥0.33× never 0).
- Baked at import (steps 14/15) from wiki/economics/architecture_trait_
  catalog.toml + architecture_trait_bias.toml. FK-validated (body_id →
  bodies, template_tag → trait_templates), enum + multiplier-range checks,
  graceful on absent source. Source location provisional pending Q-107;
  the baked tables are invariant per D-232. Retires the round-2
  atlas_body_culture tables.
- Catalog ships a 3-template bootstrap seed to prove the bake end-to-end;
  the full ~35-template catalog is #1005, hero pins #1017.
- Stamp sources updated (IMPORT_ECONOMICS_SOURCES + GENERATOR_SOURCES).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 10:47:35 +02:00
jpmschweitzerandClaude Opus 4.8 00c8574e11 data(economics): address PR #147 review — fused/activity pin fixes
Review fixes for the #1016 cultural pass (Hoshe/Paula/Miri):

- Kruger 60 (GJ 860A) xhosa -> cosmopolitan (Miri): GTTR describes an
  Afrikaner/Xhosa/Zulu founding "without mapping cleanly onto any of
  them" — the textbook genuinely-fused case, which taxonomy rule 4
  resolves to cosmopolitan rather than one of the three. Commented.
- Altmark (GJ 357) financial_technocratic -> cosmopolitan (Paula):
  reverts to the original shipped value; the GTTR reads as commercial
  non-alignment, not credential-based technocracy.
- Kampala Gate (GJ 667B) + Moyale (GJ 1292) keep swahili but add inline
  comments documenting that these are East-African-INTERIOR founders
  (Baganda; Kenyan/Ethiopian Borana-Oromo) for which the taxonomy has no
  dedicated token — swahili is the nearest available, NULL would fall to
  the wrong corridor default. Gap logged for a follow-up taxonomy value.
  Mwangaza (693) swahili is literally correct (coastal) — untouched.
- heritage-taxonomy-draft.md: stale "(38)" subheadings -> "(47)" to match
  the actual canonical count (Hoshe/Miri doc typo).

Retracted: Pedra Seca (GJ 421B) xhosa — reviewer thought it lacked
authored backing, but its gttr.md explicitly states "Afrikaans and Xhosa
heritage"; pin kept.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 10:23:42 +02:00
jpmschweitzerandClaude Opus 4.8 9e791240c5 chore(meta): update changelog
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 10:06:56 +02:00
jpmschweitzerandClaude Opus 4.8 21793c1a94 data(economics): #1016 cultural-write pass + canonical 47-value heritage migration
Completes the deferred cultural half of #1016 and migrates the heritage
vocabulary to the canonical 47-value taxonomy (D-237).

Migration (mechanical):
- _CULTURAL_HERITAGE replaced with the canonical 47-value set
  (docs/.../heritage-taxonomy-draft.md). Renamed the 4 shipped hero
  values to canonical: afrikaans_cape->afrikaans, french_provencal->
  french, italian_northern->italian, norse_compact->nordic.

Content pass (authored from GTTR/name heritage triage, 6-sector fan-out):
- +139 new cultural_specialization pins and 1 change (Altmark
  cosmopolitan->financial_technocratic), taking coverage 32 -> 171
  across 54 distinct values. Each pin is grounded in an explicit GTTR
  founding-community statement or system/feature name etymology, applied
  only where founding heritage DIVERGES from the corridor baseline
  (D-167/D-232); corridor-typical systems left NULL.

Distribution is now balanced and diverse — portuguese 15, german 12,
vietnamese 10, afrikaans 10 (no longer dominant post-#1019), then a long
tail filling the previously-missing slots (welsh, herero, czech, akan,
hungarian, konkan, afro_brazilian, cape_verdean, shona, arab/persian/
turkic, etc.).

Judgment calls (flagged for review):
- Kruger 60 + Pedra Seca -> xhosa (mixed SA founding; adds diversity).
  Pedra Seca has a Portuguese name but Afrikaans/Xhosa GTTR founding —
  name/heritage mismatch noted for a future content fix.
- Kampala Gate / Moyale / Mwangaza -> swahili as the nearest token for
  East-African-interior heritage; taxonomy may want a dedicated value.
- Kept hero pins Kensho=scholarly, Keid=scholarly, Nyrheim=nordic.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 10:06:23 +02:00
jpmschweitzerandClaude Opus 4.8 e1e1185cc3 fix(content): correct Golestan topology wording to match 2-aperture data
GJ 680 has aperture_count=2 / gate_connections=2, linking two distinct
systems (Athabasca GJ 1289 and Enugu GJ 75) — but the prose, inherited
verbatim from the original Carnarvon page, described "one gate in, one
gate out, both the same gate" and "one aperture connecting to Enugu and
Athabasca" (self-contradictory). Reword the gttr and index to two gates
while keeping the system's low-through-traffic spur character (the
spur_end topology label and "nobody routes through here" identity are
unchanged). Resolves the deferred PR #146 review finding.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 09:27:30 +02:00
jpmschweitzerandClaude Opus 4.8 8dc6c0ed42 data(content): address PR #146 review — Ghawr rename, faction pins, catalog
Review fixes for the #1019 heritage rebalance:

- Marib's farm world Ghouta -> Ghawr (Miri): "Ghouta" carries strong
  modern associations (2013 Damascus attack); Ghawr (the Jordan Rift
  Valley, an irrigated lowland farming region) keeps the water-fed-
  drylands sense without the resonance.
- Add dominant_faction = "independent" to GJ 914A / 508A / 1245B (Hoshe)
  — all five rebalanced systems are self-governing independent
  communities; uniform with siblings GJ 722 / 680 and clears V-FAC-01.
- catalog.md: fill GJ 914A -> Marib and GJ 722 -> Sığınak (Hoshe) — the
  two anchor systems still showed em-dash placeholders.
- Marib gttr: name the second aperture neighbour Sığınak instead of the
  bare "GJ 722" (Miri).

Pre-existing Golestan aperture/spur wording (reviewers split on whether
it is even wrong) is left for a separate content pass — not introduced
by this rename.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 09:17:07 +02:00
jpmschweitzerandClaude Opus 4.8 36ae931997 chore(meta): update changelog
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 07:12:00 +02:00
jpmschweitzerandClaude Opus 4.8 03188de16d docs(workshops): record #1019 heritage rebalance batch
The pre-execution conversion plan and shipped-state addendum for the
South-African heritage rebalance: per-system field maps, the worked
gttr rewrites, the cross-reference cleanup list, and the rationale for
keeping load-bearing mystery systems out of scope.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 07:11:20 +02:00
jpmschweitzerandClaude Opus 4.8 8844bef139 data(content): rebalance 5 thin afrikaans systems to MENA heritage (#1019)
The corridor over-represented southern-African heritage (afrikaans on 15
systems; arab=2, persian/turkic=0) as an artifact of the earlier
name-balancing overcorrecting. Convert five genuinely-thin afrikaans
systems — whose distinctive threads are heritage-agnostic — into
Arab/Persian/Turkic founders so the cultural cascade is built on a
balanced set. Load-bearing mystery systems (Eerste Wacht / Helderoog /
Brandpunt etc.) are left untouched.

  Droëland  (GJ 914A)  -> Marib    (arab)
  Stilwater (GJ 508A)  -> Sawad    (arab)
  Koeberg   (GJ 1245B) -> Akhgar   (persian)
  Skuilplek (GJ 722)   -> Siginak  (turkic)
  Carnarvon (GJ 680)   -> Golestan (persian; Mostert dynasty -> Farahani)

Result: afrikaans 15->10; arab 0->2, persian 0->2, turkic 0->1.

Full narrative retouch (not just names): gttr.md + index.md prose,
body/station proper_names, cultural_specialization, gttr_hook,
atlas_city_names, and the four brand-corps named after the converted
systems (corp IDs kept stable; only display names + products renamed).
Cross-references in neighbour systems, the catalog, the aggregate
drifter guides, and the atlas proposals are updated to match.

arab/persian/turkic added to _CULTURAL_HERITAGE so the values validate;
the full canonical 47-value migration remains #1016.

Also fixes a pre-existing canon bug: Sawad (GJ 508A) no longer claims
GJ 914A is "unsettled" — it is Marib, a three-century settlement.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 07:11:10 +02:00
jpmschweitzerandClaude Opus 4.8 01ba14ada9 docs(workshops): canonical D-237 heritage taxonomy (47 values, approved)
Reconciles the ~30 inconsistent heritage proposals from the 6-sector #1016
content pass into one canonical cultural_specialization heritage vocabulary
(47 values). Key reconciliations: norse_compact->nordic (un-fuses people from
faction), spanish_iberian+spanish_hispanic->spanish, brazilian_portuguese+
lusophone_frontier->brazilian, italian_northern->italian, afrikaans_cape->
afrikaans, french_provencal+french_canadian->french. No compound values; one
token per system (blends take dominant heritage or the cosmopolitan activity
value).

Maintainer decisions (2026-06-01): keep ALL values distinct (no lumping —
welsh/afro_brazilian/cape_malay/xhosa/luxembourgish/konkan all retained);
keep empty-but-complete slots (yoruba/hausa/hungarian/persian/turkic). Fills
the Middle-East gap the pass exposed: arab/persian/turkic added even at near-
zero current usage so the canon is complete.

Logs the southern-African overrepresentation (~25/300, afrikaans=15) as a DLC
content-rewrite rebalancing task (#1019) rather than lumping the taxonomy.

Migration note included: 4 already-shipped hero cultural values use old suffix
forms (afrikaans_cape, french_provencal, italian_northern, norse_compact) and
will be renamed in the culture-write pass when _CULTURAL_HERITAGE is replaced.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 16:49:00 +02:00
jpmschweitzerandClaude Opus 4.8 8addf04835 data(economics): #1016 econ+faction content pass (143 systems, cultural deferred)
The 6-sector content pass (core pilot + deep_frontier/north/east/south/west,
~260 systems read from both index.md and gttr.md) authored economic_
specialization + dominant_faction for the systems whose lore makes those
identities distinct. Conservative pin bar: corridor-typical systems left NULL
for the heuristic/derivation fallback.

Totals now: economic 170 (27 hero + 143), faction 76 (27 hero + 49),
cultural still 27 (DEFERRED — the culture pass needs a canonical heritage
vocabulary first; the agents surfaced ~30 real heritages from the names plus a
genuine Middle-East/Arabic gap, to be reconciled into one taxonomy before
writing cultural_specialization).

regen-db green, stamp fresh, all CI guardrails pass. Soft warnings are the
expected D-177 MonopolySource review list, the known Arbour estate/breadbasket
question, and sparse-corp_presence W-FAC-03 advisories — none blocking.

Notable pins: Haodu financial_hub/syndic_dominant, Matamba legal_archive,
Inhambane/Sonnwend/etc breadbasket, the terroir cluster, the west_reach
Compact zone, Aurelius governance_center/veil_institute. FLAGs left NULL
pending human calls (ISPI=veil_institute?, Gate-Corp-as-faction).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 16:27:24 +02:00
jpmschweitzerandClaude Opus 4.8 0251c65b96 feat(wiki): surface D-237 specialization fields in the system infobox
generate_infobox() now renders economic_specialization (Specialization) and
cultural_specialization (Cultural Register) as read-only infobox rows when
authored (NULL = omitted, falls to heuristic). dominant_faction already
showed via Governance. Makes the authored generator-input values visible on
the wiki face so the #1016 content pass output renders. Verified: Ran shows
'breadbasket'/'agrarian'; unauthored systems omit the rows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 14:29:37 +02:00
jpmschweitzerandClaude Opus 4.8 e0d796357f docs(decisions): document economic_base_primary vs economic_specialization seam (D-237)
Records that the two are a deliberate parallel representation, not a
duplicate: economic_base_primary is uncomputable GTTR prose flavor rendered
to the wiki infobox; economic_specialization is the higher-fidelity enum the
D-233 generator consumes. Notes they must stay mutually consistent (#1016
authors the enum from the same lore; W-SES-03 flags divergence), that
unification was considered and rejected (each carries info the other can't),
and clarifies the DB-canonical / index.md-projection data flow (only the
prose sections are authored in-place). Surfaced when the content-pass review
questioned the redundancy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 13:42:38 +02:00
jpmschweitzerandClaude Opus 4.8 c6abff0f44 chore(db): regen systems.db — D-237 specialization layer populated
make regen-db with the D-237 authored layer live: 27-row
specialization_vocabulary, economic_specialization + cultural_specialization
on 27 hero systems, dominant_faction on the authored set. Meta stamp fresh
(check-systems-db OK). Unauthored systems remain NULL pending the #1014
fallback and #1016 content pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 13:09:56 +02:00
jpmschweitzerandClaude Opus 4.8 9fe4ddfb04 fix(economics): restore dropped vocab_rows.append in import_system_specialization
The #1015 V-SES-03 edit replaced the vocabulary validation loop body and
dropped the trailing vocab_rows.append(), so specialization_vocabulary was
inserted with 0 rows even though per-system UPSERTs (which read the dict
directly via valid_spec_ids) still succeeded. regen-db reported
'vocab 0 | economic 27 | ...'. Restored the append at the end of the loop.

Verified: regen-db now writes 27 vocab rows + 27/27 economic/cultural;
spot checks financial_hub=NonPhysical/Specialist, Ran=breadbasket.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 13:09:49 +02:00
jpmschweitzerandClaude Opus 4.8 2f994c1795 feat(economics): D-237 specialization CI guardrails + coverage report (#1015)
Adds the V-SES / V-FAC guardrail suite to import_system_specialization():

Always-on hard errors (abort the import transaction): V-SES-01 economic
value in vocabulary; V-SES-03 production_ubiquity_override >= commodity
catalog concentration (HUB values with empty override exempt — they
intentionally project lower); V-SES-04 cultural value in the combined
activity+heritage vocabulary; V-SES-05 faction in 8-value vocab; V-SES-06
vocab commodity FK; plus unknown system_id.

Completeness gates — V-SES-02 (every inhabited system resolves a non-null
economic value) and V-FAC-01 (every inhabited named system has authored
faction) — are HARD only under --strict-specialization (default off). Their
preconditions are the #1014 heuristic fallback (blocked by #982) and the
#1016 content pass; until those land they print as warnings so regen-db
stays green.

Soft warnings (always print): W-SES-01 MonopolySource D-177 review list,
W-SES-02 single-value concentration, W-SES-08 estate_farming + large pop,
W-FAC-01 compact + TRACTUS currency, W-FAC-02 compact + MonopolySource
(terroir/marble exempt), W-FAC-03 syndic_dominant w/o corp HQ, W-FAC-04
compact_sympathetic + MARK currency. Coverage report always prints
economic/cultural/faction counts + BulkClass/ProductionUbiquity dists.

Cultural vocabulary is _CULTURAL_ACTIVITY | _CULTURAL_HERITAGE module
constants — #1016 adds heritage values there in one place.

Validated on a copy of the live DB: default exits 0 (gates as warnings, real
W-SES-08 hit on Arbour); strict exits 1 on V-SES-02 (Sol, Struve) and rolls
back cleanly; dry-run exits 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 12:44:03 +02:00
jpmschweitzerandClaude Opus 4.8 883c1ab0b1 feat(economics): import_system_specialization() — D-237 authored layer (#1013)
Wires the D-237 authored specialization layer into import_economics.py:

- import_system_specialization(): reads specialization_vocabulary.toml
  (FK-validated against commodities; repopulates specialization_vocabulary)
  and system_specialization.toml (UPSERTs economic_specialization +
  cultural_specialization onto system_economy, dominant_faction onto
  system_factions for authored systems only). Hard errors on bad commodity
  FK / projected enum / unknown system_id abort the transaction; prints a
  coverage report + missing-row warnings.
- Called as step 4b in main() (after commodities so the FK resolves, before
  currency zones). economic/cultural columns are importer-owned and cleared
  to NULL first for idempotency; dominant_faction only overwritten for
  authored systems (never globally cleared — shared with other derivation).
- specialization_vocabulary added to MIGRATION_SQL (after commodities for FK)
  so the migration path on existing DBs creates the table, not just fresh
  systems-schema.sql builds — this is what #1011 missed. Also cleared before
  commodities in the FK-safe clear block.
- Both source TOMLs added to IMPORT_ECONOMICS_SOURCES and mirrored in
  check-systems-db-stamp GENERATOR_SOURCES so editing them flips the stamp.

Validated: full non-dry-run import on a copy of the live systems.db exits 0;
27 vocab rows, 27/27/27 economic/cultural/faction set, no missing rows;
spot checks Ran=breadbasket/agrarian, Vuurkloof=independent,
financial_hub=NonPhysical/Specialist. Dry-run also exits 0.

V-SES CI guardrail suite remains #1015; code note flags that V-SES-03
(equal-or-higher) must not hard-fail HUB specializations.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 09:09:06 +02:00