# The demo contract Every demo is one directory under `src/demos//` plus one Python package under `envs/`. The shell renders anything that satisfies the interfaces in `src/lib/demo-kit/types.ts`. Nothing else in the repository needs editing — the header, the gallery, the router and the sitemap are all generated from the registry, and the registry finds demos by existence. That is the whole design goal: **demo number nine must not be able to break demo number one.** ## The rules, and why each exists 1. **The directory name is the slug.** Two sources of truth for a URL is one too many. 2. **`meta.ts` is eager, serialisable and React-free.** It is imported for every demo on every page load, because the header needs all of them to render. `icon` is a lucide icon *name*, not a component — importing the component would pull lucide into the entry chunk on behalf of a demo nobody opened. 3. **`demo.tsx` is lazy.** Everything expensive lives behind it. 4. **A demo imports from `@/lib/demo-kit` and nowhere deeper.** Reaching into `@/lib/demo-kit/player` couples a demo to an implementation detail; the barrel is the contract's surface. 5. **A demo never imports from `@/components/demo/`.** The shell renders demos; demos do not reach into the shell. 6. **The shell never mentions a slug.** `if (slug === 'wordle')` in `src/components/demo/` is a contract bug — either fix the contract or expose a `SlotRegion`. `check-demos` fails on it. 7. **Reward weights sum to 1.0, and at least one component is a `counterweight`.** A reward with only objectives teaches the crude version of what you asked for. 8. **A counterweight must be in genuine tension with the objective.** If every good policy also scores 1.0 on it, it is a `gate` — declare it as one. The probe ladder is where you prove the difference: two good policies, neither dominating. 9. **`null` is "not scored", never `0.0`.** A zero is a claim that the policy did badly. A null is an admission that we do not know. Rendering the second as the first is the quiet way a demo starts lying. 10. **Every `DemoStep` sets `announce`.** `prefers-reduced-motion` clamps every animation to nothing, so colour alone carries the result — and colour alone is not a result. The announcement *is* the feedback for anyone not looking at the screen. 11. **A `RunRef` with `kind: 'intervened'` must name its `intervention`.** This exists so a prompt change can never be presented as a training result by omission. 12. **A `spec` demo ships a real specification** — task, legal actions, deterministic grader, counterweight, and the eval command that would run it. A coming-soon card is not a specification, and `check-demos` refuses one. ## Adding a demo ```bash pnpm demo:new claims # copies src/demos/_template and envs/_template ``` Then, in order: 1. **Write the environment first**, in `envs//`. The engine, the reward, and a probe ladder. If the reward cannot be probed, it cannot be trusted, and nothing downstream is worth building. 2. **Port the scorer to TypeScript** and add it to the conformance gate. The browser must be able to re-derive what the environment recorded, or the verify badge is decoration. 3. **Capture rollouts** with `envs/capture.py`. Include at least one run the agent loses; a demo where the agent always wins teaches nothing about the reward. 4. **Write `meta.ts`, `narrative.ts`, `surface.tsx`, `adapter.ts`** and the demo module. The narrative's `limits` must name which demo answers each gap. 5. `pnpm check && pnpm build`. ## The marker syntax for code receipts `RewardSpec.source.marker` selects a region of a source file to highlight: ```python # region: pig-demo/reward ... # endregion: pig-demo/reward ``` Exclusive of the marker lines, **exactly one pair per file**. Zero or two or more is a fatal error in `check-receipts.mjs`, because a silently-wrong region would quote the wrong code under a claim that it is the code that ran. ## What to expect to change The contract was written against a turn-based game. The first vertical demo is deliberately a one-shot classifier, so it stresses the weakest axis — no engine, no interactive driver, possibly a single run. **Budget one demo-kit refactor there and treat it as expected rather than as a failure.** If more than a slot is needed, widen the contract once, deliberately, updating `CONTRACT.md` and `_template` in the same commit.