1.2 Repository layout¶
The point of this page is to turn a symptom into a path. A bug report about sprite scaling, a feature request for a new tape format, a failing screenshot — each of them lives somewhere specific, and the directory tree is regular enough that knowing the rule is usually faster than grepping.
Top level¶
| Path | What it is |
|---|---|
src/ |
All emulator source. One subdirectory per subsystem — see below. |
test/ |
Every test: unit suites (one directory per subsystem), the regression suite under test/00regression/, the declared-suite manifests, the harness and its self-test, and the traceability tooling. |
doc/ |
Documentation. doc/design/ holds design plans (including the roadmap), doc/testing/ the per-subsystem test plans and the generated traceability matrix, doc/man/ the man-page source, doc/user-guide/ and doc/developer-guide/ the rendered and committed guides. |
demo/ |
z88dk test programs, one directory per feature (copper_demo, tilemap_demo, lores_demo, …), built to .nex/.tap by demo/Makefile. Most regression screenshots are of these. |
packaging/ |
Everything that turns a build into a package: rpm/, debian/, flatpak/, macos/, windows/, the AppStream metadata in assets/, gen-debian-changelog.sh, and sync-version.sh, which does the part of a version bump that must be committed. |
third_party/ |
Vendored dependencies: fuse-z80/ (the Z80 core, GPLv2-or-later), spdlog/ (logging, submodule), zot/ (TZX playback, MIT), crtemu/ (CRT-emulation shader, MIT / Unlicense; patched, every patch marked JNEXT PATCH and listed in its README.jnext.md), fatfs/ (FAT filesystem, used for SD-image work; carries one local addition, f_syncvol(), marked in ff.c, which flushes the volume before jnext unmounts one it has modified). |
tools/ |
Developer utilities that are not part of the build: SD-image fixers, CSpect/DZRP differential-debugging helpers, profiler post-processing, the user-guide server, crtshot (one picture per CRT effect, for the user guide). |
roms/ |
nextboot.rom and nothing else. The 8 KB FPGA boot ROM is embedded into the binary at build time; every other ROM is read out of the SD-card image at runtime. Git-ignored beyond that file. |
cmake/ |
GenerateVersion.cmake — derives the version from version.yaml. |
data/ |
48rom.map, a symbol map for the 48K ROM used when disassembling. |
docker/ |
Dockerfiles for the Linux and Windows cross-builds. |
.github/ |
CI and release workflows, plus the issue templates. |
.claude/ |
The AI-assisted development apparatus: reusable agent definitions, task recipes, git hooks that enforce the branch rules, and process notes. Not part of the product, but it is where the project's conventions are made executable. |
.prompts/ |
The daily task files, one per working day, kept as part of the project's record. Not part of the product. |
The root also carries CMakeLists.txt, a large self-documenting Makefile
(run make with no target and it lists every target it has), version.yaml
(the single source of truth for the version), releases.yaml (the allowlist
that gates public GitHub releases), CLAUDE.md (the project conventions), and
the two mkdocs configs — mkdocs.yml for the user guide, mkdocs-devguide.yml
for this one.
Inside src/¶
Most of these directories hold an emulated subsystem, and chapter 3 opens with a table mapping each one back to the VHDL it was derived from. The rest — the frontends, the debugger UI, the profiler — surround the emulation rather than being part of it.
| Directory | Responsibility |
|---|---|
core/ |
Emulator — the top-level machine that owns every subsystem — plus Clock, Scheduler, EmulatorConfig, the CLI option table (cli_options.h), the logging wrapper (log.h), the state-serialisation primitives (saveable.h), all the file loaders and savers (NEX, SNA, SZX, Z80, TAP, TZX, WAV, RZX, JNS) and the extension dispatch that picks one (snapshot_file), the screenshot writers (PNG and .SCR), the host-side FAT32 reader that extracts ROMs from the SD image, the SD-card provisioner, and the video recorder. |
cpu/ |
Z80Cpu, the wrapper around the vendored FUSE core; the Z80N extension opcodes (z80n_ext — 31 of them, not the 26 the roadmap still says); and the IM2 interrupt controller with its client mixin. |
memory/ |
Mmu (8 × 8 K slots, the MemoryInterface implementation), Ram, Rom, the ContentionModel, AttributeMux — the beam-time replay of mid-frame writes to every VRAM byte the ULA, the tilemap and LoRes fetch — and SramBeamLog, the same for Layer 2's SRAM bitmap. |
video/ |
The layers and the compositor: Ula, Lores, Layer2, Tilemap, SpriteEngine, PaletteManager, VideoTiming (raster counters), and Renderer, which composites them. |
audio/ |
AyChip and the TurboSound triple wrapper, Dac, Beeper, I2s, and the Mixer that sums them; plus the WAV recorder and the DAC trace recorder. |
port/ |
PortDispatch (mask/value I/O decode) and NextReg, the NextREG register file. |
peripheral/ |
Copper, CTC, DMA, DivMMC, Multiface, the NMI source pipeline, SPI, the SD card device, I²C with its DS1307 RTC, the UART, and the adapters that bind the emulated ESP-01 to UART 0. |
input/ |
The ZX keyboard matrix, joysticks, Kempston mouse, MD6 connector, membrane stick, NR 0x0B I/O mode, the F-key state machine, the phantom typist that types LOAD "" for you, and the host-side joystick/mouse dispatchers. |
platform/ |
The SDL frontend (sdl_app, sdl_display, sdl_audio, sdl_input), the headless frontend (headless_app), the cold-boot helper, the small header-only policies for frame pacing and render skipping, and the POK trainer engine (pok_trainers: the .pok parser, the session that applies and removes trainers through the debugger backend, and the --delayed-pok-frames request the two non-Qt loops run; see 3.8). |
gui/ |
The Qt 6 frontend: QtApp, MainWindow, EmulatorWidget, the preferences dialog and the saved-configuration store. gui/icons/ holds the toolbar icon set: monochrome SVGs, one per toolbar action, whose only colour is currentColor (rules in doc/design/ICONSET-DESIGN.md). color_scheme_policy.h and desktop_color_scheme.* make the GUI follow the desktop's light/dark preference (see 2.1). crt_gl_overlay.*, crt_shader_policy.h and crtemu_impl.cpp are the optional OpenGL CRT shader (Qt 6 with OpenGLWidgets only, -DENABLE_CRT_GL): a QOpenGLWidget that exists only while the shader is on, drawn by the vendored crtemu; with it off there is no widget, no GL context and no per-frame work (doc/design/CRT-GL-FILTER.md). |
qt/ |
Header-only Qt code that both Qt libraries need — the debugger keymap's Qt conversions, the menu-bar Alt-navigation style, and palette_tint_qt.h, which makes the colours a panel picks for itself work on a dark palette (see 2.1). No library target, no Emulator; it exists so that jnext_gui and jnext_debugger can share it without depending on each other, and so that src/debug/ stays free of Qt (debug_qt_free_test fails if a Qt include or a *_qt.h reappears there). |
toolbar_icons/ |
The toolbar icon loader (jnext_toolbar_icons), the one Qt library both jnext_gui (the Main toolbar) and jnext_debugger (the Debug toolbar) link. It compiles gui/icons/*.svg in as Qt resources and renders each with QSvgRenderer (QtSvg; the QIcon SVG plugin is not used, so no plugin is deployed), currentColor replaced by the palette's WindowText for the Normal state and its Disabled WindowText for the Disabled one, at 16, 24, 32 and 48 px. toolbar_icons::bind() re-renders an action's icon when its toolbar receives QEvent::PaletteChange, so a light/dark switch follows at run time. A missing resource, or an SVG that will not parse, has no currentColor or draws nothing, throws: never a blank button. toolbar_icons_test pins all of it, and lints every file in gui/icons/ against the design's rules. |
debugger/ |
The Qt 6 debugger window and its panels — CPU, disassembly, memory, MMU, stack, call stack, watches, breakpoints, video, sprites, copper, NextREG, audio, script — a frontend of the debug/ backend (see 3.10.2). Compiled only when ENABLE_DEBUGGER=ON. |
debug/ |
The debugger backend, pure C++ with no GUI dependency: disassembler, breakpoint set, DebugState, trace log, call stack, symbol table, and the rewind ring buffer. Plus the four published headers debugger.h, events.h, inspect.h, result.h and the jnext::dbg::Debugger facade behind them (debugger*.cpp) — the backend the three loop owners host and every debugger frontend talks to (see 3.9). |
remote/ |
The socket transport the debugger protocol servers share (jnext_remote): one non-blocking listener driven by the backend's pump(), over esp01's socket seam, plus the in-memory fake their unit suites use. remote/dzrp/ is the DeZog Remote Protocol server (--dzrp-port, see 3.10.3); remote/gdb/ is the GDB remote serial protocol server for z88dk-gdb (--gdb-port, see 3.10.4); remote/zrcp/ is the ZEsarUX remote command protocol server (--zrcp-port, see 3.10.5). |
script/ |
The debugger scripting language, .jds (jnext_script): lexer, parser, load-time checks, the expression compiler as a library (compile_expr / eval_expr), the engine that runs scripts as backend subscriptions, the loop owners' ScriptHost (--script, --script-key, --map, --record-script) and the recorder. Qt-free, over the jnext::dbg::Debugger facade only (see 3.10.6). |
esp01/ |
The emulated ESP-01 WiFi module — AT-command engine, socket layer, worker thread — built as its own library with its own tests. |
profiler/ |
The per-physical-address T-state profiler behind --profile. |
doc/ |
Markdown sources for the user guide and this developer guide, plus the Graphviz diagram sources. |
save/ |
A CMake target that currently has no sources; the serialisation primitives it was meant to hold live in core/saveable.h. |
Three of those names describe features rather than hardware, and are worth knowing before you meet them in the code.
The phantom typist in src/input/ is what makes --load game.tap just
work. Starting a tape means typing something first — LOAD "" at a 48K BASIC
prompt, ENTER on the 128K and +3 loader menus — and the moment that prompt
becomes ready differs by machine. Rather than guessing with a fixed delay, the
typist watches the guest scanning its keyboard and starts typing once the ROM's
input loop is demonstrably running.
AttributeMux in src/memory/ exists for multicolour demos. The ULA
fetches every pixel and attribute byte again on every scanline, and a
character cell's attribute byte on each of its eight, so a program that rewrites
attributes between those fetches gets eight different colours down a single
cell — the technique Nirvana-class engines are built on — and a write that
lands after a pixel byte's fetch is not seen until the next scanline. Since
JNEXT composites the frame at the end, those writes have to be logged and
replayed at the instant the ULA fetches each byte to survive at all.
The tilemap and LoRes read bank 5 / bank 7 through the same log, and
SramBeamLog does the same for the SRAM bitmap Layer 2 fetches.
The rewind ring buffer in src/debug/ is the machinery behind the
debugger's backwards execution: step back one instruction, jump back a frame,
or drag a slider to a frame in recent history. It works by snapshotting the
entire machine at every frame boundary into a ring of fixed-size slots, then
restoring the nearest snapshot and replaying forward to the point you asked
for. That is why the state-serialisation contract in core/saveable.h reaches
into every subsystem: a rewind is only ever as faithful as the least complete
save_state() in the machine.
Which directories may see SDL and Qt¶
There is one architectural boundary in this tree that is worth stating
explicitly, because breaking it is easy and the consequence arrives late: the
emulation core knows about neither SDL nor Qt. src/core, src/cpu,
src/memory, src/video, src/audio, src/port and src/peripheral contain
no #include of either — only prose comments naming them. That is precisely
what makes the same Emulator object usable from an SDL window, from a Qt
widget and from a headless loop with no display at all, and therefore what
makes the unit suites and the regression suite possible.
Qt is confined to src/gui, src/debugger, src/toolbar_icons and the header-only src/qt. SDL is owned by src/platform
— and, to be honest about it, by four headers in src/input: keyboard.h,
gamepad_host.h, joystick_dispatcher.h and mouse_dispatcher.h all include
<SDL3/SDL.h>. The keyboard case is structural rather than accidental.
Keyboard::set_key() takes an SDL_Scancode, which makes SDL's scancode enum
the project's vocabulary for host keys; the Qt frontend translates its own key
events into that vocabulary rather than introducing a second one. Traffic in
the other direction stays clean: callbacks such as on_input_state_restored
and on_joystick_source_changed carry no SDL or Qt type in their signature.