Build Brief 3: FIRE — the pan, the rib eye, the salt doctrine
P2 cut from MASTERPLAN for an Opus 4.8 session to execute cold: M16 salt (the dose-curve bone, two salts, the timing matrix and its named sin), M17 the pan (butter state machine, sear Field, flip, baste — mushrooms and bell pepper as first customers), M18 rib eye (season/ cook/rest/cut/pair — the early-cut juice flood is the lesson, no UI timer, the steak's slump is the clock), M19 the steak day. Working agreements + every trap this session paid for (assetUrl subpath, born- hidden panels, stale-harness guard, m4pro retries, worktree hygiene). Asset batch seeds 180-209. Ends with: deploy, then cut Brief 4 from P3. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
db31273fc9
commit
5d29bb2d96
225
OPUS-BUILD-BRIEF-3.md
Normal file
225
OPUS-BUILD-BRIEF-3.md
Normal file
@ -0,0 +1,225 @@
|
||||
# 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.
|
||||
Loading…
Reference in New Issue
Block a user