Files
settled-reach/server/src/atlas/layer_proxy.rs
T
2026-07-25 16:53:41 +02:00

6036 lines
258 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Atlas layer-stream proxy handler (#969, D-225).
//!
//! Serves a body's generation-cascade layer data to the client, compute-on-
//! demand and mod-first:
//! - **Cache hit** → serialize the cached `Layer1Output` and reply `Ready`.
//! - **Cache miss** → resolve the body's source heightmap ([`BodySourceResolver`]),
//! enqueue an `Immediate` `AnalyzeBody` on the background queue (#968), and
//! reply `Pending` (the client re-requests; the drain system populates the
//! cache, so a later request hits).
//!
//! Pure handler logic; the bridge wiring (message routing) is the proxy's other
//! half. No baking — the heightmap is the only source of truth (D-225).
use bevy_ecs::prelude::Resource;
use serde::{Deserialize, Serialize};
use crate::atlas::body_params_reader::BodyParamsReader;
use crate::atlas::body_world_state::{BodyWorldState, BodyWorldStateCache, RiverNetwork, SimTick};
use crate::atlas::cascade::CascadeLayer;
use crate::atlas::city_context_reader::CityContextReader;
use crate::atlas::district_profile::{BodyParams, DistrictPos};
use crate::atlas::gen_queue::{GenPriority, GenWorkItem, GenerationQueue};
use crate::atlas::layer1::Layer1Output;
use crate::atlas::river_course::{self, EdgeTerminusKind, InventedCourse};
use crate::atlas::road_graph::RoadNodeKind;
use crate::atlas::scale::DISTRICT_M;
use crate::atlas::source_resolver::{BodySourceResolver, SourceResolveError};
use crate::bridge::ConnectionId;
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).
pub(crate) const DEFAULT_SEA_LEVEL: f32 = 0.3;
/// Hard server-side clamp on [`AtlasLayerRequest::window_n`] (D-226 T-1124
/// amendment §4, binding numbers). 64×64 districts ≈ 131 km per side — the
/// same window size `aliveness_probe --render`'s default already proved out
/// server-side (T-1123). **Never trust `window_n` from the wire** — every
/// caller clamps to `[1, DISTRICT_WINDOW_MAX_N]` before deriving.
///
/// **Finer-than-district / district rungs ONLY (T-1152).** This ceiling was
/// calibrated for district spacing; applying it unchanged to `Region`
/// requests would be nonsensical — see [`DISTRICT_WINDOW_MAX_N_REGION`]'s doc
/// for why `Region` needs its own, much larger per-axis ceiling on the SAME
/// `n` (window extent in districts).
pub const DISTRICT_WINDOW_MAX_N: u32 = 64;
/// Per-axis ceiling on [`AtlasLayerRequest::window_n`] for
/// [`WindowGranularity::Region`] requests ONLY (T-1152 step 4: "work out what
/// n means at region granularity ... and document it"). `n` is always the
/// window extent in DISTRICTS regardless of rung (T-1150 design doc §2,
/// unchanged) — but [`DISTRICT_WINDOW_MAX_N`] (64 districts ≈ 131 km) was
/// sized for the district rung's own per-cell world extent, and reusing it
/// unchanged for `Region` would clamp every region window to well under
/// ONE region's own 100-district side ([`crate::atlas::scale::DISTRICTS_PER_REGION`]),
/// making [`WindowGranularity::cell_grid_side`] round every legal `n` down
/// to a degenerate 1×1 cell grid — a "region view" that can never show more
/// than one region cell is not a useful rung.
///
/// Derived (not a new magic number, per the D-243 instruction): the largest
/// `n` for which `cell_grid_side(n) == sqrt(WIRE_CAP_CELLS)` (64 region
/// cells across, the same [`WIRE_CAP_CELLS`]-derived side length district
/// mode already reaches at its own cap) is
/// `sqrt(WIRE_CAP_CELLS) * DISTRICTS_PER_REGION = 64 * 100 = 6,400`
/// districts (≈13,100 km — comfortably covering a planetary hemisphere's
/// worth of region tiles in one capped request). The WIRE-SIZE ceiling
/// ([`WIRE_CAP_CELLS`] via [`clamp_window_n_v2`]) is still the actual
/// enforcement point (never trusted from the wire) — this constant only
/// widens the PER-AXIS ceiling far enough that the wire-size math has room
/// to matter for `Region`, exactly mirroring how [`DISTRICT_WINDOW_MAX_N`]
/// relates to the wire-size ceiling at district granularity (see
/// `clamp_window_n`'s doc: "Applied AFTER the per-axis clamp so a request
/// that already satisfies [the per-axis cap] still shrinks further").
pub const DISTRICT_WINDOW_MAX_N_REGION: u32 =
WIRE_CAP_CELLS_SQRT * crate::atlas::scale::DISTRICTS_PER_REGION as u32;
/// `WIRE_CAP_CELLS`'s integer square root (64) — computed once as a `const`
/// so [`DISTRICT_WINDOW_MAX_N_REGION`]'s derivation is checkable at compile
/// time rather than repeating the literal `64` as an uncommented magic
/// number. `WIRE_CAP_CELLS = 4_096 = 64²` exactly (see that constant's own
/// doc), so this is exact integer arithmetic, not an approximation.
const WIRE_CAP_CELLS_SQRT: u32 = 64;
const _: () = assert!(WIRE_CAP_CELLS_SQRT * WIRE_CAP_CELLS_SQRT == WIRE_CAP_CELLS);
/// Finer-than-district spacing multipliers (T-1150, zoom ladder design doc
/// §3/§5): the number of derived cells per district side. `1` = district
/// spacing (2,048 m/cell, [`DISTRICT_WINDOW_MAX_N`]'s existing behavior).
/// `4` = quarter spacing (512 m/cell, D-243) — Option B from the design doc:
/// full reclassification at the finer spacing via `derive_at_metres`, not a
/// coarser-cell interpolation.
///
/// **T-1159:** these used to also be legal VALUES of the wire-facing
/// `AtlasLayerRequest::window_granularity: u32` field (resolved via the
/// now-removed `resolve_window_granularity`) — that field is retired, fully
/// shadowed by [`WindowGranularity`] since T-1152. These constants remain as
/// internal spacing-multiplier values (see [`WindowGranularity::spacing_multiplier`],
/// [`clamp_window_n`]).
pub const WINDOW_GRANULARITY_DISTRICT: u32 = 1;
pub const WINDOW_GRANULARITY_QUARTER: u32 = 4;
/// Server-side wire-size ceiling (T-1150, design doc §3 "Cell-count cap"):
/// `window_n² × granularity² ≤ WIRE_CAP_CELLS`. At `WIRE_CAP_CELLS = 4,096`,
/// district `n=64` (the existing [`DISTRICT_WINDOW_MAX_N`] cap) sits exactly
/// at the ceiling (64² × 1² = 4,096), and quarter mode is clamped to
/// `n=16` districts across (16² × 4² = 4,096 — matching the design doc's
/// worked example, "Quarter, capped to same cell budget (n≈16 districts
/// across)") — this is the "extent shrinks as granularity refines, payload
/// stays ~constant" rule the design doc requires, enforced here (never
/// trusted from the wire) rather than merely asserted.
pub const WIRE_CAP_CELLS: u32 = 4_096;
// ---------------------------------------------------------------------------
// WindowGranularity (T-1152, R5 redesign — D-226 T-1143-rulings amendment's
// "Wire-contract note (T-1150, PR #191 review — Tyre)")
// ---------------------------------------------------------------------------
/// The full window-derivation-granularity vocabulary (T-1152), superseding
/// `window_granularity: u32`'s finer-than-district-only ceiling (Tyre's
/// wire-contract note, D-226 T-1143-rulings amendment: "the `u32` multiplier
/// field expresses finer-than-district integer multiples only ... a new
/// magic value is not the path"). This is the R5 redesign the note demands:
/// an explicit **named-variant enum**, the same wire pattern [`RoadNodeKind`]
/// already uses on this carrier (a plain `#[derive(Serialize, Deserialize)]`
/// enum with no `#[repr]`/manual impl serializes as its variant name over
/// `rmp_serde`, not an integer discriminant — deliberately NOT the
/// `repr(u8)`-cast-to-`Vec<u8>` convention the six dense per-cell arrays use;
/// this field is a scalar tag, not a bulk payload, so the string-tag
/// legibility is worth the few extra wire bytes one field costs).
///
/// **Why an enum and not a signed/log-scale int (the R5 alternative the risk
/// row named):** a log-scale `i32` still needs a lookup table to turn back
/// into a spacing, and a "cannot express" bug (someone passing `-2` and
/// expecting quarter-of-quarter) is silent at the type level. An enum with an
/// exhaustive match in [`WindowGranularity::spacing_m`] makes "this variant
/// has no defined spacing" a compile error, not a runtime surprise — the same
/// reasoning `MorphologyZone`'s exhaustive-match discipline already
/// established for this codebase (D-239 §6).
///
/// **Resolution (T-1152, simplified T-1159):** [`AtlasLayerRequest::window_granularity_v2`]
/// resolves directly via [`resolve_window_granularity_v2`], the single
/// widening point for this enum. Absent (`#[serde(default)]`, `None`)
/// resolves to `District`.
///
/// **T-1159:** this used to also reconcile against a legacy
/// `AtlasLayerRequest::window_granularity: u32` field (whenever THIS field
/// was absent) — that field is retired, fully shadowed since T-1152 and
/// never sent as anything but its byte-compatible default by any caller in
/// this codebase (no external client exists, single-repo client/server pair).
///
/// **Unknown → District** at every resolution boundary (never trust the
/// wire) — same posture as every other wire-decoded enum in this module.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum WindowGranularity {
/// 512 m/cell (D-243 `QUARTER_M`) — finer than district, T-1150 Option B.
Quarter,
/// 2,048 m/cell (D-243 `DISTRICT_M`) — the original, still-default rung.
District,
/// 204,800 m/cell (D-243 `REGION_M`) — the coarser-than-district rung R5
/// flagged as unrepresentable in the `u32` multiplier encoding. Serves
/// BOTH the design doc's named "Region" row and the "Orbital/planetary"
/// row (R4: "Acceptable if the planetary rung's tiling effectively
/// subsumes it" — they share one derivation mode, `derive_orbital_at_metres`,
/// and one spacing; there is no third, coarser rung to distinguish them
/// by today, so one variant covers both named rows without inventing a
/// spacing the design doc never specified).
Region,
}
impl WindowGranularity {
/// Cell spacing in metres — the single source of truth every caller
/// (server derive loop, cap math, client mirror) must read through,
/// rather than re-deriving the metre value from the variant name.
/// Sourced from `scale::` (D-243), never a magic number local to this
/// module (T-1152 instruction: "Update the D-243-derived spacing
/// constants from `scale::` rather than new magic numbers").
pub fn spacing_m(self) -> f64 {
match self {
WindowGranularity::Quarter => crate::atlas::scale::QUARTER_M as f64,
WindowGranularity::District => DISTRICT_M as f64,
WindowGranularity::Region => crate::atlas::scale::REGION_M as f64,
}
}
/// The finer-than-district spacing multiplier ([`WINDOW_GRANULARITY_DISTRICT`]
/// / [`WINDOW_GRANULARITY_QUARTER`]) this variant corresponds to — `None`
/// for `Region`, which has no such multiplier (its spacing is coarser,
/// not a finer subdivision of a district). Used only by
/// [`clamp_window_n_v2`]'s District/Quarter branch to reuse
/// [`clamp_window_n`]'s per-axis-cap math rather than re-deriving it.
///
/// **T-1159:** this used to double as the wire-back-compat value for the
/// now-retired `window_granularity: u32` echo (`DistrictWindowLayer.granularity`)
/// — that role, and the `key_u32()` method that served it, are gone; this
/// is purely an internal spacing-multiplier lookup now.
fn spacing_multiplier(self) -> Option<u32> {
match self {
WindowGranularity::District => Some(WINDOW_GRANULARITY_DISTRICT),
WindowGranularity::Quarter => Some(WINDOW_GRANULARITY_QUARTER),
WindowGranularity::Region => None,
}
}
/// The derived cell-grid side length (in CELLS, at this granularity) for
/// a window whose extent is `n` DISTRICTS (T-1152 step 4: "work out what
/// n means at region granularity against D-243's region=100-district
/// side and document it" — this is that answer, made a total function
/// instead of inline arithmetic at each call site).
///
/// `n` is ALWAYS the window extent in DISTRICTS regardless of
/// granularity (T-1150 design doc §2, unchanged by this ticket: "the
/// window's `n` stays the DISTRICT extent"). The derived grid's side
/// length scales by [`Self::spacing_m`] relative to [`DISTRICT_M`]:
///
/// - `Quarter` (512 m, 4 cells/district side): `side = n * 4` — MORE
/// cells than districts requested (T-1150's existing behavior,
/// unchanged).
/// - `District` (2,048 m, 1:1): `side = n` — unchanged.
/// - `Region` (204,800 m = 100 districts/side, D-243
/// `DISTRICTS_PER_REGION`): `side = round(n / 100)`, minimum 1. A
/// region-granularity request with the SAME `n` as a district request
/// derives a FAR SMALLER cell grid (a region window with `n=64`
/// districts — the per-axis cap — derives only a 1×1 region cell,
/// since 64 districts is well under one region's 100-district side).
/// This inversion (finer rungs MULTIPLY cell count by `n`; `Region`
/// DIVIDES it) is why [`clamp_window_n`]'s wire-cap math needs a
/// region-aware branch too (see that function) — a flat
/// `n² × multiplier² ≤ WIRE_CAP_CELLS` formula would make `n` for
/// `Region` requests nonsensically tiny if `multiplier` were naively
/// `1/100`. Rounding (not floor/ceil) keeps the mapping the closest
/// integer approximation of the true ratio; minimum 1 so an `n` smaller
/// than one region never derives a degenerate empty grid.
pub fn cell_grid_side(self, n: u32) -> i32 {
match self {
WindowGranularity::Quarter => (n * WINDOW_GRANULARITY_QUARTER) as i32,
WindowGranularity::District => n as i32,
WindowGranularity::Region => {
let dpr = crate::atlas::scale::DISTRICTS_PER_REGION as f64;
((n as f64 / dpr).round() as i32).max(1)
}
}
}
}
/// Resolve a request's granularity to a [`WindowGranularity`] — **the single
/// widening point for the full (finer- and coarser-than-district) vocabulary**
/// (T-1152). Absent (`None`, `#[serde(default)]`) resolves to
/// [`WindowGranularity::District`] — unknown/malformed variants can't reach
/// this function at all (`rmp_serde` rejects an unrecognized enum variant
/// name at decode time, so "unknown" for this field means "absent", never
/// "present but garbage").
///
/// **T-1159:** this function used to fall back to the legacy `window_granularity: u32`
/// field (via the now-removed `resolve_window_granularity`) when
/// `window_granularity_v2` was absent — that fallback is retired along with
/// the field itself (fully shadowed since T-1152, no pre-T-1152 client
/// exists). A future coarser-than-region rung is added by widening this
/// function's match AND [`WindowGranularity`]'s variant list together.
fn resolve_window_granularity_v2(req: &AtlasLayerRequest) -> WindowGranularity {
req.window_granularity_v2
.unwrap_or(WindowGranularity::District)
}
/// Clamp `window_n` against BOTH the existing per-axis cap
/// ([`DISTRICT_WINDOW_MAX_N`]) and the granularity-aware wire-size ceiling
/// ([`WIRE_CAP_CELLS`]) — `window_n² × granularity² ≤ WIRE_CAP_CELLS` (T-1150
/// design doc §3). Applied AFTER the per-axis clamp so a request that already
/// satisfies `DISTRICT_WINDOW_MAX_N` still shrinks further at granularity 4.
///
/// **This clamp is echoed, not silently applied** — `serve_district_window`
/// puts the CLAMPED `n` into `DistrictWindowLayer.n`, so a client that
/// requests an oversized `n` gets back a smaller one. Any client-side
/// staleness guard comparing its own requested `n` against the echo MUST
/// mirror this exact function first (PR #191 review, Tyre C1) — see
/// `atlas_window_request.gd`'s `_clamp_window_n_mirror()`, which matches this
/// function bit-for-bit, the same load-bearing-mirror pattern
/// `canonicalize_district_center()` (`atlas_descend_geometry.gd`) already
/// uses for `normalize_window_center`.
fn clamp_window_n(raw_n: u32, granularity: u32) -> u32 {
let n = raw_n.clamp(1, DISTRICT_WINDOW_MAX_N);
let g = granularity.max(1);
let cap_n = (WIRE_CAP_CELLS as f64).sqrt() / g as f64;
n.min(cap_n.floor().max(1.0) as u32)
}
/// [`WindowGranularity`]-aware twin of [`clamp_window_n`] (T-1152 step 4) —
/// the SAME two-stage discipline (per-axis clamp, THEN the wire-size
/// ceiling on the DERIVED cell count, never trusted from the wire), but
/// computed through [`WindowGranularity::cell_grid_side`] so it is correct
/// for BOTH directions (finer multiplies cell count; `Region` divides it —
/// see that method's doc) instead of assuming the finer-only
/// `n × multiplier` relationship [`clamp_window_n`] hard-codes.
///
/// - **`District`/`Quarter`:** per-axis cap is [`DISTRICT_WINDOW_MAX_N`]
/// (64, unchanged) — byte-identical clamped `n` to [`clamp_window_n`] for
/// every input these two variants can produce (verified by
/// `clamp_window_n_v2_delegates_to_legacy_for_district_and_quarter`,
/// below).
/// - **`Region`:** per-axis cap is [`DISTRICT_WINDOW_MAX_N_REGION`] (6,400 —
/// see that constant's doc for the derivation), then a halving loop walks
/// `n` back if `cell_grid_side(n)` would still exceed `sqrt(WIRE_CAP_CELLS)`
/// region cells across.
///
/// **This loop is defensive, not currently reachable — stated plainly, not
/// left implicit.** `DISTRICT_WINDOW_MAX_N_REGION` is DERIVED as
/// `sqrt(WIRE_CAP_CELLS) * DISTRICTS_PER_REGION` specifically so the
/// per-axis clamp alone already forecloses the loop's trigger condition: a
/// brute-force sweep of every `raw_n` in `[1, DISTRICT_WINDOW_MAX_N_REGION]`
/// shows `cell_grid_side(n)` never exceeds `sqrt(WIRE_CAP_CELLS)` (64), so
/// `n /= 2` never executes for any input the per-axis clamp lets through —
/// verified by `clamp_window_n_v2_region_per_axis_cap_alone_satisfies_wire_cap_for_all_inputs`,
/// which pins BOTH the invariant (`cell_grid_side(result)² ≤ WIRE_CAP_CELLS`)
/// AND the loop's current no-op status (`result == raw_n.clamp(1,
/// DISTRICT_WINDOW_MAX_N_REGION)` for every swept input). The loop is kept
/// anyway as the general, correct algorithm (region's `cell_grid_side` is a
/// ROUNDING division, not the finer rungs' exact multiplication, so there
/// is no closed-form inverse the way `cap_n = sqrt(WIRE_CAP_CELLS) / g` is
/// for the finer case) — it is the safety net for a FUTURE cap derivation
/// that doesn't land exactly on the boundary (a new rung from a later
/// measurement pass, or a `WIRE_CAP_CELLS` retune that isn't a perfect
/// square times `DISTRICTS_PER_REGION`). If a future constant change makes
/// the loop actually fire, the pinned no-op assertion above breaks loudly,
/// forcing a deliberate look rather than a silent behavior change.
fn clamp_window_n_v2(raw_n: u32, granularity: WindowGranularity) -> u32 {
match granularity {
WindowGranularity::District | WindowGranularity::Quarter => clamp_window_n(
raw_n,
granularity
.spacing_multiplier()
.unwrap_or(WINDOW_GRANULARITY_DISTRICT),
),
WindowGranularity::Region => {
let mut n = raw_n.clamp(1, DISTRICT_WINDOW_MAX_N_REGION);
while granularity.cell_grid_side(n).pow(2) as u32 > WIRE_CAP_CELLS && n > 1 {
n /= 2;
}
n.max(1)
}
}
}
/// Quantized `window_min_wl_m` bands (T-1150, zoom ladder design doc §5;
/// retuned T-1162 part (c); band 4 re-derived per Tyre's PR #194 review I1).
/// `0` (no cutoff) plus the two coarse legacy bands
/// [`crate::atlas::detail_scatter::OCTAVE_WAVELENGTHS_M`]`[0..2]` (32,768/
/// 16,384/8,192 m — carried over unchanged, no rung claims these as its own
/// Nyquist floor), plus **District's own Nyquist floor** computed directly
/// from the rung's spacing (`2 × DISTRICT_M = 4,096` m), plus **Quarter's own
/// Nyquist floor** (`2 × QUARTER_M = 1,024` m).
///
/// **Dependency direction (Tyre, PR #194 I1):** the cutoff's semantic job is
/// Nyquist truncation — a property of the RUNG (its sample spacing), never of
/// any one invention field's octave choices. Band 4 is therefore derived from
/// `scale::DISTRICT_M` directly, NOT from
/// `detail_scatter::OCTAVE_WAVELENGTHS_M[3]` (the old T-1162 wiring) — that
/// coupling ran backwards: a future `detail_scatter` retune could silently
/// redefine the Atlas rung floor, and the coast warp already extends its OWN
/// octaves below `OCTAVE_WAVELENGTHS_M`'s range (T-1162 part a), proving no
/// single invention field's array is entitled to dictate the floor. The two
/// values happen to coincide today (4,096 = `2 × DISTRICT_M` = also
/// `OCTAVE_WAVELENGTHS_M[3]`) — the `const _: () = assert!(...)` immediately
/// below pins that coincidence so a future drift on EITHER side breaks the
/// build loudly instead of silently decoupling the two meanings.
///
/// **Pre-T-1162 bands unchanged in VALUE** (32,768/16,384/8,192/4,096 stay
/// exactly where they were) — but **NOT unchanged in behavior at 8,192/4,096
/// specifically**: `terrain_detail`'s own octave array is untouched by this
/// ticket, so THAT contribution is byte-identical at every pre-existing band,
/// but the coast warp's octave array gained genuinely new 8,192/4,096 m
/// entries (T-1162 part a) that a District-rung request quantizing to those
/// bands now legitimately admits — District's real Nyquist floor (4,096 m)
/// is coarse enough to resolve that content, so this is intended enrichment
/// at District too, not a leak (see `coast_invention::WARP_OCTAVE_WAVELENGTHS_M`'s
/// doc for the full admit/exclude table). Only the 32,768/16,384 bands are
/// truly inert-to-this-ticket (no octave in any extended array falls in that
/// range). The sub-district relief band ([`crate::atlas::detail_scatter::VOXEL_OCTAVE_WAVELENGTHS_M`],
/// all ≤1,024 m) and the coast warp's two finest additions (2,048/1,024 m)
/// are genuinely excluded at every pre-existing band (all ≥4,096) — those are
/// Quarter-exclusive, admitted only by the new `1,024` band. The new band is
/// additive at the END of the array — appending, not reordering, keeps every
/// existing index-based reference to the first four entries valid.
///
/// Descending order except the leading `0.0` sentinel, matched by
/// `quantize_min_wl_m`'s scan below.
const MIN_WL_BANDS_M: [f64; 6] = [
0.0,
crate::atlas::detail_scatter::OCTAVE_WAVELENGTHS_M[0],
crate::atlas::detail_scatter::OCTAVE_WAVELENGTHS_M[1],
crate::atlas::detail_scatter::OCTAVE_WAVELENGTHS_M[2],
2.0 * DISTRICT_M as f64,
2.0 * crate::atlas::scale::QUARTER_M as f64,
];
/// Direction-agnostic guard (Tyre, PR #194 I1): `MIN_WL_BANDS_M`'s District
/// band is now derived from `2 × DISTRICT_M`, deliberately decoupled from
/// `detail_scatter::OCTAVE_WAVELENGTHS_M[3]` (see the doc above). The two
/// values are expected to keep coinciding — `terrain_detail`'s finest octave
/// IS meant to sit at District's Nyquist floor — but nothing in the type
/// system enforces that anymore now that the dependency runs one direction
/// only. This assert exists so that if either side ever drifts (a
/// `detail_scatter` octave retune, or a `DISTRICT_M` scale-ladder change).
/// the build breaks loudly and a human decides deliberately whether the
/// coincidence should be restored or the two meanings were meant to diverge
/// — never a silent redefinition of the Atlas rung floor.
const _: () =
assert!(crate::atlas::detail_scatter::OCTAVE_WAVELENGTHS_M[3] as i64 == 2 * DISTRICT_M as i64);
/// Snap a wire-supplied `window_min_wl_m` to the nearest fixed band in
/// [`MIN_WL_BANDS_M`] (T-1150, design doc §5's gap-fix): "as specified,
/// `window_min_wl_m` is viewport-continuous while the cache key/echo tuple is
/// `(body, center, n, granularity)` — same key, different `min_wl`, would
/// silently collide. Fix: quantize `min_wl_m` to a small fixed set of bands
/// ... and add the quantized band to both the echo and the cache key." This
/// is that quantization, applied unconditionally to every request before it
/// touches either the cache key or the `DeriveWindow` work item — **never
/// the raw wire value past this point**, same discipline as `window_n`'s
/// clamp and `window_center`'s normalization. Nearest-band snap (ties round
/// to the coarser/lower band, i.e. `<=` on the running best distance) keeps
/// the mapping total and deterministic for any `u32` input, including values
/// far outside the octave range (e.g. `u32::MAX` snaps to the coarsest band).
pub(crate) fn quantize_min_wl_m(raw: u32) -> u32 {
let raw_f = raw as f64;
let mut best = MIN_WL_BANDS_M[0];
let mut best_dist = (raw_f - best).abs();
for &band in &MIN_WL_BANDS_M[1..] {
let dist = (raw_f - band).abs();
if dist < best_dist {
best = band;
best_dist = dist;
}
}
best as u32
}
/// A client request for a body's generation layers (D-225), extended with an
/// optional district-resolution window query (D-226 T-1124 amendment §1, T-1137).
///
/// `up_to` is a forward-compat seam that is **not yet honored**: `run_work_item`
/// (`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)]
pub struct AtlasLayerRequest {
pub body_id: String,
pub up_to: CascadeLayer,
/// District-window centre (D-226 T-1124 amendment §1, T-1137). `None` = no
/// window requested (whole-body layers only — today's behavior, byte-unchanged
/// for every existing caller thanks to `#[serde(default)]`).
#[serde(default)]
pub window_center: Option<DistrictPos>,
/// Window side length in districts. Ignored when `window_center` is `None`.
/// Clamped server-side to `[1, DISTRICT_WINDOW_MAX_N]` — **never trusted
/// from the wire** (D-226 T-1124 amendment §4).
#[serde(default)]
pub window_n: u32,
/// Window derivation granularity (T-1152, R5 redesign — see
/// [`WindowGranularity`]'s doc for the full rationale). `#[serde(default)]`
/// (`None`) resolves to [`WindowGranularity::District`] (today's default
/// behavior, byte-compatible with every pre-T-1150 caller) via
/// [`resolve_window_granularity_v2`] — **never trusted from the wire**,
/// unrecognized/absent values fall back to district.
///
/// **T-1159:** the legacy `window_granularity: u32` field this superseded
/// (T-1150's finer-than-district-only encoding) is retired — this enum
/// fully shadowed it since T-1152 landed, and no pre-T-1152 client exists
/// (single-repo client/server pair). See D-255(c): the `district_window`
/// carrier itself stays alive byte-unchanged for its existing consumer;
/// only the redundant `u32` alongside this enum is gone.
#[serde(default)]
pub window_granularity_v2: Option<WindowGranularity>,
/// Octave cutoff for the invented-terrain scatter (T-1149's
/// `min_wavelength_m`), in whole metres. `0` (absent) = no cutoff = the
/// pre-T-1150 behavior.
///
/// **Quantization contract (Hoshe 1 / Tyre C3, PR #191 review; design doc
/// §5):** the wire value here is an UNQUANTIZED, unclamped raw passthrough
/// — client codecs may send any `u32`. The SERVER is the one place
/// quantization happens: `serve_district_window` snaps every request's
/// value to the nearest fixed band in
/// [`MIN_WL_BANDS_M`] via [`quantize_min_wl_m`] BEFORE it ever touches
/// the cache key or the `DeriveWindow` work item, and the QUANTIZED value
/// (not this raw field) is what gets echoed back on
/// `DistrictWindowLayer.min_wl_m` and used as the cache key component.
/// This closes the §5 gap: without quantization, two requests differing
/// only in a continuous-valued `min_wl_m` would silently miss each
/// other's cache entries (the unbounded-key-space problem §5 exists to
/// close) — the client is free to send a viewport-continuous estimate;
/// the server's quantization is what makes the key space bounded again.
#[serde(default)]
pub window_min_wl_m: u32,
}
/// Status of a layer response (D-225).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum AtlasLayerStatus {
/// Layer data is ready (`layer1` is populated).
Ready,
/// Analysis was enqueued; the client should re-request shortly.
Pending,
/// The body is unknown or has no source terrain — re-requesting won't help.
NotFound,
/// Resolution / IO failure (message for the client log).
Error(String),
}
/// The cascade's coarse district grid, surfaced for the Atlas generation overlay
/// (T-1046, D-226). This is the **planetary-scale** view — one cell per coarse
/// grid square (`grid_w/cols` heightmap pixels) — not the on-demand 2 km districts
/// (those derive only when a player enters a settlement, Phase 5). `morphology`
/// and `elev_q` are row-major (`rows × cols`); `morphology[i]` is a `MorphologyZone`
/// discriminant (D-239 §6, `repr(u8)`), `elev_q[i]` is 0100 elevation for relief
/// shading. The client maps `cols × rows` onto the displayed heightmap.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DistrictGridLayer {
pub cols: u32,
pub rows: u32,
pub morphology: Vec<u8>,
pub elev_q: Vec<u8>,
}
/// 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) + the quarter-footprint overlay (T-1112,
/// T-1119) + the district-resolution window query (T-1124, T-1137), or a
/// non-ready status.
///
/// Growth ceiling (governance-bounded): the one-`Option`-field-per-layer
/// pattern tops out at six fields for the **dense whole-body layer family**
/// (`district_grid`, `road_graph`, `settlements`, `region_grid`,
/// `quarter_footprints` — each a compute-once, cache-per-body snapshot) —
/// 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. **That budget is now consumed:** a seventh *whole-body*
/// field is not a naming exercise like the six before it — a future
/// generation-layer addition needs its own governance, not a drive-by field.
///
/// The D-226 T-1124 amendment (2026-07-18) RESOLVED what carries the next
/// addition, and it is NOT this family: a **windowed viewport query** is a
/// categorically different payload (keyed on the *request* `(body, center, n)`,
/// re-fetched per pan, not a per-body snapshot). `district_window` (wired here,
/// T-1137) rides on `AtlasLayerResponse` but is explicitly OUTSIDE the
/// whole-body family and does not count against the six-field ceiling above
/// (D-226 T-1124 §2). The windowed family has its own hard cap: exactly ONE
/// windowed-query field; a second windowed query (a second viewport, a
/// windowed chunk-preview) is a dedicated response message by rule, not a
/// second `Option` here (D-226 T-1124 §2, symmetric with the request-side
/// five-shape demux ceiling in `bridge/mod.rs`).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AtlasLayerResponse {
pub body_id: String,
pub status: AtlasLayerStatus,
pub layer1: Option<Layer1Output>,
/// The coarse district/morphology grid for the Atlas overlay (T-1046).
/// `Some` on a cache hit once the DistrictProfile layer has run; `None` otherwise.
pub district_grid: Option<DistrictGridLayer>,
/// The inter-settlement road/rail graph overlay (T-960 §1, T-1038).
/// `Some` on a cache hit once the RoadGraph layer has run; `None` otherwise
/// (including a body with zero placed settlements — an empty graph has no
/// nodes to draw, so it collapses to `None` the same way `district_grid`
/// does for an unrun layer).
pub road_graph: Option<RoadGraphLayer>,
/// The settlement-placement overlay (T-960 §2, #955). `Some` on a cache hit
/// once the Settlement layer has placed at least one city; `None` otherwise.
pub settlements: Option<SettlementLayer>,
/// 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 requested district window (D-226 T-1124 amendment, T-1137), or
/// `None` when the request carried no `window_center` / no window data is
/// cached yet for a pending derive. Distinct from the five layers above:
/// keyed on the REQUEST `(body, center, n)`, not on the body alone — see
/// the struct-level doc.
pub district_window: Option<DistrictWindowLayer>,
/// 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).
/// Returns `None` when the DistrictProfile layer has not run (empty `districts`).
/// The grid is dense `[0, cols) × [0, rows)` (the cascade tiles the full
/// heightmap), so the extent comes from the maximum `SurveyCellPos` (D-256(b)
/// — this is the coarse survey raster, not the true district grid).
pub fn build_district_grid(
state: &crate::atlas::body_world_state::BodyWorldState,
) -> Option<DistrictGridLayer> {
if state.districts.is_empty() {
return None;
}
let cols = state.districts.keys().map(|p| p.0).max().unwrap_or(0) as u32 + 1;
let rows = state.districts.keys().map(|p| p.1).max().unwrap_or(0) as u32 + 1;
let n = (cols * rows) as usize;
let mut morphology = vec![0u8; n];
let mut elev_q = vec![0u8; n];
for (pos, profile) in &state.districts {
let (x, y) = (pos.0, pos.1);
if x < 0 || y < 0 {
continue;
}
let i = (y as u32 * cols + x as u32) as usize;
if i < n {
morphology[i] = profile.morphology_zone as u8;
elev_q[i] = profile.elev_q.clamp(0, 100) as u8;
}
}
Some(DistrictGridLayer {
cols,
rows,
morphology,
elev_q,
})
}
// ---------------------------------------------------------------------------
// RegionGridLayer (T-1113, D-243 §3)
// ---------------------------------------------------------------------------
/// The ~205 km region climate grid for the Atlas overlay (T-1113), dense
/// row-major like [`DistrictGridLayer`] (the T-1046 encoding precedent).
/// Serves the **mean-state** `RegionClock` fields only — the Q-105 tick-phase
/// callbacks are deferred, so what ships is the static climate context.
///
/// Wire encoding is all-integer (D-010 wire discipline):
/// - `season[i]` / `weather[i]` — the `repr(u8)` discriminants of
/// `SeasonPhase` / `WeatherState` (pinned, append-only).
/// - `mean_temp_dc[i]` — mean-annual temperature baseline in **deci-°C**
/// (×10, `round`ed; 0.1 °C is ample for a map overlay). `i16::MIN` is the
/// sentinel for "no atmosphere → no temperature" (airless bodies carry
/// `mean_temp_c: None`); real values are class-band-clamped far inside
/// i16 range.
/// - `moisture_q[i]` — the 0100 region moisture primitive.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RegionGridLayer {
pub cols: u32,
pub rows: u32,
pub season: Vec<u8>,
pub weather: Vec<u8>,
pub mean_temp_dc: Vec<i16>,
pub moisture_q: Vec<u8>,
}
/// Sentinel for "airless body — no temperature baseline" in
/// [`RegionGridLayer::mean_temp_dc`].
pub const REGION_TEMP_NONE_DC: i16 = i16::MIN;
/// Build the [`RegionGridLayer`] from a body's cached state (T-1113).
/// Returns `None` when the Region layer has not run (empty `regions`).
/// The stored region set is the dense covering grid `[0, cols) × [0, rows)`
/// (see `cascade::LayerRegionOutput` — no blend-padding ring), so the extent
/// comes from the maximum `RegionPos`, mirroring [`build_district_grid`].
pub fn build_region_grid(
state: &crate::atlas::body_world_state::BodyWorldState,
) -> Option<RegionGridLayer> {
if state.regions.is_empty() {
return None;
}
let cols = state.regions.keys().map(|(x, _)| *x).max().unwrap_or(0) as u32 + 1;
let rows = state.regions.keys().map(|(_, y)| *y).max().unwrap_or(0) as u32 + 1;
let n = (cols * rows) as usize;
let mut season = vec![0u8; n];
let mut weather = vec![0u8; n];
let mut mean_temp_dc = vec![REGION_TEMP_NONE_DC; n];
let mut moisture_q = vec![0u8; n];
for (&(x, y), profile) in &state.regions {
if x < 0 || y < 0 {
continue;
}
let i = (y as u32 * cols + x as u32) as usize;
if i < n {
season[i] = profile.clock.season as u8;
weather[i] = profile.clock.weather as u8;
mean_temp_dc[i] = match profile.clock.mean_temp_c {
Some(t) => {
((t * 10.0).round() as i32).clamp(i16::MIN as i32 + 1, i16::MAX as i32) as i16
}
None => REGION_TEMP_NONE_DC,
};
moisture_q[i] = profile.moisture_q.clamp(0, 100) as u8;
}
}
Some(RegionGridLayer {
cols,
rows,
season,
weather,
mean_temp_dc,
moisture_q,
})
}
// ---------------------------------------------------------------------------
// DistrictWindowLayer (D-226 T-1124 amendment, T-1137)
// ---------------------------------------------------------------------------
/// The requested district window: an `n × n` grid of TRUE 2 km districts
/// (or, at `granularity = 4`, an effective `(4n) × (4n)` grid of 512 m
/// quarters covering the SAME world extent — see `granularity` doc below),
/// derived on-demand via `district_profile::derive_district`/`derive_at_metres`
/// (D-226 T-1124 amendment §2, T-1150). **Echoes `center`/`n`/`granularity`
/// back** — this is the client's race-condition guard, not a convenience
/// field: because the derivation is pure and deterministic (D-227), the same
/// `(center, n, granularity, min_wl_m)` query always yields the same payload,
/// so the echoed tuple *is* the cache/staleness key the client compares
/// against its most recently requested window (`body_id` disambiguation
/// rides the enclosing `AtlasLayerResponse`, not the echo — see the
/// amendment).
///
/// All six arrays are dense row-major (`i = row * side + col`, where `side`
/// is `n` at district granularity or `4n` at quarter granularity), matching
/// the `DistrictGridLayer`/`RegionGridLayer` indexing convention. Per-cell
/// wire cost is 7 bytes (1+1+2+1+1+1) before MessagePack framing overhead
/// (D-226 T-1124 amendment §4).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DistrictWindowLayer {
pub center: DistrictPos,
/// Window extent in DISTRICTS — this does NOT change with granularity
/// (T-1150 design doc §2: "the window's `n` stays the DISTRICT extent").
/// The derived cell grid's actual side length is `n * granularity`.
pub n: u32,
/// The R5-redesigned granularity (T-1152) — see [`WindowGranularity`]'s
/// doc. Always populated (never `None`): the server always resolves a
/// concrete rung internally via [`resolve_window_granularity_v2`].
///
/// **T-1159:** the legacy `granularity: u32` echo this field used to sit
/// alongside (a wire-back-compat value for pre-T-1152 clients, which do
/// not exist — single-repo client/server pair) is retired. This enum
/// echo has been the sole granularity signal since T-1152.
pub granularity_v2: WindowGranularity,
/// The `min_wavelength_m` octave cutoff (T-1149) this window was derived
/// with, in whole metres (`0` = no cutoff). Echoed for the same reason as
/// `granularity` — two windows at identical `(center, n, granularity)`
/// but different cutoffs are NOT the same payload and must not alias.
pub min_wl_m: u32,
/// `MorphologyZone` discriminant, the frozen 17-zone vocabulary (D-239 §6).
pub morphology: Vec<u8>,
/// 0-100, matches `DistrictGridLayer.elev_q` encoding.
pub elev_q: Vec<u8>,
/// Deci-°C, [`REGION_TEMP_NONE_DC`] sentinel — the SAME scheme as
/// `RegionGridLayer.mean_temp_dc`, deliberately not a separate
/// district-tier quantization (one temperature colorizer spans both zoom
/// levels, D-226 T-1124 amendment §2).
pub temp_dc: Vec<i16>,
/// 0-100, matches `DistrictGridLayer` precedent.
pub moisture_q: Vec<u8>,
/// `VegetationClass` discriminant, 0-6 including `Marine = 6` (T-1126) —
/// any client palette MUST be exhaustive over `Marine` (D-226 T-1124
/// amendment §3, non-negotiable — the ocean-blind-vegetation bug this
/// field caught at the district tier).
pub vegetation: Vec<u8>,
/// `GlaciationGrade` discriminant, 0-4 (T-1127).
pub glaciation: Vec<u8>,
/// Invented river course polylines intersecting this window (T-1170,
/// Ruling 1b/1c/3h). **Not part of the windowed-family ceiling** (D-226
/// T-1124 §2 [HARD]) — that ceiling counts windowed-QUERY fields; this is
/// content of the ONE existing windowed payload, arriving on the same
/// echo key with the same staleness semantics as the six dense arrays
/// above (governance capture: `governance/decisions/architecture.md`,
/// D-226 amendment 2026-07-23, course-invention carrier note).
/// `#[serde(default)]` — the additive T-1124 §1 pattern: a pre-T-1170
/// payload/fixture decodes to an empty `Vec`, never an error.
#[serde(default)]
pub courses: Vec<RiverCourse>,
}
/// One invented river course polyline intersecting a window (T-1170, Ruling
/// 3h). Only edges whose amplitude-inflated chord bounding box intersects the
/// window ship; `points` are cropped to the window plus one station beyond
/// each edge of it (so client-side polyline drawing has continuity into the
/// next window without needing to stitch across a request boundary).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RiverCourse {
/// The packed upstream-cell id (`river_course::pack_cell_id`) — the
/// edge's stable identity (Ruling 2d), stable across every window/rung
/// that ships this same edge.
pub edge_id: u32,
/// `river_class` at the edge's upstream cell (0=stream, 1=tributary,
/// 2=trunk) — the SAME vocabulary `RiverNetwork.river_class` uses, so
/// client-side per-rung/per-class filtering (Araminta's presentation
/// tables, Ruling 5c) reuses the existing decode path.
pub class: u8,
/// Points along the course, in absolute world metres, cropped to this
/// window (+ one station beyond each edge, Ruling 3h).
pub points: Vec<(i32, i32)>,
/// How this course's downstream end resolves (Ruling 3e/3f) — `None` when
/// the course's true downstream terminus (whether `Mouth` or
/// `ContinuesBeyondWindow`) falls outside this window's cropped point
/// range, so nothing about the terminus can be asserted from this
/// payload alone.
pub terminus: CourseTerminus,
}
/// [`RiverCourse::terminus`] — the course's downstream-end classification on
/// the wire (Ruling 3h).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum CourseTerminus {
/// The course's downstream end is not within this window's cropped point
/// range — the real terminus (whatever it is) lies in a different window.
None,
/// The course reaches a real sea/lake crossing within this window (Ruling
/// 3e) — the last point in `points` is the resolved invented-coast
/// terminus.
Mouth,
/// The course reaches a grid-edge drain (Ruling 3f) — a grid artifact,
/// not a mouth; the last point in `points` is the last in-grid station,
/// with no mouth marker implied.
EdgeDrain,
/// The course's downstream end is a real river cell beyond this window's
/// crop range — i.e. an `Interior`-terminus edge whose full extent is
/// wider than what got cropped in. The client draws the polyline without
/// a terminus marker and expects it to continue in an adjacent window.
ContinuesBeyondWindow,
}
/// Key for the server-side window derive cache (T-1137, extended T-1150,
/// extended again T-1152): `(body_id, center, n, granularity, min_wl_m)`.
/// D-227 purity means a cached window is valid forever for a given
/// body+seed — no staleness/TTL invalidation is needed, only a bound on
/// unbounded growth (see [`DistrictWindowCache`]). `granularity`/`min_wl_m`
/// MUST be part of the key — the design doc's aliasing risk (§3): a
/// granularity-4 request at the same `(body, center, n)` as a granularity-1
/// request is a DIFFERENT payload and must land in a different cache slot,
/// never silently overwrite or be served by the other.
///
/// **The granularity slot is [`WindowGranularity`] itself (T-1152), not the
/// legacy `u32`** — carrying the full enum here (rather than relying on
/// [`WindowGranularity::key_u32`]'s reserved-sentinel trick alone) is what
/// makes a `Region` window's cache slot structurally distinct from a
/// `District`/`Quarter` one, satisfying the "carry the new representation"
/// requirement at the cache-key touch point directly rather than through an
/// encoding side-channel. `WindowGranularity`'s `Ord` derive (declaration
/// order: `Quarter < District < Region`) makes this legal as a `BTreeMap`
/// key (D-010 determinism — ordered iteration, no `HashMap`).
pub type DistrictWindowKey = (String, DistrictPos, u32, WindowGranularity, u32);
/// Bounded LRU-ish cache of completed district-window derives (T-1137), a
/// sibling to [`BodyWorldStateCache`] rather than a field on it: windows are
/// keyed on the *request* `(body, center, n)`, not the body alone (see the
/// struct-level doc on [`AtlasLayerResponse`]), so they don't fit the
/// per-body cache's keying at all. Eviction is capacity-only FIFO-by-insertion
/// (not access-recency LRU like `BodyWorldStateCache`) — window requests are
/// comparatively rare and cheap to re-derive on a genuine miss (a background
/// re-submit, never a stall), so exact recency tracking isn't worth the
/// bookkeeping; a simple bound against unbounded growth is enough.
#[derive(Resource, Debug, Default)]
pub struct DistrictWindowCache {
entries: std::collections::BTreeMap<DistrictWindowKey, DistrictWindowLayer>,
/// Insertion order, oldest first — the eviction queue.
order: std::collections::VecDeque<DistrictWindowKey>,
capacity: usize,
}
/// Default capacity for [`DistrictWindowCache`] — generous relative to
/// `BodyWorldStateCache::CACHE_CAPACITY` (50 bodies) since each entry here is
/// far smaller (a handful of `Vec<u8>`/`Vec<i16>` at `n ≤ 64`, ≤ 28 KiB raw vs.
/// `BodyWorldState`'s full heightmap + districts + regions), and several
/// windows can legitimately be live per body (a player panning around).
pub const DISTRICT_WINDOW_CACHE_CAPACITY: usize = 256;
impl DistrictWindowCache {
pub fn new(capacity: usize) -> Self {
Self {
entries: std::collections::BTreeMap::new(),
order: std::collections::VecDeque::new(),
capacity,
}
}
/// Look up a cached window by its full key. Never mutates — window
/// validity has no time component (D-227), so there is nothing to bump.
pub fn get(&self, key: &DistrictWindowKey) -> Option<&DistrictWindowLayer> {
self.entries.get(key)
}
/// Insert a completed window derive, evicting the oldest entry first if
/// at capacity. Re-inserting an existing key replaces the value without
/// moving it in the eviction order (D-227: the value can only ever be
/// identical, so this is a no-op in practice, but stays correct either way).
pub fn insert(&mut self, key: DistrictWindowKey, layer: DistrictWindowLayer) {
if !self.entries.contains_key(&key) {
if self.entries.len() >= self.capacity {
if let Some(victim) = self.order.pop_front() {
self.entries.remove(&victim);
}
}
self.order.push_back(key.clone());
}
self.entries.insert(key, layer);
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
/// One derived cell's packed wire fields — the per-cell output of the window
/// loop body, shared between the serial and parallel builders (T-1151) so the
/// packing logic can never drift between them.
struct WindowCell {
morphology: u8,
elev_q: u8,
temp_dc: i16,
moisture_q: u8,
vegetation: u8,
glaciation: u8,
}
/// Derive one window cell at `(row, col)` and pack its wire fields. Pure
/// (D-227) — the whole reason row-chunked `par_iter` (T-1151) is safe: every
/// cell is an independent function of its own world-metre position, nothing
/// shared mutably.
///
/// `granularity` (T-1150, widened to [`WindowGranularity`] T-1152) selects
/// BOTH the metre spacing between cells ([`WindowGranularity::spacing_m`])
/// AND the derivation function: finer-than-district rungs (`District`,
/// `Quarter`) call `derive_at_metres` (full derivation, `invent_primitives`
/// included) exactly as T-1150 shipped; `Region` calls
/// [`crate::atlas::district_profile::derive_orbital_at_metres`] instead — the
/// design doc §2/§4 orbital row's region-baseline-blend-only path, no
/// `invent_primitives` call. This is the ONE place the two derivation
/// functions fork based on rung — everything else in the window-building
/// pipeline (`scatter_row`, the six-array packing, the cache/echo plumbing)
/// is identical regardless of which function ran, because both produce a
/// `DistrictProfile` and this function's WindowCell-packing tail (below) is
/// shared.
///
/// `half_cells` is HALF the cell-grid side (`side / 2`, already in the
/// caller's cell units, not districts), so `center` (a `DistrictPos`, always
/// district-scale) is converted to a world-metre origin once by the caller
/// and offset here in `spacing_m()` units — this is what makes the quarter
/// grid cover the SAME world rect as the district grid at 4x the cell
/// density (design doc §2 Option B), and what makes a region-granularity
/// window cover a proportionally larger world rect at 1/100x the cell
/// density (D-243: `DISTRICTS_PER_REGION = 100`).
#[allow(clippy::too_many_arguments)]
fn derive_window_cell(
seed: SeedChain,
body_id: &str,
params: &crate::atlas::district_profile::BodyParams,
ta: &crate::atlas::features::TerrainAnalysis,
climate: &crate::atlas::district_profile::ClimateConstants,
center_world_m: (f64, f64),
half_cells: i32,
granularity: WindowGranularity,
min_wavelength_m: f64,
row: i32,
col: i32,
nearby_courses: &[InventedCourse],
) -> WindowCell {
// Row 0 = northmost, matching aliveness_probe's render_window_panels
// (derive_at_metres maps negative wy to negative lat_frac = north).
let step_m = granularity.spacing_m();
let wx = center_world_m.0 + (col - half_cells) as f64 * step_m;
let wy = center_world_m.1 + (row - half_cells) as f64 * step_m;
let prof = match granularity {
WindowGranularity::Region => crate::atlas::district_profile::derive_orbital_at_metres(
seed, body_id, params, ta, wx, wy, climate,
),
WindowGranularity::District | WindowGranularity::Quarter => {
crate::atlas::district_profile::derive_at_metres(
seed,
body_id,
params,
ta,
wx,
wy,
climate,
min_wavelength_m,
nearby_courses,
)
}
};
WindowCell {
morphology: prof.morphology_zone as u8,
elev_q: prof.elev_q.clamp(0, 100) as u8,
temp_dc: match prof.temperature_c {
Some(t) => {
((t * 10.0).round() as i32).clamp(i16::MIN as i32 + 1, i16::MAX as i32) as i16
}
None => REGION_TEMP_NONE_DC,
},
moisture_q: prof.moisture_q.clamp(0, 100) as u8,
vegetation: prof.vegetation_class as u8,
glaciation: prof.glaciation_grade as u8,
}
}
/// Scatter a computed row of [`WindowCell`]s into the six flat output arrays
/// at row-major offset `row * side`.
#[allow(clippy::too_many_arguments)]
fn scatter_row(
row_cells: &[WindowCell],
row: i32,
side: i32,
morphology: &mut [u8],
elev_q: &mut [u8],
temp_dc: &mut [i16],
moisture_q: &mut [u8],
vegetation: &mut [u8],
glaciation: &mut [u8],
) {
let base = (row * side) as usize;
for (col, cell) in row_cells.iter().enumerate() {
let i = base + col;
morphology[i] = cell.morphology;
elev_q[i] = cell.elev_q;
temp_dc[i] = cell.temp_dc;
moisture_q[i] = cell.moisture_q;
vegetation[i] = cell.vegetation;
glaciation[i] = cell.glaciation;
}
}
/// Resolve `center` (a district-grid position) to its world-metre origin —
/// shared by both window builders so the district->metres convention can
/// never drift between them. Mirrors `derive_district`'s own quantization
/// (`district_profile.rs`) exactly: `dm = DISTRICT_M`, `(dx*dm, dy*dm)`.
fn center_to_world_m(center: DistrictPos) -> (f64, f64) {
let dm = DISTRICT_M as f64;
(center.0 as f64 * dm, center.1 as f64 * dm)
}
/// Peak Stage-B course amplitude never exceeds this fraction of an edge's
/// chord (mirrors `river_course::STAGE_B_PEAK_FRACTION_OF_CHORD` — kept as an
/// independent constant here, not a re-export, so the culling inflation and
/// the actual amplitude cap can never silently decouple through a shared
/// mutable import path; a `const _: () = assert!(...)` below pins the two
/// values equal). Used to inflate an edge's chord bounding box before the
/// window-intersection cull (Ruling 3h: "amplitude-inflated chord bbox").
const COURSE_BBOX_INFLATION_FRACTION: f64 = 0.08;
const _: () = assert!(
(COURSE_BBOX_INFLATION_FRACTION * 1_000_000.0) as i64
== (crate::atlas::river_course::STAGE_B_PEAK_FRACTION_OF_CHORD * 1_000_000.0) as i64
);
/// The window's world-metre rect, `(x0, y0, x1, y1)` — the SAME convention
/// [`derive_window_cell`] uses to place cells: `step = granularity.spacing_m()`,
/// `[center_world_m - half*step, center_world_m + (side-half)*step)` on each
/// axis. Shared by [`invent_courses_near_window`] and [`crop_courses_for_wire`]
/// so the rect can never drift between the two.
fn window_world_rect(
center_world_m: (f64, f64),
half_cells: i32,
side: i32,
step_m: f64,
) -> (f64, f64, f64, f64) {
(
center_world_m.0 - half_cells as f64 * step_m,
center_world_m.1 - half_cells as f64 * step_m,
center_world_m.0 + (side - half_cells) as f64 * step_m,
center_world_m.1 + (side - half_cells) as f64 * step_m,
)
}
/// Invent every river course whose amplitude-inflated chord bounding box
/// intersects this window (T-1170 A2, Ruling 1b/3h/4b) — the FULL-precision
/// [`InventedCourse`] list, NOT yet cropped to the window or converted to the
/// wire [`RiverCourse`] shape. This is the single source both consumers read
/// from: [`derive_window_cell`]'s per-cell riparian test (T-1168, Ruling 4b:
/// "in the window path, T-1170's already-invented courses") and
/// [`crop_courses_for_wire`]'s wire packing — computed ONCE per window,
/// before the per-cell derive loop, rather than twice or per-cell.
///
/// Pure function of `(seed, body, river_network, window rect, granularity,
/// min_wavelength_m)` — independent of whether the caller derives cells
/// serially or in parallel, which is why both [`build_district_window_layer`]
/// and its `#[cfg(test)]` serial twin call this SAME function.
///
/// Region granularity draws courses via the whole-body skeleton path (Ruling
/// 5a — the rung-truncated course degenerates to the straight chord at
/// Region spacing, so the skeleton dots/chords ARE the course there). No
/// windowed course invention at Region — an empty result here is correct,
/// not a gap.
#[allow(clippy::too_many_arguments)]
fn invent_courses_near_window(
seed: SeedChain,
params: &crate::atlas::district_profile::BodyParams,
ta: &crate::atlas::features::TerrainAnalysis,
river_network: &RiverNetwork,
window_rect: (f64, f64, f64, f64),
granularity: WindowGranularity,
min_wavelength_m: f64,
) -> Vec<InventedCourse> {
if granularity == WindowGranularity::Region {
return Vec::new();
}
let step_m = granularity.spacing_m();
let (win_x0, win_y0, win_x1, win_y1) = window_rect;
let edges = river_course::build_edges(river_network);
let mut courses = Vec::new();
for edge in &edges {
let anchor_a = crate::atlas::district_profile::pixel_to_world_m(
edge.upstream.1 as f64,
edge.upstream.0 as f64,
ta.w,
ta.h,
params.body_radius_km,
);
let anchor_b = crate::atlas::district_profile::pixel_to_world_m(
edge.downstream.1 as f64,
edge.downstream.0 as f64,
ta.w,
ta.h,
params.body_radius_km,
);
let chord_m =
((anchor_a.0 - anchor_b.0).powi(2) + (anchor_a.1 - anchor_b.1).powi(2)).sqrt();
let inflate_m = chord_m * COURSE_BBOX_INFLATION_FRACTION;
let (bx0, bx1) = (
anchor_a.0.min(anchor_b.0) - inflate_m,
anchor_a.0.max(anchor_b.0) + inflate_m,
);
let (by0, by1) = (
anchor_a.1.min(anchor_b.1) - inflate_m,
anchor_a.1.max(anchor_b.1) + inflate_m,
);
// Bbox-vs-window intersection cull — most edges cull to zero for any
// given window (Ruling 4b's "most cells cull to zero edges" applies
// symmetrically here: most EDGES cull out of any one window).
if bx1 < win_x0 || bx0 > win_x1 || by1 < win_y0 || by0 > win_y1 {
continue;
}
courses.push(river_course::invent_course(
seed,
edge,
ta,
params,
step_m,
min_wavelength_m,
));
}
courses
}
/// Crop the window's already-invented courses ([`invent_courses_near_window`])
/// to the wire [`RiverCourse`] shape (Ruling 3h) — window rect + one station
/// beyond each edge, terminus resolution (A3, Ruling 3e/3f).
///
/// `station_spacing_m` is the rung's own cell spacing (`granularity.spacing_m()`
/// — District 2,048 m / Quarter 512 m) — threaded to [`resolve_mouth_terminus`]'s
/// land-at-final-anchor probe, which extends "one cell length" (Ruling 3e's own
/// words), not one Stage-B segment length (Tyre, PR #197 review issue 1).
#[allow(clippy::too_many_arguments)]
fn crop_courses_for_wire(
invented: &[InventedCourse],
window_rect: (f64, f64, f64, f64),
seed: SeedChain,
body_id: &str,
params: &crate::atlas::district_profile::BodyParams,
ta: &crate::atlas::features::TerrainAnalysis,
climate: &crate::atlas::district_profile::ClimateConstants,
min_wavelength_m: f64,
station_spacing_m: f64,
) -> Vec<RiverCourse> {
invented
.iter()
.filter_map(|course| {
crop_course_to_window(
course,
window_rect,
seed,
body_id,
params,
ta,
climate,
min_wavelength_m,
station_spacing_m,
)
})
.collect()
}
/// Crop an [`InventedCourse`]'s full-edge point list to `window_rect` (+ one
/// station beyond each edge, Ruling 3h) and resolve its wire [`CourseTerminus`]
/// (A3, Ruling 3e/3f). Returns `None` when the course has zero points inside
/// (or adjacent to) the window — the caller's cull is a cheap bbox pre-filter,
/// this is the exact per-point check.
#[allow(clippy::too_many_arguments)]
fn crop_course_to_window(
course: &InventedCourse,
window_rect: (f64, f64, f64, f64),
seed: SeedChain,
body_id: &str,
params: &crate::atlas::district_profile::BodyParams,
ta: &crate::atlas::features::TerrainAnalysis,
climate: &crate::atlas::district_profile::ClimateConstants,
min_wavelength_m: f64,
station_spacing_m: f64,
) -> Option<RiverCourse> {
let (x0, y0, x1, y1) = window_rect;
let inside = |p: &(f64, f64)| p.0 >= x0 && p.0 <= x1 && p.1 >= y0 && p.1 <= y1;
let n = course.points.len();
let mut first_in: Option<usize> = None;
let mut last_in: Option<usize> = None;
for (i, p) in course.points.iter().enumerate() {
if inside(p) {
first_in.get_or_insert(i);
last_in = Some(i);
}
}
let (first_in, last_in) = match (first_in, last_in) {
(Some(a), Some(b)) => (a, b),
_ => return None, // no point of this course falls inside the window
};
// Crop range: one station beyond each edge (Ruling 3h), clamped to the
// course's own point range.
let lo = first_in.saturating_sub(1);
let hi = (last_in + 1).min(n.saturating_sub(1));
let points: Vec<(i32, i32)> = course.points[lo..=hi]
.iter()
.map(|p| (p.0.round() as i32, p.1.round() as i32))
.collect();
// Terminus resolution (A3, Ruling 3e/3f): only meaningful if the
// course's TRUE downstream end (the last point of the full, uncropped
// course) is within this cropped range — otherwise the real terminus
// lies in a different window and this one just sees a mid-course
// passthrough.
let true_end_included = hi == n.saturating_sub(1);
let terminus = if !true_end_included {
CourseTerminus::ContinuesBeyondWindow
} else {
match course.terminus {
EdgeTerminusKind::EdgeDrain => CourseTerminus::EdgeDrain,
EdgeTerminusKind::Interior => CourseTerminus::ContinuesBeyondWindow,
EdgeTerminusKind::Mouth => {
match resolve_mouth_terminus(
course,
seed,
body_id,
params,
ta,
climate,
min_wavelength_m,
station_spacing_m,
) {
Some(mouth_point) => {
// Replace the cropped course's tail with the resolved
// mouth point (bisected against the last land
// station) so the wire polyline ends exactly at the
// invented-coast crossing, not at the raw upstream
// anchor placeholder `build_edges` recorded.
let mut pts = points;
if let Some(last) = pts.last_mut() {
*last = (mouth_point.0.round() as i32, mouth_point.1.round() as i32);
}
return Some(RiverCourse {
edge_id: course.edge_id,
class: course.class,
points: pts,
terminus: CourseTerminus::Mouth,
});
}
None => CourseTerminus::None, // degenerate: never found water (Ruling 3e land-at-anchor case)
}
}
}
};
Some(RiverCourse {
edge_id: course.edge_id,
class: course.class,
points,
terminus,
})
}
/// Number of bisection iterations for the mouth-terminus search (Ruling 3e,
/// binding: "fixed 6 iterations").
const MOUTH_BISECT_ITERATIONS: u32 = 6;
/// Walk a `Mouth`-terminus course's stations upstream→downstream, sampling
/// the SAME rung-consistent morphology water verdict the window's own cells
/// use (`derive_at_metres(...).morphology_zone` — Ruling 3e, binding: "never
/// raw `ocean_frac`"). First water station found → bisect against the
/// previous land station (fixed [`MOUTH_BISECT_ITERATIONS`]) → the resolved
/// terminus point. If no station (including one D8-direction cell-length
/// probe past the final anchor) samples water, returns `None` — the
/// degenerate "drawn coast receded past this edge" case (Ruling 3e), which
/// the caller renders with no mouth flag.
///
/// `station_spacing_m` is the rung's own cell spacing — the probe extends
/// exactly "one cell length" past the final anchor (Ruling 3e's own words),
/// in the direction of the final Stage-B segment, but scaled to
/// `station_spacing_m` rather than that segment's own (possibly much
/// shorter, near-zero at a taper-to-zero anchor) length.
fn resolve_mouth_terminus(
course: &InventedCourse,
seed: SeedChain,
body_id: &str,
params: &crate::atlas::district_profile::BodyParams,
ta: &crate::atlas::features::TerrainAnalysis,
climate: &crate::atlas::district_profile::ClimateConstants,
min_wavelength_m: f64,
station_spacing_m: f64,
) -> Option<(f64, f64)> {
let is_water = |p: (f64, f64)| -> bool {
// `&[]`: the mouth-termination water-verdict probe has no use for
// the riparian signal (it only reads `morphology_zone`, never
// `vegetation_class`) — an empty course slice is a correct, cheap
// no-op here (T-1168's `nearby_courses` param never affects
// morphology, only vegetation, so this can never mis-terminate).
let prof = crate::atlas::district_profile::derive_at_metres(
seed,
body_id,
params,
ta,
p.0,
p.1,
climate,
min_wavelength_m,
&[],
);
matches!(
prof.morphology_zone,
crate::simulation::generator::MorphologyZone::OpenOcean
| crate::simulation::generator::MorphologyZone::Lake
)
};
let pts = &course.points;
if pts.is_empty() {
return None;
}
// Walk upstream -> downstream (points are already stored in that order).
let mut prev_land = pts[0];
for &p in pts.iter() {
if is_water(p) {
return Some(bisect_to_waterline(prev_land, p, is_water));
}
prev_land = p;
}
// Final anchor still land: extend ONE CELL LENGTH (`station_spacing_m` —
// Ruling 3e's own words, "up to one cell length probing", not one
// Stage-B segment length, which can be much shorter near a
// taper-to-zero anchor — Tyre, PR #197 review issue 1) along the final
// segment's own direction, as a single probe.
if pts.len() >= 2 {
let a = pts[pts.len() - 2];
let b = pts[pts.len() - 1];
let (dx, dy) = (b.0 - a.0, b.1 - a.1);
let len = (dx * dx + dy * dy).sqrt();
if len > 1e-6 {
let (ux, uy) = (dx / len, dy / len); // unit direction of the final segment
let probe = (b.0 + ux * station_spacing_m, b.1 + uy * station_spacing_m);
if is_water(probe) {
return Some(bisect_to_waterline(b, probe, is_water));
}
}
}
None // degenerate: still land — terminate with no mouth flag (caller's job)
}
/// Bisect between a known-land point and a known-water point for
/// [`MOUTH_BISECT_ITERATIONS`] iterations, returning the point closest to the
/// water side of the crossing.
fn bisect_to_waterline(
land: (f64, f64),
water: (f64, f64),
is_water: impl Fn((f64, f64)) -> bool,
) -> (f64, f64) {
let mut lo = land; // land
let mut hi = water; // water
for _ in 0..MOUTH_BISECT_ITERATIONS {
let mid = ((lo.0 + hi.0) * 0.5, (lo.1 + hi.1) * 0.5);
if is_water(mid) {
hi = mid;
} else {
lo = mid;
}
}
hi
}
/// Build a [`DistrictWindowLayer`] by deriving every cell in the window
/// around `center` (T-1137, extended T-1150). Mirrors
/// `aliveness_probe::render_window_panels`'s derive loop exactly (the probe
/// this design promotes to a served layer, D-226 T-1124 amendment §2) — same
/// row-major indexing, same per-cell derive call.
///
/// `n` MUST already be clamped by the caller ([`clamp_window_n`],
/// `[1, DISTRICT_WINDOW_MAX_N]` AND the granularity-aware `WIRE_CAP_CELLS`
/// ceiling) — this function trusts it verbatim (the clamp is
/// `handle_atlas_request`'s job, applied once at the wire boundary, not
/// re-checked on every internal caller per the existing codebase convention
/// of clamping at the edge).
///
/// `n` is always the window extent in DISTRICTS (design doc §2 Option B: "the
/// window's `n` stays the DISTRICT extent"). The derived cell-grid side
/// length is [`WindowGranularity::cell_grid_side`] — `n × 4` at `Quarter`,
/// `n` at `District`, `round(n / 100)` at `Region` (T-1152, D-243's
/// `DISTRICTS_PER_REGION = 100`; see that method's doc for the full
/// finer-multiplies/coarser-divides rationale). Full reclassification at the
/// rung's own spacing (`derive_at_metres`/`derive_orbital_at_metres` per
/// [`derive_window_cell`]'s dispatch), never a coarser-cell interpolation.
///
/// **Row-chunked `par_iter` (T-1151):** each cell is a pure function of its
/// own position (D-227), so rows can derive in parallel with no shared
/// mutable state. Chunking by ROW (not per-cell) amortizes Rayon's own
/// task-dispatch overhead against the ~1.2 µs/cell derive cost (design doc
/// §7: naive per-cell parallelization risks the dispatch overhead itself
/// costing more than the work) — one Rayon task per row means `side` tasks of
/// `side` cells each, not `side²` tasks of one cell each. This applies
/// unchanged to the `Region` rung (T-1152 step 4: "progressive capped-density
/// tiling on the SAME carrier ... No new message shape") — the SAME row-chunked
/// parallel loop, cache, and coalescing machinery serve every rung.
/// [`build_district_window_layer_serial`] is kept alongside this as the
/// golden-comparison baseline (T-1151 acceptance: bit-identical serial vs.
/// parallel output, exact row-major array ordering preserved either way).
#[allow(clippy::too_many_arguments)]
pub fn build_district_window_layer(
seed: SeedChain,
body_id: &str,
params: &crate::atlas::district_profile::BodyParams,
ta: &crate::atlas::features::TerrainAnalysis,
river_network: &RiverNetwork,
center: DistrictPos,
n: u32,
climate: &crate::atlas::district_profile::ClimateConstants,
granularity: WindowGranularity,
min_wl_m: u32,
) -> DistrictWindowLayer {
use rayon::prelude::*;
let side = granularity.cell_grid_side(n);
let half = side / 2;
let min_wavelength_m = min_wl_m as f64;
let center_world_m = center_to_world_m(center);
let cells = (side * side) as usize;
let mut morphology = vec![0u8; cells];
let mut elev_q = vec![0u8; cells];
let mut temp_dc = vec![REGION_TEMP_NONE_DC; cells];
let mut moisture_q = vec![0u8; cells];
let mut vegetation = vec![0u8; cells];
let mut glaciation = vec![0u8; cells];
// T-1170 A2/T-1168 A5: invent this window's river courses ONCE, before
// the per-cell derive loop — this is the single source both the per-cell
// riparian test (T-1168, threaded into `derive_window_cell` below) and
// the wire course packing (crop step, after the loop) read from. Doing
// this first (not per-cell, not twice) is what keeps the window-cost
// delta close to the Discipline item 2 ~5% budget.
let step_m = granularity.spacing_m();
let window_rect = window_world_rect(center_world_m, half, side, step_m);
let invented_courses = invent_courses_near_window(
seed,
params,
ta,
river_network,
window_rect,
granularity,
min_wavelength_m,
);
// One Rayon task per row: derive_window_cell(row, ..) for every col, then
// scatter that row's results into the flat arrays. Row order in the
// output collection is preserved by `par_iter` (it yields in index
// order), so the scatter below reproduces the exact row-major layout the
// serial loop produces.
let rows: Vec<Vec<WindowCell>> = (0..side)
.into_par_iter()
.map(|row| {
(0..side)
.map(|col| {
derive_window_cell(
seed,
body_id,
params,
ta,
climate,
center_world_m,
half,
granularity,
min_wavelength_m,
row,
col,
&invented_courses,
)
})
.collect()
})
.collect();
for (row, row_cells) in rows.into_iter().enumerate() {
scatter_row(
&row_cells,
row as i32,
side,
&mut morphology,
&mut elev_q,
&mut temp_dc,
&mut moisture_q,
&mut vegetation,
&mut glaciation,
);
}
let courses = crop_courses_for_wire(
&invented_courses,
window_rect,
seed,
body_id,
params,
ta,
climate,
min_wavelength_m,
step_m,
);
DistrictWindowLayer {
center,
n,
granularity_v2: granularity,
min_wl_m,
morphology,
elev_q,
temp_dc,
moisture_q,
vegetation,
glaciation,
courses,
}
}
/// Serial twin of [`build_district_window_layer`] (T-1151) — the pre-parallel
/// row/col double loop, kept ONLY as the golden-comparison baseline for the
/// bit-identical serial-vs-parallel test. Not used by production callers.
#[cfg(test)]
#[allow(clippy::too_many_arguments)]
fn build_district_window_layer_serial(
seed: SeedChain,
body_id: &str,
params: &crate::atlas::district_profile::BodyParams,
ta: &crate::atlas::features::TerrainAnalysis,
river_network: &RiverNetwork,
center: DistrictPos,
n: u32,
climate: &crate::atlas::district_profile::ClimateConstants,
granularity: WindowGranularity,
min_wl_m: u32,
) -> DistrictWindowLayer {
let side = granularity.cell_grid_side(n);
let half = side / 2;
let min_wavelength_m = min_wl_m as f64;
let center_world_m = center_to_world_m(center);
let cells = (side * side) as usize;
let mut morphology = vec![0u8; cells];
let mut elev_q = vec![0u8; cells];
let mut temp_dc = vec![REGION_TEMP_NONE_DC; cells];
let mut moisture_q = vec![0u8; cells];
let mut vegetation = vec![0u8; cells];
let mut glaciation = vec![0u8; cells];
let step_m = granularity.spacing_m();
let window_rect = window_world_rect(center_world_m, half, side, step_m);
let invented_courses = invent_courses_near_window(
seed,
params,
ta,
river_network,
window_rect,
granularity,
min_wavelength_m,
);
for row in 0..side {
let row_cells: Vec<WindowCell> = (0..side)
.map(|col| {
derive_window_cell(
seed,
body_id,
params,
ta,
climate,
center_world_m,
half,
granularity,
min_wavelength_m,
row,
col,
&invented_courses,
)
})
.collect();
scatter_row(
&row_cells,
row,
side,
&mut morphology,
&mut elev_q,
&mut temp_dc,
&mut moisture_q,
&mut vegetation,
&mut glaciation,
);
}
let courses = crop_courses_for_wire(
&invented_courses,
window_rect,
seed,
body_id,
params,
ta,
climate,
min_wavelength_m,
step_m,
);
DistrictWindowLayer {
center,
n,
granularity_v2: granularity,
min_wl_m,
morphology,
elev_q,
temp_dc,
moisture_q,
vegetation,
glaciation,
courses,
}
}
// ---------------------------------------------------------------------------
// 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)
// ---------------------------------------------------------------------------
/// One node in the [`RoadGraphLayer`] overlay — a settlement junction or a
/// waypoint. Trimmed from the internal [`crate::atlas::road_graph::RoadNode`]:
/// `degree` and `parent_edge` are internal bookkeeping a planetary-map overlay
/// doesn't need (degree is trivially re-derivable client-side by counting
/// edges per node index if a renderer wants junction highlighting).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RoadGraphNode {
/// Position in working-heightmap-grid coordinates `(row, col)` — the same
/// space as `Layer1Output` attractors/rivers and `SettlementLayer` positions.
pub position: (u16, u16),
pub kind: RoadNodeKind,
/// The settlement's `city_id` (cross-references `SettlementLayer`), or
/// `None` for a waypoint.
pub city_id: Option<u64>,
}
/// One edge in the [`RoadGraphLayer`] overlay — a routed road or rail segment.
/// Trimmed from [`crate::atlas::road_graph::RoadEdge`]: `length_cells` is an
/// internal A* routing-grid measure with no meaning outside that grid's scale
/// (the polyline `path` is what an overlay actually draws).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RoadGraphEdge {
/// `nodes` indices of the endpoint settlements (`from < to`).
pub from: usize,
pub to: usize,
/// Routed polyline in working-heightmap-grid coordinates `(row, col)`.
pub path: Vec<(u16, u16)>,
pub maintenance: MaintenanceAuthority,
/// `true` if this edge is a railroad; `false` is a road.
pub is_rail: bool,
/// Joined `systems.db` named-route id, if any (empty pool today — D-223).
pub named_route_id: Option<String>,
}
/// The inter-settlement road/rail graph, trimmed for the Atlas planetary-map
/// overlay (T-960 §1, D-211, T-1038). See [`RoadGraphNode`]/[`RoadGraphEdge`]
/// for what was dropped from the internal `RoadGraph`.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RoadGraphLayer {
pub nodes: Vec<RoadGraphNode>,
pub edges: Vec<RoadGraphEdge>,
}
/// Build the [`RoadGraphLayer`] from a body's cached state (T-960 §1).
/// Returns `None` when the RoadGraph layer has not run, which coincides
/// exactly with "no settlements placed" (`build_road_graph` returns an empty
/// graph for zero placements, and every placement yields at least one node).
pub fn build_road_graph_layer(state: &BodyWorldState) -> Option<RoadGraphLayer> {
if state.road_graph.nodes.is_empty() {
return None;
}
let nodes = state
.road_graph
.nodes
.iter()
.map(|n| RoadGraphNode {
position: n.position,
kind: n.kind,
city_id: n.city_id,
})
.collect();
let edges = state
.road_graph
.edges
.iter()
.map(|e| RoadGraphEdge {
from: e.from,
to: e.to,
path: e.path.clone(),
maintenance: e.maintenance,
is_rail: e.is_rail,
named_route_id: e.named_route_id.clone(),
})
.collect();
Some(RoadGraphLayer { nodes, edges })
}
// ---------------------------------------------------------------------------
// SettlementLayer (T-960 §2, #955)
// ---------------------------------------------------------------------------
/// Coarse settlement size class for the Atlas overlay (T-960 §2), derived from
/// raw population using the same Tier A/B population cutoffs the D-211
/// placement pipeline already uses (`attractor_matching::match_cities`):
/// Tier A (≥ 1,000,000 or `NameLocked`) settlements are `Major`, Tier B
/// (50,000999,999) are `Standard`, and everything else (Tier C / synthetic
/// overflow) is `Minor`. A display bucket, not new simulation truth.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum SettlementSizeClass {
Major,
Standard,
Minor,
}
impl SettlementSizeClass {
/// Bucket a raw population using the D-211 Tier A/B cutoffs.
pub fn from_population(population: i64) -> Self {
if population >= 1_000_000 {
SettlementSizeClass::Major
} else if population >= 50_000 {
SettlementSizeClass::Standard
} else {
SettlementSizeClass::Minor
}
}
}
/// One placed settlement in the [`SettlementLayer`] overlay (T-960 §2).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SettlementEntry {
pub city_id: u64,
pub name: String,
/// Position in working-heightmap-grid coordinates `(row, col)` — the same
/// space as `Layer1Output` attractors/rivers (T-960 §2: match the
/// coordinate convention layer1 features already use so the client
/// transforms identically).
pub position: (u16, u16),
pub size_class: SettlementSizeClass,
/// Authored `atlas_city_names.kind == 'capital'` (not population-derived).
pub is_capital: bool,
/// Cheap derived flag: `true` if the settlement's anchoring attractor is
/// water-adjacent (`CoastalAccess` / `RiverMouth` / `LakeShore`).
pub is_port: bool,
}
/// The settlement-placement overlay for one body (T-960 §2, #955, D-211).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SettlementLayer {
pub settlements: Vec<SettlementEntry>,
}
/// `true` for the water-adjacent attractor types a settlement counts as a
/// "port" for cheaply (T-960 §2). No "foothold" flag: unlike `is_port`, there
/// is no existing concept in the placement data this could derive from
/// without inventing new business logic — left out (see the T-960 report).
fn is_port_attractor(at: AttractorType) -> bool {
matches!(
at,
AttractorType::CoastalAccess | AttractorType::RiverMouth | AttractorType::LakeShore
)
}
/// Build the [`SettlementLayer`] from a body's cached state (T-960 §2).
/// Returns `None` when the Settlement layer has not placed any city yet.
pub fn build_settlement_layer(state: &BodyWorldState) -> Option<SettlementLayer> {
if state.placements.is_empty() {
return None;
}
let settlements = state
.placements
.iter()
.map(|p| SettlementEntry {
city_id: p.city_id,
name: p.name.clone(),
position: p.position,
size_class: SettlementSizeClass::from_population(p.population),
is_capital: p.is_capital,
is_port: is_port_attractor(p.attractor_type),
})
.collect();
Some(SettlementLayer { settlements })
}
/// Normalize a wire-supplied district-window centre against the body's
/// physical geometry (T-1142 — a letterbox-click bug sent `window_center`
/// wildly out of a body's valid range; the server accepted it, derived
/// clamped garbage inside `derive_district`, and CACHED that garbage under
/// the raw un-normalized key). Mirrors `district_profile::derive_district`'s
/// own forward mapping (`district_profile.rs:1426-1451`) EXACTLY, so a window
/// centre that survives this normalization derives identically to how
/// `derive_district` would have resolved it anyway — this function only
/// closes the gap between "derive_district silently clamps/wraps internally"
/// and "the SERVING path (cache key, coalescing key) saw the raw value".
///
/// **Column (longitude) wraps** — `rem_euclid` against the body's
/// circumference in districts, mirroring the forward map's
/// `(wx / circumference_m).rem_euclid(1.0)` (longitude is periodic; a click
/// at column 12276 on a body whose circumference is a few hundred districts
/// wide is the SAME point as some small in-range column, not garbage).
///
/// **Row (latitude) clamps** — to `±half_meridian_districts`, mirroring the
/// forward map's `(wy / meridian_m).clamp(-0.5, 0.5)` (latitude is NOT
/// periodic; it terminates at the poles, so out-of-range rows collapse to the
/// nearest pole rather than wrapping — same asymmetry `derive_district`
/// itself already encodes).
///
/// Both bounds are derived from `body_radius_km` via the SAME `DISTRICT_M`
/// (2 048 m, D-243) constant the forward map uses — no independent magic
/// numbers that could silently drift out of sync with `derive_district`.
///
/// **No radius (`body_radius_km` absent/non-positive — tiny test bodies
/// only, per `BodyParams`'s own doc: "e.g. tiny test bodies"; every real
/// `systems.db` body row carries a radius):** identity, no wrap/clamp. The
/// forward map's own no-radius branch has no periodicity concept either (it
/// clamps the FRACTIONAL PIXEL position directly against the heightmap's
/// working-grid dimensions, which aren't known at request-serving time — only
/// inside the Rayon work item once the heightmap is loaded); a real letterbox
/// click can never hit this branch, so it is out of this fix's scope.
fn normalize_window_center(params: &BodyParams, center: DistrictPos) -> DistrictPos {
let (dx, dy) = center;
match params.body_radius_km {
Some(r_km) if r_km > 0.0 => {
let circumference_m = std::f64::consts::TAU * r_km * 1000.0;
let meridian_m = std::f64::consts::PI * r_km * 1000.0;
// Whole districts per full circumference / per half-meridian —
// rounded (not truncated) so the bound matches the forward map's
// continuous fraction as closely as an integer district grid can.
let districts_per_circumference =
(circumference_m / DISTRICT_M as f64).round().max(1.0) as i32;
let half_meridian_districts = (meridian_m / DISTRICT_M as f64 / 2.0).round() as i32;
let wrapped_dx = dx.rem_euclid(districts_per_circumference);
let clamped_dy = dy.clamp(-half_meridian_districts, half_meridian_districts);
(wrapped_dx, clamped_dy)
}
// No radius: derive_district's own fallback has no wrap/clamp concept
// at the DistrictPos level (see the doc above) — identity.
_ => center,
}
}
/// Resolve `req`'s district-window query, if any (D-226 T-1124 amendment,
/// T-1137). Returns `None` immediately when `req.window_center` is absent (no
/// window requested — the common case, zero cost).
///
/// **Independent of the whole-body cache state** (the amendment is explicit:
/// "the window derivation depends only on `TerrainAnalysis` + `BodyParams`
/// being resolvable for the body ... not on which whole-body layers the
/// cascade has cached") — so this runs whether `handle_atlas_request` is
/// about to take its cache-hit or cache-miss branch, sharing neither's control
/// flow.
///
/// **`window_center` is normalized via [`normalize_window_center`] BEFORE the
/// `DistrictWindowCache` key AND the `submit_window` coalescing key are built**
/// (T-1142) — this is why `body_params` is read HERE, unconditionally,
/// rather than only inside the former miss-branch: normalization needs
/// `body_radius_km` to compute the wrap/clamp bounds, and it must happen
/// before either key exists, or an insane request and its sane normalized
/// twin would land in different cache entries (exactly the bug this fix
/// closes — a garbage `window_center` was cached standalone instead of
/// collapsing onto its valid twin). The coalescing key itself is
/// `(ConnectionId, body_id)` — it never carried `center`, so coalescing
/// was never at risk of diverging per-center; normalizing before
/// `submit_window` matters only so the work item DERIVES (and echoes) the
/// canonical center. The one-time cost
/// (a single indexed `bodies` row read) is paid on every window request now,
/// not just on a cache miss — a request whose normalized center hits the
/// cache still needed this read to know WHICH key to check.
///
/// Cache hit (`(body_id, normalized_center, n)` already in `window_cache`) →
/// `Some` immediately, no queue submission (D-227: a previously-derived
/// window for this body+seed is valid forever, no staleness check needed).
/// Cache miss → submit a `DeriveWindow` work item (queue-based, per the
/// amendment's binding serving model — never inline here) and return `None`;
/// the *next* request for this `(body, normalized_center, n)` re-checks the
/// cache and finds it populated once `drain_generation_completions` has
/// processed the completion (the existing D-225 poll-and-recheck-cache
/// pattern every other layer already uses, not a push).
///
/// `window_n` is clamped to `[1, DISTRICT_WINDOW_MAX_N]` (or
/// `DISTRICT_WINDOW_MAX_N_REGION` at `Region` granularity, T-1152) AND the
/// granularity-aware `WIRE_CAP_CELLS` ceiling here — the ONE place that clamp
/// is applied; nothing downstream re-checks the wire value. The granularity
/// itself is resolved via [`resolve_window_granularity_v2`] at the same
/// boundary (T-1150, widened T-1152 — see that function's doc for the
/// legacy-`u32`-vs-`window_granularity_v2` precedence rule).
#[allow(clippy::too_many_arguments)]
fn serve_district_window(
req: &AtlasLayerRequest,
window_cache: &mut DistrictWindowCache,
queue: &GenerationQueue,
resolver: &BodySourceResolver,
body_params_reader: Option<&BodyParamsReader>,
world_seed: u64,
conn_id: ConnectionId,
) -> Option<DistrictWindowLayer> {
let raw_center = req.window_center?;
let granularity = resolve_window_granularity_v2(req);
let n = clamp_window_n_v2(req.window_n, granularity);
// T-1150 design doc §5: quantize BEFORE either the cache key or the
// DeriveWindow work item sees it — the raw wire value never reaches
// either (same discipline as window_n's clamp above and
// normalize_window_center's wrap/clamp below).
let min_wl_m = quantize_min_wl_m(req.window_min_wl_m);
// body_params is needed to normalize the centre BEFORE either cache key
// exists (T-1142) — read it first, unconditionally (not gated on a cache
// miss like the former structure). A read failure here can't distinguish
// "insane vs. sane center" for the key, so it's a hard skip for the whole
// window (window stays None on this response), same failure posture the
// former miss-only read already had.
let Some(reader) = body_params_reader else {
tracing::warn!(
body_id = %req.body_id,
"district window request: no body_params_reader wired — window stays None"
);
return None;
};
let body_params = match reader.read_body_params(&req.body_id) {
Ok(p) => p,
Err(e) => {
tracing::warn!(
body_id = %req.body_id,
error = %e,
"district window request: body_params read failed — window stays None"
);
return None;
}
};
let center = normalize_window_center(&body_params, raw_center);
if center != raw_center {
tracing::debug!(
body_id = %req.body_id,
raw = ?raw_center,
normalized = ?center,
"district window request: out-of-range window_center normalized (T-1142)"
);
}
// T-1150: granularity + min_wl_m are part of the cache key — a
// granularity-4 request at the same (body, center, n) as a granularity-1
// request is a DIFFERENT payload and must never alias onto the same slot
// (design doc §3's aliasing risk, the mandatory regression test below).
let key: DistrictWindowKey = (req.body_id.clone(), center, n, granularity, min_wl_m);
if let Some(layer) = window_cache.get(&key) {
return Some(layer.clone());
}
// Miss — resolve the heightmap and submit a background derive.
// Read/resolve failures are non-fatal for the window (log + skip): the
// window simply stays None on this response, same as an unrun whole-body
// layer, rather than failing the entire AtlasLayerResponse.
let heightmap_path = match resolver.resolve(&req.body_id) {
Ok(p) => p,
Err(e) => {
tracing::warn!(
body_id = %req.body_id,
error = %e,
"district window request: heightmap resolve failed — window stays None"
);
return None;
}
};
queue.submit_window(
GenWorkItem::DeriveWindow {
body_id: req.body_id.clone(),
conn_id,
heightmap_path,
sea_level: DEFAULT_SEA_LEVEL,
body_seed: SeedChain::for_body(world_seed, &req.body_id),
body_params: Box::new(body_params),
center,
n,
granularity,
min_wl_m,
},
GenPriority::Immediate,
);
None
}
/// Serve one layer request (D-225). `current_tick` stamps the cache LRU on hit;
/// `world_seed` derives the body's `SeedChain` for the enqueued analysis.
///
/// `city_reader` supplies the body's settlements for Layer-3 placement (#955),
/// read on a cache miss. `None` (or a read failure) places no cities — the
/// cascade still runs Layer 1; the body just gets no settlement placements.
///
/// `body_params_reader` supplies the body's physical parameters for the
/// DistrictProfile carrier layer (T-1032, D-239 §1), read on a cache miss.
/// `None` (or a read failure) passes `body_params: None` to the work item,
/// causing the cascade to stop at `CascadeLayer::Settlement` (pre-T-1032
/// behaviour). A successful read passes `Some(Box::new(params))`, enabling
/// the full `CascadeLayer::DistrictProfile` path.
///
/// `window_cache` + `conn_id` serve the optional district-window query
/// (D-226 T-1124 amendment, T-1137) via [`serve_district_window`] — see that
/// function for the caching/coalescing model. `conn_id` is used ONLY as the
/// window request's coalescing key; nothing else in this function is
/// connection-aware (the D-254 §2 convention this proxy already follows).
#[allow(clippy::too_many_arguments)]
pub fn handle_atlas_request(
req: &AtlasLayerRequest,
cache: &mut BodyWorldStateCache,
window_cache: &mut DistrictWindowCache,
queue: &GenerationQueue,
resolver: &BodySourceResolver,
city_reader: Option<&CityContextReader>,
body_params_reader: Option<&BodyParamsReader>,
world_seed: u64,
current_tick: SimTick,
conn_id: ConnectionId,
) -> AtlasLayerResponse {
let district_window = serve_district_window(
req,
window_cache,
queue,
resolver,
body_params_reader,
world_seed,
conn_id,
);
// Cache hit — serve immediately.
if let Some(state) = cache.get(&req.body_id, current_tick) {
let layer1 = Layer1Output {
body_id: state.body_id.clone(),
river_network: state.river_network.clone(),
drainage_basins: state.drainage_basins.clone(),
attractors: state.attractors.clone(),
// The cascade ran on the downsampled heightmap, so its dims are the
// working grid all Layer-1 positions are expressed in (#960).
grid_w: state.heightmap_width,
grid_h: state.heightmap_height,
// survey_basin_dirs is transient — it is aggregated during run_layer1
// and consumed by derive_all_districts before being stored on
// BodyWorldState. When reconstructing Layer1Output from the cache for
// the client response, the per-survey-cell direction is already encoded
// in DistrictProfile.basin_direction (BodyWorldState.districts) and is
// not needed again here. Supply an empty map.
survey_basin_dirs: std::collections::BTreeMap::new(),
// T-1169: mirrors state.attractors.clone() above — feature_names
// is stored on BodyWorldState (see cascade::into_body_world_state)
// and cloned back out here, same reconstruction discipline as
// every other Layer1Output field on this cache-hit path.
feature_names: state.feature_names.clone(),
};
let district_grid = build_district_grid(state);
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,
layer1: Some(layer1),
district_grid,
road_graph,
settlements,
region_grid,
district_window,
quarter_footprints,
};
}
// Miss — resolve the source heightmap and enqueue background analysis.
match resolver.resolve(&req.body_id) {
Ok(heightmap_path) => {
// Pre-resolve this body's settlements + system faction so the Rayon
// work item stays DB-free (#955/#956, D-225). Read failures are
// non-fatal: log and fall back (no cities / no faction → frontier).
let (cities, dominant_faction) = match city_reader {
Some(reader) => {
let cities = reader
.read_body_settlements(&req.body_id)
.unwrap_or_else(|e| {
tracing::warn!(
body_id = %req.body_id,
error = %e,
"settlement read failed; placing no cities"
);
Vec::new()
});
let faction = reader
.read_body_dominant_faction(&req.body_id)
.unwrap_or_else(|e| {
tracing::warn!(
body_id = %req.body_id,
error = %e,
"dominant_faction read failed; defaulting to frontier"
);
None
});
(cities, faction)
}
None => (Vec::new(), None),
};
// Pre-resolve this body's reserved river/mountain name pools so
// the Rayon work item stays DB-free (T-1169, D-223, same D-225
// pattern as `cities` above). Read failure is non-fatal: log and
// fall back to no names (attach_feature_names degrades gracefully
// — every attractor simply gets no name).
let (river_names, mountain_names) = match city_reader {
Some(reader) => match reader.read_body_feature_names(&req.body_id) {
Ok(rows) => {
let mut rivers = Vec::new();
let mut mountains = Vec::new();
for row in rows {
match row.feature_type.as_str() {
"river" => rivers.push(row.name),
"mountain" => mountains.push(row.name),
_ => {}
}
}
(rivers, mountains)
}
Err(e) => {
tracing::warn!(
body_id = %req.body_id,
error = %e,
"feature name read failed; attaching no names"
);
(Vec::new(), Vec::new())
}
},
None => (Vec::new(), Vec::new()),
};
// Pre-resolve body physical params so the Rayon work item stays
// DB-free (D-225 pattern). Read failures are non-fatal: log and
// fall back to None (cascade stops at Settlement, pre-T-1032
// behaviour, rather than aborting the entire analysis).
let body_params = match body_params_reader {
Some(reader) => reader
.read_body_params(&req.body_id)
.map(|p| Some(Box::new(p)))
.unwrap_or_else(|e| {
tracing::warn!(
body_id = %req.body_id,
error = %e,
"body_params read failed; district layer skipped"
);
None
}),
None => None,
};
queue.submit(
GenWorkItem::AnalyzeBody {
body_id: req.body_id.clone(),
heightmap_path,
sea_level: DEFAULT_SEA_LEVEL,
body_seed: SeedChain::for_body(world_seed, &req.body_id),
cities,
dominant_faction,
body_params,
river_names,
mountain_names,
},
GenPriority::Immediate,
);
AtlasLayerResponse {
body_id: req.body_id.clone(),
status: AtlasLayerStatus::Pending,
layer1: None,
district_grid: None,
road_graph: None,
settlements: None,
region_grid: None,
district_window,
quarter_footprints: None,
}
}
// Unknown / no terrain → re-requesting won't help.
Err(SourceResolveError::UnknownBody(_))
| Err(SourceResolveError::NoTerrainReference { .. }) => AtlasLayerResponse {
body_id: req.body_id.clone(),
status: AtlasLayerStatus::NotFound,
layer1: None,
district_grid: None,
road_graph: None,
settlements: None,
region_grid: None,
district_window,
quarter_footprints: None,
},
Err(e) => AtlasLayerResponse {
body_id: req.body_id.clone(),
status: AtlasLayerStatus::Error(e.to_string()),
layer1: None,
district_grid: None,
road_graph: None,
settlements: None,
region_grid: None,
district_window,
quarter_footprints: None,
},
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::atlas::body_world_state::{BodyWorldState, RiverNetwork, CACHE_CAPACITY};
use crate::atlas::gen_queue::GenCompletion;
use rusqlite::Connection;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU32, Ordering};
use std::time::Duration;
static SEQ: AtomicU32 = AtomicU32::new(0);
#[test]
fn district_grid_built_from_cached_districts() {
use crate::atlas::district_profile::{
DistrictProfile, GlaciationGrade, PrecipitationClass, TectonicClass, VegetationClass,
};
use crate::simulation::generator::MorphologyZone;
let dp = |zone: MorphologyZone, elev: i32| DistrictProfile {
morphology_zone: zone,
tectonic_class: TectonicClass::Stable,
glaciation_grade: GlaciationGrade::None,
precipitation_class: PrecipitationClass::Arid,
slope_q: 0,
elev_q: elev,
ocean_fraction_q: 0,
lake_margin_q: 0,
river_threshold: 200,
temperature_c: Some(10.0),
moisture_q: 50,
vegetation_class: VegetationClass::Barren,
basin_direction: crate::atlas::scale::BasinDirection::default(),
};
let mut state = BodyWorldState {
body_id: "GJ1c".into(),
heightmap: vec![],
heightmap_width: 16,
heightmap_height: 8,
sea_level: 0.3,
river_network: RiverNetwork::default(),
drainage_basins: vec![],
attractors: vec![],
feature_names: vec![],
placements: vec![],
road_graph: crate::atlas::road_graph::RoadGraph::default(),
quarters: std::collections::BTreeMap::new(),
districts: std::collections::BTreeMap::new(),
regions: std::collections::BTreeMap::new(),
last_accessed: 0,
};
// 3×2 grid with two distinct zones at the corners.
state.districts.insert(
crate::atlas::scale::SurveyCellPos(0, 0),
dp(MorphologyZone::AlluvialPlain, 10),
);
state.districts.insert(
crate::atlas::scale::SurveyCellPos(2, 1),
dp(MorphologyZone::Alpine, 90),
);
let grid = build_district_grid(&state).expect("districts present → Some grid");
assert_eq!((grid.cols, grid.rows), (3, 2));
assert_eq!(grid.morphology.len(), 6);
assert_eq!(grid.morphology[0], MorphologyZone::AlluvialPlain as u8);
assert_eq!(
grid.morphology[grid.cols as usize + 2],
MorphologyZone::Alpine as u8
);
assert_eq!(grid.elev_q[grid.cols as usize + 2], 90);
// Empty districts → None (DistrictProfile layer hasn't run).
state.districts.clear();
assert!(build_district_grid(&state).is_none());
}
/// T-1113: the region climate grid mirrors the district-grid encoding —
/// `None` when the Region layer hasn't run; dense row-major with the
/// integer wire quantization (deci-°C temp, `i16::MIN` airless sentinel)
/// when it has.
#[test]
fn build_region_grid_encodes_dense_quantized_climate() {
use crate::atlas::region_profile::{RegionClock, RegionProfile, SeasonPhase, WeatherState};
let mut state = blank_state("GJ1c");
// Empty regions → None (the Region layer hasn't run).
assert!(build_region_grid(&state).is_none());
// A 2×1 covering grid: one temperate region, one airless-style region
// (mean_temp_c = None → the sentinel).
state.regions.insert(
(0, 0),
RegionProfile {
pos: (0, 0),
clock: RegionClock {
season: SeasonPhase::Summer,
weather: WeatherState::Clear,
mean_temp_c: Some(12.34),
},
latitude_deg: 45.0,
moisture_q: 80,
},
);
state.regions.insert(
(1, 0),
RegionProfile {
pos: (1, 0),
clock: RegionClock {
season: SeasonPhase::Winter,
weather: WeatherState::Snow,
mean_temp_c: None,
},
latitude_deg: -10.0,
moisture_q: 5,
},
);
let grid = build_region_grid(&state).expect("regions present → Some grid");
assert_eq!((grid.cols, grid.rows), (2, 1));
assert_eq!(grid.season.len(), 2);
assert_eq!(grid.season[0], SeasonPhase::Summer as u8);
assert_eq!(grid.weather[0], WeatherState::Clear as u8);
// 12.34 °C → 123 deci-°C (rounded).
assert_eq!(grid.mean_temp_dc[0], 123);
assert_eq!(grid.moisture_q[0], 80);
assert_eq!(grid.season[1], SeasonPhase::Winter as u8);
assert_eq!(grid.weather[1], WeatherState::Snow as u8);
assert_eq!(
grid.mean_temp_dc[1], REGION_TEMP_NONE_DC,
"airless None maps to the sentinel"
);
assert_eq!(grid.moisture_q[1], 5);
}
// -----------------------------------------------------------------------
// DistrictWindowLayer (D-226 T-1124 amendment, T-1137)
// -----------------------------------------------------------------------
/// Minimal deterministic heightmap fixture, mirroring
/// `district_profile::tests::test_hm` (T-1137: the window path shares the
/// same on-demand `derive_district` call, so it earns the same fixture
/// shape).
fn window_test_hm() -> crate::atlas::heightmap::BodyHeightmap {
use crate::atlas::heightmap::BodyHeightmap;
let (w, h) = (64u32, 32u32);
let n = (w * h) as usize;
let data = (0..n)
.map(|i| {
let r = (i / w as usize) as f32 / h as f32;
let c = (i % w as usize) as f32 / w as f32;
(r * 0.6 + c * 0.4).min(1.0)
})
.collect();
BodyHeightmap {
body_id: "test".into(),
width: w,
height: h,
data,
sea_level: 0.3,
}
}
fn window_test_ta(
hm: &crate::atlas::heightmap::BodyHeightmap,
) -> crate::atlas::features::TerrainAnalysis {
use crate::atlas::drainage;
use crate::atlas::features::TerrainAnalysis;
let dr = drainage::analyze(&hm.data, hm.width, hm.height, hm.sea_level);
TerrainAnalysis::analyze(hm, &dr)
}
/// T-1170: the `RiverNetwork` companion to [`window_test_ta`] — most
/// existing window-builder tests don't care about courses at all (this
/// synthetic gradient fixture may have zero river cells), so an empty
/// default is the common case; call sites that DO care about courses use
/// a real fixture (`window_test_gj1c_network`) instead.
fn window_test_river_network(
hm: &crate::atlas::heightmap::BodyHeightmap,
) -> crate::atlas::body_world_state::RiverNetwork {
use crate::atlas::drainage;
drainage::analyze(&hm.data, hm.width, hm.height, hm.sea_level).river_network
}
fn window_test_params() -> crate::atlas::district_profile::BodyParams {
crate::atlas::district_profile::BodyParams {
hydrosphere: Some("ocean".into()),
atmosphere: Some("breathable".into()),
planet_class: Some("temperate".into()),
body_radius_km: Some(6371.0),
..Default::default()
}
}
/// `build_district_window_layer` produces a dense `n × n` row-major grid
/// (the `DistrictGridLayer`/`RegionGridLayer` indexing convention) whose
/// cell count and per-array lengths match `n`, and whose values are
/// pulled straight from the corresponding `derive_district` profile field
/// (T-1137).
#[test]
fn build_district_window_layer_produces_dense_n_by_n_grid() {
let hm = window_test_hm();
let ta = window_test_ta(&hm);
let rn = window_test_river_network(&hm);
let params = window_test_params();
let climate = crate::atlas::district_profile::ClimateConstants::default();
let seed = SeedChain::root(42).derive(SeedDomain::Body, 1);
let n = 4u32;
let layer = build_district_window_layer(
seed,
"test_body",
&params,
&ta,
&rn,
(10, -5),
n,
&climate,
WindowGranularity::District,
0,
);
assert_eq!(layer.center, (10, -5));
assert_eq!(layer.n, n);
assert_eq!(layer.granularity_v2, WindowGranularity::District);
assert_eq!(layer.min_wl_m, 0);
let cells = (n * n) as usize;
assert_eq!(layer.morphology.len(), cells);
assert_eq!(layer.elev_q.len(), cells);
assert_eq!(layer.temp_dc.len(), cells);
assert_eq!(layer.moisture_q.len(), cells);
assert_eq!(layer.vegetation.len(), cells);
assert_eq!(layer.glaciation.len(), cells);
// Spot-check one cell against a direct derive_district call — the
// window builder must not transform the profile's values, only pack
// them (row 0, col 0 → district (center.0 - n/2, center.1 - n/2)).
let half = (n / 2) as i32;
let dp = (10 - half, -5 - half);
let prof = crate::atlas::district_profile::derive_district(
seed,
"test_body",
&params,
&ta,
dp,
&climate,
);
assert_eq!(layer.morphology[0], prof.morphology_zone as u8);
assert_eq!(layer.elev_q[0], prof.elev_q.clamp(0, 100) as u8);
assert_eq!(layer.moisture_q[0], prof.moisture_q.clamp(0, 100) as u8);
assert_eq!(layer.vegetation[0], prof.vegetation_class as u8);
assert_eq!(layer.glaciation[0], prof.glaciation_grade as u8);
}
/// T-1170/T-1168 A5 integration, strengthened by D-256: the batch path
/// (`derive_district_profile`, sourcing courses via `near_perennial_water_at`
/// on demand) and the window path (`build_district_window_layer`,
/// sourcing courses via the pre-invented `Vec<InventedCourse>`) must
/// resolve the SAME riparian verdict for the SAME world position —
/// Ruling 4b's "batch and window paths can never silently disagree"
/// binding requirement, checked end to end (not just at the
/// `near_perennial_water`/`near_perennial_water_at` unit level).
///
/// **Design note (D-256):** pre-D-256 this test deliberately did NOT
/// compare the batch and window paths' full `DistrictProfile` output for
/// "the same district" — `derive_district_profile`'s cell-aggregate-centre
/// sampling and the window path's district-origin sampling resolved
/// genuinely different world positions for a shared nominal `DistrictPos`
/// (the batch namespace collision D-256 rules on). With sampling unified
/// (one derive core, D-256(c)), that framing is now FALSE: batch and
/// window positions agree BY CONSTRUCTION — `derive_district_profile` is
/// a thin wrapper over the exact same [`crate::atlas::district_profile::derive_at_metres`]
/// family the window path calls, at the survey cell's own D-256(b) centre
/// world metres. `full_district_profile_matches_derive_at_metres_at_shared_survey_cell_centre`
/// below is the strengthened full-profile bit-identical check this design
/// note used to explicitly rule out; this test keeps the riparian-signal
/// check as a focused shared-exact-position regression (the ONE hand-wired
/// signal T-1168 added, worth its own targeted assertion).
#[test]
fn window_and_batch_paths_agree_on_riparian_signal_near_a_real_river_edge() {
use crate::atlas::drainage;
use crate::atlas::heightmap::load_heightmap_png;
use crate::atlas::river_course;
let src = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../wiki/star-systems/GJ-1/bodies/GJ1c/heightmap.png");
let heightmap =
load_heightmap_png(&src, "GJ1c", 0.3).expect("decode committed GJ1c heightmap");
let small = heightmap.downsample(256, 128);
let dr = drainage::analyze(&small.data, small.width, small.height, small.sea_level);
let ta = crate::atlas::features::TerrainAnalysis::analyze(&small, &dr);
let rn = &dr.river_network;
assert!(
!rn.river_cells.is_empty(),
"GJ1c downsample must have river cells for this test to be meaningful"
);
let params = crate::atlas::district_profile::BodyParams {
hydrosphere: Some("ocean".into()),
atmosphere: Some("breathable".into()),
planet_class: Some("temperate".into()),
body_radius_km: Some(6371.0),
..Default::default()
};
let seed = SeedChain::root(0xC0FFEE_u64).derive(SeedDomain::Body, 1);
let station_spacing_m = DISTRICT_M as f64;
// Invent a real edge and sample a point exactly on its course.
let edges = river_course::build_edges(rn);
let edge = edges
.iter()
.find(|e| e.terminus == river_course::EdgeTerminusKind::Interior)
.expect("GJ1c should have an interior river edge");
let course = river_course::invent_course(seed, edge, &ta, &params, station_spacing_m, 0.0);
let on_course = course.points[course.points.len() / 2];
// Batch path: near_perennial_water_at (invents nearby edges on demand
// from `rn` directly).
let batch_signal = river_course::near_perennial_water_at(
seed,
&ta,
&params,
rn,
on_course,
station_spacing_m,
0.0,
);
// Window path: invent_courses_near_window (the SAME pre-invention step
// `build_district_window_layer` uses) around a window rect containing
// `on_course`, then near_perennial_water against that pre-invented list.
let window_rect = (
on_course.0 - 10_000.0,
on_course.1 - 10_000.0,
on_course.0 + 10_000.0,
on_course.1 + 10_000.0,
);
let invented = invent_courses_near_window(
seed,
&params,
&ta,
rn,
window_rect,
WindowGranularity::District,
0.0,
);
let window_signal = river_course::near_perennial_water(on_course, &invented);
assert!(
batch_signal,
"a point exactly on an invented course must read near_perennial_water_at == true (batch path)"
);
assert_eq!(
batch_signal, window_signal,
"batch (near_perennial_water_at) and window (invent_courses_near_window + \
near_perennial_water) paths must agree on the riparian verdict at the SAME \
world position {on_course:?}"
);
}
/// D-256(c): the batch survey-cell profile == `derive_at_metres` at the
/// SAME survey-cell-centre world metres — bit-identical, field for field
/// (basin overridden on the expectation since the core always returns the
/// default and the wrapper post-call-overrides it; riparian threaded
/// equivalently through both paths' own mechanism). This is the
/// strengthened replacement for the pre-D-256 "legitimate different
/// positions" design note on the riparian-only test above — positions now
/// agree by construction, so the FULL profile must too.
#[test]
fn full_district_profile_matches_derive_at_metres_at_shared_survey_cell_centre() {
use crate::atlas::district_profile::{self, ClimateConstants};
use crate::atlas::drainage;
use crate::atlas::heightmap::load_heightmap_png;
use crate::atlas::scale::{self, BasinDirection, SurveyCellPos};
use std::collections::BTreeMap;
let src = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../wiki/star-systems/GJ-1/bodies/GJ1c/heightmap.png");
let heightmap =
load_heightmap_png(&src, "GJ1c", 0.3).expect("decode committed GJ1c heightmap");
let small = heightmap.downsample(256, 128);
let dr = drainage::analyze(&small.data, small.width, small.height, small.sea_level);
let ta = crate::atlas::features::TerrainAnalysis::analyze(&small, &dr);
let rn = &dr.river_network;
let params = crate::atlas::district_profile::BodyParams {
hydrosphere: Some("ocean".into()),
atmosphere: Some("breathable".into()),
planet_class: Some("temperate".into()),
body_radius_km: Some(6371.0),
..Default::default()
};
let seed = SeedChain::root(0xC0FFEE_u64).derive(SeedDomain::Body, 1);
let climate = ClimateConstants::default();
let gcpr = scale::HEIGHTMAP_CELLS_PER_DISTRICT;
// An interior survey cell (not clamped at the grid edge — that case
// is covered by the dedicated edge-truncation test below).
let cell = SurveyCellPos(10, 6);
let basin = BasinDirection::East;
let batch_profile = district_profile::derive_district_profile(
seed,
&params,
&ta,
cell,
gcpr,
&climate,
"GJ1c",
&BTreeMap::new(),
basin,
Some(rn),
);
// The SAME survey-cell-centre world metres, resolved via the SAME
// D-256(b) bridge function derive_district_profile uses internally.
let (world_x_m, world_y_m) = district_profile::survey_cell_centre_world_m(
cell,
gcpr,
ta.w,
ta.h,
params.body_radius_km,
);
// Riparian equivalent: near_perennial_water_at against the same
// RiverNetwork, same station spacing/cutoff derive_district_profile
// uses internally — the SAME on-demand course invention, not the
// window path's pre-invented slice (this is the batch-vs-on-demand
// core identity, not the batch-vs-window riparian check above).
//
// derive_at_metres's PUBLIC signature only accepts a pre-invented
// `nearby_courses` slice (never a raw bool — Ruling 4a), so to force
// the SAME riparian verdict through the public path a single-point
// synthetic course exactly at (world_x_m, world_y_m) is threaded when
// the on-demand signal is true; an empty slice when false. Either way
// `near_perennial_water((wx,wy), courses)` evaluates to the identical
// bool the batch core read.
let near_perennial_water = river_course::near_perennial_water_at(
seed,
&ta,
&params,
rn,
(world_x_m, world_y_m),
DISTRICT_M as f64,
0.0,
);
let riparian_equivalent: Vec<InventedCourse> = if near_perennial_water {
vec![InventedCourse {
edge_id: 0,
class: 0,
terminus: EdgeTerminusKind::Interior,
points: vec![(world_x_m, world_y_m)],
bbox: (world_x_m, world_y_m, world_x_m, world_y_m),
}]
} else {
Vec::new()
};
let mut expected = district_profile::derive_at_metres(
seed,
"GJ1c",
&params,
&ta,
world_x_m,
world_y_m,
&climate,
0.0,
&riparian_equivalent,
);
// basin_direction (inert field, D-256(c) proof) — the core always
// returns the default; the wrapper post-call-overrides it.
expected.basin_direction = basin;
assert_eq!(
batch_profile.morphology_zone as u8,
expected.morphology_zone as u8
);
assert_eq!(
batch_profile.tectonic_class as u8,
expected.tectonic_class as u8
);
assert_eq!(
batch_profile.glaciation_grade as u8,
expected.glaciation_grade as u8
);
assert_eq!(
batch_profile.precipitation_class as u8,
expected.precipitation_class as u8
);
assert_eq!(batch_profile.slope_q, expected.slope_q);
assert_eq!(batch_profile.elev_q, expected.elev_q);
assert_eq!(batch_profile.ocean_fraction_q, expected.ocean_fraction_q);
assert_eq!(batch_profile.river_threshold, expected.river_threshold);
assert_eq!(batch_profile.temperature_c, expected.temperature_c);
assert_eq!(batch_profile.moisture_q, expected.moisture_q);
assert_eq!(
batch_profile.vegetation_class as u8,
expected.vegetation_class as u8
);
assert_eq!(
batch_profile.basin_direction as u8,
expected.basin_direction as u8
);
}
/// D-256(c) invariant: the wrapper (`derive_district_profile`) ≡ core
/// (`derive_at_metres`) identity holds at survey-grid edge cases — the
/// anti-meridian column (`px` near the wrap boundary), the pole rows
/// (latitude clamp), and edge-truncated cells (the covering pixel block
/// itself clamped at `w`/`h`, not just the resulting position). No
/// riparian signal threaded here (both sides get `river_network: None` /
/// `nearby_courses: &[]` — the riparian equivalence is the DEDICATED
/// concern of the test above; this one isolates the geometric position
/// mapping across the grid's hard edges).
#[test]
fn wrapper_matches_core_at_survey_grid_edge_cases() {
use crate::atlas::district_profile::{self, ClimateConstants};
use crate::atlas::heightmap::load_heightmap_png;
use crate::atlas::scale::{self, BasinDirection, SurveyCellPos};
use std::collections::BTreeMap;
// The committed GJ1c heightmap (same fixture the other integration
// tests in this module use), downsampled to the standard 256×128
// working grid so the survey raster is a real body's shape.
let src = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../wiki/star-systems/GJ-1/bodies/GJ1c/heightmap.png");
let heightmap =
load_heightmap_png(&src, "GJ1c", 0.3).expect("decode committed GJ1c heightmap");
let hm = heightmap.downsample(256, 128);
let dr = crate::atlas::drainage::analyze(&hm.data, hm.width, hm.height, hm.sea_level);
let ta = crate::atlas::features::TerrainAnalysis::analyze(&hm, &dr);
let params = crate::atlas::district_profile::BodyParams {
hydrosphere: Some("ocean".into()),
atmosphere: Some("breathable".into()),
planet_class: Some("temperate".into()),
body_radius_km: Some(6371.0),
..Default::default()
};
let seed = SeedChain::root(0xC0FFEE_u64).derive(SeedDomain::Body, 2);
let climate = ClimateConstants::default();
let gcpr = scale::HEIGHTMAP_CELLS_PER_DISTRICT;
let survey_cols = (hm.width as usize).div_ceil(gcpr) as i32; // 32
let survey_rows = (hm.height as usize).div_ceil(gcpr) as i32; // 16
let cases: &[(&str, SurveyCellPos)] = &[
("west edge / row 0 corner", SurveyCellPos(0, 0)),
(
"anti-meridian column (max col)",
SurveyCellPos(survey_cols - 1, survey_rows / 2),
),
("north pole row", SurveyCellPos(survey_cols / 2, 0)),
(
"south pole row",
SurveyCellPos(survey_cols / 2, survey_rows - 1),
),
(
"edge-truncated SE corner",
SurveyCellPos(survey_cols - 1, survey_rows - 1),
),
];
for &(label, cell) in cases {
let batch_profile = district_profile::derive_district_profile(
seed,
&params,
&ta,
cell,
gcpr,
&climate,
"GJ1c",
&BTreeMap::new(),
BasinDirection::North, // default — no override under test here
None,
);
let (world_x_m, world_y_m) = district_profile::survey_cell_centre_world_m(
cell,
gcpr,
ta.w,
ta.h,
params.body_radius_km,
);
let expected = district_profile::derive_at_metres(
seed,
"GJ1c",
&params,
&ta,
world_x_m,
world_y_m,
&climate,
0.0,
&[],
);
assert_eq!(
batch_profile.morphology_zone as u8, expected.morphology_zone as u8,
"[{label}] morphology_zone mismatch at {cell:?}"
);
assert_eq!(
batch_profile.elev_q, expected.elev_q,
"[{label}] elev_q mismatch at {cell:?}"
);
assert_eq!(
batch_profile.slope_q, expected.slope_q,
"[{label}] slope_q mismatch at {cell:?}"
);
assert_eq!(
batch_profile.ocean_fraction_q, expected.ocean_fraction_q,
"[{label}] ocean_fraction_q mismatch at {cell:?}"
);
assert_eq!(
batch_profile.temperature_c, expected.temperature_c,
"[{label}] temperature_c mismatch at {cell:?}"
);
assert_eq!(
batch_profile.moisture_q, expected.moisture_q,
"[{label}] moisture_q mismatch at {cell:?}"
);
}
}
/// Discipline item 3(a), mandatory: two overlapping windows sharing a
/// stretch of the same edge must produce BYTE-IDENTICAL course points
/// for that shared stretch (Ruling 1e, the window-independence
/// invariant — "stations are generated at deterministic global
/// arc-length positions along the edge; the window crops, it never
/// re-parametrizes"). Two windows at different centers, both containing
/// the same real GJ1c edge, must report the identical `RiverCourse` for
/// that edge wherever both windows' cropped ranges overlap.
#[test]
fn overlapping_windows_produce_byte_identical_course_points() {
use crate::atlas::drainage;
use crate::atlas::heightmap::load_heightmap_png;
use crate::atlas::river_course;
let src = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../wiki/star-systems/GJ-1/bodies/GJ1c/heightmap.png");
let heightmap =
load_heightmap_png(&src, "GJ1c", 0.3).expect("decode committed GJ1c heightmap");
let small = heightmap.downsample(256, 128);
let dr = drainage::analyze(&small.data, small.width, small.height, small.sea_level);
let ta = crate::atlas::features::TerrainAnalysis::analyze(&small, &dr);
let rn = &dr.river_network;
let params = crate::atlas::district_profile::BodyParams {
hydrosphere: Some("ocean".into()),
atmosphere: Some("breathable".into()),
planet_class: Some("temperate".into()),
body_radius_km: Some(6371.0),
..Default::default()
};
let seed = SeedChain::root(0xC0FFEE_u64).derive(SeedDomain::Body, 1);
let station_spacing_m = DISTRICT_M as f64;
let edges = river_course::build_edges(rn);
// Find the LONGEST interior edge (by point count) so the two windows
// below can each cover a genuine, well-inside-their-bounds stretch —
// a short edge's course could produce degenerate/edge-of-range
// overlaps that don't actually exercise the invariant.
let edge = edges
.iter()
.filter(|e| e.terminus == river_course::EdgeTerminusKind::Interior)
.max_by_key(|e| {
let course =
river_course::invent_course(seed, e, &ta, &params, station_spacing_m, 0.0);
course.points.len()
})
.expect("GJ1c should have an interior river edge");
let full_course =
river_course::invent_course(seed, edge, &ta, &params, station_spacing_m, 0.0);
assert!(
full_course.points.len() >= 4,
"need a course with enough stations to construct two overlapping windows"
);
// A midpoint on the course — the shared stretch two different
// windows will both cover.
let mid = full_course.points[full_course.points.len() / 2];
// Two DIFFERENT window rects, both containing `mid` well inside
// their bounds (so both windows' crop ranges include the shared
// stretch, not just a single boundary point).
let window_a = (
mid.0 - 20_000.0,
mid.1 - 20_000.0,
mid.0 + 5_000.0,
mid.1 + 5_000.0,
);
let window_b = (
mid.0 - 5_000.0,
mid.1 - 5_000.0,
mid.0 + 20_000.0,
mid.1 + 20_000.0,
);
let invented_a = invent_courses_near_window(
seed,
&params,
&ta,
rn,
window_a,
WindowGranularity::District,
0.0,
);
let invented_b = invent_courses_near_window(
seed,
&params,
&ta,
rn,
window_b,
WindowGranularity::District,
0.0,
);
let course_a = invented_a
.iter()
.find(|c| c.edge_id == edge.edge_id)
.expect("edge must be invented for window A");
let course_b = invented_b
.iter()
.find(|c| c.edge_id == edge.edge_id)
.expect("edge must be invented for window B");
// Ruling 1e's actual invariant: invent_courses_near_window returns
// the FULL invented course for any edge that culls in — never
// window-cropped or re-parametrized at this layer (cropping happens
// later, in crop_courses_for_wire). So the two windows' invented
// points for the SAME edge must be byte-identical in full, not just
// over some overlap region — this is the direct proof that
// invention is independent of the window rect entirely.
assert_eq!(
course_a.points, course_b.points,
"the same edge invented from two different windows must be byte-identical (D-227/Ruling 1e)"
);
}
/// Discipline item 3(b), mandatory: Quarter course points must stay
/// within the truncated-octave amplitude bound of the District course at
/// the same world position (Ruling 3b's cross-rung invariant — "the
/// Quarter course is the District course plus octaves in the (1,024
/// m..4,096 m) band"). Checked via the perpendicular deviation between
/// the two rungs' station lists never exceeding the District-rung peak
/// amplitude cap by more than a small tolerance (Quarter's extra octaves
/// can only ADD bounded displacement on top of the District shape, never
/// diverge unboundedly).
#[test]
fn quarter_course_stays_within_district_amplitude_bound() {
use crate::atlas::drainage;
use crate::atlas::heightmap::load_heightmap_png;
use crate::atlas::river_course;
let src = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../wiki/star-systems/GJ-1/bodies/GJ1c/heightmap.png");
let heightmap =
load_heightmap_png(&src, "GJ1c", 0.3).expect("decode committed GJ1c heightmap");
let small = heightmap.downsample(256, 128);
let dr = drainage::analyze(&small.data, small.width, small.height, small.sea_level);
let ta = crate::atlas::features::TerrainAnalysis::analyze(&small, &dr);
let rn = &dr.river_network;
let params = crate::atlas::district_profile::BodyParams {
hydrosphere: Some("ocean".into()),
atmosphere: Some("breathable".into()),
planet_class: Some("temperate".into()),
body_radius_km: Some(6371.0),
..Default::default()
};
let seed = SeedChain::root(0xC0FFEE_u64).derive(SeedDomain::Body, 1);
let edges = river_course::build_edges(rn);
let edge = edges
.iter()
.find(|e| e.terminus == river_course::EdgeTerminusKind::Interior)
.expect("GJ1c should have an interior river edge");
let district_course = river_course::invent_course(
seed,
edge,
&ta,
&params,
DISTRICT_M as f64,
2.0 * DISTRICT_M as f64, // District's real Nyquist-floor cutoff
);
let quarter_course = river_course::invent_course(
seed,
edge,
&ta,
&params,
crate::atlas::scale::QUARTER_M as f64,
2.0 * crate::atlas::scale::QUARTER_M as f64, // Quarter's real cutoff
);
// For each District station, find the nearest Quarter station (by
// arc-length proxy: nearest point in world space) and confirm the
// deviation stays within the District-rung amplitude cap (Stage B's
// own hard cap, Ruling 3c) plus a small numeric tolerance — Quarter
// must refine the shape, never blow past the amplitude budget the
// SAME peak-fraction-of-chord cap governs at every rung.
let anchor_a = district_course.points[0];
let anchor_b = *district_course.points.last().unwrap();
let chord_m =
((anchor_a.0 - anchor_b.0).powi(2) + (anchor_a.1 - anchor_b.1).powi(2)).sqrt();
let cap_m = (chord_m * river_course::STAGE_B_PEAK_FRACTION_OF_CHORD)
.min(crate::atlas::scale::QUARTER_M as f64 * 0.5)
* 1.35; // widest class_scale entry (trunk)
for &dp in &district_course.points {
let nearest_q = quarter_course
.points
.iter()
.min_by(|a, b| {
let da = (a.0 - dp.0).powi(2) + (a.1 - dp.1).powi(2);
let db = (b.0 - dp.0).powi(2) + (b.1 - dp.1).powi(2);
da.partial_cmp(&db).unwrap()
})
.unwrap();
let dist = ((nearest_q.0 - dp.0).powi(2) + (nearest_q.1 - dp.1).powi(2)).sqrt();
assert!(
dist <= cap_m + 50.0, // small slack for nearest-station (not exact arc-length) matching
"Quarter course deviates {dist} m from the nearest District station — \
exceeds the {cap_m} m amplitude bound (Ruling 3b cross-rung invariant)"
);
}
}
/// **T-1170 PR #197 review, Hoshe #1 acceptance test (blocking, permanent
/// — not a throwaway probe).** Every real Mouth edge on the GJ1c golden
/// fixture (256×128 downsample, the SAME fixture `cascade_golden.rs`
/// pins — 3 mouths: `[(38,47), (38,98), (124,239)]`) must resolve
/// `CourseTerminus::Mouth`, not `CourseTerminus::None`.
///
/// **What this guards:** before the fix, `build_edges` set
/// `downstream = upstream` for every Mouth edge (a same-cell
/// placeholder — the SAME discard-then-need-it-later anti-pattern
/// Ruling 2b's `river_downstream` field fixed for interior pointers,
/// applied a second time to the seaward neighbor `extract_river_network`
/// already computes and then threw away). That zeroed the chord
/// (`chord_m < 1.0`), which tripped `invent_course`'s degenerate
/// single-point return, which made `resolve_mouth_terminus`'s station
/// walk a no-op (a 1-point course can't reach the `pts.len() >= 2`
/// fallback probe either) — all 3 real GJ1c mouths silently resolved
/// `CourseTerminus::None` instead of `Mouth`, and since Ruling 3g retired
/// the District/Quarter draw-time clip on the promise of real termini,
/// mouths would have disappeared entirely at those rungs. The fix:
/// `RiverNetwork::river_seaward` (additive, captured in the same
/// `extract_river_network` pass) carries the real seaward neighbor
/// through to `build_edges`, giving Mouth edges a genuine ~one-cell
/// chord to invent a course along.
#[test]
fn all_real_gj1c_mouths_resolve_to_mouth_terminus_not_none() {
use crate::atlas::drainage;
use crate::atlas::heightmap::load_heightmap_png;
use crate::atlas::river_course;
let src = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../wiki/star-systems/GJ-1/bodies/GJ1c/heightmap.png");
let heightmap =
load_heightmap_png(&src, "GJ1c", 0.3).expect("decode committed GJ1c heightmap");
let small = heightmap.downsample(256, 128);
let dr = drainage::analyze(&small.data, small.width, small.height, small.sea_level);
let ta = crate::atlas::features::TerrainAnalysis::analyze(&small, &dr);
let rn = &dr.river_network;
let params = crate::atlas::district_profile::BodyParams {
hydrosphere: Some("ocean".into()),
atmosphere: Some("breathable".into()),
planet_class: Some("temperate".into()),
body_radius_km: Some(6371.0),
..Default::default()
};
let climate = crate::atlas::district_profile::ClimateConstants::default();
let seed = SeedChain::root(0xC0FFEE_u64).derive(SeedDomain::Body, 1);
let station_spacing_m = DISTRICT_M as f64;
let edges = river_course::build_edges(rn);
let mouth_edges: Vec<_> = edges
.iter()
.filter(|e| e.terminus == river_course::EdgeTerminusKind::Mouth)
.collect();
assert_eq!(
mouth_edges.len(),
rn.mouths.len(),
"build_edges must produce exactly one Mouth edge per RiverNetwork.mouths entry"
);
assert_eq!(
mouth_edges.len(),
3,
"GJ1c at this downsample is expected to have 3 real mouths (matches the \
committed cascade_golden fixture) — if this count changes, re-verify against \
tests/golden/cascade_layer1.json before updating this assertion"
);
let mut resolved_mouth_count = 0;
for edge in &mouth_edges {
// Sanity: the fix means Mouth edges get a real, non-degenerate
// chord toward the seaward neighbor — never upstream==downstream.
assert_ne!(
edge.upstream, edge.downstream,
"Mouth edge {:?} still has a same-cell placeholder downstream — \
river_seaward threading regressed",
edge.edge_id
);
let course =
river_course::invent_course(seed, edge, &ta, &params, station_spacing_m, 0.0);
assert!(
course.points.len() >= 2,
"Mouth edge {:?} invented a degenerate {}-point course — the chord-length \
fix regressed",
edge.edge_id,
course.points.len()
);
// Window rect generous enough to contain the whole short mouth
// course (mouths are ~one cell chord, so a wide margin is cheap).
let (min_x, max_x) = course
.points
.iter()
.fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), p| {
(lo.min(p.0), hi.max(p.0))
});
let (min_y, max_y) = course
.points
.iter()
.fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), p| {
(lo.min(p.1), hi.max(p.1))
});
let margin = 50_000.0;
let window_rect = (
min_x - margin,
min_y - margin,
max_x + margin,
max_y + margin,
);
let wire = crop_course_to_window(
&course,
window_rect,
seed,
"GJ1c",
&params,
&ta,
&climate,
0.0,
station_spacing_m,
)
.unwrap_or_else(|| {
panic!(
"Mouth edge {:?} cropped to nothing in its own window",
edge.edge_id
)
});
assert_eq!(
wire.terminus,
CourseTerminus::Mouth,
"Mouth edge {:?} (upstream {:?}, downstream {:?}) resolved {:?} instead of \
CourseTerminus::Mouth",
edge.edge_id,
edge.upstream,
edge.downstream,
wire.terminus
);
resolved_mouth_count += 1;
}
assert_eq!(
resolved_mouth_count, 3,
"acceptance criterion (Hoshe #1): all 3 real GJ1c mouths must resolve \
CourseTerminus::Mouth"
);
}
/// **T-1170 PR #197 review round 2 (coordinator's live GJ380c/Lendel
/// repro) — the upstream-cut coverage gap in the test above, closed.**
///
/// `all_real_gj1c_mouths_resolve_to_mouth_terminus_not_none` derives its
/// window rect from the invented course's OWN min/max point bbox, so by
/// construction that window always contains the WHOLE course (both
/// `first_in == 0` and `last_in == n-1`) — it never exercises a window
/// that cuts the UPSTREAM anchor while the true downstream terminus
/// still falls inside. `crop_course_to_window`'s terminus branch is
/// driven entirely by `last_in`/`hi` (the downstream side); this test is
/// the direct proof that an upstream cut (`first_in > 0`, i.e. `lo > 0`)
/// does NOT collapse the terminus flag to `ContinuesBeyondWindow` — the
/// coordinator's hypothesis (a) from the live-server investigation,
/// falsified here as a permanent regression case rather than only a
/// throwaway probe (`mouth_repro_probe.rs`, deleted after this landed).
///
/// Window construction: take one real GJ1c mouth edge's full invented
/// course, find the TRUE (uncropped) terminus point, then build a window
/// rect deliberately offset upstream along the course's own tail
/// direction so its near edge sits well past the upstream anchor (cutting
/// it out of range) while its far edge still comfortably contains the
/// true terminus.
#[test]
fn mouth_terminus_survives_an_upstream_only_crop() {
use crate::atlas::drainage;
use crate::atlas::heightmap::load_heightmap_png;
use crate::atlas::river_course;
let src = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../wiki/star-systems/GJ-1/bodies/GJ1c/heightmap.png");
let heightmap =
load_heightmap_png(&src, "GJ1c", 0.3).expect("decode committed GJ1c heightmap");
let small = heightmap.downsample(256, 128);
let dr = drainage::analyze(&small.data, small.width, small.height, small.sea_level);
let ta = crate::atlas::features::TerrainAnalysis::analyze(&small, &dr);
let rn = &dr.river_network;
let params = crate::atlas::district_profile::BodyParams {
hydrosphere: Some("ocean".into()),
atmosphere: Some("breathable".into()),
planet_class: Some("temperate".into()),
body_radius_km: Some(6371.0),
..Default::default()
};
let climate = crate::atlas::district_profile::ClimateConstants::default();
let seed = SeedChain::root(0xC0FFEE_u64).derive(SeedDomain::Body, 1);
let station_spacing_m = DISTRICT_M as f64;
let edges = river_course::build_edges(rn);
let edge = edges
.iter()
.find(|e| e.terminus == river_course::EdgeTerminusKind::Mouth)
.expect("GJ1c fixture must have at least one Mouth edge");
let course = river_course::invent_course(seed, edge, &ta, &params, station_spacing_m, 0.0);
assert!(
course.points.len() >= 2,
"need a non-degenerate course to construct a meaningful upstream cut"
);
let upstream_anchor = course.points[0];
let true_end = *course.points.last().unwrap();
// Window offset upstream along the course's own tail direction (the
// last segment), far enough that the upstream anchor falls outside
// the window but the true terminus stays comfortably inside.
let second_last = course.points[course.points.len().saturating_sub(2)];
let (dx, dy) = (true_end.0 - second_last.0, true_end.1 - second_last.1);
let len = (dx * dx + dy * dy).sqrt().max(1e-9);
let (ux, uy) = (dx / len, dy / len);
// Half the upstream->downstream distance keeps the window's near
// edge well clear of the upstream anchor for any real mouth chord
// (mouth edges are short, ~one D8 step), while the far edge margin
// below still comfortably covers the terminus.
let chord_m = ((true_end.0 - upstream_anchor.0).powi(2)
+ (true_end.1 - upstream_anchor.1).powi(2))
.sqrt();
let offset_m = (chord_m * 0.5).max(5_000.0);
let center = (true_end.0 - ux * offset_m, true_end.1 - uy * offset_m);
let half_extent_m = (chord_m * 0.5).max(5_000.0);
let window_rect = (
center.0 - half_extent_m,
center.1 - half_extent_m,
center.0 + half_extent_m,
center.1 + half_extent_m,
);
// Sanity on the window construction itself (not the code under
// test): the upstream anchor must genuinely be cropped out, and the
// true terminus must genuinely be inside — otherwise this test
// isn't exercising the branch it claims to.
let inside = |p: (f64, f64)| {
p.0 >= window_rect.0
&& p.0 <= window_rect.2
&& p.1 >= window_rect.1
&& p.1 <= window_rect.3
};
assert!(
!inside(upstream_anchor),
"test construction error: upstream anchor {upstream_anchor:?} must be OUTSIDE \
the window {window_rect:?} for this to be a real upstream-cut case"
);
assert!(
inside(true_end),
"test construction error: true terminus {true_end:?} must be INSIDE the \
window {window_rect:?} for this to test the terminus-survives claim"
);
let wire = crop_course_to_window(
&course,
window_rect,
seed,
"GJ1c",
&params,
&ta,
&climate,
0.0,
station_spacing_m,
)
.unwrap_or_else(|| panic!("course cropped to nothing despite containing the terminus"));
assert_eq!(
wire.terminus,
CourseTerminus::Mouth,
"an upstream-only crop (anchor cut, true terminus still in-window) must NOT \
collapse the terminus flag — got {:?} for edge {:?}",
wire.terminus,
edge.edge_id
);
}
/// **T-1170 live GJ380c/Lendel reconciliation dossier (coordinator's
/// request) — walk/paint agreement invariant, pinned permanently.**
///
/// Live capture at GJ380c Quarter n=32 (server-clamped to n=16 — see
/// below), district (13195,-2383), reported the ENTIRE visible course
/// (44 wire points, `terminus=Mouth`) painting as water tones, with the
/// resolved terminus apparently landing deep in open water. Investigated
/// via an instrumented reproduction of `resolve_mouth_terminus`'s exact
/// walk (temporary probe, deleted after this landed) — DETERMINATION:
/// this is not a bug. The window's world-metre rect
/// (`x∈[27006976,27039744]`) simply sits ~60+ km from the course's
/// upstream land anchor and close to the resolved coastal terminus, so
/// the overwhelming majority of what's IN FRAME is genuinely
/// Lake/OpenOcean-painted — confirmed by hand-computing the exact RGB
/// (`MORPHOLOGY_RGB_OPAQUE` × elevation lightness) for the coordinator's
/// sampled pixel colors, which matched Lake/OpenOcean to the rounding
/// digit. The walk's own station-by-station classification (178 land
/// stations, then 35 water stations, ZERO flip-flops) is perfectly
/// monotonic and the resolved terminus sits ~240 m from the true
/// land→water crossing — not "tens of km past coast".
///
/// **The invariant this test pins, since the raw "N consecutive water
/// stations" framing turned out not to be the real signal:** the wire
/// course's resolved `Mouth` terminus point must land in a window CELL
/// whose PAINTED morphology is genuinely `OpenOcean`/`Lake` (never a
/// land-family zone) — i.e. the termination walk and the window's own
/// per-cell classification, sampled independently via the SAME
/// `derive_at_metres` call, must agree at the terminus. This is the
/// walk/paint reconciliation the dossier was asked to determine, made
/// permanent and mechanism-agnostic (it would catch a REAL divergence —
/// wrap slip, sign flip, station-order bug — regardless of which
/// specific window happens to expose it).
#[test]
fn mouth_terminus_lands_in_a_painted_water_cell_on_real_gj380c() {
use crate::atlas::body_params_reader::BodyParamsReader;
use crate::atlas::drainage;
use crate::atlas::heightmap::{load_heightmap_png, GRID_H, GRID_W};
use crate::atlas::river_course;
let manifest = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let src = manifest.join("../wiki/star-systems/GJ-380/bodies/GJ380c/heightmap.png");
let systems_db = manifest.join("data/systems.db");
let params_reader = BodyParamsReader::open(&systems_db).expect("open committed systems.db");
let params = params_reader
.read_body_params("GJ380c")
.expect("read GJ380c body params from committed systems.db");
let heightmap = load_heightmap_png(&src, "GJ380c", 0.3).expect("decode GJ380c heightmap");
let working = if heightmap.width > GRID_W || heightmap.height > GRID_H {
heightmap.downsample(GRID_W, GRID_H)
} else {
heightmap
};
let dr = drainage::analyze(
&working.data,
working.width,
working.height,
working.sea_level,
);
let ta = crate::atlas::features::TerrainAnalysis::analyze(&working, &dr);
let rn = &dr.river_network;
// The exact real mouth cell from the live capture.
let target_px: (u16, u16) = (63, 354);
let edges = river_course::build_edges(rn);
let edge = edges
.iter()
.find(|e| e.edge_id == river_course::pack_cell_id(target_px))
.expect("GJ380c must have a Mouth edge at the live-captured cell");
assert_eq!(
edge.edge_id, 4129122,
"must be the same edge the live capture reported"
);
let seed = SeedChain::for_body(0, "GJ380c"); // production default world_seed
let climate = crate::atlas::district_profile::ClimateConstants::default();
// The EXACT live window shape: district (13195,-2383), REQUESTED
// n=32, Quarter granularity — server-side clamps n=32 down to n=16
// at Quarter granularity (`clamp_window_n`: cap_n = sqrt(4096)/4 =
// 16), which `build_district_window_layer` does NOT do itself (it
// trusts n verbatim by its own doc) — the request-handling layer
// (`handle_atlas_request`) applies `clamp_window_n_v2` BEFORE
// calling it. Pass the already-clamped n=16 here to match what a
// real client request actually receives.
let center = (13195, -2383);
let clamped_n = 16u32;
let layer = build_district_window_layer(
seed,
"GJ380c",
&params,
&ta,
rn,
center,
clamped_n,
&climate,
WindowGranularity::Quarter,
0,
);
let wire = layer
.courses
.iter()
.find(|c| c.edge_id == edge.edge_id)
.expect("edge 4129122 must ship in this window, matching the live capture");
assert_eq!(
wire.points.len(),
44,
"point count must match the live capture exactly, confirming this IS the \
reported window"
);
assert_eq!(wire.terminus, CourseTerminus::Mouth);
// The invariant: the terminus point's containing Quarter cell, read
// from THIS SAME layer's own painted `morphology` array, must be
// OpenOcean or Lake.
let side = WindowGranularity::Quarter.cell_grid_side(clamped_n);
let half = side / 2;
let step_m = WindowGranularity::Quarter.spacing_m();
let center_world_m = (
center.0 as f64 * DISTRICT_M as f64,
center.1 as f64 * DISTRICT_M as f64,
);
let (tx, ty) = *wire.points.last().unwrap();
let col = ((tx as f64 - center_world_m.0) / step_m + half as f64).round() as i32;
let row = ((ty as f64 - center_world_m.1) / step_m + half as f64).round() as i32;
assert!(
row >= 0 && row < side && col >= 0 && col < side,
"terminus point ({tx},{ty}) must map to an in-window cell, got (row={row},col={col})"
);
let cell_idx = (row * side + col) as usize;
let painted = layer.morphology[cell_idx];
assert!(
painted == crate::simulation::generator::MorphologyZone::OpenOcean as u8
|| painted == crate::simulation::generator::MorphologyZone::Lake as u8,
"walk/paint DIVERGENCE: the resolved Mouth terminus ({tx},{ty}) landed in a cell \
painted with morphology discriminant {painted} (not OpenOcean=0 or Lake=1) — the \
termination walk's own water verdict must agree with the window's painted \
classification at the SAME position"
);
}
/// Clamped-window edge: `n = 1` is the minimum valid window (a single
/// district) — no panic, no empty output, exactly one cell per array.
#[test]
fn build_district_window_layer_handles_n_equals_one() {
let hm = window_test_hm();
let ta = window_test_ta(&hm);
let rn = window_test_river_network(&hm);
let params = window_test_params();
let climate = crate::atlas::district_profile::ClimateConstants::default();
let seed = SeedChain::root(1).derive(SeedDomain::Body, 1);
let layer = build_district_window_layer(
seed,
"test_body",
&params,
&ta,
&rn,
(0, 0),
1,
&climate,
WindowGranularity::District,
0,
);
assert_eq!(layer.n, 1);
assert_eq!(layer.morphology.len(), 1);
assert_eq!(layer.elev_q.len(), 1);
assert_eq!(layer.temp_dc.len(), 1);
assert_eq!(layer.moisture_q.len(), 1);
assert_eq!(layer.vegetation.len(), 1);
assert_eq!(layer.glaciation.len(), 1);
}
/// Determinism spot-check (D-010, T-1123 precedent promoted to a real
/// test per the ticket): two full derive passes over the SAME window are
/// byte-identical, at a window size large enough to exercise many cells
/// (mirrors `aliveness_probe --render`'s own two-pass proof, now pinned
/// as a unit test rather than a probe-only demonstration).
#[test]
fn build_district_window_layer_two_passes_are_byte_identical() {
let hm = window_test_hm();
let ta = window_test_ta(&hm);
let rn = window_test_river_network(&hm);
let params = window_test_params();
let climate = crate::atlas::district_profile::ClimateConstants::default();
let seed = SeedChain::root(7).derive(SeedDomain::Body, 3);
let n = 8u32;
let first = build_district_window_layer(
seed,
"test_body",
&params,
&ta,
&rn,
(3, -2),
n,
&climate,
WindowGranularity::District,
0,
);
let second = build_district_window_layer(
seed,
"test_body",
&params,
&ta,
&rn,
(3, -2),
n,
&climate,
WindowGranularity::District,
0,
);
assert_eq!(
first, second,
"two full derive passes over the same (center, n) must be byte-identical (D-010/D-227)"
);
}
/// T-1151 acceptance: the row-chunked `par_iter` window build
/// ([`build_district_window_layer`]) must be bit-identical to the
/// pre-parallel serial baseline ([`build_district_window_layer_serial`])
/// — same inputs, same output, exact row-major array ordering preserved.
/// Run at a window size large enough (16×16 = 256 cells) to actually
/// exercise multiple Rayon-dispatched rows, not just n=1.
#[test]
fn build_district_window_layer_parallel_matches_serial() {
let hm = window_test_hm();
let ta = window_test_ta(&hm);
let rn = window_test_river_network(&hm);
let params = window_test_params();
let climate = crate::atlas::district_profile::ClimateConstants::default();
let seed = SeedChain::root(13).derive(SeedDomain::Body, 4);
let n = 16u32;
let center = (5, -9);
let parallel = build_district_window_layer(
seed,
"test_body",
&params,
&ta,
&rn,
center,
n,
&climate,
WindowGranularity::District,
0,
);
let serial = build_district_window_layer_serial(
seed,
"test_body",
&params,
&ta,
&rn,
center,
n,
&climate,
WindowGranularity::District,
0,
);
assert_eq!(
parallel, serial,
"row-chunked par_iter window build must be bit-identical to the serial baseline"
);
// Row-major ordering check, explicit (not just struct equality): the
// parallel path collects one Vec<WindowCell> per row via par_iter,
// which preserves index order (`par_iter().map(...).collect()` is
// order-preserving), but pin the ordering assumption directly too.
assert_eq!(parallel.morphology.len(), (n * n) as usize);
assert_eq!(parallel.center, center);
assert_eq!(parallel.n, n);
}
/// FULL-PATH determinism (PR #187 review — Tyre C3, binding, load-bearing
/// for save-file lineage under D-227): the test above reuses ONE `ta` for
/// both passes, which only proves `build_district_window_layer` (the
/// packer) is a pure function of its arguments — it says nothing about
/// whether re-running `run_layer1` itself (the D8 drainage pass +
/// `TerrainAnalysis::analyze`) is deterministic, which is exactly what the
/// production `DeriveWindow` path depends on (T-1137's `TerrainAnalysis`
/// re-derive / `TerrainAnalysisCache::get_or_derive` on a cache miss, and
/// `aliveness_probe --render`'s workaround before it).
///
/// This test runs `run_layer1` TWICE, independently, from the SAME
/// `(seed, heightmap)` inputs — no shared `ta` — and asserts the two
/// COMPLETE `DistrictWindowLayer` outputs (derive AND pack) are
/// byte-identical. D-227's save-file guarantee ("same seed/body/position →
/// same derived output, always") is only as strong as the weakest link in
/// that chain; this closes the gap the packer-only test left open.
#[test]
fn full_path_two_independent_run_layer1_passes_produce_identical_window() {
let hm = window_test_hm();
let params = window_test_params();
let climate = crate::atlas::district_profile::ClimateConstants::default();
let seed = SeedChain::root(11).derive(SeedDomain::Body, 5);
let n = 6u32;
let center = (4, -1);
// Two INDEPENDENT calls to run_layer1 — each re-runs D8 drainage +
// TerrainAnalysis::analyze from scratch on the SAME heightmap, exactly
// mirroring what a cold TerrainAnalysisCache miss does on the real
// DeriveWindow path (or a second body eviction re-pay).
let (l1_pass1, ta_pass1) = crate::atlas::layer1::run_layer1(&hm);
let (l1_pass2, ta_pass2) = crate::atlas::layer1::run_layer1(&hm);
// Confirm the two independent TerrainAnalysis derivations themselves
// agree field-by-field — a precise failure signal if drainage/analyze
// ever introduces nondeterminism (unordered iteration, uninitialized
// memory, etc.) BEFORE the packer even runs.
assert_eq!(ta_pass1.ocean_mask, ta_pass2.ocean_mask);
assert_eq!(ta_pass1.lake_mask, ta_pass2.lake_mask);
assert_eq!(ta_pass1.water_dist, ta_pass2.water_dist);
assert_eq!(ta_pass1.slope_deg, ta_pass2.slope_deg);
assert_eq!(ta_pass1.elev_pct, ta_pass2.elev_pct);
// T-1184: two independent hydrology solves (each run_layer1 call
// solves fresh — no shared HydrologyResult) must also agree
// byte-for-byte, extending this test's own "weakest link in the
// determinism chain" rationale to the newest field on TerrainAnalysis.
let hydro1 = ta_pass1
.hydrology
.as_ref()
.expect("run_layer1 must populate hydrology");
let hydro2 = ta_pass2
.hydrology
.as_ref()
.expect("run_layer1 must populate hydrology");
assert_eq!(hydro1.elevation, hydro2.elevation);
assert_eq!(hydro1.filled, hydro2.filled);
// Now the FULL path: pack a DistrictWindowLayer from each independent
// TerrainAnalysis and confirm the complete served payload agrees.
let window_from_pass1 = build_district_window_layer(
seed,
"test_body",
&params,
&ta_pass1,
&l1_pass1.river_network,
center,
n,
&climate,
WindowGranularity::District,
0,
);
let window_from_pass2 = build_district_window_layer(
seed,
"test_body",
&params,
&ta_pass2,
&l1_pass2.river_network,
center,
n,
&climate,
WindowGranularity::District,
0,
);
assert_eq!(
window_from_pass1, window_from_pass2,
"two independent run_layer1 derivations from the same (seed, heightmap) \
must pack to a byte-identical DistrictWindowLayer end to end (D-227)"
);
}
/// [`DistrictWindowCache`] insert/get round-trips, and a capacity-1 cache
/// evicts the oldest entry FIFO — mirroring `BodyWorldStateCache`'s own
/// `evicts_lru_on_overflow` precedent, adapted to this cache's
/// capacity-only insertion-order eviction (no access-recency tracking,
/// per the struct doc: D-227 means a cached window has no staleness to
/// track, only unbounded growth to bound).
#[test]
fn district_window_cache_insert_get_and_evict() {
let mut cache = DistrictWindowCache::new(2);
let key_a: DistrictWindowKey = ("Alpha".into(), (0, 0), 4, WindowGranularity::District, 0);
let key_b: DistrictWindowKey = ("Beta".into(), (1, 1), 4, WindowGranularity::District, 0);
let key_c: DistrictWindowKey = ("Gamma".into(), (2, 2), 4, WindowGranularity::District, 0);
let mk = |center, n| DistrictWindowLayer {
center,
n,
granularity_v2: WindowGranularity::District,
min_wl_m: 0,
morphology: vec![0; (n * n) as usize],
elev_q: vec![0; (n * n) as usize],
temp_dc: vec![REGION_TEMP_NONE_DC; (n * n) as usize],
moisture_q: vec![0; (n * n) as usize],
vegetation: vec![0; (n * n) as usize],
glaciation: vec![0; (n * n) as usize],
courses: Vec::new(),
};
assert!(cache.get(&key_a).is_none());
cache.insert(key_a.clone(), mk((0, 0), 4));
cache.insert(key_b.clone(), mk((1, 1), 4));
assert_eq!(cache.len(), 2);
assert!(cache.get(&key_a).is_some());
assert!(cache.get(&key_b).is_some());
// Cache at capacity (2): inserting a third entry evicts key_a (oldest).
cache.insert(key_c.clone(), mk((2, 2), 4));
assert_eq!(cache.len(), 2);
assert!(
cache.get(&key_a).is_none(),
"key_a should have been evicted"
);
assert!(cache.get(&key_b).is_some());
assert!(cache.get(&key_c).is_some());
}
/// `handle_atlas_request`'s window branch clamps `window_n` server-side to
/// `[1, DISTRICT_WINDOW_MAX_N]` — a request claiming an oversized `n` on
/// the wire never reaches `build_district_window_layer` un-clamped. This
/// exercises the full request→submit→drain→cache→re-request loop with an
/// out-of-range `window_n`, confirming the CACHED layer (once the
/// background derive completes) carries the CLAMPED `n`, not the
/// requested one.
#[test]
fn handle_atlas_request_clamps_oversized_window_n() {
let mut cache = BodyWorldStateCache::new(CACHE_CAPACITY);
let mut window_cache = DistrictWindowCache::new(DISTRICT_WINDOW_CACHE_CAPACITY);
let (_db, resolver, params_reader, _root) = resolver_and_params_reader("GJ1c");
let queue = GenerationQueue::with_threads(1);
let oversized_req = AtlasLayerRequest {
body_id: "GJ1c".to_string(),
up_to: CascadeLayer::Topography,
window_center: Some((0, 0)),
window_n: DISTRICT_WINDOW_MAX_N * 10, // wildly over the wire — must clamp, not trust
window_granularity_v2: None,
window_min_wl_m: 0,
};
let resp = handle_atlas_request(
&oversized_req,
&mut cache,
&mut window_cache,
&queue,
&resolver,
None,
Some(&params_reader),
42,
1,
test_conn_id(),
);
// First request: window not yet cached → None, but a DeriveWindow
// must have been submitted (checked via the drain below).
assert!(resp.district_window.is_none());
// Wait for the Rayon DeriveWindow work item to complete.
std::thread::sleep(Duration::from_millis(300));
let completions = queue.drain_completions();
let window_completion = completions.into_iter().find_map(|c| {
if let GenCompletion::WindowDerived { body_id, layer } = c {
if body_id == "GJ1c" {
return Some(layer);
}
}
None
});
let layer = window_completion.expect("DeriveWindow must complete for GJ1c");
assert_eq!(
layer.n, DISTRICT_WINDOW_MAX_N,
"server must clamp window_n to DISTRICT_WINDOW_MAX_N, never trust the wire value"
);
}
/// **Contract-pinning test (PR #191 review, Tyre C1):** a quarter
/// (granularity=4) request for `n=32` echoes the WIRE-CAP-CLAMPED `n=16`,
/// not the requested 32 — `32² × 4² = 16,384` cells, 4x over
/// `WIRE_CAP_CELLS`. This is the exact scenario the review flagged as
/// silently breaking the client the moment T-1153 requests quarter at
/// n=32: the server echoes a DIFFERENT `n` than what was asked for, and
/// any client staleness guard comparing raw `_n` against the echo must
/// already know this will happen (see
/// `atlas_window_request.gd::_clamp_window_n_mirror()`, the client-side
/// fix landed alongside this test).
#[test]
fn quarter_n32_request_echoes_clamped_n16() {
let mut cache = BodyWorldStateCache::new(CACHE_CAPACITY);
let mut window_cache = DistrictWindowCache::new(DISTRICT_WINDOW_CACHE_CAPACITY);
let (_db, resolver, params_reader, _root) = resolver_and_params_reader("GJ1c");
let queue = GenerationQueue::with_threads(1);
let quarter_n32_req = AtlasLayerRequest {
body_id: "GJ1c".to_string(),
up_to: CascadeLayer::Topography,
window_center: Some((0, 0)),
window_n: 32,
window_granularity_v2: Some(WindowGranularity::Quarter),
window_min_wl_m: 0,
};
let resp = handle_atlas_request(
&quarter_n32_req,
&mut cache,
&mut window_cache,
&queue,
&resolver,
None,
Some(&params_reader),
42,
1,
test_conn_id(),
);
assert!(resp.district_window.is_none(), "first request — cache miss");
std::thread::sleep(Duration::from_millis(300));
let completions = queue.drain_completions();
let window_completion = completions.into_iter().find_map(|c| {
if let GenCompletion::WindowDerived { body_id, layer } = c {
if body_id == "GJ1c" {
return Some(layer);
}
}
None
});
let layer = window_completion.expect("DeriveWindow must complete for GJ1c");
assert_eq!(
layer.granularity_v2,
WindowGranularity::Quarter,
"granularity must echo back as requested (4 is within budget on its own)"
);
assert_eq!(
layer.n, 16,
"a quarter n=32 request must echo the wire-cap-clamped n=16, not the requested 32"
);
}
// -------------------------------------------------------------------
// clamp_window_n (T-1150)
// -------------------------------------------------------------------
/// District granularity: the per-axis DISTRICT_WINDOW_MAX_N cap alone
/// governs (64² × 1² = 4,096 = WIRE_CAP_CELLS exactly, so the cap is
/// never tighter than DISTRICT_WINDOW_MAX_N at granularity 1).
#[test]
fn clamp_window_n_district_granularity_uses_per_axis_cap() {
assert_eq!(
clamp_window_n(DISTRICT_WINDOW_MAX_N * 10, WINDOW_GRANULARITY_DISTRICT),
DISTRICT_WINDOW_MAX_N
);
assert_eq!(clamp_window_n(32, WINDOW_GRANULARITY_DISTRICT), 32);
assert_eq!(clamp_window_n(0, WINDOW_GRANULARITY_DISTRICT), 1);
}
/// Quarter granularity: the wire-size ceiling bites BEFORE the per-axis
/// cap — a request for n=64 at granularity 4 would derive 256×256=65,536
/// cells (16x over budget), so it must clamp down to n=16
/// (16² × 4² = 4,096 = WIRE_CAP_CELLS exactly — matching the design
/// doc's §3 worked example, "Quarter, capped to same cell budget
/// (n≈16 districts across)"), never to the raw DISTRICT_WINDOW_MAX_N=64.
#[test]
fn clamp_window_n_quarter_granularity_uses_wire_cap_not_per_axis_cap() {
let clamped = clamp_window_n(DISTRICT_WINDOW_MAX_N, WINDOW_GRANULARITY_QUARTER);
assert_eq!(
clamped, 16,
"quarter granularity must clamp n to keep (n*granularity)^2 <= WIRE_CAP_CELLS"
);
assert!(
clamped * clamped * WINDOW_GRANULARITY_QUARTER * WINDOW_GRANULARITY_QUARTER
<= WIRE_CAP_CELLS,
"clamped cell count must never exceed WIRE_CAP_CELLS"
);
}
/// A small requested `n` at quarter granularity is left unclamped when it
/// already fits the budget (the cap must not be a flat floor/ceiling
/// substitution — only trims when the request would actually overflow).
#[test]
fn clamp_window_n_quarter_granularity_leaves_small_n_unclamped() {
assert_eq!(clamp_window_n(8, WINDOW_GRANULARITY_QUARTER), 8);
}
// -------------------------------------------------------------------
// clamp_window_n_v2 (T-1152; PR #192 review — Hoshe, coordinator ruling
// 2026-07-22: test 2 reframed per the brute-force finding that the
// Region halving loop is unreachable at the CURRENT constants — see
// clamp_window_n_v2's doc comment for the full rationale)
// -------------------------------------------------------------------
/// The exact boundary: `n = DISTRICT_WINDOW_MAX_N_REGION` (6,400) is the
/// largest per-axis-legal `n`, and it lands EXACTLY on the wire-size
/// ceiling (`cell_grid_side(6400) = 64 = sqrt(WIRE_CAP_CELLS)`,
/// `64² = 4,096 = WIRE_CAP_CELLS`) — uncontested, meaning the request is
/// NOT further reduced by the halving loop; the per-axis clamp alone is
/// already exact at this boundary.
#[test]
fn clamp_window_n_v2_region_exact_boundary_n6400_uncontested() {
let result = clamp_window_n_v2(DISTRICT_WINDOW_MAX_N_REGION, WindowGranularity::Region);
assert_eq!(
result, DISTRICT_WINDOW_MAX_N_REGION,
"n=6400 must pass through unmodified — it already lands exactly on the ceiling"
);
let side = WindowGranularity::Region.cell_grid_side(result) as u32;
assert_eq!(
side * side,
WIRE_CAP_CELLS,
"n=6400's cell_grid_side must land EXACTLY on WIRE_CAP_CELLS, not under or over it"
);
}
/// **Reframed per the coordinator's 2026-07-22 ruling (PR #192 review —
/// Hoshe).** The originally-briefed name/shape
/// (`clamp_window_n_v2_region_halving_loop_fires_above_boundary`, e.g.
/// n=6450) does not hold: `raw_n.clamp(1, DISTRICT_WINDOW_MAX_N_REGION)`
/// runs BEFORE the halving loop's condition is ever checked, so any
/// `raw_n > DISTRICT_WINDOW_MAX_N_REGION` is clamped to exactly 6,400 —
/// the SAME uncontested boundary the test above proves — before
/// `cell_grid_side` ever sees the raw value. A brute-force sweep (done
/// by hand before writing this test, see `clamp_window_n_v2`'s doc
/// comment) confirms `cell_grid_side(n)` never exceeds `sqrt(WIRE_CAP_CELLS)`
/// for ANY `n` in `[1, DISTRICT_WINDOW_MAX_N_REGION]` — so the halving
/// loop is unreachable at the CURRENT constant derivation, not a bug to
/// manufacture a test around (coordinator's option 1, not option 2).
///
/// This test proves the ACTUAL property: the per-axis cap ALONE already
/// satisfies the wire-size ceiling for every reachable input, and pins
/// the loop's current no-op status explicitly — swept across
/// `[1, 2 × DISTRICT_WINDOW_MAX_N_REGION]` (double the legal range, so
/// wildly-oversized wire values are covered too, never trusting the
/// wire). If a FUTURE constant change (a new rung, a `WIRE_CAP_CELLS`
/// retune) ever makes the loop fire, the second assertion below breaks
/// LOUDLY — forcing a deliberate look rather than a silent behavior
/// change (exactly the safety-net role the loop exists for).
#[test]
fn clamp_window_n_v2_region_per_axis_cap_alone_satisfies_wire_cap_for_all_inputs() {
for raw_n in 1..=(2 * DISTRICT_WINDOW_MAX_N_REGION) {
let result = clamp_window_n_v2(raw_n, WindowGranularity::Region);
let side = WindowGranularity::Region.cell_grid_side(result) as u32;
assert!(
side * side <= WIRE_CAP_CELLS,
"raw_n={raw_n}: clamped result {result} (side {side}) exceeds WIRE_CAP_CELLS"
);
assert_eq!(
result,
raw_n.clamp(1, DISTRICT_WINDOW_MAX_N_REGION),
"raw_n={raw_n}: the halving loop must be a no-op at current constants — \
the per-axis clamp alone must already be the final answer"
);
}
}
/// `District`/`Quarter` through `clamp_window_n_v2` must be BYTE-IDENTICAL
/// to the legacy `clamp_window_n` for every input either variant can
/// legally carry — `clamp_window_n_v2` is documented as delegating to the
/// legacy function unchanged for these two rungs, this pins that claim
/// with a sweep rather than a handful of spot values.
#[test]
fn clamp_window_n_v2_delegates_to_legacy_for_district_and_quarter() {
// Sweep well past DISTRICT_WINDOW_MAX_N so the "never trust the wire"
// oversized-input case is covered too, not just in-range values.
for raw_n in 0..=(DISTRICT_WINDOW_MAX_N * 3) {
assert_eq!(
clamp_window_n_v2(raw_n, WindowGranularity::District),
clamp_window_n(raw_n, WINDOW_GRANULARITY_DISTRICT),
"District: clamp_window_n_v2 must match clamp_window_n exactly at raw_n={raw_n}"
);
assert_eq!(
clamp_window_n_v2(raw_n, WindowGranularity::Quarter),
clamp_window_n(raw_n, WINDOW_GRANULARITY_QUARTER),
"Quarter: clamp_window_n_v2 must match clamp_window_n exactly at raw_n={raw_n}"
);
}
}
// -------------------------------------------------------------------
// quantize_min_wl_m (T-1150, PR #191 review — Hoshe 1 / Tyre C3, design doc §5)
// -------------------------------------------------------------------
#[test]
fn quantize_min_wl_m_exact_band_values_are_stable() {
for &band in &MIN_WL_BANDS_M {
assert_eq!(quantize_min_wl_m(band as u32), band as u32);
}
}
#[test]
fn quantize_min_wl_m_zero_stays_zero() {
assert_eq!(quantize_min_wl_m(0), 0);
}
/// A value nearer to 0 than to the finest real octave band (T-1162: now
/// 1,024, the Quarter-rung floor) snaps to 0 (no cutoff) — the band set
/// includes 0 as a real, selectable band, not just a special-cased default.
#[test]
fn quantize_min_wl_m_small_value_snaps_to_zero_band() {
assert_eq!(quantize_min_wl_m(500), 0);
}
/// A value between two real octave bands snaps to the NEAREST one, not
/// always up or always down.
#[test]
fn quantize_min_wl_m_mid_value_snaps_to_nearest_band() {
// Between 4,096 and 8,192: 5,000 is nearer 4,096 (dist 904 vs 3,192).
assert_eq!(quantize_min_wl_m(5_000), 4_096);
// 7,500 is nearer 8,192 (dist 692 vs 3,404).
assert_eq!(quantize_min_wl_m(7_500), 8_192);
}
/// T-1162: a value between the new 1,024 band and 0 snaps to whichever is
/// nearer — exercises the new finest band specifically, not just the
/// pre-existing four.
#[test]
fn quantize_min_wl_m_snaps_to_new_quarter_floor_band() {
// Nearer 1,024 than 0 (dist 224 vs 800).
assert_eq!(quantize_min_wl_m(800), 1_024);
// Nearer 1,024 than 4,096 (dist 476 vs 2,596).
assert_eq!(quantize_min_wl_m(1_500), 1_024);
}
/// A value far above the coarsest band snaps to the coarsest band, never
/// panics or overflows — quantization must be a TOTAL function over all
/// u32 input (never trust the wire).
#[test]
fn quantize_min_wl_m_huge_value_snaps_to_coarsest_band() {
assert_eq!(quantize_min_wl_m(u32::MAX), 32_768);
assert_eq!(quantize_min_wl_m(1_000_000), 32_768);
}
/// The mandatory §5 aliasing-closing test: two requests differing only in
/// an UNQUANTIZED `min_wl_m` that both fall in the SAME band must share
/// ONE cache entry, not two — this is the exact gap §5 flags ("same key,
/// different min_wl, would silently collide" becomes "same key, same
/// quantized min_wl, correctly coalesce").
#[test]
fn two_requests_in_same_min_wl_band_share_one_cache_entry() {
let mut cache = BodyWorldStateCache::new(CACHE_CAPACITY);
let mut window_cache = DistrictWindowCache::new(DISTRICT_WINDOW_CACHE_CAPACITY);
let (_db, resolver, params_reader, _root) =
resolver_and_params_reader_with_radius("BandBody", 6371.0);
let queue = GenerationQueue::with_threads(2);
// Both values are nearer 4,096 than any other band (4,000 and 4,300
// both round to 4,096 — see the mid-value test above for the
// distance math), so they must land in the SAME quantized band.
let req_a = AtlasLayerRequest {
body_id: "BandBody".to_string(),
up_to: CascadeLayer::Topography,
window_center: Some((10, -5)),
window_n: 4,
window_granularity_v2: None,
window_min_wl_m: 4_000,
};
let req_b = AtlasLayerRequest {
body_id: "BandBody".to_string(),
up_to: CascadeLayer::Topography,
window_center: Some((10, -5)),
window_n: 4,
window_granularity_v2: None,
window_min_wl_m: 4_300,
};
handle_atlas_request(
&req_a,
&mut cache,
&mut window_cache,
&queue,
&resolver,
None,
Some(&params_reader),
42,
1,
test_conn_id(),
);
handle_atlas_request(
&req_b,
&mut cache,
&mut window_cache,
&queue,
&resolver,
None,
Some(&params_reader),
42,
1,
test_conn_id(),
);
std::thread::sleep(Duration::from_millis(300));
let completions = queue.drain_completions();
for c in completions {
if let GenCompletion::WindowDerived { body_id, layer } = c {
if body_id == "BandBody" {
window_cache.insert(
(
body_id,
layer.center,
layer.n,
layer.granularity_v2,
layer.min_wl_m,
),
*layer,
);
}
}
}
assert_eq!(
window_cache.len(),
1,
"two requests in the SAME quantized min_wl_m band at identical \
(body, center, n, granularity) must share ONE cache entry, not two"
);
// Re-request both — each must hit the SAME cached entry and echo the
// QUANTIZED band (4,096), not either raw wire value.
let resp_a = handle_atlas_request(
&req_a,
&mut cache,
&mut window_cache,
&queue,
&resolver,
None,
Some(&params_reader),
42,
2,
test_conn_id(),
);
let resp_b = handle_atlas_request(
&req_b,
&mut cache,
&mut window_cache,
&queue,
&resolver,
None,
Some(&params_reader),
42,
2,
test_conn_id(),
);
let layer_a = resp_a.district_window.expect("req_a must hit the cache");
let layer_b = resp_b.district_window.expect("req_b must hit the cache");
assert_eq!(layer_a.min_wl_m, 4_096, "echo must be the QUANTIZED band");
assert_eq!(layer_b.min_wl_m, 4_096, "echo must be the QUANTIZED band");
assert_eq!(
layer_a, layer_b,
"both requests must resolve to the identical cached layer"
);
}
// -------------------------------------------------------------------
// normalize_window_center (T-1142 — letterbox-click out-of-range bug)
// -------------------------------------------------------------------
/// A small-body fixture (500 km radius) whose bounds are hand-checkable:
/// `districts_per_circumference = round(TAU*500_000/2048) = 1534`,
/// `half_meridian_districts = round(PI*500_000/2048/2) = 383`. The
/// literal column/row this fixture uses (`12276`, `3021`) are the exact
/// values the reported T-1142 letterbox-click bug sent — at THIS radius
/// they genuinely overflow both bounds (at Earth radius, coincidentally,
/// they wouldn't — the bounds are tens of thousands of districts wide),
/// so this is a faithful small-body reproduction, not just an
/// arbitrarily-chosen out-of-range pair.
fn small_body_params() -> BodyParams {
BodyParams {
hydrosphere: Some("ocean".into()),
atmosphere: Some("breathable".into()),
planet_class: Some("temperate".into()),
body_radius_km: Some(500.0),
..Default::default()
}
}
/// Out-of-range COLUMN wraps (longitude is periodic) to its in-range
/// canonical twin via `rem_euclid` — mirroring `derive_district`'s own
/// `(wx / circumference_m).rem_euclid(1.0)` forward map.
#[test]
fn normalize_window_center_wraps_out_of_range_column() {
let params = small_body_params();
// districts_per_circumference = 1534 (hand-computed above).
// 12276 rem_euclid 1534 = 4 (12276 = 8*1534 + 4).
assert_eq!(
12276_i32.rem_euclid(1534),
4,
"sanity: the hand-computed wrap"
);
let (nx, ny) = normalize_window_center(&params, (12276, 0));
assert_eq!(
nx, 4,
"out-of-range column wraps to its canonical in-range twin"
);
assert_eq!(ny, 0, "an in-range row is untouched");
// The canonical twin normalizes to itself (idempotent).
let (nx2, _) = normalize_window_center(&params, (4, 0));
assert_eq!(nx2, 4);
// Negative columns wrap too (rem_euclid, not truncating rem) —
// longitude has no sign discontinuity.
let (nx3, _) = normalize_window_center(&params, (-1, 0));
assert_eq!(
nx3, 1533,
"negative column wraps to the top of the range, not a negative remainder"
);
}
/// Beyond-pole ROW clamps (latitude terminates, does not wrap) to
/// `±half_meridian_districts` — mirroring `derive_district`'s own
/// `(wy / meridian_m).clamp(-0.5, 0.5)` forward map. This is the
/// asymmetry the coordinator's fix explicitly calls out: columns wrap,
/// rows clamp — never the other way around.
#[test]
fn normalize_window_center_clamps_beyond_pole_row() {
let params = small_body_params();
// half_meridian_districts = 383 (hand-computed above).
let (_, ny) = normalize_window_center(&params, (0, 3021));
assert_eq!(
ny, 383,
"beyond-pole row clamps to the pole boundary, not wraps"
);
let (_, ny_neg) = normalize_window_center(&params, (0, -9000));
assert_eq!(ny_neg, -383, "clamping is symmetric at both poles");
// A row exactly at the boundary is untouched.
let (_, ny_boundary) = normalize_window_center(&params, (0, 383));
assert_eq!(ny_boundary, 383);
}
/// An in-range center (well inside both bounds) is returned UNCHANGED —
/// normalization must be a no-op for the overwhelming common case (every
/// legitimate click), not just a defensive clamp that happens to also
/// preserve valid input.
#[test]
fn normalize_window_center_leaves_in_range_center_unchanged() {
let params = small_body_params();
let center = (100, -50);
assert_eq!(normalize_window_center(&params, center), center);
// (0, 0) — the origin — is always in range regardless of body size.
assert_eq!(normalize_window_center(&params, (0, 0)), (0, 0));
}
/// No-radius bodies (tiny test-body fallback, `body_radius_km: None`) get
/// IDENTITY — `derive_district`'s own no-radius branch has no
/// wrap/clamp-in-district-space concept (see the function doc); an
/// extreme center here is out of this fix's scope by design, not an
/// oversight.
#[test]
fn normalize_window_center_no_radius_is_identity() {
let params = BodyParams::default(); // body_radius_km: None
let extreme = (999_999, -999_999);
assert_eq!(normalize_window_center(&params, extreme), extreme);
}
/// End-to-end (the coordinator's core ask): an out-of-range
/// `window_center` and its already-normalized twin, requested through the
/// REAL `handle_atlas_request` path, land in the SAME `DistrictWindowCache`
/// entry and produce a byte-identical `DistrictWindowLayer` — normalization
/// happens BEFORE the cache key is built, so an insane request and its
/// sane twin never diverge into separate cache entries (the bug this fix
/// closes: the insane request was cached STANDALONE).
#[test]
fn insane_and_sane_twin_requests_share_one_cache_entry() {
let mut cache = BodyWorldStateCache::new(CACHE_CAPACITY);
let mut window_cache = DistrictWindowCache::new(DISTRICT_WINDOW_CACHE_CAPACITY);
let (_db, resolver, params_reader, _root) =
resolver_and_params_reader_with_radius("SmallMoon", 500.0);
let queue = GenerationQueue::with_threads(1);
// The insane request — the reported T-1142 letterbox-click values.
let insane_req = AtlasLayerRequest {
body_id: "SmallMoon".to_string(),
up_to: CascadeLayer::Topography,
window_center: Some((12276, 3021)),
window_n: 4,
window_granularity_v2: None,
window_min_wl_m: 0,
};
let resp1 = handle_atlas_request(
&insane_req,
&mut cache,
&mut window_cache,
&queue,
&resolver,
None,
Some(&params_reader),
42,
1,
test_conn_id(),
);
assert!(
resp1.district_window.is_none(),
"first request — cache miss, DeriveWindow submitted"
);
// Wait for the background derive to complete and drain it into the cache
// (mirrors handle_atlas_request_clamps_oversized_window_n's pattern).
std::thread::sleep(Duration::from_millis(300));
let completions = queue.drain_completions();
for c in completions {
if let GenCompletion::WindowDerived { body_id, layer } = c {
if body_id == "SmallMoon" {
window_cache.insert(
(
body_id,
layer.center,
layer.n,
layer.granularity_v2,
layer.min_wl_m,
),
*layer,
);
}
}
}
// Exactly ONE entry must exist in the window cache after the insane
// request's derive completes — normalization means it was keyed on
// the canonical (4, 383), not the raw (12276, 3021).
assert_eq!(
window_cache.len(),
1,
"the insane request's derive must be cached under its NORMALIZED key"
);
// The "sane twin" — the already-normalized canonical center — hits
// the SAME cache entry the insane request just populated.
let sane_twin_req = AtlasLayerRequest {
body_id: "SmallMoon".to_string(),
up_to: CascadeLayer::Topography,
window_center: Some((4, 383)), // the hand-computed canonical twin
window_n: 4,
window_granularity_v2: None,
window_min_wl_m: 0,
};
let resp2 = handle_atlas_request(
&sane_twin_req,
&mut cache,
&mut window_cache,
&queue,
&resolver,
None,
Some(&params_reader),
42,
2,
test_conn_id(),
);
let twin_layer = resp2.district_window.expect(
"the sane twin must hit the cache the insane request populated — no new derive needed",
);
assert_eq!(
window_cache.len(),
1,
"the sane twin must NOT create a second cache entry"
);
// Re-request the ORIGINAL insane center too — it must ALSO now hit
// the same populated cache entry (both requests normalize to the
// same key).
let resp3 = handle_atlas_request(
&insane_req,
&mut cache,
&mut window_cache,
&queue,
&resolver,
None,
Some(&params_reader),
42,
3,
test_conn_id(),
);
let insane_layer_second_try = resp3
.district_window
.expect("the insane request, re-requested, must ALSO hit the shared cache entry");
assert_eq!(
twin_layer, insane_layer_second_try,
"the insane request and its sane twin must resolve to a BYTE-IDENTICAL layer"
);
assert_eq!(
window_cache.len(),
1,
"still exactly one entry — neither re-request created a second one"
);
}
/// **MANDATORY aliasing regression (T-1150, design doc §3's flagged
/// aliasing risk):** a granularity-4 (quarter) request and a
/// granularity-1 (district) request at the IDENTICAL `(body, center, n)`
/// must produce DISTINCT `DistrictWindowCache` entries and correct
/// per-granularity payloads — never silently alias onto the same slot
/// and serve one rung's data for the other's request.
#[test]
fn granularity_4_and_granularity_1_requests_produce_distinct_cache_entries() {
let mut cache = BodyWorldStateCache::new(CACHE_CAPACITY);
let mut window_cache = DistrictWindowCache::new(DISTRICT_WINDOW_CACHE_CAPACITY);
let (_db, resolver, params_reader, _root) =
resolver_and_params_reader_with_radius("AliasBody", 6371.0);
// 3 threads: BOTH DeriveWindow items (district + quarter) need to
// dispatch concurrently with the AnalyzeBody item the first request's
// whole-body cache miss also enqueues (handle_atlas_request always
// fires an AnalyzeBody alongside the window derive on a cold body) —
// 2 threads left one DeriveWindow stuck behind AnalyzeBody within the
// single drain_completions() call below.
let queue = GenerationQueue::with_threads(3);
let center = Some((10, -5));
let n = 4u32;
let district_req = AtlasLayerRequest {
body_id: "AliasBody".to_string(),
up_to: CascadeLayer::Topography,
window_center: center,
window_n: n,
window_granularity_v2: None,
window_min_wl_m: 0,
};
let quarter_req = AtlasLayerRequest {
body_id: "AliasBody".to_string(),
up_to: CascadeLayer::Topography,
window_center: center,
window_n: n,
window_granularity_v2: Some(WindowGranularity::Quarter),
window_min_wl_m: 0,
};
// Fire both requests — same (body, center, n), different granularity.
handle_atlas_request(
&district_req,
&mut cache,
&mut window_cache,
&queue,
&resolver,
None,
Some(&params_reader),
42,
1,
test_conn_id(),
);
handle_atlas_request(
&quarter_req,
&mut cache,
&mut window_cache,
&queue,
&resolver,
None,
Some(&params_reader),
42,
1,
test_conn_id(),
);
std::thread::sleep(Duration::from_millis(300));
let completions = queue.drain_completions();
for c in completions {
if let GenCompletion::WindowDerived { body_id, layer } = c {
if body_id == "AliasBody" {
window_cache.insert(
(
body_id,
layer.center,
layer.n,
layer.granularity_v2,
layer.min_wl_m,
),
*layer,
);
}
}
}
assert_eq!(
window_cache.len(),
2,
"district and quarter requests at the SAME (body, center, n) must occupy \
TWO distinct cache entries, not alias onto one"
);
// Re-request both — each must now hit ITS OWN cached entry and return
// the CORRECT per-granularity payload (not the other rung's data).
let district_resp = handle_atlas_request(
&district_req,
&mut cache,
&mut window_cache,
&queue,
&resolver,
None,
Some(&params_reader),
42,
2,
test_conn_id(),
);
let quarter_resp = handle_atlas_request(
&quarter_req,
&mut cache,
&mut window_cache,
&queue,
&resolver,
None,
Some(&params_reader),
42,
2,
test_conn_id(),
);
let district_layer = district_resp
.district_window
.expect("district request must hit its own cached entry");
let quarter_layer = quarter_resp
.district_window
.expect("quarter request must hit its own cached entry");
assert_eq!(district_layer.granularity_v2, WindowGranularity::District);
assert_eq!(quarter_layer.granularity_v2, WindowGranularity::Quarter);
// n echoes the DISTRICT extent unchanged at both granularities
// (design doc §2: "the window's n stays the DISTRICT extent").
assert_eq!(district_layer.n, n);
assert_eq!(quarter_layer.n, n);
// The derived CELL GRID differs: n×n at district, (4n)×(4n) at quarter.
assert_eq!(district_layer.morphology.len(), (n * n) as usize);
assert_eq!(
quarter_layer.morphology.len(),
(n * WINDOW_GRANULARITY_QUARTER * n * WINDOW_GRANULARITY_QUARTER) as usize
);
// Correct per-granularity payload, not the other rung's data reused:
// the quarter grid must show finer per-cell VARIATION than a naive
// 4x-repeat of the district grid would (Option B — full
// reclassification at 512 m, not a coarser-cell interpolation).
let quarter_elev_range = {
let min = quarter_layer.elev_q.iter().min().copied().unwrap_or(0);
let max = quarter_layer.elev_q.iter().max().copied().unwrap_or(0);
max - min
};
assert!(
quarter_elev_range > 0,
"quarter-granularity window must show real sub-district elevation \
variation, not a blocky repeat of the district cells"
);
}
/// **MANDATORY aliasing regression for the T-1152 coarser rung** — the
/// SAME discipline `granularity_4_and_granularity_1_requests_produce_distinct_cache_entries`
/// established for finer-than-district rungs, extended to `Region`
/// (T-1150 design doc §3's aliasing risk, generalized by the T-1152
/// wire-contract note: "mirror the T-1150 aliasing tests for at least
/// one coarser rung"). A `Region`-granularity request and a
/// `District`-granularity request at the IDENTICAL `(body, center, n)`
/// must produce DISTINCT cache entries, distinct payload shapes, and the
/// clamp/echo contract must hold at the coarse rung too (PR #191 C1
/// lesson generalized: the client's mirror of `clamp_window_n_v2` MUST
/// be derivable from the same constants this test exercises).
#[test]
fn region_and_district_requests_produce_distinct_cache_entries() {
let mut cache = BodyWorldStateCache::new(CACHE_CAPACITY);
let mut window_cache = DistrictWindowCache::new(DISTRICT_WINDOW_CACHE_CAPACITY);
let (_db, resolver, params_reader, _root) =
resolver_and_params_reader_with_radius("OrbitalAliasBody", 6371.0);
// See the district/quarter alias test above for the thread-count
// rationale (AnalyzeBody + two DeriveWindow items must all be able to
// dispatch concurrently within one drain_completions() call).
let queue = GenerationQueue::with_threads(3);
let center = Some((10, -5));
let n = 4u32;
let district_req = AtlasLayerRequest {
body_id: "OrbitalAliasBody".to_string(),
up_to: CascadeLayer::Topography,
window_center: center,
window_n: n,
window_granularity_v2: None,
window_min_wl_m: 0,
};
let region_req = AtlasLayerRequest {
body_id: "OrbitalAliasBody".to_string(),
up_to: CascadeLayer::Topography,
window_center: center,
window_n: n,
window_granularity_v2: Some(WindowGranularity::Region),
window_min_wl_m: 0,
};
handle_atlas_request(
&district_req,
&mut cache,
&mut window_cache,
&queue,
&resolver,
None,
Some(&params_reader),
42,
1,
test_conn_id(),
);
handle_atlas_request(
&region_req,
&mut cache,
&mut window_cache,
&queue,
&resolver,
None,
Some(&params_reader),
42,
1,
test_conn_id(),
);
std::thread::sleep(Duration::from_millis(300));
let completions = queue.drain_completions();
for c in completions {
if let GenCompletion::WindowDerived { body_id, layer } = c {
if body_id == "OrbitalAliasBody" {
window_cache.insert(
(
body_id,
layer.center,
layer.n,
layer.granularity_v2,
layer.min_wl_m,
),
*layer,
);
}
}
}
assert_eq!(
window_cache.len(),
2,
"district and region requests at the SAME (body, center, n) must occupy \
TWO distinct cache entries, not alias onto one"
);
let district_resp = handle_atlas_request(
&district_req,
&mut cache,
&mut window_cache,
&queue,
&resolver,
None,
Some(&params_reader),
42,
2,
test_conn_id(),
);
let region_resp = handle_atlas_request(
&region_req,
&mut cache,
&mut window_cache,
&queue,
&resolver,
None,
Some(&params_reader),
42,
2,
test_conn_id(),
);
let district_layer = district_resp
.district_window
.expect("district request must hit its own cached entry");
let region_layer = region_resp
.district_window
.expect("region request must hit its own cached entry");
assert_eq!(district_layer.granularity_v2, WindowGranularity::District);
assert_eq!(region_layer.granularity_v2, WindowGranularity::Region);
assert_ne!(region_layer.granularity_v2, district_layer.granularity_v2);
// n echoes the DISTRICT extent unchanged (design doc §2), same as
// every other rung — the derived CELL GRID is what differs.
assert_eq!(district_layer.n, n);
assert_eq!(region_layer.n, n);
assert_eq!(district_layer.morphology.len(), (n * n) as usize);
// n=4 districts is far under one region's 100-district side, so
// cell_grid_side rounds down to the minimum 1x1 region cell —
// exercising the "region divides, doesn't multiply" cell-count
// relationship WindowGranularity::cell_grid_side documents.
assert_eq!(
region_layer.morphology.len(),
1,
"n=4 districts is far under one region's 100-district side; \
cell_grid_side must round down to a single region cell, not zero \
and not a district-sized grid"
);
}
/// The clamp/echo contract at the coarse (Region) rung (T-1152, the PR
/// #191 C1 lesson generalized): a region request whose `n` would derive
/// MORE than `sqrt(WIRE_CAP_CELLS)` region cells across must clamp `n`
/// down and echo the CLAMPED value — Stig's client-side mirror of
/// `clamp_window_n_v2` must be derivable from
/// `DISTRICT_WINDOW_MAX_N_REGION`/`WIRE_CAP_CELLS`/`DISTRICTS_PER_REGION`
/// alone, exactly as `_clamp_window_n_mirror()` already mirrors
/// `clamp_window_n` for the finer rungs.
#[test]
fn region_request_oversized_n_clamps_and_echoes_clamped_n() {
let mut cache = BodyWorldStateCache::new(CACHE_CAPACITY);
let mut window_cache = DistrictWindowCache::new(DISTRICT_WINDOW_CACHE_CAPACITY);
let (_db, resolver, params_reader, _root) =
resolver_and_params_reader_with_radius("OrbitalCapBody", 6371.0);
let queue = GenerationQueue::with_threads(1);
// Far over DISTRICT_WINDOW_MAX_N_REGION (6,400) — must clamp, never
// trust the wire (same discipline as the district-rung oversized-n test).
let oversized_region_req = AtlasLayerRequest {
body_id: "OrbitalCapBody".to_string(),
up_to: CascadeLayer::Topography,
window_center: Some((0, 0)),
window_n: DISTRICT_WINDOW_MAX_N_REGION * 10,
window_granularity_v2: Some(WindowGranularity::Region),
window_min_wl_m: 0,
};
let resp = handle_atlas_request(
&oversized_region_req,
&mut cache,
&mut window_cache,
&queue,
&resolver,
None,
Some(&params_reader),
42,
1,
test_conn_id(),
);
assert!(resp.district_window.is_none(), "first request — cache miss");
std::thread::sleep(Duration::from_millis(300));
let completions = queue.drain_completions();
let window_completion = completions.into_iter().find_map(|c| {
if let GenCompletion::WindowDerived { body_id, layer } = c {
if body_id == "OrbitalCapBody" {
return Some(layer);
}
}
None
});
let layer = window_completion.expect("DeriveWindow must complete for OrbitalCapBody");
assert_eq!(layer.granularity_v2, WindowGranularity::Region);
assert!(
layer.n <= DISTRICT_WINDOW_MAX_N_REGION,
"echoed n must be clamped to DISTRICT_WINDOW_MAX_N_REGION, not the raw oversized value"
);
let side = layer.granularity_v2.cell_grid_side(layer.n);
assert!(
(side as u32) * (side as u32) <= WIRE_CAP_CELLS,
"clamped cell count must never exceed WIRE_CAP_CELLS at Region granularity either"
);
}
/// The echoed `center` on `DistrictWindowLayer` is the NORMALIZED value,
/// not the raw wire value — the client's D-227 staleness guard (D-226
/// T-1124 amendment §2) must see what was ACTUALLY derived, so it can
/// correctly match this response against its own (now also normalized,
/// per the T-1142 fix note to the client team) cache key.
#[test]
fn echoed_center_is_the_normalized_value_not_the_raw_wire_value() {
let mut cache = BodyWorldStateCache::new(CACHE_CAPACITY);
let mut window_cache = DistrictWindowCache::new(DISTRICT_WINDOW_CACHE_CAPACITY);
let (_db, resolver, params_reader, _root) =
resolver_and_params_reader_with_radius("SmallMoon2", 500.0);
let queue = GenerationQueue::with_threads(1);
let insane_req = AtlasLayerRequest {
body_id: "SmallMoon2".to_string(),
up_to: CascadeLayer::Topography,
window_center: Some((12276, 3021)), // raw, out-of-range
window_n: 4,
window_granularity_v2: None,
window_min_wl_m: 0,
};
handle_atlas_request(
&insane_req,
&mut cache,
&mut window_cache,
&queue,
&resolver,
None,
Some(&params_reader),
42,
1,
test_conn_id(),
);
std::thread::sleep(Duration::from_millis(300));
let completions = queue.drain_completions();
let window_completion = completions.into_iter().find_map(|c| {
if let GenCompletion::WindowDerived { body_id, layer } = c {
if body_id == "SmallMoon2" {
return Some(layer);
}
}
None
});
let layer = window_completion.expect("DeriveWindow must complete");
assert_eq!(
layer.center,
(4, 383),
"the completed/echoed layer.center is the NORMALIZED value, not the raw (12276, 3021)"
);
assert_ne!(
layer.center,
(12276, 3021),
"the raw out-of-range wire value must never be echoed back"
);
}
/// `serve_district_window` returns `None` (no window requested) when the
/// request carries no `window_center` — the common case, and the ONLY
/// path every pre-T-1137 caller takes (wire back-compat: an old client's
/// `{body_id, up_to}` frame decodes with `window_center: None` via
/// `#[serde(default)]`).
#[test]
fn handle_atlas_request_no_window_center_leaves_district_window_none() {
let mut cache = BodyWorldStateCache::new(CACHE_CAPACITY);
let mut window_cache = DistrictWindowCache::new(DISTRICT_WINDOW_CACHE_CAPACITY);
let (_db, resolver) = empty_resolver();
let queue = GenerationQueue::with_threads(1);
let resp = handle_atlas_request(
&req("GJ1c"), // window_center: None
&mut cache,
&mut window_cache,
&queue,
&resolver,
None,
None,
42,
1,
test_conn_id(),
);
assert!(resp.district_window.is_none());
assert!(
window_cache.is_empty(),
"no window requested → no DeriveWindow submitted, cache stays empty"
);
}
/// `AtlasLayerResponse.district_window` survives a MessagePack round trip
/// (mirrors the existing `atlas_layer_response_with_new_layers_round_trips_msgpack`
/// precedent) — the wire shape every field the amendment specifies:
/// echoed `center`/`n`, all six parallel arrays including the
/// `REGION_TEMP_NONE_DC` sentinel and `VegetationClass::Marine = 6`.
#[test]
fn district_window_layer_round_trips_msgpack_inside_response() {
let window = DistrictWindowLayer {
center: (10, -5),
n: 2,
granularity_v2: WindowGranularity::District,
min_wl_m: 0,
morphology: vec![0, 8, 14, 16],
elev_q: vec![0, 45, 98, 60],
temp_dc: vec![205, 150, REGION_TEMP_NONE_DC, 80],
moisture_q: vec![90, 55, 0, 100],
vegetation: vec![6, 3, 0, 5], // includes Marine = 6
glaciation: vec![0, 0, 4, 1],
courses: Vec::new(),
};
let resp = AtlasLayerResponse {
body_id: "GJ1c".into(),
status: AtlasLayerStatus::Ready,
layer1: None,
district_grid: None,
road_graph: None,
settlements: None,
region_grid: None,
district_window: Some(window.clone()),
quarter_footprints: None,
};
let bytes = rmp_serde::to_vec_named(&resp).expect("encode");
let decoded: AtlasLayerResponse = rmp_serde::from_slice(&bytes).expect("decode");
let dw = decoded
.district_window
.expect("district_window survives round trip");
assert_eq!(dw, window);
assert_eq!(dw.center, (10, -5));
assert_eq!(dw.n, 2);
assert_eq!(
dw.temp_dc[2], REGION_TEMP_NONE_DC,
"airless sentinel preserved"
);
assert_eq!(dw.vegetation[0], 6, "Marine discriminant preserved");
}
/// Wire back-compat (D-226 T-1124 amendment §1): a pre-T-1137 request
/// frame carrying only `{body_id, up_to}` — no `window_center`/`window_n`
/// keys at all — decodes cleanly via `#[serde(default)]`, byte-unchanged
/// for every existing caller.
#[test]
fn old_request_frame_without_window_fields_decodes_with_none() {
#[derive(serde::Serialize)]
struct OldAtlasLayerRequest {
body_id: String,
up_to: CascadeLayer,
}
let old = OldAtlasLayerRequest {
body_id: "GJ1c".into(),
up_to: CascadeLayer::Topography,
};
let bytes = rmp_serde::to_vec_named(&old).expect("encode old-shape frame");
let decoded: AtlasLayerRequest = rmp_serde::from_slice(&bytes).expect("decode");
assert_eq!(decoded.body_id, "GJ1c");
assert_eq!(decoded.up_to, CascadeLayer::Topography);
assert_eq!(decoded.window_center, None);
assert_eq!(decoded.window_n, 0);
assert_eq!(
decoded.window_granularity_v2, None,
"T-1152/T-1159: absent window_granularity_v2 decodes to None (district), byte-compatible"
);
assert_eq!(
decoded.window_min_wl_m, 0,
"T-1150: absent window_min_wl_m decodes to 0 (no cutoff), byte-compatible"
);
}
/// 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 {
BodyWorldState {
body_id: body_id.into(),
heightmap: vec![],
heightmap_width: 16,
heightmap_height: 8,
sea_level: 0.3,
river_network: RiverNetwork::default(),
drainage_basins: vec![],
attractors: vec![],
feature_names: vec![],
placements: vec![],
road_graph: crate::atlas::road_graph::RoadGraph::default(),
quarters: std::collections::BTreeMap::new(),
districts: std::collections::BTreeMap::new(),
regions: std::collections::BTreeMap::new(),
last_accessed: 0,
}
}
/// T-960 §1: `build_road_graph_layer` trims the internal `RoadGraph` (drops
/// `degree`/`parent_edge`/`length_cells`) while keeping everything a
/// planetary-map overlay needs (positions, kind, polyline, maintenance,
/// rail flag, named-route id).
#[test]
fn road_graph_layer_built_from_cached_state() {
use crate::atlas::road_graph::{RoadEdge, RoadGraph, RoadNode};
use crate::simulation::generator::MaintenanceAuthority;
let mut state = blank_state("GJ1c");
state.road_graph = RoadGraph {
nodes: vec![
RoadNode {
city_id: Some(1),
position: (10, 20),
kind: RoadNodeKind::Settlement,
degree: 1,
parent_edge: None,
is_hub: false,
},
RoadNode {
city_id: None,
position: (15, 25),
kind: RoadNodeKind::Waypoint,
degree: 0,
parent_edge: Some(0),
is_hub: false,
},
],
edges: vec![RoadEdge {
from: 0,
to: 1,
path: vec![(10, 20), (15, 25)],
length_cells: 20, // internal routing-grid measure — dropped
maintenance: MaintenanceAuthority::Trade,
named_route_id: Some("split/hwy-1".into()),
is_rail: true,
}],
};
let layer = build_road_graph_layer(&state).expect("populated road_graph → Some");
assert_eq!(layer.nodes.len(), 2);
assert_eq!(layer.nodes[0].position, (10, 20));
assert_eq!(layer.nodes[0].kind, RoadNodeKind::Settlement);
assert_eq!(layer.nodes[0].city_id, Some(1));
assert_eq!(layer.nodes[1].kind, RoadNodeKind::Waypoint);
assert_eq!(layer.nodes[1].city_id, None);
assert_eq!(layer.edges.len(), 1);
assert_eq!(layer.edges[0].path, vec![(10, 20), (15, 25)]);
assert_eq!(layer.edges[0].maintenance, MaintenanceAuthority::Trade);
assert!(layer.edges[0].is_rail);
assert_eq!(
layer.edges[0].named_route_id.as_deref(),
Some("split/hwy-1")
);
// Layer hasn't run (or zero settlements) → None, mirroring district_grid.
let unrun = blank_state("GJ1c");
assert!(build_road_graph_layer(&unrun).is_none());
}
/// T-960 §2: `build_settlement_layer` derives `size_class` from population
/// using the D-211 Tier A/B cutoffs, threads `is_capital` straight through,
/// and derives `is_port` cheaply from the anchoring attractor type.
#[test]
fn settlement_layer_built_from_cached_placements() {
use crate::atlas::attractor_matching::CityPlacement;
use crate::simulation::generator::{
ArrangementPattern, FoundingOrientation, PoliticalArchetype,
};
let mk = |city_id: u64,
name: &str,
pos: (u16, u16),
population: i64,
is_capital: bool,
attractor_type: AttractorType| CityPlacement {
city_id,
name: name.to_string(),
position: pos,
attractor_type,
score: 1000,
synthetic: false,
political_archetype: PoliticalArchetype::Commission,
arrangement_pattern: ArrangementPattern::RadialCore,
founding_orientation: FoundingOrientation::Cardinal,
population,
is_capital,
is_standalone_hq: false,
};
let mut state = blank_state("GJ1c");
state.placements = vec![
mk(
1,
"Port Aldren",
(12, 58),
2_000_000,
true,
AttractorType::CoastalAccess,
),
mk(
2,
"Millbrook",
(30, 40),
200_000,
false,
AttractorType::ValleyFloor,
),
mk(
3,
"Farmstead Rell",
(50, 60),
8_000,
false,
AttractorType::PlainCenter,
),
];
let layer = build_settlement_layer(&state).expect("populated placements → Some");
assert_eq!(layer.settlements.len(), 3);
let capital = layer.settlements.iter().find(|s| s.city_id == 1).unwrap();
assert_eq!(capital.name, "Port Aldren");
assert_eq!(capital.position, (12, 58));
assert_eq!(capital.size_class, SettlementSizeClass::Major);
assert!(capital.is_capital);
assert!(capital.is_port, "CoastalAccess must read as a port");
let mid = layer.settlements.iter().find(|s| s.city_id == 2).unwrap();
assert_eq!(mid.size_class, SettlementSizeClass::Standard);
assert!(!mid.is_capital);
assert!(!mid.is_port, "ValleyFloor is not a port attractor");
let small = layer.settlements.iter().find(|s| s.city_id == 3).unwrap();
assert_eq!(small.size_class, SettlementSizeClass::Minor);
assert!(!small.is_port);
// No placements → None.
let unrun = blank_state("GJ1c");
assert!(build_settlement_layer(&unrun).is_none());
}
/// 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).
#[test]
fn atlas_layer_response_with_new_layers_round_trips_msgpack() {
use crate::atlas::attractor_matching::CityPlacement;
use crate::atlas::road_graph::{RoadEdge, RoadGraph, RoadNode};
use crate::simulation::generator::{
ArrangementPattern, BlockSkeleton, DistrictType, FoundingOrientation,
MaintenanceAuthority, PoliticalArchetype, QuarterSkeleton, QuarterWorldState,
ZoningType,
};
let mut state = blank_state("GJ1c");
state.placements = vec![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,
}];
state.road_graph = RoadGraph {
nodes: vec![RoadNode {
city_id: Some(1),
position: (12, 58),
kind: RoadNodeKind::Settlement,
degree: 0,
parent_edge: None,
is_hub: false,
}],
edges: vec![RoadEdge {
from: 0,
to: 0,
path: vec![(12, 58)],
length_cells: 0,
maintenance: MaintenanceAuthority::Administrative,
named_route_id: None,
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(),
status: AtlasLayerStatus::Ready,
layer1: None,
district_grid: None,
road_graph: build_road_graph_layer(&state),
settlements: build_settlement_layer(&state),
region_grid: build_region_grid(&state),
district_window: None,
quarter_footprints: build_quarter_footprint_layer(&state, world_seed),
};
let bytes = rmp_serde::to_vec_named(&resp).expect("encode");
let decoded: AtlasLayerResponse = rmp_serde::from_slice(&bytes).expect("decode");
assert_eq!(decoded.body_id, "GJ1c");
let rg = decoded.road_graph.expect("road_graph survives round trip");
assert_eq!(rg.nodes[0].position, (12, 58));
assert_eq!(
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");
assert_eq!(settlements.settlements[0].name, "Port Aldren");
assert_eq!(
settlements.settlements[0].size_class,
SettlementSizeClass::Major
);
assert!(settlements.settlements[0].is_capital);
assert!(settlements.settlements[0].is_port);
}
fn req(body_id: &str) -> AtlasLayerRequest {
AtlasLayerRequest {
body_id: body_id.to_string(),
up_to: CascadeLayer::Topography,
window_center: None,
window_n: 0,
window_granularity_v2: None,
window_min_wl_m: 0,
}
}
fn test_conn_id() -> ConnectionId {
ConnectionId(1)
}
const REL: &str = "wiki/star-systems/GJ-1/bodies/GJ1c/heightmap.png";
/// systems.db with one bodies row, + a base root containing a tiny 16-bit
/// heightmap PNG at the body's terrain_reference. Returns (db, resolver).
fn resolver_with_body(body_id: &str) -> (PathBuf, BodySourceResolver) {
let n = SEQ.fetch_add(1, Ordering::Relaxed);
let db = std::env::temp_dir().join(format!("sr_proxy_{}_{n}.db", std::process::id()));
let _ = std::fs::remove_file(&db);
let conn = Connection::open(&db).unwrap();
conn.execute(
"CREATE TABLE bodies (body_id TEXT PRIMARY KEY, terrain_reference TEXT)",
[],
)
.unwrap();
conn.execute(
"INSERT INTO bodies (body_id, terrain_reference) VALUES (?1, ?2)",
rusqlite::params![body_id, REL],
)
.unwrap();
let root = std::env::temp_dir().join(format!("sr_proxyroot_{}_{n}", std::process::id()));
write_tiny_heightmap(&root.join(REL));
let resolver = BodySourceResolver::open(&db, vec![root]).unwrap();
(db, resolver)
}
fn write_tiny_heightmap(path: &Path) {
use std::io::BufWriter;
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
let file = std::fs::File::create(path).unwrap();
let mut enc = png::Encoder::new(BufWriter::new(file), 32, 16);
enc.set_color(png::ColorType::Grayscale);
enc.set_depth(png::BitDepth::Sixteen);
let mut w = enc.write_header().unwrap();
let data: Vec<u8> = (0..32u32 * 16)
.flat_map(|i| (((i * 600) % 65536) as u16).to_be_bytes())
.collect();
w.write_image_data(&data).unwrap();
}
fn empty_resolver() -> (PathBuf, BodySourceResolver) {
let n = SEQ.fetch_add(1, Ordering::Relaxed);
let db = std::env::temp_dir().join(format!("sr_proxye_{}_{n}.db", std::process::id()));
let _ = std::fs::remove_file(&db);
let conn = Connection::open(&db).unwrap();
conn.execute(
"CREATE TABLE bodies (body_id TEXT, terrain_reference TEXT)",
[],
)
.unwrap();
let resolver = BodySourceResolver::open(&db, vec![std::env::temp_dir()]).unwrap();
(db, resolver)
}
#[test]
fn cache_hit_is_ready() {
let mut cache = BodyWorldStateCache::new(CACHE_CAPACITY);
cache.insert(BodyWorldState {
body_id: "GJ1c".into(),
heightmap: vec![0.0; 4],
heightmap_width: 2,
heightmap_height: 2,
sea_level: 0.3,
river_network: RiverNetwork::default(),
drainage_basins: vec![],
attractors: vec![],
feature_names: vec![],
placements: vec![],
road_graph: crate::atlas::road_graph::RoadGraph::default(),
quarters: std::collections::BTreeMap::new(),
districts: std::collections::BTreeMap::new(),
regions: std::collections::BTreeMap::new(),
last_accessed: 0,
});
let (_db, resolver) = empty_resolver();
let queue = GenerationQueue::with_threads(1);
let mut window_cache = DistrictWindowCache::new(DISTRICT_WINDOW_CACHE_CAPACITY);
let resp = handle_atlas_request(
&req("GJ1c"),
&mut cache,
&mut window_cache,
&queue,
&resolver,
None,
None,
42,
1,
test_conn_id(),
);
assert_eq!(resp.status, AtlasLayerStatus::Ready);
assert_eq!(resp.layer1.expect("layer1").body_id, "GJ1c");
}
#[test]
fn cache_miss_enqueues_and_pends_then_analyzes() {
let mut cache = BodyWorldStateCache::new(CACHE_CAPACITY);
let (_db, resolver) = resolver_with_body("GJ1c");
let queue = GenerationQueue::with_threads(1);
let mut window_cache = DistrictWindowCache::new(DISTRICT_WINDOW_CACHE_CAPACITY);
let resp = handle_atlas_request(
&req("GJ1c"),
&mut cache,
&mut window_cache,
&queue,
&resolver,
None,
None,
42,
1,
test_conn_id(),
);
assert_eq!(resp.status, AtlasLayerStatus::Pending);
assert!(resp.layer1.is_none());
// The enqueued analysis runs the real cascade and completes.
std::thread::sleep(Duration::from_millis(150));
let completions = queue.drain_completions();
assert!(
completions.iter().any(
|c| matches!(c, GenCompletion::BodyAnalyzed { body_id, .. } if body_id == "GJ1c")
),
"miss should enqueue an AnalyzeBody that completes: {completions:?}"
);
}
#[test]
fn unknown_body_is_not_found() {
let mut cache = BodyWorldStateCache::new(CACHE_CAPACITY);
let (_db, resolver) = empty_resolver();
let queue = GenerationQueue::with_threads(1);
let mut window_cache = DistrictWindowCache::new(DISTRICT_WINDOW_CACHE_CAPACITY);
let resp = handle_atlas_request(
&req("ghost"),
&mut cache,
&mut window_cache,
&queue,
&resolver,
None,
None,
42,
1,
test_conn_id(),
);
assert_eq!(resp.status, AtlasLayerStatus::NotFound);
}
/// Build a DB with the columns needed by both `BodySourceResolver` and
/// `BodyParamsReader` for the same body (Earth radius, 6371 km), plus a
/// tiny heightmap root.
///
/// Returns (db_path, resolver, body_params_reader, _root_kept_alive).
fn resolver_and_params_reader(
body_id: &str,
) -> (
PathBuf,
BodySourceResolver,
crate::atlas::body_params_reader::BodyParamsReader,
PathBuf, // root dir — must stay alive for the test duration
) {
resolver_and_params_reader_with_radius(body_id, 6371.0)
}
/// Same as [`resolver_and_params_reader`] with a caller-chosen
/// `body_radius_km` (T-1142: the window-centre normalization tests need a
/// SMALL body — at Earth radius the district-circumference/half-meridian
/// bounds are tens of thousands of districts wide, too large for a
/// hand-checkable out-of-range test value).
fn resolver_and_params_reader_with_radius(
body_id: &str,
r_km: f64,
) -> (
PathBuf,
BodySourceResolver,
crate::atlas::body_params_reader::BodyParamsReader,
PathBuf, // root dir — must stay alive for the test duration
) {
let n = SEQ.fetch_add(1, Ordering::Relaxed);
let db = std::env::temp_dir().join(format!("sr_proxybp_{}_{n}.db", std::process::id()));
let _ = std::fs::remove_file(&db);
let conn = Connection::open(&db).unwrap();
conn.execute_batch(
"CREATE TABLE star_systems (
system_id TEXT PRIMARY KEY,
spectral_class TEXT,
star_type TEXT
);
CREATE TABLE bodies (
body_id TEXT PRIMARY KEY,
system_id TEXT,
terrain_reference TEXT,
hydrosphere TEXT,
atmosphere TEXT,
planet_class TEXT,
body_radius_km REAL,
orbital_period_days REAL,
axial_tilt_deg REAL
);",
)
.unwrap();
conn.execute(
"INSERT INTO star_systems (system_id, spectral_class, star_type) VALUES ('GJ-1', 'G', 'main_sequence')",
[],
)
.unwrap();
conn.execute(
"INSERT INTO bodies (body_id, system_id, terrain_reference, hydrosphere, atmosphere, planet_class, body_radius_km, orbital_period_days, axial_tilt_deg)
VALUES (?1, 'GJ-1', ?2, 'ocean', 'breathable', 'temperate', ?3, 365.25, 23.5)",
rusqlite::params![body_id, REL, r_km],
)
.unwrap();
drop(conn);
let root = std::env::temp_dir().join(format!("sr_proxybproot_{}_{n}", std::process::id()));
write_tiny_heightmap(&root.join(REL));
let resolver = BodySourceResolver::open(&db, vec![root.clone()]).unwrap();
let params_reader = crate::atlas::body_params_reader::BodyParamsReader::open(&db).unwrap();
(db, resolver, params_reader, root)
}
/// With body_params_reader wired, a cache miss enqueues an AnalyzeBody that
/// 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);
let (_db, resolver, params_reader, _root) = resolver_and_params_reader("GJ1c");
let queue = GenerationQueue::with_threads(1);
let mut window_cache = DistrictWindowCache::new(DISTRICT_WINDOW_CACHE_CAPACITY);
let resp = handle_atlas_request(
&req("GJ1c"),
&mut cache,
&mut window_cache,
&queue,
&resolver,
None,
Some(&params_reader),
42,
1,
test_conn_id(),
);
assert_eq!(resp.status, AtlasLayerStatus::Pending);
// Wait for the Rayon work item to complete.
std::thread::sleep(Duration::from_millis(300));
let completions = queue.drain_completions();
let body_state = completions
.into_iter()
.find_map(|c| {
if let GenCompletion::BodyAnalyzed { body_id, state } = c {
if body_id == "GJ1c" {
return Some(state);
}
}
None
})
.expect("AnalyzeBody must complete for GJ1c");
assert!(
!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,
&mut window_cache,
&queue,
&resolver,
None,
Some(&params_reader),
42,
2,
test_conn_id(),
);
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.
#[test]
fn no_body_params_reader_leaves_regions_empty() {
let mut cache = BodyWorldStateCache::new(CACHE_CAPACITY);
let (_db, resolver) = resolver_with_body("GJ1c");
let queue = GenerationQueue::with_threads(1);
let mut window_cache = DistrictWindowCache::new(DISTRICT_WINDOW_CACHE_CAPACITY);
let resp = handle_atlas_request(
&req("GJ1c"),
&mut cache,
&mut window_cache,
&queue,
&resolver,
None,
None, // no body_params_reader
42,
1,
test_conn_id(),
);
assert_eq!(resp.status, AtlasLayerStatus::Pending);
std::thread::sleep(Duration::from_millis(300));
let completions = queue.drain_completions();
let body_state = completions
.into_iter()
.find_map(|c| {
if let GenCompletion::BodyAnalyzed { body_id, state } = c {
if body_id == "GJ1c" {
return Some(state);
}
}
None
})
.expect("AnalyzeBody must complete for GJ1c");
assert!(
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)"
);
}
}