Skip to main content

FixtureRunner

Struct FixtureRunner 

Source
pub struct FixtureRunner { /* private fields */ }
Expand description

Canonical entry point of the systemless library.

FixtureRunner owns the three pieces of guest state — the M68kCpu interpreter, the MacMemoryBus, and the TrapDispatcher (Toolbox

  • OS trap handlers) — and exposes the load / step / halt-inspect surface that drives them.

Lifecycle:

  1. FixtureRunner::new — allocate guest RAM + dispatcher.
  2. crate::game::load_game — auto-detect StuffIt / MacBinary, populate guest memory, seed CPU state.
  3. run_steps (preferred) or run — drive the CPU. run_steps returns (steps_executed, still_running); run runs until halt or FixtureRunnerConfig::max_instructions.
  4. After halt: halted_pc / halted_trap / halted_sp / halted_d0 expose per-halt detail, and halted_by_exit_to_shell classifies the common clean application-exit path.

Defaults: kiosk mode (Mac menu bar suppressed regardless of the guest’s MBarHeight); arrow keys NOT remapped to numpad. Override each via set_menu_bar_visible / set_arrows_as_numpad or the SYSTEMLESS_SHOW_MENU_BAR env var.

See examples/run_headless.rs for a runnable end-to-end example.

Implementations§

Source§

impl FixtureRunner

Source

pub fn new(ram_size: usize, config: FixtureRunnerConfig) -> Self

Construct a fresh runner with ram_size bytes of guest RAM and the given FixtureRunnerConfig. The CPU starts halted at PC = 0; the framebuffer is whatever bytes the host allocator hands us. Call load_app (or the higher-level systemless::game::load_game) to populate guest memory and seed the run state, then drive the guest with run_steps.

ram_size is typically 4 MiB to 16 MiB — most games never push past 8 MiB. The runner allocates a single contiguous host region of this size; bumping it costs only the upfront alloc.

The dispatcher defaults to kiosk mode (Mac menu bar suppressed, regardless of the guest’s MBarHeight). Call set_menu_bar_visible to opt back in to the original Mac menu-bar behaviour.

Source

pub fn is_halted(&self) -> bool

Returns true once guest execution has stopped.

Source

pub fn halted_by_exit_to_shell(&self) -> bool

Returns true when the halt was the documented clean application termination path, _ExitToShell ($A9F4).

This lets runners and tests distinguish apps that intentionally quit from halts caused by faults, invalid PCs, or other fatal errors.

Source

pub fn guest_tick(&self) -> u32

Source

pub fn host_now(&self) -> Instant

Source

pub fn halted_trap(&self) -> Option<u16>

Source

pub fn halted_pc(&self) -> Option<u32>

Source

pub fn halted_sp(&self) -> Option<u32>

Source

pub fn halted_stack_word0(&self) -> Option<u16>

Source

pub fn halted_stack_word(&self, word_index: u32) -> Option<u16>

Source

pub fn halted_d0(&self) -> Option<u32>

Source

pub fn total_instructions(&self) -> u64

Source

pub fn debug_overlay_snapshot( &self, frame_stats: DebugOverlayFrameStats, ) -> DebugOverlaySnapshot

Source

pub fn cpu(&self) -> &M68kCpu

Source

pub fn cpu_mut(&mut self) -> &mut M68kCpu

Source

pub fn bus(&self) -> &MacMemoryBus

Source

pub fn bus_mut(&mut self) -> &mut MacMemoryBus

Source

pub fn dispatcher(&self) -> &TrapDispatcher

Source

pub fn dispatcher_mut(&mut self) -> &mut TrapDispatcher

Source

pub fn ui_theme(&self) -> &'static dyn UiTheme

Returns the selected UI theme provider. classic-system7 is the default and maps to the existing renderer path; non-classic providers are explicit Systemless-owned rendering contracts.

Source

pub fn ui_theme_id(&self) -> UiThemeId

Source

pub fn theme_metrics_mode(&self) -> ThemeMetricsMode

Source

pub fn uses_classic_guest_metrics(&self) -> bool

Source

pub fn set_menu_bar_visible(&mut self, visible: bool)

Show or hide the Mac menu bar.

systemless runs in kiosk mode by default — the Mac menu bar is suppressed regardless of the guest’s MBarHeight ($0BAA) value and DrawMenuBar is a no-op. This matches the typical embedding case (running a single classic Mac game inside a fullscreen host window) where the host owns the chrome and the guest’s menu bar would just diverge from the original-machine appearance whenever the cursor entered y < 20.

Pass true to opt back in to original Mac behavior — for example, when running a Mac application that relies on the menu bar as its primary user surface.

