Phase 5: raster IRQ + frame timing
Replace busy-wait frame counter with a 50 Hz raster IRQ at line 311 (PAL stable line). The IRQ handler increments a 16-bit frame_count and calls game_step() once per frame. Per-state timing now uses enter_frame timestamps + frame_count comparisons. - New tick.h/tick.c: install one RIRQ via Oscar64's rirq library, call a __interrupt handler that bumps frame_count and runs the state machine. Mask CIA 1 + CIA 2 IRQs and set RST8 (the high bit of the 9-bit raster register) so the IRQ fires at line 311 not line 55. - game.h: expose volatile frame_count, replace per-state 'frame' counter with enter_frame timestamps. - game.c: use frame_count - enter_frame everywhere; sample SID $D41B at READY enter for a random 100..250 frame WAIT duration; trigger a low-square-wave stinger on SID voice 1 when DRAW faults out (no fire for 500 frames) and gate it off ~0.2 sec later via a counter decremented every frame. - main.c: replace the busy-wait loop with rasterirq_setup() and an empty for(;;); idle. (Filename is tick.c/.h not rasterirq.c/.h because the oscar64 library's own rasterirq.c does '#include "rasterirq.h"' to pull in its own header, and that include would otherwise pick up ours and lose NUM_IRQS.)
This commit is contained in:
+125
@@ -0,0 +1,125 @@
|
||||
// tick.c — install a single raster IRQ at line 311 (PAL stable
|
||||
// line) that calls the per-frame game tick handler.
|
||||
//
|
||||
// The Oscar64 rasterirq library provides the boilerplate: ISR stub,
|
||||
// table management, sort, and start. We add the two C64-specific
|
||||
// bits the library doesn't do for us when running with KERNAL banked
|
||||
// out:
|
||||
//
|
||||
// 1. Mask CIA 1 and CIA 2 IRQs (write 0x7F to $DC0D/$DD0D). This
|
||||
// stops the jiffy-clock Timer A from latching an IRQ that would
|
||||
// fire the moment we RTI out of the raster IRQ. (The rirq
|
||||
// library only does this for the *_kernal() init paths, which
|
||||
// call into the KERNAL ISR at $EA31 to acknowledge; we can't
|
||||
// because KERNAL is banked out.)
|
||||
//
|
||||
// 2. Use rirq_init(false), which installs the rirq_isr_ram_io
|
||||
// handler at the hardware IRQ vector ($FFFE/$FFFF). This
|
||||
// handler does NOT call into KERNAL — it acks the raster IRQ
|
||||
// (asl $d019) and returns directly via rti. Combined with the
|
||||
// CIA mask, this means the only IRQ we ever service is the
|
||||
// raster IRQ.
|
||||
//
|
||||
// The RIRQ code is a single "wait for line, then JSR frame_tick_handler,
|
||||
// RTS" stub. We use rirq_call() to install the JSR.
|
||||
//
|
||||
// **PAL line 311 and the 9-bit raster counter.** PAL frames are 312
|
||||
// lines (0..311), so the stable line 311 is outside the 8-bit
|
||||
// $D012 range. The VIC's raster register is 9 bits: the high bit
|
||||
// is bit 7 of $D011 (VIC_CTRL1_RST8), and the low 8 bits are $D012.
|
||||
// The oscar64 rirq library's `rirq_set(n, row, code)` takes a `byte
|
||||
// row` (0..255) and writes (row - 1) to $D012 internally, so it
|
||||
// cannot directly address line 311. The workaround is:
|
||||
//
|
||||
// - Pass row = 56 to rirq_set (i.e. 311 - 256 + 1, where the +1
|
||||
// accounts for the library's "one line below" convention).
|
||||
// - Set VIC_CTRL1_RST8 = 1 after rirq_sort and BEFORE enabling
|
||||
// CPU IRQ, so the raster comparison becomes (1 << 8) | 55 = 311.
|
||||
//
|
||||
// We can't use rirq_start() because it clears RST8 and overwrites
|
||||
// $D012 with 100 (a "kick start" line that lets the first IRQ fire
|
||||
// quickly). Instead we do the equivalent of rirq_start inline, with
|
||||
// RST8 left at 1 and $D012 left at the rirq_sort value.
|
||||
//
|
||||
// **File naming.** This file is named tick.c (not rasterirq.c)
|
||||
// because the oscar64 library's rasterirq.c does `#include
|
||||
// "rasterirq.h"` to find its own rasterirq.h, and that include
|
||||
// resolves relative to the compile CWD. If our header were also
|
||||
// named rasterirq.h, the library would pick up ours and the
|
||||
// NUM_IRQS / RIRQCode defines would be missing. The naming is
|
||||
// purely a workaround for the library's include style.
|
||||
|
||||
#include "tick.h"
|
||||
#include "game.h"
|
||||
#include <c64/vic.h>
|
||||
#include <c64/cia.h>
|
||||
#include <c64/rasterirq.h>
|
||||
|
||||
// The single RIRQ code slot. One IRQ = one wait + one JSR + one RTS.
|
||||
static RIRQCode frame_tick;
|
||||
|
||||
// The per-frame handler, called from the raster IRQ at line 311.
|
||||
//
|
||||
// Marked __interrupt so the compiler saves/restores any zero-page
|
||||
// registers the function (or game_step) uses. This matches the
|
||||
// autocrawler.c pattern in the oscar64 samples. Note that this
|
||||
// function is NOT the 6502 ISR — the rirq_isr_ram_io stub installed
|
||||
// by rirq_init is the actual ISR. This function is called via JSR
|
||||
// from the rirq_isr, and returns with RTS. A/X/Y are saved by the
|
||||
// rirq_isr, so we can clobber them freely.
|
||||
__interrupt void frame_tick_handler(void)
|
||||
{
|
||||
frame_count++;
|
||||
game_step();
|
||||
}
|
||||
|
||||
void rasterirq_setup(void)
|
||||
{
|
||||
// 1. Mask all CIA 1 and CIA 2 interrupt sources. The ICR at
|
||||
// $DC0D/$DD0D is a set/clear register: bit 7 = 0 means
|
||||
// "clear", bits 0-4 = 0x1F means "clear all source mask
|
||||
// bits". Writing 0x7F disables every source. A second
|
||||
// write acknowledges any latched IRQ; reading would do the
|
||||
// same but writing is fine.
|
||||
cia1.icr = 0x7f;
|
||||
cia2.icr = 0x7f;
|
||||
cia1.icr = 0x7f;
|
||||
cia2.icr = 0x7f;
|
||||
|
||||
// 2. Install the raster IRQ system. false = use the hardware
|
||||
// IRQ vector at $FFFE, no KERNAL continuation (KERNAL is
|
||||
// banked out by memmap_setup).
|
||||
rirq_init(false);
|
||||
|
||||
// 3. Build the RIRQ code: a single call to frame_tick_handler.
|
||||
// size=1 = one op slot. rirq_call at index 0 replaces the
|
||||
// STY $xxxx stub at offset 9 with a JSR frame_tick_handler.
|
||||
// The resulting code is: wait + JSR handler + RTS.
|
||||
rirq_build(&frame_tick, 1);
|
||||
rirq_call(&frame_tick, 0, frame_tick_handler);
|
||||
|
||||
// 4. Place this RIRQ at line 56 in the rirq library's 8-bit
|
||||
// view. Combined with RST8=1 (set in step 6 below) the
|
||||
// actual raster comparison becomes 256 + (56 - 1) = 311.
|
||||
rirq_set(0, 56, &frame_tick);
|
||||
|
||||
// 5. Sort the RIRQ list. This also writes $D012 = 56 - 1 = 55
|
||||
// (the low byte of the 9-bit row 311) and sets nextIRQ = 0.
|
||||
rirq_sort();
|
||||
|
||||
// 6. Set the high bit of the 9-bit raster register. This
|
||||
// makes the VIC compare the raster counter against 256 + 55
|
||||
// = 311 instead of just 55. Must happen before we enable
|
||||
// CPU IRQ (CLI), otherwise the first IRQ might fire at the
|
||||
// wrong line.
|
||||
vic.ctrl1 |= VIC_CTRL1_RST8;
|
||||
|
||||
// 7. Start the raster IRQ. We can't use rirq_start() because
|
||||
// it would clear RST8 and overwrite $D012 with 100. Instead
|
||||
// we do the same thing minus those two writes: acknowledge
|
||||
// any pending VIC IRQ, then enable CPU IRQ.
|
||||
__asm {
|
||||
asl $d019
|
||||
cli
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user