fix(simulation): PR #191 review round — n-clamp mirror, min_wl band quantization, coalescing coverage, fixture consumer

All seven Hoshe/Tyre findings addressed, none retracted:
- n-clamp/echo/staleness triangle (Tyre C1): client _clamp_window_n_mirror
  (bit-for-bit twin of the server clamp, canonicalize_district_center
  precedent) applied before _n is stored/sent; server test pins the
  quarter n=32 -> echo 16 contract.
- min_wl band quantization (Hoshe 1/Tyre C3): quantize_min_wl_m snaps to
  MIN_WL_BANDS_M {0, 32768, 16384, 8192, 4096} before cache key and echo
  (design doc §5's unbounded-key fix), reusing the one true
  OCTAVE_WAVELENGTHS_M array; docstrings now state the server-quantizes/
  client-sends-raw split; same-band cache-sharing test.
- coalescing granularity axis (Hoshe 2): two tests pin different-
  granularity requests as separate in-flight slots and same-granularity
  coalescing unchanged.
- orphaned fixture (Hoshe 3): test_protocol.gd consumer decodes
  atlas_response_ready_with_window.msgpack through the real IPC path and
  asserts the new fields.
- atlas_window_request coverage (Hoshe 4): new test file — stale-drop on
  granularity mismatch, old-server-shape defaults accepted, clamp mirror
  formula + wiring. First draft's quarter-via-request_now test would have
  passed for the wrong reason (request_now resets granularity by design
  until T-1153) — split into formula pin + reachable-path wiring proof.
- granularity type seam (Tyre C2): field + resolver docstrings state
  finer-only integer multiples with resolve_window_granularity as the
  single widening point; matching contract note added to the D-226
  T-1143-rulings amendment.

cargo --lib 1807/1807; goldens bit-identical; gdlint clean.
This commit is contained in:
2026-07-22 00:47:53 +02:00
parent 3e87fd5b4f
commit 0159a63cc2
8 changed files with 670 additions and 14 deletions
@@ -48,6 +48,15 @@ const MAX_RETRIES: int = 20 # ~10s ceiling, matches atlas_generation_proxy.gd's
const DEFAULT_GRANULARITY: int = AtlasWindowCache.DISTRICT_GRANULARITY
const DEFAULT_MIN_WL_M: int = 0
## Mirrors server/src/atlas/layer_proxy.rs's DISTRICT_WINDOW_MAX_N /
## WIRE_CAP_CELLS exactly (PR #191 review, Tyre C1). `_clamp_window_n_mirror()`
## below reproduces `clamp_window_n()` bit-for-bit — the load-bearing-mirror
## pattern `AtlasDescendGeometry.canonicalize_district_center()` already uses
## for the server's `normalize_window_center()`. Keep both numbers in sync
## with the server constants of the same name if either ever changes.
const SERVER_DISTRICT_WINDOW_MAX_N: int = 64
const SERVER_WIRE_CAP_CELLS: int = 4_096
var _owner = null # AtlasWindowViewer (untyped to avoid cyclic ref)
var _cache = null # AtlasWindowCache
var _body_id: String = ""
@@ -86,6 +95,38 @@ func reset() -> void:
_debounce_timer.stop()
## Mirrors server/src/atlas/layer_proxy.rs's `clamp_window_n(raw_n,
## granularity)` EXACTLY (PR #191 review, Tyre C1 — "the sharpest" finding):
## `serve_district_window` echoes the CLAMPED `n` back in
## `DistrictWindowLayer.n`, but `on_response()`'s staleness guard compares the
## echo against `_n`. Without this mirror, `_n` would hold the RAW requested
## value while the server echoes the CLAMPED one — the moment a caller
## requests quarter (granularity=4) at n=32, the server clamps to n=16 and
## echoes THAT, `on_response()` sees `echoed_n=16 != _n=32`, decides the
## response is stale, and the window silently never loads (no error, no log
## on this side — just an eternally-pending request).
##
## Clamping HERE, before `_n` is ever stored or sent, means `_n` already
## equals what the server will echo — no drift between the two sides, the
## SAME load-bearing-mirror pattern `AtlasDescendGeometry.
## canonicalize_district_center()` uses for the server's
## `normalize_window_center()` (see that function's docstring for the general
## rationale: canonicalizing before the request is sent means the client's
## held state already equals what the server will echo back).
##
## Formula, bit-for-bit: `n = raw_n.clamp(1, SERVER_DISTRICT_WINDOW_MAX_N)`,
## then `n = min(n, floor(sqrt(SERVER_WIRE_CAP_CELLS) / max(granularity, 1)))`
## — applied in that order (per-axis cap first, then the granularity-aware
## wire-size ceiling), matching `clamp_window_n`'s own comment ("Applied AFTER
## the per-axis clamp so a request that already satisfies
## DISTRICT_WINDOW_MAX_N still shrinks further at granularity 4").
static func _clamp_window_n_mirror(raw_n: int, granularity: int) -> int:
var n: int = clampi(raw_n, 1, SERVER_DISTRICT_WINDOW_MAX_N)
var g: int = maxi(granularity, 1)
var cap_n: int = int(floor(sqrt(float(SERVER_WIRE_CAP_CELLS)) / float(g)))
return mini(n, maxi(cap_n, 1))
## Entry point + pan re-request: request the window centered on `center`
## (a DistrictPos-equivalent Vector2i) for `body_id`. Cache hit -> immediate
## synchronous window_ready emit, no network traffic at all. Cache miss ->
@@ -97,12 +138,12 @@ func reset() -> void:
func request_now(body_id: String, center: Vector2i, n: int = DISTRICT_WINDOW_DEFAULT_N) -> void:
_body_id = body_id
_center = center
_n = n
_granularity = DEFAULT_GRANULARITY
_min_wl_m = DEFAULT_MIN_WL_M
_n = _clamp_window_n_mirror(n, _granularity) # Tyre C1 — mirror BEFORE storing/requesting
_debounce_timer.stop() # a direct request supersedes any pending debounced one
var cached: Variant = _cache.get_window(body_id, center, n, _granularity, _min_wl_m)
var cached: Variant = _cache.get_window(body_id, center, _n, _granularity, _min_wl_m)
if cached != null:
_pending = false
_retries = 0
@@ -111,7 +152,7 @@ func request_now(body_id: String, center: Vector2i, n: int = DISTRICT_WINDOW_DEF
_pending = true
_retries = 0
SimBridge.request_atlas_layers(body_id, "Topography", center, n, _granularity, _min_wl_m)
SimBridge.request_atlas_layers(body_id, "Topography", center, _n, _granularity, _min_wl_m)
## Pan-triggered re-request (§4/§5: "150ms after the last drag-release, not
@@ -122,9 +163,9 @@ func request_now(body_id: String, center: Vector2i, n: int = DISTRICT_WINDOW_DEF
func request_debounced(body_id: String, center: Vector2i, n: int = DISTRICT_WINDOW_DEFAULT_N) -> void:
_body_id = body_id
_center = center
_n = n
_granularity = DEFAULT_GRANULARITY
_min_wl_m = DEFAULT_MIN_WL_M
_n = _clamp_window_n_mirror(n, _granularity) # Tyre C1 — mirror BEFORE storing/requesting
_debounce_timer.start()