fix(client): PR #192 review round — per-rung legend, Region coalescing + clamp boundary tests, halving-loop honesty
All Hoshe/Araminta findings addressed (Tyre approved outright), none retracted — plus a Dudley stop-and-flag discovery that improved on the asked-for fix: - Legend 100x lie (Araminta, blocking): subtitle computed via spacing_for_rung() and refreshed at all three _held_granularity_v2 write sites. Region test asserts 204.800 km/cell; the District direction needed a stale-header-aware helper — a District-only test spuriously passes against the old literal by coincidence. - Region coalescing coverage (Hoshe 1): both directions tested (Region-vs-District separate slots; Region-vs-Region coalesces). - Clamp boundary tests + dangling citations (Hoshe 2): writing the requested halving-loop-fires test surfaced that the loop is PROVABLY UNREACHABLE at current constants (per-axis clamp forecloses it — brute-forced independently on both server and client sides). Ruling: the loop stays as defensive code; the test became a property sweep pinning both the wire-cap invariant and the loop's no-op status (a future constant change breaks it loudly); doc comments on both sides drop the load-bearing framing and state the truth; the old client mirror test that claimed the loop fires (passing on the per-axis clamp alone) is replaced the same way. Client citations now name the real server tests verbatim. - Governance (Tyre): D-226 amendment note — progressive cross-rung refinement EXTENDS T-1124 §4 (not supersedes); legacy u32 field scheduled for retirement (T-1159). Server: 1818 lib tests green, clippy/fmt clean. Client: zoom_ladder 48/48, window_request 26/26, viewer 74/74; gdlint clean. Every fix revert-verified.
This commit is contained in:
@@ -339,14 +339,25 @@ func test_oversized_n_request_stores_clamped_n_and_accepts_matching_echo() -> vo
|
||||
# =============================================================================
|
||||
# (d) Region clamp mirror (T-1152/T-1153) — mirrors
|
||||
# server/src/atlas/layer_proxy.rs's clamp_window_n_v2 EXACTLY, including the
|
||||
# Region branch's bounded halving loop (no closed form, per Dudley's own
|
||||
# doc — "replicate the loop exactly").
|
||||
# Region branch's bounded halving loop.
|
||||
#
|
||||
# PR #192 review (Dudley, server-side analysis): the halving loop is
|
||||
# PROVABLY UNREACHABLE at current constants — the per-axis clamp to
|
||||
# SERVER_DISTRICT_WINDOW_MAX_N_REGION (6,400) forecloses it. Brute-forced,
|
||||
# the max cell_grid_side over ALL reachable (post-per-axis-clamp) n is
|
||||
# exactly 64 — the wire-cap boundary itself, never over it — so the loop's
|
||||
# `>` guard is never true for any input. Ruling: the loop STAYS as
|
||||
# defensive code (a future constant change could make it reachable again),
|
||||
# but the test suite must not claim it "fires" when it provably doesn't.
|
||||
# See server/src/atlas/layer_proxy.rs's
|
||||
# clamp_window_n_v2_region_per_axis_cap_alone_satisfies_wire_cap_for_all_inputs
|
||||
# for the server-side property-sweep pin this client-side suite mirrors.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
## District/Quarter through the v2 mirror must be BYTE-IDENTICAL to the
|
||||
## legacy mirror — the server's own
|
||||
## `clamp_window_n_v2_matches_legacy_for_finer_than_district_rungs`
|
||||
## `clamp_window_n_v2_delegates_to_legacy_for_district_and_quarter`
|
||||
## guarantee, restated client-side.
|
||||
func test_clamp_window_n_mirror_v2_matches_legacy_for_district_and_quarter() -> void:
|
||||
assert_int(
|
||||
@@ -362,9 +373,9 @@ func test_clamp_window_n_mirror_v2_matches_legacy_for_district_and_quarter() ->
|
||||
## 64, and 64² = 4,096 = WIRE_CAP_CELLS EXACTLY — the halving loop's `>`
|
||||
## condition is false at the boundary, so this must clamp to EXACTLY 6,400,
|
||||
## not halve further. This is the server's own
|
||||
## `region_per_axis_cap_lands_exactly_on_wire_cap_when_uncontested`-shaped
|
||||
## boundary (WIRE_CAP_CELLS_SQRT * DISTRICTS_PER_REGION is DERIVED to land
|
||||
## here exactly, per that constant's own doc).
|
||||
## `clamp_window_n_v2_region_exact_boundary_n6400_uncontested` guarantee,
|
||||
## restated client-side (WIRE_CAP_CELLS_SQRT * DISTRICTS_PER_REGION is
|
||||
## DERIVED to land here exactly, per that constant's own doc).
|
||||
func test_clamp_window_n_mirror_v2_region_boundary_is_exact() -> void:
|
||||
var n: int = AtlasWindowRequest._clamp_window_n_mirror_v2(
|
||||
AtlasWindowRequest.SERVER_DISTRICT_WINDOW_MAX_N_REGION, AtlasWindowRequest.GRANULARITY_V2_REGION
|
||||
@@ -392,23 +403,48 @@ func test_clamp_window_n_mirror_v2_region_oversized_n_clamps_within_both_bounds(
|
||||
).is_less_equal(AtlasWindowRequest.SERVER_WIRE_CAP_CELLS)
|
||||
|
||||
|
||||
## A REGION request whose derived grid would exceed WIRE_CAP_CELLS (n set so
|
||||
## cell_grid_side(n) rounds to 65, just over the 64-per-axis wire-size
|
||||
## ceiling) must actually HALVE — this is the case the boundary test above
|
||||
## deliberately sits just below, so this one confirms the loop body actually
|
||||
## fires, not just that its guard conditions are correct at the edges.
|
||||
func test_clamp_window_n_mirror_v2_region_halves_when_over_wire_cap() -> void:
|
||||
# n=6,450 -> cell_grid_side = round(64.5) = 65 (round-half-away-from-zero,
|
||||
# matching Rust's f64::round() and GDScript's roundi() for non-negative
|
||||
# inputs) -> 65² = 4,225 > WIRE_CAP_CELLS (4,096) -> must halve at least once.
|
||||
var n: int = AtlasWindowRequest._clamp_window_n_mirror_v2(
|
||||
6450, AtlasWindowRequest.GRANULARITY_V2_REGION
|
||||
)
|
||||
assert_int(n).override_failure_message(
|
||||
"a Region request whose grid exceeds WIRE_CAP_CELLS must be halved down, not left at 6,450"
|
||||
).is_less(6450)
|
||||
var side: int = AtlasWindowRequest._cell_grid_side_region_mirror(n)
|
||||
assert_int(side * side).is_less_equal(AtlasWindowRequest.SERVER_WIRE_CAP_CELLS)
|
||||
## PR #192 review (Dudley's unreachability finding, applied client-side): the
|
||||
## halving loop's `>` guard is PROVABLY never true at current constants — the
|
||||
## per-axis clamp to SERVER_DISTRICT_WINDOW_MAX_N_REGION (6,400) happens
|
||||
## FIRST and unconditionally, and cell_grid_side(6400) = 64 lands EXACTLY on
|
||||
## the wire-cap boundary (64² = WIRE_CAP_CELLS), never over it. A prior
|
||||
## version of this test claimed n=6,450 "exercises" the loop firing — it does
|
||||
## not: 6,450 clamps to 6,400 before the loop ever runs, so the test was
|
||||
## passing on the per-axis clamp alone, not on anything the loop itself did
|
||||
## (the same mock-diverges-from-reality class of bug hunted in review round
|
||||
## 2). Reframed as a property sweep, mirroring the server's own
|
||||
## `clamp_window_n_v2_region_per_axis_cap_alone_satisfies_wire_cap_for_all_inputs`
|
||||
## (Dudley): for every raw n across the legal range (including values far
|
||||
## past the per-axis cap), (i) the per-axis-clamped n never gets modified any
|
||||
## further by the loop — pre-loop n and post-clamp n are byte-identical —
|
||||
## and (ii) the wire-cap invariant holds regardless. The loop itself stays as
|
||||
## defensive code (a future constant change could make it reachable again);
|
||||
## this test documents that it is a no-op today rather than asserting a
|
||||
## behavior that never actually happens.
|
||||
func test_clamp_window_n_mirror_v2_region_per_axis_cap_alone_satisfies_wire_cap_for_all_inputs() -> void:
|
||||
var sample_raw_ns: Array = [
|
||||
1, 100, 6399, 6400, 6401, 6450, 6500,
|
||||
AtlasWindowRequest.SERVER_DISTRICT_WINDOW_MAX_N_REGION * 10,
|
||||
]
|
||||
for raw_n: int in sample_raw_ns:
|
||||
var pre_loop_n: int = clampi(raw_n, 1, AtlasWindowRequest.SERVER_DISTRICT_WINDOW_MAX_N_REGION)
|
||||
var clamped_n: int = AtlasWindowRequest._clamp_window_n_mirror_v2(
|
||||
raw_n, AtlasWindowRequest.GRANULARITY_V2_REGION
|
||||
)
|
||||
assert_int(clamped_n).override_failure_message(
|
||||
(
|
||||
"the per-axis clamp alone must already satisfy the wire cap for"
|
||||
+ " raw_n=%d — the halving loop is provably unreachable at current"
|
||||
+ " constants (max cell_grid_side over all reachable n is exactly"
|
||||
+ " 64, the wire-cap boundary itself), so it must never further"
|
||||
+ " modify what the per-axis clamp already produced"
|
||||
) % raw_n
|
||||
).is_equal(pre_loop_n)
|
||||
|
||||
var side: int = AtlasWindowRequest._cell_grid_side_region_mirror(clamped_n)
|
||||
assert_int(side * side).override_failure_message(
|
||||
"the wire-cap invariant must hold for raw_n=%d regardless" % raw_n
|
||||
).is_less_equal(AtlasWindowRequest.SERVER_WIRE_CAP_CELLS)
|
||||
|
||||
|
||||
## n smaller than one region (n < 100) must clamp its cell-grid side to a
|
||||
|
||||
@@ -218,6 +218,59 @@ func test_new_rung_window_swaps_in_once_it_arrives() -> void:
|
||||
assert_str(v._held_granularity_v2).is_equal("Region")
|
||||
|
||||
|
||||
## refresh() clear()s via queue_free() (deferred, not synchronous) — a legend
|
||||
## that has refreshed more than once in the same frame (build-time refresh at
|
||||
## _ready(), then an entry-time refresh) can have STALE not-yet-freed
|
||||
## children still parented alongside the new ones. add_component() always
|
||||
## APPENDS, so the current ImplantHeader is the LAST one in the list, never
|
||||
## assumed to be [0].
|
||||
static func _current_legend_header(legend_panel) -> ImplantHeader:
|
||||
var children: Array = legend_panel.get_implant_children()
|
||||
for i in range(children.size() - 1, -1, -1):
|
||||
if children[i] is ImplantHeader:
|
||||
return children[i]
|
||||
return null
|
||||
|
||||
|
||||
## PR #192 review (Araminta, BLOCKING): the legend subtitle used to hardcode
|
||||
## District's own "2.048 km/cell" — a 100x lie whenever the viewer actually
|
||||
## holds Region (204.8 km/cell). While in the orbital tile-mode rest state
|
||||
## (Region granularity), the legend must read Region's real spacing, not the
|
||||
## stale District literal.
|
||||
func test_legend_subtitle_reflects_region_spacing_in_tile_mode() -> void:
|
||||
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
|
||||
add_child(v)
|
||||
var radius_km := 6238.4 # GJ380c (Lendel) — needs tiling, so enters at Region
|
||||
v.enter_orbital({"body_id": "GJ380c", "body_radius_km": radius_km}, {})
|
||||
assert_bool(v.is_tile_mode()).is_true()
|
||||
assert_str(v._held_granularity_v2).is_equal("Region")
|
||||
|
||||
var header: ImplantHeader = _current_legend_header(v._legend_panel)
|
||||
assert_str(header._subtitle_label.text).override_failure_message(
|
||||
"legend subtitle must reflect Region's real 204.800 km/cell spacing while"
|
||||
+ " the viewer holds Region granularity, not a hardcoded District figure"
|
||||
).contains("204.800 km/cell")
|
||||
|
||||
|
||||
## Same bug, the other direction: after crossing INTO a single-window District
|
||||
## rung, the legend must re-render with District's own spacing — proving the
|
||||
## legend actually refreshes on a rung change rather than being stuck at
|
||||
## whatever it showed on the FIRST refresh() call (T-1153's _build_legend_panel()
|
||||
## fires one at _ready() time, before any real rung is held).
|
||||
func test_legend_subtitle_reflects_district_spacing_after_crossing_in() -> void:
|
||||
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
|
||||
add_child(v)
|
||||
v.enter({"body_id": "GJ380c"}, {}, Vector2i(10, 20), 2)
|
||||
assert_str(v._held_granularity_v2).is_equal("District")
|
||||
|
||||
var header: ImplantHeader = _current_legend_header(v._legend_panel)
|
||||
assert_str(header._subtitle_label.text).override_failure_message(
|
||||
"legend subtitle must re-render at District's own 2.048 km/cell spacing"
|
||||
+ " once the viewer holds a District-rung window — proving refresh() is"
|
||||
+ " actually wired to the rung change, not just called once at build time"
|
||||
).contains("2.048 km/cell")
|
||||
|
||||
|
||||
## A response for a rung OTHER than what's currently requested (e.g. a
|
||||
## District response arriving after the viewer has already moved on to a
|
||||
## Region request — a rapid wheel-zoom race) must be discarded as stale, the
|
||||
|
||||
@@ -16,6 +16,7 @@ const PANEL_MARGIN: float = 16.0
|
||||
const LEGEND_PANEL_WIDTH: float = 260.0
|
||||
|
||||
const AtlasOverlayColors := preload("res://ui/implant/apps/atlas/atlas_overlay_colors.gd")
|
||||
const AtlasWindowGeometry := preload("res://ui/implant/apps/atlas/atlas_window_geometry.gd")
|
||||
|
||||
## The morphology base layer folds its 17 zones into ~5 family rows (§5:
|
||||
## "mirroring T-1112's 'not everything earns permanent screen space'
|
||||
@@ -55,13 +56,28 @@ func reposition() -> void:
|
||||
## Always shows the base-layer key (morphology + elevation reading, always
|
||||
## on) plus glaciation (always-on modifier), then whichever toggle overlay is
|
||||
## currently active, if any.
|
||||
##
|
||||
## The subtitle's km/cell reading is NOT a fixed "district window" label —
|
||||
## it was until this fix hardcoded District's own 2.048 km/cell, which was a
|
||||
## 100x lie whenever the viewer actually holds Region (204.8 km/cell) or
|
||||
## Quarter (0.512 km/cell). Read through AtlasWindowGeometry.
|
||||
## spacing_for_rung() — the SAME pure lookup _refresh_screen_header() uses
|
||||
## for the main screen header's subtitle (screen_header_content()) — keyed
|
||||
## off the viewer's current get_held_granularity_v2(), so the legend and
|
||||
## header can never disagree. AtlasWindowViewer calls refresh() at every
|
||||
## point _held_granularity_v2 changes (_enter_tile_mode(), _enter_at_rung(),
|
||||
## _on_window_ready()'s rung-swap adoption) — see those call sites.
|
||||
func refresh() -> void:
|
||||
if _viewer == null:
|
||||
return
|
||||
clear()
|
||||
visible = true
|
||||
|
||||
add_component(ImplantHeader.new("REGIONAL LEGEND", "district window · 2.048 km/cell"))
|
||||
var spacing_km: float = AtlasWindowGeometry.spacing_for_rung(_viewer.get_held_granularity_v2()) / 1000.0
|
||||
var subtitle: String = "%s window · %.3f km/cell" % [
|
||||
_viewer.get_held_granularity_v2().to_lower(), spacing_km
|
||||
]
|
||||
add_component(ImplantHeader.new("REGIONAL LEGEND", subtitle))
|
||||
add_component(ImplantSeparator.new())
|
||||
|
||||
_add_morphology_section()
|
||||
|
||||
@@ -25,8 +25,8 @@ extends Control
|
||||
## keeps drawing — progressive refinement, no blank frame (§6). A pan
|
||||
## past the held window's edge re-requests the SAME rung at a new center.
|
||||
## - Zooming fully out snaps to the CANONICAL planetary frame (Jeroen's HARD
|
||||
## condition) — see _maybe_reset_to_canonical_frame() — which, on a body
|
||||
## needing tiling, re-enters `_tile_mode` (live round 3, design doc §4).
|
||||
## condition, see _maybe_reset_to_canonical_frame()) — on a body needing
|
||||
## tiling, re-enters `_tile_mode` (live round 3, design doc §4).
|
||||
## - _window_request (atlas_window_request.gd) owns the single-window
|
||||
## cache/debounce/retry; _tile_set (atlas_window_tile_set.gd) owns N of
|
||||
## those for the tiled rest state — this Control decides WHICH is active.
|
||||
@@ -34,8 +34,7 @@ extends Control
|
||||
## Navigation (Jeroen's input-model ruling: LMB-drag panning BREAKS click
|
||||
## semantics with future map objects, so it's removed entirely):
|
||||
## WASD / arrow keys continuous pan, held (frame-rate independent, _process)
|
||||
## Edge scrolling cursor within EDGE_SCROLL_MARGIN_PX of a viewport
|
||||
## edge pans toward it (suppressed over UI / unfocused)
|
||||
## Edge scrolling cursor within EDGE_SCROLL_MARGIN_PX pans toward it (suppressed over UI / unfocused)
|
||||
## Mouse wheel cursor-anchored zoom; crosses rungs continuously (T-1153)
|
||||
## Esc back (nav.pop())
|
||||
|
||||
@@ -47,14 +46,13 @@ const OVERLAY_BAR_HEADER_RESERVE: float = 360.0
|
||||
## T-1153: MIN_ZOOM/MAX_ZOOM are a wide safety clamp on the raw display
|
||||
## multiplier, NOT a rung boundary — wheel zoom is CONTINUOUS and UNCLAMPED
|
||||
## ACROSS RUNGS (D-013): crossing a rung's coverage ceiling
|
||||
## (AtlasWindowGeometry.select_rung()) re-requests a DIFFERENT granularity
|
||||
## at the SAME apparent screen extent, never clamping _view_zoom itself.
|
||||
## (AtlasWindowGeometry.select_rung()) re-requests a DIFFERENT granularity at
|
||||
## the SAME apparent screen extent, never clamping _view_zoom itself.
|
||||
## set_view() (T-1120 capture API) clamps to this same range independently.
|
||||
##
|
||||
## MIN_ZOOM must stay low enough that fit_window_view()'s COVER fit for
|
||||
## enter_orbital()'s largest legal `n` is never itself clamped — that would
|
||||
## silently show LESS than the whole body, breaking Jeroen's HARD condition.
|
||||
## 0.0005 covers a ~120,000 km-radius body at a 3840px 4K viewport.
|
||||
## enter_orbital()'s largest legal `n` is never itself clamped (would
|
||||
## silently show LESS than the whole body). 0.0005 covers a ~120,000 km-
|
||||
## radius body at a 3840px 4K viewport.
|
||||
const MIN_ZOOM: float = 0.0005
|
||||
const MAX_ZOOM: float = 64.0
|
||||
const ZOOM_STEP: float = 1.15
|
||||
@@ -66,10 +64,9 @@ const ZOOM_STEP: float = 1.15
|
||||
const PAN_SPEED_CANVAS_PX_S: float = 96.0
|
||||
|
||||
## T-1145 item 2: cursor-to-edge distance (px) that triggers edge-scroll —
|
||||
## Jeroen's own number ("~24px").
|
||||
## Jeroen's own number ("~24px"). Uses the SAME speed as WASD (one pan feel,
|
||||
## two triggers) — no separate constant, _process() reads PAN_SPEED_CANVAS_PX_S for both.
|
||||
const EDGE_SCROLL_MARGIN_PX: float = 24.0
|
||||
## Edge-scroll uses the SAME speed as WASD (one pan feel, two triggers) —
|
||||
## no separate constant, _process() reads PAN_SPEED_CANVAS_PX_S for both.
|
||||
|
||||
## Pixel size of one district cell at zoom=1.0 — a fixed on-screen scale
|
||||
## (unlike AtlasViewer's heightmap, there is no source texture dictating a
|
||||
@@ -82,17 +79,14 @@ const COLOR_BG: Color = Color("#0d1117")
|
||||
## Border-fade target (§5 "what renders during the wait"): the underlying
|
||||
## whole-body heightmap's own background tint, so the newly-exposed edge
|
||||
## reads as "real data seen through", not a placeholder block. Reuses
|
||||
## AtlasViewer's own COLOR_HEIGHTMAP_TINT-adjacent dim value rather than
|
||||
## inventing a new one — this IS a dimmer/less-certain read of the same
|
||||
## planetary data, not a different visual language.
|
||||
## AtlasViewer's own COLOR_HEIGHTMAP_TINT-adjacent dim value — a dimmer/
|
||||
## less-certain read of the same planetary data, not a different visual language.
|
||||
const COLOR_BORDER_FADE: Color = Color(0.20, 0.24, 0.30, 0.55)
|
||||
|
||||
## T-1153/R6: pending-refinement wash — the border-fade's referent repointed
|
||||
## to "the previous derived composite at this position" for a rung-crossing
|
||||
## zoom (progressive refinement leaves real data on screen, unlike the
|
||||
## no-composite-yet case COLOR_BORDER_FADE covers). Same hue family, much
|
||||
## lighter alpha — a hint that something sharper is arriving, not a claim
|
||||
## that the current view is empty or wrong.
|
||||
## T-1153/R6: pending-refinement wash — border-fade's referent repointed to
|
||||
## "the previous derived composite at this position" for a rung-crossing zoom
|
||||
## (real data stays on screen, unlike the no-composite-yet case above). Same
|
||||
## hue, lighter alpha — hints something sharper is arriving, not that the view is wrong.
|
||||
const COLOR_PENDING_REFINEMENT_WASH: Color = Color(0.20, 0.24, 0.30, 0.12)
|
||||
|
||||
## D-243: 2,048 m per district side.
|
||||
@@ -311,6 +305,7 @@ func _enter_tile_mode(body: Dictionary, system: Dictionary, radius_km: float) ->
|
||||
_window_request.reset()
|
||||
_tile_set.enter(_dict_str(_body, "body_id", ""), radius_km)
|
||||
_refresh_screen_header()
|
||||
_legend_panel.refresh()
|
||||
grab_focus()
|
||||
queue_redraw()
|
||||
_overlay_node.queue_redraw()
|
||||
@@ -346,6 +341,7 @@ func _enter_at_rung(
|
||||
_dict_str(_body, "body_id", ""), _held_center, clamped_n, granularity_v2
|
||||
)
|
||||
_refresh_screen_header()
|
||||
_legend_panel.refresh()
|
||||
grab_focus()
|
||||
queue_redraw()
|
||||
_overlay_node.queue_redraw()
|
||||
@@ -413,6 +409,12 @@ func get_held_n() -> int:
|
||||
return _held_n
|
||||
|
||||
|
||||
## Currently-HELD rung tag — legend reads this via spacing_for_rung(),
|
||||
## mirroring _refresh_screen_header()'s own use of the field.
|
||||
func get_held_granularity_v2() -> String:
|
||||
return _held_granularity_v2
|
||||
|
||||
|
||||
## Live round 5: current body's radius — mosaic draw needs `cols` for
|
||||
## nearest_wrap_image()'s wrap resolution. Mirrors the
|
||||
## `_body.get("body_radius_km", 0.0)` pattern used throughout this file.
|
||||
@@ -495,6 +497,7 @@ func _on_window_ready(window: Dictionary) -> void:
|
||||
_fit_and_center()
|
||||
_awaiting_first_window = false
|
||||
_refresh_screen_header()
|
||||
_legend_panel.refresh()
|
||||
queue_redraw()
|
||||
_overlay_node.queue_redraw()
|
||||
|
||||
@@ -968,12 +971,9 @@ func _notification(what: int) -> void:
|
||||
_position_overlay_bar()
|
||||
if _legend_panel:
|
||||
_legend_panel.reposition()
|
||||
# T-1142: re-fit on resize too, same _user_adjusted guard as the other
|
||||
# two auto-fit events (enter, first window arrival) — never fights a
|
||||
# manually-adjusted view. _canvas guard matches _overlay_bar/
|
||||
# _legend_panel above: NOTIFICATION_RESIZED can fire mid-_ready()
|
||||
# (anchor_right/anchor_bottom assignment triggers it) BEFORE _canvas
|
||||
# is constructed — confirmed the hard way (gdUnit add_child() crash).
|
||||
# T-1142: re-fit on resize (_user_adjusted guard, as other auto-fit
|
||||
# events). _canvas guard: NOTIFICATION_RESIZED can fire mid-_ready()
|
||||
# before _canvas exists (gdUnit add_child() crash, confirmed).
|
||||
if _canvas and not _user_adjusted:
|
||||
_fit_and_center()
|
||||
elif what == NOTIFICATION_APPLICATION_FOCUS_OUT:
|
||||
|
||||
Reference in New Issue
Block a user