Commit Graph

259 Commits (b233fa040d3c7d95e544e71fcd6b2000f175a080)

Author SHA1 Message Date
VinPropane d6ef4fd811 NEO-24: race-free target/select via optional positionHint
Followup #5: after locking alpha, walking into beta's ring, stopping and
pressing Tab could still return `out_of_range` even when client and server
agreed on position (Δ=0). The freshness-kick move-stream POST and the
target/select POST race on separate HTTP connections so the select can be
validated against a stale stored snap.

Extend TargetSelectRequest v1 with an optional positionHint {x,y,z}.
Server uses the hint for the radius check and for the echoed
targetState.validity; the hint is advisory and does not write to the
position store (move-stream stays the sole write path). Client attaches
the hint automatically whenever set_freshness_kick(...) is wired, so
headless unit tests that skip the wiring fall through to the no-hint
behavior. Freshness kick is kept in place to refresh the stored snap for
later validity GETs but is no longer load-bearing for denial correctness.

Server: 3 new tests (hint accepts when stored is stale, hint-far denies,
hint never writes to position store). Client: 1 new test asserting the
hint is in the POST body when wired. All 62 server + 96 client tests
pass.

Plan Decision 8, review follow-up #5, manual QA section 6, and NEO-23
contract notes updated. New bruno request exercises the hint path.
2026-04-21 23:52:20 -04:00
VinPropane a7453eb82f NEO-24: unify prototype target radius at 6 m, overlap rings at origin
All prototype targets now share `PrototypeTargetRegistry.SharedLockRadius`
(= 6 m). Anchors moved to alpha `(-3, 0.5, -3)` and beta `(3, 0, 3)` so
the two 6 m rings overlap ~3.5 m wide centered at origin, letting Tab
flip between targets without locomotion. Spawn `(-5, -5)` keeps alpha
in range (~2.83 m) and beta out (~11.31 m) so the existing soft-lock
denial QA path at spawn still holds.

