A Rust and Bevy port of Stardust 1.1, James Burton's 1995 Macintosh puzzle game (freeware, THINK Pascal, art in PixelPaint). Fifty levels of walking, levitating and building stardust blocks to reach the exit portal.
| Path | What |
|---|---|
src/ |
The game and level editor (Bevy 0.19) |
crates/stardust-core |
Deterministic actions, animation frames and timing, no engine dependencies |
crates/stardust-level |
Level model, RON format, ASCII legend, importer for the original data, passwords |
crates/stardust-extract |
CLI that pulls art, sounds and levels out of the original archive |
crates/macrsrc |
Classic Mac resource fork, PICT and snd decoders |
crates/stuffit |
StuffIt 5 archive reader with the Arsenic decompressor |
archive/ |
The original Stardust_Mac_EN.sit |
vendor/ |
Faithful exports of every original resource (generated) |
assets/ |
Sprite sheets, sounds and levels the game loads (generated) |
python3 scripts/extract.pyThe script downloads the original archive
into archive/ if needed, verifies its checksum, then runs the Rust extractor.
It writes original resources to vendor/ and game-ready art, sounds and all
50 levels to assets/. Hero sheets retain their original pixel positions and masks.
Generate the assets with the extraction script above before building.
cargo run| Key | Action |
|---|---|
| Arrows or WASD | Walk, up, crouch |
| Space / Return | Magic: build or destroy the block in front |
| Down + Magic | Build or destroy diagonally down (or the green block underfoot) |
| Up + Magic | Levitate on a green block, or destroy the green block above |
| Up in an exit portal | Finish the level |
| R | Reset the level |
| 1 / 2 / 3 | Fast / normal / slow animation |
| Esc | Back to the title |
Title screen: Space new game, P enter a password, I instructions, S the
story, E level editor, G GitHub project (also clickable).
Levels are RON files with one string per row. The legend is mnemonic rather than the original digits:
. empty * star wall # gray wall I entrance O exit
U warp pocket R red wall + star ~ fall wall
< one-way (only passable moving left) > one-way (only passable moving right)
^ elevator | tunnel % vertical warp ( companion ) companion mark
b blue block g green block / " & pass-through decorations
assets/levels/campaign.ron lists the level files in play order. Each level
carries its name, optional password and which hero is drawn.
Press E on the title screen. Left-click paints the current brush, right-click
erases, the mouse wheel or [ ] change the brush, 1–9 and Page Up/Down
load campaign levels, P test-plays, N starts a new level and Ctrl+S saves
to assets/levels/ (native builds only; the browser build logs the RON).
Requires Node.js 22 or newer as well as Rust and Python 3.
python3 scripts/web.pyThe script extracts missing assets, installs the WebAssembly target and matching
wasm-bindgen CLI, optimizes the build with Binaryen, copies assets, and opens
http://127.0.0.1:8080.
Press Ctrl+C to stop the server. The complete site is in target/web/.
Touchscreens show a D-pad, Magic, Reset and Menu. Hold a direction and Magic together for combined actions. Browser audio starts after a tap or keypress.
To build without launching the server and deploy to Cloudflare:
python3 scripts/web.py --build-only
npx wrangler@4 deploy --config web/wrangler.jsoncThe configuration serves the site at stardust.dusheck.com. The dusheck.com
zone must be active in your Cloudflare account; Wrangler creates the custom domain
and its DNS record. Run npx wrangler@4 login once for local deployments.
The regression suite compares 1,092 recorded scenarios (364 at each speed) with results from the original executable: position, facing, completion, runtime tiles, ticks, presentation delays, background restoration, sprite rectangles and sound requests. It covers input combinations, magic, crumbling, warps, death, respawn, reunion and input sequences on all 50 levels, including 150 solver-generated routes. Other tests check extracted pixels, passwords, transitions, audio channels and presentation at 30, 60 and 144 updates per second.
Resting tiles in the original 1.1 binary do not pulse or cycle colors. Star and
gray walls choose one of nine patterns at level load ($3dc2); idle drawing
restores cached pixels ($3952). The idle-tile fixtures and pixel tests cover
this at all three speeds. Glow-like changes during magic, crumbling and portal
actions are part of their recorded animation frames.
python3 scripts/extract.py
cargo test --workspace --no-fail-fast
cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --all --check
node --test tests/web_controls.test.mjsNormal tests use compact checked-in replay recipes and require no emulator.
binary_replays.tsv stores each initial board, speed, hero, held keys and elapsed
input ticks, plus a SHA-256 of every action's complete observation, including
all runtime tiles, frame delays, restoration/blit rectangles and sound requests.
The digest also includes the scenario header and record boundaries. This replaces
the repetitive 5 MB snapshot file; expanded evidence is generated under target/.
The fixture harness executes original CPU instructions with mocked Macintosh drawing, audio and input calls and controlled TickCount and Random. CI re-executes that binary and checks that the committed replay expectations still match. Solver routes supplement the hand-written interaction matrix and campaign probes. They include complete solutions for 24 levels at all three speeds; the remaining 26 levels have exploration prefixes. Tests enforce this completion coverage as well as zero mismatches in the recorded state, timing, drawing and sound fields. This is regression evidence, not a proof of equivalence for every possible input.
Audio-device latency is not reproduced; the original unpaced reunion wipe runs in order at rendering cadence. Internal representations such as password obfuscation need not match. QuickDraw scan conversion and random behavior were checked against Apple's original sources.
To regenerate fixtures, install uv and run:
cargo run -p stardust-extract --example dump_code -- archive/Stardust_Mac_EN.sit target/binary-audit
uv run scripts/audit_binary.py --write-fixturesThe harness verifies CODE 1 SHA-256
6a1495f0ded01362361b4672011ee76ecc60c1704e34047ef743bbd1b20065ed.
Disassembly and expanded traces go to target/binary-audit/; gameplay and password
fixtures live under their crates' tests/fixtures/, and transition fixtures live in
tests/fixtures/transitions.tsv.
The test-only solver searches the real Sim with a deterministic, bounded
best-first search that favors new local arrangements. Its state identity preserves
edited tiles, facing, stance, warp latch, turn cooldown, presentation deadline and
active crumble timers.
A relaxed distance map guides the search around walls; it does not decide game
physics. The historical Stardust-Solver
explores classical planning and describes its limits; it inspired the approach,
but none of its rule implementation is used as a reference.
# Requires extracted assets. Default: 50,000 distinct states per level/speed.
cargo test -p stardust-core --release --lib generate_campaign_routes -- --ignored --nocapture
# Validate candidate completion claims in the original binary and record its results.
uv run scripts/audit_binary.py --routes target/binary-audit/solver-candidates.json --write-fixtures
cargo test -p stardust-coreSet STARDUST_SOLVER_STATES to change the state budget. Search is offline and
opt-in; ordinary tests only replay the committed routes. Existing complete
solutions are replayed and retained when searching for additional routes. A budget
limit does not mean a level is unsolvable, and routes are not guaranteed to be shortest.
campaign_routes.json preserves the inputs, completion claims and search counts
needed to regenerate the oracle fixtures without rerunning the search.
To verify expectations without changing them, or test the fixture guards:
uv run scripts/audit_binary.py --check-fixtures
uv run scripts/test_audit_binary.pyA checksum failure identifies the scenario and saves the port's full observations
to target/binary-audit/actual-SCENARIO.tsv. Regenerate that original scenario,
then compare individual fields to locate the first differing action:
uv run scripts/audit_binary.py --scenario solver_01_2
STARDUST_ORIGINAL_TRACE="$PWD/target/binary-audit/binary_scenarios.tsv" \
cargo test -p stardust-core --test binary_fidelity all_recorded_scenarios -- --nocaptureThe compact format hashes the UTF-8 bytes of the scenario header followed by LF-terminated tab-separated observations. Each observation contains keys, elapsed input ticks, position, facing, completion, the row-major runtime grid in hex, requested frame delays, ordered frame drawing/sound operations, and elapsed action ticks. Both exporters use this explicit format; it is independent of Rust's debug formatting and of hash-map iteration order.
Useful CODE 1 offsets: input/actions $4166–47d8, animations $232e–393e,
restore/present/draw $3952–3ce2, crumble $21fa–22ce, loading $4b06–4d34,
exit wipe $1e96, reunion wipe $1f86, passwords $1202–18aa, sound $314–440.
Native builds accept a scripted session, handy for screenshots and reviews:
STARDUST_KEYS="1:space;3:space;5:right*1.2" STARDUST_SHOTS="6:play.png" cargo runF12 saves a screenshot at any time.