Skip to content

Camera-driver authority: precedence as data, one camera-write site#286

Open
rulkens wants to merge 7 commits into
mainfrom
worktree-tour-implementation
Open

Camera-driver authority: precedence as data, one camera-write site#286
rulkens wants to merge 7 commits into
mainfrom
worktree-tour-implementation

Conversation

@rulkens

@rulkens rulkens commented Jun 7, 2026

Copy link
Copy Markdown
Owner

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 writes state.cam exactly once. Precedence becomes data (a priority number); 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

  • Precedence × statement-order → a CameraDriver.priority number scanned by runCameraDrivers. Re-ranking or slotting in the tour driver (priority 80) becomes a one-line declaration, not frame-loop surgery.
  • "Camera is moving" × place → the write site and the RoD stillAnimating predicate previously each encoded "input/tween/auto-rotate active". Now one home (drivers + isActive) feeds both: runCameraDrivers(deps.drivers, …) and deps.drivers.some(d => d.isActive(nowMs)).
  • The driver list rides on RunFrameDeps (built once in startLoop), not EngineState — 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 in spaceMouseSubsystem's cancelTween callback; the registry only resolves the same-frame race.

Commits

  • CameraDriver type — precedence as data
  • runCameraDrivers — single-writer arbiter (+ 4 unit tests)
  • wrapper drivers + buildCameraDrivers + RunFrameDeps.drivers wiring (+ 6 wrapper tests)
  • runFrame writes 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

rulkens and others added 7 commits June 8, 2026 00:30
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>
@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 5a8cde3 Commit Preview URL

Branch Preview URL
Jun 07 2026, 11:51 PM

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