Commit Graph

63 Commits

Author SHA1 Message Date
type-two
322aeec16d THE GHOST: '▶ watch him do it' — every day driver replays as a live demo
The dev harness ships in production now, because it turned out to be the
best teacher in the building. Any ticket whose day has a registered driver
grows a '▶ watch him do it' button: the same deterministic script that
verifies the day in CI performs it live on the real scene, real input,
real judge — then the judge waves it off ('A demonstration. None of it
counts. Now you.'), the save restores from a snapshot, and the day deals
again for the player to play for real.

The hard part was time. A demo must ride the app's own rAF loop to be
watchable — but hidden tabs pause rAF AND throttle setTimeout down to one
fire per MINUTE, which froze the show mid-egg. F(n) now has three gears:
sync (verification), rAF-riding (visible demo), and fast-forward when
document.hidden — no audience, no theatre; he finishes the dish while
you're away. Also fixed a straggler from the async conversion: the
egg-day float loop fired six un-awaited drags concurrently, so nothing
floated, the liar egg passed as safe, and the demo cracked it into the
bowl (5.6/10 with a fart). Awaited, the glass does its work.

Verified live, hidden-tab and visible: demos score 15:9.4 16:8.0 17:10.0
19:9.0 20:10.0 21:9.9 22:9.9, localStorage untouched during playback,
NEXT restores the snapshot and replays the day for real.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:27:07 +10:00
type-two
56389dabab THE DAILY DEAL + SHARE: one deal for everyone, a result you can paste
THE DAILY — a button on the title deals one shared order per real date:
the seed IS the date (FNV hash), so everyone on Earth gets the same deal
today, station and all. Scored into save.daily[date] (best-of), the
campaign day counter untouched — NEXT after a daily returns you to your
regular day. The title button becomes 'THE DAILY ✓ 9.9' once played.
Judge closes with 'The Daily. Same deal for everyone today — come back
tomorrow.'

SHARE — a button on every verdict copies a one-line result to the
clipboard: 'TOASTSIM daily 2026-07-20 — 🥚 S 9.9/10 · partly.party/
toastsim' (station emoji per order; campaign days read 'day N'). The
wordle-paste for toast.

Verified live: daily deals deterministically (same date → same order,
today's is a poach), played to S 9.9, best saved, share text exact,
NEXT preserved campaign day 20, title shows the checkmark.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 16:44:41 +10:00
type-two
f74ee61bbd THE POLAROID: a shareable verdict card from the judge screen
A POLAROID button on the scorecard re-renders the dish under the
spotlight, centre-crops it into a cream polaroid frame, and stamps the
day, the grade (coloured), the total, and the judge's line of the day
in italics — 'partly.party/toastsim' in the corner — then downloads as
toastsim-dayN-GRADE.png. The judge's lines are the most quotable thing
in the game; now they travel.

Verified: PNG downloads (real capture — frame cream, photo window lit,
not the black-buffer trap; explicit re-render before toDataURL).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 04:57:36 +10:00
type-two
9c4e366b65 The benedict (day 22) + the kitchen's memory: regulars and streaks
THE BENEDICT — the first toast+poach composition: day 22 toasts the
bread in the kitchen, then onPopped routes to the poach pot instead of
the drawer (the bruschetta's oven trick, generalized); judgeBenedict
reads THE BREAD off the slice (browning/evenness/char) and THE POACH off
the egg (white/wobble/form), and The Plate sogs BOTH if you serve the
egg undrained — 'You put a wet egg on good toast. Now it is neither.'
Also in the endless rotation (electric pot past day 28).

THE KITCHEN REMEMBERS — recordVerdict on every serve path:
- Streaks: consecutive S grades tracked in the save; the judge notices
  at 3 ('I am beginning to suspect competence.'), 5 ('I have started a
  file.'), every 5 after, and eulogizes a broken one ('It was 7. We do
  not speak of it.').
- The regulars: per-customer visits/last/best in the save. Return
  tickets read 'visit №3 — last time: S' (or '...F. they came BACK.'),
  and the judge compares: worse-than-last-time and redemption both get
  a line. Delivered via JudgeView.extraLines, one-shot per verdict.

Verified live: day 22 starts at the kitchen, poaches after the pop,
combined card 9.1/10 with the architecture line; streak 1->2->3 fires
the line at three; day-20 rebooking shows the visit note.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 04:52:36 +10:00
type-two
bffee40c6f THE POACH: day 21 — the shiver, the vortex, the drop, the wobble test
The near-game, treated with respect (masterplan P3). sim/poach.ts (pure,
rides the heat-source bone): the pot chases the knob on the fuel's lag
and the poach wants the SHIVER (5.8-7.4) — cooler rags the white on the
way in, a rolling boil (>8.2) tears it the whole time. The VORTEX is the
reamer-twist at pot scale: swirl builds it, it DECAYS (swirl then drop,
not swirl then admire), and the strength at the drop is the judged form.
Two-layer set: white first, the yolk only firms once the white is mostly
there — lift in the window (white >=0.85, yolk <=0.35) or you've boiled
an egg the long way. Then DRAIN on the slotted spoon. Stirring a dropped
egg is vandalism (rags it directly).

scenes/poach.ts: WHEEL flame, circular-drag swirl (real angular sweep),
SPACE drop, L lift, hold-to-drain, ENTER serves; vortex streaks that spin
and fade, shiver-vs-boil bubbles, the white gathering tighter as it sets,
a hard yolk going pale. judgePoach: THE POACH (White/Wobble/Form) + The
Drain; the wobble line is the dossier's: 'It should tremble. Like you,
right now.' Day 21 authored + poach in the endless rotation (electric
pot past day 27).

Verified with REAL input: shiver + 91% vortex + lift-at-tremble + drain
= 9.9/10 'it TREMBLES — perfect', 'one neat comet (vortex 91%)'; still
water into a rolling boil, overstayed, undrained = 3.6/10, 'a pot of
rags', 'it does not move at all'.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 02:34:50 +10:00
type-two
717ef56150 THE EGGS: day 20 — the crack, the separation, the float test, the beat
The egg dossier lands (atlas §1.5). sim/eggs.ts (pure): a carton of six,
each egg rolling freshness off the carton's age; the FLOAT TEST reads it
out (sinks flat / stands up / FLOATS) for free; the CRACK is a strength
window (soft = nothing, window = clean, hard = shell in the whites, fish
the bits out); SEPARATION passes the yolk shell-to-shell (fast breaks it,
old yolks are weaker — the float test also tells you how gently to pass);
a ROTTEN egg cracked blind ruins the bowl: green puff + a seed-varied
synth FART (audio.fart — pitch/length/wobble per egg, the tremolo loses
its nerve halfway). D dumps a ruined bowl; the dump clears the whites but
NOT the judge's memory (rottenCracked survives — no sin-laundering).
An old carton always has at least one liar in it (deterministic; the
glass finds it — floor, not lottery).

scenes/eggbench.ts: drag-to-glass float test, drag-to-rim + HOLD-SPACE
charge crack, alternate arrows to separate, click shell bits to fish.
judgeEggs: THE EGGS (Count/Shell/Whites) + The Nose; full line bank
('There is a GLASS OF WATER on the bench. It exists for exactly this.').
Day 20 authored (pavlova order) + eggs arm in the procedural rotation
(older cartons + need 4 past day 26).

Verified via REAL input: float-all + dodge + patient passes = 10.0/10;
blind rotten crack = the beat, dump recovers whites, The Nose remembers
= 6.3. Sim bars: crack window, float words, old-yolk weakness, fishing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 01:53:08 +10:00
type-two
64bf04c0b6 The endless mode uses the whole kitchen; the ledger makes days replayable
1) Station-mix procedural days: past day 19 the kitchen deals from every
station it taught — toast ~34%, then prep cuts (technique onion dice past
21), juice (better reamers + more pips later), the grill (4 pieces past
24), the pan (electric arrives 22, induction 26), the rib eye (random
grain, electric past 23), and the fridge delivery. Each with its own
ticket voice. Deals are seeded BY DAY: a reload re-deals the identical
order (no reroll-scumming) and the harness can reproduce any day.

2) THE LEDGER on the title: one chip per reached day with your best grade
stamped on it (S green through F red), click to replay. save.maxDay
tracks furthest-ever progress so replaying day 5 for the S never locks
day 19 away; old saves migrate (maxDay defaults to day).

Verified live: 26-day sweep deals all 7 stations, deterministic re-deals;
chip click starts day 5; procedural day-26 steak (electric fuel ramp)
played end-to-end: rested, cut across a random grain, 9.1/10.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 01:27:25 +10:00
type-two
1e5d069c99 Rib-eye review fixes: rest-per-cut, no-cut guard, sear evenness, GPU leak (6 findings)
Adversarial review of the steak day found seven issues; fixed six:
- REST SCORE gamed by slice count (bug): flood summed over cuts, so
  fewer slices faked a better rest. Now scored on the PER-CUT bleed
  (moisture at the moment you committed) — cutting 1 vs 6 slices at the
  same rest is identical (verified 0.682 == 0.682); slice count is pure
  visual now.
- ZERO-CUT SERVE rewarded (design): an uncut steak scored restScore 1.0.
  steakResult now gives 0 rest with no cuts, and the board blocks serving
  until you've actually cut.
- UNEVEN SEAR masked (edge-case): The Cook used only (down+up)/2, so one
  charred + one raw face averaged to 'perfect'. sideGap now threads pan ->
  board and docks cookScore (an uneven sear isn't the right steak).
- GPU LEAK (bug): grain lines + slice seams were removed from the scene
  but never disposed across rounds. reset()/dispose() now free them.
- panIngredient could go stale onto a steak day (nit): cleared in beginDay.
- FLOOD FEEDBACK on three different scales (nit): the puddle + '% bled out'
  HUD now track the same /1.1 the judge scores on — what you see is what
  he tastes.
Left #7 (pan 'well-seared' tell sits hotter than a MEDIUM steak wants):
over-searing a medium is correctly a Cook penalty; S stays reachable.

Verified: good rest+across -> S(9.4); cut early -> 5.3 (bled); rest score
count-independent; zero-cut -> rest 0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 23:29:05 +10:00
type-two
f81d1b5c82 The rib eye is playable: day 19, sear -> REST -> cut (it bleeds)
Wired steak.ts into a live day. scenes/steakboard.ts — the resting board:
the steak lands off the pan full of juice, DRAG across the grain (pale
fibre lines shown) to slice; cut it before it rests and a dark juice
puddle FLOODS the board. Day 19 routes pan-sear -> resting board -> judge
(pan's two seared faces carry the interior doneness). judgeSteak scores
THE STEAK (The Cook / The Rest / The Cut) + The Board, with a steakhouse-
grandfather line bank ('You cut it the second it left the pan. It bled out
on the board. I watched the whole thing.').

Verified live end-to-end: rested + across grain -> S (9.5); cut hot -> 'it
bled' -> 5.4; rested but along the grain -> 'chewy' -> 6.9. The three acts
each move the grade. Real-drag cutting confirmed (4 slices, flood 6.9).

Also fixed a latent crash: verdictLines could call fill() with an
undefined line when the best-scoring row (e.g. The Board) had no BANK
entry — now guarded.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 23:05:45 +10:00
type-two
848f4b3fcb The rib eye sim: rest clock (it bleeds), grain-aware cut — verified
src/sim/steak.ts — the crown jewel's three acts as pure sim. REST: a
steak comes off the pan at moisture 0.95 and relaxes toward 0.6 over ~20s;
cut it hot and the juice FLOODS the board, rest it and it's a whisper (no
UI timer — the meat tells you). CUT: a grain axis; across = |sin(cut-grain)|
tender, along = a rope; knife wobble tears instead of slices. COOK:
interior doneness vs the rare/medium/well target.

Verified via t.steakMarquee: cut hot -> flood 10.5 (rest 0); rested ->
flood 0 (rest 1); across grain cut 1.0 vs along grain 0; wobble -> 6 tears
(cut 0.2). The 'it bleeds' feel John wanted, measured.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 22:54:28 +10:00
type-two
afe2bf174b Cold-chain review fixes: reload-dodge, crash-safety, fair ceiling (6 findings)
Adversarial review of the cross-day loop found six real issues; all fixed
and re-verified:
- RELOAD EXPLOIT (edge-case): enterPan consumed + persisted fridge stock
  at day-START, so a reload mid-pan re-fetched null and DODGED the rot-cap
  (and honest players lost the ingredient). Now the consumed stock is held
  (pendingFridge) and committed atomically with the day advance in onNext —
  a reload re-fetches the same item deterministically. Verified: after
  entering day 18 the mushroom is still in save.fridge until the day turns.
- CRASH (edge-case): loadSave passed save.fridge unvalidated → restoreStore
  hard-crashed on a corrupt/unknown-zone entry. restoreStore is now
  corruption-tolerant (guards Array + known zone). Verified: garbage save
  → no crash, falls back to a fresh ingredient.
- FAIRNESS (design): The Ingredient scored linearly on quality, so even a
  perfectly-stored 'fresh' mushroom (0.77) capped the ceiling below S. Now
  scored on the freshness BAND — fresh = full marks — so optimal cold
  storage reaches S (verified 0.77 'fresh' from the coldest back shelf),
  use-soon dents (A), off/dangerous near-zero + hard-cap (F).
- word/quality mismatch: usableQuality's word now derives from the docked
  quality (a thawed item reads its real handed-over state).
- freezer thaw 0.82 -> 0.78: a 3-day-frozen mid-perishable (0.75) no longer
  out-scores the coldest fridge shelf (0.77) — the texture cost is real.
- rename-safety: FRIDGE_MUSHROOM_ID shared constant couples the two days.

Gradient verified: optimal->S, decent->A(7.4), bad->F(3.0); plain pan 8.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 22:15:39 +10:00
type-two
3f91bedc7f The cold chain closes the loop: stock day 17, cook it day 18
The fridge persists in the save (serializeStore/restoreStore/fetch in
coldchain.ts). Day 18 'the lunch rush' pan-sears the very mushrooms you
stored on day 17 — fetched FIFO at whatever freshness your storage left
them. judgePan gains 'The Ingredient' row; a dangerous/off one HARD-CAPS
the total (no sear saves rot). Judge line bank for the fetched ingredient.

Verified live: identical cook (72%/57% sear), well-stored mushroom serves
7.4/10, badly-stored (warm door) serves the SAME sear but 3.0/10 (F) with
'The Ingredient: off the shelf — dangerous' capping it. Plain pan day 16
unaffected (8.0) — backward compatible. Day-17 delivery gains mushrooms;
Order.pan.fromFridge flag; save carries the stock.

Harness: t.coldChainArc(good?).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 21:52:27 +10:00
type-two
2085f68655 The cold chain is playable: day 17, put the delivery away
Wired coldchain.ts into a live day. scenes/fridge.ts — a front-view fridge
with zones (freezer drawer / cold back / mid shelves / warm door + glass
shelves), a delivery you drag in, items that recolour as they spoil. Day
17 'put it away': stock it right, ENTER shuts the door (3 days pass),
ENTER again and the inspector opens it. judgeFridge scores THE FRIDGE
(The Cold / Hygiene) + The Waste; hygiene-inspector line bank ('Raw
chicken dripping onto the salad. That is not a fridge, that is a crime
scene with a light in it.').

Sim fix: contamination drips across the whole fridge interior by shelf
height (raw-on-bottom is the real lesson), not just within one zone.

Verified live: day 17 routes to the fridge; good storage (raw low,
perishables cold, RTE high) grades S (10.0); bad storage (raw high in the
warm door, crammed) grades C (3.5) — 38% off, 38% dripped on, 3 wasted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 21:22:20 +10:00
type-two
a2ecfdda8f Atlas P10.5: the cold chain doctrine (spoilage/placement/rotation/freezer)
Cure's defensive twin, spec'd around the verified coldchain.ts sim. The
scene + cross-day carryover + inspector hygiene row are the remaining
build to make it playable; composes with the shop, the eggs' float test,
and the cure (same day-clock, opposite sign).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 20:56:55 +10:00
type-two
e7ed091077 The cold chain: spoilage, placement, rotation, contamination (sim)
src/sim/coldchain.ts — the temperature clock pointed the other way. Every
perishable has a freshness 1->0 that only falls, on the DAY clock (the cure
system's defensive twin); how fast is decided by the zone's cold. Plus the
three disciplines on top: PLACEMENT (fridge zones door->back warmest->coldest,
cram it and airflow chokes, raw-over-ready-to-eat drips and contaminates),
ROTATION (FIFO pull -- use the oldest or it rots in the back = waste), and
THE FREEZER (stops the clock but the thaw costs texture). Fresh -> use-soon
-> off -> dangerous.

Verified headless (t.coldZones/coldRotation/coldOverpack/coldContam/coldFreezer):
- zones (raw fish, 3 days): bench/door dangerous, fridge off, back use-soon,
  freezer fresh (0.94) -- a real placement gradient.
- rotation: FIFO 0 waste vs newest-first 2 tubs rotted.
- overpack: crammed fridge spoils milk to 0 vs 0.29 sparse.
- contamination: raw-above-salad -> unsafe (q 0.29); same-shelf -> safe (0.88).
- freezer: preserves 0.93 the bench lost (0), thaw docks usable to 0.76.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 20:55:55 +10:00
type-two
8b13ac68a8 M17 pan shipped as a playable day (16); heat brief complete
Routed the pan into a real order: day 16 'pan-sear the mushrooms on the
gas' — butter in, wait for the FOAM not the smoke, land the food, brown
both faces, baste in the noisette. judgePan scores THE SEAR / BOTH SIDES
/ THE BUTTER with a butter-snob line bank. New scorecard group THE PAN;
the cast-iron skillet GLB renders with a fuel-coloured flame.

Balance fix (the floor): food shields the butter once it lands, so a
clean cook keeps the butter at noisette instead of racing to burnt — a
full sear is now winnable. Emergent fuel character kept: induction sears
both sides dead-even (0.016), gas flares the first side so you turn it
early (0.085) — both under the 0.1 bar.

Verified live: day 16 routes to the pan, plays end to end, grades A;
judge nails it ('cooked in different postcodes... foamed not burnt, you
were watching'). Cast-iron pan + butter + searing mushroom on screen.

Heat brief M-H1..M-H6 + M-H4 all shipped and live.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 20:43:30 +10:00
type-two
c528ba49ab M17 + M-H4: the pan, born fuel-aware — butter, the flip, the baste
src/sim/pan.ts (pure) — the toaster generalized with two new things:
BUTTER as a state machine (cold->melting->foaming->noisette->burnt, keyed
on pan temp x time) and a second SIDE. The pan reads a HeatSource, so the
knob commands a target and the pan temp chases it on the fuel's lag; gas
foams fast, electric drags and remembers, induction is exact and never
flares. Per-side sear Fields (down side browns fast, up side radiant-slow
until you flip or baste); baste paints hot butter onto the up face (noisette
= bonus, burnt = bitterness); rendered fat flares a flame, never induction.

Verified headless (all M17 + M-H4 bars):
- butter foaming window shrinks with heat: 10.4s @4, 6.0s @7, 4.6s @9.
- well-timed flip -> both sides within 0.008 (bar <0.1); never flip ->
  one side 1.2 (burnt), other 0.23 (raw).
- baste in noisette flags the bonus, up-side 0.073->0.433; burnt-butter
  baste writes bitterness 0.48.
- M-H4: noisette on gas 11.0s vs electric 14.4s (~3.4s faster); induction
  output pinned at knob 8 with fat searing (never flares).

Harness: t.panButter/panFlip/panNoFlip/panBaste/panFuel.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 20:30:34 +10:00
type-two
94a5f2f293 Heat M-H6 shipped: the charcoal grill is a playable day (day 15)
Routed the grill into a real order: day 15 'chuck these on the barbie'
sends the whole day outdoors — bank the coals hot one side / cool the
other, sear each piece then move it to the lee before the fat flares,
serve. judgeGrill scores THE SEAR / THE CHAR / THE GRILL with a char-snob
line bank ('There is no char on this. There is a memory of char. A
rumour.'). New scorecard group THE GRILL + grill/bruschetta order headers.

Balance retune (the floor): RENDER_AT 0.62 lifted above the good-sear
window so a clean sear is reachable with NO flare — sear-then-pull is a
reliable win, only overcooking invites the flames.

Verified end-to-end in-browser: day 15 routes to the grill; careful
sear-and-pull grades S (9.4/10), 100% well-seared, no carbon, no flares;
leaving pieces over the coals chars to carbon (0.9/10, 31 flares). All
M-H6 sim bars still green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 16:27:54 +10:00
type-two
ff1c3deafc Heat M-H6 scene: the charcoal grill, top-down and glowing
scenes/grill.ts (GrillView) — the coal bed rendered as an additive glow
texture from the zone Field, food pieces you drag between the sear zone
and the cool lee, flares that lick up when fat renders over hot coals.
Registered in game.ts (hidden, in hideAllScenes, grillView getter).
Harness t.grillDemo drives the real scene end-to-end.

Verified in-browser: banked a bright hot-left bed, placed food across the
gradient, cooked through the real View — bed glows, food browns, flares
fire. Screenshot confirms the two-zone gradient reads at a glance.
Framing fix: glow above the grate, camera overhead, low open bowl (the
kettle GLB is a closed dome that occludes a top-down cook).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 16:14:04 +10:00
type-two
81c0151975 Heat M-H6 sim: charcoal is a Field you arrange, not a dial
src/sim/charcoal.ts — the fuel with no knob. bankCoals paints a 2D heat
bed (a screaming zone + a cool lee); zoneDecayStep only cools it; food
sears by the coal heat beneath it; a rendering piece over hot coals
flares and chars until you move it. One bed, two outcomes by placement.

Verified (t.zoneStdev/zoneDecay/grillTwoZones/grillFlareDodge):
- two-zone bed stdev 0.303 (bar >0.25) — a real gradient.
- bed ceiling monotone-decays 1.68->0.61 over 90s (sear early, hold late).
- hot-zone piece sears 0.72 while cool-lee piece holds at 0.
- FLARE DODGE: left over coals = done 2.12 / 46 flares (carbon); moved to
  the lee in time = 0.66 clean sear / 0 flares. Active play is the floor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 16:04:05 +10:00
type-two
e6509479d2 Heat M-H3 (environment) + M-H5 noise: wind, the windbreak floor, the ZAP
M-H3 environment sim: newOutdoorEnv/addSmoke/SMOKE_ZAP. Wind perturbs a
flame's output AND steals its heat; a windbreak (sheltered) forces it
flat; induction shrugs wind off; indoor smoke trips the ZAP exactly once.
Verified: windy gas stdev 0.2635 mean 5.66, sheltered stdev 0.0065 mean
6.99, induction stdev 0, zap fires 1x indoors / 0x outdoors.

M-H5 noise half: cheap_electric hob stdev 0.1244 (>0.12) vs induction
0.0198 (<0.04) vs fan 0.0064 — cheap gear is Field noise, not a tax. The
'both reach 10/10' half waits on the M17 pan.

Harness: t.envVar, t.zapTest, t.hobNoise.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 15:55:34 +10:00
type-two
f6404586cd Heat-source asset cast: 14 GLBs (cooktops, pans, BBQ, outdoor, utensils)
gas/electric/induction hobs, camp stove, cast iron + frying pan, wok,
saucepan, kettle + gas BBQ, spatula, wooden spoon, tongs, ladle. Seeds
181-219. Props await their stations (M17 pan, M-H6 charcoal, wok night).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 15:22:27 +10:00
type-two
b76820b88e Heat sources M-H1/M-H2: the knob is not the heat
New bone src/sim/heatsource.ts: a HeatSource sits between the knob and
the browning integrator — the dial sets target, output chases it on a
fuel-specific lag, every station reads output. Five fuels (gas/electric/
induction/charcoal/radiant) + appliance SKUs (oven_gas, oven_fan,
cheap_electric). roasting.ts retrofit is golden-preserving: optional
src?, reads src.output ?? power, byte-identical when absent.

Verified in-browser:
- GOLDEN: no-src oven = mean 0.698 / blister 1.0 / collapse 0 (== HEAD).
- M-H1 chase 3->9: gas 0.433s, electric 6.0s, induction 0.133s (distinct).
- residual after 9->0: gas 0@1s, electric 6.6@6s, charcoal 9@30s.
- M-H2: gas oven stdev 0.1207 vs fan 0.0016 at equal mean 0.6001 (75x).
- day 14 bruschetta now roasts on oven_gas, still grades S (9.62); oven
  HUD reads 'GAS OVEN 8 · blue flame', tomatoes visibly hot-at-centre.

Doctrine: OPUS-BUILD-BRIEF-HEAT.md (the cross-cutting heat & environment
doctrine — 'you never cook the knob, you cook the lag'). Harness:
t.heatChase/residual/ovenStdev/ovenGolden/ovenDemo.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 14:16:54 +10:00
type-two
9e315bd7a9 gen-assets: heat lane — cooktops, pans, outdoor BBQ, utensils (seeds 181-219)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 13:45:40 +10:00
type-two
10b27e37fa Drawer drag: soften the grab spring + clamp held-piece velocity
Grabbing a buried piece built up spring tension against the pieces on
top, then broke free at ~9g (force cap 90*m, no speed limit) and batted
the whole pile out of the drawer. Softened the spring (K 165->55, damping
13->22, force cap 90->22) so it EASES toward the cursor, and added the
real fix: a hard velocity clamp (3.2 u/s) on the held piece so it can
never become a projectile — every downstream momentum transfer is bounded
by that one line.

Verified (harness drives the identical dragGrabbed path): a violent yank
now peaks the pile at 2.89 u/s with ZERO pieces flung out (was: everything
flew off screen); a controlled lift still raises a piece to y=2.09 over
the 1.47 rim and commits it out. Fishing still works, catapult gone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 13:31:30 +10:00
type-two
7b6005cf86 Fix: cutlery drawer swallowed all clicks (invisible full-screen panel)
The drawer's info card lives in a .drawer-panel that's inset:0 full-bleed
with pointer-events:none — but the base rule '#ui > *' re-enables
pointer-events:auto and its ID specificity (1,0,0) beats the class's
(0,1,0), so the invisible sheet covered the canvas and ate every drag.
The kitchen panel is a small positioned card so it never covered
anything; the drawer is the first full-bleed canvas-drag scene, which is
why it surfaced here. Scoped the container's rule to '#ui > .drawer-panel'
so it wins the specificity war and clicks reach the 3D pieces.

Verified: real DOM pointerdown now reaches the canvas, a piece grabs on
press and lifts (y 0.4). Harness never caught it — it injects input
directly, bypassing DOM pointer-events entirely.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 12:43:10 +10:00
type-two
a1ae1be6d6 Atlas grows: the egg dossier, the air fryer, and P12 THE CAREER
Egg dossier (feeds Brief 4/P3): the crack, shell-half separation with
yolk integrity, the float test as free information (so the rotten-egg
fart beat is always YOUR fault, seed-varied so it lands every time),
mayonnaise as a two-hand emulsion with the real rescue, the stick
blender pulse doctrine + THE ZAP (300ms skeleton flash, once a day max,
'I saw your bones, love.').

Air fryer (P6): arrangement scored by Clark-Evans INVERTED (spacing
with margins, contact shadows stay pale in the Field), the shake, and
CookPrograms — convoluted multi-step recipe cards (180 for 5, turn,
190 for 15) with no auto-advance; several programs at once is the
service clock in miniature.

P12 THE CAREER: money via tips, the paper catalogue, equipment quality
as tiers-modifying-existing-numbers (small board rolls dice onto the
floor, copper bowl genuinely whips faster, heavy pan smooths the sear
Field — noise and events, never flat score taxes), knife dulling +
honing/whetstone, a three-branch skill tree where every node is a verb
(stat-stick nodes banned), and rare primo offers the recipes notice.
Hooks stubbed from P6 on so it bolts on without refactors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 16:48:06 +10:00
type-two
3f3b073da9 THE KITCHEN ATLAS: phases P6-P11 — every medium, tool, and dish
Three new doctrines: IRREVERSIBILITY (seasoning is a one-way vector
walk — taste to see, dilute to compromise, never subtract), INFORMATION
TOOLS (thermometer/taste-spoon reveal instead of change; knowing costs
time, the judge respects a confident blind serve), SPEC CUTS (brunoise/
julienne/chiffonade/tourne as data on the existing cutting sim).

P6 water & oil (the fryer's menace), P7 the spice rack (curry capstone,
chili's 8s lag trap, bloom/toast/grind), P8 the knife roll (knife SET
as data, mandoline, melon baller, PREP DRILLS score-attack mode), P9
directed heat (the blowtorch paints a browning Field; creme brulee ends
in the judge's spoon-tap CRACK test), P10 cold & time (cures that span
real days, the churn, granita interrupts), P11 the banquet (fork-anchor
carving, three plates landing hot at once, credits).

Seeds 400-629 reserved per phase, same-seed families for prop sets.
Kill list live: microwave is a cameo, wellington waits, sous vide is a
joke prop. Everything rides the bones — new poetry on proven physics.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 16:35:06 +10:00
type-two
5d29bb2d96 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>
2026-07-18 16:00:33 +10:00
type-two
db31273fc9 Deploy-base-aware asset URLs; live at partly.party/toastsim
Absolute '/assets/...' paths 404 under the /toastsim/ subpath (and the
root /assets/ URL is another game's — returns 200 with wrong content,
the classic partly.party trap). assetUrl() prefixes BASE_URL inside
loadProp + the four direct loads (bench/backdrop textures, title art,
judge portraits). Verified live: JS application/javascript, toaster.glb
4.5MB binary, title_art image/png, title screen renders.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 15:58:18 +10:00
type-two
d5ed20cacb M15 bruschetta merged: Stage 3 complete, Brief 2 fully executed
Live gate: happy path S (9.63) — roast 0.698/blister 1.0, distribution
R 1.25, balance 3.82, sog 0.217 crisp, rub 43%->1.0, temp 1.0. Failure
paths named: 48s@7 roast collapses to sauce (B, roast 0, bench 0);
skipped brie toggle temp 0; thin slice + 30s dawdle 'it bends (1.00)' —
the finger-press line fires. Every M10-M15 milestone in Brief 2 is now
shipped and gate-verified; next: MASTERPLAN P2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 15:30:57 +10:00
type-two
46b1d8fd72 Merge branch 'worktree-agent-aa2e6cce1a128c28b' 2026-07-18 15:26:40 +10:00
type-two
cc39478a6e M15 bruschetta capstone: roast → assemble → the sog finger-press, day 14 end to end
The multi-station order, on the proven bones. Cut the sourdough (M10) →
toast (M1) → roast the tomatoes (toaster generalized) → assemble (garlic rub,
topping point-set, sog clock) → serve. Judge groups The Bread / The Topping /
The Bench. Planning is the mechanic: the temperature clock drifts the two
cheeses on real service time.

What I built on top of the partial work (sims + judge criteria + lines were
already in place and good — kept them):
- Temperature clock WIRED (sim/tempclock.ts): TempClock armed on bruschetta
  days, steps every tick while timing; a per-perishable fridge/bench toggle UI
  (game.showTempToggle) that reads the judge's own readiness word live; a
  toggle costs 8s service (reopening the fridge has teeth). Ticket hints:
  "get the brie out now", "leave the parmesan in".
- Roast tomatoes WIRED: OvenView routed after the toast lands on bruschetta
  orders (kitchen.onPopped branch → enterOven). Browning Field reused, blister
  past 0.6, collapse to sauce past 0.9. oven_tray.glb is absent → loadProp
  catch-fallback to the procedural tray (graceful, as designed).
- Assembly scene BUILT (scenes/assembly.ts, new): own toasted garlic-rubbed
  slice; G rubs the clove (one-swipe coverage), 1/2/3 drop tomato/cheese/basil
  as a point set; ENTER serves. Distribution scored by Clark-Evans (the rind
  code, fed the topping list); balance by mass-per-area band.
- Sog clock: rate x wet-mass x moisture / thickness — the doorstop resists,
  the thin slice surrenders; the judge presses a finger ("It bends. Toast
  should not bend.").
- Day 14 order added; enterOven/enterAssembly/serveBruschetta follow the exact
  runNextPrep/enterPrep/enterJuice machinery. Scorecard grouped
  bread/topping/bench via judge()'s existing group path.
- Harness (src/dev.ts): t.roast(s,power), t.rub(passes), t.assemble({...}),
  t.sogWait(s), t.tempToggle(id), and t.bruschetta() happy path returning the
  full grouped stats.

Fixes to the partial work: judge.ts shadowed the `order` param with a local
`const order` (rename → groupRank; would not typecheck); oven.ts material
opacity access typed as Material|Material[] (cast once).

Tuning (measured headless — the pure sims run without three.js):
- assembly SOG_RATE 0.006 → 0.004: doorstop (0.31) under a normal wet load
  (5 tomato ~2.1) served promptly (8s) stays CRISP (sog 0.21); dawdled 30s goes
  soft (0.77); a thin slice (0.12) bends inside ~15s. The bakery loaf matters.
- assembly MASS_BAND [1.1,2.1] → [2.5,4.5]: the old band scored a normal build
  (5t+4c+3b = 3.82 total mass) as an off-band 0; the band now sits around a
  proper generous topping. Sparse ~1.3 reads bare, a heap ~6.6 slides off.
- roasting RATE 0.042 kept; comment corrected to the measured window (power 6:
  stalls wet to ~26s, blisters 28-36s, collapses past ~40s).
- brie softensInSec 80 (from partial work) kept: out at order start, spreadable
  by assembly.

VERIFY (dev build; run in the console):
  t.start(); t.bruschetta()
Expected (headless sim + live path):
  roast:        {mean ~0.70, blister ~1.0, collapse 0, score ~1.0}
  distribution: ~1.0   (Clark-Evans R ~1.25, evenly strewn)
  balance:      {mass ~3.82 in band [2.5,4.5], score ~1.0}
  sog:          {value ~0.21, word "crisp", score ~0.99}
  rub:          {coverage ~0.35-0.5, score ~1.0}
  temp:         {score 1.0 (brie taken out, parmesan left in)}
  bench:        ~1.0   (clean roast, no slump)
  grade A/S, total ~9

Sub-mechanic checks (after t.bruschetta up to the oven, or standalone):
  t.roast(26,6)  → mean ~0.56, blister ~0     ("under-roasted")
  t.roast(42,6)  → collapse ~0.69             ("collapsing to sauce")
  skip t.tempToggle('brie') → Temperature 0, worst "brie fridge-hard"
  thin slice + t.sogWait(15) → sog ~1.0, "it bends"

typecheck clean (tsc --noEmit), vite build clean. node_modules is a symlink to
the main checkout and is deliberately NOT committed (dir-only .gitignore rule
misses the symlink; staged the 12 source files explicitly).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 15:25:21 +10:00
type-two
6f74d31469 Kitchen panel no longer leaks over station scenes
It was born visible — a day starting at a station (prep/juice/grate)
never passes through kitchen.enter()/exit(), so the BROWNING panel sat
over every station HUD. Hidden at construction like every other scene;
verified hidden at bench, shown after handoff, hidden at juice.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 15:18:06 +10:00
type-two
8f5641a57f Pieces render true to pattern: dice = chunk grid, wedges = exploded sectors
Dice show the criss-cross (drawn 1.6x true size — honest proportions
vanish against the board at game camera distance); wedges explode 0.28
from center so gaps and uneven angles read. Slices keep the fan. Plus
scripts/deploy.sh (partly.party/toastsim, cratewars pattern) — pending
an ssh permission grant to run the final docker cp step.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 15:13:21 +10:00
type-two
03e3d046fc Pantry cast GLBs: three mushrooms + bell pepper
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 21:49:00 +10:00
type-two
708d66316c Prep batch complete (12/12: oil bottle, oven tray); pantry lane added
gen-assets.sh gains 'pantry': mushrooms x3 + bell pepper, seeds 167-179.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 21:27:05 +10:00
type-two
e84b27b5ae Prep cast GLBs: grater, onion, garlic, orange half, juice glass, basil
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 20:55:12 +10:00
type-two
0e38cadc64 M14 + juice routing merged; Stage 2 complete and gate-verified
Day 12: juice order routed end-to-end (72.8%, 4 pips, THE JUICE beside
THE TOAST on one scorecard). Day 13: the stop-line — proper dice stops
at 0.846 and scores 1.0; through-the-root scores 0.0 at identical CV.
Sting 0.098 quick → 1.0 after 10s idle; the screen blurs to genuinely
unreadable (screenshot-verified). Juicer GLBs landed mid-review.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 19:22:31 +10:00
type-two
d962b77e66 Merge branch 'worktree-agent-afbcb63060ae43337' 2026-07-17 19:20:56 +10:00
type-two
e1e5ce823a M12 routed + M14 onions: the juice detour and the stop-line
Stage 2 Lane B. Two jobs, both riding the existing prep-bench machinery
(prepQueue drained through enterPrep/runNextPrep before enterToastFlow, results
folded into the judged scorecard) — extended, not rebuilt.

JOB 1 — the juice station is a real day now.
- orders.ts: PrepStep is a discriminated union; new 'juice' kind carries the
  juicer tier + optional halve + fixed seed count. Day 12 (Deidre): "fresh
  orange juice with the toast" — juicer comes from the ORDER (Pyramid Classic,
  the starter, no seed moat), the halve is left clean (dead-centre) so the twist
  is the whole test; the halve->juice chain stays M12's own later order. seeds
  fixed at 5 so Pips is a skill row, not a lottery.
- game.ts: the Game owns a JuiceView (like PrepView). runNextPrep branches on
  step kind; enterJuice mirrors enterPrep. The glass is scored on its own
  criteria (juiceCriteria) folded into judge() via a new `extra` param; its
  spray joins The Bench through the same prepResult path a cut order uses.
- lines.ts: JUICE_LINES merged into the verdict BANK; juicerRecognitionLine
  wired off order.prep (the Juicernaut announces itself before the verdict).
- dev.ts: t.juice(revs, press) drives the REAL routed game.juiceView (not the
  __tj sandbox); follow with t.handoff() to serve the glass.

JOB 2 — M14 onions, the technique test (brief section 6).
- The stop-line rides the existing cut sim with one additive hook: on the
  onion dice's root-ward first pass, RELEASING the blade mid-saw commits a cut
  that stops short of the root (liftKnife now commits + returns its frame).
  finishCut is factored so a stopped cut and a through cut share the path.
  Saw all the way (progress hits 1) and the root severs -> the layers scatter.
  Gated behind pattern { kind:'dice', root:true } so day 10's plain dice
  (M11-verified) is untouched. That is the "ending a cut deliberately short"
  feel — restraint one cut at a time, not a menu choice.
- Sting clock: stingStep raises sting with open cut-surface x time x how much
  you crushed rather than sliced (blunt/squashy = tear gas). Rendered as a
  cheap full-screen DOM overlay (radial vignette + backdrop blur) that grows
  with sting, plus an "eyes fine/watering/streaming" readout and a green
  stop-line marker on the onion.
- Scored as two new rows (judging.ts): The Dice (stem discipline, w1.3) and
  The Eyes (sting endured, w0.7). The Pieces (CV) stays the criss-cross
  spacing, so the two skills are told apart. Lines for 'dice' and 'sting'.
- Day 13 (the inspector): dice the onion PROPERLY with The Best Thing.
- dev.ts: t.chop gains stopAt; t.diceOnion({stopAt|through}) drives both.

Constants and why:
- STOP_MIN 0.15 — a release shallower than this is a fumble, not a stop.
- STING_RATE 0.02 — a sharp, quick clean dice finishes ~0.05 (clear-eyed);
  the same onion left open 10s floods to 1.0. Sharp+fast wins, dawdling blinds.
- stemDiscipline band: full marks 0.55..0.9 depth, graze 0.9..0.97, through
  >=0.97 = confetti; undercut <0.55 = joined chunks.
- t.diceOnion default stopAt 0.8 -> commits ~0.85, level with the 0.82 marker.

Verified headless (deterministic; sim is pure):

Day 12 juice, t.day(12); t.juice(3, 0.7):
  extracted 0.728, theoretical 1.0, yieldOfReachable 0.728, pips 4,
  seedsCaught 0 (Classic has no moat -> serve the pips or fish them).
  Criteria: The Juice score 0.631 (w1.3), Pips 0.200 (w1.1). t.handoff()
  serves the glass; the toast half then runs as any non-loaf day, and the
  scorecard shows THE PREP (The Juice, Pips, The Bench) beside THE TOAST/SPREAD.

Day 13 onion, t.day(13); t.diceOnion():           // proper dice, stop short
  16 pieces, CV 0, rootThrough 0, stopDepths [0.85,0.85,0.85],
  The Dice score 1.000, sting 0.052 -> The Eyes 0.939 ("clear-eyed").

t.day(13); t.diceOnion({ through: true }):          // cut through the root
  rootThrough 3, stopDepths [1,1,1], The Dice score 0.000 (confetti line
  fires), CV still 0 -> the disaster reads on The Dice, not a lying CV.

Sting accumulation: after a proper dice, t.run(600) (10s idle) ->
  sting 0.052 -> 1.000 ("blind"), The Eyes 0.939 -> 0.000. The cut onion
  stings while it sits, exactly as it should.

Plain dice unaffected: onion { kind:'dice', n:4 } (no root) -> 16 pieces,
  CV 0, rootThrough 0, phase done — M11's day 10 is byte-for-byte the same test.

typecheck clean, npm run build clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 19:19:49 +10:00
type-two
57a68ad4cf M13 grater merged + knuckle retune: easing off is a real defense
Agent build verified live: confident 8 strokes = 0.61 pile clean bench,
scrubby 12 = 0.095 yield with 0.097 sprayed stray, soft block clogs
solid (smear 0.53), lazy zest 62% pith vs rotated 0%. Retune: pressure
floor 0.4 on the knuckle roll — 10/10 gentle runs on a small block
bloodless, 10/10 hard runs bleed (was ~1-in-3 grazes when careful).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 19:09:21 +10:00
type-two
efe4d1d1a6 M13: the grater — one stroke, and the block shrinks toward your knuckles
The prep bench's third station, built on the M12 juice lane's exact shape:
pure sim (sim/grating.ts, no three.js), a mirroring scene (scenes/grate.ts),
and a separate harness on window.__tg (dev-grate.ts). One additive DEV import
line in main.ts. Judge integration is exported and documented in-file
(grateResult / grateCriteria / GRATE_LINES / knuckleLine) — NOT wired into
game/* (that lane is owned elsewhere this stage).

The verb is a STROKE down the box-grater face: length × pressure → shred mass.
Long confident strokes land on the pile; short scrubby ones shear off the
board (mess). Two customers on one prop — cheese (parmesan) and zest (orange):

- Cheese shrinks the block as you take mass off it. As it gets SMALL your
  knuckles near the teeth: knuckle-graze risk = pressure × stroke-length ×
  block-proximity, so it only bites in the tense last third. Parmesan grates
  clean IF cold; it softens on the bench (temp clock, warmOnBench) and soft
  cheese SMEARS — output drops, the teeth CLOG (clearClog costs service time).
  The butter inversion: butter wants warm, cheese wants cold.
- Zest: each of 8 orange regions has its own depth; grate one past the pith
  line (ingredient gratable.pithDepth 0.35) and it sheds white pith into the
  pile instead of zest. Rotate (scroll) to a fresh region — the skill is
  rotation discipline. The pith idea is re-derived from the ingredient's own
  pithDepth; grating deliberately does NOT import juicing (independent lanes).

Judge: The Gratings/The Zest (amount band, w1.0, docked hard per knuckle),
The Pith (zest, w1.1), The Grate (cheese clog+smear, w0.9). Bench scored
separately via result.bench, same path prep/juice use. 8 bad + 3 good lines
per criterion, plus the knuckle opener.

Tuning (first principles, then measured headless on the pure sim):
- RASP 0.12, gratable.yield 0.9 → a long stroke (len 0.9 × press 0.7) sheds
  ~0.068/stroke; ~7-8 strokes make an in-band pile.
- KNUCKLE_SAFE_BLOCK 0.35: proximity is zero above it, ramps to 1 at empty.
  KNUCKLE_GAIN 0.6. Oranges never shrink, so zest never grazes (its danger is
  pith, not knuckles) — by design.
- ZEST_DEPTH_GAIN 0.18 → a region hits the 0.35 pith line in ~3 hard strokes,
  so rotate by then. ZEST_MASS_FRAC 0.4 (zest is a whisper vs a cheese pile).
- SPRAY_GAIN 0.9 on (1-length)^2 — spray climbs steeply as strokes shorten.
- SMEAR_LOSS 0.8, CLOG_GAIN 0.35 × grater.clogProne (box 0.7 / microplane 1.3).

Verified: tsc --noEmit clean, npm run build clean. Sim driven headless with
the scene's own seed (0x6ac0f). __tg calls and expected numbers:

  __tg.enter({ingredient:'parmesan'}); __tg.stroke(8,0.7,0.9); __tg.stats()
    → gratings≈0.61 "a good pile", blockLeft≈0.38, knuckleHits 0, clog 0;
      The Gratings 1.0, The Grate 1.0.
  __tg.enter({ingredient:'parmesan'}); __tg.stroke(12,0.6,0.25); __tg.stats()
    → scrub: gratings≈0.10 "a dusting", stray≈0.10 off the board, block barely
      touched (0.81); The Gratings 0.24.
  __tg.enter({ingredient:'parmesan',softness:1}); __tg.stroke(8,0.8,0.9); __tg.stats()
    → soft: clog 1.0, smear≈0.61, gratings≈0.05; The Grate 0.0 "clogged solid".
  __tg.enter({ingredient:'parmesan',block:0.25}); __tg.stroke(8,0.9,0.95); __tg.stats()
    → danger: knuckleHits 4, blockLeft≈0.02, gratings≈0.24; The Gratings 0.008.
  __tg.enter({ingredient:'parmesan',block:0.25}); __tg.stroke(8,0.9,0.35); __tg.stats()
    → ease off: knuckleHits 0, gratings≈0.22; The Gratings 0.54. The lesson pays.
  __tg.enter({ingredient:'orange'}); __tg.stroke(5,0.6,0.8) with rotate every 2
    → gratings≈0.12 in band, pithFrac 0; The Zest 1.0, The Pith 1.0.
  __tg.enter({ingredient:'orange'}); __tg.stroke(9,0.9,0.9); __tg.stats()  // no rotation
    → pithFrac≈0.74 "74% pith"; The Pith 0.0.

(Scene HUD, pile mound, red knuckle flash, and scroll-rotate are visual — the
reviewer drives __tg and screenshots; the numbers above come from the pure sim
the scene feeds.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 19:06:40 +10:00
type-two
32d1cced69 Drop accidentally-committed agent worktree pointer; ignore .claude/worktrees
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 18:51:53 +10:00
type-two
b11d464985 M12 core merged + brief STATUS updated to the Stage 1 finish line
Juicer verified through __tj: Pyramid 72.8%/4 pips, Juicernaut 100%/all
caught, damaged halve caps at theoretical 0.6, mash 8.5% + pulp,
reversals 77.1% vs 72.8% equal-sweep. Station renders (cone, moat,
filling glass, live HUD). Not yet routed into a day — integration point
documented in sim/juicing.ts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 18:51:37 +10:00
type-two
8e249b4dba Merge branch 'worktree-agent-a994a2ac1eff4f7dc' 2026-07-17 18:49:26 +10:00
type-two
d2ab84d549 M12 WIP: oranges and the ribbed pyramid — the reamer twist is causal
The juice station, on the same bones. Press-and-twist reuses the jar's swirl
detector: signed angular sweep of the pointer around the reamer becomes juice
one frame at a time, so twist rate IS juice rate (the feel that had to be right,
brief §9). Dwell is the downforce — holding the half down ramps press, and the
twist while you hold is what juices.

NEW src/sim/juicing.ts (pure, no three — mirrors cutting.ts):
- theoreticalYield(halve) = base·(1−2|offset|)·(1−0.5·wedge): the halve sets the
  reachable ceiling, so a butchered 60/40 caps you at ~70% and the bar can't lie.
- juiceTwist(s, dTheta, press, dt, rand): pulse = sweep·press·ream, capped by
  what's reachable; twist REVERSALS bite a fresh face (a small bonus, real reamer
  physics); pressing WITHOUT twisting mashes → pulp + bench spray + no juice;
  pressing too HARD shoves seeds past the moat; grinding a DRY half reams the pith
  → bitterness that no yield buys back.
- Three tiers as tools (like knives differ): Pyramid Classic (no moat, ream 0.92),
  Ribbed Deluxe (catches ~80%, +4%), The Juicernaut (catches all, +10%, ream 1.32).
- Single game integration point, documented in-file: juiceResult(session) → the
  number-bag; juiceCriteria(result) → The Juice (yield band − bitterness − murk,
  w1.3) + Pips (seed count, w1.1); JUICE_LINES (5 bad / 3 good each) +
  juicerRecognitionLine(). Bench rides the existing benchCriterion via result.bench.

NEW src/scenes/juice.ts: JuiceView mirroring prep.ts — procedural ribbed cone in
a moat dish, glass with a rising juice column, orange half, mess field for spray,
loadProp GLB fallbacks (juicer_pyramid/juice_glass). Live HUD reads the judge's
own numbers: "62% of what this orange had", a ceiling marker the fill can't pass,
pips (click to fish out), bench word, pith warning.

NEW src/dev-juice.ts: SEPARATE installer (no collision with dev.ts) — owns its own
JuiceView, drives the REAL input path (pointer circles for the twist, press
injected like dev.ts injects kitchen.power). Exposes window.__tj. main.ts: one
additive DEV-only import line.

VERIFY (reviewer, ~3 console calls after the dev server loads):
  __tj.enter({ seeds: 5 }); __tj.twist(3, 0.7); __tj.stats()
    → yieldOfOrange ~0.73, pips 4 (Pyramid lets seeds through), bench dirtied
  __tj.enter({ juicer: 'juicernaut', seeds: 5 }); __tj.twist(4, 0.8); __tj.stats()
    → yieldOfReachable ~1.0, pips 0 (catches all), bitterness rises if you grind on
  __tj.enter({ offset: 0.2 }); __tj.twist(6, 0.8); __tj.stats()
    → theoretical ~0.60, yieldOfOrange caps at ~0.60 (the butchered halve)
  __tj.enter(); __tj.mash(1.0, 0.9); __tj.stats()
    → extracted 0, pulp/spray > 0, pips forced out (lean-without-twist = a mash)
  __tj.enter(); __tj.wristle(6, 0.7); __tj.stats()  vs  __tj.twist(3, 0.7)
    → wristle ~0.77 vs twist ~0.73 at equal sweep: reversals pay off

Measured (standalone port of the sim, same constants):
  pyramid 3rev@0.7 → 0.728 | @0.9 → 0.936 | 5rev@0.85 → 1.0 (bitter 0.38, over-reamed)
  deluxe 4rev@0.8 → caught 3/5, 2 pips | juicernaut → caught 5/5, 0 pips
  butcher off .2 → capped 0.60 | mash 1s@0.9 → ext 0, pulp 0.135, 3 pips

tsc --noEmit clean; npm run build clean. Not routed into game.ts yet (game lane's
job) — the harness owns the view so the station is fully drivable now.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 18:48:37 +10:00
type-two
d7ce7b7961 M11 finish: days route through the prep bench; dice & wedges driven for real
Order.prep is no longer decorative. beginDay() now drains an order's prep
queue through the bench BEFORE the toaster, folding each PrepResult into the
judged day, then falls through to the existing toast flow (loaf knife-trip or
straight to the kitchen). The bench→kitchen ENTER handoff is the same verified
door; both the standalone harness path (t.prep) and the day flow go through one
enterPrep().

What changed:
- orders.ts: PrepStep gains an optional `knife` (prep knives are supplied by
  the order, not fished from the drawer — that trip is for the spread tool).
  prepAsk() words a step for the ticket. Two new scripted days:
    day 10 — dice an onion (dinner knife), then golden white + butter.
    day 11 — six wedges of tomato (dinner knife), then a scrape on golden white.
  The onion showcases dice (layered flesh punishes a bad cut); the wet tomato
  showcases wedges (press-without-saw skates and hoses the board).
- game.ts: prepQueue drained by runNextPrep(); enterToastFlow() extracted from
  beginDay(); startPrep() now delegates to enterPrep(); mergePrep() folds a
  second step honestly (worst CV, summed mess, all pieces) though every shipped
  order is single-step. Ticket renders a warn-chip per prep step.
- dev.ts: t.wedges({n,dy,wobble}) and t.dice({n,dy,wobble}) drive a whole
  pattern deterministically off the existing t.chop() — the dice's two rotated
  passes are just 2·(n-1) chops (the sim banks pass A and rotates the board
  itself). t.handoff() taps the ENTER the bench waits on. Both return prepStats.

Measured (pure-sim, formulas from cutting.ts, positions the harness uses):
- wedges n=6, cuts at [-0.5,-0.167,0.167] → 6 wedges, CV 0.0000 (even).
- dice   n=4, cuts [-0.25,0,0.25] both passes → 16 cells, CV 0.0000 (even).
- wedges with wobbled spacing [-0.5,-0.35,0.28] → CV 0.6351 ("a lottery").
CV 0 for even placement confirms the pieces criterion is a real skill test on
both new patterns; wobble/dy in the helpers exercises the failure end.

Verify (dev build, window.__t):
  t.start(); t.day(10); t.dice({n:4}); t.prepStats()  // 16 pieces, low CV, bench
  t.handoff();                                          // lands in the kitchen
  t.toast(9); t.pick('butter_knife'); t.spread(); t.kitchen.serve()
    // scorecard groups THE PREP (The Pieces / The Bench) + THE TOAST / SPREAD
  t.start(); t.day(11); t.wedges({n:6}); t.prepStats()  // 6 pieces, low CV
  Lottery/mess check: t.day(10); t.dice({n:4, dy:0.35, wobble:0.06}) → high CV,
    messy bench, "a crime scene".

typecheck clean, vite build clean. Untouched: sim/juicing.ts, scenes/prep.ts,
gen-assets.sh, MASTERPLAN.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 18:41:13 +10:00
type-two
161d488190 MASTERPLAN: toast to full service — six phases on the same bones
Salt & butter as cross-cutting foundation systems (salt-type × timing
matrix, butter state machine closing the whip→butter loop), P2 FIRE
(steak: season/cook/rest/cut/pair), P3 eggs & air (whisk shear, poach
vortex, two-layer set field), P4 the pass (composition, swoosh, dosing
danger), P5 service. Build model: parallel opus agents in worktree
lanes, reviewer holds the browser, harness numbers gate every merge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 18:30:38 +10:00
type-two
003aede82f Bell pepper in the cast; the pantry backlog spec'd in the brief (§7.5)
- bell_pepper: waxy slick skin (2 slips pressed vs tomato's 2 at higher
  displacement), 60-seed core — cutting it scatters seeds, bench reads
  'a crime scene' with zero extra code. Hollowness ponytail-deferred.
- Brief §7.5: capers+wasabi as the shared dosing-danger class, the
  mustard family on the jar/drawer system, rib eye as its own milestone
  (cook/rest/cut-across-grain/mustard pairing), baked stuffed potatoes
  (lid cut, scoop-as-inverse-spread, restuff). Each rides existing bones.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 18:22:29 +10:00