Skip to content

fix(mcp): frame_shot aims the live camera it places (#86) - #91

Merged
HaD0Yun merged 3 commits into
mainfrom
fix/issue-86
Sep 3, 2026
Merged

fix(mcp): frame_shot aims the live camera it places (#86)#91
HaD0Yun merged 3 commits into
mainfrom
fix/issue-86

Conversation

@HaD0Yun

@HaD0Yun HaD0Yun commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Closes #86.

frame_shot sent position and lens only; a connected editor kept whatever orientation the last gesture left, so every view except front lost the subject while describe_shot still reported "98% of frame height".

Fix

  • Live protocol: set_camera gains an optional, additive lookAtX/lookAtY/lookAtZ triple (complete or absent; documented in mcp/LIVE-PROTOCOL.md, no version bump).
  • frame_shot always sends the framing pivot the shot vocabulary is measured against (FRAMING_PIVOT_Y named). The set_camera MCP tool accepts optional look_at_x/y/z and forwards them only when all three are given.
  • Editor set_camera handler: when aimed, writes yaw/pitch into look.current (the orientation of record) and the camera rotation, so commitManualCameraFraming measures the framing it just applied.

Tests (RED on main, GREEN here)

  • mcp/verify-live.mjs: frame_shot must forward a finite lookAt* equal to the subject pivot (node tier, gates npm test).
  • mcp/verify-live-editor-model.mjs (CI mcp-live gating): after frame_shot profile, the shot camera forward vector points at the pivot within 2 deg (was 86.4 deg off).
  • mcp/verify-live-capture.mjs: all five views report visiblePixelCount > 0 and behindCameraPlane === false (before: 0 px on four views, behind the plane on three).
  • cd mcp && npm run verify: 420 framing combinations still pass.

Thanks to @popixoxipop-collab for the precise report and reference patch.

frame_shot sent position and lens only, so a connected editor kept whatever
orientation the last human gesture left behind. Only `front` still framed the
subject — the other four views orbited the lens away and dropped the subject
out of frame, behind the camera plane from profile round to back, while
describe_shot kept reporting "the subject fills 98% of frame height" because
the shot vocabulary is derived from distance and lens on the assumption that
the lens points at the framing pivot.

set_camera now carries an optional lookAtX/lookAtY/lookAtZ triple (additive to
live protocol v1; an editor that ignores it degrades to the old
position-only behaviour), frame_shot always sends the framing pivot the
vocabulary is measured against, and the editor writes the resulting yaw/pitch
into look.current as well as the camera — that ref is the orientation of
record, so commitManualCameraFraming now measures the framing it just applied
instead of the one the last gesture left.

The regression is asserted twice: verify-live-editor-model gates in CI without
pixels (the shot camera's forward vector must point at the pivot within 2deg
after a profile frame_shot), and verify-live-capture renders all five views and
requires visible pixels with the subject in front of the lens.
Keeps the before/after 5-view visibility measurements next to the fix: the
regression suites assert the invariant, this records what a real editor on
hardware GL actually rendered before and after, so the numbers can be
re-derived without re-running the browser suites.
…remount (#86)

A cast model whose FBX is still downloading suspends the R3F scene graph: the
shot camera is unmounted, and when the mesh lands React remounts every sibling
rig on a fresh camera object. Two things went wrong across that gap. The live
set_camera handler wrote to shotCamRef.current, which was null, so the position
and aim were dropped; and ShotRig's preset effect ran again on mount and seeded
the medium preset over whatever was there. On a slow runner (CI, cold cache)
frame_shot therefore landed in the gap and the editor came back on the default
camera, 57deg off the subject it had just been told to frame.

The preset is now applied once per (preset, nonce) using a stamp held in App,
and the shot camera's last position is kept in an App-level ref that the live
handler writes even when the camera is unmounted; a remount restores that
position and look.current instead of re-seeding. verify-live-editor-model holds
the x-bot FBX at the network layer, frames the shot while the canvas is
suspended, releases it and only then reads the camera, so the runner's timing
is the test's timing.
@HaD0Yun
HaD0Yun merged commit 4c4e496 into main Sep 3, 2026
6 checks passed
@popixoxipop-collab

Copy link
Copy Markdown

Read through this — nice work, and thanks for picking it up. You actually took it further than my patch: exposing look_at_x/y/z on set_camera directly (not just inside frame_shot), and the CI-gating test with the real angle measurement (86.4°) is a nice touch. Cheering you on!

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.

frame_shot places the shot camera but never aims it — every view except front loses the subject

2 participants