diff --git a/src/runtime-three/runtime-host.ts b/src/runtime-three/runtime-host.ts index 6669e672..2655de03 100644 --- a/src/runtime-three/runtime-host.ts +++ b/src/runtime-three/runtime-host.ts @@ -329,7 +329,9 @@ const DIALOGUE_PARTICIPANT_RESTORE_EPSILON_DEGREES = 0.5; const TARGETING_LUX_FOLLOW_RATE = 8; const TARGETING_LUX_BOB_RATE = 4.2; const TARGETING_LUX_PULSE_RATE = 6.5; -const PROPOSED_TARGET_CAMERA_ASSIST_STRENGTH = 0.28; +// Proposed-target camera nudging is intentionally disabled for now. Lux alone +// should communicate proposal without moving the gameplay camera. +// const PROPOSED_TARGET_CAMERA_ASSIST_STRENGTH = 0.28; const ACTIVE_TARGET_CAMERA_ASSIST_STRENGTH = 0.55; export function resolveRuntimeTargetVisualPlacement(target: { @@ -5576,12 +5578,14 @@ export class RuntimeHost { }; } - if (this.proposedRuntimeTarget !== null) { - return { - targetPosition: this.proposedRuntimeTarget.center, - strength: PROPOSED_TARGET_CAMERA_ASSIST_STRENGTH - }; - } + // Keep this branch commented instead of deleted; proposed-target nudging may + // come back later after the Lux readability pass has settled. + // if (this.proposedRuntimeTarget !== null) { + // return { + // targetPosition: this.proposedRuntimeTarget.center, + // strength: PROPOSED_TARGET_CAMERA_ASSIST_STRENGTH + // }; + // } return null; } @@ -5739,6 +5743,7 @@ export class RuntimeHost { if (event.code === "Escape" && this.activeRuntimeTargetReference !== null) { event.preventDefault(); + event.stopImmediatePropagation(); this.clearActiveRuntimeTarget(); return; } diff --git a/tests/unit/runtime-host.test.ts b/tests/unit/runtime-host.test.ts index 748e8fcc..b0550f0e 100644 --- a/tests/unit/runtime-host.test.ts +++ b/tests/unit/runtime-host.test.ts @@ -3129,6 +3129,74 @@ describe("RuntimeHost", () => { expect(placement.activeMarkerScale).toBeGreaterThan(1); }); + it("uses Lux-only proposal feedback and consumes Escape when clearing an active target", () => { + const host = new RuntimeHost({ + enableRendering: false + }); + const hostInternals = host as unknown as { + runtimeScene: unknown; + sceneReady: boolean; + activeController: unknown; + thirdPersonController: unknown; + proposedRuntimeTarget: { + kind: "npc" | "interactable"; + entityId: string; + center: { x: number; y: number; z: number }; + } | null; + activeRuntimeTargetReference: { + kind: "npc" | "interactable"; + entityId: string; + } | null; + resolveThirdPersonTargetAssist(): unknown; + handleRuntimeKeyDown(event: KeyboardEvent): void; + }; + const escapeEvent = { + code: "Escape", + defaultPrevented: false, + repeat: false, + altKey: false, + ctrlKey: false, + metaKey: false, + target: null, + preventDefault: vi.fn(), + stopImmediatePropagation: vi.fn() + } as unknown as KeyboardEvent; + + hostInternals.runtimeScene = { + playerInputBindings: { + keyboard: { + pauseTime: "KeyP" + } + }, + entities: { + cameraRigs: [], + interactables: [], + npcs: [] + }, + interactionLinks: [] + } as never; + hostInternals.sceneReady = true; + hostInternals.activeController = hostInternals.thirdPersonController; + hostInternals.proposedRuntimeTarget = { + kind: "npc", + entityId: "npc-proposed", + center: { x: 0, y: 1, z: 3 } + }; + + expect(hostInternals.resolveThirdPersonTargetAssist()).toBeNull(); + + hostInternals.activeRuntimeTargetReference = { + kind: "npc", + entityId: "npc-active" + }; + hostInternals.handleRuntimeKeyDown(escapeEvent); + + expect(hostInternals.activeRuntimeTargetReference).toBeNull(); + expect(escapeEvent.preventDefault).toHaveBeenCalledTimes(1); + expect(escapeEvent.stopImmediatePropagation).toHaveBeenCalledTimes(1); + host.dispose(); + }); + it("clears runtime targeting when switching into first-person mode", () => { const host = new RuntimeHost({ enableRendering: false