fa-ribeiro/Chip8NX

A modular, profile-driven CHIP-8 emulator in TypeScript.

★ 1Forks 0TypeScriptGitHub ↗Compare

README

Chip8NX

Chip8NX = CHIP-8 + N(ext) / e(X)tensible

A modular, profile-driven CHIP-8 emulator in TypeScript.

Chip8NX is a CHIP-8 emulator/interpreter built as a hands-on exercise in TypeScript, object-oriented design, emulator architecture, testing, and software engineering.

The project supports Classic CHIP-8, CHIP-48 2.25, SUPER-CHIP 1.1, and SUPER-CHIP Modern through explicit machine profiles, while keeping reusable emulator semantics separate from host-specific applications and inspection tooling.

Status

Current release: v1.0.0 — Stable Architecture and Debugger

v1.0.0 marks the first stable Chip8NX architecture and public API contract.

The stable release supports four built-in machine profiles:

  • Classic CHIP-8 — the original baseline machine;
  • CHIP-48 2.25 — the alternate historical profile introduced in v0.8.0;
  • SUPER-CHIP 1.1 — the historical extended machine introduced in v0.9.0;
  • SUPER-CHIP Modern — the modern compatibility target introduced in v0.11.0.

The release preserves the project boundary established during the 0.x milestones: Core owns reusable machine semantics, Inspection remains passive, and host applications own presentation and debugger policy.

The Web host now provides the complete v1.0.0 interactive workflow: ROM loading, Start/Pause/Continue, Step, Reset, profile selection, physical and virtual keypad input, display and audio presentation, live CPU state, address breakpoints, Nearby disassembly, bounded Trace history, and passive Memory inspection. Breakpoints pause before execution, and Continue remains suppressed across retryable attempts at the stopped address until execution actually leaves it.

The Terminal host remains intentionally Classic CHIP-8-only. XO-CHIP, conditional breakpoints, watchpoints, state editing, and step-over/step-out remain outside the v1.0.0 completion scope.

External conformance evidence includes the pinned Classic/CHIP-48/SUPER-CHIP coverage documented under packages/core/tests/conformance/, including Timendus v4.2 Quirks and Scrolling coverage for the supported SUPER-CHIP profiles.

Goals

This project is intended both as an emulator and as a learning exercise.

The main goals are to:

  • build a solid foundation in TypeScript;
  • practice object-oriented and modular software design;
  • understand the CHIP-8 architecture and instruction set;
  • model CHIP-8 variants and quirks explicitly when multiple behaviors genuinely need to coexist;
  • keep components independently testable and replaceable;
  • use external conformance ROMs as behavioral acceptance tests;
  • support multiple host applications without coupling the emulator core to a specific UI or platform.

Architecture

Chip8NX separates reusable machine semantics, passive inspection tooling, and host-specific application concerns.

flowchart LR
    Apps["Applications"]
    Inspection["@chip8nx/inspection"]
    Core["@chip8nx/core"]

    Apps -->|"depends on"| Core
    Apps -->|"when needed"| Inspection
    Inspection -->|"depends on"| Core
Loading

The arrows represent dependency direction.

@chip8nx/core owns the emulated machine: machine profiles, machine state and capabilities, initialization, CPU execution, runtime orchestration, scheduling, and the minimal CPU-observation contract.

A machine profile separates three concerns:

machine characteristics / resources
instructionSet
    → which instruction semantics exist
quirks
    → how shared instructions vary

Within Core, one instruction attempt follows the canonical path:

Memory → Cpu → Decoder → Instruction → InstructionExecutor → ExecutionContext

Cpu owns fetch/decode/execute sequencing. Decoder translates encoded opcodes into typed instructions without consulting the active profile. InstructionExecutor then applies those typed semantics through the focused state and capability components grouped by ExecutionContext, using the selected instructionSet and shared-instruction quirks.

Profile semantics are not stored in ExecutionContext: composition distributes them to the collaborators that own the corresponding behavior.

@chip8nx/inspection builds only on Core's public API and provides passive tooling:

instruction formatting
disassembly
bounded instruction-trace history
trace formatting

Core does not depend on Inspection.

Applications are the composition roots. They construct Core components, choose host adapters, optionally compose Inspection tools, and own platform-specific concerns such as rendering, audio presentation, keyboard adaptation, filesystem access, terminal or DOM interaction, and lifecycle integration.

This keeps the reusable responsibilities distinct:

Core
    → what the machine is and what happened

Inspection
    → how machine semantics and observations can be inspected

Applications
    → how the machine is hosted and presented

For diagrams and more detail, see Architecture.

