Skip to content

Latest commit

 

History

History
329 lines (239 loc) · 9.55 KB

File metadata and controls

329 lines (239 loc) · 9.55 KB

AGENTS.md — Fuse ZX Spectrum Emulator

Fuse (the Free Unix Spectrum Emulator) is a C codebase targeting multiple Unix platforms plus macOS and Windows. It emulates ZX Spectrum hardware. The canonical style guide is in hacking/coding_style.txt.


Build System

Fuse uses GNU Autotools. A full build from scratch:

./autogen.sh          # Run once after cloning; generates ./configure
./configure           # Select UI backend, peripherals, etc.
make                  # Build the fuse binary

Common ./configure flags:

Flag Effect
--with-sdl SDL UI (easiest on macOS)
--with-gtk GTK+ UI
--without-gtk --without-x Headless/null UI
--prefix=/usr/local Installation prefix

The macOS Cocoa port now lives in the top-level sibling directory ../fusepb/ and has its own Fuse.xcodeproj; the wrapper repo drives normal builds from its top-level Makefile, which in turn invokes the Xcode project and regenerates Perl-derived source as needed.


Code Generation

Several source files are generated by Perl scripts at build time and must not be edited by hand:

  • settings.c / settings.h / options.h — from settings.dat, ui/options.dat
  • z80/opcodes_base.c, z80/z80_cb.c, z80/z80_ddfd.c, z80/z80_ddfdcb.c, z80/z80_ed.c — from the corresponding .dat files via z80/z80.pl

If you need to change settings or Z80 opcode behaviour, edit the .dat / .pl source, then re-run make.


Testing

Z80 Core Test

make test

Builds z80/coretest, runs it, and diffs output against z80/tests/tests.expected. This is a standalone binary that does not require the full emulator to be running.

Unit Tests (built into the binary)

./fuse --unittests

Unit tests are compiled into the main fuse binary. They cover ULA contention, floating bus, memory pool, all machine memory paging, and all peripheral port mappings. There is no separate test binary for these.

Running a Single Test

There is no built-in test filter. To exercise a specific area:

  1. Identify the relevant static int foo_test( void ) function in unittests/unittests.c.
  2. Temporarily call only that function from unittests_run() and rebuild.
  3. Run ./fuse --unittests.

Manual Peripheral Smoke Tests

See hacking/peripheral_tests.txt for step-by-step instructions on manually verifying disk/tape/peripheral behaviour under a running emulator.


Linting and Formatting

Fuse has no CI linter. The formatter of record is Uncrustify:

uncrustify -c hacking/uncrustify.cfg --replace myfile.c

GNU Indent is also documented as an alternative:

indent -br -ce -prs -npcs -nsaf -nsai -nsaw -nsob input.c -o output.c
# Fix known indent quirks:
sed -i 's/switch (/switch(/g' output.c
sed -i 's/(  )/()/g' output.c

Do not reformat existing files wholesale; apply formatting only to new or substantially rewritten code.


Code Style

All rules come from hacking/coding_style.txt. Key points:

Language

  • C, targeting C89 compatibility (no C99/C11 features without a configure guard). Objective-C is used only in the top-level fusepb/ Cocoa port.
  • Perl is used for code generation only.

Indentation and Spacing

  • 2 spaces per indent level; no tabs.
  • Space after ( and before ) in function calls when there are arguments: foo( a, b ) — but foo() with no arguments has no inner spaces.
  • Spaces around binary operators; no space for ++/--.
  • Array subscripts take spaces unless the index is very short: array[ idx ] but array[i] is acceptable.
  • Extra spaces to align related assignments vertically are encouraged.

