feat(simulation): relief_q — a local relief signal the deep rungs can resolve (T-1213)
The District and Quarter rungs rendered as flat colour, and the cause was not the biome work everyone assumed. Measured on Ferrath through the production canvas builder: at District the mean |elev_q delta| between neighbouring gridunits is 0.02, and NOT ONE PAIR in a 1290x540 frame differs by 2. elev_q spans 0-100 across the body's whole 8 km elevation range, so ONE STEP IS 80 METRES. A District canvas covers 2,048 m of ground, where the rolling relief a walker navigates by is metres to tens of metres -- a fraction of a single step. The sub-district detail IS generated (invent_primitives' scatter and relief bands compute it) and then rounded away. Confirmed by running the diagnostic with the octave cutoff disabled: still 0.02. relief_q carries that same invented fine component against a scale chosen to resolve it: 0-100 about a flat 50, RELIEF_FULL_SCALE_M = 400 m either side, so 8 m per step -- ten times finer than elev_q. elev_q keeps its body-absolute meaning and the Atlas legend stays true. Measured effect, elev_q vs relief_q (distinct values / mean 4-cell delta): Region 49 / 2.38 -> 101 / 21.86 District 10 / 0.08 -> 35 / 0.35 Quarter 8 / 0.02 -> 19 / 0.06 FIXED metre scale, never per-canvas normalization: the value for a piece of ground must not depend on what else is in frame, or the same hillside changes tone as the viewer pans. And it excludes elev_pct deliberately -- this is the departure from the surrounding land, not height above sea level; including the base would re-introduce the body-scale dominance that makes elev_q unusable down here. 50 at the orbital rungs, which skip invent_primitives by design. Nothing is lost: Global and Region still have varied elev_q (101 and 49 distinct values), and the client takes whichever field carries signal via a max, with no rung-name branching. The client's ruggedness driver changes with it. It was an elev_q GRADIENT, which cannot work across rungs -- the same 4-cell delta reads 21.86 at Region and 0.08 at District, so any single full-scale constant either saturates one or vanishes on the other. relief_q states relief outright, so |relief_q - 50| is the answer directly and a fixed metre scale is immune to that by construction. An absent plane reads FLAT, not zero -- 0 on this field means maximum relief BELOW flat, so a payload without it would have stippled the entire map. That is reachable: the field is #[serde(default)] so old-shape payloads decode. Two colorize tests whose fixtures predate the plane caught it. 2004 server tests, 1838 client tests, 0 failed. clippy clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -223,6 +223,30 @@ pub struct DistrictProfile {
|
||||
/// Average of `TerrainAnalysis.elev_pct` × 100 across district cells.
|
||||
pub elev_q: i32,
|
||||
|
||||
/// **Local** relief on a FIXED fine metre scale — 0–100 with 50 = flat,
|
||||
/// each step [`RELIEF_FULL_SCALE_M`] / 50 metres (T-1213, 2026-08-07).
|
||||
///
|
||||
/// WHY THIS IS NOT `elev_q`. `elev_q` spans the body's whole elevation
|
||||
/// range: 0–100 across [`MAX_REGION_ELEVATION_KM`] = 8 km, so **one step is
|
||||
/// 80 metres**. That is the right granularity for a whole-body map and
|
||||
/// useless below it — a District canvas covers 2,048 m of ground, where the
|
||||
/// rolling relief a walker navigates by is metres to tens of metres, i.e.
|
||||
/// a fraction of one `elev_q` step. Measured on Ferrath through the
|
||||
/// production canvas builder: at District the mean |elev_q delta| between
|
||||
/// neighbouring gridunits is **0.02**, and not one pair in a 1290×540 frame
|
||||
/// differs by 2. The sub-district detail IS generated — `scatter` and
|
||||
/// `relief` below compute it — and then rounded away.
|
||||
///
|
||||
/// So this field carries that same invented fine component against a scale
|
||||
/// chosen to resolve it, rather than against the body. It is the signal the
|
||||
/// deep rungs draw texture from; `elev_q` keeps its body-absolute meaning
|
||||
/// and the Atlas legend stays true.
|
||||
///
|
||||
/// Signed about 50 rather than a magnitude, because a hollow and a rise are
|
||||
/// different ground and a consumer that wants only roughness can take
|
||||
/// `|relief_q - 50|` — the reverse is not recoverable.
|
||||
pub relief_q: i32,
|
||||
|
||||
/// Ocean fraction for this district (0–100 scale, integer).
|
||||
pub ocean_fraction_q: i32,
|
||||
|
||||
@@ -722,6 +746,17 @@ pub fn derive_morphology_zone(
|
||||
/// span can replace it when body relief data is carried (T-1032).
|
||||
const MAX_REGION_ELEVATION_KM: f64 = 8.0;
|
||||
|
||||
/// Full-scale swing of [`DistrictProfile::relief_q`], in metres either side of
|
||||
/// flat. 400 m spans everything from a gentle roll to a substantial ridge at
|
||||
/// the sub-district scale, and puts one quantization step at **8 m** — ten
|
||||
/// times finer than `elev_q`'s 80 m, which is what makes the deep rungs
|
||||
/// resolvable at all.
|
||||
///
|
||||
/// A FIXED metre scale on purpose, never per-canvas normalization: the value
|
||||
/// for a piece of ground must not depend on what else is in frame, or the same
|
||||
/// hillside would change tone as the viewer pans and the map would shimmer.
|
||||
const RELIEF_FULL_SCALE_M: f64 = 400.0;
|
||||
|
||||
/// Climate tuning constants. `ClimateConstants::default()` holds the embedded
|
||||
/// values and is the authoritative runtime source **today**; the source-canonical
|
||||
/// `server/data/climate_constants.toml` mirrors those same values and is the file
|
||||
@@ -1145,6 +1180,9 @@ const WINDOW_RELIEF_SALT: u64 = 0x5EED_C0DE;
|
||||
struct InventedPrimitives {
|
||||
slope_q: i32,
|
||||
elev_q: i32,
|
||||
/// Local sub-district relief on the fixed [`RELIEF_FULL_SCALE_M`] scale —
|
||||
/// see [`DistrictProfile::relief_q`].
|
||||
relief_q: i32,
|
||||
ocean_fraction_q: i32,
|
||||
}
|
||||
|
||||
@@ -1360,8 +1398,22 @@ fn invent_primitives(
|
||||
// 0.0 at every cutoff ≥ 2,048 m (District and coarser), so this sum is
|
||||
// byte-identical to the pre-T-1162 `elev_pct + scatter` wherever the
|
||||
// cutoff discipline says it must be.
|
||||
// The SAME invented fine component the elevation sum above folds in, kept
|
||||
// on its own fixed metre scale instead of the body's (T-1213). `scatter`
|
||||
// and `relief` are normalized fractions of MAX_REGION_ELEVATION_KM, so
|
||||
// scaling by it gives metres; RELIEF_FULL_SCALE_M then maps +/-400 m onto
|
||||
// 0..100 about a flat 50, i.e. 8 m per step against elev_q's 80 m.
|
||||
//
|
||||
// Deliberately excludes `elev_pct`: this is LOCAL relief, the departure
|
||||
// from the surrounding land, not height above sea level. Including the
|
||||
// base would re-introduce exactly the body-scale dominance that makes
|
||||
// elev_q unusable down here.
|
||||
let relief_m = (scatter + relief) * MAX_REGION_ELEVATION_KM * 1_000.0;
|
||||
let relief_q = ((50.0 + (relief_m / RELIEF_FULL_SCALE_M) * 50.0).round() as i32).clamp(0, 100);
|
||||
|
||||
InventedPrimitives {
|
||||
elev_q: (((elev_pct + scatter + relief) * 100.0).round() as i32).clamp(0, 100),
|
||||
relief_q,
|
||||
slope_q: (((local_slope + ruggedness * (scatter.abs() + relief.abs()) + carve) * 100.0)
|
||||
.round() as i32)
|
||||
.clamp(0, 100),
|
||||
@@ -1636,6 +1688,11 @@ fn build_district_profile(
|
||||
climate: &ClimateConstants,
|
||||
slope_q: i32,
|
||||
elev_q: i32,
|
||||
// Local sub-district relief, fixed metre scale — see
|
||||
// `DistrictProfile::relief_q`. Inert to every other field's derivation (a
|
||||
// presentation-tier signal, not a classification input), so it is carried
|
||||
// straight through to the output.
|
||||
relief_q: i32,
|
||||
ocean_fraction_q: i32,
|
||||
region_baseline_c: Option<f32>,
|
||||
basin_direction: BasinDirection,
|
||||
@@ -1733,6 +1790,7 @@ fn build_district_profile(
|
||||
precipitation_class,
|
||||
slope_q,
|
||||
elev_q,
|
||||
relief_q,
|
||||
ocean_fraction_q,
|
||||
lake_margin_q,
|
||||
river_threshold,
|
||||
@@ -1986,6 +2044,7 @@ fn derive_at_metres_with_riparian(
|
||||
climate,
|
||||
prims.slope_q,
|
||||
prims.elev_q,
|
||||
prims.relief_q,
|
||||
prims.ocean_fraction_q,
|
||||
region_baseline_c,
|
||||
BasinDirection::default(),
|
||||
@@ -2182,6 +2241,14 @@ pub fn derive_orbital_at_metres(
|
||||
climate,
|
||||
slope_q,
|
||||
elev_q,
|
||||
// 50 = flat. The orbital rung skips `invent_primitives` altogether
|
||||
// (that is the whole point of this path — envelope-only, no scatter,
|
||||
// no warp), so no sub-district relief is computed here and reporting a
|
||||
// neutral midpoint is the honest answer rather than a fabricated one.
|
||||
// Nothing is lost visually: at Global and Region `elev_q` still varies
|
||||
// properly — 101 and 49 distinct values respectively on Ferrath — and
|
||||
// it is only below them that it flattens and `relief_q` takes over.
|
||||
50,
|
||||
ocean_fraction_q,
|
||||
region_baseline_c,
|
||||
BasinDirection::default(),
|
||||
|
||||
Reference in New Issue
Block a user