Quake Tactical Computer app icon

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

Flow diagram: an old Mac running Quake II or Quake 1 sends UDP JSON on port 27999 to an iPhone relay, which forwards it to the Apple Watch over WatchConnectivity. The Mac and phone find each other on the LAN via the Bonjour service _q2watch._udp. The watch shows vitals, objectives, haptics, sounds and a workout session.
The Mac and phone find each other over Bonjour; the feed flows Mac → phone → watch.

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.

Apple Watch tactical computer Vitals screen: 100 HP in large amber numerals, a heart icon reading 79, a HEALTH gauge, ARMOR and AMMO gauges, and WPN BLASTER
Vitals — health, armour, ammo, weapon and live heart rate.
Apple Watch tactical computer Status screen: SECTOR OUTER BASE, two numbered mission objectives, and a PROGRESS section with a KILLS counter
Status — sector and objectives from the F1 help computer.
Apple Watch tactical computer Workout screen: a timer, BPM and KCAL readouts, a green RECORDING status, and Pause and End buttons
Workout — heart rate and calories, and the trick that keeps the screen awake.

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

A 2005 iMac G5 running Quake II with a Strogg enemy on screen, and an iPhone beside it showing the tactical computer HUD at 78 HP
The iMac and the phone, same state on both.
The iMac, the iPhone and an Apple Watch on a wrist, all three showing the cracked-glass red skull death overlay
iMac, phone and watch all flatline together.
Quake II on screen with the iPhone tactical computer HUD in portrait alongside, showing vitals, mission objectives and a comms ticker
The phone HUD in portrait — vitals, mission and comms.
Quake II on screen with the iPhone tactical computer HUD in landscape, showing the cracked-glass skull at 2 HP
The phone HUD in landscape, taking its last hit.

# 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.

# Tech stack

Swift SwiftUI watchOS WatchConnectivity Network framework HealthKit C UDP / JSON Bonjour PowerPC