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>
186 lines
6.4 KiB
GDScript
186 lines
6.4 KiB
GDScript
class_name AtlasApp
|
|
extends ImplantApp
|
|
## Atlas implant app (#844, #836, D-191).
|
|
## Reach map → system orbital → planet entry → regional zoom ladder.
|
|
## Registered as "implant/map" in FULLSCREEN mode.
|
|
##
|
|
## T-1153 (D-226 T-1143-rulings amendment): the "district" nav-stack screen
|
|
## (T-1138's windowed drill-down, a separate nav.push() hop from "regional")
|
|
## RETIRES as a nav-stack level — the continuous cursor-anchored zoom ladder
|
|
## means descent/ascent through every rung (Region -> District -> Quarter)
|
|
## happens INSIDE the "regional" screen via zoom, not by pushing a new
|
|
## screen. D-013 "the zoom gesture owns spatial descent" is restored for
|
|
## this seam (Jeroen's ruling) — descent is a continuous gesture, not a
|
|
## discrete nav hop. Esc from anywhere in the ladder is therefore a single
|
|
## nav.pop() back to "system", exactly the same _handle_key() KEY_ESCAPE
|
|
## branch every other non-"reach" screen already uses; there is no longer a
|
|
## "district" screen entry in the match/registration table.
|
|
|
|
signal economics_link_requested(system_id: String)
|
|
|
|
var _systems: Array = []
|
|
var _system_lookup: Dictionary = {} # system_id → system dict
|
|
|
|
var _reach_screen = null # ReachScreen
|
|
var _system_screen = null # SystemScreen
|
|
var _planet_screen = null # PlanetScreen
|
|
var _regional_screen = null # RegionalScreen — now the whole zoom ladder (T-1153)
|
|
|
|
|
|
func _ready() -> void:
|
|
manifest = load("res://ui/implant/apps/atlas/app.tres")
|
|
super._ready()
|
|
|
|
|
|
func on_install() -> void:
|
|
_load_system_data()
|
|
# T-949: the star map now arrives over the bridge, possibly after this app
|
|
# is already installed (or before the handshake completes at all) — refresh
|
|
# once it lands instead of assuming _load_system_data's first call has data.
|
|
SimBridge.star_map_received.connect(_on_star_map_received)
|
|
var implant_theme = load("res://ui/implant/default_implant.tres")
|
|
|
|
_reach_screen = ReachScreen.new()
|
|
_reach_screen.setup(implant_theme)
|
|
_reach_screen.set_systems(_systems, _system_lookup)
|
|
_reach_screen.system_selected.connect(_on_system_selected)
|
|
register_screen("reach", _reach_screen)
|
|
|
|
_system_screen = SystemScreen.new()
|
|
_system_screen.setup(implant_theme)
|
|
_system_screen.set_systems(_systems)
|
|
_system_screen.body_selected.connect(_on_body_selected)
|
|
register_screen("system", _system_screen)
|
|
|
|
_planet_screen = PlanetScreen.new()
|
|
_planet_screen.setup(implant_theme)
|
|
register_screen("planet", _planet_screen)
|
|
|
|
_regional_screen = RegionalScreen.new()
|
|
_regional_screen.back_requested.connect(_on_regional_back)
|
|
register_screen("regional", _regional_screen)
|
|
|
|
nav.set_default("reach")
|
|
|
|
|
|
func on_open(_mode: int) -> void:
|
|
# Base class handles nav.push_default() on first open.
|
|
if current_screen_id() == "reach" and _reach_screen:
|
|
_reach_screen.refresh_info_panel_visibility()
|
|
|
|
|
|
func _unhandled_key_input(event: InputEvent) -> void:
|
|
if not event is InputEventKey:
|
|
return
|
|
if manifest == null or not HudGroups.is_app_active(manifest.app_path):
|
|
return
|
|
if not event.is_pressed() or event.is_echo():
|
|
return
|
|
# "regional" (the whole stepped zoom ladder, StepCanvasViewer, T-1182)
|
|
# handles its own Esc via _gui_input — a stray M/other unhandled key on
|
|
# that screen must not ALSO fire this app's own _handle_key underneath
|
|
# the viewer's own handling.
|
|
if current_screen_id() == "regional":
|
|
return
|
|
_handle_key(event as InputEventKey)
|
|
get_viewport().set_input_as_handled()
|
|
|
|
|
|
func _handle_key(event: InputEventKey) -> void:
|
|
match event.physical_keycode:
|
|
KEY_M:
|
|
HudGroups.close_app()
|
|
KEY_ESCAPE:
|
|
var has_open_panel: bool = _system_screen and (
|
|
_system_screen.has_body_panel_open() or _system_screen.has_station_panel_open()
|
|
)
|
|
if current_screen_id() == "system" and has_open_panel:
|
|
_system_screen.close_panels()
|
|
elif current_screen_id() == "reach":
|
|
HudGroups.close_app()
|
|
else:
|
|
nav.pop()
|
|
KEY_ENTER, KEY_KP_ENTER:
|
|
_handle_enter()
|
|
KEY_BRACKETLEFT:
|
|
if current_screen_id() == "system" and _system_screen:
|
|
_system_screen.navigate_system(-1)
|
|
KEY_BRACKETRIGHT:
|
|
if current_screen_id() == "system" and _system_screen:
|
|
_system_screen.navigate_system(1)
|
|
|
|
|
|
func _handle_enter() -> void:
|
|
match current_screen_id():
|
|
"reach":
|
|
if _reach_screen and _reach_screen.has_selection():
|
|
_reach_screen.trigger_enter()
|
|
"system":
|
|
if _system_screen and not _system_screen.is_in_orbital():
|
|
var sys: Dictionary = _system_screen.current_system()
|
|
nav.replace("system", {"mode": "orbital", "system": sys})
|
|
elif _system_screen and _system_screen.has_body_panel_open():
|
|
var body: Dictionary = _system_screen.get_selected_body()
|
|
if body.get("terrain_reference") != null:
|
|
nav.push("regional", {
|
|
"body": body,
|
|
"system": nav.current_payload().get("system", {}),
|
|
})
|
|
|
|
|
|
# =============================================================================
|
|
# Signal handlers
|
|
# =============================================================================
|
|
|
|
|
|
func _on_system_selected(system_id: String) -> void:
|
|
var idx: int = _system_idx_by_id(system_id)
|
|
if _system_screen:
|
|
_system_screen.set_selected_idx(idx)
|
|
var system: Dictionary = _system_lookup.get(system_id, {"system_id": system_id})
|
|
nav.push("system", {"mode": "orbital", "system": system})
|
|
|
|
|
|
func _on_body_selected(body: Dictionary) -> void:
|
|
nav.push("regional", {
|
|
"body": body,
|
|
"system": nav.current_payload().get("system", {}),
|
|
})
|
|
|
|
|
|
func _on_regional_back() -> void:
|
|
nav.pop()
|
|
|
|
|
|
## T-949: the star map arrived — cache it, rebuild the local system
|
|
## list/lookup, and push it into whichever screens already exist. set_systems()
|
|
## is safe to call again after initial setup (ReachScreen/SystemScreen both
|
|
## just recompute their layout from the new data).
|
|
func _on_star_map_received(response: Dictionary) -> void:
|
|
SystemIndex.ingest(response)
|
|
_load_system_data()
|
|
if _reach_screen:
|
|
_reach_screen.set_systems(_systems, _system_lookup)
|
|
if _system_screen:
|
|
_system_screen.set_systems(_systems)
|
|
|
|
|
|
# =============================================================================
|
|
# Helpers
|
|
# =============================================================================
|
|
|
|
|
|
func _system_idx_by_id(system_id: String) -> int:
|
|
for i: int in range(_systems.size()):
|
|
if _systems[i].get("system_id", "") == system_id:
|
|
return i
|
|
return 0
|
|
|
|
|
|
func _load_system_data() -> void:
|
|
SystemIndex.request_refresh()
|
|
_systems = SystemIndex.get_sorted_systems()
|
|
_system_lookup.clear()
|
|
for node: Dictionary in _systems:
|
|
_system_lookup[node.get("system_id", "")] = node
|