// Refill golden: run the full resolution pipeline clear -> collapse -> refill // over a HAND-CRAFTED seeded board and snapshot every stage. The starting board // is the run-free O/G checkerboard from match_detect carrying three disjoint // matches (RRR row 1, PPP row 5, YYY col 6); clearing punches 9 holes, gravity // floats them to the top of their columns, and refill drops fresh gems in. // // Determinism is shown three independent ways, all locked by the snapshot and by // asserts: // * stream-continuation — each refilled hole holds exactly the NEXT draw of the // board's own RNG, replayed from the pre-refill state. A reseed-from-scratch // or a constant fill both fail this. // * reproducibility — the same start + seed refills to a byte-identical board. // * threading across refills — re-opening the just-filled holes and refilling // again yields DIFFERENT gems, proving the RNG advances rather than reseeding. #import "modules/std.sx"; #import "board.sx"; t :: #import "test.sx"; SEED :: 1337; // Inverse of `gem_char`: map a board character back to its Gem so the starting // board can be written as a human-readable grid. The hole glyph maps to `.empty`. char_to_gem :: (c: u8) -> Gem { if c == EMPTY_CHAR { return .empty; } for 0..GEM_COUNT (i) { if GEM_CHARS[i] == c { return cast(Gem) i; } } .red } // Load an 8x8 board from `rows` (top row first, each exactly BOARD_COLS chars). // The RNG is left unseeded — callers seed it before drawing. load_board :: (rows: []string) -> Board { b : Board = ---; for 0..BOARD_ROWS (row) { line := rows[row]; for 0..BOARD_COLS (col) { b.set(col, row, char_to_gem(line[col])); } } b } count_empties :: (b: *Board) -> i64 { n : i64 = 0; for 0..BOARD_CELLS (i) { if b.cells[i] == .empty { n += 1; } } n } boards_equal :: (a: *Board, b: *Board) -> bool { for 0..BOARD_CELLS (i) { if a.cells[i] != b.cells[i] { return false; } } true } // A fresh starting board with its RNG seeded from SEED. Because the RNG lives on // the board, the refill it later performs is reproducible for SEED. The board is // a run-free checkerboard carrying three disjoint matches — a horizontal RRR run // (row 1, cols 0-2), a horizontal PPP run (row 5, cols 2-4) and a vertical YYY // run (col 6, rows 4-6) — so clearing them punches 9 holes across six columns. fresh_board :: () -> Board { b := load_board(.[ "OGOGOGOG", "RRROGOGO", "OGOGOGOG", "GOGOGOGO", "OGOGOGYG", "GOPPPOYO", "OGOGOGYG", "GOGOGOGO", ]); b.rng = rng_seeded(SEED); b } main :: () -> i32 { print("== refill (seeded) ==\n"); // Pipeline, snapshotting each stage. b := fresh_board(); out("start:\n"); out(board_dump(@b)); clear_matches(@b); out("after clear:\n"); out(board_dump(@b)); collapse(@b); out("after collapse:\n"); out(board_dump(@b)); // Snapshot the holed board (cells + RNG state) BEFORE refill, so we can both // check which cells refill touched and replay the stream from the same state. pre := b; filled := refill(@b); out("after refill:\n"); out(board_dump(@b)); print("filled {} holes\n", filled); // (1) No empty cells remain. t.expect(count_empties(@b) == 0, "refill: board has zero empty cells"); // (2) Stream continuation + not-a-constant: every refilled cell holds exactly // the next draw of the board's RNG, taken row-major from the pre-refill state, // and the drawn gems are not all identical. v := Rng.{ state = pre.rng.state }; stream_ok := true; distinct := false; have_first := false; first : Gem = .empty; for 0..BOARD_CELLS (i) { if pre.cells[i] == .empty { want := cast(Gem) v.next_range(GEM_COUNT); if b.cells[i] != want { stream_ok = false; } if !have_first { first = b.cells[i]; have_first = true; } else if b.cells[i] != first { distinct = true; } } } t.expect(stream_ok, "refill: each hole holds the next seeded-stream gem"); t.expect(distinct, "refill: drawn gems vary (not a fixed constant)"); // (3) Reproducibility: same start + seed refills to a byte-identical board. b2 := fresh_board(); clear_matches(@b2); collapse(@b2); refill(@b2); t.expect(boards_equal(@b, @b2), "refill: same start + seed -> identical board"); // (4) Threading across refills: re-open exactly the cells the first refill // filled, then refill again. The board's RNG has advanced past the first // fill, so the second fill draws new gems — proof it does NOT reseed per call. holes_n := 0; hole_idx : [BOARD_CELLS]i64 = ---; fill1 : [BOARD_CELLS]Gem = ---; for 0..BOARD_CELLS (i) { if pre.cells[i] == .empty { hole_idx[holes_n] = i; fill1[holes_n] = b.cells[i]; holes_n += 1; } } for 0..holes_n (k) { b.cells[hole_idx[k]] = .empty; } refill(@b); differs := false; for 0..holes_n (k) { if b.cells[hole_idx[k]] != fill1[k] { differs = true; } } t.expect(differs, "refill: a second refill of the same holes draws new gems (RNG threads, no reseed)"); print("ok: refill fills every hole from the seeded stream\n"); return 0; }