feat(simulation): lake_margin_q depth band — lake shorelines gain gradient vocabulary (T-1188)

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>
This commit is contained in:
2026-07-25 13:08:44 +02:00
co-authored by Claude Fable 5
parent 07f0fa307f
commit dec5b0bc43
12 changed files with 218 additions and 33 deletions
@@ -34,10 +34,17 @@ class_name StepCanvasProtocol
## (duplicated here per browse_protocol.gd's own "genuinely standalone"
## precedent, not shared via a Callable).
## EncodedStepCanvas — a map: {width, height, morphology, elev_q, temp_dc,
## moisture_q, vegetation, settlement_id, glaciation, flooded_q, courses,
## cliffs}. The six PNG-per-field dense planes (morphology/elev_q/
## moisture_q/vegetation/glaciation/flooded_q) are each a map
## {"png_bytes": [...]} — png_bytes is a Rust `Vec<u8>` with NO
## moisture_q, vegetation, settlement_id, lake_margin_q, glaciation,
## flooded_q, courses, cliffs}. `lake_margin_q` (T-1188) is a MessagePack
## map key that did not exist before this codec version — an older server
## build's payload simply omits it (`d.get("lake_margin_q")` below returns
## null, decode_png_field() then returns an empty PackedByteArray, the
## same "field absent -> draws as the colorize fallback" posture every
## other optional plane on this wire already has); a client this new
## talking to that old a server is not a supported combination anyway
## (D-192 co-ship). The seven PNG-per-field dense planes (morphology/
## elev_q/moisture_q/vegetation/lake_margin_q/glaciation/flooded_q) are
## each a map {"png_bytes": [...]} — png_bytes is a Rust `Vec<u8>` with NO
## serde_bytes annotation anywhere in this codebase (confirmed: grep for
## serde_bytes across server/src returns nothing), so serde's blanket
## Vec<T> impl serializes it via serialize_seq — a msgpack ARRAY of
@@ -173,6 +180,7 @@ static func _decode_encoded_canvas(raw: Variant) -> Variant:
if settlement_id_raw is Dictionary
else []
),
"lake_margin_q": decode_png_field(d.get("lake_margin_q")),
"glaciation": decode_png_field(d.get("glaciation")),
"flooded_q": decode_png_field(d.get("flooded_q")),
"courses": d.get("courses", []),