P17.3: organic fall — per-round landing squash-&-settle (sx, iOS sim)

Give each landing gem a wide-and-short squash-&-settle bounce as it touches
its destination, applied WITHIN the fall so EVERY cascade round bounces
(staggered per column), not only the final whole-move settle.

One envelope, one bounce: land_squash is now LAND_SQUASH_A * squash_envelope
(P15.1) over its normalized window, so the per-round fall bounce and the
settle bounce are the exact same shape. render_fall/render_clear age a
per-column bounce from each column's touch-down instant (fall_landing_frac *
FALL_ANIM_DUR) via the shared rest_squash + delivering_round helpers, so a gem
still in the air draws unsquashed and only a landed gem flattens; the squash
carries across the fall->clear seam.

Double-bounce reconciliation (approach a): drive the bounce from the per-round
fall and DROP the old whole-move "stamp at age 0" settle. The settle stamp is
now BACK-DATED per column (clock - (total - round_land_time)) so render_gems
resumes land_squash exactly where render_fall left off at the render_anim ->
render_gems seam — one continuous bounce, no double-pop.

Amplitude tuned 0.13 -> 0.18 (~13% peak) so the bounce reads while staying
tasteful; durations unchanged, so the cascade-cue snapshots don't churn.
M3TE_ANIM_TIME=0 still reproduces goldens/p6_idle_t0.png (a resting board
carries no landing stamp). New goldens/p17_land.png pins a staggered landing
mid-pour (M3TE_FX=11 ANIM_TIME=1.94). tests/easing.sx gains a landing-instant
section pinning fall_landing_frac / round_land_time; tests/gem_pose.sx stays
green (land_squash values are identical).
This commit is contained in:
swipelab
2026-06-06 12:29:11 +03:00
parent eeffe7a7db
commit 00b126d44c
8 changed files with 197 additions and 16 deletions

17
main.sx
View File

@@ -265,9 +265,24 @@ frame :: () {
if !g_motion.pinned { g_motion.clock += g_delta_time; }
if g_anim != null {
if g_anim_prev_active and !g_anim.active {
// On the frame the timeline settles, hand the final round's
// per-column landing bounce to land_squash so render_gems resumes it
// seamlessly. Each gem the LAST round delivered to cell i is
// back-dated to when its column actually touched down — (1 -
// fall_landing_frac)·FALL_ANIM_DUR ago — so the bounce picks up
// exactly where render_fall left it: one bounce, no double-pop at the
// render_anim → render_gems seam. A gem that settled in an earlier
// round already bounced then (its back-dated age exceeds LAND_DUR, so
// land_squash reads rest); a gem that never moved is skipped.
mv := @g_anim.move;
total := g_anim.total();
last := mv.rounds.len - 1;
for 0..BOARD_CELLS: (i) {
if mv.pre[i] != mv.final[i] { g_motion.stamp_land(i); }
m := delivering_round(mv, i, last);
if m >= 0 {
col := i % BOARD_COLS;
g_motion.stamp_land_at(i, g_motion.clock - (total - round_land_time(m, col)));
}
}
}
g_anim_prev_active = g_anim.active;