From 8d9ad0ecd3ec768ccb1c1d478c9cd6a42811dcb8 Mon Sep 17 00:00:00 2001 From: VinPropane Date: Sun, 12 Apr 2026 17:06:30 -0400 Subject: [PATCH] NEON-29: Stable idle latch + Y anchor for approach treads Hold latched zero-vel idle through STABLE_IDLE_UNLATCH_TICKS consecutive unstable readings so thin tread lips do not drop into corrective move_and_slide every other frame. Idle anchor now freezes Y as well as XZ. Reset latch on goal/snap/arrival; seed latch on arrival when support is stable after idle tick. Update hold_idle_anchor GdUnit expectation. --- client/scripts/player.gd | 31 +++++++++++++++++++++++++++++-- client/test/player_test.gd | 2 +- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/client/scripts/player.gd b/client/scripts/player.gd index baac815..1cbd688 100644 --- a/client/scripts/player.gd +++ b/client/scripts/player.gd @@ -32,6 +32,8 @@ const IDLE_RIM_MIN_FLOOR_UP_DOT: float = 0.968 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 ## 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). @@ -77,6 +79,10 @@ var _step_assist_cooldown: int = 0 var _step_assist_active: bool = false var _idle_anchor_active: bool = false var _idle_anchor_xz: Vector2 = Vector2.ZERO +var _idle_anchor_y: float = 0.0 +## Latched stable idle: stay on zero-vel + anchor through brief Jolt false negatives on treads. +var _idle_stable_latched: bool = false +var _idle_stable_unlatch_streak: int = 0 var _idle_manual_correction_ticks: int = 0 var _debug_last_idle_xz: Vector2 = Vector2.INF var _debug_idle_heartbeat: int = 0 @@ -103,6 +109,8 @@ func set_authoritative_nav_goal(world_pos: Vector3) -> void: _step_assist_active = false _idle_anchor_active = false _idle_manual_correction_ticks = 0 + _idle_stable_latched = false + _idle_stable_unlatch_streak = 0 _walk_nav_column_steering = false _nav_agent.set_target_position(world_pos) @@ -114,6 +122,8 @@ func clear_nav_goal() -> void: _step_assist_active = false _idle_anchor_active = false _idle_manual_correction_ticks = 0 + _idle_stable_latched = false + _idle_stable_unlatch_streak = 0 _walk_nav_column_steering = false _nav_agent.set_target_position(global_position) @@ -151,6 +161,8 @@ func snap_to_server(world_pos: Vector3) -> void: _step_assist_active = false _idle_anchor_active = false _idle_manual_correction_ticks = 0 + _idle_stable_latched = false + _idle_stable_unlatch_streak = 0 _walk_nav_column_steering = false _nav_agent.set_target_position(settled) reset_physics_interpolation() @@ -416,9 +428,10 @@ func _hold_idle_anchor() -> void: var pos := global_position if not _idle_anchor_active: _idle_anchor_xz = Vector2(pos.x, pos.z) + _idle_anchor_y = pos.y _idle_anchor_active = true return - global_position = Vector3(_idle_anchor_xz.x, pos.y, _idle_anchor_xz.y) + global_position = Vector3(_idle_anchor_xz.x, _idle_anchor_y, _idle_anchor_xz.y) func _debug_trace_idle_state(tag: String) -> void: @@ -624,7 +637,17 @@ func _physics_process(delta: float) -> void: ) if not _has_walk_goal: floor_block_on_wall = true - if _stable_idle_support(): + var raw_stable: bool = _stable_idle_support() + if raw_stable: + _idle_stable_unlatch_streak = 0 + _idle_stable_latched = true + elif _idle_stable_latched: + _idle_stable_unlatch_streak += 1 + if _idle_stable_unlatch_streak >= STABLE_IDLE_UNLATCH_TICKS: + _idle_stable_latched = false + _idle_stable_unlatch_streak = 0 + _idle_anchor_active = false + if _idle_stable_latched: _idle_manual_correction_ticks = 0 velocity = Vector3.ZERO floor_snap_length = FLOOR_SNAP_IDLE @@ -685,10 +708,14 @@ func _physics_process(delta: float) -> void: _step_assist_active = false _idle_anchor_active = false _idle_manual_correction_ticks = 0 + _idle_stable_latched = false + _idle_stable_unlatch_streak = 0 _walk_nav_column_steering = false _nav_agent.set_target_position(global_position) _physics_idle_tick(delta) if _stable_idle_support(): + _idle_stable_latched = true + _idle_stable_unlatch_streak = 0 _idle_manual_correction_ticks = 0 velocity = Vector3.ZERO floor_snap_length = FLOOR_SNAP_IDLE diff --git a/client/test/player_test.gd b/client/test/player_test.gd index 724a4a2..2cb68d7 100644 --- a/client/test/player_test.gd +++ b/client/test/player_test.gd @@ -103,7 +103,7 @@ func test_hold_idle_anchor_sets_anchor_first_then_clamps_xz() -> void: p.global_position = Vector3(1.2, 0.7, 2.3) p.call("_hold_idle_anchor") - assert_that(p.global_position).is_equal(Vector3(1.0, 0.7, 2.0)) + assert_that(p.global_position).is_equal(Vector3(1.0, 0.5, 2.0)) func test_snap_to_server_clears_idle_anchor() -> void: