P16.1: organic legal swap — overshoot/settle (ease_out_back)

Replace the legal swap's flat ease_out_cubic decelerating slide with the
P15.1 ease_out_back overshoot curve: the two swapped gems shoot ~10% PAST
their target cells, then settle exactly onto them. Purely visual — the curve
pins f(0)=0 / f(1)=1, so t==0 is the rest pose and t==1 lands byte-on-cell;
the committed move and final board are unchanged. SWAP_ANIM_DUR (0.16 s) is
untouched, so the cascade-cue timing snapshots do not churn.

Only the legal branch of render_swap changes; the illegal ping-back is left
as-is for P16.2. Model/logic untouched (FFI is the only non-sx surface).

Golden goldens/p16_swap.png: M3TE_FX=3 (top-row swap (5,0)<->(6,0)) pinned at
M3TE_ANIM_TIME=0.10 (swap-phase t~0.625, near the overshoot peak). Measured
against calibrated cell centers: the red lands ~8% left of col-5 and the green
~12% right of col-6 (both PAST target), while every unswapped gem stays
centered; at M3TE_ANIM_TIME=0 the same gems sit dead-on their pre-swap cells.

Gate: `sx build --target ios-sim main.sx` links clean; `tools/run_tests.sh`
22/22 (anim_plan, easing, cascade_rounds, cascade_cue all green).
This commit is contained in:
swipelab
2026-06-06 10:32:42 +03:00
parent a2c5995fc2
commit 8402f49503
3 changed files with 31 additions and 1 deletions

View File

@@ -214,6 +214,32 @@ env SIMCTL_CHILD_M3TE_ANIM_TIME=0 SIMCTL_CHILD_M3TE_SELECT=27 \
The selection gloss is purely visual: it never gates input (`BoardAnim.active`
owns gating) and never touches board / score / move state.
### Organic swap motion (P16.1)
A legal swap no longer slides flatly into place: `render_swap` drives the two
gems with `ease_out_back` (the P15.1 overshoot curve), so they shoot a touch PAST
their target cells then settle exactly onto them. The curve pins `f(0)=0` and
`f(1)=1`, so the swap stays purely visual — `t==0` is the rest pose and `t==1`
lands byte-on-cell; the committed move and final board are unchanged. The reused
`SWAP_ANIM_DUR` (0.16 s) is untouched, so the cascade-cue timing snapshots
(`tests/cascade_rounds.sx` / `cascade_cue.sx`) don't churn.
Capture the overshoot with the same `M3TE_FX` hook, pinned near the peak
(swap-phase `t ≈ 0.625`, where the gems are ~10 % past target):
```bash
# Swapped gems caught PAST their target cells (overshoot): goldens/p16_swap.png
env SIMCTL_CHILD_M3TE_FX=3 SIMCTL_CHILD_M3TE_ANIM_TIME=0.10 \
xcrun simctl launch --terminate-running-process booted co.swipelab.m3te
# Same swap at exact rest (t=0) — gems sit dead-on their pre-swap cells:
env SIMCTL_CHILD_M3TE_FX=3 SIMCTL_CHILD_M3TE_ANIM_TIME=0 \
xcrun simctl launch --terminate-running-process booted co.swipelab.m3te
```
`M3TE_FX=3` is the top-row swap `(5,0)↔(6,0)` that completes the vertical red
3-match in column 5; at `M3TE_ANIM_TIME=0.10` the red lands ~8 % left of col-5
and the green ~12 % right of col-6 (every unswapped gem stays centered).
## Audio bank (P10) — final model
The SFX bank (`audio.sx`) is a purely additive layer over iOS **System Sound

View File

@@ -415,7 +415,11 @@ BoardView :: struct {
p : f32 = ---;
if mv.legal {
p = ease_out_cubic(t);
// Overshoot-and-settle: the two gems shoot a touch PAST their target
// cells, then settle exactly onto them, instead of decelerating flatly
// into place. ease_out_back pins f(0)=0 and f(1)=1, so t==0 is the rest
// pose and t==1 lands byte-on-cell — the swap stays purely visual.
p = ease_out_back(t);
} else if t < 0.5 {
p = ease_out_cubic(t * 2.0);
} else {

BIN
goldens/p16_swap.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB