fix(simulation): hemisphere-correct latitude + lake depth shading (T-1186, T-1188) #206

Closed
jpmschweitzer wants to merge 0 commits from atlas-latitude-fix into main
Owner

Summary

The server half of the Atlas visual-defect batch (the frames Jeroen flagged).

T-1186 — climate now respects latitude on both hemispheres. region_centre_latitude_deg was pole-anchored (row 0 = north pole, lat_frac clamped [0,1]) while the derive core keys equator-anchored SIGNED region rows — so every northern-hemisphere region clamped to +90° (polar everywhere) and the south read as compressed northern tropics. Now mirrors the derive core's exact inverse mapping. The ticket's own datapoint flips precisely as predicted: GJ338Bd region (136,43) baseline +13.94°/26.8°C → −76.06°/10.3°C; the 76°S district cell derives Lake/2.07°C/Light glaciation instead of an 18°C ice-free lake. Goldens regenerated (stable on rerun); two unit tests repositioned (their probe coordinates meant different places under the old convention). Convention audit: step-canvas rung-0 already used the correct signed convention; layer_proxy's pole-anchored pseudo-grid stays deferred to T-1181's rung-0 rebuild per D-256(f) — unification proposal on record there.

T-1188 — lake shorelines gain a depth gradient. Hypothesis (a) (broken positional refinement) disproven first: shoreline-crossing sweeps at 2048/512/128m + a 10m fine sweep all land on the same continuous crossing. Hypothesis (b) confirmed: every coastal-transition morphology gate keys ocean_fraction_q, definitionally 0 in a lake basin — lake edges had no gradient vocabulary. New DistrictProfile.lake_margin_q (0-100 settled-hydrology depth band from the same bilinear pair the lake test already samples; ceiling calibrated against GJ338Bd's 5,043-cell depth survey), threaded through both derive paths → EncodedStepCanvas → client protocol → terrain-layer plane → colorize (Lake cells shade by depth band, not bedrock elev_q). project.yaml 0.4.0 → 0.4.1 — the new wire field invalidates the client disk cache via its version tag (T-1183's D-192 mechanism).

Verification

Full double gate green on push: client 3,520/3,520 (includes SR_LIVE e2e), server 1,943 lib + believability/derivation/cascade goldens + step_canvas_acceptance_gate 5/5 (lossless round-trip + cache-hit==cache-miss with the new field), clippy -D warnings, deny. Merge seam with PR #205 taken cleanly (terrain-layer auto-merge verified by targeted suite re-runs: colorize 18/18, viewer 76/76). Reviews next; the District-frame eyeball (depth-shaded lake on corrected climate) runs on the final tip — merge waits on it.

🤖 Generated with Claude Code

