47f6c39e2b
32 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
47f6c39e2b |
THE MANDOLINE: day 28 — yield against skin
P8's machine, and the one station where evenness is FREE. A knife makes you earn every slice; the mandoline hands you a perfect one every stroke. So the game is not the cut. The game is the last third of the vegetable, where your knuckles get close to the blade and the only thing between them is a guard that wastes what it protects. The trade, and it is the whole station: bare-handed shaves further down for a better yield and is COMPLETELY SAFE while the thing is long — the risk is zero above a comfortable stub and climbs steeply below it, so careful play never bleeds and greed does. The guard is not a probability, it is a fact: with it on you cannot be cut. It also cannot grip a stub, so it stops you early and leaves waste in the bin. Bleeding is not a bad row, it is a health inspector: the plate caps at 3.5 and the detail reads 'that food does not leave my kitchen'. Two fixes the verification forced: - The first tuning asked for 14 slices, and a guarded cook tops out at about 13 — so the safe route could not satisfy the ticket and the player was choosing between failing the ask and bleeding. That breaks the house rule that careful play is reliably safe. The ask is 12 now (10..13 procedurally), reachable guarded, with bare-handed greed worth a couple of extra slices and nothing you could not otherwise earn. - The waste scale punished the guard so hard that safe play was mediocre by construction. Re-centred on the guard floor: the guard costs a little yield, it does not sink the run. And a bug the screenshot caught, which turned out to be three bugs: the nick message is sticky by design (updateHud refuses to overwrite it), and NOTHING cleared it on reset — so a fresh cook was told 'THE BLADE FOUND YOU' in red before touching the machine. The fryer's 'it CAUGHT you' and the air fryer's 'jumped the rack' had the identical trap. All three cleared on reset. Verified: careful 9.6 (13 slices, guarded, hands intact); greedy 15 slices — MORE yield — and 3.5 for bleeding on them; word clean on a fresh board; curry 10.0, chips 10.0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
0e092e2551 |
THE SPICE RACK: day 27 — six axes, no subtract, and a chili that lies
P7, and the irreversibility doctrine generalised. The pot proved it on one axis; flavour space has six — salt, heat, acid, sweet, bitter, aroma — and every ticket is a REGION you are trying to land in. Pouring is the only verb that moves the point. Nothing moves it back. THE CHILI IS THE STATION. Its heat does not arrive when you add it; it BLOOMS eight seconds later. So TASTE — the one honest instrument on the bench — physically cannot see the heat you just put in. It tells you the truth about a pot that is about to stop being true. The readout is a MEMORY, not a meter: it prints how many seconds ago the spoon said it, and the state line will admit 'something in there is still coming up' — but the pips do not move, because a tongue is not a sensor. The second pinch is the one that ruins the dish and the game never says don't. DILUTE is the only eraser and a deliberately bad one: it drags every axis toward nothing, including the four you had right, and leaves you a big pot of not very much. Two fixes that mattered more than the feature: 1. Tasting fast-forwarded the bloom. taste() added 3s to the flavour clock ON TOP of the 3-second spoon animation the clock already ran through — so a cook could taste twice to skip the lag, defusing the only trap the station has. Removed. 2. THE STATION CARD HAS BEEN INVISIBLE. `.slicer-card` sits at (26,26); the order ticket sits at (22,20) and is 272 wide and 365 tall. The card was painted completely underneath it — elementFromPoint at the card's centre returned the ticket's own button. That is EVERY station in the game: the pan's butter word, the poach's shiver, the fryer's oil, the steamer's greens — the entire no-meters interface, hidden behind the ask. Moved to the opposite corner: what they want on the left, what the pan is telling you on the right. Flavour is also not scored as a mean, because nobody eats an average — they taste the loudest thing in the bowl. The card is pulled hard toward its worst row, so five perfect axes cannot hide one that is screaming. Verified: honest curry 10.0; the chili trap 5.4 (spoon read 0.00 heat, second pinch bloomed to 0.73, 'that is not warm, that is ANGRY') — 8.1 before the worst-axis weighting, which was far too kind; the panicker who over-salts and thins twice 5.2, 'a big pot of not very much'. Steamer, pasta, chips and air fryer all unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
9ec99add02 |
THE STEAMER: day 26 — the lid is opaque, and that is the whole game
The last of the atlas's water page, and the only station that takes your EYES away. Steam works under a lid; a lid you can see through is not a lid. So every few seconds you make the same trade: LID ON and it cooks properly while you are completely blind, or LID OFF and you can read exactly how green they are while the steam walks out of the pot and the clock drops to a fifth. There is no doneness meter here — the state line refuses to print one, because with the lid on a cook could not know it. There is a lid, and there is your nerve. The lid is opaque in the literal sense: an actual dome that occludes the florets in 3D. Take it off and it parks beside the pot and the greens are simply readable again. That is the entire UI. VIBRANCY is the headline row, exactly as the atlas asked — grey broccoli is a visible confession. Steam keeps colour; a violent boil that leaps up through the basket murders it, and so does cooking them past tender. The florets slump as well as fade, so the shape confesses before the colour does. And the pot boils dry. That is the reason not to crank the flame to nine and walk away: scorched metal caps the plate at 2.5, the same doctrine as brine in the pasta water. First tuning had it drying in 58 seconds, which made an enthusiastic cook flip a coin rather than make a mistake — softened so neglect is punished and enthusiasm is not. Verified: honest steam 10.0 (vivid, tender, water to spare); the hard boil 4.7 with 'army green. I can see it from here' and 'that was a BOIL. steam is gentler than you were'; the dry pot 0.7. Pasta, chips and eggs all still 10.0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e9fca4f5c9 |
THE POT: day 25 — you can always add more salt. you can never add less.
Pasta night, and the kitchen's first genuinely irreversible verb. Salt is a HELD pour, not a click: hold S and it falls, you stop it and nothing else will, and `salt` is a number that only ever goes up. The words walk you to the edge and then stop helping — 'shy of it, more while you still can' becomes 'seasoned like the sea. stop.' becomes 'heavy handed. there is no going back.' Salt thrown in after the pasta counts a quarter: it seasons the water you are about to pour down the sink. The rest is the atlas's pasta doctrine: pasta wants a ROLLING boil, and a lazy simmer glues the strands into one brick because they sit in their own starch instead of tumbling. Al dente is a window in immersion seconds that never appears on screen. And the last verb is restraint — RESERVE a cup of the starchy water before you drain, or the sauce has nothing to hold onto. Two things the adversarial pass caught, both about stakes being fake: - Brine scored 8.6. An inedible plate cannot be averaged away by good timing elsewhere, so ruin now falls off a cliff (not a ramp) and a brined pot is CAPPED at 3.0 — the one verb you could not take back gets the one score you cannot climb out of. - The sourdough starter was buttering up chip day. Her 'is this a toast day' test was a blocklist of stations that existed when she shipped, so air fryer, fryer and pot all silently qualified as toast. Replaced with stationOf()/isToastDay() in orders.ts — every station flag listed in exactly one place, where the next station cannot slip past it. Verified: honest pot 10.0; brine 3.0 at the cap; the lazy cook (simmer, salt after the drop, drained every drop) 4.8 with all three rows naming the exact sin. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d1645bae6e |
THE FRYER: day 24 — twice, love. once for cooked, once for golden
The pot of oil, from the atlas: oil is the fear mechanic and the double
fry is the physics, not the flavour text. Cold oil soaks (grease). Hot
oil seals — and the seal is REAL: past ~0.8 gold the crust shuts and the
inside stops listening, so one screaming-hot fry gives you 'firm in the
middle — the seal beat the cook'. The honest path is the card's path:
blanch in the shimmer until fluffy, REST on the rack while the steam
escapes (dryness is what golds — the oil spends it, the rack rebuilds
it), then back in HOT for the colour. The basket knocks the oil down a
full degree when it lands, because thermal mass does not care about
your schedule.
THE SPIT is the knuckle lesson with a floor: raw chips in oil past 8.4
throw it back, and a spit only burns the hand PARKED OVER THE POT — the
pointer is your hand, and standing back costs nothing. The judge's row
reads 'it spat 13 times; you did not flinch' or names exactly what the
hovering cost you.
First cut had two dead spots the adversarial pass caught: the seal was
too weak (a single hot fry still cooked through for 9.1) and the spit
window was unreachable (the basket-knock kept the oil under SPIT_AT for
the whole raw phase). Rebalanced: seal 1.25x, gold rate up, spit at 8.4.
Verified: double-fry 10.0 ('proper gold · fluffy inside · dry. drained.
correct · 2 fries — as the card says'); one-hot-fry-hand-hovering 7.6
with 13 burns. Day 24 on the card, procedural arm, ghost demo slot 24,
hero_chips on the scorecard, Ray's portrait already on the ticket.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
782971ee83 |
THE CARD: the recipe stops being narrative and becomes law
The multi-cycle timer doctrine from the atlas, first station: the air
fryer's cursed card ('180° until colour · SHAKE · 190° to golden') is
now DATA — deg1, deg2, and a half-time shake — and the sim keeps the
ledger to judge it by: heat-weighted seconds at every knob position,
split into pre-shake and post-shake phases, plus the mean browning at
each shake so 'half-time' is a real place (0.15–0.5) and not a vibe.
A new row on the scorecard, The Card: 40% ran phase one at its degrees,
40% phase two at its, 20% shook in the window. Ignore it entirely and
the detail reads 'no shake. the card wept'; right temps but a mistimed
shake is its own sentence. Procedural wing days now deal varied cards
(170/180 into 190/200).
The best bug of the batch: the verifier caught MY demo driver setting
knob 8 for the finish — that is 200°, and the card says 190°. 'roughly
the card, squinting.' The judge was right and the demonstrator now
obeys: 'as written', 9.5/10.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
29f6b25c5b |
THE AIR FRYER: day 23 — air is the ingredient
John's spec from the atlas, honored verbatim: the basket is a small 2D
tray and ARRANGEMENT is the skill. Six wings arrive DUMPED in a pile;
every piece wants a clear gap to breathe (Clark–Evans inverted — the
same spacing statistics that reward an even topping spread here reward
margins). Smothered wings brown slow and keep a PALE face the judge can
see; steam is a LOCAL sin — a corner pile in an empty tray still steams
(the first cut used global fill fraction, which six wings could never
trip; the adversarial pass caught the dead mechanic and the smother
statistic replaced it). The drawer pauses the cook and bleeds the heat —
hovering costs. The SHAKE is the egg-crack grammar: hold F, release; a
sane charge turns the pale faces to the wind, a full-send puts five
wings on his floor and the corner eye goes horrified.
New station, full rails: sim/airfryer.ts (pure, seeded), the top-down
scene with the sliding basket (snap-out on reset — a stale docked tray
was eating the demo's opening drags), judgeAirfryer (THE CRISP golden
band / THE TURN pale faces / THE AIR steam / THE COUNT floor wings),
day 23 on the card with the cursed recipe card, a procedural endless
arm, fan-whir foley that dies when the drawer opens, ghost demo slot 23,
and the wing man's portrait (high-five not coming).
Verified: honest lattice play 9.5 twice back-to-back; the lazy pile
3.6 ('a steamer wearing a fryer badge', raw, never shaken); full-charge
shake loses 5 of 6 with the horrified eye.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
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>
|
||
|
|
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>
|
||
|
|
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> |
||
|
|
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>
|
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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>
|
||
|
|
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>
|
||
|
|
faf196657e |
M11 WIP: the prep bench core — one board, and it remembers
Code-complete and verified headless; NOT wired into a day yet (see brief §0.5). The core, all reusing the M0-M10 bones: - sim/ingredients.ts: the cast (tomato/orange/onion/garlic/parmesan/brie/ cucumber/avocado/roast_tomato) with shape, skin slip, flesh moisture+layers, seeds, sting, temperature needs, gratability. - sim/cutting.ts: CutSession = ingredient + knife + pattern (halve/slices/ wedges/dice), generalizing the M10 slicing sim. New physics: slip (press a flat blade on slippery skin and it skates, the aim jumps, juice sprays) and juice (moisture x speed, so deliberate leaks and frantic hoses). - sim/mess.ts: the bench is judged. Field over the board UV + a solids list; the wipe is the butter brush in reverse and mass-conserving, so one pass smears and the second cleans. - scenes/prep.ts: aim/bite/saw, deepening seams, pieces fan out, juice stain texture, instanced solids, live 'bench: clean/smeared/a crime scene'. - judging.ts: The Pieces (CV, w1.3) + The Bench (w0.9); clarkEvans extracted for M15's toppings; criteria grouped by station and the card grouped with it. - lines.ts: 8 lines each, in his voice. Measured, not asserted: - Even deliberate slices -> CV ~0, bench mass 0.51. - Sloppy + frantic (0.3 u/frame) -> CV 0.72, mass 18.8, 16 solids, 'a crime scene'. - Dinner knife pressed on a tomato without sawing -> skates, aim jumps 0 -> 0.12. The Best Thing under the same press bites in 7 frames, 0 slips. - Wipe two passes -> mass 0.51 -> 0.09. Tuning found by playing: slice CV runs over piece WIDTHS not volumes (round ingredients make end slices thin by geometry — a perfect cut scored 0.40, which is a geometry lesson, not a skill test); juice coefficient 0.5 -> 0.1 (the first tomato flooded the board before the second cut). Assets: gen_3d retries once per stage, bakery seeds bumped, prep cast queued. 2D landed (bruschetta_hero, wonderslice_bag). 3D is BLOCKED: every bg_remove fails because m4pro's disk is 100% full. Procedural stand-ins carry the bench. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e5be266ce6 |
M10: the bakery, verified — the wobble follows you into the toaster
Played end-to-end via the extended harness (t.start/day/pick/slice): - Day 9 knife trip: bread_knife dragged above the rim, 0.45s dwell commits. - Clean doorstop (bread knife, 0.05 u/frame): 5 strokes, wedge 0, squash 0. STROKE_GAIN 0.16 -> 0.10 to hit the 4-6 deliberate-stroke feel. - The Best Thing: 3 strokes, and the same wobble input that wedges the bread knife to 0.299 only reaches 0.044 — nearly wobble-proof, as specced. - Coupling: wobbled cut toasts at browning stdev 0.088 vs 0.071 clean, same mean — 'a bit wedged' band (>0.18) confirmed in the Cut criterion. - Slam cut (0.4 u/frame): squash 1.0, arrives pre-damaged (damage 0.02 pre-toast). - Soup spoon siege: 17 strokes / 166 frames, miserable but possible. - Brands: quality now drives heat-noise amp (0.12+0.42*(1-q)) AND blotch frequency (3+3*(1-q)); WONDERSLICE visibly patchier than Crustworthy at 10s. - Slicer visuals: cut seam deepens with progress; freed slice tips off the loaf and lands crumb-up (doneTimer 0.7 -> 1.05 for the fall). - gen-assets.sh: retry-once per stage (rsync flakes), bakery seeds bumped, prep-bench cast queued ($1 prep). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
ff006e65db |
M8: crunchy vs smooth, and the jar that separates
Natural peanut butter stratifies while it sits. The jar carries the state:
strat 0..1, set per order (smooth 0.75, crunchy 0.9 — chunks sink). The first
unstirred dip takes the oil slick off the top; the ones after it dig into what
the oil left behind. Verified dip trace, no stirring: 0.875, 0.669, 0.462,
0.256. Swirl the knife in the jar (~2.5 circles of angular sweep; holding still
stirs nothing) and every dip is 0.5. Stirring costs time, and time costs toast
warmth — the mechanic pays the same tax as everything else.
Consistency pushes back through the physics that already exist: an oily load
multiplies the yield down (glides at any angle, goes on thin), a dry one
multiplies it up (fights like cold butter, tears). The shader shows it — slick
is dark and shiny, grout is pale and matte.
The judge reads what LANDED, not what the jar was: every deposit remembers the
oil it landed at, and the criterion scores the mass-weighted mean AND stdev.
The stdev is the design catch, found by verification: a slick strip and a grout
strip average to 0.54 — "just right" — and the first cut scored the lazy play
as if the jar had been stirred. Nobody eats the average:
never stirred, strips oil 0.54 ±0.23 "slick here, grout there" C
one slick dip, whole oil 0.87 ±0.00 "a slick"
stirred first oil 0.50 ±0.00 "just right"
Crunchy reuses the particle system wholesale — chunks ride the knife, shed per
stroke-distance, get judged with the same Clark–Evans index ("Peanut chunks —
21 bits, evenly strewn (R 1.20)"), and the rind line bank is genericised via a
{bits} placeholder so he complains correctly about either.
Day 6 is crunchy now ("the PROPER kind"), the ticket warns when a jar has been
sitting, and the JAR bar shows how mixed it is. Five new judge lines, including
"You did not stir the jar. The jar knows. I know."
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
70922bd855 |
M7: marmalade — rind, and a judge who scores its distribution
A particulate spread. The jelly is ordinary rheology; the rind is discrete solids that ride the knife and drop off per unit of stroke DISTANCE, not per second — a dabbing knife makes a pile, a moving knife lays a trail, and since the shed rate scales with what's left on the blade, one dip cannot cover the slice. Even rind takes several dips placed with intent. Scraping picks bits back up (60% survive the blade), so a clump can be argued with. The judge scores the scatter with the Clark–Evans nearest-neighbour index over the bread's own area, and quotes it: R near 0 is one clump, 1 is random, above 1 is deliberate. Verified end to end on day 8: dab in a corner 8 bits "one clump (R 0.24)" drags grade to B three placed dips 24 bits "evenly strewn (R 1.17)" A, 8.9/10 First cut shed per-second; even careful strokes dumped the whole load in the first half-second and scored "clumped (R 0.50)". Distance-based shedding is what makes evenness the reward for technique rather than luck. Rind renders as instanced chunks parented to the slice mesh, so it rides the pop, the landing, and the pedestal turn. Points, not a field: the judge wants nearest-neighbour statistics, and those want points. Same sRGB trap as M3, new spot: the instanced material's colour was passed as raw floats, which the renderer reads as linear — candied orange rendered as pale butter. setRGB(..., SRGBColorSpace). New content: day 8 handwritten (Deidre wants a piece in every bite), marmalade in the procedural pool, five rind-district lines for the judge, and a marmalade jar GLB generated on-device like everything else, with a plain-glass fallback while it loads. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b07d57e9e9 |
M3: judging — the scorecard, the judge, and a colour-space bug
The loop closes: ticket -> toast -> spread -> ENTER -> verdict -> next day.
- judging.ts: 9 weighted criteria, each reading the same fields the knife was
pushing around, so every line of the scorecard points at something real
("0.78 against 0.68 asked", "thick — thin asked", "28% burnt").
- lines.ts: ~45 lines keyed to whichever criterion actually decided the score,
so the verdict and the scorecard always agree — the verdict just has feelings
about it. MITEY has its own vocabulary.
- judge.ts: the toast turns on a pedestal under a spotlight, grade stamps in,
heatmap toggle for browning/spread. The generated inspector reacts by grade.
- orders.ts: seven handwritten days, then procedural. Day 1 is soft butter on
white; day 7 is a translucent film of MITEY on wet sourdough with fridge-hard
butter and a steak knife.
The find: every art colour was authored as sRGB and fed straight into a linear
lighting pipeline. Linear 0.14 encodes back out to sRGB ~0.4, so "near-black"
MITEY rendered as TAN and saturated butter washed to pale cream. This was the
root cause of the legibility fights in M0 and M2 — I'd been treating the symptom
by pushing the specular around. One line (albedo = pow(albedo, 2.2), mixing
stays in sRGB because that's the space the palette was picked in) and the whole
art direction landed: MITEY is genuinely black, butter is butter, the crust went
from cream to a rich golden brown.
Also: the specular lobe was far too broad. The slice is flat with the light and
camera both above it, so dot(N,H) ~0.98 everywhere and a wide lobe blankets the
whole slice in white. Tightened, so only ridges tilted into the light catch —
which is what a spread actually looks like.
The slice rolls its own lighting and ignores scene lights, so the judge's
spotlight did nothing to it; it now swaps its own rig for presentation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
eb74170011 |
M2: spreading — one control, three behaviours
Knife angle (wheel) is the only input, and spread/scrape/gouge all fall out of it.
Verified by driving real input through the real code paths:
cold toast + fridge butter -> damage 0.015, spread 0.029 (tears, as designed)
fresh warm toast -> damage 0, spread 0.117 (flows cleanly)
burnt toast, knife on edge -> char 99.9% -> 82.7% -> 54.5%
evenness 0.029 -> 0.123 -> 0.226
pale toast, knife on edge -> gouges; steak knife gouges 3.2x harder
That evenness column is the mechanic: scraping rescues you from char and wrecks
uniformity doing it. The judge will have opinions.
The trap is calibrated, not hoped for. Pressure comes from steepness, but past
SCRAPE_ANGLE the knife stops spreading:
fridge butter / cold toast yield 0.72 needs angle 0.75 IMPOSSIBLE
bench butter / cold toast yield 0.54 needs angle 0.57 (cliff at 0.62)
fridge butter / fresh toast yield 0.36 needs angle 0.38 fine
soft butter / fresh toast yield 0.16 needs angle 0.12 dream mode
So cold toast + hard butter cannot be spread at any angle, and the way out isn't
technique — it's not dawdling. The pressure gauge draws both marks so you can
see the gold sitting past the red and understand why you're losing.
Emergent and kept: a steak knife's narrow blade concentrates pressure enough to
beat cold butter's yield. It's the right tool for cold butter and a menace
everywhere else.
- cutlery.ts: 9 hand-authored archetypes (silhouettes are gameplay — the drawer
has to be fair) + compound box colliders for M4.
- dev.ts: harness that drives real gestures deterministically. Earns its keep.
Two bugs: cutlery meshes used mesh.rotation.x = -PI/2, which sends a profile
drawn toward +y to -z and one drawn toward -y to +z — the handle and blade were
laid out in opposite directions, overlapping, nowhere near the cursor. Now
rotated at the geometry level. And resize() computed aspect = 0/0 = NaN when the
container reports zero, which poisons the projection matrix so every raycast
silently misses — i.e. the entire mechanic stops with no error.
Metals need something to reflect: added a RoomEnvironment IBL and rebalanced the
direct rig, which was tuned before it existed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|