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.
_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).
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.
Planning doc for expanding the prototype client district to ~5×
current footprint with terraces, step geometry, and extra obstacles
for camera/nav stress QA.