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.pull/41/head
parent
103f8bbe19
commit
1867e3e45c
|
|
@ -22,7 +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.
|
||||
const STABLE_IDLE_FLOOR_MIN_UP_DOT: float = 0.9998
|
||||
## 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
|
||||
## 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`**).
|
||||
|
|
@ -359,14 +360,30 @@ func _physics_idle_tick(delta: float) -> void:
|
|||
move_and_slide()
|
||||
|
||||
|
||||
static func idle_slide_contacts_are_ridged(slide_normals: Array[Vector3]) -> bool:
|
||||
var upish := 0
|
||||
var wallish := 0
|
||||
for n: Vector3 in slide_normals:
|
||||
var ny: float = n.y
|
||||
if ny > 0.65:
|
||||
upish += 1
|
||||
elif ny < 0.45:
|
||||
wallish += 1
|
||||
return upish >= 1 and wallish >= 1
|
||||
|
||||
|
||||
static func idle_support_is_stable(
|
||||
on_floor: bool, floor_normal: Vector3, _slide_normals: Array[Vector3], loose_ticks: int
|
||||
on_floor: bool, floor_normal: Vector3, slide_normals: Array[Vector3], loose_ticks: int
|
||||
) -> bool:
|
||||
## `_slide_normals` is reserved for future extra-contact analysis.
|
||||
if not on_floor or loose_ticks > 0:
|
||||
if not on_floor:
|
||||
return false
|
||||
if floor_normal.dot(Vector3.UP) < STABLE_IDLE_FLOOR_MIN_UP_DOT:
|
||||
return false
|
||||
# Previously any `_floor_angle_loose_ticks > 0` blocked stable idle for the full post-stop
|
||||
# window (~0.8 s), forcing rim nudges + `move_and_slide` on **flat** approach treads — XZ/Y
|
||||
# jitter next to thin steps. Keep corrective settle only when slides show floor + wall (lip).
|
||||
if loose_ticks > 0 and idle_slide_contacts_are_ridged(slide_normals):
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
|
|
@ -471,15 +488,7 @@ func _notification(what: int) -> void:
|
|||
|
||||
|
||||
func _idle_ridged_floor_contacts() -> bool:
|
||||
var upish := 0
|
||||
var wallish := 0
|
||||
for i: int in get_slide_collision_count():
|
||||
var ny: float = get_slide_collision(i).get_normal().y
|
||||
if ny > 0.65:
|
||||
upish += 1
|
||||
elif ny < 0.45:
|
||||
wallish += 1
|
||||
return upish >= 1 and wallish >= 1
|
||||
return idle_slide_contacts_are_ridged(_current_slide_normals())
|
||||
|
||||
|
||||
func _maybe_idle_rim_settle_nudge() -> bool:
|
||||
|
|
|
|||
|
|
@ -68,12 +68,18 @@ func test_idle_support_is_stable_true_when_flat_floor_has_extra_contacts() -> vo
|
|||
assert_that(stable).is_true()
|
||||
|
||||
|
||||
func test_idle_support_is_stable_false_when_loose_ticks_active() -> void:
|
||||
var slide_normals: Array[Vector3] = [Vector3.UP]
|
||||
func test_idle_support_is_stable_false_when_loose_ticks_and_ridged_slide_contacts() -> void:
|
||||
var slide_normals: Array[Vector3] = [Vector3.UP, Vector3(1.0, 0.0, 0.0)]
|
||||
var stable: bool = PLAYER_SCRIPT.idle_support_is_stable(true, Vector3.UP, slide_normals, 12)
|
||||
assert_that(stable).is_false()
|
||||
|
||||
|
||||
func test_idle_support_is_stable_true_when_loose_ticks_but_flat_open_support() -> void:
|
||||
var slide_normals: Array[Vector3] = [Vector3.UP]
|
||||
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,6 +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.
|
||||
- **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