Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

139 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Nautiloid — a GBA demake

A Game Boy Advance homebrew retelling of the opening mission of Baldur's Gate 3 ("Escape the Nautiloid"): 16-bit-style tile exploration with FF4-Advance menus and portraits, and real D&D 5e SRD combat fought right on the map, Chrono-Trigger style — visible initiative rolls, actions and bonus actions, opportunity attacks, and every die drawn as it lands, colored by damage type. The ship is alive: sentries patrol with real vision cones, and stealth, ambushes, and surprise rounds cut both ways. A PSG chiptune score, all on a real 240×160 GBA in about 15 minutes of play.

Everything here is built from scratch: no game engine, no devkitPro, no libgba, no ripped assets. The toolchain is stock Homebrew LLVM (clang + lld); the sprites, tiles, maps, and music are generated by Python scripts under tools/; the cartridge header, startup code, and interrupt handler are hand-written.

gameplay: title, the nautiloid, battles, the helm finale

Play

Grab a prebuilt nautiloid.gba from the Releases page and load it in any accurate GBA emulator (mGBA recommended), or build it yourself:

make        # build build/nautiloid.gba
make run    # build and launch in mGBA

Requires Homebrew LLVM and lld (brew install llvm lld), python3, and — for make runmgba.

The ROM (build/nautiloid.gba) runs on mGBA, other accurate emulators, and real hardware (a flashcart / EverDrive).

Controls

Button Field Battle / Menus
D-pad Walk Move cursor
A Talk / examine / confirm Confirm
B Cancel / back
Start Party menu: Status / Equip / Items / Tactics
Start (title) Begin
Select (title) Jukebox (press A on Under Selune for synced karaoke lyrics)
L (title) Attract mode: the game plays itself

Hold A or B to fast-forward dialogue text. Tactics (in the party menu or mid-battle) sets DQ-style AI per member — Orders, Wisely, All Out, Healer, No Slots — so companions can fight themselves while you drive the hero.

The mission

Wake in a pod aboard a mind flayer warship falling through Avernus. First pick your class — any of the twelve D&D classes, three subclasses each — then choose who you are: a custom Tav built from scratch (race, background, standard-array stats, skill proficiencies), that class's origin hero (Astarion the rogue, Gale the wizard, Karlach the barbarian, Lae'zel the fighter, Shadowheart the cleric, Wyll the warlock), or The Dark Urge — a custom character, any class, who hears intrusive thoughts. Then fight your way to the helm before the ship crashes.

Faithful beats from the BG3 prologue, condensed into an FF4 dungeon:

  • The Nursery — wake in a burst pod; a larva pool, a restoration device.
  • The Surgery — free the brain of the dying elf Myrnath. It is a newborn intellect devourer; spare it and Us fights at your side (or mutilate it).
  • The Deck — a red dragon strafes the hull; Lae'zel the githyanki joins you against a pack of imps.
  • The Ceremorphosis Chamber — find the eldritch rune, slot it into the console, and will Shadowheart's pod open. Recruit her, or leave her.
  • The HelmCommander Zhalk duels a mind flayer while a transponder countdown ticks. Connect the nerves and escape — or kill Zhalk for the Everburn Blade first, if you can survive him.

Your choices — who you freed, who you saved, whether you took the blade — are tallied over the wreckage on the Ravaged Beach.

Your character

All twelve SRD classes are playable to level 3, each with its real kit: a barbarian who rages (damage resistance + a bonus to every swing), a monk spending ki on Flurry of Blows and Patient Defense, a paladin who arms Divine Smite onto the next hit and heals with Lay on Hands, a warlock casting Eldritch Blast on short-rest pact slots, a sorcerer, and a druid who Wild Shapes into a beast — a replacement actor with its own HP that hands you back the moment it drops, staged like a Final Fantasy summon. Casters draw from a table of 83 SRD spells (levels 0–2); prepared casters swap their prepared list in the party menu. Leveling up offers real choices — the defining one being your subclass (each class has its SRD subclass plus two original homebrew ones). Racial traits reach into the dice: halfling Luck rerolls natural 1s, half-orc Relentless Endurance refuses to drop, Savage Attacks piles on crit dice. And skill checks happen on the field — the visible d20 rolls right in the dialog box, "Arcana 15+4=19 vs 12 PASS," to read a rune or slip past a patrol.

Combat is SRD 5e for real: d20 + modifiers vs AC with every die drawn as it lands (polyhedra in damage-type colors), advantage/disadvantage, crits that double dice, spell slots, concentration, and class features at their real levels — Second Wind at fighter 1, Action Surge at 2, sneak attack from Hide, Sleep as an HP-pool with auto-crits on sleepers. Fights happen where you stand: initiative pops over every head, the camera frames the brawl, melee dashes in, downed friends lie where they fell, sleeping foes snore, and enemies pick targets by expected value (imps hunt your squishiest). Defeat offers an instant retry.

