Commit Graph
434 Commits
Author SHA1 Message Date
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
jpmschweitzerandClaude Fable 5 b09a50efbf fix(tooling): depth-only epsilon bias for the clip discriminator + corrected findings
The pass-B garment shift is now a depth-only bias in the vertex shader (no
screen-space parallax), eliminating silhouette-growth false positives. This
supersedes the previous commit's mid-run numbers: final peasant run is
33/72 clip flags, ALL genuine tight-proximity findings — 0/18 on front
views (discriminator proof), sleeveless armhole seams on average_f (side),
deep-crouch waist gap (back, worst 150px), collar nape. Bare-arm-crossing-
torso cases correctly reclassed exposed_skin (non-gating). Sensitivity
knobs: clip_epsilon_m (3cm) + --min-pixels (8), tuned to surface tight
seams; calibrate against the first real modern garments.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 14:59:32 +02:00
jpmschweitzerandClaude Fable 5 74fa16260d feat(tooling): two-pass clip discriminator for garment QA (T-1089)
Second garment-only render pass per view at the identical paused animation
time; the analyzer intersects so body-key pixels split into exposed_skin
(no garment behind — informational: collars, sleeveless arms) vs
clip_through (garment behind — gating). Highlights differ: lime exposed,
red clip. Peasant re-run: 72 captures, 56 clip-through flags — real
collar micro-clips under crouch/walk plus suspected 1px boundary
artifacts; gate threshold + garment-mask dilation are the tuning knobs,
to be calibrated against the first real modern garments.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 14:51:04 +02:00
jpmschweitzerandClaude Fable 5 eaca6c8c44 feat(tooling): chromakey garment-clipping QA harness (T-1089)
Automated garment-under-animation QA: CharacterVisual composite with the
garment's covered body segments overridden to flat unshaded magenta, cycled
clips x frames x 4 yaws; PIL analyzer flags connected key-pixel blobs and
emits report.json + highlighted failure frames. Capture scene lives under
client/tools/garment_qa/ (res:// boundary; outside the gdUnit scan root),
driver/analyzer/config under tooling/garment-qa/.

Verified: 72 captures across peasant set x average_m/f x Walk/Sprint/
Crouch_Fwd. Finding: no true mid-cloth clip-through; flags are coverage-claim
vs silhouette mismatch (sleeveless/short-sleeve exposure at collar/cuffs) —
a two-pass garment-behind-pixel discriminator is the queued refinement.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 14:26:58 +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.8 8dc6c0ed42 data(content): address PR #146 review — Ghawr rename, faction pins, catalog
Review fixes for the #1019 heritage rebalance:

- Marib's farm world Ghouta -> Ghawr (Miri): "Ghouta" carries strong
  modern associations (2013 Damascus attack); Ghawr (the Jordan Rift
  Valley, an irrigated lowland farming region) keeps the water-fed-
  drylands sense without the resonance.
- Add dominant_faction = "independent" to GJ 914A / 508A / 1245B (Hoshe)
  — all five rebalanced systems are self-governing independent
  communities; uniform with siblings GJ 722 / 680 and clears V-FAC-01.
- catalog.md: fill GJ 914A -> Marib and GJ 722 -> Sığınak (Hoshe) — the
  two anchor systems still showed em-dash placeholders.
- Marib gttr: name the second aperture neighbour Sığınak instead of the
  bare "GJ 722" (Miri).

Pre-existing Golestan aperture/spur wording (reviewers split on whether
it is even wrong) is left for a separate content pass — not introduced
by this rename.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 09:17:07 +02:00
jpmschweitzerandClaude Opus 4.8 8844bef139 data(content): rebalance 5 thin afrikaans systems to MENA heritage (#1019)
The corridor over-represented southern-African heritage (afrikaans on 15
systems; arab=2, persian/turkic=0) as an artifact of the earlier
name-balancing overcorrecting. Convert five genuinely-thin afrikaans
systems — whose distinctive threads are heritage-agnostic — into
Arab/Persian/Turkic founders so the cultural cascade is built on a
balanced set. Load-bearing mystery systems (Eerste Wacht / Helderoog /
Brandpunt etc.) are left untouched.

  Droëland  (GJ 914A)  -> Marib    (arab)
  Stilwater (GJ 508A)  -> Sawad    (arab)
  Koeberg   (GJ 1245B) -> Akhgar   (persian)
  Skuilplek (GJ 722)   -> Siginak  (turkic)
  Carnarvon (GJ 680)   -> Golestan (persian; Mostert dynasty -> Farahani)

Result: afrikaans 15->10; arab 0->2, persian 0->2, turkic 0->1.

Full narrative retouch (not just names): gttr.md + index.md prose,
body/station proper_names, cultural_specialization, gttr_hook,
atlas_city_names, and the four brand-corps named after the converted
systems (corp IDs kept stable; only display names + products renamed).
Cross-references in neighbour systems, the catalog, the aggregate
drifter guides, and the atlas proposals are updated to match.

arab/persian/turkic added to _CULTURAL_HERITAGE so the values validate;
the full canonical 47-value migration remains #1016.

Also fixes a pre-existing canon bug: Sawad (GJ 508A) no longer claims
GJ 914A is "unsettled" — it is Marib, a three-century settlement.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 07:11:10 +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 6134296840 fix(ui): atlas overlay scale, opener robustness, rivers match water (#960)
- Generation overlays map positions from Layer1Output.grid_w/grid_h onto the
  displayed texture (not the markers.json grid), fixing the off-by-2 scale.
- AtlasViewer loads ImplantPending by path instead of referencing the new
  class_name, so a stale global-class cache (a session that hasn't re-imported
  after the class was added) can't fail to parse AtlasViewer and break the
  atlas from opening.
- Rivers and mouths use the reliefmap's own water colour (sampled median ocean
  blue) so river lines blend into surface water instead of reading as a
  distinct-coloured line flowing onto the sea.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 10:03:47 +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 616f7e9f3b feat(ui): wire atlas Layer-1 request/re-poll to generation overlays (#960)
show_body() now requests the body's Layer-1 cascade output via
SimBridge.request_atlas_layers(); the response feeds set_generation_layer1().
The proxy returns Pending on a cache miss and generates in the background
(D-225), so the viewer re-requests every 0.5s (20-retry ceiling) until Ready,
guarding against stale responses by body_id. Serverless/test mode is a no-op
(overlays stay empty). Connected in _ready, torn down in _exit_tree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 19:07:59 +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.7 b71f339996 feat: complete file-based heightmap migration — tEXt sea_level, client relief, drop BLOB (#963)
- heightmap.rs: read sea_level from the PNG tEXt chunk (bake writes it),
  default-fallback param; new test reads_sea_level_from_text_chunk.
- client atlas_viewer.gd: load reliefmap.png (color display) instead of
  heightmap.png (now 16-bit grayscale elevation, cascade-only).
- drop atlas_body_heightmaps: removed from systems-schema.sql; DROP TABLE in
  import_economics MIGRATION_SQL (the PNG is the store now).
- D-202 amendment: implementation-status note (consumer + producer done),
  resolving the review's 'reads done but producer pending' point.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 08:23:23 +02:00
jpmschweitzerandClaude Opus 4.6 bbb7e5b0f9 fix(ui): regional atlas — wiki path loading, overlay bar input, header width
Heightmap and markers load from wiki filesystem path via FileAccess
instead of res:// (wiki is outside the Godot project root). Overlay
bar buttons now receive mouse events — viewer skips input processing
when cursor is over UI elements. Header gets explicit minimum width
to prevent vertical text stacking. Shadow economy overlay removed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-03 21:03:09 +02:00
jpmschweitzerandClaude Opus 4.6 768f37fd83 feat(ui): inline body detail panel on system orbital view
Body info panel with globe artwork renders on the right side of the
system map instead of navigating to a separate screen. Single-click
shows/swaps panel, double-click opens regional heightmap view, ESC
closes panel. Station panel also moved to right side for consistency.
Moon and station positioning clears parent body radius + label space.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-03 21:02:54 +02:00
jpmschweitzerandClaude Opus 4.6 29f9945291 feat(db): body_radius_km scatter + gas giant/moon scale classes
Deterministic ±scatter on body radii seeded by body_id hash — no two
bodies share the same radius. Gas giants 40k-60k km, moons 200-2600 km,
rocky planets ±15% from class base. Oort/asteroid skip radius (NULL).
Sol system gets real planetary radii. body_radius_km exported to
star_map_data.json for client orbital diagram sizing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-03 20:11:31 +02:00
jpmschweitzerandClaude Opus 4.6 e8b4dc74b5 feat(ui): atlas system orbital redesign — horizontal orrery with globe textures
Half-sun on left edge, planets left-to-right with radius-based sizing
(Earth = 24px reference, unclamped — gas giants fill the view). Globe
textures loaded from wiki at runtime. Double-click on reach map opens
system view. M closes atlas from any screen, ESC navigates back.

Fixes Godot 4.6 type inference parse errors in main.gd (var zoom/speed).
Uses physical_keycode throughout for layout-independent key handling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-03 20:11:16 +02:00
jpmschweitzerandClaude Opus 4.6 13259dbfd5 feat(client): render province boundaries on planetary map (#927, D-205)
Export pre-computed drainage basin boundaries from atlas_province_boundaries
into markers.json during generate_atlas. AtlasMarkerOverlay draws province
polylines with semi-transparent fill under the political_zones overlay.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-03 16:12:59 +02:00
jpmschweitzerandClaude Sonnet 4.6 94ab992da5 fix(client): sim_bridge — Messagepack parse-order + type annotations on untyped field calls
Replace Messagepack class_name ref with inline load() in _process().
Add explicit int/PackedByteArray annotations on locals derived from
untyped _bridge/_server fields (pid, err, bridge_status, msg, send_err)
so GDScript can type-check call sites.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 09:27:40 +02:00
jpmschweitzerandClaude Sonnet 4.6 698c5eea9b fix(client): parse-order fixes for Protocol autoload and MetaScreen extends
Protocol.gd is an autoload — replace Messagepack class_name refs with
inline load() calls via a static helper. main_menu.gd extends MetaScreen
by class_name which fails at parse time; switch to path-based extends.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 09:27:22 +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