So far in this series, I’ve tested Adventure Uncartridged, which had an available original to compare directly against, Gauntlet Reforged, which had no available original at all, and Improbable Mission, which had an original but, this time, the original isn’t something I compare against. It’s the actual program I’m running.

In 1983, arcades had a new kind of machine sitting among the Pac-Man and Galaga cabinets. It didn’t draw its pictures with a handful of colored blocks. It played a movie. Dragon’s Lair put a laserdisc player inside the cabinet and showed full animation by Don Bluth’s studio, the kind of thing you would otherwise see in a theater.

You were Dirk the Daring, going into a castle to rescue Princess Daphne, and the cabinet charged fifty cents a pop for those rescue attempts. That’s twice the usual quarter, folks, and as a kid you felt it. But we paid it. Then we watched Dirk die. A lot.
The mechanics of the game were painfully simple. The game showed you a scene, and somewhere in it there was a moment, often a fraction of a second, when you had to push the stick the right way or push a button to swing the sword. Miss it and you got a lovingly animated death scene.

A wave of laserdisc games followed: Space Ace, Cliff Hanger, Badlands, Cobra Command, Road Blaster and more. Most of them are now very hard to play, in terms of being able to play them at all. The players wore out, the discs rotted, and the boards are scarce.

Because I apparently can’t leave old games alone, I built Laser Vault, an emulator for these machines. It’s open sourced, written in C with SDL3 for the window and sound and FFmpeg for the video. It runs twenty games on eleven different arcade boards. Unlike the other games in this series, it isn’t playable in a browser. You need your own copy of each game: its ROM chips and the disc’s video. I’ve preserved the folders for the games in this post on the Internet Archive, which I have made available:
Laser Vault is an emulator, not a recreation. It contains no game code, no artwork and no video from any of these games. It runs the original ROMs and plays the original disc video, which you supply. Or, as I showed above, what I supplied for you. You can check my full archive for everything I’m doing. This includes material for other retro projects I’m working on.
Let’s talk about testing all this. And this time the shape of the problem is genuinely different from the last three.
What’s Different This Time
With the Adventure, Gauntlet and Impossible Mission revisions, I wrote the game. The original, when there was one, was a reference I compared my version against.
Here, I didn’t write the game at all. The game is the original program, byte for byte, as it was burned into the ROM chips in 1983. What I wrote is the machine it runs on: the processor (a Z80, a 6502, a 6809 or an 8088, depending on the game), the memory map, the interrupts, the chips that draw text over the picture, the sound chips, and a model of the laserdisc player that the game sends commands to.
That flips the usual testing question. I’m not asking “does the game behave correctly?” The games are all roughly forty years old and were shipped and played. They are what they are, at this point. I’m asking “does my machine behave enough like the real one that the game can’t tell the difference?” The game is both the thing I’m running and the most demanding test I have.
This is the situation anyone testing a platform is in: an operating system, a browser engine, a runtime, a compiler. You don’t own, and often can’t fully control, the programs that run on it. And they will use your platform in ways you never thought of. And some ways you did think of but hoped no one else would. This is actually a huge challenge in game testing in general, at least for PC gaming, where the possible configurations of how the game is played on an operating system, with various drivers and devices, can differ wildly.
How This Was Built
It was not easy, to be honest, and not all of that was due to my incredibly rusty C skills, as it turned out. I had to disassemble the games, run headless experiments and work out where each game keeps its moves. I played every game, and when something felt wrong, it was my job to recognize that and figure out why.
This is why I’m not convinced an AI could have really helped me here. Too much of this was strict emulation (objective) coupled with a feeling of how that emulation was running (subjective). Quality is often a mix of both the objective and subjective, so this wasn’t a shock. My Don’t Be Such a Tester was somewhat predicated on this distinction.
Now, I bring all this up because this is very much in line with that testing knowledge isn’t just testing skill article that I wrote. I had to interleave my activities quite a bit here as a developer and a tester.
My “disassemble the games” part from above probably sounds more mysterious than it is, so let me show you what it means with Dragon’s Lair, using nothing but the game’s first ROM chip.
What a ROM Looks Like
A ROM chip is just numbers. The first of Dragon’s Lair‘s four chips holds 8,192 bytes, and if you open the file, this is a slice of what you see:
1C70: 23 10 F5 D9 C9 06 02 B0 32 38 E0 C9 00 21 40 A0
1C80: 3A 08 C0 EE FF CB 67 28 26 CB 6E 28 2E CB 76 20
1C90: 04 CB F6 CB FE E6 0F C6 40 23 06 0B BD 20 16 CB
1CA0: 6E 28 1C CB 76 20 04 CB F6 CB FE 23 10 EE C9 CB
The number on the left is the address and the rest are the bytes stored there, in hexadecimal. There’s no structure to see. Some of these bytes are instructions for the processor, some are data, and nothing in the file says which is which.
A disassembler turns the bytes back into the processor’s instructions. Dragon’s Lair runs on a Z80, so 3A 08 C0 becomes LD A,(C008h): “load register A from address C008.” Starting at 1C7D, the bytes above come out as the following (and the comments after the semicolons are mine; the game has none):
1C7D 21 40 A0 LD HL,A040h ; HL points at a byte in RAM: the sword's flags
1C80 3A 08 C0 LD A,(C008h) ; read the joystick and sword port
1C83 EE FF XOR FFh ; flip every bit: a pressed switch reads as 0
1C85 CB 67 BIT 4,A ; bit 4 is the sword
1C87 28 26 JR Z,1CAFh ; not pressed: go and clear the flags
1C89 CB 6E BIT 5,(HL) ; was it already down last time?
1C8B 28 2E JR Z,1CBBh ; no: just note that it's down (bit 5), for now
1C8D CB 76 BIT 6,(HL) ; already reported this press?
1C8F 20 04 JR NZ,1C95h ; yes: nothing new
1C91 CB F6 SET 6,(HL) ; no: mark it reported...
1C93 CB FE SET 7,(HL) ; ...and flag a fresh press for the game to act on
1C95 E6 0F AND 0Fh ; now the stick: the low four bits are its direction
How did I know what C008 was, or that bit 4 is the sword? Partly from the popular emulator MAME’s notes on the board, but mostly from the code itself: this routine is the only place the game reads C008 while you’re playing, and it treats bit 4 differently from the low four bits. It then goes on to handle the stick the same way, with eleven more flag bytes from A041 to A04B, one for each direction: 1 is up, 2 is down, 4 is left, 8 is right, and the diagonals are sums of those.
Twelve instructions, and they already tell a tester three things. The switches are active low, so an emulator that reports a pressed button as 1 has a game that thinks every button is held down. A press only counts once the game has seen it on two reads in a row, so a press that’s too short is ignored, and that’s the game’s doing, not the emulator’s. And a fresh press is flagged once and then left alone: hold the sword down and it counts as one swing, not a swing on every frame.
Notice that none of this comes from a manual that I could read. The ROM is the specification. It’s exact, it’s complete, and it can’t be out of date, because it’s the thing that shipped. It just isn’t written for people.
Finding Where the Moves Live
Once you know where the presses go, you can look for whatever reads them. That turned out to be a routine at 1755, which walks a list in RAM starting at A0E0, 16 bytes per entry. Here’s the heart of it:
17A3 3E 0F LD A,0Fh
17A5 DD A6 00 AND (IX+0) ; the entry's byte 0, low four bits: which input
17A8 21 40 A0 LD HL,A040h
17AB 85 ADD A,L ; point at that input's flags (A040 + input)
17AC 6F LD L,A
17AD CB 7E BIT 7,(HL) ; a fresh press of it?
17AF 28 DE JR Z,178Fh ; no: on to the next entry
17B1 3E 03 LD A,03h
17B3 A1 AND C ; which time window are we in...
17B4 C6 02 ADD A,02h ; ...picks one of bytes 2 to 5
...
17C0 22 5A A0 LD (A05Ah),HL ; remember the chosen outcome
17C3 3E 01 LD A,01h
17C5 CD 8E 1B CALL 1B8Eh ; and beep
Reading code only gets you a theory, though. To test it, I ran the game headless, with no window and no video, as fast as the computer would go. I put in two coins (or, rather, simulated doing so), pressed start, touched nothing else, and printed the list every time it changed. When Dirk reaches the first room, the list holds this:
A0E0: A8 13 08 03 07 00 00 00 00 00 00 00 00 25 1B 3F
A0F0: 81 01 08 00 00 00 00 00 00 00 00 00 00 4A 00 00
A100: 82 01 08 00 00 00 00 00 00 00 00 00 00 4A 00 00
Here’s how that first line decodes, following the code:
A8: the top bit says this entry is live; the low four bits are 8, which means right.13: the low two bits say there are 3 time windows. The rest picks an outcome for each window: the first outcome, then the second, then the first again.08 03 07: the outcome codes. The game turns each one into a stretch of the disc to play next.25 1B 3F: how long each window lasts, in the game’s timer ticks, about 30 a second. In the full dump, only the first of these counts down; the others wait their turn.
Put together: push right in the first 1.2 seconds and you get outcome 08. Push it in the next 0.9 seconds and you get 03. Push it later and you’re back to 08. The other two entries are up (81) and down (82), each with one window and one outcome: 08.
That was a hypothesis, but it made a prediction I could check. So I checked it: I ran the game again, pressing right at different times. Too early: the disc searched to the death scene and my remaining lives count dropped. In the second window: the disc played on to the next room and the score went up by 300. Up or down: the same death as pressing too early. Outcome 08 is death; outcome 03 is the way onward. And, in case it wasn’t obvious, the list itself never says so. It took the experiment to find out.
That’s the loop I used for every game I worked to support with this: read the code until you have a theory, then run the machine headless and make it prove you right or wrong. Disassembly tells you what the game could do. Only an experiment tells you what it actually does.
Adding to the fun, every game in Laser Vault kept its moves somewhere different, in a different form, written for a different processor. I’ll come back to this, because the list you’ve just seen is exactly what my “autoplay” concept reads.
Purpose and Audience
As before, and in all cases in this series, the strategy shown for testing serves two kinds of tester.
- Testers who explore by playing. The program, the games, a keyboard or a gamepad, the documentation, and a lot of patience along with some fast reflexes.
- Testers who explore the code. Testers who can read C and, ideally, a little assembly; using the source, the headless tracing tools and the game’s own code, disassembled.
The second track matters more here than it did for the other games. A player can tell you that Dirk died when he shouldn’t have. Only someone reading the game’s code can tell you whether the game or the emulator was wrong.
What Correct Means
Here’s what a tester can lean on, in order of authority.
- The game’s own code. The ROM is the specification of what the game expects from the hardware. If the game reads a port and treats bit 4 as the fire button, then bit 4 is the fire button, whatever anyone else says. Many games even test themselves: Badlands checks its RAM and checksums both of its program ROMs every time it powers on, and Dragon’s Lair II has a whole self-test at start-up.
- Recordings of real chips. The processors have independent test suites that compare every instruction against real silicon. The Z80 runs Frank Cringle’s ZEXALL exerciser, the 6502 runs Klaus Dormann’s functional test, and the 8088 runs Daniel Balsom’s SingleStepTests, which were recorded from a real 8088.
- Other emulators. MAME documents most of these boards, and the Daphne emulator and its successor Hypseus were built for exactly these games. They’re the best map available.
- Manuals, schematics and people’s memories. Operator manuals list the DIP switches. Memories tell you how the game felt.
The interesting part is that third item. MAME is a respected, decades-old project, and I found it was entirely wrong more than once. Its notes for Badlands had the two banks of DIP switches the other way round. Its color table for Esh’s Aurunmilla had its two halves swapped. It labels two of Cliff Hanger‘s inputs backwards. In every case, the game’s own code settled it: the code that reads the coinage switches reads one address, not the other.
A strong reference is still a reference, not an oracle. When the authority you trust most disagrees with the evidence in front of you, check the evidence before you dismiss it. Sometimes the famous source is the one that’s wrong.
Risk Areas
When I ranked the risks, the processor came out lower than I expected. It’s well tested. The risk lives in the parts nobody wrote down.
| Rank | Risk | Why it is risky here | Tracks |
| 1 | Timing between chips | The game, the interrupts and the laserdisc player all run on their own clocks. On Badlands, getting two interrupts in the wrong order made the game send the player half a command | Technical |
| 2 | The laserdisc player | Every game depends on the player’s searches, frame numbers and status replies, and each player model has its own quirks. Badlands‘ player even ran Konami’s own modified firmware | Both |
| 3 | Undocumented hardware | Several of these boards are listed as “not working” in MAME. What a register does is often only known by reading the code that uses it | Technical |
| 4 | ROM set variants | Most games had several revisions, fan-made enhancements and regional versions, each with different addresses and sometimes a different laserdisc player | Both |
| 5 | The game files people have | The video comes from decades-old community rips. Some copies leave a clip out of the index file, and some are scrambled and can’t be read at all | Human |
| 6 | Picture and sound in step | The board draws scores and sights over a video that has to arrive on time, while sound comes from the disc and from the board’s own chips | Human |
| 7 | The less-tested processors | The 6809 and the COP421 microcontroller have no independent test suite yet. They’re checked only by the games running on them | Technical |
Notice how many of these come down to the same thing: no specification exists, so the only test is whether the original game is happy.
Test Layers and Testability
One thing makes an emulator unusually testable: it’s deterministic by construction. Nothing in it depends on the wall clock. Every chip advances by counted clock cycles. Give it the same ROMs and the same button presses at the same cycles, and it plays exactly the same game, every time, on every machine. The games have plenty of randomness, but it all comes from their own counters, which run on that same emulated clock.
Compare that with Gauntlet Reforged, where I had to chase down a sound effect whose timing was quietly changing the random numbers. Here, determinism isn’t something I had to add after the fact. It falls out of how the thing is built. That’s a design choice worth stealing!
That determinism makes these seams possible:
| Seam | What it gives a tester |
| Headless tracing | A tool runs a game with no window or video, hundreds of times faster than real time, printing every command the game sends to the player and every change on the score display |
| Scripted presses | Any button can be held at a given second for a given length, so a play-through is just a list of presses |
| RAM snapshots | A probe can write out all of the game’s memory every frame, so two runs can be compared byte by byte |
| Frame grabs | Any disc frame can be written out as an image, and the running game can screenshot the picture with its overlay |
| The game’s own switches | DIP switches give free play, unlimited lives, test modes and self-playing demos, all set from the command line |
| Recording | A game can be written to a video file instead of a window: every frame with its sound, and the same video every time |
And these are the layers built on them:
| Layer | What it checks | Track |
| Processor test suites | Every instruction against recordings of real chips | Technical |
| The games’ own self-tests | RAM, ROM checksums and hardware checks at power-on | Technical |
| Headless play-throughs | A game played from coin to ending without a window, checking where the disc goes | Technical |
| Recorded play-throughs | A whole autoplayed game as a video, for a person to watch at normal speed | Both |
| Builds on three systems | The same code compiles on Windows, Linux and macOS | Technical |
| Exploratory play | Time-boxed sessions in the real window, with a controller | Human |
Heuristics for Exploratory Play
These need no code. I would encourage a tester to pick a few per session.
- Watch the boot. Most of these games test their hardware at power-on. A game that sits on a blank screen or resets itself is often telling you exactly which part failed.
- Let the attract mode run. The attract mode exercises searches and playback without any input. Leave it for ten minutes and see whether it ever loses its place on the disc.
- Die on purpose. Every death makes the game search to a death scene and back. Deaths are the heaviest workout the player model gets.
- Press at the edges. Press a move as early as you can and as late as you can. The emulator’s timing shows up at the edges of a window, not in the middle.
- Flip every switch. Set each DIP switch and check it does what the manual says. Then check it against the code, because the manual and the code don’t always agree.
- Try every ROM set you have. Revisions and fan-made sets move things around. A fix for one can break another.
Heuristics for Reading the Code
- Find where the game reads its inputs. Everything a player does goes through a handful of reads from one or two addresses. Follow them and you find the gameplay.
- Trust the code over the labels. When documentation and the game’s code disagree about what a bit means, the code wins.
- Look for what the game writes to its own RAM. Games keep their state in memory in remarkably readable ways. A byte that counts down 3, 2, 1, 0 as you hit a target is a gift.
- Compare two runs frame by frame. When something differs, the first frame where memory differs points straight at the cause.
- Treat a “not working” game as an invitation. When MAME says a board doesn’t work, the missing pieces are usually small, and the game’s code says what they are. This is literally what I encountered in every situation.
And, Yes, the AI Part
You knew this was coming. In the other posts, I built agents that played the games. For Laser Vault, I wanted the same thing: a way for the agent to play each game from the first scene to the ending. That proves the ending is reachable on my emulator, which is the best end-to-end test an emulator can have. If any chip misbehaves badly enough, anywhere along the way, the game will notice.
Here’s the twist. This doesn’t really AI to do much. Laser Vault‘s autoplay doesn’t learn anything and doesn’t look at the picture. It reads the game’s mind, as it were.
Think about what one of these games has to do. It’s playing a scene from the disc, and at some moment it’s waiting for one particular input. To judge your press, the game has to know which input it wants and when the window opens and closes. That information has to be in the machine somewhere. It’s either in the ROM or in RAM. And the emulator can see all of the machine. Add those facts up and what you see is that the job is not to build a player. The job is to find where each game keeps its answers!
That’s a testing lesson in its own right. Before you build an elaborate oracle, check whether the system already knows the answer and is just not telling you.
Every game hides its answers differently, though, and that’s where it got interesting. Here I’ll talk about five of the games I support, roughly from easiest to hardest.
Cliff Hanger: The Answer Key
Cliff Hanger (Stern, 1983) is built from animation out of the Lupin III films. Cliff has to save Clarissa from Count Draco, and the game prompts you to move the stick, press the hands button or press the feet button.

