Skip to content

feat(camera): perceptually uniform focus moves - #531

Open
rulkens wants to merge 24 commits into
mainfrom
worktree-focus-move-interpolation
Open

feat(camera): perceptually uniform focus moves#531
rulkens wants to merge 24 commits into
mainfrom
worktree-focus-move-interpolation

Conversation

@rulkens

@rulkens rulkens commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Replaces the two independently-eased target and distance channels in a focus move with one geodesic through van Wijk & Nuij's (u, w) view space, and derives the move's duration from the geodesic's length instead of pinning every move at 600 ms.

Spec: docs/superpowers/specs/2026-07-31-perceptually-uniform-focus-moves.md
Plan: docs/superpowers/plans/2026-07-31-perceptually-uniform-focus-moves.md (10 tasks)

What the measurements changed

The reversal metric everyone reached for first is a dead end: today's path scores 0 reversals at every view angle with path/net displacement of exactly 1.0000 (it is a straight line by construction), and the geodesic scores 0 too. The metric distinguishes nothing, so no test is built on it.

The metric that discriminates is perceived velocity under the paper's own metric — coefficient of variation across the move:

move today (easeOutCubic) today (linear ease) glide
star → Milky Way 15.30 13.66 0.000
Earth → nearby star 3.64 1.97 0.000
MW → Virgo 2.49 1.25 0.000
galaxy → galaxy 0.89 0.000 0.000

The middle column is why the ease change (task 1) lands first and alone: for galaxy → galaxy, the most common focus click, the current channel model is already uniform once the ease is linear. There, easeOutCubic is the whole defect.

Calibration (settled)

ρ = 1.42, V = 6, duration = clamp(S / V, 0.4 s, 4.0 s). ρ turned out to be a far weaker knob than the paper's framing suggests — star → Milky Way peaks at exactly 1.00× its endpoint distance at every ρ in [0.8, 3.0], because the destination view already frames the whole journey.

Progress

  • 1 — focus tweens honour the descriptor's ease, default 'linear'
  • 2 — PathTrackCompositeTrack (mechanical rename)
  • 3 — P1: a composite track declares the channels it owns
  • 4 — P2: thread fovYRad into the compile path
  • 5 — zoomPanGeodesic + calibration constants
  • 6 — glidePath (units in, duration out)
  • 7 — the glide effect arm + buildGlideTrack
  • 8 — rewire the two producers and tweenToClip
  • 9 — entanglement-radar review
  • 10 — verification + visual pass

Out of scope, named

followBody is a third interpolation path outside evaluateClip with the same root cause — every planet focus goes through it, so planet approaches keep the old feel. Filed at docs/backlog/2026-07-31-followbody-third-interpolation-path.md.

🤖 Generated with Claude Code

Focus moves ease target and distance as independent channels; the eye sees
their composition, so neither channel's curve controls what it perceives.
Replaces both with one geodesic through van Wijk & Nuij's (u, w) view space
and derives duration from path length.

The prior attempt (5da4fea, reverted in e7acdc5) flipped only the distance
channel to log space. That is the correct solution to the paper's DEGENERATE
case — pure zoom, where w(s) = w0·exp(kρs) — applied to the general one, which
is why it moved nothing on a star approach and introduced a direction reversal
zooming out.

Ground preparation found the joint half-built: PathTrack is already a
composite writer superseding the base layer for all four channels, and
buildPathTrack's ds = sqrt(angular² + dLog²) IS the van Wijk metric at ρ = 1.
The only missing joint is that supersession is all-four-or-nothing, so a writer
owning target+distance while yaw/pitch stay independent is inexpressible.
Per-move duration needs no prep — CameraTweenDescriptor.durationMs is already
per-descriptor and the wake gate reads presence, not elapsed.

Two numerical landmines are recorded because both fail silently and both bite
at skymap's scale range: the paper's r_i formula returns -Infinity for three of
four real moves (use asinh), and its u(s) subtracts two ~1e11 terms, returning
exactly 0 for a six-decade zoom with a small pan.

