Time Pilot
Time Pilot (Konami, 1982), designed by Yoshiki Okamoto, is the free-roaming aerial combat classic in which your fighter stays fixed at the centre of the screen while the whole world scrolls around it, flying through five eras of aviation -- 1910, 1940, 1970, 1982 and 2001 -- shooting down waves of enemy craft, rescuing parachuting pilots and destroying each era's giant boss aircraft. This build runs the original Konami board in the browser: a Zilog Z80 driving Konami's early tile and sprite hardware, with the authentic ROMs, booting straight into attract mode. It is wired to the emulators.org in-frame debugger so you can single-step the Z80, read and write the registers and the full 64K memory, and set execution breakpoints and write watchpoints.
Runs on: Web browser
Time Pilot Online Emulator
Play Time Pilot using JavaScript directly in your browser.
Controls
Configurations
| Configuration | Emulator | Machine | OS | Legal | |
|---|---|---|---|---|---|
| Time Pilot | Time Pilot | Time Pilot | grey | Open ⛶ |
Machine emulated
The Time Pilot arcade board (Konami, 1982) is an all-Z80 design: a Zilog Z80 at 3.072 MHz runs the game while a second Z80 drives two AY-3-8910 PSGs for the sound. Konami's early tile-and-sprite hardware draws a 32×32 character map and up to 24 hardware sprites over a rotated monitor, and the game's trademark trick is that your plane never moves: it sits at the centre of the screen and the world scrolls beneath it in any of eight directions.
Chips
Notes
Embedding
Time Pilot is Konami's 1982 arcade machine: a Zilog Z80 main CPU at 3.072 MHz that runs the game, and a second Zilog Z80 sound CPU driving two AY-3-8910 PSGs. This build is a from-scratch JavaScript emulation whose board is a port of MAME's konami/timeplt.cpp. The main Z80 core is Molly Howell's MIT Z80.js. The whole board runs from a host-owned loop, so the debugger can pause and single-step it.
Boot. Constructing the machine fetches the ROMs, decodes the character and sprite graphics and the four colour PROMs, resets the main Z80 and starts the loop; the board comes up in attract mode:
var m = new TimePilot({ canvas, romBase, manifest });
m.boot(); // fetch ROMs, decode GFX + PROMs, reset the Z80, run
// each animation frame runs one 60Hz field of the main Z80, then draws the screen
The machine object. Everything the host and debugger need is a field or method on the machine:
| Member | Kind | What it does |
|---|---|---|
m.cpu | field | The main Z80 core. Registers are read with getReg(name) / written with setReg(name,v) for A F B C D E H L, the AF/BC/DE/HL pairs, IX IY SP PC, and I R. |
m.dbgRead(a) / m.dbgWrite(a,v) | method | Side-effect-free read of the main 64K bus and the CPU write path -- what the debugger's memory views read and poke. |
m.setInput(id, down) | method | Drive a control (up, down, left, right, fire, coin, start1, start2) into the input ports. |
EMU_BOOT.transport | field | The pause / resume / step / breakpoint / watchpoint surface the shared debugger drives. |
Video. The screen is a 32×32 map of 8×8 two-bit characters (videoram holds the tile code, colorram the attribute: colour, an X/Y flip and a priority bit) plus up to 24 four-bit 16×16 sprites read from two parallel banks. Colour is fixed in four PROMs: two 32×5 PROMs give 32 master colours through a resistor DAC, and a char and a sprite 256×4 lookup PROM map each layer's pen into them. Background tiles draw first, then sprites, then the priority-bit tiles draw over the sprites. The renderer draws a 256×224 raster and rotates it (ROT90) to a 224×256 portrait, like the upright cabinet.
Debugger integration
Because the whole board is ordinary JavaScript and the host owns the run loop, the debugger's controls need no changes to the CPU core: the loop can pause, single-step and check breakpoints between any two instructions. window.EMU_BOOT.transport maps the shared debugger onto the main Z80:
- pause / resume / isPaused -- stop or restart the
requestAnimationFrameloop. - stepInsn(n) -- run exactly n Z80 instructions through the core's
run_instruction()entry. - step(n) -- advance n whole 60 Hz fields of the main Z80, each honouring the one vblank NMI when the control latch has it enabled.
- breakpoints -- a
Setof PC values checked before each instruction; a match pauses before the instruction runs. - watchpoints -- the Z80's memory-write path flags a hit when a watched address is written, and the loop pauses on it.
The stock Z80.js keeps its registers in a closure and exposes a whole-core getState()/setState(). To make the debugger's live register window cheap, the vendored core is given get_pc() and set_pc() hooks so the run loop can read PC before every instruction (for breakpoints) without allocating a state object, plus get_iff1() and a proper NMI entry so a held vblank interrupt is honoured. The machine wraps getState/setState in focused getReg/setReg/getFlag/setFlag helpers that the plug-in binds to. Memory reads use the machine's side-effect-free bus read, which returns work RAM, the video RAMs, the two sprite banks, the program ROM and the input ports without side effects, so inspecting memory never disturbs the board. The Z80 disassembler is the shared decoder at /debugger/src/cpus/z80.js -- reused unchanged, since the Z80 is already a first-class debugger CPU.
Architecture
The Time Pilot board (Konami, 1982) is an all-Z80 design built from two Z80s and Konami's early tile/sprite hardware, run on a rotated (ROT90) monitor:
- Main CPU -- Zilog Z80 at 3.072 MHz (18.432 MHz / 6). Program ROM at
0x0000–0x5fff, tile-attribute RAM at0xa000, tile-code RAM at0xa400, work RAM at0xa800, two sprite banks at0xb000and0xb400, and an I/O page at0xc000holding the beam-position read, the DIP banks, the three input ports and an LS259 control latch. This is the CPU the debugger targets. - Interrupts -- the main Z80 takes a single hardware NMI at vblank each field, gated by latch line Q0 (NMI enable); the NMI vectors to
0x0066. - Control latch -- an LS259 at
0xc300–0xc30f(one line per address pair, data on D0) holds NMI enable (Q0), flip screen (Q1, inverted), the sound-CPU IRQ strobe (Q2), sound mute (Q3), video enable (Q4) and the two coin counters (Q5/Q6). - Tilemap -- a 32×32 map of 8×8 two-bit characters. The code comes from videoram and a high bit of the colorram attribute; the attribute also holds a five-bit colour, an X and a Y flip, and a priority "category" bit that lets a tile draw in front of the sprites.
- Sprites -- up to 24 four-bit 16×16 sprites, each described across the two
0xb000/0xb400banks: X, Y (as241 - y), an eight-bit code, a six-bit colour and X/Y flip. Transparent pen 0. - Sound CPU -- a second Z80 driving two AY-3-8910 PSGs, fed one-byte commands the main CPU writes at
0xc000and strobed by latch line Q2. This sound board is fully emulated: the sound Z80 runs its owntm7program and the two PSGs are synthesised, so the machine makes sound (see the Sound section below). - Palette -- there is no palette RAM. Two 32×5 PROMs, decoded through a resistor DAC, give 32 master colours; a char and a sprite 256×4 lookup PROM map each layer's pen to one of those colours (chars use the upper sixteen, sprites the lower sixteen).
The graphics ROMs are decoded to one-byte-per-pixel tiles and sprites once at boot; the visible 256×224 raster is rotated 90° to the 224×256 portrait of the upright cabinet.
Sound
Pattern S (authored synthesis). The board that was previously stubbed is now fully run. Time Pilot's sound is a second Zilog Z80 (its own tm7 program) driving two AY-3-8910 PSGs, exactly as on the real hardware and MAME's shared timeplt_a.cpp. Both the sound Z80 and the two PSGs are clocked at 14.31818 MHz / 8 = 1.789772 MHz.
What was added. A from-scratch AY-3-8910 (timepilot-ay.js): three 12-bit square-tone channels (f = clock / (16 × period)), a 17-bit-LFSR noise generator, a 16-step hardware envelope, and the chip's 4-bit logarithmic volume DAC. Two instances are mixed. The sound Z80 runs one video field per frame on the second Z80 core, over its real memory map: ROM at 0x0000, RAM at 0x3000, AY #1 data/adress at 0x4000/0x5000, AY #2 at 0x6000/0x7000. AY #1 port A reads the sound-command latch the main CPU writes at 0xc000; port B reads the LS90 divide-by-ten timer the program polls for tempo. The main CPU's LS259 line Q2 strobe raises the sound Z80's IM1 interrupt (a held line, delivered once interrupts are enabled), and line Q3 gates the amplifier mute.
Pitch and rate. The PSGs are generated directly at the shared sink's EmuAudio.sampleRate: each output sample advances the chip's internal (clock/8) generators by (clock/8) / sampleRate ticks and averages, so tone frequencies are exact with no separate resampling. Each field pushes exactly Math.round(EmuAudio.sampleRate / 60) interleaved-stereo Int16 samples; the mono chip mix is duplicated to both channels through a one-pole DC blocker.
Mute. window.EMU_BOOT.transport exposes isMuted()/setMute(), wired to the shared EmuAudio sink; audio starts muted (browsers block audio before a gesture) and the Sound button unmutes it from a real click. Time Pilot's attract mode plays its theme music automatically, so it is audible as soon as the sound is unmuted.