It took a couple LLM prompts plus a few simple syntax tweaks from my side and now DOOM on the Apple Watch has audio. 🤯 It was almost as easy as typing iddqd
. To get around lack of SDL support, AVFoundation is used. Check out the GitHub commits.
Category: Fun
This weekend I vibe coded a tribute to ’90s Gateways, 3Dfx, and DOS with DJGPP, Glide, and Allegro. I originally set up my build environment in DOSBox-X, but then moved to cross-compilation from Linux.
From there I dropped the game into 86Box, which I already had configured with an emulated Voodoo3 3000, for quicker testing.
Finally, I ran it on my Pentium 3 machine with Windows 98 and a real Voodoo3 3000. ChatGPT gets most of the credit for the C code that runs the game, I get credit for the cow voiceover work. 🐄
Merry DOS-mas!
Now I can control my LED Christmas Tree from DOS! 🎄💾 I hope to share more details on the code + dev stack, but basically I used:
- Open Watcom V2
- Packet drivers
- mTCP
- 86Box + Windows 11 for development
- A Gateway 2000 ColorBook 486 for the final test 😎
Pinball Arcade Trial Achievement
64×64 LED Matrix + Doom

From the weekend hacks department: I now have DOOM running on my 64×64 matrix!
- A Raspberry Pi 2B drives the 64×64 3mm pitch LED matrix via the Matrix Bonnet.
- I’m using SDL2 to handle scaling the original resolution of 320×200 down to 64×40 as well as the game ticks, user input, and sound.
- It was mainly an exercise in Makefiles and linking C libraries – the hard parts were done in the libraries. 🙌 Generic Doom / Rpi RGB LED Matrix
- The matrix is covered with an acrylic panel that smooths out the LEDs and makes it pleasant to look at.
- The flickering effect isn’t seen in real life, it’s just how the camera captured it.
GitHub repo: https://github.com/twstokes/doom-matrix
I first want to acknowledge that I did the thing that I try to never do: I showed off a snazzy project, left some hints here and there of how it worked, said I would follow up with full details… and never did. That’s lame.
I’ve had multiple people reach out for more info and I’m glad they did, since that’s pushed me to finally get some repos public and this belated follow-up written. Apologies!
To jump straight to it, I’ve published these two repos:
Update March 2025: The main repo URL has been updated as this is now a library.
Hardware
Let’s first go over the hardware involved. The most important piece, of course, is the Alfa-Zeta XY5.
In my case, the 14×28 board was made up of two 7×28 panels connected together via RJ-11.

The panels are pricey, but they can be thought of as “hardware easy-mode”. Alfa-Zeta has done the hard job building the controller that drives the hardware and all we have to do is supply power and an RS-485 signal that abides by their protocol.
If you purchase a panel from them there are two important documents to request:
- The main manual that describes the specs, features, and things like the DIP switch settings.
- The protocol for sending commands to the controllers (which is really simple).
These can easily found by searching around, but if you own a panel the company should supply them. Most of the protocol can be deduced by looking at open source code.
Components
- Alfa-Zeta XY5 – 14×28 Flip Dot display
- NodeMCU ESP8266 – MCU
- 3.3v RS-485 to TTL – Allows the MCU to communicate over RS-485
- ALITOVE AC 100-240V to DC 24V 5A Power Supply – Required to power the panels
- BINZET DC 12V 24V to 5V 5A – Optional, used to step down power to the MCU so we have one power source
The 24V -> 5V converter isn’t necessary if you supply power to the MCU independently, say through a USB power adapter.
Connection overview

- 24V DC goes to both panels
- 24V DC goes to the step-down converter, 5V DC goes to the 5V input of the NodeMCU
- NodeMCU is wired to the RS-485 to TTL converter
VCC -> 3.3v
Gnd -> Gnd
DE -> 3.3v pulled high because we're always transmitting
RE -> 3.3v pulled high because we're always transmitting
DI -> TX[x] x being 0 or higher depending on board
RO -> RX[x] most boards only have the main serial IO, but boards like the Mega have multiple
- RS-485 -> Only one panel controller – not both