Also records that planets never reach this path at all — watchFocusTweenSaga
bails for any row liveBodyPosition resolves, so planet approach is followBody's
lerp. Famous stars do fall through to the tween. That third path gets a backlog
item rather than a fix here; its target moves every frame, so sharing the
fixed-endpoint evaluator may not be right.

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

cloudflare-workers-and-pages Bot commented Jul 31, 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 f0148ac Commit Preview URL

Branch Preview URL
Jul 31 2026, 09:07 PM

rulkens and others added 6 commits July 31, 2026 02:52
Swept rho in [0.8, 3.0] against real skymap constants. Settled on
rho=1.42, V=6, duration clamped to [0.4s, 4s].

The sweep changed the framing: rho is near-irrelevant for the move that
motivated this work. Star -> Milky Way peaks at exactly 1.00x its
endpoint distance at every rho, because the destination view is already
~21x wider than the journey. The wobble is fixed by the coupling, not
the pull-back.

Also settles the ease default to 'linear' -- constant s velocity is the
model's entire claim, and an ease-out is what produces the 'grows in the
last two frames' symptom in the first place.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Re-measured star -> Milky Way in full 3-D, sweeping the angle between the
view direction and the direction of travel. e7acdc5 was right and the
prototype was wrong: today's path has 0 reversals at every angle and
path/net displacement of exactly 1.0000.

That is forced, not lucky. With yaw/pitch unchanged the view direction F
is fixed, so cam(t) = cam(0) + e(t)·[delta·uhat - (d1-d0)·F] -- a fixed
vector scaled by a monotone scalar. No ease and no view angle bends it.
The geodesic scores 0 too, so the metric cannot distinguish the models
and must not become the pinning test.

Replaced it with perceived-velocity uniformity, which is what the model
actually claims: CV 0.000 for the glide against 15.30 / 3.64 / 2.49 /
0.89 for four sampled moves today.

Second result: for galaxy -> galaxy the current channel model is already
perfectly uniform once the ease is linear. easeOutCubic alone inflates it
to 4.80e7. For the most common focus click the ease is the whole defect
and the geodesic changes nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
10 TDD tasks. Prep P1 (composite track declares its channels) and P2
(fovYRad through the compile path) sequenced first as their own commits,
per spec section 6.

Task 1 is the ease change, sequenced ahead of the geodesic because the
measurement showed it is independently valuable: for galaxy -> galaxy the
current channel model is already perfectly uniform once the ease is
linear. It also exposes a real bug -- CameraTweenDescriptor.easing is
never read; tweenToClip hardcodes easeOutCubic on all four channels.

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

CameraTweenDescriptor.easing was never read -- tweenToClip hardcoded
easeOutCubic on all four channels, so the field was decorative and #530's
widening of it to the full Ease union had no effect on this path.

Both focus-tween producers now ask for 'linear'. Measured under the van
Wijk & Nuij metric, a galaxy -> galaxy move (the commonest focus click)
is already perfectly uniform under the current two-channel model once the
ease is linear -- coefficient of variation 0.000 against 0.89, max/min
1.00 against 4.80e7. An ease-out spends its last decade of scale in its
last few frames, which is the 'arrives too fast' symptom itself.

Plan task 1 of 10: docs/superpowers/plans/2026-07-31-perceptually-uniform-focus-moves.md

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

Mechanical rename only, no behaviour change. A flyPath is one kind of
composite writer; a glide will be another, owning a subset of channels.
Docblocks now describe the general shape rather than 'a flyPath'.

Symbol rename via npm run refactor; the object field and the
module-private validator renamed by hand (the CLI does exported symbols).

Plan task 2 of 10.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Spec section 6, prep P1. CompositeTrack gains channels: readonly
Channel[] and sample returns Partial<CameraPose>; the evaluator resolves
each channel independently, falling back to the base layer for channels
no active track declares. validateCompositeExclusivity checks the
declared set instead of looping ALL_CHANNELS.

