Skip to content

Latest commit

 

History

93 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PIRATE'S FOLLY

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.

Playing

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. DEADBEEF is 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.

Building

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 install

Output: pirates_folly.gb (128 KiB, MBC5+RAM+BATTERY) plus build/ artifacts (symbol and map files used by the test suite).

Web build

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)

Testing

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 parity
  • test_m2.pytest_m5.py — worldgen/streaming, ports & economy, combat, the Nine Isles
  • test_sound.py — APU driver and song/SFX triggers
  • test_sgb.py — SGB header/detection, forced-run of the border transfer
  • test_ports_m6.py — port content pass
  • tests/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 statistics
  • tests/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.

Project layout

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

How it works

  • The world is a pure function of the seed. WorldTile(x, y) derives terrain from value noise — a Mix16 coordinate 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, so 00000000, FFFFFFFF, and AAAAAAAA don'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 $4000 at all times except inside the SGB border transfer — which always ends in LoadTiles, 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 the FarCall3/FarCall4 trampolines 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 $A000 with 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.

License

MIT © 2026 Spencer Churchill. The shanties are public domain.

Releases

Contributors

Languages