SNK NEO-GEO · MVS & AES · MOTOROLA 68000 & ZILOG Z80

Neo Geo Complete Architecture & Programming Course

The comprehensive Programming course for designing, programming, and mastering original arcade software on the Neo Geo hardware. Covers bare-metal register control, the high-performance NeoGeoSDK 2D Game Engine, and the multi-channel Yamaha YM2610 sound driver.

Official Release: NeoGeoSDK v1.7.0 Published by Eagle Software Full Hardware & API Reference

Chapter 01

Preface & Course Scope

This course provides a comprehensive, rigorous technical foundation for software development on the SNK Neo Geo hardware architecture. Originally introduced in 1990 for arcade operators as the Multi Video System (MVS) and home enthusiasts as the Advanced Entertainment System (AES), the Neo Geo remains one of the most powerful and distinctive 2D sprite-based architectures in computing history.

This technical guide is written for systems programmers, game developers, and embedded software engineers who wish to build robust, high-performance cartridge software. It covers the machine from two complementary perspectives:

1. Direct Hardware Architecture

A rigorous breakdown of the silicon: Motorola 68000 bus timing, memory maps, Z80 co-processing, LSPC line-sprite rendering, VRAM timing windows, hardware auto-animation, color palette word encoding, and cabinet interrupt arbitration.

2. The NeoGeoSDK Framework

The modern, production-grade C engineering stack developed by Eagle Software. Covers the 35 modules of the 2D Game Engine (sdk/2d_engine/), VBlank deferred render queues, compound sprite group management, and the multi-channel YM2610 audio driver.

Unlike modern graphics systems that rely on 3D framebuffers and shader pipelines, the Neo Geo features no traditional frame buffer and no background tile planes. Every visual element on screen—from full-screen parallax backdrops and gigantic bosses down to individual projectiles—is composed of 16-pixel-wide hardware sprite strips evaluated in real-time on a line-by-line basis. Mastering the Neo Geo requires thinking in line buffers, fixed-point math, scanline budgets, and vertical blanking deadlines.

Official Repositories & Distribution This documentation is maintained in conjunction with NeoGeoSDK v1.7.0. The official SDK repository is hosted at github.com/eaglesoftware777/neogeosdk. Commercial development tools, hardware research, and official releases are published at eaglesoftware.biz.

Chapter 02

System Architecture & Silicon Topology

The Neo Geo architecture is founded on a dual-processor topology supported by specialized custom ASICs designed to offload video generation, audio playback, bus arbitration, and arcade cabinet administration.

+-------------------------------------------------------------------------+ | MOTOROLA 68000 CPU (12.0 MHz) | | System Master / Gameplay Logic | +-----------------------------------+-------------------------------------+ | 16-bit Data / 24-bit Address Bus v +-----------------------------------+-------------------------------------+ | SYSTEM GLUE ASICs & ARBITERS | | PRO-CT0 / PRO-B0 / NEO-B1 / NEO-C1 (Bus Decoding, Watchdog, Joypads) | | NEO-E0 (Exception Vector Table Multiplexer) | | NEO-F0 (MVS Coin Mechanics, RTC uPD4990A, Credit LEDs, Dipswitches) | +--------+--------------------------+-------------------------+-----------+ | | | v v v +-----------------+ +------------------+ +-------------------+ | P-ROM (PROG) | | WORK RAM (64KB) | | SOUND LATCH $3200 | | Program Code | | $100000-$10FFFF | | 8-bit Command Port| +-----------------+ +------------------+ +---------+---------+ | NMI Interrupt ($0066) | Ack Poll v +-----------------+ +------------------+ +---------+---------+ | LSPC2-A2 VDP | | PALETTE RAM 16KB | | ZILOG Z80 CPU | | Line-Sprite |<------>| 2 Banks of 256 | | (4.0 MHz) | | Video Engine | | 16-Color Palettes| +---------+---------+ +--------+--------+ +------------------+ | | v +-----+-----+ +---------+---------+ | | | YAMAHA YM2610 | v v | OPNB Sound Chip | +-------+ +-------+ +---------+---------+ | C-ROM | | S-ROM | | | 16x16 | | 8x8 | +---------+---------+ |Sprite | | FIX | | V-ROM (Samples) | | Tiles | | Tiles | | ADPCM-A & ADPCM-B | +-------+ +-------+ +-------------------+

1. Motorola 68HC000 (Main CPU)

The primary CPU is a Motorola 68HC000 running at 12.000 MHz. It has a 16-bit external data bus, a 24-bit external address bus (providing 16 MB of direct linear address space), and internal 32-bit data and address registers (D0-D7, A0-A7). On Neo Geo cartridge hardware, user software runs strictly in Supervisor Mode, giving direct access to privileged instructions and all memory-mapped hardware registers.

2. Zilog Z80 (Audio Co-Processor)

Audio is isolated on an independent subsystem powered by a Zilog Z80 microprocessor operating at 4.000 MHz. The Z80 possesses its own dedicated 16-bit address space, 2 KB of high-speed static scratchpad RAM at $F800–$FFFF, and access to bank-switched M1 ROM. The 68000 does not interact with the Yamaha sound chip directly; it communicates exclusively by writing 8-bit command bytes into a hardware latch at $320000.

3. Custom Support Chipsets

  • LSPC2-A2 (Line Sprite Processor): The core video processor. Manages internal 68 KB VRAM, addresses external C-ROM (sprite graphics) and S-ROM (FIX text), renders 381 sprite strips into dual scanline buffers, drives raster interrupt timers, and outputs pixel indices to Palette RAM.
  • PRO-CT0 / PRO-B0 / NEO-B1 / NEO-C1: System bus decoders and system controllers across different board revisions. They decode memory maps, manage the hardware watchdog timer, handle joystick input ports, and coordinate the 68000-to-Z80 communication latch.
  • NEO-E0: Interrupt vector multiplexer. Automatically swaps the 68000 CPU vector table between the motherboard BIOS and the cartridge program ROM.
  • NEO-F0 (MVS Cabinets): Arcade I/O ASIC. Controls the coin lockout solenoids, mechanical coin meters, test/service switches, hardware DIP switches, battery-backed calendar RTC (uPD4990A), and multiplexed 7-segment LED credit displays.
  • NEO-ZMC / NEO-ZMC2: High-speed memory mapping ASICs mounted on the cartridge CHA and PROG boards to handle Z80 bank-switching and ADPCM sample addressing.
Arcade development laboratory with cathode-ray test benches and 68000 systems.
Fig. 1. Hardware engineering laboratory: cathode-ray monitors, test instruments, and 68000 target system boards.

4. Bus Timing and Wait States

The 68000 executes basic bus cycles in 4 clock ticks (333.3 ns at 12 MHz). However, the video processor (LSPC) and certain system registers impose hardware wait states:

Access TargetBus WidthWait StatesEngineering Note
P-ROM Bank 0 ($000000)16-bit0 cyclesFull speed program execution.
User Work RAM ($100000)16-bit0 cyclesZero wait-state fast 64 KB SRAM.
LSPC VRAM Data ($3C0002)16-bit12–16 cyclesSynchronized with internal LSPC memory slots. Modulo auto-increments.
Palette RAM ($400000)16-bit0–2 cyclesDirect write; writing during active display produces visual noise (snow).
Memory Card ($800000)8-bit2–4 cyclesJEIDA card bus. Even bytes return $FF.

Chapter 03

Memory Maps & System Address Spaces

Understanding the exact 24-bit physical memory layout is essential for robust software architecture. The Neo Geo address space is divided into program ROM, high-speed work RAM, memory-mapped system I/O, video registers, palette storage, and non-volatile backup memory.

Motorola 68000 Physical Address Map

Address RangeCapacityDesignationFunction & Description
$000000–$0FFFFF1,048,576 BP-ROM Bank 0Cartridge program ROM. Exception vector table at $0000–$03FF. Cartridge header at $0100.
$100000–$10F2FF62,208 BUSER WORK RAMFast static RAM dedicated to game variables, engine pools, stack, and heap.
$10F300–$10FFFF3,328 BBIOS WORK RAMReserved exclusively for System BIOS OS variables, joystick state buffers, and coin accounting.
$200000–$2FFFFF1,048,576 BP-ROM Bank 1Second 1 MB cartridge code bank, or bankswitched window for multi-megabyte titles.
$300000–$3000012 BytesREG_P1CNT / DIPRead: P1 joystick inputs (active low). Write: Kick hardware watchdog timer.
$320000–$3200012 BytesREG_SOUNDWrite: 8-bit sound command byte to Z80. Read: Sound driver status/handshake latch.
$340000–$3400012 BytesREG_P2CNTRead: P2 joystick inputs (active low).
$380000–$3800012 BytesREG_STATUS_BStart/Select buttons, MVS vs AES flag (bit 7), Memory card status detection.
$3A000B / $1B2 BytesBRDFIX / CRTFIXFIX tile source multiplexer. $3A000B selects BIOS SFIX; $3A001B selects Cartridge S1.
$3A000F / $1F2 BytesPALBANK0 / 1Palette bank select. $3A000F activates Palette Bank 0; $3A001F activates Palette Bank 1.
$3A00132 BytesREG_SWPROMException vector control: routes CPU vector lookups to Cartridge P-ROM.
$3C0000–$3C001F32 BytesLSPC REGISTERSVRAM address, read/write data, modulo auto-increment, raster timers, and IRQ acknowledge.
$400000–$401FFF8,192 WordsPALETTE RAMCurrent bank palette storage (256 palettes x 16 words). Backdrop color at $401FFE.
$800000–$BFFFFFVariableMEMORY CARDRemovable JEIDA memory card interface (8-bit bus; even bytes read $FF).
$C00000–$C1FFFF131,072 BSYSTEM BIOS128 KB System Motherboard ROM containing boot routines, font, and OS services.
$D00000–$D0FFFF65,536 BBACKUP NVRAMMVS only: Battery-backed SRAM for arcade operator soft DIPs and financial bookkeeping.
Critical Hardware Fact: The Backdrop Register The active backdrop color register is hardwired to $401FFE (the final word of Palette Bank 0). The address $402000 is an inactive address mirror that will produce no change on physical silicon. Always use the SDK helper setBACKDROP(color) or write directly to $401FFE.

