Skip to content

docs(spec): animation system — Layer-1 runner + tour saga, radar-hardened#363

Merged
rulkens merged 7 commits into
mainfrom
docs/animation-system-runner
Jun 24, 2026
Merged

docs(spec): animation system — Layer-1 runner + tour saga, radar-hardened#363
rulkens merged 7 commits into
mainfrom
docs/animation-system-runner

Conversation

@rulkens

@rulkens rulkens commented Jun 23, 2026

Copy link
Copy Markdown
Owner

Refines the committed animation-system design spec (docs/superpowers/specs/2026-06-19-animation-system-design.md, PR #346) with the resolved Layer-1 runner design and a design-time entanglement-radar pass. Docs-only — no code.

What changed

Grill resolution (Q1–Q16) — full transcript in docs/grill-sessions/animation-runner-placement-2026-06-21.md. The clip runner is not a god-object CameraDriver; it splits into:

  • a camera facet — a clip@95 driver in the table (orbitDrag-shaped: camera.clip Intent flag + a clipPlayer Resource, pure pose read), so the table stays the single camera-pose authority;
  • a scene + clock + lifecycle facet — the clipPlayer subsystem that ticks first, composes base/vel/osc, and fires scene effects.

Key decisions recorded: registration-time single-writer validation (complete because clips are static data); show/hide/fade/scene/focus scene vocabulary with opacity single-writer + bridge suspension; structured-concurrency fork; all-or-nothing cancellation via endClip() + a [CANCEL] hook; the animatable channel set ≡ CameraPose (roll/fov deferred, the spline camera is a generator not a channel); and a discrete click-advanced tour saga (dwell never frozen).

Entanglement-radar pass (docs(spec): un-braid…):

  • commit-on-edge lifted to a commitsOnEdge driver-row property instead of a hardcoded id OR-chain in runFrame;
  • one canonical CHANNEL_SPACE record (helpers + validator read it; action space is an optional override);
  • documented why opacity stays a suspended bridge (two exclusive writers) rather than a camera-style arbitration table (N concurrent).

Scope

Design only, awaiting plan. The throwaway recording spikes that proved the vocabulary live on the separate draft PR #354 and are not included here.

🤖 Generated with Claude Code

rulkens and others added 2 commits June 23, 2026 11:25
Channel set ≡ CameraPose (4 channels; roll/fov + the spline camera
deferred as a generator, not new channels), and the Layer-2 tour saga
(discrete click-advanced beats, one clip per stop, dwell never frozen via
a perpetual dwellDrift clip in the race; capture/restore via the real
wiring seam). Adds the full Q1–Q16 grill transcript.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three design-time findings on the animation spec:
- commit-on-edge: lift `commitsOnEdge` to the driver row instead of
  growing a hardcoded id OR-chain in runFrame (dissolves the branch and
  its future growth).
- channels: one canonical CHANNEL_SPACE record read by helpers +
  validator; action `space` is an optional override (no triplicated map).
- opacity: document why it stays a suspended bridge (two exclusive
  writers) rather than a camera-style arbitration table (N concurrent).

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

cloudflare-workers-and-pages Bot commented Jun 23, 2026

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 2439175 Commit Preview URL

Branch Preview URL
Jun 24 2026, 09:04 AM

rulkens and others added 5 commits June 23, 2026 14:14
The grill made the clip a live-pose Resource gated by an Intent flag
(orbitDrag-shaped), on the reasoning that the 60Hz pose can't be Intent.
That conflated the pose (derived, fine) with the clip descriptor
(low-freq Intent). The focus tween is the proof: it's also a
per-frame-changing pose, yet it's a pure evaluator over a store
descriptor, not a Resource.

So the clip takes the same shape: `camera.clip: { data: ClipData }` in
the store; `pose: (s,_cam,elapsed) => evaluateClip(data, elapsed)` pure;
no live-pose register; rides cameraClock like the tween. The clipPlayer
shrinks to scene-cues + lifecycle. evaluateTween becomes the one-segment
case of evaluateClip. Buys serializable live state, near-free scrub, and
frame-rate-independent determinism; costs a closed-form ∫vel.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A clip dispatches the same intents the UI does, so it trips every
production saga that reacts to them. The spec un-braided one
(watchFades) and hand-waved the rest. Three fixes:

- focus() planted a camera.tween@60 that outlived the clip and
  hijacked the camera at clip-end (dormancy-by-priority ≠ suspension).
  Suspend watchFocusTween alongside watchFades, via one declared
  SUSPEND_DURING_CLIP set + whileNoClip wrapper — not a per-saga guard.
- show/hide reuse the syncVisibilityFades body (add an optional
  durationMs; it already takes only?), not a duplicated manifest walk.
- captureScene/restoreScene widen the snapshot to selection.focus so a
  beat's focus() + isolation dim don't leak past the tour's restore.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…model)

Three-agent parallel audit + grill. Folds in:

- #1 start:'live' resolved to a concrete pose at startClip from
  cameraRuntime.lastPose.current (mirrors focusTweenSaga baking 'from'),
  so evaluateClip stays pure AND the inter-clip handoff is robust to the
  skipped commit-on-edge.
- #2 endClip() clears a stale camera.tween (suspension stops only NEW
  tweens); watchSelectionRows stays live (it drives the isolation dim).
