// Hit-test golden (P4.4): lock the touch→cell mapping `BoardLayout.point_to_cell` // as the exact inverse of `cell_frame`. The two are written independently — one // multiplies a cell index by cell_size, the other divides a point by cell_size // and truncates — so round-tripping every cell center back to its own cell is a // real check, not a tautology. BoardView and P5's swap input both reuse this // mapping, so a drift here would silently send taps/swaps to the wrong cell. // // Imports BoardLayout (no GL/stb), not BoardView, so it links headless. It also // avoids tests/test.sx, whose modules/process.sx → modules/trace.sx pulls in a // second `Frame` struct that collides with the UI `Frame`. 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"; main :: () -> s32 { // 800×600 with no safe inset → a 600px square grid, cell 75, centered: the // grid origin lands at (100, 0). Integer math keeps the dump deterministic. lay : BoardLayout = ---; lay.compute(Frame.make(0.0, 0.0, 800.0, 600.0), EdgeInsets.zero()); print("grid origin ({},{}) cell {}\n", cast(s64) lay.origin.x, cast(s64) lay.origin.y, cast(s64) lay.cell_size); fails : s64 = 0; // Every cell center must map back to its own cell. hits : s64 = 0; for 0..BOARD_ROWS (row) { for 0..BOARD_COLS (col) { cf := lay.cell_frame(col, row); center := Point.{ x = cf.mid_x(), y = cf.mid_y() }; if h := lay.point_to_cell(center) { if h.col == col and h.row == row { hits += 1; } } } } if hits != BOARD_CELLS { fails += 1; } print("ok: {}/{} cell centers round-trip\n", hits, BOARD_CELLS); // A cell's top-left corner belongs to that cell (the leading edge is // inclusive), so corner-of-(3,5) resolves to (3,5). corner := Point.{ x = lay.origin.x + 3.0 * lay.cell_size, y = lay.origin.y + 5.0 * lay.cell_size }; corner_col : s64 = -1; corner_row : s64 = -1; if h := lay.point_to_cell(corner) { corner_col = h.col; corner_row = h.row; } if corner_col != 3 or corner_row != 5 { fails += 1; } print("corner maps to ({},{})\n", corner_col, corner_row); // Off-board taps reject (null): left of, above, and right of the grid. None // should resolve to a cell, so the on-board count must stay 0. off_left := Point.{ x = lay.origin.x - 5.0, y = lay.origin.y + 10.0 }; off_above := Point.{ x = lay.origin.x + 10.0, y = lay.origin.y - 5.0 }; off_right := Point.{ x = lay.origin.x + 8.0 * lay.cell_size + 1.0, y = lay.origin.y + 10.0 }; on_board : s64 = 0; if h := lay.point_to_cell(off_left) { on_board += 1; print("off_left hit ({},{})\n", h.col, h.row); } if h := lay.point_to_cell(off_above) { on_board += 1; print("off_above hit ({},{})\n", h.col, h.row); } if h := lay.point_to_cell(off_right) { on_board += 1; print("off_right hit ({},{})\n", h.col, h.row); } if on_board != 0 { fails += 1; } print("ok: {} off-board taps resolved to a cell\n", on_board); if fails == 0 { print("ok: hit-test mapping is the inverse of the layout\n"); return 0; } print("FAIL: {} hit-test checks failed\n", fails); return 1; }