Step 0402 - Frontend Boot ROM Integration + Fileless Stub Mode

1. Hardware Concept

Boot ROM and Initialization Pipeline

TheBoot ROMof the Game Boy is a small internal ROM (256 bytes for DMG, 2304 bytes for CGB) which runs before the cartridge code. Its purpose is:

  1. Show the Nintendo logo: Logo scroll animation (license requirement)
  2. Validate the cartridge: Verify logo checksum in the ROM header
  3. Initialize hardware: Configure I/O registers (LCDC, BGP, CGB palettes, etc.)
  4. Transfer control: Write 0xFF50=1 to disable Boot ROM and jump to 0x0100

After Boot ROM, the hardware state is consistent and predictable. Games depend on this state for initialization. Without Boot ROM, the I/O registers are left at undefined or incorrect values, causing problems like BGP=0x00 (invalid palette, white screen).

Optional Boot ROM Support in the Emulator

To comply withClean Room compliance, the Boot ROM is NOT included in the repository. However, the emulator now supports:

  • Real Boot ROM: User can provide their own Boot ROM (legally extracted from real hardware)
  • stub mode: Minimal post-boot configuration without running proprietary binary (for validation)
  • Skip-boot mode: Standard post-boot state (PC=0x0100, predefined registers)

Sources: Pan Docs - Power Up Sequence, Bread Docs - FF50 Register

2. Implementation

A. Frontend: CLI and Environment Variables

Modifications inmain.py

Two CLI flags were added:

parser.add_argument(
    "--bootrom",
    type=str,
    default=None,
    help="Path to Boot ROM file (DMG: 256 bytes, CGB: 2304 bytes)",
)
parser.add_argument(
    "--bootrom-stub",
    action="store_true",
    help="Enable Boot ROM stub mode (no binary, only post-boot state)",
)

Priority logic:

  1. Yeah--bootrom PATHis specified, load Boot ROM from file
  2. If not, consult environment variableVIBOY_BOOTROM
  3. Yeah--bootrom-stubis specified, activate stub mode
  4. If nothing is specified, use skip-boot mode (default behavior)

Modifications insrc/viboy.py

The builder ofViboynow accepts optional parameters:

def __init__(
    self, 
    rom_path: str | Path | None = None, 
    use_cpp_core: bool = True,
    bootrom_bytes: bytes | None = None,
    bootrom_stub: bool = False
) -> None:
    #...
    self._bootrom_bytes = bootrom_bytes
    self._bootrom_stub = bootrom_stub

Inload_cartridge(), the Boot ROM or stub is applied:

if self._bootrom_bytes:
    # Load real Boot ROM (user provided)
    self._mmu.set_boot_rom(self._bootrom_bytes)
    if self._mmu.is_boot_rom_enabled():
        self._regs.pc = 0x0000 # Boot ROM active: PC starts at 0x0000
    else:
        self._initialize_post_boot_state()
elif self._bootrom_stub:
    # Stub mode: activate stub without proprietary binary
    self._mmu.enable_bootrom_stub(True, cgb_mode=False)
    self._initialize_post_boot_state()
else:
    # Skip-boot mode (default)
    self._initialize_post_boot_state()

B. Backend: Stub Mode in C++

Implementation inMMU.cpp

The methodenable_bootrom_stub()applies a minimal post-boot state:

void MMU::enable_bootrom_stub(bool enable, bool cgb_mode) {
    if (!enable) {
        boot_rom_enabled_ = false;
        boot_rom_.clear();
        return;
    }
    
    // Set I/O registers to post-boot state (according to Pan Docs)
    memory_[0xFF40] = 0x91;  // LCDC: LCD ON, BG ON, Tilemap 0x9800
    memory_[0xFF47] = 0xFC;  // BGP: Standard palette (00=white, 11=black)
    memory_[0xFF42] = 0x00;  // SCY: Scroll Y = 0
    memory_[0xFF43] = 0x00;  // SCX: Scroll X = 0
    memory_[0xFF48] = 0xFF;  // OBP0: Sprite palette 0
    memory_[0xFF49] = 0xFF;  // OBP1: Sprite palette 1
    memory_[0xFFFF] = 0x01;  // IE: VBlank interrupt enabled
    
    // Write 0xFF50 = 1 to simulate the Boot ROM finished
    memory_[0xFF50] = 0x01;
    boot_rom_enabled_ = false;
    boot_rom_.clear();
}

Wrapper Cython (mmu.pyx)

def enable_bootrom_stub(self, bool enable, bool cgb_mode=False):
    """
    Enables Boot ROM stub mode (no proprietary binary).
    
    The stub does NOT emulate real boot instructions. Just force a minimal set
    Documented post-boot status (Pan Docs) and flag boot_rom_enabled_=false
    immediately.
    """
    if self._mmu == NULL:
        raise MemoryError("The C++ MMU instance does not exist.")
    
    self._mmu.enable_bootrom_stub(enable, cgb_mode)