Braces

  • Opening brace on the same line as the control construct: if( condition ) {
  • Cuddled else: } else {
  • Exception: function definition opening brace goes on its own line.

Function Definitions

static int
my_function( int arg1, const char *arg2 )
{
  /* body */
}

Return type on its own line; function name starts at column 1.

Naming

Kind Convention
Variables, functions all_lowercase_with_underscores
#define macros, enum constants IN_UPPERCASE_WITH_UNDERSCORES
Header guards #ifndef FUSE_<FILENAME>_H
Struct typedefs lower_case_t (e.g. fuse_machine_info)

Comments

  • C-style only: /* ... */. C++ // comments are forbidden.
  • Multi-line comments use the block style:
    /* First line
       continuation */
  • Copyright header in every file; GPL v2 boilerplate.

Includes

Every .c file must start with:

#include <config.h>

Then system headers (<stdio.h>, <stdlib.h>, etc.), then third-party headers (<libspectrum.h>), then local headers ("fuse.h", "module.h", etc.). Local headers use relative paths from the source root.

Error Handling

Functions return int: 0 = success, non-zero = error code. The canonical pattern is:

int error;

error = some_function( arg );
if( error ) return error;

Do not use exceptions or abort() for recoverable errors. ui_error() is used to report errors to the user interface before returning an error code.

Test Assertions

Inside unittests/unittests.c, use the TEST_ASSERT macro:

TEST_ASSERT( condition );

It prints file, line, and expression on failure and increments the failure count. Test functions return int (0 = pass, >0 = number of failures).


Key Architectural Concepts

Concept Location Notes
Startup/teardown ordering infrastructure/startup_manager.c Topological-sort dependency registration; call startup_manager_register() per module
Module hooks (reset, ROM-CS, snapshot) module.c / module.h Broadcast to all registered modules
Peripheral I/O periph.c / periph.h Register read/write handlers with address mask+value pairs
Event queue event.c / event.h Priority queue of T-state–timestamped future events
Z80 registers z80/z80_macros.h Endian-aware union; use macros (A, BC, HL, …) not raw struct members
Display pipeline display.cuidisplay_area() Dirty-rectangle tracking; UI backends implement uidisplay_*
Machine abstraction machines/ Each machine sets up memory map, contention table, and spectrum_* callbacks

macOS Cocoa Port — Thread Safety

The emulator runs on a background thread via -[Emulator connectWithPorts:] in ../fusepb/models/Emulator.m. All AppKit and OpenGL operations must be dispatched to the main thread or macOS will hard-crash (this enforcement became strict on macOS 26+).

Patterns for main-thread dispatch

/* Singleton lazy init — use dispatch_sync so the caller gets the instance */
+ (MyController *)singleton
{
  static MyController *instance = nil;
  if( !instance ) {
    if( [NSThread isMainThread] ) {
      instance = [[MyController alloc] init];
    } else {
      dispatch_sync( dispatch_get_main_queue(), ^{
        instance = [[MyController alloc] init];
      });
    }
  }
  return instance;
}

/* ObjC call that must block the emulator thread until done */
[obj performSelectorOnMainThread:@selector(myMethod:)
                      withObject:arg
                   waitUntilDone:YES];

/* Fire-and-forget UI update */
dispatch_async( dispatch_get_main_queue(), ^{
  /* AppKit call here */
});

Wrapping C-pointer arguments for performSelectorOnMainThread:

performSelectorOnMainThread:withObject: takes an id, so raw C pointers must be boxed:

/* Sender side */
NSValue *val = [NSValue valueWithPointer:myPtr];
[obj performSelectorOnMainThread:@selector(myMethodWithValue:)
                      withObject:val
                   waitUntilDone:YES];

/* Receiver side */
- (void)myMethodWithValue:(NSValue *)val
{
  MyStruct *ptr = [val pointerValue];
  /* use ptr */
}

macOS Cocoa Port — Xcode Build

Build command (run from the wrapper repo root):

make fuse

For direct Xcode builds, open ../fusepb/Fuse.xcodeproj. The wrapper repo's Makefile is the preferred maintainer entry point because it passes the repo root/dependency paths explicitly and performs the post-build re-sign steps.

Key generated headers

config.h and libspectrum.h are not in the fuse root; they are generated at build time and placed in ../fusepb/:

  • ../fusepb/config.h — autoconf output
  • ../fusepb/libspectrum.h — generated by ../fusepb/generate.pl

The Xcode project now stages managed dependencies under ../fusepb/deps/ and builds plugin source directly from ../fusepb/plugins/.


Repository Layout (fuse/ submodule)

fuse.c / fuse.h          Main entry point and global state
machines/                Per-machine emulation (48K, 128K, +2, +3, …)
peripherals/             All peripheral emulations (ULA, AY, disk, IDE, NIC)
z80/                     Z80 core + Perl-generated opcode tables
ui/                      UI backends (SDL, GTK, Xlib, Cocoa, null, …)
debugger/                Built-in debugger (Lex/Yacc command parser)
sound/                   Sound output backends
unittests/               Unit test framework and tests
infrastructure/          Startup manager
hacking/                 Developer docs: coding style, formatter config, notes
perl/                    Shared Perl modules used by code generators
roms/                    ZX Spectrum ROM binaries

Related wrapper-repo directories live one level up:

../fusepb/               Native macOS Cocoa port (Xcode project)
../fusepb/plugins/       In-tree Quick Look + Spotlight plugin source
../fusepb/deps/          Staged managed dependency sources