## Summary The server half of the Atlas visual-defect batch (the frames Jeroen flagged). **T-1186 — climate now respects latitude on both hemispheres.** `region_centre_latitude_deg` was pole-anchored (row 0 = north pole, lat_frac clamped [0,1]) while the derive core keys equator-anchored SIGNED region rows — so every northern-hemisphere region clamped to +90° (polar everywhere) and the south read as compressed northern tropics. Now mirrors the derive core's exact inverse mapping. The ticket's own datapoint flips precisely as predicted: GJ338Bd region (136,43) baseline +13.94°/26.8°C → −76.06°/10.3°C; the 76°S district cell derives Lake/2.07°C/Light glaciation instead of an 18°C ice-free lake. Goldens regenerated (stable on rerun); two unit tests repositioned (their probe coordinates meant different places under the old convention). Convention audit: step-canvas rung-0 already used the correct signed convention; layer_proxy's pole-anchored pseudo-grid stays deferred to T-1181's rung-0 rebuild per D-256(f) — unification proposal on record there. **T-1188 — lake shorelines gain a depth gradient.** Hypothesis (a) (broken positional refinement) disproven first: shoreline-crossing sweeps at 2048/512/128m + a 10m fine sweep all land on the same continuous crossing. Hypothesis (b) confirmed: every coastal-transition morphology gate keys ocean_fraction_q, definitionally 0 in a lake basin — lake edges had no gradient vocabulary. New `DistrictProfile.lake_margin_q` (0-100 settled-hydrology depth band from the same bilinear pair the lake test already samples; ceiling calibrated against GJ338Bd's 5,043-cell depth survey), threaded through both derive paths → EncodedStepCanvas → client protocol → terrain-layer plane → colorize (Lake cells shade by depth band, not bedrock elev_q). **project.yaml 0.4.0 → 0.4.1** — the new wire field invalidates the client disk cache via its version tag (T-1183's D-192 mechanism). ## Verification Full double gate green on push: client 3,520/3,520 (includes SR_LIVE e2e), server 1,943 lib + believability/derivation/cascade goldens + step_canvas_acceptance_gate 5/5 (lossless round-trip + cache-hit==cache-miss with the new field), clippy -D warnings, deny. Merge seam with PR #205 taken cleanly (terrain-layer auto-merge verified by targeted suite re-runs: colorize 18/18, viewer 76/76). Reviews next; the District-frame eyeball (depth-shaded lake on corrected climate) runs on the final tip — merge waits on it. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jpmschweitzer added 4 commits 2026-07-25 13:38:19 +02:00
region_centre_latitude_deg mapped row 0 to the north pole with lat_frac
clamped [0,1] while the derive core floor-divides equator-anchored signed
world metres — every northern-hemisphere region clamped to +90 (polar
everywhere) and the southern hemisphere read as compressed northern
tropics. Now mirrors the derive core's exact inverse mapping (lat_frac =
centre_y/meridian clamped [-0.5,0.5], lat = -frac*180). The ticket's
in-the-wild datapoint flips as predicted: GJ338Bd region (136,43), old
baseline +13.94N/26.8C -> new -76.06S/10.3C; the 76S district cell now
derives Lake/2.07C/Light glaciation. Goldens regenerated (believability +
window derivation, stable on rerun); two region_profile unit tests
repositioned — their probe coordinates meant different places under the
old convention. step_canvas rung-0 already used the correct signed
convention; layer_proxy's pole-anchored pseudo-grid stays deferred to
T-1181's rung-0 rebuild per D-256(f).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Lake edges rendered as hard step-edges while ocean coasts got multi-tone
transition bands: every coastal-transition morphology gate keys on
ocean_fraction_q, definitionally 0 inside a lake basin (hypothesis (b)
of the ticket; (a) disproven first — a shoreline-crossing sweep at
2048/512/128m plus a 10m fine sweep all land on the same continuous
crossing, so positional refinement was never broken). New
DistrictProfile.lake_margin_q (0-100 settled-hydrology depth band, from
the same bilinear filled/elevation pair the lake test already samples;
ceiling calibrated just above the observed p90 depth on GJ338Bd's 5,043
flooded cells), threaded through both derive paths onto
EncodedStepCanvas (serde-default for shape tolerance) and down the
client: protocol decode, terrain-layer plane, colorize shades Lake cells
by depth band instead of elev_q (bedrock-under-water, the wrong signal).
project.yaml 0.4.0 -> 0.4.1: the new wire field must invalidate the
client disk cache via its version tag (T-1183's D-192 mechanism).
Acceptance gates green with the new field (lossless round-trip,
cache-hit==cache-miss, every rung).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts:
#	CHANGELOG.md
jpmschweitzer added 1 commit 2026-07-25 13:45:47 +02:00
Hoshe's review finding: the build_step_canvas row-space convention block
still described region_centre_latitude_deg as pole-anchored/bugged and
said 'T-1186 stays open' — both false after this PR's own first commit.
Rewritten to reflect the fixed state; the deliberate signed-convention
rationale and the layer_proxy deferral (still true, still T-1181's scope)
are preserved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Author
Owner

Review: atlas-latitude-fix -> main (type: code)

Reviewers: Hoshe (code quality) + Tyre (architecture).

Tyre (architecture): APPROVE

No comments. Verification highlights worth the record: the latitude fix is structurally a single-point correction (one production call path, no parallel formula — verified by caller graph); the golden diff shows exactly the D-256-predicted blast radius (a south-of-equator probe flipping −4.3 °C polar-misread → +12.3 °C correctly-read while morphology/elevation/ocean-fraction topology stays byte-identical across all 20 probes); the layer_proxy deferral's reader-isolation claim holds (sole consumer is the stale body-view overlay); lake_margin_q is D-255(c)/(e)/(f)-conformant and the acceptance gate covers the new field for free via whole-struct equality; the version bump targets the one persistent store needing invalidation. The LAKE_MARGIN_DEPTH_CEILING single-body calibration was examined and cleared: it operates in the per-body-normalized elevation-fraction domain (partially auto-scales), degrades safely, and is isolated from the believability metric — the right rigor for a cosmetic shading parameter.

Hoshe (code quality): REQUEST_CHANGES

# File Issue Resolution
1 step_canvas.rs:899-926 Stale doc block on build_step_canvas still described region_centre_latitude_deg as pole-anchored/bugged and stated "T-1186 stays open" — both false after this PR's own first commit; would mislead the next reader Fixed — block rewritten to the resolved state, preserving the still-true signed-convention rationale and the layer_proxy/T-1181 deferral (b39dd4995)

Hoshe's verified-clean list: the latitude inverse mapping matches the derive core bit-for-bit (including the +0.5 row-centre offset and clamp bounds); the two repositioned unit tests preserve their assertion intent; believability golden churn is small-magnitude and direction-plausible, not a rewrite; lake_margin_q threading, clamps, no-hydrology default, serde-default decode, and the Lake-only colorize gating are all correct; the #205 merge seam is coherent.

Verdict: CHANGES REQUESTED -> addressed in b39dd4995; the District/Global eyeball on the final tip gates the merge.

# Review: atlas-latitude-fix -> main (type: code) Reviewers: Hoshe (code quality) + Tyre (architecture). ### Tyre (architecture): APPROVE No comments. Verification highlights worth the record: the latitude fix is structurally a single-point correction (one production call path, no parallel formula — verified by caller graph); the golden diff shows exactly the D-256-predicted blast radius (a south-of-equator probe flipping −4.3 °C polar-misread → +12.3 °C correctly-read while morphology/elevation/ocean-fraction topology stays byte-identical across all 20 probes); the layer_proxy deferral's reader-isolation claim holds (sole consumer is the stale body-view overlay); lake_margin_q is D-255(c)/(e)/(f)-conformant and the acceptance gate covers the new field for free via whole-struct equality; the version bump targets the one persistent store needing invalidation. The LAKE_MARGIN_DEPTH_CEILING single-body calibration was examined and cleared: it operates in the per-body-normalized elevation-fraction domain (partially auto-scales), degrades safely, and is isolated from the believability metric — the right rigor for a cosmetic shading parameter. ### Hoshe (code quality): REQUEST_CHANGES | # | File | Issue | Resolution | |---|------|-------|------------| | 1 | step_canvas.rs:899-926 | Stale doc block on build_step_canvas still described region_centre_latitude_deg as pole-anchored/bugged and stated "T-1186 stays open" — both false after this PR's own first commit; would mislead the next reader | **Fixed** — block rewritten to the resolved state, preserving the still-true signed-convention rationale and the layer_proxy/T-1181 deferral (`b39dd4995`) | Hoshe's verified-clean list: the latitude inverse mapping matches the derive core bit-for-bit (including the +0.5 row-centre offset and clamp bounds); the two repositioned unit tests preserve their assertion intent; believability golden churn is small-magnitude and direction-plausible, not a rewrite; lake_margin_q threading, clamps, no-hydrology default, serde-default decode, and the Lake-only colorize gating are all correct; the #205 merge seam is coherent. ### Verdict: CHANGES REQUESTED -> addressed in `b39dd4995`; the District/Global eyeball on the final tip gates the merge.
jpmschweitzer added 2 commits 2026-07-25 14:25:51 +02:00
The PR #206 eyeball caught what every numeric gate passed: the depth
signal was visually flat on both test lakes (GJ1c lmq=0 across the whole
basin; GJ338Bd 0-13 of 100). Two compounding causes: a fixed absolute
ceiling (one body's p90 cell depth) compressing skewed depth
distributions into single digits, and heightmap-pitch depth variation
being sub-texel-tiny within most basins. lake_margin_q is now
depth / the basin's own maximum settled depth: HydrologyResult grows
basin_max_depth_scaled (computed in solve() from existing basin_cells
membership, broadcast per basin), threaded through
HydrologySample.basin_max_depth, normalized in lake_from_hydrology_at
with a degenerate-basin epsilon guard (a genuinely uniform pond shades
flat — honest, not forced). Lake EXISTENCE (filled > original) is
untouched — only tone changes. Measured at district spacing:
GJ1c min=0 p50=33 max=84; GJ338Bd min=8 p50=38 max=70 — full-range
shore-to-deep ramps on both. No perceptual curve added: the linear
per-basin ramp is already well-quartiled. project.yaml 0.4.1 -> 0.4.2
(0.4.1-tagged canvases carrying flat-lmq semantics reached real disk
caches during eyeball runs and must miss). Acceptance gates green;
zero golden churn (lake_margin_q not captured by either golden shape).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Author
Owner

Eyeball round 1 → fix round 2 (T-1188 depth gradient)

The first eyeball FAILED the lake-depth shading despite every numeric gate passing: the mechanism worked end-to-end but the signal was visually flat — GJ1c's lake at lake_margin_q=0 across the entire basin, GJ338Bd using ~10% of the range. Root cause: a fixed absolute depth ceiling (one body's p90 cell depth) compressing skewed depth distributions, compounded by heightmap-pitch depth variation being sub-texel-tiny within most basins.

Fix (39f0fd8c5 + style 4a9567c66): per-basin normalizationlake_margin_q = depth / the basin's own maximum settled depth. HydrologyResult grows basin_max_depth_scaled (computed in solve() from existing basin membership, broadcast per basin), threaded through HydrologySample, normalized in lake_from_hydrology_at with a degenerate-basin epsilon guard (a genuinely uniform pond shades flat — honest, not forced). Lake EXISTENCE is untouched; only tone changes. Measured distributions at district spacing: GJ1c min=0/p50=33/max=84, GJ338Bd min=8/p50=38/max=70 — full-range shore→deep ramps on both, vs round 1's 0 and 0–13. No perceptual curve: linear is already well-quartiled. project.yaml → 0.4.2 (0.4.1-tagged flat-semantic canvases reached real disk caches during eyeball runs and must miss). Acceptance gates green; zero golden churn.

Also adjudicated from eyeball round 1:

  • The "76°S lake still not cold-looking" finding is retracted as an expectation error: grade-1 Light glaciation renders at alpha 0.0 by design (erosion signatures, not ice cover — the ported reference implementation's own documented rule), and a 10.3 °C baseline legitimately reads green. The temperature/glaciation data flip was already proven live.
  • T-1186's both-poles ice banding: PASS by color decomposition (north edge 100% grade-2, south edge climbing to 28% — asymmetry noted; the re-eyeball adds a second body's Global as a symmetry check).

Re-eyeball on 4a9567c66 gates the merge.

# Eyeball round 1 → fix round 2 (T-1188 depth gradient) The first eyeball FAILED the lake-depth shading despite every numeric gate passing: the mechanism worked end-to-end but the signal was visually flat — GJ1c's lake at `lake_margin_q=0` across the entire basin, GJ338Bd using ~10% of the range. Root cause: a fixed absolute depth ceiling (one body's p90 cell depth) compressing skewed depth distributions, compounded by heightmap-pitch depth variation being sub-texel-tiny within most basins. **Fix (`39f0fd8c5` + style `4a9567c66`): per-basin normalization** — `lake_margin_q = depth / the basin's own maximum settled depth`. `HydrologyResult` grows `basin_max_depth_scaled` (computed in `solve()` from existing basin membership, broadcast per basin), threaded through `HydrologySample`, normalized in `lake_from_hydrology_at` with a degenerate-basin epsilon guard (a genuinely uniform pond shades flat — honest, not forced). Lake EXISTENCE is untouched; only tone changes. Measured distributions at district spacing: GJ1c min=0/p50=33/max=84, GJ338Bd min=8/p50=38/max=70 — full-range shore→deep ramps on both, vs round 1's 0 and 0–13. No perceptual curve: linear is already well-quartiled. `project.yaml` → 0.4.2 (0.4.1-tagged flat-semantic canvases reached real disk caches during eyeball runs and must miss). Acceptance gates green; zero golden churn. **Also adjudicated from eyeball round 1:** - The "76°S lake still not cold-looking" finding is **retracted as an expectation error**: grade-1 Light glaciation renders at alpha 0.0 *by design* (erosion signatures, not ice cover — the ported reference implementation's own documented rule), and a 10.3 °C baseline legitimately reads green. The temperature/glaciation data flip was already proven live. - T-1186's both-poles ice banding: PASS by color decomposition (north edge 100% grade-2, south edge climbing to 28% — asymmetry noted; the re-eyeball adds a second body's Global as a symmetry check). Re-eyeball on `4a9567c66` gates the merge.
Author
Owner

Re-eyeball round 2 — PASS (merge gate cleared)

Live run on 4a9567c66, rebuilt server, recalibrated criteria.

  • GJ1c district lake: 67 distinct color bands, radial shore→center darkening confirmed by depth heatmap + centroid-distance monotonicity — categorical improvement over round 1's flat single tone.
  • GJ338Bd district lake: 98 bands, strictly monotone radial gradient (centroid-distance thirds 54.3 → 83.5 → 112.2).
  • GJ338Bd Global symmetry check: polar banding present at both edges; asymmetry runs the OPPOSITE direction from GJ1c at ~3× smaller magnitude — the latitude fix has no systematic polar bias; GJ1c's north-dominance is genuine per-body variation. Not ticket-worthy.
  • Version tag: stale 0.4.1/0.4.0 entries confirmed on disk going in; every touched key now 0.4.2 with fresh timestamps, untouched keys stayed stale — per-key miss-and-refetch, no blanket flush, nothing stale ever decoded.

Captures: .cache/screenshots/t1186-88-eyeball-r2/. Input-path fidelity label as established (in-process navigation; unchanged by this PR). Merging.

# Re-eyeball round 2 — PASS (merge gate cleared) Live run on `4a9567c66`, rebuilt server, recalibrated criteria. - **GJ1c district lake:** 67 distinct color bands, radial shore→center darkening confirmed by depth heatmap + centroid-distance monotonicity — categorical improvement over round 1's flat single tone. - **GJ338Bd district lake:** 98 bands, strictly monotone radial gradient (centroid-distance thirds 54.3 → 83.5 → 112.2). - **GJ338Bd Global symmetry check:** polar banding present at both edges; asymmetry runs the OPPOSITE direction from GJ1c at ~3× smaller magnitude — the latitude fix has no systematic polar bias; GJ1c's north-dominance is genuine per-body variation. Not ticket-worthy. - **Version tag:** stale 0.4.1/0.4.0 entries confirmed on disk going in; every touched key now 0.4.2 with fresh timestamps, untouched keys stayed stale — per-key miss-and-refetch, no blanket flush, nothing stale ever decoded. Captures: `.cache/screenshots/t1186-88-eyeball-r2/`. Input-path fidelity label as established (in-process navigation; unchanged by this PR). Merging.
jpmschweitzer closed this pull request 2026-07-25 14:45:32 +02:00

Pull request closed

This pull request cannot be reopened because the branch was deleted.
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: jpmschweitzer/settled-reach#206