3. Tests and Verification

Compile Command

python3 setup.py build_ext --inplace

Result: ✅ Compilation successful (no errors)

Controlled Tests (30 seconds each)

Test 1: Tetris DX (skip-boot, baseline)

timeout 30s python3 main.py roms/tetris_dx.gbc > logs/step0402_skip_tetris_dx.log 2>&1

Results:

  • ✅ No regressions
  • Frame 720:gameplay_state=YES
  • TileData: 23.0% (1416/6144 bytes)
  • UniqueTiles: 256/256
  • LCDC: changed to 0x81 in frame 677
  • BGP: changed to 0xE4 on frame 711

Test 2: Zelda DX (skip-boot, baseline)

timeout 30s python3 main.py roms/Oro.gbc > logs/step0402_skip_zelda_dx.log 2>&1

Results:

  • LCDC: 0xE3 from frame 0 (changed immediately)
  • BGP: 0x00 from frame 0 (known issue: invalid palette)
  • TileData: 0% (never loads tiles)
  • TileMap: 100% (2048/2048 bytes, but all 0x00)
  • UniqueTiles: 1/256 (only one tile ID)
  • gameplay_state=NO

Test 3: Zelda DX (stub)

timeout 30s python3 main.py --bootrom-stub roms/Oro.gbc > logs/step0402_stub_zelda_dx.log 2>&1

Results:

  • ✅ Stub activated correctly:[BOOTROM-STUB] Activating stub mode (DMG)
  • Stub configured: LCDC=0x91, BGP=0xFC, SCY=0, SCX=0, OBP0=0xFF, OBP1=0xFF, IE=0x01, FF50=0x01
  • But: The game overwrites BGP to 0x00 immediately (frame 0)
  • LCDC: changed to 0xE3 by the game (frame 0)
  • Results identical to skip-boot: TileData 0%, gameplay_state=NO

Analysis of Results

Tetris DX: No regressions. Skip-boot mode (default) still works perfectly.

Zelda DX: The stub was successfully activated and configured I/O registers, but the gameoverwrites BGP to 0x00 immediately. This confirms the hypothesis of Step 0400:

Zelda DX/Pokemon Redexpect the actual Boot ROM to configure specific CGB palettes, not just the basic I/O registers. The stub configures basic DMG values (BGP=0xFC), but the code The game assumes that the Boot ROM has already configured CGB palettes and overwrites them with specific values (which are 0x00 when there is no valid data).

This means that the stubtechnically it works(post-boot state applies), but it is not enough for games that depend on the Boot ROM's advanced CGB configuration.

4. Conclusions and Next Steps

Step 0402 Achievements

  • ✅ Official optional Boot ROM integration on frontend (CLI + env var)
  • ✅ Stub mode implemented in C++ (no proprietary binary)
  • ✅ No regressions in working ROMs (Tetris DX)
  • ✅ Validated end-to-end wiring (frontend → backend → MMU)
  • ✅ Prepared for real Boot ROM when the user provides it

Stub Limitations

The stubdoes not emulate the real Boot ROM. Just configure a minimal post-boot state (DMG). Games that rely on advanced CGB settings (CGB palettes, HDMA, etc.) will not work with the stub.

Next Steps

  1. Step 0403: Document how user can legally extract Boot ROM from real hardware
  2. Step 0404: Implement partial Boot ROM emulation (logo scroll, no proprietary code)
  3. Step 0405: Investigate specific CGB settings that Zelda DX/Pokemon Red expect

Lessons Learned

  • Clean Room and pragmatism: We can support Boot ROM without violating clean room (user provides it)
  • Stub vs real Boot: The stub is useful for validation, but does not replace the actual Boot ROM
  • Game dependencies: Some games rely heavily on Boot ROM (more than expected)

5. Modified Files

Frontend

  • main.py: Added flags--bootromand--bootrom-stub, reading env varVIBOY_BOOTROM
  • src/viboy.py: Builder updated to receivebootrom_bytesandbootrom_stub, application logic inload_cartridge()

Backend (C++)

  • src/core/cpp/MMU.hpp: Declaration ofenable_bootrom_stub()
  • src/core/cpp/MMU.cpp: Implementation ofenable_bootrom_stub()

Cython Wrapper

  • src/core/cython/mmu.pxd: Declaration ofenable_bootrom_stub()
  • src/core/cython/mmu.pyx:Python wrapperenable_bootrom_stub()

Documentation

  • docs/bitacora/entries/2026-01-01__0402__bootrom-cli-env-y-stub.html: This entry
  • docs/bitacora/index.html: Updated with link to this entry
  • docs/report_phase_2/part_00_steps_0370_0401.md: Updated with Step 0402

6. References