1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-08 17:06:04 +00:00
nescript/docs/nes-reference.md
Claude 548787ac8a
W0110 inline fallback warning + docs refresh
W0110: when a function marked `inline` has a body shape the IR
lowerer can't splice (conditional early return, loops, nested
control flow, empty void body), the analyzer now emits a
warning at the declaration site so the declined hint is
visible instead of silently falling back to a regular JSR.

Implementation:
  - New `W0110` error code in `src/errors/diagnostic.rs` (warning level).
  - New `pub fn can_inline_fun(return_type, body) -> bool` in
    `src/ir/lowering.rs`, extracted from the existing capture
    logic so the analyzer and the IR lowerer share the same
    eligibility rules and can never drift.
  - New `check_inline_declinability` analyzer pass called from
    the tail of `analyze_program`, mirroring the existing
    `check_sprite_scanline_budget` / `check_unreachable_states`
    passes. Emits W0110 with help + note text pointing at the
    two accepted body shapes.
  - `capture_inline_bodies` now defers to `can_inline_fun`
    instead of duplicating the match pattern, so the two sides
    stay in lockstep by construction.

Four regression tests in `src/analyzer/tests.rs` cover the
conditional-return and while-loop declines plus the two
accepted shapes (single-return expression, void sequence).

Example source cleanups: `wrap52` in `examples/war/deck.ne`
and `abs_diff` in both `examples/arrays_and_functions.ne` and
`examples/loop_break_continue.ne` drop the `inline` keyword.
All three were dead hints — the `inline` was being silently
declined before this change, so removing it is source-only;
the three ROMs are byte-identical, all 32 emulator goldens
still match.

Docs refresh
  - `docs/language-guide.md`: rewrote the Inline Functions section
    (real behaviour + W0110), added W0105/W0106/W0107/W0108/W0109/
    W0110 to the warnings table, added the `debug.sprite_overflow*`
    builtins + sprite-per-scanline mitigations section to the
    Debug Mode docs, added a `cycle_sprites` statement entry and
    cross-referenced it from `draw`.
  - `docs/nes-reference.md`: fleshed out the "NEScript Memory
    Usage" block with the full ZP + high-RAM layout, including
    the new `$07EF` / `$07FC` / `$07FD` slots for sprite cycling
    and the debug sprite-overflow telemetry.
  - `docs/future-work.md`: documented all four debug query
    builtins in the "What ships today" block; updated the open
    "OAM allocation strategy" question to reference the shipped
    `cycle_sprites` path and ask about an automatic-flicker
    game attribute as a follow-up.
  - `docs/architecture.md`: updated the `ir/` and `optimizer/`
    module summaries to describe real inline splicing (now
    in lowering, not the optimizer).
  - `README.md`: reframed the `inline` bullet from "hint" to
    "real splicing for single-return / void-body shapes";
    expanded the debug-support bullet to mention the four
    query builtins and their stripping in release builds; added
    a new bullet for the three-layer sprite-per-scanline
    mitigations; bumped the test count from 497 → 694; updated
    the war.ne entry to mention the seven compiler bugs are all
    fixed and point readers at `git log` (instead of the
    deleted COMPILER_BUGS.md).
  - `examples/README.md`: same `git log`-pointing rewrite for
    the war.ne entry.

Deletions
  - `examples/war/COMPILER_BUGS.md` is removed. All seven
    catalogued bugs are fixed; the file's historical value
    lives in `git log` now. Every source-code comment and doc
    reference to the file has been updated to either point at
    `git log` or just describe the bug in place.

Test count: 616 unit + 75 integration + 3 doctests = 694 total.
Clippy / fmt clean. 32/32 emulator goldens match.

https://claude.ai/code/session_0143dTgh3UeRrtfHgQwzcv5z
2026-04-15 23:19:07 +00:00

9.7 KiB

NES Hardware Quick Reference

A concise reference to the NES hardware for NEScript contributors. Understanding these constraints explains many of the compiler's design decisions.


