diff --git a/src/runtime-three/runtime-host.ts b/src/runtime-three/runtime-host.ts index 066142a8..54ed3d68 100644 --- a/src/runtime-three/runtime-host.ts +++ b/src/runtime-three/runtime-host.ts @@ -5733,7 +5733,7 @@ export class RuntimeHost { let bestCandidate: RuntimeTargetCandidate | null = null; let bestAlignment = TARGETING_SCREEN_SWITCH_MIN_ALIGNMENT; let bestScreenDistance = 0; - const inputX = input.horizontal / inputLength; + const inputX = -input.horizontal / inputLength; const inputY = input.vertical / inputLength; for (const candidate of this.runtimeTargetCandidates) { diff --git a/tests/unit/third-person-navigation-controller.test.ts b/tests/unit/third-person-navigation-controller.test.ts index f1913f38..ed9248c3 100644 --- a/tests/unit/third-person-navigation-controller.test.ts +++ b/tests/unit/third-person-navigation-controller.test.ts @@ -167,6 +167,52 @@ describe("ThirdPersonNavigationController", () => { controller.deactivate(context); }); + it("uses lock-on look input as a temporary offset that returns to center", () => { + const { context } = createRuntimeControllerContext(); + const controller = new ThirdPersonNavigationController(); + let axes = [0, 0, 1, 0]; + const getGamepads = vi.fn<() => Gamepad[]>(() => [ + createMockGamepad({ + axes + }) + ]); + const handleRuntimeTargetLookInput = vi.fn(() => ({ + activeTargetLocked: true, + switchedTarget: false, + switchInputHeld: false + })); + const targetContext = { + ...context, + handleRuntimeTargetLookInput + }; + + Object.defineProperty(navigator, "getGamepads", { + configurable: true, + value: getGamepads + }); + + controller.activate(targetContext); + + const initialCameraX = targetContext.camera.position.x; + controller.update(0.1); + const offsetCameraX = targetContext.camera.position.x; + + axes = [0, 0, 0, 0]; + controller.update(0.5); + const returnedCameraX = targetContext.camera.position.x; + + expect(offsetCameraX).not.toBeCloseTo(initialCameraX); + expect(Math.abs(returnedCameraX - initialCameraX)).toBeLessThan( + Math.abs(offsetCameraX - initialCameraX) + ); + expect(handleRuntimeTargetLookInput).toHaveBeenCalledWith({ + horizontal: expect.any(Number), + vertical: 0 + }); + + controller.deactivate(targetContext); + }); + it("uses the authored movement template speed for third-person motion telemetry", () => { const playerStart = createPlayerStartEntity({