Z80 Audio CPU Address Map

Z80 AddressCapacityMappingPurpose
$0000–$7FFF32,768 BM1 Fixed Bank 0Driver initialization, NMI handler ($0066), Timer B tick ($0038), command FIFO.
$8000–$BFFF16,384 BSwitchable Bank 3Music sequence streams, MML tables, instrument definitions.
$C000–$DFFF8,192 BSwitchable Bank 2Audio patch voice libraries, FM operator definitions.
$E000–$EFFF4,096 BSwitchable Bank 1Secondary sample sequence tables.
$F000–$F7FF2,048 BSwitchable Bank 0Dynamic banking bank window.
$F800–$FFFF2,048 BZ80 Work RAMDriver scratchpad RAM, command ring buffer, channel status, volume matrices.

LSPC Video Processor VRAM Map (68 KB)

The LSPC manages an internal 16-bit wide video memory consisting of 68,416 words. The 68000 accesses VRAM indirectly by loading an address into REG_VRAMADDR ($3C0000) and transferring words through REG_VRAMRW ($3C0002):

VRAM Word AddressBlock NameFunctionality
$0000–$6FFFSCB1 (Slow VRAM)Sprite tile indices and attributes (381 sprites x 32 tiles = 12,192 words x 2 words per tile).
$7000–$77FFFIX MapOverlay text matrix (40 columns x 32 rows = 1,280 words). High nibble selects palette (0-15).
$7800–$7FFFReserved VRAMInternal LSPC register scratch area.
$8000–$81FFSCB2 (Fast VRAM)Shrink coefficients: horizontal shrink (upper nibble) and vertical shrink (lower byte).
$8200–$83FFSCB3 (Fast VRAM)Vertical position (bits 15-7), sticky chain flag (bit 6), and active tile height (bits 5-0).
$8400–$85FFSCB4 (Fast VRAM)Horizontal screen position (bits 15-7). Torus wrap arithmetic at 512 pixels.
$8600–$86FFScanline Sprite TablesInternal active line-sprite evaluation buffers populated automatically by the LSPC.

Chapter 04

Cartridge Architecture & ROM Layout

A standard Neo Geo cartridge consists of two distinct printed circuit boards (PCBs) housed within a rugged plastic shell: the PROG Board and the CHA Board. The cartridge interfaces with the Neo Geo motherboard via two 100-pin edge connectors (200 pins total).

Physical internal construction of Neo Geo PROG and CHA boards.
Fig. 2. The dual-board architecture. Left: PROG board with 68000 code and ADPCM samples. Right: CHA board with sprite C-ROMs, FIX S-ROM, and Z80 M-ROM.

ROM Types and Roles

ROM TypeExtensionHost BoardBus WidthDescription & Purpose
P-ROM.p1, .p2PROG16-bitMotorola 68000 machine code. Word-addressed. In MAME, bytes are interleaved/swapped.
V-ROM.v1, .v2PROG8-bitYamaha ADPCM sample data. Divided into ADPCM-A and ADPCM-B sample spaces.
C-ROM.c1, .c2...CHA32-bit (pair)16x16 4 bpp sprite graphic tiles. Interleaved across odd/even ROM pairs.
S-ROM.s1CHA8-bit8x8 4 bpp FIX layer font and UI tiles. Standard size is 128 KB.
M-ROM.m1CHA8-bitZ80 audio driver program, MML music sequence tables, and sound effects definitions.

C-ROM Planar Interleaving

Sprite tiles are 16x16 pixels at 4 bits per pixel (128 bytes per tile). In the cartridge hardware, bitplanes are physically divided across paired ROM chips to supply 32 bits of tile data in a single clock cycle:

Pixel Bitplanes: Bit 0 & Bit 1 ======> ODD C-ROM (e.g. C1, C3) Bit 2 & Bit 3 ======> EVEN C-ROM (e.g. C2, C4) Tile Memory Organization: - 16x16 tile is divided into four 8x8 quadrants. - Quadrant order: Top-Right, Bottom-Right, Top-Left, Bottom-Left. - Pixels are packed into 16-bit words representing bitplanes.

The Artbox pipeline in the NeoGeoSDK automatically converts standard PNG graphics into this interleaved planar layout during make art, eliminating manual bit-shifting.

Chapter 05

The System BIOS & The USER Subroutine

Unlike PC software that initializes through a linear main() entry point, a Neo Geo game cartridge functions as an embedded state machine managed by the System BIOS. The BIOS initializes hardware registers, displays the boot eyecatcher, services the MVS test menu, and invokes the game code through the USER subroutine.

The USER Dispatch Protocol

When the BIOS yields control to the cartridge, it jumps to the game's user() entry point and passes a request code in the byte register BIOS_USER_REQUEST ($10FDAE):

CodeNameRequired Action by the Game
0USER_INITInitialize soft DIP defaults, clear game state, reset progress. Interrupts are disabled. Must kick watchdog! Set BIOS_USER_MODE = 0.
1USER_EYECATCHExecute custom animated boot logo. Header byte $114 controls delegation. Kick watchdog continuously.
2USER_DEMOExecute the attract mode demonstration reel. On AES, this also serves as the title screen until the player presses Start.
3USER_TITLEMVS arcade title screen after coin insertion. Must poll BIOS_COMPULSION_TIMER ($10FDDA) to auto-start if operator sets timer.

When the game finishes its requested routine, it does not execute a simple assembly rts. It must perform a jump back to the BIOS return vector: jmp BIOSF_SYSTEM_RETURN.

Essential BIOS System Variables

AddressSymbolDescription
$10FD80BIOS_SYSTEM_MODEWrite $80 to claim VBlank interrupt processing for the game. Value 0 keeps VBlank in BIOS.
$10FD82BIOS_MVS_FLAGSystem indicator: 0 = AES Home Console; 1 = MVS Arcade Cabinet.
$10FD83BIOS_COUNTRY_CODERegion flag: 0 = Japan; 1 = USA; 2 = Europe.
$10FDAFBIOS_USER_MODEGame state: 0 = Init/Splash; 1 = Demo/Title; 2 = Active In-Game Play.
$10FD84BIOS_GAME_DIP16-byte array containing operator-configured software DIP switch settings.
$10FDDABIOS_COMPULSION_TIMERCountdown timer decremented by BIOS during title screen. Auto-starts game when it reaches zero.

Standard Cartridge Header ($100–$120)

/* Standard Neo Geo Cartridge Header layout in P-ROM */
__attribute__((section(".header")))
const NeoGeoHeader game_header = {
    .magic          = { 'N', 'E', 'O', '-', 'G', 'E', 'O', 0x00 }, /* Must match "NEO-GEO" */
    .ngh_id         = 0x0777,                 /* Unique NGH Game ID */
    .rom_size       = 0x0020,                 /* Program ROM size in 128KB units */
    .backup_size    = 0x0001,                 /* Backup RAM requirements */
    .eyecatch_flag  = 0x0000,                 /* 0 = Standard BIOS Logo, 1 = Custom Logo */
    .security_code  = { 0x00, 0x00, 0x00, 0x00 } /* BIOS hardware protection verification */
};

Chapter 06

Interrupts, Vertical Blank & The Hardware Watchdog

Execution timing on the Neo Geo is dictated by hardware interrupts synchronized with the CRT cathode beam. Managing these interrupts correctly ensures tear-free visuals, jitter-free input polling, and prevents the hardware watchdog from resetting the system.

Motorola 68000 Autovector Interrupts

InterruptVectorSourceHardware FrequencyEngineering Duty
IRQ Level 1$0064Vertical Blank (VBlank)59.18 Hz (NTSC) / 50.0 Hz (PAL)Drives game frame tick, kicks watchdog, drains VRAM/palette render queue.
IRQ Level 2$0068LSPC Line Raster TimerProgrammable scanlineMid-frame raster effects (split-screen scroll, water reflections, palette changes).
IRQ Level 3$006CAuxiliary / CabinetEdge triggerAcknowledge immediately; reserved on standard cartridge hardware.

Interrupts must be acknowledged by writing to REG_IRQACK at $3C000C. Bit 0 acknowledges IRQ1 (VBlank), Bit 1 acknowledges IRQ2 (Timer), and Bit 2 acknowledges IRQ3.

The Hardware Watchdog

To prevent unattended arcade cabinets from freezing indefinitely in commercial environments, the system hardware includes an integrated watchdog counter. If the game fails to access the watchdog register within approximately 128 milliseconds (~7.5 video frames), the hardware asserts an unmaskable system reset line, immediately rebooting the machine.

Servicing the Watchdog Writing to REG_DIPSW ($300001) clears the watchdog timer. In C, always call the SDK macro kickWatchDog():
  • Must be called once per frame inside the Vertical Blank handler.
  • Must be called inside any extended loading, decompression, or memory clearing loops that run with interrupts disabled.
  • Must be called during the USER_INIT BIOS callback before initializing subsystem variables.

The 60 Hz Frame Budget

Scanline 0 Scanline 224 |---------------------- ACTIVE DISPLAY (13.67 ms) ----------------------| [ Run Gameplay Physics ] [ AI Think Loops ] [ Populate Render Queue ] | Scanline 224 Scanline 262 v |-------------------- VERTICAL BLANK (3.00 ms) -------------------| [ IRQ1 Fires ] -> [ Kick Watchdog ] -> [ Drain Queue into VRAM ] -> [ Poll Joypad ]

