P8.1: minimal match/clear SFX via iOS System Sound Services (sx FFI)

Feasibility spike outcome: iOS audio from sx is feasible with no sx-library
change. System Sound Services is plain C, reached with the same `#foreign`
FFI uikit.sx already uses (UIApplicationMain / dlsym / CACurrentMediaTime);
AudioToolbox + CoreFoundation are linked per-target in build.sx.

Smallest viable SFX: one short CC0 clip (Kenney Interface Sounds, CC0 1.0)
played when a swap clears a match. Purely additive — audio.sx reads/writes
no score/board/move state; the wiring in board_view only adds a call.

- audio.sx: load clear.wav once, AudioServicesPlaySystemSound on clear
- board_view.sx: trigger sfx_clear() on a legal swap that clears (>=1 round)
- main.sx: allocate + init g_audio at boot
- build.sx: link AudioToolbox + CoreFoundation on iOS
- assets/audio/clear.wav (+ one-line CC0 credit in LICENSE.txt)

Verified: ios-sim build links; 18/18 tests pass; sim boot log shows
"[sx] audio: clear cue loaded" (AudioServicesCreateSystemSoundID succeeded,
asset shipped in the bundle and decoded).
This commit is contained in:
swipelab
2026-06-05 18:19:33 +03:00
parent 0a3cd1561b
commit f0a13293bb
6 changed files with 115 additions and 0 deletions

View File

@@ -21,6 +21,7 @@
#import "board_fx.sx";
#import "gem_anim.sx";
#import "swipe.sx";
#import "audio.sx";
// Fraction of a cell each gem occupies; the remainder is margin so a gem sits
// inside its cell tile rather than touching the tile's edges.
@@ -622,6 +623,10 @@ impl View for BoardView {
mv := plan_and_commit(self.board, intent.a, intent.b);
if self.anim != null { self.anim.begin(mv); }
if self.fx != null { self.fx.begin(@mv); }
// SFX (P8.1). Additive only — plays a short cue when a swap
// actually clears a match; reads no score/board state and
// writes none. A legal move has >=1 cascade round.
if mv.legal and mv.rounds.len > 0 { sfx_clear(); }
self.sel.clear();
} else {
if hit := self.layout.point_to_cell(start) {