fix(client): the deep rungs were flat because relief_q fell off the wire (T-1213)
`relief_q` is the one field with signal below District — elev_q's 80 m steps
quantise sub-district detail away, which is precisely why relief_q was invented.
The server has encoded it since 5eb394b36 and the terrain layer has asked for it
by name ever since. step_canvas_protocol.gd's decode dictionary never listed the
key, so `canvas.get("relief_q")` was always null and the plane arrived nowhere.
The server half of that change landed; the protocol half did not.
That is the whole reason Region and below rendered as a flat wash. Measured plane
variety at District before the fix:
{morphology: 1, elev_q: 11, relief_q: 0, moisture_q: 25, vegetation: 3}
A 0 there means ABSENT, not constant — a distinction the capture could not make
until this commit adds it, and the reason two earlier sessions read the flatness
as a missing generator rather than a missing key.
Also spends the field properly. It drove a stipple PROBABILITY only, so a ridge
and a plain differed in dot density, which at one pixel per cell reads as noise;
and `_ruggedness()` took absf(relief_q - 50), discarding the sign the server
deliberately preserved ("a hollow and a rise are different ground... the reverse
is not recoverable"). Relief now shades continuously and signed — rises lighten,
hollows darken — UNDER the stipple rather than instead of it. Ruggedness
(unsigned) and elevation (signed) are different questions and both are worth
asking.
Ladder, before -> after (tooling/atlas-flatness, lum p1-p99):
Global 145.69 -> 145.69 unchanged, correct: relief_q is flat 50 at
orbital rungs by construction
Region 33.59 -> 71.01 2.1x
District 13.72 -> 77.01 5.6x
Quarter 11.01 -> 42.56 3.9x
Structure retention Global->Quarter: 7.6% -> 29%.
NOT finished, and the ticket says so: Region now reads as heavy speckle, because
ruggedness is real data instead of an elev_q-gradient fallback and far more cells
earn a mark than the T-1194 tuning assumed; District reads as soft blobby relief,
form without directionality. Both are grammar/tuning follow-ups on a channel that
finally carries signal.
0.4.9 is a REQUIRED bump. The disk cache stores the DECODED canvas, so every
earlier entry physically lacks the field and would keep rendering flat against a
build that reads it — the first bump in this series where a warm cache is wrong
about CONTENT, not merely stale. tooling/canvas_sources.py gains
step_canvas_protocol.gd for the same reason: it decides which planes exist, the
cache stores its output, and the T-1242 gate would not have flagged this fix
while the registry stopped at ui/.../step_canvas/.
Regression cover: every protocol test passed throughout the weeks the plane was
missing, because each asserted a field it already knew about and none asserted
the SET. There is now a test walking all eight dense planes of EncodedStepCanvas,
verified by disabling the fix and watching it fail by name.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -176,6 +176,55 @@ func test_decode_encoded_canvas_passes_through_temp_dc_and_settlement_id_as_arra
|
||||
assert_that(canvas["settlement_id"]).is_equal([0, 7])
|
||||
|
||||
|
||||
## T-1213 REGRESSION. `relief_q` shipped server-side, the terrain layer asked for
|
||||
## it by name, and this decode never listed the key — so the plane the deep rungs
|
||||
## exist to draw arrived nowhere for weeks, and District rendered as a flat wash.
|
||||
## Every test here passed throughout, because each asserted a field it already
|
||||
## knew about; none asserted the SET.
|
||||
##
|
||||
## So this one is written against the wire contract rather than against a list of
|
||||
## fields someone remembered: every dense plane the server encodes must survive
|
||||
## the decode. Adding a plane to EncodedStepCanvas without adding it here now
|
||||
## fails loudly instead of rendering as "that rung is just flat".
|
||||
func test_decode_encoded_canvas_carries_every_dense_plane() -> void:
|
||||
# The eight PNG-per-field planes of EncodedStepCanvas (step_canvas.rs).
|
||||
var planes := [
|
||||
"morphology",
|
||||
"elev_q",
|
||||
"moisture_q",
|
||||
"vegetation",
|
||||
"lake_margin_q",
|
||||
"relief_q",
|
||||
"glaciation",
|
||||
"flooded_q",
|
||||
]
|
||||
var canvas_in := {"width": 1, "height": 1, "temp_dc": {"values": [0]}}
|
||||
for plane: String in planes:
|
||||
# A one-pixel L8 PNG payload in the array-of-ints shape the wire uses.
|
||||
canvas_in[plane] = {"png_bytes": [137, 80, 78, 71]}
|
||||
var raw := {
|
||||
"body_id": "GJ380c",
|
||||
"rung": "District",
|
||||
"status": "Ready",
|
||||
"canvas": canvas_in,
|
||||
}
|
||||
|
||||
var decoded = Protocol.step_canvas_response_from_raw(raw)
|
||||
var canvas: Dictionary = decoded["canvas"]
|
||||
for plane: String in planes:
|
||||
assert_bool(canvas.has(plane)).override_failure_message(
|
||||
(
|
||||
"decoded canvas is missing the '%s' plane — the server encodes it and "
|
||||
+ "the renderer reads it, so a missing key here renders as a flat rung "
|
||||
+ "rather than as an error (T-1213)"
|
||||
)
|
||||
% plane
|
||||
).is_true()
|
||||
assert_that(canvas[plane]).override_failure_message(
|
||||
"plane '%s' decoded to null rather than bytes" % plane
|
||||
).is_not_null()
|
||||
|
||||
|
||||
func test_decode_encoded_canvas_passes_through_courses_and_cliffs_unshaped() -> void:
|
||||
var courses := [{"edge_id": 1, "class": 2, "points": [[0, 0], [100, 100]], "terminus": "Mouth"}]
|
||||
var cliffs := [{"point": [5, 5], "channel_depth_dm": 10, "cliff_edge": true}]
|
||||
|
||||
@@ -335,7 +335,7 @@ func _log_atlas_view_transform(tree_root: Node, scenario_name: String) -> void:
|
||||
(
|
||||
"visual_capture: view-transform[%s] rung=%s world_center=%s held_extent=%s "
|
||||
+ "canvas_position=%s canvas_scale=%s footprint_px=%s canvas_cells=%dx%d "
|
||||
+ "courses=%d runs=%d longest=%.1fpx drawn=%d settlements=%d"
|
||||
+ "courses=%d runs=%d longest=%.1fpx drawn=%d settlements=%d planes=%s"
|
||||
)
|
||||
% [
|
||||
scenario_name,
|
||||
@@ -352,6 +352,7 @@ func _log_atlas_view_transform(tree_root: Node, scenario_name: String) -> void:
|
||||
float(summary.get("longest_run_px", 0.0)),
|
||||
int(summary.get("drawn_course_count", 0)),
|
||||
int(summary.get("settlement_count", 0)),
|
||||
str(summary.get("plane_variety", {})),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user