Quick start

Requirements

  • Deno 2.x

Check the project

deno task check

This performs TypeScript checking, formatting validation, and linting.

Run unit and integration tests

deno task test

Third-party conformance ROMs are intentionally not included in the repository, so conformance tests remain separate from the default test task.

Run conformance tests

After installing the required external ROM fixtures as documented in packages/core/tests/conformance/README.md:

deno task test:conformance

Run the CI contract locally

deno task ci

Run the terminal application

deno task terminal <rom-path>

The terminal host presents the 64×32 Classic framebuffer using Unicode block characters with a retro green presentation and accepts the conventional CHIP-8 keyboard mapping.

To run the same emulator with line-oriented instruction tracing:

deno task terminal --trace <rom-path>

Trace mode keeps keyboard input and emulated execution active while disabling the Terminal's alternate-screen framebuffer presentation so trace lines can use stdout cleanly.

Press Escape to exit. Ctrl+C remains available as an alternative exit path.

Compare terminal composition levels

The terminal application provides the first Chip8NX composition case study.

The same terminal host is available as three runnable examples:

apps/terminal/examples/01-components.ts
apps/terminal/examples/02-standard-compositions.ts
apps/terminal/examples/03-standard-host.ts

The model has now been evaluated against the Web host and remains a terminal-specific composition model rather than a mandatory project-wide framework. See Terminal composition levels and Host composition evaluation.

Run the Web application

Start the Vite development server:

deno task web

Open the URL reported by Vite in a browser, load a CHIP-8 ROM file, and the Web host will initialize and run it.

The Web host provides:

  • Canvas framebuffer presentation for Classic and SUPER-CHIP display geometry;
  • selectable Classic CHIP-8, CHIP-48, SUPER-CHIP 1.1, and SUPER-CHIP Modern machine profiles;
  • physical and virtual CHIP-8 keyboard input;
  • Start/Pause, Step, and Reset execution controls;
  • Web Audio sound presentation;
  • live CPU-state inspection;
  • best-effort nearby disassembly around the current program counter;
  • bounded recent instruction-attempt history;
  • Web-local address breakpoints with enable/disable/remove controls, nearby-gutter toggles, pause-before-execute behavior, and breakpoint-aware Continue;
  • passive memory inspection with exact-address navigation, 64-byte pages, and quick jumps to PC and I;
  • responsive desktop and narrow-screen layouts;
  • persistent Retro Green, Retro Amber, Dark, and LCD Calculator appearance themes;
  • compact machine/runtime status and configuration presentation.

Chip8NX Web Inspection Workbench running the Variant Detection ROM

Chip8NX Web Inspection Workbench running the Variant Detection ROM.

To verify the production Web build:

deno task web:build

Disassemble a ROM

The disassembler CLI provides an exploratory linear view of a CHIP-8 ROM:

deno task disassemble <rom-path>

For example:

deno task disassemble packages/core/tests/conformance/roms/test_opcode.ch8

Output contains the source address, opcode word, and decoded Classic CHIP-8 instruction:

0x200  124E  JP 0x24E
0x202  EAAC  UNKNOWN
0x204  AAEA  LD I, 0xAEA

Unsupported words are reported as UNKNOWN and traversal continues. The CLI performs a linear sweep and does not attempt to distinguish executable code from embedded data. A word that decodes successfully may therefore still represent sprite, table, string, or other non-executable data.

See Disassembling CHIP-8 programs.

Generate API documentation

deno task docs:build

Generated API documentation is written to:

build/docs/api/

The generated API reference covers the public entrypoints of both @chip8nx/core and @chip8nx/inspection.

Check public API documentation

deno task docs:check

Repository structure

Chip8NX/
├── packages/
│   ├── core/
│   │   ├── mod.ts
│   │   └── src/
│   └── inspection/
│       ├── mod.ts
│       └── src/
├── apps/
│   ├── terminal/
│   ├── web/
│   └── disassembler/
├── docs/
├── build/
└── deno.json

packages/core

@chip8nx/core contains the reusable CHIP-8 machine.

Its responsibilities include:

  • machine profiles and initialization;
  • focused machine state and capability boundaries;
  • opcode decoding and typed instruction semantics;
  • CPU execution;
  • runtime scheduling and timing;
  • timers and vertical blank;
  • display-buffer state;
  • keyboard, font, and random-number capability seams;
  • the minimal CPU instruction-observation contract.

Core does not depend on host-specific presentation or passive Inspection tooling.

packages/inspection

@chip8nx/inspection contains host-independent passive tools built on Core's public API.