Behaviour-neutral: flyPath declares all four channels, so the suite
passing unchanged is the deliverable. Verified by mutation -- declaring
only 'distance' makes 3 flyPath tests fail on yaw/pitch/target, which is
what proves the evaluator reads the declared channel set rather than
trusting whatever keys sample() returns. That distinction is
load-bearing: the declared set is what validateCompositeExclusivity
checked against, so a track returning an extra key would otherwise
silently override a base writer the validator had already allowed.

ALL_CHANNELS moves to channelSpace so buildPathTrack can import it
without a cycle back through compileClip.

Plan task 3 of 10.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@rulkens
rulkens force-pushed the worktree-focus-move-interpolation branch from 901c88d to 67fe659 Compare July 31, 2026 10:55
rulkens and others added 2 commits July 31, 2026 13:24
Spec section 6, prep P2. compileClip(data, frameBasis?, fovYRad?) and
evaluateClip(data, elapsedSec, frameBasis?, fovYRad?); the compile cache
keys on it too. Both cameraDrivers rows pass the live
state.cameraRuntime.projection.fovYRad, matching what followBody already
did.

Behaviour-neutral: nothing reads fovYRad yet. It is a pure passthrough
with no default applied here, mirroring frameBasis, whose "absent =>
identity" is likewise resolved by the consumer rather than by compileClip.

The one test stub that needed extending is cameraDrivers.test.ts's
FAKE_ENGINE_STATE; the other three buildCameraDrivers callers already
carry projection.fovYRad or never reach a pose() that reads it. No
optional chaining was added to paper over a missing field -- a missing
projection in production should be a hard TypeError.

Plan task 4 of 10.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The pure model: eq. 9 parametrised by arc length in the perceptual
metric, so constant ds/dt is constant perceived velocity. Unit-agnostic,
no knowledge of CameraPose, Mpc or FOV.

Two of the paper's printed formulas are replaced because they do not
survive skymap's scale range, and both replacements are pinned by tests
that were mutation-verified against the literal forms:

  r_i = asinh(-b_i), not ln(-b + sqrt(b^2+1)). Over a 1000-combination
  sweep the literal returns -Infinity in 379 cases and a wrong-but-finite
  value in 137 more -- 52% broken. b reaches 1e37 here because our moves
  are far more zoom-dominated than the 2-D map browsing the paper targets.

  u(s) = u0 + (w0/rho^2)*sinh(rho*s)/cosh(rho*s+r0), not the literal
  cosh(r0)*tanh(...) - sinh(r0), which subtracts two ~1e18 quantities
  whose difference is the answer and returns exactly u0 for small du.

The test file transcribes eq. 9 verbatim as an independent oracle, in the
ease.test.ts REF pattern -- but only exercises it at the paper's own
scale, where the literal forms are still sound. At skymap's scale the
verbatim transcription IS the bug, which is what the property tests pin.

Plan task 5 of 10.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@rulkens rulkens changed the title docs: spec perceptually-uniform focus moves feat(camera): perceptually uniform focus moves Jul 31, 2026
rulkens and others added 15 commits July 31, 2026 14:21
One home for the unit contract: w = 2*distance*tan(fovY/2) (fovY, not
fovX, no aspect factor), du = |target1 - target0| with the scalar u
lifted back onto the straight segment, and
durationSec = clamp(length / V, MIN, MAX).

It exists because both startCameraTween producers need a duration and
cannot reach buildGlideTrack, so without it the conversion would be
re-derived in three places -- which is exactly how the 2*tan factor goes
missing in one of them.

The target path is a straight line; the geodesic bows in w, never in
world space. Pinned by a test asserting the cross product with the
segment direction stays ~0 and the projection onto it is monotone.

