Commit Graph
467 Commits
Author SHA1 Message Date
jpmschweitzer 0159a63cc2 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.
2026-07-22 00:47:53 +02:00
jpmschweitzer 3e87fd5b4f feat(simulation): T-1151 window par_iter + T-1150 granularity carrier (five touch points + aliasing tests)
T-1151: build_district_window_layer dispatches one Rayon task per row
(pure derive_window_cell via derive_at_metres), scattered row-major into
the flat arrays; a cfg(test) serial path backs the bit-identical
parallel-vs-serial golden.

T-1150: serde-default window_granularity (1=district, 4=quarter) +
window_min_wl_m on AtlasLayerRequest — additive, no sixth demux shape,
old frames decode unchanged (tested). Quarter mode = full
reclassification at 512m spacing over the same world rect ((4n)x(4n)
cells); WIRE_CAP_CELLS=4096 enforces n*granularity <= cap (quarter
clamps n to 16, the design doc's worked example). Granularity + min_wl
key ALL five touch points: DistrictWindowLayer echo, server FIFO-256
cache key (now a 5-tuple), per-connection coalescing key, client
request codec (omitted-at-default wire fields), client LRU key.

Mandatory aliasing regressions on both ends: identical (body, center, n)
at granularity 1 vs 4 produce distinct cache entries and correct
per-granularity payload shapes (server, 3-thread queue to avoid the
AnalyzeBody thread contention found while writing it) and distinct
client cache keys (gdUnit). Replay fixture regenerated — the layer
struct grew two echoed fields (231->254 bytes, content verified).

Client requests stay district-granularity by default — quarter requests
arrive with T-1153's rung selection.
2026-07-22 00:27:40 +02:00
jpmschweitzer 32a7b1e425 fix(client): T-1146 review round — gate the T-1088 seams, kill the settle fallback tautology, neutralize AutoPause leak
PR #190 review (Hoshe + Tyre), all five findings addressed:
- Tyre: queue_move_step/queue_stance_toggle (the T-1088 click-to-move seams)
  now share the full suppression predicate — all three input_queue producers
  in the module are gated, making the header's 'every action this file can
  produce' claim true (previously the seams relied on the sandbox caller's
  own freeze). +3 regression tests.
- Hoshe: the live suite's settle=baseline timeout fallback compared baseline
  to itself, vacuously passing the no-movement claim — a timeout now fails
  loudly (both tests).
- Hoshe: the unit suite's real open_app/close_app transitions fire the
  global auto-pause handler; with ambient SimBridge.state left CONNECTED by
  earlier suites (test_hub_teleport has no after_test) each transition
  silently appended AutoPause/AutoResume to the shared _outbound_buffer.
  before_test now forces DISCONNECTED (restored in after_test) and clears
  the buffer.
- Hoshe: documented why the live suite's reset_test_state() call is inert
  but still correct.

Full suite: 3374/3374.
2026-07-21 20:00:18 +02:00
jpmschweitzer 4af3dd2607 test(client): T-1146 — full-suite isolation for the occlusion-gate suites
Unit suite: pin facing to North via the T-1088 facing_angle_provider seam.
D-054 makes movement facing-relative, and _update_facing_from_mouse() derives
facing from engine/session globals (viewport mouse position vs the
GameState.player_position anchor) — in full-suite order 'press W' resolved to
MOVE_NORTHWEST (the gate failure at line 187).

Live suite, three independent leaks from the shared autoloads, each of which
passed in isolation and failed in gate order:
- snapshot starvation: poll_snapshot() is consume-and-clear; a node leaked
  into the gdUnit tree by an earlier suite steals every snapshot during our
  awaits. Capture snapshot_received (fires on every consume, ours or theirs)
  alongside a direct poll + explicit SimBridge._process pump.
