Skip to content

Fold the focus camera-tween into watchFocusTween#358

Merged
rulkens merged 1 commit into
mainfrom
refactor/focus-tween-saga
Jun 20, 2026
Merged

Fold the focus camera-tween into watchFocusTween#358
rulkens merged 1 commit into
mainfrom
refactor/focus-tween-saga

Conversation

@rulkens

@rulkens rulkens commented Jun 20, 2026

Copy link
Copy Markdown
Owner

What

The focus camera-tween was computed through an engine-injected chain:

updateSelectionFocus(ref)
  -> watchFocusTween -> runFocusTween (injected) -> makeRunFocusTween
     -> FocusTweenTable -> tweenToGalaxy / tweenToStructure / tweenToCameraSnapshot
        -> dispatch(startCameraTween) + scheduler.requestRender()

This folds all of that into the saga itself:

updateSelectionFocus(ref)
  -> watchFocusTween
     -> extractSelectionRow(ref, resolveDeps())          // ref -> row
     -> cameraRuntime()                                   // live from-pose + lens FOV (SagaContext)
     -> focusTweenDescriptor(row, from, fovY)             // pure galaxy/structure/milkyWay table
     -> put(startCameraTween(desc))

Why

  • One home for the focus effect. The tween is the effect of the focus Intent; it now lives entirely in watchFocusTween as a thin resolve→build→dispatch shell, instead of being threaded through an engine-injected runner + table.
  • milkyWay is no longer special. It was the odd arm going through tweenToCameraSnapshot; it's now a peer case in the pure focusTweenDescriptor table, emitting the same startCameraTween as galaxy/structure (tagged-union table dispatch, not a per-type handler set).
  • No redundant render-wake. startCameraTween is a camera/* write, so watchWake/WAKE_ROUTES wakes the loop by construction (and the focus-ref write also wakes via watchSelectionWake). The explicit scheduler.requestRender() calls are gone.
  • Engine/store seam, not a mirror. The genuinely-live state the saga needs (the visible from-pose + projection FOV) is read via a new cameraRuntime() reader on SagaContext — the same well-worn injection as resolveDeps / reconcile / runTierTransition. The mutable cameraRuntime Resources stay out of the timeless camera slice by design.

Deletes

makeRunFocusTween, tweenToGalaxy, tweenToStructure, the FocusTweenTable wiring in engine.ts, the orphaned TweenTarget type, and runFocusTween from SagaContext. tweenToCameraSnapshot stays for the home button (minus its redundant requestRender).

Net −433 lines.

Tests

  • New focusTweenDescriptor unit tests (per-arm target/distance, yaw/pitch preserved, fresh-array copy).
  • Rewritten watchFocusTween saga test (dispatches startCameraTween with the built descriptor; no-ops on non-focus write / cam-not-ready / null ref).
  • SagaContext contract test updated runFocusTween -> cameraRuntime.
  • npm run typecheck clean; full suite 2977 passing.

Follow-up (separate PR, by request): relocate the pure camera math (poseOf, evaluateTween, spinAutoRotate, *FocusDistance, …) into src/utils/camera/.

🤖 Generated with Claude Code

The focus camera-tween was computed through an engine-injected chain:
watchFocusTween -> runFocusTween -> makeRunFocusTween -> FocusTweenTable ->
tweenToGalaxy/tweenToStructure/tweenToCameraSnapshot, each dispatching
startCameraTween plus an explicit requestRender.

Collapse that into the saga itself. watchFocusTween now resolves the ref to
a row (resolveDeps), reads the live from-pose + lens FOV via a new
cameraRuntime() SagaContext reader, builds the startCameraTween payload with
a pure focusTweenDescriptor table (galaxy/structure/milkyWay arms), and
dispatches it. The milkyWay arm becomes a peer case in that one table instead
of a special tweenToCameraSnapshot snapshot.

The explicit requestRender calls are gone: startCameraTween is a camera/*
write, so watchWake/WAKE_ROUTES wakes the loop by construction (and the focus
ref write also wakes via watchSelectionWake).

Deletes makeRunFocusTween, tweenToGalaxy, tweenToStructure, the FocusTweenTable
wiring, the orphaned TweenTarget type, and runFocusTween from SagaContext.
tweenToCameraSnapshot stays for the home button, minus its redundant
requestRender.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
skymap 9928788 Commit Preview URL

Branch Preview URL
Jun 20 2026, 04:39 PM

@rulkens rulkens merged commit d4c48c2 into main Jun 20, 2026
2 checks passed
rulkens added a commit that referenced this pull request Jun 20, 2026
…spike

Brings in the camera-pose-as-derived-state refactor (#357) + the
focus-tween fold into watchFocusTween (#358). The CameraDriver contract
changed from a mutating apply(cam, nowMs) to a pure pose(s, cam,
elapsedMs) that RETURNS a CameraPose; assembleOrbitCamera derives
position from it.

Migrated the four throwaway spike drivers (webshow / flowshow / flyout /
floworbit) to the new contract:

- apply(cam) mutation -> pose() returning a fresh CameraPose; no more
  updatePosition (the resolver derives position via assembleOrbitCamera).
- Self-clock via performance.now(): the driver-table elapsed clock
  (elapsedForWinner) only serves the 'tween'/'autoRotate' ids, so any
  other driver id receives elapsedMs === 0.
- Self-sustain the loop: shouldKeepTicking reads camera liveness off the
  store (drag/tween/autoRotate), so a spike driver gating on local phase
  is invisible to it; each driver now pokes requestRender() per frame or
  the take freezes after one tick.
- priority 80 -> 90: orbitDrag now occupies 80, so the takes move above
  the store movers to keep owning the camera.
- Drop the fovYRad set (CameraPose carries no FOV); framing reads the
  live projection FOV off the forwarded cam.

Co-Authored-By: Claude Opus 4.8 (1M context) <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