Commit Graph
219 Commits
Author SHA1 Message Date
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 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 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 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 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 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 a7801a942a feat(client): 3D locomotion sandbox — character walks the live Gauntlet (T-1088)
New SR_LIVE sandbox scene: CharacterVisual composited in a 3D greybox world
derived from server snapshots. Per-leg constant-velocity interpolation keyed
to the stance throttle, 'server feet / client eyes' facing (wire octant while
moving, client aim octant idle), cadence-synced gait state machine on
AnimationPlayer custom blends, D-148 orthographic follow camera (-30deg
default, T-cycle presets), sim-space grid shader, camera-side wall cutaway,
accumulating never-evict tile store with four-state visibility tint.

Additive seams only: InputMapper.facing_angle_provider (2D path unchanged),
CharacterVisual.play_animation blend_time param + get_animation_player().
Visual harness gains per-scenario scene field + SR_AUTOPILOT input scripting.
210 new gdUnit assertions across five suites; verified live (230/230 total,
clean smoke, screenshot at .cache/screenshots/locomotion_idle_live.png).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:23:16 +02:00
jpmschweitzerandClaude Opus 4.8 568370b7c2 fix(client): space proof moves past the stance cooldown — test_proof_corner_reveal flake (T-1037)
test_proof_corner_reveal drove moves back-to-back. The Gauntlet player is
Walk stance (ticks_per_move=2, server stance.rs), so a move arriving within
the cooldown window is silently throttled (movement.rs::apply_move ->
PlayerMoveCooldown::try_move). _send_and_receive returned the instant a move
landed, so the next send raced the cooldown and an occasional move was
dropped — leaving the player one tile short, which (via the test's cumulative
expectations) cascaded into the corner-reveal perception assert. A different
move dropped each run = flaky; exposed by the heavier post-cascade tick timing.

Test-side fix only — game behaviour is correct and the Gauntlet world is
immutable: after a move lands, wait for the server clock to advance
COOLDOWN_TICKS past the landing tick before the next send. Full client suite
2538/2538 green; the proof suite drops from ~28s (timeouts) to ~4s.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 16:24:13 +02:00
jpmschweitzerandClaude Opus 4.8 cfd5e18d0a feat(client): district morphology generation overlay in the Atlas (T-1046, D-226)
Client half — the cascade's district tier is now visible in the Atlas map.

- protocol.gd: decode the district_grid field from AtlasLayerResponse.
- atlas_viewer.gd: a gen_district ('MRPH') toggle in OVERLAY_DEFS + the
  district-grid generation state (set/get) + wire it from the Ready response.
- atlas_marker_overlay.gd: _draw_gen_district() paints the coarse cols×rows grid,
  each cell coloured by its MorphologyZone discriminant (D-239 §6, 17-zone
  palette), semi-transparent under the Layer-1 line overlays. Planetary map view
  (not the 2km on-demand districts — those are Phase 5 in-world).

gdUnit4 test: overlay registered + district grid round-trips through the viewer
and the protocol decode. Visual tuning of the palette can follow once eyeballed
in the running Atlas.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 12:55:59 +02:00
jpmschweitzerandClaude Fable 5 c64231e8ee fix(client): clear the test debt — 2 production bugs, suite fully green (T-973 et al.)
Production fixes surfaced by honest test triage:
- hud_groups.gd: _set_group_z crashed on freed HUD nodes — the typed loop
  variable errors before the is_instance_valid guard runs; prune first
- fog_state.gd: _resize cleared _prev_visible (world-space keys survive
  resizes), so pre-resize tiles never decayed VISIBLE→EXPLORED (D-059)

Test debt (T-928/929/934/935/936/937/938/939, T-864, T-973): lambda
local-capture bugs rewritten with array captures (now assert exact
emission counts), e2e suites updated to the current handshake +
StartupMessage protocol and stream-aware reads against the live binary,
fog perf test measures steady state, chime test pins the shipped 800ms
catalog asset (D-067 amended separately), monologue gdUnit4 API typo,
battery-warning tests follow the MetaScreen on_open lifecycle. 3 sprint2
proof tests revived (corner_reveal had passed from the wrong tile — NPC3
blocks (18,14); route corrected). Soft-skips converted to real do_skip
reporting. T-1068: 7 orphan .gd.uid deleted, _format_pop/_format_radius
deduped into atlas_format.gd (preload, no class_name — headless cache).

