Simulation

Probar provides deterministic game simulation for testing.

Basic Simulation

#![allow(unused)]
fn main() {
use jugar_probar::{run_simulation, SimulationConfig};

let config = SimulationConfig::new(seed, num_frames);
let result = run_simulation(config, |frame| {
    // Return inputs for this frame
    vec![]  // No inputs
});

assert!(result.completed);
println!("Final state hash: {}", result.state_hash);
}

Simulation with Inputs

#![allow(unused)]
fn main() {
use jugar_probar::{run_simulation, SimulationConfig, InputEvent};

let config = SimulationConfig::new(42, 300);
let result = run_simulation(config, |frame| {
    // Move paddle up for first 100 frames
    if frame < 100 {
        vec![InputEvent::key_held("KeyW")]
    } else {
        vec![]
    }
});
}

Input Events

#![allow(unused)]
fn main() {
// Keyboard
InputEvent::key_press("Space")      // Just pressed
InputEvent::key_held("KeyW")        // Held down
InputEvent::key_release("Escape")   // Just released

// Mouse
InputEvent::mouse_move(400.0, 300.0)
InputEvent::mouse_press(MouseButton::Left)
InputEvent::mouse_release(MouseButton::Left)

// Touch
InputEvent::touch_start(0, 100.0, 200.0)  // id, x, y
InputEvent::touch_move(0, 150.0, 250.0)
InputEvent::touch_end(0)
}

Deterministic Replay

#![allow(unused)]
fn main() {
use jugar_probar::{run_simulation, run_replay, SimulationConfig};

// Record a simulation
let config = SimulationConfig::new(42, 500);
let recording = run_simulation(config, |frame| {
    vec![InputEvent::key_press("ArrowUp")]
});

// Replay it
let replay = run_replay(&recording);

// Verify determinism
assert!(replay.determinism_verified);
assert_eq!(recording.state_hash, replay.state_hash);
}

Simulation Config

#![allow(unused)]
fn main() {
pub struct SimulationConfig {
    pub seed: u64,           // Random seed for reproducibility
    pub frames: u32,         // Number of frames to simulate
    pub fixed_dt: f32,       // Timestep (default: 1/60)
    pub max_time: f32,       // Max real time (for timeout)
}

let config = SimulationConfig {
    seed: 12345,
    frames: 1000,
    fixed_dt: 1.0 / 60.0,
    max_time: 60.0,
};
}

Simulation Result

#![allow(unused)]
fn main() {
pub struct SimulationResult {
    pub completed: bool,
    pub frames_run: u32,
    pub state_hash: u64,
    pub final_state: GameState,
    pub recording: Recording,
    pub events: Vec<GameEvent>,
}
}

Recording Format

#![allow(unused)]
fn main() {
pub struct Recording {
    pub seed: u64,
    pub frames: Vec<FrameInputs>,
    pub state_snapshots: Vec<StateSnapshot>,
}

pub struct FrameInputs {
    pub frame: u32,
    pub inputs: Vec<InputEvent>,
}
}

Invariant Checking

#![allow(unused)]
fn main() {
use jugar_probar::{run_simulation_with_invariants, Invariant};

let invariants = vec![
    Invariant::new("ball_in_bounds", |state| {
        state.ball.x >= 0.0 && state.ball.x <= 800.0 &&
        state.ball.y >= 0.0 && state.ball.y <= 600.0
    }),
    Invariant::new("score_valid", |state| {
        state.score_left <= 10 && state.score_right <= 10
    }),
];

let result = run_simulation_with_invariants(config, invariants, |_| vec![]);

assert!(result.all_invariants_held);
for violation in &result.violations {
    println!("Violation at frame {}: {}", violation.frame, violation.invariant);
}
}

Scenario Testing

#![allow(unused)]
fn main() {
#[test]
fn test_game_scenarios() {
    let scenarios = vec![
        ("player_wins", |f| if f < 500 { vec![key("KeyW")] } else { vec![] }),
        ("ai_wins", |_| vec![]),  // No player input
        ("timeout", |_| vec![key("KeyP")]),  // Pause
    ];

    for (name, input_fn) in scenarios {
        let config = SimulationConfig::new(42, 1000);
        let result = run_simulation(config, input_fn);

        println!("Scenario '{}': score = {} - {}",
            name, result.final_state.score_left, result.final_state.score_right);
    }
}
}

Performance Benchmarking

#![allow(unused)]
fn main() {
use std::time::Instant;

#[test]
fn benchmark_simulation() {
    let config = SimulationConfig::new(42, 10000);

    let start = Instant::now();
    let result = run_simulation(config, |_| vec![]);
    let elapsed = start.elapsed();

    println!("10000 frames in {:?}", elapsed);
    println!("FPS: {}", 10000.0 / elapsed.as_secs_f64());

    // Should run faster than real-time
    assert!(elapsed.as_secs_f64() < 10000.0 / 60.0);
}
}