The same toggle is also accessible via the SYSTEMLESS_SHOW_MENU_BAR environment variable (set to any value to show) and via systemless --show-menu-bar. This library method is the preferred entry point for library embedders that don’t want to depend on environment-variable plumbing.

Inside Macintosh Volume I, I-354 (DrawMenuBar); Inside Macintosh Volume V, V-245 (MBarHeight global).

Source

pub fn menu_bar_visible(&self) -> bool

Returns true when the Mac menu bar is currently being rendered. In the default kiosk configuration this returns false.

Source

pub fn disassemble_at(&self, pc: u32, count: usize) -> Vec<(u32, String, u32)>

Disassemble M68K instructions starting at pc for count instruction words. Returns one entry per word: (pc, mnemonic, size_in_bytes). The size includes any operand words consumed by the instruction; advance pc by size to reach the next.

Unknown opcodes (including A-line traps and other reserved patterns) come back as DC.W $XXXX with size 2 — the same convention the underlying m68k::dasm::disassemble uses. Reads past the end of guest RAM yield (addr, "<unmapped>", 2) rather than panicking.

Diagnostic helper for pixel-divergence and trap-misroute investigations: pair with the framebuffer-write tracer (SYSTEMLESS_TRACE_FB_WRITE_RANGE) to see what the guest is actually executing at a suspect PC.

Source

pub fn print_opcode_histogram(&self, top_n: usize)

Dump the top-N M68K opcodes by execution count. No-op when SYSTEMLESS_TRACE_OPCODE_COUNTS wasn’t set at startup. Format: [OPCODE-HIST] 43210123 $3F3C MOVE.W #imm,-(SP) Unknown opcodes fall back to showing just the hex word.

Source

pub fn print_pc_histogram(&self, top_n: usize)

Dump the top-N hottest PCs by sampled hit count. No-op when SYSTEMLESS_TRACE_HOT_PC is unset. Each count represents one PC_SAMPLE_INTERVAL (=1000) M68K instructions; multiply by 1000 for an approximate instruction count attributed to that PC.

Source

pub fn install_application_clut(&mut self, clut: [[u16; 3]; 256])

Source

pub fn set_app_start_time(&mut self, secs: u32)

Source

pub fn set_application_partition_size(&mut self, bytes: Option<u32>)

Source

pub fn app_start_time(&self) -> Option<u32>

Getter for the pinned Mac epoch seconds. Returns None when no pin has been applied — without a pin, init_app falls back to current_mac_epoch_seconds() (host wall-clock), which leaks into the guest’s Time global ($020C) and breaks reproducibility.

Source

pub fn application_partition_size(&self) -> Option<u32>

Source

pub fn enable_oracle_recording( &mut self, output_dir: impl AsRef<Path>, source: OracleSource, ) -> Result<()>

Source

pub fn composite_frame(&mut self)

Composite chrome/dialog overlays onto the framebuffer. Call before reading raw pixels for screenshots.

Source

pub fn set_arrows_as_numpad(&mut self, enabled: bool)

Enable or disable arrow-key-to-numpad remapping.

Source

pub fn arrows_as_numpad(&self) -> bool

Returns true when arrow keys are remapped to numpad key codes.

Source

pub fn set_mouse_position(&mut self, v: i16, h: i16)

Move the mouse without changing the button state. Updates the dispatcher’s tracked position and the six mouse-position low-memory globals (MTemp / RawMouse / Mouse) so guest code that reads them directly sees the new coordinates immediately. Leaves MBState ($0172) untouched. Inside Macintosh Volume II, II-371.

Source

pub fn push_mouse_down(&mut self, v: i16, h: i16)

Inject a mouse-down event and sync low-memory globals.

On real hardware the VBL interrupt handler updates MBState ($0172) and the mouse-position globals whenever the button state changes. Since our HLE has no interrupt-driven mouse driver, we sync these globals here so that code polling the low-memory locations directly (instead of calling Button or GetNextEvent) sees the correct state.

Source

pub fn push_mouse_up(&mut self, v: i16, h: i16)

Inject a mouse-up event.

Sync MBState ($0172) immediately so code that polls the low-memory byte directly (rather than calling Button or GetNextEvent) sees the release without waiting for the next tick advance.

On real hardware the ADB manager polls the mouse at ~200 Hz, updating MBState within a few milliseconds of the physical release. Deferring the update to the next advance_guest_tick left MBState stale for an entire tick (~16 ms), which is longer than real hardware and caused frame-rate-dependent games to read the wrong button state for too many loop iterations after a click-up. Inside Macintosh Volume II, II-371

