From 7ce6cc08fd3e90206655e6492e8ea759309cb817 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Tue, 21 Jul 2026 15:22:24 +0200 Subject: [PATCH] test(ui): pin the _user_adjusted resize guard both ways; fix coalescing doc overstatement (PR #188 review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hoshe's finding: the flag introduced so auto-fit never fights a manual view had zero coverage on exactly that branch. Two tests drive the REAL _gui_input path (synthetic drag), then fire NOTIFICATION_RESIZED: user-adjusted view survives a resize untouched (zoom AND offset); an unadjusted view re-fits to the new viewport. 52/52. Non-blocking doc note also taken: layer_proxy's normalization comment claimed the twins would otherwise 'coalesce independently' — the coalescing key is (ConnectionId, body_id) and never carried center; rewritten to say what normalization actually buys on that path (the work item derives and echoes the canonical center). Co-Authored-By: Claude Fable 5 --- client/tests/test_atlas_window_viewer.gd | 63 ++++++++++++++++++++++++ server/src/atlas/layer_proxy.rs | 10 ++-- 2 files changed, 70 insertions(+), 3 deletions(-) diff --git a/client/tests/test_atlas_window_viewer.gd b/client/tests/test_atlas_window_viewer.gd index e87d89077..824c7c597 100644 --- a/client/tests/test_atlas_window_viewer.gd +++ b/client/tests/test_atlas_window_viewer.gd @@ -430,3 +430,66 @@ func test_center_one_column_past_the_seam_shares_a_cache_key_with_its_twin() -> SimBridge.atlas_layers_received.emit(_mock_response("GJ380c", window)) assert_that(v1.get_district_window()).is_equal(window) assert_that(v2.get_district_window()).is_equal(window) + + +# ============================================================================= +# _user_adjusted guard (PR #188 review) — the flag exists so auto-fit NEVER +# fights a manually-adjusted view. The one branch that makes that true +# (resize while user-adjusted) had no coverage; both directions pinned here, +# driving the REAL _gui_input path (synthetic events), not the flag directly. +# ============================================================================= + + +func _drag_viewer(v: AtlasWindowViewer, from: Vector2, to: Vector2) -> void: + var down := InputEventMouseButton.new() + down.button_index = MOUSE_BUTTON_LEFT + down.pressed = true + down.position = from + down.global_position = from + v._gui_input(down) + var move := InputEventMouseMotion.new() + move.position = to + move.global_position = to + v._gui_input(move) + var up := InputEventMouseButton.new() + up.button_index = MOUSE_BUTTON_LEFT + up.pressed = false + up.position = to + up.global_position = to + v._gui_input(up) + + +func test_resize_after_manual_drag_keeps_user_view() -> void: + var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new()) + add_child(v) + v.size = Vector2(1280.0, 720.0) + v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {}, Vector2i(10, 20), 32) + + _drag_viewer(v, Vector2(600.0, 400.0), Vector2(540.0, 380.0)) + var user_zoom: float = v.get_view_zoom() + var user_offset: Vector2 = v.get_view_offset() + + v.size = Vector2(1600.0, 900.0) + v.notification(Control.NOTIFICATION_RESIZED) + + assert_float(v.get_view_zoom()).override_failure_message( + "resize while user-adjusted must NOT re-fit — zoom belongs to the user" + ).is_equal_approx(user_zoom, 0.0001) + assert_vector(v.get_view_offset()).override_failure_message( + "resize while user-adjusted must NOT re-center — offset belongs to the user" + ).is_equal_approx(user_offset, Vector2(0.001, 0.001)) + + +func test_resize_without_user_adjustment_refits() -> void: + var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new()) + add_child(v) + v.size = Vector2(1280.0, 720.0) + v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {}, Vector2i(10, 20), 32) + var fitted_zoom: float = v.get_view_zoom() + + v.size = Vector2(640.0, 360.0) + v.notification(Control.NOTIFICATION_RESIZED) + + assert_float(v.get_view_zoom()).override_failure_message( + "resize with no manual adjustment must re-fit to the new viewport" + ).is_not_equal(fitted_zoom) diff --git a/server/src/atlas/layer_proxy.rs b/server/src/atlas/layer_proxy.rs index fc7b30768..5735a1301 100644 --- a/server/src/atlas/layer_proxy.rs +++ b/server/src/atlas/layer_proxy.rs @@ -821,9 +821,13 @@ fn normalize_window_center(params: &BodyParams, center: DistrictPos) -> District /// rather than only inside the former miss-branch: normalization needs /// `body_radius_km` to compute the wrap/clamp bounds, and it must happen /// before either key exists, or an insane request and its sane normalized -/// twin would land in different cache entries / coalesce independently -/// (exactly the bug this fix closes — a garbage `window_center` was cached -/// standalone instead of collapsing onto its valid twin). The one-time cost +/// twin would land in different cache entries (exactly the bug this fix +/// closes — a garbage `window_center` was cached standalone instead of +/// collapsing onto its valid twin). The coalescing key itself is +/// `(ConnectionId, body_id)` — it never carried `center`, so coalescing +/// was never at risk of diverging per-center; normalizing before +/// `submit_window` matters only so the work item DERIVES (and echoes) the +/// canonical center. The one-time cost /// (a single indexed `bodies` row read) is paid on every window request now, /// not just on a cache miss — a request whose normalized center hits the /// cache still needed this read to know WHICH key to check.