This was the easy one. Each scene is a script in the ROM, and each move is an 18-byte record. When the game checks a move, it points a register at the record, and the record says, in plain bytes, which control is right and the frame numbers its window opens and closes on, written in decimal digits. My autoplay test harness (the “not really an AI” thing) presses that control as the game starts checking.
Result: it plays set 1 from the first scene to “CONGRATULATIONS, YOU HAVE COMPLETED THIS CHALLENGE” without losing a life. The one wrinkle was that my notes had the stick’s bits wrong. That’s exactly why the game’s code beats anyone’s notes, mine included.
Esh’s Aurunmilla: The Answer Key with a Catch
Esh’s Aurunmilla (Funai and Gakken, 1984) is the obscure one. MAME lists it as not working; Laser Vault got it going by reading the game’s code. A lamp on the control panel lights at each danger, and the animation shows the way out.

Again, the scenes are records in the ROM. The record says which control is the right answer, and the game even uses that same byte to decide which lamp to light. Easy. Except the first version of autoplay pressed the correct direction in every scene and the game ignored it. Every time.
The reason is a small piece of game design. The game doesn’t look at what key you’re holding. It looks for a new press, by comparing what you’re holding now with what you held a moment ago. And when a prompt opens, it sets “what you held a moment ago” to “everything.” So a stick you’re already holding as the window opens never counts. You have to press it fresh. Autoplay thus had to be designed to press and release several times a second through the window, the way a nervous player would.
Result: all 133 scenes of the first round, on one life, through to the ending. Then the game starts a second round with the scenes in a different order, and autoplay just keeps going.
Badlands: The Answer Nobody Wrote Down
Badlands (Konami, 1984) is a western. Buck rides into town after the gang that killed his family. There’s one button. Wait for the outlaw to draw, then fire. Fire too early and it doesn’t count, and your gun takes half a second to reload, so you can’t fire again in time. Fire too late and you’re the one on crutches. Of all the games on Laser Vault, it’s the one people find hardest to play.

