Files
settled-reach/client/tests/test_atlas_window_cache.gd
jpmschweitzerandClaude Fable 5 8da9670e0f feat(simulation): feature-name pipeline wired + legacy window_granularity u32 retired (T-1169, T-1159)
One commit for two tickets whose changes share the bridge/plugin
plumbing files. T-1169 connects the three dormant feature-name pieces:
atlas_feature_names populated at regen (17,891 rows — 15,190 mountain,
2,701 river — via populate_atlas_feature_names mirroring the city-names
importer; systems.db regenerated, stamp fresh), attach_feature_names
wired into the cascade's Topography block with name pools threaded
DB-free through AnalyzeBody (D-225 pattern) and assignments stored on
Layer1Output/BodyWorldState for future consumers, and a
FeatureNamesRequest/Response read proxy as the bridge's 7th tagged
envelope (D-236 pattern, both SimBridge impls). Client label DRAW is
deliberately NOT here — implementation proved both river and mountain
labels need a wire-carried position (the pool is position-free; course
polylines aren't correlated with the named attractors by construction) —
deferred to T-1195's single design pass. cascade_layer1 golden re-pinned
(additive feature_names field).

T-1159 retires the legacy u32 granularity field fully shadowed by
window_granularity_v2: AtlasLayerRequest.window_granularity,
DistrictWindowLayer.granularity echo, the u32::MAX sentinel, and
resolve_window_granularity are gone server-side; client encode paths and
the caller-less atlas_window_cache legacy key component dropped;
msgpack fixtures regenerated; the T-1150 aliasing regression test now
drives through the surviving enum field. The district_window carrier
itself survives byte-compatible per D-255(c).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 16:10:27 +02:00

200 lines
9.2 KiB
GDScript

## T-1138 (D-226 T-1124 amendment §4): tests for the client-side
## DistrictWindowLayer LRU cache — keyed on (body_id, center, n), LRU-evict
## only (D-227 determinism means no freshness check is ever needed).
class_name TestAtlasWindowCache
extends GdUnitTestSuite
const AtlasWindowCache := preload("res://ui/implant/apps/atlas/atlas_window_cache.gd")
func test_make_key_distinguishes_body_center_and_n() -> void:
var k1 := AtlasWindowCache.make_key("GJ1c", Vector2i(10, 20), 32)
var k2 := AtlasWindowCache.make_key("GJ1d", Vector2i(10, 20), 32) # different body
var k3 := AtlasWindowCache.make_key("GJ1c", Vector2i(11, 20), 32) # different center
var k4 := AtlasWindowCache.make_key("GJ1c", Vector2i(10, 20), 64) # different n
assert_str(k1).is_not_equal(k2)
assert_str(k1).is_not_equal(k3)
assert_str(k1).is_not_equal(k4)
func test_miss_returns_null_and_has_reports_false() -> void:
var cache := AtlasWindowCache.new()
assert_that(cache.get_window("GJ1c", Vector2i(0, 0), 32)).is_null()
assert_bool(cache.has("GJ1c", Vector2i(0, 0), 32)).is_false()
func test_put_then_get_round_trips_exact_window() -> void:
var cache := AtlasWindowCache.new()
var window := {"center": [10, 20], "n": 32, "morphology": PackedByteArray([1, 2, 3])}
cache.put("GJ1c", Vector2i(10, 20), 32, window)
assert_bool(cache.has("GJ1c", Vector2i(10, 20), 32)).is_true()
assert_that(cache.get_window("GJ1c", Vector2i(10, 20), 32)).is_equal(window)
## D-227: a window fetched once is valid FOREVER for that (body, center, n) —
## no expiry, no invalidation path. Repeated get_window() calls across a
## simulated time gap (just repeated calls here — there is no clock in this
## cache at all) must keep returning the same stored value.
func test_cached_window_never_expires() -> void:
var cache := AtlasWindowCache.new()
var window := {"center": [0, 0], "n": 32}
cache.put("GJ1c", Vector2i(0, 0), 32, window)
for _i in range(50):
assert_that(cache.get_window("GJ1c", Vector2i(0, 0), 32)).is_equal(window)
## Different (body_id, center, n) tuples never collide — this is the whole
## point of the composite key (§4: "cacheable client-side keyed on
## (body_id, center, n)").
func test_different_bodies_same_center_do_not_collide() -> void:
var cache := AtlasWindowCache.new()
var window_a := {"body": "GJ1c"}
var window_b := {"body": "GJ1d"}
cache.put("GJ1c", Vector2i(10, 20), 32, window_a)
cache.put("GJ1d", Vector2i(10, 20), 32, window_b)
assert_that(cache.get_window("GJ1c", Vector2i(10, 20), 32)).is_equal(window_a)
assert_that(cache.get_window("GJ1d", Vector2i(10, 20), 32)).is_equal(window_b)
## Overwriting the same key replaces the value (e.g. a re-fetch of an
## already-cached window from a server that somehow returns a different
## payload — should never happen under D-227, but the cache itself must not
## silently keep the stale one on an explicit put()).
func test_put_overwrites_existing_key() -> void:
var cache := AtlasWindowCache.new()
cache.put("GJ1c", Vector2i(0, 0), 32, {"v": 1})
cache.put("GJ1c", Vector2i(0, 0), 32, {"v": 2})
assert_int(cache.size()).is_equal(1)
assert_that(cache.get_window("GJ1c", Vector2i(0, 0), 32)).is_equal({"v": 2})
# =============================================================================
# LRU eviction
# =============================================================================
func test_eviction_drops_least_recently_used_on_overflow() -> void:
var cache := AtlasWindowCache.new(2) # max 2 entries
cache.put("GJ1c", Vector2i(0, 0), 32, {"id": "a"})
cache.put("GJ1c", Vector2i(1, 0), 32, {"id": "b"})
cache.put("GJ1c", Vector2i(2, 0), 32, {"id": "c"}) # evicts (0,0) — oldest, untouched
assert_int(cache.size()).is_equal(2)
assert_bool(cache.has("GJ1c", Vector2i(0, 0), 32)).override_failure_message(
"oldest entry should have been evicted"
).is_false()
assert_bool(cache.has("GJ1c", Vector2i(1, 0), 32)).is_true()
assert_bool(cache.has("GJ1c", Vector2i(2, 0), 32)).is_true()
## get_window() touches an entry (moves it to most-recently-used) — reading an
## entry must protect it from the NEXT eviction, otherwise "LRU" degrades to
## FIFO the moment anything reads from the cache (the common case: Esc-then-
## re-enter and pan-back are exactly "read a recently-cached window", §4).
func test_get_touches_entry_and_protects_it_from_eviction() -> void:
var cache := AtlasWindowCache.new(2)
cache.put("GJ1c", Vector2i(0, 0), 32, {"id": "a"})
cache.put("GJ1c", Vector2i(1, 0), 32, {"id": "b"})
cache.get_window("GJ1c", Vector2i(0, 0), 32) # touch (0,0) — now most-recently-used
cache.put("GJ1c", Vector2i(2, 0), 32, {"id": "c"}) # should evict (1,0), not (0,0)
assert_bool(cache.has("GJ1c", Vector2i(0, 0), 32)).override_failure_message(
"touched entry should survive eviction"
).is_true()
assert_bool(cache.has("GJ1c", Vector2i(1, 0), 32)).override_failure_message(
"untouched entry should be the one evicted"
).is_false()
func test_max_entries_clamped_to_at_least_one() -> void:
var cache := AtlasWindowCache.new(0)
cache.put("GJ1c", Vector2i(0, 0), 32, {"id": "a"})
cache.put("GJ1c", Vector2i(1, 0), 32, {"id": "b"})
assert_int(cache.size()).is_equal(1)
func test_clear_empties_the_cache() -> void:
var cache := AtlasWindowCache.new()
cache.put("GJ1c", Vector2i(0, 0), 32, {"id": "a"})
cache.clear()
assert_int(cache.size()).is_equal(0)
assert_bool(cache.has("GJ1c", Vector2i(0, 0), 32)).is_false()
# =============================================================================
# min_wl_m (T-1150, zoom ladder design doc §3 aliasing risk)
# =============================================================================
## **MANDATORY aliasing regression (client half, T-1150):** two requests
## identical except for `min_wl_m` (the octave cutoff) must not collide —
## different cutoffs are different derived payloads (T-1149/T-1150).
func test_make_key_distinguishes_min_wl_m_at_identical_body_center_n() -> void:
var k_uncut := AtlasWindowCache.make_key("GJ1c", Vector2i(10, 20), 32, 0)
var k_cut := AtlasWindowCache.make_key("GJ1c", Vector2i(10, 20), 32, 512)
assert_str(k_uncut).is_not_equal(k_cut)
## Omitting min_wl_m (every pre-T-1150 call site) must produce the SAME key
## as passing the explicit no-cutoff default — byte/string compatibility for
## existing callers, not just "doesn't crash".
func test_omitted_min_wl_m_matches_explicit_default() -> void:
var k_omitted := AtlasWindowCache.make_key("GJ1c", Vector2i(10, 20), 32)
var k_explicit := AtlasWindowCache.make_key("GJ1c", Vector2i(10, 20), 32, 0)
assert_str(k_omitted).is_equal(k_explicit)
# =============================================================================
# granularity_v2 (T-1152/T-1153): the string-tag axis — the ONLY thing that
# distinguishes Region from District/Quarter (WindowGranularity has no
# integer representation at all — see the server's own doc). This is the
# SAME mandatory-aliasing regression class as the min_wl_m tests above,
# extended to the new axis.
#
# T-1159: these tests used to also carry a legacy int `granularity`
# positional argument (mirroring the server's now-retired
# `window_granularity: u32` wire field) — removed along with the cache's own
# legacy key component (see atlas_window_cache.gd's doc).
# =============================================================================
## **MANDATORY aliasing regression (T-1152/T-1153):** a Region-rung key and a
## District-rung key at the IDENTICAL (body_id, center, n, min_wl_m) must be
## DISTINCT cache keys — granularity_v2 is the only axis that can tell them
## apart.
func test_make_key_distinguishes_granularity_v2_region_from_district() -> void:
var k_district := AtlasWindowCache.make_key("GJ1c", Vector2i(10, 20), 32, 0, "District")
var k_region := AtlasWindowCache.make_key("GJ1c", Vector2i(10, 20), 32, 0, "Region")
assert_str(k_district).is_not_equal(k_region)
## Omitting granularity_v2 (every pre-T-1152 call site) must produce the SAME
## key as passing the explicit "District" default — byte/string
## compatibility, same contract as the min_wl_m default above.
func test_omitted_granularity_v2_matches_explicit_district_default() -> void:
var k_omitted := AtlasWindowCache.make_key("GJ1c", Vector2i(10, 20), 32)
var k_explicit := AtlasWindowCache.make_key("GJ1c", Vector2i(10, 20), 32, 0, "District")
assert_str(k_omitted).is_equal(k_explicit)
## End-to-end through put()/get_window(): a Region-rung window and a
## District-rung window at the identical (body, center, n) must both be
## independently retrievable — the exact scenario a full-zoom-out-then-back-in
## at the SAME (center, n) would hit if a player oscillates across the
## District/Region boundary.
func test_region_and_district_windows_coexist_at_identical_body_center_n() -> void:
var cache := AtlasWindowCache.new()
var district_window := {"granularity_v2": "District", "id": "district"}
var region_window := {"granularity_v2": "Region", "id": "region"}
cache.put("GJ1c", Vector2i(10, 20), 32, district_window, 0, "District")
cache.put("GJ1c", Vector2i(10, 20), 32, region_window, 0, "Region")
assert_int(cache.size()).is_equal(2)
assert_that(
cache.get_window("GJ1c", Vector2i(10, 20), 32, 0, "District")
).is_equal(district_window)
assert_that(
cache.get_window("GJ1c", Vector2i(10, 20), 32, 0, "Region")
).is_equal(region_window)