neurodoom 0.6.7

Deterministic no_std Doom engine with semantic and depth perception buffers for AI
Documentation

neurodoom

Deterministic pure-Rust Doom engine with per-pixel semantic and depth perception buffers for AI and multi-agent work.

  • #![no_std] core (uses alloc), headless by default, 320x200 output.
  • Classic single-player reproduction in ClassicDoomRules.
  • Generic over any GameRules implementation for custom multiplayer or training setups.

History

Started as a thin wrapper around PureDOOM (a single-header, dependency-free C port of Doom). The plan was to bind the C engine and hang perception buffers off it. In practice, almost every subsystem — WAD parser, map loader, BSP + column rasterizer, visplanes, sprite system, physics, state machine, sector specials, game loop — got rewritten in pure Rust so the engine could be #![no_std], panic-averse, and deterministic.

At a glance

use neurodoom::{ClassicEngine, PeerId, PlayerAction};

let wad = std::fs::read("doom1.wad")?;
let mut engine = ClassicEngine::new(&wad, "E1M1")?;

engine.tick_single(PeerId(0), PlayerAction::default());

let rgba     = engine.framebuffer();       // 320 * 200 * 4 bytes
let semantic = engine.semantic_buffer();   // 1 SemanticClass per pixel
let depth    = engine.depth_buffer();      // fixed-point distance per pixel

Examples

All three examples need a Doom WAD. Point DOOM_WAD at one, or place doom1.wad in the project root.

# Playable single-player with level progression across the episode.
cargo run --release --example classic

# 24 reactive bots in a 6x4 grid, fighting in E1M1 with no monsters.
# Each cell shows the bot's POV with HUD + animated weapon. Dead cells
# switch to depth view.
cargo run --release --example ai_multiplayer

# Play back the recorded DEMO1 / DEMO2 / DEMO3 lumps from the WAD —
# no user input, just feeds the stored tic-commands to the engine.
cargo run --release --example demo

Controls are documented in each example's file header.

Multi-peer rendering

DoomEngine::tick(...) is the single-player convenience — it runs simulation for one tick and renders PeerId(0)'s view. For multi-peer work (several AI bots sharing one world, each needing its own view), split the two:

engine.simulate(&actions, &[]);      // advance sim, no render
for peer in peers {
    engine.render_for(peer);          // re-render from that peer's eye
    // read engine.{framebuffer,semantic_buffer,depth_buffer}()
}

See examples/ai_multiplayer.rs for the full pattern (including staggered rendering to amortize the per-peer cost).

Further reading

Module-level docs (cargo doc --open) cover each subsystem. The crate is panic-averse by design: direct panic sites are surfaced as clippy warnings and Cargo.toml sets panic = "abort" on both dev and release. See src/render/mod.rs for the policy details.

License

BSD-3-Clause. See LICENSE.