Source

pub fn push_key_down(&mut self, mac_key: u8, char_code: u8)

Inject a key-down event, applying arrow→numpad remapping if configured.

Source

pub fn push_key_up(&mut self, mac_key: u8, char_code: u8)

Inject a key-up event, applying arrow→numpad remapping if configured.

Source

pub fn set_audio(&mut self, audio: Box<dyn AudioBackend>)

Set the audio output backend. If not set, no audio is produced.

Source

pub fn set_instructions_per_tick(&mut self, instructions_per_tick: u32)

Source

pub fn instructions_per_tick(&self) -> u32

Source

pub fn set_wait_sleep_cap_in_headless(&mut self, cap: Option<u32>)

Cap the per-WaitNextEvent-call sleep tick advance in headless mode. None (default) preserves the legacy drain-all behavior. Some(n) caps each WNE sleep to at most n tick advances, mirroring GUI mode.

Source

pub fn wait_sleep_cap_in_headless(&self) -> Option<u32>

Source

pub fn drain_audio(&mut self) -> Vec<u8>

Drain accumulated audio samples for external consumers (e.g. WASM). Returns unsigned 8-bit mono PCM at 22050 Hz (silence = 0x80).

Source

pub fn drain_audio_into(&mut self, out: &mut Vec<u8>)

Drain accumulated audio samples into a caller-owned buffer.

This avoids transferring the runner’s Vec allocation out on every browser frame, so the next audio mix can reuse its existing capacity.

Source

pub fn audio_buffer_len(&self) -> usize

Current number of buffered audio samples (for diagnostics).

Source

pub fn has_pending_sound_work(&self) -> bool

Source

pub fn is_ui_tracking_active(&self) -> bool

Source

pub fn force_advance_guest_tick(&mut self)

Advance the guest tick counter by one, firing VBL and timer tasks. Used by the GUI runner to force-advance ticks when the CPU can’t keep up with wall-clock time (e.g. during expensive PICT draws).

Source

pub fn set_output_path(&mut self, path: PathBuf)

Source

pub fn vfs_read(&self, filename: &str) -> Option<&[u8]>

Get the contents of a file from the virtual filesystem.

Source

pub fn vfs_file_summaries(&mut self) -> Vec<VfsFileSummary>

Source

pub fn vfs_file_summaries_where<F>(&mut self, include: F) -> Vec<VfsFileSummary>
where F: FnMut(&str) -> bool,

Source

pub fn vfs_file_stats_where<F>(&mut self, include: F) -> Vec<VfsFileStat>
where F: FnMut(&str) -> bool,

Source

pub fn vfs_file_summary(&mut self, path: &str) -> Option<VfsFileSummary>

Source

pub fn vfs_file_snapshot(&mut self, path: &str) -> Option<VfsFileSnapshot>

Source

pub fn import_vfs_file(&mut self, file: &VfsFileSnapshot)

Source

pub fn import_vfs_file_relative_to_launched_app( &mut self, relative_dir: &str, file: &VfsFileSnapshot, ) -> Result<(), String>

Source

pub fn remove_vfs_file(&mut self, path: &str) -> bool

Source

pub fn step(&mut self) -> StepResult

Execute exactly one 68k instruction. Returns the per-step result (continue / halted / unimplemented opcode). Most embedders should call run_steps instead — it amortises the per-step bookkeeping (tick advancement, halt detection, trace ring filling) across the whole budget.

Source

pub fn load_app(&mut self, fork: &ResourceFork) -> Option<LoadedApp>

Load a parsed Mac resource fork into guest memory: registers every resource with the Resource Manager, links the application CODE segments through the trap dispatcher’s segment table, and returns a LoadedApp describing the entry-point base address and per-segment offsets.

Lower-level than systemless::game::load_game — that helper auto-detects StuffIt / MacBinary / raw-resource-fork containers and calls this method internally. Use load_app directly only when you’ve already parsed the resource fork yourself (e.g. building a custom test fixture).

Source

pub fn init_app(&mut self, app: &LoadedApp)

Seed the Mac-canonical low-memory globals (MemTop, CurStackBase, ApplLimit, Lo3Bytes, Ticks, etc.) and the A5 World start so run_steps lands the guest in a runnable state. Must be called after load_app and before the first call to run_steps; the higher-level systemless::game::load_game helper invokes it automatically.

Without init_app, A5-relative startup code (CodeWarrior / Think C runtimes, e.g. Koji / Munchies) sees CurStackBase = 0 and spins forever in the globals-decompression loop.

Source

pub fn mix_audio(&mut self, num_samples: usize)

