Discover MCPs & agents
Loading MCPs and agents…
Loading MCPs and agents…
A PDP-11 emulator, a MACRO-11 assembler and Arkanoid written in PDP-11 assembly — built in a single Claude Code session, guided by an engineer who remembers the real thing.
From the repo.
A PDP-11 emulator in TypeScript: a dependency-free core, an assembler (MACRO-11 subset), a React shell for the browser — and three games written in PDP-11 assembly: Arkanoid, Tetris and Pac-Man. Switch between them with the tabs above the screen.

Live: https://pdp11-arkanoid.azurewebsites.net/
Controls — Arkanoid: ← → move the paddle, Space (or ↑) launches the ball. Tetris: Space starts the game and pauses it again, ← → move, ↑ rotates, ↓ hard-drops. Pac-Man: arrows steer, Space starts and pauses. On a phone an on-screen joystick appears. Pause the emulator to inspect the CPU registers mid-game.
Tetris on a PDP-11 is no accident: the original was written by Alexey Pajitnov in 1984 on an Elektronika-60, a Soviet PDP-11 clone — so this runs it on (an emulation of) the machine it was born on.
This project is an experiment in AI-assisted development: the entire thing — the emulator core, the assembler, the React shell, the Arkanoid game in PDP-11 assembly, the tests and the deployment pipeline — was written in a single conversation with Claude Code, guided by an engineer with 40+ years of experience who remembers the real PDP-11 era. The human set the architecture straight more than once (no 64K decode lookup table in 1975 — the decoder is a switch cascade mirroring the combinational logic of the real machine); the AI typed fast and kept the test suite green. Total time from "let's sketch the modules" to the deployed public site: one day.
| Range | Size | Purpose |
|---|---|---|
| 000000–000377 | 256 B | Interrupt and trap vectors |
| 000400–057777 | ~24 KB | Code, data, stack |
| 060000–157777 | 32 KB | Framebuffer 512×512, 1 bpp, 64 bytes per row |
| 160000–177777 | 8 KB | I/O page |
Pixel (x, y): byte 060000 + (y << 6) + (x >> 3), bit x & 7.
| Address | Device | Description |
|---|---|---|
| 177544 | Speaker | bit 0 is the cone position, Sinclair Spectrum style. There is no tone generator: the program toggles the bit and the toggle rate is the pitch. Music costs CPU cycles, like it did in 1982. |
| 177546 | KW11 line clock | bit 6 — interrupt enable, bit 7 — tick flag. Ticks at 60 Hz, interrupts through vector 100 at priority 6. This is the game's "vsync". |
| 172540–172544 | KW11-P programmable clock | repeated-interval countdown: rate select (bits 0–1: 100 kHz, 10 kHz, or the external input — wired here to a 125 kHz crystal), interrupt enable (bit 6), done (bit 7); vector 104. The sound engine of all three games: a note's half-period goes into the count-set buffer (A₄ = 142 → 440.1 Hz) and every interrupt flips the speaker cone — a tone that costs the CPU only its interrupts and goes on sounding across frame boundaries, which is why their frame handlers run at priority 4 and latch against re-entry. Writing the buffer mid-note changes pitch without resetting the phase. |
| 177570 | Joystick | read-only: bit 0 — left, bit 1 — right, bit 2 — fire, bit 3 — down, bit 4 — up. A mask of the currently held buttons. |
Every game drives the speaker through the KW11-P, so nothing busy-waits on a beep any more: a script is a list of (preset, frames) pairs, an effect and a tune run at once, and the effect owns the cone while the tune counts on underneath. Arkanoid plays In the Hall of the Mountain King while the ball waits on the paddle — twice round, the second time an octave up and faster. Tetris waits with Korobeiniki, the tune it made famous: Tetris was written on an Elektronika-60, a Soviet PDP-11 clone.
src/core/ — the emulator core, pure TS, knows nothing about the browser:
memory.ts — 64 KB, one ArrayBuffer with byte and word views;bus.ts — address routing, I/O page dispatch, an onAccess hook;cpu.ts — registers R0–R7, PSW, all 8 addressing modes, step(), traps
and interrupts;instructions.ts — two cooperating forms of the instruction set:
decode() — a cascade of nested switches over the octal fields of the
instruction word, the software analog of the combinational decode
network in the real processor (the structure mirrors the opcode map in
the PDP-11 Processor Handbook); and DEFS — the "programmer's reference
card": data that feeds the assembler, the disassembler and the UI.
A test cross-checks the two on all 65536 opcode words;machine.ts — the assembled machine + state snapshots (rewind/replay);devices/ — KW11, joystick, one-bit speaker.src/asm/ — a two-pass assembler, MACRO-11 subset: labels, SYM = expr,
. = addr, .WORD/.BYTE/.ASCII/.ASCIZ/.BLKW/.BLKB/.EVEN/.END, expressions
(octal by default, 64. for decimal, 'A for a character, left-to-right
evaluation with no operator precedence — just like the real MACRO-11), all
addressing modes. Above 0177 the machine's character set is code page 437,
so box-drawing characters written literally in a .ASCII string assemble
to the pseudographics codes every micro of the era had in its character ROM
— which is how the Pac-Man maze stays a picture in the source. Output: an image + a symbol table + an address ↔ source
line map for the debugger.src/web/ — the React shell: a canvas screen (framebuffer → ImageData once
per frame), keyboard → joystick, Run/Pause/Step/Frame, a register panel,
an assembly editor. npm run dev → http://localhost:5173.programs/arkanoid.s — Arkanoid: a 6×15 brick field, a 4×4 ball with
per-pixel movement (bit mask tables), bounce angle controlled by where the
ball hits the paddle, a score rendered in a 5×7 digit font, lives.
programs/tetris.s — Tetris: a 10×20 well of 24×24 cells, seven pieces ×
four rotations packed as 4×4-box cell tables, the board as 20 row bitmasks,
full-line clears with BCD scoring, levels that speed up gravity, an LFSR
piece bag, delayed auto-shift, and a waiting screen with the tune on the
programmable clock. No multiply or divide —
the whole layout is powers of two, so addressing stays additive.
programs/pacman.s — Pac-Man: a 28×31-tile maze drawn in the source as a
picture, in the machine's own box-drawing characters — one character per
16×16 cell, each glyph carrying its own joints, so walls meet flush by
construction and solidity is read back off the picture rather than the
picture worked out from a solidity map. Plus
16×16 sprites pre-shifted for the four even bit phases, and the arcade's
actual ghost AI — at each tile center a ghost minimizes the squared
distance to its personal target (a table of squares stands in for the
missing multiply). All four personalities, scatter/chase waves, frightened
mode, the ghost house — and Pinky's genuine 1980 look-up overflow bug,
reproduced on purpose.
programs/demo.s — a simple bouncing-ball demo.Designed in and covered by tests; a UI can plug in without touching the core:
cpu.tracer — an event per executed instruction (PC, opcode, name, PSW);bus.onAccess — observes every memory/device access;Machine.snapshot()/restore() — the full machine state; the core is
deterministic, so "initial state + per-frame inputs" gives exact replay
and rewind;InstrDef.units — which functional units an instruction engages (for a
future animated CPU schematic);cpu.cycles — an approximate cycle counter.The base instruction set is implemented (no EIS, FPU or MMU — with a 512-pixel row all address arithmetic reduces to shifts). Faithfully done: byte order, autoincrement by 1/2, MOVB sign-extension into registers, the odd-address trap (vector 4), bus timeout on unmapped I/O addresses, interrupt priorities, WAIT, RTI/RTT, EMT/TRAP, double-fault handling.
npm run dev # Vite dev server
npm run build # production build
npm test # vitest, core/asm/game tests
npm run typecheck # tsc --noEmit
Replace {MCP_ENDPOINT_URL} with this MCP’s endpoint URL (from its repo or docs above). No API key — you connect directly.
Tool
OS
Config file: ~/.cursor/mcp.json
{
"mcpServers": {
"mcp-server": {
"url": "{MCP_ENDPOINT_URL}"
}
}
}Paste into mcpServers in the config file. Restart Cursor after saving.
If this MCP is also published on mcpchannel.ai, you can subscribe from Browse and use the gateway config there instead.