Files
settled-reach/client/tests/test_step_canvas_request.gd
T
jpmschweitzerandClaude Fable 5 d28d24fd26 feat(ui): step-canvas map component — RTT terrain + stepped zoom (D-255, T-1182)
The two-layer client rebuild per D-255(a)(b)(e), replacing the
_canvas.scale continuous-zoom model with one viewer, one path, all six
rungs:

- step_canvas_protocol.gd: StepCanvasRequest/Response codec against
  the T-1181 wire contract — incl. the discovered png_bytes subtlety
  (rmp_serde without serde_bytes emits a msgpack int-array, not bin;
  decode repacks via PackedByteArray before load_png_from_buffer) and
  the extent-echo rule (read the server-clamped extent, never assume
  the requested one).
- step_canvas/ component: transport (six-rung ladder, cursor-anchored
  scroll steps, edge-scroll/WASD pan with re-request on edge crossing,
  hard reset-to-Global), RTT terrain layer (Image.set_pixel colorize
  per the c1 measured ruling, texture.update reuse on step-cross,
  NEAREST coarse / LINEAR fine per rung), unscaled screen-space
  annotation sibling (courses + settlement markers at literal px),
  in-memory LRU cache (Tier 1; T-1183 layers the disk tiers beneath),
  request lifecycle (pending retry, staleness gate, extent echo).
- Full _canvas.scale retirement in the same change: the zoom-scaled
  canvas model, the _zs compensation family, select_rung /
  MAX_COVERAGE_M / compute_tile_grid, the orbital-mosaic-vs-window
  two-path split, _view_zoom/_canonical_fit_zoom — 10 source files
  deleted; their 14 test suites deleted with them (T-1157 dead-goldens
  rule; replacement visual-capture coverage is re-scoped T-1157).
- Surviving surfaces kept per the ticket: atlas_window_cache.gd's LRU
  shape (the ticket's named file atlas_window_tile_set.gd was the
  retiring orchestrator; the real LRU shape lives in
  atlas_window_cache.gd — cited in step_canvas_cache.gd), overlay
  colors, legend/overlay-bar chrome, AtlasViewer descend geometry.

Determinism boundary per D-255(e): the client interpolates only within
the closed server-supplied input set. 7 new gdUnit suites (164 cases)
incl. a real extent-echo bug caught by its own test during
implementation. Full client suite green (exit 0) with the live-gated
suites running against a worktree server build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 08:53:48 +02:00

209 lines
6.7 KiB
GDScript