Mix and queue audio samples without full frame finalization. Used to keep the audio buffer fed during long CPU frames.

Source

pub fn mix_gui_audio_slice(&mut self, audio_samples: usize)

Mix a GUI-only audio slice without running foreground guest code or redrawing the frame. Used by realtime frontends to let Sound Manager doubleback callbacks run between small audio chunks when TickCount is already caught up to the wall clock.

Source

pub fn run_steps_with_audio( &mut self, max_steps: usize, tick_override: Option<u32>, audio_samples: usize, ) -> (usize, bool)

Run for a specific number of steps and mix the supplied amount of host audio. Returns the number of instructions executed and whether the CPU is still running.

tick_override: If Some(ticks), Ticks is capped to the supplied external wall-clock target. If None, Ticks advances from the runner’s configured instruction cadence.

Source

pub fn run_realtime_steps_with_audio( &mut self, max_steps: usize, audio_samples: usize, ) -> (usize, bool)

Run a realtime GUI/WASM slice using the runner’s internal tick cadence. The caller is responsible for converting wall-clock time into max_steps and audio_samples.

Source

pub fn run_gui_slice_with_audio( &mut self, max_steps: usize, deadline_tick: u32, audio_samples: usize, ) -> (usize, bool)

Run a GUI frame slice paced by wall-clock time.

Wall-clock GUI pacing works differently from the reference-runtime oracle: in the oracle, ticks are driven purely by the instruction budget (deterministic, host-speed-independent). In the GUI, the user expects the game to run at real time regardless of how fast the emulator can execute instructions, so the caller computes a deadline_tick from host wall-clock time and we cap $016A advancement there. The CPU runs flat out (up to max_steps) until either the tick cap is hit or the instruction budget is exhausted, at which point the caller yields to the UI thread for rendering.

Source

pub fn run_gui_cpu_slice( &mut self, max_steps: usize, deadline_tick: u32, ) -> (usize, bool)

Run a GUI CPU slice paced by wall-clock time without finalizing a host frame. Browser frontends use this to execute several small CPU batches and then redraw chrome / mix queued audio once for the outer frame.

Source

pub fn run_pending_sound_work(&mut self, max_steps: usize) -> (usize, bool)

Run pending Sound Manager interrupt work without advancing TickCount or continuing into foreground guest code after the callback returns.

Source

pub fn run_steps( &mut self, max_steps: usize, tick_override: Option<u32>, ) -> (usize, bool)

Run for a specific number of steps (for GUI/headless callers that don’t provide a real wall-clock audio budget).

Returns (steps_executed, still_running) — note that the bool is still_running, not halted. false means the CPU halted (via ExitToShell, an unimplemented opcode, or a memory fault). The per-halt detail (trap word, PC, SP, D0) is exposed via the halted_trap, halted_pc, halted_sp, halted_d0 accessors after this call returns.

Source

pub fn run(&mut self) -> Result<()>

Run the 68k guest until it halts or FixtureRunnerConfig::max_instructions is reached. Returns:

  • Ok(()) on a clean halt (Stopped, ExitToShell, or invalid PC).
  • Err(Error::Halted) is not returned here — halt-via-trap maps to Ok(()). Trap dispatch errors (other than Halted) propagate.
  • Error::Timeout when the instruction count cap is reached before any halt condition fires.

Most embedders should prefer FixtureRunner::run_steps, which gives you per-call budget control, returns whether the CPU is still running, and exposes per-halt detail via the halted_pc / halted_trap accessors.

Source

pub fn dump_trace(&self)

Print the last N executed instructions to stderr in PC/Op/Reg form. Used by halt paths in run / run_steps_internal to surface the run-up to a crash. Early-exits when the trace buffer is empty (the default — SYSTEMLESS_TRACE_BUFFER=1 must be set to populate the buffer in the first place).

Trait Implementations§

Source§

impl Drop for FixtureRunner

Dump the diagnostic histograms when the runner is dropped. Each print_*_histogram already early-returns when its env-var gate isn’t set, so this is a no-op for normal runs (including tests). Investigate interactive-mode behavior with SYSTEMLESS_TRACE_TRAP_COUNTS=1, SYSTEMLESS_TRACE_OPCODE_COUNTS=1, SYSTEMLESS_TRACE_HOT_PC=1, or SYSTEMLESS_TRACE_TRAP_TIMING=1.

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<S, T> Duplex<S> for T
where T: FromSample<S> + ToSample<S>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<S> FromSample<S> for S

Source§

fn from_sample_(s: S) -> S

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> ToSample<U> for T
where U: FromSample<T>,

Source§

fn to_sample_(self) -> U

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more