In NTSC mode, a complete frame lasts 16.67 milliseconds (262 total scanlines). The active display occupies scanlines 0 through 223 (13.67 ms). The vertical blanking interval (VBlank) spans scanlines 224 through 261 (approx. 3.00 ms). Heavy VRAM operations and all palette updates must be completed within this ~3.0 ms window to prevent visible visual tearing and color snow.

Chapter 07

Video Architecture & The LSPC Line-Sprite Processor

The Neo Geo does not utilize a conventional linear frame buffer or tilemap-based scrolling background planes. Instead, the video system is powered by the custom LSPC2-A2 (Line Sprite Processor) ASIC, which composites graphics line-by-line using dual high-speed internal line buffers.

16-bit arcade fighting game graphics demonstrating massive sprite scaling and multi-layer compositing.
Fig. 3. The Line Sprite Processor compositing multiple large character strips and parallax scenery in real time.

The Line-Buffer Architecture

While Scanline N is being transmitted as analog RGB to the CRT monitor from Line Buffer A, the LSPC scans its active sprite list in VRAM and fetches 4 bpp pixel data from C-ROM and S-ROM to render Scanline N+1 into Line Buffer B. At the horizontal blanking interval, the roles of the two line buffers swap.

Resolution

320 x 224 visible pixels (NTSC) or 320 x 256 visible pixels (PAL), output as standard 15 kHz 240p RGB video.

Sprite Capacity

381 active sprite strips on screen simultaneously. Each strip is 16 pixels wide and up to 512 pixels tall.

Scanline Limits

A maximum of 96 sprite strips can be composited per scanline. Any 97th sprite on that scanline is dropped by hardware.

Hardware Sprite Priority

Hardware Law: Higher Slot Renders in Front On physical Neo Geo hardware, sprite priority is determined strictly by the sprite slot index: A higher sprite slot number is rendered in front of a lower sprite slot number.
  • Slots 1–32: Background parallax scenery.
  • Slots 33–95: Secondary props, items, and mid-ground terrain.
  • Slots 96–223: Primary characters, players, and large bosses.
  • Slots 224–287: Foreground visual effects, projectiles, and particle systems.
  • Slot 0: Reserved / unused by convention.

Direct LSPC Hardware Registers ($3C0000–$3C000E)

AddressRegisterAccessFunction
$3C0000REG_VRAMADDRWriteSets the target 16-bit internal VRAM word address.
$3C0002REG_VRAMRWRead / WriteTransfers data into/out of VRAM. Automatically applies modulo increment.
$3C0004REG_VRAMMODWriteSets auto-increment step: commonly +1 for linear data, or +64 for vertical tile strips.
$3C0006REG_LSPCMODEWriteControls auto-animation divider clock and programmable raster timer modes.
$3C0008REG_TIMERHIGHWriteRaster line interrupt timer counter (upper bits).
$3C000AREG_TIMERLOWWriteRaster line interrupt timer counter (lower bits).
$3C000CREG_IRQACKWriteAcknowledges active interrupts (Bit 0: VBlank, Bit 1: Timer, Bit 2: IRQ3).

Chapter 08

Color Palettes, Word Formats & Display Timing

The Neo Geo color pipeline is managed by an integrated 16 KB Palette RAM divided into two selectable banks of 256 palettes each. Each palette contains 16 colors, providing 4,096 simultaneous colors on screen out of a master hardware color space of 8,192 colors.

16-Bit Color Word Encoding

Each color in Palette RAM is stored as a 16-bit big-endian word featuring a master luminance Dark bit (D) and three 4-bit color primaries (Red, Green, Blue):

Bit: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---------------+---------------+-------------+---------------+ | D | R3 R2 R1 R0| G3 G2 G1 G0| 0 0 0 | B3 B2 B1 B0| +---+---------------+---------------+-------------+---------------+ D (Bit 15): Dark Bit. When 0, darkens the color output by approximately 50%. R (Bits 14-11): 4-bit Red component (values 0-15). G (Bits 10-7): 4-bit Green component (values 0-15). Bits 6-4: Must always be written as 000. B (Bits 3-0): 4-bit Blue component (values 0-15).

Palette Banking & Special Registers

  • Palette Banks: The system features two 16 KB banks. Writing to $3A000F activates Palette Bank 0; writing to $3A001F activates Palette Bank 1.
  • Color Index 0 (Transparency): Index 0 of every palette is transparent when evaluated by the LSPC compositor. By SNK hardware convention, index 0 is initialized to $8000 (reference black).
  • Backdrop Color: When no sprite or FIX tile covers a pixel, the LSPC displays the backdrop color stored at address $401FFE.
  • Preventing Palette Snow: Writing to Palette RAM while the cathode ray is scanning out active pixels causes high-frequency bus contention that appears as white dots ("snow") across the display. All palette updates, fades, and color flashes must be executed strictly during Vertical Blank.

Chapter 09

The FIX Layer Architecture

The FIX layer is an independent, non-scrolling character matrix dedicated to head-up displays (HUD), player scores, dialogue subtitles, coin counters, and system menus. It is always rendered in front of all 381 sprite strips.

Matrix Dimensions and Memory Layout

The FIX layer consists of a grid of 40 columns by 32 rows of 8x8 pixel tiles (4 bpp), mapped directly into internal VRAM starting at word address $7000.

PropertySpecificationEngineering Detail
Grid Dimensions40 columns x 32 rows1,280 total character cells.
Visible Resolution320 x 224 pixelsTop 2 rows and bottom 2 rows are concealed in standard CRT overscan.
Coordinate Mappingy_mem = y_screen + 2The first visible scanline corresponds to row 2 in VRAM. Row 0 is in the upper blanking margin.
Palette AllowancePalettes 0 through 15 onlyBits 15-12 of the FIX word encode the palette. Palettes 16-255 cannot be used.
Cell Word Format(pal << 12) | tile_id12-bit tile index allows addressing up to 4,096 8x8 glyphs in S-ROM.

Motherboard SFIX vs Cartridge S1 ROM

The system motherboard contains an internal SFIX ROM used by the BIOS for service tests, while the cartridge CHA board carries the game's custom S1 ROM. Writing to $3A000B (BRDFIX) routes FIX lookups to the motherboard BIOS ROM, while writing to $3A001B (CRTFIX) restores the cartridge S1 ROM. Whenever the game calls the BIOS routine SYS_FIX_CLEAR, it must immediately write CRTFIX to restore access to its custom game typography.

The Opaque Space Bug The default space character (ASCII 32) in the standard BIOS font is rendered as a solid, opaque rectangular box of color index 2. If a game prints a space glyph to "erase" text, it will draw an opaque rectangle over the background sprites. To clear a cell transparently, write tile index 0 with palette 0 using ng_fix_blank_cell().

Chapter 10

Hardware Sprites: SCB1, SCB2, SCB3, and SCB4

Every moving actor, background plane, and projectile on the Neo Geo is constructed from vertical sprite strips. Each strip is exactly 16 pixels wide and contains from 1 to 32 stacked 16x16 tiles. The hardware attributes of all 381 sprite strips are managed across four Sprite Control Blocks (SCB1–SCB4) in VRAM.

The Four Sprite Control Blocks

Control BlockVRAM AddressEncoding & Bit Definitions
SCB1
(Tile & Attributes)
$0000–$6FFFTwo words per tile:
Word 0: Lower 16 bits of tile index in C-ROM.
Word 1: Palette index (bits 15-8), Auto-Anim (bits 7-6), Vertical Flip (bit 1), Horizontal Flip (bit 0), Tile Index MSBs (bits 3-2).
SCB2
(Shrink Factors)
$8000–$81FFOne word per strip:
Bits 15-12: Horizontal shrink coefficient (values 0–15, where 15 = 100% full width).
Bits 7-0: Vertical shrink coefficient (values 0–255, where 255 = 100% full height).
SCB3
(Y Pos & Height)
$8200–$83FFOne word per strip:
Bits 15-7: Vertical screen position encoded as (496 - Y) & 0x1FF.
Bit 6: Sticky Bit (chains strip to preceding sprite strip).
Bits 5-0: Active tile height (1 to 32 tiles).
SCB4
(X Position)
$8400–$85FFOne word per strip:
Bits 15-7: Horizontal screen position encoded as (X & 0x1FF) << 7. Wraps on a 512-pixel circular torus.

Hardware Sprite Chaining (The Sticky Bit)

To construct compound sprites wider than 16 pixels (such as a 64x64 pixel boss), multiple adjacent sprite strips are chained together horizontally. Strip 0 acts as the Driver Strip, defining the base X position, Y position, active tile height, and vertical shrink. Subsequent follower strips set Bit 6 (Sticky Bit) in SCB3. Follower strips automatically inherit the Y position, tile height, and vertical shrink of the driver strip, eliminating the need to update Y coordinates across multi-strip actors every frame.

Asymmetric Scaling: The NG_SCALE Macro

The Neo Geo hardware scales sprites by dropping columns and rows, but the X and Y axes are encoded with different precision:

  • The X axis has 16 discrete shrink steps (encoded in bits 15-12).
  • The Y axis has 256 discrete shrink steps (encoded in bits 7-0).

Writing the same raw value to both axes will result in distorted, non-square scaling. The NeoGeoSDK provides the NG_SCALE(n) macro in sdk/2d_engine/ng_defs.h, which calculates proportional, aspect-correct values for both axes simultaneously.

Chapter 11

Cabinet I/O, Player Controls & Coin Systems

The Neo Geo system interfaces with arcade cabinet controls, coin doors, memory cards, and non-volatile operator memory through memory-mapped I/O registers arbitrated by the NEO-C1 and NEO-F0 custom gate arrays.

