P16.2: organic illegal swap — springy bounce-back (+ M3TE_BADSWAP hook)
render_swap's rejected-swap branch now drives the two gems with a P15.1 spring-based bounce-back (bad_swap_bounce): a quick lunge toward the neighbour, then a damped spring home that overshoots rest by a bounded amount and settles to exactly 0. f(0)=f(1)=0, so the move stays purely visual — board byte-identical to pre-swap, no score/move spent. - board_anim.sx: add bad_swap_bounce envelope (lunge via ease_out_cubic, settle via 1 - spring(u)); BADSWAP_LUNGE_T/AMP constants. - board_view.sx: replace the linear ping-out illegal branch with the bounce. - main.sx: add illegal_swaps (complement of legal_swaps, same row-major order) + the startup-only M3TE_BADSWAP=n capture hook; mirrors M3TE_FX. - tests/easing.sx: append bounce-envelope assertions (endpoints, single lunge peak + location, damped settle); regenerate expected snapshot. - README.md: document the M3TE_BADSWAP recipe + goldens/p16_badswap.png. Gate green: ios-sim build links, 22 logic snapshots pass (anim_plan model invariants unchanged; SWAP_ANIM_DUR untouched so cascade-cue snapshots do not churn).
This commit is contained in:
@@ -76,6 +76,27 @@ squash_envelope :: (t: f32) -> f32 {
|
||||
sin(TAU * SQUASH_OSC * t) * d * d
|
||||
}
|
||||
|
||||
// Illegal-swap bounce-back envelope (P16.2): the displacement FRACTION the two
|
||||
// swapped gems travel toward the rejected neighbour over the swap segment. A quick
|
||||
// lunge OUT to BADSWAP_LUNGE_AMP (the single peak, at t==BADSWAP_LUNGE_T), then a
|
||||
// damped spring HOME that slightly overshoots past rest and settles to EXACTLY 0.
|
||||
// f(0)=0 and f(1)=0, so the swap stays purely visual — t==0 and t==1 are both the
|
||||
// rest pose. The settle reuses P15.1's `spring`: `1 - spring(u)` is the spring's
|
||||
// own (1-u)^3·cos envelope, which carries the value from the peak down through 0,
|
||||
// a bounded dip below rest, and back to exactly 0 — so the wobble matches the rest
|
||||
// of the organic pass and f(1) is pinned, not merely approached.
|
||||
BADSWAP_LUNGE_T :f32: 0.36; // where the lunge reaches its peak
|
||||
BADSWAP_LUNGE_AMP :f32: 0.42; // how far toward the neighbour (cell fraction)
|
||||
bad_swap_bounce :: (t: f32) -> f32 {
|
||||
if t <= 0.0 { return 0.0; }
|
||||
if t >= 1.0 { return 0.0; }
|
||||
if t < BADSWAP_LUNGE_T {
|
||||
return BADSWAP_LUNGE_AMP * ease_out_cubic(t / BADSWAP_LUNGE_T);
|
||||
}
|
||||
u := (t - BADSWAP_LUNGE_T) / (1.0 - BADSWAP_LUNGE_T);
|
||||
BADSWAP_LUNGE_AMP * (1.0 - spring(u))
|
||||
}
|
||||
|
||||
// One recorded cascade round. `before` is the board at the round's start (the
|
||||
// swapped board for round 0, the previous round's `after` otherwise — never has
|
||||
// holes). `matched` flags the cells cleared this round (they scale out). `src`
|
||||
|
||||
Reference in New Issue
Block a user