A procedurally-generated pirate voyage for the Game Boy. Every sea is born from an 8-digit seed; share a seed, share a world.
You are a pirate captain with a small ship, a blank chart, and an ocean that has never existed before. Somewhere out there, ringing the center of the world, lie the Nine Isles of Legend — Libertalia, Whydah Deep, Kraken Skerry, The Locker, Old Roger Rock, Kidds Cache, Fiddlers Green, Dutchman Cape, and Maroon Spit. Each is guarded. Each hides a fragment of a chart. Assemble all nine, survive the final fleet, and the Treasure of the Nine Isles is yours.
Runs on DMG, CGB, AGB, SGB, emulators, and Analogue Pocket. DMG-first (4 shades), with color palettes auto-detected on CGB. On a Super Game Boy each sea gets a day or night border, chosen by the seed itself.
Play now in your browser: https://splch.github.io/pirates-folly/ — the ROM runs in a WebAssembly emulator, no install needed. Progress autosaves in the browser, and touch devices get an on-screen gamepad.
Get pirates_folly.gb from the
latest rolling release
(rebuilt on every push to main) or build it yourself,
then flash it to a cartridge or open it in any Game Boy emulator
(BGB, SameBoy, mGBA, PyBoy, ...).
| Button | At sea | In port | Elsewhere |
|---|---|---|---|
| D-pad | Sail (momentum — ease off!) | Menus | Edit seed digits |
| A | Dock at a beach / fire cannons / go ashore (with the dinghy) | Confirm / buy (RIGHT) | New game (seed screen); ashore: dig the X / reboard |
| B | Quit to seed screen (press twice; unsaved) | Back / set sail | Fire the pistol (ashore) |
| SELECT | Mute sound | Mute sound | Re-roll the seed (seed screen) |
| START | The chart (your map fills in as you explore) | — | Continue a saved game |
- Eight hex digits, one ocean.
DEADBEEFis a fine first sea. A starts a new voyage with the edited seed; START continues a saved one (the game autosaves whenever you dock or leave port, and on victory). - Chart everything. Sailing into a new cell inks it into your map. Newly charted waters may hold pirates (~1 in 5, ≈19%) or storms — and even charted waters stay a little dangerous (re-rolls at ~¼ rate). Pirates grow bolder with every fragment you carry; isle cells roll no random encounters, but their guardians will find you.
- Spot ports from the sea: beaches with plank-dock tiles are dockable. Trade four goods (rum, silk, spice, cannon), repair the hull, recruit crew, and ask the tavern for rumors — it knows the nearest port and the nearest unclaimed Isle of Legend. The shipyard refits your ship for gold: plating (+5 hull, twice), sails (+50% top speed), long guns (+40% reach), and a dinghy for going ashore.
- Go ashore. With the dinghy aboard, press A beside any beach to land and explore the island on foot — the same world at twice the zoom, so every coastline is exactly where the chart says. Dig up buried chests, salvage washed-up cargo, and read the grim landmarks of those who came before — all seeded per chart cell, with dug sites staying dug (saved). Snakes and skeletons guard the islands: press B for the pistol, and watch your hearts — at zero, the island takes a toll in gold and drags you back to the dinghy. Walk back to the dinghy and press A to reboard.
- Walk into town. Port districts are real settlements ashore: find the tavern, market, shipyard, and harbor house, and step up to one to use it — the same port screens, reached on foot. B backs out to the street.
- Win, and keep sailing. The Treasure's curse: after victory, charted waters roll encounters at full rate — a won sea never calms.
- Digging. An isle's fragment only comes out of the ground once its guardian is sunk — and you'll need the dinghy: land on the isle, walk to the X, and dig it up. No dinghy, no Treasure.
- Merchants. Lone sails hail you at close range: buy a small lot at half price (A), or rob the strongbox (B) — half of them sail with an escort that will come for you.
- The kraken. Rises only in deep water, rarely (~1 in 128 encounter rolls): 8 HP, quick guns, and a 60–123 gold hoard.
- Battery save, twice over. Two rotating save slots with sequence numbers; a corrupted slot falls back to the other copy automatically.
- Hull is life. Ramming a wild coast, enemy shot, and storm-tossed drifting all cost hull (watch the H reading at the bottom of the screen, beside your gold and fragment count); port shores are soft — bumping a dock costs nothing. At 0 you wreck: lose half your gold and wake in open water with a patched hull. Your greatest gold haul is remembered on the seed screen — and a fully inked chart pays a 500-gold bounty.
See MANUAL.md for the full captain's handbook.
Requires RGBDS (v1.0.x; CI pins v1.0.3). Either put
rgbasm/rgblink/rgbfix on your PATH, or point RGBDS at a bin
directory:
make # uses tools/rgbds/bin/ by default
make RGBDS=/path/to/bin/ # use your own RGBDS installOutput: pirates_folly.gb (128 KiB, MBC5+RAM+BATTERY) plus build/ artifacts
(symbol and map files used by the test suite).
The web/ directory is a static binjgb
(MIT) site deployed to GitHub Pages by .github/workflows/pages.yml on every
push to main (the workflow builds the ROM fresh, so the page always ships
the latest main). To preview locally:
make && cp pirates_folly.gb web/ && (cd web && python3 -m http.server)Headless PyBoy tests drive the real ROM and assert on VRAM, WRAM symbols, and hardware registers:
pip install pyboy
make check # builds the ROM, runs every test_*.py, then lint_worlds.py(or run them by hand: for t in tests/test_*.py; do python "$t"; done,
then python tests/lint_worlds.py — a seed sweep of spawns, isle land,
and the port census.)
test_title.py/test_cgb.py— title screen, CGB palette init, DMG/CGB paritytest_m2.py…test_m5.py— worldgen/streaming, ports & economy, combat, the Nine Islestest_sound.py— APU driver and song/SFX triggerstest_sgb.py— SGB header/detection, forced-run of the border transfertest_ports_m6.py— port content passtests/lint_worlds.py— reimplements the worldgen math in Python and sweeps 16 seeds against the running ROM, validating spawns land on water, every isle contains land, and port-district statisticstests/tune_balance.py— combat balance harness (port-access distance, automated duels)
CI (.github/workflows/ci.yml) builds the ROM and runs the whole suite on
every push and PR, uploading the .gb as an artifact.
src/ RGBDS assembly (the whole game is hand-written SM83)
main.asm boot, state machine, title & seed editor, save init
world.asm procedural ocean: value noise, streaming blits, fog of war, chart
sail.asm sailing physics, smooth scrolling, tile streaming, HUD, wreck
combat.asm pirates, guardians, broadsides, storms
shore.asm shore mode: exploring land on foot at 2x zoom (bank 4)
isles.asm the Nine Isles, digs, final battle, victory
port.asm docking, market, tavern, repair/recruit, battery save/load
sound.asm 3-channel shanty driver + SFX (no hUGEDriver)
sgb.asm SGB border transfer (CHR_TRN/PCT_TRN), seed-picked day/night
sgb_day.inc, sgb_night.inc generated border data (tools/png2sgb.py)
tiles.asm hand-drawn 2bpp tiles & 3x5 font (gfx literals), CGB palettes
rng.asm Mul8, Mix16 coordinate hash, xorshift16 PRNG
joypad.asm input with new-press detection and auto-repeat
defs.inc shared constants (states, tiles, tuning knobs)
tests/ PyBoy headless test suite + world lint + bank lint + tuner
res/ SGB border art (256x224 PNGs)
tools/ png2sgb.py (SGB border converter), vendored RGBDS (gitignored)
docs/ PIRATE_GAME_PLAN.md, PIRATE_LORE.md, dev references
MANUAL.md the player's manual
- The world is a pure function of the seed.
WorldTile(x, y)derives terrain from value noise — aMix16coordinate hash on an 8-tile lattice, bilinear elevation, thresholds for deep/shallow/sand/grass/forest/mountain. Nothing about the world is ever stored; only your changes are (fog of war, port markers, fragment/guardian bits, position, gold, cargo). Shore mode samples the same elevation function with 4-bit interpolation fractions (2x zoom), so landmass silhouettes match the sea chart exactly. - 32-bit seed, 16-bit fold. The seed editor's 8 hex digits fold to
wSeed16 = Mix16(b0:b1) + Mix16(b2:b3)— an additive fold, so00000000,FFFFFFFF, andAAAAAAAAdon't collapse to the same world. - Streaming scroll. The logic phase generates the entering row/column into staging buffers; VBlank code blits ≤21 tiles into the wrapping BG map, runs OAM DMA from HRAM, animates water, and updates the HUD window.
- Everything else is hashed too. Port districts (~19% of 4×4-tile districts), port names (16×16 prefix/suffix), market price drift, tavern rumors, encounter rolls, and the Nine Isles' positions are all pure hash functions of coordinates + seed — deterministic and storage-free.
- Bank 3 holds almost everything. Game code (worldgen, sailing,
combat, ports, isles, sound driver) and all data (tiles, font, songs,
strings, tables) live in ROMX bank 3, kept mapped at
$4000at all times except inside the SGB border transfer — which always ends inLoadTiles, restoring bank 3 before any banked return address is popped. Shore mode lives in ROMX bank 4: it shares ROM0 helpers (blitters, HUD, printing, hashing) and reaches bank 3 through theFarCall3/FarCall4trampolines in main.asm. ROM0 keeps only what must run regardless of the mapped bank: boot, main loop, the VBlank handler, joypad/RNG, the tile loader, the shared helpers, and the SGB transfer machinery itself (plus the SGB packets, which are read mid-transfer while border banks 1/2 are mapped). - SGB borders. On SGB/SGB2 (detected via the boot ROM's C register),
the border is beamed over with CHR_TRN/PCT_TRN VRAM transfers while the
screen is frozen with MASK_EN: 256 SNES 4bpp tiles, a 32x28+1 map, and
three 15-color palettes per border, all generated offline from PNG art.
Day or night is bit 0 of
wSeed16— the sky is part of the world. - Battery save. MBC5 SRAM at
$A000with magic bytes, a version field, and a checksum; validated on boot. Isle positions are recomputed from the seed on load, never saved. - Music. Six public-domain shanties — Drunken Sailor (title), Wellerman (sailing), Spanish Ladies (port), Blow the Man Down (battle), Rolling Home (victory), Leave Her Johnny (shipwreck) — on a hand-rolled 3-channel driver (pulse melody, pulse bass, noise percussion) with priority SFX.
The design doc is docs/PIRATE_GAME_PLAN.md; the names and rumors draw on docs/PIRATE_LORE.md, a researched reference on Golden Age piracy.
MIT © 2026 Spencer Churchill. The shanties are public domain.