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 from one language and one set of source files.
- 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) build for every console unchanged.
File extensions
.mosโ mosaik source files.cโ generated C (intermediate build output).gb/.gbc/.duck/.sms/.gg/.nesโ GBDK ROM output.lnx/.pceโ cc65 ROM output (Atari Lynx / PC Engine)
- Modern Syntax: Familiar syntax inspired by Lua/Python for retro console development
- Nine Consoles: Game Boy / Color / Pocket / Mega Duck, SMS, GG, NES (GBDK) and Atari Lynx, PC Engine (cc65) โ one language, two backends
- Portable Core: Structs, enums, modules, control flow, and the portable stdlib subset compile identically on every target
- 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: Built-in helpers for video, input, sprites, text, sound (a portable beep channel on all nine consoles), scrollable background tilemap (all nine consoles โ hardware tilemap on GBDK targets, VDC BAT on the PCE, a composited Suzy background sprite on the Lynx), window (GB family), and draw primitives (Lynx)
- Asset Pipeline: PNGs listed in
mosaik.toml(or passed with--asset) become tile data on every console โ no hex arrays in source - One-shot Setup:
setup_tools.pydownloads the toolchains and test emulators for you
For a precise, status-tagged list of what the language implements today versus what is still planned, see
docs/mosaik_lang_spec.md.
- Python 3.7+ with the
tomlpackage - GBDK-2020 โ installed by
setup_tools.pyintogbdk/; also auto-detected viaGBDK_HOMEorPATH - cc65 โ installed by
setup_tools.pyintocc65/; also auto-detected viaCC65_HOMEorPATH(needed only for Lynx/PCE targets) - PyBoy โ Python Game Boy emulator, optional, for running GBDK ROMs (see below)
- libretro.py + Lynx cores โ optional, for headless Atari Lynx testing
setup_tools.py downloads and installs everything into the folders the build
tool expects (all of them 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| Component | Installs | Used for |
|---|---|---|
gbdk |
GBDK-2020 (latest GitHub release) โ gbdk/ |
building all GBDK-backend consoles |
cc65 |
cc65 Windows snapshot โ cc65/ |
building Atari Lynx / PC Engine |
cores |
Handy + Beetle Lynx libretro cores โ emu/libretro/ |
headless Lynx testing |
python |
pyboy, libretro.py, pillow, toml (pip) |
build tool + emulator harnesses |
Notes:
- cc65 binary snapshots exist for Windows only; on Linux/macOS install cc65
from your package manager and set
CC65_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 for each toolchain:
- The
GBDK_HOME/CC65_HOMEenvironment variable - A
gbdk-2020/orgbdk//cc65/folder next tomosaik8.py(whatsetup_tools.pycreates) - The system
PATH(lcc/cl65)
MosaiK8 only builds ROMs โ it has no run command, so it never depends on an
installed emulator. To see a build working, open the ROM in any emulator
directly.
PyBoy is the recommended way to verify Game Boy ROMs: it's a Python-based emulator that can run headless and expose the rendered screen and memory, so you can assert that something actually drew (see Testing).
pip install pyboy
pyboy samples/build/gameboy/bounce.gb # GBDK ROMsAtari Lynx and PC Engine ROMs run headlessly through the bundled libretro harness:
python emu/libretro/run_lynx.py samples/build/lynx/pong.lnx 300 --png out.png
python emu/libretro/run_lynx.py samples/build/pce/pong.pce 300 --core mednafen_pce_fast --png out.pngAny GUI emulator that accepts a ROM path works too (BGB/mGBA for .gb/.gbc,
Mednafen for .lnx/.pce/.sms/.gg/.nes, โฆ).
python mosaik8.py init my_game
cd my_gameThis creates:
my_game/
โโโ mosaik.toml # Project configuration
โโโ src/
โโโ main.mos # Main source file
Edit src/main.mos:
module "main" {
import "platform.video"
import "platform.input"
var player_x: u8 = 80
var player_y: u8 = 72
var frame_count: u8 = 0
function update_player() {
if input.pressed(INPUT_LEFT) and player_x > 0 {
player_x -= 1
}
if input.pressed(INPUT_RIGHT) and player_x < 152 {
player_x += 1
}
if input.pressed(INPUT_UP) and player_y > 0 {
player_y -= 1
}
if input.pressed(INPUT_DOWN) and player_y < 136 {
player_y += 1
}
}
function main() {
video.enable_lcd()
loop {
frame_count += 1
update_player()
video.wait_vblank()
}
}
export main
}
# Build for Game Boy (uses mosaik.toml in the current directory)
python mosaik8.py build
# Build for Game Boy Color
python mosaik8.py build --platform gameboy_color
# ...or any other supported console
python mosaik8.py build --platform nes
python mosaik8.py build --platform sms
python mosaik8.py build --platform lynx samples/hello.mos
# To see a build run, open the ROM in an emulator directly (no `run` command):
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). Each links a native ROM (.gb, .gbc, .pocket, .duck, .sms,
.gg, .nes, .lnx, .pce).
Two backends, one language: GBDK consoles are linked by GBDK's lcc; the cc65
consoles (Atari Lynx, PC Engine) are linked by cc65's cl65. The
lexer/parser/typechecker and all logic codegen are shared โ only the
standard-library lowering differs. A program that stays within the portable
stdlib subset (text, input, timing, sound, sprites) builds for all nine
consoles unchanged; size the world with the per-target
SCREEN_WIDTH/SCREEN_HEIGHT constants and it behaves right everywhere too
(see samples/bounce.mos). What each console supports is recorded in a
capability registry; calling something a console lacks (the window layer
outside the Game Boy family, graphics.draw outside the Lynx, GB REG_*
registers elsewhere, ...) is a clear compile-time error โ gate such code with
if platform == "...". See docs/cc65-backend-plan.md and
docs/platform-support-plan.md.
# Build the same source for a GBDK console and both cc65 consoles
python mosaik8.py build --platform gameboy samples/hello.mos
python mosaik8.py build --platform lynx samples/hello.mos
python mosaik8.py build --platform pce samples/hello.mosThe build tool has exactly two modes, chosen by what you pass to build. There
is no "scan the whole tree" mode โ you always point it at one source file or
one project.
Pass a single .mos file. A build/ folder is created next to that file,
and the generated .c and ROM are named after the source file.
python mosaik8.py build samples/text_simple.mos
# -> samples/build/gameboy/text_simple.c
# samples/build/gameboy/text_simple.gb
# samples/build/gameboy_color/text_simple.c
# samples/build/gameboy_color/text_simple.gb
# Limit to a single platform:
python mosaik8.py build --platform gameboy samples/bounce.mosPass a mosaik.toml (or a directory containing one, or nothing to use
./mosaik.toml). The tool reads the project file, compiles the .mos files
in the [source] folder, writes output to the [build] output_dir, and names
the .c and ROM after [project] name. Target platforms come from
[project] target_platforms.
# Any of these build the projects/game project:
python mosaik8.py build projects/game/mosaik.toml
python mosaik8.py build projects/game # directory containing mosaik.toml
cd projects/game && python ../../mosaik8.py build # uses ./mosaik.toml
# -> projects/game/build/gameboy/game.c
# projects/game/build/gameboy/game.gbThere is no run command โ open the built ROM in an emulator directly (PyBoy
for GBDK consoles, the libretro harness for the Lynx).
Note: TOML string values must be quoted. Use
folder = "src/", notfolder = src/. A malformed project file is reported as an error rather than silently ignored.
my_project/
โโโ mosaik.toml # Project configuration
โโโ src/ # Source files (.mos)
โ โโโ main.mos # Main module
โ โโโ player.mos # Game modules
โ โโโ enemies.mos
โโโ assets/ # Graphics, audio, data
โ โโโ sprites/
โ โโโ backgrounds/
โโโ build/ # Build output
โโโ gameboy/
โ โโโ my_project.gb # Game Boy ROM
โโโ gameboy_color/
โโโ my_project.gb # Game Boy Color ROM
All .mos files of a build are compiled together into one program: in
project mode every file under [source] folder, in single-file mode the given
file plus everything it (transitively) imports โ import "player" loads
player.mos next to the importing file (import "game.utils" โ
game/utils.mos). Use the last segment of a module's name to reference it,
and remember only exported names are visible:
-- src/main.mos -- src/player.mos
module "main" { module "player" {
import "player" var x: u8 = 80
function update() { ... }
function main() { export update, x
loop { player.update() } }
}
export main
}
Calling a function a module doesn't export, importing a module no file
defines, or defining the same module twice are clear compile-time errors. See
projects/multifile for a working example.
[project]
name = "my_game" # names the output .c and ROM
version = "1.0.0"
target_platforms = ["gameboy", "gameboy_color"]
[source]
folder = "src/" # where .mos sources live (relative to this file)
[assets]
sprites = ["assets/sprites.png"] # PNGs converted to tile data at build time
[build]
optimization_level = 2
debug_symbols = true
rom_size = "32KB"
ram_size = "8KB"
output_dir = "build" # build output (relative to this file)
[platforms.gameboy]
features = ["save_support"]
memory_layout = "standard"
[platforms.gameboy_color]
features = ["save_support", "color", "speed_switch"]
memory_layout = "expanded"
[dependencies]
stdlib = "1.0"Instead of embedding hex tile arrays in source, list PNGs under [assets]
(project mode) or pass --asset file.png (single-file mode). The build
converts each PNG to Game Boy 2bpp tile data and injects it into the program
as two ready-to-use constants, named after the file:
assets/sprites.png -> sprites_tiles (const u8 array, 16 bytes/tile)
sprites_tile_count (number of 8x8 tiles)
sprite.set_data(0, sprites_tile_count, sprites_tiles)
GB 2bpp is the interchange format on every console: the GB family uploads
it directly, the NES and Game Gear/SMS convert it in GBDK's set_sprite_data
compatibility layer, and the Lynx sprite engine converts it for the Suzy
blitter โ so one PNG serves all targets.
Authoring rules (see mosaik_assets.py for details):
- The image must be a multiple of 8 pixels each way; it is cut into 8ร8 tiles left-to-right, top-to-bottom.
- An indexed PNG with a palette of โค 4 entries maps each palette index straight to the GB colour value 0โ3 (exact control; index 0 = transparent for sprites).
- Anything else maps per pixel: transparent (alpha < 128) or near-white โ 0, then light โ 1, dark โ 2, black โ 3 by luminance.
The projects/shmup project is the worked example:
projects/shmup/assets/sprites.png is the pregenerated sheet (regenerated by
projects/shmup/assets/gen_sprites.py), and projects/shmup/src/shmup.mos
ships a complete game on top of it.
# Build a single source file (build/ created next to it)
python mosaik8.py build samples/text_simple.mos
# Build a project (reads mosaik.toml)
python mosaik8.py build projects/game/mosaik.toml
python mosaik8.py build # uses ./mosaik.toml
# Restrict to one platform / add debug symbols
python mosaik8.py build --platform gameboy_color samples/bounce.mos
python mosaik8.py build --debug projects/game
# Clean build artifacts (project-mode build/ in the current directory)
python mosaik8.py clean
# Show version
python mosaik8.py versionNote:
cleanremoves the project-modebuild/directory. In single-file mode the output lives next to the source, e.g.samples/build/.
The samples/ folder contains ready-to-build mosaik programs:
| Sample | What it shows |
|---|---|
text_simple.mos |
Static text rendering |
text.mos |
Variables, counters, text.print_number |
text_complex.mos |
Nested conditions, multiple counters |
bounce.mos |
Structs, signed math, input handling; sized by SCREEN_WIDTH/SCREEN_HEIGHT, so it uses the whole screen on every sprite-capable console |
pong.mos |
A small game loop, same screen-geometry portability |
beep.mos |
platform.sound: the portable beep channel (A/B play tones; builds for all 9 consoles) |
cross_platform.mos |
Per-platform conditional compilation + the SCREEN_* constants; builds for all 9 consoles |
hello.mos |
The portable Tier-1 subset (text/input/timing), one source for both backends |
draw.mos |
The Lynx-only graphics.draw TGI primitives, platform-gated |
graphics_showcase.mos |
Sprites, background, window HUD, palette registers (Game Boy family) |
novascape.mos |
A complete game port (Game Boy family) |
Build any one of them in single-file mode (output goes to samples/build/):
python mosaik8.py build samples/bounce.mosFull project examples live in projects/ and are built in project mode:
python mosaik8.py build projects/game # Box Runner -> projects/game/build/<console>/game.*
python mosaik8.py build projects/shmup # Starfall shmup -> projects/shmup/build/<console>/starfall.*
python mosaik8.py build projects/background # scrolling bkg -> projects/background/build/<console>/background.*projects/shmup (Starfall) is a vertical shoot'em up for Game Boy, Game Gear
and Atari Lynx from one source, with all graphics pregenerated from a PNG via
the asset pipeline (see Assets above). projects/background scrolls a
32ร32-tile world with graphics.bkg and walks an animated sprite on top โ
one source for all nine consoles, including the Lynx (no tilemap hardware;
the backend composites the map into one big Suzy background sprite) and the
PC Engine (real VDC tilemap + scroll registers).
See a built ROM run by opening it 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)Unit tests live in tests/. Run the whole suite (and optionally rebuild every
sample end-to-end) with the test runner:
# Run all unit tests
python tests/run_all.py
# Run all unit tests AND compile every sample to a ROM, for every console it
# supports (derived from the capability registry โ ~90 builds), plus the
# projects/game and projects/shmup projects
python tests/run_all.py --samples
# Optional behavioral checks: drive the built ROMs in emulators
# (PyBoy for Game Boy; --lynx also screen-diffs the Lynx shmup through the
# libretro harness)
python tests/verify_roms.py --lynxYou can also run an individual test directly:
python tests/enum_test.py
python tests/test_fixes.pyEach test prints a PASS/FAIL line; run_all.py exits non-zero if anything
fails, so it is suitable for CI.
module "example" {
import "platform.video"
-- Comments use double dashes
var health: u8 = 100
const MAX_SPEED: u8 = 5
type Position = struct {
x: u8,
y: u8
}
enum Direction {
UP = 0,
DOWN = 1,
LEFT = 2,
RIGHT = 3
}
function move_player(pos: Position, dir: Direction) -> Position {
if dir == UP and pos.y > 0 {
pos.y -= 1
}
return pos
}
export move_player, Direction
}
function control_flow_demo() {
var i: u8 = 0
-- if / else if / else
if i == 0 {
i = 1
} else if i == 1 {
i = 2
} else {
i = 3
}
-- Infinite loop (compiles to `while (1)`); use `return` to leave it
loop {
i += 1
if i >= 10 { return }
}
-- Conditional loop with break / continue
while i < 20 {
i += 1
if i == 15 { continue }
if i == 18 { break }
}
-- switch with multi-value labels and an optional default arm.
-- Each case auto-breaks; list values to share one body.
switch i {
case 0 { i = 1 }
case 1, 2, 3 { i = 2 }
default { i = 9 }
}
-- Numeric range for-loop: `for <var> in <start>..<end>` (end is exclusive)
for n in 0..8 {
i += n
}
}
-- `local function` marks a function private to its module.
local function helper(x: u8) -> u8 {
return x + 1
}
Operators: arithmetic
+ - * / %, comparison== != < > <= >=, logicaland or not, and assignment= += -=. Integer literals may be written in decimal, hex (0xE4), or binary (0b1010).foronly iterates numeric ranges (not arrays).Parameters โ including structs and arrays โ are passed by value (there are no pointer/reference types yet), so a function mutating a struct argument changes only its local copy.
-- Primitive types
u8 -- Unsigned 8-bit (0-255)
i8 -- Signed 8-bit (-128 to 127)
u16 -- Unsigned 16-bit (0-65535)
i16 -- Signed 16-bit (-32768 to 32767)
bool -- Boolean
addr -- Memory address
-- Array types
array[u8, 160] -- Array of 160 bytes
array[Position, 32] -- Array of 32 positions
-- `const` arrays become real C `const` tables (great for tile/map/music data):
const TILE: array[u8, 8] = [0x3C, 0x42, 0x42, 0x42, 0x42, 0x42, 0x3C, 0x00]
-- Struct types
type Sprite = struct {
x: u8,
y: u8,
tile: u8,
flags: u8
}
module "graphics" {
import "platform.video"
-- Conditional compilation
if platform == "gameboy_color" {
function set_palette(colors: array[u16, 4]) {
-- GBC-specific palette code
}
} else {
function set_palette(colors: array[u16, 4]) {
-- DMG grayscale fallback
}
}
export set_palette
}
The platform == ... condition is evaluated against the build target, so
each ROM links only the matching branch. Conditions may combine ==, !=,
and, or, not over platform and string literals (including else if
chains); the literal matches the canonical name or an alias ("gbc" โก
"gameboy_color"). Both branches are always parsed, so syntax errors surface on
every target, and an unresolvable condition falls back to the then branch.
mosaik targets all nine supported consoles via --platform:
# GBDK consoles
python mosaik8.py build --platform sms samples/cross_platform.mos
python mosaik8.py build --platform nes samples/cross_platform.mos
# cc65 consoles
python mosaik8.py build --platform lynx samples/hello.mos
python mosaik8.py build --platform pce samples/hello.mosSee samples/cross_platform.mos for one program that builds for all nine consoles.
See samples/hello.mos for a program portable across both backends.
These built-in modules resolve at compile time and map to platform C helpers. Which calls are available on which console is enforced by the capability registry (a clear compile-time error is raised if you call something a target doesn't support โ no silent failures).
| Import | Provides |
|---|---|
platform.video |
enable_lcd(), disable_lcd(), wait_vblank(), show_sprites(), hide_sprites(), show_background(), show_window(), hide_window(), and the per-target screen geometry constants SCREEN_WIDTH/SCREEN_HEIGHT (pixels) and SCREEN_COLS/SCREEN_ROWS (text cells) |
platform.input |
pressed(button), held(button), and INPUT_A/B/SELECT/START/RIGHT/LEFT/UP/DOWN |
platform.hardware |
write(address, value), read(address), and REG_DIV/REG_NR10/REG_BGP/REG_OBP0/REG_OBP1 (the REG_* constants are Game Boy addresses and exist only on the GB family) |
platform.system |
delay(ms), random(), seed_random(seed) |
platform.sound |
beep(freq, frames), stop() โ one portable square-wave channel on every console (GB-family APU, SMS/GG PSG, NES APU, Lynx Mikey, PCE PSG). beep is non-blocking; the duration counts down in wait_vblank ticks (60 โ 1 s; 0 = until stop()) |
graphics.sprite |
set_data(first, count, data), set_tile(id, tile), get_tile(id), set_prop(id, prop), move(id, x, y) โ screen-pixel coordinates, (0, 0) = top-left of the visible screen on every console โ FLIP_X, FLIP_Y |
graphics.bkg |
set_data(first, count, data), set_tiles(x, y, w, h, tiles), scroll(dx, dy), move(x, y) โ a 32ร32-tile scrollable background with u8 wrap-around on every console: the GBDK targets and the PCE scroll their tilemap hardware; the Lynx (no tilemap layer) composites the map into one large Suzy background sprite re-blitted with wrapped offsets each frame (see projects/background) |
graphics.window |
set_tiles(x, y, w, h, tiles), move(x, y) |
graphics.text |
print_string(x, y, text), print_number(x, y, n), clear_area(x, y, w, h) |
video.enable_lcd()
if input.pressed(INPUT_A) { text.print_string(2, 2, "A pressed") }
text.print_number(2, 4, score)
video.wait_vblank()
platform.hardware is the escape hatch for memory-mapped I/O the higher-level
modules don't cover yet โ sound registers, palettes, the divider, etc. It maps
to raw volatile byte access at a 16-bit address:
import "platform.hardware"
hw.write(REG_BGP, 0xE4) -- set the background palette
var seed: u8 = hw.read(REG_DIV) -- the divider makes a cheap RNG source
The graphics.* and platform.system modules wrap GBDK's sprite, background,
window, and utility calls:
import "graphics.sprite"
import "graphics.bkg"
sprite.set_data(0, 2, SHIP_TILES) -- upload two 8x8 tiles
sprite.set_tile(0, 0)
sprite.set_prop(0, FLIP_X) -- mirror it horizontally
sprite.move(0, 84, 78)
bkg.set_data(0, 3, BKG_TILES)
bkg.set_tiles(0, 0, 20, 18, screen) -- fill a region of the tilemap
bkg.scroll(1, 0) -- nudge the background each frame
See samples/graphics_showcase.mos for a runnable demo (scrolling starfield,
an animated ship you can fly around, a window HUD, and palette cycling).
module "pong" {
import "platform.video"
import "platform.input"
var paddle_y: u8 = 72
var ball_x: u8 = 80
var ball_y: u8 = 72
var ball_dx: i8 = 1
var ball_dy: i8 = 1
function update_paddle() {
if input.pressed(INPUT_UP) and paddle_y > 0 {
paddle_y -= 2
}
if input.pressed(INPUT_DOWN) and paddle_y < 128 {
paddle_y += 2
}
}
function update_ball() {
ball_x += ball_dx
ball_y += ball_dy
-- Bounce off walls (SCREEN_* adapt to the build target)
if ball_y <= 0 or ball_y >= SCREEN_HEIGHT {
ball_dy = -ball_dy
}
-- Reset if ball goes off screen
if ball_x <= 0 or ball_x >= SCREEN_WIDTH {
ball_x = SCREEN_WIDTH / 2
ball_y = SCREEN_HEIGHT / 2
}
}
function main() {
video.enable_lcd()
loop {
update_paddle()
update_ball()
video.wait_vblank()
}
}
export main
}
module "animation" {
import "platform.video"
type Animation = struct {
frames: array[u8, 8],
frame_count: u8,
current_frame: u8,
timer: u8,
speed: u8
}
function create_animation(speed: u8) -> Animation {
var anim: Animation = {
frames: [0, 1, 2, 3, 4, 5, 6, 7],
frame_count: 8,
current_frame: 0,
timer: 0,
speed: speed
}
return anim
}
function update_animation(anim: Animation) {
anim.timer += 1
if anim.timer >= anim.speed {
anim.timer = 0
anim.current_frame += 1
if anim.current_frame >= anim.frame_count {
anim.current_frame = 0
}
}
}
export Animation, create_animation, update_animation
}
GBDK Not Found
Error: GBDK tool 'lcc' not found- Run
python setup_tools.py --only gbdkto download and install GBDK-2020 intogbdk/ - Or set
GBDK_HOMEto an existing install:$env:GBDK_HOME = "C:\path\to\gbdk-2020"(PowerShell) orexport GBDK_HOME=/path/to/gbdk-2020(bash) - Or add the GBDK
bin/directory to yourPATH
A .gb ROM was treated as a source file
Error: 'charmap' codec can't decode byte 0x90 ...- Source files must use the
.mosextension;.gbis reserved for compiled ROMs - The discovery step only picks up
.mosfiles and skipsbuild/folders
Compilation Errors
Compilation failed: Unknown type: 'MyType'- Check type definitions are declared before use
- Verify imports are correct
- Review syntax for typos
ROM Size Issues
If a program is too large, the error comes from the GBDK toolchain (sdcc/makebin)
during linking, not from mosaik โ the rom_size/ram_size keys in
mosaik.toml are accepted but not currently enforced by the build tool.
- Split work into smaller functions / reuse buffers
- Let SDCC optimize; keep data tables compact
- For larger programs, GBDK ROM banking applies (handled by the toolchain)
# Build with debug symbols
python mosaik8.py build --debug
# This passes -debug to lcc and generates:
# - .map files with memory layout
# - .sym files with symbol information- 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.
- GBDK-2020 Team - For the excellent multi-console development kit
- cc65 Team - For the cc65 toolchain powering the Atari Lynx and PC Engine backend
- libretro / Handy / Beetle Lynx - For the cores powering headless Lynx testing
- Retro Development Community - For resources and inspiration across all platforms
- Lua and Python Communities - For syntax inspiration
- Issues: Report bugs and request features on GitHub Issues
- Documentation: See the
docs/directory for detailed guides - Community: Join the retro console development Discord/forums
Happy Retro Console Development! ๐ฎโจ