- tick poisoning: SimBridge left test_mode=true + CONNECTED lets the leaked
  poller apply TestHarness mock snapshots (~tick 15+) into
  GameState.current_tick during our spawn awaits; the fresh server's own
  ticks (1..5) then stamp backward and its InputQueue panics ('tick ordering
  violated', last=16 new=3). Neutralize SimBridge before the first await,
  zero current_tick, clear _last_snapshot/_outbound_buffer both directions.
- crash-on-assert-failure: gdUnit asserts don't halt; guard the player-entity
  dereferences so a missing Player reports cleanly instead of a runtime error.

Full suite: 3368/3368.
2026-07-21 19:49:42 +02:00
jpmschweitzerandClaude Fable 5 cd52fe8843 fix(client): T-1146 gameplay input gated while an implant screen occludes gameplay
InputMapper polled the D-054 move_* actions (and queued discrete
gameplay actions) unconditionally — WASD with a fullscreen implant
open walked the character blind. Poll-site gate (both _process and
_unhandled_input, the file's existing dialogue/free-camera early-return
idiom): nothing is enqueued while occluded, so no backlog can flush on
close.

Gate signal is HudGroups.gameplay_occluded, deliberately NOT
is_implant_active(): that flag is also true for INSERT mode (economics
monitor panel), where gameplay stays visible and playable by design —
the naive gate would have broken WASD there. Local _gameplay_occluded
mirror via the existing signal; regression tests pin the INSERT
distinction.

Held-state semantics: open-while-held stops on the next poll;
close-while-held requires release-then-repress (a
_suppress_move_until_release latch armed on the close transition,
cleared only when EVERY movement key is released — one-of-two released
does not clear, tested). Facing exempt from the latch (re-sync, not
lurch).

Audited action set: movement/facing-send/INTERACT/perception/pause/
stance/teleport/quicksave-load gated; OPEN_MENU exempt (Esc must close
the implant), BUG_REPORT/OPEN_JOURNAL exempt (client-only, never reach
send_input); dialog-driven direct SimBridge sends (settings, dialogue
pause, quit-to-menu save) out of scope by design. The pre-existing
server-side AutoPause defense observed firing correctly alongside.

Drive-by: pre-existing gdlint class-definitions-order violation in
input_mapper.gd fixed (public/private var ordering) — file lint-clean
for the first time.

Tests: 16 unit/state (real autoloads, real open_app/close_app
transitions, Input.action_press engine state) + 2 LIVE against a real
spawned server through the REAL client pipeline (position frozen for
20 held-W ticks while occluded; still frozen 10 ticks after close
without re-press; resumes on re-press) — port-retry/wall-clock/
COOLDOWN_TICKS conventions reused from test_input_roundtrip. 12-suite
regression sweep of every InputMapper consumer green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 19:16:12 +02:00
jpmschweitzerandClaude Fable 5 372ce37bb6 feat(ui): T-1145 round-2 polish — cover-fit, WASD+edge-scroll pan, smoothed composite (Jeroen rulings)
Cover-fit: fit_window_view zooms from the viewport's LARGER dimension,
no margin factor (any factor under 1.0 leaves a long-axis gap — checked
numerically) — the composite fills edge to edge, overhanging the short
axis into pan-space; the refloat center-equality early-return already
prevents refetch churn at rest (proved, not just tested).

Input model (Jeroen: drag breaks click semantics with map objects):
LMB-drag pan REMOVED from the regional window; clicks are
object-reserved. Pan = held WASD/arrows polled in _process (delta- and
zoom-scaled, camera-pans-toward-key convention verified numerically)
plus edge-scroll within 24px of the viewport border; both suppressed
over UI and on OS focus loss; both set _user_adjusted; wheel zoom and
Esc unchanged. Reads RAW physical keycodes deliberately — independent
of the shared D-054 move_* InputMap actions bound to the same keys
(whose occlusion-leak is pre-existing and now ticketed as T-1146).
Pole wall + east-west wrap unchanged, re-driven through the new
inputs; drag tests replaced, not kept.

Smoothed composite (interim pending T-1143): per-cell colors bake into
an n x n Image/ImageTexture (exact existing colorizer incl. overlay +
ice tint) drawn once with LINEAR filtering — GPU bilinear reads as
terrain, the planetary heightmap's own treatment. Crisp per-cell path
preserved behind COMPOSITE_SMOOTH for T-1143 A/B. Rebuild only on
reference-identity change of window/toggle (is_same — verified true
reference equality; value-equal distinct dicts DO rebuild).

Governance: T-1145 amendment paragraph on D-226 T-1124 SS5 (all three
supersessions); pql decisions validate ok.

Suites: window_viewer 74/74, window_geometry 32/32, window_overlay
(new) 16/16; gdlint clean on all six files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 16:51:24 +02:00
jpmschweitzerandClaude Fable 5 7ce6cc08fd test(ui): pin the _user_adjusted resize guard both ways; fix coalescing doc overstatement (PR #188 review)
Hoshe's finding: the flag introduced so auto-fit never fights a manual
view had zero coverage on exactly that branch. Two tests drive the
REAL _gui_input path (synthetic drag), then fire NOTIFICATION_RESIZED:
user-adjusted view survives a resize untouched (zoom AND offset);
an unadjusted view re-fits to the new viewport. 52/52.

Non-blocking doc note also taken: layer_proxy's normalization comment
claimed the twins would otherwise 'coalesce independently' — the
coalescing key is (ConnectionId, body_id) and never carried center;
rewritten to say what normalization actually buys on that path (the
work item derives and echoes the canonical center).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 15:22:24 +02:00
jpmschweitzerandClaude Fable 5 831625019a fix(ui): T-1142 client — descent bounds gate, fit-and-center, pole wall, east-west wrap, body-name header
Bounds gate: AtlasDescendGeometry.is_on_texture() — ONE helper feeding
both the reticle guard and the click fall-through (the T-1140 lesson:
the visible affordance always matches the click); half-open
[0,tex_w)x[0,tex_h) boundary pinned at the exact edge. Letterbox
clicks no longer show a reticle or descend.

Fit-and-center: pure fit_window_view() (new atlas_window_geometry.gd)
wired into enter(), the FIRST window arrival, and NOTIFICATION_RESIZED
— gated by a _user_adjusted flag so the fit never fights manual
zoom/pan (flag clears only on a fresh enter). Found-own-bug: RESIZED
can fire mid-_ready() before _canvas exists — null-guarded like the
sibling panels.

Pole wall (Jeroen's ruling): clamp_pan_offset_to_pole_wall() clamps
the WINDOW EDGE, not the center, in screen space from the fitted
transform — Y only; wired into the drag handler and every fit (a
fresh fit can itself need the wall on a tiny body — the
window-taller-than-planet case is handled and tested). Three numeric
hand-traces preceded the code; a first-draft test using GJ380c's huge
radius silently never exercised the clamp — replaced with a synthetic
small radius.

East-west wrap (Jeroen's ruling): canonicalize_district_center() —
posmod column wrap (verified against a live Godot process to match
Rust rem_euclid bit-for-bit), clamped row; district_extent() shares
the exact formula (incl. .max(1)) with the server's
normalize_window_center so echoes and cache keys agree on canonical
form. Canonicalization applies only to the FINAL refetch center — the
edge-crossing decision stays in absolute district space (first-pass
math error caught by hand-trace). Seam-adjacent cache-key sharing
tested. Pan offset itself has no x wall — circumnavigation is
seamless.

Header: body proper_name/body_id ahead of the coordinates (the cheap
half of T-1141, noted in code). Drag-pan verified through the REAL
DistrictScreen-to-viewer chain and pinned by test (no fix needed).

140 tests across three suites, 0 failures; 94 sibling tests no
ripple; gdlint clean (atlas_viewer.gd at the 1000-line cap a second
round — structural extraction flagged for maintenance).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 14:53:32 +02:00
jpmschweitzerandClaude Fable 5 4eed3bccb2 fix(ui): latitude-inverse denominator + reticle city-hover gate (PR #187 findings)
Hoshe's blocking find: district_pos_at divided the latitude axis by
tex_h where the server forward map uses ta.h.saturating_sub(1)
(district_profile.rs:1436; ground-truth inverse aliveness_probe.rs:511)
— every off-equator click descended into the wrong district, up to
~20km drift, live-repro'd at 10.2km. Fixed to /(tex_h - 1.0) with the
reference implementation's degenerate-texture guard; verified against
the repro (row 50: old formula recovered 41, fixed recovers 50 exact).
Columns were already correct (longitude wraps — the asymmetry the
docstring documented but the code didn't implement).

Regression tests transcribe the server forward formula (source cited)
and round-trip three off-equator rows — one per hemisphere plus
near-pole — asserting EXACT recovery; a dedicated drift guard is
framed as Tyre's C2 (a client-side twin of a server mapping owns its
own round-trip proof). Root-caused the coverage hole: the old
equator test sampled tex_h*0.5 believing it was the equator pixel —
the true equator is (tex_h-1)*0.5, so the test never verified what it
claimed; corrected with the why documented.

Araminta's find: the descend reticle drew unconditionally on hover —
over a city the marker flared white (city-click signal) while the
reticle+extent label promised descent, though the click correctly
resolved to city data. Extracted _should_draw_descend_reticle() with
the missing _hovered_city.is_empty() clause; three state tests pin
shows-over-map / hides-over-city / hides-when-idle.

test_atlas_descend_entry 32/32; adjacent clusters no ripple; gdlint
clean (atlas_viewer.gd at exactly the 1000-line cap).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 13:57:28 +02:00
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
jpmschweitzerandClaude Fable 5 172ce124c8 feat(simulation): T-1137 windowed district layer — queue-served, coalesced, determinism-tested (D-226 T-1124 SS1-SS4)
AtlasLayerRequest gains window_center/window_n (serde-default, absent
= whole-body, wire back-compat; demux untouched — up_to stays the
discriminator). DistrictWindowLayer echoes center/n + six parallel
arrays (morphology, elev_q, temp_dc i16 with the region sentinel,
moisture_q, vegetation incl Marine=6, glaciation).

Serving per the amendment's binding model: NEVER inline —
GenWorkItem::DeriveWindow rides the Rayon queue, completion drain
caches into DistrictWindowCache (bounded FIFO 256; no staleness by
D-227 purity, capacity bound only), serve_district_window polls the
cache and returns Pending-shaped None until derived. Per-connection
coalescing: submit_window supersedes a still-pending item for the
same (ConnectionId, body) — the surviving item is the newer one,
proven by dedicated tests.

TerrainAnalysis decision (option b, numbers in ticket/PR): re-derive
via run_layer1 in the DeriveWindow branch rather than caching ~1.5MB
x 50 LRU slots (~100MB permanent, the exact D-203 bloat T-1044's own
text guarded against); ~45ms one-time on the Rayon path, invisible to
the tick thread. T-1044 confirmed within-cascade-only (cascade.rs:358
still drops the analysis before BodyWorldState) — the fork was open.

Window derive loop promoted from aliveness_probe::render_window_panels;
determinism promoted from probe-only proof to a real test (two passes
byte-identical). New fixture atlas_response_ready_with_window
exercises all six arrays incl. the airless sentinel and Marine; three
existing fixtures gain district_window: None. Server clamps window_n
to 1..=DISTRICT_WINDOW_MAX_N=64 (never trust the wire).

1774/1774 lib + 19/19 bridge_tcp green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 12:13:59 +02:00
jpmschweitzerandClaude Fable 5 872aea041f fix(ui): overlay bar empty flow area must not eat map input (PR #186 review)
Hoshe's finding: the wrap fix gave the bar container the full
header-adjacent width, and with ALIGNMENT_END + the pre-existing
MOUSE_FILTER_STOP that left a ~700px dead strip (1920px screens) left
of the chips silently swallowing map clicks/drags near the top edge —
a regression the wrap change introduced by widening the rect without
revisiting the filter. Two edits: the container is now
MOUSE_FILTER_IGNORE (the legend-panel/header/empty-notice idiom; chip
Buttons STOP their own events so toggles and tooltips are unaffected),
and _is_over_ui() no longer checks the bar rect (with IGNORE, chip
events never reach the viewer — checking the wide rect would recreate
the dead strip). Regression test pins both: filter mode + an
empty-strip point not registering as UI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 09:26:02 +02:00
jpmschweitzerandClaude Fable 5 b56f6b0bc8 fix(ui): T-1140 overlay bar wraps instead of overflowing; strip dev ids from tooltips
Jeroen's first make atlas hands-on: 17 overlay chips in a single
HBoxContainer row ran off the right screen edge (positioning also
guessed the bar's width with a 520px fallback). The bar is now an
HFlowContainer with ALIGNMENT_END; the viewer gives it everything
right of the header (OVERLAY_BAR_HEADER_RESERVE) and rows wrap
right-aligned — verified via live capture, all 18 chips visible.

OVERLAY_DEFS tooltips leaked dev provenance into player-facing text —
ticket ids (#960, T-960, T-1046, T-1118, T-1119) and decision ids
(D-181, D-226, D-243) mean nothing in-game. Stripped to plain
language; the visibility-ladder semantics (public signal / observable
/ needs corporate contact / needs insider access) stay.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 08:59:08 +02:00
jpmschweitzerandClaude Fable 5 e41cca2ded feat(ui): T-1118 climate overlay + T-1119 quarter glyphs — client halves, with file-size extractions
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>
2026-07-18 12:25:47 +02:00
jpmschweitzerandClaude Fable 5 8165c61186 feat(simulation): T-1119 QuarterFootprintLayer — server half (touch points 1-2) + populated wire fixtures
ZoningType gains PartialOrd/Ord (DistrictType T-994 mirror) for the
dominant_zoning declaration-order tie-break. layer_proxy:
QuarterFootprintEntry/QuarterFootprintLayer (BTreeMap keyed by
city_id; density_avg_pct floor-mean, dominant_* mode with
declaration-order tie-break, landmark/corridor counts) +
build_quarter_footprint_layer gated None on empty state.quarters;
sixth AtlasLayerResponse field wired into all construction sites;
growth-ceiling doc updated to say the whole-body budget is consumed
(what carries a seventh is T-1124's design, resolved in the same
branch's amendment). Placements whose derived Layer4Quarter id is not
yet in state.quarters are SKIPPED (skeleton gen is async Low-priority
— entries may be a proper subset; client decodes defensively). Stale
TODO(#957) removed (premise predates T-957 landing).

Tie-break bug caught by its own test: Iterator::max_by_key over a
BTreeMap returns the LAST max on ties — backwards from
declaration-order-first; replaced with an explicit forward walk that
replaces only on strictly-greater count.

gen_fixtures: populated RegionGridLayer (T-1118 replay coverage) +
QuarterFootprintLayer literals in the ready fixture; msgpack fixtures
regenerated via make fixtures. Note: landmark_count is inert on real
data today (BlockSkeleton.landmark hardcoded None until landmark
placement lands); fixtures use non-zero counts to exercise the logic.

5 new unit tests + populated round-trip; atlas:: 549 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 12:03:28 +02:00
jpmschweitzerandClaude Fable 5 90c562a6a3 feat(ui): T-1133 data browser — implant/browser app, six index+detail screens (D-254 SS4)
New implant app (app_path implant/browser, key B, fullscreen), sibling
to the Atlas per Jeroen's IA ruling: kind picker -> generic filterable
index (live search-mode typing) -> generic detail, parameterized per
kind, composed entirely from D-169 components. available_in_companion
left unset (default true) — the app appears in the companion shell
automatically via the generic-host seam, zero companion-side wiring.

browser_adapter.gd is the sole home of literal wire field names: maps
Oscar's BrowseResponse contract ({id, primary, secondary} index rows;
BrowseDetail enum-as-single-key-map) to view models for all six kinds,
folding join partners (system economy/factions/culture, corporation
presence, commodity production chains with nested Leontief inputs).
browse_protocol.gd split out of protocol.gd (max-file-lines);
sim_bridge gains browse_response_received + request_browse_index/detail.

Live-data catch: a present-but-NULL key (unnamed asteroid belt
proper_name) bypasses Dictionary.get fallbacks and rendered '<null>' —
_display_or() null-vs-absent helper applied across all six detail
mappers, 4 regression tests distinct from the absent-key cases.

43 gdUnit adapter cases; full suite 3074 green. Live-verified against
a real server + real systems.db: all six kinds Ready with real row
counts (301/3240/466/165/36/28), detail drill-down, NotFound on bogus
ids. Spawn-mode DB resolution issue found during verification is
pre-existing (cwd-relative data/systems.db) — server-side fix follows
separately.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 10:24:39 +02:00
jpmschweitzerandClaude Fable 5 9b5c113961 fix(client): defer companion close-interceptor re-open out of the app_changed emit (PR #183 review)
Tyre's finding: _on_hud_app_changed ran open_app synchronously from
INSIDE close_app's app_changed emit; close_app continues past the emit
and resets _active_app, clobbering the re-open — Atlas left visible
(z already raised) but is_app_active() false, so atlas_app's input
guard rejects every key: keyboard soft-lock after one M/Escape on the
reach screen. Fix: open_app.call_deferred, out of the signal frame.

Two regression tests pin the contract at the HudGroups level (no
server needed): the synchronous shape must keep getting clobbered and
the deferred shape must survive — if HudGroups emit semantics ever
change, both flag the contract shift for a deliberate look.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 09:29:29 +02:00
jpmschweitzerandClaude Fable 5 cffe760d36 feat(client): T-1132 standalone Atlas companion shell + make atlas (D-254 SS3)
New atlas_standalone scene/script: attach-or-spawn boot (one REAL
connect_to_sim attempt at SR_PORT-or-9876 — a separate throwaway TCP
probe was proven by live run to kill a pre-accept-loop server via
broken handshake pipe; never abandon a connected socket), else spawn
--port 0 via new ServerProcess.start_with_pipe + LISTENING:{port}
stdout parse, retry against the resolved port. Reader role wired end
to end: protocol.encode_startup_message optional role param (empty
omits the wire key — byte-identical for all existing callers),
sim_bridge.connection_role suppresses the post-handshake
RequestAllSettings auto-send, hud_groups skips AutoPause/AutoResume
sends for readers (all three would otherwise burn Reader violation
strikes per the T-1130 matrix — endorsed by Oscar).

Generic implant host per D-254 SS3: the shell instantiates ALL
registered implant apps; implant_app_manifest gains
available_in_companion (opt-out, default true) and
implant_registry.instantiate_all a standalone filter param (default
preserves hud.gd behavior byte-identically). Boot order is
instantiate_all THEN open_app (reverse renders a permanently black
window — app_changed fires with no listener; matches hud.gd's order).
Owned-server lifecycle: _exit_tree stops a spawned child, attached
servers survive companion close. Known engine limitation documented:
raw SIGTERM bypasses all Godot notifications and orphans a spawned
server; WM close paths verified clean.

Live-verified: spawn-mode (301 systems rendered from systems.db over
the wire), attach-mode, two simultaneous readers, clean shutdown with
zero orphan processes. 14 new gdUnit tests (port/LISTENING parsing).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 09:09:09 +02:00
jpmschweitzerandClaude Fable 5 fcf913a413 fix(client): move golden-provenance key out of scenarios{} — crashed visual_capture --list (PR #180 review, Hoshe)
The _comment_atlas_goldens key inside scenarios{} hit _print_list's .get() on a bare String (reproduced: Invalid call on String at visual_capture.gd:699). Moved to a top-level key alongside resolution/golden_dir; _print_list now also skips _-prefixed and non-Dictionary entries so future comment keys cannot crash it. Verified: --list runs clean, JSON valid.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 21:40:03 +02:00
jpmschweitzerandClaude Fable 5 3819e65759 test(client): pace input-roundtrip moves past the D-053 stance cooldown (T-1068 fix ported)
Root-caused with server trace logs: Walk stance throttles 1 move per 2 ticks and SILENTLY discards early moves (movement.rs apply_move -> try_move, TRACE-only) — under load the test's MoveEast landed exactly 1 tick after MoveNorth and was consumed. Server behavior is correct, deliberate D-053, pinned by Rust tests; production clients re-send while keys are held. Fix: COOLDOWN_TICKS=3 post-ack spacing, same as test_sprint2_proof.gd (T-1068) whose fix never reached this file due to helper copy-paste. Pre-fix repro at run 17/30 under load; post-fix 30/30 stress green + full suite 2956/2956.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 21:30:52 +02:00
jpmschweitzerandClaude Fable 5 8d1121d13a test(client): wall-clock deadline + 10s ceiling for input-roundtrip response waits
Second distinct flake in this suite today (gate run: MoveEast observed mid-move at the 5s window). The old accounting accrued elapsed only on empty polls — load-dependent effective window. Wall-clock deadline via Time.get_ticks_msec; ceiling 5s->10s (exit-on-arrival, costs nothing when healthy). Targeted suite 6/6.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 20:47:12 +02:00
jpmschweitzerandClaude Fable 5 9427b165f0 docs(client): golden provenance notes — atlas_* goldens are machine-local until T-1121 (PR #180 review, Tyre T1)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 20:42:37 +02:00
jpmschweitzerandClaude Fable 5 56b9f135d0 test(client): add_child AtlasViewer in view-api tests — bare .new() has nil _canvas
Three of the four new tests skipped scene-tree entry, so _ready() never resolved _canvas and set_view crashed _apply_transform. Mirrors the fourth (passing) test. Targeted suite green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 20:34:01 +02:00
jpmschweitzerandClaude Fable 5 2b2f4f8abb test(client): 12 Atlas golden screenshots — 7 bodies x 3 zooms x 3 overlay sets (T-1120)
Curated from the 42-shot matrix, one per body spanning all zoom levels and overlay sets; self-consistency verified 12/12 at exact pixel match on this box. .import sidecars force-added per the existing golden convention. Bodies: GJ380c, GJ144e, GJ338Bd, GJ820Bc (Metropolis + Cygni yard), GJ251c (T-1116 water/no-roads exhibit), GJ244Ad (frozen), GJ445c-m1 (moon).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 20:29:53 +02:00
jpmschweitzerandClaude Fable 5 799156b140 feat(client): Atlas screenshot capture matrix + set_view API (T-1120)
AtlasViewer gains public set_view(zoom, offset)/get_view_offset (clamped, review-flagged API gap) + gdUnit suite. visual_capture.gd gains an atlas_matrix scenario (one boot, tests/atlas_shots.json-driven: 7 verified bodies x zoom x overlay sets, signal-gated waits on atlas_layers_received/city_names_received with timeout fallback). 12 individually-addressable golden scenarios registered in tests/visual.json. Root-level settings.db* gitignored (capture-spawned servers write their settings store to cwd). Fixes found live: GDScript lambdas capture value-type locals BY VALUE (wait-flag moved to Array carrier); RegionalScreen.enter() no-ops when the screen id is unchanged (direct enter() per body after the first).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 20:29:14 +02:00
jpmschweitzerandClaude Fable 5 bba14cb9ba test(client): retry input-roundtrip server spawn on fresh port when it dies pre-connect
The server binds AFTER ~250ms of plugin/sim init, so a random-port collision (os error 98) escapes the 0.15s spawn-liveness check and surfaced as a hard test failure at the push gate. Death during the connect window now retries on a fresh port within MAX_PORT_ATTEMPTS. Verified: targeted suite 6/6.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 19:05:43 +02:00
jpmschweitzerandClaude Fable 5 4f52abd532 feat(simulation): wire region climate layer into the cascade + proxy (T-1113, D-243)
CascadeLayer::Region appended per the enum's append-only Ord rule (depends only on seed/body_params/heightmap dims — documented); BodyWorldState.regions BTreeMap per the districts precedent; RegionGridLayer dense row-major all-integer encoding (season/weather repr(u8) discriminants, mean_temp deci-degC i16 with i16::MIN airless sentinel, moisture_q u8); build_region_grid mirrors build_district_grid; protocol.gd region_grid passthrough (visual overlay deliberately out of scope); wire fixtures regenerated via make fixtures. atlas:: suite 507 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 18:51:43 +02:00
jpmschweitzerandClaude Fable 5 3304ee30da fix(simulation): PR #176 review round — all 12 findings + 2 recommendations addressed
H1 demux: ShapeProbe defensive multi-shape rejection (union frames now Err, not first-match; +2 tests) and doc claim made honest. H2/T1 SystemIndex.reset_test_state() folded into SimBridge.reset_test_state() (load() inline per autoload rule) + has_pending_request() accessor. H3 no-op tests now assert the replay flag both directions. H4 retry test actually ingests a failure and asserts the retry semantic. H5 error fixture uses the normalized status string. H6 bridge_tcp e2e sends all five frame shapes over real TCP (star-map + city-names buffers asserted). H7 positive replay-on-CONNECTED test via the test_local_bridge test-mode-flip precedent (stub bridge captures + decodes the request bytes). H8/T2 stale PLACEHOLDER doc replaced with the confirmed contract. H9 is_capital doc matches the COALESCE reality. T-r1 demux ceiling written down (next shape = tagged envelope). T-r2 AtlasLayerResponse governance ceiling comment. Lead item: the four cargo-fmt-formatted files from the gate round are now committed (layer_proxy/plugin/bridge-mod/main). H10 note for the record: the 13 snapshot_*.msgpack fixtures in commit 845737617 were regenerated because they were stale against their own generator (pre-existing version-key removal) — verified harmless, no client reads that key.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 17:31:15 +02:00
jpmschweitzerandClaude Fable 5 845737617c feat(client): atlas roads/settlements overlays + legend + server-API data delivery (T-960, T-949)
T-960: gen_l2_roads (MaintenanceAuthority-colored polylines, rail styling, junction markers) + gen_l3_settlements (size-scaled markers, capital shape, name labels) overlays — cities render on generated bodies for the first time; left-side generation legend panel (D-226 item 3, data-driven per-overlay spec, implant component library); protocol.gd decodes road_graph/settlements + the two new response types. T-949: system_index/atlas_app/overview_screen migrated off the direct star_map_data.json read to StarMapRequest over the bridge (loading state + replay-on-connect, no silent file fallback); atlas_viewer _load_markers requests CityNamesResponse for non-Sol bodies; Sol keeps the legacy authored markers.json geometry read (D-236/T-1073, load-bearing guard). Lead fix: _send_star_map_request now carries the same guard as request_star_map — the autoload's _star_map_wanted leaked across gdUnit suites and the unguarded replay-on-CONNECTED crashed 8 pre-existing flow tests on a Nil bridge; reset_test_state clears the flag. Fixtures regenerated via gen_fixtures (road/settlement samples). Full suite 2946/2946.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 15:45:54 +02:00
jpmschweitzerandClaude Fable 5 6bda9f1697 feat(simulation): auto-pause sim on implant-fullscreen — inspection substrate (T-970)
D-226 layer 1. World-advancing phases gated on pause: Movement/Storyteller/Knowledge/TickAdvance set-gated via sim_not_paused; Simulation + Economy gated per-system at their registration sites — collect_sound_events and serve_econ_state_query stay unconditioned (transient-buffer clear + paused-allowed query; set-gating Simulation leaked a stale tick-7 footstep into frozen snapshots — caught by golden_suite, fixed without touching the fixture; regression test encodes the bug shape). New PlayerAction::AutoPause/AutoResume + AutoPauseState resource implement Option A reconciliation: auto-resume only fires if auto-pause caused the pause; manual pause and Half rate survive implant open/close. PauseParams SystemParam bundle keeps process_player_input under the 16-param ceiling (BookmarkInputParams precedent). Client: HudGroups.gameplay_occluded now sends AutoPause/AutoResume via send_named_action; 5 gdUnit tests + 7 Rust tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 15:44:56 +02:00
jpmschweitzerandClaude Fable 5 3d6dcc9d05 docs+fix: sidequest close-out — T-1095 head tint, design doc, wardrobe docs, changelog (T-1088/T-1089)
T-1095 fixed: template heads now receive the body's skin tone (same
treatment as seg_head) — _load_head passes tone.lit with a full-white
fallback mask through _attach_to_bone; without a mask sidecar the tint was
silently ignored, so untinted templates read grey against mid/dark tones.
Verified by lookbook render (head_001 on child, tone 6 — face matches arms).

Docs: the T-1088 locomotion design document committed to docs/design/ (the
sandbox code's 'design §' references now resolve in-repo); character-asset-
organization gains §11 Wardrobe Pipeline and sheds three stale claims (the
never-built indexed-greyscale mask plan, single-mask-per-asset for clothing,
the resolved multi-region open question); CHANGELOG carries the sidequest
entry; D-250 amended with the shipped cutaway-modes verdict (mode 2 default,
user-decided live).

Tickets: T-1088, T-1089, T-1095 closed; T-1096 filed (S9 live feel-tuning,
needs the user). The Fable-5 cascade exception is closed — the cascade
resumes at Phase 4 (T-750).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 14:15:56 +02:00
jpmschweitzerandClaude Fable 5 e709462a7b fix(assets): convex toe boxes on closed footwear — no more foot-shaped shoes (T-1089)
User review finding: sneakers/formal shoes/boots conformed to individual
toes (and toes poked the closed front). Root cause: the skin-conforming
clearance clamp ran AFTER toe smoothing and re-imprinted the original toe
bumps; boots also copied per-toe skin weights (ripple under flex). Fix:
shared base.convex_toe_box() — per-slice enclosing ellipse from the skin,
notch fill, projection onto the smooth cap (outside skin by construction),
extended rounded nose past the longest toe, uniform feathered ball-bone
binding so shoes flex rigidly at the ball joint. Style-parameterized
(sneakers roomy / formal sleek tapered / boots chunky). Re-authored x 11
bodies; QA all-green (worst 82px « 150 gate; residuals are collar/sole-edge
slivers, not toes). Lookbook shots re-rendered on the desktop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 12:59:00 +02:00
jpmschweitzerandClaude Fable 5 e562418d06 fix(assets): lookbook findings — 44 missing extracted albedos, manifest nesting, lookbook tool (T-1089)
The lookbook render pass load-checked all 264 garment GLBs and found four
garments (boots, cargo pants, jeans, tank top) shipped WITHOUT their
per-body Godot-extracted albedo textures — 44 GLBs would fail to load after
a clean import. Textures regenerated via forced reimport and committed.
Also: swimsuit_onepiece manifest entry was double-nested (lead's merge bug —
runtime unaffected, reads coverage.json); flattened. The parameterized
lookbook_scene tool (drives the production compositor, auto-framing, job
list) is committed for future catalogue renders. Grey head-template finding
filed as T-1095. Lookbook itself delivered to the desktop, not committed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 12:04:36 +02:00
jpmschweitzerandClaude Fable 5 e5ca650164 feat(assets): outfit presets — 12 named complete looks over the wardrobe (T-1089)
casual street/warm/hoodie, business, black suit, training, gym, two swim
looks, worker, thrds cold-weather, and sleepwear-as-tints (soft palette over
daywear meshes — no dedicated sleep meshes). Per-item 4-region tints + brand
logos where sensible; all references validated against the manifest. The
seam for NPC dressing, shop outfits, and creation-screen randomize.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:23:15 +02:00
jpmschweitzerandClaude Fable 5 ded5c62515 feat(assets): wardrobe wave 2 — footwear, sportswear, swimwear, sweater, cargos, thrds parka, slides (T-1089)
Twelve garments, per-body on all 11 bodies, chromakey-gated (worst clips:
parka 3px, boots 54, sweater/tank 83, sneakers 100 final-geometry, slides
123, swim trunks 132, cargo 138 — all under the 150px gate), previewed:
tank top, sweater (crew-neck via per-body ring-valley probe — first draft
read mock-neck, fixed by measured rim circularization), track jacket
(recolorable sleeve-stripe region), joggers (side-stripe region), cargo
pants, swim trunks, one-piece swimsuit, sneakers (prism-sole), formal
shoes, ankle boots (calf shaft), slides (open strap + sole), and the
hip-length thrds parka — the first canon-branded garment (Braemar
cold-weather cooperative), quilted, logo-capable.

Tops now hem into real hip geometry (the natural seg_torso bottom is a
9-14cm tooth ring — the sweater established the hem-into-hips practice).
Manifest merged by the lead: 24 clothing entries with region/default-tint
metadata. Full modern catalogue: 21 garments across tops/bottoms/feet/
full-body x casual/formal/sport/swim/outerwear.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:20:18 +02:00
jpmschweitzerandClaude Fable 5 c765efd54e feat(assets): wardrobe wave 1 — per-body shells, 8 garments, try-on UI (T-1089)
Infra: offset-shell gains --per-body mode (each body's own segments, cut/mask
thresholds derived from that body's bone landmarks — reproduces the
hand-calibrated reference constants exactly on average_m); compositor prefers
<body>_mask.png with reference_mask.png fallback; tshirt re-authored per-body
on all 11 (the Q-060 torso poke-through class is GONE — residual flags are a
sleeve-hem epsilon artifact on thick arms, offset-insensitive, documented).

Garments (all per-body x 11, chromakey-gated <=150px worst, previewed):
hoodie (hood-down roll, kangaroo pocket, logo), button-down (collar/placket),
shorts, jeans (analytic denim field driving albedo+mask together; boundary
weld + open-rim flattening — real segment-splitter findings), formal pants,
jacket (over-shirt standoff, zip), suit_jacket_black (lapel region, tintable
shirt triangle — the hand-author proof), uniform_utility (11-segment
coverall, gap-free waist join by construction, 4-zone showcase, logo patch).

Try-on UI: creation screen shows per-region tint pickers (multi_region
garments) + logo picker (logo_capable + logos/*.png scan), data-driven off
manifest+coverage. Manifest merged by the lead: 12 clothing entries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 00:35:20 +02:00
jpmschweitzerandClaude Fable 5 278e8b7c21 feat(client): compositor plumb for multi-region garments + per-character logos (T-1089)
_apply_clothing_shader routes coverage.json multi_region garments to
toon_garment (4 tints, cascading fallback) with the brand logo resolved from
the new descriptor field clothing_logos (item_id -> brand id -> logos/<id>.png
— a logo is a per-CHARACTER wardrobe choice, so it rides the descriptor, not
the garment). Legacy garments keep the single-tint path untouched. Server-side
descriptor mirror stays parked with T-1060/Q-114 (appearance is client-local).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 22:21:52 +02:00
jpmschweitzerandClaude Fable 5 e22ea0fa4a feat(assets): wardrobe engine + proof t-shirt — batch-fit, offset-shell, 4-region tint, thrds logo (T-1089)
Engine: tooling/garment-fit/blender_batch_fit_skinned.py (G1 — the skinned
Surface-Deform batch the old script couldn't produce; self-check green),
blender_author_offset_shell.py (route c: garment shells from OUR body
segments, weights inherited by construction, bone-plane cuts, procedural
RGBA region mask, UV2 chest channel), make_logo.py. Shader:
toon_garment.gdshader — channel-blended 4-region tint + UV2 logo composited
after tint / before toon shading. Proof: tshirt_modern fitted to the six
healthy bodies, manifest entry with style:modern + logo_capable, thrds
wordmark, 18-assertion test suite, 216-capture chromakey QA.

Key finding (Q-060 evidence): single-reference SD-fit of an offset-shell
degrades on girth-divergent bodies (muscular_m worst) — 24mm standoff
tripled headroom but the mechanism limits. Route guidance recorded on
T-1089: per-body shell authoring for offset-shell garments; SD-fit for
derived/hand-authored ones.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 22:21:33 +02:00
jpmschweitzerandClaude Fable 5 b54b8189d9 fix(assets): T-1090 — five fork bodies rebuilt; mesh+armature scale baked together
Root cause was double: (1) segment_body's apply_scale scaled fork MESH
vertices but not each segment's embedded armature — the shared-skeleton
compositor relocates segments by bone name, so internally-inconsistent
segments exploded (child worst at 0.72x: head bone 0.35m above its mesh —
detached heads, spider arms); (2) thin/heavy were stale high-poly artifacts
from an older segmentation, missing seg_hips. Fix: apply_fork_scale bakes
mesh AND embedded armature via transform_apply (edit-bone poking shears
chains — first attempt proved it); new blender_rebuild_forks.py rebuilds
exactly the five from the owned UBC Source exports. All five now 19 low-poly
segments matching the healthy six.

QA on the real compositor (idle+walk, front+side): 5/5 coherent; healthy
controls unchanged. Q-060 answered at the extremes: 15/15 peasant-garment
Surface Deform binds on the forks, zero shrinkwrap fallbacks, no
bust-through — the 6-of-11 placeholder debt is paid (fork garment variants
included). Follow-up filed: T-1094 (child/teen composite at adult height —
pre-existing shared-skeleton normalization, not a regression).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 22:06:10 +02:00
jpmschweitzerandClaude Fable 5 c82ea3ec92 feat(client): purchased UAL tiers wired — lib/Clip addressing, stance enter/exits, turn-in-place (T-1088)
ual1.glb (120 clips) + ual2.glb (134) replace the free-tier subsets;
CharacterVisual loads both under explicit library names with lib/Clip exact
addressing (bare names keep cross-library search). Gait table re-pointed;
two new one-shot overlays in the gait machine: stance transitions
(Sprint/Crouch Enter/Exit, destination-Enter priority, clip-length timer,
movement never stalls) and turn-in-place (Turn90/180 L/R from shortest-arc
sign, >=60/135 deg idle yaw jumps, retrigger-guarded). All clip names
verified by dumping the imported GLBs. 70 gait tests + live smoke green
(both libraries load with exact counts).

S9 live-tune list: turn handedness (unverified headless — one-line swap),
TURN_SPEED_SCALE=2.0 compromise vs the snappy yaw ease, turn-on-stop feel.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:53:35 +02:00
jpmschweitzerandClaude Fable 5 e46aa404fc test(client): corner-reveal proof updated to D-252 semantics + deterministic cone wait
The proof encoded move-writes-facing ('Move West -> now facing West'); under
D-252 the view changes only via SetFacing, so the test now looks West
explicitly (helper gains an action_data passthrough). The +2-tick wait could
catch the facing flip before the visibility recompute — the assertion now
polls (bounded) until the westward cone content lands.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:15:32 +02:00
jpmschweitzerandClaude Fable 5 629c0a9b1e feat(client): D-252 view/movement split — body from leg velocity, view is the mouse (T-1093)
D-252 (new record, amends D-054/D-249, resolves Q-084's walk-vs-aim split):
Facing is view-only; movement no longer writes it. Client side: the rig's
moving-body yaw now always derives from leg velocity (the wire octant is the
VIEW and must never rotate the body — the follow-only commit flag
generalizes and disappears); the layered head/torso look-at runs during any
movement, WASD included; the ~100ms post-step re-assert mitigation is
removed as dead (server-side facing_from_delta removal lands separately).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 20:59:06 +02:00
jpmschweitzerandClaude Fable 5 6453f71220 fix(client): re-assert mouse facing after each follow step — vision-cone flap mitigation (T-1088)
Live finding: during a path-follow the cone snapped to path-forward on every
accepted step (server facing_from_delta overwrites Facing; the change-gated
SetFacing never re-sends an unchanged octant) and stayed there until the
mouse crossed an octant boundary. The follower now re-asserts the current
mouse octant 100ms (~2 ticks) after each emitted step — same-tick re-asserts
lose, movement wins within a tick — shrinking the flap to a ~100ms blip.
Full stability needs server-side aim-lock: filed T-1093 (Q-084's walk-vs-aim
split, now with play evidence); wire semantics untouched per the sidequest's
presentation-layer constraint.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 20:44:52 +02:00
jpmschweitzerandClaude Fable 5 cb6b39331d feat(client): follow-facing — body commits to path, mouse drives layered head/torso look-at (T-1088)
During an RMB path-follow the body yaw locks to the leg direction (rig
commit_body_to_motion, from interpolated velocity — immune to SetFacing
interleaving between throttled steps). The mouse instead drives a layered
look-at: LookAtModifier3D pair on Head (±70°) + spine_02 (±30° torso twist
for looking far lateral/behind — past their sum the character physically
cannot look further without turning). Forward axis measured from the
armature rest pose (+Z), not guessed. Influence fades in/out on follow
start/end. Pure client presentation per D-249; SetFacing still rides the
wire, so the server vision cone follows the mouse while walking — the
character looks where the player points.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 20:35:43 +02:00
jpmschweitzerandClaude Fable 5 72f88dae1d fix(client): median-of-5 timing in fog perf test — kills load flake (T-1092)
test_visibility_texture_update_performance asserted a single wall-clock
sample against the 0.5ms budget and blocked two pushes while a live session
shared the machine (0.704ms / 0.623ms; 46/46 green in isolation). Median of
5 runs preserves the D-059 budget intent while absorbing load spikes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 20:18:01 +02:00
jpmschweitzerandClaude Fable 5 6372537f2f fix(client): bottom-wall scan pierces 2-thick walls + corner diagonal; whole-box quantized cuts (T-1088)
Live-session findings, three in one: (1) Gauntlet walls are 2 tiles thick —
the outer row's camera-far neighbor is the inner WALL, so its flag stayed 0
and it stood full-height behind the collapsed inner stub; the flag now scans
up to 3 tiles through consecutive KNOWN walls (unknown stops the scan — no
assumptions past the info boundary), and any newly-learned tile refreshes the
wall chain behind it. (2) Corner pillars at the screen-bottom junction never
collapsed — their interior floor sits DIAGONALLY behind, which no straight
scan line reaches; the combined diagonal joins the scan set. (3) Per-fragment
smoothstep falloff carved organic notches (the 'sphere' read) — the cut now
computes per-instance in the vertex shader from the box's tile center and
quantizes at 0.5: every wall box is either full or stub, X2-crisp.

Verified via locomotion_cutaway autopilot capture: south runs read as clean
stub rows, corners collapse, camera-far walls stand.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 20:10:28 +02:00
jpmschweitzerandClaude Fable 5 49c8994943 feat(client): move-here path preview + RMB gesture vocabulary (T-1088, Q-084)
Hover marker + optimal path line over the KNOWN tile store only — the
character plans through what they know; fog is unpathable (info boundary at
the planning layer; the follower additionally revalidates every remaining
tile per step). Pure static 8-dir A*: uniform cost 1 incl. diagonals (D-248
time-optimal, no sqrt2), no corner-cutting, terrain-cost provider seam for
Phase-4 terrain. Execution streams ordinary Move* steps through the existing
throttle — zero protocol change, server validates every step.

RMB vocabulary (live-session spec): click = walk there at current stance;
double-click = sprint there (ToggleStanceUp burst — server toggle handler
verified cooldown-free so bursts climb deterministically — with net-zero
restore on arrival; a double upgrades the active follow in place);
long-press >=400ms = go there then Crouch on arrival, no restore (input-
vocabulary prototype; real cover mechanics are future combat design).
Cancellation: WASD override (stance kept), invalidation, teleport,
suppression. Two additive InputMapper seams (queue_move_step,
queue_stance_toggle); mouse unproject shared with the facing provider
(ground_hit_local). 44 new gdUnit tests across finder + follower ladders.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 19:52:03 +02:00
jpmschweitzerandClaude Fable 5 98032e9d5a feat(client): cutaway defaults to mode 2 (both) — user verdict from the live session (T-1088)
Known interiors open at a glance (bottom walls low) AND the sightline
corridor punches through unexplored exterior walls; C still cycles modes.
sandbox_constants.gd also carries the path-preview/gesture knobs for the
click-to-move feature landing in the next commit (single tuning file).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 19:51:38 +02:00
jpmschweitzerandClaude Fable 5 e32a041778 feat(client): cutaway modes — corridor / bottom-walls-low / both, C-key live toggle (T-1088)
Live-session two-minds resolution: the trippy sightline corridor stays as a
mode (candidate signature look); mode 1 drops any wall with a KNOWN non-wall
tile on its camera-far side to the stub (X2 bottom-walls read — the player
sees the room without walking to it); mode 2 combines. Bottom-wall flag rides
MultiMesh INSTANCE_CUSTOM.r, updated on paint and when newly-learned floors
promote camera-ward walls; camera-far directions derived from the live
WorldRoot basis, never hand-derived. Default stays corridor pending the
feel verdict.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 19:16:14 +02:00
jpmschweitzerandClaude Fable 5 2db7dd25e1 fix(client): cutaway cuts the camera-character sightline corridor, not a proximity dome (T-1088)
Live-session finding: the half-disc cut bit nearby side walls while leaving
the wall actually occluding the character at full height, with dome-contour
scallops across the two-row walls. Rewritten as a sightline corridor: walls
within CUT_CORRIDOR_HALF_W of the character's x, camera-side, out to a
pitch-derived reach (WALL_H / tan(pitch) — how far a wall can still occlude
in ortho; ~4.3 m at -30, breathes with the T-cycle tilt) drop to the stub.
FollowCamera3D exposes pitch_deg(); reach clamped 0.5-16 m.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 19:01:49 +02:00