# Camera and Xbox controller controls

## Model Studio mouse and keyboard

The **Camera** panel selects what a left-mouse drag does: **Orbit model**, **Pan camera**, or **Free look**. Orbit moves around the current target. Pan translates the camera and its target together. Free look changes the target and pointing direction while keeping the camera position fixed. Switching modes does not jump the view.

- Shift + left drag, right drag, or middle drag: pan regardless of selected mode.
- Alt + left drag: free look regardless of selected mode.
- Scroll: zoom/dolly along the viewing direction.
- Click the 3D viewport, then use W/S forward/back, A/D left/right, Q/E down/up, and arrow keys to look.
- F: frame the selected model and return to orbit mode. C: toggle orbit/free look.
- Space: pause/resume animation. Camera navigation remains available while paused.

Camera movement is independent of the model's Rotation and Scale controls. Scaling a model now changes its visible size without automatically moving the camera; use Frame selected afterward if needed.

## Connect and activate

Use the Xbox controller already paired with the computer. Open the workshop through localhost or HTTPS, focus the browser tab, and press/release a controller button so the browser exposes the device. The **Xbox Controller** panel reports detection and readiness. Click **Focus 3D view for control** when returning from a form field. Release sticks, triggers and buttons after reconnecting or changing focus; neutral controls re-arm input.

The implementation uses the browser's [W3C standard gamepad layout](https://www.w3.org/TR/gamepad/). It does not pair Bluetooth devices or assume raw axis/button indices for unrecognized layouts. The Xbox system/Guide button remains managed by the browser/operating system. **Controller controls & settings** lists all assigned controls, live input values, sensitivity and deadzone settings. The default radial stick deadzone is 15%; a small trigger deadzone also removes noise. Settings persist between scenes for the current page session.

| Input | Model Studio | Flight |
| --- | --- | --- |
| Left stick | Pan camera | Bank; pull back to raise nose |
| Right stick | Orbit, or free look in Free look mode | Look around pilot/chase view |
| LT / RT | Zoom out / in | Decrease / increase throttle |
| LB / RB | Rotate selected model | Left / right rudder |
| D-pad left / right | Select previous / next existing model | Toggle navigation / landing lights |
| D-pad up / down | Increase / decrease model scale | Retract / extend flaps |
| A / B | First / second model action, when available | Switch view / center look |
| X | Frame selected | Toggle gear |
| Y | Toggle orbit/free look | Toggle guided/manual |
| Hold left-stick click | Precision camera/model movement | Click levels wings |
| Right-stick click | Frame selected | Center look |
| Menu | Pause / resume | Pause / resume |
| View | Show / hide controller guide | Show / hide controller guide |

In flight, moving flight axes or throttle takes manual control from the guided tour. Right-stick looking alone does not. **Reverse flight pitch direction** is available in controller settings. During landscape inspection, the sticks pan/orbit and triggers zoom; A returns to flight. See FLIGHT_CONTROLLER.md for exact flight integration.

Model loading and arbitrary model-specific property editing remain in the library/inspector panel. Controller A/B invoke actions supplied by the selected package; no action is invented for a package that exposes none. Camera/model transforms remain available for every compatible model.

## Reuse in another application

`OrbitControls` remains in the engine with default keyboard input disabled. Enable `{keyboard:true}` for a focused viewport and call `tick(realDt)` from the consumer's input loop. Public methods are `setMode(mode)`, `orbitBy(yawRadians, pitchRadians)`, `lookBy(yawRadians, pitchRadians)`, `panBy(right, up, forward=0)` in world units relative to the camera, and `zoomBy(logAmount)` (positive out). Positive look yaw turns left; positive look pitch aims upward. Movement methods return whether they changed the view and invoke `onChange`. Disposal clears keys, captures and listeners.

`GamepadInput` is a separate reusable engine module. It supplies neutral, deadzone-filtered `left`, `right`, `lt`, `rt`, `down`, and rising-edge `pressed` values. `poll({active})` returns connection/mapping/arming metadata plus input state. Unsupported mappings and inactive input return neutral commands. `select(index)` supports explicit device selection in other consumers; the workshop automatically retains its current controller or chooses a standard-mapped one. Injecting a provider is supported for deterministic tests.

The shell owns the mappings and a lightweight input animation loop separate from simulation time. This permits Menu to resume a paused renderer and permits paused camera movement. Blur, hidden-page and disconnect events clear stored commands immediately, even when animation-frame polling is suspended. The next active input requires a neutral pose. Editing input/select fields suspends controller actions. Aircraft simulation time and commands remain paused during camera inspection.

## Verification scope

`tests/gamepad-input.test.mjs`, `camera-controls.test.mjs` and `flight-controls.test.mjs` cover 22 focused cases. `tests/controller.html` exercises 13 integration checks with actual rendered Studio/Flight scenes and synthetic controller snapshots, including the UI polling loop while paused. These are not physical wireless-controller tests. See VERIFICATION.md and verification/controller-v022.json for the final device status and packaged scope.
