ClickDOOM runs DOOM in ClickHouse SQL in two modes. Emulation mode executes
the engine's RV32IM binary on a CPU implemented in SQL. Native mode implements
the engine's simulation and renderer as SQL, checked against the real engine
running in the reference emulator. README.md describes both modes.
CONTRIBUTING.md is the contributor-facing entry point.
DEVELOPING.md carries the build, test and benchmark mechanics
in full: read it for a target, a database or a bench convention.
AI_POLICY.md sets the bar a change has to clear.
make help lists every target. The tree tells you the rest, so this file does
not repeat it.
These documents state properties the code cannot show you, and both are cited by name rather than restated.
PURITY.md defines what "runs in ClickHouse" means, as numbered
PUR-N rules. Its Enforcement table says which of them
scripts/check_purity.sh reaches and which are review-only. Most have no
textual signature, so review is the only thing standing behind them.
SPEC.md is emulation mode's contract: the CPU, the memory map,
the MMIO surface, the batch execution contract, the trace format, the
determinism rules. NATIVE.md is native mode's: the state row,
the tic, the frame, the resident statements and parity with the reference
emulator. If a change needs a contract to move, open a spec-change issue. Do
not diverge from it temporarily.
Touching an invariant is not automatically wrong. The change then has to say how the property still holds, and citing the number is the reviewable form of that.
A comment says what the code does and what a caller may rely on. Why it is that way and not the alternative belongs in the commit message, where it is dated and attached to the diff. Apply that sentence by sentence: if deleting a sentence changes nothing a reader would do differently, it goes.
- Document what exists. Not what was, not what will be. No "used to", no "not wired up yet", no "lands in a follow-up". A plan that needs recording is an issue.
- State a fact once. Copies drift. Cite the file that owns it.
- No issue numbers, pull request numbers or SPEC section references in a comment. A comment stands on its own. If it needs a reference to make sense, it is carrying an argument that belongs in the commit message.
- Guardrails stay. A check that looks redundant is usually the one that fired once.
Commit messages carry the rationale and relevant history; comments describe the code a reader has in front of them. The writing style applies to both. The em-dash used as a dramatic pause, the antithesis frame ("a bound on patience, not a deadline"), the evaluative tail (", which is the whole point") and intensifiers that add nothing all read as machine-written. Replacing one with another is not a fix.
Title is scope: imperative summary, at most 72 characters.
CONTRIBUTING.md lists the scopes. Branches are scope/short-desc.
Messages have to make sense to outsiders: no plan or phase references, no
shorthand that only resolves in one session. No AI attribution in git. No
Co-Authored-By for a model, no "Generated with" footer.
One logical change per commit. A diff past roughly 400 lines is worth splitting. Skip the drive-by cleanup: a formatting sweep bundled with a fix makes the fix harder to review and harder to revert.
Read .github/pull_request_template.md and
use it as the body structure. It asks for evidence, for the PUR-N rules the
change touches, and for spec impact.
gh pr create has no template flag, and --fill takes the commit message
instead, which leaves the body without an Evidence section. Write the body to a
file and pass it:
gh pr create --title 'driver: add a live integration test' \
--body-file <filled.md>
If a model did most of the work, name it in the description. That is not held against the change, and it is the one place the attribution belongs. A model does not know its own version, so it writes the name the session lead gives it rather than a guess.
Blank issues are off, so every issue starts from a form in
.github/ISSUE_TEMPLATE/. Each form's name and
description say what it is for.
gh cannot read these forms. --template handles markdown templates only and
these are .yml forms, so it is ignored whatever flags you pass
(cli/cli#5865). Read the form, render
its fields to markdown yourself, and post that: each field's label as a ###
heading, and a render: field inside a fenced block of that language.
gh issue create --body-file <rendered.md> \
--title 'sqlcpu: divergence at icount 48211008' \
--label divergence
Each form sets its own title prefix and labels. Nothing applies them to a
--body-file, so pass them yourself. --label is dropped silently without
triage permission, so read the issue back after creating it.
- Determinism.
now(),rand()and their relatives never touch a computation path. - Purity. When in doubt whether something counts as computation, it does.
- Never edit
rom/PINNED_HASH,SPEC.mdorPURITY.mdto turn a red check green. A red check is information. - File divergences with full repro fields, even when you fix them in the same pull request. The report history is the project's debugging memory.
- A check that never ran is indistinguishable from one that passed. Require positive evidence; never infer success from the absence of failure. The shape recurs: a correct rule that nothing reaches, a case covered on one side of a differential only, a smoke test that stops short of the boundary it claims to cover, a checker whose own output nobody reads. Before trusting a check, automated or in review, confirm it ran against what you think it ran against.
make gatesgreen, checked by exit code.- Normative documents changed in the same commit as the behaviour they describe.
- Evidence in the pull request body: real command output, not a claim that tests pass.
- No unrun or failing tests handed over. If something is blocked, say which part and why, rather than narrowing the task to what passed.