Those images are showing the first gunfight, three frames from the disc: the window opens at frame 3236 as the gun comes up, the muzzle flash is at 3240, and the window closes at 3251.
The game runs from a script in its ROM, and the gunfights are easy to spot in it. A gunfight command gives the scene’s first and last frames, the window for the shot, where the game goes if you miss and where it goes if you hit. For the very first gunman, the window is disc frames 3236 to 3251, and the muzzle flash comes four frames in. If you’re reacting to the flash, you’re already late.
Autoplay fired at the start of every window, and every gunfight became a hit. Then my lives started disappearing anyway. So something was killing me. But what?
The culprit was a third kind of command, with one window and the same two exits: “no shot” and “shot.” In some scenes, shooting early is a bonus that skips straight past the next gunfight. In others, shooting is exactly what gets you killed. Nothing in the command says which kind it is. Except, as it turned out, the points. After the two exits, each command lists the points each branch scores:
| Scene | Points for no shot | Points for shooting | What happens if you shoot |
| The first quick draw | 0 | 5555 | You skip the gunfight |
| A later scene | 300 | 0 | Buck loses a life |
| Another | 0 | 0 | Buck loses a life |
So the rule became: shoot only when the shot scores. Twenty minutes of play later, every shot was right and no lives were lost. Buck actually gained two lives on bonuses. And at the end of the first round, after the last gunfight, Buck holsters his gun and rides off into the sunset under “YOU WIN, CONGRATULATIONS!” before the game starts over with the scenes in a new order.
When the data doesn’t tell you which outcome is correct, look for indirect evidence. Here, the designers never wrote down “this one’s a trap.” But they did decide what to reward, and the reward gave the answer away.
There was one more surprise. In two scenes, the stricter windows the game uses in later rounds can’t be hit at all: the window closes before it opens. That isn’t an emulator bug. It’s a bug in the game’s own data, shipped in 1984 and sitting there ever since! One of them leads straight into another gunfight, which autoplay wins. The other could cost a life in a later round, though it never came up in testing. An instrument that reads the game’s data closely enough will find the game’s own mistakes, too.
Cobra Command: Learning the Rules by Breaking Them
I’ll come back to this one as the case study below. It’s the game where I had to work out almost every rule by watching autoplay fail.
Dragon’s Lair: When the Answer Key Has No Answers
And then there’s the original. Dragon’s Lair keeps a list in RAM of every input it’s watching for during a scene. Each entry names an input, up to three time windows, and an outcome code for each window: which stretch of the disc to play next. That sounds like everything autoplay needs. It isn’t. Nothing in the list says which outcome is good. An outcome code is just a number. It might be the way on, or it might be Dirk getting eaten.

