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.
|
## Rare edge contacts can keep corrective nudges alive forever; budget them, then hold x/z.
|
||||||
const IDLE_MANUAL_CORRECTION_MAX_TICKS: int = 8
|
const IDLE_MANUAL_CORRECTION_MAX_TICKS: int = 8
|
||||||
## Consecutive unstable-idle physics ticks before leaving latched stable idle (thin tread lip).
|
## 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`**).
|
## Horizontal nudge per tick for rim / straddle settle (**`_maybe_idle_rim_settle_nudge`**).
|
||||||
const IDLE_RIM_SETTLE_STEP: float = 0.004
|
const IDLE_RIM_SETTLE_STEP: float = 0.004
|
||||||
## Used by tests and vertical routing checks (feet vs goal surface).
|
## Used by tests and vertical routing checks (feet vs goal surface).
|
||||||
|
|
@ -403,10 +403,16 @@ static func idle_support_is_stable(
|
||||||
return false
|
return false
|
||||||
if floor_normal.dot(Vector3.UP) < STABLE_IDLE_FLOOR_MIN_UP_DOT:
|
if floor_normal.dot(Vector3.UP) < STABLE_IDLE_FLOOR_MIN_UP_DOT:
|
||||||
return false
|
return false
|
||||||
# Previously any `_floor_angle_loose_ticks > 0` blocked stable idle for the full post-stop
|
# Post-stop **ridged** slides (tread + riser) used to force unstable idle for the entire
|
||||||
# window (~0.8 s), forcing rim nudges + `move_and_slide` on **flat** approach treads — XZ/Y
|
# `loose_ticks` window even when the **floor** is level — e.g. idle on a flat approach tread
|
||||||
# jitter next to thin steps. Keep corrective settle only when slides show floor + wall (lip).
|
# hugging a vertical face stayed in corrective `move_and_slide` ~0.8 s (HUD X/Y/Z noise).
|
||||||
if loose_ticks > 0 and idle_slide_contacts_are_ridged(slide_normals):
|
# 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 false
|
||||||
return true
|
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()
|
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 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)
|
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()
|
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)
|
## 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.
|
- **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.
|
- **Not in this pass:** **`PrototypeTerminal`**, **MoveReject** props, **runtime random bumps** — add geometry when those need climbable access.
|
||||||
|
|
||||||
## Open questions / risks
|
## Open questions / risks
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue