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