- #3 the suspend guard gates per-action INSIDE takeEvery's worker, not
  around the watcher (a watcher registers once at boot).
- #4 the tour ends only via an explicit TOUR_EXIT racing the beat loop;
  camera input is swallowed by design (dissolves the isUserCameraInput
  self-abort trap).
- #5 fade() writes a third composed opacity channel, clipOpacity
  (intentOpacity x focusRecession x clipOpacity), the shape structureFocus
  already uses, reset on clip end. Dissolves the intent desync, the
  clip-end freeze, and shrinks the suspend-set to watchFocusTween alone.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dary, saga seam)

- #6: 'clip joins the clock' is a triple — clipStartMs + clipElapsed +
  elapsedForWinner arm; omitting any leaves the clip frozen at t=0.
- #7: residual rate-velocity is a within-clip layer; the single-pose handoff
  bakes position only, so it dies at the clip boundary by design (dwellDrift /
  ramp-down disciplines).
- #9: BeatData.effects are plain Action[] (put verbatim, no applyIntent
  wrapper); capture/restore reach the existing captureSettings/restoreSettings
  helpers as ReconcileEffects closures via getContext('reconcile'), since a
  saga has no state/store in scope to pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Plan A (clip model): Effect/ClipData types, pure evaluateClip with the
clock triple, the clip@95 driver row + commitsOnEdge, the clipPlayer
Resource (tick-first cues + lifecycle), the clipOpacity channel + fade()
+ the full scene-verb vocabulary, re-express the flyout spike.

Plan B (playClip seam): fold evaluateTween into evaluateClip (focus = the
one-segment case; camera.tween stays a separate Intent), the playClip
Promise seam with saturated-pose clip-end sequencing, suspendDuringClip
parking watchFocusTween, verify endClip clears a dormant camera.tween.

Plan C (tour saga): BeatData, visitBeat/guidedTour with TOUR_EXIT-raced
cancellation, captureScene/restoreScene as ReconcileEffects closures, the
dwellDrift/flyTo builders — purely Layer-2 orchestration consuming the
Layer-1 vocabulary.

Cross-plan boundaries reconciled to single ownership: clipOpacity keyed
by VisibilityLayerKey owned by Plan A; scene verbs + SceneEffect owned by
Plan A (recordings use them sans saga); suspendDuringClip owned by Plan B.
Spec BeatData.focus corrected SourceRef -> SelectionRef.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@rulkens rulkens merged commit 76af429 into main Jun 24, 2026
2 checks passed
rulkens added a commit that referenced this pull request Jun 24, 2026
Reconciles the squashed docs PR #363 (radar-hardened tour-saga plan) with
the feat branch. Resolves two add/add plan conflicts:
- clip-model plan: keep feat's completed [x] checkbox record
- tour-saga plan: take main's radar-hardened Task 8 (feat never executed Plan C)

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