Rust Classic-mode engine
A second Rust crate for the Classic ruleset, written for the mobile game-collection API; nothing consumes it yet.
On this page
Nothing consumes it yet.
Design
This crate implements Classic mode for deterministic replay. Classic has a decreasing move clock, can deal a gray disc as the incoming piece, and uses historical 7,000-point scoring, archival, for its rise bonus. Every random future arrives as an argument, which makes a submitted game reproducible without embedding a random generator in the rules.
Implementation
Randomness at the boundary
A numbered drop carries no hidden value. A gray drop must carry the value that will appear when it opens. A successful rise must receive its next hidden row, and a non-terminal move receives the next visible disc. The function checks those combinations before touching the board.
/// Play one Classic move against an explicit future.
///
/// `dropped_hidden` must be `Some(1..=7)` exactly when the visible drop is
/// gray. `covered_row` is required only when this move successfully raises a
/// row. `next_disc` is ignored on terminal moves.
pub fn play_move(
state: &State,
latent: &LatentBoard,
column: usize,
dropped_hidden: Option<u8>,
covered_row: Option<CoveredRow>,
next_disc: u8,
) -> Option<MoveResult> {
if state.game_over || column >= BOARD_SIZE || state.board[index(0, column)] != EMPTY {
return None;
}
assert_droppable(state.next_disc);
match (state.next_disc, dropped_hidden) {
(SOLID, Some(value)) => assert_numbered(value),
(SOLID, None) => panic!("a dropped gray disc needs a hidden value"),
(_, Some(_)) => panic!("a numbered drop cannot carry a hidden value"),
(_, None) => {}
}
let mut board = state.board;
let mut hidden = *latent;
let dropped_index = place_disc(&mut board, column, state.next_disc)?;
hidden[dropped_index] = dropped_hidden.unwrap_or(EMPTY);
let mut score_delta = 0;
let mut waves = Vec::new();
resolve_cascade(&mut board, &mut hidden, 1, &mut score_delta, &mut waves);Visible and hidden boards stay paired
The representation is intentionally plain: one byte array for visible cells and another for hidden values. Gravity and row rises copy the arrays together, so a gray disc keeps its value as it moves.
fn apply_gravity(board: &mut Board, latent: &mut LatentBoard) {
let before = *board;
let hidden_before = *latent;
*board = [EMPTY; CELL_COUNT];
*latent = [EMPTY; CELL_COUNT];
for column in 0..BOARD_SIZE {
let mut destination = BOARD_SIZE - 1;
for row in (0..BOARD_SIZE).rev() {
let offset = index(row, column);
if before[offset] == EMPTY {
continue;
}
let target = index(destination, column);
board[target] = before[offset];
latent[target] = hidden_before[offset];
destination = destination.saturating_sub(1);
}
}
}
fn raise_row(board: &mut Board, latent: &mut LatentBoard, row: CoveredRow) {
let before = *board;
let hidden_before = *latent;
*board = [EMPTY; CELL_COUNT];
*latent = [EMPTY; CELL_COUNT];
for source_row in 1..BOARD_SIZE {
for column in 0..BOARD_SIZE {
board[index(source_row - 1, column)] = before[index(source_row, column)];
latent[index(source_row - 1, column)] = hidden_before[index(source_row, column)];
}
}
for column in 0..BOARD_SIZE {
board[index(BOARD_SIZE - 1, column)] = SOLID;
latent[index(BOARD_SIZE - 1, column)] = row[column];
}
}Uses
The design fits server-side validation of a mobile game tape and leaves future research free to choose its own sampling policy. The current mobile validation path still uses the TypeScript Classic engine, and no policy or service calls this Rust crate today.
Verification
The crate shares one conformance transition with the TypeScript Classic tests and carries focused unit tests for its clock, scoring, gray drops and level boundary. It has no whole-game parity record.
Technical recordThe tests currently carried by the Classic Rust crate
clock_and_bonus_match_classicgray_drop_keeps_its_hidden_value_through_gravitylevel_boundary_uses_seven_thousand_and_a_decreasing_clockshared_typescript_conformance_transition
Limits
One shared transition checks a useful boundary, while it cannot establish whole-trajectory parity. The crate also has no consumer, benchmark or native parity harness. Its next useful step is integration with an actual replay caller followed by paired full-game tapes against the TypeScript Classic implementation.
Agent contextMissing integration work and the minimum next parity gate
Run cargo test --manifest-path src/core/rust/classic-engine/Cargo.toml for the local crate. A whole-game gate should feed identical explicit tapes to the Rust and TypeScript Classic engines and compare every board, latent board, score delta, clock transition, terminal flag and wave in order.
Keep historical 7,000-point scoring, archival, separate from corrected five-move Hardcore results. Do not place Classic scores in a comparison table with corrected 17,000-point results.