From 7d960223b97bb235cea3ff0edb809a992258a48c Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Thu, 30 Apr 2026 00:34:02 +0200 Subject: [PATCH] Add unit tests for climbing detection and gamepad camera look in FirstPersonNavigationController --- ...first-person-navigation-controller.test.ts | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tests/unit/first-person-navigation-controller.test.ts b/tests/unit/first-person-navigation-controller.test.ts index 4e1f3b75..6598417c 100644 --- a/tests/unit/first-person-navigation-controller.test.ts +++ b/tests/unit/first-person-navigation-controller.test.ts @@ -309,6 +309,54 @@ describe("FirstPersonNavigationController", () => { }); }); + it("enters climbing when first-person movement runs into a climbable face", () => { + const { context } = createRuntimeControllerContext( + createPlayerStartEntity({ + id: "entity-player-start-first-person-climb" + }), + (feetPosition, motion) => ({ + feetPosition: { + x: feetPosition.x + motion.x, + y: feetPosition.y + motion.y, + z: feetPosition.z + motion.z + }, + grounded: false, + collisionCount: 0, + groundCollisionNormal: null, + collidedAxes: { + x: false, + y: false, + z: false + } + }) + ); + Object.assign(context, { + resolvePlayerClimbSurface: vi.fn(() => ({ + brushId: "brush-climbable-wall", + faceId: "negZ", + point: { x: 0, y: 1, z: 0.75 }, + normal: { x: 0, y: 0, z: -1 }, + distance: 0.75 + })) + }); + const controller = new FirstPersonNavigationController(); + + controller.activate(context); + window.dispatchEvent(new KeyboardEvent("keydown", { code: "KeyW" })); + controller.update(1); + + const telemetry = + context.setPlayerControllerTelemetry.mock.calls.at(-1)?.[0]; + + expect(telemetry?.locomotionState.locomotionMode).toBe("climbing"); + expect(telemetry?.feetPosition.y).toBeCloseTo(2.4); + + window.dispatchEvent(new KeyboardEvent("keyup", { code: "KeyW" })); + controller.deactivate(context, { + releasePointerLock: false + }); + }); + it("uses the gamepad right stick for camera look without requiring pointer lock", () => { const { context } = createRuntimeControllerContext(); const controller = new FirstPersonNavigationController();