Files
m3te/tests/swipe_intent.sx
swipelab f290e2614a P5.1: drag → adjacent-swap intent mapping (pure sx)
Add swipe.sx: a pure swipe_intent(layout, start, end) -> ?Swap that turns a
touch drag into an optional adjacent-swap intent (A, B). A is the cell under the
drag start (via BoardLayout.point_to_cell); B is its orthogonal neighbour along
the drag's dominant axis (larger of |dx|,|dy|, ties horizontal). Returns null for
sub-threshold drags (a tap, threshold = cell_size * 0.5), starts off the board,
or neighbours off the board. No rendering, no model mutation.

Lock the logic with tests/swipe_intent.sx (+ expected golden), feeding synthetic
down/up positions for right/left/up/down, sub-threshold tap, diagonal→dominant
(both axes), edge-outward off-board, and start-off-board. bash tools/run_tests.sh
passes (13/13); sx build --target ios-sim main.sx compiles.
2026-06-05 00:14:44 +03:00

83 lines
4.0 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Swipe-intent golden (P5.1): lock the pure drag → adjacent-swap mapping
// `swipe_intent`. Feeds SYNTHETIC down/up screen positions (built from the SAME
// BoardLayout the renderer uses) and asserts the resolved swap intent (A, B), so
// a drift in axis/direction/threshold logic changes both the dump and the exit
// code. No rendering, no model mutation — links headless like tests/hit_test.sx.
//
// Layout: 800×600, no safe inset → 600px square grid, cell 75, origin (100, 0);
// swipe threshold = cell_size * 0.5 = 37.5px on the dominant axis. A 60px drag
// clears it; a 10px drag does not. Failure is signalled via a non-zero exit code
// (the runner checks exit code AND stdout).
#import "modules/std.sx";
#import "board.sx";
#import "board_layout.sx";
#import "swipe.sx";
cell_center :: (lay: *BoardLayout, col: s64, row: s64) -> Point {
cf := lay.cell_frame(col, row);
Point.{ x = cf.mid_x(), y = cf.mid_y() }
}
// Print the resolved intent (locked in the golden) and report whether it matches
// the expected adjacent pair (A, B). Drives the exit code alongside the dump.
expect_swap :: (label: string, got: ?Swap, ac: s64, ar: s64, bc: s64, br: s64) -> bool {
if s := got {
print("{}: ({},{})->({},{})\n", label, s.a.col, s.a.row, s.b.col, s.b.row);
return s.a.col == ac and s.a.row == ar and s.b.col == bc and s.b.row == br;
}
print("{}: none\n", label);
false
}
expect_none :: (label: string, got: ?Swap) -> bool {
if s := got {
print("{}: ({},{})->({},{})\n", label, s.a.col, s.a.row, s.b.col, s.b.row);
return false;
}
print("{}: none\n", label);
true
}
main :: () -> s32 {
lay : BoardLayout = ---;
lay.compute(Frame.make(0.0, 0.0, 800.0, 600.0), EdgeInsets.zero());
print("grid origin ({},{}) cell {} threshold {}\n",
cast(s64) lay.origin.x, cast(s64) lay.origin.y, cast(s64) lay.cell_size,
cast(s64) (lay.cell_size * SWIPE_THRESHOLD_FRACTION));
fails : s64 = 0;
// A known interior cell; every cardinal swipe from it stays on the board.
start := cell_center(@lay, 3, 5);
D : f32 = 60.0; // dominant-axis travel, well above the 37.5 threshold
if !expect_swap("right", swipe_intent(@lay, start, Point.{ x = start.x + D, y = start.y }), 3, 5, 4, 5) { fails += 1; }
if !expect_swap("left", swipe_intent(@lay, start, Point.{ x = start.x - D, y = start.y }), 3, 5, 2, 5) { fails += 1; }
if !expect_swap("up", swipe_intent(@lay, start, Point.{ x = start.x, y = start.y - D }), 3, 5, 3, 4) { fails += 1; }
if !expect_swap("down", swipe_intent(@lay, start, Point.{ x = start.x, y = start.y + D }), 3, 5, 3, 6) { fails += 1; }
// Below threshold on both axes → a tap, not a swipe.
if !expect_none("short", swipe_intent(@lay, start, Point.{ x = start.x + 10.0, y = start.y + 5.0 })) { fails += 1; }
// Diagonal drags resolve to the dominant axis (larger of |dx|, |dy|).
if !expect_swap("diag-horizontal", swipe_intent(@lay, start, Point.{ x = start.x + D, y = start.y + 25.0 }), 3, 5, 4, 5) { fails += 1; }
if !expect_swap("diag-vertical", swipe_intent(@lay, start, Point.{ x = start.x + 20.0, y = start.y - D }), 3, 5, 3, 4) { fails += 1; }
// Edge cell swiped outward → neighbour off the board → no intent.
edge_right := cell_center(@lay, 7, 0);
if !expect_none("offboard-right", swipe_intent(@lay, edge_right, Point.{ x = edge_right.x + D, y = edge_right.y })) { fails += 1; }
edge_top := cell_center(@lay, 2, 0);
if !expect_none("offboard-up", swipe_intent(@lay, edge_top, Point.{ x = edge_top.x, y = edge_top.y - D })) { fails += 1; }
// Drag starting off the board (left of the grid) → no intent.
off_start := Point.{ x = lay.origin.x - 5.0, y = lay.origin.y + 10.0 };
if !expect_none("start-offboard", swipe_intent(@lay, off_start, Point.{ x = off_start.x + D, y = off_start.y })) { fails += 1; }
if fails == 0 {
print("ok: swipe-intent mapping resolves all cases\n");
return 0;
}
print("FAIL: {} swipe-intent checks failed\n", fails);
return 1;
}