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).