P18.1: organic combine — anticipation pop on match clear (sx, iOS sim)
Sharpen clear_pop_scale from a plain pop-then-shrink into a candy pop in three beats over its local 0..1: a tiny anticipation squash dip (~8% below rest), a snappy overshoot to ~1.40x via P15.1's ease_out_back, then an accelerating collapse to nothing (ease_in_quad). Endpoints stay locked (t=0 -> 1.0 rest, t=1 -> 0.0 gone), so the seam to the model board is clean and M3TE_ANIM_TIME=0 still reproduces the rest board; the particle burst / score popup compose on top unchanged. render_clear and CLEAR_ANIM_DUR are untouched, so cascade-cue timing snapshots don't churn and the model is unchanged. tests/gem_pose.sx now pins the new envelope (locked rest endpoints, the anticipation dip, the overshoot, the monotonic post-peak collapse) with its expected snapshot updated. goldens/p18_pop.png: new mid-clear pop golden at the overshoot peak (M3TE_FX=3 M3TE_ANIM_TIME=0.21). goldens/p6_fx_match.png refreshed for the new pop shape (same scene at its documented 0.22). README documents P18.1.
This commit is contained in:
34
README.md
34
README.md
@@ -384,6 +384,40 @@ env SIMCTL_CHILD_M3TE_FX=11 SIMCTL_CHILD_M3TE_ANIM_TIME=3.0 \
|
|||||||
no column has landed yet, so neither carries squash. The change is render-only — no
|
no column has landed yet, so neither carries squash. The change is render-only — no
|
||||||
`board.sx` model change, and a resting board is untouched.
|
`board.sx` model change, and a resting board is untouched.
|
||||||
|
|
||||||
|
### Organic combine — anticipation pop on clear (P18.1)
|
||||||
|
|
||||||
|
Matched gems no longer just pop-then-shrink flatly: `clear_pop_scale` now shapes the
|
||||||
|
clear as a candy pop in three beats over its local `0..1` — a tiny anticipation
|
||||||
|
squash dip (a "gather" ~8 % below rest), a snappy overshoot up to ~1.40× via P15.1's
|
||||||
|
`ease_out_back`, then an accelerating collapse to nothing (`ease_in_quad`). The
|
||||||
|
endpoints stay LOCKED — `t==0 → 1.0` (rest) and `t==1 → 0.0` (gone) — so the seam to
|
||||||
|
the model board is clean and `M3TE_ANIM_TIME=0` still reproduces the resting board;
|
||||||
|
the soft particle burst / `+points` popup (`board_fx.sx`) compose on top.
|
||||||
|
`tests/gem_pose.sx` pins the new envelope (locked rest endpoints, the anticipation
|
||||||
|
dip below rest, the overshoot above 1, and the strictly monotonic post-peak
|
||||||
|
collapse). `CLEAR_ANIM_DUR` (0.14 s) is unchanged, so the per-round cascade-cue
|
||||||
|
timing snapshots (`tests/cascade_rounds.sx` / `cascade_cue.sx`) don't churn.
|
||||||
|
|
||||||
|
The pop peaks at clear-phase local `t ≈ 0.37`; for `M3TE_FX=3` (the seed-1337
|
||||||
|
vertical red 3-match) the clear window is `[0.16, 0.30)` s, so `0.21` catches the
|
||||||
|
matched gems at their fullest overshoot, composed with the burst and "+30" popup:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Anticipation/overshoot candy pop at its peak (composed with the burst):
|
||||||
|
# goldens/p18_pop.png
|
||||||
|
env SIMCTL_CHILD_M3TE_FX=3 SIMCTL_CHILD_M3TE_ANIM_TIME=0.21 \
|
||||||
|
xcrun simctl launch --terminate-running-process booted co.swipelab.m3te
|
||||||
|
# Same match at exact rest (t=0) — board sits at its resting pose, no pop:
|
||||||
|
env SIMCTL_CHILD_M3TE_FX=3 SIMCTL_CHILD_M3TE_ANIM_TIME=0 \
|
||||||
|
xcrun simctl launch --terminate-running-process booted co.swipelab.m3te
|
||||||
|
```
|
||||||
|
|
||||||
|
`goldens/p6_fx_match.png` (the P11.1 burst+popup reference, pinned at `0.22`) was
|
||||||
|
refreshed for the new pop shape. The per-gem STAGGER of the explosions is the next
|
||||||
|
step (P18.2); this step is the per-gem pop SHAPE only. The change is render-only —
|
||||||
|
no `board.sx` model change, and normal play is byte-identical apart from the clear's
|
||||||
|
pop curve.
|
||||||
|
|
||||||
### FPS counter — dev overlay (P20.1)
|
### FPS counter — dev overlay (P20.1)
|
||||||
|
|
||||||
A small FPS readout for gauging frame cost while tuning the animations. It is a
|
A small FPS readout for gauging frame cost while tuning the animations. It is a
|
||||||
|
|||||||
35
gem_anim.sx
35
gem_anim.sx
@@ -76,22 +76,39 @@ land_squash :: (tl: f32) -> f32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- Clear pop ---------------------------------------------------------------
|
// --- Clear pop ---------------------------------------------------------------
|
||||||
// The matched-gem clear: a snappy outward pop then a collapse to nothing over its
|
// The matched-gem clear, shaped as a candy pop in three beats over its local
|
||||||
// local 0..1, so the clear reads as a satisfying candy pop rather than a plain
|
// 0..1: a tiny anticipation squash dip (a "gather" just below rest), a snappy
|
||||||
// shrink. A fast rise to a bigger overshoot makes the snap read; the soft
|
// overshoot up to the peak via P15.1's ease_out_back, then an accelerating
|
||||||
// particle burst / score popup (board_fx.sx) compose on top.
|
// collapse to nothing. Endpoints are LOCKED — t==0 -> 1.0 (rest) and t==1 -> 0.0
|
||||||
CLEAR_POP_A :f32: 0.34; // overshoot height above resting scale
|
// (gone) — so the seam to the model board stays clean; the soft particle burst /
|
||||||
CLEAR_POP_RISE :f32: 0.18; // fraction of the window spent rising to the peak
|
// score popup (board_fx.sx) compose on top.
|
||||||
|
CLEAR_DIP_T :f32: 0.16; // fraction of the window spent on the anticipation dip
|
||||||
|
CLEAR_DIP_A :f32: 0.08; // how far the gem compresses below rest before popping
|
||||||
|
CLEAR_POP_RISE :f32: 0.52; // window fraction at which the overshoot peak is reached
|
||||||
|
CLEAR_POP_A :f32: 0.36; // overshoot height above resting scale
|
||||||
|
|
||||||
clear_pop_scale :: (t: f32) -> f32 {
|
clear_pop_scale :: (t: f32) -> f32 {
|
||||||
if t <= 0.0 { return 1.0; }
|
if t <= 0.0 { return 1.0; }
|
||||||
if t >= 1.0 { return 0.0; }
|
if t >= 1.0 { return 0.0; }
|
||||||
if t < CLEAR_POP_RISE {
|
if t < CLEAR_DIP_T {
|
||||||
return 1.0 + CLEAR_POP_A * (t / CLEAR_POP_RISE);
|
// Anticipation gather: sin(PI*u) is 0 at both ends, so t==0 stays exactly
|
||||||
|
// at rest and the dip hands off to the rise at rest — a brief squash, not
|
||||||
|
// a step.
|
||||||
|
u := t / CLEAR_DIP_T;
|
||||||
|
return 1.0 - CLEAR_DIP_A * sin(PI * u);
|
||||||
}
|
}
|
||||||
|
if t < CLEAR_POP_RISE {
|
||||||
|
// Snap up to the peak: ease_out_back rises from rest, shoots a touch past
|
||||||
|
// 1+A, then eases back to exactly 1+A at the seam (its locked f(1)=1), so
|
||||||
|
// the maximum is a single clean overshoot with no second reversal.
|
||||||
|
u := (t - CLEAR_DIP_T) / (CLEAR_POP_RISE - CLEAR_DIP_T);
|
||||||
|
return 1.0 + CLEAR_POP_A * ease_out_back(u);
|
||||||
|
}
|
||||||
|
// Collapse to nothing: accelerate the shrink from the peak so the gem vanishes
|
||||||
|
// as the burst takes over. ease_in_quad pins the seam at the peak and t==1 at 0.
|
||||||
peak := 1.0 + CLEAR_POP_A;
|
peak := 1.0 + CLEAR_POP_A;
|
||||||
u := (t - CLEAR_POP_RISE) / (1.0 - CLEAR_POP_RISE);
|
u := (t - CLEAR_POP_RISE) / (1.0 - CLEAR_POP_RISE);
|
||||||
peak * (1.0 - u * u)
|
peak * (1.0 - ease_in_quad(u))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Live per-gem animation state, heap-allocated (like BoardAnim/BoardFx) so it
|
// Live per-gem animation state, heap-allocated (like BoardAnim/BoardFx) so it
|
||||||
|
|||||||
BIN
goldens/p18_pop.png
Normal file
BIN
goldens/p18_pop.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.2 MiB After Width: | Height: | Size: 3.2 MiB |
@@ -7,7 +7,7 @@ select_start_rest true select_end_rest true select_mid_pops true
|
|||||||
== land squash envelope ==
|
== land squash envelope ==
|
||||||
land_start_rest true land_end_rest true land_mid_wobbles true
|
land_start_rest true land_end_rest true land_mid_wobbles true
|
||||||
== clear pop envelope ==
|
== clear pop envelope ==
|
||||||
clear_start_full true clear_end_gone true clear_overshoots true
|
clear_start_full true clear_end_gone true clear_dips true clear_overshoots true clear_collapses true
|
||||||
== gem motion land bookkeeping ==
|
== gem motion land bookkeeping ==
|
||||||
motion_init true motion_no_land true motion_fresh_land true
|
motion_init true motion_no_land true motion_fresh_land true
|
||||||
== gem motion restart resets landings ==
|
== gem motion restart resets landings ==
|
||||||
|
|||||||
@@ -71,15 +71,30 @@ main :: () -> s32 {
|
|||||||
if !l_end { fails += 1; }
|
if !l_end { fails += 1; }
|
||||||
if !l_mid { fails += 1; }
|
if !l_mid { fails += 1; }
|
||||||
|
|
||||||
// 5. Clear pop: full at t=0, gone at t=1, overshoots above 1 in between.
|
// 5. Clear pop: full at t=0, gone at t=1, a tiny anticipation dip BELOW rest in
|
||||||
|
// the gather window, an overshoot above 1 mid-window, then — past the peak —
|
||||||
|
// a strictly monotonic collapse to nothing (a single clean pop, no second
|
||||||
|
// reversal). The locked t=0/t=1 endpoints keep the seam to the model board.
|
||||||
print("== clear pop envelope ==\n");
|
print("== clear pop envelope ==\n");
|
||||||
c_start := approx(clear_pop_scale(0.0), 1.0);
|
c_start := approx(clear_pop_scale(0.0), 1.0);
|
||||||
c_end := approx(clear_pop_scale(1.0), 0.0);
|
c_end := approx(clear_pop_scale(1.0), 0.0);
|
||||||
|
c_dip := clear_pop_scale(CLEAR_DIP_T * 0.5) < 1.0;
|
||||||
c_peak := clear_pop_scale(0.30) > 1.1;
|
c_peak := clear_pop_scale(0.30) > 1.1;
|
||||||
print("clear_start_full {} clear_end_gone {} clear_overshoots {}\n", c_start, c_end, c_peak);
|
c_collapse := true;
|
||||||
|
pc := clear_pop_scale(CLEAR_POP_RISE);
|
||||||
|
for 1..21: (i) {
|
||||||
|
tt := CLEAR_POP_RISE + (1.0 - CLEAR_POP_RISE) * cast(f32) i / 20.0;
|
||||||
|
vv := clear_pop_scale(tt);
|
||||||
|
if vv > pc + 0.000001 { c_collapse = false; }
|
||||||
|
pc = vv;
|
||||||
|
}
|
||||||
|
print("clear_start_full {} clear_end_gone {} clear_dips {} clear_overshoots {} clear_collapses {}\n",
|
||||||
|
c_start, c_end, c_dip, c_peak, c_collapse);
|
||||||
if !c_start { fails += 1; }
|
if !c_start { fails += 1; }
|
||||||
if !c_end { fails += 1; }
|
if !c_end { fails += 1; }
|
||||||
|
if !c_dip { fails += 1; }
|
||||||
if !c_peak { fails += 1; }
|
if !c_peak { fails += 1; }
|
||||||
|
if !c_collapse { fails += 1; }
|
||||||
|
|
||||||
// 6. GemMotion land bookkeeping: fresh state is unpinned at t=0, a never-
|
// 6. GemMotion land bookkeeping: fresh state is unpinned at t=0, a never-
|
||||||
// landed cell rests, and a freshly-stamped land reads age 0.
|
// landed cell rests, and a freshly-stamped land reads age 0.
|
||||||
|
|||||||
Reference in New Issue
Block a user