Files
daedalian/README.md
2026-10-31 18:12:15 -05:00

3.9 KiB

Daedalian Opus, ported to Rust

A rewrite of the Game Boy game as ordinary Rust, translated by hand from the disassembly in ../disassembly/. Unlike ../rust/ (a static recompilation that still runs the original instructions against emulated registers and memory), this is the game itself: the screens, the puzzle rules, the map, the story and the music sequencer are Rust functions over named state, so they can be read and changed.

cargo run --release                 # looks for "Daedalian Opus (USA).gb" here or in ..
cargo run --release -- --rom PATH

Controls: arrows = D-pad, Z = A, X = B, Enter = Start, Right Shift / Backspace = Select, Esc = quit.

What comes from where

The program contains no game content. At startup rom.rs parses the ROM into typed data: levels (Level: starting pieces, board layout, cursor start, music, pieces needed, which piece drops on the map), piece shapes, screen layouts (tilemap draw scripts), story text, passwords, tile graphics and the music byte code. Small numbers that are really code (coordinates, tile numbers for digits, timings) live in the Rust source as named constants.

Layout

file what
src/game/mod.rs Game state, the top-level flow (title → story → map → puzzle → ...), the per-frame tasks, the clear-time clock
src/game/puzzle.rs the puzzle: picking up, moving, rotating, flipping and placing pieces; the win check; reset; the level-clear screen
src/game/map.rs the map between levels: walking, castle doors, the bridge, the password bird, the piece that drops from the sky
src/game/menus.rs title, how to play, password entry, puzzle list
src/game/story.rs story screens and the ending
src/sound.rs the game's sound driver: an 8-channel byte-code sequencer (format documented at the top)
src/apu.rs the Game Boy sound chip, as the instrument the driver plays
src/video.rs tile graphics, two background maps, 40 sprites, palettes, and the renderer
src/rom.rs ROM loading and parsing
src/input.rs buttons, with the game's d-pad auto-repeat
src/main.rs window (winit + pixels), audio (cpal), frame pacing
src/headless.rs scripted runs for testing

The game code is written the way the original is structured: straight-line code that calls wait_frame() whenever it needs the next frame (to animate, or to wait for a button). It runs on its own thread; wait_frame sends the finished picture and sound to the host and blocks until the host sends the next frame's buttons.

The display model (tiles, maps, sprites) is kept because it's what the art is made of, so the game still draws by placing tiles and sprites. Map addresses like 0x9800 remain the way cells are named, since the screen layout scripts in the ROM use them.

Faithfulness

It's checked against the recompiled build in ../rust/ with the headless runner. Same inputs give the same screens (title, every menu, story pages, map walks, levels 1-3 and 36 solved, the ending), and the sound driver's chip register writes match frame for frame. Known differences:

  • Loading a screen takes no time here, where the original spent a few frames copying graphics with the display off, so the port runs a few frames ahead.
  • The original queued tile updates to the next vblank; here they happen immediately, so some changes appear one frame sooner.
  • The original's unused "resume music" sound (id $96) points at garbage; it just resumes.

Original quirks are kept on purpose and marked (sic) in the source. For example, undoing a Select flip that doesn't fit uses Start's rule.

Headless testing

cargo run --release -- --headless OUT_DIR 120 start:2 90 shot:title probe goto:44:54

Tokens: N run N frames; BUTTON:N hold a button for N frames (up down left right a b start select); shot:NAME save a PNG; probe print the level and cursor; goto:YY:XX steer the puzzle cursor (hex sprite coordinates). Audio goes to OUT_DIR/audio.wav.