Between the set-piece fights the ship itself pushes back. Imps patrol with forward vision cones — blunder into one and it runs you down for an ambush where your whole side is surprised; skirt its peripheral vision (a rogue-led party walks quieter) and strike from behind to surprise it instead, in the full 5e sense: the surprised side simply loses round one. Doors between the surgery, deck, and pod rooms open both ways, so gear you missed stays reachable — weapons turn up on corpses and in chests, and what you find knows who you are: the dead githyanki duelist offers a bard a rapier, a rogue a shortsword, a ranger a trident. Zhalk's Everburn Blade still burns (a real weapon rider: +1d4 fire, DEX save for half) if you can take it from him.

How it's built

src/            bare-metal GBA C + the game
  crt0.s        cartridge header, stacks, section init, BIOS IRQ dispatch
  gba.h         MMIO register map
  video.c       Mode 0 background + brightness fades
  text.c        FF4 window chrome, word-wrapped typewriter dialogue, menus
  oam.c         sprite (OBJ) manager
  audio.c/.h    PSG tracker + SFX driver, ticked from VBlank
  field.c       tilemap rooms, grid walking, camera, NPCs, event triggers
  encounter.c   5e battles on the field map: initiative, action economy,
                dice display, opportunity attacks, DQ-style party tactics
  party5.c      5e character sheets for the party (SRD standard array)
  game.c        title, prologue crawl, class select, name entry
  events.c      the story: rooms, dialogue, cutscenes, the finale
  data.c        party stats, class kits, enemy definitions
  menu.c        Start menu: status sheets, equipment, items, tactics
rules/          pure C 5e SRD combat core -- dual-target: compiled into the
                ROM and natively for `make test-rules` (property tests:
                closed-form expectations validated against Monte Carlo,
                held to a ratcheted coverage floor)
tools/          Python asset pipeline (no binary art in the repo)
  mkassets.py   compiles art/music sources -> build/gen/assets.{c,h}
  mksrd.py      generates C tables from extracted SRD 5.1/5.2.1 data
  srd/          SRD data (CC-BY-4.0), homebrew monster overrides, invariants
  art/          tiles, field & battle sprites, room maps (ASCII pixel art)
  music/        original FF4-idiom chiptunes as note data
  fixrom.py     writes the Nintendo logo + header checksum (gbafix equivalent)
test/           headless mGBA test harness
  runner.c      libmgba driver: scripted input, screenshots, memory peek/poke
  scenario.py   deterministic full playthroughs for verification
  host/         the real game logic compiled natively against a fake engine,
                run under ASan/UBSan with its own ratcheted coverage floor
gba.ld          linker script (ROM / IWRAM / EWRAM)
Makefile

Verification

The game is validated by driving full playthroughs headlessly. test/runner.c links against libmgba, injects controller input, and screenshots story beats; a small in-ROM demo mode (auto-advancing dialogue, a poked choice buffer, and a "field ready" flag the harness waits on) makes runs deterministic without guessing frame counts. test/scenario.py encodes several branching paths (different classes, sparing vs. mutilating Us, saving vs. abandoning Shadowheart, connecting the transponder vs. killing Zhalk) that are run and screenshot-checked end to end, plus living-encounter scenarios: sneaking a backstab on a patroller (sneak_strike), getting run down by one (cone_ambush), and the helm's sleepers (helm_sleepz).

Underneath the playthroughs, the game logic itself -- menus, character sheets, and full 5e battles -- also compiles natively against a fake engine (test/host/) and runs under AddressSanitizer/UBSan, with battles driven through the real tactic AI so spellcasting, concentration, wild shape, and the helm's allies are asserted behaviorally, not just observed not to crash.

make gate runs the whole ritual in one command -- ROM and runner builds, the rules and division-helper suites, the sanitizer-clad host suite, the counterpoint linter, every scripted playthrough (the four class paths, stealth/ambush encounters, and targeted checks for wild shape, the level-up subclass pick, spell preparation, origins, the Dark Urge, field skill checks, and the crash screen) with structural pass/fail assertions, and two ratcheted coverage floors. A built-in crash reporter (src/panic.c) draws a panic screen with breadcrumbs and a git build id, mirrors it to the mGBA log at FATAL, and a VBlank watchdog reports the exact PC of any main-loop hang.

Debug flags (CodeBreaker cheats)

The engine's demo flags live at fixed EWRAM addresses; mGBA's cheat interface (Tools → Cheats, CodeBreaker format) can set them:

Code Effect
3203FF00 0001 Attract mode (same as L on the title)
3203FF07 0001 With attract on: dialogue auto-advances, battles stay manual
3203FF02 0002 Attract mode goes for the Zhalk kill at the helm

Credits

Original game Baldur's Gate 3 © Larian Studios; Final Fantasy IV © Square Enix. This is a non-commercial fan demake with all-original code, art, and music — no assets from either game are used. The 8×8 text font is font8x8 by Daniel Hepper (public domain).

This work includes material from the System Reference Document 5.1 and the System Reference Document 5.2.1 by Wizards of the Coast LLC, available at dndbeyond.com/srd, licensed under the Creative Commons Attribution 4.0 International License. Creatures not in the SRD (the devourer, cambion commander, and mind flayer stand-ins) are original homebrew.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages