Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save liamlangli/de720e0df69ee5e4313163283a41bf30 to your computer and use it in GitHub Desktop.

Select an option

Save liamlangli/de720e0df69ee5e4313163283a41bf30 to your computer and use it in GitHub Desktop.
GBA dev env agents.md
# AGENTS.md - Game Boy Advance Development Guide
This repository targets Nintendo Game Boy Advance homebrew in C/C++/ARM assembly, built with the official `devkitpro/devkitarm` Docker image and tested/debugged with mGBA. Treat this file as the standing instruction set for any AI coding agent, human contributor, or automation working in this project.
Primary learning/reference source: TONC, "GBA Programming in rot13" by Jasper Vijn and the gbadev.net community. Use TONC for concepts, libtonc-style conventions, register explanations, and working examples. Use GBATEK for exact register-level reference when TONC is not specific enough.
---
## 1. Mission and Scope
Build GBA software that is:
- Correct on real GBA hardware, not merely on one emulator.
- Efficient enough for a 16.78 MHz ARM7TDMI with tiny RAM and no operating system.
- Easy to build reproducibly through Docker.
- Debuggable through mGBA, GDB, logs, assertions, test ROMs, and small isolated demos.
- Written in clear C first, with C++ used cautiously and assembly reserved for measured hot paths.
Do not assume POSIX, a filesystem, dynamic loading, threads, heap abundance, floating-point hardware, a GPU command processor, or an operating system. The GBA is memory-mapped hardware plus CPU plus cartridge ROM.
---
## 2. Canonical Build Environment
### 2.1 Toolchain
Use the official Docker image:
```sh
docker pull devkitpro/devkitarm:latest
```
For reproducible CI/release builds, pin a dated tag instead of `latest`, for example:
```sh
docker pull devkitpro/devkitarm:20260221
```
Use `latest` during active prototyping only when accepting toolchain drift is okay. Record the exact image tag or digest used for releases.
The container provides devkitARM under `/opt/devkitpro/devkitARM` and devkitPro tools under `/opt/devkitpro/tools/bin`. Expected environment:
```sh
DEVKITPRO=/opt/devkitpro
DEVKITARM=/opt/devkitpro/devkitARM
PATH=/opt/devkitpro/devkitARM/bin:/opt/devkitpro/tools/bin:$PATH
```
### 2.2 Build Commands
Run builds from the repository root:
```sh
docker run --rm -it \
-v "$PWD":/work \
-w /work \
devkitpro/devkitarm:latest \
make
```
Clean:
```sh
docker run --rm -it -v "$PWD":/work -w /work devkitpro/devkitarm:latest make clean
```
Release build with a pinned image:
```sh
docker run --rm -it \
-v "$PWD":/work \
-w /work \
devkitpro/devkitarm:20260221 \
make clean all
```
### 2.3 Expected Build Outputs
A standard GBA build should produce at least:
- `build/*.o` object files.
- `*.elf` unstripped ELF with symbols for debugging.
- `*.gba` final ROM image with a valid GBA header.
- Optional `*.map` linker map for memory inspection.
The final ROM must be passed through `gbafix` unless the Makefile or devkitPro template already does this. A ROM that runs in mGBA but has an unfixed header may fail elsewhere.
### 2.4 Makefile Rules
Prefer the devkitPro GBA Makefile pattern or a minimal equivalent. The build should be incremental, separate compilation from linking, and include asset conversion steps.
Typical project layout:
```text
.
├── AGENTS.md
├── Makefile
├── source/
│ ├── main.c
│ ├── video.c
│ ├── audio.c
│ ├── input.c
│ └── irq.c
├── include/
│ └── project/*.h
├── data/
│ ├── gfx/
│ ├── maps/
│ ├── audio/
│ └── generated/
├── build/
└── dist/
```
Never commit generated object files. Generated C/header asset files may be committed only if the project intentionally avoids requiring converter tools in the build.
---
## 3. Running and Debugging
### 3.1 Normal Emulator Run
```sh
mgba dist/game.gba
```
or, if the Makefile writes the ROM at the root:
```sh
mgba game.gba
```
Use mGBA for everyday testing because it is accurate enough for development and includes developer tooling such as memory viewers, debug logging, and a GDB server.
### 3.2 mGBA Command-Line Debugger
Start mGBA's built-in command-line debugger:
```sh
mgba -d dist/game.gba
```
Use it for quick breakpoints, disassembly, register inspection, stepping, and CPU-state checks.
### 3.3 GDB Remote Debugging with mGBA
Start mGBA with the GDB stub:
```sh
mgba -g dist/game.gba
```
By default, mGBA uses port `2345`. In another terminal:
```sh
docker run --rm -it \
-v "$PWD":/work \
-w /work \
--network host \
devkitpro/devkitarm:latest \
arm-none-eabi-gdb dist/game.elf
```
Inside GDB:
```gdb
target remote localhost:2345
break main
continue
```
If `--network host` is unavailable, run GDB on the host or expose/connect to the correct host address. Always debug the ELF file, not the `.gba`, because the ELF contains symbols.
### 3.4 Debug Build Flags
For debug builds, prefer:
```make
CFLAGS += -g -Og
```
For release builds, prefer:
```make
CFLAGS += -O2
```
Use `-O3` only after measurement. Do not blindly optimize for speed if code size grows enough to harm cache/fetch behavior or ROM bandwidth.
### 3.5 Emulator Matrix
Use at least:
- mGBA: default development emulator and GDB debugging.
- NanoBoyAdvance or SkyEmu: accuracy cross-check for timing-sensitive behavior.
- Real hardware or flash cart: final validation, especially for DMA timing, audio, save memory, and interrupt behavior.
- no$gba debug version when a visual debugger, profiler, or memory-access checking is needed.
---
## 4. Non-Negotiable Hardware Facts
### 4.1 CPU
- CPU: ARM7TDMI, 32-bit RISC, clocked at `2^24 Hz` = about `16.78 MHz`.
- Two instruction sets: ARM, 32-bit instructions; Thumb, 16-bit instructions.
- Normal code in ROM should usually be Thumb for compactness and ROM bus efficiency.
- Hot code may be ARM placed in IWRAM for speed.
- No floating-point unit. Use fixed-point math and lookup tables for gameplay, transforms, audio rates, and effects.
### 4.2 Display
- Resolution: `240 x 160` pixels.
- Color: 15-bit BGR, stored in 16-bit values: `0bbbbbgggggrrrrr` with one unused high bit.
- Refresh: about `59.73 Hz`.
- One frame: `280,896` CPU cycles.
- One scanline: `1,232` cycles.
- HDraw: `240` pixels, `960` cycles.
- HBlank: `68` pixel-times, `272` cycles.
- VDraw: `160` scanlines, `197,120` cycles.
- VBlank: `68` scanlines, `83,776` cycles.
Use VBlank for OAM, palette, VRAM, scroll register, and high-level state commits whenever possible. Use HBlank only for intentional raster effects or HDMA.
### 4.3 Memory Map
Core regions:
| Region | Address | Size | Bus | Purpose |
|---|---:|---:|---:|---|
| BIOS ROM | `0x00000000` | 16 KiB | 32-bit | BIOS/SWI routines; executable but not normally readable |
| EWRAM | `0x02000000` | 256 KiB | 16-bit | General work RAM; good for large data |
| IWRAM | `0x03000000` | 32 KiB | 32-bit | Fast internal RAM; hot code/data/IRQ stacks |
| I/O | `0x04000000` | 1 KiB | 32-bit | Memory-mapped registers |
| Palette RAM | `0x05000000` | 1 KiB | 16-bit | 256 BG colors + 256 OBJ colors |
| VRAM | `0x06000000` | 96 KiB | 16-bit | BG/OBJ tile, map, and bitmap data |
| OAM | `0x07000000` | 1 KiB | 32-bit | 128 hardware object entries |
| Game Pak ROM | `0x08000000` | up to 32 MiB | 16-bit | Code and read-only data |
| Cart RAM | `0x0E000000` | variable | 8-bit | Save memory: SRAM/Flash/EEPROM |
Never write to ROM. Never treat I/O registers as ordinary RAM. Use `volatile` for memory-mapped registers and shared IRQ state.
### 4.4 Video Capabilities
- 6 video modes: modes 0-2 tiled, modes 3-5 bitmap.
- 4 background layers in tiled modes, with mode-dependent regular/affine support.
- 128 hardware objects/sprites.
- Up to 4 tilemap backgrounds, one sprite/object layer, windows, mosaic, alpha blending, brightness fade, affine transforms.
- Objects range from `8x8` to `64x64` pixels.
- Sprite and BG pixels with palette index 0 are transparent in paletted modes.
### 4.5 Audio Capabilities
- 6 channels total.
- 4 Game Boy PSG channels: two square-wave channels, one programmable wave channel, one noise channel.
- 2 DirectSound FIFO channels, A and B, for signed 8-bit PCM samples.
- DirectSound playback is timer-driven and usually fed by DMA channel 1 or 2.
- Audio code must be timing-aware; underfeeding FIFO causes pops/clicks.
### 4.6 Input
- 10 buttons: A, B, Select, Start, Right, Left, Up, Down, R, L.
- `REG_KEYINPUT` bits are active-low: a pressed key reads as `0`.
- Always edge-detect for menu actions and button-down for continuous movement.
- Debounce game actions at the gameplay layer, not by sleeping the CPU.
### 4.7 DMA
- 4 DMA channels.
- DMA0: highest priority; internal RAM only; avoid casual use.
- DMA1/DMA2: often used for DirectSound FIFO feeding.
- DMA3: general-purpose copies/fills and bulk transfers.
- DMA can start immediately, at VBlank, at HBlank, or special refresh/FIFO timing depending on channel/mode.
- DMA count is in units of halfwords or words, not bytes.
- DMA fill source is an address containing the fill value, not the immediate value itself.
- DMA halts the CPU during transfer. Do not assume interrupts continue normally during a large DMA copy.
### 4.8 Timers
- 4 hardware timers.
- Base clock: CPU clock, about `16.78 MHz`.
- Prescalers: 1, 64, 256, 1024 cycles.
- Timers can overflow, cascade, raise IRQs, and drive DirectSound sample rates.
- VBlank is the primary game clock for most gameplay. Hardware timers are for sub-frame timing, profiling, audio, fixed-rate systems, or precise delays.
### 4.9 Interrupts
- Main registers: `REG_IE`, `REG_IF`, `REG_IME`.
- `REG_IME` is the master interrupt enable.
- `REG_IE` selects which interrupts are accepted.
- `REG_IF` records pending interrupts; acknowledge by writing `1` to the handled bit.
- Many interrupts require enabling both the receiver (`REG_IE`) and sender-side register bit, e.g. VBlank/HBlank/VCount in `REG_DISPSTAT`.
- Interrupt routines must be short, deterministic, and careful with shared state.
- Use VBlank IRQ or `VBlankIntrWait` instead of busy-waiting when practical.
### 4.10 BIOS/SWI
The BIOS provides SWI calls for reset, halt, `IntrWait`, `VBlankIntrWait`, division, square root, arctangent, CPUSet/CPUFastSet, affine setup, decompression, sound routines, multiboot, and more. Prefer libtonc/devkitPro wrappers where available.
---
## 5. Rendering Strategy
### 5.1 Choose the Right Video Mode
Use tiled modes for most games.
- Mode 0: four regular tiled backgrounds. Best default for tile-based 2D games.
- Mode 1: two regular BGs plus one affine BG.
- Mode 2: two affine BGs.
- Mode 3: `240x160`, 16bpp bitmap, one frame. Easy but slow and VRAM-heavy.
- Mode 4: `240x160`, 8bpp paletted bitmap, two pages. Useful for page-flipped software rendering.
- Mode 5: `160x128`, 16bpp bitmap, two pages. Smaller framebuffer, page-flipped.
Bitmap modes are good for demos, generated images, software 3D, or highly dynamic full-screen effects. They are usually not the right default for conventional sprite/tile games.
### 5.2 VBlank Discipline
During active drawing, VRAM/OAM/palette writes can cause visual artifacts or undefined behavior. The standard frame loop should be:
```c
while (1) {
read_input();
update_game_logic();
prepare_render_state();
VBlankIntrWait(); // or vid_vsync() / irq-driven equivalent
commit_oam();
commit_bg_scrolls();
commit_palette_changes();
commit_vram_uploads_small();
}
```
Large VRAM uploads should be split across frames, done during forced blank, or scheduled during loading screens.
### 5.3 Backgrounds
For tiled backgrounds:
- Tiles are always `8x8` pixels.
- Use charblocks for tile graphics and screenblocks for tile maps.
- A screenblock is a tilemap block in VRAM.
- Use 4bpp for memory efficiency when 16 colors per palette is enough.
- Use 8bpp when the art needs a full 256-color palette.
- Scroll with BG scroll registers instead of redrawing pixels.
- Use affine BGs for rotation/scaling, Mode 7 style effects, or large transformed surfaces.
### 5.4 Sprites / Objects
To display a sprite:
1. Convert image data into 4bpp or 8bpp GBA object tiles.
2. Load object tiles into object VRAM.
3. Load object palette data into object palette RAM.
4. Fill OAM attributes: position, shape, size, tile index, palette bank, priority, affine flags.
5. Enable objects in `REG_DISPCNT` and choose 1D or 2D mapping.
Use 1D object mapping unless the project has a specific reason not to. Keep a shadow OAM array in normal RAM, update it during game logic, then DMA/copy to real OAM in VBlank.
### 5.5 Palettes
- 15-bit colors: 5 bits each for red, green, blue.
- BG palette memory and OBJ palette memory are separate.
- In 4bpp, each tile references one of 16 palette banks.
- In 8bpp, tiles reference one 256-color palette.
- Palette index 0 is transparent for sprites and commonly transparent for tile layers depending on context.
Palette effects are cheap. Prefer palette cycling/fading over rewriting large tile data.
### 5.6 Special Effects
Use hardware effects where possible:
- Mosaic for chunky transitions.
- Alpha blending for layer combinations.
- Brightness increase/decrease for fades.
- Window registers for masks, spotlights, UI clipping, and split-screen effects.
- HBlank IRQ/HDMA for scanline effects.
Keep raster effects isolated and documented. Timing-sensitive code should be tested in at least two emulators and, before release, real hardware.
---
## 6. Audio Strategy
### 6.1 PSG Channels
Use the four legacy Game Boy channels for simple sound effects when appropriate:
- Square 1 and Square 2: beeps, UI sounds, simple melody.
- Wave: small custom waveform playback.
- Noise: explosions, hits, footsteps, percussion.
These are hardware-generated and CPU-light, but limited.
### 6.2 DirectSound
Use DirectSound A/B for PCM music and sample playback:
- Samples are signed 8-bit PCM.
- Feed FIFO A/B with DMA1 or DMA2.
- Use a hardware timer to set sample rate.
- Keep mixing code deterministic and efficient.
- Double-buffer or ring-buffer audio data if writing a custom mixer.
For a game-scale project, strongly consider a known GBA audio library or devkitPro-compatible engine rather than inventing a full tracker/streaming stack immediately.
### 6.3 Audio Rules
- Do not allocate audio buffers dynamically during gameplay.
- Avoid expensive mixing in VBlank if it risks missing rendering commits.
- Keep audio IRQ/DMA handling minimal.
- Document sample rates, buffer sizes, and timer reload values.
- Test for pops/clicks under gameplay load, not only in empty scenes.
---
## 7. Code Style and Architecture
### 7.1 Language
- Use C99 or conservative C++ only if the Makefile and runtime are configured for it.
- Prefer fixed-size integer types: `u8`, `u16`, `u32`, `s8`, `s16`, `s32` or `<stdint.h>` equivalents.
- Use `volatile` for hardware registers and data shared with IRQ handlers.
- Avoid heap allocation in gameplay code. Prefer static pools, arenas, and compile-time buffers.
- Avoid recursion.
- Avoid floating point in runtime code. Use fixed-point math.
### 7.2 Register Access
Use libtonc/devkitPro headers when possible. If defining registers manually:
```c
#define REG_DISPCNT (*(volatile u16*)0x04000000)
```
Never hide volatile register reads behind non-volatile cached variables unless intentionally taking a snapshot.
### 7.3 Frame Loop Ownership
Separate systems:
- `input`: reads and edge-detects keypad state.
- `game`: updates gameplay state.
- `render`: builds shadow OAM/BG/palette state.
- `video`: commits state to hardware during VBlank.
- `audio`: owns timers/DMA/FIFO/audio buffers.
- `irq`: owns interrupt setup and dispatch.
- `assets`: generated data and metadata.
Avoid mixing direct hardware writes into gameplay logic. Gameplay should request visual/audio changes; platform layers should perform hardware writes at safe times.
### 7.4 Fixed-Point Math
Use a project-wide fixed-point convention, for example:
```c
typedef s32 fix16; // 16.16 fixed point
#define FIX_SHIFT 16
#define FIX_ONE (1 << FIX_SHIFT)
#define INT2FIX(n) ((n) << FIX_SHIFT)
#define FIX2INT(x) ((x) >> FIX_SHIFT)
```
Document any alternative such as 8.8 for affine parameters or subpixel positions.
### 7.5 Assembly
Assembly is allowed only when:
- Profiling shows C is insufficient.
- The function boundary and ABI are documented.
- A C fallback or test harness exists where practical.
- The code specifies ARM vs Thumb state and target section.
Place hot ARM code in IWRAM only after measuring benefit and confirming memory budget.
---
## 8. Assets Pipeline
### 8.1 Graphics
Use tools that produce GBA-native data:
- Tiles: 4bpp/8bpp, `8x8` tile order.
- Tilemaps: screenblock-compatible maps or project-defined compressed maps.
- Palettes: 15-bit BGR values.
- Sprites: object tiles respecting shape/size limits.
Preferred rule: convert assets at build time from source PNG/Tiled/etc. to generated C/header/binary data.
### 8.2 Maps
For large maps:
- Store metatiles when possible.
- Stream chunks into screenblocks as the camera moves.
- Keep collision maps separate from visual maps.
- Compress ROM data where useful, decompress into EWRAM/VRAM during loading.
### 8.3 Compression
GBA BIOS supports decompression SWIs including LZ77, Huffman, RLE, and differential filters. Use them for ROM-size reduction, but budget decompression time and destination restrictions.
### 8.4 Audio Assets
Track, for each asset:
- Format.
- Sample rate.
- Signed/unsigned conversion status.
- Loop points.
- Target playback channel/system.
- Memory residency: ROM, EWRAM, IWRAM, streaming buffer.
---
## 9. Performance Rules
### 9.1 Frame Budget
At 59.73 Hz, one frame is `280,896` cycles. Most of that is visible scanout, not a safe time to blindly mutate display memory. VBlank is `83,776` cycles. Treat VBlank as precious.
### 9.2 Prefer Hardware
- Use BG scroll registers instead of moving pixels.
- Use sprites instead of software blitting actors.
- Use palette effects instead of mass pixel edits.
- Use DMA for bulk copies, but do not spam DMA for tiny copies.
- Use affine hardware where it fits.
### 9.3 Measure
Use timers, mGBA tooling, no$gba profiling, and controlled stress scenes. Do not optimize based only on intuition.
### 9.4 Common Pitfalls
- Writing too much VRAM during active display.
- Forgetting active-low keypad bits.
- DMA count in bytes instead of halfwords/words.
- DMA fill using an immediate value instead of an address.
- Updating real OAM outside VBlank.
- Dividing by zero in BIOS `Div`.
- Assuming emulator behavior equals hardware behavior.
- Using `int`/`float` casually in tight loops without understanding codegen.
- Letting Makefile paths contain spaces.
- Debugging a `.gba` file instead of the symbol-rich `.elf`.
---
## 10. Testing and Validation
### 10.1 Minimum Checks Before Commit
Run:
```sh
docker run --rm -it -v "$PWD":/work -w /work devkitpro/devkitarm:latest make clean all
mgba dist/game.gba
```
Manually verify:
- Boot succeeds.
- First screen renders correctly.
- Input works.
- Audio starts/stops without obvious clicks.
- No visible tearing during normal gameplay.
- No OAM corruption when many sprites are active.
### 10.2 Regression ROMs
When fixing hardware bugs, create tiny test scenes:
- `tests/vblank_oam/`
- `tests/dma_copy/`
- `tests/timer_audio/`
- `tests/bg_scroll/`
- `tests/sprite_affine/`
Each should build to a ROM and document expected visual/audio behavior.
### 10.3 Hardware Verification Checklist
Before a release:
- Test on mGBA.
- Test on at least one stricter emulator.
- Test on real GBA/DS/Lite/Micro or trusted hardware route if available.
- Test save behavior if cart RAM is used.
- Test long-running audio and scene transitions.
- Test after a cold boot, not only emulator reset.
---
## 11. CI Guidance
A minimal CI build can run Docker and invoke `make`:
```sh
docker run --rm \
-v "$PWD":/work \
-w /work \
devkitpro/devkitarm:20260221 \
make clean all
```
Archive `.gba`, `.elf`, and `.map` as build artifacts. For releases, also record:
- Git commit hash.
- Docker image tag/digest.
- Compiler version: `arm-none-eabi-gcc --version`.
- ROM SHA-256.
---
## 12. Documentation Rules for Agents
When editing this project:
1. Prefer small, reviewable changes.
2. Update comments when changing hardware assumptions.
3. Do not invent register values from memory; verify with TONC, libtonc headers, GBATEK, or devkitPro examples.
4. Do not introduce dependencies that are unavailable in `devkitpro/devkitarm` unless the Docker workflow is updated.
5. Keep asset-source files and generated files clearly separated.
6. Document any timing-sensitive code with the relevant frame/scanline/VBlank budget.
7. When adding assembly, document ABI, clobbers, alignment, ARM/Thumb state, and section placement.
8. If behavior is emulator-specific, say so in the code comment or issue.
9. Preserve the `.elf` for debugging.
10. Never remove `gbafix`/ROM-header finalization unless replacing it with an equivalent.
---
## 13. Suggested Starter Make Targets
```make
.PHONY: all clean run debug gdb release
all:
$(MAKE) build
run: all
mgba dist/game.gba
debug: all
mgba -g dist/game.gba
gdb: all
docker run --rm -it -v "$(PWD)":/work -w /work --network host devkitpro/devkitarm:latest arm-none-eabi-gdb dist/game.elf
release:
docker run --rm -it -v "$(PWD)":/work -w /work devkitpro/devkitarm:20260221 make clean all
clean:
rm -rf build dist
```
Adapt this to the actual devkitPro template in the repository. Do not blindly overwrite a working Makefile.
---
## 14. Source References
Use these references in this order:
1. TONC introduction and chapter index: `https://gbadev.net/tonc/intro.html`
2. TONC hardware overview: `https://gbadev.net/tonc/hardware.html`
3. TONC setup/devkitARM/mGBA: `https://gbadev.net/tonc/setup.html`
4. TONC video timing and display: `https://gbadev.net/tonc/video.html`
5. TONC bitmap modes: `https://gbadev.net/tonc/bitmaps.html`
6. TONC sprites/backgrounds: `https://gbadev.net/tonc/objbg.html`, `https://gbadev.net/tonc/regobj.html`, `https://gbadev.net/tonc/regbg.html`
7. TONC DMA: `https://gbadev.net/tonc/dma.html`
8. TONC timers: `https://gbadev.net/tonc/timers.html`
9. TONC interrupts: `https://gbadev.net/tonc/interrupts.html`
10. TONC BIOS/SWI: `https://gbadev.net/tonc/swi.html`
11. TONC ARM assembly: `https://gbadev.net/tonc/asm.html`
12. GBATEK technical reference: `https://problemkaputt.de/gbatek-index.htm`
13. devkitPro Docker Hub: `https://hub.docker.com/r/devkitpro/devkitarm/tags/`
14. mGBA: `https://mgba.io/`
---
## 15. Default Agent Response to New Tasks
When asked to implement a feature:
1. Identify which subsystem is affected: input, gameplay, video, audio, DMA, IRQ, assets, build, or debug.
2. State any hardware limits that constrain the feature.
3. Make the smallest implementation plan.
4. Modify code and assets.
5. Build with Docker.
6. Provide run/debug commands.
7. Note what was not tested on hardware.
When asked a hardware question:
1. Answer with the relevant register/memory/timing facts.
2. Explain the safe usage pattern.
3. Mention common failure modes.
4. Point to TONC/GBATEK section names or URLs.
When asked to optimize:
1. Measure or explain how to measure.
2. Check algorithmic cost first.
3. Prefer hardware features.
4. Consider memory placement and data width.
5. Use assembly only after evidence.
---
## 16. Working Assumptions
Unless a task says otherwise:
- Target hardware: Game Boy Advance-compatible hardware.
- Toolchain: `devkitpro/devkitarm` Docker image.
- Main language: C.
- Library baseline: libtonc/devkitPro GBA headers are allowed.
- Emulator: mGBA first.
- Frame pacing: VBlank-synchronized 59.73 Hz.
- Rendering default: tiled backgrounds plus sprites.
- Audio default: PSG for simple SFX, DirectSound/timer/DMA or library for PCM/music.
- Debug symbols: keep `.elf`.
- Release ROM: fixed `.gba` header.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment