mirror of
https://github.com/imjasonh/nescript
synced 2026-07-09 17:28:00 +00:00
debug: add debug.frame_overrun_count() and debug.frame_overran() builtins
The frame-overrun counter at $07FF was previously only readable via `peek(0x07FF)`, which forces every program that wants to guard against missed frames to know the magic address. This adds two named query expressions: - `debug.frame_overrun_count()` — cumulative miss count since reset - `debug.frame_overran()` — sticky bit cleared by the next wait_frame, so `debug.assert(not debug.frame_overran())` catches a miss in the previous window without waiting for the counter to roll over. The sticky bit lives at $07FE alongside the existing counter and is set inside the same NMI-time overrun branch. Release builds emit none of the runtime side: the NMI handler still skips both writes, the codegen `wait_frame` only clears $07FE in debug mode, and committed example ROMs stay byte-identical. The new expression form parses through `parse_primary`'s `KwDebug` arm, so the existing `debug.log(...)` / `debug.assert(...)` *statement* parser stays untouched. The analyzer rejects unknown methods with E0201 and stray arguments with E0203 so typos don't silently compile to a zero load. https://claude.ai/code/session_01KEczoNUX3WmcFLfq6iAQxB
This commit is contained in:
parent
bf7819ca0f
commit
d4e613fb7c
9 changed files with 401 additions and 7 deletions
|
|
@ -89,13 +89,24 @@ pub const ZP_PENDING_BG_ATTRS_HI: u8 = 0x17;
|
|||
/// handler whenever it fires while the previous frame's ready
|
||||
/// flag is still set — which means the main loop didn't consume
|
||||
/// it, so user code spent more than one vblank-to-vblank window
|
||||
/// processing the last frame. Read it with `peek(0x07FF)` in
|
||||
/// user code to see how many overruns have happened since reset,
|
||||
/// or watch the address in a Mesen memory viewer. Placed at the
|
||||
/// top of main RAM to minimise the chance of a collision with
|
||||
/// analyzer-allocated variables (which grow from $0300 upward).
|
||||
/// processing the last frame. Read it with `peek(0x07FF)` or
|
||||
/// `debug.frame_overrun_count()` in user code to see how many
|
||||
/// overruns have happened since reset, or watch the address in
|
||||
/// a Mesen memory viewer. Placed at the top of main RAM to
|
||||
/// minimise the chance of a collision with analyzer-allocated
|
||||
/// variables (which grow from $0300 upward).
|
||||
pub const DEBUG_FRAME_OVERRUN_ADDR: u16 = 0x07FF;
|
||||
|
||||
/// Debug-mode "did the previous frame overrun" sticky bit. Set
|
||||
/// to 1 by the NMI handler at the same time as it bumps
|
||||
/// [`DEBUG_FRAME_OVERRUN_ADDR`], and cleared to 0 by `wait_frame`
|
||||
/// once the main loop catches up. Exposed to user code as
|
||||
/// `debug.frame_overran()` — a per-frame "did this frame finish
|
||||
/// in time" predicate suited for `debug.assert(!debug.frame_overran())`
|
||||
/// guards. Lives one byte below the cumulative counter so the
|
||||
/// two can be inspected together in a Mesen memory viewer.
|
||||
pub const DEBUG_FRAME_OVERRUN_FLAG_ADDR: u16 = 0x07FE;
|
||||
|
||||
// ── Extra channel state ──
|
||||
//
|
||||
// The pulse-1 sfx and pulse-2 music channels live in zero page
|
||||
|
|
@ -344,6 +355,16 @@ pub fn gen_nmi(has_ppu_updates: bool, has_audio: bool, debug_mode: bool) -> Vec<
|
|||
INC,
|
||||
AM::Absolute(DEBUG_FRAME_OVERRUN_ADDR),
|
||||
));
|
||||
// Set the per-frame sticky bit. It stays set until the
|
||||
// next `wait_frame` clears it, so a single
|
||||
// `debug.assert(!debug.frame_overran())` guard at the top
|
||||
// of `on frame { ... }` catches any miss in the previous
|
||||
// window.
|
||||
out.push(Instruction::new(LDA, AM::Immediate(0x01)));
|
||||
out.push(Instruction::new(
|
||||
STA,
|
||||
AM::Absolute(DEBUG_FRAME_OVERRUN_FLAG_ADDR),
|
||||
));
|
||||
out.push(Instruction::new(
|
||||
NOP,
|
||||
AM::Label("__debug_no_overrun".into()),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue