pub struct FixtureRunner { /* private fields */ }Expand description
Canonical entry point of the systemless library.
FixtureRunner owns the three pieces of guest state: the M68kCpu
backend, the MacMemoryBus, and the TrapDispatcher (Toolbox and OS
trap handlers). It exposes the load, execute, and halt-inspection
surface that drives them.
Lifecycle:
FixtureRunner::new— allocate guest RAM + dispatcher.crate::game::load_game— auto-detect StuffIt / MacBinary, populate guest memory, seed CPU state.run_steps(preferred) orrun— drive the CPU.run_stepsreturns(steps_executed, still_running);runruns until halt orFixtureRunnerConfig::max_instructions.- After halt:
halted_pc/halted_trap/halted_sp/halted_d0expose per-halt detail, andhalted_by_exit_to_shellclassifies the common clean application-exit path.
Defaults: guest-controlled Mac menu-bar visibility; arrow keys NOT
remapped to numpad. Frontends can select a kiosk policy through
set_menu_bar_policy.
See examples/run_headless.rs for a runnable end-to-end example.
Implementations§
Source§impl FixtureRunner
impl FixtureRunner
Sourcepub fn new(ram_size: usize, config: FixtureRunnerConfig) -> Self
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 begins at PC = 0 with reset
vectors not yet loaded, and guest RAM is zero-initialized. 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.
The runner allocates one contiguous host region of ram_size bytes.
crate::game::RAM_SIZE provides the canonical profile-driven size;
tests and specialized embedders may choose a smaller allocation.
The dispatcher follows the guest’s menu-bar state by default. Frontends
that own the surrounding chrome can select an explicit kiosk policy in
FixtureRunnerConfig or through
set_menu_bar_policy.
Sourcepub fn set_prefer_powerpc_executables(&mut self, enabled: bool)
pub fn set_prefer_powerpc_executables(&mut self, enabled: bool)
Prefer a PowerPC application fragment over a classic CODE fallback
when loading a fat application. The default remains classic 68k for
callers that do not select an architecture explicitly.
Sourcepub fn prefers_powerpc_executables(&self) -> bool
pub fn prefers_powerpc_executables(&self) -> bool
Returns whether fat applications should prefer a PowerPC fragment.
pub fn set_external_q3_renderer_enabled(&mut self, enabled: bool)
pub fn take_q3_gpu_frame(&mut self) -> Option<PpcQ3GpuFrame>
Sourcepub fn halted_by_exit_to_shell(&self) -> bool
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.
pub fn guest_tick(&self) -> u32
pub fn host_now(&self) -> Instant
pub fn halted_trap(&self) -> Option<u16>
pub fn halted_pc(&self) -> Option<u32>
pub fn halted_sp(&self) -> Option<u32>
pub fn halted_stack_word0(&self) -> Option<u16>
pub fn halted_stack_word(&self, word_index: u32) -> Option<u16>
pub fn halted_d0(&self) -> Option<u32>
pub fn total_instructions(&self) -> u64
pub fn debug_overlay_snapshot( &self, frame_stats: DebugOverlayFrameStats, ) -> DebugOverlaySnapshot
pub fn cpu(&self) -> &M68kCpu
pub fn cpu_mut(&mut self) -> &mut M68kCpu
pub fn bus(&self) -> &MacMemoryBus
pub fn bus_mut(&mut self) -> &mut MacMemoryBus
pub fn dispatcher(&self) -> &TrapDispatcher
pub fn dispatcher_mut(&mut self) -> &mut TrapDispatcher
Sourcepub fn ui_theme(&self) -> &'static dyn UiTheme
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.
pub fn ui_theme_id(&self) -> UiThemeId
pub fn theme_metrics_mode(&self) -> ThemeMetricsMode
pub fn uses_classic_guest_metrics(&self) -> bool
Select the host presentation policy for the classic Mac menu bar.
Return the current host presentation policy for the menu bar.
Compatibility toggle for embedders using the older boolean API.
true restores guest-controlled visibility. false permanently hides
the bar; game frontends that only need an initial kiosk presentation
should use MenuBarPolicy::InitialKiosk instead.
Inside Macintosh Volume I, I-354 (DrawMenuBar); Inside Macintosh Volume V, V-245 (MBarHeight global).
Returns whether host policy currently permits the Mac menu bar to be
rendered. Guest MBarHeight and fullscreen state may still hide it.
Sourcepub fn disassemble_at(&self, pc: u32, count: usize) -> Vec<(u32, String, u32)>
pub fn disassemble_at(&self, pc: u32, count: usize) -> Vec<(u32, String, u32)>
Disassemble count M68K instructions starting at pc.
Returns (pc, mnemonic, size_in_bytes) for each instruction. The size
includes extension words; advance pc by size to reach the next
instruction.
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.
Sourcepub fn print_opcode_histogram(&self, top_n: usize)
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.
Sourcepub fn print_pc_histogram(&self, top_n: usize)
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.
Sourcepub fn print_ppc_fetch_histogram(&self, top_n: usize)
pub fn print_ppc_fetch_histogram(&self, top_n: usize)
Dump the PPC fetched-instruction histogram. No-op unless
SYSTEMLESS_TRACE_PPC_FETCH_COUNTS=1 is set. This is
reachable-code evidence: unlike PEF static scans, literal
pools and embedded data never enter this count.
Sourcepub fn print_ppc_import_histogram(&self, top_n: usize)
pub fn print_ppc_import_histogram(&self, top_n: usize)
Dump the PPC HLE import histogram. No-op unless
SYSTEMLESS_PPC_IMPORT_HIST=1 is set.
Sourcepub fn print_ppc_gworld_dump(&mut self, top_n: usize)
pub fn print_ppc_gworld_dump(&mut self, top_n: usize)
Dump tracked PPC GWorld fingerprints. No-op unless
SYSTEMLESS_PPC_GWORLD_DUMP=1 is set.
Sourcepub fn print_ppc_unimpl_histogram(&self, top_n: usize)
pub fn print_ppc_unimpl_histogram(&self, top_n: usize)
Dump unsupported PPC runtime stops. No-op unless
SYSTEMLESS_PPC_UNIMPL_HIST=1 is set.
pub fn install_application_clut(&mut self, clut: [[u16; 3]; 256])
pub fn set_app_start_time(&mut self, secs: u32)
pub fn set_application_partition_size(&mut self, bytes: Option<u32>)
Sourcepub fn app_start_time(&self) -> Option<u32>
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.
pub fn application_partition_size(&self) -> Option<u32>
Sourcepub fn set_trace_sink(&mut self, sink: Box<dyn TraceSink>)
pub fn set_trace_sink(&mut self, sink: Box<dyn TraceSink>)
Install a trace sink to receive runtime events and screen
snapshots (see crate::trace::TraceSink). The host owns the sink
and decides how/where its output is persisted.
pub fn record_oracle_checkpoint_snapshot(&mut self, name: &str) -> Result<u64>
pub fn record_oracle_script_input( &mut self, fields: BTreeMap<String, String>, ) -> Result<()>
Sourcepub fn composite_frame(&mut self)
pub fn composite_frame(&mut self)
Synchronize deferred PPC visual/VFS state and composite chrome/dialog overlays. Call before reading raw pixels for screenshots.
Sourcepub fn set_arrows_as_numpad(&mut self, enabled: bool)
pub fn set_arrows_as_numpad(&mut self, enabled: bool)
Enable or disable arrow-key-to-numpad remapping.
Sourcepub fn arrows_as_numpad(&self) -> bool
pub fn arrows_as_numpad(&self) -> bool
Returns true when arrow keys are remapped to numpad key codes.
Sourcepub fn configured_screen_depth(&self) -> u16
pub fn configured_screen_depth(&self) -> u16
Return the configured initial 68K indexed main-framebuffer depth. This does not report an explicit or default native PowerPC depth.
Sourcepub fn set_powerpc_screen_depth(
&mut self,
screen_depth: u16,
) -> Result<(), String>
pub fn set_powerpc_screen_depth( &mut self, screen_depth: u16, ) -> Result<(), String>
Explicitly select the display depth used by subsequently loaded native PowerPC applications. Leaving this unset preserves the historical 16-bit PowerPC architecture default, independently of the configured 68K framebuffer depth.
Sourcepub fn set_mouse_position(&mut self, v: i16, h: i16)
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.
Return an immutable snapshot of the guest’s current Menu Manager list. Native and web frontends can use this without exposing mutable Toolbox internals.
Route a host-presented menu selection back through the guest’s normal mouseDown -> FindWindow -> MenuSelect path. Returns false if the menu or item is no longer present, enabled, and selectable.
Sourcepub fn push_mouse_down(&mut self, v: i16, h: i16)
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.
Sourcepub fn push_mouse_up(&mut self, v: i16, h: i16)
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
Sourcepub fn push_key_down(&mut self, mac_key: u8, char_code: u8)
pub fn push_key_down(&mut self, mac_key: u8, char_code: u8)
Inject a key-down event, applying arrow→numpad remapping if configured.
Sourcepub fn push_key_up(&mut self, mac_key: u8, char_code: u8)
pub fn push_key_up(&mut self, mac_key: u8, char_code: u8)
Inject a key-up event, applying arrow→numpad remapping if configured.
Sourcepub fn set_audio(&mut self, audio: Box<dyn AudioBackend>)
pub fn set_audio(&mut self, audio: Box<dyn AudioBackend>)
Set the audio output backend. If not set, no audio is produced.
pub fn set_instructions_per_tick(&mut self, instructions_per_tick: u32)
pub fn instructions_per_tick(&self) -> u32
Sourcepub fn is_powerpc_app(&self) -> bool
pub fn is_powerpc_app(&self) -> bool
Whether the active application executes through the PowerPC runtime.
Sourcepub fn set_wait_sleep_cap_in_headless(&mut self, cap: Option<u32>)
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.
pub fn wait_sleep_cap_in_headless(&self) -> Option<u32>
Sourcepub fn drain_audio(&mut self) -> Vec<u8> ⓘ
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).
Sourcepub fn drain_audio_into(&mut self, out: &mut Vec<u8>)
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.
Sourcepub fn audio_buffer_len(&self) -> usize
pub fn audio_buffer_len(&self) -> usize
Current number of buffered audio samples (for diagnostics).
pub fn has_pending_sound_work(&self) -> bool
pub fn is_ui_tracking_active(&self) -> bool
Sourcepub fn force_advance_guest_tick(&mut self)
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).
pub fn set_output_path(&mut self, path: PathBuf)
Sourcepub fn vfs_read(&self, filename: &str) -> Option<&[u8]>
pub fn vfs_read(&self, filename: &str) -> Option<&[u8]>
Get the contents of a file from the virtual filesystem.
pub fn vfs_file_summaries(&mut self) -> Vec<VfsFileSummary>
pub fn vfs_file_summaries_where<F>(&mut self, include: F) -> Vec<VfsFileSummary>
pub fn vfs_file_stats_where<F>(&mut self, include: F) -> Vec<VfsFileStat>
pub fn vfs_file_summary(&mut self, path: &str) -> Option<VfsFileSummary>
pub fn vfs_file_snapshot(&mut self, path: &str) -> Option<VfsFileSnapshot>
Sourcepub fn prepare_vfs_resource_forks_for_native_export(&mut self) -> usize
pub fn prepare_vfs_resource_forks_for_native_export(&mut self) -> usize
Reconstruct resource entries embedded by installers and publish the complete forks through the virtual filesystem snapshot API.
pub fn import_vfs_file(&mut self, file: &VfsFileSnapshot)
pub fn import_vfs_file_relative_to_launched_app( &mut self, relative_dir: &str, file: &VfsFileSnapshot, ) -> Result<(), String>
pub fn remove_vfs_file(&mut self, path: &str) -> bool
Sourcepub fn step(&mut self) -> StepResult
pub fn step(&mut self) -> StepResult
Execute exactly one 68k instruction through the precise CPU path.
The result distinguishes ordinary completion, STOP, and an A-line trap.
Most embedders should call run_steps instead; it
amortizes tick advancement, halt detection, and trace collection across
the instruction budget.
Sourcepub fn load_app(&mut self, fork: &ResourceFork) -> Option<LoadedApp>
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).
Sourcepub fn init_app(&mut self, app: &LoadedApp)
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.
Sourcepub fn mix_audio(&mut self, num_samples: usize)
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.
Sourcepub fn mix_gui_audio_slice(&mut self, audio_samples: usize)
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.
Sourcepub fn run_steps_with_audio(
&mut self,
max_steps: usize,
tick_override: Option<u32>,
audio_samples: usize,
) -> (usize, bool)
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.
Sourcepub fn run_realtime_steps_with_audio(
&mut self,
max_steps: usize,
audio_samples: usize,
) -> (usize, bool)
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.
Sourcepub fn run_gui_slice_with_audio(
&mut self,
max_steps: usize,
deadline_tick: u32,
audio_samples: usize,
) -> (usize, bool)
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:
in the reference runtime, 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.
Sourcepub fn run_gui_cpu_slice(
&mut self,
max_steps: usize,
deadline_tick: u32,
) -> (usize, bool)
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.
Sourcepub fn run_pending_sound_work(&mut self, max_steps: usize) -> (usize, bool)
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.
Sourcepub fn run_steps(
&mut self,
max_steps: usize,
tick_override: Option<u32>,
) -> (usize, bool)
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.
Sourcepub fn run(&mut self) -> Result<()>
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 toOk(()). Trap dispatch errors (other thanHalted) propagate.Error::Timeoutwhen 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.
Sourcepub fn dump_trace(&self)
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, SYSTEMLESS_TRACE_PPC_FETCH_COUNTS=1, or
SYSTEMLESS_PPC_IMPORT_HIST=1, SYSTEMLESS_PPC_UNIMPL_HIST=1,
SYSTEMLESS_PPC_GWORLD_DUMP=1, or SYSTEMLESS_TRACE_TRAP_TIMING=1.
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, SYSTEMLESS_TRACE_PPC_FETCH_COUNTS=1, or
SYSTEMLESS_PPC_IMPORT_HIST=1, SYSTEMLESS_PPC_UNIMPL_HIST=1,
SYSTEMLESS_PPC_GWORLD_DUMP=1, or SYSTEMLESS_TRACE_TRAP_TIMING=1.