`prototype_target_constants.gd` mirrors the shared radius + new anchors.
The range-aware Tab picker now **skips the currently-locked id** so Tab
always expresses a swap intent — if the only in-range target is already
locked, the fallback picks the next cycle id and the server denies
(preserves NEO-23's "Tab = visible denial when no swap possible" rule).

Tests:
- Server: new `PlayerTargetStateReaderTests.ComputeValidity_AtOrigin_…`
  asserts both targets report `Ok` at origin. Existing boundary test
  rewritten for the new alpha anchor.
- Client: new `test_tab_from_locked_alpha_at_origin_swaps_to_beta_when_both_in_range`
  and `test_tab_when_only_current_lock_is_in_range_falls_back_to_cycle_for_denial`.
  Existing in-range/out-of-range positions updated for the new layout.

Server 59/59 and client 95/95 suites pass.

Plan Decision 7, review follow-up #4, manual QA section 5 (new "Tab-flip
at origin" overlap sanity), and README updated. Per-target radii will
return with real combat design (E5.M1) — this is a prototype stub.
2026-04-21 23:37:42 -04:00
VinPropane 63fecb1099 NEO-24: range-aware Tab cycle skips out-of-range anchors before POST
Previously Tab from no-lock always picked `ORDERED_IDS[0]` (alpha), so
standing next to beta and pressing Tab was denied with `out_of_range`
even though the client HUD clearly showed beta in range.

Added `PrototypeTargetConstants.next_in_range_id_after(current, world)`
which walks the cycle and returns the first anchor whose client-side
horizontal distance is within the anchor radius.
`TargetSelectionClient.request_tab_next()` prefers that pick when the
player reference is wired (same path `set_freshness_kick` uses) and
falls back to the plain cycle order when nothing is in range so the
server still owns the denial reason.

Server contract (NEO-23) untouched. Tests cover the beta pick, the
nothing-in-range fallback, and the no-player-ref regression. Plan
Decision 6 + review follow-up #3 + README + manual QA updated.
2026-04-21 23:26:56 -04:00
VinPropane e31cd2df68 NEO-24: freshen server position before target-select + surface divergence on HUD
Fix "Tab says denied even though I'm in range of beta": root cause is the
race between the 20 Hz move-stream sampler and the target-select POST. A
Tab press right after the capsule stops can be validated against the
server's last sampled position, which trails the visible capsule by
several meters.

Client-side fixes (server contract unchanged):

- TargetSelectionClient.set_freshness_kick(authority, player): before each
  POST /target/select, submit a move-stream with the current player
  position so the server's stored snapshot is as fresh as possible when
  the range check runs. Wired in main.gd; optional so tests without the
  wiring still pass (regression-covered).
- PlayerPositionLabel now shows a "srv: (x,y,z) Δ=<d>m age=<ms>" line
  populated from PositionAuthorityClient.authoritative_ack, so any
  residual client/server divergence is visible in the HUD and future
  reports can point at it directly.
- Two new tests: select POST kicks exactly one freshness stream with the
  current player position before POSTing, and the kick is a no-op when
  the wiring is absent.

Docs: plan Decision 5, review follow-up #2, manual QA section 5 and
section 1, and client README updated to describe the new behaviors.
2026-04-21 23:21:16 -04:00
VinPropane 71ae829482 NEO-24: add visible target-anchor markers + HUD distance lines
Players reported targeting "feels inconsistent" because the server
PrototypeTargetRegistry anchors (alpha (-5,-5) r=8, beta (8,8) r=4) have
no visible meshes — the only console visible in the scene is the NEO-9
PrototypeTerminal at origin, which is unrelated to NEO-23/24 targeting.

Adds a display-only mirror of the server registry:

- client/scripts/prototype_target_markers.gd spawns a colored mast +
  translucent flat radius ring per anchor so the lock radius is
  visible in-world.
- client/scripts/prototype_target_constants.gd gains ANCHORS with
  position/radius/color, matching PrototypeTargetRegistry.cs. Marked
  display-only; server remains authoritative for validity.
- TargetLockLabel now renders a per-anchor distance line each physics
  tick ("<id>: <d> m / <radius> (in|out)") so players can see the
  relationship between the visible capsule and the lock radius and
  immediately spot client/server position drift.

Also updates README, plan (Decision 4), manual QA (Section 1 boot +
Section 4 locomotion), and the review file's follow-up section to
describe the UX fix.
2026-04-21 23:04:03 -04:00
VinPropane 435a32b3b3 NEO-24: fix movement-driven target refresh via authoritative_ack signal
Review 2026-04-21 caught that the hybrid soft-lock refresh path is
unreachable during normal WASD locomotion. `main.gd` was wiring
`TargetSelectionClient` to `PositionAuthorityClient.authoritative_position_received`,
which only fires on boot sync and move-rejection resync — successful
`move-stream` 200s are intentionally silent to avoid RTT rubber-banding.
So walking out of an alpha/beta radius never triggered the throttled
`GET /target` the HUD depends on, and validity stayed stuck at `ok`.

Add a separate `authoritative_ack(world)` signal on `PositionAuthorityClient`
emitted from both `BOOT_GET` 200 and successful `move-stream` 200, and
switch `TargetSelectionClient` (+ `main.gd`) to it. The snap signal keeps
its current semantics so rubber-band suppression is untouched; the ack
signal is a pure heartbeat that cooldown-throttled consumers can hook.

Also adds an integration test that wires both real clients together (per
the review's suggestion 1) so renames or mis-wires between the two
scripts fail loudly rather than silently skipping the end-to-end path.

- `client/scripts/position_authority_client.gd`: new `authoritative_ack`
  signal; extracted `_parse_world_from_response` helper to share JSON
  parse across `BOOT_GET` and `STREAM_POST`; emit ack from both.
- `client/scripts/target_selection_client.gd`: rename
  `on_authoritative_position_snap` to `on_authoritative_ack`; drop
  `apply_as_snap` gate (no longer meaningful).
- `client/scripts/main.gd`: connect the new signal.
- `client/test/position_authority_client_test.gd`: assert ack on boot
  200 and stream 200.
- `client/test/target_selection_client_test.gd`: rename tests for new
  handler.
- `client/test/target_refresh_on_locomotion_test.gd`: new integration
  suite wiring real authority + target clients.
- Plan Decision 3 + Tests row + Files-to-modify row updated.
- `docs/reviews/2026-04-21-NEO-24.md`: committed with blocking issue +
  suggestions struck through with `Done.` notes per
  planning-implementation-docs rule.
- `docs/decomposition/modules/documentation_and_implementation_alignment.md`:
  E1.M3 row now reflects NEO-24 landed.
- `client/README.md` + `docs/manual-qa/NEO-24.md`: refreshed signal names
  so the soft-lock refresh description matches the wiring.

Tests: 88/88 GdUnit headless passed locally.
2026-04-21 22:35:41 -04:00
VinPropane ee572e3661 NEO-24: client tab-target + lock HUD synced to server
Adds `TargetSelectionClient` (Godot, HTTP to NEO-23 targeting endpoints):
- Tab cycles ids in ascending order matching `PrototypeTargetRegistry`
  (`prototype_target_alpha`, `prototype_target_beta`) and wraps.
- Esc clears by POSTing with `targetId` omitted.
- UI always paints from the server's authoritative `targetState`
  (success and denials alike); `UICanvas/TargetLockLabel` shows
  `lockedTargetId`, `validity`, `sequence`, and (on deny) `reasonCode`.
- Movement-triggered refresh: while a lock is held, subsequent
  `authoritative_position_received` snaps fire a throttled GET
  (~250 ms cooldown) so `validity` flips to `out_of_range` after
  locomotion without duplicating server radius math on the client.

Registers `target_tab` (Tab) / `target_clear` (Esc) in `project.godot`;
documents bindings + manual QA in `client/README.md`.

Tests: `target_selection_client_test.gd` covers GET parse, tab ordering
from empty/alpha/wrap, POST body shape, denial preserves server state,
clear omits targetId, movement refresh fires only while locked, cooldown
collapses bursts to one GET, and `apply_as_snap=false` is ignored.
All 85 client GdUnit cases pass; gdlint and gdformat clean.

See `docs/plans/NEO-24-implementation-plan.md`.
2026-04-21 22:14:16 -04:00
VinPropane abb6e6b81b NEO-22: Add Godot .uid sidecars for WASD locomotion scripts 2026-04-17 20:51:16 -04:00
VinPropane 866c973af7 NEO-22: Cache scrape contact in WASD tick; close review items
run_wasd: one _has_scrape_vertical_contact() before move_and_slide and one
after; fold repeated predicates into wish_active_scrape_vertical_pre and
post_slide_wish_floor_scrape.

Review doc: mark suggestions #2/#4 done; suggestion #1, nits, verification
ledge bullet, and partial doc rows reviewed/deferred with strikethrough notes.
2026-04-17 20:21:13 -04:00
VinPropane c79c58c298 NEO-22: Unit-test WASD locomotion pure helpers
Extract static helpers (floor-ray continuation, median feet Y, micro-slip
XZ projection, wish-aligned horizontal velocity) from the tick path so
GdUnit can cover seam math without physics doubles. Add
player_locomotion_wasd_test.gd, document in README, and update NEO-22
plan/review.
2026-04-17 20:16:25 -04:00
VinPropane 90a17bc43d NEO-22: Extract WASD locomotion for gdlint max-file-lines
Move the WASD physics tick into player_locomotion_wasd.gd so player.gd
stays under the 1600-line CI cap. Fix gdlint class order (enum before
consts), load-constant naming, and wrap long doc lines. Update NEO-22
review and plan; refresh gdlintrc comment.
2026-04-17 20:13:36 -04:00
VinPropane a4095cb9bf NEO-22: Remove click-to-move client and legacy move POST/verify
Delete ground_pick and GroundPick wiring; PositionAuthorityClient now only
boot GET and move-stream POST. main.gd snaps on authoritative position only.
Update client/server READMEs, cursor rule example, NEO-22 plan note, and
GdUnit authority tests (stream 400 + boot resync).
2026-04-17 20:06:46 -04:00
VinPropane 76ff2cddac NEO-22: clear idle anchor during WASD so stop does not snap back
Stable idle latches _idle_anchor_active and _hold_idle_anchor clamps XZ each
tick. Locomotion bypasses that branch, so the anchor stayed at the pre-walk
position while the capsule moved; releasing keys ran idle again and
_hold_idle_anchor teleported to the stale anchor.
2026-04-17 19:37:58 -04:00
VinPropane 6cc2ac60d4 NEO-22: stop applying move-stream HTTP body as client teleport
Successful move-stream responses echoed server position roughly one RTT behind
integrated move_and_slide, so apply_as_snap still fired whenever dh exceeded
the locomotion skip threshold. Treat stream 200 as ack-only: do not emit
authoritative_position_received. Boot GET and click-move verify unchanged.
Update README + GdUnit expectation.
2026-04-17 19:34:45 -04:00
VinPropane 39a41ac62c NEO-22: suppress stream snap-back while locomoting
Stream HTTP responses can lag integrated move_and_slide by roughly one RTT,
so apply_as_snap was pulling the capsule backward each tick while moving.
Skip small XZ corrections when wish or horizontal speed indicates locomotion,
with a cap so large desync still snaps. move_rejected sets a one-shot flag so
resync after move-stream 400 still applies the authoritative pose.
2026-04-17 19:29:56 -04:00
VinPropane 878b1f4aee NEO-22: fix stream rubber-banding (coalesce, idle gate, soft snap)
Queue only latest move-stream target per POST; clear pending while busy.
Skip stream submits when idle; skip tiny authoritative snaps after boot.
2026-04-17 19:27:10 -04:00
VinPropane a2c6c9f93e NEO-22: add move-stream API and WASD client locomotion
Introduce POST /game/players/{id}/move-stream with ordered targets, full-chain
validation before apply, and PositionStateResponse. Godot client uses camera-
relative WASD, queues samples, and snaps to stream responses; debug builds keep
click-to-move via ground_pick. Update READMEs, E1.M1 snapshot, and tests.
2026-04-17 19:21:33 -04:00
VinPropane 9bdd928c80 NEON-29: Fix gdlint (player returns/lines, split idle tests, gdlintrc cap) 2026-04-15 23:48:15 -04:00
VinPropane 2b72d6bcb9 NEON-29: Prototype smooth hill piece, movement script updates, NEO-22 plan
Add prototype_smooth_hill_piece for QA geometry alongside main scene wiring.
Refresh player locomotion and main scene integration from stashed work.
Track docs/plans/NEO-22-implementation-plan.md (WASD replaces click-to-move).
2026-04-15 23:39:47 -04:00
VinPropane a7ad695143 NEON-29: Apply stashed WIP (main.tscn, movement validation, docs) 2026-04-13 13:24:05 -04:00
VinPropane 4a51e64f25 Merge branch 'main' into NEON-29-expand-prototype-district 2026-04-13 13:22:51 -04:00
VinPropane 7e17ff563b chore: point docs and rules at Linear NEO issue ids
Migrate Jira NEON-* references to Linear NEO-* and linear.app links in
docs, Cursor rules, READMEs, and traceability comments. Renamed
implementation plans and dated reviews to NEO filenames.

Branch is based on origin/main with this commit only (no NEON-29
gameplay chain). client/README follows main’s district/map copy;
player.gd matches main aside from NEO-14 debug trace prefixes.
2026-04-13 13:15:32 -04:00
VinPropane 2333d29ece NEON-29: End column stair ghost-walk (arrive + stuck release).
Traces showed has_goal=true, vlat&&ncol, MOVE_SPEED XZ flipping while
stable=false: vert_err ~0.31 stayed above VERT_ARRIVE_EPS so arrival never
cleared the goal — not idle jitter.

Run vertical-route latch before arrival; when on floor with vlat&&ncol,
allow vert_err up to WALK_COLUMN_NEAR_ARRIVE_VERT (0.36 m) if horiz_dist
<= ARRIVE_EPS. Backup: clear_nav_goal after WALK_COLUMN_STUCK_FRAMES with
net drift < WALK_COLUMN_STUCK_MAX_DRIFT in the same regime.
2026-04-12 19:57:54 -04:00
VinPropane 50a8b609be NEON-29: Latch stable idle on ridged stair treads (idle jitter).
Idle jitter was not walk/peel: without has_goal, stable idle required
floor_normal·up >= 0.998. Tread+riser contacts often report ~0.996–0.997,
so stable idle never latched and rim settle nudge + idle move_and_slide
ran every tick. Use STABLE_IDLE_FLOOR_HOLD_MIN_UP_DOT (0.992) for the
floor-flat check when slide contacts are ridged and we are still using the
strict entry threshold.
2026-04-12 19:49:19 -04:00
VinPropane db7973d368 NEON-29: Tie tread XZ freeze to peel suspend + vert arrival band.
Replace vlat/ncol + tight vert_sep window (often missed or band-flickered) with:
peel suspend, horiz_dist <= 0.38 m, VERT_ARRIVE_EPS < verr <= 0.18 m, goal
not above feet. Aligns freeze with tread snap/peel regime; caps XZ against
5 m peel-suspend fallback.
2026-04-12 19:44:53 -04:00
VinPropane ea3903d8ab NEON-29: Freeze horizontal velocity on column tread wobble band.
When vlat&&ncol, XZ within ~0.92*ARRIVE_EPS, and vert_sep in a narrow band
above vertical-arrival noise, zero XZ velocity so nav slide cannot drive ~6 cm
Y oscillation on violet stairs. Descending fallback when column latch is off;
skip when goal is above feet (climb still needs XZ).
2026-04-12 19:41:39 -04:00
VinPropane ec903d9c11 NEON-29: Drop tread speed cap; widen stair continuation probe.
Remove WALK_TREAD_STABILITY horiz cap (it ran for whole vlat&&ncol band and
made the last steps sluggish without fixing jitter).

Raise WALK_CONTINUATION_MAX_BELOW_FEET 0.078→0.108 m so ~0.104 m tread drops
count as walk continuation; probe/debounced peel+snap stop flipping each tick.
Kept below ~0.25 m so ~0.3 m gold→floor void still rejects the slab.

Revert NAV_PATH_STEER_MIN_LOOKAHEAD to 0.22 m.
2026-04-12 19:15:27 -04:00
VinPropane 4a08ede736 NEON-29: Rename h_scale to avoid shadowing Node3D.scale. 2026-04-12 19:12:52 -04:00
VinPropane 50bb615763 NEON-29: Cap walk speed on tread stability band for stair jitter.
Steering reverse clamp was reverted (blocked arrival). Instead, when on floor
and (vlat && ncol && vert_sep band) or tight XZ + small vert_sep, clamp
horizontal speed to 1.35 m/s so move_and_slide does not drive riser ping-pong
at full MOVE_SPEED. Bump path steer min lookahead 0.22→0.28 m.
2026-04-12 19:12:43 -04:00
VinPropane bf968b1817 NEON-29: Revert steering reverse clamp (blocked arrival, void fall).
_walk_clamp_steering_reverse kept prior horizontal velocity whenever the new
steer-to-goal direction opposed current motion (dot < -0.35). That prevented
ever closing ARRIVE_EPS in many geometries, so walk never cleared, XZ stayed at
MOVE_SPEED, and air gravity could run indefinitely (y << 0).

Restore direct/path steering without the clamp; NAV_COLUMN_STEER_EXIT_DIST
back to 0.97.
2026-04-12 19:09:51 -04:00
VinPropane bfc70678c4 NEON-29: Reject 180° walk steering flips on stair edges.
Path lookahead vs direct goal could reverse horizontal velocity every physics
tick on treads (NEON-16), driving slide/snap Y jitter. Clamp new steer to
prior bearing when dot < -0.35 and speed is above a small threshold.

Widen NAV_COLUMN_STEER_EXIT_DIST (0.97→1.12) to reduce path/direct mode
chatter when horiz_dist wobbles near the old band.
2026-04-12 19:07:15 -04:00
VinPropane 038505ea3e NEON-29: Gate descend lip snap on debounced ledge peel.
DESCEND_LIP_SNAP_CAP (0.1) ran whenever goal was below feet by geometry,
independent of probe debounce. Feet_y noise on treads toggled 0.32 vs 0.1
snap and caused ~2–3 cm Y jitter. Apply lip cap only when debounced peel is
armed and not in peel suspend; otherwise keep FLOOR_SNAP_MOVING.
2026-04-12 19:03:50 -04:00
VinPropane 73d26a90d7 NEON-29: Debounce floor probe for ledge peel and zero snap.
Continuation rays reject stair treads (~0.104 m) under WALK_CONTINUATION_MAX_BELOW_FEET
(0.078 m), so is_on_floor + probe flickered every tick and toggled peel vs full snap
(Y jitter ~1.0 m). Arm peel/snap aggression only after WALK_PEEL_PROBE_FALSE_FRAMES
consecutive probe failures; deep descent still arms immediately.

Widen horiz peel suspend (0.34→0.46 m vert, 2→5 m XZ) for goal/tread separation gaps.
2026-04-12 19:00:59 -04:00
VinPropane 4e47e9a037 NEON-29: Extend tread peel suspend when vlat unlatches.
Widen vertical-route latch off-sep (0.038→0.085) so descent toward floor
does not drop vlat while feet still wobble on stairs.

Add horiz+vert fallback: suspend peel and restore snap when vert_sep ≤0.34 m
and horiz_dist ≤2 m, covering ncol=false paths where goal Y matches slab
height but tread peel still fought snap (violet stairs ~y=0.92).
2026-04-12 18:55:41 -04:00
VinPropane 26ad33230d NEON-29: Suspend ledge peel on column treads to stop idle jitter.
When vertical route is latched and nav column steering is active, peel
plus zero floor snap caused a 3-frame limit cycle (NEON-16 traces).
Skip peel and restore normal snap while feet are within 0.48 m of the
goal surface vertically.
2026-04-12 18:51:07 -04:00
VinPropane 33b6db979e NEON-29: Stair descent — tighter continuation probe + stronger vertical accel
Lower WALK_CONTINUATION_MAX_BELOW_FEET to 0.078 m (under ~0.104 m treads) so
the next tread is not treated as forward walk support; ledge peel runs on
descents and pulls down onto each step instead of gliding with weak air gravity.

Raise PLAYER_VERTICAL_ACCEL_MULTIPLIER to 9× for peel, walk-air, and idle-air.
2026-04-12 18:47:38 -04:00
VinPropane b924707202 NEON-29: Faster vertical descent — accel multiplier + peel until feet arrive
Apply PLAYER_VERTICAL_ACCEL_MULTIPLIER (~2.45×) to walk-airborne gravity,
ledge peel integration, and idle-airborne gravity for snappier falls.

Peel used to clear when goal.y >= feet_y - DESCEND_GOAL_Y_MARGIN, which
stopped peeling ~6 cm above the click surface while XZ was already at the
destination — use vertical_arrival_error <= VERT_ARRIVE_EPS instead, with
a small guard when the goal is clearly above the feet (climb).
2026-04-12 18:44:51 -04:00
VinPropane a78b44263c NEON-29: Ledge peel via move_and_collide + fallback gravity
Direct global_position.y edits may not stick vs Jolt after move_and_slide.
Use move_and_collide for the peel step so motion goes through physics.

If get_gravity() is near zero, use -9.81 for peel, walk air, and idle air.
2026-04-12 18:41:20 -04:00
VinPropane fb1ac80fb0 NEON-29: Ledge peel via position — GROUNDED eats vy on floor
CharacterBody3D MOTION_MODE_GROUNDED move_and_slide clears downward
velocity while is_on_floor(), so gravity on velocity never produced
vertical motion off terrace lips (all prior probe/snap tweaks were no-ops).

After each walk move_and_slide, when the goal surface is below the feet
and probes/deep-descent say drop, accumulate peel_vy += g·dt and apply
global_position.y += peel_vy·dt. Airborne walk still uses velocity gravity.

Set physics/3d/default_gravity=9.81 explicitly in project.godot.
2026-04-12 18:36:53 -04:00
VinPropane 55ce6f3e3b NEON-29: Deep-descent walk gravity bypass for violet deck vs floor goal
Rays and is_on_floor() can still agree the capsule is supported while the
authoritative click is on the main floor far below (TerracePlatformB lip).

When feet are >0.42 m above the goal surface Y, always add walk gravity and
zero moving floor snap. Shorter drops (e.g. gold step to floor) still use
the probe heuristic only.
2026-04-12 18:31:01 -04:00
VinPropane 91b802c679 NEON-29: Fix walk ledge probes for descending + same-column clicks
Degenerate XZ (velocity and want_goal_h ~0) made _walk_has_close_floor_probe_below
return after center-only, suppressing ledge gravity for the whole move.

Add _resolve_walk_probe_dir_xz: when goal surface is below feet, prefer
horizontal bearing to the goal, else nav path lookahead, else fallbacks.
Non-descending motion keeps velocity-first probes.

Remove the nav-column-only want_goal_h override (superseded by descending
branch). Reverts the regression where the capsule stayed on violet until XZ.
2026-04-12 18:25:22 -04:00
VinPropane db1f4da068 NEON-29: Aim walk ledge probes at click when nav column descends
Column steering follows path tangents on the violet deck while the
authoritative goal sits on the lower floor; velocity-aligned support rays
kept hitting deck in the tangent direction and suppressed ledge gravity.
Use want_goal_h for probe offsets when vertical routing + column steer
+ on floor + goal surface below feet.
2026-04-12 18:18:43 -04:00
VinPropane c3fd9892e3 NEON-29: Ignore walk probe hits far below feet (void saw lower floor)
Down-rays use 0.32 m depth; a gold step is ~0.3 m above the slab, so a
forward sample over empty space still intersected the gray floor and
passed the up-normal test. That looked like continued support, so ledge
gravity and snap-off never fired.

Only treat a hit as walk support if its Y is within 0.14 m below the
current feet (one tread scale); deeper hits are a lower deck / pit.
2026-04-12 18:10:36 -04:00
VinPropane 6bd1acd9e7 NEON-29: Require forward floor probe to keep walk snap/gravity sane off ledges
Walk support used any ray hit under the foot disk, so the center sample
could still see the platform while the leading edge was over a void.
is_on_floor() stayed true from the lip, snap stayed strong, and vy was
zeroed each tick — horizontal skate until XZ arrival.

Now require a center hit plus at least one forward-biased sample in the
movement direction when horizontal intent is non-zero; use velocity XZ
for probe direction so nav/path steering matches the rays.
2026-04-12 18:08:01 -04:00
VinPropane 1f6625c659 NEON-29: Fix nav column steer ping-pong — path lookahead + air bee-line 2026-04-12 18:03:47 -04:00
VinPropane 7666cce7e5 NEON-29: Ledge fall — multi foot probes + zero snap when void
Single center ray + flat floor-normal gate missed tilted lip contacts
and still hit deck under capsule center while leading edge hung over void.
Moving FLOOR_SNAP_MOVING then cancelled gravity each tick.

Sample down rays at center and offsets along walk direction; depth 0.32;
set floor_snap_length 0 when is_on_floor but no probe hit.
2026-04-12 17:56:56 -04:00
VinPropane 6cb66d7567 NEON-29: Walk gravity when is_on_floor but feet probe finds void
Jolt + floor snap could keep is_on_floor true over open space toward a
lower click target, so vy stayed 0 until arrival. Add a short downward
ray under the capsule feet (WALK_SUPPORT_PROBE_DEPTH) and apply gravity
when no upward-facing hit is close, only on fairly level floor normals.
2026-04-12 17:50:09 -04:00
VinPropane 2821c5973b NEON-29: Fix nav path steer ping-pong (get_next_path_position)
Vertical+column branch scanned path for first point with XZ offset >5 cm;
Jolt moved the body across that edge each tick and flipped the chosen
waypoint → 180° velocity oscillation (vlat && ncol).

Use NavigationAgent3D.get_next_path_position() when navigation not
finished; fall back to auth goal when finished or degenerate.
2026-04-12 17:37:48 -04:00
VinPropane 865eed2e53 NEON-29: Ease flat-floor idle latch (HUD XZ drift)
Raise stable-idle floor up-dot entry to 0.998 so Jolt normals on a flat
box can latch the idle anchor; 0.999 rarely tripped and corrective
move_and_slide + rim/bump nudges kept moving XZ.

Add STABLE_IDLE_ENTER_STREAK_FRAMES (2) before first latch; arrival
path still latches immediately. Unlatch budget 8→10 ticks.
2026-04-12 17:33:49 -04:00
VinPropane edec9158e4 NEON-29: Hysteresis for walk vertical routing (tread ping-pong)
feet_y wobble from move_and_slide on approach treads crossed
DESCEND_GOAL_Y_MARGIN each frame, toggling path vs direct steer.
Schmitt latch (WALK_VERT_ROUTE_LATCH_ON_SEP/OFF_SEP) with reset on
nav lifecycle. Extend debug_idle_trace with feet_y, vert_sep, vlat,
ncol. Add player_test for latch reset.
2026-04-12 17:17:36 -04:00
VinPropane 46f8851a1a NEON-29: Hold-threshold stable idle + physics HUD coords
While _idle_stable_latched, use STABLE_IDLE_FLOOR_HOLD_MIN_UP_DOT (0.992) for
idle_support floor flatness so tread-edge normal flicker does not unlatch as
often. idle_support_is_stable gains optional min_flat_up_dot; unlatch streak 8.
Prototype position label updates in main _physics_process. Add GdUnit for param.
2026-04-12 17:13:37 -04:00
VinPropane 5f6058b0ce NEON-29: Stable idle on flat tread next to riser (loose+ridged)
loose_ticks+ridged no longer forces ~0.8s corrective idle when floor normal
is still level (tread corner with vertical riser). Gate that branch on
shallow floor (dot < IDLE_RIM_MIN_FLOOR_UP_DOT). Bump unlatch streak to 5.
Update GdUnit + NEON-29 plan.
2026-04-12 17:09:55 -04:00
VinPropane 8d9ad0ecd3 NEON-29: Stable idle latch + Y anchor for approach treads
Hold latched zero-vel idle through STABLE_IDLE_UNLATCH_TICKS consecutive
unstable readings so thin tread lips do not drop into corrective move_and_slide
every other frame. Idle anchor now freezes Y as well as XZ. Reset latch on
goal/snap/arrival; seed latch on arrival when support is stable after idle tick.
Update hold_idle_anchor GdUnit expectation.
2026-04-12 17:06:30 -04:00
VinPropane ff6c337e1d NEON-29: Hysteresis for nav column steering (walk ping-pong)
Single-radius switch at DIRECT_APPROACH_RADIUS let horiz_dist cross every
frame between direct-to-goal and nav-path steering, flipping velocity ~180°.
Schmitt band NAV_COLUMN_STEER_ENTER/EXIT with latched _walk_nav_column_steering;
reset on new goal, snap, arrival, clear.
2026-04-12 17:02:17 -04:00
VinPropane 0f4c23b815 NEON-29: Reduce flat-floor idle XZ jitter after walks
Stop forcing moving floor_max_angle for the full post-stop window on open
flat support; tighten ridged wallish threshold; relax stable floor dot;
on arrival, take stable-idle path when support is already stable after
one idle tick. Add GdUnit case for shallow seam normals.
2026-04-12 16:56:40 -04:00
VinPropane 173c348030 NEON-29: Show player world x,y,z in prototype HUD
Add UICanvas/PlayerPositionLabel (top-left) and refresh each frame from main.gd.
2026-04-12 16:52:19 -04:00
VinPropane 1867e3e45c NEON-29: Fix idle jitter on flat treads after stop
idle_support_is_stable no longer treats every post-stop loose-tick frame as
unstable; only slide normals showing floor+wall (lip) keep corrective idle.
Deduplicate ridged-contact check via idle_slide_contacts_are_ridged.
Relax STABLE_IDLE_FLOOR_MIN_UP_DOT slightly for Jolt normal flicker.
Update GdUnit cases and NEON-29 plan note.
2026-04-12 16:50:21 -04:00
VinPropane dbd9d67f45 NEON-29: Cap step assist to navigation agent_max_climb (0.35 m).
TerracePlatformB top is ~0.6 m above the floor; agent_max_climb only
limited nav links, while chained step assist could still climb in one
click. Refuse assist when goal surface is more than 0.35 m above feet
so the gold TerraceStepB path (or a second click) is required.
2026-04-12 01:12:10 -04:00
VinPropane e448075797 NEON-29: Bee-line walk to goal; nav path only for vertical routing.
Default get_next_path_position steering caused crawl/stuck behavior with
Jolt at 120 Hz. Steer XZ toward the authoritative target always except
when the goal surface is meaningfully above/below the feet and the
player is within DIRECT_APPROACH_RADIUS — then use the baked path for
ramps/treads. Update README and NEON-29 plan.
2026-04-12 01:07:35 -04:00
VinPropane 4df71ac0cc NEON-29: Fix floor walk stall from tight nav agent distances.
Revert path_desired_distance and target_desired_distance to 0.35; 0.22
was below practical per-tick progress toward waypoints and led to
oscillation or stuck path state. When NavigationAgent reports finished
but horizontal distance still exceeds ARRIVE_EPS, steer directly
toward the authoritative goal in XZ.
2026-04-12 01:03:40 -04:00
VinPropane 3a7658f55f NEON-29: Roll back walk oscillation on treads (stall replan, seam clear).
Remove walk-stall nav replan and seam-based step-assist clearing that
churned targets and toggled floor_block_on_wall on small rises. Restore
0.11 m assist with 8-tick cooldown; cap wallish snap only when the goal
is meaningfully above the feet. Drop descending_stall floor_block branch.
Document rollback in the implementation plan.
2026-04-12 00:56:13 -04:00
VinPropane 8d1785d288 NEON-29: Clear step assist on seam walls; replan nav on walk stall
Step assist stayed active when is_on_wall from mesh seams after landing on
the goal height — keep reduced snap and odd slides. Clear assist when
vert_err and feet vs goal show we are on the target slab.

If horizontal speed stays low with a far goal, refresh NavigationAgent3D
target after 8 ticks to break stale paths.
2026-04-12 00:53:36 -04:00
VinPropane b96bec7981 NEON-29: Teal cardinal — snap cap during climb, descend stall, assist tune
Cap walk floor snap to WALK_STEP_ASSIST_SNAP when climbing into a blocking
wall (stops 0.32m snap fighting step assist). Restore descend lip snap cap;
relax floor_block when descend stalls (low vh, still far in xz). Step assist
lift 0.16 m, cooldown 1 tick.
2026-04-12 00:45:02 -04:00
VinPropane d81e915a0a NEON-29: Teal pad lips — wall normal assist, rim descend, +12mm lift
Step assist: use is_on_wall + get_wall_normal opposing want; keep slide loop
with dot < -0.02.

Descend: relax floor_block on shallow floor normal at internal edges; widen
vertical-ish slide threshold slightly.

Raise TerracePlatformA Y by 0.012 m vs main floor for coplanarity.
2026-04-12 00:42:25 -04:00
VinPropane 81ceeaab27 NEON-29: Doc fix — lip QA on TerracePlatformA, not Terrace B
Clarify README, plan, and player.gd comments: screenshots were the large
SE pad (10×10, 0.3 m step), not the NW TerraceStepB/TerracePlatformB pair.
2026-04-12 00:39:04 -04:00
VinPropane 00b5be7b0c NEON-29: Fix step assist at lips; descend floor_block only at wall
Remove the forward-velocity dot gate that blocked assist when pushing
head-on into a step (dot ~1 vs max 0.52).

Relax floor_block_on_wall only for true descends (goal below feet) when
is_on_wall or slide hits a vertical-ish normal, so platform deck motion
stays stable.
2026-04-12 00:37:38 -04:00
VinPropane 7d03b17136 NEON-29: Remove descend bypass; fix walk gravity on terraces
Applying gravity whenever the nav goal was below feet stayed true for the
entire walk across TerracePlatformB (feet ~0.6 m vs floor goal Y=0),
corrupting horizontal velocity and slide behavior.

Drop the descend bee-line branch; follow the nav mesh for vertical routing.
Restore full horizontal velocity set with vy=0 and air-only gravity.
Update README and NEON-29 plan to match.
2026-04-12 00:32:02 -04:00
VinPropane e73477da6b NEON-29: Fix walk velocity Y — real gravity on descend, no oscillation
Steer only XZ in _set_horizontal_velocity_toward so vy can accumulate.
Apply gravity when airborne or goal-below-feet; stomp vy on grounded
non-descend moves. Drop descend floor_block relax and on-floor gravity
mult; cap snap for full descend without a feet_y band that flipped
snap length at the lip.
2026-04-12 00:23:18 -04:00
VinPropane 3efe65d9bd NEON-29: Descend on-floor gravity + snap cap; revert terrace Y lift
Reverts the 15mm TerraceStepB/TerracePlatformB root offset that regressed
floor-to-step movement.

When the walk goal is below the feet, apply gravity even while is_on_floor
so lip contacts are not stuck with vy cleared every tick; cap descend
floor_snap_length so the upper surface does not win over internal edges.
2026-04-12 00:17:09 -04:00
VinPropane f20957975d NEON-29: Fix terrace B descend (geometry gap + simpler floor_block)
Raise TerraceStepB and TerracePlatformB 15mm so boxes are not coplanar
with the main floor top, reducing Jolt internal-edge hangs.

Remove descend lip stall, peel multipliers, and snap cap; keep a single
floor_block_on_wall=false policy for the whole descend branch to avoid
oscillation. Document in NEON-29 plan.
2026-04-12 00:14:10 -04:00
VinPropane 275645c6cf NEON-29: Descend floor_block + weak lip peel (fix stuck step/platform→floor)
Relax floor_block whenever feet are >7 cm above a lower goal — stall/wall was too
late for step1→floor. Re-tighten only in the last centimeters (wallish/stall).

Apply DESCEND_WEAK_PEEL_MULT on_floor while descending every frame; ramp to
DESCEND_LIP_GRAVITY_MULT when stalled/wallish/carry. Skip peel when airborne
(full gravity from _apply_walk_air_gravity).
2026-04-12 00:07:36 -04:00
VinPropane ac9818a2cd NEON-29: Stop clearing floor_block_on_wall for ascent (fix floor→step 1)
Relaxing floor_block when goal was above feet and wallish prevented reliable
step-up; restore default true for climbing. Keep descend-only relaxation.
2026-04-12 00:05:18 -04:00
VinPropane d1f738249d NEON-29: Replace full-time descend gravity with stall + floor_block peel
Always-on gravity while on_floor and descending made platform→floor feel glued
(slow vertical creep, no horizontal until landing). Remove that; use 7x gravity
only when lip_stall>0, wallish slide, or prior move left low horizontal speed
while still far from goal (carry_lip_from_prior_move).

Track _descend_lip_stall_ticks after descend/nomap moves; reset on new goal,
arrival, snap, clear_nav.

Relax floor_block_on_wall when goal is above feet and wallish (floor→step) or
goal below and (wallish or stall) so slides can clear terrace lips.

Reset floor_block_on_wall when idle or arrival.
2026-04-12 00:03:52 -04:00
VinPropane cc63e9bdd7 NEON-29: Reliable descend lip peel + capped floor snap
Wall/slide heuristics missed many step→floor stalls (e.g. far floor clicks).
While descending with is_on_floor, always add gravity to vy before move_and_slide.
Cap floor_snap_length when feet stay meaningfully above the goal so strong snap
cannot yank the capsule back onto the step internal edge; cap lifts near landing
(feet within 8 cm of goal Y). Apply snap helper on nomap + descend branches.
2026-04-11 23:58:09 -04:00
VinPropane f70dbe8f88 NEON-29: Descend lip peel when on_floor + wallish (first step → floor)
First terrace step to main floor could stall: is_on_floor stayed true on the
step top while a vertical face zeroed horizontal motion; vy was reset to 0 so
no gravity ran. Taller drops broke contact sooner so second→floor worked.
While descending (goal below feet) and on floor, if is_on_wall or any slide
normal is wall-ish (ny < 0.55), integrate gravity into vy before move_and_slide.
2026-04-11 23:55:03 -04:00
VinPropane 2a25a24e0b NEON-29: Idle gravity + snap when airborne (ledge / step drop)
_physics_idle_tick zeroed velocity with no gravity, so after walking off a
terrace or arriving slightly above the floor FLOOR_SNAP_IDLE could not reach
the ground and the body hovered until a new walk goal. Apply get_gravity()*delta
and FLOOR_SNAP_MOVING while !is_on_floor(). Budgeted idle branch now runs the
same physics when airborne instead of returning without move_and_slide.
2026-04-11 23:51:25 -04:00
VinPropane 6b471b2102 NEON-29: Fix walk steering when goal shares XZ; air gravity; player mask
- _set_horizontal_velocity_toward: optional fallback xz when delta.xz ~ 0 (avoids +X hack).
- Direct-approach branch: if horiz_dist to goal is ~0 but not arrived vertically, steer
  using the first navigation path point with horizontal separation.
- Apply get_gravity()*delta while walking and not on_floor so capsules land and Jolt
  registers floor contacts.
- Set Player collision_mask=3 explicitly; README collision bullet updated.
2026-04-11 23:46:09 -04:00
VinPropane cb001a9369 NEON-29: fix descend bypass using wrong feet_y, trapping player on first step
capsule_feet_y was called with CAPSULE_HALF_HEIGHT (0.5, cylinder half only),
giving a "code-feet" that is 0.4 m above the actual capsule bottom.

From the first step (body Y=1.2): code-feet = 0.7, which is above the
platform goal (Y=0.6). 0.6 < 0.64 triggered the descend bypass even though
the player needed to ascend. Each bypass tick used FLOOR_SNAP_MOVING=0.32 m,
which snapped the capsule back to the step surface (0.11 m below the
assist-lifted bottom), undoing every step assist lift in an oscillation loop.
The player appeared to float at first-step height and could not reach the
second platform.

Fix: use CAPSULE_HALF_HEIGHT + PLAYER_CAPSULE_RADIUS (=0.9, the actual
capsule bottom) for the feet_y in the descend bypass condition. The bypass
now fires only when the goal surface is genuinely below the player's physical
feet, not when the player needs to ascend through stepped geometry.

Also use WALK_STEP_ASSIST_SNAP instead of FLOOR_SNAP_MOVING in the descend
bypass when _step_assist_active is true, so any remaining overlap cannot undo
a step assist lift via the large snap.
2026-04-11 22:46:17 -04:00
VinPropane 1944eeafe8 NEON-29: fix step assist triggering on floor bumps (directional wall check)
The is_on_wall() short-circuits in _step_assist_has_support() and
_step_assist_wallish_blocks() caused the assist to fire whenever the capsule
touched any wall contact — including the sides of random floor bumps while
crossing flat ground toward a distant elevated goal. After a 0.11 m lift,
floor_snap_length = 0.09 m could not reach the original floor (0.11 m away)
and _step_assist_active never cleared, so the capsule floated permanently.

Fixes:
- Remove is_on_wall() blanket short-circuit from both functions.
- _step_assist_has_support() now takes want_dir_xz and accepts wall-ish
  contacts only when they oppose the direction of travel (dot < -0.2),
  i.e. genuine step faces, not sideways bump contacts.
- _step_assist_wallish_blocks() relies solely on the directional collision
  loop (dot < -0.04); no is_on_wall() bypass.
- _try_walk_step_assist() reordered to compute want before calling both
  functions so the same direction vector is used throughout.
- Added "floating free" fallback: if _step_assist_active and no collisions
  and not on floor, clear the flag so FLOOR_SNAP_MOVING restores on the
  next tick. Applied to all three physics paths.
2026-04-11 22:30:07 -04:00
VinPropane 453000d721 NEON-29: fix step-climb regression (snap Y floor + step assist snap)
Two independent root causes kept the player from climbing terrace steps:

1. snap_to_server used the raw surface Y from the server (e.g. 0.6 m for a
   platform) as global_position.y directly.  With capsule total half-height
   = 0.9 m this placed the bottom at -0.3 m underground.  The stale
   is_on_floor()==true + _floor_angle_loose_ticks==0 path skipped every
   move_and_slide(), so Jolt never resolved the penetration and the capsule
   froze at an intermediate Y.
   Fix: clamp global_position.y >= CAPSULE_HALF_HEIGHT + PLAYER_CAPSULE_RADIUS
   (0.9 m) in snap_to_server; force FLOOR_ANGLE_LOOSE_TICKS_AFTER_STOP
   corrective idle ticks afterwards.

2. Each _try_walk_step_assist() lift (0.11 m) was immediately undone by
   floor_snap_length = FLOOR_SNAP_MOVING = 0.32 m snapping the capsule back
   to the lower floor, which was only 0.11 m below the lifted bottom.
   Fix: add WALK_STEP_ASSIST_SNAP = 0.09 m constant and _step_assist_active
   bool.  While climbing, floor_snap_length uses the smaller constant in
   both _after_walk_move_and_slide() and the main physics loop.  The flag
   is cleared when the capsule lands cleanly (is_on_floor && !is_on_wall),
   or when the nav goal is cleared/snapped to server.

Also: is_on_wall() now counts as support in _step_assist_has_support() so
the assist fires while the capsule is floating but pressed against a step
face; _step_assist_active cleared on arrival, snap, and goal-clear; all
temporary DBG print() statements removed.
2026-04-11 22:15:24 -04:00
VinPropane ef211ab4ac NEON-29: fix arrival check using actual capsule bottom for vert_err
The vertical arrival error was computed with CAPSULE_HALF_HEIGHT (0.5),
placing code-feet at body_y - 0.5 = 0.4 on a floor-level player.
Step surfaces at Y=0.3 are only 0.1 m away from that reference, and
Jolt can nudge the body down to ~0.8 when the bottom hemisphere contacts
the step edge, making code-feet = 0.3 and vert_err = 0 — triggering
immediate arrival while the player is still at floor level.

Fix: pass CAPSULE_HALF_HEIGHT + PLAYER_CAPSULE_RADIUS (= 0.9) to
vertical_arrival_error so actual feet = body_y - 0.9 = 0.0 on the
floor. Step goals (Y=0.3) now give vert_err=0.3 >> VERT_ARRIVE_EPS,
preventing premature arrival. Arrival fires correctly once the step
assist lifts the player onto the surface (body_y=1.2, err=0).
Add two tests pinning the new pre/post-climb behaviour.
2026-04-11 19:35:39 -04:00
VinPropane 382e662f1d NEON-29: fix step assist guard and lift for flat-faced terrace steps
_try_walk_step_assist compared goal surface Y to the capsule centre
(~0.9 m above floor) instead of the actual capsule bottom, disabling
the assist for any surface below ~0.925 m — every terrace we added.
The lift formula used offsets 0.45/0.55 instead of the full capsule
total half-height (CAPSULE_HALF_HEIGHT + PLAYER_CAPSULE_RADIUS = 0.9),
producing zero/negative lift.

Fix: guard uses actual_feet_y = pos.y - CAPSULE_HALF_HEIGHT - PLAYER_CAPSULE_RADIUS;
lift targets goal.y + total_half_height via clampf. Flat floor clicks
and cylinder bumps are unaffected (guard still disabled when goal.y is
at or below true feet level).
2026-04-11 19:21:37 -04:00
VinPropane d1bd20582a chore: Fix gdlint/gdformat and harden pre-push on Windows
- main.gd: class member order (prvvars before @onready); wrap doc lines to 100 cols.

- isometric_follow_camera.gd: wrap doc lines to 100 cols.

- position_authority_client.gd: gdformat.

- pre-push: resolve .venv-gd/Scripts/gdlint.exe and gdformat.exe (Windows venv).

- Add scripts/install-git-hooks.ps1; README notes hook install and why CI can still fail.
2026-04-11 00:01:25 -04:00
VinPropane 398317d64f NEON-28: Dev obstacle smoke, nav reparent on hide, and docs
- Input action dev_toggle_occluder_obstacle (Ctrl+Shift+K): toggle obstacle off nav source via reparent to World, rebake NavigationRegion3D, sync NavigationAgent3D; physics/process/collision hardening unchanged intent.

- Occluder dictionary keys by instance id; related client fixes and README/project input note.

- Plan and review: PARSED_GEOMETRY_BOTH / runtime geometry removal lesson for future mechanics.
2026-04-10 23:55:50 -04:00
VinPropane 1ab3fc1dca NEON-28: fix F9 nav rebake coroutine + resync agent goal 2026-04-10 23:13:00 -04:00
VinPropane bb39a64ebd NEON-28: rebake nav after F9 obstacle removal (dev smoke) 2026-04-10 23:11:01 -04:00
VinPropane b702b14be4 NEON-28: drop freed occluder keys without material restore 2026-04-10 23:09:12 -04:00
VinPropane 6340693b46 NEON-28: run follow camera in physics after Player (fix jitter) 2026-04-10 22:23:50 -04:00
VinPropane f0ff5c9d77 NEON-28: fix occluder key check order for freed instances 2026-04-10 22:20:51 -04:00
VinPropane 15d304fa2c NEON-28: camera contracts, E6.M2 adjacency, occluder purge 2026-04-10 22:11:40 -04:00
VinPropane a66bf885f2 NEON-16: apply repo-local gdformat
Apply the repo-local gdformat output to the touched GDScript files so the pre-push formatting hook passes. This is a formatting-only follow-up to the lint-hook cleanup.
2026-04-10 00:50:29 -04:00
VinPropane 4b861a69df NEON-16: satisfy GDScript lint hooks
Refactor the bump escape helper and shared test script loading so the branch passes the repo's pre-push GDScript lint checks. This keeps the runtime behavior intact while unblocking push and PR creation.
2026-04-10 00:48:11 -04:00
VinPropane 1f5c1e7619 NEON-16: harden idle jitter follow-up
Harden idle settling so flat ground, obstacle contacts, and rare bump-edge cases stop drifting instead of looping corrective nudges. Also resolves the saved review follow-up items and syncs the runtime docs to the final feet-height and idle-anchor behavior.
2026-04-10 00:46:53 -04:00
VinPropane 0fa2c0f93b NEON-30: skip occluder bodies in ground-pick raycast loop
ground_pick.gd now checks _collider_is_occluder before the walkable
break: when the ray hits a body in the "occluder" group it advances
OCCLUDER_PICK_THROUGH past the hit point and continues, unconditionally
and regardless of OcclusionPolicy fade state.  Non-occluder click
targeting is unchanged.

Three unit tests added to ground_pick_test.gd covering occluder ancestry
detection.  Plan open questions and module doc updated.
2026-04-09 22:51:22 -04:00
VinPropane 986613fd26 NEON-27: apply gdformat to isometric_follow_camera.gd
Style-only: flatten short boolean expression in occlusion_policy_is_valid;
reformat string interpolation in push_warning calls.
2026-04-08 23:56:21 -04:00
VinPropane f1b4e50729 NEON-27: finalise review (Approved) + Godot uid files
Review verdict updated to Approved; all blocking issues and suggestions
resolved and documented. Add auto-generated Godot .uid sidecar files for
occlusion_policy.gd and occlusion_policy_test.gd.
2026-04-08 23:55:29 -04:00
VinPropane 5e16fdccc8 NEON-27: restore occluders on all _process early-return paths
Fixes review blocking issue: when the follow target is null (freed,
renamed, or path unresolved) or the Camera3D child is missing,
_restore_all_occluders() is now called before returning so geometry
cannot be left stuck semi-transparent.
2026-04-08 23:50:48 -04:00
VinPropane 15d405261f NEON-27: implement OcclusionPolicy — RayCast-based material fade
Adds OcclusionPolicy resource and wires it into IsometricFollowCamera.
Each _process frame, iterative rays from the camera eye to the player
focus detect bodies tagged "occluder"; their MeshInstance3D surfaces are
overridden with a transparent StandardMaterial3D (fade_alpha=0.25, instant).
Materials restore when the body clears the ray. Null-material surfaces
(e.g. the prototype Obstacle with no material assigned) receive a plain
transparent StandardMaterial3D so they fade correctly.

- client/scripts/occlusion_policy.gd — new Resource (enabled, fade_alpha,
  occluder_group, occluder_collision_mask, max_occluder_cast_depth,
  occluder_count_log_threshold, is_valid)
- client/resources/isometric_occlusion_policy.tres — default instance
- client/scripts/isometric_follow_camera.gd — occlusion_policy export,
  _update_occlusion pipeline, _apply_occluder_fade/_restore_occluder helpers,
  static occlusion_policy_is_valid guard, _exit_tree cleanup
- client/scenes/main.tscn — Obstacle tagged "occluder", policy assigned
- client/test/occlusion_policy_test.gd — new GdUnit4 unit tests
- client/test/isometric_follow_camera_test.gd — policy guard static tests
- docs/decomposition/modules/E1_M2_IsometricCameraController.md — NEON-27
  snapshot + readability demo gate note
- docs/plans/NEON-27-implementation-plan.md — null-material decision recorded
2026-04-08 23:34:00 -04:00
VinPropane cdf9d59bd9 NEON-26: add pre-push GDScript lint hook
Document the repo's enforced GDScript lint rules and add a local pre-push hook so gdlint/gdformat failures are caught before CI.
2026-04-08 23:10:40 -04:00
VinPropane ff830813c7 NEON-26: address code review follow-ups (zoom validation, docs) 2026-04-08 22:58:28 -04:00
VinPropane 753ce84861 NEON-26: ZoomBandConfig, discrete zoom input, CameraState sync 2026-04-08 22:32:45 -04:00
VinPropane 9f6297c46d NEON-25: gdformat isometric_follow_camera.gd 2026-04-08 00:08:00 -04:00
VinPropane 387b61b577 NEON-25: fix gdlint (line length, class-definitions-order) 2026-04-08 00:05:55 -04:00
VinPropane e96f70d889 NEON-25: follow-up from code review (missing target warn, docs, E1.M2 status) 2026-04-07 23:54:27 -04:00
VinPropane 4930fd89a6 NEON-25: isometric follow camera, CameraState, tests, and docs 2026-04-07 23:46:04 -04:00
VinPropane 791c7dad1b
Merge branch 'main' into NS-21-gdscript-unit-testing 2026-04-06 22:22:16 -04:00
VinPropane 8113e48cc8 NS-21: add GdUnit4, client tests, and CI headless run
- Vendor gdUnit4 v6.1.2 under client/addons; enable plugin in project.godot
- Tests: player.gd API, position authority HTTP flow (Node transport double),
  ground_pick walkable ancestry; test double script for injection
- position_authority_client: _create_http_request factory, Node-typed _http,
  emit move_rejected unknown for empty/missing reasonCode strings
- gdscript workflow: lint scripts+test, format check, cache Godot 4.6, GdUnit CLI
- README testing section; gitignore client/reports; testing-expectations Godot harness
2026-04-06 22:08:37 -04:00
VinPropane 72897a4924 NS-24: Fix GDScript lint (line length, gdlint, gdformat)
- Shorten file-header and doc comments to satisfy max-line-length.
- Rename bump collision preload const to BUMP_COLLISION_CONSTS_SCRIPT.
- Refactor idle floor-angle and walk-step logic for return/format rules.
2026-04-06 00:41:32 -04:00
VinPropane 7b11d4e238 NS-24: Eliminate idle jitter and movement QA bumps (Godot client)
Physics: Jolt 3D; physics_interpolation off; 120 TPS. CharacterBody3D idle
path uses dual floor_max_angle, rim/straddle and ridged-contact handling,
rim settle and random-floor-bump lip/wall escape, floor_block_on_wall,
NavigationAgent3D avoidance off, per-node interp off. No global_transform in
_process (avoid render/physics ghosting).

QA geometry: random_floor_bumps.gd (load from main.gd); each bump on its own
StaticBody3D under NavigationRegion3D; random_floor_bump_collision_constants.gd
for collider fudge and RANDOM_FLOOR_BUMP_MESH_GROUP.

Scene: player safe_margin and mesh Y lift; MoveRejectPedestal / MoveRejectFarPad
renamed from NS19*; Mat_player_capsule without polygon_offset (per follow-up).

Docs: NS-24 plan + resolution, client/README manual steps, code review
2026-04-05-NS-24.md; server/README bump height note. Code/scene identifiers
avoid ticket-based ns19_* naming.
2026-04-06 00:32:41 -04:00
VinPropane 8956545d3c NS-23: Document idle skip vs is_on_floor / move_and_slide 2026-04-05 15:02:13 -04:00
VinPropane 8fe70a75de NS-23: Comment DESCEND_GOAL_Y_MARGIN (surface Y vs mid-capsule) 2026-04-05 15:01:29 -04:00
VinPropane 39b4760270 NS-23: Skip move_and_slide when idle on floor (stop rest jitter)
Zero-velocity move_and_slide each physics tick still reapplies floor snap and
collision, which often reads as vibration. If there is no walk goal (or we
just arrived), velocity is zero, and is_on_floor(), return early.
2026-04-05 02:06:06 -04:00
VinPropane 6c73557c3b NS-23: Restore player.gd from e1beb82 (bumps ok, obstacle bee-line)
User-requested rollback to the pre-72eef09 behavior: skip nav waypoints when
auth goal.y is below body (floor pick vs mid-capsule), steering xz straight at
the goal — good for bump departure, bad for gray-box detours. Revert
floor_snap_length to 0.28 to match that snapshot.
2026-04-05 02:02:57 -04:00
VinPropane ee6c4b2718 NS-23: Simplify path-follow — drop bump/step-down heuristics
Restore a straight Godot nav pattern: steer toward get_next_path_position until
finish or final approach, then toward goal; horizontal-only arrival by xz.
Removes footprint rays, bump-step group, and step-off vy layering that fought
the engine. Slightly raise Player floor_snap_length for small ledges.
2026-04-05 02:01:17 -04:00
VinPropane 25d21c0011 NS-23: Remove bump rim velocity unstick (fixes edge vibration)
Per-frame wall-normal + goal boosts fought a flipping wall normal and capped
re-vectoring, causing back-and-forth jitter. Drop _apply_bump_rim_wall_unstick
and related helpers/constants; keep direct step-down + footprint logic only.
STEP_OFF_DESCEND_SPEED 2.2 → 1.75.
2026-04-05 01:58:57 -04:00
VinPropane 430770e5bf NS-23: Rename bump helpers; WALK_BUMP_STEP_GROUP for scene group
Remove ticket-style names from collider/footprint APIs (_collider_in_bump_step_group,
_departing_bump_step_to_lower_floor, touches_bump_step). Editor group string stays
ns19_bump in one const until main.tscn renames the group.
2026-04-05 01:57:36 -04:00
VinPropane 38aa4bfcd5 NS-23: Unstick bump rim — wall peel + goal boost when descending
Convex frustum sides turn goal steering into tangential rim slide with little
net progress. When footprint still hits ns19_bump and dest floor is lower by
at least STEP_OFF_MIN_DROP, add horizontal wall-normal push (if it does not
strongly oppose goal) plus a small goal-direction boost, cap xz speed.
Slightly raise STEP_OFF_DESCEND_SPEED for the step-off branch.
2026-04-05 01:56:56 -04:00
VinPropane 8053f08804 NS-23: Arrival footing uses footprint max_y (avoid false stop on bump lip)
Premature arrival used _floor_y_under_body() vs dest footing; at the rim that
can read floor while xz is already within ARRIVE_EPS, clearing the walk goal.
Match step-down logic: compare destination footing to footprint max_y and
cache footprint scan once per physics frame.
2026-04-05 01:54:42 -04:00
VinPropane 003750c7ae NS-23: Direct step-down while on ns19_bump up to auth horiz limit
Nav paths often wrap along bump rims when the goal is far; the 8 m cap then
disabled step-off and left rim-following. Detect NS-19 bumps via
group on footprint rays; when any hit is a bump and vertical drop is in the
NS-19 band, steer straight to the goal up to 18 m (server max step). Non-bump
podiums keep the 8 m close cap. Single footprint pass returns max_y + bump.
2026-04-05 01:52:04 -04:00
VinPropane f67705f4a3 NS-23: Step-off footing uses capsule footprint max + wider horiz
At bump lips, center/lower rays often hit the floor first while the body is
still on the rim, so drop read ~0 and nav rim-follow resumed. Max floor Y
over center, lower probe, and 8 xz offsets (cardinal + diagonal) keeps
step-down bypass active until the whole footprint clears. Raise
STEP_OFF_MAX_HORIZ to 8 m (still capped by small drop band).
2026-04-05 01:48:55 -04:00
VinPropane b1ce87c5cc NS-23: Revert global gravity; use step-off-only descend vy
Project gravity while walking made motion worse (jitter, floor fights).
Restore horizontal-only seek everywhere except the short step-down bypass,
where a fixed downward speed helps clear NS-19 bump lips without affecting
normal nav or idle air state.
2026-04-05 01:46:08 -04:00
VinPropane 127e67f02b NS-23: Apply gravity when airborne so bump ledges are not sticky
Horizontal seek only sets vx/vz; zero vy only on floor. Use project default
gravity vector when not on floor (with and without walk goal) so stepping
off NS-19 bumps can fall to the lower surface instead of hanging with vy=0.
2026-04-05 01:43:56 -04:00
VinPropane 3b4340830a NS-23: Fix bump depart — goal Y vs surface footing + arrive by footing
- Ray down at goal xz for destination surface Y; step-off compares two footings.
- Arrive when horiz close and either capsule Y matches goal or underfoot matches
  dest footing (server may send center 0.9 or pick surface 0).
2026-04-05 01:39:45 -04:00
VinPropane dbae80627f NS-23: Tighten bump step-off bypass (dual floor ray, drop band, horiz cap)
- Sample floor Y from center and lower probe; use max for bump rim.
- Only bypass nav for step-sized vertical drop with wider near-goal horiz.
- Loosen next-waypoint stickiness so rim waypoints fall back to goal sooner.
2026-04-05 01:36:32 -04:00
VinPropane 8b2b64a326 NS-23: bump step-off bypass using floor-under-feet, not body Y
Ray down for floor Y under the capsule; skip nav only when goal is a bit
lower than that surface, drop ≤0.55 m, and horiz ≤1.05 m. Matches
leaving a bump without comparing pick/body origin (which broke obstacle
pathing). Long crosses still use waypoints.
2026-04-05 01:33:57 -04:00
VinPropane 72eef09d15 NS-23: remove descend nav skip — restored obstacle pathing
goal.y < body.y matched almost every move because ray picks use floor Y
while CharacterBody3D origin is mid-capsule, so we always bee-lined and
ignored waypoints. Rely on small-xz next-waypoint fallback for bump rims
instead.
2026-04-05 01:32:02 -04:00
VinPropane e1beb82a75 NS-23: unstuck bump top — descend steers xz to goal; doc abs(dy)
Server already allows downward steps via abs(ΔY) vs MaxVerticalStep; document
that explicitly.

Client: when authoritative goal Y is below the body, skip nav waypoints and
steer horizontally toward the goal only — mesh waypoints under the rim
gave ~no horizontal speed (felt like wrong collision).
2026-04-05 01:29:31 -04:00
VinPropane b8176f8fa1 NS-23: simplify client movement — horizontal only, no bump nav hacks
Replace stacked NS-19 workarounds (underfoot rays, plateau nav skip, 3D
climb/descent steering, rim escape) with one rule: set horizontal
velocity toward waypoint/goal, force velocity.y = 0, move_and_slide +
floor_* handle height along geometry. Server MoveCommand remains the
single place for allow/deny on illegal steps.
2026-04-05 01:26:59 -04:00
VinPropane fba2c8e171 NS-19: large bump — wider underfoot rays + shallower frustum
- Add xz-offset down-rays (~0.42 m) so rim/lean on the 1 m Bump A still
  counts as on-bump for nav skip (single center column could miss).
- Widen Bump A convex base 0.65→0.72 (top 0.5 unchanged): gentler ~34°
  sides vs ~45° to reduce CharacterBody3D snags; mesh unchanged.
2026-04-05 01:19:28 -04:00
VinPropane 35d16d15db NS-23: harden bump departure (always skip nav on bump, steer dy<0)
- If any of three down-rays (body + two lower origins) hits ns19_bump,
  skip NavigationAgent waypoints unconditionally on that footprint.
- Steering: any goal lower than origin with same xz (dy < 0) uses rim
  escape; dy in (-0.04, 0) previously zeroed velocity and could hang.
2026-04-05 01:16:07 -04:00
VinPropane 66d7438a59 NS-23: broaden NS-19 bump nav bypass for departure
Plateau-only (strict flat normal + is_on_floor) missed rim poses and
convex top edges. Skip waypoints when a down ray from body or feet
offset hits ns19_bump and either the goal is lower than the body origin
(typical floor click off bump) or we are on a flatter floor normal
(~15°). Keeps waypoint routing on bump slopes when climbing past
obstacles unless the move is clearly downward.
2026-04-05 01:14:19 -04:00
VinPropane db1fe0f46b NS-23: skip nav waypoints on NS-19 bump plateaus only
Restore obstacle routing via waypoints elsewhere; on bump tops nav often
prefers a downward segment and breaks departure. Detect plateau with
is_on_floor + flat normal + ray down into group ns19_bump; then steer
only toward authoritative goal xz. Slopes keep waypoint pathing.
2026-04-05 01:12:11 -04:00
VinPropane a5ec8df4a7 NS-23: terminal collision, nav around obstacle, agent under region
- Add BoxShape3D to PrototypeTerminal (layer 1, walkable) so the player
  cannot pass through; matches terminal mesh size.
- Bake navigation from collision layer 1 only so the player (layer 2) is
  not merged into the mesh; reparent Player under NavigationRegion3D so
  NavigationAgent3D resolves a valid map.
- Restore nav waypoint following for long horizontal legs; keep horizontal
  steering toward next/goal so bump departure behavior stays sane.
2026-04-05 01:10:16 -04:00
VinPropane 05d7916cc7 NS-23: fix bump departure — stepped pick + goal-only steer
Ground pick: step the ray through steep walkable hits (0.09 < dot UP < 0.64)
so a view that grazes bump sides still resolves to flat floor beyond; walls
(dot ~0) stop the chain.

Player: drop NavigationAgent3D waypoint following; steer only toward the
server goal in xz so plateau paths are not dominated by vertical nav
segments. move_and_slide handles slopes and obstacles.
2026-04-05 01:07:02 -04:00
VinPropane 725bef1133 NS-23: allow ground pick on bump slopes (fix stuck departure)
MIN_WALKABLE_NORMAL_DOT_UP 0.82 is stricter than NS-19 frustum slopes
(~45° / dot 0.71 on Bump A, ~39° / 0.78 on B). Clicks off the plateau
often hit the slope first; the pick was dropped with no MoveCommand.
Lower to 0.64 so ramps register; vertical walls remain ~0.
2026-04-05 01:03:51 -04:00
VinPropane 4b9e30c624 NS-23: fix false arrival when leaving bump tops
VERT_ARRIVE_EPS (0.16) was larger than the bump rise (~0.12–0.15), so a
floor click beside the plateau often satisfied arrival (horiz + vert) on
the first frame and cleared the walk goal before any motion.

Tighten vertical arrival to 0.055. Add a steer fallback when the target
shares xz but is lower (plateau vs floor underfoot) so we still pick a
horizontal escape toward the rim.
2026-04-05 01:01:22 -04:00
VinPropane 7ffcf54d80 NS-23: horizontal nav steering so bump-top departure works
Path waypoints from the plateau often sit on the slope almost under the
player (same xz, lower y). 3D steer + y clamp gave almost no horizontal
velocity, so standing on top then clicking off looked stuck. Steer in xz
at MOVE_SPEED and let move_and_slide follow floor/slope; keep 3D only for
vertical climb when xz is aligned.
2026-04-05 00:58:10 -04:00
VinPropane f6baf3bf72 NS-23: NS-19 bump single frustum collider (fix departure edges)
Replace top slab + four ramp wedges with one ConvexPolygonShape3D frustum
per bump so plateau and slopes are one manifold (no collider seams at
table edges when walking off).

Add CharacterBody3D safe_margin to reduce edge contact with physics.
2026-04-05 00:55:15 -04:00
VinPropane 0e79250f1a NS-23: NS-19 bump convex ramps; drop autostep
- Add four invisible ConvexPolygonShape3D ramp wedges per bump (walkable) so
  move_and_slide can climb/descend without test_move ledge hacks.
- Remove _try_autostep / _maybe_autostep_after_slide and LEDGE_DROP_STEPS;
  they mis-detected geometry and fought CharacterBody3D slide.
- Fix duplicate subresource unique_id on BumpA RampWest and BumpB RampSouth.
2026-04-05 00:48:51 -04:00
VinPropane 394e2a71fb NS-23: Fix const LEDGE_DROP_STEPS (Godot const expression rule) 2026-04-05 00:44:15 -04:00
VinPropane b007c7fe58 NS-23: Autostep down+forward to walk off bump edges 2026-04-05 00:42:50 -04:00
VinPropane 6fd49a2e1b NS-23: Auto-step onto NS-19 bumps via CharacterBody3D.test_move 2026-04-05 00:39:30 -04:00
VinPropane 6d00070c00 NS-23: Climb NS-19 bumps with 3D steer + vertical arrival + floor snap 2026-04-05 00:36:43 -04:00
VinPropane 510422d8a2 NS-23: Drive motion from stored server goal; ignore agent finished flag 2026-04-05 00:33:28 -04:00
VinPropane f49c8b88d2 NS-23: Fix nav movement (sync bake, map guard, path/fallback steering) 2026-04-05 00:29:58 -04:00
VinPropane 0ac059fa3b NS-23: NavigationAgent3D path-follow with server move authority 2026-04-05 00:23:03 -04:00
VinPropane 1f9260e8ba chore(client): order const/var before @onready for gdlint 2026-04-05 00:02:59 -04:00
VinPropane 7fffc3137a NS-19: auto-clear move reject label after 4s 2026-04-04 23:49:14 -04:00
VinPropane 7ac6b66b51 NS-19: ignore vertical walkable surfaces in ground ray pick 2026-04-04 23:45:54 -04:00
VinPropane 32d2153acb NS-19: server move validation, client rejection UX, scene QA props 2026-04-04 23:39:40 -04:00