Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/risc0/risc0/llms.txt

Use this file to discover all available pages before exploring further.

Demonstrates running the Bevy game engine inside the RISC Zero zkVM to create verifiable, deterministic game simulations.

What You’ll Learn

  • Running game engines in the zkVM
  • Using Bevy’s Entity Component System (ECS)
  • Creating verifiable game states
  • Proving game logic without revealing inputs

Overview

This minimal example shows how to:
  • Set up Bevy’s ECS in the zkVM
  • Define components and systems
  • Run deterministic game ticks
  • Generate proofs of game state
Bevy is a powerful game engine. This example uses only the core ECS, but demonstrates the potential for complex game logic verification.

How It Works

1
Guest Program
2
The guest runs a simple Bevy simulation:
3
use risc0_zkvm::guest::env;
use bevy_core::Outputs;
use bevy_ecs::{prelude::*, world::World};

#[derive(Component)]
struct Position {
    x: f32,
    y: f32,
}

#[derive(Component)]
struct Velocity {
    x: f32,
    y: f32,
}

#[derive(StageLabel)]
pub struct UpdateLabel;

// System that moves entities with Position and Velocity
fn movement(mut query: Query<(&mut Position, &Velocity)>) {
    for (mut position, velocity) in &mut query {
        position.x += velocity.x;
        position.y += velocity.y;
    }
}

