Files
settled-reach/client/tests/test_step_canvas_protocol.gd
T
jpmschweitzerandClaude Fable 5 76d82a6a51 fix(ui): PR #203 review round — total retirement + bin decode + wired reset
Tyre finding: the orphaned AtlasViewer cluster is now actually deleted
(atlas_viewer, atlas_marker_overlay, atlas_descend_geometry,
atlas_legend_panel — a sixth orphan found beyond the review list —
atlas_generation_proxy, atlas_generation_state; ~2,497 lines), with
reachability re-verified across preload/class_name/res:// strings,
every .tscn, and the standalone companion app. Test suites triaged,
not blanket-deleted: 5 pure AtlasOverlayColors tests relocated into
test_atlas_window_colors, the live type-identity regression guard
relocated into test_step_canvas_viewer, dead coverage deleted. A real
harness gap surfaced during diligence and RULED, not patched:
visual_scenarios/visual_capture golden shots call retired
continuous-zoom API — no shim (would resurrect what D-255 kills);
inventory recorded on re-scoped T-1157 (gate-invisible, manual
targets only).

Hoshe finding 1: decode_png_field now detects the [Error,
PackedByteArray] bin-shape from messagepack.gd explicitly — a genuine
msgpack bin payload decodes correctly instead of silently collapsing
to [0,0]; test built from a real round-tripped bin decode.

Hoshe finding 2: the hard zoom-out reset is wired — ascend at rung 0
with a drifted view triggers _reset_to_global (the restored HARD
condition), behavioral tests through the real input path.

Notes folded: refloat + edge-scroll test coverage, legend smoke suite,
Vector2i narrowing-safety comment with computed headroom.

gdlint clean on touched files; full client suite 3364/3364.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 09:32:52 +02:00