Plan task 6 of 10.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A glide is a composite writer over target + distance only, so yaw and
pitch stay independent scalar tweens beside it (spec section 5.2). It
goes on Effect, not CameraAction: every CameraAction arm carries a single
ch: Channel and is a per-channel writer, which is why flyPath lives on
Effect too.

The compileClip arm returns track.endSec - atSec, NOT effect.over: with
`over` omitted the duration only exists once the geodesic has been
measured, and the timeline cursor has to move by the derived value or
every following effect lands at the wrong time.

This is the first writer to own a SUBSET of channels, so it is where P1's
per-channel merge is finally proven end-to-end. The evaluateClip test
pins the coupling rather than smoke-testing: at half time the move has
98% of its pan done while the zoom still has an order of magnitude to
run, which no independently-eased pair of channels can produce.

Nothing constructs a glide yet -- the focus path is still four
independent channel tweens. Task 8 does the cutover.

Known gap, recorded in the code: compositePoseAt's "copy only declared
channels" guard cannot fail today. Both builders return exactly what they
declare, pinned in their own tests, so the guard is a backstop for a
third builder drifting. Verified by mutation -- replacing it with a blind
Object.assign keeps all 1417 engine tests green.

Plan task 7 of 10.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The cutover. tweenToClip now emits one glide over target + distance plus
scalar yaw/pitch tweens beside it, and both startCameraTween producers
derive durationMs from glidePath(...).durationSec instead of the flat
600 ms FOCUS_TWEEN_MS. A hop across a galaxy and a descent to a planet
surface stop taking the same time.

Settles spec section 5.2: yaw/pitch stay independent scalar tweens. Van
Wijk & Nuij does not model orientation, angles are scale-free so they
never had the 1/d problem, and focus moves carry yaw/pitch through
unchanged anyway.

Verified by mutation -- reverting tweenToClip to the four independent
channel tweens fails two tests, each on a signature value of the
uncoupled model: 876.25 (lerp(10,1000,easeOutCubic(0.5))) and 60 (the
arithmetic midpoint of 10..110).

The cameraDrivers unit-slip oracle is rewritten, not deleted. Its
descriptor is a pure zoom, so it now lands on the degenerate branch and
the expectation becomes exponential: distance(f) = d0*(d1/d0)^f, the
paper's own closed form re-derived in the test with rho cancelling. The
>10 / <1000 bounds that actually catch a forgotten /1000 are unchanged.

FOCUS_TWEEN_MS survives untouched as followBody's constant; only its
docblock changed, because it claimed to be shared by every focus tween
and that would have invited someone to wire it back.

Plan task 8 of 10.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The paper's rho = 1.42 made a galaxy click rise to 8.79x its endpoint
distance, which reads as an unwanted zoom-out. Low rho makes zooming
expensive in the metric and panning cheap, so the path stops climbing;
at 0.15 it never rises past its destination. V and the clamp come down
together because the moves were also too long.

Also measured and rejected: a strictly monotone path (straight in u,
log w) that can never rise. Its arc length is 3.7e9 for star -> Milky Way
against the geodesic's 19.8, and 1.4e17 for Earth -> universe -- panning
at low altitude across a huge separation costs enormous perceived
distance, which is exactly what the bow-out exists to avoid. The bow-out
is load-bearing at large scale ratios; rho is the right lever.

Known consequence, recorded in glideCalibration's docblock: below about
rho = 0.3 the arc length goes bimodal, because the 1/rho^2 weight on the
zoom term swamps the pan term. Same-scale moves collapse to ~1 unit and
scale-changing ones explode past 180, so most moves now land on a clamp
bound rather than a derived duration.

Two tests had gone blind to that and are repaired rather than relaxed:

  glidePath's FOV test compared two durations that both pinned to the
  same clamp bound, so it could no longer detect a dropped 2*tan(fovY/2)
  factor. It now probes at(0.5).distance, which cannot be clamped.

  focusTweenDescriptor's duration test varied SEPARATION, which the
  arc length is barely sensitive to at this rho. It now varies the
  destination framing scale, which is what actually moves the length.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The visual pass overturned the deferral. With galaxy moves smooth, the
