214. Parse ines header

add

Lesson 214 of 356 · tests/chapter_02_rom_loading/test_214_parse_ines_header.py

add emulator/cartridge/ines.py::parse_ines_header.

Why this step exists

The parser validates the fixed envelope before converting bytes 4-7 into the immutable INesHeader introduced in lesson 213. It depends on the constants from lesson 212 and the dataclass from lesson 213.

Suggested implementation

def parse_ines_header(data: bytes) -> INesHeader:
    if len(data) < INES_HEADER_SIZE:
        raise ValueError("iNES data is too short")
    if data[0:4] != INES_MAGIC:
        raise ValueError("Invalid iNES header")

    prg_rom_banks = data[4]
    chr_rom_banks = data[5]
    flags_6 = data[6]
    flags_7 = data[7]
    has_trainer = (flags_6 & 0b0000_0100) != 0
    mapper_number = (flags_6 >> 4) | (flags_7 & 0xF0)
    return INesHeader(
        prg_rom_banks,
        chr_rom_banks,
        mapper_number,
        has_trainer,
        flags_6,
        flags_7,
    )

Invariants: reject short input before indexing it; require b"NES"; use flags 6's upper nibble as mapper bits 0-3 and flags 7's upper nibble as bits 4-7; preserve both raw flags. A common mistake is multiplying bytes 4 and 5 here: they remain bank counts.

Out of scope for this step

1. Lesson 215 groups a parsed header with ROM sections.
2. Lesson 216 extracts and validates declared PRG/CHR payload bytes.

Run this lesson

uv run pytest tests/chapter_02_rom_loading/test_214_parse_ines_header.py -v