fix(simulation): PR #179 review round — F1-F4

F1: T-1112 amendment touch-point list gains the ZoningType PartialOrd/Ord derive (mirroring DistrictType's T-994 precedent) so the dominant_zoning declaration-order tie-break is computable. F2: stale terminal-layer doc on AtlasLayerRequest.up_to corrected to Region. F3: believability cascade_for_body bumped to Region — harness mirrors production depth, invariant documented. F4: end-to-end dispatch coverage — populated-regions test asserts regions + second handle_atlas_request returns region_grid; no-params sibling asserts regions empty. Suites: layer_proxy 10, believability 4, gen_queue 8, cascade 10.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 19:21:26 +02:00
co-authored by Claude Fable 5
parent bba14cb9ba
commit 9d92d8090f
3 changed files with 45 additions and 6 deletions
+1 -1
View File
@@ -1595,7 +1595,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser
}
```
- **(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).
- **(4) Six 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`); `ZoningType` gains `PartialOrd, Ord` derives mirroring `DistrictType`'s T-994 precedent (`server/src/simulation/generator.rs` — declaration order is not a stability-pinned wire format on either enum, so the additive derive is safe), which is what makes §1's lowest-declaration-order tie-break computable for `dominant_zoning` (PR #179 review: `DistrictType` already carries the derives, `ZoningType` does not — the tie rule itself is unchanged); `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`.
+9 -2
View File
@@ -497,7 +497,14 @@ pub fn seed_to_u64(seed: &str) -> u64 {
}
/// Resolve the committed inputs for `body_id` and run the real deterministic cascade
/// (through the road graph), returning the per-body world state to [`analyze`].
/// (through the full production depth, `CascadeLayer::Region`), returning the
/// per-body world state to [`analyze`].
///
/// The terminal layer here MIRRORS production (`gen_queue.rs::run_work_item`) by
/// ruling (PR #179 F3): a shallower harness run would silently hand future
/// believability conditions (D-245's climate-appropriateness is the natural
/// consumer of `state.regions`) an empty layer that production populates.
/// When production's terminal advances, advance this one with it.
///
/// `Err` if `systems.db` or the body's `heightmap.png` cannot be found, or the body
/// has no params — callers (the regression harness) may *skip* on that rather than
@@ -529,7 +536,7 @@ pub fn cascade_for_body(world_seed: u64, body_id: &str) -> Result<BodyWorldState
&cities,
None,
Some(&params),
CascadeLayer::RoadGraph,
CascadeLayer::Region,
);
Ok(snapshot.into_body_world_state())
}
+35 -3
View File
@@ -31,8 +31,8 @@ const DEFAULT_SEA_LEVEL: f32 = 0.3;
/// A client request for a body's generation layers (D-225).
///
/// `up_to` is a forward-compat seam that is **not yet honored**: `run_work_item`
/// (`gen_queue.rs`) currently runs the cascade through `CascadeLayer::RoadGraph`
/// (the terminal layer, T-1038) unconditionally on every request, ignoring this
/// (`gen_queue.rs`) currently runs the cascade through `CascadeLayer::Region`
/// (the terminal layer, T-1113) unconditionally on every request, ignoring this
/// field. Wiring per-request depth (and the partial caching it implies) is
/// deferred to #1021.
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -1104,7 +1104,11 @@ mod tests {
}
/// With body_params_reader wired, a cache miss enqueues an AnalyzeBody that
/// completes with populated `districts` (DistrictProfile layer ran).
/// completes with populated `districts` (DistrictProfile layer ran) AND
/// populated `regions` (Region layer ran — the production terminal,
/// T-1113). Then the completed state served back through
/// `handle_atlas_request` carries a `region_grid` — closing the full
/// dispatch → Ready → region_grid loop (PR #179 F4).
#[test]
fn body_params_reader_wired_produces_populated_regions() {
let mut cache = BodyWorldStateCache::new(CACHE_CAPACITY);
@@ -1142,6 +1146,29 @@ mod tests {
!body_state.districts.is_empty(),
"districts must be populated when body_params_reader is wired (T-1032 dispatch path)"
);
assert!(
!body_state.regions.is_empty(),
"regions must be populated when body_params_reader is wired (T-1113 dispatch path)"
);
// Serve the completed state back through the proxy: the cache-hit
// branch must build and include the region grid.
cache.insert(body_state);
let ready = handle_atlas_request(
&req("GJ1c"),
&mut cache,
&queue,
&resolver,
None,
Some(&params_reader),
42,
2,
);
assert_eq!(ready.status, AtlasLayerStatus::Ready);
assert!(
ready.region_grid.is_some(),
"a Ready response for a Region-populated body must carry region_grid"
);
}
/// Without body_params_reader (None), districts is empty — pre-T-1032 behaviour.
@@ -1181,5 +1208,10 @@ mod tests {
body_state.districts.is_empty(),
"districts must remain empty when no body_params_reader is wired"
);
assert!(
body_state.regions.is_empty(),
"regions must remain empty when no body_params_reader is wired \
(the Region layer gates on body_params, T-1113)"
);
}
}