planet target snap reads as a defect rather than a known gap.

Recorded where the snap actually is, because it is not where it appears:
followBody declares pivotsOnFocusedBody, so applyFocusedBodyPivot
absolutely SETS target = bodyPosition + panOffset after the driver runs.
The driver's own `target: livePos` is already dead code, and any
interpolation inside the driver would be discarded.

Two shapes were considered. Making the pin interpolate preserves the
body-owns-the-pivot un-braid but leaves distance independently eased --
it would relocate this feature's defect to planets rather than remove it.
So followBody instead opts out of the pin and authors its full pose,
exactly as clip and tween already do and for the same stated reason.

Backlog item removed in the same change, per the hygiene convention.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four sliders under settings.debug.glide, seeded from DEFAULT_GLIDE_TUNING
so the numbers stay spelled once in glideCalibration. Picking rho by
rebuilding was the bottleneck; it is a feel question and needs a live
control.

rho has to reach BOTH consumers or the slider lies: the duration
derivation in the producers, and the compiled path shape in
buildGlideTrack. It is carried on CameraTweenDescriptor next to easing,
so the descriptor states the exact rho its durationMs was measured under
and the two cannot disagree.

glidePath's 4th param became Partial<GlideTuning>, read field-by-field
with ?? rather than a spread: buildGlideTrack relays an absent knob as an
explicit `undefined`, and a spread would overwrite the default with it --
silently sending rho: undefined into the geodesic for every authored clip
glide.

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

The last path with the old feel. followBody drops pivotsOnFocusedBody and
authors its full pose, exactly as clip and tween do and for the same
stated reason -- they keyframe a path including the target.

The snap was never in the driver. followBody's `target: livePos` was
already dead code: applyFocusedBodyPivot absolutely SETS target =
body + panOffset after the driver returns, so interpolating inside the
driver would have changed nothing. Opting out is what moves the target.

Making the pin interpolate instead was considered and rejected in the
spec: it preserves the body-owns-the-pivot un-braid but leaves distance
independently eased, relocating this feature's own defect to planets.

The geodesic is re-measured every approach frame from the fixed capture
to the CURRENT pivot -- the body moves under the approach, and van Wijk &
Nuij's stateless recomputation is what absorbs a moving endpoint. Only
the DURATION is frozen at activation, because shouldKeepTicking gates the
render loop on that same field: a duration re-derived per frame would
drift out from under the wake window and freeze the approach part-way,
resuming only on the next input event. Mutation-verified -- restoring a
flat 600 ms window fails 3 tests, in both the freeze and the waste
direction.

FOCUS_TWEEN_MS had no readers left; focusTweenDuration.ts is deleted.

Two behaviour notes. Grabbing a drag mid-approach snaps the pivot -- the
cost the spec accepted when followBody left the pin. Releasing one is
strictly better than before: the re-capture edge now also ends the
approach, where the old code lerped from a stale capture at an
unsaturated t and yanked.

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

rho = 0 was asked for. It is a SINGULARITY, not a small value: the
metric's zoom term is 1/rho^2, so b divides by zero, asinh gives
-Infinity, and length evaluates (Inf - Inf)/0 -> NaN. Nothing downstream
rejects a NaN pose; it reaches the camera as a dead frame.

The measurement gives the same intent safely: the path CONVERGES below
~0.05 -- numerically identical at 0.05, 0.02, 0.01 and 0.001, with a
galaxy click sitting at exactly its endpoint distance mid-move and a pure
zoom passing through the geometric mean. That is the rho->0 limit, so
0.05 is the default and glidePath floors rho at GLIDE_RHO_MIN. The guard
has its own test, mutation-verified.

