No description
  • Shell 48.5%
  • Python 22.6%
  • HTML 14.7%
  • C 14.2%
Find a file
2026-08-11 18:33:04 +03:00
.vscode Seed m3te repo 2026-07-23 23:32:22 +03:00
assets app store 2026-08-11 18:33:04 +03:00
goldens Fix menu-game slide transition to actually translate pages 2026-08-06 03:14:18 +03:00
src macos metal 2026-08-11 13:28:14 +03:00
store/appstore app store 2026-08-11 18:33:04 +03:00
tests Fix selected gem invisibility on live path 2026-08-06 03:23:05 +03:00
tools Fix selected gem invisibility on live path 2026-08-06 03:23:05 +03:00
.gitignore Seed m3te repo 2026-07-23 23:32:22 +03:00
build.sx macos metal 2026-08-11 13:28:14 +03:00
main.sx macos metal 2026-08-11 13:28:14 +03:00
README.md Fix selected gem invisibility on live path 2026-08-06 03:23:05 +03:00
shell.html Seed m3te repo 2026-07-23 23:32:22 +03:00

m3te

A candy match-3 game written entirely in sx, running on iOS, Android, macOS, and web (wasm). Game logic, rendering, input, UI, audio wiring, and animation are all sx; the only non-sx surface is FFI to system frameworks (UIKit/Metal/AudioToolbox on iOS, AAudio on Android, SDL3 on macOS/wasm).

Play: swipe adjacent gems, clear matches, cascade for combos, beat the score goal before moves run out. Ten hand-tuned levels with unlock progression and a best-run high score.

Requires the sx compiler on PATH, or set SX to the binary (e.g. export SX=/path/to/sx). Run everything from the repo root.


Gate

The gate commands verify cross-target compilation and pure-sx test suite:

export PATH="$HOME/projects/sx/zig-out/bin:$PATH"
sx build --target ios-sim main.sx
bash tools/run_tests.sh
sx build main.sx
sx build --target aarch64-linux-android26 main.sx
sx build --target wasm main.sx

Logic tests: any tests/<name>.sx with a tests/expected/<name>.exit marker. Regenerate after intentional changes: bash tools/run_tests.sh --update.


Build & run

iOS Simulator

sx build --target ios-sim main.sx
udid=$(xcrun simctl list devices booted | grep -Eo '[0-9A-Fa-f-]{36}' | head -1)
xcrun simctl install "$udid" sx-out/ios/M3te.app
xcrun simctl launch --terminate-running-process "$udid" co.swipelab.m3te
sleep 2
xcrun simctl io "$udid" screenshot /tmp/m3te.png

If more than one simulator is Booted, pass $udid explicitly (booted is ambiguous). Always wait ~2s after launch before screenshotting to bypass the white launch screen. Goldens compare the board + HUD region, not the status strip (which differs per grab).

Android (API 26+ — AAudio)

sx build --target aarch64-linux-android26 main.sx
adb install -r sx-out/android/m3te.apk
adb shell monkey -p co.swipelab.m3te -c android.intent.category.LAUNCHER 1
adb exec-out screencap -p > /tmp/m3te_android.png

The aarch64-linux-android26 target is required for libaaudio linking. Cue logs: adb logcat -d -s sx.

macOS

sx build main.sx
./sx-out/macos/M3te          # run from repo root so assets/ resolves

Web (wasm)

sx build --target wasm main.sx
(cd sx-out/wasm32 && python3 -m http.server 8000)   # open localhost:8000

Audio starts suspended (browser autoplay) and resumes on the first tap. Also builds for 64-bit web target via sx build --target wasm64-unknown-emscripten main.sx.


Architecture & View Layer

main.sx initializes UiRoot and UIRenderer runtimes, passing input events into the compose Router.

  • Compose View Layer: All UI screens, panels, buttons, chips, and banners build as compose trees (View hierarchy).
  • Candy Components (src/views/candy.sx): Design system providing glossy candy fills, bevel lips, top sheens, bright rims, shadowed labels, and custom layout frames.
  • Compose Router: Input taps route through tappable compose components and event handlers.
  • App Compositor (src/views/app_view.sx): Eased lock-step sliding page transition (AppSlide) between menu.sx and board_view.sx with depth parallax background.
  • Custom Rendering: Custom rendering is reserved strictly for grid cell tiles/gems (BoardGridView), transient match particle FX (FxOverlay), and background ambient decorations (orbs/sparkles).

Layout

Path Role
main.sx Entry, frame loop, GPU setup, UiRoot + UIRenderer runtime, M3TE_* capture hooks
build.sx Per-target build / bundle config
src/game/ Headless model: board, layout, anim, FX, levels, session, swipe
src/views/ Compose view layer: candy.sx, menu.sx, board_view.sx, app_view.sx
src/audio/ SFX + BGM mixer; iOS / AAudio / SDL backends
src/save/ {unlocked, best, sfx, music} — file / localStorage
tests/ Pure-sx snapshots (tools/run_tests.sh)
goldens/ Reference screenshots (not pixel-exact)
tools/regen_goldens.sh Regenerate all goldens via capture hooks (needs a booted sim)

Capture hooks

Startup-only env vars (normal play is untouched). On simctl launch, prefix with SIMCTL_CHILD_ and pass --terminate-running-process so a fresh process re-reads the pins.

var effect
M3TE_ANIM_TIME=t Freeze animation clock at phase t (0 = rest)
M3TE_TRANS=t Mid-slide at progress t (menu to game)
M3TE_SELECT=i Force-select cell i (row*8+col), with ts=0 (pinned to clock)
M3TE_SELECT_LIVE=i Force-select cell i, back-dating since so ts=1.0 (simulates live path)
M3TE_FX=n Commit n-th legal swap + FX/audio (3 = single match, 11 = depth-5 cascade on seed 1337)
M3TE_BADSWAP=n Commit n-th illegal adjacent pair → bounce-back (41 on seed 1337)
M3TE_TARGET=0 Force level won
M3TE_MOVE_LIMIT=0 Force level lost
M3TE_RESTART=1 Restart board after overrides
M3TE_FPS=1 Show corner FPS overlay

Example (resting board golden):

SIMCTL_CHILD_M3TE_ANIM_TIME=0 xcrun simctl launch --terminate-running-process \
  "$udid" co.swipelab.m3te
sleep 2 && xcrun simctl io "$udid" screenshot goldens/p6_idle_t0.png

Full scenario list: bash tools/regen_goldens.sh. Cue order on device:

xcrun simctl spawn "$udid" log show --last 30s \
  --predicate 'eventMessage CONTAINS "[sx] audio"' --style compact

On macOS, pass hooks in the shell env (M3TE_FX=11 ./sx-out/macos/M3te).


Assets

  • SFX — curated Triple Treat pack clips (not synthesized): mono WAV 44100/Int16. Provenance per cue in assets/audio/LICENSE.txt. Read-only pitch check: tools/measure_pitch.py. Do not regenerate with a synth script.
  • Musicassets/audio/music.wav (CC0 “Happy” by Alex McCulloch). Toggle on the main menu with SFX.
  • Art — candy gems / background under assets/. Refresh goldens via tools/regen_goldens.sh after art changes.

Conventions

  • Everything in sx; FFI only for system frameworks.
  • Capture hooks are startup-only and env-guarded.
  • Goldens are visual references — compare board + HUD, not the status strip.