## T-1182 tests: step_canvas_request.gd — request lifecycle (cache hit/miss,
## staleness gate, the extent ECHO rule). Live mode is required for
## SimBridge.request_step_canvas() to actually send (test_mode short-circuits
## it), so these tests exercise on_response()/request_now() against a
## directly-constructed StepCanvasRequest node without a live bridge
## connection — request_now() on a cache MISS calls into SimBridge, which is
## a silent no-op in test_mode (SimBridge.test_mode defaults true outside
## SR_LIVE=1), so no live server is needed for these assertions.
class_name TestStepCanvasRequest
extends GdUnitTestSuite
const StepCanvasRequestScript := preload("res://ui/implant/apps/atlas/step_canvas/step_canvas_request.gd")
func test_cache_hit_emits_canvas_ready_synchronously_with_no_pending_state() -> void:
var req = auto_free(StepCanvasRequestScript.new())
add_child(req)
var canvas := {"width": 64, "height": 64}
req.get_cache().put("GJ1c", "District", Vector2i(0, 0), Vector2i(64, 64), canvas)
var received: Array = []
req.canvas_ready.connect(func(c: Dictionary) -> void: received.append(c))
req.request_now("GJ1c", "District", Vector2i(0, 0), Vector2i(64, 64))
assert_int(received.size()).is_equal(1)
assert_that(received[0]).is_equal(canvas)
assert_bool(req.is_pending()).is_false()
func test_cache_miss_sets_pending_true() -> void:
var req = auto_free(StepCanvasRequestScript.new())
add_child(req)
req.request_now("GJ1c", "Chunk", Vector2i(0, 0), Vector2i(64, 64))
assert_bool(req.is_pending()).is_true()
func test_on_response_ignores_a_response_for_a_different_body() -> void:
var req = auto_free(StepCanvasRequestScript.new())
add_child(req)
req.request_now("GJ1c", "District", Vector2i(0, 0), Vector2i(64, 64))
var received: Array = []
req.canvas_ready.connect(func(c: Dictionary) -> void: received.append(c))
req.on_response(
{
"body_id": "SomeOtherBody",
"rung": "District",
"center": Vector2i(0, 0),
"min_wl_m": 0,
"status": "Ready",
"canvas": {"width": 1},
}
)
assert_int(received.size()).is_equal(0)
func test_on_response_ignores_a_response_for_a_different_rung() -> void:
var req = auto_free(StepCanvasRequestScript.new())
add_child(req)
req.request_now("GJ1c", "District", Vector2i(0, 0), Vector2i(64, 64))
var received: Array = []
req.canvas_ready.connect(func(c: Dictionary) -> void: received.append(c))
req.on_response(
{
"body_id": "GJ1c",
"rung": "Chunk", # a different rung answering — must be ignored
"center": Vector2i(0, 0),
"min_wl_m": 0,
"status": "Ready",
"canvas": {"width": 1},
}
)
assert_int(received.size()).is_equal(0)
func test_on_response_ignores_a_stale_center_for_a_fixed_rung() -> void:
var req = auto_free(StepCanvasRequestScript.new())
add_child(req)
req.request_now("GJ1c", "District", Vector2i(0, 0), Vector2i(64, 64))
var received: Array = []
req.canvas_ready.connect(func(c: Dictionary) -> void: received.append(c))
req.on_response(
{
"body_id": "GJ1c",
"rung": "District",
"center": Vector2i(999, 999), # answers a since-panned-away-from center
"min_wl_m": 0,
"status": "Ready",
"canvas": {"width": 1},
}
)
assert_int(received.size()).is_equal(0)
## Global ignores center/extent server-side — a Global response's staleness
## check must NOT compare center at all (an echoed (0,0) sentinel must not
## be rejected as "stale" against whatever was requested).
func test_on_response_global_rung_ignores_center_in_staleness_check() -> void:
var req = auto_free(StepCanvasRequestScript.new())
add_child(req)
req.request_now("GJ1c", "Global", Vector2i.ZERO, Vector2i.ZERO)
var received: Array = []
req.canvas_ready.connect(func(c: Dictionary) -> void: received.append(c))
req.on_response(
{
"body_id": "GJ1c",
"rung": "Global",
"center": Vector2i.ZERO,
"extent": Vector2i.ZERO,
"min_wl_m": 0,
"status": "Ready",
"canvas": {"width": 19_139, "height": 9_569},
}
)
assert_int(received.size()).is_equal(1)
## The extent ECHO rule (T-1181 wire addendum, mandatory): held extent comes
## from the RESPONSE's own echoed extent, never the requested one — a
## server-side clamp can shrink the actual canvas below what was asked for.
func test_on_response_holds_the_echoed_extent_not_the_requested_one() -> void:
var req = auto_free(StepCanvasRequestScript.new())
add_child(req)
req.request_now("GJ1c", "Chunk", Vector2i(0, 0), Vector2i(9_999, 9_999))
req.on_response(
{
"body_id": "GJ1c",
"rung": "Chunk",
"center": Vector2i(0, 0),
"extent": Vector2i(3_840, 2_160), # server-clamped echo, smaller than requested
"min_wl_m": 0,
"status": "Ready",
"canvas": {"width": 3_840, "height": 2_160},
}
)
assert_that(req.get_held_extent()).is_equal(Vector2i(3_840, 2_160))
## Global's held extent comes from the canvas's own width/height (the wire
## extent echo is a fixed (0,0) sentinel for that rung — nothing to read).
func test_on_response_global_held_extent_derives_from_canvas_dimensions() -> void:
var req = auto_free(StepCanvasRequestScript.new())
add_child(req)
req.request_now("GJ1c", "Global", Vector2i.ZERO, Vector2i.ZERO)
req.on_response(
{
"body_id": "GJ1c",
"rung": "Global",
"center": Vector2i.ZERO,
"extent": Vector2i.ZERO,
"min_wl_m": 0,
"status": "Ready",
"canvas": {"width": 19_139, "height": 9_569},
}
)
assert_that(req.get_held_extent()).is_equal(Vector2i(19_139, 9_569))
func test_on_response_ready_with_null_canvas_retries_rather_than_adopting() -> void:
var req = auto_free(StepCanvasRequestScript.new())
add_child(req)
req.request_now("GJ1c", "District", Vector2i(0, 0), Vector2i(64, 64))
var received: Array = []
req.canvas_ready.connect(func(c: Dictionary) -> void: received.append(c))
req.on_response(
{
"body_id": "GJ1c",
"rung": "District",
"center": Vector2i(0, 0),
"min_wl_m": 0,
"status": "Ready",
"canvas": null,
}
)
assert_int(received.size()).is_equal(0)
assert_bool(req.is_pending()).is_true()
func test_on_response_not_found_gives_up_immediately() -> void:
var req = auto_free(StepCanvasRequestScript.new())
add_child(req)
req.request_now("GJ1c", "Chunk", Vector2i(0, 0), Vector2i(64, 64))
req.on_response({"body_id": "GJ1c", "rung": "Chunk", "status": "NotFound"})
assert_bool(req.is_pending()).is_false()
func test_on_response_stores_a_fresh_ready_canvas_in_the_cache() -> void:
var req = auto_free(StepCanvasRequestScript.new())
add_child(req)
req.request_now("GJ1c", "District", Vector2i(5, 5), Vector2i(64, 64))
req.on_response(
{
"body_id": "GJ1c",
"rung": "District",
"center": Vector2i(5, 5),
"extent": Vector2i(64, 64),
"min_wl_m": 0,
"status": "Ready",
"canvas": {"width": 64, "height": 64},
}
)
assert_bool(req.get_cache().has("GJ1c", "District", Vector2i(5, 5), Vector2i(64, 64))).is_true()