Skip to content

v0.4.0 — The console gets a body, and a language of its own

Latest

Choose a tag to compare

@Maartz Maartz released this 09 Aug 19:24
· 2 commits to master since this release

Atomboy runs in both directions now. It still plays Game Boy and Game Boy Color
games — but the instruction table behind the emulated CPU also emits a language
that compiles Elixir into cartridges, and a complete emulator in RISC-V
assembly that runs on a chip you can hold. On the playing side, the window
grew a console around itself, learned to write time down and replay it, and
learned to fork.

Potion — the console, written from the other side

  • A language in the lineage of Andy Gavin's GOOL: the surface is Elixir, the
    semantics are the console's, and the output is a real 32 KB cartridge —
    header, Nintendo logo, checksums — that runs here or on hardware.
  • Six games in games/, each written to teach the compiler something: a walking
    square, two screens with solid walls, a room bigger than the screen with a
    chasing camera, Pong with a title screen and an opponent that can lose, Tower
    the upward platformer, and Moananas Island.
  • Sound as notation rather than registers — motifs, triplets, bar lines, bends,
    three voices sharing four channels — generated identically to what the BEAM
    emulator produces.
  • The assembler is derived from the same instruction table as the emulator's
    decoder, so the two cannot disagree, and the emulator is the compiler's test
    harness down to pixel-exact assertions.
  • ELIXIR_ERL_OPTIONS="-noinput" mix atomboy.live games/pong.exs keeps the
    console hot: edit the game, save, and the running machine picks up the new
    cartridge without losing the ball's position or the score.

The native core — the emulator, in RISC-V

  • The same instruction table emits a complete SM83 emulator in RV32 assembly,
    written by plain Elixir: no C, no linker, no toolchain in the loop.
  • Validated differentially against the Elixir emulator as oracle, with the
    comparison run inside the guest — dmg-acid2 comes out pixel-identical, and
    the four voices match Atomboy.APU sample for sample.
  • 18.0 RV32 instructions per SM83 instruction on a register-move loop, 27.5 on a
    mixed block, 19,576 bytes of code all told — 59.7% of an ESP32-C6's
    instruction cache, which was the whole point.
  • esp32/native/ runs it on an ESP32-C6 with an ILI9341 panel and an I2S
    amplifier. The bus turned out to be the budget and the sound turned out to be
    the clock; both lessons are recorded beside the code that acts on them.

The console gets a body

  • ⌘B draws a console around the screen — four bodies chosen by the panel
    preset, because the screen and the plastic around it are one decision: the
    DMG's gray, the Pocket's silver, the CGB's teal, and a living-room television
    for crt.
  • Drawn rather than photographed, from each body's real millimetres, so they
    stay crisp at any size. The D-pad rocks and the buttons sink under your thumb,
    from the keyboard or a gamepad, and the power LED dims to an ember on pause.
  • Under them, the LCD simulation: response curve, dot structure, crosstalk, a
    contrast dial, and an Age slider that creeps dead columns in from the edges.
  • View → Scale 1×–5× (⌥⌘1…⌥⌘5) sets the window to exactly 160×144×N.

The time machine

  • Record Movie ⇧⌘R writes a .tas: a header, an anchor, and one byte per
    frame holding the eight buttons. The core touches no wall clock and no
    randomness, so replay is not approximate — it is the same frames.
  • Re-recording is what makes it a tool: load a state while recording and the
    take is truncated to that frame and keeps going. Everything after it never
    happened.
  • Frame Advance (.) buys exactly one frame while paused and closes the
    pause behind it.
  • Export Movie… ⌘⇧E renders a take to MP4 with sound or GIF without, in a
    second engine beside the one you are playing, so the game does not stop while
    the film is developed. Needs ffmpeg (brew install ffmpeg); the CLI has
    --record, --replay, --export and mix atomboy.export.

The multiverse

  • ⌘D forks the running game into four live universes. They take the same
    buttons, so all four D-pads rock in unison, but each one's divider counter is
    nudged by a different offset and game RNG feeds on divider timing: same input,
    different luck.
  • Universe 0 is the unperturbed original — the future that would have happened
    anyway.
  • Click one to listen to it, double-click to commit and let it grow into the
    only one left, Esc to abort onto universe 0.
  • While four machines run, anything that would have to answer "in which
    universe?" is refused at the engine, and the menu greys itself out from the
    engine's own announcement rather than from a guess.

The clock

  • Settings → Clock, per game: keep the console on real time, or hand the
    cartridge a date of your choosing, with a +1 day button for the games that
    want to be slept on.
  • During a take the panel locks. The recording owns the clock, deriving its hour
    from its anchor and its frame count, so the same movie tells the cartridge the
    same hour on every replay.

Fixes, and the repo in English

  • Turbo picks its speed — 2×, 4×, 8× or Uncapped — held on a key or a shoulder,
    latched from the menu and the HUD, and refused while the cable is plugged,
    because the protocol is a paced duet.
  • The Settings window is finally allowed to shut its own door.
  • Saves live in a library keyed by the cartridge header, so renaming or moving a
    ROM never orphans what it had; Saves… ⌘⇧S browses them with thumbnails,
    and profiles let two players share one ROM.
  • The link cable publishes a Bonjour service, so joining is a list of the
    consoles on your network rather than an address to type.
  • The codebase speaks English: identifiers, comments and UI strings across the
    emulator core, the front ends, Potion and the macOS shell.
  • The documentation is an 87-line README and four pages behind it —
    docs/features.md, docs/design.md, docs/potion.md, docs/native.md
    plus two deep dives that each follow one thing all the way down.

Assets

File
Atomboy.app.zip the macOS app (Apple Silicon), signed and notarized
atomboy_macos_arm standalone CLI binary, macOS (Apple Silicon)
atomboy_linux_x64 standalone CLI binary, Linux x86-64

The app is Developer ID signed, hardened, and stapled: it double-clicks clean.
The bare CLI binaries are ad-hoc signed — on first launch, right-click → Open
(or xattr -d com.apple.quarantine).

Or via Homebrew:

brew install --cask maartz/tap/atomboy      # Atomboy.app
brew install maartz/tap/atomboy-cli         # the terminal binary