Commit Graph

115 Commits

Author SHA1 Message Date
swipelab
5eaf91b22d 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).
2026-06-06 13:20:52 +03:00
swipelab
70a69864c1 Merge branch 'flow/m3te/P18.1' into m3te-plan 2026-06-06 13:05:26 +03:00
swipelab
54a7eba432 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.
2026-06-06 13:01:37 +03:00
swipelab
0a90dfff19 Merge branch 'flow/m3te/P17.3' into m3te-plan 2026-06-06 12:34:41 +03:00
swipelab
00b126d44c 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).
2026-06-06 12:29:11 +03:00
swipelab
eeffe7a7db Merge branch 'flow/m3te/P20.1' into m3te-plan 2026-06-06 11:51:28 +03:00
swipelab
ff88e4ab87 P20.1: FPS counter — env-gated dev overlay (M3TE_FPS), off by default
Add a small top-left FPS readout for gauging frame cost while tuning the
organic animations. Gated behind the M3TE_FPS startup env pin (read like the
other M3TE_* hooks); unset/=0 renders nothing, so default play and every
committed golden stay byte-identical.

- main.sx: g_fps_on (from M3TE_FPS) + g_fps_avg_dt, an EMA of delta_time
  (FPS_DT_SMOOTH=0.9) advanced only on the gated path; build_ui passes the
  smoothed FPS + flag into BoardView. delta_time is real wall-clock even when
  M3TE_ANIM_TIME pins the scene, so the counter stays live while frozen.
- board_view.sx: BoardView.fps_on/fps fields + render_fps_overlay — "FPS n"
  in the top-left safe-area corner (clear of notch/Dynamic Island + the HUD),
  dark grape text over a bright halo. Drawn last, only when fps_on.
- README.md: document M3TE_FPS (sim SIMCTL_CHILD_ + device devicectl env).
- goldens/p20_fps.png: FPS overlay over the resting board (M3TE_FPS=1,
  M3TE_ANIM_TIME=0); FPS digits are dynamic, rest pinned == p6_idle_t0 region.

Verified: ios-sim build + 22 logic tests green. Unset capture's board+HUD
region is byte-identical to goldens/p6_idle_t0.png; the only ON-vs-OFF delta
is the top-left FPS text box.
2026-06-06 11:46:51 +03:00
swipelab
d0c90a6833 Merge branch 'flow/m3te/P17.2' into m3te-plan 2026-06-06 11:39:03 +03:00
swipelab
02d856275c P17.2: organic fall — per-column stagger (cascade pour)
render_fall now offsets each COLUMN's drop START by a small bounded delay
(fall_stagger_t) so a refilled/collapsed row pours in as a left-to-right cascade
instead of every gem snapping down in one flat lockstep row. Column col waits
FALL_STAGGER_MAX (0.30) * col/7 of the fall window, then falls over the remaining
1 - 0.30, with that local progress fed through ease_in_cubic so each column still
accelerates under gravity within its own window.

Bounded by construction: 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.

FALL_ANIM_DUR (0.22s) and the timeline helpers (phase/total/cascade_rounds_started)
are unchanged, so the per-round cascade-cue timing snapshots don't churn and live
per-round audio is unaffected. Render-only — no board.sx model change.

tests/easing.sx pins fall_stagger_t: f(0)=0, f(1)=1 across all columns (no gem
unlanded), per-column monotonicity, and the mid-fall cascade ordering (each later
column strictly behind the one before). tests/anim_plan.sx (final==model,
contiguity) stays green.

Golden goldens/p17_stagger.png: M3TE_FX=11 (depth-5 cascade, seed 1337) pinned at
M3TE_ANIM_TIME=1.91 — round 4 refills columns 2-7 by one cell each, so the top row
reads as a left-to-right staircase (vs the pre-stagger flat row in p17_fall.png).
2026-06-06 11:35:36 +03:00
swipelab
8d4e7acd2b Merge branch 'flow/m3te/P17.1' into m3te-plan 2026-06-06 11:18:35 +03:00
swipelab
f68ed9a2b3 P17.1: organic fall — accelerate under gravity (ease_in_cubic)
render_fall now drives the per-round drop with ease_in_cubic (P15.1 accel-from-
rest) instead of ease_out_cubic, so falling gems start slow and accelerate into
place like gravity rather than decelerating. f(1)=1 is pinned, so every gem still
lands exactly on its destination cell and move.final is untouched. FALL_ANIM_DUR
(0.22s) is unchanged, so the cascade-cue timing snapshots don't churn.

