A desktop application featuring a first-person 3D ASCII labyrinth adventure game built with Rust, Tauri, and JavaScript.
Visit the landing page to see a demo and download the game for your platform.
- 3D ASCII Raycasting Engine: Real-time 3D rendering using ASCII characters with depth perception
- Fractal Dithering: Surface-stable fractal dithering for enhanced visual quality (using MPL 2.0 licensed code)
- Procedural Maze Generation: Randomly generated labyrinths using recursive backtracking algorithm
- 5 levels with best-time persistence and level-complete flow
- Adaptive music: Layered stems (base, pressure, chase, dread) with procedural fallback; click viewport to unlock audio
- First-Person Controls: Smooth movement and rotation with WASD + Q/E keys
- Cross-Platform: Built with Tauri for Windows, macOS, and Linux support
- Landing page:
index.htmlon Vercel with release downloads viaapi/github-releases.js
- W: Move forward
- S: Move backward
- A: Strafe left
- D: Strafe right
- Q: Turn left
- E: Turn right
- ESC: Exit game
- Clone the repository:
git clone git@github.com:ledoit/Matrix-Maze.git
cd Matrix-Maze- Install frontend dependencies (all npm commands run from
app/):
cd app
npm install- The Rust dependencies will be automatically installed when you build the project.
Current version: 1.3.0 (app/package.json, app/src-tauri/Cargo.toml)
Run the development server:
cd app
npm install
npm run tauri devThis will:
- Start the Vite dev server for the frontend
- Compile the Rust backend
- Launch the Tauri application window
Build the application for production:
cd app
npm run tauri buildThe built application will be in app/src-tauri/target/release/ (or app/src-tauri/target/release/bundle/ for installers).
.
โโโ index.html # Landing page (fullscreen playable shell, deployed to Vercel)
โโโ game/ # Generated browser game build (iframe embed at /game/)
โโโ vercel.json # Vercel deployment config
โโโ api/ # Serverless GitHub releases proxy (landing downloads)
โโโ BUILD.md # Release build checklist
โโโ TODO.md # Open issues (audio polish, mute, perf)
โโโ app/ # Game application
โ โโโ src/ # Frontend (HTML/CSS/JavaScript)
โ โ โโโ main.js # Game loop (host-agnostic)
โ โ โโโ backend.js # GameBackend: Tauri invoke (desktop) vs WASM (browser)
โ โ โโโ wasm/ # Generated wasm-bindgen output (committed)
โ โ โโโ style.css # Styling
โ โโโ scripts/
โ โ โโโ build-wasm.sh # Compiles the Rust core to WebAssembly
โ โโโ src-tauri/ # Rust backend
โ โ โโโ src/
โ โ โ โโโ lib.rs # Shared game library (native + wasm)
โ โ โ โโโ main.rs # Tauri (native) entry point
โ โ โ โโโ wasm_api.rs # Browser (wasm-bindgen) entry points
โ โ โ โโโ platform.rs # Per-host time / RNG / persistence shims
โ โ โ โโโ game.rs # Game state and logic
โ โ โ โโโ maze.rs # Maze generation
โ โ โ โโโ raycast.rs # 3D raycasting engine
โ โ โโโ Cargo.toml # Rust dependencies (dual-target: native + wasm)
โ โโโ index.html # Game HTML entry point
โ โโโ package.json # Node.js dependencies
โโโ README.md
The same Rust game logic that powers the desktop app is compiled to WebAssembly so the game
runs in Chrome/Firefox/Safari with no install. There is no JS reimplementation โ game.rs,
maze.rs, raycast.rs and dither/ are shared between both targets:
app/src-tauri/is a dual-target crate. The native build produces the Tauri binary (src/main.rs); thewasm32-unknown-unknownbuild produces a cdylib whosesrc/wasm_api.rsexposes the sameinit_game/update_game/render_frame/next_levelentry points.- Host differences (wall-clock time, RNG entropy, best-time persistence) are isolated in
src/platform.rsโstdon desktop,js-sys/web-sys(localStorage) in the browser. - The frontend
app/src/main.jstalks to a thinGameBackend(app/src/backend.js) that isTauriBackend(viainvoke) on desktop andWasmBackend(via the wasm module) in the browser. Tauri APIs are only dynamically imported on desktop, so nothing crashes when__TAURI__is absent.
Prerequisites (one-time):
rustup target add wasm32-unknown-unknown
cargo install wasm-bindgen-cli --version 0.2.100 # must match the wasm-bindgen crate pinThen, from app/:
npm install
npm run build:wasm # Rust -> wasm, regenerates app/src/wasm/ (only when Rust changes)
npm run dev # local dev server (http://localhost:1420)
npm run build:web # production browser build -> ../game (iframe embed)app/src/wasm/ and game/ are committed build artifacts, so the Vite build and Vercel do
not need a Rust toolchain โ only re-run build:wasm / build:web when the Rust game logic
or frontend changes.
- Prod: https://matrix-maze.menhir-holdings.com/ โ sidebar + Play; game embedded
/game/โ iframe-only WASM build (not a public entry point)- Legacy
/play/and/dev/redirect to/ - Deploy: push
mainโ Vercel. In-flight: PR preview URL (do not redirect*.vercel.apppreviews)
Hub ops (DNS / Vercel lattice): stonehenge docs/DNS.md, docs/VERCEL.md.
When this repo lives inside the Menhir monorepo, adaptive music is produced in Music/Kaiser/projects/matrix-maze/ and copied into app/public/audio/music/ by the client sync script.
- Build stems in Kaiser:
Music/Kaiser/export/convert-stems.bat matrix-maze(or.shon Unix) after placing WAVs inMusic/Kaiser/projects/matrix-maze/export/inbox/. - From
app/:npm run music:sync
Details: app/public/audio/music/README.md and Music/Kaiser/projects/matrix-maze/README.md.
The game uses a raycasting algorithm similar to classic games like Wolfenstein 3D:
- For each column of the screen, a ray is cast from the player's position
- The ray intersects with walls in the maze
- Distance is calculated and used to determine wall height (perspective projection)
- ASCII characters are chosen based on distance to create depth perception
The maze is generated using a recursive backtracking algorithm:
- Creates a perfect maze (one path between any two points)
- Ensures the player starts at a valid position
- Guarantees an exit point
- Frontend captures keyboard input
- Input is sent to the Rust game logic via the active
GameBackendโ Tauri commands on desktop, or the WebAssembly module in the browser - Game state is updated (player position, rotation)
- Frame is rendered using raycasting
- ASCII frame is returned to the frontend and displayed
This project is licensed under "All Rights Reserved" - see the LICENSE file for details.
The dithering module (app/src-tauri/src/dither/) is licensed under the Mozilla Public License, v. 2.0 (MPL 2.0). This code was ported from Dither3D by Rune Skovbo Johansen. See LICENSE-MPL for the full MPL 2.0 license text.
The MPL 2.0 license applies only to the files in the dither/ module. All other code in this repository remains under the "All Rights Reserved" license.