Commit Graph

104 Commits (ae66b6b313bb364c773fc9ebe70e888951dd421a)

Author SHA1 Message Date
VinPropane ae66b6b313 NEON-29: Fix player–world collision pairing (env mask includes layer 2)
Player uses collision_layer 2 so camera pick rays (mask 1) skip the capsule.
Static bodies defaulted to collision_mask 1, so they never scanned layer 2 and
Godot produced no floor/wall contacts—walk appeared stuck with is_on_floor
false. Set collision_mask=3 on all NavigationRegion3D StaticBody3D walkables
and occluders; bumps inherit from Floor at spawn. Document the pairing in README.
2026-04-11 23:42:14 -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 afb810db9d NEON-29: correct README — FarPad no longer rejects, only pedestal top does
Horizontal distance was never a valid rejection reason; remove references
to FarPad as a reject target and clarify that MaxVerticalStep (pillar top)
is the only remaining server rejection criterion.
2026-04-11 18:20:43 -04:00
VinPropane ac75af9c73 NEON-29: remove horizontal step limit; add HorizontalStepEnabled flag
Expanded 45×45 district made the 18 m MaxHorizontalStep too restrictive
(floor diagonal ~63 m). Add HorizontalStepEnabled to MovementValidationOptions
(default false, matching DistrictBoundsEnabled pattern) so the check is
skipped unless explicitly opted in. Add test for disabled case (9 pass).
Update README and plan with the decision.
2026-04-11 18:17:32 -04:00
VinPropane 6879d283a9 NEON-29: expand prototype district to 45×45 with terraces and obstacles
Replace 20×20 floor with 45×45 (~5× area). Add three occluder
obstacles (ObstacleB/C/D) distributed across the expanded space for
camera/occlusion stress QA. Add three terrace structures (TerracePlatformA,
TerraceStepB+TerracePlatformB, TerracePlatformC) for vertical variation;
each height transition respects agent_max_climb=0.35 m so the nav mesh
routes onto all surfaces. Update README with district layout table and
designer/QA limits.
2026-04-11 18:12:33 -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 0792da702a chore: disable ASP.NET HttpLogging; update client project.godot 2026-04-10 22:27:35 -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 1f8c0d04d1 NEON-30: address review — check AC boxes, add manual-check section to README 2026-04-09 23:03:17 -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 0d71b873af NEON-26: add finer zoom band ladder (11 steps) 2026-04-08 22:37:33 -04:00
VinPropane 806777b6d5 NEON-26: widen default zoom bands, stronger zoom-out 2026-04-08 22:35:54 -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 0a86215bc0 NEON-25: sync project.godot (GdUnit inspector pref; editor section order) 2026-04-07 23:58:47 -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 fa19ab7337 chore: migrate Jira references from NS-* to NEON-* in docs and rules
Rename implementation plans and dated reviews to NEON issue numbers per
Atlassian project re-key. Update browse URLs, cross-links, module docs,
client/server READMEs, and Cursor rules (project key NEON).
2026-04-07 00:15:06 -04:00
VinPropane da72ea8264 NS-21: fail CI if Godot logs SCRIPT ERROR during GdUnit discovery
GdUnit exits 0 when broken suites are skipped; grep log for parse/load errors.
2026-04-06 22:34:43 -04:00
VinPropane 4f46fa580b NS-21: fix ground_pick_test const — preload not load (Godot 4.6)
load() is not a constant expression; suite failed parse and was skipped in CI
while gdlint passed. Restores 11 tests across 3 suites.
2026-04-06 22:32:05 -04:00
VinPropane 1f70bfb80a NS-21: track GdUnit4 bin/ scripts (unignore vs root bin/ rule)
Root .gitignore bin/ matched client/addons/gdUnit4/bin/, so CI never had
GdUnitCmdTool.gd. Negate that path; add the four bin files.
2026-04-06 22:29:08 -04:00
VinPropane 7b2c35f57c NS-21: fix GdUnit CI path casing; preflight and README note
Linux res:// paths are case-sensitive; gdunit4 breaks, gdUnit4 is correct.
2026-04-06 22:27:36 -04:00
VinPropane f09517af6a NS-21: fix gdlint in test suites (load-once, line length, const name) 2026-04-06 22:25:23 -04:00
VinPropane 791c7dad1b
Merge branch 'main' into NS-21-gdscript-unit-testing 2026-04-06 22:22:16 -04:00
VinPropane 449b58b40a NS-21: merge main into project.godot (Jolt physics, MSAA, GdUnit plugin) 2026-04-06 22:21:20 -04:00
VinPropane 7b7f37f92b NS-21: sync project.godot order, C# api uid; ignore GdUnitRunner.cfg 2026-04-06 22:19:50 -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 d22d252b59 docs: Link NS-24 for idle jitter (plan + client README) 2026-04-05 15:04:09 -04:00