Controller Input Ports

Player inputs are exposed at $300000 (Player 1) and $340000 (Player 2). All button and directional lines are active-low (a pressed switch pulls the bit to logic 0):

Port $300000 (Player 1) / $340000 (Player 2): Bit 0: Up Bit 4: Button A Bit 1: Down Bit 5: Button B Bit 2: Left Bit 6: Button C Bit 3: Right Bit 7: Button D Port $380000 (REG_STATUS_B): Bit 0: P1 Start Bit 4: Memory Card Inserted (0 = Present) Bit 1: P1 Select Bit 5: Memory Card Write-Protected Bit 2: P2 Start Bit 7: MVS Hardware Mode (0 = AES Home, 1 = MVS Arcade) Bit 3: P2 Select

Arcade Coin & Credit Accounting

On MVS arcade hardware, coin handling is governed by the BIOS to ensure operator auditing integrity. When a coin passes through the mechanical coin switch, the BIOS increments hardware coin counters, drives the coin lockout coils (which physically reject coins when max credits are reached), triggers the game's COIN_SOUND callback, and records financial statistics in battery-backed Backup NVRAM at $D00000. User games must never decrement credits directly; they must call the documented BIOS credit decrement service BIOS_CREDIT_DEC.

Chapter 12

Direct Hardware Register Programming

Writing software at the bare-metal level requires mastering direct register communication with the LSPC video processor, configuring auto-increment modulos, programming the raster interrupt timer, and loading color palettes during Vertical Blank.

1. VRAM Addressing & Modulo Auto-Increment

The LSPC exposes two 16-bit register ports for accessing VRAM: REG_VRAMADDR ($3C0000) and REG_VRAMRW ($3C0002). Writing a data word to REG_VRAMRW automatically advances the internal VRAM pointer by the step value set in REG_VRAMMOD ($3C0004). This hardware auto-increment allows high-speed burst writes without manually reloading addresses.

/* Direct register definitions for low-level access */
#define REG_VRAMADDR  (*(volatile uint16_t *)0x3C0000)
#define REG_VRAMRW    (*(volatile uint16_t *)0x3C0002)
#define REG_VRAMMOD   (*(volatile uint16_t *)0x3C0004)
#define REG_IRQACK    (*(volatile uint16_t *)0x3C000C)
#define REG_DIPSW     (*(volatile uint8_t  *)0x300001)

/* Burst clear all 381 sprite slots in VRAM */
void hw_clear_all_sprites(void) {
    uint16_t i;

    /* Clear SCB3 (Y position, sticky, height): VRAM $8200-$837D */
    REG_VRAMMOD = 1;              /* Increment by +1 word per write */
    REG_VRAMADDR = 0x8200;
    for (i = 0; i < 381; ++i) {
        REG_VRAMRW = 0x0000;      /* Height = 0, disabled */
    }

    /* Park SCB4 (X position) off-screen: VRAM $8400-$857D */
    REG_VRAMADDR = 0x8400;
    for (i = 0; i < 381; ++i) {
        REG_VRAMRW = (496 << 7);  /* Park coordinate */
    }
}

2. Low-Level Sprite Setup & Position Updates

The following routine configures a single 16-pixel wide vertical sprite strip consisting of 4 stacked 16x16 tiles in SCB1, sets full size in SCB2, vertical position in SCB3, and horizontal position in SCB4:

void hw_spawn_sprite_strip(uint16_t slot, int16_t x, int16_t y,
                           uint16_t base_tile, uint8_t pal_idx, uint8_t height) {
    uint16_t t;
    uint16_t scb1_addr = slot * 64; /* 32 tiles x 2 words per tile = 64 words */

    /* 1. Populate SCB1 tile indices and attribute words */
    REG_VRAMMOD = 1;
    REG_VRAMADDR = scb1_addr;
    for (t = 0; t < height; ++t) {
        REG_VRAMRW = base_tile + t;                       /* Tile index LSB */
        REG_VRAMRW = ((uint16_t)pal_idx << 8);            /* Palette + flags */
    }

    /* 2. Configure SCB2: Full width (0x0F) and full height (0xFF) */
    REG_VRAMADDR = 0x8000 + slot;
    REG_VRAMRW = (0x0F << 8) | 0xFF;

    /* 3. Configure SCB3: Y position = (496 - y), no sticky, height in tiles */
    REG_VRAMADDR = 0x8200 + slot;
    REG_VRAMRW = (((496 - y) & 0x1FF) << 7) | (height & 0x3F);

    /* 4. Configure SCB4: X position = (x << 7) */
    REG_VRAMADDR = 0x8400 + slot;
    REG_VRAMRW = ((x & 0x1FF) << 7);
}

/* Ultra-fast position update: touches only SCB4 (X) and SCB3 (Y) */
void hw_move_sprite_fast(uint16_t slot, int16_t x, int16_t y, uint8_t height) {
    REG_VRAMADDR = 0x8400 + slot;
    REG_VRAMRW = ((x & 0x1FF) << 7);

    REG_VRAMADDR = 0x8200 + slot;
    REG_VRAMRW = (((496 - y) & 0x1FF) << 7) | (height & 0x3F);
}

3. Dynamic Palette Loading & Smooth Color Fading

The following function uploads a 16-color palette directly into Palette RAM and demonstrates calculating intermediate brightness steps for a fade-to-black effect:

#define PALETTE_RAM_BASE ((volatile uint16_t *)0x400000)
#define REG_BACKDROP     (*(volatile uint16_t *)0x401FFE)

void hw_load_palette(uint8_t pal_index, const uint16_t *src_colors) {
    volatile uint16_t *dst = PALETTE_RAM_BASE + (pal_index * 16);
    uint8_t i;
    for (i = 0; i < 16; ++i) {
        dst[i] = src_colors[i];
    }
}

/* Real-time color fade step: scales RGB primaries toward black */
uint16_t hw_fade_color(uint16_t color, uint8_t step, uint8_t max_steps) {
    uint16_t r = (color >> 11) & 0x0F;
    uint16_t g = (color >> 7)  & 0x0F;
    uint16_t b = color & 0x0F;
    uint16_t d = color & 0x8000; /* Dark bit */

    r = (r * step) / max_steps;
    g = (g * step) / max_steps;
    b = (b * step) / max_steps;

    return d | (r << 11) | (g << 7) | b;
}

4. Programmable Raster Line Timer (IRQ2)

The LSPC can trigger Level 2 autovector interrupts at an exact CRT scanline. This allows mid-screen palette bank swaps, split-screen scrolling, or water reflections without CPU polling.

#define REG_TIMERHIGH (*(volatile uint16_t *)0x3C0008)
#define REG_TIMERLOW  (*(volatile uint16_t *)0x3C000A)
#define REG_LSPCMODE  (*(volatile uint16_t *)0x3C0006)

void hw_setup_raster_timer(uint16_t scanline) {
    /* Configure LSPC raster timer to fire at specific line */
    REG_TIMERHIGH = 0x0000;
    REG_TIMERLOW  = scanline;
    REG_LSPCMODE  |= 0x0080;   /* Enable line timer interrupt mode */
}

/* Level 2 Interrupt Service Routine (called automatically at target scanline) */
__attribute__((interrupt)) void irq2_raster_handler(void) {
    /* Perform split-second raster modification (e.g. shift backdrop to water color) */
    REG_BACKDROP = 0x0008;     /* Dark blue water */

    /* Acknowledge Level 2 interrupt */
    REG_IRQACK = 0x0002;
}

Chapter 13

The Bare-Metal Hello World & Bring-Up Pipeline

Before integrating complex sprite hierarchies, every Neo Geo engineer begins with a minimal viable cartridge ROM. The Hello World specimen establishes the BIOS USER contract, clears hardware state, prints text to the FIX layer, kicks the watchdog, and verifies sound triggering.

Minimal Bootable Program

#include "sdk/neogeo.h"

void game_attract(void) {
    /* 1. Initialize audio subsystem and hardware state */
    soundInit();
    clearSprs();
    clearFix();
    setBACKDROP(0x0000); /* Pitch black backdrop */

    /* 2. Print initial diagnostics on the FIX text layer */
    fixtext_out(6, 8,  "EAGLE SOFTWARE NEOGEOSDK", 0);
    fixtext_out(6, 10, "BARE-METAL SYSTEM BRING-UP", 1);
    fixtext_out(6, 12, "WATCHDOG: ACTIVE [60 HZ]", 0);

    /* 3. Trigger initial confirmation audio sample */
    playSFX(0);

    /* 4. Closed frame heartbeat loop */
    for (;;) {
        waitVbl();        /* Wait for Vertical Blanking Interval */
        kickWatchDog();   /* Clear the hardware watchdog counter */
    }
}

The Five Verification Milestones

  1. Compile Cartridge ROMs: Run make GAME=helloworld all. Verify generation of 772-p1.p1 (68k code), 772-c1.c1 (sprites), 772-s1.s1 (FIX tiles), 772-m1.m1 (Z80 driver), and 772-v1.v1 (samples).
  2. Launch Under MAME: Execute make GAME=helloworld test. Confirm the BIOS eyecatcher plays, followed by the crisp text display.
  3. Coin and Start Input: In MVS mode, press 5 to deposit a virtual coin. Verify credit counter increments and COIN_SOUND fires cleanly.
  4. Watchdog Stress Test: Deliberately comment out kickWatchDog() in a test build; verify that MAME resets the CPU after ~7.5 frames, proving the watchdog watchdog is armed.
  5. Hot Text Patching: Modify the string in main.c, rebuild only the program binary with make p1, and confirm instantaneous reload.

Chapter 14

Engine Architecture & The VBlank Render Queue