CPU: Ricoh 2A03 (MOS 6502 variant)

  • Clock speed: 1.79 MHz (NTSC), 1.66 MHz (PAL)
  • Architecture: 8-bit data bus, 16-bit address bus
  • Registers:
    • A (Accumulator) -- 8-bit, used for arithmetic and logic
    • X (Index X) -- 8-bit, used for indexing and counting
    • Y (Index Y) -- 8-bit, used for indexing and counting
    • SP (Stack Pointer) -- 8-bit, points into the $0100-$01FF range
    • P (Status) -- 8-bit flags: N(egative), V(overflow), B(reak), D(ecimal), I(nterrupt), Z(ero), C(arry)
    • PC (Program Counter) -- 16-bit
  • No multiply, divide, or floating point instructions
  • Instruction set: 56 official opcodes with multiple addressing modes (implied, immediate, zero page, zero page X/Y, absolute, absolute X/Y, indirect, indexed indirect, indirect indexed)

Why This Matters for NEScript

The 6502's 3-register architecture drives the compiler's register allocator. The lack of multiply/divide means the compiler emits software routines for *, /, and %. Zero-page addressing is 1 byte shorter and 1 cycle faster than absolute addressing, which is why fast variable placement matters.


Memory Map

Address Range Size Description
$0000-$00FF 256 bytes Zero page -- fast access
$0100-$01FF 256 bytes Stack (grows downward)
$0200-$02FF 256 bytes OAM shadow buffer (convention)
$0300-$07FF 1280 bytes General purpose RAM
$0800-$1FFF -- Mirrors of $0000-$07FF
$2000-$2007 8 bytes PPU registers
$2008-$3FFF -- Mirrors of PPU registers
$4000-$4017 24 bytes APU and I/O registers
$4018-$401F 8 bytes CPU test mode (normally disabled)
$4020-$5FFF -- Expansion ROM (mapper-dependent)
$6000-$7FFF 8 KB SRAM / PRG RAM (if present)
$8000-$BFFF 16 KB PRG ROM lower bank
$C000-$FFFF 16 KB PRG ROM upper bank (or fixed bank)

Interrupt Vectors

Address Vector Description
$FFFA NMI Non-Maskable Interrupt (vertical blank)
$FFFC RESET Power-on / reset entry point
$FFFE IRQ Interrupt Request (mapper-dependent)

NEScript Memory Usage

