81 lines
3.9 KiB
Markdown
81 lines
3.9 KiB
Markdown
# 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`.
|