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>
209 lines
6.7 KiB
GDScript
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()
|