mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 17:06:04 +00:00
compiler: PRNG / edge input / palette fade / AxROM / CNROM / FCEUX labels
Closes seven of the cc65/nesdoug parity gaps catalogued in docs/future-work.md in a single pass. All of the new features are gated on marker labels so programs that don't use them produce byte-identical ROM output (every pre-existing committed .nes file round-trips unchanged). Language / runtime additions: - `rand8()` / `rand16()` / `seed_rand(u16)` intrinsics backed by a 16-bit Galois LFSR (~30 bytes of runtime, ~40 cycles per draw). Reset path seeds state to 0xACE1 so the first draw is useful even without explicit seeding. - `p1.button.a.pressed` / `.released` edge-triggered input via a new ReadInputEdge IR op plus an NMI-side prev-frame snapshot into $07E6/$07E7, gated on the `__edge_input_used` marker. - `set_palette_brightness(level)` builtin mapping levels 0..8 to PPU mask emphasis bytes (`$2001`) for neslib-style screen fades. - `mapper: AxROM` (iNES 7) with automatic 32 KB PRG padding so emulators that enforce mapper-7's 32 KB page size boot cleanly. - `mapper: CNROM` (iNES 3) with a reset-time CHR bank 0 select. - `--fceux-labels <prefix>` CLI flag emitting per-bank `.nl` label files and a `.ram.nl` file for FCEUX's debugger. Tests + examples: - Five new example programs with committed .nes ROMs and pixel+audio goldens: prng_demo, edge_input_demo, palette_brightness_demo, axrom_simple, cnrom_simple. - Seven integration tests covering JSR emission, the omitted-when-unused invariant, the NMI prev-input snapshot, the correct mapper numbers for AxROM/CNROM, and negative tests for unknown button names and bad rand8 arity. - `is_intrinsic()` now runs in expression-position Call paths too, so `var x = rand8(1, 2)` errors at compile time instead of silently dropping the extra arguments.
This commit is contained in:
parent
c96135fd86
commit
7507459787
35 changed files with 1272 additions and 22 deletions
|
|
@ -41,6 +41,11 @@ Open any `.nes` file in an NES emulator ([Mesen](https://www.mesen.ca/), [FCEUX]
|
|||
| `pong.ne` | **production-quality Pong**, powerups, multi-ball, multi-file | A complete Pong game split across `examples/pong/*.ne`. CPU VS CPU / 1 PLAYER / 2 PLAYERS title menu with brisk pulse-2 title march and autopilot, smooth ball physics with wall and paddle bouncing, CPU AI that tracks the ball with a reaction lag and dead zone, three powerup types (LONG paddle for 5 hits, FAST ball on next hit, MULTI-ball on next hit spawning 3 balls) that bounce around the field and are caught by paddle AABB overlap, multi-ball scoring (each ball scores a point, round continues until last ball exits), inner phase machine (`P_SERVE`/`P_PLAY`/`P_POINT`), and a "PLAYER N WINS" victory screen with the builtin fanfare. First-to-7 wins. |
|
||||
| `feature_canary.ne` | **regression canary**, state-locals, uninitialized struct-field writes, u16, arrays, `slow` placement, function returns | A minimal program whose sole job is to paint a green universal backdrop at frame 180 when every memory-affecting language construct round-trips a write through the compiler correctly, and to flip to red if any check fails. Each check writes a distinctive byte through one construct (state-local, uninit struct field, u8/u16 global, array element, `slow`-placed u8, function call return), reads it back, and clears `all_ok` on mismatch. Because the emulator harness compares pixels at frame 180, any compiler regression that silently drops one of these writes turns the committed golden red — the structural counter to the "goldens capture whatever happens, not what should happen" failure mode that let PR #31 survive for a year. |
|
||||
| `sha256.ne` | **interactive SHA-256**, inline-asm 32-bit primitives, multi-file | A full FIPS 180-4 SHA-256 hasher split across `examples/sha256/*.ne`. An on-screen 5×8 keyboard grid lets the player type up to 16 ASCII characters (`A`..`Z`, `0`..`9`, space, `.`, backspace, enter), and pressing ↵ runs the 48-entry message-schedule expansion + 64-round compression on the NES itself. Every 32-bit primitive (`copy`, `xor`, `and`, `add`, `not`, rotate-right, shift-right) is hand-tuned inline assembly that walks the four little-endian bytes of a word with `LDA {wk},X` / `ADC {wk},Y` chains, so a whole round costs a few thousand cycles. The phased driver runs four schedule steps or four rounds per frame so the full compression finishes well under a second, and the 64-character hex digest renders as sprites in 8 rows of 8 glyphs at the bottom of the screen. The jsnes golden auto-types `"NES"` after 1 s of keyboard idle and captures its hash `AE9145DB5CABC41FE34B54E34AF8881F462362EA20FD8F861B26532FFBB84E0D`. |
|
||||
| `prng_demo.ne` | `rand8()`, `rand16()`, `seed_rand()` | Exercises the runtime xorshift PRNG end-to-end. Four sprite positions are drawn from fresh `rand8()` draws every frame with a `rand16()` sample mixed in. `seed_rand(0x1234)` pins the initial state so the golden is deterministic. The `__rand_used` marker gates linking of `gen_prng` + the reset-time seed — programs that never call any of the three get zero ROM / cycle overhead. |
|
||||
| `edge_input_demo.ne` | `p1.button.a.pressed`, `p1.button.b.released` | Demonstrates edge-triggered input. The A-sprite advances exactly once per press transition (holding the button does nothing) and the B-sprite advances on release. Lowering emits `IrOp::ReadInputEdge`, which stores the previous-frame input byte into main RAM and XORs it against the current byte at the read site. The NMI handler snapshots both prev bytes before strobing, gated on the `__edge_input_used` marker. |
|
||||
| `palette_brightness_demo.ne` | `set_palette_brightness(level)` | Cycles through the 9 brightness levels (0 = blank, 4 = normal, 8 = max emphasis) every 20 frames. Exercises the neslib-style `pal_bright` mapping onto `$2001` PPU mask emphasis bits. The runtime routine `__set_palette_brightness` is spliced in only when user code references the builtin. |
|
||||
| `axrom_simple.ne` | `mapper: AxROM` (mapper 7) | Single-screen AxROM demo. The linker pads PRG to 32 KB (one blank 16 KB bank plus our 16 KB fixed bank) so emulators that enforce mapper-7's 32 KB page size boot cleanly. Register layout: bit 4 of `$8000` selects single-screen lower / upper nametable. |
|
||||
| `cnrom_simple.ne` | `mapper: CNROM` (mapper 3) | CNROM demo. Fixed 32 KB PRG, switchable 8 KB CHR. Single-bank CNROM is functionally equivalent to NROM at the PRG level, but the iNES header reports mapper 3 and the runtime writes a CHR bank 0 select at reset. |
|
||||
|
||||
## Emulator Controls
|
||||
|
||||
|
|
|
|||
25
examples/axrom_simple.ne
Normal file
25
examples/axrom_simple.ne
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// AxROM (mapper 7) demo — single 32 KB PRG with single-screen
|
||||
// mirroring. Most homebrew AxROM games have multiple 32 KB pages;
|
||||
// this minimal demo runs in bank 0 of a 32 KB-padded ROM to
|
||||
// exercise the mapper's reset, bank-select register, and iNES
|
||||
// header emission.
|
||||
|
||||
game "AxROM Demo" {
|
||||
mapper: AxROM
|
||||
}
|
||||
|
||||
var px: u8 = 120
|
||||
var dx: u8 = 1
|
||||
|
||||
on frame {
|
||||
if dx == 1 {
|
||||
px += 1
|
||||
if px >= 240 { dx = 0 }
|
||||
} else {
|
||||
px -= 1
|
||||
if px == 0 { dx = 1 }
|
||||
}
|
||||
draw Ball at: (px, 100)
|
||||
}
|
||||
|
||||
start Main
|
||||
BIN
examples/axrom_simple.nes
Normal file
BIN
examples/axrom_simple.nes
Normal file
Binary file not shown.
24
examples/cnrom_simple.ne
Normal file
24
examples/cnrom_simple.ne
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// CNROM (mapper 3) demo — fixed 32 KB PRG, switchable 8 KB CHR.
|
||||
// Single-bank CNROM exercises the mapper reset, header emission,
|
||||
// and compatible runtime — functionally equivalent to NROM but
|
||||
// with a different iNES mapper number.
|
||||
|
||||
game "CNROM Demo" {
|
||||
mapper: CNROM
|
||||
}
|
||||
|
||||
var px: u8 = 120
|
||||
var dx: u8 = 1
|
||||
|
||||
on frame {
|
||||
if dx == 1 {
|
||||
px += 1
|
||||
if px >= 240 { dx = 0 }
|
||||
} else {
|
||||
px -= 1
|
||||
if px == 0 { dx = 1 }
|
||||
}
|
||||
draw Ball at: (px, 100)
|
||||
}
|
||||
|
||||
start Main
|
||||
BIN
examples/cnrom_simple.nes
Normal file
BIN
examples/cnrom_simple.nes
Normal file
Binary file not shown.
42
examples/edge_input_demo.ne
Normal file
42
examples/edge_input_demo.ne
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// Edge-triggered input demo — the A / B buttons toggle a pair
|
||||
// of sprites using `.pressed` / `.released` rather than the
|
||||
// level-state `p1.button.a`. Held buttons only fire once per
|
||||
// press, which is the canonical menu / one-shot action pattern.
|
||||
|
||||
game "Edge Input Demo" {
|
||||
mapper: NROM
|
||||
}
|
||||
|
||||
var ax: u8 = 64
|
||||
var bx: u8 = 120
|
||||
var toggle_a: u8 = 0
|
||||
var toggle_b: u8 = 0
|
||||
|
||||
on frame {
|
||||
// Each press moves the A-sprite 8 pixels right. Because
|
||||
// `.pressed` fires once per press transition, holding the
|
||||
// button down does not accelerate movement.
|
||||
if p1.button.a.pressed {
|
||||
ax += 8
|
||||
toggle_a = toggle_a ^ 1
|
||||
}
|
||||
|
||||
// Release moves the B-sprite 4 pixels right on letting go.
|
||||
if p1.button.b.released {
|
||||
bx += 4
|
||||
toggle_b = toggle_b ^ 1
|
||||
}
|
||||
|
||||
// Drive the frame colours off the toggles so the output is
|
||||
// observable in the emulator golden.
|
||||
draw Ball at: (ax, 80)
|
||||
draw Ball at: (bx, 120)
|
||||
|
||||
// A tiny visual beacon for "toggle_a" / "toggle_b" so the
|
||||
// golden captures the state even before the user presses
|
||||
// anything.
|
||||
if toggle_a == 1 { draw Ball at: (ax, 90) }
|
||||
if toggle_b == 1 { draw Ball at: (bx, 130) }
|
||||
}
|
||||
|
||||
start Main
|
||||
BIN
examples/edge_input_demo.nes
Normal file
BIN
examples/edge_input_demo.nes
Normal file
Binary file not shown.
28
examples/palette_brightness_demo.ne
Normal file
28
examples/palette_brightness_demo.ne
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// Palette brightness demo — cycle through the 9 brightness levels
|
||||
// every ~20 frames. Exercises the `set_palette_brightness` builtin,
|
||||
// which writes PPU mask emphasis bits for cheap neslib-style fades.
|
||||
|
||||
game "Palette Brightness Demo" {
|
||||
mapper: NROM
|
||||
}
|
||||
|
||||
var frame: u8 = 0
|
||||
var level: u8 = 4 // normal
|
||||
|
||||
on frame {
|
||||
frame += 1
|
||||
// Every 20 frames, bump the brightness level. Roll back to 0
|
||||
// at 9 so we cycle through the full 0..8 range.
|
||||
if frame >= 20 {
|
||||
frame = 0
|
||||
level += 1
|
||||
if level >= 9 {
|
||||
level = 0
|
||||
}
|
||||
set_palette_brightness(level)
|
||||
}
|
||||
|
||||
draw Ball at: (120, 100)
|
||||
}
|
||||
|
||||
start Main
|
||||
BIN
examples/palette_brightness_demo.nes
Normal file
BIN
examples/palette_brightness_demo.nes
Normal file
Binary file not shown.
45
examples/prng_demo.ne
Normal file
45
examples/prng_demo.ne
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// PRNG demo — sprite positions driven by the runtime PRNG.
|
||||
//
|
||||
// Each frame draws four sprites at addresses pulled from the
|
||||
// xorshift-style `rand8()` intrinsic, with an extra `rand16()`
|
||||
// sample binned into a horizontal velocity. Exercises `rand8`,
|
||||
// `rand16`, and `seed_rand` end-to-end.
|
||||
|
||||
game "PRNG Demo" {
|
||||
mapper: NROM
|
||||
}
|
||||
|
||||
var seeded: u8 = 0
|
||||
|
||||
on frame {
|
||||
if seeded == 0 {
|
||||
// Pin the seed so the recording is deterministic. The
|
||||
// runtime forces bit 0 of the low byte high so a zero
|
||||
// seed doesn't stick the LFSR.
|
||||
seed_rand(0x1234)
|
||||
seeded = 1
|
||||
}
|
||||
|
||||
// Four random sprites per frame. Each call pulls fresh
|
||||
// entropy from the shared PRNG state, so the positions
|
||||
// drift over time instead of staying fixed.
|
||||
var x1: u8 = rand8()
|
||||
var y1: u8 = rand8()
|
||||
draw Ball at: (x1, y1)
|
||||
|
||||
var x2: u8 = rand8()
|
||||
var y2: u8 = rand8()
|
||||
draw Ball at: (x2, y2)
|
||||
|
||||
// rand16() returns u16; truncate to u8 for the draw.
|
||||
var r: u16 = rand16()
|
||||
var x3: u8 = r as u8
|
||||
var y3: u8 = (r >> 8) as u8
|
||||
draw Ball at: (x3, y3)
|
||||
|
||||
var x4: u8 = rand8()
|
||||
var y4: u8 = rand8()
|
||||
draw Ball at: (x4, y4)
|
||||
}
|
||||
|
||||
start Main
|
||||
BIN
examples/prng_demo.nes
Normal file
BIN
examples/prng_demo.nes
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue