P7.2: goal HUD + win/lose banner + restart button (sx, iOS sim)

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.
This commit is contained in:
swipelab
2026-06-05 14:57:27 +03:00
parent bf38c7a100
commit 5be379f180
10 changed files with 284 additions and 7 deletions

14
main.sx
View File

@@ -82,7 +82,7 @@ g_anim_prev_active : bool = false;
// Rebuilt each frame inside the pipeline's arena; carries the current safe-area
// insets so the grid stays inside the notch / home-indicator region.
build_ui :: () -> View {
BoardView.{ board = g_board, assets = g_assets, sel = g_sel, drag = g_drag, anim = g_anim, fx = g_fx, fxassets = g_fxassets, motion = g_motion, safe = g_safe_insets }
BoardView.{ board = g_board, assets = g_assets, sel = g_sel, drag = g_drag, anim = g_anim, fx = g_fx, fxassets = g_fxassets, motion = g_motion, safe = g_safe_insets, seed = BOARD_SEED }
}
// Deterministic capture (P6.3). The idle loop is always-on, so a live screenshot
@@ -300,6 +300,18 @@ main :: () -> void {
}
}
// Level-state capture hooks (P7.2): override the goal / move budget so a
// terminal status can be screenshot without scripting a swipe. M3TE_TARGET=0
// makes the fresh board read WON immediately (score 0 ≥ goal 0);
// M3TE_MOVE_LIMIT=0 makes it read LOST (budget spent below the goal). With
// M3TE_RESTART set non-zero the board is then restart()-ed, capturing the
// fresh in_progress board the restart button produces.
if tg := read_env("M3TE_TARGET") { g_board.target_score = parse_s64(tg); }
if ml := read_env("M3TE_MOVE_LIMIT") { g_board.move_limit = parse_s64(ml); }
if rs := read_env("M3TE_RESTART") {
if parse_s64(rs) != 0 { g_board.restart(BOARD_SEED); }
}
g_pipeline.set_body(closure(build_ui));
g_plat.run_frame_loop(closure(frame));