On this page
Source
Used by

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.

lib.rsMake every random future an explicit move input
Rustlines 92–123
/// 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);
The type boundary separates visible pieces, hidden values and future covered rows before the cascade begins.

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.

lib.rsMove a gray disc and its hidden value together
Rustlines 300–335
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];
    }
}
Both transforms apply the same source and destination indexes to the visible and latent arrays.

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_classic
  • gray_drop_keeps_its_hidden_value_through_gravity
  • level_boundary_uses_seven_thousand_and_a_decreasing_clock
  • shared_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.