chore(meta): T-1039/T-1043 -> done (PR #168 merged)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-16 13:05:04 +02:00
co-authored by Claude Opus 4.8
parent 387ee1b4c2
commit 718a97cda6
2 changed files with 99 additions and 0 deletions
@@ -2311,3 +2311,5 @@ INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, chang
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', 'done', NULL, '2026-06-16 09:38:09', '2026-06-16 09:38:09', '2026-06-16 09:38:09', NULL, '4330f81878fa8a6594547b56a89f8875', 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 ('06FBPQ034DKN4ENSGX5HTYTBJG', 'status', 'backlog', 'in_progress', NULL, '2026-06-16 09:42:26', '2026-06-16 09:42:26', '2026-06-16 09:42:26', NULL, '7bd85077ca660bad8cc34fc9b739ca61', 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 ('06FBPQ02KN9147H4Z12KJW7H1M', 'status', 'backlog', 'in_progress', NULL, '2026-06-16 09:42:26', '2026-06-16 09:42:26', '2026-06-16 09:42:26', NULL, 'd8f01ba3260dce078727d36de414b255', 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 ('06FBPQ02KN9147H4Z12KJW7H1M', 'status', 'in_progress', 'done', NULL, '2026-06-16 11:04:51', '2026-06-16 11:04:51', '2026-06-16 11:04:51', NULL, '44f484ff205bb503a5574ae47949f83f', 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 ('06FBPQ034DKN4ENSGX5HTYTBJG', 'status', 'in_progress', 'done', NULL, '2026-06-16 11:04:51', '2026-06-16 11:04:51', '2026-06-16 11:04:51', NULL, 'e0fc9bd58973f8b3bc3445f786ff6597', 2) ON CONFLICT(hash) DO NOTHING;
+97
View File
@@ -3016,3 +3016,100 @@ STALENESS / verified current locations: road_entry_directions field = generator.
COORDINATION: T-1039 and T-1043 both edit build_skeleton_work_item (plugin.rs:180-213). Do them as ONE combined branch/PR (single implementer), not two parallel edits to the same function. Thread road_entry_directions next to political_archetype/morphology_zone in the same override block.
Acceptance (unchanged): a dispatched settlement with 1 road connection produces 1 AccessKind::QuarterEdge node on the correct octant; the BlockJunction fallback fires only for genuinely isolated settlements.', 'in_progress', 'medium', NULL, 'server', 'D-234', '2026-06-12 10:23:50', '2026-06-16 09:42:26', NULL, 'dbe27cb938a21c2cd7754c4589e8032c', 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 ('06FBPQ034DKN4ENSGX5HTYTBJG', 'task', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Derive road_entry_directions at L3 and thread through the dispatch (interim: MST-neighbor bearings + water_bearing)', 'Workshopped-but-unticketed cascade work captured 2026-06-12 (cascade-refocus grounding pass). Full grounded scope follows.
Derive road_entry_directions at Layer 3 and thread them through the GenerateSkeleton dispatch. CityGenerationContext.road_entry_directions (server/src/simulation/generator.rs:1083-1084, compass octants 0=N..7=NW) is stubbed empty (city_context_reader.rs:478-479) and never overridden by the T-1022 dispatch, so derive_access_points (skeleton_gen.rs:1008-1036) never emits QuarterEdge access nodes every quarter falls back to reservation gates or a single central BlockJunction (:1029-1034), and the D-234 arterial graph has no outward connection to the world.
Workshop contract: the field is a per-city output of the inter-settlement road graph (generation-cascade/round-3-notes.md:104 ''Layer C: Road graph (road_entry_directions per city)''; burnelli-sheldon-round4.md:308). Full-fidelity derivation therefore lands with the inter-settlement-transport ticket: octant = bearing of each connecting road edge where it crosses the city footprint radius (D-204 footprint), de-duplicated per octant, retaining per-entry quality for the consumer rule below.
Interim derivation (no road graph required, deterministic, replace-don''t-rework): derive octants at L3 from (a) bearings to the settlement''s MST-neighbor placements (positions already in BodyWorldState.placements) and (b) the founding-orientation water bearing (TerrainAnalysis::water_bearing, 8-octant integer D-234 implementation note) for port/river settlements. Thread through build_skeleton_work_item (plugin.rs:179-212) next to the founding_orientation override.
Consumer rule to honour (already decided): the AdminFacing arrangement pattern picks its prestige edge as the edge facing the highest-quality road entry (paula-round2.md:428-433, paula-round3.md:320-322) so the octant list should be ordered or annotated by quality once the graph exists. Player-experience intent: the approach should present the city''s FoundingOrientation face (ozzie-round1.md:96-100).
Acceptance: a dispatched settlement with >=1 neighbor produces >=1 AccessKind::QuarterEdge node on the correct octant; the BlockJunction fallback fires only for genuinely isolated settlements. Parent: T-750. Blocked-by (soft): inter-settlement-transport ticket for full fidelity. Refs: D-234, D-213, D-204; T-990 (consumer, done), T-1022 (dispatch, done).
---
Refinement (2026-06-15, /whats-next Si pass) READY, with a SCOPE UPDATE. The "interim: MST-neighbor bearings" framing in the title is now obsolete.
T-1038 ROAD GRAPH HAS SHIPPED go full-fidelity, skip the interim. The inter-settlement road graph is live in the tree: road_graph.rs exists; the cascade runs CascadeLayer::RoadGraph (gen_queue.rs:383); BodyWorldState.road_graph (body_world_state.rs:91) is populated BEFORE GenCompletion::BodyAnalyzed fires, so at L3->L4 dispatch (plugin.rs:99-133) state.road_graph is already live. Therefore do NOT implement the MST-neighbor approximation derive octants directly from state.road_graph.edges for the city''s node (match by RoadNode.city_id), bearing = city.position -> neighbor.position, octant = bearing_deg / 45. (NB: T-1038''s ticket still shows in_progress though its code shipped consume the live road_graph regardless; flag if road_graph turns out not fully populated for some bodies.)
CURRENT REFS: CityGenerationContext.road_entry_directions = generator.rs:1107 (Vec<u8>, octants 0=N..7=NW); empty stub city_context_reader.rs:479; derive_access_points skeleton_gen.rs:1008-1035; BlockJunction fallback :1029-1034; build_skeleton_work_item plugin.rs:180-213 (insert next to the founding_orientation override at :194).
ISOLATED SETTLEMENTS (no road_graph edges): take the water bearing from placement.founding_orientation Coastal{facing_degrees} / RiverAligned{bearing_degrees} already encode it (octant = degrees/45). Note TerrainAnalysis::water_bearing (features.rs:133, u16 deg, 360=none) is NOT stored on CityPlacement/BodyWorldState, so reading founding_orientation is the clean access path (no attractor position-match needed).
CONSUMER: T-990 (done) emits AccessKind::QuarterEdge per octant in derive_access_points. The AdminFacing prestige-edge rule (pick the edge facing the highest-quality road entry) is forward-looking no AdminFacing code exists yet so order/annotate octants by quality only once that consumer lands.
ACCEPTANCE: a dispatched settlement whose city_id has >=1 road_graph edge emits >=1 AccessKind::QuarterEdge on the matching octant; BlockJunction fallback fires only for genuinely isolated settlements.
Governing: D-234, D-213, D-204 (footprint radius). T-990 (consumer, done), T-1022 (dispatch, done), T-1038 (road graph, shipped).
OVERLAP w/ T-1039: both edit build_skeleton_work_item (plugin.rs), city_context_reader.rs, CityGenerationContext (generator.rs). Implement both in ONE pass (one agent), not parallel.
---
Refinement (2026-06-15, /whats-next Si pass) locked decision + staleness corrections. Original scope authored 2026-06-12.
DECISION road_entry_directions source: **USE T-1038''s RoadGraph (full fidelity). LOCKED.** The interim MST-neighbor path is DROPPED.
- T-1038 has shipped a populated `BodyWorldState.road_graph` (RoadGraph) with `RoadNode.city_id` and `RoadEdge.path` (A*-routed polylines in heightmap working-grid coords), available at dispatch time (BodyWorldState is fully populated before the GenCompletion branch in plugin.rs).
- Derive each entry octant from the bearing of the connecting road edge where it crosses the city footprint radius (D-204 footprint_radius), de-duplicated per octant the D-234 "full fidelity" definition. Order/annotate by edge quality so the AdminFacing consumer (paula-round2.md:428-433: prestige edge faces the highest-quality road entry) can pick correctly.
- COUPLING ACCEPTED: T-1038 is still in_progress; if its RoadGraph field shapes shift, this consumer adjusts. (User accepted this tradeoff over the throwaway interim path.)
water_bearing: NOT a CityPlacement field it is encoded inside the founding_orientation enum arms (Coastal{facing_degrees}, RiverAligned{bearing_degrees}); TerrainAnalysis is not stored on BodyWorldState (T-1044 TODO). With the RoadGraph as the primary source, water_bearing is secondary; if used as a supplementary octant for port/river settlements, extract the bearing from those two arms only Cardinal/Free/TerrainFollowing variants get no water octant.
STALENESS / verified current locations: road_entry_directions field = generator.rs:1107; stub = city_context_reader.rs:478-479; derive_access_points = skeleton_gen.rs:1008-1036 (BlockJunction fallback :1029-1034); build_skeleton_work_item = plugin.rs:180-213. (Confirmed unchanged by T-1077/T-1078.)
COORDINATION: T-1039 and T-1043 both edit build_skeleton_work_item (plugin.rs:180-213). Do them as ONE combined branch/PR (single implementer), not two parallel edits to the same function. Thread road_entry_directions next to political_archetype/morphology_zone in the same override block.
Acceptance (unchanged): a dispatched settlement with 1 road connection produces 1 AccessKind::QuarterEdge node on the correct octant; the BlockJunction fallback fires only for genuinely isolated settlements.', 'done', 'medium', NULL, 'server', 'D-234', '2026-06-12 10:23:50', '2026-06-16 11:04:51', NULL, 'b4e826fe18cf961d6800470e396b3141', 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 ('06FBPQ02KN9147H4Z12KJW7H1M', 'task', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Thread political_archetype / arrangement_pattern / morphology_zone through the L3→L4 GenerateSkeleton dispatch', 'Workshopped-but-unticketed cascade work captured 2026-06-12 (cascade-refocus grounding pass). Full grounded scope follows.
Thread the remaining Layer-3 character fields through the L3->L4 GenerateSkeleton dispatch. T-1022 wired the dispatch, but build_skeleton_work_item (server/src/atlas/plugin.rs:179-212) overrides only founding_orientation (:192-193); political_archetype stays the Commission stub (city_context_reader.rs:471-474) and morphology_zone stays AlluvialPlain (:487-488) even though:
- CityPlacement already carries the real political_archetype (D-214, derived from TerritorialStatus + economic_role at attractor_matching.rs:595-602) and arrangement_pattern (D-215, city_character at :282-293) fields :49-64;
- the covering region''s real morphology_zone (D-239 §6 frozen vocab, T-1027, region_profile.rs:180/:499) is available from snapshot.layer_region, populated in the same cascade run (cascade.rs:238-240) map placement.position to its covering region cell.
Consequences of the stubs: skeleton_gen''s morphology gating (D-234a Topology selection) always takes the AlluvialPlain Prim-MST mesh path fjord/delta cities get plains street networks; block_irregularity/layout-mode always sees Commission (skeleton_gen.rs:68-77). D-234''s implementation note explicitly lists this threading as the pending dependency of the (done, tested, dormant) morphology-gated street rules.
Also decide arrangement_pattern''s path: CityGenerationContext has NO arrangement_pattern field (generator.rs:1078 has political_archetype only; ArrangementPattern :442 is defined but unconsumed at L4). Either (a) add the field and have skeleton_gen consume the D-215 explicit pattern, or (b) record that L4 derives layout from archetype alone and the placement''s pattern is Atlas/UI-only. Option (a) matches the workshop intent (ozzie-round1.md:112-118 backs explicit arrangement).
Miri''s queryability requirement (tile-derivation-contract/tyre-round1.md:308-311) morphology_zone readable at Atlas-generation time is satisfied by reading from BodyWorldState regions, no recompute.
Acceptance: dispatch a body with a FjordWall-region coastal placement in a CorpTerritory province -> the generated quarter uses Ribbon topology and CompanyTown-class layout, not mesh+Commission. Parent: T-750. Refs: D-214, D-215, D-228, D-234, D-239 §6; T-1022 (predecessor), T-983, T-1014 (adjacent, different fields).
---
Refinement (2026-06-15, /whats-next Si pass) READY. Supersedes stale region-scale refs (authored 2026-06-12, pre-T-1077 RegionProfile->DistrictProfile rename + T-1078). Current tree:
MORPHOLOGY CARRIER MOVED: morphology_zone now lives on DistrictProfile.morphology_zone (district_profile.rs:193, 2km carrier), NOT region_profile.rs. Snapshot field is layer_district (not layer_region gone); map is BodyWorldState.districts (body_world_state.rs:102), populated by cascade (cascade.rs:270, converted :110-126). The ticket''s snapshot.layer_region / region_profile.rs:180/:499 / "covering region" refs are stale.
PLACEMENT->DISTRICT MAPPING: CityPlacement.position is heightmap pixel (row,col) (u16,u16) (attractor_matching.rs:51). DistrictPos = position / CELLS_PER_DISTRICT (8). No helper yet add inline. Inverse at district_profile.rs:1023-1026.
STUB SITES: political_archetype stub city_context_reader.rs:474 (Commission); morphology_zone stub :488 (AlluvialPlain); both in context_from_read_set() (:462). build_skeleton_work_item (plugin.rs:180-213) threads only founding_orientation (:194) and does NOT receive the districts map pass &state.districts in (available in the GenCompletion::BodyAnalyzed arm, plugin.rs:101) or resolve before the call; fallback AlluvialPlain on miss.
THREE OVERRIDES:
- political_archetype: one-liner context.political_archetype = placement.political_archetype (real D-214 value, attractor_matching.rs:58/:602), parallel to founding_orientation at plugin.rs:194.
- morphology_zone: state.districts.get(&(pos.0/8, pos.1/8)).map(|d| d.morphology_zone).unwrap_or(AlluvialPlain).
- arrangement_pattern: OPTION (a) (D-215-backed) CityGenerationContext has NO arrangement_pattern field (generator.rs:1098-1124); add it, thread placement.arrangement_pattern (attractor_matching.rs:60/:629), have skeleton_gen consume it (doesn''t yet).
CONSUMERS LIVE-BUT-DORMANT: street_topology() (skeleton_gen.rs:1048) gates on MorphologyZone (Ribbon: Fjord/CliffCoast/MountainPass/Alpine/ValleyFloor; HubSpoke: Delta/Estuarine/OpenOcean/Lake/DuneStrand/TidalFlat; Mesh: AlluvialPlain/MeanderReach/RiverBank/BraidedPlain/Wetland/Volcanic); derive_layout_mode (:235)+block_irregularity (:68) gate on political_archetype. T-989/T-990 done always get AlluvialPlain+Commission until this lands.
ACCEPTANCE: FjordWall-region coastal placement in CorpTerritory -> Ribbon topology + CompanyTown/CampusGrid layout, not mesh+Commission.
Governing: D-234 (primary), D-214, D-215 (-> option a), D-239 sec 6 (frozen vocab, now district_profile.rs), D-243.
OVERLAP w/ T-1043: both edit build_skeleton_work_item (plugin.rs), city_context_reader.rs, CityGenerationContext (generator.rs), skeleton_gen consumers. Implement together in ONE pass (one agent), not parallel.
---
Refinement (2026-06-15, /whats-next Si pass) staleness corrections + locked decision. Original scope authored 2026-06-12, BEFORE T-1077 (RegionProfileDistrictProfile rename) and T-1078 (region climate tier, merged today). Corrected refs below.
STALENESS CORRECTIONS (the description''s code refs are pre-T-1077/T-1078):
- morphology_zone lives on **DistrictProfile** (server/src/atlas/district_profile.rs:193), NOT on RegionProfile (region_profile.rs:155 is a pure climate carrier no morphology_zone). The ticket''s "region_profile.rs:180/:499" and "snapshot.layer_region" are wrong.
- Access path at dispatch: `state.districts.get(&district_pos)` on BodyWorldState.districts (server/src/atlas/body_world_state.rs:102). The cascade snapshot field is `layer_district` (cascade.rs:79), not `layer_region`.
- PositionDistrictPos: CityPlacement.position is (u16,u16) heightmap-pixel coords (attractor_matching.rs:51). Convert via the cells-per-district factor (currently a LOCAL `CELLS_PER_REGION = 8` in cascade.rs:260 pre-D-243 naming). Do NOT hardcode 8 in plugin.rs: export a helper/const (prefer reusing server/src/atlas/scale.rs addressing) and call it from the dispatch.
- Verified current locations: build_skeleton_work_item = plugin.rs:180-213 (founding_orientation override is the threading site); political_archetype stub ~city_context_reader.rs:471-474; political_archetype derivation = attractor_matching.rs:602; CityPlacement fields ~attractor_matching.rs:49-64.
DECISION arrangement_pattern path: **OPTION (b), re-derive. LOCKED.**
- skeleton_gen re-derives via the single shared pure fn `attractor_matching::arrangement_pattern(&political_archetype, &economic_role)` (attractor_matching.rs:629). Both inputs are available at L4: economic_role is ALREADY threaded (plugin.rs:189 reads it, :209 passes it); political_archetype is threaded by THIS ticket. NO new CityGenerationContext field; NO data-contract amendment.
- Rationale: arrangement_pattern is a pure TOTAL function (6-way archetype match + the `economic_role=="transit_hub"` override) no seed/RNG/DB/clock/float. Re-derivation is provably identical to L3; cheaper to compute than to store; aligns with the "DB is only a cache, everything re-derivable from seed" principle (no stored derived spatial verdict).
- **HARDENING REQUIRED (the drift tripwire):** add a parity test asserting, for representative (archetype, economic_role) pairs, `arrangement_pattern(&placement.political_archetype, &placement.economic_role) == placement.arrangement_pattern` (CityPlacement already stores the L3-computed value at attractor_matching.rs:60). This locks L3L4 and trips the instant anyone changes arrangement_pattern''s input set. Optionally debug_assert the same equality in the dispatch. Never inline a second copy of the derivation both sites call the one function.
COORDINATION: T-1039 and T-1043 both edit build_skeleton_work_item (plugin.rs:180-213). Do them as ONE combined branch/PR (single implementer), not two parallel edits to the same function.
Acceptance (unchanged): dispatch a FjordWall-region coastal placement in a CorpTerritory province the generated quarter uses Ribbon topology and CompanyTown-class layout, not the AlluvialPlain mesh + Commission stub path.', 'done', 'high', NULL, 'server', 'D-234', '2026-06-12 10:23:49', '2026-06-16 11:04:51', NULL, 'faf93cbd0ceefe3a8b1052659931de23', 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);