test(simulation): the conservation gate's monoculture check was a tautology (T-1213)

D-258 invariant 2 says descending the ladder must reveal COMPOSITION — a cell
reading forest must be able to contain the clearings and rock the vote
suppressed. One assertion stood behind that, and it read:

    assert!(tally.len() > 1 || share == 1.0, ...)

A single-class tally has a 100% share by definition, so both branches are always
satisfiable: the check could never fail, including in the exact case its own
message names, "or nothing was composed". The invariant had a test and no gate.

Split into the two bounds the invariant actually has, because it is two-sided:
conservation caps how much may be invented (majority > 50%, already asserted) and
composition sets a floor on how little (minority >= 0.1%). Verified by raising
the floor to 2% and watching it fail on the measured 1.07%, then restoring it —
the floor is a tripwire for "did anything happen", deliberately far below the
measurement rather than tuned to it.

Measured at the descent ladder's own anchor on Ferrath:
  conservation: majority class 3 at 98.9% across 2 classes {1: 175, 3: 16209}

So composition IS working in the data and conservation holds. The map is flat
anyway, and tooling/atlas-flatness (added here) says why the eye was not enough:

    rung      distinct   lum p1-p99
    Global        1581       145.69
    Region        2923        33.59
    District        53        13.72
    Quarter         46        11.01

Region carries almost TWICE Global's distinct-colour count while holding a
quarter of its structure — the dither pass adds colour noise, not information, so
a colour-count metric would have called the flattest rung the richest. Structure
falls ~92% from Global to Quarter.

The cause is a channel mismatch rather than a missing generator: composition
perturbs moisture_q/slope_q, and the base map draws morphology hue x elev_q
lightness. The ladder scenarios pass no overlays deliberately, so the composed
fields are never rendered in the very shots that judge this work. Recorded on
T-1213 with the three ways forward; the choice touches D-258 and is Jeroen's.

The gate is still #[ignore]d — noted on the ticket as worth moving into a harness
that runs, since believability and window-derivation already load real bodies in
the normal cargo test path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-16 12:35:19 +02:00
co-authored by Claude Opus 5
parent 6e6218d654
commit 869837f728
4 changed files with 449 additions and 2 deletions
+42 -2
View File
@@ -110,6 +110,21 @@ pub const MOISTURE_COMPOSITION_CEILING_Q: i32 = 12;
/// slope.
pub const SLOPE_COMPOSITION_CEILING_Q: i32 = 6;
/// The floor on how much of a District patch the minority classes must hold for
/// composition to count as having happened (D-258 invariant 2's lower bound).
///
/// Deliberately far below the measured value rather than tuned to it: at the
/// descent ladder's own anchor on Ferrath the minority holds 1.07% (175 of
/// 16,384 samples), and this floor is 0.1%. It is a "did anything happen at all"
/// tripwire, not a quality bar — the bar for whether the map READS as composed
/// is visual, and lives in the ladder captures rather than in an assertion.
///
/// One-sided on purpose. The upper bound (conservation — the majority must
/// survive) is the invariant's other half and is asserted separately; a single
/// combined check is what let the previous version collapse into a tautology.
#[cfg(test)]
const MIN_COMPOSED_MINORITY_SHARE: f64 = 0.001;
/// The fine-band composition field at a world position, in `[-1, 1]`.
///
/// Only [`VOXEL_OCTAVE_WAVELENGTHS_M`] (1,024–128 m) — the band that actually
@@ -398,9 +413,34 @@ mod tests {
tally
);
// ...and it must not be a monoculture either, or nothing was composed.
//
// This assertion used to read `tally.len() > 1 || share == 1.0`, which is
// a TAUTOLOGY: a single-class tally has a 100% share by definition, so
// both branches were always satisfiable and the check could never fail —
// including in the exact case its own message describes, "nothing was
// composed". It was the only thing standing behind D-258's invariant 2
// (descending reveals COMPOSITION), and it was standing behind nothing.
//
// Both bounds are now asserted separately, because the invariant is
// two-sided: conservation caps how much may be invented (above), and
// composition sets a floor on how little (here). Measured at this anchor:
// 2 classes, minority 175/16384 = 1.07%.
assert!(
tally.len() > 1 || share == 1.0,
"tally {tally:?} — expected either composition or an honestly uniform patch"
tally.len() > 1,
"tally {tally:?} — a District patch resolved to ONE vegetation class. \
D-258 invariant 2 says a summarised cell must be able to CONTAIN the \
minority it suppressed; a monoculture here means the fine tier did \
nothing at this scale, which is the flat map this work exists to fix."
);
let minority = total - count;
let minority_share = minority as f64 / total as f64;
assert!(
minority_share >= MIN_COMPOSED_MINORITY_SHARE,
"minority classes hold {:.2}% of the patch, below the {:.2}% floor — \
inclusions this sparse are indistinguishable from none at map scale. \
tally {tally:?}",
100.0 * minority_share,
100.0 * MIN_COMPOSED_MINORITY_SHARE,
);
eprintln!(
"conservation: majority class {majority} at {:.1}% across {} classes {:?}",