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.pull/41/head
parent
173c348030
commit
0f4c23b815
|
|
@ -22,8 +22,8 @@ const FLOOR_SNAP_IDLE: float = 0.11
|
|||
## Below this floor-normal dot up, idle tick uses **moving** `floor_max_angle` (rim).
|
||||
const IDLE_RIM_MIN_FLOOR_UP_DOT: float = 0.968
|
||||
## Stable flat idle support: skip corrective idle motion when support is effectively level.
|
||||
## Slightly below 1.0 so Jolt does not flicker across the threshold on thin treads.
|
||||
const STABLE_IDLE_FLOOR_MIN_UP_DOT: float = 0.9996
|
||||
## Below 1.0 so Jolt seam normals do not flicker across the threshold (idle XZ noise).
|
||||
const STABLE_IDLE_FLOOR_MIN_UP_DOT: float = 0.999
|
||||
## Rare edge contacts can keep corrective nudges alive forever; budget them, then hold x/z.
|
||||
const IDLE_MANUAL_CORRECTION_MAX_TICKS: int = 8
|
||||
## Horizontal nudge per tick for rim / straddle settle (**`_maybe_idle_rim_settle_nudge`**).
|
||||
|
|
@ -367,7 +367,8 @@ static func idle_slide_contacts_are_ridged(slide_normals: Array[Vector3]) -> boo
|
|||
var ny: float = n.y
|
||||
if ny > 0.65:
|
||||
upish += 1
|
||||
elif ny < 0.45:
|
||||
# Steeper than ~70° from horizontal — shallow seam faces (ny≈0.4) are not “lip” walls.
|
||||
elif ny < 0.35:
|
||||
wallish += 1
|
||||
return upish >= 1 and wallish >= 1
|
||||
|
||||
|
|
@ -598,7 +599,10 @@ func _snap_capsule_upright() -> void:
|
|||
|
||||
func _physics_process(delta: float) -> void:
|
||||
_debug_trace_frame += 1
|
||||
var use_loose_floor_angle: bool = _has_walk_goal or _floor_angle_loose_ticks > 0
|
||||
# Do not tie wide floor angle to `_floor_angle_loose_ticks` alone: after a walk on **flat**
|
||||
# ground that still widened angle for ~0.8 s, `move_and_slide` + idle snap caused visible XZ
|
||||
# jitter. Use moving max angle only while walking or when idle support is shallow / ridged.
|
||||
var use_loose_floor_angle: bool = _has_walk_goal
|
||||
if not _has_walk_goal:
|
||||
var floor_up_dot: float = get_floor_normal().dot(Vector3.UP)
|
||||
var shallow_idle_floor: bool = is_on_floor() and floor_up_dot < IDLE_RIM_MIN_FLOOR_UP_DOT
|
||||
|
|
@ -672,6 +676,15 @@ func _physics_process(delta: float) -> void:
|
|||
_idle_manual_correction_ticks = 0
|
||||
_nav_agent.set_target_position(global_position)
|
||||
_physics_idle_tick(delta)
|
||||
if _stable_idle_support():
|
||||
_idle_manual_correction_ticks = 0
|
||||
velocity = Vector3.ZERO
|
||||
floor_snap_length = FLOOR_SNAP_IDLE
|
||||
_hold_idle_anchor()
|
||||
_debug_trace_idle_state("arrival_stable")
|
||||
_debug_trace_transform("physics")
|
||||
_snap_capsule_upright()
|
||||
return
|
||||
if _apply_idle_manual_correction():
|
||||
_idle_manual_correction_ticks += 1
|
||||
_debug_trace_transform("physics")
|
||||
|
|
|
|||
|
|
@ -80,6 +80,14 @@ func test_idle_support_is_stable_true_when_loose_ticks_but_flat_open_support() -
|
|||
assert_that(stable).is_true()
|
||||
|
||||
|
||||
func test_idle_support_is_stable_true_when_loose_ticks_and_shallow_seam_not_ridged() -> void:
|
||||
# Second normal has ny=0.42 — not “wallish” (ny < 0.35) for ridged lip detection.
|
||||
var n2: Vector3 = Vector3(0.0, 0.42, sqrt(1.0 - 0.42 * 0.42)).normalized()
|
||||
var slide_normals: Array[Vector3] = [Vector3.UP, n2]
|
||||
var stable: bool = PLAYER_SCRIPT.idle_support_is_stable(true, Vector3.UP, slide_normals, 12)
|
||||
assert_that(stable).is_true()
|
||||
|
||||
|
||||
func test_idle_support_is_stable_false_when_not_on_floor() -> void:
|
||||
var slide_normals: Array[Vector3] = [Vector3.UP]
|
||||
var stable: bool = PLAYER_SCRIPT.idle_support_is_stable(false, Vector3.UP, slide_normals, 0)
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ No new automated GDScript tests are added for this story — there is no new GDS
|
|||
## Approach treads (district convention)
|
||||
|
||||
- **Shipped in scene:** **`TerracePlatformC_Approach`** (`PCS_*`, teal-style **3×4** cardinals, **7.2 m** NS / **WE** span), **`TerraceStepB_Approach`** (`TSB_*`, **S/E/W** only — north meets violet), **`TerracePlatformB_Approach`** (`TPB_*`, **N/E/W**, **six** ~**0.104 m** risers for **0.6 m** deck height; south remains **gold step**). **Smooth ramps** for other props can follow the same rise/run idea without new `player.gd` logic.
|
||||
- **Idle jitter on thin treads:** **`idle_support_is_stable`** used to return false for the entire **`FLOOR_ANGLE_LOOSE_TICKS_AFTER_STOP`** window whenever **`loose_ticks > 0`**, so the capsule ran **rim / corrective** idle on **flat** tread tops after every stop — **`idle_manual_correction`** nudges vs anchor caused visible **XZ/Y** noise. **Fix:** treat as stable during loose ticks unless **`idle_slide_contacts_are_ridged`** (floor + wall in slide normals). Slightly relaxed **`STABLE_IDLE_FLOOR_MIN_UP_DOT`** (**0.9998 → 0.9996**) for Jolt normal flicker.
|
||||
- **Idle jitter on thin treads / flat stops:** (1) **`idle_support_is_stable`** no longer treats every **`loose_ticks > 0`** frame as unstable unless slides are **ridged** (true lip: up + steep wall normal). (2) **`use_loose_floor_angle`** no longer follows **`_floor_angle_loose_ticks` alone** — post-stop **50°** floor max on **open flat** caused **`move_and_slide`** XZ noise for ~0.8 s. (3) **`STABLE_IDLE_FLOOR_MIN_UP_DOT`** **0.999**; ridged **wallish** threshold **ny < 0.35** (shallow seam faces not lips). (4) **Arrival** frame skips rim correction when already **stable** after the first idle tick.
|
||||
- **Not in this pass:** **`PrototypeTerminal`**, **MoveReject** props, **runtime random bumps** — add geometry when those need climbable access.
|
||||
|
||||
## Open questions / risks
|
||||
|
|
|
|||
Loading…
Reference in New Issue