Open Cadet: how I rebuilt Space Cadet Pinball as fully open source
By Andrew Nakas · Published July 1, 2026 · Updated August 17, 2026 · ~9 minute read
▶ Play it in your browser · Get the CC0 data package on GitHub to run it on the desktop engine.
Nobody chose to play 3D Pinball for Windows – Space Cadet. It was just there, under Start, then Programs, then Accessories, then Games. At some point on a slow afternoon you clicked it, and then you kept clicking it for years. Microsoft shipped it with Windows from 95 through XP and then dropped it. A lot of people have been quietly annoyed about that ever since.
There is already a good open-source revival of the game. What there wasn't is a version you can just hand to someone. All of them stop in the same place: now go and find your own copy of the original data file. I wanted to get rid of that step. Open Cadet is what came out of it, a from-scratch public-domain replacement for that file, so the open engine runs with no Microsoft files in it.
It took longer than I expected. The hard part was the law, not the art.
The engine was never the problem
Someone else had already done the difficult half. k4zmu2a/SpaceCadetPinball is an MIT-licensed reimplementation of the original game's C++. The physics, the rendering and the table logic have all been rewritten cleanly and given away. It compiles to WebAssembly, which is the only reason a pinball table can run in a browser tab at all. I didn't have to repeat any of that work.
But an engine with nothing to render is a black window. Everything you see and hear lives in one proprietary binary called PINBALL.DAT, plus a pile of .wav files. That means the playfield art, every sprite, the colour palette, the sounds, the table geometry and the physics constants. Microsoft never open-sourced any of it. So every port ends up saying some version of the same sentence: supply your own PINBALL.DAT from a Windows XP install.
In 2026 that is a strange thing to ask someone. The realistic ways to get that file are to still own a machine from twenty years ago, or to download it from somewhere you probably shouldn't. Calling the result open source always felt like a stretch. The engine is free, and then there is a proprietary lump in the middle that everyone agrees not to look at. The only honest way out was to build a new data file from nothing.
The line between a fact and a drawing
This is where most of the project actually went, so I want to be precise about it.
You can't copy Microsoft's data file and put a new label on it. The art and the audio are creative work and they are copyrighted. But copyright covers expression and not facts, and a file like this one is a mixture of both jammed into the same binary. Separating them is the whole job. It is the same principle Wine and ReactOS run on. You are allowed to write an original thing that interoperates with a documented format.
So I went through the file and sorted every part of it into one of two piles.
The first pile is facts, and those stay. How the container lays out its groups and records. The group names and type codes the engine looks up. The table geometry, meaning wall-segment coordinates, bumper positions and the boundary. The physics parameters: elasticity, gravity, ball radius, thresholds. And the filename contract, the fact that the engine goes looking for sound12.wav specifically. Those are measurements and specifications. They are what the engine has to find in order to work at all, and under the reasoning in Feist v. Rural, facts and function-dictated data aren't copyrightable. Reusing them is what interoperability means.
The second pile is somebody's creative work, and all of it had to be replaced. Every pixel of art. The palette. Every sound. Every string of text. Nothing copied, sampled or traced. That is the pile that would infringe, so that is the pile I rebuilt.
The format itself was never a mystery, because the engine is open source and it has to parse the file. k4zmu2a's partman.cpp lays out the byte structure plainly: a header signature, then N groups, each holding typed entries. The types are 8-bit sprites, a 256-colour palette, 16-bit depth maps, integer arrays for geometry, float arrays for physics, and strings. Knowing the shape of a valid file was easy. The open question was what to put inside it.
Building it
The art
The obvious approach is to clone the original pixel for pixel, same blue gradient and all. That is legally reckless and also a bit pointless. Open Cadet is the same kind of game and its own thing to look at: still a top-down space table, but darker, with a neon look. Every sprite is drawn from scratch. That covers the playfield, the ball, the flippers and their animation frames, the bumpers in lit and unlit states, the plunger, the rollover lights, the targets and the digits on the score panel.
Two constraints in the format shaped this more than any aesthetic decision did. The first is that every 8-bit sprite shares one 256-colour palette. Not one each, one total. The background, the ball and every object have to agree, so nothing can be designed in isolation. The palette has to be planned as a single set up front, and changing one colour late means checking everything again.
The second one took me a while to appreciate. Each sprite carries a matching 16-bit depth map, and those z-values are not decoration. The engine uses them as functional geometry. They tell the renderer the viewing angle and how a surface catches light, and they are why a flat 2D table appears to sit at that convincing three-quarter tilt. Get them wrong and the perspective comes unstuck, which looks worse than bad art does. So the depth magnitudes stayed as the facts they are, driving the projection, while every silhouette on top of them got redrawn.
The sound
The audio doesn't live in the data file. The engine loads separate .wav files by number, so sound1.wav, sound12.wav and so on. The mapping from number to event is a fact. The audio itself is not.
All 47 of them are synthesized from scratch with numpy. Sine tones, filtered noise and decay envelopes, shaped into plunger releases, bumper hits, flipper clacks, drains and mission jingles. Nothing was recorded and nothing was sampled. Each one is a waveform computed and written out as a 16-bit mono file. They don't sound like the originals. They sound like a pinball table, which is the part that matters.
The moment it loaded
A builder script packs it all into a PINBALL.DAT: header struct, quantised sprites, the shared palette, depth maps, the geometry and physics arrays, and original strings for the labels and mission names. There is no partial credit. The engine's loader either accepts the file or it doesn't, and for a long time it didn't.
When it finally did, the header read back PARTOUT(4.0)RESOURCE followed by the title string, Open Cadet. The file had stopped being a hopeful pile of bytes. It is about 900 KB, roughly 1.4 MB of table data in total, sitting next to the engine's WebAssembly binary.
One thing amused me later. When I went to package the files as a standalone release, I found they didn't exist anywhere as loose files. They only lived inside the Emscripten virtual filesystem, baked into the .data blob. Getting them out meant reading the file table in the generated JavaScript, which lists every file as a byte range into that blob, then slicing them out by offset. My own data, extracted from my own build, by parsing the loader. There is a README.txt in there too, which is an Emscripten build artifact and not mine, so it didn't make the cut.
Getting it into a browser tab
This part was easy, because the engine already targets WebAssembly. The build is an ordinary Emscripten bundle: a .wasm binary, a JavaScript loader, and the packed virtual filesystem holding PINBALL.DAT and the 47 sounds. No plugins and no install.
One thing worth knowing if you build something similar. Browsers refuse to play audio until the user interacts with the page, so a game that starts silently looks broken to anyone who doesn't click first. The loader waits for your first click or keypress and resumes the audio context then. It is four lines of code, and it decides whether people think the sound works.
Unlike most of what is on this site, this isn't Wine or DOSBox. There is no emulated CPU underneath. It is a native port of the game compiled to WebAssembly, so it runs at full speed. You can play it right now.
It's CC0, so please take it
The whole point was removing restrictions, so the data package is public domain under CC0. The art, the palette, the sounds and the strings are all original work dedicated to the public domain. Copy it, change it, sell it, don't credit me. Paired with the MIT engine, that makes this the first version of the game that is free the whole way down.
To be plain about what this is. Open Cadet has nothing to do with Microsoft and isn't endorsed by them. It contains none of the original's artwork, audio or wording. It shares only the functional format and physics that interoperating requires. If you want the real Space Cadet, this isn't it and isn't trying to be.
One table shouldn't be the ceiling
This is the part I keep thinking about. Once there is an open engine and an open data format with a working builder, a table stops being a finished thing and becomes a template.
Everything after that is art and tuning. A new table means a new set of sprites, a new set of sounds, and a geometry and physics description run through the same builder. The engine doesn't change at all. That is a very different proposition from before, when making a board meant reverse-engineering a proprietary file first. And because the baseline is CC0, anyone can fork the neon table, replace the art, retune the physics until it feels right, and put the result out freely. Every board built that way carries the same guarantee: no proprietary files, so anyone can host it and anyone can play it.
Space Cadet was the proof that the approach works. Take something locked behind a proprietary data file, separate the facts from the art, rebuild the art from scratch, and give it away. The engine has been free for years. There is no good reason the content can't be too.
The data package, the builder and the notes on what was reused and why are on GitHub. If I have got something wrong, technically or legally, I would like to hear it.