Two tests had gone blind to the shorter clamp and are repaired, not
relaxed. buildGlideTrack's "a longer path takes longer" compared against
a destination that now saturates GLIDE_MAX_SEC, so it would have passed
vacuously the moment any other move saturated; it now varies destination
SCALE (what arc length is sensitive to at this rho) between two endpoints
asserted to lie strictly inside the clamp.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The arrival was the ask: with `linear`, a deep descent spends only 8.8%
of its wall clock (0.13s of 1.5s) with the target between 0.6x and 1x of
its final apparent size. Measured on the same move:

  linear        8.8%   0.13s
  easeOutSine  26.9%   0.40s
  easeOutCubic 44.5%   0.67s
  easeOutQuart 54.5%   0.82s
  easeOutExpo  65.0%   0.97s

This does not contradict the earlier argument for `linear`. The old
easeOutCubic was harmful because it was applied to two INDEPENDENTLY
eased channels in linear distance space, back-loading distance while the
target ran on its own schedule. Applied to arc length along the geodesic
it is a pure time-warp on a coupled path -- spec section 2.4: easing
reparametrises the path without deforming it. So the ease is now a free
aesthetic choice where before it was load-bearing and wrong.

Every *Back and *Elastic curve is excluded from the list, with the reason
recorded: on a geodesic an overshoot walks the arc PAST its endpoint, so
the camera flies through the target and back, and nothing throws.

Threaded to followBody as well as the two tween producers -- it sampled
the glide with raw linear progress, so the selector would have visibly
worked for galaxies and silently done nothing for planets, the same shape
of bug rho had. Mutation-verified: dropping the ease there fails the
named test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reported as wild frame-to-frame target jumps on the last leg of an
Andromeda -> Earth move, worst under easeOut curves and unaffected by rho.
It was neither the path nor the ease: it was float64.

glidePath computed the target as lerp(from, to, u/du). Near arrival that
is the difference of two ~0.78 Mpc coordinates, so the result quantises
to one ULP of the SEPARATION: 1.732e-16 Mpc, or 0.84 EARTH RADII.
Measured with easeOutQuart, the final frames sit 8.9e-16 Mpc from Earth
while the target jumps 2.9e-15 -- 3.3x the camera-to-target distance, so
Earth swings across the whole field of view between frames. rho cannot
help because it is not the path; easeOut is worse only because it lingers
where the quantisation dominates.

Fixed by measuring the offset from whichever endpoint is nearer, using
the SAME geodesic run in reverse for the far half, so the small quantity
is computed directly instead of as (large - large). Verified the reverse
traces the identical curve: lengths agree exactly and u_fwd + u_rev = du
to 1e-15. Tail jitter drops from 1.5e-2 of the camera distance to 2.8e-13.

Same class of fix as zoomPanGeodesic's landmine 2, and the third time
this shape of bug has appeared in this feature -- a small answer obtained
by cancelling two large ones is the recurring hazard of a 19-decade
scale range.

Also sets the calibration defaults: rho 0.18, min 0.6s, max 2.2s, ease
easeOutQuint.

Three tests needed repair, each a real weakness the new defaults exposed:
a hardcoded 'linear' expectation that would have passed even if the
producer ignored the tuning; two clamp-saturated endpoints; and a
toBeCloseTo(..., 6) comparing Earth-framing distances, where an absolute
5e-7 tolerance calls every pair of poses equal.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Section 5 was still asserting rho 1.42 / V 6 / clamp [0.4s, 4s] with a
duration table derived from them, and still framing 'linear' as the
default with an eased arrival as heresy. Shipped is rho 0.18, V 20,
clamp [0.6s, 2.2s], easeOutQuint -- the opposite on both counts. Anyone
reading it next month would be wrong about every number that matters.

Rewritten as what shipped, then WHY the two earlier calibrations lost.
Both were internally consistent and both were rejected on sight, which is
the actual finding: this was a feel question that measurement narrows but
does not settle. That is also why the sliders exist.

