{"id":50467856,"url":"https://github.com/darkliquid/ironwail-go","last_synced_at":"2026-06-01T08:30:51.524Z","repository":{"id":346882055,"uuid":"1171702084","full_name":"darkliquid/ironwail-go","owner":"darkliquid","description":"An attempt at porting the entirety of the Ironwail source port of Quake from C to Go as an AI learning exercise.","archived":false,"fork":false,"pushed_at":"2026-05-23T19:00:51.000Z","size":8629,"stargazers_count":2,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-23T21:06:11.357Z","etag":null,"topics":["ai","c","educational-project","golang","port","quake"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/darkliquid.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-03T14:15:43.000Z","updated_at":"2026-05-23T19:00:57.000Z","dependencies_parsed_at":null,"dependency_job_id":"e5471692-ee68-4301-8865-c1abe46180fd","html_url":"https://github.com/darkliquid/ironwail-go","commit_stats":null,"previous_names":["darkliquid/ironwail-go"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/darkliquid/ironwail-go","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darkliquid%2Fironwail-go","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darkliquid%2Fironwail-go/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darkliquid%2Fironwail-go/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darkliquid%2Fironwail-go/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/darkliquid","download_url":"https://codeload.github.com/darkliquid/ironwail-go/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darkliquid%2Fironwail-go/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33767434,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-01T02:00:06.963Z","response_time":115,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","c","educational-project","golang","port","quake"],"created_at":"2026-06-01T08:30:50.608Z","updated_at":"2026-06-01T08:30:51.515Z","avatar_url":"https://github.com/darkliquid.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Ironwail Go\n\nIronwail Go is an exercise in porting the entire [Ironwail][1] Quake codebase\nfrom C to Go, for the purposes of learning and education. It is an experiment\nto get more experience with agentic coding and furthermore to learn more about\nthe Quake engine, game programming and indulge in a bit of nostalgia from my\nschool days of hacking together Quake mods and maps.\n\n## Did you say agentic coding? Is this just AI slop?\n\nYes and no. A large portion of the codebase has been written entirely by AI\nagents converting the C code to Go. However, I've been fairly hands on in\nterms of planning and guiding that work, as well as reviewing and making\nmanual changes of my own.\n\nIn terms of tooling, mostly GitHub Copilot has been used, with a smattering of\nother things, but the vast majority of agentic work has been done with\n**Claude Opus 4.6** and **GPT-5.4**.\n\nThe [yggdrasil][3] repo has been invaluable for documenting and mapping the\ngrowing codebase, providing structural overviews and cross-references that help\nagents and humans alike navigate the port.\n\n## Differences from Ironwail\n\nWell, apart from the obvious that this is Go, rather than C, I'm building this\nwith the following changes:\n\n- [gogpu/WebGPU][4] as the canonical gameplay renderer/runtime\n- Dividing the codebase up into packages\n- Use Go stdlib for as much as possible, rather than custom implementations of\n  things from the original C codebase\n\nAdditionally, I'm trying to build it as readable as possible, with extensive\ncommenting and to keep as much of the codebase in Go as practical. The\ncanonical GoGPU renderer keeps the gameplay and engine logic in Go without\nrequiring a separate legacy renderer runtime path.\n\n## Project Status \u0026 Parity\n\nhttps://github.com/user-attachments/assets/b652d2c6-74ce-41bb-90fa-8976262e043a\n\nThe goal of this project is 100% behavioral parity with the original C\n[Ironwail][1] engine through the canonical GoGPU path. Regular parity audits are\ncarried out, and detailed tracking of gaps, workflows, and differences lives in\nthe [Parity Guide](docs/PARITY.md).\n\nIf you are new to the codebase and want to learn how the engine works, please see our\n[Architecture \u0026 Learning Guide](docs/LEARNING_GUIDE.md).\n\nFor the Go-to-QuakeC toolchain and gameplay-language subset used by this\nrepository, see the [QGo / QuakeGo Guide](docs/QGO_QUAKEGO_GUIDE.md).\n\n\u003e **Note:** None of the original Ironwail C developers have reviewed or\n\u003e endorsed the work done in this repository.\n\n## Building\n\nThe toolchain is built around [mise][2] which provides both the tooling and\nthe tasks for running tests, builds, etc.\n\nYou can see what tasks are available to run using `mise tasks`\n\nThe canonical parity/build path is the GoGPU runtime:\n\n- `mise run test`\n- `mise run build`\n- `mise run smoke-map-start`\n- `mise run parity-ref`\n- `mise run parity-go`\n- `mise run parity-compare`\n\nThe parity screenshot harness now targets the GoGPU path by default and\nacts like a real gate:\n\n- `mise run parity-ref` captures deterministic reference screenshots from C\n  Ironwail into `testdata/parity/reference/`\n- `mise run parity-go` captures the matching Go GoGPU screenshots into\n  `testdata/parity/go/`\n- `mise run parity-compare` writes visual diffs to `testdata/parity/diff/` and\n  exits nonzero if captures are missing or if any scene exceeds the configured\n  mismatch threshold\n\nThere are no longer alternate renderer/audio/input build-tag variants for\nnormal development. `mise run build` is the canonical gameplay build, using the\nGoGPU renderer, renderer-provided input backend, and Oto audio backend by\ndefault.\n\n## Runtime Profiling\n\nThe executable exposes file-based profiling commands directly through the in-game\nconsole. They are meant for focused captures during gameplay/debug sessions\nwithout requiring an always-on HTTP profiling server.\n\n### pprof capture commands\n\n| Command | Purpose |\n| --- | --- |\n| `profile_cpu_start [filename]` | Start a CPU pprof capture. |\n| `profile_cpu_stop` | Stop the active CPU capture and flush it to disk. |\n| `profile_dump_heap [filename]` | Write a one-shot heap profile. |\n| `profile_dump_allocs [filename]` | Write a one-shot allocs profile. |\n\nNotes:\n\n- If `filename` is omitted, output goes to\n  `\u003cbasedir\u003e/\u003cmoddir\u003e/profiles/ironwail_YYYYMMDD_HHMMSS_\u003ckind\u003e.pprof`.\n- Relative paths are resolved under the current `\u003cbasedir\u003e/\u003cmoddir\u003e/`.\n- Only one CPU profile can be active at a time.\n- Orderly quit stops an active CPU profile automatically, so captures are still\n  flushed if you exit without typing `profile_cpu_stop`.\n- Heap and allocs dumps force a GC before writing so the snapshot reflects the\n  just-played scenario more closely.\n\nExample workflow:\n\n```text\nprofile_cpu_start\nmap start\nprofile_cpu_stop\nprofile_dump_heap\nprofile_dump_allocs\n```\n\nAnalyze captures with Go's standard tooling:\n\n```text\ngo tool pprof ./ironwailgo id1/profiles/ironwail_20260405_190000_cpu.pprof\ngo tool pprof ./ironwailgo id1/profiles/ironwail_20260405_190030_heap.pprof\n```\n\n### Lightweight in-engine timing/profiling\n\n- `host_speeds 1` enables frame timing logs from the host/server/renderer. With\n  it enabled, normal output includes `host_speeds`, `server_speeds`,\n  `render_thread_speeds`, `render_entities_speeds`, and `render_world_speeds`\n  records.\n- `profile` is the QuakeC host command. It prints the top 10 QC function profile\n  counters for the active local server and resets those counters on read.\n\n## Debug Telemetry\n\nThe server now exposes an opt-in debug telemetry mode for following trigger,\nphysics, and QuakeC activity from the in-game console/log output. This is aimed\nat parity debugging and engine-side investigation rather than end-user\ngameplay.\n\n### Debug CVars\n\n| CVar | Default | Purpose |\n| --- | --- | --- |\n| `sv_debug_telemetry` | `0` | Enables engine-side server telemetry events. |\n| `sv_debug_telemetry_events` | `all` | Event mask. Accepts `all`, `none`, a numeric mask such as `0x21`, or a token list such as `trigger,touch,blocked,physics,frame,qc`. |\n| `sv_debug_telemetry_classname` | `\"\"` | Optional classname filter. Exact matches are supported, and glob patterns such as `trigger_*` also work. |\n| `sv_debug_telemetry_entnum` | `-1` | Optional entity-number filter. Use `-1`/`all` for everything, or lists/ranges such as `1,4-6`. |\n| `sv_debug_telemetry_summary` | `1` | Per-frame summary mode: `0` off, `1` only frames with matching events, `2` every frame. |\n| `sv_debug_qc_trace` | `0` | Enables QuakeC call tracing routed through the server telemetry output. |\n| `sv_debug_qc_trace_verbosity` | `1` | QuakeC trace verbosity ceiling. `1` logs function enter/leave events, `2` also includes builtin calls. |\n\n### Scope\n\nCurrent engine-side telemetry focuses on server execution paths that are useful\nwhen debugging map logic and parity issues:\n\n- frame boundaries and `StartFrame`\n- entity `think` execution\n- touch/impact callbacks\n- trigger `touchLinks` scans and callback dispatch\n- pusher/blocker physics callbacks\n- QuakeC call chains executed through the server's QC wrapper\n\nQC profiling counters are already implemented through the `profile` host command\n(top 10 functions, reset-on-read, local server only). That command is separate\nfrom telemetry tracing. This means QC profiling is in scope and considered\nimplemented for parity purposes; there is no current plan to add a full\nstatement-by-statement VM profiler as part of this telemetry feature.\n\nQC tracing is not a generic whole-engine instruction trace. It follows QuakeC\nfunction entry/leave activity and optional builtin calls for server-side paths\nthat execute through `executeQCFunction`.\n\n### Output Behavior\n\nTelemetry lines are emitted with a `[svdbg ...]` prefix through the normal\nconsole/log path. Event lines include frame/time metadata plus the best current\nentity snapshot:\n\n```text\n[svdbg frame=12 time=4.200 kind=trigger] ent=57 classname=\"trigger_once\" targetname=\"door1\" target=\"door1\" model=\"*3\" origin=(256.0 128.0 32.0) touchlinks callback begin other=1 fn=42\n```\n\nQC trace lines add call depth, phase, and the resolved function name/index:\n\n```text\n[svdbg frame=12 time=4.200 kind=qc depth=2 phase=enter fn=trigger_relay[#17]] ent=57 classname=\"trigger_once\" targetname=\"door1\" target=\"door1\" model=\"*3\" origin=(256.0 128.0 32.0) self=57 other=1 other_classname=\"player\"\n```\n\nPer-frame summaries are controlled by `sv_debug_telemetry_summary`:\n\n- `0`: no summary line\n- `1`: summary only when at least one matching event was logged\n- `2`: summary for every frame, including quiet frames\n\nExample summary:\n\n```text\n[svdbg frame=12 time=4.200 dt=0.050] summary total=7 qc=2 counts=frame=2,trigger=2,think=1,qc=2\n```\n\n### Filters and Common Usage\n\nCommon filters can be combined:\n\n```text\nsv_debug_telemetry 1\nsv_debug_telemetry_events trigger,qc,frame\nsv_debug_telemetry_classname trigger_*\nsv_debug_telemetry_entnum 57,60-62\nsv_debug_telemetry_summary 1\nsv_debug_qc_trace 1\nsv_debug_qc_trace_verbosity 2\n```\n\nNotes:\n\n- token separators for `sv_debug_telemetry_events` include commas, pipes,\n  plus signs, and whitespace\n- classname matching is case-insensitive\n- entity filters are explicit lists/ranges, not glob patterns\n- QC trace output is still subject to the `qc` event mask, so masking out\n  `qc` disables trace output even if `sv_debug_qc_trace` is `1`\n- the `use` event token is part of the mask parser, but the current server-side\n  instrumentation is centered on frame/trigger/touch/think/blocked/physics/qc\n  paths\n\n### Limitations and Noise Caveats\n\n- This is intentionally verbose and can produce a lot of output in busy maps,\n  especially with `sv_debug_telemetry_summary 2` or QC builtin tracing enabled.\n- The current coverage is server-centric. It does not attempt to trace every\n  renderer, client, or filesystem path.\n- QC trace output reports function-oriented events (`enter`, `leave`, and\n  optional `builtin`) rather than a full statement-by-statement VM trace.\n- Trigger-heavy maps can still be noisy even with classname filters, because\n  begin/end bookkeeping and callback messages are logged around each observed\n  path.\n- Output is emitted to the console/log stream only; if you want to keep a\n  capture, redirect stdout/stderr or save the console log externally.\n\n[1]:https://github.com/andrei-drexler/ironwail\n[2]:https://mise.jdx.dev\n[3]:https://github.com/krzysztofdudek/Yggdrasil\n[4]:https://github.com/gogpu/gogpu\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdarkliquid%2Fironwail-go","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdarkliquid%2Fironwail-go","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdarkliquid%2Fironwail-go/lists"}