From c6091cb31c4606f5297018ed58cd1442b6bfa623 Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Thu, 30 Apr 2026 00:33:13 +0200 Subject: [PATCH] Enhance climbing mechanics with detailed input and surface detection --- .../first-person-navigation-controller.ts | 38 +++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/src/runtime-three/first-person-navigation-controller.ts b/src/runtime-three/first-person-navigation-controller.ts index 39459cae..781a98e6 100644 --- a/src/runtime-three/first-person-navigation-controller.ts +++ b/src/runtime-three/first-person-navigation-controller.ts @@ -15,6 +15,8 @@ import { CLIMB_INPUT_ACTIVE_THRESHOLD, CLIMB_SPEED_METERS_PER_SECOND, computeClimbPlaneMovement, + isClimbMovementIntoSurface, + resolveClimbPlanarInputDirection, shouldEnterClimbing, shouldExitClimbing, type RuntimePlayerClimbSurface @@ -425,7 +427,10 @@ export class FirstPersonNavigationController implements NavigationController { this.publishTelemetry(); } - private resolveClimbProbeDirection(movementYawRadians: number): Vec3 { + private resolveClimbProbeDirection( + movementYawRadians: number, + inputState: ReturnType + ): Vec3 { if (this.climbSurface !== null) { return { x: -this.climbSurface.normal.x, @@ -434,6 +439,15 @@ export class FirstPersonNavigationController implements NavigationController { }; } + const inputDirection = resolveClimbPlanarInputDirection( + inputState, + movementYawRadians + ); + + if (inputDirection.direction !== null) { + return inputDirection.direction; + } + return { x: Math.sin(movementYawRadians), y: 0, @@ -491,22 +505,27 @@ export class FirstPersonNavigationController implements NavigationController { const climbPressed = inputState.climb > CLIMB_INPUT_ACTIVE_THRESHOLD; const jumpPressed = inputState.jump > CLIMB_INPUT_ACTIVE_THRESHOLD; - if (!climbPressed) { - this.climbLatchBlocked = false; - } - const climbSurface = this.context.resolvePlayerClimbSurface?.( this.feetPosition, - this.resolveClimbProbeDirection(movementYawRadians), + this.resolveClimbProbeDirection(movementYawRadians, inputState), this.standingPlayerShape, this.climbSurface ) ?? null; + const movementIntoSurface = isClimbMovementIntoSurface({ + input: inputState, + movementYawRadians, + surface: climbSurface + }); + const climbIntentActive = climbPressed || movementIntoSurface; + + if (!climbIntentActive) { + this.climbLatchBlocked = false; + } if ( this.climbSurface !== null && shouldExitClimbing({ - climbInput: inputState.climb, surface: climbSurface, jumpPressed }) @@ -572,10 +591,6 @@ export class FirstPersonNavigationController implements NavigationController { return true; } - if (climbSurface === null && climbPressed) { - this.climbLatchBlocked = true; - } - return false; } @@ -584,6 +599,7 @@ export class FirstPersonNavigationController implements NavigationController { (this.climbLatchBlocked || !shouldEnterClimbing({ climbInput: inputState.climb, + movementIntoSurface, surface: climbSurface, jumpPressed }))