Skip to content

W-SIM-6: glacial relief — ELA-gated ice incision + till moraine #416

Description

@mrtlvmbt

Context

Fourth landform slice of the worldgen-relief roadmap (integration branch worldgen-relief, NOT
main
), after tectonic W-SIM-4a (#396/#397, corrected #404), aeolian W-SIM-3a (#403/#406) and volcanic
W-SIM-5 (#410/#411). Base this work off worldgen-relief HEAD (c0d7950); the PR bases on and merges
into worldgen-relief.

Grounded in RnD sim/world/16-glacial-and-karst-landforms.md (this slice = the glacial gate only;
the karst gate and the glacial sub-forms listed under Out of scope are deferred). Glacial relief is a
NEW landform family: ice as a slow viscous flow that follows the existing drainage downhill,
abrades and quarries the rock it flows over (subtractive), and drops the excavated debris as
till where it melts (additive). Unlike volcanic (constructive, pre-erosion) it reshapes
ALREADY-eroded relief, so per RnD 16 §1 it is inserted POST-erosion, PRE-final-classify:

base height → volcanic → erosion → GLACIAL(this) → [karst, future] → aeolian → final classify

The forcing is a CLIMATE THRESHOLD — the ELA (Equilibrium Line Altitude / snowline): cells whose
terrain is colder than freezing (T ≤ T_freeze, read from the existing pure-integer lapse-rate
temperature field, centidegrees) are ice-covered. Ice forms on the cold/high belts only, so glaciation
is naturally rare and confined (like the toxicity belts), leaving the temperate world untouched.

Two determinism-mandated passes, in strict order (RnD 16 §6 — the load-bearing invariant):

  1. Subtractive ice-incision (single-signed): ice routes downhill, erosion concentrates where ice
    flux is high (deep troughs) and thin tributaries carve less (hanging valleys emerge). Ice-incision
    only ever LOWERS height → single-signed, monotone.
  2. Additive till deposition (separate pass, AFTER the incision pass settles): the rock-flour
    excavated in pass 1 is spent as an integer ledger — moraine ridges built where ice melts (the ice
    margin).

Mixing the two signs in one loop is forbidden (RnD 16 §6: a height-raise inside the routing loop
flip-flops the flow field and never settles). This split is a WHAT (a determinism requirement), not a
HOW — the implementer chooses the routing/iteration mechanism, subject to the acceptance invariants
below (pure-integer, order-independent, fixed non-convergence-terminated iteration in the house style of
the existing erosion macro-loop).

Opt-in, default-OFF (mirror enable_tectonics/enable_aeolian/enable_volcanic), threaded the
same orthogonal way through caps::classify_and_capsProcgenWorld::new; the OFF-path must be
byte-identical so the v2 acceptance corridors and existing goldens (which run with glacial OFF) are
untouched.

Feasibility (grounding, NOT prescription): the codebase already carries the primitives an
implementation would build on — a pure-integer lapse-rate temperature field, an integer downhill
drainage/flow-accumulation primitive with a total-order tie-break, and a fixed-count
recompute-drainage-each-iteration erosion macro-loop. HOW to compose them is the implementer's call;
the acceptance criteria constrain the OUTCOME, not the mechanism. See
pm/reports/w-sim-glacial-grounding.md.

Goal

When enable_glacial = true, the post-erosion height field gains a deterministic glacial signature:
ice-covered cells (an ELA temperature gate) are carved by a monotone, single-signed ice-incision
pass that deepens/widens high-ice-flux valleys into U-shaped troughs and overdeepened basins, and the
excavated rock-flour is then redeposited as Till moraine ridges at the ice margin by a separate
additive pass, with total excavation and total deposition balanced by an integer ledger. Both the trough
(a subtractive form the erosion-only baseline cannot make — fluvial erosion incises V-notches, not
flat-floored overdeepened U-troughs) and the moraine (a positive till ridge) are features absent from
the current worldgen-relief world. enable_glacial = false reproduces that world byte-for-byte.

Acceptance criteria

  • New opt-in glacial stage gated by an enable_glacial flag, default-OFF, threaded to
    ProcgenWorld::new the same orthogonal way enable_tectonics/enable_aeolian/enable_volcanic
    are (a new independent parameter; every production call site passes false). Inserted
    POST-erosion, PRE-final-classify — it consumes the fully eroded height field and hands its
    reshaped height + new till material forward.
  • OFF-path byte-identical: enable_glacial = false reproduces the pre-slice
    classify_and_caps / ProcgenWorld output byte-for-byte — no new RNG/noise draw and no ELA/ice
    computation of any kind when off (the gate SKIPS the whole pass, not merely discards it). A
    determinism test asserts this against the existing baseline (mirror the
    tectonics/aeolian/volcanic OFF-path tests in caps.rs).
  • ELA ice-mask is pure-integer and computed ONCE at stage entry (RnD 16 §6, §pitfalls): the ice
    mask is an integer threshold on the lapse-rate temperature field (T ≤ T_freeze, centidegrees) —
    no f32 snowline compare that could ULP-flip the ice edge, and the mask is derived ONCE from the
    post-erosion height at the start of the stage, NOT re-derived inside the incision iterations
    (which would couple mask→height→mask and risk non-convergence). A test asserts the mask is a pure
    deterministic function of (seed, dim, height), byte-identical across repeated generation; a
    warm world / an ice-free T_freeze yields an EMPTY mask ⇒ a byte-identical OFF-path result.
  • Ice-incision is subtractive-only, fixed-iteration, deterministic (RnD 16 §6; matches the
    erosion macro-loop's house style — a FIXED iteration count, NOT ε/convergence-terminated, per the
    [erosion] R10 determinism rule): every height change in the pass is a non-increasing delta
    (single-signed — no cell's height ever RISES during incision); the routing is downhill with a
    total-order tie-break so the result is order-independent / byte-identical across runs. Tests
    assert (a) no cell rises during the incision pass, and (b) the pass is byte-identical across two
    standalone runs on the golden grid.
  • U-trough structural signature (anti-forcing-clean, ON-vs-OFF at fixed coordinates, no tuned
    threshold):
    valley cells are identified on the glacial-OFF baseline (the highest-flux valley
    cells at that state). At those SAME fixed cell coordinates, the glacial-ON field shows the
    diagnostic glacial change RELATIVE to glacial-OFF: the valley floor is lowered (overdeepened)
    AND the cross-valley section is flatter-floored and steeper-walled than glacial-OFF. The test
    asserts the DIRECTION of change (ON vs OFF, same seed, same coordinates) — not an absolute
    magnitude tuned to pass — on ≥3 independent valley segments; the erosion-only baseline yields the
    V-form (no overdeepening) at those coordinates. The segments are selected DETERMINISTICALLY on
    the OFF baseline (the highest-flux valley cells, pairwise separated by a fixed minimum cell
    distance so they are geographically independent, not implementer-cherry-picked adjacent cells) —
    the selection rule is part of the test, not a free choice. (Lesson W-SIM-4a/5: verify a sharp structural
    feature the baseline can't produce, measured against the same-seed OFF baseline, not a bulk count
    or a hand-tuned number.)
  • Till deposition is a separate additive-only pass writing Till (RnD 16 §6): after the
    incision pass settles, the excavated volume is deposited as moraine — a positive height ridge at
    the ice margin — tagged with a new MaterialId::Till. Till is APPENDED to
    gen::material::MaterialId (append-only: existing discriminants Air=0 … Tuff=6 unchanged; new
    one = Till = 7); the OFF-path never emits it. A test asserts ≥1 moraine cell is a local height
    MAXIMUM (a positive ridge) at the ice margin and carries Till.
  • Mass-ledger conservation (integer-exact, export = 0 for this slice): total height excavated by
    the incision pass equals total height deposited by the till pass — Σ excavated ≡ Σ deposited as
    an exact integer equality, with NO export term (all rock-flour is retained as till in this slice;
    glaciofluvial outwash / loess export is explicitly deferred — see Out of scope). A conserved
    golden pins the equality (same class as volcanic's v2_golden_conserved_*).
  • Post-glacial height stays valid: every cell height after the stage is an integer in the
    terrain's existing valid range [0, hmax] (no underflow past 0 from over-incision, no overflow at
    the ceiling from deposition); a test asserts the bound holds.
  • Determinism golden vector (two-pass, CI-sourced): pinned exact glacial-ON height + surface
    material at fixed grid coordinates for the golden (seed, dim, hmax), read off the arm64 golden
    job, re-derivable from the module's algorithm doc — mirrors the
    tectonics/aeolian/volcanic golden-vector tests. Pass 1 = code + placeholder pin + push
    (STATUS: blocked@N: awaiting CI (pass 2 of 2)); pass 2 = pin the CI-revealed value, finish the PR.
  • Orthogonality: enabling glacial alone leaves cells outside the ice mask byte-identical to the
    glacial-OFF world (the pass is a no-op off the ice belts), and glacial composes with any subset of
    tectonics/aeolian/volcanic without cross-contamination — a test mirrors the existing pairwise
    orthogonality checks.
  • Pure integer / fixed-point throughout — no f32/f64 anywhere in any new gen/ file (the
    recursive glob no-float guard world/tests/no_float_guard_gen.rs covers it automatically);
    quantize-before-compare for every ELA / slope / ice-flux threshold.
  • scripts/compile-check.sh PASS quoted in the final report; pre-merge code-critic self-review
    run against the branch diff + this ТЗ, verdict block appended as a PR comment.

Out of scope

Deferred to later glacial/karst sub-slices (do NOT build these here):

  • Karst gate entirely (dissolution mask, closed depressions/dolines/poljes, ponor→spring conduit,
    caves) — RnD 16 §4–5; a separate landform gate, its 3D cave geometry belongs to the dive [18] slice.
  • Fjord — a U-trough eroded below sea_level then drowned (RnD 16 §3); needs the sea-level /
    coastal interaction (the 5th roadmap slice) → defer to avoid coupling two unshipped landforms.
  • Glaciofluvial sub-forms + outwash export — drumlins, eskers, outwash/zandr plains, kames,
    erratics/boulder-lag, and the non-zero mass-export term they introduce (RnD 16 §3) → later glacial
    sub-slice; this slice ships the trough + terminal/lateral moraine core with export = 0.
  • Roche moutonnée / ice-direction surface texture — a surface material-pattern slot like columnar
    jointing, not macrorelief (RnD 16 §3).
  • Aspect-shift ELA (±70–320 m folded-aspect integer-LUT, RnD 16 §1) — a refinement of the ELA
    field; this slice uses the lat+lapse ELA without the aspect term (kept simple, still pure-integer).
  • Glacial→aeolian loess coupling (outwash/rock-flour as a loess source discharging the aeolian
    [13 §5] reserve, RnD 16 §7) — a read on the aeolian side; aeolian already shipped without it, so the
    coupling is its own follow-up, not this slice.
  • Live glaciation / retreat (Ф2+) — moving moraines, drifting ELA as a warm elevation-delta overlay
    (RnD 16 §6, R29). This slice produces the COLD immutable base only.
  • Refactoring the enable_* flags into a config struct. enable_glacial is added as a 5th trailing
    bool, matching the established house style (enable_patchiness/tectonics/aeolian/volcanic). The
    proliferating-positional-bool mis-wire risk is real, but folding all five into a GenConfig is a
    golden-neutral cross-cutting refactor that would balloon this feature's diff and touch every call
    site — it belongs in its own infra slice. Guard here instead: the OFF-path byte-identity test + the
    ON-path gate-changes-height test catch a mis-wired slot, and the new flag carries a call-site comment
    marking its fixed position in the argument list.

Verification

  • Cloud CIcommit → git push → scripts/ci-report.sh (exit 0 = green). The v2 goldens
    (v2_golden_conserved_*, arm64) plus the new glacial golden vector + conserved-ledger golden are the
    gate; scripts/compile-check.sh PASS is quoted pre-push (rebased-from-worldgen-relief call sites).
  • Golden re-pin (pass 2) — read the glacial-ON pinned height/material and the conserved-ledger value
    off .ci-report/failed.log (v2-golden-arm64 job), then pin.
  • No live-app / render assertion — this slice is numeric-verify only (UI/render out of roadmap scope).

Lane

sim

Passes

2

Depends-on

None (bases on worldgen-relief HEAD c0d7950; independent of the future karst/coastal slices).

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent:BOwned by dev agent Bgolden-touchTouches determinism golden — serialize, single-writerlane:simSimulation subsystem (crates/animata-sim)

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions