Skip to content

let embedded replay viewers opt into native camera input - #104

Open
JBoggsy wants to merge 1 commit into
replay-controlfrom
replay-control-native-camera
Open

JBoggsy wants to merge 1 commit into
replay-controlfrom
replay-control-native-camera

Conversation

@JBoggsy

@JBoggsy JBoggsy commented Oct 11, 2026

Copy link
Copy Markdown

Let embedded replay viewers opt into native camera input

Depends on #102 (stacked on replay-control).

What and why

With embed=1 the viewer blocks all mouse, wheel and touch input so the host page can draw over it. Coaching hosts also want a "camera tool" mode where the game's own camera takes the pointer, including rotation, without leaving embed mode. This adds an opt-in switch for that.

  • replay.html: a nativeCamera=1 URL option, polyworldReplay.setNativeCamera(on) and a nativeCamera postMessage command. While on, the capture-phase blocker lets pointer events through; key presses are still kept from the game and forwarded to the host. state().nativeCamera reports the current value. It only works with embed=1; non-embedded viewers already take input.
  • GotA: embedded input goes through a new updateEmbeddedCamera: drag pans (the ground follows the pointer), right-drag orbits (sideways turns, up/down tilts), wheel or pinch zooms. It never selects units. A drag starts only on a press seen while input is enabled, so toggling the switch mid-drag can't leave a drag stuck.
  • GotA had no camera rotation at all — its RTS camera is fixed-north. So this PR adds orbit angles: rtsCameraEye takes optional yaw/pitch (defaults unchanged), rtsDragPan pans relative to an orbited camera, and the GotA spectator pan uses it (identical at yaw 0). The director always resets to north.
  • camera({mode: 'fixed', ...}) takes optional yaw and pitch in radians (NaN/omitted keeps the current angle), so a host can restore the default view with yaw: 0, pitch: 0.92. This changes the polyworld_replay_camera export signature (two extra floats); replay.html is the only caller.
  • Embedded wheel zoom uses exp(-scroll * 0.002) instead of the native 0.92^(scroll/3): Chrome on macOS reports about 100 scroll steps per wheel notch, which with the native curve jumps straight to the closest zoom in one event.

What to look at

  • examples/gods_of_the_arena/graphics.nim: the follow-selection tail of updateCamera moved unchanged into followSelectedCamera, so the embedded branch can return early and still follow. The embedded flag moved up so the camera closures can see it.
  • Orbit gesture choice (right-drag) and the pitch range 0.35..1.45 rad in rtscameras.nim.
  • A pan stops following a unit; an orbit or zoom keeps following it.

Tests

macOS arm64, Nim 2.2.10, Emscripten 6.0.12.

  • nim r tests/test_rtscameras.nim (new orbit/pan cases), tests/test_replaycontrols.nim (camera angles, NaN keeps), tests/test_player.nim: pass.
  • nim r tests/tests.nim: all tests passed; tests/test_directors.nim: pass (run on this branch merged with the two sibling PRs).
  • nim check GotA desktop, -d:headless, -d:coworld: pass.
  • test_browser_with_playwright.py --control-only --gpu: all checks pass, including the new native camera check (real mouse: blocked wheel does nothing; with the switch on, left-drag pans, right-drag turns the view matrix, wheel zooms, Space stays with the host, yaw: 0, pitch: 0.92 restores the north matrix exactly, and turning it off over postMessage blocks input again). Also passes on SwiftShader.
  • Used downstream in PlayerADE's coaching Camera tool: an overlay drawing stays aligned to within 2e-5 px after a native rotation (headed Chromium, Metal).

Docs: docs/replay-control.md (URL option, gesture table, setNativeCamera, camera angles).

🤖 Generated with Claude Code

Coaching hosts want the game's own camera gestures while a camera tool is
active, but embed=1 blocks all pointer input. Add an opt-in switch that lets
pointer input through to the camera only, with orbit, while keys stay with
the host.

- replay.html: nativeCamera=1 URL option, setNativeCamera(on) and the
  nativeCamera postMessage command; state() reports nativeCamera. The
  capture-phase blocker skips pointer events while it is on.
- replaycontrols.nim: replayNativeCamera(); fixed camera requests take
  optional yaw and pitch (NaN keeps the current angle).
- rtscameras.nim: rtsCameraEye takes yaw/pitch (defaults unchanged) and
  rtsDragPan pans relative to an orbited camera.
- GotA: embedded input goes through updateEmbeddedCamera (drag pans,
  right-drag orbits, wheel/pinch zooms, never selects); drags start only on
  a press seen while enabled. The director resets the orbit to north.
  Selection following moved into followSelectedCamera unchanged.
- Tests: rtscameras orbit/pan cases, replay control camera angles, browser
  native camera check (pan, orbit, zoom, keys still forwarded, restore
  north, off again).
- Docs: docs/replay-control.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant