Pure-Go Quake 1 (id Tech 1, 1996) engine for bare-metal TamaGo + UEFI, hand-ported from the tyrquake NetQuake (single-player) branch and wrapped in cloud-boot virtio adapters.
Sibling of go-doom/engine in the DOOM → Quake roadmap. Family siblings: go-quake2 (id Tech 2 — reserved) and go-quake3 (id Tech 3 — reserved).
Phase Q-1a in flight. Hand-porting tyrquake-NQ to pure Go, one module at a time, with parity tests against the upstream C behaviour.
| Component | State |
|---|---|
| Porting conventions (CONVENTIONS.md) | done |
reference/ tyrquake source mirror (commit 6531579) |
done |
common: mathlib, crc, cmd, cvar, zone, qstr, sizebuf, qpath, qparse, msg, qargs, pak, vfs, wad, keys, protocol, anorms |
done (17 packages · 100% cov) |
models: bspfile, mdl, spr, model (magic-bytes dispatcher) |
done (4 packages · 100% cov) |
bsptrace (Mod_HullPointContents + Mod_TraceHull) |
done (87.8% cov — backfill backlog) |
progs (QuakeC VM — 65 opcodes + edicts + parser + 9 math builtins) |
done (100% cov) |
server (host + sv_world + sv_main + sv_phys + sv_user) |
pending |
client + soft renderer (r_*, d_*, cl_*) |
pending |
sound (snd_dma + snd_mem + snd_mix) |
pending |
| 64 remaining QuakeC builtins (need sv_world for traceline/findradius/...) | pending |
backend/tamago/ virtio adapters |
pending (mirrors godoom shape) |
embedpak/ shareware loader |
pending |
cmd/harvest-reference oracle |
pending |
phaseQ1_oci_quake1_soft_boot.go (in cloud-boot/tamago-uefi) |
pending |
| provable-test gates A · B · C-1 · C-2 | pending |
Phase Q-1a's open question Q-1 was first answered
"ccgo-transpile" — a mechanical C-to-Go translation via
modernc.org/ccgo/v4 of a Linux-portable Quake source tree. We tried
two upstreams in earnest and documented why both failed:
- quakeforge uses C23 (
nullptr,#embed, etc.); ccgo's parser predates C23. See internal/transpile/FINDING-C23.md. - tyrquake is clean C99 but ccgo's internal checker drops symbols
on real game-code constructs (extern globals, function-pointer
dispatch tables, packed structs). The link stage then reports
"undefined: com_argc" even though
com_argcIS defined incommon.c. See internal/transpile/FINDING-CCGO-LIMITS.md.
ccgo is production-quality for SQLite-class C (it's literally how
modernc.org/sqlite is built), but Quake's surface exposes the v4
checker's soft spots faster than ccgo's curated test corpus does.
The remaining options were:
| Option | Velocity | Audit posture | Picked |
|---|---|---|---|
Fork darkliquid/ironwail-go |
fastest | low (AI-assisted upstream, unreviewed) | no |
| Hand-port (LLM-assisted, from tyrquake-NQ) | medium | high (every line under operator's git history) | YES |
| Wait on ccgo upstream maturity | slowest | high | deferred (track as a watch) |
The hand-port also gives us natural alignment with TamaGo constraints
(no malloc churn, no goroutines for the main loop, careful with go
keyword pressure on the bare-metal scheduler) that no transpiled engine
would ship with out of the box.
For each tyrquake C module:
- Mirror the C source verbatim into
reference/(no edits) so the diff against upstream stays inspectable. - Port the module under
<module>/<module>.gofollowing the rules in CONVENTIONS.md. Hand-written or LLM- drafted then operator-reviewed; either way the final Go is operator-owned. - Test parity against the C upstream: feed the same input through both and assert byte-equal output where the operation is deterministic (math, hash, parse), or bounded-tolerance where it is not (float trig).
- Coverage target = 100% of the new Go package (the project-wide convention inherited from go-virtio and go-deltasync).
- Commit with a
port: <module>prefix. Each commit lands one buildable, tested module so bisecting the engine remains practical.
pak0.pak for the shareware Episode 1 (Dimension of the Doomed) is
freely redistributable per id Software's grant and lands in-tree under
embedpak/. Operators can override at boot with their own pak0+pak1
via a probe env. CI gates always exercise the committed shareware pak
so the reference oracle is reproducible.
The engine ships a GOOS=js GOARCH=wasm build alongside the bare-metal
TamaGo target. The backend/wasm adapter wires backend.Backend onto
Canvas2D (framebuffer), DOM events (input, with Pointer Lock for
mouse-look), and WebAudio (sound). Two top-level tasks drive it:
task build-wasm # compiles cmd/quake-wasm -> cmd/quake-wasm/web/quake.wasm
task serve-wasm # binds localhost:8080 to cmd/quake-wasm/web/then open http://localhost:8080/ in any modern browser. task wasm
chains both. The single-step build is large (~180 MB; the Go runtime
ships the full stdlib in wasm builds); first-load is a one-shot
cache.
The default wasm build embeds embedpak/empty.pak + the music
tracks, so the binary balloons to ~180 MB the moment a real
shareware pak is dropped in. For browser distribution that's
wasteful — every page load re-fetches the whole thing. Build
with -tags no_embed_assets instead and the wasm payload stays
~11 MB; the pak + music tracks ship as individual layers in an
OCI Distribution v2 registry (any registry that speaks the
OCI spec: Distribution v2, ghcr.io, harbor,
self-hosted, …) and the wasm fetches them on demand the first
time a file is Open'd, with an IndexedDB write-through cache so
reloads in the same browser skip the network round-trip.
# 1. Pack the local pak + music into an OCI image-layout dir.
go run ./cmd/oci-pack-quake --out _oci --ref quake-assets:latest
# 2. Push the layout to any registry. oras [1] is the easy path:
oras push localhost:5000/quake-assets:latest \
--manifest-config /dev/null:application/vnd.go-quake1.config.v1+json \
$(find _oci/blobs/sha256 -type f)
# 3. Build the slim wasm with the registry URL baked in. The
# -X main.OCIReference linker flag is what tells the binary
# which registry to hit at boot.
OCI_REFERENCE=http://localhost:5000/quake-assets:latest task build-wasm-oci
# 4. Serve the wasm + open in a browser.
task serve-wasmThe runtime falls back to the embedded pak (and from there to the
synthetic-asset bootstrap) on any OCI failure, so an unreachable
registry never aborts the boot — it just degrades to the local
default. See engine/ociassets/ for the package + tests.
Every Quake phase inherits the four-gate protocol shipped with DOOM (doom-provable-protocol.md):
- GATE A — engine determinism, BYTE-EQUAL frames at checkpoint tics
- GATE B — guest virtio-gpu fidelity, χ² ≤ tolerance
- GATE C-1 — audio event stream, BYTE-EQUAL CacheSound/PlaySound log
- GATE C-2 — guest WAV bounded tolerance (per-second RMS envelope)
- Wrapper code (
backend/,embedpak/,cmd/,internal/,.github/): BSD-3-Clause - Ported engine packages (
anorms/,bspfile/,bsptrace/,cmd/,crc/,cvar/,keys/,mathlib/,mdl/,model/,msg/,pak/,progs/,protocol/,qargs/,qparse/,qpath/,qstr/,sizebuf/,spr/,vfs/,wad/,zone/): GPL-2.0-or-later (inherited from tyrquake / upstream id Quake source) - The
reference/mirror of tyrquake C: GPL-2.0-or-later (verbatim)
See LICENSE for the full split.