Its current responsibilities include:

  • CHIP-8 instruction formatting;
  • strict disassembly;
  • bounded instruction-trace history;
  • human-readable trace formatting;
  • CPU-state-change trace decoration.

Inspection does not control execution and does not own host-specific output.

apps/terminal

The Terminal application is a host composition case study.

It uses Core for emulation and may compose Inspection formatters for optional line-oriented trace output.

Terminal-specific rendering, keyboard adaptation, CLI options, and output policy remain application-owned.

apps/web

The Web application hosts the CHIP-8 machine in a browser.

It composes both reusable packages:

@chip8nx/core
    machine execution
    runtime and scheduling
    authoritative CPU observation

@chip8nx/inspection
    instruction formatting
    nearby disassembly
    bounded trace history
    trace formatting

The application owns the browser-specific policy around those capabilities: machine-profile selection, profile-appropriate instruction formatting, Canvas rendering, audio presentation, keyboard adaptation, ROM loading, execution controls, inspection-window selection, responsive DOM presentation, appearance themes, and UI lifecycle.

Reset reinitializes the current session using its retained profile. Selecting a different profile creates a fresh session from the retained ROM image and preserves the host's previous running or paused state.

Passive inspection failures remain application-visible data rather than emulator failures. The Web inspector can therefore expose undecodable nearby bytes and failed CPU attempts without giving the Inspection package execution-control responsibility.

apps/disassembler

The disassembler application is a command-line inspection tool.

It combines:

Core
    Memory
    Decoder
    InvalidOpcodeError

Inspection
    Disassembler
    ClassicInstructionFormatter

The reusable Inspection disassembler is strict. The application adds tolerant whole-ROM exploration policy by catching invalid opcodes per instruction-sized word and continuing.

docs

Long-form architecture, guides, reference material, and Architecture Decision Records.

Generated API documentation is written under:

build/docs/api/

and is not committed to source control.

Documentation

Source-level API behavior belongs close to TypeScript implementation in JSDoc. Project documentation explains how larger pieces collaborate and why major decisions were made.

Milestone history

v0.0.1 — IBM Logo POC ✓

A real CHIP-8 ROM executes end-to-end and produces the expected framebuffer.

v0.1.0 — corax89 Opcode Conformance ✓

The original corax89 opcode test succeeds through the normal Chip8NX machine pipeline.

v0.2.0 — Classic CHIP-8 Baseline ✓

The Classic implementation passes the relevant Timendus Corax+, Flags, Quirks, and Keypad tests and has an explicit opcode-family coverage audit.

See Classic CHIP-8 opcode coverage audit.

v0.3.0 — Interactive Terminal Host ✓

The first complete Chip8NX host provides terminal framebuffer presentation, interactive keyboard input, clean terminal lifecycle management, and runnable examples demonstrating Level-1, Level-2, and Level-3 composition.

v0.4.0 — Interactive Web Host ✓

The second complete Chip8NX host provides browser ROM loading, Canvas framebuffer presentation, physical and virtual keyboard input, execution lifecycle controls, and Web Audio sound presentation.

The Web host also completes the second application-composition case study, validating the current Core host boundaries while keeping host-level composition application-specific.

v0.5.0 — Disassembly and Inspection ✓

Core gains a reusable read-only instruction-inspection path built on the existing typed decoder, together with a pluggable instruction-formatting boundary and a conventional Classic CHIP-8 formatter.

The release also adds a small command-line disassembler for exploratory whole-ROM inspection. Unsupported words are rendered as UNKNOWN without changing the strict Core range-disassembly contract. The implementation and application are documented through dedicated architecture and usage guides and validated against real CHIP-8 ROMs containing mixed code and data.

v0.6.0 — Tracing and Execution Observation ✓

Core gains optional structured observation of real CPU instruction attempts, including successful and failed attempts, before/after CPU state, observer-failure isolation, and preservation of the original execution error.

Trace formatting remains separate from observation, with conventional Classic trace formatting and composable CPU-state-change decoration. InstructionTraceBuffer adds bounded chronological history suitable for future inspection consumers.

The Terminal --trace mode provides the first external proof of concept while keeping output and host presentation outside Core.

Together with the v0.5.0 disassembly boundary, this milestone establishes the reusable inspection foundation for future debugger and analysis tooling without prematurely adding breakpoints, execution control, event infrastructure, replay, or whole-machine tracing.

v0.7.0 — Web Inspection Workbench ✓

The Web host becomes Chip8NX's first interactive inspection workbench.

It composes the Core CPU-observation boundary with the extracted @chip8nx/inspection package to provide live CPU state, bounded best-effort disassembly around the current program counter, and recent successful or failed CPU instruction attempts.