The NeoGeoSDK 2D Game Engine (located in sdk/2d_engine/) is a modular, high-performance C99 framework designed to deliver arcade-grade visual spectacles while strictly respecting the physical limitations of the 68000 bus and LSPC line processor.

Core Engineering Principles

16.16 Fixed-Point Math

Zero floating-point instructions. All physics calculations, velocities, and projectile trajectories use 16.16 fixed-point arithmetic via ng_fixed.h.

Zero In-Game Allocation

No calls to malloc() or dynamic heap allocation during play. All character pools, particle buffers, and action tables are pre-allocated statically.

Zero Division in Frame Loops

Hardware division on the 68000 consumes up to 158 clock cycles. Division is replaced by bit-shifts, reciprocal multiplication, and lookup tables.

The 128-Slot Render Queue (ng_render_queue.h)

Writing directly to VRAM while gameplay logic runs causes visible tearing, because writes collide with the LSPC active line compositing pipeline. The NeoGeoSDK introduces a lock-free Render Queue that buffers visual update commands during the frame and flushes them in a rapid burst inside the 3.0 ms Vertical Blank window:

/* Render Queue command lifecycle */
while (game_running) {
    /* Phase 1: Logic & Physics (Active Display Scanlines 0-223) */
    update_player_movement();
    update_enemy_ai();
    check_collisions();

    /* Commands are queued, NOT written to hardware yet */
    ng_rq_sprite_pos(hero_slot, hero_x, hero_y);
    ng_rq_sprite_shrink(hero_slot, shrink_val);

    /* Phase 2: Frame Sync & VBlank Queue Drain (Scanlines 224-261) */
    waitVbl();
    kickWatchDog();
    ng_render_queue_flush(); /* Bursts up to 128 commands into VRAM within ~2 ms */
}

Render Queue Command Types

Command ConstantPayloadHardware TargetVRAM Cost
NG_RQ_SPRITE_POSslot, x, ySCB4 (X) & SCB3 (Y)2 words
NG_RQ_SPRITE_TILEslot, tile_indexSCB1 lower word1 word
NG_RQ_SPRITE_PALslot, palette_idxSCB1 upper word1 word
NG_RQ_SPRITE_SHRINKslot, x_shrink, y_shrinkSCB2 shrink word1 word
NG_RQ_SPRITE_HIDEslotSCB3 height = 01 word
NG_RQ_PAL_UPLOADpalette_slot, color_array[16]Palette RAM ($400000)16 words

Chapter 15

Characters, NPCs & Action State Machines

Dynamic actors in the NeoGeoSDK are managed by the Character Subsystem (ng_chars.h) and driven by declarative action script tables (ng_actions.h).

The 64-Slot Character Pool

The engine maintains a static pool of 64 character instances (NGCharacter). Each character encapsulates:

  • Life States: NG_CHAR_LIFE_FREE, NG_CHAR_LIFE_ALLOCATED, NG_CHAR_LIFE_VISIBLE, NG_CHAR_LIFE_HIDDEN.
  • 16.16 Fixed-Point Coordinates: x_fp, y_fp, vx_fp, vy_fp, ax_fp, ay_fp.
  • Collision Volumes: body_rect (physics boundary) and hit_rect (weapon strike boundary).
  • Priority Bands: Assigned to hardware rendering bands (Background, NPC, Enemy, Player, FX).

Declarative Action Scripts

Character animations and state transitions are defined using compact command arrays, eliminating manual frame counters:

#include "sdk/2d_engine/ng_engine.h"

/* Player Running Animation: sets horizontal speed, sequences 4 tiles, loops */
static const NGActionCmd ACT_HERO_RUN[] = {
    SPEED(FIX(2), 0),               /* Advance at 2.0 pixels/frame */
    FRAME(TILE_HERO_RUN_0, 4),      /* Show tile for 4 video frames */
    FRAME(TILE_HERO_RUN_1, 4),
    FRAME(TILE_HERO_RUN_2, 4),
    FRAME(TILE_HERO_RUN_3, 4),
    LOOP()                          /* Repeat indefinitely */
};

/* Player Slash Attack: stops movement, plays SFX, activates weapon hitbox */
static const NGActionCmd ACT_HERO_SLASH[] = {
    SPEED(0, 0),
    SOUND(SOUND_SFX_SLASH),         /* Dispatches audio trigger to Z80 */
    FRAME(TILE_HERO_PREPARE, 2),
    HITBOX(16, -8, 32, 24),         /* Activates hit_rect: x_off, y_off, w, h */
    FRAME(TILE_HERO_SWING, 6),
    HITBOX_OFF(),                   /* Deactivates strike volume */
    FRAME(TILE_HERO_RECOVER, 4),
    END()                           /* Transitions back to default state */
};

Chapter 16

Compound Sprite Groups & Hardware Chaining

Because individual Neo Geo sprites are vertical strips only 16 pixels wide, wide actors must be composed from multi-strip chains managed by ng_sprite_group.h.

Compound Actor (48x64 pixels = 3 horizontal strips x 4 vertical tiles): +---------------+---------------+---------------+ | STRIP 0 | STRIP 1 | STRIP 2 | | Driver Strip | Sticky Bit=1 | Sticky Bit=1 | | X = 100 | Inherits Y | Inherits Y | | Y = 150 | Inherits H | Inherits H | | Height = 4 | Inherits V-Sc| Inherits V-Sc| +---------------+---------------+---------------+

Dirty-Flag Optimization

Uploading an entire compound sprite group to VRAM requires writing dozens of words across SCB1, SCB2, SCB3, and SCB4. If executed every frame for multiple actors, VRAM bus bandwidth is quickly exhausted. The NGSpriteGroup structure maintains dirty tracking flags:

  • NG_SGF_DIRTY_POS: Only updates the driver strip's SCB4 (X) and SCB3 (Y). Cost: exactly 2 VRAM words! Follower strips update automatically via hardware sticky bits.
  • NG_SGF_DIRTY_SHRINK: Updates SCB2 shrink words across the strips using NG_SCALE().
  • NG_SGF_DIRTY_TILE: Updates SCB1 tile indices only when an animation frame advances.
/* Setup and movement of a 3-strip compound actor */
NGSpriteGroup boss_group;

void init_boss_graphics(void) {
    ng_sprite_group_init(&boss_group, BOSS_SLOT_BASE, 3 /* strips */, 4 /* rows */);
    ng_sprite_group_set_tile_base(&boss_group, TILE_BOSS_START);
    ng_sprite_group_set_palette(&boss_group, PAL_BOSS_ID);
    ng_sprite_group_set_scale(&boss_group, NG_SCALE(16), NG_SCALE(16)); /* 100% full size */
    ng_sprite_group_upload(&boss_group); /* Full initial burst upload */
}

void update_boss_position(int16_t new_x, int16_t new_y) {
    /* Only marks DIRTY_POS; flush will write only 2 words */
    ng_sprite_group_set_pos(&boss_group, new_x, new_y);
    ng_sprite_group_flush(&boss_group);
}

Chapter 17

Dynamic Cameras, Parallax Scrolling & Pseudo-3D

The Camera Subsystem (ng_camera.h) and Level Module (ng_level.h) translate world-space game coordinates into screen-space sprite coordinates, supporting smooth parallax scrolling and pseudo-3D scaling effects.

Camera Modes & Features

  • Target Following with Dead Zones: The camera smoothly follows the player's 16.16 position while enforcing a customizable dead zone so micro-adjustments do not cause camera jitter.
  • Velocity Look-Ahead: Shifts the view ahead of fast-moving characters (e.g. supersonic aircraft in Sky Lance) so the player can anticipate approaching threats.
  • Trauma Screen Shake: A decaying sinusoidal offset function applied to camera scroll registers to simulate heavy impacts and explosions.
  • Axis Locking: Lock horizontal axis for vertical shooters, lock vertical axis for side-scrolling beat-'em-ups, or allow unrestricted omnidirectional scrolling.

Pseudo-3D Projection (ng_depthfx.h)

Because the LSPC hardware can dynamically shrink sprites from 100% down to 0.4%, the NeoGeoSDK includes a 3D perspective projection pipeline. World coordinates (X, Y, Z) are projected into screen coordinates with matching hardware shrink coefficients, enabling scaling boss approaches and 3D starfields:

/* Projecting a 3D object into 2D sprite space */
void render_scaling_meteor(NGVec3 *pos, uint16_t slot) {
    NGVec2 screen_pt;
    uint8_t shrink;

    /* Perspective divide: screen = center + (world / Z) */
    ng_depth_project(pos, &screen_pt, &shrink);

    if (shrink > 0) {
        ng_rq_sprite_pos(slot, screen_pt.x, screen_pt.y);
        ng_rq_sprite_shrink(slot, shrink);
    } else {
        ng_rq_sprite_hide(slot); /* Behind camera */
    }
}

Chapter 18

Collision Detection, Physics & Game Feel

Juicy arcade gameplay depends on razor-sharp collision response, rigid platform physics, and visceral feedback mechanisms like freeze-frame hitstop, directional screen shake, and damage palette flashes.

Axis-Aligned Bounding Box (AABB) Intersection

The engine resolves hitbox collisions using integer comparison routines: ng_rect_hit(). Characters expose two distinct bounding rectangles:

  • ng_char_body_rect(c): The physical actor footprint used for world boundaries and obstacle collisions.
  • ng_char_hit_rect(c): The active weapon/projectile offensive hitbox, enabled dynamically during attack action script frames.

The Unified Impact System (ng_feedback.h)

When a decisive strike connects, calling ng_impact_event() orchestrates four coordinated physical feedback systems in a single call:

