P6.1: swap/clear/fall move tweens (sx, iOS sim)
Add a purely-visual animation timeline so the board no longer snaps on a move. board_anim.sx records, on a value-copy of the pre-move board, the swap and each cascade round's matched cells + per-column fall provenance, then BoardView plays it over delta_time: the two swapped gems SLIDE between cells (and ping out-and-back on an illegal swap), matched gems SCALE OUT, and survivors FALL into place while refills drop in from above the grid. The model stays authoritative: plan_and_commit still calls commit_swap on the real board exactly as before, and the recording replays the identical primitives from the identical cells + RNG state, so the timeline ends ON the model's settled board. tests/anim_plan.sx is the determinism guard — it asserts the committed board, score, moves, and the timeline's final state all equal an independent commit_swap of the same move, that the rounds are contiguous, and that an illegal swap records nothing and leaves the board untouched. All pre-existing logic/cascade goldens stay green. Evidence (sx-test-metal, iOS 26.0, time-sampled with temporarily-lengthened durations; committed durations are the short production values): goldens/p6_anim_swap.png gems sliding between (5,4)/(6,4) goldens/p6_anim_clear.png matched reds scaling out in row 4 goldens/p6_anim_fall.png gems mid-fall with gaps + refill dropping in goldens/p6_anim_after.png settled board == model (SCORE 30, MOVES 29/30)
This commit is contained in:
17
main.sx
17
main.sx
@@ -15,6 +15,7 @@
|
||||
#import "modules/platform/uikit.sx";
|
||||
#import "board.sx";
|
||||
#import "board_view.sx";
|
||||
#import "board_anim.sx";
|
||||
|
||||
#run configure_build();
|
||||
|
||||
@@ -24,6 +25,7 @@ BOARD_SEED :: 1337;
|
||||
|
||||
g_plat : Platform = ---;
|
||||
g_pipeline : *UIPipeline = ---;
|
||||
g_delta_time : f32 = 0.016;
|
||||
g_viewport_w : f32 = 800.0;
|
||||
g_viewport_h : f32 = 600.0;
|
||||
g_safe_insets : EdgeInsets = .{};
|
||||
@@ -49,14 +51,20 @@ g_sel : *BoardSelection = null;
|
||||
// so the drag start must persist between them.
|
||||
g_drag : *DragInput = null;
|
||||
|
||||
// In-flight move animation (P6.1). Heap-allocated for the same reason: a swipe
|
||||
// begins the swap/clear/fall timeline, which then plays out over many subsequent
|
||||
// frames, so the timeline state must persist across BoardView's per-frame rebuild.
|
||||
g_anim : *BoardAnim = null;
|
||||
|
||||
// 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, safe = g_safe_insets }
|
||||
BoardView.{ board = g_board, assets = g_assets, sel = g_sel, drag = g_drag, anim = g_anim, safe = g_safe_insets }
|
||||
}
|
||||
|
||||
frame :: () {
|
||||
fc := g_plat.begin_frame();
|
||||
g_delta_time = fc.delta_time;
|
||||
g_viewport_w = fc.viewport_w;
|
||||
g_viewport_h = fc.viewport_h;
|
||||
g_safe_insets = g_plat.safe_insets();
|
||||
@@ -76,6 +84,10 @@ frame :: () {
|
||||
g_pipeline.dispatch_event(ev);
|
||||
}
|
||||
|
||||
// Advance the in-flight move animation by this frame's delta before rendering,
|
||||
// so the board view draws the timeline slice for the current wall-clock time.
|
||||
if g_anim != null { g_anim.tick(g_delta_time); }
|
||||
|
||||
inline if OS == .ios {
|
||||
// Lazy-attach Metal once -[SxAppDelegate didFinishLaunching:] has
|
||||
// installed the SxMetalView and its bounds have been measured; both can
|
||||
@@ -152,6 +164,9 @@ main :: () -> void {
|
||||
g_drag = xx context.allocator.alloc(size_of(DragInput));
|
||||
g_drag.init();
|
||||
|
||||
g_anim = xx context.allocator.alloc(size_of(BoardAnim));
|
||||
g_anim.init();
|
||||
|
||||
g_pipeline.set_body(closure(build_ui));
|
||||
|
||||
g_plat.run_frame_loop(closure(frame));
|
||||
|
||||
Reference in New Issue
Block a user