# TOASTSIM — Build Brief 3: FIRE (the pan, the steak, the salt doctrine) *Read this and execute it. It is written to work cold for an Opus 4.8 session with no other context. It cuts MASTERPLAN.md Phase P2 into buildable milestones. Read MASTERPLAN.md §1–§3 first (the design law and the bones), then §0.5 STATUS in OPUS-BUILD-BRIEF-2.md (everything M0–M15 is DONE and gate-verified — do not rebuild any of it), then this file top to bottom.* ## 0. STATUS (update this section as you go — a cold session reads here first) - Nothing in this brief is built yet. Brief 2 (M10–M15) is fully executed: bakery, prep bench, juicer, grater, onions, bruschetta — all routed into days 10–14, all gate-verified with numbers in the commit log. - **LIVE 🌐** https://partly.party/toastsim/ — deploy with `bash scripts/deploy.sh` (build `--base=/toastsim/` → rsync staging → docker cp into `forum-nginx`). Cloudflare fronts it; curl cache-busted. - The asset pipeline works end to end (34 GLBs + full 2D on disk). Fire new batches FIRST, code while they render. ## 1. Working agreements (how this repo builds — non-negotiable) 1. **The design law** is MASTERPLAN §1. One game. Every mechanic rides the bones (Field / point stats / dose curves / temp clocks / dwell commits / judge bands) or improves them for everyone. 2. **Harness first, verify always.** Every mechanic gets deterministic `src/dev.ts` helpers driving REAL input (study `t.chop`, `t.juice`, `t.bruschetta`). Every milestone commits with measured numbers in the message and the exact console calls a reviewer can replay. Typecheck (`./node_modules/.bin/tsc --noEmit`) and `npm run build` stay clean. If you have a browser, verify in it; if you are a worktree agent, compile the pure sims and measure headless — the reviewer runs the live gate. 3. **Milestone discipline**: one commit per milestone, message = what you measured. One NEW pressure per day, never two. If a feel is wrong, fixing feel beats advancing. 4. **Failure must read as a lesson, not a lottery.** M13's knuckle retune is the precedent: careful play must be RELIABLY safe (10/10), reckless play reliably punished. If a punishment fires probabilistically on correct play, that's a design bug — add the floor. 5. **Multi-agent lanes** (if built by parallel agents): pure sim + its scene + its `dev-*.ts` sandbox is one lane; `src/game/*` routing + `src/dev.ts` is another; never two agents in one file. Integration points are exported functions documented in-file (`juiceResult()` is the pattern). Reviewer holds the browser and gates every merge live. 6. **Pre-authorized:** all tuning constants; cutting scope inside a milestone to protect feel; procedural-vs-generated per asset. **Forbidden:** paid or cloud dependencies, accounts, telemetry, touching `~/Documents/sandoniette` or `~/MODELBEAST` internals (use `mb` only). ## 2. Known traps (each cost real time once — don't pay twice) - **Asset paths must go through `assetUrl()`** (`src/scenes/assets.ts`) or `loadProp` — a bare `'/assets/...'` works in dev and silently breaks under the live `/toastsim/` subpath (the root `/assets/` on partly.party is another game's and returns 200 with WRONG content). - **Panels are born hidden.** Every scene's `Panel` gets `.hide()` in the constructor; only `enter()` shows it (the kitchen leaked over every station for four milestones before this was found). - **Harness helpers must drive the real flow.** `t.roast()` standalone reads a stale oven session if the oven wasn't opened by the day flow — drive failure paths through the full order sequence. Guard new helpers against this (throw or auto-route rather than return stale numbers). - **MODELBEAST machine facts** (verified 2026-07-18): client at `~/MODELBEAST` (NOT ~/Documents), token `MB_TOKEN` in `~/Documents/backnforth/.env`, `MB_HOST=http://100.89.131.57:8777`. Node m4's venvs are dead; `scripts/gen-assets.sh` already retries every stage 10× with 60s pauses — keep that until m4pro is rebuilt. - **Worktree agents**: symlink `node_modules` from the main checkout to build; remove the symlink before committing. Never `git add` a worktree directory into the main repo (it's gitignored now, but stay awake). - **`mergePrep` merges multi-step prep orders** (worst CV, summed mess) but no shipped order exercises it — if P2 ships a multi-step day, verify it. ## 3. The salt doctrine — build it ONCE, it outlives the steak (M16) Salt is two objects and a timing question (MASTERPLAN §2), and the **dose curve** underneath it is a first-class bone that capers/wasabi (Brief 2 §7.5) will reuse verbatim later. Get the shape right here. ### 3.1 `src/sim/dosing.ts` (pure, no three.js) - `DoseCurve { under: number; band: [number, number]; ruin: number }` — score(mass): below `under` = unnoticed (0.5, not punished, just no credit); inside `band` = 1.0; between band and `ruin` = falling fast; past `ruin` = 0 and a named incident (`ruined: true`). - `DoseSession`: repeated pinch gestures accumulate mass with per-pinch variance (a pinch is ~0.08–0.12, seeded RNG). The SKILL is stopping. - Two salts as data, not code: `table_salt` (fine, dissolves, `phase: 'cooking'`) and `flake_salt` (the good stuff, `phase: 'finishing'`). - `saltTimingScore(salt, when)`: the matrix. Table during cook = 1.0; table at the pass = 0.5 "sad little grains"; flakes at the pass = 1.0 + small bonus; **flakes into the pan = 0.15 and the judge's named sin** (line bank: "You cooked with the finishing salt. I can hear it weeping."). - Integration exports, documented in-file: `doseResult()`, `doseCriteria()`, `SALT_LINES`. ### 3.2 Scene + routing - Pinch gesture: hold over the dish + tap (or short drag) = one pinch; the salt cellar (fine) and salt dish (flakes) are two distinct props at the station — WHICH one you reached for is the whole timing test. - Wire salt into two existing moments, small: a "season it, love" modifier on a toast day (salt on buttered toast — flakes at the pass, correct), and into the M17 pan (below). No new day needed for M16 alone. - Harness: `t.pinch(n)`, `t.salt(kind, when)`, `t.doseStats()`. - **Exit bar**: dose curve measured at 5 masses (under/band-low/band-mid/ over/ruin); wrong-salt matrix all four cells measured; a 3-pinch band landing scores 1.0 ten out of ten (variance must not push a correct pinch count out of band — floor it like the knuckle roll). ## 4. M17 — THE PAN (the one new primitive of P2) The pan = the toaster generalized to a continuous, visible, modulated heat source under a Field. Everything else in P2 rides it. ### 4.1 `src/sim/pan.ts` (pure) - `PanSession`: heat knob 1–9 (dwell to change, like the browning dial), pan temp drifts toward knob setting on the temp clock (thermal lag ~8s — the lag IS the skill). - **Butter as medium** (MASTERPLAN §2): drop butter in → state machine on (temp × time): `cold → melting → foaming → browned (noisette) → burnt`. Foaming is THE window ("the moment"); noisette = flavour bonus if you land food during it; burnt = bitterness penalty + smoke + mess. States must be readable from sound/visual cues, not a meter. - **Sear Field**: per-side 2D browning Field on the food (reuse the browning math — same Field class), driven by contact + pan temp + butter state. Flip = a gesture (drag up-and-over, dwell commits, tongs prop). - **Basting**: spoon-tilt gesture paints hot butter over the up-face — a spread stroke that writes into the sear Field of the side NOT touching the pan. Comedy and skill: baste with burnt butter and you paint bitterness. - First customers: **mushrooms and bell pepper** (already in the cast, `roastable: true` — mushrooms brown beautifully, wet pepper spits). - Harness: `t.pan(heat)`, `t.butterIn()`, `t.foodIn(id, side)`, `t.flip()`, `t.baste(n)`, `t.searStats()` → per-side mean/stdev, butter state log. - **Exit bar**: butter state timeline measured at heat 4/7/9 (foaming window duration at each); a mushroom cooked flip-at-the-right-time gives two sides within 0.1 mean of each other; never-flipped = one side 0.9+ burnt / one raw; baste during noisette raises up-face mean with the bonus flag; burnt-butter baste writes bitterness. Numbers in the commit. ## 5. M18 — THE RIB EYE (the crown jewel) Five judged acts (MASTERPLAN P2). The steak is one ingredient but the biggest single ingredient build in the game — treat its sub-feels as gates. ### 5.1 The ingredient - `rib_eye` in `src/sim/ingredients.ts`: `shape: 'block'`-ish (its own silhouette is fine), `grain: { axis: number }` — NEW field, the fiber direction. `flesh.moisture` starts 0.95 raw. - Fridge/bench temp state (cold steak sears worse — reuse tempclock; ticket hints "get it out early, love" — the brie lesson generalizes). ### 5.2 The acts 1. **Season** (uses M16): table salt before the pan (proper early = best, just-before = fine), flakes only at the pass. The timing matrix judges. 2. **Cook** (uses M17): sear Field per side on the pan, doneness = interior ramp — a 1D depth profile per Field cell (surface sear vs center temp; reuse the browning ramp pointed inward). Target from the ticket: rare/medium/ruined bands. Butter-baste finishes it. 3. **REST** — the nerve test and the named feel of P2. Rest clock: moisture decays 0.95 → 0.6 over ~20s of real time. Cut at t: juice flood mass ∝ current moisture − 0.6 — cut at 0s and the mess field drowns the board (the flood MUST be spectacular — it's the lesson); cut at 20s and it's a whisper. The ticket tempts ("he's waiting, love"); the judge times you. NO UI timer — the steak visibly relaxes (subtle slump + steam rate) and that's your only clock. 4. **Cut** (rides the cutting sim): score cut-angle vs `grain.axis` — across = 1.0, along = "chewing gum" penalty; wobble TEARS (uses the existing wobble accumulator with a meat-specific tear threshold). 5. **Pair**: the mustard family — 4 jars as drawer entries (hot english / dijon / wholegrain / american), the order names or implies one (procedural line flavor: "something with a BITE, love"). Wrong mustard = pairing miss (judged, small weight), not a technique fail. - Judge groups: **THE SEASON / THE COOK / THE REST / THE CUT / THE PAIRING** + The Bench. 8+ lines each, house voice. The rest-flood line bank deserves love ("You cut it EARLY. It wept. We both watched."). - Harness: `t.steak({saltAt, heat, flipAt, restS, cutAngle, mustard})` one-call driver + individual verbs; `t.steakStats()` grouped. - **Exit bar**: doneness ramp measured at 3 cook profiles; rest flood mass at 0s/8s/20s (monotone, spectacular at 0); across vs along grain scored 1.0 vs ≤0.3; wobbled cut tears; the full matrix day: a perfect steak scores S, each single sin drops exactly one named row. The FEEL gate (reviewer judges in browser): you must genuinely want to cut early, and the flood must teach you never to do it again. ## 6. M19 — THE STEAK DAY + stage close-out - Day 15: "rib eye, medium, with the right mustard" — full routing: fridge decision at order start → season → pan → rest (the kitchen keeps moving: toast still due!) → cut → plate → pass with flakes. The steak resting WHILE you do other work is the planning pressure — one new pressure, as always. - Escalation hooks (data, not systems): later procedural days can ask rare/well, different mustards, mushrooms-on-the-side (M17 reuse). - Update §0.5 STATUS in Brief 2 AND §0 here; commit; deploy (`bash scripts/deploy.sh`); screenshot the steak scorecard for the log. - **Exit bar**: `t.day(15)` end-to-end S run live in the reviewer's browser with numbers; the three P2 feels signed off: the foaming window, the early-cut flood, the across-grain slice. ## 7. Assets — fire this batch FIRST (seeds 180–209; style/shot tokens from gen-assets.sh) Add a `do_fire()` lane to `scripts/gen-assets.sh` (pattern: `do_pantry`): - 3D: cast-iron skillet w/ pour spout (183), raw rib eye with marbling (186 — prompt the marbling explicitly), cooked steak rested + sliced showing pink interior (189), salt cellar with tiny spoon (193), pinch dish of flaky salt (196), butter block on paper (199), steel tongs (202), 4 squat mustard jars — red/yellow/brown/grain lids (203–206), basting spoon (209). - 2D: pan-smoke wisps sprite (231), steam curl sprite (234). - Steak INTERIOR (the doneness gradient) stays procedural — it carries gameplay. Marbling texture on the raw prop is art; the sear Field is sim. ## 8. Milestone order and the bar M16 salt → M17 pan → M18 rib eye → M19 steak day. One commit each, message = what you measured. M16+M17 parallelize as two lanes (dosing/scene vs pan/scene) with game routing in a third; M18 is single-lane (integrative); M19 is routing + polish. After M19 ships: STOP, update both STATUS blocks, deploy, and cut Brief 4 from MASTERPLAN P3 (eggs & air) — re-planned against what the pan taught you about heat feels. Do not start P3 inside this brief. The bet, restated: the pan is the toaster with its dial ripped off and handed to you mid-cook. If the foaming window, the early-cut flood, and the across-grain slice all FEEL right, P2 turns the toast shack into a kitchen — and the judge finally has something worth unsheathing his best lines for.