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>
247 lines
9.5 KiB
GDScript
247 lines
9.5 KiB
GDScript
## 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)
|