That image is showing the end of the line, as autoplay sees it: the sword, the dragon, the bubble and the kiss, disc frames 30760 to 31120.
So here, and only here, autoplay has to genuinely think ahead. When a new set of moves opens, it takes a copy of the whole emulated machine and tries each move on the copy, running it ahead many times faster than real time to see whether Dirk loses a life. The determinism I mentioned earlier is what makes this possible: a copy of the machine is a perfect prediction of the real one.
Even then, it took several rounds of failure to get right:
- The game sometimes stores a press and acts on it about a second later, so “the window closed” doesn’t mean “the move was taken.”
- Some fatal moves don’t kill Dirk right away. They open new moves, and the death comes six or seven seconds later. So a move only counts as safe if the move after it can be survived too.
- The planning is slow enough to stutter the picture, so it now runs on its own thread, a few moves ahead, checking that the real game is still in the state the plan expected before each press.
Result: it plays the original arcade version and the fan-made enhancements I’ve tested it with to the rescue of Daphne, without losing a life. That’s about eleven minutes of game time: Dirk’s sword finds the dragon at disc frame 30725. In the headless tracer, the whole game takes about fifteen seconds. Space Ace uses the same list, and autoplay beats that too at every skill level.
Here it is in the window, from the start of a game, in real time. Every move is autoplay’s:

