feat(simulation): step-canvas serving — tagged envelope (D-255, T-1181)

The D-225-discharging wire migration per D-255(c): a new
server/src/atlas/step_canvas.rs subsystem (six-rung StepCanvasRung
ladder — Global variable-extent rung 0 through Chunk 64m — the
StepCanvasRequest/StepCanvasResponse tagged envelope extending the
proven ShapeProbe discriminated-shape pattern, PNG-per-field dense +
MessagePack-native sparse encoding per the T-1179 measured table, and
both server cache tiers: the structurally keep-always GlobalTierCache
and the dual-axis StepCanvasCache with storage TTLs per rung and
SIM_STATE_TTL clock-bucket staleness per the D-227 amendment (1)
formula). Wired through bridge/{mod,tcp,local}.rs (sixth demux shape,
send_step_canvas_response mirroring the five existing senders),
gen_queue.rs (DeriveStepCanvas work item with per-connection-per-rung
coalescing, reusing the shared TerrainAnalysisCache), and plugin.rs
(serve/complete systems, lazy D-206 rung-0 population).

Acceptance gate (mandatory per D-227 amendment (3)):
tests/step_canvas_acceptance_gate.rs — cache-hit == cache-miss
byte-identical for every rung, lossless encode round-trips, cache
round-trips vs fresh derive, distinct-center sanity. 5/5 pass.

Station-spacing cap ADOPTED: course stations floored to District
spacing (2,048 m) at finer rungs — the S2-measured +38-87% chunk/block
course cost had zero display benefit at the same station density
(COURSE_STATION_SPACING_FLOOR_M).

Documented honest gaps, not shortcuts: settlement_id is a proximity
approximation (no footprint polygons exist yet); glaciation/flooded_q
sim-state planes are wire-shape-ready D-253 stubs; rung-0 uses signed
equator-anchored rows (the canonical D-256 core convention — T-1186's
wrong-latitude behavior applies unchanged and unfixed here, by
instruction).

Legacy district_window carrier byte-unchanged: window_derivation_golden
6/6 byte-identical, all district_window suites pass unmodified. Full
suite at implementation time: 2127 passed across 45 binaries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-25 03:13:08 +02:00
co-authored by Claude Fable 5
parent 5151c9010f
commit cd2f342421
12 changed files with 2699 additions and 23 deletions
+2 -2
View File
@@ -31,7 +31,7 @@ use crate::simulation::generator::{AttractorType, DistrictType, MaintenanceAutho
/// Fallback sea level when the heightmap PNG carries no `sea_level` tEXt chunk
/// (the loader prefers the chunk; this is only the floor).
const DEFAULT_SEA_LEVEL: f32 = 0.3;
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
@@ -484,7 +484,7 @@ const _: () =
/// 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).
fn quantize_min_wl_m(raw: u32) -> u32 {
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();