fn main() {
    let turns: u32 = env::read();
    
    // Create Bevy world
    let mut world = World::new();
    
    // Spawn entity with position and velocity
    let entity = world
        .spawn((
            Position { x: 0.0, y: 0.0 },
            Velocity { x: 1.0, y: 0.0 },
        ))
        .id();

    // Create schedule with movement system
    let mut schedule = Schedule::default();
    schedule.add_stage(
        UpdateLabel,
        SystemStage::single_threaded().with_system(movement),
    );
    
    // Verify initial position
    {
        let entity_ref = world.entity(entity);
        let position = entity_ref.get::<Position>().unwrap();
        assert!(position.x == 0.0);
    }

    // Run timesteps
    for _ in 0..turns {
        env::log("running timestep...");
        schedule.run(&mut world);
    }
    
    // Commit final position
    {
        let entity_ref = world.entity(entity);
        let position = entity_ref.get::<Position>().unwrap();
        let out = Outputs {
            position: position.x,
        };
        env::commit(&out);
    }
}
4
Key Components:
5
  • Entities: Game objects (spawned entity)
  • Components: Data attached to entities (Position, Velocity)
  • Systems: Logic that operates on components (movement)
  • Schedule: Orchestrates system execution
  • 6
    Host Program
    7
    The host specifies how many timesteps to simulate:
    8
    use bevy_methods::{BEVY_ELF, BEVY_ID};
    use risc0_zkvm::{ExecutorEnv, default_prover};
    
    fn main() {
        let turns: u32 = 10;
    
        let env = ExecutorEnv::builder()
            .write(&turns)
            .unwrap()
            .build()
            .unwrap();
    
        let receipt = default_prover()
            .prove(env, BEVY_ELF)
            .unwrap()
            .receipt;
    
        receipt.verify(BEVY_ID).unwrap();
    
        let output: Outputs = receipt.journal.decode().unwrap();
        
        println!("After {} turns, position.x = {}", turns, output.position);
        println!("Expected: {}", turns);
        assert_eq!(output.position, turns as f32);
    }
    

    Running the Example

    cargo run --release
    
    Output:
    After 10 turns, position.x = 10.0
    Expected: 10
    
    The entity moved 10 units (1 unit per turn with velocity.x = 1.0).

    What Gets Proven?

    The receipt proves:
    1. Deterministic Execution: The game ran exactly N timesteps
    2. Valid Game Logic: Movement system applied correctly
    3. Final State: The entity is at the committed position
    4. No Tampering: Game state wasn’t manipulated

    Bevy ECS Architecture

    Entities

    Unique identifiers for game objects:
    let entity = world.spawn((/* components */)).id();
    

    Components

    Data structs attached to entities:
    #[derive(Component)]
    struct Health {
        current: u32,
        max: u32,
    }
    
    #[derive(Component)]
    struct Player {
        name: String,
    }
    

    Systems

    Functions that query and modify components:
    fn damage_system(
        mut query: Query<&mut Health, With<Player>>
    ) {
        for mut health in &mut query {
            if health.current > 0 {
                health.current -= 10;
            }
        }
    }
    

    Queries

    Filter entities by component combinations:
    // All entities with Position AND Velocity
    Query<(&mut Position, &Velocity)>
    
    // All entities with Player but WITHOUT Enemy
    Query<&Player, Without<Enemy>>
    
    // All entities with Health (read-only)
    Query<&Health>
    

    Schedules

    Organize system execution order:
    let mut schedule = Schedule::default();
    schedule.add_stage(
        UpdateLabel,
        SystemStage::single_threaded()
            .with_system(input_system)
            .with_system(movement_system)
            .with_system(collision_system)
            .with_system(render_system),
    );
    

    Use Cases

    Verifiable Game Outcomes

    Prove game results without revealing player actions:
    Player A actions (secret) ┐
                             ├─ Run game in zkVM → Receipt
    Player B actions (secret) ┘
    
    Receipt proves:
    - Game ran with correct rules
    - Final score is accurate
    - No cheating occurred
    

    Onchain Gaming

    Generate proofs offchain, verify onchain:
    function submitGameResult(
        bytes memory finalState,
        bytes memory receipt
    ) external {
        // Verify zkVM receipt
        risc0Verifier.verify(receipt, GAME_ID);
        
        // Extract and process final state
        GameState memory state = abi.decode(
            finalState,
            (GameState)
        );
        
        // Award tokens based on score
        gameToken.mint(msg.sender, state.score);
    }
    

    AI Training Verification

    Prove AI trained with specific game outcomes:
    Game Replay → Run in zkVM → Extract Training Data → Proof of Data Source
    

    Tournaments

    Verify tournament matches:
    Match 1 Receipt ─┐
                    ├─ Aggregate → Tournament Result
    Match 2 Receipt ─┤
    Match 3 Receipt ─┘
    

    Anti-Cheat

    Prove legitimate gameplay:
    Client Game State → Generate Proof → Server Verifies → Accept/Reject
    

    Extending the Example

    Multiple Entities

    Simulate multiple game objects:
    fn main() {
        let mut world = World::new();
        
        // Spawn multiple entities
        for i in 0..10 {
            world.spawn((
                Position { x: i as f32, y: 0.0 },
                Velocity { x: 1.0, y: 0.5 },
            ));
        }
        
        // Run simulation...
    }
    

    Collision Detection

    Add collision system:
    fn collision_system(
        query: Query<(&Position, Entity)>,
        mut commands: Commands,
    ) {
        let positions: Vec<_> = query.iter().collect();
        
        for i in 0..positions.len() {
            for j in (i+1)..positions.len() {
                let (pos1, entity1) = positions[i];
                let (pos2, entity2) = positions[j];
                
                let dist = ((pos1.x - pos2.x).powi(2) + 
                            (pos1.y - pos2.y).powi(2)).sqrt();
                
                if dist < 1.0 {
                    // Handle collision
                    commands.entity(entity1).despawn();
                }
            }
        }
    }
    

    Player Input

    Process player actions:
    #[derive(Component)]
    struct PlayerAction {
        direction: Direction,
    }
    
    fn main() {
        let actions: Vec<PlayerAction> = env::read();
        
        // Apply actions over timesteps
        for action in actions {
            world.spawn((action,));
            schedule.run(&mut world);
        }
        
        // Commit final state
    }
    

    Resource Management

    Add game resources:
    #[derive(Resource)]
    struct GameTime {
        elapsed: f32,
    }
    
    fn main() {
        let mut world = World::new();
        world.insert_resource(GameTime { elapsed: 0.0 });
        
        // Systems can access resources
        fn time_system(time: Res<GameTime>) {
            println!("Elapsed: {}", time.elapsed);
        }
    }
    

    Performance

    MetricValue (10 turns)
    Cycles~2M
    Proving time~2-5 seconds
    Receipt size~128 KB
    Memory usage~1 GB
    Performance scales with:
    • Number of entities
    • System complexity
    • Number of timesteps

    Bevy Feature Compatibility

    This example uses bevy_ecs which works in zkVM: Works in zkVM:
    • bevy_ecs: Entity Component System
    • bevy_core: Core functionality
    • bevy_math: Math utilities
    • bevy_transform: Transforms (with limitations)
    Doesn’t work in zkVM:
    • bevy_render: Graphics (requires GPU/system calls)
    • bevy_window: Windowing (requires OS)
    • bevy_input: Input handling (requires OS)
    • bevy_audio: Audio (requires OS)
    Use only the ECS and math components. Rendering and I/O systems require OS support not available in zkVM.

    Determinism

    Games in zkVM must be deterministic: Deterministic:
    // Seed-based randomness
    let mut rng = Rng::from_seed(seed);
    let value = rng.gen();
    
    Non-deterministic (avoid):
    // System time (not available in zkVM)
    let time = SystemTime::now();  // ❌
    
    // True randomness
    let value = thread_rng().gen();  // ❌
    

    Linking with Player Identities

    Combine with signature verification:
    fn main() {
        // Verify player signed the actions
        let actions: Vec<Action> = env::read();
        let signature: Signature = env::read();
        let public_key: PublicKey = env::read();
        
        verify_signature(&actions, &signature, &public_key)
            .expect("Invalid signature");
        
        // Run game with verified actions
        let final_state = run_game(actions);
        
        env::commit(&(public_key, final_state));
    }
    
    See the ECDSA example for signature verification.

    Bonsai Integration

    For online games, use Bonsai:
    // Client submits gameplay
    let game_session = GameSession {
        player: player_address,
        actions: player_actions,
        seed: game_seed,
    };
    
    // Generate proof on Bonsai
    let receipt = bonsai_client
        .prove(GAME_ELF, game_session)
        .await?;
    
    // Submit to blockchain
    let tx = game_contract
        .submit_result(receipt.journal, receipt.seal)
        .send()
        .await?;
    
    See the blog post on Bonsai as a zk coprocessor.

    Game Development Tips

    Keep It Simple

    Start with minimal game logic:
    1. Basic movement
    2. Simple interactions
    3. Clear win/loss conditions
    4. Deterministic state

    Optimize for Cycles

    Minimize computational cost:
    • Use integer math when possible
    • Limit entity count
    • Simplify physics
    • Batch operations

    Test Determinism

    Verify games are deterministic:
    #[test]
    fn test_determinism() {
        let result1 = run_game(seed);
        let result2 = run_game(seed);
        assert_eq!(result1, result2);
    }
    

    Separate Rendering

    Keep game logic separate from presentation:
    Game Logic (zkVM) → Game State → Renderer (Client)
    

    Next Steps