P18.2: organic combine — staggered clear ripple (sx, iOS sim)

Within a clearing round the matched gems no longer all explode at once: each
gem's pop (and its burst) START is offset by a bounded per-gem delay so the
cells detonate as a ripple.

- board_anim.sx: clear_ripple_t(t,u) mirrors fall_stagger_t's (t-delay)/window,
  delaying a gem's pop START by CLEAR_STAGGER_MAX*u (0.45 of the clear window).
  Bounded: every gem still reaches local 1 (scale 0) by t==1, so none is left
  mid-pop at the seam to the fall. clear_diag_span/clear_rank rank each matched
  gem 0..1 by diagonal (col+row) PER ROUND, so even a 3-match ripples across the
  full budget.
- board_view.sx render_clear: feed each matched gem's ranked, staggered local t
  through the P18.1 clear_pop_scale (locked endpoints unchanged).
- board_fx.sx: bursts carry the same per-gem delay so they ripple in lockstep
  with the pops. Per-round audio cue (P10.10) still fires once at t0, not per gem.
- Model untouched (same cells cleared, same final board); CLEAR_ANIM_DUR fixed,
  so cascade-cue snapshots don't churn and M3TE_ANIM_TIME=0 still rests.
- tests/easing.sx: pin clear_ripple_t endpoints, bounded completion by t==1,
  monotonicity, ripple ordering, and the diagonal rank.
- goldens: add p18_stagger (M3TE_FX=3 @ 0.22); refresh p18_pop, p6_fx_match,
  p11_combo_deep (all pinned mid-clear, now showing the ripple).
This commit is contained in:
swipelab
2026-06-06 13:20:52 +03:00
parent 70a69864c1
commit 5eaf91b22d
10 changed files with 161 additions and 8 deletions

View File

@@ -494,13 +494,18 @@ BoardView :: struct {
// Clear segment: matched gems pop outward then collapse to nothing (a
// satisfying pop, composing with the particle burst); the rest hold position.
render_clear :: (self: *BoardView, ctx: *RenderContext, rd: *AnimRound, k: s64, e: f32, dim: f32, t: f32) {
pop := clear_pop_scale(t);
span := clear_diag_span(@rd.matched);
for 0..BOARD_CELLS: (i) {
g := rd.before[i];
if g == .empty { continue; }
col := i % BOARD_COLS;
row := i / BOARD_COLS;
if rd.matched.cells[i] {
// Ripple: each matched gem's pop START is offset by its diagonal
// rank within the round (clear_ripple_t), so the matched cells
// explode as a wave instead of simultaneously; every gem still
// reaches scale 0 by t==1, keeping the seam to the fall clean.
pop := clear_pop_scale(clear_ripple_t(t, clear_rank(span, col, row)));
gf := self.gem_frame_scaled(col, row, dim, pop);
self.draw_gem(ctx, gf, cast(s64) g);
} else {