Quake Tactical Computer
Quake II's in-fiction help computer, rebuilt as an iPhone and Apple Watch app. An old Mac streams its live game state to your phone and wrist — vitals, objectives, damage haptics and in-game sounds — and logs the match as a workout.
![]()
Quake II's help computer is the F1 screen — the marine's own terminal, showing health, mission and how far through it you are. This is that, on your wrist. An old Mac running Quake streams its live game state onto the network, an iPhone relays it to an Apple Watch, and the watch renders an amber-phosphor terminal with health, armour, ammo, weapon, powerups and objectives. It buzzes when you take damage, plays the game's pickup and pain sounds, and records the whole match as a Health workout with heart rate and calories. Quake II, Quake 1 and the new Quake III port all drive the same companion.
# How it works
A small patch in the engine, cl_watchlink.c, reads the player's state once a frame and sends it out as newline-delimited JSON over UDP. JSON rather than a packed binary struct on purpose: the PowerPC Macs are big-endian, so a text format stays byte-order-proof and is trivial to debug with nc -ul 27999. The feed is off unless you set a watch_host cvar, so the normal build and the benchmarks behave identically. Set it to auto and the Mac discovers the phone itself — the phone advertises a Bonjour service, _q2watch._udp, and the Mac browses for it and resolves the address.
The iPhone is a first-class half of the companion, not just a pipe. It holds the UDP socket, decodes each line into a game-state model, draws the same HUD on its own screen, and relays the state to the watch over WatchConnectivity — latest-wins for the vitals heartbeat, low-latency messages for one-off events. The watch can open a socket itself, but background Wi-Fi listening on watchOS is unreliable and power-hungry; letting the phone hold the connection is the pattern that holds up for a whole game.
# The watch
One Alien-style CRT terminal: phosphor readouts on near-black, amber for health, green for armour, cyan for ammo, turning red as health drops. Static scanlines and a refresh sweep sit over the top, and a glitch tears the screen at random, on a tap and on every hit. You swipe between Vitals, Status, Comms and Workout pages. The phosphor and CRT effects live in PhosphorUI.swift.
This clip is recorded straight off the iPhone while playing, so the screen effects show in motion:
A minute of play captured on the iPhone in landscape. Game sound is down; the audio is the companion's own event sounds. Watch the readouts glitch and redden as the health drops.
The workout screen earns its place twice over. watchOS gives an ordinary app no way to stay on-screen, so the keep-awake mechanism is a real HKWorkoutSession — an "Other" workout that holds the always-on display, collects heart rate and active energy while you play, and saves to Health when the game ends. Damage haptics are there too, off by default: every watchOS haptic carries an audible tick you can't silence without Silent Mode, so it's a toggle for when you want it.
# On the desk
# Watch a game
The game's own sound is turned right down in these — what you can hear is the watch and phone playing Quake's pickup, pain and jump sounds off the live event feed.
A full game on the 2005 iMac G5, with the iPhone's screen overlaid in portrait. The capture is choppy because screen-recording is hard work for a G5; on the screen itself it runs smooth.
The same setup with the phone in landscape, where the HUD lays out as a wider panel. Watch the health drop into the red and the cracked-glass skull take over at zero.
Filmed on a GoPro so you can see the iMac, the phone and the watch at once. They all carry the same state, and they all flatline together at the end.
# Every game
The companion doesn't know or care which game is on the other end. Quake 1 and Quake III speak the same JSON on the same UDP port (27999) and the same Bonjour service, so one app works across all three, unchanged. The difference is the objectives panel: Quake 1 and Quake III have no F1 help computer, so the watch drops that panel and shows the level with the vitals. Health, armour, ammo, weapon, powerups, pickups and the damage buzz all work the same.
# The engine feed
The feed lives in the two engine repos, behind the watch_host cvar. Both emit the same lines:
{"t":"vitals","hp":87,"armor":50,"ammo":24,"sel":"Super Shotgun","frags":3,"pu":{"icon":"quad","sec":18}}
{"t":"event","kind":"centerprint","msg":"You got the Railgun"}
{"t":"event","kind":"damage","health":1,"armor":0,"ammo":0}
{"t":"event","kind":"psound","msg":"jump1"} The Quake II side is cl_watchlink.c; the Quake 1 and Quake III sides are its QuakeSpasm and Quake III siblings. The full wire format is documented in WATCHLINK.md, and a short Python listener lets you point watch_host at your dev machine and watch the JSON stream with no watch in the loop.