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>
124 lines
5.5 KiB
GDScript
124 lines
5.5 KiB
GDScript
## T-1182 tests: step_canvas_cache.gd — the client-side in-memory LRU cache
|
|
## for decoded step-canvas payloads. Keyed on (body_id, rung, center, extent,
|
|
## min_wl_m), the exact tuple server/src/atlas/step_canvas.rs's own
|
|
## StepCanvasCache keys on. Mirrors test_atlas_window_cache.gd's own
|
|
## conventions (the surviving LRU shape this file is adapted from).
|
|
class_name TestStepCanvasCache
|
|
extends GdUnitTestSuite
|
|
|
|
const StepCanvasCache := preload("res://ui/implant/apps/atlas/step_canvas/step_canvas_cache.gd")
|
|
|
|
|
|
func test_make_key_distinguishes_body_rung_center_and_extent() -> void:
|
|
var k1 := StepCanvasCache.make_key("GJ1c", "District", Vector2i(10, 20), Vector2i(64, 64))
|
|
var k2 := StepCanvasCache.make_key("GJ1d", "District", Vector2i(10, 20), Vector2i(64, 64))
|
|
var k3 := StepCanvasCache.make_key("GJ1c", "Chunk", Vector2i(10, 20), Vector2i(64, 64))
|
|
var k4 := StepCanvasCache.make_key("GJ1c", "District", Vector2i(11, 20), Vector2i(64, 64))
|
|
var k5 := StepCanvasCache.make_key("GJ1c", "District", Vector2i(10, 20), Vector2i(32, 32))
|
|
assert_str(k1).is_not_equal(k2)
|
|
assert_str(k1).is_not_equal(k3)
|
|
assert_str(k1).is_not_equal(k4)
|
|
assert_str(k1).is_not_equal(k5)
|
|
|
|
|
|
## Global collapses center/extent to a fixed sentinel regardless of what's
|
|
## passed — every Global request for the same body_id must land on ONE slot.
|
|
func test_make_key_global_ignores_center_and_extent() -> void:
|
|
var k1 := StepCanvasCache.make_key("GJ1c", "Global", Vector2i(10, 20), Vector2i(64, 64))
|
|
var k2 := StepCanvasCache.make_key("GJ1c", "Global", Vector2i(999, -999), Vector2i(1, 1))
|
|
assert_str(k1).is_equal(k2)
|
|
|
|
|
|
func test_miss_returns_null_and_has_reports_false() -> void:
|
|
var cache := StepCanvasCache.new()
|
|
assert_that(cache.get_canvas("GJ1c", "District", Vector2i.ZERO, Vector2i(64, 64))).is_null()
|
|
assert_bool(cache.has("GJ1c", "District", Vector2i.ZERO, Vector2i(64, 64))).is_false()
|
|
|
|
|
|
func test_put_then_get_round_trips_exact_canvas() -> void:
|
|
var cache := StepCanvasCache.new()
|
|
var canvas := {"width": 64, "height": 64}
|
|
cache.put("GJ1c", "District", Vector2i(10, 20), Vector2i(64, 64), canvas)
|
|
assert_bool(cache.has("GJ1c", "District", Vector2i(10, 20), Vector2i(64, 64))).is_true()
|
|
assert_that(cache.get_canvas("GJ1c", "District", Vector2i(10, 20), Vector2i(64, 64))).is_equal(
|
|
canvas
|
|
)
|
|
|
|
|
|
## D-227: a canvas fetched once is valid FOREVER for that exact key — no
|
|
## expiry, no invalidation path.
|
|
func test_cached_canvas_never_expires() -> void:
|
|
var cache := StepCanvasCache.new()
|
|
var canvas := {"width": 1}
|
|
cache.put("GJ1c", "Chunk", Vector2i.ZERO, Vector2i(1, 1), canvas)
|
|
for _i in range(50):
|
|
assert_that(cache.get_canvas("GJ1c", "Chunk", Vector2i.ZERO, Vector2i(1, 1))).is_equal(canvas)
|
|
|
|
|
|
func test_different_rungs_at_identical_center_extent_do_not_collide() -> void:
|
|
var cache := StepCanvasCache.new()
|
|
var district_canvas := {"rung": "District"}
|
|
var chunk_canvas := {"rung": "Chunk"}
|
|
cache.put("GJ1c", "District", Vector2i(10, 20), Vector2i(64, 64), district_canvas)
|
|
cache.put("GJ1c", "Chunk", Vector2i(10, 20), Vector2i(64, 64), chunk_canvas)
|
|
assert_int(cache.size()).is_equal(2)
|
|
assert_that(cache.get_canvas("GJ1c", "District", Vector2i(10, 20), Vector2i(64, 64))).is_equal(
|
|
district_canvas
|
|
)
|
|
assert_that(cache.get_canvas("GJ1c", "Chunk", Vector2i(10, 20), Vector2i(64, 64))).is_equal(
|
|
chunk_canvas
|
|
)
|
|
|
|
|
|
func test_put_overwrites_existing_key() -> void:
|
|
var cache := StepCanvasCache.new()
|
|
cache.put("GJ1c", "District", Vector2i.ZERO, Vector2i(64, 64), {"v": 1})
|
|
cache.put("GJ1c", "District", Vector2i.ZERO, Vector2i(64, 64), {"v": 2})
|
|
assert_int(cache.size()).is_equal(1)
|
|
assert_that(cache.get_canvas("GJ1c", "District", Vector2i.ZERO, Vector2i(64, 64))).is_equal(
|
|
{"v": 2}
|
|
)
|
|
|
|
|
|
func test_eviction_drops_least_recently_used_on_overflow() -> void:
|
|
var cache := StepCanvasCache.new(2)
|
|
cache.put("GJ1c", "District", Vector2i(0, 0), Vector2i(64, 64), {"id": "a"})
|
|
cache.put("GJ1c", "District", Vector2i(1, 0), Vector2i(64, 64), {"id": "b"})
|
|
cache.put("GJ1c", "District", Vector2i(2, 0), Vector2i(64, 64), {"id": "c"})
|
|
|
|
assert_int(cache.size()).is_equal(2)
|
|
assert_bool(cache.has("GJ1c", "District", Vector2i(0, 0), Vector2i(64, 64))).override_failure_message(
|
|
"oldest entry should have been evicted"
|
|
).is_false()
|
|
assert_bool(cache.has("GJ1c", "District", Vector2i(2, 0), Vector2i(64, 64))).is_true()
|
|
|
|
|
|
func test_get_touches_entry_and_protects_it_from_eviction() -> void:
|
|
var cache := StepCanvasCache.new(2)
|
|
cache.put("GJ1c", "District", Vector2i(0, 0), Vector2i(64, 64), {"id": "a"})
|
|
cache.put("GJ1c", "District", Vector2i(1, 0), Vector2i(64, 64), {"id": "b"})
|
|
cache.get_canvas("GJ1c", "District", Vector2i(0, 0), Vector2i(64, 64))
|
|
cache.put("GJ1c", "District", Vector2i(2, 0), Vector2i(64, 64), {"id": "c"})
|
|
|
|
assert_bool(cache.has("GJ1c", "District", Vector2i(0, 0), Vector2i(64, 64))).override_failure_message(
|
|
"touched entry should survive eviction"
|
|
).is_true()
|
|
assert_bool(cache.has("GJ1c", "District", Vector2i(1, 0), Vector2i(64, 64))).override_failure_message(
|
|
"untouched entry should be the one evicted"
|
|
).is_false()
|
|
|
|
|
|
func test_max_entries_clamped_to_at_least_one() -> void:
|
|
var cache := StepCanvasCache.new(0)
|
|
cache.put("GJ1c", "District", Vector2i(0, 0), Vector2i(64, 64), {"id": "a"})
|
|
cache.put("GJ1c", "District", Vector2i(1, 0), Vector2i(64, 64), {"id": "b"})
|
|
assert_int(cache.size()).is_equal(1)
|
|
|
|
|
|
func test_clear_empties_the_cache() -> void:
|
|
var cache := StepCanvasCache.new()
|
|
cache.put("GJ1c", "District", Vector2i.ZERO, Vector2i(64, 64), {"id": "a"})
|
|
cache.clear()
|
|
assert_int(cache.size()).is_equal(0)
|
|
assert_bool(cache.has("GJ1c", "District", Vector2i.ZERO, Vector2i(64, 64))).is_false()
|