Background
Split out from the cinematic double-tap zoom umbrella (#36, item 5 of the zoom-rendering audit). A first implementation was attempted and deliberately reverted — read the retro on #36 before picking this up.
Original intent (from the audit)
CAMERA_ANIM_DURATION_MS = 300 regardless of how big the zoom change is. Going from 0.3× to 3× (a 10× zoom step) takes the same 300ms as going from 0.9× to 1.0×. The audit's claim: bigger jumps feel rushed, smaller ones sluggish. Proposed formula:
const scaleRatio = Math.max(scaleStart / scaleEnd, scaleEnd / scaleStart);
const durationMs = Math.min(500, 250 + Math.log2(scaleRatio) * 50);
What happened when it was tried
Implemented on feat/cinematic-double-tap-zoom (PR #42, closed unmerged) and reverted. Feel-testing in the harness did not bear out the premise: users perceive deceleration, not absolute duration. easeOutCubic front-loads enough that a fixed 300ms reads as comfortable for both 1.1× and 10× steps — the eye locks onto the soft landing. Stretching big jumps to ~415ms made them feel slower, not more cinematic.
Remaining live scope
Revisit only after #39 (native UI-thread camera interpolator, post-Rust-port) lands. Today every camera-animation frame pays a JS-thread bridge cost, which confounds any timing feel-test; once the tween runs off the JS thread, the per-frame cost profile changes and the question is worth re-asking with clean conditions. Treat the audit's formula as a starting point, not a spec.
Acceptance criteria
Refs: #36
Blocked by: #39
Agent prompt
Repo: workspace-sh/react-native-jsoncanvas. Read issue #36 in full (especially the
two retro comments — PR #42 implemented this and was reverted after feel-testing),
then issue #39, then this issue.
Precondition: #39 (native UI-thread camera interpolator) must be merged. If it is
not, stop and report — the JS-thread tween's per-frame bridge cost confounds any
duration feel-test.
Task: re-evaluate scale-aware camera animation duration under the new interpolator.
1. Locate CAMERA_ANIM_DURATION_MS and the camera animation path (formerly the
animateCamera rAF tween in src/renderer/; #39 will have moved or replaced it).
2. Prototype the log2-based formula from the issue body behind a quick toggle.
3. Feel-test in the harness app: small (~1.1×) and large (~10×) zoom steps, fixed
300ms vs scale-aware, with the existing easeOutCubic easing unchanged.
4. The prior finding was that perception tracks deceleration, not duration, and
fixed 300ms won. Only adopt a change if it clearly reads better; "keep 300ms"
is a valid outcome. Document the decision as a comment on this issue either way.
5. If adopted: branch + PR (Conventional Commits), "Refs: #<this issue>", run the
animation tests. Do not modify pan/pinch/scroll-wheel gesture handlers.
Background
Split out from the cinematic double-tap zoom umbrella (#36, item 5 of the zoom-rendering audit). A first implementation was attempted and deliberately reverted — read the retro on #36 before picking this up.
Original intent (from the audit)
CAMERA_ANIM_DURATION_MS = 300regardless of how big the zoom change is. Going from 0.3× to 3× (a 10× zoom step) takes the same 300ms as going from 0.9× to 1.0×. The audit's claim: bigger jumps feel rushed, smaller ones sluggish. Proposed formula:What happened when it was tried
Implemented on
feat/cinematic-double-tap-zoom(PR #42, closed unmerged) and reverted. Feel-testing in the harness did not bear out the premise: users perceive deceleration, not absolute duration. easeOutCubic front-loads enough that a fixed 300ms reads as comfortable for both 1.1× and 10× steps — the eye locks onto the soft landing. Stretching big jumps to ~415ms made them feel slower, not more cinematic.Remaining live scope
Revisit only after #39 (native UI-thread camera interpolator, post-Rust-port) lands. Today every camera-animation frame pays a JS-thread bridge cost, which confounds any timing feel-test; once the tween runs off the JS thread, the per-frame cost profile changes and the question is worth re-asking with clean conditions. Treat the audit's formula as a starting point, not a spec.
Acceptance criteria
Refs: #36
Blocked by: #39
Agent prompt