// Swipe→commit wiring golden (P5.2): prove the full input-to-model path P5.2 // adds — a touch drag resolved by `swipe_intent` and fed straight into // `commit_swap` — on the SAME seeded board the iOS app renders (SEED 1337). // Feeds SYNTHETIC down/up screen positions built from a BoardLayout, resolves // the swap intent, then commits it, asserting: // - an ILLEGAL swipe ((0,0)->(1,0): two reds → no match) reverts: the board is // byte-for-byte unchanged, score stays 0, and no move is spent; // - a known LEGAL swipe ((5,4)->(6,4): brings R into (5,4), completing R,R,R // across cols 3-5 of row 4) commits: the board changes, score accrues, and // exactly one move is spent. // No rendering, no model reach-around — it calls exactly what BoardView.handle_event // calls. Links headless like tests/swipe_intent.sx; avoids tests/test.sx because // its trace.sx pulls in a second `Frame` that collides with the UI one. Failure // is signalled via a non-zero exit code (the runner checks exit code AND stdout). #import "modules/std.sx"; #import "modules/ui/types.sx"; #import "board.sx"; #import "board_layout.sx"; #import "swipe.sx"; SEED :: 1337; cell_center :: (lay: *BoardLayout, col: i64, row: i64) -> Point { cf := lay.cell_frame(col, row); Point.{ x = cf.mid_x(), y = cf.mid_y() } } boards_equal :: (x: *Board, y: *Board) -> bool { for 0..BOARD_CELLS (i) { if !(x.cells[i] == y.cells[i]) { return false; } } true } main :: () -> i32 { // 800×600, no safe inset → 600px square grid, cell 75, origin (100, 0). A // 60px drag clears the cell*0.5 = 37.5px swipe threshold on the dominant axis. lay : BoardLayout = ---; lay.compute(Frame.make(0.0, 0.0, 800.0, 600.0), EdgeInsets.zero()); D : f32 = 60.0; fails : i64 = 0; // ── ILLEGAL swipe reverts ────────────────────────────────────────────── // (0,0) and (1,0) are both red on the seed board, so swapping them forms no // match. The rightward drag must resolve to exactly this pair, and // commit_swap must reject it — board untouched, score 0, no move spent. print("== illegal swipe reverts ==\n"); bi : Board = ---; bi.init(SEED); before : Board = bi; out(board_dump(@bi)); a_il := cell_center(@lay, 0, 0); if s := swipe_intent(@lay, a_il, Point.{ x = a_il.x + D, y = a_il.y }) { print("intent ({},{})->({},{})\n", s.a.col, s.a.row, s.b.col, s.b.row); if !(s.a.col == 0 and s.a.row == 0 and s.b.col == 1 and s.b.row == 0) { fails += 1; } mv := commit_swap(@bi, s.a, s.b); print("legal {} awarded {} score {} moves_made {} moves_remaining {}\n", mv.legal, mv.cascade.awarded, bi.score, bi.moves_made, mv.moves_remaining); if mv.legal { fails += 1; } if !boards_equal(@before, @bi) { fails += 1; } if bi.score != 0 { fails += 1; } if bi.moves_made != 0 { fails += 1; } if mv.moves_remaining != bi.move_limit { fails += 1; } } else { print("intent none\n"); fails += 1; } // ── LEGAL swipe commits ──────────────────────────────────────────────── // (5,4)->(6,4): the rightward swipe brings R into (5,4), completing R,R,R // across cols 3-5 of row 4. commit_swap applies it, resolves the cascade // (score accrues into Board.score) and spends one move; the board changes. print("== legal swipe commits ==\n"); bl : Board = ---; bl.init(SEED); pre : Board = bl; a_le := cell_center(@lay, 5, 4); if s := swipe_intent(@lay, a_le, Point.{ x = a_le.x + D, y = a_le.y }) { print("intent ({},{})->({},{})\n", s.a.col, s.a.row, s.b.col, s.b.row); if !(s.a.col == 5 and s.a.row == 4 and s.b.col == 6 and s.b.row == 4) { fails += 1; } mv := commit_swap(@bl, s.a, s.b); print("legal {} depth {} awarded {} score {} moves_made {} moves_remaining {}\n", mv.legal, mv.cascade.depth, mv.cascade.awarded, bl.score, bl.moves_made, mv.moves_remaining); if !mv.legal { fails += 1; } if boards_equal(@pre, @bl) { fails += 1; } if !(bl.score > 0) { fails += 1; } if bl.moves_made != 1 { fails += 1; } if mv.moves_remaining != bl.move_limit - 1 { fails += 1; } out("after:\n"); out(board_dump(@bl)); } else { print("intent none\n"); fails += 1; } if fails == 0 { print("ok: swipe reverts illegal, commits legal\n"); return 0; } print("FAIL: {} swipe-commit checks failed\n", fails); return 1; }