docs(meta): D-226 amendment — QuarterFootprintLayer design (T-1112; Tyre data shape + Araminta encoding)
Aggregates-only layer keyed by city_id (QuarterWorldState carries no spatial position — QuarterId is a content-addressed hash; a 512m quarter is sub-pixel at planetary projection, so no outline geometry by construction). Five quantized fields (density_avg_pct, dominant_district_type, dominant_zoning, landmark_count, corridor_count); scaled-glyph encoding anchored on the L3 settlement dot (shape=dominant type, color=density ramp); landmark/corridor counts tooltip-only per the D-226(d) aggregate-stats ceiling; five wiring touch points named for the follow-up ticket. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1560,11 +1560,48 @@ Technical foundation decisions that constrain implementation: engine, client-ser
|
||||
- **(c) Legend panel stands.** The left-side legend (shape/color key for attractor types and sub-biomes) remains an unshipped, in-scope deliverable of item (3)/T-960.
|
||||
- **(d) No tile-level Atlas map.** The planetary Atlas maps generation layers down to settlement/quarter-skeleton granularity only; chunk/tile/voxel fill (L5) is verified by the believability/derivation harnesses and inspected **in-world in Phase 5** — never as a planetary map layer (at most aggregate stats). Ratifies T-1046's implementation precedent; matching D-191 amendment + CLAUDE.md Phase-4 wording updated the same day.
|
||||
|
||||
**Amended 2026-07-16 (T-1112 — coarse quarter-footprint Atlas layer, the L4 skeleton on the planetary map):** the fourth generation-cascade layer to land on the Atlas, sitting between T-1046's `district_grid` (D-239 coarse morphology) and item (d)'s hard ceiling. Design only — implementation is a follow-up ticket (touch points named in §4 below, none built here).
|
||||
|
||||
- **(1) `QuarterFootprintLayer` data shape.** Source of truth is `BodyWorldState.quarters: BTreeMap<QuarterId, QuarterWorldState>` (`QuarterWorldState { skeleton: QuarterSkeleton, block_tags }`), where `QuarterSkeleton.blocks: [[BlockSkeleton; 4]; 4]` carries `zoning: ZoningType`, `district_type: DistrictType`, `density_pct: u8`, `landmark: Option<LandmarkSlot>` per block, plus `corridors: Vec<CorridorSpine>` at the quarter level. Critically, **`QuarterWorldState` carries no independent spatial position** — `QuarterId` is a content-addressable hash (`SeedChain::for_body(world_seed, body_id).derive(SeedDomain::Layer4Quarter, city_id).seed()`, `plugin.rs::build_skeleton_work_item`), not a coordinate. The only spatial anchor a quarter has is the `city_id` it was generated for — which `SettlementLayer`/`CityPlacement` (T-960 §2, D-211) already carries as `(city_id, position)`. So the layer is **keyed by `city_id`, joined to a quarter by recomputing the same deterministic `quarter_id` derivation** the dispatch path already uses (a pure function of already-public inputs — no new field needed anywhere) and looking it up in `state.quarters`. This also means **no city-outline geometry is derived or served** — at planetary-map projection a 512 m quarter is roughly 1/80th of a heightmap pixel (a district ≈ 2048 m is already sub-pixel at ~40–78 km/px, D-243), so there is no real silhouette to trace; the layer is aggregate stats anchored at the existing L3 settlement position, mirroring `RoadGraphLayer`/`SettlementLayer`'s established "trim the internal struct to what an overlay needs" pattern rather than inventing outline geometry with no data behind it.
|
||||
|
||||
Per settlement-with-quarters, the served aggregate is:
|
||||
```rust
|
||||
pub struct QuarterFootprintEntry {
|
||||
pub city_id: u64,
|
||||
pub density_avg_pct: u8, // basis-point mean of BlockSkeleton.density_pct, 16 blocks
|
||||
pub dominant_district_type: DistrictType, // mode across 16 blocks; ties → lowest declaration-order variant
|
||||
pub dominant_zoning: ZoningType, // mode across 16 blocks; same tie rule
|
||||
pub landmark_count: u8, // count of Some(LandmarkSlot) across 16 blocks (max 16)
|
||||
pub corridor_count: u8, // QuarterSkeleton.corridors.len(), clamped to u8
|
||||
}
|
||||
pub struct QuarterFootprintLayer {
|
||||
pub entries: BTreeMap<u64, QuarterFootprintEntry>, // keyed by city_id, D-010 determinism
|
||||
}
|
||||
```
|
||||
Five fields earn their place: `density_avg_pct` and the two dominant-mode fields are Araminta's color/shape encoding inputs (§3); `landmark_count`/`corridor_count` are inspection-only (the D-226(d) ceiling forbids them as a map-visible channel — they surface in the existing city-click sidebar instead, an `ImplantDataRow` addition, not a new draw call). Rejected from the set: per-block detail (violates the ceiling outright), `reservations`/`social_sites` counts (no consumer identified — Araminta's encoding doesn't need them and nothing else asked), and a float density (D-010 integer discipline — `density_pct` is already `u8` basis-point-flavored on the source struct, so the mean stays `u8`, no `f32` anywhere on the wire). `dominant_district_type`/`dominant_zoning` serialize as their named enum variant (serde default), following the `RoadGraphLayer`/`SettlementLayer` precedent (`RoadNodeKind`, `MaintenanceAuthority` — neither `repr(u8)`-pinned, serialized as names) rather than `district_grid`'s `as u8` byte-packing, which was specific to a dense `cols×rows` array where `MorphologyZone` is deliberately `repr(u8)`-pinned for that purpose; a handful of per-settlement aggregate fields have no such packing need. `BTreeMap<u64, _>` throughout for D-010 determinism, matching `block_tags`' own `BTreeMap<(u8,u8), _>` precedent on the source struct.
|
||||
|
||||
- **(2) Hard ceiling (binding).** This layer is the concrete instance of item (d)'s "at most aggregate stats" clause: five scalar fields per settlement, quantized `u8`, no per-block zoning/street/tag detail ever reaches the wire, and no chunk/tile/voxel data is touched (this layer reads only `QuarterSkeleton`/`BlockSkeleton`, never `FillChunk` output — a different generation phase entirely, D-230). If a future ticket wants finer planetary-scale detail than this, the answer is "no" per item (d), not "extend this struct" — the settlement/quarter-skeleton granularity ceiling applies to this layer by construction, not by restraint that could erode.
|
||||
|
||||
- **(3) Overlay encoding (Araminta).** New overlay id `gen_l4_quarters` (label `QTR`, `group: "toggle"`, matching the `gen_*` convention in `OVERLAY_DEFS`). **No outline is drawn** (per §1) — the layer is a **density-scaled glyph anchored at the existing L3 settlement position**, drawn in the same pass immediately after `_draw_gen_settlements` so it reads as "on top of" the city dot it annotates. Square side scales off `density_avg_pct` (e.g. `4.0 + density_avg * 6.0` px at 1.0 zoom, clamped `[4.0, 12.0]`) — dense build reads as a bigger block, sparse as smaller, without pretending to show real shape. **Shape carries identity, color carries intensity** (the same convention as `_sub_biome_color`/`MORPHOLOGY_COLORS`): shape = `dominant_district_type`, a small corner-notch glyph family on the filled square (plain = mixed/no clear dominant, corner tab top-right = commercial, corner tab bottom-right = industrial, small diamond cutout center = civic/landmark — capped at 3–4 variants, a coarse skeleton read, not a legend of every `DistrictType`); color = `density_avg_pct` on a single-hue intensity ramp within the existing settlement-gold family (`COLOR_SETTLEMENT` → `COLOR_SETTLEMENT_CAPITAL`-adjacent bright gold at high density), so the new layer reads as *part of* the settlement-marker family rather than a competing hue, since it always co-renders beside `gen_l3_settlements`. `landmark_count`/`corridor_count` are **not** a visual channel (§2's ceiling) — they surface as `ImplantDataRow`s in the existing city-click sidebar panel. Zoom gating reuses `SETTLEMENT_LABEL_MIN_ZOOM = 2.0` (no new threshold): below it, glyph draws at minimum size with color only (the notch is illegible at a few px anyway); at/above it, full size with the dominant-type notch visible. Legend entry (`GENERATION_LEGEND`):
|
||||
```gdscript
|
||||
{
|
||||
"overlay_id": "gen_l4_quarters",
|
||||
"title": "QUARTER FOOTPRINT — L4 (color = density, shape = dominant type)",
|
||||
"rows": [
|
||||
{"glyph": "▪", "color": Color(0.55, 0.48, 0.30, 0.6), "label": "low density"},
|
||||
{"glyph": "▪", "color": Color(0.94, 0.82, 0.38, 1.0), "label": "high density"},
|
||||
{"glyph": "◪", "color": Color.TRANSPARENT, "label": "dominant type (corner tab)"},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
- **(4) Five wiring touch points (follow-up ticket, not built here).** `AtlasLayerResponse` gains `quarter_footprints: Option<QuarterFootprintLayer>` + a `build_quarter_footprint_layer(state, placements)` function mirroring `build_district_grid`'s "empty source → `None`" contract (`server/src/atlas/layer_proxy.rs`); `protocol.gd` passthrough for the new field (mirrors the existing `district_grid`/`road_graph`/`settlements` fields); a `gen_l4_quarters` entry in `OVERLAY_DEFS` (`client/ui/implant/apps/atlas/atlas_viewer.gd`); a `_draw_gen_l4_quarters()` function imitating `_draw_gen_district`'s "read `viewer.get_generation_quarter_footprints()`, guard on `Dictionary`, draw" shape (`atlas_marker_overlay.gd`); and the `GENERATION_LEGEND` entry above (`atlas_legend_panel.gd`). `layer_proxy.rs` was mid-concurrent-edit for T-1113's `region_grid` addition at design time — read-only pass, no conflict expected (both land as new sibling `Option` fields on `AtlasLayerResponse`, following the same one-field-per-layer pattern the growth-ceiling note on that struct already anticipates naming T-1112 and T-1113 as the last two candidates).
|
||||
|
||||
- **Rationale:** Reusing the real UI — rather than a parallel offline renderer or dumped files — means the debug/review surface never diverges from what ships, and a dropped artifact can't go stale. Agent-navigability converts qualitative "does the synthesis look natural?" review from a manual eyeball pass into an automatable sweep that flags the few outliers for a human. The harness rides seams that already exist (`TickRate::Paused`, the paused-allowlist, `gameplay_occluded`, the bridge framing, the `run-visual` capture primitive) — a naming-and-contract exercise, not a new subsystem.
|
||||
- **New surface:** server pause-gating (run-conditions on the world phases keyed to a pause command); client `AtlasAgentInterface` (`observe`/`act`, Control-tree walker) + its local transport; the generation overlay rendering + selector + legend; interactive capture wired to `run-visual`.
|
||||
- **Implementation:** Phase 4 (epic T-750), built bottom-up — auto-pause substrate, T-969 proxy (D-225), T-960 viewer, agent channel, agent capture. Geography is the first consumer.
|
||||
- **Raised by:** Jeroen + Claude (design), with Tyre (channel/pause/headless architecture) + Araminta (overlay encoding + affordance UX), 2026-05-24.
|
||||
- **Cross-reference:** [D-225](#d-225) (layer-stream proxy — the data path), [D-166](#d-166) (per-layer Atlas progress viewer), [D-191](#d-191) (Atlas viewer), [D-169](#d-169) / [D-170](#d-170) (implant components / HUD occlusion — `gameplay_occluded` trigger), [D-200](#d-200) / [D-203](#d-203) (execution tiers / LRU cache), Q-099 (mod content catalog), `tests/run-visual` (capture primitive), `save_state.rs` (save-inspection consumer)
|
||||
- **Cross-reference:** [D-225](#d-225) (layer-stream proxy — the data path), [D-166](#d-166) (per-layer Atlas progress viewer), [D-191](#d-191) (Atlas viewer), [D-169](#d-169) / [D-170](#d-170) (implant components / HUD occlusion — `gameplay_occluded` trigger), [D-200](#d-200) / [D-203](#d-203) (execution tiers / LRU cache), Q-099 (mod content catalog), `tests/run-visual` (capture primitive), `save_state.rs` (save-inspection consumer). **T-1112 amendment additionally:** [D-222](#d-222) (Quarter terminology — the 512m unit this layer surfaces), [D-234](#d-234) (footprint geometry — the block-subdivision source the aggregates summarize), [D-243](#d-243) (quarter = 512m rung, and the containment ladder that makes a quarter sub-pixel at planetary projection — Araminta's no-outline rationale), [D-010](#d-010) (determinism — integer-only aggregates, `BTreeMap` keying).
|
||||
- **Dissent:** None
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user