Feedback MechanismEngine ActionPsychological Impact
Hitstop (Freeze-Frame)Halts game logic for 4–12 frames while keeping CRT refresh active.Gives attacks weight and visceral kinetic resistance.
Directional ShakeInjects high-frequency trauma displacement into the camera offset.Simulates the force of explosive impacts.
Damage Palette FlashQueues an instantaneous 1-frame tint (White, Crimson, or Cyan).Provides unmistakable visual feedback of damage received.
Audio SFX TriggerDispatches one-shot punch, slash, or explosion ADPCM sample to Z80.Acoustic confirmation of impact.

The Particle Pool (ng_particles.h)

The engine maintains a pre-allocated 32-slot particle pool with priority eviction: critical explosions cannot be dropped to make room for low-priority background dust:

/* Spawning collision particles upon impact */
if (ng_rect_hit(ng_char_hit_rect(player), ng_char_body_rect(enemy))) {
    ng_char_damage(enemy, 25);
    ng_impact_event(NG_IMPACT_HEAVY); /* Hitstop + Shake + Flash */

    /* Spawn visual hit sparks and smoke from the pool */
    ng_spawn_metal_hit(enemy->x, enemy->y, TILE_SPARK_BASE, PAL_FX_ID);
    ng_spawn_smoke(enemy->x, enemy->y, TILE_SMOKE_BASE, PAL_FX_ID);
}

Chapter 19

Complete 2D Game Scene Implementation

The following complete, production-ready C program demonstrates integrating the player character, enemy AI, bullet pooling, collision resolution, particle generation, hitstop feedback, and a live HUD into a unified 60 Hz arcade gameplay loop.

#include "sdk/neogeo.h"
#include "sdk/2d_engine/ng_engine.h"
#include "sdk/sound_ids.h"

#define MAX_BULLETS 8
typedef struct {
    int16_t x, y;
    uint8_t active;
    uint16_t slot;
} Bullet;

static NGCharacter *player;
static NGNpc       *drone;
static Bullet      bullets[MAX_BULLETS];
static uint32_t    player_score = 0;

/* Drone Think Callback: patrol horizontally, hover sinusoidally */
static void drone_ai_think(NGNpc *npc, NGCharacter *c) {
    ng_npc_think_hover(npc, c); /* Built-in gentle hovering */
    if (c->x <= 60)  c->vx_fp = FIX(1);
    if (c->x >= 260) c->vx_fp = -FIX(1);
}

void scene_gameplay_init(void) {
    uint8_t i;
    ng_scene_begin(NG_SCENE_CLEAN_DEFAULT, 1);

    /* 1. Setup World Bounds & Camera */
    ng_level_set_world_bounds(0, 0, 320, 224);
    ng_camera_set_mode(NG_CAM_FOLLOW);

    /* 2. Spawn Player Character */
    player = chars_add(CHAR_KIND_PLAYER, 160, 180);
    ng_char_set_body(player, -8, -8, 16, 16);
    player->health = 100;

    /* 3. Spawn Enemy Drone */
    drone = ng_npc_spawn(CHAR_KIND_ENEMY, 160, 50);
    ng_npc_set_think(drone, drone_ai_think, 1);
    drone->base_char->health = 50;

    /* 4. Initialize Bullet Pool */
    for (i = 0; i < MAX_BULLETS; ++i) {
        bullets[i].active = 0;
        bullets[i].slot = NG_SPR_FX_FIRST + i;
    }

    /* 5. Initialize HUD on FIX Layer */
    ng_fix_clear();
    ng_fix_puts(2, 2, "1P SCORE:", 0);
    ng_fix_puts(26, 2, "ARMOR:", 0);

    /* 6. Start Music Loop on YM2610 */
    soundPlayGameLoop(SOUND_MUSIC_A);
}

void scene_gameplay_frame(void) {
    uint16_t pressed, held;
    uint8_t i;

    /* --- 1. Read Inputs --- */
    ng_joystick_update();
    pressed = ng_joy_pressed();
    held = ng_joystick_state()->p1_held;

    /* Player Horizontal Control */
    player->vx_fp = 0;
    if (held & JOY_LEFT)  player->vx_fp = -FIX(2);
    if (held & JOY_RIGHT) player->vx_fp =  FIX(2);

    /* Fire Weapon on Button A */
    if (pressed & JOY_A) {
        for (i = 0; i < MAX_BULLETS; ++i) {
            if (!bullets[i].active) {
                bullets[i].active = 1;
                bullets[i].x = player->x;
                bullets[i].y = player->y - 12;
                playSFX(SOUND_SFX_SHOT);
                break;
            }
        }
    }

    /* --- 2. Update Bullets & Collision --- */
    for (i = 0; i < MAX_BULLETS; ++i) {
        if (bullets[i].active) {
            bullets[i].y -= 6;
            if (bullets[i].y < -16) {
                bullets[i].active = 0;
                ng_rq_sprite_hide(bullets[i].slot);
            } else {
                ng_rq_sprite_pos(bullets[i].slot, bullets[i].x, bullets[i].y);

                /* Hit test bullet vs drone body */
                if (drone->base_char->health > 0) {
                    NGRect b_rect = { bullets[i].x - 2, bullets[i].y - 4, 4, 8 };
                    if (ng_rect_hit(b_rect, ng_char_body_rect(drone->base_char))) {
                        bullets[i].active = 0;
                        ng_rq_sprite_hide(bullets[i].slot);
                        ng_char_damage(drone->base_char, 10);
                        player_score += 100;

                        /* Trigger Game Feel Package */
                        ng_impact_event(NG_IMPACT_LIGHT);
                        ng_spawn_metal_hit(bullets[i].x, bullets[i].y, TILE_SPARK_BASE, 1);
                    }
                }
            }
        }
    }

    /* --- 3. Update HUD --- */
    ng_fix_put_u32(11, 2, player_score, 0, 0);
    ng_fix_put_u16(33, 2, player->health, 0, 0);

    /* --- 4. Advance Engine & Flush Queue in VBlank --- */
    waitVbl();
    kickWatchDog();
    ng_game_engine_frame(); /* Evaluates physics, renders queue into VRAM */
}

Chapter 20

Audio Architecture & The 68000/Z80 Interconnect

The Neo Geo audio subsystem is a self-contained sound computer powered by an independent Zilog Z80 CPU operating at 4.0 MHz paired with the Yamaha YM2610 (OPNB) sound synthesizer and YM3016 stereo digital-to-analog converter.

Vintage Yamaha YM2610 sound synthesizer and support circuitry on arcade PCB.
Fig. 4. The Yamaha YM2610 OPNB sound generator and discrete stereo analog output stages.

The Four Sound Synthesis Engines

Synthesis AreaHardware ChannelsOutput SpecsStandard Arcade Employment
ADPCM-A6 channels12-bit / ~18.5 kHz fixedOne-shot sound effects: punches, explosions, weapon fire, speech samples.
ADPCM-B1 channel16-bit / up to 55.5 kHz variableHigh-fidelity streaming audio: title themes, stage loops, hardware looping.
FM Synthesis4 channels (4 operators)Yamaha OPNB FM corePolyphonic music, lead melodies, synthesized bass, dynamic stings.
SSG Sound3 square channels + noiseYamaha YM2149 / AY-3-8910 lineageClassic chimes, 8-bit stings, arcade coin jingles, white-noise percussion.

The Dual-CPU Interconnect & Handshake Protocol

Motorola 68000 Zilog Z80 | | | 1. Write command byte to $320000 | |------------------------------------------>| Hardware triggers NMI at vector $0066 | | Z80 pushes byte into RAM FIFO ring buffer | | Reply port drops to 0 (Busy) | | | 2. Poll status byte at $320000 | |<------------------------------------------| Driver processes command | | Reply port rises back to 1 (Ready) v v

Communication between the CPUs is asynchronous and buffered:

  1. The 68000 writes an 8-bit command byte into REG_SOUND ($320000).
  2. This hardware write asserts the Z80 Non-Maskable Interrupt (NMI) line at vector $0066.
  3. The Z80 NMI service routine reads the latch and enqueues the byte into a circular FIFO ring buffer in Z80 Work RAM.
  4. The Z80 main loop drains the FIFO, verifies parameters, and programs the YM2610 registers.
  5. The 68000 monitors completion via the status latch: the port idles at logic 1, drops to 0 upon receiving a command, and rises back to 1 when acknowledged.
Deadlock Warning: Polling Order The reply port idles at 1. Therefore, waiting for a 1 immediately after writing without detecting the 0 transition will return instantaneously before the Z80 has even processed the NMI. Conversely, placing an unconditional busy-wait loop immediately after every write can stall the 68000 if the Z80 is servicing a heavy music interrupt. Always use the SDK wrapper soundCommand(), which handles the 1 → 0 → 1 transition safely.

Chapter 21

Z80 Sound Driver Internals & Command Protocol

The official NeoGeoSDK audio driver (sound/driver/driver.asm) is assembled into the M1 ROM. It runs an interrupt-driven audio kernel featuring fractional tempo tracking, volume matrixing, dynamic panning, and command queueing.

Z80 Interrupt Architecture

  • Vector $0066 (NMI): 68000 Command Arrival. Rapidly reads the hardware port and pushes the command into the 32-byte circular FIFO ring buffer.
  • Vector $0038 (Timer B IRQ): Hardware Music Tick. Driven by YM2610 internal Timer B at 124.008 Hz. Advances MML music streams, steps FM envelopes, updates fractional tempo counters, and decrements volume fade ramps.

Exhaustive Sound Command Reference Table

