CLAUDE.md · diff

git:20260827.b65783e to git:20260905.e099750

65 added, 427 removed. Audit A to A.

# Delvewright — Agent Constitution
- Delvewright is an automated production line that outputs **self-contained Minecraft
- adventure "delves" on demand** for a fixed group of 1–4 players. A delve is a 2–3 hour
- (10h ceiling), story-driven, box-garden (箱庭) adventure map: adventure mode, class
- selection with pre-provided gear, zero grind. It ships as a versioned OCI image — one
- `docker run` = a joinable dungeon — and must be **provably completable by machine**
- before a human spends their one QA hour on it.
+ Delvewright is an automated production line that outputs **self-contained Minecraft adventure "delves" on demand** for a fixed group of 1–4 players. A delve is a 2–3 hour (10h ceiling), story-driven, box-garden (箱庭) adventure map: adventure mode, class selection with pre-provided gear, zero grind. It ships as a versioned OCI image — one `docker run` = a joinable dungeon — and must be **provably completable by machine** before a human spends their one QA hour on it.
- Founding decisions live in `docs/adr/` and originate from the kickoff handoff
- (`docs/handoff-2026-07-29.md`). Read the ADR index before proposing architecture.
+ The creator is an agent (ADR-0012): the LLM writes the DSL, the human gives ideas and plays the result. Every authoring surface — DSL, CLI, skill — is agent-facing. Every input to a surface is either a creative judgement (the agent's argument) or a procedural derivation (handed by the tool, never typed); an error is refused where it is entered, not at the end.
- ## This file is half of the constitution
+ Founding decisions live in `docs/adr/`. Read the ADR index before proposing architecture.
- This file holds what anyone building Delvewright must obey to produce a correct
- artifact: the architecture, the forbidden zones, the layout, the conventions, and
- the engineering doctrine. The other half is **operating practice** — how a
- particular deployment of this project is run: how work is dispatched, how a change
- is reviewed and merged, how a playtest round is staged, how a decision session is
- conducted. That half is deployment-specific, so it is not checked in.
+ ## This file is half of the constitution
- That half lives in **`CLAUDE.local.md`**, which is gitignored. It is loaded by the
- same memory loader as this file, so it carries the same force: instructions, not a
- page an agent is shown and may skim. Delivering it any other way weakens it —
- emitting it from a hook makes it a tool result, which is the standing of a doc line,
- and this file's own doctrine says a doc line is not an invocation.
+ This file holds what anyone building Delvewright must obey to produce a correct artifact. The other half — how this deployment is run: dispatch, review, merge, staging, decisions — is **`CLAUDE.local.md`**, gitignored, loaded by the same memory loader and carrying the same force. `tools/planner-state.sh` (bound to `SessionStart` and `UserPromptSubmit --if-stale 12`) refuses by name when it is absent. Without it you have half a constitution: say so and ask before improvising anything about dispatch, review, merge or staging.
- A missing memory file loads silently, so `tools/planner-state.sh` — bound to
- `SessionStart` and `UserPromptSubmit --if-stale 12` — **refuses by name when it is
- absent**, and states its size when present. A fresh clone is exactly the case where
- it is missing, and a silent no-op there would be the UNRUN vacuity mode wearing the
- fix's clothes.
+ **Neither file is edited without the owner's confirmation in conversation.** A rule is stated once and never restated; a lesson goes into a tool, a diagnostic or `docs/reference/` first. Rules state what to do, not why. **One paragraph or bullet per line; never break a line for length.**
- So: if you have not been given that page, **you have half a constitution**. Say so
- and ask before improvising anything about dispatch, review, merge or staging. Nothing
- below is conditional on it — the two halves are disjoint, not layered.
+ **Agent memory** (the auto-memory directory) holds exactly three kinds of entry: the pointer to the note that initialises a session, facts about the owner that are not rules (preferences, context, ambitions), and addresses of external resources (repositories, dashboards, channels, test worlds). **A rule, ruling or lesson is never written to memory**; a memory that restates one is deleted. Each fact has one home.
## Architecture (settled — see ADRs, do not relitigate)
- - **DSL → compiler → datapack** (ADR-0001): campaigns are schema-enforced JSON written
- by the LLM; a deterministic compiler emits the datapack. The LLM **never** writes raw
- mcfunction.
- - **Staged DSL** (ADR-0002): world/setting → NPCs → classes/gear → campaign quest plan
- → quest expansion. Each stage is a schema; later stages condition on earlier outputs.
- - **Vanilla-first** (ADR-0003): the player-facing server runs pinned vanilla + datapack
- only. Mods (PackTest, Carpet) exist solely in tooling/validation.
- - **Prefabs + jigsaw** (ADR-0004): maps assemble from a `.nbt` prefab library via
- vanilla jigsaw/template_pool with compiler-controlled seeds. No block-by-block
- generation. GDPC is a documented fallback, not built.
- - **Two-layer validation** (ADR-0005): static quest-graph reachability + command
- validation at compile time; PackTest + mineflayer critical-path bot at runtime.
- - **Determinism** (ADR-0006): same DSL + same seed → byte-identical datapack and world.
- Hard invariant, tested from day one.
+ - **DSL → compiler → datapack** (ADR-0001): campaigns are schema-enforced JSON written by the LLM; a deterministic compiler emits the datapack. The LLM **never** writes raw mcfunction.
+ - **Staged DSL** (ADR-0002): world/setting → NPCs → classes/gear → campaign quest plan → quest expansion. Each stage is a schema; later stages condition on earlier outputs.
+ - **Vanilla-first** (ADR-0003): the player-facing server runs pinned vanilla + datapack only. Mods (PackTest, Carpet) exist solely in tooling/validation.
+ - **Prefabs + jigsaw** (ADR-0004): maps assemble from a `.nbt` prefab library via vanilla jigsaw/template_pool with compiler-controlled seeds. No block-by-block generation. GDPC is a documented fallback, not built.
+ - **Two-layer validation** (ADR-0005): static quest-graph reachability + command validation at compile time; PackTest + mineflayer critical-path bot at runtime.
+ - **Determinism** (ADR-0006): same DSL + same seed → byte-identical datapack and world. Hard invariant, tested from day one.
- **OCI packaging** (ADR-0010): delve = pinned server + world + config + datapack image.
- **Pinned MC version** (ADR-0009): **Minecraft Java 1.21.11**, a long-term constant.
- - **Compiler foundation** (ADR-0011): Rust-native compiler; beet/mecha only as an
- independent CI cross-check, never as the emission path.
- - **Product form** (ADR-0012): a Claude Code skill (`/new-delve`) is the generation
- front-end — Claude Code is the agent runtime; the generated DSL documents are the
- artifact of record; building an agent runtime from scratch is permanently out of
- scope.
+ - **Compiler foundation** (ADR-0011): Rust-native compiler; beet/mecha only as an independent CI cross-check, never as the emission path.
+ - **Product form** (ADR-0012): a Claude Code skill (`/new-delve`) is the generation front-end; Claude Code is the agent runtime; the generated DSL documents are the artifact of record; building an agent runtime from scratch is permanently out of scope.
## Forbidden zones
- **No raw mcfunction authored by an LLM** — all commands come from the compiler.
- **No mods on the player-facing server** — validation-layer only.
- - **No nondeterminism in the compiler**: no wall-clock time, no unseeded RNG, no
- hash-order iteration, no absolute paths in output.
- - **No CC BY-NC / ND / unknown-license assets, ever.** Prefabs/content: original, CC0,
- CC BY, MIT, Apache-2.0, or GPL-3.0-compatible only (ADR-0013). Record provenance in
- prefab metadata.
- - **No grind mechanics in delve design**: no mining/leveling loops, resource farming,
- or base building.
- - **No runtime LLM in shipped delves** (current policy): all content — including
- dialogue — is authored at generation time; dialogue is pre-written branching
- options (spec-0001).
- - **The production host is prod-only** — never target a delve-hosting machine for dev
- or tests.
- - **Generated campaigns/worlds do not live in this repo** — they ship via GitHub
- Releases / OCI registry (content licensed separately from GPL code; ADR-0007).
+ - **No nondeterminism in the compiler**: no wall-clock time, no unseeded RNG, no hash-order iteration, no absolute paths in output.
+ - **No CC BY-NC / ND / unknown-license assets, ever.** Original, CC0, CC BY, MIT, Apache-2.0, or GPL-3.0-compatible only (ADR-0013). Record provenance in prefab metadata.
+ - **No grind mechanics in delve design**: no mining/leveling loops, resource farming, or base building.
+ - **No runtime LLM in shipped delves**: all content, dialogue included, is authored at generation time as pre-written branching options (spec-0001).
+ - **The production host is prod-only** — never target a delve-hosting machine for dev or tests.
+ - **Generated campaigns/worlds do not live in this repo** — they ship via GitHub Releases / OCI registry (content licensed separately from GPL code; ADR-0007).
- **No feature without an approved spec** in `docs/specs/`.
## Repository layout
```
CLAUDE.md # this file
docs/adr/ # architecture decision records (numbered, immutable once Accepted)
docs/specs/ # approved specs, one per feature
- docs/reference/ # live behavior records: compiler.md, tools.md, i18n.md,
- # grammar.md + how a round is run: playtest-methodology.md
- # + how a delve is generated: skill-workflow.md
- # + how a prefab is admitted: prefab-procedure.md
- # + what a fresh checkout still needs: worktree-bootstrap.md
- # + distribution-size.md
- docs/ROADMAP.md # milestones; M1 = hello-world delve
- crates/ # Rust workspace: dsl / compiler / grammar / orchestrator /
- # admit / schem / render
- gallery/ # the ENGINE's own campaign: one instance of every surface
- # the DSL declares, built on every PR, never released or
- # staged. Its piece is generated, never committed.
- prefabs/ # tileset GENERATORS + shared invariants. The .nbt library and
- # its metadata live in the CONTENT repo, reached through the
- # `campaigns/` dev symlink — see prefabs/README.md
+ docs/reference/ # live behavior records: compiler.md, tools.md, i18n.md, grammar.md,
+ # playtest-methodology.md, skill-workflow.md, prefab-procedure.md,
+ # worktree-bootstrap.md, distribution-size.md
+ docs/ROADMAP.md # milestones
+ crates/ # Rust workspace: dsl / compiler / grammar / orchestrator / admit /
+ # schem / render
+ gallery/ # the ENGINE's own campaign: one instance of every surface the DSL
+ # declares, built on every PR, never released or staged
+ prefabs/ # tileset GENERATORS + shared invariants; the .nbt library lives in
+ # the CONTENT repo, reached through the `campaigns/` dev symlink
harness/ # mineflayer bot tests (TypeScript)
- tools/ # auxiliary Python/shell tooling (skins, i18n, CI checks) —
- # never shipped in delves
+ tools/ # auxiliary Python/shell tooling — never shipped in delves
packtest/ # PackTest templates
validation/ # docker compose: headless server + bot, same image as CI & prod
```
## Methodology
- - **Spec-driven**: specs carry machine-verifiable acceptance criteria. Implementation
- sessions work against a spec; if none exists, write/propose the spec first.
- - **No hacks at any layer**: if vanilla/NBT provides an intended primitive that
- content needs, the DSL exposes it first-class — never leave it to downstream
- folklore or workarounds. If the only possible implementation of a feature is a
- lower-layer hack (e.g. raycast polling where vanilla has no primitive), the
- feature is excluded until vanilla provides one. Applies at every layer boundary:
- NBT→compiler, compiler→DSL, DSL→skill.
- - **When the record does not answer a craft question, the answer is RESEARCHED
- against established practice — never invented.** This is a research-grade
- integration project: the default is that a mature answer exists in some
- discipline and has not been looked up. Not knowing how to do something is
- evidence that the research is incomplete, not licence to think a scheme up.
- It binds at every layer — level-design craft, algorithms, tooling, review
- method — and it binds hardest where the question *feels* like a matter of
- taste, because that is where an invented answer is least likely to be
- challenged. Worked example, and the shape to recognise: asked to light an
- interior, an invented scheme distributes emitters by a periodic rule or paves
- a floor with a glowing block; the researched answer is that light is
- motivated, that pools of light and dark are a navigation grammar, and that
- the eye reads contrast rather than brightness. Four obligations follow.
- **State per rule whether it is cited or authored** — an invented scheme and a
- researched one read identically in a report unless the report says which, and
- that indistinguishability is the actual danger. **Name the weak spots**: a
- claim no source supports is written down as unsupported, not smoothed over.
- **Land the research where the next session finds it** — a record under
- `docs/reference/`, not in a session's scratch, or the same question is
- invented again. And **record the gap against the line that should have
- covered it**, so an incomplete research record is a finding rather than a
- permanent hole. The bound: research answers the question that was asked and
- stops. Unlicensed sources are ideas-only (ADR-0013, `ACKNOWLEDGEMENTS.md`).
- - **This is a general engine. Primitives are abstract, flexible and
- configurable, and never bound to one campaign's design.** A creator must be able
- to build **any** content with it. A primitive encodes a *mechanism* — a thing a
- player can press, a region that can be sealed, a body that can walk a route —
- never a *design decision* about what the mechanism is for; the genre being built
- this month is content, and content lives in campaigns. Test before adding any
- surface: **could a creator making an entirely different game want this, and can
- they configure it to their own fiction?** If it only makes sense inside this
- month's delve, it is authored content wearing a primitive's clothes.
- Corollary: **a capability belongs to the object class it acts on, not to the
- verb that first needed it.** Built onto the verb, the second object that needs
- it has no surface, and the fix looks like a second bespoke field — **a second
- bespoke field is the defect, not the fix.** Generality is decided at the FIRST
- site: retrofitting at the second means rewriting every call site and every
- proof written against the narrow one — and the second object arrives long
- after anyone remembers why the first was shaped that way.
- **Three shapes to look for in review**, hardest last:
- 1. *Keyed to the verb, not the object class.* Tell: `"X, mirroring Y"` in a doc
- comment; a hook on one variant of a sum type but not its siblings.
- 2. *A general mechanism privately re-implemented inside a verb.* The worst kind:
- the special case works perfectly, nothing ever looks, and every proof, l10n
- pass and diagnostic written for the general path silently misses the private
- copy. Before adding a "missing" hook, ask whether a general mechanism already
- IS that hook.
- 3. *The general mechanism exists but its binding is too narrow to reach the
- objects it should.* Reads as a missing feature, and the "fix" adds a new
- mechanism strictly weaker than the one it duplicates. Ask **"what does the
- existing general mechanism fail to reach, and why"** before ever asking
- "what surface is missing".
- Same shape one layer down: a hand-rolled walk enumerating 3 of 5 effect roots is
- a defect of expressibility, not of care.
- - **Debug doctrine**: a red check is information, never an obstacle. Never weaken
- a check, test, or threshold — and never reroll a seed — to get green; fix the
- root cause or escalate. Escalating a toolchain bug is success. Preserve every
- debugging lesson in the strongest available form, strongest first: compiler
- diagnostic > tooling default (automate the pitfall out of existence) > generator
- invariant > docs.
- **An intermittent red is never re-run** — it is a finding, and re-running
- discards it; an intermittent failure is an under-specified test — root-cause it.
- (Recorded trap: a `grep -q` readiness probe under `pipefail` SIGPIPEs its
- producer at the match and reads as flakiness.)
- **Non-trivial ad-hoc shell is written for bash, not the interactive shell.** The
- tool layer runs zsh, where an unquoted parameter does not word-split (a `for`
- over a 33-item list runs once) and assigning to `path` destroys `PATH` —
- both vanish under `bash -c`, so anything with a loop, an array or a variable
- holding a list goes through bash. Repo scripts are already
- `#!/usr/bin/env bash`, so no repo check can catch this, and a check would
- red correct scripts. That rule removes only those two traps — the rest of
- this doctrine stands.
- **The dangerous shell idiom is the one that returns a plausible wrong number
- instead of an error**, and an agent's own measurements are where it bites,
- because nothing downstream re-checks them. Recorded forms: hashing `shasum`
- output hashes the file PATHS too; `cargo test --test X` rebuilds the binary
- under `CARGO_BIN_EXE`, resurrecting a reverted perturbation; a `cd` in the
- first clause of a compound command persists through the rest; `git merge-tree`'s
- three-argument form under-reports conflicts against `--write-tree`. **A zero
- from a measurement that disagrees with an independent observer is the
- measurement failing, not the fact being absent.**
- **A computed key is itself a measurement and needs its own confirmation** —
- a lookup can ask the right question about the wrong key and get an honest
- answer (hashing paths instead of contents; an environment probe that resolved a
- variable's NAME from a grep matching two lines). Before trusting an answer,
- check what the question resolved to.
- The costliest computed key names an INSTRUMENT: **a frozen measurement names
- its instrument literally, by exact revision, never through an indirection**
- (a pin variable, "the pinned engine") — otherwise moving the pin silently
- re-reads every recorded figure against a different instrument.
- **A `grep` for a phrase the file wraps across a line break returns zero**, and
- zero reads as absent — ask what the pattern could match before believing what
- it did not.
- **A count equal to its own fetch limit is not a measurement — it is the
- limit.** The repair is not a bigger number but a **refusal when the page comes
- back full**, the one condition under which the answer cannot be trusted.
- Truncation fakes coverage, in the direction that reads as a clean pass.
- **A scripted string replacement that matches nothing is a silent no-op** — an
- edit script asserts its match count before it writes; a replace whose count is
- not exactly what was intended is a failure, not a no-op. (Stated binding
- counts in one line are what catch this from outside.)
- **`git checkout -- <file>` cannot tell "revert my perturbation" from "discard
- my work"** — commit before demonstrating; where the work cannot be committed
- yet, restore the perturbation from a scratch copy, never from git.
- **When a measurement is the deliverable, cross-check the number by a second
- method before reporting it.** Precondition: **a second method that shares the
- first's calibration is not a second method** — the shared premise is rarely
- the arithmetic, it is the configuration underneath (one pin, one symlink, one
- `target/`, one checkout); ask of a cross-check *what does this share with the
- first one* — the strongest second method has an unrelated failure mode: a
- different instrument, a different tree, or an observer outside the machine.
- Mirror image: **a second method must differ where the suspicion is and agree
- everywhere else** — isolate the one variable; re-deriving the rest is a second
- measurement to get wrong.
- - **CI is the sole arbiter** (ADR-0008). Nothing merges red. **Every CI job is a
- required status check** — an advisory job is a job that does not gate. Branch
- protection matches a context by its NAME STRING, so a renamed job blocks every
- PR forever, including the one that would fix it:
- `.github/required-status-checks.txt` and `tools/check-required-contexts.py`
- hold the names in lockstep, in both directions, so a rename or a new advisory
- job is an ordinary red. **CI green is admission to verification, not grounds
- to merge**: unit and CI tests prove the change broke nothing, never that it
- fixed the target. What else a change must pass, and who decides, is operating
- practice.
- - **Every validation authoring needs must be runnable on the creator's own
- machine, and this is not negotiable.** The floor is always available: clone the
- repo and build from source. Completeness is guaranteed there, never by the
- convenience layer. **Binary distribution is an optimisation, not the
- guarantee**: where a prebuilt binary cannot carry a capability, never contort
- the binary, drop the capability, or ship a diminished tool — the skill states
- how to build locally and the first run builds from source. Every skill owns an
- explicit **`Init` section that establishes a complete toolchain before any
- work begins**; a tool that cannot be bundled is acquired at the step that needs
- it. A distribution question never decides a capability question.
- - **Write short documents.** A spec or ADR is read in full by agents and in
- summary by humans; keep them as terse as correctness allows.
- - **Audience separation in docs**: every document has ONE target reader.
- Agent-facing docs (CLAUDE.md, ADRs, specs, `docs/reference/`, skills) may be
- arbitrarily technical. User/player-facing docs (READMEs, release notes,
- tutorials, storybooks) contain only what that reader needs to act — never
- internal machinery such as model tiers, subagent dispatch, worker roles, or
- pipeline plumbing. Applies to both repos, including the content repo's
- play/hosting tutorials.
- - **A reader-facing document is written in the present tense of the current
- version.** It says what the thing IS, as if it had always been that way. No
- "used to be X", "originally", "as of vN", no parenthetical citing the internal
- decision a behaviour came from. Two leaks, and the second is the one to watch:
- an internal reference number a stranger cannot resolve; and the *repair* —
- stripping the reference while narrating what it used to assert trades a
- citation for a changelog. Keep the BEHAVIOUR as a plain present-tense fact, or
- delete it. Relocating a historically-worded sentence into `docs/reference/` is
- not a fix — that is a current-behaviour record too. **ADRs are the one place
- history legitimately lives.**
- - **Nothing here owes compatibility to anything already built.** This is a
- research-grade integration project: there is no production environment and no
- user on the other side of a compatibility promise. Only the final result
- counts. A change that stops an existing campaign document compiling is not a
- defect — the document is changed or deleted, and that needs no justification.
- Time spent on backward compatibility is wasted, and so is the argument for
- discarding an old artifact: discard it. No compatibility shim, opt-in flag,
- migration path or gradual adoption is added for the benefit of existing
- content. `dsl_version` numbers a surface so a document can say which surface
- it was written against; it is not a promise that the old surface survives.
- What this does NOT relax, because none of it is about history: determinism
- (ADR-0006), the refusal to weaken a check to get green, and a diagnostic
- owing a test. Those are how "the result is good" is measured.
- - **A green gate that binds to nothing is VACUOUS, not a pass.** Three empty
- greens: *unbound* (matched zero objects), *unfenced* (the campaign's
- `dsl_version` never reached the surface the gate keys off), *unemitted*
- (declared, compiled green, never emitted). Every validation artifact states
- its binding count; a zero binding is a finding and is named in the round
- summary. Full derivation: `docs/reference/playtest-methodology.md`.
- - **A gate nothing INVOKES is not a gate — it is UNRUN**, the fourth vacuity
- mode. A check can be correct in every reviewable way and protect nothing,
- because the obligation to run it lives in a doc line. **A doc line is not an
- invocation.** A gate is done only when the event it guards cannot happen
- without it; the review question is always *what calls this, and what happens
- if someone does the guarded thing without calling it?* Bind it to the event
- (a script step, a compose `depends_on`, a required token), never to a
- checklist. Where the event has several entry points, enumerate them — an
- existence check that only looks where someone pointed is how the shape
- survives review. Where a gate must be skippable, the override is explicit,
- prints what is being overridden, and is shaped so it cannot become habit — a
- convenient override is the same defect one layer out.
- - **An opt-out must be secured by a property the defect cannot supply** — the
- sixth vacuity mode: a gate can be bound, invoked, honestly counted and
- falsifiable, yet logically incapable of separating pass from fail, because
- the escape hatch's proof obligation is entailed by the failure it exists to
- catch (an "unreachable, so sealed" opt-out proven by unreachability succeeds
- on exactly the cells that failed). The repair is a **different** demand, one
- the defect cannot supply (a sealed region must itself be closed). Two review
- questions, the second decisive: *what does this opt-out demand* — and *could
- the defect itself produce it?* Applies to every escape hatch,
- acknowledgement and override; a second hatch on the same gate is the defect.
- Where an opt-out is a choice among kinds, the effective obligation is their
- disjunction and is only as strong as the weakest — the kind must be
- determined by the object, never picked by the author.
- - **When one gate's prescription is another gate's refusal, the defect belongs to
- the PAIR.** Each half can be correct and the union unsatisfiable, reachable by
- an ordinary merge. The review question is never only *is this check right* but
- ***what does its remedy oblige, and does anything refuse that***. A gate that
- names a remedy owes a check that the remedy is **reachable**; where two gates
- guard one artifact, they are read together or not at all. Tell: a guard that
- carefully qualifies two of the three things its artifact holds was written
- against the cases its author had met.
- - **A checker reads a document the way its CONSUMER reads it.** A gate over a
- repository document is only as true as its parse: where its reading differs
- from the reading the document actually gets, the gate passes on something no
- reader can see (a markdown renderer ends a table at a blank line; a checker
- that does not is counting rows no reader sees). Two obligations: the reading
- is **cross-checked against a real implementation of the format** and that
- comparison is committed; and the parse rule is **one shared authority**,
- never a private copy per gate.
- - **A command whose response nobody reads cannot fail.** A site that issues a
- command to a server and discards the reply asserts an effect it has not
- established, and one bad line costs the whole function silently. A live
- command goes through the shared rejection rule (`tools/lib/rcon.{sh,mjs}`);
- an EMITTED command is checked against the pinned command tree by the emitter,
- not by a test, because the operator running the tool does not run
- `cargo test`. Both are bound in CI by `tools/check-live-commands.py`. The
- generalisable half: a correct rule living inside ONE call site's `ok()` gives
- the next two callers nothing to reuse — extract it.
- - **A finding is not closed until its general form is a diagnostic.** Every
- playtest finding yields two deliverables — the instance fix, and the general
- form as a diagnostic **re-run against the current build** — or an explicit
- record that only the instance was fixed, which is then a risk item at the
- next staging review.
- - **A capability-gap finding blocks staging, not just the backlog.** Triage each
- finding as content / capability gap the day it is reported; a capability gap
- means the engine work lands before the next playtest, or the round summary
- says per item that it is still open and not to test it. Audit the findings
- ledger from round 1 — never from the last round — before staging any build.
- - **A release is built from a frozen approved tree, never from a moving branch.**
- A release names the exact tree that was accepted; only files that cannot reach
- the shipped artifact (release plumbing) may be added on top, each named in the
- release request. A release refuses when the campaign tree differs from the
- approved baseline by anything unnamed.
- - **Tiered testing**: unit + static analysis on every push; PackTest integration on PR;
- full bot playthrough on release candidates only.
- - **PR-based flow even solo.** GitHub Actions. **Both repos are PUBLIC** —
- `stellarfeline/delvewright` and `stellarfeline/delvewright-campaigns` — so
- public distribution channels (GitHub Releases, crates.io, GHCR) are open to us
- by default (ADR-0017).
- - **Docs are the only persistent memory.** End every session by writing lessons back:
- new constraints → this file; new decisions → an ADR; process learnings → the relevant
- spec. If you fought the codebase and won, record how. A stale premise in THIS
- file is not a stale comment — it is a wrong conclusion, repeated every session.
- - **Compiler behavior has one live reference.** `docs/reference/compiler.md` is the
- authoritative current-behavior record for `delvec` (DSL surface, emission,
- invariants, the full DW diagnostics catalog); specs stay historical decision
- records. Any PR that changes compiler behavior updates it in the same PR — CI
- enforces the DW-code subset bidirectionally (`tools/check-dw-codes.py`, docs job).
- - **Tooling sync**: a PR that adds or changes an authoring tool or loop updates
- `docs/reference/` AND every skill whose workflow it touches, in the same PR.
- LLM-facing tools enter skills as mandatory workflow steps; human-in-the-loop
- tools enter as advisory one-line mentions at the right step — never blocking.
- A tool absent from docs and skills does not exist for future sessions. The
- inventory of the whole tool surface — every binary, script and flag, with its
- class — is `docs/reference/tools.md`.
- - **A campaign is never the engine's test surface. Engine surfaces are
- exercised against the gallery** (spec-0039) — a real campaign's content and
- its engine use cannot be separated, which is exactly what disqualifies it as
- a test surface. A campaign that stops building under a new engine is not a
- finding about the engine: **the campaign adopts, or it is deleted.** There is
- no released-versus-in-development distinction to triage, because nothing is
- released and nothing is owed compatibility.
- - **Every engine surface owes a gallery element, in the same PR.** The coverage
- gate enumerates its unit set from the compiler's own `schema --stage all`
- export — the single authority, never a parser of the source — so a new schema
- property or enum variant is an unbound unit the moment it lands. A unit is
- either **bound** in the gallery domain or **refusal-proven** by a committed
- probe the engine actually rejects with a named code. **No third state and no
- prose exemption**: the hatch demands a machine-produced refusal, which
- "nobody authored it" cannot supply. Distinct from the demo-level rule: a demo
- teaches ONE mechanic to a human and is queued; a gallery element is coverage
- and lands with the surface. Vanilla registry values (block, sound, potion
- ids) are data, never units. The gallery also owes **legibility**: a creator
- reading it sees what the engine builds and which checks fire.
- - **Every new mechanic owes a demo level**: the PR that lands a mechanic adds
- its row to `docs/demo-levels.md` — the first-party showcase queue of small
- levels that verify one mechanic and document it by example. Not necessarily
- built when the mechanic lands, but always queued.
- - **Buildings are judged at playable scale**: a structure reads as what it
- depicts, and its interior belongs to the same theme. Fine detail is
- deliberately dropped — build-art convention scales the referent up so detail
- survives; a delve is walked at player scale, so a cathedral is a
- cathedral-sized cathedral and the **silhouette carries the recognition the
- detail cannot**. The review question is always "does this read as the thing,
- and does the inside belong to it", never "is the detail right" — a piece is
- not rejected for lacking detail it was never going to have.
- - **Grandeur is playable content, not volume.** A structure is grand because
- there is a lot in it to play. **A big empty room is a small building that
- costs more to walk across.** The silhouette earns recognition from outside,
- the density from inside: ask of an oversized space *what does the player do
- in here*, and a space with no answer is cut or filled. Applied to objects:
- **when the vanilla block that names a thing is too small to carry the weight
- the story gives it, the thing is built out of blocks** — placing the block
- that shares the name is labelling the object, not depicting it.
- - **A clean auto-merge is not evidence of semantic compatibility.** When two
- branches change one subsystem's *intent*, the dangerous hunk is the one git
- resolves **without a conflict marker** — it compiles, passes clippy and every
- existing test, and can undo the other branch's whole point. **Enumerate what
- each branch claims to DO, and re-demonstrate every claim on the merged
- tree**; a textual conflict count measures nothing. Corollaries: docs merge as
- text and are never re-read — re-read them; and an integration is the first
- place a **cross-feature interaction** exists at all — name such pairs up
- front, and the test that covers one goes in with the merge.
- - Repeated workflows become skills/slash commands (`/new-campaign`, `/validate`,
- `/release`) — see ROADMAP; design them when the workflow has been done manually twice.
+ - **Make it work.** The goal is a 20+ scene, unified-appearance delve authored from a fresh content clone through `/new-delve` alone. A gate, tool or abstraction that does not move that goal is removed, without a defence. Not relaxed by this: determinism, never-weaken-a-check-to-get-green, a diagnostic owing a test. Make the safe path the default with no per-case judgement.
+ - **Spec-driven**: specs carry machine-verifiable acceptance criteria. No spec, no feature — write it first. An acceptance criterion is checked against the tree before anything is built on it; a criterion the implementation cannot yet satisfy is a recorded debt, never a pass; any rewrite that reduces what a criterion asserts is declared as a loosening in those words.
+ - **No hacks at any layer**: if vanilla provides a primitive content needs, the DSL exposes it first-class; if the only implementation is a lower-layer hack, the feature is excluded until vanilla provides one. Applies at every boundary: NBT→compiler, compiler→DSL, DSL→skill.
+ - **A craft question the record does not answer is researched against established practice, never invented.** State per rule whether it is cited or authored; name unsupported claims as such; land the research under `docs/reference/`; record the gap against the line that should have covered it; research answers the question asked and stops. Unlicensed sources are ideas-only (ADR-0013, `ACKNOWLEDGEMENTS.md`).
+ - **This is a general engine.** A primitive encodes a mechanism, never a design decision about what it is for; a creator making a different game must be able to configure it. A capability belongs to the object class it acts on, not to the verb that first needed it; a second bespoke field is the defect. Review shapes: a hook keyed to a verb rather than its object class; a general mechanism privately re-implemented inside one verb; a general mechanism whose binding is too narrow to reach the objects it should (ask what it fails to reach and why, and whether the wider site can express the rule's quantifier, before adding a surface); a parameter inferred from arguments when the caller knows more (let the caller state it).
+ - **Debug doctrine**: a red check is information. Never weaken a check, test or threshold, and never reroll a seed, to get green; fix the root cause or escalate. An intermittent red is an under-specified test, never re-run. The trigger is not the cause: a fix that only works by undoing the recent change is aimed at the trigger. Preserve a lesson in the strongest form available: compiler diagnostic > tooling default > generator invariant > docs. A regression is named with its direction (can it only turn a proof red, or can it let something ship), never fixed quietly inside an unrelated change. A hand-written field in committed data that stands where a measurement belongs is part of the tool defect's blast radius.
+ - **Measurement doctrine**: a measurement that is a deliverable is cross-checked by a second method that shares no configuration with the first, varying only the suspected variable. A computed key is itself a measurement: check what the question resolved to. A frozen measurement names its instrument by exact revision, never through an indirection. A count equal to its fetch limit is refused, not reported. A scripted replacement asserts its match count. A `grep -c` counts lines and mentions, not obligations: read what the matches say. Enumerate the container and pipe that into the count; justify every exclusion. A zero that disagrees with an independent observer is the measurement failing. An instrument is force-rebuilt before a comparison runs, with cargo's exit status asserted on its own line; a dev-profile binary's hash is not a freshness check; a build failure is a gate failure, never a fallback; a suspected non-determinism is settled by building twice. A number is written down after it is measured, in a commit body. Probe presence by reading, treating only not-found as absent. Read a failing run from the top; a tool that fails before its comparison has not compared. A story that explains the data is not evidence: build the commit it blames. Commit before demonstrating; restore a perturbation from a scratch copy, never from git; a proof script does not change refs while the instrument lives in the tree. Non-trivial ad-hoc shell runs under `bash -c`, never the interactive zsh; capture a command's status before piping it.
+ - **Vacuity**: a green gate that binds to nothing is not a pass. The modes: unbound (matched zero objects), unfenced (the version never reached the surface), unemitted (declared, compiled, never emitted), unrun (nothing invokes it; a doc line is not an invocation; a helper called only from tests is unrun), untraversed (halted before the end — always `--no-fail-fast` before comparing failure sets), and an opt-out secured by a property the defect can supply (demand something the defect cannot produce; where the opt-out is a choice among kinds, the object determines the kind; a second hatch on one gate is the defect). Every validation artifact states its binding count computed from the objects, with its denominator; a zero binding is a finding unless the objects do not exist yet. A gate is bound to the event it guards, with every entry point enumerated; any override is explicit, printed, and shaped so it cannot become habit. Test a gate by perturbing toward the vacuous shape and checking it reds, with a perturbation only that gate could catch. A record that claims to be a measurement carries a canonical hash of what was measured. Full derivation: `docs/reference/playtest-methodology.md`.
+ - **Pairs**: where two gates guard one artifact, read them together; a remedy one prescribes and the other refuses is the pair's defect; a gate that names a remedy owes a check that the remedy is reachable. A documented procedure and the gate that judges it are a pair: ask whether anything has ever executed it green.
+ - **A checker reads a document the way its consumer reads it**, cross-checked against a real implementation of the format with the comparison committed, with one shared parse rule and no private copy per gate. A checker that skips regions audits what it skipped and prints the size. A resolve-by-name over a scope where names are not unique yields a candidate, not a match. A gate proves a surface is authored, never that it is right: ask what element would answer differently if the implementation were wrong. A diagnostic's quantifier is part of what it says; read it before describing what the check covers.
+ - **Every command's response is read.** Live commands go through the shared rejection rule (`tools/lib/rcon.{sh,mjs}`); emitted commands are checked against the pinned command tree by the emitter; both bound by `tools/check-live-commands.py`. A shared rule is extracted, never copied per call site.
+ - **A finding is closed when its general form is a diagnostic re-run against the current build**, or an explicit record says only the instance was fixed. A capability-gap finding blocks staging: the engine work lands before the next playtest, or the round summary says per item it is open and not to test it. The findings ledger is audited from round 1 before any build is staged. A "known gap" in a reference document is a ledger row, not a doc line. A claim about where a defect reproduces is made by reproducing it.
+ - **CI is the sole arbiter** (ADR-0008). Every CI job is a required status check; `.github/required-status-checks.txt` and `tools/check-required-contexts.py` hold the names in lockstep in both directions. A scheduled workflow is never a gate. CI green is admission to verification, not grounds to merge. "Green" is a property of a revision; a `DIRTY` or draft PR runs nothing, so `0 failed` is information only beside a non-zero check count.
+ - **Everything runs on the creator's own machine, from source.** Where a binary cannot carry a capability, the first run builds from source. Every skill owns an `Init` section that establishes the toolchain from source before any work begins; a tool that cannot be bundled is acquired at the step that needs it. A distribution question never decides a capability question. A reclaimer names every class of resource its subject holds and proves each gone.
+ - **A release is built from a frozen approved tree**, named exactly; only release plumbing may be added on top, each file named; a release refuses when the tree differs from the approved baseline by anything unnamed.
+ - **Tiered testing**: unit + static on every push; PackTest on PR; full bot playthrough on release candidates. **PR-based flow even solo**; both repositories (`stellarfeline/delvewright`, `stellarfeline/delvewright-campaigns`) are public (ADR-0017).
+ - **Nothing owes compatibility to anything already built.** A change that stops an existing document compiling is not a defect: the document is changed or deleted, with no justification, shim, flag or migration. `dsl_version` numbers a surface; it promises nothing.
+ - **Docs are the only persistent memory.** `docs/reference/compiler.md` is the live record of `delvec` (surface, emission, invariants, every DW code; `tools/check-dw-codes.py` enforces the code subset bidirectionally). A PR that changes compiler behaviour updates it in the same PR. A PR that adds or changes an authoring tool updates `docs/reference/` and every skill it touches in the same PR — LLM-facing tools as mandatory steps, human-in-the-loop tools as one-line advisory mentions; a validated pipeline enters the skill with the PR that makes it work; `docs/reference/tools.md` is the inventory of the whole tool surface. Specs and ADRs are historical decision records; ADRs are the only place history lives. A ledger constant is enumerated, never restated as a literal; a census derivable from the object is never hand-written; a prose note recording a code fact is a pointer, not a clearance. End every session by writing lessons back, in the strongest form.
+ - **Write short documents, each for ONE reader, in the present tense of the current version.** Agent-facing docs may be arbitrarily technical; player-facing docs carry only what that reader needs to act, never internal machinery. No "used to", no version narration, no internal reference numbers a stranger cannot resolve; a stripped reference is not replaced by narrating what it asserted.
+ - **A campaign is never the engine's test surface; the gallery is** (spec-0039). Every engine surface owes a gallery element in the same PR: the coverage gate enumerates units from `delvec schema --stage all`, and a unit is bound in the gallery or refusal-proven by a committed probe (the primary plus one declared edit) the engine rejects with a named code — no third state, no prose exemption. Vanilla registry values are data, never units. A bound element is accepted by perturbing the declaration and checking an emitted byte moves; an inert element is reported as a zero binding. The gallery owes legibility: a creator reading it sees what the engine builds and which checks fire. A campaign that stops building adopts or is deleted. **Every new mechanic owes a demo level** row in `docs/demo-levels.md`, queued when the mechanic lands; an engine capability is confirmed on a demo level, never on a campaign's renders.
+ - **Buildings are judged at playable scale**: does it read as the thing, and does the inside belong to it — never is the detail right. An oversized space with no answer to "what does the player do in here" is cut or filled. When the vanilla block that names a thing is too small for the weight the story gives it, the thing is built out of blocks.
+ - **A clean auto-merge is not evidence of semantic compatibility.** Enumerate what each branch claims to do and re-demonstrate every claim on the merged tree; re-read merged docs; grep the added lines of both sides for a second "one authority" of the same rule; name cross-feature pairs up front and land their test with the merge. A generated artifact is reset to one side wholesale and regenerated after the merge commit exists, never three-way merged. Before correcting a wrong git operation, establish what the correction changes (`merge-tree --write-tree`).
+ - Repeated workflows become skills once done manually twice.
## Conventions
- - **Language policy**: all repo artifacts — docs, code comments, commit messages,
- PR descriptions, player-facing default strings — are **English-first**. English
- is the canonical source; any future i18n translates *from* the English version,
- never the reverse.
- - Rust: workspace at `crates/`, edition 2024, `cargo fmt` + `clippy -D warnings` clean.
- - TypeScript (harness only): strict mode; the harness never contains game logic, only
- assertions and navigation.
- - ADRs: sequential numbers, status field (Proposed/Accepted/Superseded), cite sources.
- Never edit an Accepted ADR's decision — supersede it.
- - Specs: numbered `spec-NNNN-<slug>.md`, each with an explicit "Acceptance criteria"
- section phrased as machine-checkable assertions.
- - Commits/PRs: conventional, small, one concern each.
- - **Privacy in repo artifacts**: repo content never includes personal information
- or verbatim personal speech, and no repository artifact records who decided
- something or when they said it. State a rule impersonally, as a fact about the
- software; personal context and the record of who decided what stay in local
- agent memory or `docs/notes/private/` (gitignored). Applies to every repo,
- including campaign GENERATION logs in the content repo. The sanctioned
- repository identifiers are ADR numbers, spec numbers and DW codes — a task id,
- a PR number or a dated attribution is not one.
- - **Attribution ledger**: any PR that adopts a third-party library, ports an
- algorithm, or leans on a paper adds its entry (with verified license) to
- `docs/ACKNOWLEDGEMENTS.md` in the same PR. Unlicensed sources are ideas-only —
- never ported.
- - **DW-diagnostic coverage**: every DW diagnostic must be covered by at least one
- test asserting its code, CI-enforced (`tools/check-dw-codes.py`, docs job) — a
- minimal, justified allowlist is the only exemption.
+ - **English-first** for every repo artifact; i18n translates from English.
+ - Rust: workspace at `crates/`, edition 2024, `cargo fmt` + `clippy -D warnings` clean. `prefabs/*-generator` are their own workspaces.
+ - TypeScript (harness only): strict mode; assertions and navigation, never game logic.
+ - ADRs: sequential, status field, cite sources; never edit an Accepted decision — supersede it. Specs: `spec-NNNN-<slug>.md` with a machine-checkable "Acceptance criteria" section. Numbers (spec, ADR, DW code, `dsl_version`) are allocated by the planner across every remote ref, never picked by a round.
+ - Commits/PRs: conventional, small, one concern each. Commit messages and every `gh` text argument come from a file, never inline; a measurement belongs in the commit body.
+ - **Privacy in repo artifacts**: no personal information, no verbatim personal speech, no record of who decided what or when. Sanctioned identifiers are ADR numbers, spec numbers and DW codes; a task id, PR number or dated attribution is not one.
+ - **Attribution ledger**: any adopted library, ported algorithm or paper gets its entry (verified license) in `docs/ACKNOWLEDGEMENTS.md` in the same PR.
+ - **DW-diagnostic coverage**: every DW diagnostic is asserted by at least one test; a minimal, justified allowlist is the only exemption.
## Environments
- **Dev**: a developer workstation (macOS). Everything must run locally.
- - **CI-equivalent**: `validation/` docker compose profile — the same image CI uses.
- "Works on my machine" means "the compose profile passes".
- - **Prod**: a delve-hosting single-board computer — which is why release images are
- multi-arch (amd64 + arm64).
+ - **CI-equivalent**: the `validation/` docker compose profile — the same image CI uses.
+ - **Prod**: a delve-hosting single-board computer; release images are multi-arch (amd64 + arm64).