@@ -21,8 +21,8 @@ use crate::atlas::gen_queue::{GenPriority, GenWorkItem, GenerationQueue};
use crate ::atlas ::layer1 ::Layer1Output ;
use crate ::atlas ::road_graph ::RoadNodeKind ;
use crate ::atlas ::source_resolver ::{ BodySourceResolver , SourceResolveError } ;
use crate ::seed ::SeedChain ;
use crate ::simulation ::generator ::{ AttractorType , MaintenanceAuthority } ;
use crate ::seed ::{ SeedChain , SeedDomain } ;
use crate ::simulation ::generator ::{ AttractorType , DistrictType , MaintenanceAuthority , ZoningType } ;
/// Fallback sea level when the heightmap PNG carries no `sea_level` tEXt chunk
/// (the loader prefers the chunk; this is only the floor).
@@ -71,12 +71,16 @@ pub struct DistrictGridLayer {
/// A layer response: the computed `Layer1Output` + the coarse district grid
/// (D-225, T-1046) + the road-graph and settlement overlays (T-960 §1/§2) +
/// the region climate grid (T-1113), or a non-ready status.
/// the region climate grid (T-1113) + the quarter-footprint overlay (T-1112,
/// T-1119), or a non-ready status.
///
/// Growth ceiling (governance-bounded): the one-`Option`-field-per-layer
/// pattern tops out around six fields — D-226's 2026-07-13 amendment (d)
/// rules out any L5/tile Atlas layer ever, leaving T-1112 (quarter
/// footprints) as the only remaining candidate.
/// pattern tops out at six fields — D-226's 2026-07-13 amendment (d) rules
/// out any L5/tile Atlas layer ever, and `quarter_footprints` below is the
/// last candidate the 2026-07-16 T-1112 amendment named. **The budget is now
/// consumed** — a seventh field is not a naming exercise like the six before
/// it; what (if anything) carries a future generation-layer addition is a
/// T-1124 design-pass question, not something to resolve here.
#[ derive(Debug, Clone, Serialize, Deserialize) ]
pub struct AtlasLayerResponse {
pub body_id : String ,
@@ -97,6 +101,13 @@ pub struct AtlasLayerResponse {
/// The region climate grid for the Atlas overlay (D-243 §3, T-1113).
/// `Some` on a cache hit once the Region layer has run; `None` otherwise.
pub region_grid : Option < RegionGridLayer > ,
/// The quarter-footprint overlay (D-226 T-1112 amendment, T-1119). `Some`
/// on a cache hit once at least one settlement's quarter skeleton has been
/// generated (`state.quarters` non-empty); `None` otherwise, including a
/// body with placed settlements whose quarters haven't finished the async
/// `GenerateSkeleton` pass yet (skeleton generation runs at `Low` priority
/// after the body's own `Ready` snapshot is cached — see `plugin.rs`).
pub quarter_footprints : Option < QuarterFootprintLayer > ,
}
/// Build the coarse [`DistrictGridLayer`] from a body's cached state (T-1046).
@@ -209,6 +220,148 @@ pub fn build_region_grid(
} )
}
// ---------------------------------------------------------------------------
// QuarterFootprintLayer (D-226 T-1112 amendment, T-1119)
// ---------------------------------------------------------------------------
/// Per-settlement aggregate over one quarter's 4× 4 `BlockSkeleton` grid, for
/// the Atlas quarter-footprint overlay (D-226 T-1112 amendment §1). Five
/// scalar fields earn their place per the amendment's hard ceiling (§2): no
/// per-block zoning/street/tag detail ever reaches the wire, and no
/// chunk/tile/voxel data is touched.
#[ derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize) ]
pub struct QuarterFootprintEntry {
pub city_id : u64 ,
/// Basis-point mean of `BlockSkeleton.density_pct` across the 16 blocks
/// (integer division, D-010 — no `f32` on the wire).
pub density_avg_pct : u8 ,
/// Mode `DistrictType` across the 16 blocks; ties resolve to the lowest
/// declaration-order variant (the `Ord` derive on `DistrictType`, T-994).
pub dominant_district_type : DistrictType ,
/// Mode `ZoningType` across the 16 blocks; same tie rule (the `Ord`
/// derive added on `ZoningType` for this ticket, T-1119).
pub dominant_zoning : ZoningType ,
/// Count of blocks with `landmark: Some(_)` across the 16 blocks (max 16).
/// Tooltip/sidebar-only per the D-226(d) ceiling — never a map-visible
/// channel (§2).
pub landmark_count : u8 ,
/// `QuarterSkeleton.corridors.len()`, clamped to `u8`. Tooltip/sidebar-only,
/// same ceiling as `landmark_count`.
pub corridor_count : u8 ,
}
/// The quarter-footprint overlay for one body (D-226 T-1112 amendment §1),
/// keyed by `city_id` — a quarter carries no independent spatial position of
/// its own (`QuarterId` is a content-addressable hash, not a coordinate), so
/// the layer anchors at the existing L3 settlement position client-side and
/// this map only needs to answer "does this settlement have quarter data, and
/// if so what does it aggregate to".
#[ derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize) ]
pub struct QuarterFootprintLayer {
/// `BTreeMap` for D-010 determinism, matching `RegionGridLayer`'s and the
/// source `QuarterWorldState.block_tags`' own `BTreeMap` precedent.
pub entries : std ::collections ::BTreeMap < u64 , QuarterFootprintEntry > ,
}
/// Aggregate one quarter's 16 `BlockSkeleton`s into a [`QuarterFootprintEntry`]
/// for `city_id`.
fn aggregate_quarter_footprint (
city_id : u64 ,
skeleton : & crate ::simulation ::generator ::QuarterSkeleton ,
) -> QuarterFootprintEntry {
let blocks : Vec < & crate ::simulation ::generator ::BlockSkeleton > =
skeleton . blocks . iter ( ) . flatten ( ) . collect ( ) ;
let n = blocks . len ( ) as u32 ; // always 16 (the fixed 4× 4 grid) — computed
// rather than hardcoded so the mean formula
// stays correct if the grid shape ever changes.
let density_sum : u32 = blocks . iter ( ) . map ( | b | b . density_pct as u32 ) . sum ( ) ;
let density_avg_pct = if n = = 0 { 0 } else { ( density_sum / n ) as u8 } ;
let dominant_district_type = mode_by_declaration_order ( blocks . iter ( ) . map ( | b | & b . district_type ) )
. cloned ( )
. unwrap_or_default ( ) ;
let dominant_zoning = mode_by_declaration_order ( blocks . iter ( ) . map ( | b | & b . zoning ) )
. cloned ( )
. unwrap_or_default ( ) ;
let landmark_count = blocks . iter ( ) . filter ( | b | b . landmark . is_some ( ) ) . count ( ) as u8 ;
let corridor_count = skeleton . corridors . len ( ) . min ( u8 ::MAX as usize ) as u8 ;
QuarterFootprintEntry {
city_id ,
density_avg_pct ,
dominant_district_type ,
dominant_zoning ,
landmark_count ,
corridor_count ,
}
}
/// Mode of an `Ord` value over an iterator, tie-broken by lowest declaration
/// order (i.e. the `Ord`-smallest value among the tied-for-max-count values).
/// `None` for an empty iterator.
///
/// **Not** `counts.into_iter().max_by_key(...)`: `Iterator::max_by_key`
/// returns the *last* maximum on a tie (documented behaviour), which is the
/// opposite of what's needed here. `BTreeMap` iterates keys in ascending
/// `Ord` order (= declaration order for these enums), so walking forward and
/// only replacing the running best on a *strictly greater* count keeps the
/// first-seen — i.e. lowest-declaration-order — winner on every tie.
fn mode_by_declaration_order < ' a , T : Ord + ' a > (
values : impl Iterator < Item = & ' a T > ,
) -> Option < & ' a T > {
let mut counts : std ::collections ::BTreeMap < & ' a T , u32 > = std ::collections ::BTreeMap ::new ( ) ;
for v in values {
* counts . entry ( v ) . or_insert ( 0 ) + = 1 ;
}
let mut best : Option < ( & ' a T , u32 ) > = None ;
for ( v , count ) in counts {
match best {
Some ( ( _ , best_count ) ) if count < = best_count = > { }
_ = > best = Some ( ( v , count ) ) ,
}
}
best . map ( | ( v , _ ) | v )
}
/// Build the [`QuarterFootprintLayer`] from a body's cached state (T-1119).
/// Returns `None` when the Quarter-skeleton layer has not run for any
/// settlement (empty `state.quarters`).
///
/// `state.quarters` carries no independent spatial position — the only
/// spatial anchor a quarter has is the `city_id` it was generated for
/// (D-226 T-1112 amendment §1). So this recomputes the same deterministic
/// `QuarterId` derivation the L3→L4 dispatch path uses
/// (`SeedChain::for_body(world_seed, body_id).derive(SeedDomain::Layer4Quarter,
/// city_id).seed()`, `plugin.rs::build_skeleton_work_item`) for every placed
/// settlement and looks it up in `state.quarters`. A placement whose derived
/// id isn't found (skeleton generation is async, dispatched at `Low` priority
/// after the body's `Ready` snapshot is already cached — `plugin.rs`) is
/// skipped, not defaulted: an absent quarter is not a zero-footprint quarter.
pub fn build_quarter_footprint_layer (
state : & BodyWorldState ,
world_seed : u64 ,
) -> Option < QuarterFootprintLayer > {
if state . quarters . is_empty ( ) {
return None ;
}
let body_chain = SeedChain ::for_body ( world_seed , & state . body_id ) ;
let mut entries = std ::collections ::BTreeMap ::new ( ) ;
for placement in & state . placements {
let quarter_id = body_chain
. derive ( SeedDomain ::Layer4Quarter , placement . city_id )
. seed ( ) ;
if let Some ( quarter_state ) = state . quarters . get ( & quarter_id ) {
entries . insert (
placement . city_id ,
aggregate_quarter_footprint ( placement . city_id , & quarter_state . skeleton ) ,
) ;
}
}
Some ( QuarterFootprintLayer { entries } )
}
// ---------------------------------------------------------------------------
// RoadGraphLayer (T-960 §1, T-1038)
// ---------------------------------------------------------------------------
@@ -422,6 +575,7 @@ pub fn handle_atlas_request(
let road_graph = build_road_graph_layer ( state ) ;
let settlements = build_settlement_layer ( state ) ;
let region_grid = build_region_grid ( state ) ;
let quarter_footprints = build_quarter_footprint_layer ( state , world_seed ) ;
return AtlasLayerResponse {
body_id : req . body_id . clone ( ) ,
status : AtlasLayerStatus ::Ready ,
@@ -430,6 +584,7 @@ pub fn handle_atlas_request(
road_graph ,
settlements ,
region_grid ,
quarter_footprints ,
} ;
}
@@ -503,6 +658,7 @@ pub fn handle_atlas_request(
road_graph : None ,
settlements : None ,
region_grid : None ,
quarter_footprints : None ,
}
}
// Unknown / no terrain → re-requesting won't help.
@@ -515,6 +671,7 @@ pub fn handle_atlas_request(
road_graph : None ,
settlements : None ,
region_grid : None ,
quarter_footprints : None ,
} ,
Err ( e ) = > AtlasLayerResponse {
body_id : req . body_id . clone ( ) ,
@@ -524,6 +681,7 @@ pub fn handle_atlas_request(
road_graph : None ,
settlements : None ,
region_grid : None ,
quarter_footprints : None ,
} ,
}
}
@@ -656,6 +814,338 @@ mod tests {
assert_eq! ( grid . moisture_q [ 1 ] , 5 ) ;
}
/// T-1119: `build_quarter_footprint_layer` returns `None` when no
/// settlement's quarter skeleton has been generated (`state.quarters`
/// empty), mirroring `build_district_grid`/`build_region_grid`'s
/// "unrun layer → None" contract.
#[ test ]
fn quarter_footprint_layer_none_when_quarters_empty ( ) {
let state = blank_state ( " GJ1c " ) ;
assert! ( build_quarter_footprint_layer ( & state , 42 ) . is_none ( ) ) ;
}
/// A `BlockSkeleton` fixture builder for quarter-footprint tests — only
/// the fields the aggregate reads are wired; the rest default.
fn block (
zoning : crate ::simulation ::generator ::ZoningType ,
district_type : DistrictType ,
density_pct : u8 ,
landmark : Option < crate ::simulation ::generator ::LandmarkSlot > ,
) -> crate ::simulation ::generator ::BlockSkeleton {
crate ::simulation ::generator ::BlockSkeleton {
zoning ,
district_type ,
density_pct ,
landmark ,
.. Default ::default ( )
}
}
/// T-1119: a populated quarter aggregates correctly — the density mean,
/// the dominant-mode fields (including a tie resolving to the lowest
/// declaration-order variant per the D-226 T-1112 amendment §1), the
/// landmark count, and the corridor count.
#[ test ]
fn quarter_footprint_layer_aggregates_populated_quarter ( ) {
use crate ::atlas ::attractor_matching ::CityPlacement ;
use crate ::simulation ::generator ::{
ArrangementPattern , FoundingOrientation , PoliticalArchetype , ZoningType ,
} ;
let mut state = blank_state ( " GJ1c " ) ;
let placement = CityPlacement {
city_id : 7 ,
name : " Millbrook " . into ( ) ,
position : ( 30 , 40 ) ,
attractor_type : AttractorType ::ValleyFloor ,
score : 500 ,
synthetic : false ,
political_archetype : PoliticalArchetype ::Commission ,
arrangement_pattern : ArrangementPattern ::RadialCore ,
founding_orientation : FoundingOrientation ::Cardinal ,
population : 200_000 ,
is_capital : false ,
is_standalone_hq : false ,
} ;
state . placements = vec! [ placement . clone ( ) ] ;
let world_seed = 42 ;
let quarter_id = SeedChain ::for_body ( world_seed , " GJ1c " )
. derive ( SeedDomain ::Layer4Quarter , placement . city_id )
. seed ( ) ;
// 16 blocks: 10 Commercial/Commercial, 6 Industrial/Industrial — a
// clean (non-tied) mode on both district_type and zoning, plus a
// known density mean and landmark/corridor counts.
let mut blocks : [ [ crate ::simulation ::generator ::BlockSkeleton ; 4 ] ; 4 ] = Default ::default ( ) ;
let mut flat : Vec < & mut crate ::simulation ::generator ::BlockSkeleton > =
blocks . iter_mut ( ) . flatten ( ) . collect ( ) ;
for ( i , b ) in flat . iter_mut ( ) . enumerate ( ) {
if i < 10 {
* * b = block ( ZoningType ::Commercial , DistrictType ::Commercial , 60 , None ) ;
} else {
* * b = block (
ZoningType ::Industrial ,
DistrictType ::Industrial ,
20 ,
Some ( " landmark " . to_string ( ) ) ,
) ;
}
}
// 3 landmarks among the Industrial blocks (indices 10, 11, 12).
* flat [ 10 ] = block (
ZoningType ::Industrial ,
DistrictType ::Industrial ,
20 ,
Some ( " A " . to_string ( ) ) ,
) ;
* flat [ 11 ] = block (
ZoningType ::Industrial ,
DistrictType ::Industrial ,
20 ,
Some ( " B " . to_string ( ) ) ,
) ;
* flat [ 12 ] = block (
ZoningType ::Industrial ,
DistrictType ::Industrial ,
20 ,
Some ( " C " . to_string ( ) ) ,
) ;
for b in flat . iter_mut ( ) . skip ( 13 ) {
* * b = block ( ZoningType ::Industrial , DistrictType ::Industrial , 20 , None ) ;
}
// (10 * 60 + 6 * 20) / 16 = 720 / 16 = 45.
state . quarters . insert (
quarter_id ,
crate ::simulation ::generator ::QuarterWorldState {
skeleton : crate ::simulation ::generator ::QuarterSkeleton {
quarter_id ,
blocks ,
corridors : vec ! [
crate ::simulation ::generator ::CorridorSpine {
from : 0 ,
to : 1 ,
path : vec ! [ ( 0 , 0 ) , ( 4 , 4 ) ] ,
} ,
crate ::simulation ::generator ::CorridorSpine {
from : 1 ,
to : 2 ,
path : vec ! [ ( 4 , 4 ) , ( 8 , 8 ) ] ,
} ,
] ,
.. Default ::default ( )
} ,
block_tags : Default ::default ( ) ,
} ,
) ;
let layer =
build_quarter_footprint_layer ( & state , world_seed ) . expect ( " populated quarters → Some " ) ;
let entry = layer . entries . get ( & 7 ) . expect ( " city_id 7 entry present " ) ;
assert_eq! ( entry . city_id , 7 ) ;
assert_eq! ( entry . density_avg_pct , 45 ) ;
assert_eq! ( entry . dominant_district_type , DistrictType ::Commercial ) ;
assert_eq! ( entry . dominant_zoning , ZoningType ::Commercial ) ;
assert_eq! ( entry . landmark_count , 3 ) ;
assert_eq! ( entry . corridor_count , 2 ) ;
}
/// T-1119: the mode tie-break resolves to the lowest declaration-order
/// variant (the `Ord` derive), per the D-226 T-1112 amendment §1's
/// explicit tie rule — this is the reason `ZoningType` gained
/// `PartialOrd`/`Ord` in this same ticket.
#[ test ]
fn quarter_footprint_layer_tie_breaks_by_declaration_order ( ) {
use crate ::atlas ::attractor_matching ::CityPlacement ;
use crate ::simulation ::generator ::{
ArrangementPattern , FoundingOrientation , PoliticalArchetype , ZoningType ,
} ;
let mut state = blank_state ( " GJ1c " ) ;
let placement = CityPlacement {
city_id : 3 ,
name : " Farmstead Rell " . into ( ) ,
position : ( 50 , 60 ) ,
attractor_type : AttractorType ::PlainCenter ,
score : 100 ,
synthetic : false ,
political_archetype : PoliticalArchetype ::Commission ,
arrangement_pattern : ArrangementPattern ::RadialCore ,
founding_orientation : FoundingOrientation ::Cardinal ,
population : 8_000 ,
is_capital : false ,
is_standalone_hq : false ,
} ;
state . placements = vec! [ placement . clone ( ) ] ;
let world_seed = 99 ;
let quarter_id = SeedChain ::for_body ( world_seed , " GJ1c " )
. derive ( SeedDomain ::Layer4Quarter , placement . city_id )
. seed ( ) ;
// 8 blocks Industrial, 8 blocks Commercial — an exact tie. Declaration
// order on both DistrictType and ZoningType lists Commercial before
// Industrial, so the tie-broken dominant must be Commercial on both.
let mut blocks : [ [ crate ::simulation ::generator ::BlockSkeleton ; 4 ] ; 4 ] = Default ::default ( ) ;
for ( i , b ) in blocks . iter_mut ( ) . flatten ( ) . enumerate ( ) {
* b = if i < 8 {
block ( ZoningType ::Industrial , DistrictType ::Industrial , 50 , None )
} else {
block ( ZoningType ::Commercial , DistrictType ::Commercial , 50 , None )
} ;
}
state . quarters . insert (
quarter_id ,
crate ::simulation ::generator ::QuarterWorldState {
skeleton : crate ::simulation ::generator ::QuarterSkeleton {
quarter_id ,
blocks ,
.. Default ::default ( )
} ,
block_tags : Default ::default ( ) ,
} ,
) ;
let layer =
build_quarter_footprint_layer ( & state , world_seed ) . expect ( " populated quarters → Some " ) ;
let entry = layer . entries . get ( & 3 ) . expect ( " city_id 3 entry present " ) ;
assert_eq! (
entry . dominant_district_type ,
DistrictType ::Commercial ,
" tie resolves to Commercial (declared before Industrial) "
) ;
assert_eq! (
entry . dominant_zoning ,
ZoningType ::Commercial ,
" tie resolves to Commercial (declared before Industrial) "
) ;
}
/// T-1119: a placement whose deterministically-derived `quarter_id` is
/// NOT yet in `state.quarters` (skeleton generation is async, dispatched
/// after the body's own snapshot is cached — D-226 T-1112 amendment §1)
/// is skipped, not defaulted. `entries` is a subset of `placements`.
#[ test ]
fn quarter_footprint_layer_skips_placement_without_matching_quarter ( ) {
use crate ::atlas ::attractor_matching ::CityPlacement ;
use crate ::simulation ::generator ::{
ArrangementPattern , FoundingOrientation , PoliticalArchetype , ZoningType ,
} ;
let mut state = blank_state ( " GJ1c " ) ;
let has_quarter = CityPlacement {
city_id : 1 ,
name : " Port Aldren " . into ( ) ,
position : ( 12 , 58 ) ,
attractor_type : AttractorType ::CoastalAccess ,
score : 1000 ,
synthetic : false ,
political_archetype : PoliticalArchetype ::Commission ,
arrangement_pattern : ArrangementPattern ::RadialCore ,
founding_orientation : FoundingOrientation ::Cardinal ,
population : 2_000_000 ,
is_capital : true ,
is_standalone_hq : false ,
} ;
let no_quarter_yet = CityPlacement {
city_id : 2 ,
name : " Farmstead Rell " . into ( ) ,
.. has_quarter . clone ( )
} ;
state . placements = vec! [ has_quarter . clone ( ) , no_quarter_yet ] ;
let world_seed = 42 ;
let quarter_id = SeedChain ::for_body ( world_seed , " GJ1c " )
. derive ( SeedDomain ::Layer4Quarter , has_quarter . city_id )
. seed ( ) ;
let mut blocks : [ [ crate ::simulation ::generator ::BlockSkeleton ; 4 ] ; 4 ] = Default ::default ( ) ;
for b in blocks . iter_mut ( ) . flatten ( ) {
* b = block ( ZoningType ::Mixed , DistrictType ::MixedUse , 10 , None ) ;
}
state . quarters . insert (
quarter_id ,
crate ::simulation ::generator ::QuarterWorldState {
skeleton : crate ::simulation ::generator ::QuarterSkeleton {
quarter_id ,
blocks ,
.. Default ::default ( )
} ,
block_tags : Default ::default ( ) ,
} ,
) ;
let layer =
build_quarter_footprint_layer ( & state , world_seed ) . expect ( " populated quarters → Some " ) ;
assert_eq! (
layer . entries . len ( ) ,
1 ,
" only the placement with a matching quarter gets an entry "
) ;
assert! ( layer . entries . contains_key ( & 1 ) ) ;
assert! (
! layer . entries . contains_key ( & 2 ) ,
" city_id 2 has no generated quarter yet — must be absent, not defaulted "
) ;
}
/// T-1119 (D-010): building the layer twice from identical state produces
/// byte-identical output — the `city_id → quarter_id` derivation and the
/// mode aggregation are pure functions of their inputs.
#[ test ]
fn quarter_footprint_layer_is_deterministic ( ) {
use crate ::atlas ::attractor_matching ::CityPlacement ;
use crate ::simulation ::generator ::{
ArrangementPattern , FoundingOrientation , PoliticalArchetype , ZoningType ,
} ;
let mut state = blank_state ( " GJ1c " ) ;
let placement = CityPlacement {
city_id : 5 ,
name : " Groombridge " . into ( ) ,
position : ( 1 , 1 ) ,
attractor_type : AttractorType ::PlainCenter ,
score : 300 ,
synthetic : false ,
political_archetype : PoliticalArchetype ::Commission ,
arrangement_pattern : ArrangementPattern ::RadialCore ,
founding_orientation : FoundingOrientation ::Cardinal ,
population : 60_000 ,
is_capital : false ,
is_standalone_hq : false ,
} ;
state . placements = vec! [ placement . clone ( ) ] ;
let world_seed = 7 ;
let quarter_id = SeedChain ::for_body ( world_seed , " GJ1c " )
. derive ( SeedDomain ::Layer4Quarter , placement . city_id )
. seed ( ) ;
let mut blocks : [ [ crate ::simulation ::generator ::BlockSkeleton ; 4 ] ; 4 ] = Default ::default ( ) ;
for ( i , b ) in blocks . iter_mut ( ) . flatten ( ) . enumerate ( ) {
* b = block (
ZoningType ::Residential ,
DistrictType ::Residential ,
( i as u8 ) * 5 ,
None ,
) ;
}
state . quarters . insert (
quarter_id ,
crate ::simulation ::generator ::QuarterWorldState {
skeleton : crate ::simulation ::generator ::QuarterSkeleton {
quarter_id ,
blocks ,
.. Default ::default ( )
} ,
block_tags : Default ::default ( ) ,
} ,
) ;
let a = build_quarter_footprint_layer ( & state , world_seed ) ;
let b = build_quarter_footprint_layer ( & state , world_seed ) ;
assert_eq! ( a , b , " identical state must produce identical output " ) ;
}
/// A blank `BodyWorldState` for tests that only care about one field —
/// callers overwrite `placements`/`road_graph`/etc. as needed.
fn blank_state ( body_id : & str ) -> BodyWorldState {
@@ -819,7 +1309,7 @@ mod tests {
assert! ( build_settlement_layer ( & unrun ) . is_none ( ) ) ;
}
/// T-960: the new layers survive a MessagePack round trip inside
/// T-960 / T-1119 : the new layers survive a MessagePack round trip inside
/// `AtlasLayerResponse` — the same wire path the bridge uses
/// (`rmp_serde::to_vec_named` / `from_slice`, matching `layer1`/
/// `district_grid`'s existing serialization).
@@ -828,7 +1318,9 @@ mod tests {
use crate ::atlas ::attractor_matching ::CityPlacement ;
use crate ::atlas ::road_graph ::{ RoadEdge , RoadGraph , RoadNode } ;
use crate ::simulation ::generator ::{
ArrangementPattern , FoundingOrientation , MaintenanceAuthority , PoliticalArchetype ,
ArrangementPattern , BlockSkeleton , DistrictType , FoundingOrientation ,
MaintenanceAuthority , PoliticalArchetype , QuarterSkeleton , QuarterWorldState ,
ZoningType ,
} ;
let mut state = blank_state ( " GJ1c " ) ;
@@ -865,6 +1357,28 @@ mod tests {
is_rail : false ,
} ] ,
} ;
let world_seed = 42 ;
let quarter_id = SeedChain ::for_body ( world_seed , " GJ1c " )
. derive ( SeedDomain ::Layer4Quarter , 1 )
. seed ( ) ;
let mut block = BlockSkeleton {
zoning : ZoningType ::Commercial ,
district_type : DistrictType ::Commercial ,
density_pct : 40 ,
.. Default ::default ( )
} ;
block . position = ( 0 , 0 ) ;
state . quarters . insert (
quarter_id ,
QuarterWorldState {
skeleton : QuarterSkeleton {
quarter_id ,
blocks : std ::array ::from_fn ( | _ | std ::array ::from_fn ( | _ | block . clone ( ) ) ) ,
.. Default ::default ( )
} ,
block_tags : Default ::default ( ) ,
} ,
) ;
let resp = AtlasLayerResponse {
body_id : " GJ1c " . into ( ) ,
@@ -874,6 +1388,7 @@ mod tests {
road_graph : build_road_graph_layer ( & state ) ,
settlements : build_settlement_layer ( & state ) ,
region_grid : build_region_grid ( & state ) ,
quarter_footprints : build_quarter_footprint_layer ( & state , world_seed ) ,
} ;
let bytes = rmp_serde ::to_vec_named ( & resp ) . expect ( " encode " ) ;
@@ -886,6 +1401,13 @@ mod tests {
rg . edges [ 0 ] . maintenance ,
MaintenanceAuthority ::Administrative
) ;
let qf = decoded
. quarter_footprints
. expect ( " quarter_footprints survives round trip " ) ;
let entry = qf . entries . get ( & 1 ) . expect ( " city_id 1 entry present " ) ;
assert_eq! ( entry . density_avg_pct , 40 ) ;
assert_eq! ( entry . dominant_district_type , DistrictType ::Commercial ) ;
assert_eq! ( entry . dominant_zoning , ZoningType ::Commercial ) ;
let settlements = decoded
. settlements
. expect ( " settlements survives round trip " ) ;