Two things promoted out of scattered comments into stated principles:

  2.3b -- three separate bugs in this feature were one bug: a small
  quantity obtained by subtracting two large ones over a 19-decade range.
  asinh, the restructured u(s), and now the two-ended target offset. None
  of them fail loudly; all three return plausible wrong values.

  8.1 -- four assertions went blind under calibration changes in a single
  session, none of them failing. Clamp-saturated comparisons and absolute
  tolerances on values spanning decades. The general check: would this
  still fail if someone re-tuned the calibration?

Plan checkboxes ticked and a completion note added recording the four
things the plan did not anticipate, all of them visual-pass driven.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Deferred from PR #531 -- each is a real seam with a named silent failure
mode, none of them blocking the feature.

The root one is GlideTuning: a record created whole, then shredded into
per-knob params at four boundaries, so rho has nine homes. Its cost is
already half-realised -- the same shape of bug has been fixed twice
during this PR (rho on the follow driver, then ease), and the third
instance is pre-built: clip-authored glides never see the store's V, min
or max.

CompositeTrack.channels is the one with the nastiest failure mode. It
restates sample's key set, and the two directions of disagreement are not
symmetric: over-returning is guarded (by a guard that provably cannot
fire), while over-DECLARING is unguarded and silently freezes the channel
at the clip's opening value -- with the writer that would have driven it
rejected at compile time.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two were live defects, two were hot-path/hygiene.

ONE HOME FOR THE ARRIVAL CURVE. glide() defaulted ease to 'linear' while
GLIDE_EASE_DEFAULT was 'easeOutQuint', so a focus tween and a
clip-authored glide of the same motion arrived on different curves, and
four docblocks asserted the wrong one. ease is now optional on the glide
Effect arm and falls through to DEFAULT_GLIDE_TUNING. Mutation-verified:
restoring the ?? 'linear' fails the new end-to-end test with 14.14 (the
linear midpoint) against 2.26 (easeOutQuint's).

AN ASSERTION THAT PASSED FOR ZERO. cameraDrivers asserted a ~9.4e-16 Mpc
framing distance with toBeCloseTo(..., 12) -- an absolute tolerance 529x
the value, so it passed for 0 and for a 100x error. The mechanical cause
was relErr being defined three times and unreachable from that directory,
so the file reached for toBeCloseTo; extracted to tests/support/relErr.ts
and all four sites import it. Mutation-verified: returning 0 at
saturation now fails, and passed under the old assertion.

A PER-FRAME SORT IN THE HOT PATH. evaluateClip copied and sorted
compositeTracks every frame, in a file whose contract advertises
precomputed work. Sorted once in compileClip, with the ordering now
documented on CompiledClip and a test that a fork-delayed track really
does emit out of order.

ONE HOME FOR THE FOCUSED-BODY PIVOT. body position + panOffset was spelled
in both applyFocusedBodyPivot and followBody's pose once followBody left
the pin. Extracted to focusedBodyPivot; the pin is a writer over it, the
driver a reader.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Section 2.4 still said "Default to 'linear'" while section 5.3 said
easeOutQuint shipped. Rewrote 2.4 to record what the original argument
got right and what it got wrong -- an ease on INDEPENDENT channels is
destructive, an ease on a coupled geodesic is a free time-warp -- and
pointed the value at its one home.

Also softened section 4's "independent confirmation of the 'linear'
default", which confirmed something narrower than it claimed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
DoD audit READY: 6077 tests / 913 files pass, both typecheck configs
clean, eslint clean, 66/66 checkboxes, no unowned TODOs, visual pass
confirmed twice (the second found the Earth-scale arrival stutter, now
fixed).

Ten planned tasks plus four unplanned, all four surfaced by looking at
the screen rather than by planning: followBody pulled into scope, a
DebugPanel tuning section, easeOutQuint over 'linear', and the arrival
stutter.

Deferred with detail files: four entanglement-radar findings, the
leaf-star sprite floor, and target vel/osc broadcasting.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@rulkens
rulkens marked this pull request as ready for review July 31, 2026 21:06
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