# 1.3.0 — `cmdAddKeyframe` easing is correct for every zoom range

Minor release. Completes the CapCut "Cubic Out" Δ-scaling fix. v1.2.0 corrected the paired path (`cmdKenBurns`); v1.3.0 extends the same model to the incremental path (`cmdAddKeyframe`). Both code paths now converge on a single shared segment model (`computeSegmentHandles`). **Not byte-identical for `add-keyframe --curve ease-out` when `Δ ≠ -0.5`** — intended correctness fix; `ken-burns` output is byte-identical to 1.2.0.

## Highlights

- 🎯 **`cmdAddKeyframe` ease-out now Δ-scales.** The start kf's outgoing handle `y` was a fixed `-0.47` regardless of zoom range. Now `round(0.94 × Δvalue, 6)` — same formula as `cmdKenBurns` since v1.2.0 and matching the CapCut "Cubic Out" preset.
- 🔗 **`cmdAddKeyframe ⇄ cmdKenBurns` parity.** A two-call sequence `add(t=0, val=A, ease-out) + add(t=dur, val=B, ease-out)` now produces output byte-identical to `cmdKenBurns(A, B, ease-out)` on `scale_x`.
- 🔄 **Neighbor handles retro-updated on insertion.** When inserting between two existing kfs, `prev.right_control` (x AND y) and `next.left_control` (x) are now recomputed. Previously they stayed pinned to obsolete intervals.
- 🧹 **Solitary kf cleanup.** A kf with neither prev nor next now gets both handles `{x: 0, y: 0}`. Previously `right` carried a phantom handle without a destination.
- 🧪 **Locked by oracle parity tests.** 208 tracked tests pass, including parity assertions (`cmdKenBurns ≡ 2× cmdAddKeyframe`), behavioral coverage (insertion, append, prepend, replace, solitary, ease-in-out retro-update), and a captured-from-CapCut-UI byte-identity oracle (`test-fixtures/oracles/cubic-out-triplet-frame-aligned.json`) that locks the formal contract: byte-identity on `x` for frame-aligned intervals, `±1μs` on non-aligned, `±1e-9` (~1 ULP) on `y`.

## Bugs fixed

### `cmdAddKeyframe` ease-out wrote a fixed `right_control.y = -0.47`

`cmdAddKeyframe` read `start.right_control.y` from `CURVE_PROFILES["ease-out"].startRightY` (`-0.47`), a fixed absolute, ignoring the value delta. The proven CapCut model (established in v1.2.0 for `cmdKenBurns`) is `start.right_control.y = round(0.94 × Δvalue, 6)`. `-0.47` was only correct when `Δvalue = -0.5` (the historical `1.5 → 1.0` Ken Burns fixture case).

**Fix:** `cmdAddKeyframe` now derives `y` from `0.94 × Δvalue` for `ease-out` via the shared `computeSegmentHandles` helper, consistent with `cmdKenBurns`.

### Neighbor handles not updated on insertion between two existing kfs

When inserting a new kf between two existing kfs, `prev.right_control` and `next.left_control` stayed pinned to the handle values computed for the original segment. The inserted kf split the segment into two, invalidating both neighbor handles.

**Fix:** On insertion, both neighbors are retro-updated:
- `prev.right_control` — x AND y (the outgoing handle of the previous kf, which now encodes the shorter `prev→new` segment);
- `next.left_control` — x only (y stays `0` for all currently supported curves).

## CLI surface

Unchanged. No new commands or flags:

```
capcut-david add-keyframe <draft> <seg-id> <property> --time <us> --value <n> [--curve <name>]
```

## Migration from 1.2.0

**No code changes required.** Existing scripts that build kfs incrementally with `cmdAddKeyframe --curve ease-out` will see corrected easing automatically — no flag change. If a workflow relied on the legacy `-0.47` constant or the phantom handle on solitary kfs, output JSON will differ; the new output is the correct CapCut Cubic Out reproduction.

## Compatibility

- CapCut ≥ 5.x desktop (Windows + macOS), JianYing 6+ unsupported — unchanged.
- Node `>= 18` — unchanged.
- Runtime dependencies: zero — unchanged.
- New tests targeting `cmdAddKeyframe` oracle parity, behavioral coverage, and captured-CapCut-UI byte-identity (208 tracked tests). All tracked tests pass; existing `cmdKenBurns` tests remain byte-identical (no regression on the v1.2.0 path). Aggregate coverage stays above the 80% gate.
- `draft_content.json` output for `add-keyframe --curve ease-out` is **not** byte-identical to 1.2.0 when `Δ ≠ -0.5` (intended correctness fix). `ken-burns` output is byte-identical to 1.2.0.

## Roadmap (1.x — non-binding)

Unchanged from 1.0.0:

- `1.x.0` — `capcut-david query` (animation / sticker / effect / filter catalogue lookup)
- `1.x.0` — `capcut-david validate <project>` (schema-invariant linter)
- `1.x.0` — JianYing 6+ research; `psycho-build` dynamic audio ducking
