Files
settled-reach/client/ui/implant/apps/atlas/atlas_window_cache.gd
T
jpmschweitzerandClaude Fable 5 9e1e6db614 feat(ui): T-1138 regional map screen — click-through descent, fixed planetary view, windowed composite (D-226 T-1124 SS5)
Entry per Jeroen's 2026-07-21 revision: planetary heightmap is now
FIXED — all drag-pan/wheel-zoom input removed (set_view/get_view_*
capture API survives for the golden harness); hover shows a
not-to-scale bracket reticle with the real extent labeled (a true
n=32 rectangle is sub-pixel on the planetary canvas — the honest
representation given the morph transition is deferred), and a click
that misses every city marker descends (city-click wins — one
gesture, two contextual reads, no modifier). Descent pushes a new
'district' nav screen centered on the click point's DistrictPos via
atlas_descend_geometry.district_pos_at (the pixel-to-district inverse
of the server mapping, verified against scale.rs).

Regional mode: atlas_window_viewer draws the composite (morphology x
elev_q lightness base; temp/moisture/veg toggles — temp reuses the
region-ramp colorizer exactly; Marine=6 transparent; glaciation
always-on tint matching apply_ice_tint's REAL gate, None|Light no-op,
over the amendment's looser prose — documented); pan-on-held-composite
with edge-crossing refetch + border-fade during the queue-based
derive wait; zoom never refetches. atlas_window_cache: LRU keyed
(body_id, center, n), touch-on-read, evict-only, no freshness (D-227).
atlas_window_request mirrors the generation-proxy pending-retry shape
for None-until-derived. Codec: window params omitted from the wire
when absent — byte-identical for every existing caller.

Live-verified against the T-1137 server in-worktree: real round-trip
on a GJ380c coastal district (6 fields x 1024 cells), echo staleness
guard, genuine ~1.4s background-derive wait, pan-edge refetch to an
adjacent window, cache-hit on re-descent with zero network. Full
client suite 3194/3194; gdlint clean on all 18 files.

Open follow-ups flagged in-code: header location label always falls
back to coordinates (nearest-settlement needs a join the district
window does not carry); atlas_standalone.gd's 'atlas_app.gd is never
modified' doc line is now imprecise.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 13:32:06 +02:00

80 lines
3.1 KiB
GDScript

extends RefCounted
## Client-side LRU cache for DistrictWindowLayer responses (T-1138, D-226
## T-1124 amendment §4 "Client cache policy").
##
## Keyed on (body_id, center, n) — D-227's determinism guarantee (same seed +
## body + position -> same derived output, always) means a previously-fetched
## window is valid FOREVER for that body+seed. This is an LRU-evict-only
## cache: no freshness check, no TTL, no invalidation path at all. The only
## reason an entry ever leaves is capacity pressure.
##
## Godot's Dictionary preserves insertion order, so "move to the end on
## touch, evict from the front on overflow" is the whole LRU implementation —
## no separate linked-list/counter bookkeeping needed.
##
## Consumed via explicit load() by path (no class_name), matching
## atlas_overlay_bar.gd/atlas_legend_panel.gd (review #8 precedent
## elsewhere in this app): the owner constructs one instance and holds it,
## same shape as those two.
const DEFAULT_MAX_ENTRIES: int = 24
var _max_entries: int = DEFAULT_MAX_ENTRIES
var _entries: Dictionary = {} # key String -> DistrictWindowLayer Dictionary
func _init(max_entries: int = DEFAULT_MAX_ENTRIES) -> void:
_max_entries = maxi(1, max_entries)
## Build the cache key from the three fields D-227 makes sufficient:
## body_id (which world+body), center (a [row, col] pair or Vector2i), and n
## (window side length). String-keyed rather than a nested Dictionary/Array
## key — Godot Dictionary keys compare by value for primitives but a
## consistent stringification sidesteps any Vector2i-vs-Array identity
## mismatch between what a caller happens to hand in.
static func make_key(body_id: String, center: Vector2i, n: int) -> String:
return "%s:%d,%d:%d" % [body_id, center.x, center.y, n]
## True if a window is already cached for this exact (body, center, n).
func has(body_id: String, center: Vector2i, n: int) -> bool:
return _entries.has(make_key(body_id, center, n))
## Fetch a cached window, touching it (move-to-most-recently-used). Returns
## null on a miss — callers must not confuse this with a real "None" server
## response, which is a different concept (§1: an as-yet-underived window is
## carried as `district_window: None` inside a `Ready` AtlasLayerResponse,
## not a cache state).
func get_window(body_id: String, center: Vector2i, n: int) -> Variant:
var key := make_key(body_id, center, n)
if not _entries.has(key):
return null
var value: Variant = _entries[key]
# Touch: erase + re-insert moves the key to the end (most-recently-used).
_entries.erase(key)
_entries[key] = value
return value
## Store a window, evicting the least-recently-used entry(ies) if over
## capacity. Overwriting an existing key also counts as a touch.
func put(body_id: String, center: Vector2i, n: int, window: Dictionary) -> void:
var key := make_key(body_id, center, n)
if _entries.has(key):
_entries.erase(key)
_entries[key] = window
while _entries.size() > _max_entries:
var oldest_key: String = _entries.keys()[0]
_entries.erase(oldest_key)
func size() -> int:
return _entries.size()
func clear() -> void:
_entries.clear()