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.pull/41/head
parent
8d9ad0ecd3
commit
5f6058b0ce
|
|
@ -33,7 +33,7 @@ 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
|
||||
## Consecutive unstable-idle physics ticks before leaving latched stable idle (thin tread lip).
|
||||
const STABLE_IDLE_UNLATCH_TICKS: int = 2
|
||||
const STABLE_IDLE_UNLATCH_TICKS: int = 5
|
||||
## Horizontal nudge per tick for rim / straddle settle (**`_maybe_idle_rim_settle_nudge`**).
|
||||
const IDLE_RIM_SETTLE_STEP: float = 0.004
|
||||
## Used by tests and vertical routing checks (feet vs goal surface).
|
||||
|
|
@ -403,10 +403,16 @@ static func idle_support_is_stable(
|
|||
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):
|
||||
# Post-stop **ridged** slides (tread + riser) used to force unstable idle for the entire
|
||||
# `loose_ticks` window even when the **floor** is level — e.g. idle on a flat approach tread
|
||||
# hugging a vertical face stayed in corrective `move_and_slide` ~0.8 s (HUD X/Y/Z noise).
|
||||
# Only demand rim settle when the reported floor is already shallow (lip straddle), not mere
|
||||
# proximity to a riser on an otherwise flat tread.
|
||||
if (
|
||||
loose_ticks > 0
|
||||
and idle_slide_contacts_are_ridged(slide_normals)
|
||||
and floor_normal.dot(Vector3.UP) < IDLE_RIM_MIN_FLOOR_UP_DOT
|
||||
):
|
||||
return false
|
||||
return true
|
||||
|
||||
|
|
|
|||
|
|
@ -68,9 +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_and_ridged_slide_contacts() -> void:
|
||||
func test_idle_support_is_stable_true_when_loose_ticks_and_ridged_but_floor_still_level() -> void:
|
||||
# Flat tread beside a riser: ridged slides + post-stop loose ticks must not block stable idle.
|
||||
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_true()
|
||||
|
||||
|
||||
func test_idle_support_is_stable_false_when_loose_ticks_and_ridged_and_shallow_floor() -> void:
|
||||
var slide_normals: Array[Vector3] = [Vector3.UP, Vector3(1.0, 0.0, 0.0)]
|
||||
var floor_n: Vector3 = Vector3(0.35, 0.88, 0.0).normalized()
|
||||
assert_that(floor_n.dot(Vector3.UP) < 0.968).is_true()
|
||||
var stable: bool = PLAYER_SCRIPT.idle_support_is_stable(true, floor_n, slide_normals, 12)
|
||||
assert_that(stable).is_false()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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 / 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.
|
||||
- **Idle jitter on thin treads / flat stops:** (1) **`idle_support_is_stable`**: post-stop **`loose_ticks` + ridged** blocks stable idle only when **`floor_normal.dot(UP) < IDLE_RIM_MIN_FLOOR_UP_DOT`** — flat tread + vertical riser (e.g. **TerraceStepB_Approach** corner) no longer spends ~0.8 s in corrective idle. (2) **`use_loose_floor_angle`** not tied to **`loose_ticks` alone**. (3) **`STABLE_IDLE_FLOOR_MIN_UP_DOT` 0.999**; ridged **wallish ny < 0.35**; **stable latch + Y anchor**; **`STABLE_IDLE_UNLATCH_TICKS` 5**. (4) **Arrival** stable shortcut; **nav column steer** hysteresis.
|
||||
- **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