CommandHexArgumentsSDK C FunctionDescription
CMD_INIT$01NonesoundInit()Cold-boots Z80 driver, resets YM2610 registers, clears FIFO.
CMD_EYECATCH$02NonesoundPlayEyecatch()Triggers official BIOS boot chime and splash sound.
CMD_RESET$03NonesoundReset()Driver soft reset; silences channels, restores default volumes.
CMD_STOP_ALL$04NonesoundStopAll()Immediately cuts all audio across FM, SSG, ADPCM-A, and ADPCM-B.
CMD_SET_VOL_A$051 byte (0–255)soundSetADPCMAVolume(v)Sets global master attenuation for all 6 ADPCM-A channels.
CMD_SET_VOL_B$061 byte (0–255)soundSetADPCMBVolume(v)Sets master volume for the ADPCM-B streaming music channel.
CMD_SET_VOL_SSG$071 byte (0–15)soundSetSSGVolume(v)Sets SSG square-wave volume (4-bit logarithmic scale).
CMD_FADE_OUT$0A1 byte (speed)soundFadeOutSpeed(spd)Starts automated hardware volume fade-out to silence.
CMD_STOP_A$0CNonesoundStopADPCMA()Silences active ADPCM-A sound effects; leaves music intact.
CMD_STOP_B$0DNonesoundStopADPCMB()Silences active ADPCM-B streaming track.
CMD_SET_TEMPO$0E1 byte (divisor)soundSetTempo(div)Configures legacy tempo divider for music sequencing.
CMD_STOP_MUSIC$0FNonesoundStopMusic()Stops MML, FM, and SSG tracks; leaves sound effects playing.
CMD_FADE_CANCEL$11NonesoundCancelFade()Aborts active fade ramp and restores base master volumes.
CMD_FADE_IN$121 byte (speed)soundFadeInSpeed(spd)Ramps volume smoothly up from silence to default master levels.
CMD_SET_VOL_FM$131 byte (0–15)soundSetFMVolume(v)Sets FM synthesis master channel volume.
CMD_SET_PAN_B$141 byte ($00-$C0)soundSetADPCMBPan(pan)Controls ADPCM-B stereo panning ($C0=Center, $80=Left, $40=Right).
CMD_SET_LOOP_B$151 byte (0 or 1)soundSetADPCMBLoop(en)Enables/disables continuous hardware looping on subsequent B starts.
CMD_SET_LFO$161 byte ($00-$0F)soundFMSetLFO(rate)Configures YM2610 hardware Low-Frequency Oscillator (LFO).
CMD_PLAY_SFX$201 byte (ID 0–127)playSFX(id)Triggers one-shot ADPCM-A sound effect from V-ROM.
CMD_PLAY_B$301 byte (ID 0–15)playSFXB(id)Starts playback of streaming ADPCM-B musical track.
CMD_PLAY_FM$401 byte (ID 0–7)playFMTrack(id)Starts compiled polyphonic FM music sequence.
CMD_PLAY_SSG$501 byte (ID 0–8)playSSGTrack(id)Starts standalone SSG square-wave melody sequence.
CMD_COIN_JINGLE$60NoneplayInsertCoinSSG()Executes authentic arcade coin insertion jingle.

Chapter 22

FM & SSG Synthesis Programming

The Yamaha YM2610 provides rich frequency modulation (FM) synthesis and authentic 8-bit programmable sound generation (SSG) directly in silicon.

FM Synthesis: 4-Operator Architecture

Each of the four FM channels is constructed from four independent sine-wave oscillators called Operators (OP1, OP2, OP3, OP4). Each operator possesses its own dedicated envelope generator defined by nine hardware registers:

FM Operator Architecture: [ Detune (DT) / Multiplier (MUL) ] ==> Sets base frequency ratio [ Total Level (TL) ] ==> Sets output volume / modulation depth [ Attack Rate (AR) ] ==> Initial envelope rise time [ Decay Rate (DR) / Key Scale (KS)] ==> Secondary decay rate & keyboard scaling [ Sustain Rate (SR) ] ==> Level maintained while key is held [ Release Rate (RR) ] ==> Final decay time after key release [ Sustain Level (SL) ] ==> Transition point between Decay and Sustain

The routing between operators is determined by the Algorithm (0–7). In Algorithm 0, all four operators are connected in series (OP1 modulates OP2, which modulates OP3, which modulates OP4), producing complex metallic and harmonic textures. In Algorithm 7, all four operators run in parallel as independent carriers, producing rich polyphonic organs and additive pads.

Low-Frequency Oscillator (LFO) Modulation

Register $22 controls the integrated LFO, generating hardware vibrato (Frequency Modulation Sensitivity, FMS) and tremolo (Amplitude Modulation Sensitivity, AMS). In C, configure the LFO rate using:

/* Configure YM2610 hardware LFO: bit 3 enables, bits 0-2 select rate (0-7) */
soundFMSetLFO(0x08 | 0x04); /* Enable LFO at medium-fast modulation rate */

SSG Square Waves & Arcade Sound FX

The SSG block provides three square-wave tone channels, a 5-bit white noise generator, and a hardware envelope generator. It is ideal for crisp coin chimes, retro laser zaps, and percussive explosions. The SDK provides high-level helpers to play pre-compiled SSG stings:

playInsertCoinSSG();           /* Authentic coin deposit chime */
playSSGTrack(SOUND_SSG_STING); /* Stage clear victory fanfare */

Chapter 23

ADPCM-A & ADPCM-B Sample Playback

The YM2610 features two distinct Adaptive Differential Pulse Code Modulation (ADPCM) subsystems that stream compressed digital audio directly from cartridge V-ROM without CPU intervention.

ADPCM-A (6-Channel Sound Effects)

  • Channels: 6 independent hardware channels.
  • Sampling Rate: Fixed hardware clock of approximately 18.5 kHz.
  • Compression: 12-bit dynamic range compressed to 4-bit nibbles.
  • Memory Limits: Samples are mapped into V-ROM via start and end address tables in M1. An individual ADPCM-A sample cannot cross a 1 MB address boundary.
  • Stereo Control: Each channel can be steered Left, Right, or Center through registers $08–$0D.

ADPCM-B (High-Fidelity Streaming & Looping)

  • Channels: 1 dedicated high-fidelity stream.
  • Sampling Rate: Fully programmable up to 55.5 kHz via 16-bit Delta-N pitch registers ($19–$1A). Standard SDK music beds are encoded at 32 kHz.
  • Compression: 16-bit linear audio compressed to 4-bit delta encoding.
  • Hardware Looping: Setting soundSetADPCMBLoop(1) instructs the YM2610 to seamlessly repeat playback when the sample end address is reached, creating gapless background music loops without CPU timing drift.

Speech Synthesis Bank

The NeoGeoSDK driver incorporates an internal pre-compiled speech library encoded as ADPCM-A samples. The 68000 can synthesize full voice phrases with zero disk I/O:

/* Trigger full phonetic speech or pre-compiled words */
speakText("ROUND 1 READY FIGHT");
playVoiceWord(SOUND_VOICE_WORD_GAME_OVER);
playVoiceNumber(player_score);

Chapter 24

Complete Audio Driver Integration & Sound Manager

The following production C module provides a complete Game Sound Manager. It queues audio commands, enforces rate limits, handles dynamic volume ducking during voice lines, and manages seamless music cross-fades during stage transitions.

#include "sdk/neogeo.h"
#include "sdk/sound_ids.h"

typedef struct {
    uint8_t current_bgm;
    uint8_t bgm_looping;
    uint8_t master_sfx_vol;
    uint8_t master_bgm_vol;
    uint8_t is_ducking;
    uint16_t duck_timer;
} SoundManager;

static SoundManager audio_mgr;

void audio_init(void) {
    soundInit();
    audio_mgr.current_bgm    = 0xFF; /* None */
    audio_mgr.bgm_looping    = 1;
    audio_mgr.master_sfx_vol = 0x38; /* Standard ADPCM-A volume */
    audio_mgr.master_bgm_vol = 0xC0; /* Standard ADPCM-B volume */
    audio_mgr.is_ducking     = 0;
    audio_mgr.duck_timer     = 0;

    soundSetADPCMAVolume(audio_mgr.master_sfx_vol);
    soundSetADPCMBVolume(audio_mgr.master_bgm_vol);
}

void audio_play_bgm(uint8_t track_id) {
    if (audio_mgr.current_bgm == track_id) return;

    /* Gracefully stop existing audio and reset scene volumes */
    soundSceneReset();
    soundSetADPCMBVolume(audio_mgr.master_bgm_vol);
    soundSetADPCMBLoop(audio_mgr.bgm_looping);
    soundSetADPCMBPan(0xC0); /* Stereo Center */

    playSFXB(track_id);
    audio_mgr.current_bgm = track_id;
}

void audio_play_voice_ducked(const char *phrase) {
    /* Duck BGM volume to 40% so spoken voice is clear */
    soundSetADPCMBVolume(audio_mgr.master_bgm_vol / 2);
    audio_mgr.is_ducking = 1;
    audio_mgr.duck_timer = 90; /* Duck for 1.5 seconds (90 frames) */

    speakText(phrase);
}

void audio_tick(void) {
    /* Manage audio ducking timer */
    if (audio_mgr.is_ducking) {
        if (--audio_mgr.duck_timer == 0) {
            audio_mgr.is_ducking = 0;
            /* Restore background music to full master volume */
            soundSetADPCMBVolume(audio_mgr.master_bgm_vol);
        }
    }
}

void audio_fade_and_stop(uint8_t speed) {
    soundFadeOutSpeed(speed);
    audio_mgr.current_bgm = 0xFF;
}

Chapter 25

The Artbox Pipeline & Studio Tools

Converting modern 24-bit RGB PNG artwork into 4 bpp interleaved C-ROM sprite bitplanes and S-ROM FIX glyphs is automated by the Artbox Pipeline (artbox/) and the interactive PyQt6 Artbox Studio.

