MosaiK8 is a framework for retro console development built around mosaik, a modern, high-level programming language. The mosaik compiler has two C backends — GBDK and cc65 — that together target nine consoles (Game Boy / Color / Pocket / Mega Duck, SMS, Game Gear, NES and Atari Lynx, PC Engine) from one language and one set of source files.
join our Discord: https://discord.gg/BtYaF6Ydwr
projects/vm-megademo: one source tree, five
consoles - a seven-part demo directed by VM8 event scripts.
You need Python 3.7+ and git. Everything else is fetched by setup_tools.py.
# 1. Get the code
git clone https://github.com/SiENcE/mosaik8.git
cd mosaik8
# 2. Fetch the toolchains (GBDK-2020, cc65), test emulators and Python packages
python setup_tools.py
# 3. Build a sample (an animated player and a pacing enemy)
python mosaik8.py build --platform gameboy projects/vm-clipdemoThe ROM lands in projects/vm-clipdemo/build/gameboy/vm-clipdemo.gb. Pass a
different --platform to build the same project for another console, for
example gameboy_color (.gbc), sms (.sms) or lynx (.lnx). Leave
--platform out to build every console in the project's target_platforms.
Next: see Start a project below.
- GBDK backend (default) — emits GBDK C, linked by the GBDK-2020
toolchain (
lcc/sdcc) into Game Boy, Game Boy Color, Analogue Pocket, Mega Duck, Sega Master System, Game Gear, and NES ROMs. - cc65 backend — emits cc65 C, linked by cc65 (
cl65) into Atari Lynx (.lnx) and PC Engine (.pce) ROMs.
The lexer, parser, type-checker, and all logic codegen are shared; only the prelude and standard-library lowering differ per backend. Programs that stay within the portable stdlib subset (text, input, timing, sound, sprites, background, palettes) build for every console unchanged.
Naming: the framework/CLI is MosaiK8; the language is mosaik.
.mos= source ·.c= generated intermediate ·.gb/.gbc/.duck/.sms/.gg/.nes= GBDK ROMs ·.lnx/.pce= cc65 ROMs.
- Python 3.7+ with the
tomlpackage (developed and tested on 3.12; on 3.11+ the standard library'stomllibis used where it is available) - GBDK-2020 — for the GBDK consoles (installed by
setup_tools.py) - cc65 — for the Atari Lynx / PC Engine targets (installed by
setup_tools.py) - (optional, for verification) PyBoy (Game Boy ROMs) and libretro.py + cores (Lynx / PCE / SMS / GG / NES)
setup_tools.py downloads and installs everything into the folders the build
tool expects (all gitignored):
python setup_tools.py # install everything that is missing
python setup_tools.py --check # report what is installed, change nothing
python setup_tools.py --only gbdk,cc65 # just the toolchains
python setup_tools.py --only cores,python # just the test emulators
python setup_tools.py --force # reinstall even if already present
python setup_tools.py --check --json # machine-readable status (the studio's setup wizard)
python setup_tools.py --prefix DIR # install into DIR instead of beside this file--prefix defaults to $MOSAIK8_HOME, else the checkout. It is where an
installed (frozen) studio puts its toolchains, since it cannot write into its
own tree; mosaik8.py looks there too (see the search order below).
Everything below is fetched from its official source onto your machine and never bundled with MosaiK8: this repository contains none of it, and each piece stays under its own licence.
| Component | Installs | Licence | Used for |
|---|---|---|---|
gbdk |
GBDK-2020 (latest GitHub release) → gbdk/ |
GPLv2 + linking exception (SDCC runtime: GPL + exception) | building all GBDK-backend consoles |
cc65 |
cc65 Windows snapshot → cc65/ |
zlib | building Atari Lynx / PC Engine |
cores |
Handy, Beetle Lynx, Holani (Lynx), Beetle PCE Fast, Genesis Plus GX (SMS/GG), FCEUmm (NES) → emu/libretro/ |
Handy zlib · Beetle Lynx / Beetle PCE Fast / FCEUmm GPLv2 · Holani GPL-3.0 · Genesis Plus GX non-commercial | headless Lynx/PCE/SMS/GG/NES testing |
python |
pyboy, libretro.py, pillow, toml (pip into site-packages) |
PyBoy LGPL-3.0 · libretro.py MIT · Pillow MIT-CMU · toml MIT | build tool + emulator harnesses, in a checkout |
pylibs |
pyboy, libretro.py as wheels → pylibs/ |
as python |
the same for an installed studio with no pip |
Genesis Plus GX's licence forbids commercial use; it is only a test emulator here and nothing you build depends on it.
Notes:
pythonandpylibsare alternatives, not a pair: a bare run pickspythonin a checkout andpylibsunder--prefix/$MOSAIK8_HOME.- cc65 binary snapshots exist for Windows only, so off Windows a bare run
SKIPS it (
--check --jsonreports it as not fetchable, with the reason); install cc65 from your package manager and setCC65_HOME. - The Beetle Lynx core needs the real Lynx boot ROM (
lynxboot.img, 512 bytes, copyrighted — not downloadable). Drop it intoemu/libretro/yourself; without it the harness falls back to the Handy core, which boots homebrew BIOS-less.
If you already have GBDK-2020 or cc65 installed elsewhere, set an environment variable instead of running the installer:
# Windows (PowerShell)
$env:GBDK_HOME = "C:\path\to\gbdk-2020" # for GBDK consoles
$env:CC65_HOME = "C:\path\to\cc65" # for Lynx / PC Engine
# Linux / macOS
export GBDK_HOME=/path/to/gbdk-2020
export CC65_HOME=/path/to/cc65Search order per toolchain (mosaik8_build.py find_gbdk / find_cc65):
the GBDK_HOME/CC65_HOME env var → $MOSAIK8_HOME/gbdk or
$MOSAIK8_HOME/cc65 (the setup_tools.py --prefix location) → a
gbdk-2020//gbdk/ or cc65/ folder next to mosaik8.py (what
setup_tools.py creates in a checkout; GBDK also tries the current directory
and a few common system paths) → the system PATH (lcc / cl65).
python mosaik8.py init my_game
cd my_gameThis creates my_game/mosaik.toml + my_game/src/main.mos.
A minimal portable program: an @ you move with the D-pad. It builds for all
nine consoles unchanged, because text works in character cells and
SCREEN_COLS / SCREEN_ROWS are each console's own size:
module "main" {
import "platform.video"
import "platform.input"
import "platform.system"
import "graphics.text"
var x: u8 = 4
var y: u8 = 4
function main() {
video.enable_lcd()
text.print_string(1, 1, "MOVE THE @")
loop {
text.print_string(x, y, " ") -- erase
if input.pressed(INPUT_LEFT) and x > 0 { x -= 1 }
if input.pressed(INPUT_RIGHT) and x < SCREEN_COLS - 1 { x += 1 }
if input.pressed(INPUT_UP) and y > 3 { y -= 1 }
if input.pressed(INPUT_DOWN) and y < SCREEN_ROWS - 1 { y += 1 }
text.print_string(x, y, "@") -- draw
system.delay(80)
video.wait_vblank()
}
}
export main
}
The full language is documented in
docs/mosaik_lang_spec.md.
# Build every console in target_platforms (uses mosaik.toml in the current
# directory; `init` sets Game Boy + Game Boy Color)
python mosaik8.py build
# ...or any other supported console
python mosaik8.py build --platform gameboy_color
python mosaik8.py build --platform nes
python mosaik8.py build --platform lynx samples/hello.mos
# ...or every supported console at once (single-file mode only)
python mosaik8.py build --all-platforms samples/hello.mos
# MosaiK8 has no `run` command — open the built ROM in an emulator directly:
pyboy samples/build/gameboy/bounce.gb # Game Boy
python emu/libretro/run_lynx.py samples/build/lynx/hello.lnx # Atari Lynx (headless)Supported --platform values: gameboy, gameboy_color, analogue_pocket,
megaduck, sms, gamegear, nes (GBDK backend), and lynx, pce (cc65
backend). A program that stays within the portable stdlib subset builds for all
nine unchanged — size the world with the per-target SCREEN_WIDTH/SCREEN_HEIGHT
constants and it behaves right everywhere too. Gate non-portable code with
if platform == "...".
The build tool has exactly two modes — there is no "scan the whole tree" mode.
- Single-file mode —
build <file.mos>: abuild/folder is created next to the file, and the generated.c/ROM are named after the source. Transitive non-stdlib imports are pulled in automatically.python mosaik8.py build samples/bounce.mos python mosaik8.py build --platform gameboy samples/pong.mos
- Project mode —
build <mosaik.toml>(or a directory containing one, or nothing for./mosaik.toml): compiles every.mosunder[source] folderinto one program and builds eachtarget_platformsconsole.python mosaik8.py build projects/game # directory containing mosaik.toml
Other commands: clean (removes the project-mode build/), init, version.
Add --debug for lcc debug symbols. See the build-system section of the
language spec for the full CLI and mosaik.toml
reference.
[project]
name = "my_game" # names the output .c and ROM
target_platforms = ["gameboy", "lynx", "pce"] # any of the nine
[source]
folder = "src/" # where .mos sources live
[assets]
sprites = ["assets/sprites.png"] # PNGs → tile data at build time
[build]
output_dir = "build"
rom_size = "64KB" # GB family only: cart geometry (banking)
ram_size = "8KB" # GB family onlyThat is a minimal file. Every key the build acts on, including the [build]
knobs for banking, the Lynx budgets and the VM8 runtime, is listed in §5.1 of
the language spec and, with defaults, in the
cheat sheet Part 6. Anything else prints a ⚠️ warning
so typos don't pass silently. Platform names accept aliases
(atari_lynx → lynx, pc_engine → pce, …).
The samples/ folder holds ready-to-build programs (hello, bounce, pong,
beep, banked, colors, metasprite, cross_platform, …); full projects
live in projects/ (game, shmup, background, colorlab,
game-slice, …). The vm-* projects are VM8 games — logic authored as
event lists (scripts/*.evt.toml) compiled to a bytecode blob
(python -m mosaik_vm scripts -o src/scripts.mos); many carry a verify.py
that asserts their behaviour headlessly on the Python reference interpreter
(mosaik_vm/refvm.py) and, where emulators are installed, on the real ROM.
Build any sample in single-file mode and any project in project mode:
python mosaik8.py build samples/bounce.mos # → samples/build/<console>/bounce.*
python mosaik8.py build projects/shmup # → projects/shmup/build/<console>/starfall.*Run the test suite:
python tests/run_all.py # unit tests
python tests/run_all.py --samples # also build every sample × console + the projects
python tests/verify_roms.py # behavioural checks (PyBoy; --lynx/--pce/--sms/--gg/--nes add cores)run_all.py exits non-zero on any failure, so it is suitable for CI. ROM
verification (PyBoy + the libretro harness) is documented in the
language spec.
- Nine consoles, one source — Game Boy / Color / Pocket / Mega Duck, SMS, Game Gear, NES (GBDK) and Atari Lynx, PC Engine (cc65).
- Modern syntax — Lua/Python-flavoured surface over an 8/16-bit type system: structs, enums, modules, control flow, first-class function pointers (callbacks), and the portable stdlib compile identically everywhere.
- Capability gating — calling a stdlib function a console lacks is a clear compile-time error, not a silent no-op or link failure.
- Standard library — video, input, sound (a portable beep + tone on all
nine; a per-console second voice, and the
hw.psgport primitive for the SN76489), sprites + metasprites, scrollable background tilemap, window (GB family), draw primitives (Lynx), andgraphics.palette(the 4-colour GB palette model on every console, degrading to greys on the 4-grey machines). - Music — an opt-in native multi-channel driver (
lib/vm/music.mos) plays a song authored as DATA across each chip's real channels: Game Boy family (pulse + wave + noise), Atari Lynx (Mikey), and SMS / Game Gear (SN76489), leaving a channel free for SFX. Byte-identical when a game uses no audio. - Asset pipeline — PNGs listed in
mosaik.toml(or--asset) become tile data on every console; a sidecar*.sprites.tomlslices a sheet into named sub-sprites ready forsprite.set_meta. - ROM banking —
bank(N)places functions in MBC5 banks to grow past 32 KB on the Game Boy family (the Sega mapper on SMS/GG, UNROM on the NES).[build] code_banksbanks whole modules there too, maps real ROM banks on the PC Engine (HuCards up to 1 MB) and turns them into cart overlays on the Lynx, wherehotkeeps a per-frame function resident. - Game framework — reusable
lib/modules (scenes, follow camera, collision — incl. a paintable per-cell collision layer decoupled from the visual tile, with one-way platforms — dialogue, HUD, callback-driven animation, genre kits) for building multi-room games. - VM8 — a bytecode VM runtime (
lib/vm/) implementing the VM8 specification (docs/vm8-spec.md): game logic is event-script DATA (scripts/*.evt.toml→ a compiled bytecode blob viapython -m mosaik_vm) run by one fixed native engine, so an edit re-compiles data, not a game loop. Threads take arguments and are joinable through heap handles, scene change / reset / load are one RAISE exception mechanism, and expressions include the bitwise group. Opt-in native packs (player / scenes+doors+triggers / per-instance entity slots / top-down combat + HUD / projectiles) run at engine speed while scripts orchestrate - including the per-frame player-vs-actor overlap that fires an actor's On Hit, the platformer knockback impulse, and script-pinned animation states. Later packs add ANGLE-AIMED projectiles (vm.trig+ anatan2RPN token, flight in sixteenths of a pixel), ladders, emote bubbles, and persistent SRAM save/load. Runs on all consoles but the NES. - Text & UI — dialogue/menu boxes with GB Studio's own geometry, a
typewriter reveal, and two font models: a resident console font, or
text.glyph_buffer, which keeps the font in ROM and rasterizes glyphs on demand into a derived band so a scene can use nearly the whole tile table. Variable-width text rides the same band, keyed on the font's own width marker. On the GB family a box sits on the hardware window layer; on SMS/GG, which have none, it is plotted in screen space with the scroll snapped to a tile and the camera held. - Colour tiers — per-tile background palettes, 4bpp background and sprite tiers (PCE and SMS/GG always, Lynx opt-in), per-frame sprite palettes, and scanline parallax bands over an LYC/STAT interrupt with per-band column streaming.
- Raster effects —
bkg.raster*, a per-scanline scroll table (one scroll per screen line: pseudo-3D roads, ripples, "mode 7" floors) on the Game Boy family and SMS / Game Gear, andsystem.cpu_fastfor the Game Boy Color's double-speed CPU.projects/raster-labproves the table on a rendered frame andprojects/mosaik-kartis a kart racer built on it. - Capacity levers — per-scene tilesets, metatile map packing, a paint
interpreter, per-room sprite residency, and
[world] streamcart streaming/banking, so a game can outgrow any one console's resident image. Per-object descriptor metasprites when a kind needs GB Studio's real placement model. - Audio choices — the portable native driver above, or hUGEDriver
(
native.huge) on the GB family for hUGETracker parity, both behind one seam; a data-driven SFX library; hUGETracker.ugemodules import directly. - Frame pacing —
[build] frame_lock = Nholds a game frame to N display frames, which is what makes a converted game's durations mean the same thing in every room (a VM frame is NOT a fixed number of display frames). - One-shot setup —
setup_tools.pydownloads the toolchains and test emulators for you.
docs/mosaik_lang_spec.md— the language: syntax, types, modules, the build system, the full standard-library reference, and the per-console support matrix. Start here to write mosaik.docs/game-framework.md— the game framework that sits on top of mosaik: scenes, actors, collision, camera, dialogue, HUD, genre loops, and a how-to for adding a new genre. Start here to build a game.docs/vm8-spec.md— the VM8 specification: the bytecode virtual machinelib/vm/implements andmosaik_vm/compiles for — machine state, binary format, the instruction set, the RPN evaluator, scheduler, waitables, exceptions, and the conformance vectors. Start here to build a script-driven (event-list) game or to hack on the VM.docs/vm8-playground.html— the VM8 reference playground: an assembler + interpreter in JavaScript, in one self-contained page (open it in a browser).docs/vram-layout.md— the per-console VRAM layout the graphics stdlib assumes.docs/cheat-sheet.md— every hard number a game author hits, per console (tiles, sprites, palettes, the VM8 pools, cart and bank-0 sizes, the Lynx buffers, audio), each with the file that owns it.docs/capacity-and-limits.md— why those limits exist, what to do when you reach one, and how big a world fits (the streaming / paint-interpreter / metatile residency levers).docs/vm8-vs-gbvm.md— VM8 set against gbvm, GB Studio's virtual machine: the same bet (a fixed engine, the game as data) and the places where VM8 deliberately took a different trade.docs/vm8-vs-gbstudio.html— MosaiK8's hard numbers per console, next to GB Studio's (open it in a browser).
GBDK tool 'lcc' not found— runpython setup_tools.py --only gbdk, or setGBDK_HOMEto an existing install, or add GBDK'sbin/to yourPATH.'charmap' codec can't decode byte ...— a.gbROM was treated as a source file. Source files must use the.mosextension.- ROM too large — on the Game Boy family, move functions into ROM banks with
bank(N)and/or set[build] rom_size(see the spec's ROM-banking section).
X/Twitter: https://x.com/Elliptic_FloW Discord: https://discord.gg/BtYaF6Ydwr create an Issue https://github.com/SiENcE/mosaik8/issues
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable
- Submit a pull request
This project is licensed under the MIT License — see the LICENSE file for details.
Third-party work it contains, derives from or links, with each licence, is in
THIRD_PARTY_NOTICES.md.
ROMs you build. The runtimes MosaiK8 links into a ROM (GBDK-2020 / SDCC with
their linking exceptions, cc65's zlib runtime, the public-domain hUGEDriver)
generally permit proprietary ROMs, subject to their licence terms and to those
of every other piece of code, art and music you put into the game. A ROM that
uses variable-width text contains code derived from CrossZGB (MIT), whose
notice must accompany it (see THIRD_PARTY_NOTICES.md; the generated C carries
it too).
- gbvm — the inspiration for the design of VM8
- hUGETracker / hUGEDriver (SuperDisk) — the music format and driver, public domain
- CrossZGB — the variable-width text renderer is derived from its
vwf_print_render - Pan Docs — the Game Boy hardware reference
- GBDK-2020 Team — the multi-console development kit
- cc65 Team — the toolchain powering the Atari Lynx and PC Engine backend
- libretro / Handy / Beetle — the cores powering headless testing
- Retro Development Community — resources and inspiration across all platforms
- Lua and Python Communities — syntax inspiration
Copyright (c) 2026 Florian Fischer




