docs(governance): file D-242 corp-HQ settlement model + T-1074/1075/1076

Reviewing the T-1038 road-graph hub model surfaced that corp HQs are
cross-referenced into atlas_city_names as reserved rows — one per corp —
producing duplicate co-named "cities" (e.g. 10 'Groombridge' rows on
GJ380c) that the spatial layers place as separate settlements.

D-242 resolves the data model: corp HQs are not cities. The HQ relationship
lives with the corp; placement is a baked, specialization-keyed preference
(CityTenant vs Standalone, since corp_type is degenerate); population and
settlement_class are install-baked with only position seed-derived. This
supersedes the D-223 corp-HQ-cross-ref and deferred-pop/class notes.

Companion tickets under T-750: T-1074 (corp-HQ model / remove the cross-ref /
specialization map / standalone HQ settlements), T-1075 (bake per-settlement
population + settlement_class), T-1076 (road-graph hub refinement — scaled-cap
hubs, co-location collapse, hybrid minor attach, deferred RailHeadFacing pass;
blocked on T-1074 + T-1075). T-1038 annotated with shipped-vs-deferred.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-14 20:13:10 +02:00
co-authored by Claude Opus 4.8
parent 1044939748
commit 301be77b49
6 changed files with 168 additions and 1 deletions
+2
View File
@@ -488,3 +488,5 @@ INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updat
INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRYMFY5VFPXGANNRSXW', '06FB0TNSRX2TD5M8RQDJRNXA24', '2026-06-07 10:34:18', '2026-06-07 10:34:18', NULL, 'c50cf57d1ed4921ebffb718e02514ce5', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash);
INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXFCP7EJV7RZK0TBF4', '06FB0TNSRXTPRZ40AC8HJGSQZ0', '2026-06-07 10:34:18', '2026-06-07 10:34:18', NULL, 'db5d2b25de9294320d1917f6444a73c9', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash);
INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRYMFY5VFPXGANNRSXW', '06FB0TNSRW8ADSD8ZK32K7SWSC', '2026-06-07 16:35:38', '2026-06-07 16:35:38', NULL, 'adecdfdf040b687fd266c8bbf5a98616', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash);
INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCDG73NW7SNCA5Y8JN905QMR', '06FCDG73V9R6N024QSFDEJ4R68', '2026-06-14 15:29:50', '2026-06-14 15:29:50', NULL, '389b010a55f227808e2b2e451acf8d36', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash);
INSERT INTO ticket_deps (blocker_record_id, blocked_record_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCDG73S4ET8KKTK62Z5KFEQM', '06FCDG73V9R6N024QSFDEJ4R68', '2026-06-14 15:29:50', '2026-06-14 15:29:50', NULL, '7e362812933c59ffe9f76ab069a83dc1', 2) ON CONFLICT(blocker_record_id, blocked_record_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash);
+51
View File
@@ -1864,3 +1864,54 @@ tooling/economy-db/import_economics.py (2,617 lines, 31 top-level defs) owns sch
Fix: extract one shared tooling/generator_sources.py imported by the script + stamp checker and read by /pr-push; then split importers/validators into modules under tooling/economy-db/; finish type hints during the split. Also fold in S-25''s connector dedup: move CONFIG_PATH/load_config/get_base_url into tooling/db/common.py (currently copy-pasted across audio/trellis/image connectors).
Closed 2026-06-12, commits 4f73624ef + 3c3fd0d0d (DB stamp regen). import_economics.py 2,620→309 (thin orchestrator, exact CLI/transaction/exit-code contract); 13 modules under tooling/economy-db/economy_import/; tooling/generator_sources.py is the single registry (self-stamping, fail-closed module globbing) consumed by the importer, check-systems-db-stamp, and the pr-process watch list via --list. Connector config helpers centralized in tooling/db/common.py. Byte-identical proof: empty full-import dump diff (107,843 lines / 37 tables), dry-run parity, brands TOML sha unchanged. Gate green incl. test-tooling + stamp check on the regenerated DB.', NULL, '2026-06-12 18:15:04', '2026-06-12 18:15:04', '2026-06-12 18:15:04', NULL, '183b4254f288bf30b1feb6612ab712f5', 2) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPQ02F02JDDBSFMGPX0XASR', 'status', 'backlog', 'in_progress', NULL, '2026-06-13 20:44:09', '2026-06-13 20:44:09', '2026-06-13 20:44:09', NULL, '1c65dd3a9d04be737f796df314c20959', 2) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPQ02F02JDDBSFMGPX0XASR', 'status', 'in_progress', 'in_progress', NULL, '2026-06-13 20:44:30', '2026-06-13 20:44:30', '2026-06-13 20:44:30', NULL, '5c619f3ffe6b19f2112f1e2af21de542', 2) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPQ02F02JDDBSFMGPX0XASR', 'description', 'Workshopped-but-unticketed cascade work captured 2026-06-12 (cascade-refocus grounding pass). Full grounded scope follows.
Generate the inter-settlement road/rail graph at Layer 2 — the workshopped-but-never-ticketed link between settlement placement (T-955/T-956, done) and everything that consumes transport topology. No road code exists today (zero RoadGraph/road_segment hits in server/src; TerritorialStatus shipped without its road-coverage inputs).
Scope (algorithm fully decided, docs/workshops/planet-down-cascade/tyre-round1.md:274-299):
1. MST force-connect all CityPlacements (Euclidean), then secondary direct links for city pairs within 30-60% of body scale whose MST path exceeds 1.5x Euclidean.
2. Route each edge with A* over the L1 terrain-cost field (terrain_modification_cost, D-210, server/src/atlas/subbiome.rs; cost spec: water impassable, roughness>0.7 = 10.0, river corridor = 0.5, flat = 1.0 — rivers as preferred valley routes). Budget ~30ms/body (round-1-notes.md:348).
3. Assign MaintenanceAuthority per edge from endpoint relationships (5-value enum Administrative/Corporate/Communal/Trade/Abandoned — generation-cascade/workshop-outcomes.md:177, resolution :257; player-readable in road condition per ozzie-round1.md:86-92).
4. Place waypoint sub-settlement nodes at midpoints of edges > ~12 regional cells (tyre-round1.md:267-272).
5. Named-route identity assignment: post-generation join of generated edges to systems.db named highway routes via optional named_route_id FK (paula-round1.md:107,:140; round-1-notes.md:322 L2-Q3 resolution under Amendment 3).
6. RailHeadFacing second pass: settlements at high-connectivity road/rail junctions get FoundingOrientation::RailHeadFacing, overriding AdminFacing-from-Synthetic (paula-round3.md:218-236) — wire into attractor_matching.rs.
Storage: RoadGraph{nodes,edges} on BodyWorldState (round-1-notes.md:257-267), session-cached, never serialized — runtime Rust, not systems.db (the atlas_road_nodes/atlas_road_edges schema shape carries forward per tyre-round1.md:42 but lives in session memory).
Downstream consumers (do NOT scope in, but design the output for them): road_entry_directions per city (see road-entry-octants ticket; generation-cascade/round-3-notes.md:104), TerritorialStatus quantitative road-coverage thresholds (round-1-notes.md:329-338), per-economic_role hinterland fill (round-1-notes.md:324-327, BRIEF.md:279), prosperity/atlas overlays.
Cascade-first fit: this is direction step (2) — the econ layer drawn onto the world between settlements. Parent: T-750. Suggested deps: blocks the road-entry-octants ticket; references D-205 (provinces), D-210, D-211, D-212, D-213.', 'Workshopped-but-unticketed cascade work captured 2026-06-12 (cascade-refocus grounding pass). Full grounded scope follows.
Generate the inter-settlement road/rail graph at Layer 2 — the workshopped-but-never-ticketed link between settlement placement (T-955/T-956, done) and everything that consumes transport topology. No road code exists today (zero RoadGraph/road_segment hits in server/src; TerritorialStatus shipped without its road-coverage inputs).
Scope (algorithm fully decided, docs/workshops/planet-down-cascade/tyre-round1.md:274-299):
1. MST force-connect all CityPlacements (Euclidean), then secondary direct links for city pairs within 30-60% of body scale whose MST path exceeds 1.5x Euclidean.
2. Route each edge with A* over the L1 terrain-cost field (terrain_modification_cost, D-210, server/src/atlas/subbiome.rs; cost spec: water impassable, roughness>0.7 = 10.0, river corridor = 0.5, flat = 1.0 — rivers as preferred valley routes). Budget ~30ms/body (round-1-notes.md:348).
3. Assign MaintenanceAuthority per edge from endpoint relationships (5-value enum Administrative/Corporate/Communal/Trade/Abandoned — generation-cascade/workshop-outcomes.md:177, resolution :257; player-readable in road condition per ozzie-round1.md:86-92).
4. Place waypoint sub-settlement nodes at midpoints of edges > ~12 regional cells (tyre-round1.md:267-272).
5. Named-route identity assignment: post-generation join of generated edges to systems.db named highway routes via optional named_route_id FK (paula-round1.md:107,:140; round-1-notes.md:322 L2-Q3 resolution under Amendment 3).
6. RailHeadFacing second pass: settlements at high-connectivity road/rail junctions get FoundingOrientation::RailHeadFacing, overriding AdminFacing-from-Synthetic (paula-round3.md:218-236) — wire into attractor_matching.rs.
Storage: RoadGraph{nodes,edges} on BodyWorldState (round-1-notes.md:257-267), session-cached, never serialized — runtime Rust, not systems.db (the atlas_road_nodes/atlas_road_edges schema shape carries forward per tyre-round1.md:42 but lives in session memory).
Downstream consumers (do NOT scope in, but design the output for them): road_entry_directions per city (see road-entry-octants ticket; generation-cascade/round-3-notes.md:104), TerritorialStatus quantitative road-coverage thresholds (round-1-notes.md:329-338), per-economic_role hinterland fill (round-1-notes.md:324-327, BRIEF.md:279), prosperity/atlas overlays.
Cascade-first fit: this is direction step (2) — the econ layer drawn onto the world between settlements. Parent: T-750. Suggested deps: blocks the road-entry-octants ticket; references D-205 (provinces), D-210, D-211, D-212, D-213.
---
Implementation (2026-06-14):
SHIPPED — deterministic road-graph CORE in server/src/atlas/road_graph.rs (CascadeLayer::RoadGraph, stored on BodyWorldState.road_graph, never serialized):
1. MST force-connect (strict Kruskal over dist^2) + secondary links (30-60% body-scale band, >1.5x MST detour).
2. A* over the L1 terrain-cost field (ocean/lake impassable, river 1/2 cost, slope>=35deg 10x, else D-210 terrain_modification_cost) on a coarse 64-wide routing grid; equirectangular column wrap.
3. MaintenanceAuthority per edge (new generator.rs enum: Administrative/Corporate/Communal/Trade/Abandoned) from endpoint political archetypes + haul length.
4. Waypoint nodes at midpoints of edges > 12 routing cells.
5. Named-route identity join (named_route_id, designed-for; pool empty post-D-223 -> no-op).
+ junction detection (RoadGraph::high_connectivity_junctions, degree>=3).
Wired into the cascade (gen_queue requests CascadeLayer::RoadGraph) + BodyWorldState. 10 road_graph unit tests + a cascade e2e test; full lib suite green.
DEFERRED (-> T-1076, gated on the D-242 settlement model T-1074/T-1075):
- step 6 RailHeadFacing second pass — the adopted FoundingOrientation enum (D-213) has no RailHeadFacing; needs a D-213 amendment + Layer-4 skeleton_gen handling. Junction data is already exposed for it.
- hub-selection refinement (scaled-cap hubs, co-location collapse, hybrid minor-settlement attach) — needs baked population/specialization (D-242).
Decision D-242 filed (corp-HQ settlement model) — resolves why corp HQs were polluting the city pool as duplicate settlements.', NULL, '2026-06-14 15:30:58', '2026-06-14 15:30:58', '2026-06-14 15:30:58', NULL, 'adca3fc7b58c20d9805752a9082817a4', 2) ON CONFLICT(hash) DO NOTHING;
+3
View File
@@ -81,3 +81,6 @@ INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPTXSEC1F0RKJVH7JVPS8NW', 'T-1071', '2026-06-12 10:40:59', '2026-06-12 10:40:59', NULL, 'b3f82a325e1724e3d218fe54ba344f64', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBQ8WGMA3552756BQAAFPMER', 'T-1072', '2026-06-12 11:41:59', '2026-06-12 11:41:59', NULL, 'fefd6fa8565a07b57ff8bbd5cadfeb7e', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBRCMEKT72S0Q6NE68JH7EXC', 'T-1073', '2026-06-12 14:18:10', '2026-06-12 14:18:10', NULL, 'e2373f7a83c096537e649e733ccde52f', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCDG73NW7SNCA5Y8JN905QMR', 'T-1074', '2026-06-14 15:29:50', '2026-06-14 15:29:50', NULL, '2bf2aa1e4d416127b28f923b273bfd67', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCDG73S4ET8KKTK62Z5KFEQM', 'T-1075', '2026-06-14 15:29:50', '2026-06-14 15:29:50', NULL, '5227303769a48815b53724fb47d42b39', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCDG73V9R6N024QSFDEJ4R68', 'T-1076', '2026-06-14 15:29:50', '2026-06-14 15:29:50', NULL, '8df24639ed7dc3c6a29bc2019da02f74', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
+92
View File
@@ -2127,3 +2127,95 @@ tooling/economy-db/import_economics.py (2,617 lines, 31 top-level defs) owns sch
Fix: extract one shared tooling/generator_sources.py imported by the script + stamp checker and read by /pr-push; then split importers/validators into modules under tooling/economy-db/; finish type hints during the split. Also fold in S-25''s connector dedup: move CONFIG_PATH/load_config/get_base_url into tooling/db/common.py (currently copy-pasted across audio/trellis/image connectors).
Closed 2026-06-12, commits 4f73624ef + 3c3fd0d0d (DB stamp regen). import_economics.py 2,620→309 (thin orchestrator, exact CLI/transaction/exit-code contract); 13 modules under tooling/economy-db/economy_import/; tooling/generator_sources.py is the single registry (self-stamping, fail-closed module globbing) consumed by the importer, check-systems-db-stamp, and the pr-process watch list via --list. Connector config helpers centralized in tooling/db/common.py. Byte-identical proof: empty full-import dump diff (107,843 lines / 37 tables), dry-run parity, brands TOML sha unchanged. Gate green incl. test-tooling + stamp check on the regenerated DB.', 'done', 'medium', NULL, NULL, NULL, '2026-06-12 10:40:59', '2026-06-12 18:15:04', NULL, 'e41a21e215fc14cfd3471d1a7d01a960', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPQ02F02JDDBSFMGPX0XASR', 'story', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Layer 2 — inter-settlement road/rail graph (MST + A* over terrain cost, MaintenanceAuthority, named-route identity)', 'Workshopped-but-unticketed cascade work captured 2026-06-12 (cascade-refocus grounding pass). Full grounded scope follows.
Generate the inter-settlement road/rail graph at Layer 2 — the workshopped-but-never-ticketed link between settlement placement (T-955/T-956, done) and everything that consumes transport topology. No road code exists today (zero RoadGraph/road_segment hits in server/src; TerritorialStatus shipped without its road-coverage inputs).
Scope (algorithm fully decided, docs/workshops/planet-down-cascade/tyre-round1.md:274-299):
1. MST force-connect all CityPlacements (Euclidean), then secondary direct links for city pairs within 30-60% of body scale whose MST path exceeds 1.5x Euclidean.
2. Route each edge with A* over the L1 terrain-cost field (terrain_modification_cost, D-210, server/src/atlas/subbiome.rs; cost spec: water impassable, roughness>0.7 = 10.0, river corridor = 0.5, flat = 1.0 — rivers as preferred valley routes). Budget ~30ms/body (round-1-notes.md:348).
3. Assign MaintenanceAuthority per edge from endpoint relationships (5-value enum Administrative/Corporate/Communal/Trade/Abandoned — generation-cascade/workshop-outcomes.md:177, resolution :257; player-readable in road condition per ozzie-round1.md:86-92).
4. Place waypoint sub-settlement nodes at midpoints of edges > ~12 regional cells (tyre-round1.md:267-272).
5. Named-route identity assignment: post-generation join of generated edges to systems.db named highway routes via optional named_route_id FK (paula-round1.md:107,:140; round-1-notes.md:322 L2-Q3 resolution under Amendment 3).
6. RailHeadFacing second pass: settlements at high-connectivity road/rail junctions get FoundingOrientation::RailHeadFacing, overriding AdminFacing-from-Synthetic (paula-round3.md:218-236) — wire into attractor_matching.rs.
Storage: RoadGraph{nodes,edges} on BodyWorldState (round-1-notes.md:257-267), session-cached, never serialized — runtime Rust, not systems.db (the atlas_road_nodes/atlas_road_edges schema shape carries forward per tyre-round1.md:42 but lives in session memory).
Downstream consumers (do NOT scope in, but design the output for them): road_entry_directions per city (see road-entry-octants ticket; generation-cascade/round-3-notes.md:104), TerritorialStatus quantitative road-coverage thresholds (round-1-notes.md:329-338), per-economic_role hinterland fill (round-1-notes.md:324-327, BRIEF.md:279), prosperity/atlas overlays.
Cascade-first fit: this is direction step (2) — the econ layer drawn onto the world between settlements. Parent: T-750. Suggested deps: blocks the road-entry-octants ticket; references D-205 (provinces), D-210, D-211, D-212, D-213.', 'in_progress', 'high', NULL, 'server', 'D-211', '2026-06-12 10:23:49', '2026-06-13 20:44:09', NULL, 'e178b187eaa605cf4d6edccf77304487', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPQ02F02JDDBSFMGPX0XASR', 'story', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Layer 2 — inter-settlement road/rail graph (MST + A* over terrain cost, MaintenanceAuthority, named-route identity)', 'Workshopped-but-unticketed cascade work captured 2026-06-12 (cascade-refocus grounding pass). Full grounded scope follows.
Generate the inter-settlement road/rail graph at Layer 2 — the workshopped-but-never-ticketed link between settlement placement (T-955/T-956, done) and everything that consumes transport topology. No road code exists today (zero RoadGraph/road_segment hits in server/src; TerritorialStatus shipped without its road-coverage inputs).
Scope (algorithm fully decided, docs/workshops/planet-down-cascade/tyre-round1.md:274-299):
1. MST force-connect all CityPlacements (Euclidean), then secondary direct links for city pairs within 30-60% of body scale whose MST path exceeds 1.5x Euclidean.
2. Route each edge with A* over the L1 terrain-cost field (terrain_modification_cost, D-210, server/src/atlas/subbiome.rs; cost spec: water impassable, roughness>0.7 = 10.0, river corridor = 0.5, flat = 1.0 — rivers as preferred valley routes). Budget ~30ms/body (round-1-notes.md:348).
3. Assign MaintenanceAuthority per edge from endpoint relationships (5-value enum Administrative/Corporate/Communal/Trade/Abandoned — generation-cascade/workshop-outcomes.md:177, resolution :257; player-readable in road condition per ozzie-round1.md:86-92).
4. Place waypoint sub-settlement nodes at midpoints of edges > ~12 regional cells (tyre-round1.md:267-272).
5. Named-route identity assignment: post-generation join of generated edges to systems.db named highway routes via optional named_route_id FK (paula-round1.md:107,:140; round-1-notes.md:322 L2-Q3 resolution under Amendment 3).
6. RailHeadFacing second pass: settlements at high-connectivity road/rail junctions get FoundingOrientation::RailHeadFacing, overriding AdminFacing-from-Synthetic (paula-round3.md:218-236) — wire into attractor_matching.rs.
Storage: RoadGraph{nodes,edges} on BodyWorldState (round-1-notes.md:257-267), session-cached, never serialized — runtime Rust, not systems.db (the atlas_road_nodes/atlas_road_edges schema shape carries forward per tyre-round1.md:42 but lives in session memory).
Downstream consumers (do NOT scope in, but design the output for them): road_entry_directions per city (see road-entry-octants ticket; generation-cascade/round-3-notes.md:104), TerritorialStatus quantitative road-coverage thresholds (round-1-notes.md:329-338), per-economic_role hinterland fill (round-1-notes.md:324-327, BRIEF.md:279), prosperity/atlas overlays.
Cascade-first fit: this is direction step (2) — the econ layer drawn onto the world between settlements. Parent: T-750. Suggested deps: blocks the road-entry-octants ticket; references D-205 (provinces), D-210, D-211, D-212, D-213.', 'in_progress', 'high', NULL, 'server', 'D-211', '2026-06-12 10:23:49', '2026-06-13 20:44:30', NULL, '591936c6e539ddf25905176ce059b676', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCDG73NW7SNCA5Y8JN905QMR', 'story', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Corp HQ settlement model — remove HQs from the city pool; specialization-keyed standalone-vs-tenant; install-baked', 'Per D-242. Corp HQs are not cities and must leave atlas_city_names.
1. Remove the D-207/D-223 reserved=1 corp-HQ cross-reference (populate_atlas_city_names_corps, tooling/economy-db/economy_import/atlas.py) — it inserts one atlas_city_names row per corp HQ, creating duplicate co-named cities (e.g. 10 Groombridge rows on GJ380c).
2. Author a source-canonical specialization -> HQ-placement map ({CityTenant, Standalone}). corp_type is degenerate (corporation/combine only); key on corporations.specialization (and/or supply_chain_role). Install-baked.
3. CityTenant HQs: a corp->city link, no new settlement (many corps may tenant one city).
4. Standalone HQs (extraction/heavy-industry/shipyards): emit their own settlement (body + position) into the cascade settlement list. Requires resolving headquarters_body (today 0/165 set; only headquarters_system + a most-populated-body heuristic).
5. Cascade settlement input becomes: city pool + standalone-HQ settlements, merged.
Amends D-207, D-223. Blocks the road-graph hub refinement.', 'backlog', 'high', NULL, 'server', 'D-242', '2026-06-14 15:29:50', '2026-06-14 15:29:50', NULL, '9176515b9f67459f2dcee6c58de1e0fa', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCDG73S4ET8KKTK62Z5KFEQM', 'story', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Bake per-settlement population + settlement_class into the install dataset (only position stays seed-derived)', 'Per D-242. population + settlement_class are install-baked, not seed-derived.
Today: body-level population is baked (273/3240 bodies > 0), but per-city population is 0 and settlement_class NULL for all 463 atlas_city_names rows. D-223 deferred these ''to placement (T-955)'', but match_cities only READS them for tiering — nothing assigns them, so the pop-from-seed step was never built and all cities fall to synthetic placement.
Bake the per-settlement population spread + settlement_class into the source data (importer/source TOMLs). Only POSITION stays seed-derived (D-211). Drop the never-built seed-population idea.
Supersedes the D-223 deferred-pop/class note. Enables significance-ranked hub selection.', 'backlog', 'high', NULL, 'server', 'D-242', '2026-06-14 15:29:50', '2026-06-14 15:29:50', NULL, 'a3e992272c50209fb1dfffbcfe6ea28c', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCDG73V9R6N024QSFDEJ4R68', 'story', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Road-graph hub refinement — scaled-cap hubs + co-location collapse + hybrid minor attach + RailHeadFacing junction pass', 'Follow-on to T-1038 (road-graph core shipped). Hub-selection refinement, gated on the resolved settlement model (D-242).
Build on server/src/atlas/road_graph.rs:
1. Hub selection: ''real cities, scaled cap'' — wiki cities anchor the MST; cap scales with body size; standalone HQs/minor settlements are non-hub.
2. Co-location collapse: group same-named settlements (defensive; the D-242 corp-HQ fix removes most at source).
3. Hybrid minor-settlement attach: snap to nearest existing road edge if close, else spur to nearest hub (add edge-split-at-attach).
4. RailHeadFacing junction pass (T-1038 step 6, deferred): junction settlements (RoadGraph::high_connectivity_junctions already computed) get FoundingOrientation::RailHeadFacing. REQUIRES a D-213 amendment (adopted enum has no RailHeadFacing) + Layer-4 skeleton_gen handling.
Needs population ranking — blocked on the corp-HQ-model + population-bake tickets.', 'backlog', 'medium', NULL, 'server', 'D-242', '2026-06-14 15:29:50', '2026-06-14 15:29:50', NULL, 'a9b2ff68ee5d9208cffb0a1ebe63bbe2', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPQ02F02JDDBSFMGPX0XASR', 'story', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Layer 2 — inter-settlement road/rail graph (MST + A* over terrain cost, MaintenanceAuthority, named-route identity)', 'Workshopped-but-unticketed cascade work captured 2026-06-12 (cascade-refocus grounding pass). Full grounded scope follows.
Generate the inter-settlement road/rail graph at Layer 2 — the workshopped-but-never-ticketed link between settlement placement (T-955/T-956, done) and everything that consumes transport topology. No road code exists today (zero RoadGraph/road_segment hits in server/src; TerritorialStatus shipped without its road-coverage inputs).
Scope (algorithm fully decided, docs/workshops/planet-down-cascade/tyre-round1.md:274-299):
1. MST force-connect all CityPlacements (Euclidean), then secondary direct links for city pairs within 30-60% of body scale whose MST path exceeds 1.5x Euclidean.
2. Route each edge with A* over the L1 terrain-cost field (terrain_modification_cost, D-210, server/src/atlas/subbiome.rs; cost spec: water impassable, roughness>0.7 = 10.0, river corridor = 0.5, flat = 1.0 — rivers as preferred valley routes). Budget ~30ms/body (round-1-notes.md:348).
3. Assign MaintenanceAuthority per edge from endpoint relationships (5-value enum Administrative/Corporate/Communal/Trade/Abandoned — generation-cascade/workshop-outcomes.md:177, resolution :257; player-readable in road condition per ozzie-round1.md:86-92).
4. Place waypoint sub-settlement nodes at midpoints of edges > ~12 regional cells (tyre-round1.md:267-272).
5. Named-route identity assignment: post-generation join of generated edges to systems.db named highway routes via optional named_route_id FK (paula-round1.md:107,:140; round-1-notes.md:322 L2-Q3 resolution under Amendment 3).
6. RailHeadFacing second pass: settlements at high-connectivity road/rail junctions get FoundingOrientation::RailHeadFacing, overriding AdminFacing-from-Synthetic (paula-round3.md:218-236) — wire into attractor_matching.rs.
Storage: RoadGraph{nodes,edges} on BodyWorldState (round-1-notes.md:257-267), session-cached, never serialized — runtime Rust, not systems.db (the atlas_road_nodes/atlas_road_edges schema shape carries forward per tyre-round1.md:42 but lives in session memory).
Downstream consumers (do NOT scope in, but design the output for them): road_entry_directions per city (see road-entry-octants ticket; generation-cascade/round-3-notes.md:104), TerritorialStatus quantitative road-coverage thresholds (round-1-notes.md:329-338), per-economic_role hinterland fill (round-1-notes.md:324-327, BRIEF.md:279), prosperity/atlas overlays.
Cascade-first fit: this is direction step (2) — the econ layer drawn onto the world between settlements. Parent: T-750. Suggested deps: blocks the road-entry-octants ticket; references D-205 (provinces), D-210, D-211, D-212, D-213.
---
Implementation (2026-06-14):
SHIPPED — deterministic road-graph CORE in server/src/atlas/road_graph.rs (CascadeLayer::RoadGraph, stored on BodyWorldState.road_graph, never serialized):
1. MST force-connect (strict Kruskal over dist^2) + secondary links (30-60% body-scale band, >1.5x MST detour).
2. A* over the L1 terrain-cost field (ocean/lake impassable, river 1/2 cost, slope>=35deg 10x, else D-210 terrain_modification_cost) on a coarse 64-wide routing grid; equirectangular column wrap.
3. MaintenanceAuthority per edge (new generator.rs enum: Administrative/Corporate/Communal/Trade/Abandoned) from endpoint political archetypes + haul length.
4. Waypoint nodes at midpoints of edges > 12 routing cells.
5. Named-route identity join (named_route_id, designed-for; pool empty post-D-223 -> no-op).
+ junction detection (RoadGraph::high_connectivity_junctions, degree>=3).
Wired into the cascade (gen_queue requests CascadeLayer::RoadGraph) + BodyWorldState. 10 road_graph unit tests + a cascade e2e test; full lib suite green.
DEFERRED (-> T-1076, gated on the D-242 settlement model T-1074/T-1075):
- step 6 RailHeadFacing second pass — the adopted FoundingOrientation enum (D-213) has no RailHeadFacing; needs a D-213 amendment + Layer-4 skeleton_gen handling. Junction data is already exposed for it.
- hub-selection refinement (scaled-cap hubs, co-location collapse, hybrid minor-settlement attach) — needs baked population/specialization (D-242).
Decision D-242 filed (corp-HQ settlement model) — resolves why corp HQs were polluting the city pool as duplicate settlements.', 'in_progress', 'high', NULL, 'server', 'D-211', '2026-06-12 10:23:49', '2026-06-14 15:30:58', NULL, '55c0e158089122497cf34e9574595d50', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);