Golden goldens/p17_fall.png: M3TE_FX=11 (depth-5 cascade, seed 1337) pinned at
M3TE_ANIM_TIME=1.51 (round 3 fall window [1.38,1.60)) — gems caught bunched-high
mid-fall, ~20% down at ~59% of the segment, vs the old curve's ~93%.
2026-06-06 11:15:10 +03:00
swipelab
fa2d4c09f2 Merge branch 'flow/m3te/P16.2' into m3te-plan 2026-06-06 11:05:45 +03:00
swipelab
4d06097b08 P16.2: organic illegal swap — springy bounce-back (+ M3TE_BADSWAP hook)
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).
2026-06-06 11:01:45 +03:00
swipelab
f9feecc8e2 Merge branch 'flow/m3te/P16.1' into m3te-plan 2026-06-06 10:50:00 +03:00
swipelab
8402f49503 P16.1: organic legal swap — overshoot/settle (ease_out_back)
Replace the legal swap's flat ease_out_cubic decelerating slide with the
P15.1 ease_out_back overshoot curve: the two swapped gems shoot ~10% PAST
their target cells, then settle exactly onto them. Purely visual — the curve
pins f(0)=0 / f(1)=1, so t==0 is the rest pose and t==1 lands byte-on-cell;
the committed move and final board are unchanged. SWAP_ANIM_DUR (0.16 s) is
untouched, so the cascade-cue timing snapshots do not churn.

Only the legal branch of render_swap changes; the illegal ping-back is left
as-is for P16.2. Model/logic untouched (FFI is the only non-sx surface).

Golden goldens/p16_swap.png: M3TE_FX=3 (top-row swap (5,0)<->(6,0)) pinned at
M3TE_ANIM_TIME=0.10 (swap-phase t~0.625, near the overshoot peak). Measured
against calibrated cell centers: the red lands ~8% left of col-5 and the green
~12% right of col-6 (both PAST target), while every unswapped gem stays
centered; at M3TE_ANIM_TIME=0 the same gems sit dead-on their pre-swap cells.

Gate: `sx build --target ios-sim main.sx` links clean; `tools/run_tests.sh`
22/22 (anim_plan, easing, cascade_rounds, cascade_cue all green).
2026-06-06 10:32:42 +03:00
swipelab
a2c5995fc2 Merge branch 'flow/m3te/P15.1' into m3te-plan 2026-06-06 10:19:02 +03:00
swipelab
1a8360ec1d P15.1: add extended easing toolkit + determinism snapshot (sx)
Pure, headless easing curves of t in [0,1] for the organic-animation pass
(swap/fall/combine juice), placed alongside the existing ease_out_cubic /
ease_in_quad in board_anim.sx: ease_in_cubic, ease_in_out_cubic, ease_out_back
(bounded overshoot, settles to exactly 1), spring (damped wobble to exactly 1),
and squash_envelope (signed squash-&-stretch landing shape). The math module has
no exp/pow, so the decaying curves use a (1-t)^n polynomial envelope that hits 0
at t==1, pinning f(1) precisely.