Case Study: Learning Cobra Command One Death at a Time
Cobra Command (Data East, 1984) puts you in the cockpit of an attack helicopter, fighting through New York, the desert and the sea. The board draws the cockpit, the radar and a gun sight over the film. Some scenes want you to shoot targets. Others want you to dodge.

That image is showing autoplay at work in stage 2: the sight sits in the target’s brackets, and the tracers are on their way.
I started with nothing but a probe that could log the game’s memory every frame. Here’s how it went, in order, because the order is the point.
Do nothing. With no input at all, the first scene runs from disc frame 4323 to 4670, then the game searches to a death scene. That gave me a target: something must happen between those frames.
Try everything. I held each control in turn through the end of the scene. Up, down, left, right and missiles all died. The guns survived. So that scene wants shooting.
Try holding it. Holding the guns from the start of the scene died. A single short burst died. Holding them from just before the target appeared survived. So there’s a window, and holding the trigger down from too early stops it counting.
Read the record. Comparing memory in a run that lived with one that died showed a byte counting 3, 2, 1, 0 as the target took hits. Around it sat the scene’s record, copied into memory as the scene started. It held up to three targets, each with a window, a position and a size, and the number of hits wanted. The last ten bytes were ASCII digits, “0777207889”: the death scene’s first and last frames, ready to send to the player.
Steering scenes. A second kind of record asks for a direction. Autoplay held the direction while the window was open, and died. Reading the code showed why. On the window’s very first frame, the game checks the direction, and fails you if you’re not already holding it. You have to anticipate.
8FD2 A5 17 LDA $17 ; the controls
8FD4 25 01 AND $01 ; the direction this scene wants
8FD6 F0 26 BEQ $8FFE ; not held: fail the scene
Anticipate, but by how much? I had autoplay start holding ten frames early. It still failed some scenes. The game’s own frame counter, it turned out, only steps every 11 frames. Ten frames of lead could fall between two steps, so the “early” press arrived on the window’s first frame anyway. Sixteen frames fixed it.
The sight wanders. Holding a direction to dodge also moves the gun sight, and the game never re-centers it. The next target scene sent its bullets to the corner of the screen. So autoplay has to aim. My first attempt sent the sight the wrong way on both axes: the byte I’d assumed was “across” moves with up and down. A test with one short press per direction settled it.
Targets that can’t be killed. Stage 2 had a target that wanted 240 hits. That’s not a hit count. Reading the missile code showed that a missile, with the sight on the target and at least five frames to spare, wipes out the whole group at once. Those 240-hit targets are the game’s way of saying “use a missile.”
And once more, at the other end. When I moved autoplay from the probe into the emulator itself, the first steering scene failed again. The game checks the direction a second time, on the first frame after the window closes. The probe had happened to keep holding a moment longer. The emulator’s version let go exactly on time, which was exactly too soon.
Result: all ten stages and the ending, without losing a life. The ending is the enemy base going up under a big “END”, followed by the staff credits, where the game lists the high scores as the “CAST”. Autoplay, with 336,900 points, gets top billing.
The last one from my list above is my favorite. The game checks the same condition at both ends of the window. A tester would call that a boundary. It’s precisely where off-by-one mistakes live, and here it was the game, not me, that was being strict.
Look back at that list. Almost every rule I learned, I learned because autoplay (my test oracle, remember) failed in a specific, repeatable way. Each failure was a wrong assumption, some of them the AI’s and some of them mine, and the game corrected every one. That’s the same pattern as the drifting replay in Gauntlet Reforged: I wasn’t hunting for rules, I was trying to build an instrument, and the instrument refused to work until I understood the system. This is why the AI couldn’t really help here. I didn’t know the system any better than I did.
Heuristics for Building a Reading Agent
While the AI agent couldn’t help with understanding the system, it could help me encode my understanding and then play that understanding back to look for regressions. Building that agent provided some lessons.
- Look for the answer in the system first. If the system judges an input, it holds the expected answer somewhere. Find it before you build anything clever.
- When the answer isn’t there, look for its fingerprints. Points, lamps and where each branch leads can all give away which outcome is right.
- Check both edges of every window. A condition checked as a window opens may be checked again as it closes.
- Know your clock’s resolution. A counter that steps every 11 frames can’t see a 10-frame lead.
- Pressed is not the same as held. Some games want a new press, not a held control.
- Do nothing first. A run with no input tells you where the first failure is, which tells you where to look.
- A win proves reachability, not fidelity. Autoplay reaching the ending proves the emulator doesn’t break the game along the way. It doesn’t prove every frame looks right. That still needs a person, though now the person can watch a recording of the whole run instead of having to play it.
- Know how a value can be reached. A frame number can be reached by playing up to it or by a search jumping onto it, and only one of those means the scene is over.
- Not every failure is yours. When the game’s own data is impossible, write it down and move on.
Recording the Runs
A headless run that reaches the ending is good evidence, but it’s evidence only I can see: a log of disc searches and moves. So Laser Vault learned one more trick. It can record a game to a video file instead of showing it in a window. With autoplay on, that’s a complete play-through, start to finish, with nobody at the controls.
Recording leans on the same determinism as everything else. Each frame of video runs the game for exactly one frame’s worth of its time, draws the picture exactly as the window would, overlay and all, and takes exactly that frame’s worth of sound from the disc and from the board’s own chips. Nothing is tied to the wall clock, so the video has every frame, in step with its sound, however busy the computer is. It usually runs several times faster than real time, and the same options make the same video every time.
Here are the six games played through by autoplay, from the attract mode to each game’s own ending:
- Dragon’s Lair (10:54), to the rescue of Daphne
- Space Ace (7:13), at Ace level, to Borf’s defeat
- Badlands (4:52), to Buck riding into the sunset
- Esh’s Aurunmilla (13:10), to the escape from Esh’s fortress
- Cobra Command (17:30), through all ten stages to the credits
- Cliff Hanger (14:18), to “YOU HAVE COMPLETED THIS CHALLENGE”
If you ever wanted to see the games play out, but didn’t want to have to actually play them, there’s your chance!
Note that my Cliff Hanger video is on my Internet Archive repo, not my YouTube channel. The reason is that the 1983 game does not use original animation. Instead, it’s an interactive movie edited entirely from two highly protected anime films: Hayao Miyazaki’s The Castle of Cagliostro and The Mystery of Mamo. I found that when you upload gameplay footage of this game, YouTube’s automated systems don’t register it as “video game footage.” They register it as a ripped, copyrighted movie owned by TMS Entertainment or Toho. You can appeal on the grounds of fair use, which I have done, but that can take a bit of time.
Making these videos turned up three more lessons, none of them about the emulator itself.
The stopping point is a boundary, too. Each recording stops when the disc reaches the last frame of the game’s ending. The first version of that check also insisted that the disc was still playing. Dragon’s Lair starts its next search on the very frame it reaches the end, so the disc was already searching, the check never fired, and the recording ran on into the next round. The second version dropped that condition, and then Cobra Command stopped at nine minutes: the game searched to the start of stage 7, which happens to sit right next to where the credits end, and the disc landed inside the range the check was watching. A frame number can be reached two ways, by playing up to it or by jumping onto it, and only one of those means “the end.”
Check what the player sees. I had confidently noted that Badlands has no ending: it just goes round and round, choosing new scenes. That came from watching where the disc went in the headless logs. When the recording was finished and I looked at the picture, there it was: “YOU WIN, CONGRATULATIONS!” over Buck riding off into the sunset, and only then the next round. The logs told the truth about the disc. They just weren’t the whole truth about the game.
Look at the whole run, not where it stopped. At one point I doubted my own documentation, which said Dragon’s Lair reaches the rescue after about eleven minutes, because a trace stopped at about that point showed Dirk on the flying horse, not in the dragon’s lair. The documentation was right. The rescue had already happened, half a minute earlier, and the game had carried straight on into its next round. I had judged the run by its last frame instead of by what happened along the way.
All three are versions of the same mistake: treating the first piece of evidence as the whole of it. A recording is useful precisely because it doesn’t let you do that. It’s the whole run, every frame, for anyone to watch. This has lessons for any sort of automation, of course.
Listening for What Isn’t There
The last stretch of work on Laser Vault was about sound, and it turned out to be the most interesting testing of the lot, because each time the question was about something missing.
Was the silence a bug? Two of the games, Us vs. Them and M.A.C.H. 3, share a sound board with a speech chip on it, the SP0250, which Laser Vault didn’t emulate. Once it did, Us vs. Them talked: I could hear its countdown on the skill-select screen. M.A.C.H. 3 didn’t. I heard its sound effects, but no speech, and the obvious conclusion was that the new chip wasn’t working for that game. But why?
Well, as it turns out, it was working. The game never speaks! Over five minutes of attract mode and play, it sent the chip thirteen frames, and not one was the kind that makes a voice; they were silence, and one frame of shaped noise switched on and off. The game’s sound program settled it: it has room for no speech at all, and every frame it sends is built from a handful of fixed values. What that showed me is that M.A.C.H. 3 uses its speech chip as a noise maker. The silence was the correct output.
“The audio files have everything you need.” GP World, Sega’s racing game, raced in silence: the cabinet’s engine, tire and crash sounds came from circuits on a separate sound board. I couldn’t find recordings of them anywhere, and what I was told, more than once, was that the disc’s soundtrack had all the sound the game needs.
That’s a claim, and claims can be tested. Measured, the soundtrack lines up with the picture exactly, and under all the driving footage there’s a steady, noisy sound, the same in both channels, which is what road and engine noise looks like. I listened to clips: crowd noise and music on the title cards, a steady drone under the road, and the announcer saying “start your engines”. So far, so true.
Then a recording of a race showed the problem. The moment the race starts, the game itself sends the laserdisc player the commands to switch both sound channels off, and they stay off for the whole race! So that was odd. The sound is on the disc; the game just never plays it while you drive. Thus, the claim was half true, which is the hardest kind of claim to catch, because the half you check first holds up.
When nothing can tell you what’s right. That left the sound board itself. The game’s code says exactly what it asks for: the engine on, its revs on a scale of 0 to 63, a coin chime, the countdown beeps, the start signal, the tires squealing, and the moments of a gear change and a crash. What it doesn’t say is what any of that sounded like, and there are no recordings and no circuit diagrams to ask. So Laser Vault‘s racing sounds are its own: an engine whose pitch follows the revs, beeps, a squeal, a thump and a crash. The documentation says, plainly, that they’re a stand-in and not a copy.
This is the one place in the project where I had no oracle at all, and I think the honest response to that is to make the gap visible. An invented sound labelled as invented is fine. An invented sound passed off as the original would be a quiet lie, and the kind of thing the next tester would waste a week on.
This is also an area where AI would not be able to help you. Sure, an AI could measure the soundtrack. It could probably even read the game’s code and count every frame sent to the speech chip. But what the AI couldn’t do was actually hear any of it. My ears confirmed what the measurements couldn’t, and the code explained silences that my ears had taken for bugs.
Heuristics for Missing Things
- Silence is a result, too. Before you call missing output a bug, find out whether the system ever produced it.
- Test the claim, not the claimant. A forum consensus is no more an oracle than a famous emulator is.
- Half-true claims hide best. Check every part of a claim, especially the parts after the first one holds up.
- Label invention as invention. Where nothing can tell you what’s correct, say so where people will read it.
Sample Charters
As in the other posts, each charter follows the form: explore a target, with some resources, to discover some information. Time boxes are suggestions.
| Charter | Track | Time box |
| Explore each game’s attract mode left running, to discover the player losing its place on the disc | Human | 30 min |
| Explore deaths in every scene of one game, to discover a search to the wrong place | Human | 45 min |
| Explore every DIP switch against the operator manual, to discover switches that disagree with it | Both | 45 min |
| Explore each ROM set of a game with autoplay, to discover a revision it can’t finish | Both | 60 min |
| Explore presses at the very edges of move windows, to discover timing that differs from the real machine | Human | 45 min |
| Explore the game’s code wherever MAME’s notes and Laser Vault disagree, to discover which is right | Technical | 60 min |
| Explore two headless runs of the same input, frame by frame, to discover anything that isn’t deterministic | Technical | 30 min |
| Explore a recorded play-through at normal speed, to discover glitches in the picture or sound that autoplay can’t see | Human | 60 min |
| Explore what a game sends its sound chips against what you hear, to discover sounds that are missing, and silences that are correct | Both | 45 min |
At the end of a session, write down what was covered, what was found and what is still unexplored. The unexplored list becomes the next charter.
Game Over
The thing I keep coming back to with this project is how much these old games know about themselves. Every one of them carries its own answer key, its own self-test and its own record of what it expects. Testing an emulator turned out to be mostly a matter of listening to the games. When the emulator was wrong, they said so, usually by resetting, freezing or killing whatever character I was playing in a given game. When an expert source was wrong, and if often was, the game’s code said so too.
And the autoplay that started as a party trick, watching Dragon’s Lair play itself, turned into the most thorough test the emulator has. If a machine I built can carry a forty-year-old game through every scene to its ending, without the game noticing anything amiss, that’s about as good a sign as I’m going to get.
Plus, I finally got to see Daphne rescued. Fifty cents at a time, I never managed it in 1983.
What’s Next?
Well, I’ve had a lot of requests to get a little more in the weeds on how all this stuff actually works. A sort of “under the hood” view, if you will. At first, I was worried that this might alienate a core group of readers but then, as I thought on it, I realized that how all this actually works allows me to talk about instrumented testing. So we’re not quite done with the lasers let. Stay tuned!