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.
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.
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).
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.
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).
Extend the HUD to show the per-level goal (SCORE x / target) alongside
moves. When the model's level_status (P7.1) is won/lost, draw a centered
overlay banner ("YOU WIN!" / "OUT OF MOVES") with a "PLAY AGAIN" restart
button over the dimmed board; the banner appears once any winning/losing
cascade animation settles. Status is read from the model, never recomputed
in the view.
A finished level freezes board-cell input; only the restart button is live.
Its rect is derived from the shared BoardLayout grid (new BannerLayout), so
the hit-test lands exactly on the drawn button. A tap reseeds the same
starting level through board.restart and clears the transient view layers,
returning to a clean in_progress board.
Banner is text + rects only (honours colour/alpha; no draw-time image tint,
issue 0002). New env capture hooks (M3TE_TARGET / M3TE_MOVE_LIMIT /
M3TE_RESTART) force a terminal status / restart for deterministic goldens.
Tests: tests/banner_layout.sx locks the restart button rect <-> hit-test
round-trip headlessly. Goldens p7_win / p7_lose / p7_restart captured on the
iOS simulator.
New gem_anim.sx adds a purely-visual per-gem pose set driven by a single
animation clock: a calm always-on idle breath (scale-pulse + bob, per-gem
phase, ramped in from rest), a selection pop, a landing squash-bounce, and
a clear pop. BoardView draws every settled gem through gem_pose_at /
gem_pose_frame; the move timeline (P6.1) and FX (P6.2) are untouched and the
input-lock semantics are unchanged (idle never locks input).
Determinism: the idle is always-on, so main reads M3TE_ANIM_TIME=<seconds>
to freeze the clock at a chosen phase (t==0 == the resting board, so the
pre-P6.3 goldens reproduce) and M3TE_SELECT=<cellIndex> to force a selection
for capture. tests/gem_pose.sx locks the t==0-rest invariant and the reaction
envelopes headlessly (fails if the idle ramp is dropped).
Goldens (deterministic capture): p6_idle_t0 (resting), p6_idle_mid (pinned
mid-breath), p6_select (selection pop on cell 3,3). Purely visual: no change
to model/score/moves/hit-testing.
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).