Produce the first deliverable of the Candy-Crush vibe pass: a bank of bright,
glossy, higher-pitched SFX WAVs under assets/audio/, all in the exact canonical
iOS System-Sound format clear.wav uses (mono, 44100 Hz, signed-16-bit PCM).
Bank: swap, match, combo1..combo5 (ascending pentatonic run C6 D6 E6 G6 A6),
win (ascending arpeggio), lose (descending stinger). Every cue sits above
clear.wav's ~784 Hz fundamental; combo1<..<combo5 step strictly upward
(1047<1175<1319<1568<1760 Hz). Each loads via AudioServicesCreateSystemSoundID
with status 0.
Synthesized by the build-time tools/synth_audio.py (pure-Python additive
synthesis; the app never runs it) and converted with afconvert. Pitch verified
with tools/measure_pitch.py. Provenance (CC0) recorded in LICENSE.txt. No sx
code changes — engine wiring is P10.2/P10.3.
Add a purely-visual, transient juice layer over a committed move — score
popups + a tinted particle/flash burst at the clears — with no change to the
model, score, moves, or settled board.
- assets/fx/particle.png: key the painted transparency checkerboard out of the
provided particle art to real alpha (8-connected border flood fill +
smooth luminance falloff that preserves the soft glow), downscaled to a
256x256 RGBA white sparkle. tools/key_particle.sx is the reproducible tool.
- board_fx.sx: BoardFx (live particle bursts + one "+points" popup) and
BoardFxAssets. The engine image path samples texture*white (no draw-time
tint), so the white sprite is tinted per gem colour at LOAD time into one
texture per colour; a burst animates by scale (grow -> shrink) and the soft
texture edges carry the fade. Combos (cascade depth > 1) burst bigger and the
popup is larger + gold. All driven by delta_time and self-pruning.
- board_anim.sx: AnimMove carries the model's cascade.awarded so the popup
shows the real payout without re-deriving any scoring in the view.
- board_view.sx / main.sx: wire BoardFx + the tinted assets, tick each frame,
spawn on a legal commit, and render bursts (clipped to the grid) under the
popups (drawn on top). Input-lock (BoardAnim.active) is untouched; FX never
gate input and may outlast the move slightly before vanishing.
Goldens (iPhone-17-class sim, iOS 26): p6_fx.png (combo: gold "+480" + bursts
mid-cascade), p6_fx_match.png (single match: "+30" + red burst), p6_fx_after.png
(settled board, FX fully gone). Gate: ios-sim build links, 15/15 logic tests
green (scoring/cascade goldens unchanged).
Add a real logic-test gate so future pure-sx game logic fails the build on a
bad assertion:
- tests/test.sx: `expect(cond, msg)` assert helper — prints a greppable
`FAIL <file>:<line>: <msg>` and exits non-zero via process.exit on failure.
- tools/run_tests.sh: snapshot runner mirroring sx/tests/run_examples.sh; runs
each tests/<name>.sx and diffs stdout + exit code against tests/expected/.
Exits 0 iff all tests pass.
- tests/arith.sx (+ expected snapshots): seed passing sanity test.
- README.md: document both halves of the gate — logic runner and the
reproducible ios-sim build/launch sequence (with device discovery).