T-1118: gen_region_grid overlay (label TMP) — _draw_gen_region_grid copies _draw_gen_district's self-contained mapping (dims from the layer dict); mean-temp cold-to-hot ramp over -50..+50 C; REGION_TEMP_NONE_DC airless sentinel = skip-cell (undrawn, never an invented color); legend entry. T-1119 (touch points 3-6): quarter_footprints protocol passthrough; gen_l4_quarters overlay (label QTR) — density-scaled glyph anchored on the L3 settlement dot joined by city_id, shape = dominant district type (corner-tab/diamond marks for Commercial/Industrial/ Administrative), color = density ramp on the settlement-gold family, zoom-gated at SETTLEMENT_LABEL_MIN_ZOOM; landmark/corridor counts never drawn (D-226(d) tooltip-only ceiling); legend entry. Structure: atlas_viewer.gd and protocol.gd were over gdlint's 1000-line cap before this batch; cleanly-separable responsibilities extracted on existing precedent — atlas_generation_state.gd (per-layer data + accessors), atlas_generation_proxy.gd (polling/retry/pending machinery), atlas_overlay_colors.gd (pure ramp/shape lookups), atlas_map_protocol.gd (atlas/starmap/citynames codec, the browse_protocol.gd delegate pattern). Public APIs preserved exactly; _gen_state stays a field default (RefCounted, pre-_ready safe) because _ready()-construction breaks every bare AtlasViewer.new() test — documented inline. Tests: registration + round-trip for both overlays; pure-function suites for the temp ramp (endpoints/midpoint/clamp/sentinel) and quarter glyph (scaling, zoom gate, ramp, notch across all 9 DistrictTypes); Tier-2 replay asserts exact literals from the real server-generated fixture incl. the airless sentinel. Color.lerp(a,b, 1.0) is not bit-exact to b — endpoint assertions use per-component is_equal_approx. Full suite 3094/3094; gdlint zero warnings incl. the two previously-over-cap files. Live capture: legend grows to 7 sections, TMP/QTR toggles clean against a live server. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
127 lines
4.6 KiB
GDScript
127 lines
4.6 KiB
GDScript
extends Node
|
|
|
|
## Generation-cascade layer-stream proxy client for AtlasViewer (#960, D-225).
|
|
## Extracted from atlas_viewer.gd (T-1118) to keep that file under the gdlint
|
|
## max-file-lines cap — this is the whole "poll the proxy, retry on Pending,
|
|
## show the diegetic GENERATING indicator, dispatch each layer field to
|
|
## AtlasGenerationState on Ready" responsibility, self-contained apart from
|
|
## reading the owning viewer's `_body`/`size` and calling its
|
|
## set_generation_*() accessors.
|
|
##
|
|
## This script has no `class_name` on purpose, matching atlas_overlay_bar.gd/
|
|
## atlas_legend_panel.gd (review #8 there): the owner (AtlasViewer) passes the
|
|
## viewer reference to _init(), and a `class_name` + required-arg _init()
|
|
## combo is a Godot editor footgun. `extends Node` (not RefCounted) because
|
|
## this owns a child Control (the pending indicator) and needs get_tree() for
|
|
## the retry timer — added as a child via
|
|
## load("res://ui/implant/apps/atlas/atlas_generation_proxy.gd").new(self).
|
|
|
|
# #960: Layer-1 proxy re-poll. The proxy returns Pending on a cache miss and
|
|
# generates in the background (D-225); the client re-requests until Ready.
|
|
const GEN_RETRY_DELAY: float = 0.5
|
|
const GEN_MAX_RETRIES: int = 20 # ~10s ceiling before giving up
|
|
|
|
var _viewer = null # AtlasViewer (untyped to avoid cyclic ref)
|
|
var _pending: bool = false # awaiting a Layer1 response (re-polls on Pending)
|
|
var _retries: int = 0
|
|
var _indicator = null # ImplantPending — loaded by path, not a class_name dep
|
|
|
|
|
|
func _init(viewer_ref = null) -> void:
|
|
_viewer = viewer_ref
|
|
|
|
|
|
func _ready() -> void:
|
|
if _viewer == null:
|
|
return
|
|
# Loaded by path (not `ImplantPending.new()`) so a stale global-class cache
|
|
# — e.g. a running session that hasn't re-imported after this class was
|
|
# added — can't fail to parse AtlasViewer and break the atlas from opening
|
|
# (#960).
|
|
_indicator = load("res://ui/implant/implant_pending.gd").new()
|
|
_indicator.name = "GenPending"
|
|
_viewer.add_child(_indicator)
|
|
_indicator.apply_implant_theme(_viewer.get_implant_theme())
|
|
_viewer.resized.connect(reposition_indicator)
|
|
reposition_indicator()
|
|
|
|
|
|
## Reset polling state for a fresh show_body() call — the caller is
|
|
## responsible for also resetting the layer data itself
|
|
## (AtlasGenerationState.reset_layer1()).
|
|
func reset() -> void:
|
|
_pending = false
|
|
_retries = 0
|
|
if _indicator:
|
|
_indicator.stop()
|
|
|
|
|
|
## Request the body's Layer-1 cascade output from the server proxy. No-op in
|
|
## test mode (SimBridge has no server connection) — the overlays simply stay
|
|
## empty, which is the correct serverless behavior.
|
|
func request(body_id: String) -> void:
|
|
if body_id.is_empty():
|
|
return
|
|
_pending = true
|
|
SimBridge.request_atlas_layers(body_id)
|
|
|
|
|
|
## Handle a Layer-1 response. Ignores responses for a stale body (the user
|
|
## navigated away). On Pending the proxy is still generating, so we re-request
|
|
## after a short delay until Ready or the retry ceiling.
|
|
func on_response(response: Dictionary, current_body_id: String) -> void:
|
|
if str(response.get("body_id", "")) != current_body_id:
|
|
return
|
|
match str(response.get("status", "")):
|
|
"Ready":
|
|
_pending = false
|
|
_retries = 0
|
|
_set_indicator(false)
|
|
_viewer.set_generation_layer1(response.get("layer1"))
|
|
_viewer.set_generation_district_grid(response.get("district_grid"))
|
|
_viewer.set_generation_road_graph(response.get("road_graph"))
|
|
_viewer.set_generation_settlements(response.get("settlements"))
|
|
_viewer.set_generation_region_grid(response.get("region_grid"))
|
|
_viewer.set_generation_quarter_footprints(response.get("quarter_footprints"))
|
|
"Pending":
|
|
if _retries < GEN_MAX_RETRIES:
|
|
_retries += 1
|
|
_set_indicator(true)
|
|
_schedule_retry(current_body_id)
|
|
else:
|
|
_pending = false # gave up — overlays stay empty
|
|
_set_indicator(false)
|
|
_:
|
|
_pending = false # NotFound / Error — nothing to draw
|
|
_set_indicator(false)
|
|
|
|
|
|
func _schedule_retry(body_id: String) -> void:
|
|
var timer := get_tree().create_timer(GEN_RETRY_DELAY)
|
|
timer.timeout.connect(
|
|
func() -> void:
|
|
# Re-request only if still on the same body and still waiting.
|
|
if _pending and _viewer.get_body_id() == body_id:
|
|
SimBridge.request_atlas_layers(body_id)
|
|
)
|
|
|
|
|
|
## Show/hide the diegetic pending indicator. Starting only when not already
|
|
## visible avoids resetting the sweep on every re-poll.
|
|
func _set_indicator(on: bool) -> void:
|
|
if _indicator == null:
|
|
return
|
|
if on:
|
|
if not _indicator.visible:
|
|
reposition_indicator()
|
|
_indicator.start("GENERATING LAYER 1")
|
|
else:
|
|
_indicator.stop()
|
|
|
|
|
|
func reposition_indicator() -> void:
|
|
if _indicator == null or _viewer == null:
|
|
return
|
|
var sz: Vector2 = _viewer.size
|
|
_indicator.position = Vector2((sz.x - _indicator.DEFAULT_WIDTH) * 0.5, sz.y * 0.45)
|