Camera-driver authority: precedence as data, one camera-write site#286
Open
rulkens wants to merge 7 commits into
Open
Camera-driver authority: precedence as data, one camera-write site#286rulkens wants to merge 7 commits into
rulkens wants to merge 7 commits into
Conversation
Adds the design spec for the two behaviour-preserving refactors that land before the guided tour: a camera-driver authority (single per-frame write-site, precedence as data) and a settings snapshot/restore seam (geography-aware readVisibility + applyVisibility with an animate flag). Both came out of an entanglement-radar pass over the camera path and engine.ts and are new findings, not in the documented backlog. Also rewrites the splash Part-2 plan from a tween-delegation stub into a cinematic-core engine seed whose camera core is the shape the full cinematic tour (docs/tour/) extends additively, and adds a dependency note pointing at the decomplection spec. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Camera-driver authority: CameraDriver type + pure runCameraDrivers
resolver + wrapper drivers on RunFrameDeps, replacing the implicit
call-order camera-mutation block and folding the RoD camera terms into
the registry. Behaviour-preserving with regression tests pinning
tween-wins-over-autoRotate and idle-holds.
Settings visibility seam: VisibilitySnapshot plain-data type +
readVisibility + applyVisibility({animate}) single write-path, with the
existing clean setters delegated through it and the async
setSourceVisibleImpl deliberately left as the toggle path. Both plans
are contract-only per plan-style and execute via
subagent-driven-development.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The seam for the camera-driver-authority decomplection: a uniform, self-describing mover unit so a single resolver can pick the per-frame camera winner by priority instead of statement order. Type-only; the resolver, wrapper drivers, and runFrame rewrite follow in later tasks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pure resolver at the heart of camera authority: among active drivers the highest priority wins and ONLY that winner's apply() runs (no cooperative blending, no apply at all when none active). Max-scan over a readonly driver list — no sort-mutation, no captured state. Four unit tests cover winner selection, inactive-higher-doesn't-block, none-active, and exact cam+nowMs forwarding. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
buildCameraDrivers wraps the engine's existing movers (spaceMouse/input 100, tweens 60, auto-rotate 20) as CameraDrivers, closing over live state so toggled settings reflect next frame. The list rides on RunFrameDeps (built once in startLoop), not EngineState — it is a per-frame dependency, not engine-owned mutable state, so the state contract stays un-widened. Auto-rotate's yaw delta is pinned to 0.000873 and recomputes position. Groundwork only: runFrame does not call the resolver yet (next task). runFrame.test fixture gets drivers:[] to stay green at this boundary. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The behaviour-preserving swap. The three sequenced per-frame camera blocks (tween.advance → spaceMouse.applyToCamera → guarded auto-rotate) collapse to a single runCameraDrivers(deps.drivers, state.cam, nowMs) call — one camera-write site, precedence by priority not statement order. The auto-rotate !tweens.isActive() guard is deleted: tween (60) outranks auto-rotate (20), so the resolver subsumes it. Cancellation is unchanged (lives in spaceMouse.applyToCamera's cancelTween callback). The render-on-demand stillAnimating gate's three camera terms collapse to deps.drivers.some(d => d.isActive(nowMs)) — equivalent because each driver's isActive maps one-to-one to an old term (proven by the wrapper suite). Three regression tests pin tween-beats-autoRotate, idle-holds, and the exact auto-rotate yaw delta. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
skymap | 5a8cde3 | Commit Preview URL Branch Preview URL |
Jun 07 2026, 11:51 PM |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
First of the pre-tour decomplections. Replaces
runFrame's implicit per-frame camera-control call order (tween.advance → spaceMouse.applyToCamera → guarded auto-rotate) with a driver registry + one resolver that writesstate.camexactly once. Precedence becomes data (aprioritynumber); the resolver is also the single source of truth for "is the camera animating?" feeding the render-on-demand gate.Behaviour-preserving throughout — every task reproduced current behaviour as a test, then refactored green.
The un-braid
CameraDriver.prioritynumber scanned byrunCameraDrivers. Re-ranking or slotting in the tour driver (priority 80) becomes a one-line declaration, not frame-loop surgery.stillAnimatingpredicate previously each encoded "input/tween/auto-rotate active". Now one home (drivers+isActive) feeds both:runCameraDrivers(deps.drivers, …)anddeps.drivers.some(d => d.isActive(nowMs)).RunFrameDeps(built once instartLoop), notEngineState— the state contract doesn't widen.The
!tweens.isActive()guard that suppressed auto-rotate during a tween is deleted — tween (60) simply outranks auto-rotate (20). Cancellation (input interrupting a tween) is unchanged: it stays inspaceMouseSubsystem'scancelTweencallback; the registry only resolves the same-frame race.Commits
CameraDrivertype — precedence as datarunCameraDrivers— single-writer arbiter (+ 4 unit tests)buildCameraDrivers+RunFrameDeps.driverswiring (+ 6 wrapper tests)runFramewrites the camera through the registry (+ 3 regression tests)Tests
2456 pass (13 new across the camera suite + runFrame regressions); typecheck clean. Entanglement-radar pass confirms the un-braid with no new knots.
Note on docs
This branch also lands the shared pre-tour decomplection spec and the forward plan docs (settings-visibility-seam, cinematic-core tour seed) that were drafted alongside it. The settings-seam and tour implementations ship as their own follow-up PRs.
🤖 Generated with Claude Code