247 lines
9.5 KiB
GDScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## T-1182 tests: StepCanvasRequest/StepCanvasResponse wire codec
## (step_canvas_protocol.gd, delegated via protocol.gd) — the D-255(c)
## tagged-envelope carrier. Mirrors test_atlas_data_delivery.gd's own
## encode/decode round-trip + decode_inbound classification conventions.
class_name TestStepCanvasProtocol
extends GdUnitTestSuite
# =============================================================================
# Encode
# =============================================================================
func test_encode_step_canvas_request_carries_discriminator_and_fields() -> void:
var bytes := Protocol.encode_step_canvas_request(
"GJ380c", "District", Vector2i(10, 20), Vector2i(64, 64), 512
)
assert_int(bytes.size()).is_greater(0)
var decoded = Messagepack.decode(bytes)
assert_that(decoded.status == null).is_true()
var raw: Dictionary = decoded.value
assert_bool(raw.get("step_canvas")).is_true()
assert_str(raw.get("body_id")).is_equal("GJ380c")
assert_str(raw.get("rung")).is_equal("District")
assert_that(raw.get("center")).is_equal([10, 20])
assert_that(raw.get("extent")).is_equal([64, 64])
assert_int(raw.get("min_wl_m")).is_equal(512)
## The rung is sent as a bare string (rmp_serde's unit-variant convention),
## never a raw integer — a Global request must carry the literal tag
## "Global", matching step_canvas.rs's own StepCanvasRung enum encoding.
func test_encode_step_canvas_request_global_rung_is_bare_string() -> void:
var bytes := Protocol.encode_step_canvas_request(
"GJ380c", "Global", Vector2i.ZERO, Vector2i.ZERO, 0
)
var decoded = Messagepack.decode(bytes)
assert_str(decoded.value.get("rung")).is_equal("Global")
# =============================================================================
# Decode — status variants
# =============================================================================
func test_step_canvas_response_from_raw_decodes_ready_status() -> void:
var raw := {
"body_id": "GJ380c",
"rung": "District",
"center": [10, 20],
"extent": [64, 64],
"min_wl_m": 0,
"status": "Ready",
"canvas": null,
}
var decoded = Protocol.step_canvas_response_from_raw(raw)
assert_str(decoded["status"]).is_equal("Ready")
assert_str(decoded["error"]).is_equal("")
assert_str(decoded["rung"]).is_equal("District")
assert_that(decoded["center"]).is_equal(Vector2i(10, 20))
assert_that(decoded["extent"]).is_equal(Vector2i(64, 64))
func test_step_canvas_response_from_raw_decodes_pending_status() -> void:
var raw := {"body_id": "GJ380c", "rung": "Chunk", "status": "Pending"}
var decoded = Protocol.step_canvas_response_from_raw(raw)
assert_str(decoded["status"]).is_equal("Pending")
assert_that(decoded["canvas"]).is_null()
func test_step_canvas_response_from_raw_decodes_error_status() -> void:
var raw := {
"body_id": "GJ380c", "rung": "Region", "status": {"Error": "no heightmap"}
}
var decoded = Protocol.step_canvas_response_from_raw(raw)
assert_str(decoded["status"]).is_equal("Error")
assert_str(decoded["error"]).is_equal("no heightmap")
## Not a step-canvas response (no "rung" key) -> null, so decode_inbound's
## dispatch doesn't misroute a plain AtlasLayerResponse here.
func test_step_canvas_response_from_raw_returns_null_without_rung_key() -> void:
var raw := {"body_id": "GJ380c", "status": "Ready"}
assert_that(Protocol.step_canvas_response_from_raw(raw)).is_null()
# =============================================================================
# decode_inbound classification — the "rung" discriminator must win BEFORE
# the generic "status"-only AtlasLayerResponse fallback (a step-canvas
# response also carries "status").
# =============================================================================
func test_decode_inbound_classifies_step_canvas() -> void:
var raw := {"body_id": "GJ380c", "rung": "District", "status": "Pending"}
var encoded = Messagepack.encode(raw)
var inbound: Dictionary = Protocol.decode_inbound(encoded.value)
assert_str(inbound["kind"]).is_equal("step_canvas")
func test_decode_inbound_still_classifies_plain_atlas_response() -> void:
var raw := {"body_id": "GJ380c", "status": "Ready"}
var encoded = Messagepack.encode(raw)
var inbound: Dictionary = Protocol.decode_inbound(encoded.value)
assert_str(inbound["kind"]).is_equal("atlas")
# =============================================================================
# EncodedStepCanvas — the PNG-per-field array-of-int wire shape
# (step_canvas.rs's png_bytes: Vec<u8> with NO serde_bytes anywhere in this
# codebase serializes via serialize_seq, a msgpack ARRAY of ints, never a
# `bin` blob — decode_png_field()/_decode_encoded_canvas() must repack that
# Array into a PackedByteArray, not expect messagepack.gd's bin_8/16/32 path).
# =============================================================================
func test_decode_png_field_repacks_array_of_ints_to_packed_byte_array() -> void:
# A 1x1 all-black L8 PNG's real byte stream, as it would arrive already
# decoded off the wire (a plain Array of ints, one per byte) — using real
# PNG magic bytes so a downstream Image.load_png_from_buffer() call
# would also succeed, not just this repack step in isolation.
var png_bytes := PackedByteArray([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])
var as_array: Array = []
for b in png_bytes:
as_array.append(b)
var field_raw := {"png_bytes": as_array}
var result: PackedByteArray = Protocol.step_canvas_response_from_raw(
{
"body_id": "GJ380c",
"rung": "Chunk",
"status": "Ready",
"canvas":
{
"width": 1,
"height": 1,
"morphology": field_raw,
"elev_q": {},
"temp_dc": {"values": []},
"moisture_q": {},
"vegetation": {},
"settlement_id": {"values": []},
"glaciation": {},
"flooded_q": {},
"courses": [],
"cliffs": [],
},
}
)["canvas"]["morphology"]
assert_that(result).is_equal(png_bytes)
func test_decode_encoded_canvas_passes_through_temp_dc_and_settlement_id_as_arrays() -> void:
var raw := {
"body_id": "GJ380c",
"rung": "Chunk",
"status": "Ready",
"canvas":
{
"width": 2,
"height": 1,
"morphology": {},
"elev_q": {},
"temp_dc": {"values": [120, -32768]},
"moisture_q": {},
"vegetation": {},
"settlement_id": {"values": [0, 7]},
"glaciation": {},
"flooded_q": {},
"courses": [],
"cliffs": [],
},
}
var decoded = Protocol.step_canvas_response_from_raw(raw)
var canvas: Dictionary = decoded["canvas"]
assert_that(canvas["temp_dc"]).is_equal([120, -32768])
assert_that(canvas["settlement_id"]).is_equal([0, 7])
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}]
var raw := {
"body_id": "GJ380c",
"rung": "Chunk",
"status": "Ready",
"canvas":
{
"width": 1,
"height": 1,
"morphology": {},
"elev_q": {},
"temp_dc": {"values": []},
"moisture_q": {},
"vegetation": {},
"settlement_id": {"values": []},
"glaciation": {},
"flooded_q": {},
"courses": courses,
"cliffs": cliffs,
},
}
var decoded = Protocol.step_canvas_response_from_raw(raw)
var canvas: Dictionary = decoded["canvas"]
assert_that(canvas["courses"]).is_equal(courses)
assert_that(canvas["cliffs"]).is_equal(cliffs)
func test_decode_png_field_malformed_input_returns_empty_packed_byte_array() -> void:
assert_that(Protocol._scp().decode_png_field(null)).is_equal(PackedByteArray())
assert_that(Protocol._scp().decode_png_field({"png_bytes": "not an array"})).is_equal(
PackedByteArray()
)
## PR #203 review (Hoshe finding 1) — the genuine msgpack `bin_8`/`bin_16`/
## `bin_32` decode path. messagepack.gd's own decoder returns
## `StreamPeerBuffer.get_partial_data()`'s `[Error, PackedByteArray]` pair for
## a bin-typed field — which passes decode_png_field()'s `is Array` guard
## just as readily as the real (today's) array-of-ints shape, so this must be
## exercised with the REAL bin-shaped decode output, not a hand-built
## Dictionary, to prove the fix actually detects it. Built by round-tripping
## a genuine PackedByteArray value through Messagepack.encode()/decode()
## directly (bypassing step_canvas_protocol.gd's own encoder, which never
## sends a PackedByteArray for png_bytes today) — this is exactly the shape
## a future serde_bytes-annotated server would produce.
func test_decode_png_field_handles_the_genuine_bin_type_shape() -> void:
var real_png_bytes := PackedByteArray([0x89, 0x50, 0x4e, 0x47, 1, 2, 3, 4])
var encoded = Messagepack.encode(real_png_bytes)
assert_that(encoded.status).is_null()
var decoded = Messagepack.decode(encoded.value)
assert_that(decoded.status).is_null()
# Sanity: this really is the [error, data] pair shape, not the plain-array
# shape the rest of this suite exercises — if messagepack.gd's own
# behavior ever changes, this assertion fails loudly instead of the test
# silently exercising the wrong code path.
var bin_shape: Variant = decoded.value
assert_bool(bin_shape is Array).is_true()
assert_int((bin_shape as Array).size()).is_equal(2)
assert_bool((bin_shape as Array)[0] is int).is_true()
assert_bool((bin_shape as Array)[1] is PackedByteArray).is_true()
var result: PackedByteArray = Protocol._scp().decode_png_field({"png_bytes": bin_shape})
assert_that(result).override_failure_message(
"bin-shaped png_bytes must decode to the REAL inner bytes, not silently"
+ " collapse to [0, 0] via PackedByteArray([int, PackedByteArray]) coercion"
).is_equal(real_png_bytes)