An Arduino Mega is driving the board in this photo.
Software
The MCU
See https://github.com/twstokes/flipdots for the code that runs on the MCU.
At the moment there isn’t much to it – you can either compile the firmware to run in a mode that writes data from UDP packets to the board, or you can draw “locally” using Adafruit GFX methods.
See the README in the repo above for more details.
iOS / iPadOS / macOS
See https://github.com/twstokes/flipdots-ios for the code that runs on these devices.
Semi-interestingly I utilized Adafruit GFX again, this time via swift-gfx-wrapper to draw to the board over UDP. It’s hacky and experimental, but that’s part of the fun.
See the README in the repo above for more details.
DOOM on the Apple Watch
I know this has been done, but I hadn’t done it, so it was my weekend nerd snipe. (no game audio)
Edit September 2025: Audio was implemented!
This was a lot easier thanks to doomgeneric!
Basic breakdown
Since doomgeneric exposes the framebuffer, I throw that into an SKTexture and that gets added to a node in the SpriteKit scene, which is subclassed to override the update
method to call doomgeneric_Tick()
. Objective-C is used for interop between C and Swift, and fulfills most of the functions listed here. SwiftUI ultimately outputs the scene.
Very few tweaks needed to be made in doomgeneric itself.
They were basically:
- Conditional compilation for a few calls that watchOS didn’t support (and we didn’t need).
- Tweaking the 32-bit color bit offsets.
- Handling a crash related to passing in arguments.
- On watchOS we pass the absolute path of the WAD file in the main bundle to the engine.
- Adjusting some SDL2 includes so headers could be found.
GitHub repo: https://github.com/twstokes/AppleGenericDoom
My C64 Setup
I haven’t spent as much time on my Commodore 64 as my other retrocomputers (which can seem modern in comparison), but my explorations over time are trending towards older hardware. I can only assume that my final stop will be an abacus.

I have three C64s all passed down from my dad. One had been devoted to a home alarm system (of course we still have the schematics), but by the time I came around it was only used for playing half-working totally not bootlegged games.
A sampling of some favorite software from my childhood:
- The Way of the Exploding Fist
- SAM (bonus: SAM in the browser!)
- Borrowed Time
- Suspended
- Impossible Mission (one of those bootlegs that hilariously kinda worked)
Retrocomputing plans
I hope to be able to fully restore at least one of these machines this year. The one pictured above powers on and is fully functional, but some flakiness at startup tells me that it’s overdue for a recap.
One not-so-smart thing I did when I unpacked all of this equipment was powering it up with the original C64 power supply. That’s a risky move and likely to damage the C64 with bad power, so I’ve since replaced it with a new modern one (see the parts list).
I’m not interested (nor do I have the space) to use these machines in the “pure way” with a CRT and 1541 drives, although I have both. Maybe down the road that would be fun, but for now I’m utilizing modern gadgets from the wonderful C64 aftermarket community.
Current parts list
- SD2IEC+ powered by the cassette port
- Blue EPYX Fastload Reloaded
- PS2 Mouse to 1351 Adapter
- Intellimouse PS/2 with serial support and trackball
- C64 power supply
- WiModem232 w/OLED wifi modem emulator
- RetroTINK 2X-Pro component / S-Video to HDMI
- User port breakout / project board V2.0 w/ reset
Next steps and ideas
- Restore one or more C64s by recapping / adding heatsinks
- Fix my joysticks / get new ones
- Play with the WiModem232 and connect to the Internet
- Play with the user port breakout and connect it to an Arduino
Cool Retro Term
It may not be real, but it’s so much fun!

From the seasonal hacks department, here’s my toy app to make it snow on macOS. ❄️
https://github.com/twstokes/snowflakes
How it works:
When the app is told to make it snow it adds full-screen non-interactive windows on each display and inside those windows adds a SpriteKit view with a scene inside that contains emitters.
That’s basically it!