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>
77 lines
3.6 KiB
GDScript
77 lines
3.6 KiB
GDScript
## T-1182 tests: StepCanvasAnnotationLayer — the unscaled screen-space
|
|
## sibling's world->screen placement math (course polylines, settlement
|
|
## markers) and course visibility/terminus handling. Draw-call correctness
|
|
## itself needs a live render pass (this cluster's existing "state-level is
|
|
## fine" allowance, per test_atlas_descend_entry.gd's own precedent) — these
|
|
## tests pin the FRAME state (_world_to_local, _cell_center_world_m) a draw
|
|
## call would read from, without requiring a SubViewport.
|
|
class_name TestStepCanvasAnnotationLayer
|
|
extends GdUnitTestSuite
|
|
|
|
const StepCanvasTransport := preload("res://ui/implant/apps/atlas/step_canvas/step_canvas_transport.gd")
|
|
|
|
|
|
func test_set_frame_stores_the_frame_and_triggers_no_crash_on_draw() -> void:
|
|
var layer: StepCanvasAnnotationLayer = auto_free(StepCanvasAnnotationLayer.new())
|
|
add_child(layer)
|
|
var canvas := {
|
|
"width": 4,
|
|
"height": 4,
|
|
"courses": [],
|
|
"settlement_id": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
}
|
|
layer.set_frame(canvas, Vector2(1000.0, 2000.0), "District", Vector2i(4, 4))
|
|
# No assertion beyond "did not crash" — set_frame()/queue_redraw() with a
|
|
# well-formed empty-feature canvas is the baseline no-op path every
|
|
# richer test below builds on.
|
|
assert_object(layer).is_not_null()
|
|
|
|
|
|
func test_clear_frame_drops_the_held_canvas() -> void:
|
|
var layer: StepCanvasAnnotationLayer = auto_free(StepCanvasAnnotationLayer.new())
|
|
add_child(layer)
|
|
layer.set_frame({"width": 1, "height": 1, "courses": []}, Vector2.ZERO, "Chunk", Vector2i(1, 1))
|
|
layer.clear_frame()
|
|
assert_that(layer._canvas).is_null()
|
|
|
|
|
|
## _cell_center_world_m() is the inverse of step_canvas.rs's own per-cell
|
|
## placement (center_world_m + (col - half_w) * step_m) — a settlement id
|
|
## read from cell (col, row) must map back to the world point that cell was
|
|
## actually derived at.
|
|
func test_cell_center_world_m_matches_the_servers_own_per_cell_placement() -> void:
|
|
var layer: StepCanvasAnnotationLayer = auto_free(StepCanvasAnnotationLayer.new())
|
|
add_child(layer)
|
|
layer.set_frame({"width": 4, "height": 4, "courses": []}, Vector2(0.0, 0.0), "District", Vector2i(4, 4))
|
|
|
|
# half_w = half_h = 2; spacing = 2048. Cell (0,0) -> (0-2)*2048 = -4096 on
|
|
# both axes; cell (2,2) (the center-ish cell) -> (2-2)*2048 = 0.
|
|
assert_that(layer._cell_center_world_m(0, 0)).is_equal(Vector2(-4096.0, -4096.0))
|
|
assert_that(layer._cell_center_world_m(2, 2)).is_equal(Vector2.ZERO)
|
|
|
|
|
|
func test_cell_center_world_m_offsets_by_the_frames_world_center() -> void:
|
|
var layer: StepCanvasAnnotationLayer = auto_free(StepCanvasAnnotationLayer.new())
|
|
add_child(layer)
|
|
layer.set_frame(
|
|
{"width": 2, "height": 2, "courses": []}, Vector2(10_000.0, 20_000.0), "Chunk", Vector2i(2, 2)
|
|
)
|
|
# half_w = half_h = 1; spacing = 64. Cell (1,1) -> center + (1-1)*64 = center.
|
|
assert_that(layer._cell_center_world_m(1, 1)).is_equal(Vector2(10_000.0, 20_000.0))
|
|
|
|
|
|
## _world_to_local() delegates to StepCanvasTransport.world_m_to_canvas_local
|
|
## with the layer's OWN held frame — this pins that the layer actually reads
|
|
## its stored _world_center/_rung/_extent_cells, not stale defaults.
|
|
func test_world_to_local_uses_the_held_frame() -> void:
|
|
var layer: StepCanvasAnnotationLayer = auto_free(StepCanvasAnnotationLayer.new())
|
|
add_child(layer)
|
|
var world_center := Vector2(5_000.0, -3_000.0)
|
|
var extent := Vector2i(32, 32)
|
|
layer.set_frame({"width": 32, "height": 32, "courses": []}, world_center, "Quarter", extent)
|
|
|
|
var expected: Vector2 = StepCanvasTransport.world_m_to_canvas_local(
|
|
world_center, world_center, "Quarter", extent
|
|
)
|
|
assert_that(layer._world_to_local(world_center)).is_equal_approx(expected, Vector2(0.01, 0.01))
|