diff --git a/server/src/atlas/cascade.rs b/server/src/atlas/cascade.rs index 53cbb6a1f..21825f178 100644 --- a/server/src/atlas/cascade.rs +++ b/server/src/atlas/cascade.rs @@ -11,9 +11,10 @@ //! path-loading [`run_cascade`] is a thin wrapper around it. //! //! **Determinism (D-010 #4):** for a fixed heightmap + [`SeedChain`], the -//! snapshot is reproducible. Layers 0–1 are RNG-free (pure functions of the -//! heightmap); the carried `SeedChain` feeds the RNG-using layers (Layer 3+, -//! D-224). +//! snapshot is reproducible. Layers 0–3 are RNG-free — Layers 0–1 are pure +//! functions of the heightmap, and Layer 3 is a pure function of +//! (attractors, cities). The carried `SeedChain` is reserved for the future +//! RNG-using layers (Layer 4+, D-224). use std::path::Path; @@ -36,7 +37,9 @@ pub enum CascadeLayer { /// Layer 1 — empty-world topography: drainage, feature tags, sub-biome (#953). Topography, /// Layer 3 — settlement placement: attractor-matched city positions (#955, D-211). - /// First RNG-using layer (uses the carried `SeedChain`). + /// Deterministic and RNG-free: a pure function of (attractors, cities) via + /// `match_cities`. The carried `SeedChain` is unused here; later stochastic + /// layers (Layer 4+) will consume it. Settlement, } @@ -258,7 +261,12 @@ mod tests { #[test] fn layers_are_ordered() { + // The `up_to >= CascadeLayer::Settlement` guards in the cascade rely on + // this declaration order — pin it explicitly so reordering the enum (or + // inserting a layer out of sequence) fails here instead of silently + // breaking which layers run. assert!(CascadeLayer::Heightmap < CascadeLayer::Topography); + assert!(CascadeLayer::Topography < CascadeLayer::Settlement); } #[test] diff --git a/server/src/atlas/features.rs b/server/src/atlas/features.rs index 809f58397..6ad45f6ab 100644 --- a/server/src/atlas/features.rs +++ b/server/src/atlas/features.rs @@ -2,8 +2,10 @@ //! //! After D8 drainage analysis (D-208), this module extracts the 7 //! `AttractorType` tags from the heightmap + river network. Each attractor has -//! a pixel position and a normalized `strength` (0.0–1.0) derived from local -//! terrain quality. +//! a pixel position and a `strength` (integer 0–100) derived from local terrain +//! quality — computed in floating point, then quantized to an integer at the +//! extraction boundary so all downstream decisions stay integer-deterministic +//! (D-010). //! //! **D-209 / D-223 reconciliation:** D-209 reads ocean/lake polygons from //! `markers.json`, but D-223 reduced markers to a names-only pool — those @@ -13,8 +15,10 @@ //! //! **Determinism (D-010 #4):** all collections iterate in sorted/row-major //! order; the final attractor list is sorted by `(attractor_type, row, col)`. -//! No `HashMap`/`HashSet` iteration. `strength` is f32 but is never used as a -//! sort key. +//! No `HashMap`/`HashSet` iteration. `strength` (integer 0–100) is not part of +//! that ordering here, so float quantization can't perturb the sort; consumers +//! that rank by strength (e.g. `layer1::attach_feature_names`) do so on the +//! integer value. use std::collections::{BTreeMap, VecDeque}; diff --git a/server/src/atlas/layer_proxy.rs b/server/src/atlas/layer_proxy.rs index ce2308445..a2db34b3d 100644 --- a/server/src/atlas/layer_proxy.rs +++ b/server/src/atlas/layer_proxy.rs @@ -25,8 +25,12 @@ use crate::seed::SeedChain; /// (the loader prefers the chunk; this is only the floor). const DEFAULT_SEA_LEVEL: f32 = 0.3; -/// A client request for a body's generation layers (D-225). `up_to` is a -/// forward-compat seam — v1 always runs the Layer-1 (Topography) cascade. +/// A client request for a body's generation layers (D-225). +/// +/// `up_to` is a forward-compat seam that is **not yet honored**: `run_work_item` +/// currently runs the cascade through `CascadeLayer::Settlement` unconditionally, +/// 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,