The compiler reserves the following:

  • $00-$0F: System use (frame counter, input, OAM cursor, SFX/music pointers, mul/div scratch)
  • $10: ZP_BANK_CURRENT (current switchable PRG bank index, only in banked programs)
  • $11-$17: PPU update slots (palette/nametable flags and pending pointers, only when the program declares palette or background blocks)
  • $18-$7F: Available for fast variables (user zero-page)
  • $80-$FF: IR codegen temp slots (scratch for expression evaluation)
  • $0200-$02FF: OAM shadow buffer (DMA'd to PPU each frame)
  • $0300-$07EE: General variables, per-function RAM (parameter spill + locals), state-local storage
  • $07EF: SPRITE_CYCLE_ADDR — rotating offset byte used by cycle_sprites (only when the program emits a cycle_sprites statement)
  • $07F0-$07F7: Audio channel state (noise/triangle/sfx-pitch pointers; only when the program declares a matching sfx)
  • $07FC: DEBUG_SPRITE_OVERFLOW_FLAG_ADDR — per-frame sticky bit (debug builds only)
  • $07FD: DEBUG_SPRITE_OVERFLOW_COUNT_ADDR — cumulative PPU sprite overflow counter (debug builds only)
  • $07FE: DEBUG_FRAME_OVERRUN_FLAG_ADDR — per-frame sticky bit (debug builds only)
  • $07FF: DEBUG_FRAME_OVERRUN_ADDR — cumulative frame overrun counter (debug builds only)

Release-mode programs that don't opt into audio, banking, debug, or sprite cycling leave the corresponding slots untouched and the analyzer is free to allocate user globals over them.


PPU (Picture Processing Unit)

  • Resolution: 256 x 240 pixels
  • Refresh rate: 60 Hz (NTSC), 50 Hz (PAL)
  • VRAM: 2 KB internal (2 nametables), expandable by mapper
  • Pattern tables: 2 tables of 256 tiles each (one for backgrounds, one for sprites), stored in CHR ROM/RAM
  • Tile size: 8x8 pixels (or 8x16 for tall sprites)
  • Color depth: 2 bits per pixel (4 colors per tile, selected from a sub-palette)

Nametables

  • 4 logical nametables, each 960 bytes of tile indices + 64 bytes of attribute data
  • With 2 KB VRAM, 2 physical nametables exist; the other 2 are mirrors
  • Mirroring arrangement (horizontal or vertical) is set by the cartridge/mapper

Sprites (OAM)

  • 64 sprites total, each defined by 4 bytes:
    • Byte 0: Y position
    • Byte 1: Tile index
    • Byte 2: Attributes (palette, priority, flip H/V)
    • Byte 3: X position
  • 8 sprites per scanline maximum (excess sprites are dropped)
  • OAM is 256 bytes, typically shadow-buffered at CPU $0200 and DMA'd via $4014

Palettes

  • Background: 4 sub-palettes, each with 4 colors (first color shared across all)
  • Sprite: 4 sub-palettes, each with 4 colors (first color is transparent)
  • Colors are indices into the NES master palette (64 entries, $00-$3F)
  • Palette RAM is 32 bytes total at PPU $3F00-$3F1F

PPU Registers

Address Name Description
$2000 PPUCTRL NMI enable, sprite size, pattern table select, nametable select
$2001 PPUMASK Color emphasis, sprite/background enable, clipping
$2002 PPUSTATUS Vblank flag, sprite 0 hit, sprite overflow
$2003 OAMADDR OAM address for writes
$2004 OAMDATA OAM data read/write
$2005 PPUSCROLL Scroll position (write twice: X, Y)
$2006 PPUADDR VRAM address (write twice: high, low)
$2007 PPUDATA VRAM data read/write

Rendering Timing

  • Vblank starts at scanline 241 and lasts ~20 scanlines (~2,273 CPU cycles)
  • PPU updates (palette, nametable, scroll) must happen during vblank
  • on frame code runs during the visible frame; the implicit wait_frame() yields until the next vblank
  • Approximately 29,780 CPU cycles per frame (NTSC)

APU (Audio Processing Unit)

Channel Type Description
Pulse 1 Square wave Variable duty cycle (12.5/25/50/75%)
Pulse 2 Square wave Same as Pulse 1
Triangle Triangle Fixed volume, good for bass
Noise Noise Pseudo-random, for percussion
DMC Sample 1-bit delta-modulated samples

APU registers span $4000-$4017. The audio driver (included automatically when sfx or music declarations exist) runs during NMI.


iNES ROM Format

The standard ROM file format for NES emulators:

Offset  Size     Description
------  -------  -----------
0       4 bytes  Magic number: "NES" followed by $1A
4       1 byte   PRG ROM size in 16 KB units
5       1 byte   CHR ROM size in 8 KB units
6       1 byte   Flags 6: mapper (low nibble), mirroring, battery, trainer
7       1 byte   Flags 7: mapper (high nibble), VS/Playchoice, NES 2.0
8-15    8 bytes  Padding (zeros)
16+     varies   PRG ROM data (N x 16384 bytes)
after   varies   CHR ROM data (N x 8192 bytes)

Common Mapper Numbers

Number Name PRG ROM CHR Notes
0 NROM 16/32 KB 8 KB ROM No bank switching
1 MMC1 Up to 256 KB Up to 128 KB Switchable 16 KB PRG banks
2 UxROM Up to 256 KB 8 KB RAM Switchable 16 KB PRG banks
4 MMC3 Up to 512 KB Up to 256 KB Scanline counter, 8 KB banks

Mirroring

  • Horizontal: nametables A-A-B-B (vertical scrolling games)
  • Vertical: nametables A-B-A-B (horizontal scrolling games)
  • Set in byte 6, bit 0 of the iNES header (0 = horizontal, 1 = vertical)

Controller

Each controller has 8 buttons read as a serial shift register via $4016 (port 1) and $4017 (port 2).

Read sequence:

  1. Write $01 then $00 to $4016 to strobe (latch button states)
  2. Read $4016 (or $4017) 8 times; bit 0 of each read is one button

Button order: A, B, Select, Start, Up, Down, Left, Right.

The NEScript runtime handles this automatically. The programmer reads button state via button.a, button.up, etc.


Key Constraints for NEScript

Constraint Limit NEScript Response
Total RAM 2 KB Static allocation, no heap
Zero page 256 bytes fast/slow hints, compiler promotion
Stack 256 bytes Call depth limit, no recursion
Sprites per frame 64 Compiler manages OAM buffer
Sprites per scanline 8 Hardware limit, no workaround
Vblank time ~2,273 cycles PPU updates must be fast
Frame budget ~29,780 cycles Frame overrun detection in debug mode
No multiply/divide HW -- Software routines, power-of-2 optimization