|
|
|
@@ -8,25 +8,23 @@ extends Control
|
|
|
|
|
## rung) down to District/Quarter lives in ONE screen/Control, not a separate
|
|
|
|
|
## planetary viewer + windowed drill-down. Renders a DistrictWindowLayer
|
|
|
|
|
## composite (or, at the orbital rest state on a large body, a MOSAIC of
|
|
|
|
|
## several — see `_tile_mode`/AtlasWindowTileSet, live round 3) at whichever
|
|
|
|
|
## rung is currently held: morphology base layer lightness-modulated by
|
|
|
|
|
## elev_q, three switchable climate/vegetation overlays, an always-on
|
|
|
|
|
## glaciation ice-tint modifier (drawing is AtlasWindowOverlay's job — this
|
|
|
|
|
## Control owns input, request orchestration, chrome, pan/zoom). One
|
|
|
|
|
## colorizer family renders every rung unchanged (design doc §6).
|
|
|
|
|
## several — see `_tile_mode`/AtlasWindowTileSet) at whichever rung is
|
|
|
|
|
## currently held: morphology base lightness-modulated by elev_q, three
|
|
|
|
|
## switchable climate/vegetation overlays, an always-on glaciation ice-tint
|
|
|
|
|
## modifier (drawing is AtlasWindowOverlay's job — this Control owns input,
|
|
|
|
|
## request orchestration, chrome, pan/zoom). One colorizer family renders
|
|
|
|
|
## every rung unchanged (design doc §6).
|
|
|
|
|
##
|
|
|
|
|
## Design notes (mirroring AtlasViewer's own split, D-226 §5, extended T-1153):
|
|
|
|
|
## - _canvas (Node2D) holds AtlasWindowOverlay; pan = _canvas.position, zoom
|
|
|
|
|
## = _canvas.scale. Zoom is client-side on the ALREADY-HELD composite
|
|
|
|
|
## frame-to-frame, but CONTINUOUS AND UNCLAMPED ACROSS RUNGS (D-013):
|
|
|
|
|
## crossing a rung's coverage ceiling (§5, AtlasWindowGeometry.
|
|
|
|
|
## select_rung()) fires a background request for the new granularity
|
|
|
|
|
## while the OLD composite 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.
|
|
|
|
|
## crossing a rung's coverage ceiling fires a background request for the
|
|
|
|
|
## new granularity while the OLD composite 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()) — on a body needing
|
|
|
|
|
## tiling, re-enters `_tile_mode` (live round 3, design doc §4).
|
|
|
|
|
## condition) — on a body needing tiling, re-enters `_tile_mode` (§4).
|
|
|
|
|
## - _window_request owns the single-window cache/debounce/retry; _tile_set
|
|
|
|
|
## owns N of those for the tiled rest state — this Control decides WHICH.
|
|
|
|
|
##
|
|
|
|
@@ -44,13 +42,12 @@ 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.
|
|
|
|
|
## set_view() (T-1120 capture API) clamps to this same range independently.
|
|
|
|
|
## MIN_ZOOM must stay low enough that enter_orbital()'s largest legal `n`
|
|
|
|
|
## COVER-fits without clamping (would silently show less than the whole
|
|
|
|
|
## body). 0.0005 covers a ~120,000 km-radius body at a 3840px 4K viewport.
|
|
|
|
|
## ACROSS RUNGS (D-013): crossing a rung's coverage ceiling 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 enter_orbital()'s
|
|
|
|
|
## largest legal `n` COVER-fits without clamping — 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
|
|
|
|
@@ -61,23 +58,20 @@ 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 "~24px"). Same speed as WASD — _process() reads
|
|
|
|
|
## PAN_SPEED_CANVAS_PX_S for both, no separate constant.
|
|
|
|
|
## (Jeroen's "~24px"). Same speed as WASD, no separate constant.
|
|
|
|
|
const EDGE_SCROLL_MARGIN_PX: float = 24.0
|
|
|
|
|
|
|
|
|
|
## Pixel size of one district cell at zoom=1.0 — a fixed on-screen scale
|
|
|
|
|
## (unlike AtlasViewer's heightmap, no source texture dictates a native pixel
|
|
|
|
|
## size; this constant IS the native size). 16px/cell at n=64 gives a
|
|
|
|
|
## ~1024px-wide composite before zoom, comfortable in a 1280x720+ viewport.
|
|
|
|
|
## (no source texture dictates a native size; this constant IS it). 16px/cell
|
|
|
|
|
## at n=64 gives a ~1024px composite before zoom, fits a 1280x720+ viewport.
|
|
|
|
|
const CELL_PIXEL_SIZE: float = 16.0
|
|
|
|
|
|
|
|
|
|
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 — a dimmer/
|
|
|
|
|
## less-certain read of the same planetary data, not a different visual
|
|
|
|
|
## language. Also read by AtlasWindowOverlay for a per-tile pending wash in
|
|
|
|
|
## the mosaic (cold-start dossier) — same "still working" cue, one color.
|
|
|
|
|
## reads as "real data seen through", not a placeholder block. Also read by
|
|
|
|
|
## AtlasWindowOverlay for a per-tile pending wash in the mosaic — same
|
|
|
|
|
## "still working" cue, one color.
|
|
|
|
|
const COLOR_BORDER_FADE: Color = Color(0.20, 0.24, 0.30, 0.55)
|
|
|
|
|
|
|
|
|
|
## T-1153/R6: pending-refinement wash — border-fade's referent repointed to
|
|
|
|
@@ -86,6 +80,11 @@ const COLOR_BORDER_FADE: Color = Color(0.20, 0.24, 0.30, 0.55)
|
|
|
|
|
## 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)
|
|
|
|
|
|
|
|
|
|
## Cold-start dossier: the "DERIVING TERRAIN…" label's own text color —
|
|
|
|
|
## default_implant.tres's text_dim (#667788), matching the implant chrome's
|
|
|
|
|
## existing dim/secondary-text role rather than inventing a new hue.
|
|
|
|
|
const COLOR_DERIVING_LABEL: Color = Color("#667788")
|
|
|
|
|
|
|
|
|
|
## D-243: 2,048 m per district side.
|
|
|
|
|
const DISTRICT_M: float = 2048.0
|
|
|
|
|
|
|
|
|
@@ -135,38 +134,33 @@ var _held_center: Vector2i = Vector2i.ZERO
|
|
|
|
|
var _held_n: int = 32
|
|
|
|
|
## T-1153: the granularity_v2 tag this viewer is currently HOLDING (the
|
|
|
|
|
## last-adopted _window's own rung) — distinct from
|
|
|
|
|
## _window_request.get_granularity_v2() (most recently REQUESTED, may be a
|
|
|
|
|
## different rung already in flight). Defaults to District (see enter()'s doc).
|
|
|
|
|
## _window_request.get_granularity_v2() (most recently REQUESTED, may
|
|
|
|
|
## already be in flight to a different rung). Defaults to District.
|
|
|
|
|
var _held_granularity_v2: String = "District"
|
|
|
|
|
|
|
|
|
|
# ── Pan/zoom state ─────────────────────────────────────────────────────────
|
|
|
|
|
var _view_offset: Vector2 = Vector2.ZERO
|
|
|
|
|
var _view_zoom: float = 1.0
|
|
|
|
|
# T-1145 item 2: last known LOCAL mouse position (this Control's coordinate
|
|
|
|
|
# space), tracked from _gui_input's motion events for the edge-scroll check
|
|
|
|
|
# in _process() — _process() has no InputEvent of its own to read a position
|
|
|
|
|
# from, so the position has to be cached from the last motion event we DID
|
|
|
|
|
# see. Starts at -ONE (an impossible in-bounds position) so edge-scroll never
|
|
|
|
|
# fires before the mouse has ever moved over this Control at least once.
|
|
|
|
|
# T-1145 item 2: last known LOCAL mouse position, tracked from _gui_input's
|
|
|
|
|
# motion events for the edge-scroll check in _process() (which has no
|
|
|
|
|
# InputEvent of its own to read a position from). Starts at -ONE (an
|
|
|
|
|
# impossible in-bounds position) so edge-scroll never fires before the mouse
|
|
|
|
|
# has ever moved over this Control at least once.
|
|
|
|
|
var _last_mouse_pos: Vector2 = Vector2(-1.0, -1.0)
|
|
|
|
|
# T-1145 item 2: whether the OS application window currently has focus —
|
|
|
|
|
# edge-scroll is suppressed while false (see _is_cursor_edge_scrolling()'s
|
|
|
|
|
# doc). Defaults true: a freshly-entered screen assumes focus until told
|
|
|
|
|
# otherwise by NOTIFICATION_APPLICATION_FOCUS_OUT (matches the game's own
|
|
|
|
|
# window normally having focus when the player is actively navigating the
|
|
|
|
|
# implant in the first place).
|
|
|
|
|
# otherwise by NOTIFICATION_APPLICATION_FOCUS_OUT.
|
|
|
|
|
var _app_has_focus: bool = true
|
|
|
|
|
# T-1142/T-1145: true once the user has manually panned (WASD/edge-scroll,
|
|
|
|
|
# T-1145) or zoomed since the last enter()/fit — auto-fit (enter, first
|
|
|
|
|
# window arrival, resize) only re-fits BEFORE this flips, so it never fights
|
|
|
|
|
# a player mid-interaction. Reset to
|
|
|
|
|
# false on every enter() (a fresh descent always starts fitted).
|
|
|
|
|
# T-1142/T-1145: true once the user has manually panned or zoomed since the
|
|
|
|
|
# last enter()/fit — auto-fit (enter, first window arrival, resize) only
|
|
|
|
|
# re-fits BEFORE this flips, so it never fights a player mid-interaction.
|
|
|
|
|
# Reset to false on every enter() (a fresh descent always starts fitted).
|
|
|
|
|
var _user_adjusted: bool = false
|
|
|
|
|
# T-1142: true from enter() until the FIRST _on_window_ready() fires (the
|
|
|
|
|
# actual composite's arrival re-fits once, in case the entry-time fit used a
|
|
|
|
|
# not-yet-final viewport size) — false after that first arrival, so LATER
|
|
|
|
|
# pan-triggered window arrivals never re-fit on their own (only entry/first-
|
|
|
|
|
# arrival/resize do, per the ticket's three named events).
|
|
|
|
|
# composite's arrival re-fits once, in case entry used a not-yet-final
|
|
|
|
|
# viewport size) — false after, so LATER pan-triggered arrivals never re-fit
|
|
|
|
|
# on their own (only entry/first-arrival/resize do).
|
|
|
|
|
var _awaiting_first_window: bool = true
|
|
|
|
|
|
|
|
|
|
# ── Overlay visibility ─────────────────────────────────────────────────────
|
|
|
|
@@ -182,9 +176,8 @@ var _window_request = null # AtlasWindowRequest
|
|
|
|
|
var _tile_set = null # AtlasWindowTileSet (T-1153, live round 3)
|
|
|
|
|
|
|
|
|
|
## T-1153 (design doc §4): true while showing the orbital rest state as a
|
|
|
|
|
## MULTI-WINDOW MOSAIC (AtlasWindowTileSet) instead of the single held
|
|
|
|
|
## composite (`_window`). Set by `_enter_tile_mode()`; cleared the moment
|
|
|
|
|
## `_maybe_reselect_rung()` crosses OUT of Region.
|
|
|
|
|
## MULTI-WINDOW MOSAIC instead of the single held composite (`_window`). Set
|
|
|
|
|
## by `_enter_tile_mode()`; cleared when `_maybe_reselect_rung()` leaves Region.
|
|
|
|
|
var _tile_mode: bool = false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -257,12 +250,10 @@ func enter(
|
|
|
|
|
## body, then wheel-zoom descends CONTINUOUSLY from there. Canonical origin
|
|
|
|
|
## = district (0,0), same quantity is_fully_zoomed_out()/
|
|
|
|
|
## _maybe_reset_to_canonical_frame() test against. No-radius bodies fall
|
|
|
|
|
## back to the District-rung default window.
|
|
|
|
|
##
|
|
|
|
|
## **Live round 3 (design doc §4): the rest state must TILE.** A single
|
|
|
|
|
## wire-capped Region window covers only a fraction of a real body's
|
|
|
|
|
## circumference. Once `compute_tile_grid()` returns MORE than one tile,
|
|
|
|
|
## entry goes through `_enter_tile_mode()` instead of `_enter_at_rung()`.
|
|
|
|
|
## back to the District-rung default window. Live round 3 (design doc §4):
|
|
|
|
|
## the rest state must TILE — a single wire-capped Region window covers only
|
|
|
|
|
## a fraction of a real body's circumference, so once compute_tile_grid()
|
|
|
|
|
## returns MORE than one tile, entry goes through _enter_tile_mode() instead.
|
|
|
|
|
func enter_orbital(body: Dictionary, system: Dictionary) -> void:
|
|
|
|
|
var radius_km: float = float(body.get("body_radius_km", 0.0))
|
|
|
|
|
if radius_km <= 0.0:
|
|
|
|
@@ -342,10 +333,10 @@ func _enter_at_rung(
|
|
|
|
|
_overlay_node.queue_redraw()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## T-1142: fit-and-center — applies AtlasWindowGeometry.fit_window_view()'s
|
|
|
|
|
## zoom/offset, then re-clamps the offset to the pole wall. Called from
|
|
|
|
|
## enter(), the FIRST _on_window_ready() after entry, and
|
|
|
|
|
## NOTIFICATION_RESIZED — never mid-interaction (guarded by _user_adjusted).
|
|
|
|
|
## T-1142: fit-and-center — applies fit_window_view()'s zoom/offset, then
|
|
|
|
|
## re-clamps to the pole wall. Called from enter(), the FIRST
|
|
|
|
|
## _on_window_ready() after entry, and NOTIFICATION_RESIZED — never
|
|
|
|
|
## mid-interaction (guarded by _user_adjusted).
|
|
|
|
|
func _fit_and_center() -> void:
|
|
|
|
|
var viewport: Vector2 = get_rect().size
|
|
|
|
|
if viewport == Vector2.ZERO:
|
|
|
|
@@ -425,9 +416,8 @@ func get_body_id() -> String:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## District-cell pixel size at zoom=1.0 — AtlasWindowOverlay reads this
|
|
|
|
|
## rather than hardcoding CELL_PIXEL_SIZE itself, so the viewer stays the
|
|
|
|
|
## single source of geometry truth (same "viewer owns the transform, overlay
|
|
|
|
|
## only draws" split as AtlasViewer/AtlasMarkerOverlay).
|
|
|
|
|
## rather than hardcoding CELL_PIXEL_SIZE, so the viewer stays the single
|
|
|
|
|
## source of geometry truth ("viewer owns the transform, overlay only draws").
|
|
|
|
|
func get_cell_pixel_size() -> float:
|
|
|
|
|
return CELL_PIXEL_SIZE
|
|
|
|
|
|
|
|
|
@@ -464,14 +454,13 @@ func _on_atlas_layers_received(response: Dictionary) -> void:
|
|
|
|
|
## because `_window` only ever gets REPLACED, never nulled, once adopted
|
|
|
|
|
## (enter()/_enter_at_rung() null it only at a fresh descent, not a swap).
|
|
|
|
|
func _on_window_ready(window: Dictionary) -> void:
|
|
|
|
|
# Only adopt the window if it still matches what THIS viewer is currently
|
|
|
|
|
# showing — AtlasWindowRequest already filtered by its own last-asked
|
|
|
|
|
# (center, n, granularity_v2) via the echo (§2/T-1150/T-1152), but a
|
|
|
|
|
# cache-hit path can fire synchronously from enter() before _held_center
|
|
|
|
|
# reflects a re-entrant call, so comparing again here removes any
|
|
|
|
|
# ordering assumption. granularity_v2 (T-1153) is compared too — a
|
|
|
|
|
# district response answering a request since moved on to Region (rapid
|
|
|
|
|
# wheel-zoom) must not be adopted just because center/n still match.
|
|
|
|
|
# Only adopt if it still matches what THIS viewer is currently showing —
|
|
|
|
|
# AtlasWindowRequest already filtered by its own last-asked echo (§2/
|
|
|
|
|
# T-1150/T-1152), but a cache-hit path can fire synchronously from
|
|
|
|
|
# enter() before _held_center reflects a re-entrant call, so comparing
|
|
|
|
|
# again removes any ordering assumption. granularity_v2 is compared too
|
|
|
|
|
# — a district response answering a request since moved on to Region
|
|
|
|
|
# (rapid wheel-zoom) must not be adopted just because center/n match.
|
|
|
|
|
var w_center := _vec_from_center(window.get("center", [0, 0]))
|
|
|
|
|
var w_n := int(window.get("n", 0))
|
|
|
|
|
var w_granularity_v2 := str(
|
|
|
|
@@ -514,10 +503,10 @@ func _on_tile_ready(_index: int) -> void:
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# View transform (mirrors AtlasViewer's own — pan is real; zoom is CURSOR-
|
|
|
|
|
# ANCHORED and CONTINUOUS ACROSS RUNGS, D-226 T-1143-rulings: the held
|
|
|
|
|
# composite is always drawn client-side-zoomed with NO re-request, but
|
|
|
|
|
# crossing a rung's spacing threshold fires a background request at the new
|
|
|
|
|
# granularity (progressive refinement, see _maybe_reselect_rung()) while the
|
|
|
|
|
# OLD composite stays on screen.
|
|
|
|
|
# composite draws client-side-zoomed with NO re-request, but crossing a
|
|
|
|
|
# rung's spacing threshold fires a background request at the new granularity
|
|
|
|
|
# (progressive refinement, _maybe_reselect_rung()) while the OLD composite
|
|
|
|
|
# stays on screen.
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -533,10 +522,9 @@ func _apply_transform() -> void:
|
|
|
|
|
## safety clamp applies); after applying, checks the top rest state.
|
|
|
|
|
func _zoom_at(mouse_pos: Vector2, factor: float) -> void:
|
|
|
|
|
var new_zoom: float = clampf(_view_zoom * factor, MIN_ZOOM, MAX_ZOOM)
|
|
|
|
|
# Live round 6: once settled at the canonical frame, a continued zoom-OUT
|
|
|
|
|
# tick must not drift `_view_zoom` below fit (see the reset's own doc for
|
|
|
|
|
# why that caused a request storm). Clamping the ZOOM here — not the
|
|
|
|
|
# reset guard — keeps the reset edge-triggered. Zoom-IN is never clamped.
|
|
|
|
|
# Live round 6: once at the canonical frame, zoom-OUT must not drift
|
|
|
|
|
# below fit (see the reset's own doc — that caused a request storm).
|
|
|
|
|
# Clamping here, not the reset guard, keeps the reset edge-triggered.
|
|
|
|
|
if new_zoom < _view_zoom and _is_at_canonical_frame():
|
|
|
|
|
var fit_zoom: float = _canonical_fit_zoom()
|
|
|
|
|
new_zoom = maxf(new_zoom, fit_zoom)
|
|
|
|
@@ -562,12 +550,12 @@ func _current_world_extent_m() -> float:
|
|
|
|
|
## step, recompute the legal rung for the NOW-displayed world extent. If it
|
|
|
|
|
## differs from what's HELD, request the new granularity centered on the
|
|
|
|
|
## CURRENT screen-center (same formula as _maybe_refloat_window()). `_held_n`
|
|
|
|
|
## MUST be re-clamped via `_clamp_window_n_mirror_v2()` for the TARGET rung
|
|
|
|
|
## (C1 clamp-mirror, a third layer up) — a stale large `_held_n` desyncs
|
|
|
|
|
## `_on_window_ready()`'s staleness check. Does NOT touch
|
|
|
|
|
## `_window`/`_held_granularity_v2` — the OLD composite keeps drawing until
|
|
|
|
|
## _on_window_ready() adopts the new one (§6 "no mode flip"; live round 5:
|
|
|
|
|
## this lag is what made `_maybe_reset_to_canonical_frame()`'s OLD guard misfire).
|
|
|
|
|
## MUST be re-clamped for the TARGET rung (C1 clamp-mirror, a third layer
|
|
|
|
|
## up) — a stale large `_held_n` desyncs `_on_window_ready()`'s staleness
|
|
|
|
|
## check. Does NOT touch `_window`/`_held_granularity_v2` — the OLD
|
|
|
|
|
## composite keeps drawing until _on_window_ready() adopts the new one (§6
|
|
|
|
|
## "no mode flip"; this lag is what made the canonical-frame reset's OLD
|
|
|
|
|
## guard misfire, live round 5).
|
|
|
|
|
func _maybe_reselect_rung() -> void:
|
|
|
|
|
if _held_n <= 0:
|
|
|
|
|
return
|
|
|
|
@@ -655,20 +643,16 @@ func _is_at_canonical_frame() -> bool:
|
|
|
|
|
## Jeroen's HARD condition: "a full zoom-out resets to the original
|
|
|
|
|
## canonical planetary frame and location." EDGE-triggered (live round 6):
|
|
|
|
|
## fires only on the transition INTO fully-zoomed-out from non-canonical.
|
|
|
|
|
##
|
|
|
|
|
## **Live round 5 fix:** the old guard checked only
|
|
|
|
|
## `_held_center`/`_held_granularity_v2` (LAGGING, updated only on response
|
|
|
|
|
## adoption) — a TILING body's granularity stayed stale "Region" after
|
|
|
|
|
## zooming IN left tile mode, misreading "already canonical." Fixed by also
|
|
|
|
|
## requiring `is_tile_mode()` to match.
|
|
|
|
|
##
|
|
|
|
|
## **Live round 6 fix (round 5's fix overshot into a storm):** a per-tick
|
|
|
|
|
## zoom-equality check made this guard LEVEL-triggered — continued zoom-out
|
|
|
|
|
## kept nudging `_view_zoom` below fit, so `enter_orbital()` fired
|
|
|
|
|
## repeatedly: tile set torn down/recreated each time, orphaning in-flight
|
|
|
|
|
## responses (nothing held -> black), flooding the server (889/897 wire
|
|
|
|
|
## responses in one phase). Fixed by moving the zoom-drift concern to
|
|
|
|
|
## `_zoom_at()`'s own floor — this guard stays edge-triggered.
|
|
|
|
|
## Live round 5 fix: also requires is_tile_mode() to match (a TILING body's
|
|
|
|
|
## granularity stayed stale "Region" after zooming IN left tile mode,
|
|
|
|
|
## misreading "already canonical" from `_held_center`/`_held_granularity_v2`
|
|
|
|
|
## alone — LAGGING fields, updated only on response adoption). Live round 6
|
|
|
|
|
## fix: a per-tick zoom-equality check had made this LEVEL-triggered —
|
|
|
|
|
## continued zoom-out flooded the server (889/897 wire responses in one
|
|
|
|
|
## phase) via repeated enter_orbital() tearing down/rebuilding the tile set,
|
|
|
|
|
## orphaning in-flight responses (nothing held -> black). Fixed by moving
|
|
|
|
|
## the zoom-drift concern to `_zoom_at()`'s own floor instead — this guard
|
|
|
|
|
## stays edge-triggered.
|
|
|
|
|
func _maybe_reset_to_canonical_frame() -> bool:
|
|
|
|
|
var radius_km: float = float(_body.get("body_radius_km", 0.0))
|
|
|
|
|
if radius_km <= 0.0:
|
|
|
|
@@ -764,6 +748,8 @@ func _draw() -> void:
|
|
|
|
|
# T-1153: this viewer's own border-fade/pending-wash are single-window
|
|
|
|
|
# concepts (one extent, one state) — a mosaic's per-tile pending wash
|
|
|
|
|
# is drawn by AtlasWindowOverlay itself (see _draw_tile_mosaic()).
|
|
|
|
|
if _tile_set and not _tile_set.has_any_tile_arrived():
|
|
|
|
|
_draw_deriving_terrain_label()
|
|
|
|
|
return
|
|
|
|
|
if _window == null:
|
|
|
|
|
# §5 "what renders during the wait": a border-fade to the underlying
|
|
|
|
@@ -793,6 +779,26 @@ func _draw_pending_refinement_wash() -> void:
|
|
|
|
|
draw_rect(Rect2(top_left, Vector2(extent, extent)), COLOR_PENDING_REFINEMENT_WASH)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Cold-start dossier: the subtle per-tile COLOR_BORDER_FADE wash alone
|
|
|
|
|
## (AtlasWindowOverlay._draw_tile_mosaic()) read as broken darkness in a
|
|
|
|
|
## live cold capture (>10s, zero tiles landed), not loading. Drawn in SCREEN
|
|
|
|
|
## space (unlike the per-tile washes, which live in the panned/zoomed
|
|
|
|
|
## _canvas) so it always reads centered/legible regardless of zoom. Dropped
|
|
|
|
|
## the instant even one tile lands (has_any_tile_arrived()) — per-tile
|
|
|
|
|
## washes alone read fine once real content fills in around the gaps.
|
|
|
|
|
## Positioning math lives in AtlasWindowGeometry.centered_label_baseline()
|
|
|
|
|
## (file-length; a pure function either way).
|
|
|
|
|
func _draw_deriving_terrain_label() -> void:
|
|
|
|
|
var label := "DERIVING TERRAIN…"
|
|
|
|
|
var font := ThemeDB.fallback_font
|
|
|
|
|
var font_size := 20
|
|
|
|
|
var text_size: Vector2 = font.get_string_size(label, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size)
|
|
|
|
|
var baseline: Vector2 = AtlasWindowGeometry.centered_label_baseline(get_rect().size, text_size)
|
|
|
|
|
draw_string(
|
|
|
|
|
font, baseline, label, HORIZONTAL_ALIGNMENT_CENTER, -1, font_size, COLOR_DERIVING_LABEL
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _build_screen_header() -> void:
|
|
|
|
|
_screen_header = ImplantHeader.new()
|
|
|
|
|
_screen_header.position = Vector2(PANEL_MARGIN, 16.0)
|
|
|
|
@@ -830,10 +836,9 @@ func _location_label() -> String:
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## No city panel / sidebar in this mode (yet) — nothing to hit-test against,
|
|
|
|
|
## so this always reads false. Wired into _gui_input exactly where
|
|
|
|
|
## AtlasViewer's own _is_over_ui is, so a future sidebar addition only
|
|
|
|
|
## needs to change THIS function's body.
|
|
|
|
|
## No city panel / sidebar in this mode (yet) — always false. Wired into
|
|
|
|
|
## _gui_input exactly where AtlasViewer's own _is_over_ui is, so a future
|
|
|
|
|
## sidebar addition only needs to change THIS function's body.
|
|
|
|
|
func _is_over_ui(_pos: Vector2) -> bool:
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
@@ -894,10 +899,9 @@ func _process(delta: float) -> void:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## The actual pan-tick state mutation, given an ALREADY-DECIDED direction and
|
|
|
|
|
## this frame's delta — frame-rate independent, zoom-scaled
|
|
|
|
|
## (PAN_SPEED_CANVAS_PX_S * _view_zoom), pole-wall clamped (T-1142). Sets
|
|
|
|
|
## _user_adjusted and triggers the pan-edge refetch (§4). Split from
|
|
|
|
|
## _process() so a test can call it directly with a synthetic direction/delta.
|
|
|
|
|
## this frame's delta — frame-rate independent, zoom-scaled, pole-wall
|
|
|
|
|
## clamped (T-1142). Sets _user_adjusted and triggers the pan-edge refetch
|
|
|
|
|
## (§4). Split from _process() so a test can call it with a synthetic input.
|
|
|
|
|
func _apply_pan_delta(direction: Vector2, delta: float) -> void:
|
|
|
|
|
var normalized: Vector2 = direction.normalized() # diagonal isn't faster than a single axis
|
|
|
|
|
_user_adjusted = true
|
|
|
|
@@ -908,9 +912,8 @@ func _apply_pan_delta(direction: Vector2, delta: float) -> void:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## WASD + arrow keys, read via Input.is_key_pressed() on the PHYSICAL keycode
|
|
|
|
|
## (not an InputMap action) — see AtlasWindowGeometry.held_pan_direction()'s
|
|
|
|
|
## doc for the full W/S/A/D-vs-gameplay-movement rationale (moved there
|
|
|
|
|
## T-1153 for file-length/testability, unchanged behavior).
|
|
|
|
|
## — see AtlasWindowGeometry.held_pan_direction()'s own doc for the full
|
|
|
|
|
## rationale (moved there for file-length/testability).
|
|
|
|
|
func _held_pan_direction() -> Vector2:
|
|
|
|
|
return AtlasWindowGeometry.held_pan_direction()
|
|
|
|
|
|
|
|
|
|