Additive only: no render code calls the new curves yet. tests/easing.sx locks,
per curve, the endpoints, overshoot/undershoot bounds, and monotonicity-where-
required (booleans only, so the snapshot is platform-stable), structured so P16.2
can append illegal-swap bounce-back assertions. Test count 21 -> 22.
2026-06-06 10:16:02 +03:00
swipelab
a62ddcf0b9 Merge branch 'flow/m3te/P14.1' into m3te-plan 2026-06-06 09:39:15 +03:00
swipelab
dbaebb779f P14.1: report sx std-library gaps surfaced by m3te 2026-06-06 09:34:42 +03:00
swipelab
f2a9579106 Merge branch 'flow/m3te/P13.1' into m3te-plan 2026-06-06 09:28:41 +03:00
swipelab
d043319d00 P13.1: fix F2 — retire stale procedural synth, doc the real Triple Treat DSP path
The README audio-regeneration section called tools/synth_audio.py the
'build-time DSP path', but that script is the original P10.1 procedural
synthesizer: its main() overwrites assets/audio/*.wav with note-frequency
synthetic audio. Following the documented path would clobber the curated
Triple Treat pack cues. Remove the script (cleanest resolution — kills the
clobber hazard) and rewrite the README to describe the actual production:
real pack clips (per-cue source in assets/audio/LICENSE.txt) down-mixed,
trimmed, faded, peak-normalized to ~-15 dBFS, re-wrapped via afconvert;
combo ladder = real Match FX ordered by brightness (P10.9); cascade one cue
per round (P10.10). measure_pitch.py kept (read-only verification, never
writes a WAV). WAVs/.sx/goldens byte-unchanged.
2026-06-06 09:23:20 +03:00
swipelab
5fa0a95cb4 P13.1: fix README cue-capture recipe — terminate before each env-pinned relaunch
The M3TE_* capture pins are read only at app startup, so a second `simctl
launch` against the still-running app reused its PID and silently ignored the
new pin (the win->lose cue recipe only ever produced `cue win`). Add
`--terminate-running-process` to every pinned launch across all capture recipes
and document the startup-only rule explicitly. Docs-only; no .sx change.
2026-06-06 09:12:10 +03:00
swipelab
2f5d60b9e1 P13.1: final vibe integration — playthrough validation, full golden sweep, finalized docs
Integration-only (no logic changes). Validated the full candy vibe in the booted
iPhone sim and brought every artifact in line with the shipped candy palette +
Triple Treat SFX bank.

Goldens — swept all 23:
- Refreshed 15 that predated the candy palette (P12): p6_idle_t0, p6_idle_mid,
  p6_select, p7_restart, p5_swap_before/after, p6_anim_swap/clear/fall/after,
  p6_fx, p6_fx_after, p6_fx_match, p6_inputlock_board, p11_combo_deep — re-captured
  via the documented M3TE_* hooks.
- Left 5 unchanged (board+HUD region byte-identical to the current build, verified
  by cropped-region hash): p4_board, p4_hud, p9_polish, p7_win, p7_lose.
- Removed 3 obsolete pre-board orange-quad goldens (app no longer renders them):
  p0_quad, p0_input_before, p0_input_after.

Docs — README.md:
- Section 2 now describes the candy board (not the old orange quad) and points at
  goldens/p6_idle_t0.png; dropped the removed p0_* references.
- Added the final audio model: Triple Treat SFX provenance + per-cue mapping, the
  per-round ascending cascade (one combo cue per round, clamped at combo5), the
  WAVE/mono/44100/Int16 @ -15 dBFS format spec, and the cue-log capture commands.
- Added image-art asset regeneration (codex imagegen via codex exec + sips
  normalize to exact per-asset dims/format).

Gate: ios-sim build links (exit 0); 21/21 pure-sx logic tests pass. Playthrough
evidence (cue NSLog ascending combo1..combo5 + win/lose stingers, screenshots)
captured in the worker report.
2026-06-06 08:57:26 +03:00
swipelab
211edbee76 Merge branch 'flow/m3te/P10.10' into m3te-plan 2026-06-06 08:41:27 +03:00
swipelab
51b3397ade P10.10: play one ascending combo cue per cascade round
For combos, play a sound for each match ascending (Candy-Crush cascade run):
as a chain resolves, EACH successive round plays the next higher cue
(combo1, combo2, … clamped at combo5) instead of a single combo cue keyed
to the final cascade depth at commit.

- board_anim.sx: add `BoardAnim.cascade_fired` (edge-trigger high-water mark,
  reset on init/begin) and the pure `cascade_rounds_started(elapsed, n)` helper
  — how many rounds have begun clearing on the swap→(clear,fall)* timeline.
- main.sx: in the frame loop, diff `cascade_rounds_started` against
  `cascade_fired` and play one ascending cue per newly-cleared round, once each,
  gated on a real multi-round chain (rounds >= 2). Additive; never touches
  board/score/move state.
- board_view.sx: drop the single `sfx_cascade(final depth)` at commit; keep
  `sfx_swap` / `sfx_match` (and win/lose) exactly as before.
- tests/cascade_rounds.sx: headless snapshot of the per-round timing + the
  ascending combo1..combo5 run with the combo5 clamp.

Sim (M3TE_FX=11, depth-5): log show shows combo1→combo2→combo3→combo4→combo5
at successive timestamps ~0.36s apart (= CLEAR+FALL per-round spacing).
2026-06-06 08:37:46 +03:00
swipelab
704ae08011 Merge branch 'flow/m3te/P10.9' into m3te-plan 2026-06-06 08:25:16 +03:00
swipelab
c3c5467723 P10.9: use real Match FX cues for the cascade combos
Replace the pitch-laddered single-pop combo ladder with FIVE distinct REAL
Match FX cues from the user's Triple Treat SFX pack (Match SFX/), ordered to
convey cascade escalation by spectral brightness / high-frequency energy
(combo1 dullest -> combo5 sparkliest):

  combo1 <- Match SFX/Match FX 2-RCM.wav  (dark/full,   centroid ~1.7 kHz)
  combo2 <- Match SFX/Match FX 4-RCM.wav  (warm mid,    centroid ~2.1 kHz)
  combo3 <- Match SFX/Match FX 6-RCM.wav  (bright mid,  centroid ~3.2 kHz)
  combo4 <- Match SFX/Match FX 7-RCM.wav  (rich+bright, centroid ~4.7 kHz)
  combo5 <- Match SFX/Match FX 3-RCM.wav  (sparkly,     centroid ~6.8 kHz)

The Match FX set does not cleanly pitch-ascend, so brightness is the ordering
signal; spectral centroid ascends monotonically 1.68 < 2.09 < 3.18 < 4.70 <
6.77 kHz. Each down-mixed to mono, trimmed to a 0.50 s onset window, eased in
(~6 ms) and rounded out with a 150 ms cosine fade-out, peak-normalized -15 dBFS.

Pure drop-in: audio.sx and all wiring are untouched; swap/match/win/lose are
byte-identical to P10.8. The depth->cue-index mapping (cascade_cue, fx_combo)
locks the integer mapping, not the audio content, so both tests stay valid.
LICENSE.txt combo provenance updated to the real Match FX sources.
2026-06-06 08:21:16 +03:00
swipelab
274d726002 Merge branch 'flow/m3te/P10.8' into m3te-plan 2026-06-06 07:55:00 +03:00
swipelab
28e32435f9 P10.8: rebuild the SFX bank from the user's Triple Treat SFX pack
Replace the 9-cue bank with best-fit selections from the user-provided
Triple_Treat_SFX.zip, converted to the engine format (mono / 44100 / Int16,
<= ~600 ms) and peak-normalized to a gentle, consistent -15 dBFS. Drop-in:
audio.sx and all wiring are untouched; only assets/audio/** changes.

Per-cue source within the pack:
  swap   <- Transition SFX/Swipe FX 1   (light swipe = the swap gesture)
  match  <- Pop:Bubble SFX/Pop FX 5     (juicy candy pop, first clear)
  combo1..5 <- Pop:Bubble SFX/Pop FX 3  (one pop pitch-laddered +0/+2/+4/+7/+9
               semitones; the pack's Match set does not ascend monotonically)
  win    <- Success:Power-Up SFX/Power Up FX 1 (short triumphant)
  lose   <- Fail SFX/Fail FX 2          (gentle tonal, not boomy)

combo1..5 ascend in fundamental: 687 < 771 < 865 < 1029 < 1155 Hz. The 30 MB
pack and its .meta/__MACOSX cruft are not committed; LICENSE.txt records the
exact per-cue source file within the pack.
2026-06-06 07:51:36 +03:00
swipelab
eca994f454 Merge branch 'flow/m3te/P10.7' into m3te-plan 2026-06-06 06:55:54 +03:00
swipelab
e4a502e922 P10.7: soften the SFX bank (gentler / less aggressive)
DSP-soften the existing P10.6 CC0 bank in place — same filenames, same
canonical WAVE/mono/44100/Int16 format, drop-in (engine untouched):

- Onset eased in: short qsin fade-in (8-14 ms) tames the attack transient
  so pops bloop instead of snap (onset peak in first 20 ms down 5.6-8.7 dB
  per cue; combo attack-to-peak 1.4-2.4 ms -> 15-26 ms).
- Highs rolled off: warm two-pole low-pass (2.6 kHz swap/lose, 3.0 kHz
  match/combo, 3.6 kHz win) for a rounded tone. Spectral centroid down
  ~40-60%; >4 kHz energy collapses (win 76%->28%, combos ~10%->0.5%).
- Quieter: re-normalized to -15.5 dBFS (swap/lose -17.5), down from ~-9/-12,
  lowering both peak and RMS on every cue.

Candy character retained; cascade ladder preserved (combo1..5 fundamentals
still ascend 1045<1173<1317<1566<1758 Hz). LICENSE provenance updated.
2026-06-06 06:52:51 +03:00
swipelab
d4de4f2b8e Merge branch 'flow/m3te/P10.6' into m3te-plan 2026-06-05 23:24:29 +03:00
swipelab
f3e3876574 P10.6: tune SFX bank to the candy character (real CC0 marimba/glock + pops)
Refine the same drop-in bank toward Candy-Crush candy/jelly character; engine
(audio.sx) untouched, same filenames/format.

- swap: freesound CC0 "Pop 01" (LilMati #506546) — soft light blip.
- match: freesound CC0 "Cartoon Pop" (Mish7913 #741368) — bright juicy candy pop.
- combo1..5: VCSL CC0 Marimba F5, real-resample pitch-laddered to a C-major
  pentatonic (C5 D5 E5 G5 A5) — warm/sweet mallet sparkle, not glassy; ascending.
- win: VCSL CC0 Glockenspiel C5/G5/C6 sequenced into a sugary ascending bell
  fanfare.
- lose: VCSL CC0 Marimba B4->G4 descending — soft gentle "aww".

All real free CC0 assets (NOT ripped from Candy Crush); provenance + license per
cue in LICENSE.txt. Mono/44100/Int16, <=580 ms, peak <=-9 dBFS (lose -12).
Combos ascend (1045<1173<1317<1566<1758 Hz). Gate green; all 9 cues load on sim.
2026-06-05 23:20:50 +03:00
swipelab
61fbfb5d79 Merge branch 'flow/m3te/P10.5' into m3te-plan 2026-06-05 22:47:06 +03:00
swipelab
ae44e5b7fb P10.5: replace synth SFX bank with real CC0 Kenney assets (quieter, cleaner)
QA: "the sfx is too loud and scratchy." Drop-in asset swap — no engine change.

Replace the 9 synthesized cues (swap, match, combo1..combo5, win, lose) with
real free-licensed (CC0 1.0) sound effects from Kenney's Interface Sounds /
Digital Audio packs:
  swap  <- pluck_002        match <- confirmation_002
  win   <- powerUp7         lose  <- minimize_006
  combo1..5 <- glass_001 pitch-laddered up a pentatonic run (0,+2,+4,+7,+9 st)
              via resample DSP -> ascending fundamentals 1918..3226 Hz.

Every cue: mono / 44100 Hz / Int16 PCM, <=0.54 s. Whole bank peak-normalized to
-9 dBFS, so peak AND RMS are well below the old synth (old peak -0.7..-2.9 dB,
mean -5.8..-9.3 dB; new peak -9.0 dB, mean -22..-30 dB). Audibly cleaner — no
synth harmonics.

LICENSE.txt rewritten with real per-file provenance (source URL + CC0). clear.wav
(already a CC0 Kenney clip, not loaded by the engine) left unchanged. Engine
(audio.sx/board_view.sx/main.sx) untouched; sim build loads all 9 cues, zero
"load failed".
2026-06-05 22:41:52 +03:00
swipelab
c5c18ef62e Merge branch 'flow/m3te/P12.3' into m3te-plan 2026-06-05 22:07:44 +03:00
swipelab
5e78d25d8b P12.3: candy clear colour for palette cohesion (sx / iOS)
Retune the GPU clear colour on both render paths (Metal on iOS, GL on
desktop) from the old dark navy (0.05,0.06,0.10) to a candy-lavender tone
sampled from the regenerated background's mid-gradient. The dark navy was
the one off-palette constant left after P12.1/P12.2; the background art
covers the full drawable in steady state, so it only ever surfaced as a
pre-load flash / potential dark seam. A single shared CLEAR_R/G/B keeps the
two paths from diverging.

Refresh the stale full-board goldens (p4_board, p9_polish), which still
showed the pre-P12.2 dark HUD, to the current cohesive candy palette.
2026-06-05 22:05:01 +03:00
swipelab
9a7a2003ef Merge branch 'flow/m3te/P12.2' into m3te-plan 2026-06-05 21:55:45 +03:00
swipelab
7d18ba7e4d P12.2: candy HUD & win/lose banner restyle (sx / iOS)
Restyle the code-drawn UI toward the candy look — colours, corner-rounding
and glossy feel only; no rect geometry moves.

- HUD: grape candy card with a glossy top sheen, a bright rounded rim and
  warm cream text on a soft purple shadow (was a flat dark translucent panel).
- Banner panel: grape candy fill under a sheen + bright rim, rounder corners.
- Titles: celebratory candy gold YOU WIN! / punchy coral OUT OF MOVES, each
  on a tinted drop shadow for pop.
- PLAY AGAIN: bubblegum candy button with a glossy sheen, bright rim and a
  darker bevel lip for a 3D candy edge.

BannerLayout rects (panel/title/button) and the restart hit-test are
untouched, so tests/banner_layout still passes. Refresh the p4_hud / p7_win /
p7_lose goldens.
2026-06-05 21:52:00 +03:00
swipelab
246dcfa224 Merge branch 'flow/m3te/P12.1' into m3te-plan 2026-06-05 21:44:48 +03:00
swipelab
5695974283 P12.1: brighter candy background & cell tile (sx / iOS)
Regenerate the board art in a Candy-Crush palette via real image
generation (codex imagegen tool), keeping each PNG's exact dims and
source format so no code/layout changes are needed:

- background.png: 863x1822 opaque RGB — bright bubblegum-pink ->
  lavender -> sky-blue candy gradient with soft bokeh sparkles
  (was the dark purple/indigo/teal gradient).
- cell.png: 128x128 RGBA — light glossy frosted candy tile with a
  warm pink/lavender tint, keeping it light so the 6 gems stay legible.

Generated at imagegen-native sizes, then center-cropped (background, to
preserve aspect) and resized with sips to the exact target dims/format.
Refresh the resting-board goldens (p9_polish, p4_board) captured at
M3TE_ANIM_TIME=0 to show the new palette with all 6 gems legible.
2026-06-05 21:34:45 +03:00
swipelab
8bbb060a67 Merge branch 'flow/m3te/P11.3' into m3te-plan 2026-06-05 21:19:51 +03:00
swipelab
cd8667d170 P11.3: glossier candy gem & selection feel (sx / iOS)
Make the selection highlight read as glossy candy without new art and
without disturbing the idle-rest invariant. board_view's render_selection
layers a soft outward glow (two concentric stroked rings — the renderer has
no blur), a warm wash, a bright rim doubled by a thin inner highlight for a
glassy edge, and a wet sheen that rides the selected gem's live pose. Every
layer is a rect/overlay (issue 0002 forbids a draw-time gem-texture tint).

The gloss is selection-only: render_selection runs solely when a cell is
selected, so the resting board (no selection) is byte-identical to before
and the t==0 idle pose stays exactly the static sprite (locked by
tests/gem_pose). The selection-pop motion still comes from gem_anim; no
board / score / move state changes and input stays gated by BoardAnim.active.

Updated goldens/p6_select.png; README documents the P11.3 selection gloss
and its reproduce commands (reusing the P6.3 M3TE_SELECT + M3TE_ANIM_TIME
hooks).
2026-06-05 21:15:44 +03:00
swipelab
a8629c378b Merge branch 'flow/m3te/P11.2' into m3te-plan 2026-06-05 20:55:37 +03:00
swipelab
0b293a2c48 P11.2: escalating combo emphasis tied to cascade depth (sx / iOS)
Scale the combo FX with cascade depth (mv.rounds.len) — the same depth the
cascade SFX (play_cascade) steps up on — so deeper cascades read as more
exciting and land in lockstep with the audio escalation. Purely visual and
self-pruning: no board / score / move state changes, and input stays gated by
BoardAnim.active alone.

- board_fx.sx: add fx_combo_level (mirrors audio's cascade_cue_index clamp:
  depth<=1 -> floor, depth>=5 -> ceiling). The +points popup now carries the
  cascade depth and grows one font step + lerps gold -> hot-gold per level
  (fx_popup_font / fx_popup_color). Every burst of a deep cascade gets a
  whole-move depth boost (FX_BURST_DEPTH) on top of the existing per-round bump.
- board_view.sx: render_fx_popups derives styling from depth and tops a combo
  with a "COMBO xN" label naming the true cascade depth.
- tests/fx_combo.sx: headless snapshot locking the depth->level/font table and
  asserting fx_combo_level matches the cascade-cue index column entry-for-entry.
- goldens/p11_combo_deep.png + README: deterministic depth-5 capture (M3TE_FX=11)
  vs the depth-1 single clear (M3TE_FX=3); FX gone after settle at a later phase.
2026-06-05 20:51:56 +03:00
swipelab
b68b60a537 Merge branch 'flow/m3te/P11.1' into m3te-plan 2026-06-05 20:40:50 +03:00
swipelab
b65e592a8c P11.1: juicier match pops & brighter bursts (sx / iOS)
Visual-juice vibe-pass, FX-only — no logic/state changes, input gating
still owned by BoardAnim.active.

- board_fx.sx: bigger, punchier match bursts — peak size 1.95->2.50 cells,
  combo bonus 0.55->0.72, and the per-gem fx tints saturated a touch (low
  channel trimmed, dominant/mid lifted) so every burst pops as a brighter,
  more vivid candy colour. The hot per-pixel tint loop's hoisted locals are
  preserved (issue 0001).
- gem_anim.sx: snappier clear pop — faster rise (0.30->0.18 of the window)
  to a bigger overshoot (CLEAR_POP_A 0.22->0.34) so the matched-gem clear
  reads as a candy snap. gem_pose's clear-pop invariants still hold.
- main.sx: M3TE_FX=<n> deterministic match-FX capture hook, mirroring the
  M3TE_SELECT pattern. Commits the n-th currently-legal swap at startup via
  the normal plan_and_commit path and begins the move timeline + burst/popup
  FX; M3TE_ANIM_TIME pins the phase and the frame loop holds the move/FX
  frozen while pinned, so the burst + "+points" screenshot identically every
  run. A larger M3TE_ANIM_TIME captures the settled, FX-gone board. Startup-
  only and guarded, so normal play is untouched.
- README.md: document the new M3TE_FX pin alongside the other capture hooks.
- goldens/p6_fx_match.png: updated deterministic golden (iOS 26 sim,
  SIMCTL_CHILD_M3TE_FX=3 SIMCTL_CHILD_M3TE_ANIM_TIME=0.22) — the vertical
  red 3-match, burst region +1.4% mean luminance / 3.2:1 brighter:dimmer vs
  the same scene on the pre-juice constants.

Gate: ios-sim build links, 19/19 logic tests green (incl. gem_pose t=0 rest).
2026-06-05 20:36:25 +03:00
swipelab
f8268a6171 Merge branch 'flow/m3te/P10.4' into m3te-plan 2026-06-05 20:08:43 +03:00
swipelab
c35c63d8a9 P10.4: snapshot-test cascade-cue depth->index mapping (sx)
Add tests/cascade_cue.sx, a headless logic test that reuses audio.sx's
pure cascade_cue_index / cascade_cue_name (the exact functions
play_cascade calls) to lock the cascade-depth -> combo-cue clamp:
depth <= 1 -> combo1, depth >= COMBO_CLIPS -> combo5, monotonic between.
Covers the escalation logic the audio playback path can't gate-cover,
with no audio. Committed stdout/exit snapshot; suite is now 19 tests.
2026-06-05 20:06:21 +03:00
swipelab
672ce63628 Merge branch 'flow/m3te/P10.3' into m3te-plan 2026-06-05 20:03:19 +03:00