feat(simulation): interstitial fill — ground-tile character between building footprints (T-1098)
The BSP leaves that lose the D-233 coverage roll in subdivide_block_footprints were computed and discarded; they are now surfaced as the interstitial rect set (BlockSubdivision), making the ground-plane classification exhaustive by construction: footprint / interstitial / street-margin-or-reserved. FillChunk carries the leaves plus a minimal BlockFillContext (interstitial_character + setback_tier, re-derived at block level via the existing pure fn); FilledChunk gains a sparse interstitial map whose absence contract is stated on the struct (missing key = footprint/street/reserved, never unknown). The pure resolution maps OperationsSurface (D-233) first, else setback_tier onto five of D-235's seven interstitial values — dock_slip/market_pad have no specified trigger in the record and point at T-1209 rather than an invented mapping. Design brief with the geometry model at docs/architecture/interstitial-fill-t1098.md (lead-approved checkpoint). Bonus fix: a degenerate setback shrink previously vanished from BOTH lists silently; it now falls through to interstitial. 14 new tests; full cargo test green incl. all golden harnesses; purity per T-987 (plan-time compute, pre-resolved work items, no cache reads). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -842,6 +842,21 @@ fn block_on_quarter_edge(row: u8, col: u8, edge: Edge) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// The two-way split of a block's BSP leaves (T-1098): the leaves that won
|
||||
/// their D-233 coverage roll become building footprints; the leaves that lost
|
||||
/// it are the block's interstitial ground — the gap between footprints. Both
|
||||
/// lists are exhaustive over the block's inner rect (street-margin-excluded)
|
||||
/// tile-space by construction: every BSP leaf lands in exactly one list.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct BlockSubdivision {
|
||||
/// Building footprints — shrunk by `setback` to leave street frontage.
|
||||
footprints: Vec<TileRect>,
|
||||
/// Interstitial ground-tile rects — the *whole* BSP leaf, unshrunk (see
|
||||
/// `subdivide_block_footprints`'s doc for why no setback shrink applies
|
||||
/// here).
|
||||
interstitial: Vec<TileRect>,
|
||||
}
|
||||
|
||||
/// Subdivide one block into building footprints (D-220/D-229/D-233/D-234).
|
||||
///
|
||||
/// Lot size + setback scale with `density_pct` (Frontier → few big lots, wide
|
||||
@@ -850,6 +865,16 @@ fn block_on_quarter_edge(row: u8, col: u8, edge: Edge) -> bool {
|
||||
/// open space (yards/parks/lots). Footprints are axis-aligned `TileRect`s in
|
||||
/// block tile-space. `waterfront` (D-234b): on the water-facing edge the street
|
||||
/// margin drops to 0 — buildings present flush to the quay (dock-orthogonal).
|
||||
///
|
||||
/// **T-1098:** returns both the footprint list *and* the interstitial leaf
|
||||
/// list — the BSP leaves that lost the coverage roll, previously discarded.
|
||||
/// An interstitial leaf is **not** shrunk by `setback`: the setback shrink
|
||||
/// only applies to leaves that become buildings (pulling back from the lot
|
||||
/// line to leave frontage for the *building's own* setback); an interstitial
|
||||
/// leaf has no building on the other side to setback from, so it keeps the
|
||||
/// whole BSP leaf rect — that full rect is exactly "the gap between
|
||||
/// buildings," including the frontage a neighbor's setback would have
|
||||
/// consumed.
|
||||
fn subdivide_block_footprints(
|
||||
density_pct: u8,
|
||||
bulk: &BulkClass,
|
||||
@@ -859,7 +884,7 @@ fn subdivide_block_footprints(
|
||||
_morphology: &MorphologyZone,
|
||||
waterfront: Option<Edge>,
|
||||
seed: SeedChain,
|
||||
) -> Vec<TileRect> {
|
||||
) -> BlockSubdivision {
|
||||
let (min_lot, max_lot, setback) = match density_pct {
|
||||
0..=20 => (24u8, 48u8, 3u8), // Frontier
|
||||
21..=45 => (16, 32, 2), // Settled
|
||||
@@ -889,24 +914,38 @@ fn subdivide_block_footprints(
|
||||
bsp(inner, 0, min_lot, max_lot, seed.seed(), &mut leaves);
|
||||
|
||||
// Keep the coverage fraction as buildings; shrink each by the setback to
|
||||
// leave street frontage. The rest is interstitial open space.
|
||||
let mut out = Vec::new();
|
||||
// leave street frontage. The rest (T-1098) is interstitial ground —
|
||||
// collected, not discarded.
|
||||
let mut footprints = Vec::new();
|
||||
let mut interstitial = Vec::new();
|
||||
for (i, lot) in leaves.iter().enumerate() {
|
||||
let roll = (splitmix64(seed.seed() ^ (i as u64 + 1)) % 100) as u8;
|
||||
if roll >= coverage {
|
||||
continue; // interstitial gap
|
||||
interstitial.push(lot.clone()); // T-1098: the whole leaf, unshrunk
|
||||
continue;
|
||||
}
|
||||
let w = lot.size.0.saturating_sub(setback);
|
||||
let h = lot.size.1.saturating_sub(setback);
|
||||
if w >= 1 && h >= 1 {
|
||||
out.push(TileRect::new(lot.origin.0, lot.origin.1, w, h));
|
||||
footprints.push(TileRect::new(lot.origin.0, lot.origin.1, w, h));
|
||||
} else {
|
||||
// Degenerate shrink (setback ate the whole lot) — the lot still
|
||||
// reads as ground, not a building; T-1098 keeps it interstitial
|
||||
// rather than silently vanishing from both lists.
|
||||
interstitial.push(lot.clone());
|
||||
}
|
||||
}
|
||||
out
|
||||
BlockSubdivision {
|
||||
footprints,
|
||||
interstitial,
|
||||
}
|
||||
}
|
||||
|
||||
/// Tag every building footprint in one block (D-229). Reserved blocks (parks,
|
||||
/// terminals, plazas) are open/special-use and get no standard building fill.
|
||||
/// Tag every building footprint in one block (D-229), plus the block's T-1098
|
||||
/// interstitial ground-tile rects. Reserved blocks (parks, terminals, plazas)
|
||||
/// are open/special-use and get no standard building fill (nor interstitial
|
||||
/// leaves — a reserved block's own fill story is out of this ticket's scope,
|
||||
/// see the T-1098 brief §4).
|
||||
///
|
||||
/// `exterior_catalog` is the D-235 exterior-grammar content (T-988),
|
||||
/// pre-resolved at L3→L4 dispatch time alongside the rest of the D-232 catalog
|
||||
@@ -920,9 +959,9 @@ fn assign_block_tags(
|
||||
waterfront: Option<Edge>,
|
||||
block_chain: SeedChain,
|
||||
exterior_catalog: &ExteriorCatalog,
|
||||
) -> Vec<BuildingPropertyTag> {
|
||||
) -> (Vec<BuildingPropertyTag>, Vec<TileRect>) {
|
||||
if block.reservation.is_some() {
|
||||
return Vec::new();
|
||||
return (Vec::new(), Vec::new());
|
||||
}
|
||||
let prosperity = context.prosperity_baseline_bps;
|
||||
let setting = &context.surrounding_biome;
|
||||
@@ -944,15 +983,17 @@ fn assign_block_tags(
|
||||
heritage_bps: context.swerve_rates_bps.1,
|
||||
};
|
||||
|
||||
let footprints = subdivide_block_footprints(
|
||||
let subdivision = subdivide_block_footprints(
|
||||
block.density_pct,
|
||||
&context.dominant_bulk_class,
|
||||
&context.morphology_zone,
|
||||
waterfront,
|
||||
block_chain,
|
||||
);
|
||||
let interstitial = subdivision.interstitial;
|
||||
|
||||
footprints
|
||||
let tags = subdivision
|
||||
.footprints
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, footprint)| {
|
||||
@@ -1032,11 +1073,24 @@ fn assign_block_tags(
|
||||
doors: Vec::new(), // D-231 door derivation is #979
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
.collect();
|
||||
|
||||
(tags, interstitial)
|
||||
}
|
||||
|
||||
/// Build the full `block_tags` map for a quarter (D-229/D-230): subdivide and tag
|
||||
/// every non-reserved block's footprints. Keyed by 4×4 block grid position.
|
||||
/// Both block-keyed maps `assign_all_block_tags` produces (T-1098): the
|
||||
/// existing per-building tags, plus the sibling interstitial-leaf map that
|
||||
/// threads block-level ground-tile geometry to `FillChunk` (see
|
||||
/// `simulation::generator::QuarterWorldState.block_interstitial`).
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct BlockTagAssignment {
|
||||
pub block_tags: BTreeMap<(u8, u8), Vec<BuildingPropertyTag>>,
|
||||
pub block_interstitial: BTreeMap<(u8, u8), Vec<TileRect>>,
|
||||
}
|
||||
|
||||
/// Build the full `block_tags` (+ T-1098 `block_interstitial`) maps for a
|
||||
/// quarter (D-229/D-230): subdivide and tag every non-reserved block's
|
||||
/// footprints. Both maps are keyed by 4×4 block grid position.
|
||||
///
|
||||
/// `exterior_catalog` is the D-235 exterior-grammar content (T-988) —
|
||||
/// threaded straight through to [`assign_block_tags`]; see its doc comment.
|
||||
@@ -1047,7 +1101,7 @@ pub fn assign_all_block_tags(
|
||||
founding_age_years: u32,
|
||||
chain: SeedChain,
|
||||
exterior_catalog: &ExteriorCatalog,
|
||||
) -> BTreeMap<(u8, u8), Vec<BuildingPropertyTag>> {
|
||||
) -> BlockTagAssignment {
|
||||
// Flush-frontage quarter edge: water-facing from a coastal founding
|
||||
// orientation (D-234b — blocks present flush to the quay), or rail-facing
|
||||
// from a RailHeadFacing orientation (T-1076 §4 — blocks present flush
|
||||
@@ -1055,13 +1109,14 @@ pub fn assign_all_block_tags(
|
||||
// mutually exclusive by construction (one orientation per settlement).
|
||||
let flush_edge = coastal_edge(&context.founding_orientation)
|
||||
.or_else(|| railhead_edge(&context.founding_orientation));
|
||||
let mut map = BTreeMap::new();
|
||||
let mut block_tags = BTreeMap::new();
|
||||
let mut block_interstitial = BTreeMap::new();
|
||||
for row in 0..4u8 {
|
||||
for col in 0..4u8 {
|
||||
let block = &skeleton.blocks[row as usize][col as usize];
|
||||
let block_chain = chain.derive(SeedDomain::Block, (row * 4 + col) as u64);
|
||||
let waterfront = flush_edge.filter(|&e| block_on_quarter_edge(row, col, e));
|
||||
let tags = assign_block_tags(
|
||||
let (tags, interstitial) = assign_block_tags(
|
||||
block,
|
||||
skeleton,
|
||||
context,
|
||||
@@ -1072,11 +1127,17 @@ pub fn assign_all_block_tags(
|
||||
exterior_catalog,
|
||||
);
|
||||
if !tags.is_empty() {
|
||||
map.insert((row, col), tags);
|
||||
block_tags.insert((row, col), tags);
|
||||
}
|
||||
if !interstitial.is_empty() {
|
||||
block_interstitial.insert((row, col), interstitial);
|
||||
}
|
||||
}
|
||||
}
|
||||
map
|
||||
BlockTagAssignment {
|
||||
block_tags,
|
||||
block_interstitial,
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -1726,15 +1787,18 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn footprints_fit_within_block_bounds() {
|
||||
let fps = subdivide_block_footprints(
|
||||
let sub = subdivide_block_footprints(
|
||||
70,
|
||||
&BulkClass::NonPhysical,
|
||||
&MorphologyZone::AlluvialPlain,
|
||||
None,
|
||||
SeedChain::root(1),
|
||||
);
|
||||
assert!(!fps.is_empty(), "a dense block should produce footprints");
|
||||
for fp in &fps {
|
||||
assert!(
|
||||
!sub.footprints.is_empty(),
|
||||
"a dense block should produce footprints"
|
||||
);
|
||||
for fp in &sub.footprints {
|
||||
assert!(fp.size.0 >= 1 && fp.size.1 >= 1);
|
||||
assert!(fp.origin.0 as u16 + fp.size.0 as u16 <= BLOCK_TILES as u16);
|
||||
assert!(fp.origin.1 as u16 + fp.size.1 as u16 <= BLOCK_TILES as u16);
|
||||
@@ -1758,10 +1822,81 @@ mod tests {
|
||||
SeedChain::root(7),
|
||||
);
|
||||
assert!(
|
||||
dense.len() > sparse.len(),
|
||||
dense.footprints.len() > sparse.footprints.len(),
|
||||
"dense ({}) should pack more lots than sparse ({})",
|
||||
dense.len(),
|
||||
sparse.len()
|
||||
dense.footprints.len(),
|
||||
sparse.footprints.len()
|
||||
);
|
||||
}
|
||||
|
||||
// ── T-1098: interstitial leaves from subdivide_block_footprints ─────────
|
||||
|
||||
#[test]
|
||||
fn interstitial_leaves_are_produced_alongside_footprints() {
|
||||
let sub = subdivide_block_footprints(
|
||||
70,
|
||||
&BulkClass::NonPhysical,
|
||||
&MorphologyZone::AlluvialPlain,
|
||||
None,
|
||||
SeedChain::root(1),
|
||||
);
|
||||
assert!(
|
||||
!sub.interstitial.is_empty(),
|
||||
"a block with coverage < 100% must produce interstitial leaves"
|
||||
);
|
||||
for leaf in &sub.interstitial {
|
||||
assert!(leaf.size.0 >= 1 && leaf.size.1 >= 1);
|
||||
assert!(leaf.origin.0 as u16 + leaf.size.0 as u16 <= BLOCK_TILES as u16);
|
||||
assert!(leaf.origin.1 as u16 + leaf.size.1 as u16 <= BLOCK_TILES as u16);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn footprints_and_interstitial_leaves_do_not_overlap() {
|
||||
// Every BSP leaf becomes exactly one of a (shrunk) footprint or an
|
||||
// (unshrunk) interstitial rect — never both, never neither is
|
||||
// reflected in this test by checking no footprint origin coincides
|
||||
// with an interstitial leaf's origin (BSP leaves are disjoint, so
|
||||
// distinct origins is a sufficient proxy for "different leaves").
|
||||
let sub = subdivide_block_footprints(
|
||||
50,
|
||||
&BulkClass::NonPhysical,
|
||||
&MorphologyZone::AlluvialPlain,
|
||||
None,
|
||||
SeedChain::root(3),
|
||||
);
|
||||
for fp in &sub.footprints {
|
||||
assert!(
|
||||
!sub.interstitial.contains(fp),
|
||||
"a footprint rect must not also appear as an interstitial leaf"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn low_coverage_bulk_class_yields_more_interstitial_than_high_coverage() {
|
||||
// BulkSolid (25-40% coverage) vs NonPhysical (85-95% coverage) at the
|
||||
// same density/seed: BulkSolid must leave far more ground uncovered.
|
||||
let bulk_solid = subdivide_block_footprints(
|
||||
70,
|
||||
&BulkClass::BulkSolid,
|
||||
&MorphologyZone::AlluvialPlain,
|
||||
None,
|
||||
SeedChain::root(9),
|
||||
);
|
||||
let non_physical = subdivide_block_footprints(
|
||||
70,
|
||||
&BulkClass::NonPhysical,
|
||||
&MorphologyZone::AlluvialPlain,
|
||||
None,
|
||||
SeedChain::root(9),
|
||||
);
|
||||
assert!(
|
||||
bulk_solid.interstitial.len() >= non_physical.interstitial.len(),
|
||||
"low-coverage BulkSolid ({}) should leave at least as much interstitial \
|
||||
ground as high-coverage NonPhysical ({})",
|
||||
bulk_solid.interstitial.len(),
|
||||
non_physical.interstitial.len()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1777,7 +1912,7 @@ mod tests {
|
||||
let ctx = make_context(PoliticalArchetype::Commission, WorldTier::Epicenter);
|
||||
let sk =
|
||||
generate_quarter_skeleton(&ctx, 100_000_000, "financial", 1, 300, SeedChain::root(42));
|
||||
let tags = assign_all_block_tags(
|
||||
let assignment = assign_all_block_tags(
|
||||
&sk,
|
||||
&ctx,
|
||||
"financial",
|
||||
@@ -1785,6 +1920,7 @@ mod tests {
|
||||
SeedChain::root(42),
|
||||
&ExteriorCatalog::default(),
|
||||
);
|
||||
let tags = &assignment.block_tags;
|
||||
assert!(!tags.is_empty(), "quarter should produce building tags");
|
||||
// Park reservation blocks (1,2),(1,3),(2,2),(2,3) get no standard fill.
|
||||
for &reserved in &[(1u8, 2u8), (1, 3), (2, 2), (2, 3)] {
|
||||
@@ -1851,7 +1987,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let sk = generate_quarter_skeleton(&ctx, 500_000, "financial", 1, 200, SeedChain::root(42));
|
||||
let tags = assign_all_block_tags(
|
||||
let assignment = assign_all_block_tags(
|
||||
&sk,
|
||||
&ctx,
|
||||
"financial",
|
||||
@@ -1861,7 +1997,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let mut found_residential = false;
|
||||
for (pos, block_tags) in &tags {
|
||||
for (pos, block_tags) in &assignment.block_tags {
|
||||
let block = &sk.blocks[pos.0 as usize][pos.1 as usize];
|
||||
if block.district_type != DistrictType::Residential {
|
||||
continue;
|
||||
@@ -1888,7 +2024,7 @@ mod tests {
|
||||
// the harmless pre-T-994 degenerate behaviour.
|
||||
let ctx = make_context(PoliticalArchetype::Commission, WorldTier::Regional);
|
||||
let sk = generate_quarter_skeleton(&ctx, 500_000, "financial", 1, 200, SeedChain::root(42));
|
||||
let tags = assign_all_block_tags(
|
||||
let assignment = assign_all_block_tags(
|
||||
&sk,
|
||||
&ctx,
|
||||
"financial",
|
||||
@@ -1896,7 +2032,7 @@ mod tests {
|
||||
SeedChain::root(42),
|
||||
&ExteriorCatalog::default(),
|
||||
);
|
||||
for block_tags in tags.values() {
|
||||
for block_tags in assignment.block_tags.values() {
|
||||
for tag in block_tags {
|
||||
assert_eq!(tag.flavor_ref, ArchitectureFlavorRef::InVocabulary(0));
|
||||
}
|
||||
@@ -1918,7 +2054,7 @@ mod tests {
|
||||
ctx.swerve_heritage_pool = vec![("old_hacienda".to_string(), 10_000)];
|
||||
|
||||
let sk = generate_quarter_skeleton(&ctx, 500_000, "financial", 1, 200, SeedChain::root(42));
|
||||
let tags_a = assign_all_block_tags(
|
||||
let assignment_a = assign_all_block_tags(
|
||||
&sk,
|
||||
&ctx,
|
||||
"financial",
|
||||
@@ -1926,7 +2062,7 @@ mod tests {
|
||||
SeedChain::root(42),
|
||||
&ExteriorCatalog::default(),
|
||||
);
|
||||
let tags_b = assign_all_block_tags(
|
||||
let assignment_b = assign_all_block_tags(
|
||||
&sk,
|
||||
&ctx,
|
||||
"financial",
|
||||
@@ -1934,11 +2070,14 @@ mod tests {
|
||||
SeedChain::root(42),
|
||||
&ExteriorCatalog::default(),
|
||||
);
|
||||
assert_eq!(tags_a, tags_b, "same seeds → same swerves (D-010)");
|
||||
assert_eq!(
|
||||
assignment_a, assignment_b,
|
||||
"same seeds → same swerves (D-010)"
|
||||
);
|
||||
|
||||
let mut total = 0usize;
|
||||
let mut swerved = 0usize;
|
||||
for block_tags in tags_a.values() {
|
||||
for block_tags in assignment_a.block_tags.values() {
|
||||
for tag in block_tags {
|
||||
total += 1;
|
||||
match &tag.flavor_ref {
|
||||
@@ -1971,7 +2110,7 @@ mod tests {
|
||||
ctx.swerve_heritage_pool = vec![("old_hacienda".to_string(), 10_000)];
|
||||
// rates stay (0, 0) from make_context
|
||||
let sk = generate_quarter_skeleton(&ctx, 500_000, "financial", 1, 200, SeedChain::root(42));
|
||||
let tags = assign_all_block_tags(
|
||||
let assignment = assign_all_block_tags(
|
||||
&sk,
|
||||
&ctx,
|
||||
"financial",
|
||||
@@ -1979,7 +2118,7 @@ mod tests {
|
||||
SeedChain::root(42),
|
||||
&ExteriorCatalog::default(),
|
||||
);
|
||||
for block_tags in tags.values() {
|
||||
for block_tags in assignment.block_tags.values() {
|
||||
for tag in block_tags {
|
||||
assert!(matches!(
|
||||
tag.flavor_ref,
|
||||
@@ -2038,7 +2177,7 @@ mod tests {
|
||||
};
|
||||
|
||||
let sk = generate_quarter_skeleton(&ctx, 500_000, "financial", 1, 200, SeedChain::root(42));
|
||||
let tags = assign_all_block_tags(
|
||||
let assignment = assign_all_block_tags(
|
||||
&sk,
|
||||
&ctx,
|
||||
"financial",
|
||||
@@ -2048,7 +2187,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let mut found_residential = false;
|
||||
for (pos, block_tags) in &tags {
|
||||
for (pos, block_tags) in &assignment.block_tags {
|
||||
let block = &sk.blocks[pos.0 as usize][pos.1 as usize];
|
||||
if block.district_type != DistrictType::Residential {
|
||||
continue;
|
||||
@@ -2094,7 +2233,7 @@ mod tests {
|
||||
};
|
||||
|
||||
let sk = generate_quarter_skeleton(&ctx, 500_000, "financial", 1, 200, SeedChain::root(42));
|
||||
let tags = assign_all_block_tags(
|
||||
let assignment = assign_all_block_tags(
|
||||
&sk,
|
||||
&ctx,
|
||||
"financial",
|
||||
@@ -2104,7 +2243,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let mut any_swerved = false;
|
||||
for block_tags in tags.values() {
|
||||
for block_tags in assignment.block_tags.values() {
|
||||
for tag in block_tags {
|
||||
if matches!(&tag.flavor_ref, ArchitectureFlavorRef::Swerve(t) if t == "foreign_temple")
|
||||
{
|
||||
@@ -2127,7 +2266,7 @@ mod tests {
|
||||
// Generic/neutral, never panic.
|
||||
let ctx = make_context(PoliticalArchetype::Commission, WorldTier::Regional);
|
||||
let sk = generate_quarter_skeleton(&ctx, 500_000, "financial", 1, 200, SeedChain::root(42));
|
||||
let tags = assign_all_block_tags(
|
||||
let assignment = assign_all_block_tags(
|
||||
&sk,
|
||||
&ctx,
|
||||
"financial",
|
||||
@@ -2135,8 +2274,8 @@ mod tests {
|
||||
SeedChain::root(42),
|
||||
&ExteriorCatalog::default(),
|
||||
);
|
||||
assert!(!tags.is_empty());
|
||||
for block_tags in tags.values() {
|
||||
assert!(!assignment.block_tags.is_empty());
|
||||
for block_tags in assignment.block_tags.values() {
|
||||
for tag in block_tags {
|
||||
assert_eq!(tag.exterior.wall_material, WallMaterial::Generic);
|
||||
assert_eq!(tag.exterior.roof_form, RoofForm::Generic);
|
||||
@@ -2365,9 +2504,9 @@ mod tests {
|
||||
SeedChain::root(3),
|
||||
);
|
||||
let min_y = |v: &[TileRect]| v.iter().map(|r| r.origin.1).min().unwrap_or(u8::MAX);
|
||||
assert!(min_y(&inland) >= BLOCK_MARGIN);
|
||||
assert!(min_y(&inland.footprints) >= BLOCK_MARGIN);
|
||||
assert!(
|
||||
min_y(&quay) < min_y(&inland),
|
||||
min_y(&quay.footprints) < min_y(&inland.footprints),
|
||||
"quay buildings should reach the water edge"
|
||||
);
|
||||
}
|
||||
@@ -2390,7 +2529,7 @@ mod tests {
|
||||
SeedChain::root(11),
|
||||
);
|
||||
let min_y = |v: &[TileRect]| v.iter().map(|r| r.origin.1).min().unwrap_or(u8::MAX);
|
||||
assert!(min_y(&quay) < min_y(&inland));
|
||||
assert!(min_y(&quay.footprints) < min_y(&inland.footprints));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user