Suite: 1264 cases/20 failures → 1268/0, independently re-verified
(2536/2536, exit 0).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 16:22:01 +02:00
jpmschweitzerandClaude Opus 4.8 b73b4e6d92 feat(simulation): Layer-1 water bearing + D-234 waterfront rule (#957)
Close the last D-234 piece — terrain water-direction extraction wired through
to founding orientation and the quarter waterfront rule:

- Layer 1: TerrainAnalysis::water_bearing — 8-octant integer bearing toward the
  nearest water from the water_dist gradient (D-010, no atan2). Stored on
  GeographicAttractor.water_bearing (360 = none).
- #956 founding orientation: coastal/river settlements now get a real
  water-facing bearing (the anchoring attractor's), replacing the 0 stub.
- #957 waterfront rule (D-234b): the water-facing quarter edge (from the
  settlement's Coastal founding orientation) drops its block setback to 0 so
  buildings present flush to the quay (dock-orthogonal). Typed Edge + coastal_edge
  + per-block gating.

Golden + atlas_response fixture rebaked (additive water_bearing field only).
8 new tests. All integer-deterministic (D-010).

Pending: the waterfront rule reads context.founding_orientation, which
city_context_reader still stubs to Cardinal — real per-settlement orientation
reaches quarter generation once the Layer-3 placement -> Layer-4 GenerateSkeleton
dispatch is wired (the remaining cross-layer integration).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 10:22:14 +02:00
jpmschweitzerandClaude Opus 4.8 31d7870823 test(simulation): rebake golden + atlas fixture for Layer-3 enrichment (#956)
DrainageBasin gained a territorial_status field, so the Layer-1 golden and the
atlas_response msgpack fixture pick it up. Both changes are additive/
representational — basin/attractor/river selection is unchanged.

- cascade_golden.rs: pass None dominant_faction; golden rebaked (only
  "territorial_status": "FrontierUnclaimed" added per basin).
- gen_fixtures.rs: set the new field in the atlas_response generator; fixture
  regenerated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 14:57:18 +02:00
jpmschweitzerandClaude Opus 4.8 adce12035a fix(simulation): route atlas_response fixtures in the serialize test
all_fixtures_deserialize panicked with "unknown fixture naming
convention: atlas_response_ready" — the test reads every .msgpack in
client/tests/fixtures/msgpack but had no branch for the atlas_response
prefix (the AtlasLayerResponse fixtures gen_fixtures emits, #969). A
pre-existing gap (present on main; the suite isn't gated by the pre-push
hook, which runs cargo-deny/ruff/JSON/stamp, not cargo test).

Add an atlas_response branch deserializing AtlasLayerResponse, and
regenerate atlas_response_ready.msgpack so its bytes match the now-integer
GeographicAttractor types (#955).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 16:31:19 +02:00
jpmschweitzerandClaude Opus 4.7 89596dbb87 test(client): live atlas generation-overlay inspection scenario (#960)
atlas_gen_open opens the real atlas app via HudGroups, navigates to GJ71c's
regional screen, and enables the Layer-1 overlays — exercising the production
opener + the live request→cascade→render pipeline against a --test-mode server
(SR_LIVE=1). Capture with: tests/run-visual --screenshot atlas_gen_open.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 10:09:48 +02:00
jpmschweitzerandClaude Opus 4.7 adbd7f6744 fix(simulation): Layer-1 self-describing grid dims + Moore basin trace (#960)
Two Layer-1 generation fixes surfaced by the per-layer atlas viewer:

- Layer1Output now carries grid_w/grid_h (the downsampled working-grid the
  positions live in). The client maps overlays from these, so the scale is
  correct for any source heightmap resolution rather than assuming the texture
  size — fixes overlays projecting at half scale into a corner.
- Drainage basin boundaries are traced as ordered, non-self-crossing contours
  via Moore-neighbour tracing instead of an angle-from-centroid sort. The sort
  produced star-shaped, self-crossing polygons for concave basins that rendered
  as straight chords across the map.

Golden (cascade_layer1.json) and the cross-language atlas_response_ready
fixture regenerated. 100 atlas lib tests + the new tracer test pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 10:02:32 +02:00
jpmschweitzerandClaude Opus 4.7 c3bcf09c72 chore(client): gdformat visual_capture + visual_scenarios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 22:54:51 +02:00
jpmschweitzerandClaude Opus 4.7 6bb21db2cb test(client): implant_pending visual scenario (#960)
Ad-hoc capture scenario that overlays the ImplantPending "generating" indicator
on the booted scene for visual sign-off — `tests/run-visual --screenshot
implant_pending`. No golden committed; this is an inspection scenario, not a
regression gate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 22:53:57 +02:00
jpmschweitzerandClaude Opus 4.7 9733cbc0af fix(client): visual_capture Protocol decode in -s mode
visual_capture.gd runs via `godot -s`, where the `class_name` registry isn't
populated — so the bare `Protocol.decode_snapshot()` reference failed to
compile, breaking ALL --screenshot/--movie/golden captures (not just the
replay scenarios that use it). Instantiate the script and call the static
decoder on the instance (then free), matching the file's existing -s-mode
load() workaround for VisualScenarios.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 22:51:51 +02:00
jpmschweitzerandClaude Opus 4.7 25ef9d2f63 feat(ui): ImplantPending — diegetic Layer-1 "generating" indicator (#960)
A reusable implant-layer busy indicator (Araminta's spec): an animated
bracket sweep `[ >>>>···· ]` with a caption, themed entirely via ImplantTheme.
The Atlas shows it centered over the canvas while the Layer-1 proxy is Pending
and hides it on Ready — so cache hits never flash it and serverless mode never
shows it.

Layer separation (per direction): ImplantPending belongs to the diegetic
implant UI only. The global, non-diegetic UI layer must use its own busy
indicator with its own visuals — sharing the sweep logic is fine, resting on
this theme/these glyphs is not. Documented in the component header.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 21:05:39 +02:00
jpmschweitzerandClaude Opus 4.7 69ed80c96c chore(client): gdformat pass on atlas codec scripts
Formatting-only cleanup of the #969/#960-A codec + bridge scripts to match
gdformat output (the pre-push gdformat check is advisory; these landed
un-formatted). No behavior change — 70/70 protocol tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 19:03:29 +02:00
jpmschweitzerandClaude Opus 4.7 33a2ed6aae feat(ui): atlas generation overlays — Layer-1 rivers/basins/attractors (#960)
Render the proxied Layer1Output (D-225) as toggleable Atlas overlays. The
viewer stores the decoded Layer1Output via set_generation_layer1() and the
marker overlay draws three new layers gated by their visibility flags:

  RVR  river_cells as dots, confluences as small circles, mouths as
       double-ring sea-terminus markers
  BAS  drainage-basin boundaries as thin closed polylines + faint fill
  ATR  geographic attractors — shape by attractor_type (Araminta's 7-shape
       vocabulary), color by sub_biome, size by strength; <0.15 culled

Layer-1 positions are [row, col] in the 512x256 working grid, which matches
the viewer's grid_to_canvas transform, so they project directly onto the
heightmap. OVERLAY_DEFS gains gen_l1_rivers/basins/attractors (toggle group)
so AtlasOverlayBar auto-exposes them as toggle buttons.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 19:02:02 +02:00
jpmschweitzerandClaude Opus 4.7 03d0264eb0 feat(client): atlas request/response transport on the bridge (#960, D-225)
Client transport half of the layer-stream protocol.
- SimBridge.request_atlas_layers(body_id) sends an AtlasLayerRequest frame
  (live mode only; no-op in test mode); responses arrive via a new
  atlas_layers_received signal.
- receive_bytes now decodes each frame ONCE via Protocol.decode_inbound and
  branches by shape (snapshot vs atlas response) — avoids double-decoding the
  20 Hz snapshot path. decode_snapshot is split into decode_raw +
  _decode_snapshot_from_raw (public decode_snapshot unchanged, so the 70 protocol
  tests stay the regression guard); decode_inbound returns {kind, value}.

70/70 protocol tests pass, including the new decode_inbound classifier test.
(Pre-existing client-suite failures in server-dependent e2e/roundtrip + unrelated
audio/fog/dialogue suites are unchanged — verified identical at baseline.)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 18:36:39 +02:00
jpmschweitzerandClaude Opus 4.7 a9ba8ba90c feat(client): atlas layer-stream codec — encode request / decode response (#960, D-225)
Client half of the layer-stream protocol (codec only; transport wiring next).
- protocol.gd: encode_atlas_layer_request (bare {body_id, up_to} map so the
  server demux routes it to the proxy, not the PlayerInput array) and
  decode_atlas_layer_response (-> {body_id, status, error, layer1}; returns null
  for non-atlas frames, e.g. a snapshot, so receive_bytes can disambiguate).
- gen_fixtures.rs: cross-language fixtures (atlas_response_ready/pending/
  not_found) from real rmp_serde output, matching the test_protocol.gd pattern.
- test_protocol.gd: 5 tests decode the fixtures + verify a snapshot is not
  mistaken for a response + the request encodes to the right shape. 68/68 pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 18:25:41 +02:00
jpmschweitzerandClaude Opus 4.6 702fed5922 fix(client): address PR #141 review — parse-order, tween guard, test fixes
- Fix autoload parse-order violations: sim_bridge.gd, input_mapper.gd,
  audio_manager.gd now use load() for class_name types instead of
  direct references (LocalBridge, ServerProcess, Constants)
- Collapse redundant tween validity guard in dialogue_box.gd to
  is_instance_valid(panel) only
- Add clarifying comments to fog test resize assertions (8-tile
  padding trigger, 32x32 fixture assumption)
- Fix test_examine_display_sprint18 case 2: GameState.has() →
  "field" in GameState (Node vs Dictionary API)
- Fix test_game_state_sprint20: rename before_each → before_test
  (GdUnit4 lifecycle hook)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-02 22:50:17 +02:00
jpmschweitzerandClaude Opus 4.6 e002f811f0 fix(client): free camera review fixes — action toggle, zoom reset, discrete guard, tests (#898)
- Replace raw KEY_F4 check with Input.is_action_just_pressed("free_camera") to
  consume the registered project action (matches F3/F12 dev toggle pattern)
- Reset camera.zoom to Vector2.ONE when toggling free camera off so zoom does
  not bleed into normal gameplay
- Add free_camera_mode guard to InputMapper._unhandled_input() so discrete
  actions (INTERACT, stance, pause) are suppressed alongside movement
- Add client/tests/test_free_camera.gd: flag default, movement suppression,
  discrete action suppression, zoom constant contracts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-02 18:03:06 +02:00
jpmschweitzerandClaude Opus 4.6 3f5b4258ba feat(client): sprint 38 — free camera viewer, archetype strip, test fixes
- Add free camera mode (F4 toggle): WASD pan, scroll zoom, decoupled
  from player position (#898)
- Strip archetype-driven code: remove character_archetype, lattice_profile,
  and lattice color palettes from client (#882)
- Fix confrontation_monologue signal not firing in headless test mode (#867)
- Revive fog state behavioral tests: EXP_EXPLORED persistence, grow-only
  bounds, texture-resize copy, BoundaryWall handling (#879)
- Triage pre-existing test failures: fix examine_display dismiss timing,
  fog test position fragility, rendering snapshot assertions,
  time_display format (#871)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-02 10:19:11 +02:00
jpmschweitzerandClaude Opus 4.7 60733738f2 fix(client): PR #135 review — T1/T3/H4-H7 blocking + nits
Code changes addressing PR #135 review (Tyre + Hoshe):

- **T3 (blocking):** test_merge_path_flows_sprint37.gd `_load_main_menu`
  and `_load_char_create` now assert the scene loaded instead of silently
  returning. Missing .tscn → red test, not falsely green.
- **T1:** sim_bridge.gd signal `handshake_complete(protocol_version: int)`
  was D-192 residue with no listeners. Drop the int parameter entirely
  and the literal-0 emit.
- **H4:** test_new_game_catalog_snapshot_resolves_loading_state now
  asserts SimBridge.state == CONNECTED terminus, not just the loading
  flag — guarantees full flow completion, not merely flag-clear.
- **H5:** test_protocol_bridge.gd file-level comment refreshed; drops
  reference to removed protocol-version check tests.
- **H6:** test_p0_regressions.gd `_make_snapshot_bytes` comment refreshed
  and version field removed from fixture dict (D-192: not required).
- **H7:** test_merge_path_flows_sprint37.gd `_make_catalog_snapshot`
  drops version field from fixture dict (D-192).

Follow-up tickets filed for reviewer suggestions:
- **T2:** #889 — revive EntityRenderer sprite constants coverage
  (D-044 ENTITY_WIDTH/HEIGHT, asserted by deleted test_sprite_integration).
- **T4:** #890 — UI timeout fallback for bookmark catalog wait in
  main_menu (systemic 'catalog never arrives' class beyond #872's
  TCP-batch race).
- **T5/T6:** #891 — scene-flow test tier docs + test-only reset
  helpers (SimBridge.reset_for_test, MetaStack.reset_for_test) +
  minimal public API on scenes so UI refactors don't break all four
  flow tests simultaneously.

Verification:
- `make lint-client` — no script errors
- `gdlint client/scripts/ client/ui/` — no problems
- `make test-client` — 2428/2488 passing. 60 remaining failures are
  pre-existing, unrelated to sprint 37 (test_dialogue_sprint20 #558
  signals, test_input_roundtrip integration-sans-server, etc.).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 10:06:39 +02:00
jpmschweitzerandClaude Opus 4.7 bc310a3ca5 fix(client): sprint 37 test-pass fixes — parse error + test hygiene
- character_creation.gd:1614: type-annotate dir_name to fix GDScript
  inference parse error. CARDINAL_DIRS is an untyped const Array, so
  `var dir_name := CARDINAL_DIRS[idx]` failed type inference and blocked
  test_character_creation_sprint28.gd test discovery. Runtime was lenient
  but gdUnit4's scanner is strict.
- test_merge_path_flows_sprint37.gd (_load_char_create): call add_child
  before _update_start_btn_state so @onready vars (_footer_start) are
  bound when the helper dereferences them. Unblocks 2 merge-path tests.
- test_anti_tedium.gd (test_bug_report_sends_unpause_on_close): call
  dialog.close() instead of the old dialog._close() rename casualty.
  `on_close()` is a lifecycle hook — it doesn't actually transition
  state; `close()` is what MetaScreen exposes.
- test_anti_tedium.gd (before_test): clear MetaStack._stack. Prior tests
  leave stale freed dialog refs on the stack; _any_pausing() iterates
  the stack during close() and crashes with "previously freed".

Net: sprint-37 test files (test_merge_path_flows_sprint37.gd,
test_anti_tedium.gd) now pass. Overall suite: 2428/2488 passing
(60 remaining failures are pre-existing, unrelated to sprint 37).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 09:06:51 +02:00
jpmschweitzerandClaude Sonnet 4.6 d72fcc7847 fix(protocol): drop PROTOCOL_VERSION lockstep — D-192 (#875)
Removes the version-mismatch guard from Protocol.decode_snapshot() and the
PROTOCOL_VERSION constant from the client (server side done in #874).

Core changes:
- protocol.gd: remove const PROTOCOL_VERSION, remove version mismatch guard,
  remove "version" from return dict, add gauntlet_mode/room_id decode
- sim_bridge.gd: remove handshake version check; relax handshake guard to
  require only a valid Dictionary (server no longer sends protocol_version);
  emit handshake_complete(0) for API compat
- loading_screen.gd: drop "· protocol N" suffix from version label
- test_harness.gd: replace Protocol.PROTOCOL_VERSION with literal 23

Test updates (21 files): replace "version": Protocol.PROTOCOL_VERSION with
"version": 23 in all snapshot bytes dicts; remove snapshot.version == N
assertions; remove version-rejection tests (test_rejects_version_6,
test_decode_snapshot_rejects_missing_version, test_decode_snapshot_rejects_old_version,
test_protocol_rejects_version_mismatch, test_sim_bridge_test_snapshot_uses_current_protocol_version).

Also includes: #872 bookmark_catalog carry-forward regression test, and
#873 merge-path flow tests (test_merge_path_flows_sprint37.gd).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 17:29:49 +02:00
jpmschweitzerandClaude Sonnet 4.6 708ab25614 fix(tests): sprint-37 sweep — #866, #869, #870 client test cleanup
#866: Fix _escape_bbcode in dialogue_box.gd — only escape '[', not ']'.
Chaining .replace(']', '[rb]') after .replace('[', '[lb]') corrupted the
[lb] escape itself. Updated test_dialogue_sprint18.gd accordingly.

#869: Migrate test_anti_tedium + test_bug_report_ring_buffer to instantiate
BugReportDialog via .tscn (BugReportDialogScene.instantiate()) instead of
bare Control.new() + set_script(). Sprint 36 moved bug_report_dialog.gd to
extends MetaScreen; the old load path was an orphan .uid file returning null.

#870: Delete 8 parse-error test files (test_debug_overlay_sprint19,
test_entanglement_sprint22, test_fog_sprint22, test_journal_sprint18,
test_minimap_sprint18, test_session_manager_sprint19, test_sprint30,
test_sprite_integration) + .uid sidecars. Coverage tickets filed for
fog/journal/minimap (#879, #880, #881). Sprint docs updated with #882 note.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 17:28:24 +02:00
jpmschweitzer 50ac9041ad fix(tests): PR #134 review round 3 — seed start-state + PID log path
- test_character_creation_sprint28: before_each now seeds
  _selected_bookmark_id and _selected_location_id so the new disabled-
  guard in _on_start() (round 2) doesn't silently block 5 existing
  tests that call _on_start()/KEY_ENTER without setting up a valid
  bookmark selection. Restores the 2 tests Hoshe flagged as R2-H1 plus
  3 siblings that would have degraded the same way under the guard.
- tests/run-godot: LOG_FILE now includes $$ (PID) so concurrent runs
  across worktrees don't clobber each other's logs. Path is echoed
  back via the stdout JSON "log" field and the stderr hint line, so
  callers never need to predict it (R2-H2).
2026-04-21 14:30:38 +02:00
jpmschweitzer 8cd5405427 fix(tests): unstick compositor cleanup; drop tautological version asserts
test_character_visual_sprint28: after_test() was freeing every node
returned by get_children(), including GdUnit4's own internal infrastructure
attached to the suite. That destroyed the runner mid-suite, hanging
make test-client indefinitely on the second compositor test. Now tracks
the nodes _make_compositor() spawned and frees only those. Suite goes
from "hangs forever" to 52/52 pass in 39s.

test_protocol_bridge, test_signal_sprint24: delete the
test_protocol_version_is_NN assertions. They asserted a constant equals
its own literal, failed mechanically on every protocol bump, and never
caught a real bug. Field-presence and roundtrip behavior is covered by
the surrounding tests; the runtime mismatch guard is exercised by
test_rejects_version_6. Surfaced D-192 (drop the version handshake
entirely) — see ticket #868.
2026-04-21 09:48:26 +02:00
jpmschweitzerandClaude Opus 4.6 c68197f86a test(client): align test_character_creation_sprint28 to 4-tab structure
W5 restructured character_creation's TabContainer to 4 top-level tabs
(Bookmark, Appearance, Skills, Debug) from the old 5-tab flat layout.
Three assertions in test_character_creation_sprint28.gd still referred
to the old shape; they didn't fail because the suite runs vacuously
in headless (the 3D SubViewport scene can't instantiate without a
render context), but the assertions were stale and would fire wrong
once the suite eventually runs non-headless.

Fixed:
- test_tab_container_has_five_tabs → renamed test_tab_container_
  has_four_tabs, expected count 5 → 4.
- test_tab_names: expected ["Body","Head","Hair","Clothing","Debug"]
  → ["Bookmark","Appearance","Skills","Debug"]
- test_tab_navigation_wraps: current_tab = 4 (invalid on a 4-tab
  container) → 3.

Header note added documenting the vacuous-headless behavior so the
suite reads correctly.

88/88 pass — unchanged — but the assertions are now correct for
non-headless invocation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-20 22:40:04 +02:00
jpmschweitzerandClaude Opus 4.6 35858fa0fa feat(ui): Bookmark tab content + CharacterProfile signal payload (Workstream 6)
Fills in the Bookmark tab stubbed in W5 with the full spec from
Araminta: card list + detail view + start-button gating. Also changes
the creation_confirmed signal to carry a CharacterProfile instead of
bare CharacterVisualDescriptor, consolidating bookmark + location
selection into one payload object.

Bookmark tab (left pane, 35%):
- ScrollContainer over VBoxContainer of card Buttons, one per entry in
  GameState.bookmark_catalog. Each card: title (PRIMARY_TEXT,
  font_header 15px) / subtitle (DIM_TEXT, font_small 10px, clipped) /
  career badge (ACCENT_ACTIVE, all-caps). Selected state uses existing
  ITEM_SELECTED_BG + ITEM_SELECTED_BORDER. custom_minimum_size
  Vector2(180, 64).

Detail view (right pane, 65%):
- ImplantPanel composed via add_component:
  - ImplantHeader (bookmark.title, bookmark.subtitle)
  - ImplantSeparator
  - ImplantTextBlock (flavor, autowrap, PRIMARY_TEXT)
  - ImplantSeparator
  - ImplantDataRow CAREER (accent_active) / CAPITAL (accent_positive,
    format "%d Tractus") / STARTING LOCATION
  - ImplantSeparator
  - [location picker space reserved — W7 fills it]

Selection:
- Card click stores _selected_bookmark_id, auto-assigns
  _selected_location_id from bookmark.default_location, rebuilds
  detail view.
- Start button (footer) gated on both _selected_bookmark_id and
  _selected_location_id non-empty.
- Randomize while Bookmark tab is active picks a random bookmark +
  one of its allowed_locations and skips appearance randomization.

Signal contract change:
- creation_confirmed(profile: CharacterProfile) replaces
  creation_confirmed(descriptor: CharacterVisualDescriptor).
- CharacterProfile now extends RefCounted (was Resource) with
  non-exported fields — it's a one-shot signal payload, never
  persisted. This also sidesteps the scanner error that the prior
  @export var descriptor: CharacterVisualDescriptor on a Resource
  caused (RefCounted types cannot be @export-ed).
- _on_start emits a CharacterProfile built from _descriptor +
  _selected_bookmark_id + _selected_location_id, then sends
  ConfirmBookmark via SimBridge.send_named_action before scene
  transition.

Test updates:
- test_character_creation_sprint28.gd signal receivers switched to
  untyped to accept CharacterProfile without hitting class_name
  parse-order at test-suite scan time. 88/88 pass.

Verification:
- gdlint clean
- godot --headless --path client --quit — no SCRIPT ERROR (prior
  character_profile.gd scanner noise now gone after the RefCounted
  conversion)
- test_character_creation_sprint28 88/88, test_protocol 62/62,
  test_implant_nav_stack 52/52

Workstream 7 (location picker as sub-component of the Bookmark detail
view) follows. W8 fills the Skills tab.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-20 19:36:00 +02:00
jpmschweitzerandClaude Opus 4.6 88202ad679 test(client): triage 3 chronically broken test suites (Task #21)
Clean the regression signal for the remaining MetaScreen workstreams
by either fixing or surgically skipping tests that had been failing
for design reasons or against stale APIs.

test_sprint2_proof.gd — all 3 tests prefixed skip_test_. Root cause:
hardcoded Sprint 2 room coordinates + protocol v1 assumptions; not
adaptable to current protocol v23 or Gauntlet layout. Suite now reports
0 tests rather than 14 failures / 3 errors.

test_dialogue_sprint18.gd — 40 tests pass (was 48 errors / 3 failures).
Root cause of the errors: GameState.has() calls hitting Node.has()
which does not exist. Fixed by removing guards and accessing
GameState.current_examine_result directly (present since v14 / #174).
Two real bugs surfaced after the error noise cleared; skipped with
ticket references:
- #866 (high): dialogue_box._escape_bbcode chains .replace('[','[lb]')
  .replace(']','[rb]') which turns [lb] into [lb[rb]. BBCode injection
  guard broken.
- #867: confrontation_monologue signal doesn't fire in headless; the
  create_tween call in _start_confrontation_beat likely aborts before
  the emit.

test_client_p2.gd — 26 tests pass (was 2 failures). Three #117-fallout
camera-smoothing tests skipped (main.gd disables
position_smoothing_enabled permanently by design since #117 manual
lerp). One MonologueDisplay API test skipped pending #864 (asserts
mono.is_visible, but the display was refactored to _visible:
Array[Dictionary]).

No production code changes. Every skipped test carries a skip_test_
prefix + inline TODO pointing at the owning ticket. Bug tickets #864,
#866, #867 filed to the backlog.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-20 19:33:57 +02:00
jpmschweitzerandClaude Opus 4.6 41e895796c feat(client): protocol v23 — bookmark_catalog decode + bookmark actions (Workstream 3)
Adds client-side wire support for the bookmark catalog (#614) and the
two associated player actions. PROTOCOL_VERSION bumps from 21 to 23:
- v22 (server): RequestBookmarkCatalog + ConfirmBookmark player actions
- v23 (server): bookmark_catalog field on ObserverSnapshot

Decode:
- protocol.gd decode_snapshot extracts optional bookmark_catalog.
  Defensive parse of BookmarkWire fields (id, title, subtitle, flavor,
  default_location, allowed_locations, allowed_locations_cultures,
  career, starting_capital_tractus). Missing or malformed → null.
- snapshot_handler.gd caches the catalog into GameState.bookmark_catalog
  on each snapshot (server pushes on tick 0; re-fetchable via
  RequestBookmarkCatalog).
- GameState gains bookmark_catalog: Array = [] (untyped per autoload
  parse-order discipline; default empty so callers can iterate without
  null checks).

Encode:
- encode_request_bookmark_catalog() — unit variant, sent to trigger a
  re-push if the cached catalog is missing.
- encode_confirm_bookmark(bookmark_id, starting_location_id) — struct
  variant matching server rmp_serde shape. Called from character
  creation on Start (lands in Workstream 6).

Tests:
- 5 new cases in test_protocol.gd: hand-built bookmark_catalog decode
  (all 9 fields asserted), fixture-based decode round-trip, missing-
  field null behavior, RequestBookmarkCatalog encode roundtrip,
  ConfirmBookmark encode roundtrip.
- All 12 existing snapshot fixtures regenerated from server via
  `cargo test --test gen_fixtures -- --ignored`. The new
  snapshot_with_bookmark_catalog.msgpack fixture was generated by the
  same pass.

Verification:
- gdlint clean
- godot --headless --path client --quit — no SCRIPT ERROR
- test_protocol 62/62, test_client_p3 24/24, test_implant_nav_stack
  52/52, test_implant_registry 42/42, test_implant_app_lifecycle 36/36

Workstream 4 (Option A sequencing via loading_screen + SimBridge
connect) lands next.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-20 00:25:10 +02:00
jpmschweitzerandClaude Opus 4.6 f24d08f756 refactor(ui): migrate 6 meta screens to MetaScreen pattern (Workstream 2)
Relocates main_menu, character_creation, settings_dialog, debug_console,
bug_report_dialog, loading_screen from flat client/ui/ into structured
client/ui/meta/screens/<name>/. All six now extend MetaScreen instead
of Control; the base handles open/close lifecycle, visibility,
captures_input, and — for overlays — the sim-pause contract.

Screen policies set per Tyre's proposal:
- settings_dialog: pauses_sim=false, PUSHES onto MetaStack
- debug_console: pauses_sim=true, PUSHES (D-088 routing via base)
- bug_report_dialog: pauses_sim=true, PUSHES
- loading_screen: closable_by_escape=false, PUSHES
- main_menu, character_creation: scene-roots, extend MetaScreen for
  the lifecycle contract only, do NOT push onto the stack

character_creation stays at its current surface (tabs, descriptor,
creation_confirmed signal unchanged). Tab consolidation and
CharacterProfile migration happen in Workstreams 5 and 6.

Knock-on changes:
- main.tscn ModalLayer CanvasLayer renamed to MetaLayer; main.gd
  @onready refs updated; constants.gd comment updated; test_client_p3
  and test_ui_framework_sprint15 assertions updated; test_monologue_display
  and .tscn header comments updated.
- OPEN_MENU handler now pushes settings_dialog onto MetaStack before
  calling open(). Full ESC priority chain lands in Workstream 4.
- atlas_app.gd: _unhandled_key_input signature widened from
  InputEventKey to InputEvent with an is-check, per Godot 4 API. Pre-
  existing narrowing was silently tolerated until main.tscn started
  fully instantiating under the new pattern.
- test_client_p3: entity_renderer type annotations corrected from
  ColorRect to Sprite2D (stale since a prior refactor); facing
  indicator rotation assertion switched to angle_difference() for
  modular-safe comparison.

Verification:
- gdlint client/scripts/ client/ui/ — zero problems
- godot --headless --path client --quit — no SCRIPT ERROR
- test_client_p3: 24/24 pass
- test_ui_framework_sprint15: 54/54 pass
- test_implant_nav_stack: 52/52 pass
- test_implant_registry: 42/42 pass
- test_implant_app_lifecycle: 36/36 pass

Workstream 1 foundation (84105916) remains unchanged. Workstreams 3-8
follow: protocol layer, Option A sequencing, 3-tab restructure,
Bookmark tab, location picker, Skills stub.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-19 21:58:24 +02:00
jpmschweitzerandClaude Opus 4.6 a8321702c3 test(ui): PR #131 review — implant primitive tests (item 5)
Three test suites covering ImplantNavStack, ImplantRegistry, and
ImplantApp lifecycle:

- test_implant_nav_stack.gd: 26 tests — push/pop/replace/reset,
  push_default, is_empty, current/current_payload, signal emission,
  re-entrancy guard, stack-floor-to-default-on-last-pop.
- test_implant_registry.gd: 21 tests — _is_valid_manifest validation,
  lazy scan flag, get_manifests / get_resolved_mode both trigger scan,
  cache on second call, real scan finds atlas and economics, no
  duplicate keys, _MODE_MAP coverage, get_app_instance null before
  instantiate_all, CURRENT_SCHEMA_VERSION = 1.
- test_implant_app_lifecycle.gd: 18 tests — nav created in _ready,
  starts hidden, open FULLSCREEN/INSERT makes visible, GAMEPLAY/wrong
  path doesn't open, nav non-empty on open, close hides, app-switching
  closes active, preserves_state true/false, on_insert_deactivated
  gated on INSERT (closes) vs FULLSCREEN (no-op), register_screen
  adds hidden child, duplicate id does not overwrite (first-wins).

Two team-lead fix-ups before commit (Stig caught the class_name
parse-order issue but used the wrong gdUnit4 hook names):
- before_each/after_each renamed to before_test/after_test per
  gdUnit4 API. test_game_state.gd's use of before_each appears to
  work by coincidence (that test resets autoload state rather than
  constructing objects, so the never-called hook didn't matter);
  tests that rely on hook-driven setup need the correct names.
- test_register_screen_duplicate_id_does_not_overwrite rewritten to
  assert the actual contract (first-wins on _screens dict +
  duplicate screen is not reparented) instead of Control.visible
  default, which defaults to true regardless of registration.

Also removed a stray client/ui/implant/apps/collision_test/app.tres
fixture left over from Hoshe's earlier manual collision-warning
verification. It was untracked and would have blocked atlas from
registering at runtime (KEY_M collision, collision_test won the scan
order). Not committing it.

All three suites exit 0, totals 26/26, 42/42, 36/36 passed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-19 15:36:04 +02:00