16-color pixel art stage scenery featuring mountains, waterfall, and pagoda.
Fig. 5. Multi-layer 16-color pixel art stage scenery processed through the Artbox pipeline for NeoGeoSDK.

Asset Specification: artbox/assets.cfg

Assets are organized into four primary classes in artbox/assets.cfg:

Asset ClassInput FolderGrid AlignmentOutput Destination
Backgroundsartbox/in/320x224 or multi-pageLow sprite slots (1–32) for parallax scenery.
Charactersartbox/in/16x16 pixel incrementsCharacter sprite range (slots 96–223).
Enemies / Bossesartbox/in/16x16 pixel incrementsEnemy sprite range (slots 160–255).
FIX Overlay Glyphsartbox/infix/8x8 pixel gridS-ROM (S1), mapped to VRAM $7000.

Automated Quantization & Planar Splitting

  1. Color Quantization: The pipeline automatically maps 24-bit PNG colors down to the strict Neo Geo budget: 15 visible colors plus 1 transparent color (index 0) per 16x16 tile.
  2. Palette Banking: If an illustration contains multiple color themes, Artbox groups tiles into optimal 16-color palettes and generates a header mapping table (artbox/sprite_meta.h).
  3. Planar Bitplane Splitting: The encoder separates pixel bitplanes into odd (bitplanes 0 & 1) and even (bitplanes 2 & 3) binary streams for C1/C2 ROM burning.

Interactive Studio GUI Tools

# Launch the visual Artbox Studio (Tile viewer, hitbox editor, animation player)
python3 artbox/artbox_studio.py

# Launch the Sound Studio (FM patch synthesizer, MML composer, ADPCM audition)
python3 sound/sound_studio.py

Chapter 26

Twelve-Step Engineering Roadmap to a Complete Title

Building a commercial-grade Neo Geo game requires a structured, iterative development discipline. Each step produces an independently testable, bootable ROM.

  1. The Bare-Metal Boot (Hello World): Compile games/helloworld. Verify motherboard BIOS boot, watchdog kick, credit LED servicing, and text rendering on the FIX layer.
  2. The USER Subroutine Contract: Implement clean handlers for Request 0 (Soft DIP init), Request 1 (Custom Eyecatcher), Request 2 (Attract Reel), and Request 3 (Title Screen). Ensure BIOS_USER_MODE transitions correctly.
  3. The Head-Up Display (HUD): Construct scoreboards, energy gauges, and credit counters on the FIX layer using ng_fix.h. Verify display on a CRT monitor safe area (columns 1–38, visible rows 0–27).
  4. Color Palette Architecture: Assign palettes: 0 for FIX text, 1–15 for scenery, 16+ for characters. Implement smooth, tear-free palette fade-ins using the Render Queue.
  5. Single Actor Hardware Bring-Up: Upload a 16x16 character strip into slot 96. Drive horizontal and vertical movement via joystick registers. Confirm torus wrapping at coordinate 512.
  6. Action Script Animation States: Define declarative action scripts for Idle, Run, Jump, and Attack using ng_actions.h. Bind joystick edges to animation triggers.
  7. Audio Subsystem Integration: Establish the Z80 sound driver. Fire one-shot ADPCM-A sound effects on attacks, play an ADPCM-B looping bed during gameplay, and trigger coin chimes on deposit.
  8. World Bounds & Scrolling Camera: Expand world boundaries beyond 320x224. Configure camera follow with dead zones and look-ahead. Add a scrolling parallax background in low sprite slots (1–32).
  9. Adversaries, AI & Collision: Spawn enemy NPCs with patrol think callbacks. Detect weapon collisions using AABB rectangles. Trigger hitstop, camera shake, and damage flashes on impact.
  10. The Gameplay Progression Loop: Build wave spawning, item drop tables, boss encounter phases, and stage transition routines.
  11. Attract Mode & High Score Persistence: Implement an attract loop that plays automated demonstration reels. Save top scores and player progress to MVS battery-backed Backup RAM at $D00000.
  12. Final Release Packaging: Compile production ROMs, verify CRCs against the MAME hash XML, and burn physical EPROMs/Flash cartridges.

Chapter 27

Reference Game Analysis: Sky Lance

Sky Lance (games/skylance, NGH ID 779) serves as the complete, reference arcade production within the NeoGeoSDK. It demonstrates every advanced engine capability operating on strict 60 Hz hardware timing.

Production Features

  • Three selectable fighter pilots with unique handling and fire rates.
  • Seven distinct vertical scrolling stages spanning oceans, cities, and bases.
  • Multi-phase boss encounters with compound multi-strip scaling.
  • Full ADPCM-B looping background music and voice announcements.

Architecture Walkthrough Order

  1. game.mk: Target ROM configuration, ID 779, asset lists.
  2. user.c: BIOS USER callback state transitions.
  3. scenes/scene_play.c: Complete 60 Hz frame game loop.
  4. scenes/pilot_select.c: Character selection carousel.
  5. artbox/assets.cfg: Asset pipeline configuration.

The companion laboratory ROM, games/demo (ID 777), contains 25 interactive chapters demonstrating every single engine module—from starfields and compound sprite scaling to audio ducking and performance profilers.

Chapter 28

Emulation, Verification & Physical Hardware Sign-Off

While modern emulators like MAME provide rapid development turnaround, releasing a commercial-grade Neo Geo game requires rigorous validation across multiple BIOS revisions and physical hardware testing.

Testing with MAME

Launch tests using the official makefile targets, which configure ROM paths and hash definitions automatically:

# Test MVS Arcade mode with European BIOS
make GAME=skylance test

# Test AES Home Console mode
make GAME=skylance test-aes

# Test with UniBIOS 4.0
make GAME=skylance test-unibios

Pre-Release Hardware Sign-Off Checklist

  1. Universal BIOS Boot: Clean boot verified under MVS Japan, MVS USA, MVS Europe, AES, and Universe BIOS without memory errors.
  2. Watchdog Safety: The watchdog counter is refreshed cleanly during boot initialization, attract mode, gameplay, pause, and game-over screens.
  3. Coin Auditing Integrity: Coins deposit reliably; credits decrement strictly through BIOS services; credit LEDs display correct numbers.
  4. Overscan Legibility: All essential HUD elements, health bars, and score numerals sit inside CRT safe columns 1–38 and visible rows 0–27.
  5. Line Sprite Limit Budget: No scanline exceeds 96 sprite strips during heavy firefights or boss encounters.
  6. Tear-Free VRAM & Palette Uploads: Zero palette snow during play; all VRAM updates queued and flushed strictly during Vertical Blank.
  7. Audio Protocol Stability: Sound commands execute without locking the 68000; ADPCM-B music loops smoothly without audible pops or timing drift.
  8. Clean Session Termination: Game-over screen returns control to the BIOS with BIOS_USER_MODE correctly set.
  9. Cryptographic Verification: Region CRCs and SHA-1 hashes match the build manifest; make dist zip executes cleanly on stock MAME installations.

Chapter 29

Hardware Register Reference & Official Resources

Essential Motorola 68000 System Registers

Physical AddressRegister NameAccessEngineering Summary
$300000REG_P1CNTReadPlayer 1 Joystick & Buttons A/B/C/D (active low).
$300001REG_DIPSWRead / WriteRead: Hardware DIP switches. Write: Kicks hardware watchdog.
$320000REG_SOUNDRead / WriteWrite: Command byte to Z80. Read: Sound driver ready/busy status.
$340000REG_P2CNTReadPlayer 2 Joystick & Buttons A/B/C/D (active low).
$380000REG_STATUS_BReadStart/Select buttons, MVS/AES mode flag (bit 7), Memory Card state.
$3A000BBRDFIXWriteSelects Motherboard SFIX ROM for FIX text layer.
$3A001BCRTFIXWriteSelects Cartridge S1 ROM for FIX text layer.
$3A000FPALBANK0WriteActivates Palette Bank 0 (Palettes 0–255).
$3A001FPALBANK1WriteActivates Palette Bank 1 (Palettes 0–255).
$3A0013REG_SWPROMWriteSelects Cartridge P-ROM for CPU exception vectors.
$3C0000REG_VRAMADDRWriteSets target 16-bit VRAM word address pointer.
$3C0002REG_VRAMRWRead / WriteTransfers data into/out of VRAM; auto-increments by modulo.
$3C0004REG_VRAMMODWriteConfigures modulo auto-increment step (+1, +64, etc.).
$3C0006REG_LSPCMODEWriteControls auto-animation divider clock and raster timer modes.
$3C0008REG_TIMERHIGHWriteLSPC raster line timer reload value (high bits).
$3C000AREG_TIMERLOWWriteLSPC raster line timer reload value (low bits).
$3C000CREG_IRQACKWriteAcknowledges interrupts (Bit 0: VBlank, Bit 1: Timer, Bit 2: IRQ3).
$401FFEREG_BACKDROPWriteActive backdrop color word in Palette Bank 0.

Core Engineering Doctrine Summary

  • Sprite Ordering: Higher sprite slot number is rendered in front of lower sprite slot number.
  • Hardware Scaling: The LSPC shrinks sprites; it never stretches. Proportional scaling requires NG_SCALE().
  • Active Character Height: SCB3 height defines on-screen characters regardless of shrink. Unused rows must hold blank tile 0.
  • FIX Layer Alignment: Screen row y corresponds to memory row y + 2. Limit palettes to 0–15. Clear with transparent tile 0.
  • Watchdog Discipline: Kick REG_DIPSW every frame inside VBlank and inside all long initialization loops.
  • Audio Protocol: Send audio commands via soundCommand(). Poll for 1 → 0 → 1 ready transition.
  • Deterministic Execution: No floating-point instructions, no dynamic malloc() during play, no division in hot frame loops. 16.16 fixed-point is king.

Authoritative Developer Links