The release also evolves the browser host into a responsive play-and-inspection workspace with unified Start/Pause control, compact ROM loading, explicit keyboard mapping, machine-state presentation, theme-aware command controls, and persistent Retro Green, Retro Amber, and Dark themes whose palettes also drive Canvas framebuffer presentation.

Inspection remains deliberately read-only: breakpoints, watchpoints, pause conditions, step-over/step-out behavior, memory editing, and other debugger execution-control semantics remain deferred until concrete reusable requirements emerge.

v0.8.0 — CHIP-8 Profiles / Variant Foundation ✓

Chip8NX evolves from a single Classic CHIP-8 target into a demonstrated multi-profile emulator architecture with built-in CLASSIC_CHIP8_PROFILE and CHIP48_PROFILE machine definitions.

Chip8Profile now describes the complete emulated machine, combining architectural characteristics with explicit compatibility-sensitive semantics for shift source, Fx55 / Fx65 index-register updates, Bnnn jump offsets, logic-operation VF behavior, sprite overflow, and sprite draw timing. CHIP-48 2.25 additionally demonstrates profile-specific font data and machine timing.

The Web host provides interactive Classic CHIP-8 / CHIP-48 2.25 profile selection, profile-appropriate instruction formatting, profile-preserving Reset behavior, and fresh machine-session composition when the selected historical target changes.

Compatibility is independently validated with Gulrak's Variant Detection Test v1.4, executing the same external ROM under both profiles and comparing each against its own stable framebuffer result.

The milestone establishes the variant foundation without introducing a generic quirk engine, strategy hierarchy, profile registry, or universal machine-session abstraction: new variation continues to be modeled only when concrete historical targets demonstrate the need.

v0.9.0 — SUPER-CHIP 1.1 ✓

Chip8NX adds its first extended CHIP-8-family machine profile: historical SUPER-CHIP 1.1.

The display model evolves from fixed geometry to an explicit display specification capable of representing SUPER-CHIP's 64×32 and 128×64 modes over one shared 128×64 backing framebuffer. Display mode remains machine state, while the Web Canvas adapter renders the resulting physical framebuffer without reproducing SUPER-CHIP semantics in the presentation layer.

The release adds SUPER-CHIP scrolling and mode-control instructions, interpreter exit, extended Dxy0 sprites, mode-specific draw timing and VF behavior, the historical ten-byte decimal font through Fx30, and persistent V0–V7 RPL flags through Fx75 / Fx85.

SUPER-CHIP is composed through the same Chip8Profile, ExecutionContext, decoder, executor, runtime, initialization, and host boundaries already used by Classic CHIP-8 and CHIP-48. No parallel emulator hierarchy or generic quirk engine is introduced.

The Web profile selector now exposes all three supported machines:

Classic CHIP-8
CHIP-48
SUPER-CHIP 1.1

Changing profile rebuilds the current Web machine session around the selected profile while preserving host-owned persistent state such as the SUPER-CHIP RPL flags.

v0.10.0 — Profile Semantics and SUPER-CHIP Hardening ✓

Chip8NX hardens the multi-profile architecture without adding a new machine target.

Chip8Profile now distinguishes machine characteristics and resources, instruction-set membership, and quirks of shared instructions. The public Chip8InstructionSet model makes extension-specific semantics explicit, while Chip8Quirks is narrowed to genuine behavioral variation of instructions shared across supported machines.

SUPER-CHIP profile isolation is strengthened with regression coverage proving that extended resources do not accidentally grant extended instruction semantics. External conformance is expanded with Timendus v4.2 legacy SUPER-CHIP Quirks and Scrolling coverage, including both low- and high-resolution scrolling modes.

The release also normalizes profile font resources, clarifies InstructionExecutor composition, and reconciles the architecture, guide, reference, and release documentation around the refined profile model.

v0.11.0 — SUPER-CHIP Modern ✓

Chip8NX adds a fourth built-in machine target: SUPER-CHIP Modern.

The release demonstrates that two SUPER-CHIP dialects can share the same extension opcode family and structural resources while retaining different exact semantics. Chip8InstructionSet now distinguishes superchip-1.1 from superchip-modern, while shared-instruction variation continues to live in the narrower Chip8Quirks model.

Modern mode switches clear the framebuffer, scrolling uses logical pixels, 00C0 is a zero-row no-op, Dxy0 is 16×16 in both display modes, collision VF is boolean, drawing is immediate, and Fx1E continues when I leaves the 4 KiB memory range. Historical SUPER-CHIP 1.1 retains its documented legacy behavior.

