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.
2026-04-18 18:13:18 +00:00
|
|
|
// PRNG demo — sprite positions driven by the runtime PRNG.
|
|
|
|
|
//
|
review: tighten PRNG / void-intrinsic / FCEUX path handling
Follow-up cleanup on the cc65 parity batch. Addresses issues found
during a post-commit code review.
**Correctness fixes:**
- `rand8()` / `rand16()` at statement position (result discarded)
were being eliminated by DCE because `op_dest` returned
`Some(dest)` for Rand8/Rand16 even though the ops have a visible
side effect — advancing the PRNG state. Now `op_dest` returns
`None` for both, keeping the JSR regardless of liveness. New
regression test `rand8_statement_survives_dce`.
- Void-only intrinsics (`poke`, `seed_rand`, `set_palette_brightness`)
used in expression position (e.g. `var x = seed_rand(42)`) were
panicking the linker with an unresolved `__ir_fn_X` label. The
analyzer now emits E0203 with a clear message; new
`void_intrinsic_in_expression_position_errors` test covers all
three names.
- Statement-position `rand8()` / `rand16()` weren't lowered at all
(they fell through to the default Call path). Now both lower to
their IR op with a fresh temp that nothing reads; the JSR still
runs so the PRNG state advances.
- `--fceux-labels foo.nes` was producing `foo.0.nl` because
`PathBuf::with_extension` replaces instead of appends. Rewritten
to literally append `.<bank>.nl` / `.ram.nl` to the OsString, so
users get the FCEUX-expected `foo.nes.<bank>.nl` naming.
- Linker now asserts CNROM / AxROM don't accept user-declared
switchable PRG banks — their page sizes don't fit the 16 KB per
bank model, and silently producing a mis-sized ROM is worse than
a loud panic.
**PRNG cleanup:**
- Removed the stream-of-consciousness comment block in `gen_prng`
that described three abandoned algorithms before landing on the
actual Galois LFSR.
- Simplified `__rand16` to a single JSR + LDX instead of two
JSRs + TAY/TYA round-trip — a single shift already produces 16
fresh bits, the doubled call just burned ~40 cycles. The golden
PNG for `prng_demo` was regenerated to reflect the new sequence.
- Rewrote the `gen_prng` doc comment to accurately describe the
algorithm as a Galois LFSR (it was mislabelled as xorshift).
- Rewrote the `gen_palette_brightness` doc comment with a proper
table of level→mask mappings — the prior prose description
didn't match the actual table values.
**Tests:**
- Three new unit tests in `linker::debug_symbols` covering the
FCEUX `.nl` renderer: user-facing labels only, empty output when
no user labels exist, and deterministic sorting in `.ram.nl`.
- Extended `nes2_mapper_high_nibble_in_byte_8_is_zero_for_small_mappers`
to cover AxROM + CNROM.
- Renumbered priority list in future-work.md after removing the
shipped sections (J, K, N, parts of V and Y).
All 737 tests + 40/40 emulator goldens still green.
2026-04-18 18:48:55 +00:00
|
|
|
// Each frame draws four sprites at pseudorandom coordinates pulled
|
|
|
|
|
// from the Galois-LFSR `rand8()` intrinsic, plus one sprite whose
|
|
|
|
|
// (x, y) pair comes from a single `rand16()` draw. Exercises all
|
|
|
|
|
// three PRNG intrinsics (`rand8`, `rand16`, `seed_rand`) end-to-end.
|
|
|
|
|
// `seed_rand(0x1234)` on the first frame pins the sequence so the
|
|
|
|
|
// committed golden is deterministic.
|
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.
2026-04-18 18:13:18 +00:00
|
|
|
|
|
|
|
|
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
|