Merge branch 'flow/m3te/P17.2' into m3te-plan
This commit is contained in:
35
README.md
35
README.md
@@ -309,6 +309,41 @@ swap→(clear,fall)\* timeline (swap 0.16, then clear 0.14 + fall 0.22 per round
|
|||||||
`1.51` lands squarely in round 3's fall. The change is render-only — no `board.sx`
|
`1.51` lands squarely in round 3's fall. The change is render-only — no `board.sx`
|
||||||
model change, and normal play is byte-identical apart from the fall's motion curve.
|
model change, and normal play is byte-identical apart from the fall's motion curve.
|
||||||
|
|
||||||
|
### Organic fall — per-column stagger (P17.2)
|
||||||
|
|
||||||
|
The fall no longer drops a whole row in lockstep: `render_fall` offsets each
|
||||||
|
COLUMN's drop START by a small bounded delay (`fall_stagger_t`), so a refilled row
|
||||||
|
pours in as a left-to-right cascade. Column `col` waits `FALL_STAGGER_MAX·col/7`
|
||||||
|
(`FALL_STAGGER_MAX = 0.30`) of the fall window, then falls over the remaining
|
||||||
|
`1 - 0.30`, feeding that local progress through `ease_in_cubic` so each column still
|
||||||
|
accelerates under gravity within its own window. The LAST column lands EXACTLY at
|
||||||
|
`t=1` and every earlier column strictly before it, so NO gem is ever left mid-air at
|
||||||
|
the segment end — the seam to the next round / settled board stays invisible and
|
||||||
|
`move.final` is untouched (`tests/anim_plan.sx` contiguity stays green;
|
||||||
|
`tests/easing.sx` pins `fall_stagger_t`'s `f(0)=0`, `f(1)=1`, monotonicity, and the
|
||||||
|
mid-fall cascade ordering). `FALL_ANIM_DUR` (0.22 s) is unchanged, so the per-round
|
||||||
|
cascade-cue snapshots don't churn.
|
||||||
|
|
||||||
|
The cleanest tell is a round where several adjacent columns refill the SAME
|
||||||
|
distance: in lockstep their gems share one height (a flat row); staggered, they
|
||||||
|
form a diagonal. On seed 1337, `M3TE_FX=11` **round 4** refills columns 2–7 by one
|
||||||
|
cell each (its fall window `[1.74, 1.96)` s); at `1.91` the leading column has just
|
||||||
|
landed while the trailing column is still ~⅔ cell high, so the top row reads as a
|
||||||
|
left-to-right staircase instead of a flat band (contrast `goldens/p17_fall.png`,
|
||||||
|
which is pre-stagger lockstep):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Refilled row pouring in as a staggered cascade: goldens/p17_stagger.png
|
||||||
|
env SIMCTL_CHILD_M3TE_FX=11 SIMCTL_CHILD_M3TE_ANIM_TIME=1.91 \
|
||||||
|
xcrun simctl launch --terminate-running-process booted co.swipelab.m3te
|
||||||
|
# Same cascade past the timeline — every gem landed exactly on the model board:
|
||||||
|
env SIMCTL_CHILD_M3TE_FX=11 SIMCTL_CHILD_M3TE_ANIM_TIME=3.0 \
|
||||||
|
xcrun simctl launch --terminate-running-process booted co.swipelab.m3te
|
||||||
|
```
|
||||||
|
|
||||||
|
The change is render-only — no `board.sx` model change, and normal play is
|
||||||
|
byte-identical apart from the fall's per-column timing.
|
||||||
|
|
||||||
## Audio bank (P10) — final model
|
## Audio bank (P10) — final model
|
||||||
|
|
||||||
The SFX bank (`audio.sx`) is a purely additive layer over iOS **System Sound
|
The SFX bank (`audio.sx`) is a purely additive layer over iOS **System Sound
|
||||||
|
|||||||
@@ -97,6 +97,26 @@ bad_swap_bounce :: (t: f32) -> f32 {
|
|||||||
BADSWAP_LUNGE_AMP * (1.0 - spring(u))
|
BADSWAP_LUNGE_AMP * (1.0 - spring(u))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Per-column fall stagger (P17.2): within the fall window, each column starts its
|
||||||
|
// drop at a small BOUNDED delay so a refilled/collapsed row pours in as a cascade
|
||||||
|
// instead of every gem snapping down in one flat lockstep row. Column `col` waits
|
||||||
|
// FALL_STAGGER_MAX * col/(BOARD_COLS-1) of the window, then falls over the
|
||||||
|
// remaining `1 - FALL_STAGGER_MAX`, so the LAST column lands EXACTLY at t==1 and
|
||||||
|
// every earlier column lands strictly before it — no gem is ever left mid-air when
|
||||||
|
// the segment ends (the seam to the next round / settled board stays invisible).
|
||||||
|
// Returns the column's LOCAL 0..1 progress; render_fall feeds it through
|
||||||
|
// ease_in_cubic so each column still accelerates under gravity within its window.
|
||||||
|
// `tests/easing.sx` pins f(0)=0, f(1)=1, monotonicity, and the cascade ordering.
|
||||||
|
FALL_STAGGER_MAX :f32: 0.30;
|
||||||
|
fall_stagger_t :: (t: f32, col: s64) -> f32 {
|
||||||
|
delay := FALL_STAGGER_MAX * (cast(f32) col / cast(f32) (BOARD_COLS - 1));
|
||||||
|
window := 1.0 - FALL_STAGGER_MAX;
|
||||||
|
lt := (t - delay) / window;
|
||||||
|
if lt <= 0.0 { return 0.0; }
|
||||||
|
if lt >= 1.0 { return 1.0; }
|
||||||
|
lt
|
||||||
|
}
|
||||||
|
|
||||||
// One recorded cascade round. `before` is the board at the round's start (the
|
// 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
|
// 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`
|
// holes). `matched` flags the cells cleared this round (they scale out). `src`
|
||||||
|
|||||||
@@ -531,16 +531,19 @@ BoardView :: struct {
|
|||||||
|
|
||||||
// Fall segment: every gem of the round's settled board accelerates under
|
// Fall segment: every gem of the round's settled board accelerates under
|
||||||
// gravity from its source row (above the board for refills) down to its
|
// gravity from its source row (above the board for refills) down to its
|
||||||
// destination cell. ease_in_cubic pins f(1)=1, so each gem lands exactly on
|
// destination cell. Each COLUMN's drop starts at a small staggered delay
|
||||||
// its cell and the seam to the next round / settled board stays invisible.
|
// (fall_stagger_t) so a refilled row pours in as a cascade rather than a flat
|
||||||
|
// lockstep row; ease_in_cubic pins each column's f(1)=1, and fall_stagger_t
|
||||||
|
// guarantees every column reaches 1 by t==1, so each gem lands exactly on its
|
||||||
|
// cell and the seam to the next round / settled board stays invisible.
|
||||||
render_fall :: (self: *BoardView, ctx: *RenderContext, rd: *AnimRound, inset: f32, dim: f32, t: f32) {
|
render_fall :: (self: *BoardView, ctx: *RenderContext, rd: *AnimRound, inset: f32, dim: f32, t: f32) {
|
||||||
te := ease_in_cubic(t);
|
|
||||||
for 0..BOARD_CELLS: (i) {
|
for 0..BOARD_CELLS: (i) {
|
||||||
g := rd.after[i];
|
g := rd.after[i];
|
||||||
if g == .empty { continue; }
|
if g == .empty { continue; }
|
||||||
col := i % BOARD_COLS;
|
col := i % BOARD_COLS;
|
||||||
drow := i / BOARD_COLS;
|
drow := i / BOARD_COLS;
|
||||||
src := rd.src[i];
|
src := rd.src[i];
|
||||||
|
te := ease_in_cubic(fall_stagger_t(t, col));
|
||||||
cur_row := cast(f32) src + (cast(f32) drow - cast(f32) src) * te;
|
cur_row := cast(f32) src + (cast(f32) drow - cast(f32) src) * te;
|
||||||
gf := self.gem_frame(cast(f32) col, cur_row, inset, dim);
|
gf := self.gem_frame(cast(f32) col, cur_row, inset, dim);
|
||||||
self.draw_gem(ctx, gf, cast(s64) g);
|
self.draw_gem(ctx, gf, cast(s64) g);
|
||||||
|
|||||||
BIN
goldens/p17_stagger.png
Normal file
BIN
goldens/p17_stagger.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 MiB |
@@ -141,6 +141,41 @@ main :: () -> s32 {
|
|||||||
if !bb_overshoot_bounded { fails += 1; }
|
if !bb_overshoot_bounded { fails += 1; }
|
||||||
if !bb_settles { fails += 1; }
|
if !bb_settles { fails += 1; }
|
||||||
|
|
||||||
|
// 6. Per-column fall stagger (P17.2): the fall window offsets each column's drop
|
||||||
|
// START by a BOUNDED delay so a refilled row pours in as a cascade, yet EVERY
|
||||||
|
// column still lands EXACTLY on its cell by the segment end. Lock: at t==0 no
|
||||||
|
// column has moved; at t==1 EVERY column has reached local progress 1 (no gem
|
||||||
|
// left mid-air — the seam to the next round stays invisible); per-column local
|
||||||
|
// progress is monotonic in t; and MID-fall the columns form a cascade — each
|
||||||
|
// later column has made STRICTLY LESS progress than the one before (its drop
|
||||||
|
// starts later), the opposite of a flat lockstep row sharing one progress.
|
||||||
|
print("== fall stagger bounded ==\n");
|
||||||
|
stg_t0 := true; stg_t1 := true;
|
||||||
|
for 0..BOARD_COLS: (c) {
|
||||||
|
if fall_stagger_t(0.0, c) != 0.0 { stg_t0 = false; }
|
||||||
|
if fall_stagger_t(1.0, c) != 1.0 { stg_t1 = false; }
|
||||||
|
}
|
||||||
|
stg_cascade := true;
|
||||||
|
for 1..BOARD_COLS: (c) {
|
||||||
|
if !(fall_stagger_t(0.5, c) < fall_stagger_t(0.5, c - 1)) { stg_cascade = false; }
|
||||||
|
}
|
||||||
|
stg_mono := true;
|
||||||
|
for 0..BOARD_COLS: (c) {
|
||||||
|
pp := fall_stagger_t(0.0, c);
|
||||||
|
for 1..21: (i) {
|
||||||
|
tt := cast(f32) i / 20.0;
|
||||||
|
vv := fall_stagger_t(tt, c);
|
||||||
|
if vv < pp - 0.000001 { stg_mono = false; }
|
||||||
|
pp = vv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print("stagger_t0 {} stagger_t1 {} stagger_cascade {} stagger_mono {}\n",
|
||||||
|
stg_t0, stg_t1, stg_cascade, stg_mono);
|
||||||
|
if !stg_t0 { fails += 1; }
|
||||||
|
if !stg_t1 { fails += 1; }
|
||||||
|
if !stg_cascade { fails += 1; }
|
||||||
|
if !stg_mono { fails += 1; }
|
||||||
|
|
||||||
if fails == 0 {
|
if fails == 0 {
|
||||||
print("ok: easing toolkit endpoints locked + amplitudes bounded\n");
|
print("ok: easing toolkit endpoints locked + amplitudes bounded\n");
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -8,4 +8,6 @@ back_overshoots true back_bounded true spring_overshoots true spring_bounded tru
|
|||||||
squash_moves true squash_two_sided true squash_bounded true
|
squash_moves true squash_two_sided true squash_bounded true
|
||||||
== illegal-swap bounce ==
|
== illegal-swap bounce ==
|
||||||
bounce_ends true peak_amp true peak_loc true overshoots true overshoot_bounded true settles true
|
bounce_ends true peak_amp true peak_loc true overshoots true overshoot_bounded true settles true
|
||||||
|
== fall stagger bounded ==
|
||||||
|
stagger_t0 true stagger_t1 true stagger_cascade true stagger_mono true
|
||||||
ok: easing toolkit endpoints locked + amplitudes bounded
|
ok: easing toolkit endpoints locked + amplitudes bounded
|
||||||
|
|||||||
Reference in New Issue
Block a user