Timendus v4.2 Quirks and Scrolling conformance runs provide independent Modern-profile evidence through the same machine pipeline used by the other profiles. The Web host exposes the new profile alongside Classic CHIP-8, CHIP-48 2.25, and SUPER-CHIP 1.1.

The milestone also includes a bounded stabilization pass: Web session lifecycle coordination and input recovery are made explicit, timing type factories become their own branding boundary, instruction execution becomes compile-time exhaustive, and release-facing documentation/tooling is reconciled.

v1.0.0 — Stable Architecture and Debugger ✓

Chip8NX reaches its first stable architecture and public API contract.

The four built-in machine profiles—Classic CHIP-8, CHIP-48 2.25, SUPER-CHIP 1.1, and SUPER-CHIP Modern—remain explicit compatibility targets with profile-specific instruction-set semantics and quirks. Core keeps the reusable machine model, Inspection remains passive, and host applications retain ownership of lifecycle and debugger policy.

The Web host completes its bounded debugger/workbench scope with address breakpoints, explicit breakpoint pause reasons, retry-safe Continue behavior, nearby disassembly, bounded trace history, CPU-state inspection, and passive memory navigation. The debugger remains deliberately host-local rather than becoming a reusable Core or Inspection subsystem.

The release also hardens the stable public surface by making instruction exports explicit, sharing Web address parsing/validation policy between debugger and memory tooling, reconciling current architecture and release documentation, and reviewing the public API before the 1.0.0 stability boundary.

Future work

Post-v1.0.0 development can proceed across areas such as:

  • richer debugger behavior beyond the current Web-local address breakpoints, such as conditional breakpoints, watchpoints, step-over/step-out, or state editing, when concrete workflows justify it;
  • richer memory or static-analysis inspection beyond the current passive page view, such as search, watch integration, or editing, when concrete workflows justify it;
  • XO-CHIP remains intentionally outside the project completion scope; its larger architectural extensions should be treated as separate future work rather than a prerequisite for v1.0.0;
  • further public reusable-package API and composition refinement when additional consumers create demonstrated pressure for change;
  • desktop hosts;
  • additional SUPER-CHIP historical/conformance evidence where external tests expose meaningful behavior not already represented.

The current CPU-observation boundary deliberately remains observational. The Web host now layers its own address-breakpoint policy on Core's generic scheduled CPU execution gate, while Inspection remains passive and unaware of execution control. Reusable debugger infrastructure, watchpoints, richer stepping policy, observer fan-out, timestamps, replay, whole-machine snapshots, persistent trace formats, and richer history-query APIs should still be introduced only when concrete debugger or analysis consumers demonstrate the need.

The current disassembler likewise remains a small inspection foundation rather than a full static-analysis system. Features such as control-flow analysis, code/data classification, labels, descriptions, and richer tolerant-disassembly models should be introduced only when concrete consumers justify them.

References

The project is developed with reference to:

  • CHIP-8 Variant Database / CHIP-8-KB — Classic CHIP-8;
  • Matthew Mikolay's CHIP-8 technical reference;
  • Tobias V. Langhoff's CHIP-8 emulator guide;
  • corax89 CHIP-8 test ROM;
  • Timendus CHIP-8 test suite.
  • Gulrak / Cadmium Variant Detection Test.

Historical behavior is resolved against evidence appropriate to the selected machine profile rather than assuming one behavioral interpretation for every CHIP-8-family target.

Versioning

The project follows Semantic Versioning.

v1.0.0 establishes the first stable Chip8NX architecture and public API contract, with agreed conformance requirements for the supported built-in machine profiles and stabilized host, inspection, and debugger boundaries.

From 1.0.0 onward:

  • PATCH releases contain backward-compatible bug fixes and maintenance;
  • MINOR releases may add backward-compatible capabilities;
  • MAJOR releases are reserved for intentional breaking changes to the stable public contract.

The 1.0.0 stability contract covers Classic CHIP-8, CHIP-48 2.25, SUPER-CHIP 1.1, and SUPER-CHIP Modern, together with the documented Terminal and Web host boundaries, passive Inspection model, and Web-local debugger workflow. XO-CHIP and IDE-style debugger features such as conditional breakpoints, watchpoints, state editing, and step-over/step-out remain outside that contract.

See CHANGELOG.md for release history.

License

Chip8NX source code is licensed under the MIT License.

Third-party conformance ROMs and other external materials remain subject to their respective upstream licenses and are not covered by the Chip8NX MIT license. See THIRD_PARTY_NOTICES.md.

Contributors

fa-ribeiro

Issues