Commit Graph

19 Commits

Author SHA1 Message Date
type-two
00e3f1694e THE PHONE: the game was unplayable on one, and an audit says so
Moving the station card off the ticket last commit only fixed desktop.
An automated occlusion sweep — enter every station, elementFromPoint at
the card's centre, check who is actually painted there — confirmed all
13 cards clear at 1280px, and then found the same collision alive and
well at 375px, where a 240px card and a 272px ticket cannot both sit at
the top of a 375px screen. Three real problems, all of them "nobody has
opened this on a phone since the touch controls shipped":

1. THE PANELS. Both were still desktop width. Narrowed to 50vw/41vw on
   small screens with the type scaled down, and the ticket capped at
   46vh and scrollable so a chatty customer cannot shove the card off
   the bottom of the world.

2. THE CAMERA. Every scene is composed left-to-right — the spice rack,
   the coal bed, the drawer — and a perspective camera holds its
   VERTICAL fov, so a portrait phone silently crops the sides off all of
   it. On a 375x812 screen the outer spice jars were simply not on the
   display, and you cannot drag a jar you cannot see. Narrow viewports
   now widen the fov to hold the horizontal extent instead — capped at
   74 degrees, because the honest 16:9 lock wants 112 and that is not a
   camera, it is a peephole (tried it; the bench went small and distant
   in the middle of an empty floor). Desktop is untouched at 42.

3. THE TICKET COVERED THE BENCH. Even framed correctly, the ask sat on
   top of three of the six spice jars. It now folds on a tap to the day,
   the customer and the demo button, with 'tap to read' underneath, and
   unfolds again on the next order because you have not read that one.

Verified at 375x812: no occlusion at any station, no horizontal
overflow, outer jars at 0.75 NDC and reachable, ticket 376px → 97px
folded. Desktop back to fov 42 and unchanged: grill 9.4, pan 8.0, eggs
10.0, chips 10.0, curry 10.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 20:05:01 +10:00
type-two
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>
2026-07-26 19:51:43 +10:00
type-two
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>
2026-07-21 03:17:49 +10:00
type-two
d998b74abc THE FACES: every regular gets a portrait, the jar gets three, the dish gets one
Twenty-two assets off the Cloudflare FLUX lane (scripts/gen-art-batch.sh
— idempotent, fixed seed per character, ~5s each, barely a dent in the
10k daily neurons):

- Thirteen regulars on their tickets, clipped into the corner like a
  loyalty card photo: Deidre's floral cardigan, Ray's flat cap, the
  child on tiptoes with a fistful of coins, the bloke from two doors
  down in his dressing gown, the delivery mid-yawn at 6am, the lunch
  rush as three impatient torsos, the pavlova order in her pearls. The
  inspector's ticket uses his own portrait; a name with no face made
  simply shows no frame (onerror removes it).
- THE MOTHER's jar rendered in all three states on the title button:
  bubbling over the rim, sulking under grey liquor, and dead with a
  tiny white surrender flag planted in the crust.
- Six dish heroes on the scorecard beside the ask: the skewers, the
  pan, the sliced rib eye, the whites bowl, the poached egg mid-drip,
  the benedict.

Verified live: pavlova portrait on day 20's ticket, benedict table on
21's, jar_sulking at gap 3, hero_eggs on the eggs scorecard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 02:37:15 +10:00
type-two
ef66e1aa7a THE MOTHER: a sourdough starter that lives in your save and holds a grudge
After five days survived, a jar appears on the title shelf. She wants
feeding — one click — every new day you reach, and the stamps are maxDay
based so replaying old days for the S never starves her. Fed within a
day, toaster-days earn +0.2 and the line 'The starter is lively — the
crumb held. (She did that, not you.)'. Three days forgotten and the jar
sulks, and the crumb knows. Five and she goes still: the button becomes
a funeral, and one click begins THE MOTHER (second of her name) — she
forgives, mostly. The deaths counter never resets. Station days (grill,
pan, cold, steak, eggs, poach) are honestly out of her reach.

Also hardened loadSave against non-finite numbers: NaN and Infinity
stringify to null, and one bad number was enough to rot every derived
stat downstream. Every numeric field now refuses them on the way in.

Verified live: jar born fed at maxDay, starved to a funeral, resurrected
as second-of-her-name (deaths=1 persisted), lively bonus fired on a
bruschetta (9.6 -> 9.8 with the line), sulk line at gap 3, save numbers
intact after the full arc.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 01:45:44 +10:00
type-two
9eb1c2db3a THE THUMBS: phones get their keys back
A touch strip for devices with no wheel and no keyboard — which is most
of the phones the SHARE links land on. Each button IS its key: pointer
down dispatches the real keydown, pointer up the real keyup, so the
holds (crack charge, spoon drain) behave exactly like the physical key,
and the heat arrows synthesise real wheel ticks at the canvas. Drags
already worked — the input layer was pointer-events from birth.

Only appears on touch devices (desktop cooks keep a clean bench; __tp()
summons it for the curious), hides while the title is up, and the small-
screen pass folds the rest: the title stacks and stops overflowing, the
ledger fits, the judge's eye steps up out of the strip's way.

Verified at 375x812: no horizontal scroll, buttons drive the real input
path (heat arrows walked the poach knob 0 to 7 through the button
handlers), pad hidden at title / shown in service, day-21 pot playable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 01:37:37 +10:00
type-two
146c6f36c1 THE CRITIC: every 10th day, the flaw is the headline
Days 10, 20, 30... he seats himself. The ticket warns you in red, the
scorecard portrait drains to grayscale, and the scoring changes shape:
the worst row on the card counts DOUBLE. Perfection survives him
untouched (doubling the worst weight of a flawless run moves nothing) —
anything less gets leaned on, exactly where it hurts. His lines are his
own: an S gets 'I came to write a takedown. I cannot. This is the worse
outcome — for me.'; an A learns that 'competent' has never once appeared
in a good review; a failure gets mercy in print, which is worse.

The Daily is exempt (same deal for everyone means the same judge), and
demonstrations reset the flag — he doesn't review his own cooking.

Verified live on day 20: banner up, grayscale portrait, critic line on
the card, day 21 clean; flawless eggs still 10.0 by construction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 01:32:36 +10:00
type-two
a8ae26f2d4 THE SHADOW: hard mode is him saying nothing
Once you have pulled a single S, the title offers THE INSPECTOR'S SHADOW:
arm it and the kitchen's words go dark for the whole run — no state
words, no wobble words, no browning gauge. The ticket still asks and the
controls hint survives, but the read is the face in the corner, the
sound of the pan, and the food itself. Every diegetic channel we built
was practice for playing without the subtitles.

Take an S with the words off and the ledger chip earns a star (S★), the
save remembers it (shadowed, per day, survives loadSave's corruption
filter — which was quietly dropping unknown fields; fixed), and he gives
you the only compliment he has: 'In the silence, too. I have nothing
left to teach you. ★'

Verified live: toggle arms, body.shadow hides the words (computed
visibility checked), S-in-silence writes shadowed.day20, star renders on
the chip after reload.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:46:44 +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
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
monster
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>
2026-07-17 17:17:47 +10:00
monster
31f2eff28e M10 WIP + Build Brief 2: the bakery is code-complete, the prep bench is specced
M10 (code-complete, verification pending — see OPUS-BUILD-BRIEF-2.md §2):
- Brands with mechanical quality (WONDERSLICE 0.25 vs Crustworthy 0.8 etc.):
  quality scales heat-map patchiness and crumb fragility. Bakery sourdough is
  loaf-only.
- Slicing minigame: aim thickness, saw with vertical strokes; progress/wobble/
  squash; wedge slices brown unevenly in the toaster (the bad cut follows you);
  squash arrives as pre-existing damage.
- Bread Knife + THE BEST THING (heavy, soft-scalloped, saw eff 1.65) join the
  drawer; on loaf days the drawer trip happens BEFORE toasting ("they are
  waiting") and whatever you fish out is what you slice with.
- Day 9 handwritten (the inspector wants a doorstop with parallel faces),
  procedural brands/loaf days from day 10, The Cut criterion (band + wedge +
  squash, TBT signature bonus), cut line bank.
- Verified so far: day-9 ticket chips, knife-trip drawer with bread_knife AND
  the_best_thing present, timer label, typecheck + build. Slicer end-to-end
  play, tuning, and couplings assertions are itemised in the brief.

OPUS-BUILD-BRIEF-2.md — the prep bench: generalized ingredients + cutting
(slip, juice, piece-evenness CV), the judged mess field, oranges + ribbed
pyramid juicer (halve evenness caps yield, seeds by juicer tier, the
Juicernaut), the grater (zest/pith line, cheese needs cold, knuckles), onions
(sting that blurs REAL vision, the stop-short-of-the-stem criss-cross dice),
and bruschetta as the capstone (temperature planning, roast tomatoes on the
browning field, topping Clark-Evans, the sog clock vs doorstop slices).

Bakery asset batch partially failed (flux errors + one lost GLB) — regen
instructions with retries are in the brief §8.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 16:24:05 +10:00
monster
16860cf5d6 M9: the fresh-eyes pass — played it cold, fixed what a stranger hits
Sat down with it like someone who'd never seen it. Five findings, all
reproduced before fixing, all verified after:

1. The kitchen panel was a cheat console. BREAD/SPREAD/TOOL/BUTTER are the
   order's facts, but the widgets stayed live — you could fetch a soup spoon
   and then quietly re-select the butter knife. Locked during play; the
   browning dial stays yours because it's a real control.

2. The drawer card printed the ANSWER under the silhouette. The entire
   "similar but not identical" puzzle, solved by a caption. The name is gone;
   after ten seconds of fumbling it fades in as a pity: "fine. it's the
   butter knife."

3. Lifting anything above the rim committed it instantly — but fishing means
   lifting blockers, so you could accidentally "choose" whatever you were
   moving aside. Commit now needs 0.45s of continuous hold above the rim,
   which doubles as a beat to compare what you're holding against the
   silhouette. Verified: a wobbly lift takes ~0.95s because dipping below
   the rim resets the clock, and that's correct.

4. Coverage scored 20% on a slice a player had visibly covered corner to
   corner. Root cause: the "covered" floor scaled with the ASKED amount, so
   Coverage and Amount both punished thinness — double jeopardy. Coverage now
   asks its own question (are there bare patches? floor = 0.06 absolute) and
   the same conscientious play scores 84%.

5. Nothing told you how much was on the toast until the verdict. New live
   readout, computed by the judge's own functions: "on the toast: thin —
   normal asked · 84% covered". Watching it, a played-well day 1 went
   8.8 -> 9.0, and the missing 0.2 is named on the card (stripes), not hidden.

Also: the drawer holds 11 pieces dropped down a tighter column (a heap you
rummage in, not an exploded diagram — padded with extra teaspoons, like every
real drawer), it sits on a bench instead of floating in a void, Service is
sim-time rather than wall-clock, and the title mentions the drawer exists.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 15:42:09 +10:00
monster
5b09f23c7e M5+M6: content, generated assets, title, sound, perf
Content (verified — same dial, same 18s, four breads that disagree):

  bread        4s     9s     18s    char
  white        0.22   0.54   1.08   93%
  multigrain   0.20   0.52   1.06   92%
  raisin       0.30   0.76   1.14   99.8%
  sourdough    0.11   0.33   0.74   14%

Sourdough stalls at half the rate then accelerates hardest (x3.0 vs white's
x2.45) as its water boils off; raisin browns 40% faster and is essentially
carbon by 18s. Seven handwritten days then procedural, day persisted.

Assets, all generated on-device and free, all reproducible from fixed seeds:
- the judge's five expressions (one seed, one description, only the expression
  phrase varying — he stays the same man while his face falls)
- title art, wooden bench, blurred kitchen backdrop
- butter dish / peanut butter / MITEY jars as GLBs, normalised on load
  (recentre, scale to fit, sit on y=0) rather than hand-fixed, so regenerating
  them doesn't mean re-fixing them

The toaster stays procedural: the generated one is prettier but this one has a
lever we can drive and a dial that turns. Judgment call, per the brief.

Sound is synthesised, no samples: clatter is driven by real Rapier contact-force
events and scaled by force, which is the only way a drawer of cutlery can sound
like one. Plus lever clunk, knife scrape pitched by pressure, and the stamp.

Perf, measured (16.7ms is the 60fps budget):
  kitchen 0.99ms/step · drawer 0.12ms · drawer while dragging 0.21ms

Production build: 2.87MB / 1.0MB gzipped, mostly Rapier's inlined wasm + three.

Textures carry colorSpace = SRGBColorSpace explicitly — same class of bug as the
albedo gamma fix in M3, and it washes them out silently if you forget.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 22:21:06 +10:00
monster
1ba66f0cdf M4: the cutlery drawer — and the link that makes it matter
toast -> drawer -> spread. Eight pieces settle into a real tangle; find the one
on the card, drag it up and over the rim, and whatever you're holding is what
you're spreading with.

The link, verified end to end (day 4, butter softness 0.3):

  4s in the drawer   warmth 0.78  yield 0.343  needs angle 0.36  comfortable
  50s in the drawer  warmth 0.17  yield 0.565  needs angle 0.60  cliff is 0.62

Dawdling walks you to within two hundredths of the scrape cliff. On day 7
(fridge-hard butter) it tips over into genuinely unspreadable. The drawer isn't
a timer bolted on the side — it's the thing that makes the butter hard.

Physics choices that earn their keep:
- Compound boxes, never trimeshes. A trimesh fork tangled in a trimesh fork is
  a solver nightmare and slow with it; the tine envelope is what matters anyway.
- Grab is a capped PD spring, not a fixed joint, so a snagged piece fights the
  ones lying on it and loses instead of tunnelling through them.
- The solver is fixed-stepped so a frame hitch can't detonate the pile, and the
  drawer settles offline so you open it on an existing mess.
- Clatter is synthesised from real contact-force events, velocity-scaled — a
  drawer of cutlery is a noise, and the noise has to come from the collisions.

Three fixes:
- Quaternion.toArray() returns [x,y,z,w]; Rapier wants {x,y,z,w}. Every axis
  read back undefined -> NaN in the solver -> wasm panic on the first step.
- The drag plane faced the camera, but the camera looks down into the drawer, so
  dragging to the top of the screen couldn't physically lift anything over the
  rim. It's a vertical plane now: up-screen means up.
- Cooling lived in kitchen.update(), which stops running the moment the drawer
  opens — so the toast stayed hot no matter how long you rummaged, quietly
  cancelling the entire mechanic. It's an app-level tick now.

The drawer was 4.6x3.4 and the pieces politely spread out flat. Tangling needs
crowding: 3.1x2.2, dropped down a narrow column so they land on each other.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 22:13:08 +10:00
monster
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>
2026-07-16 22:04:22 +10:00
monster
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>
2026-07-16 21:51:34 +10:00
monster
67cfe56bb9 M1: toasting — browning sim, toaster, lever, and the pop
Playable: pick a bread, set the dial, drop the lever, watch it brown, pop it
onto the plate. No timer readout — you go by smell.

- toasting.ts: heat map from real toaster behaviour (element stripes, cool top
  edge where the slice stands proud of the slot, edge falloff, per-run noise
  bias). Moisture must boil off before the crust browns at full rate, so
  sourdough stalls then catches up; sugar browns and burns early.
  Rate is measured against the heat map's actual ~0.77 mean, not guessed:
  power 6 -> golden 0.511 at 10s, verified in-browser.
- Props on a real scale (1 unit ~ 11cm, one slice wide) — the toaster is
  meant to dwarf the bread.
- The pop solves a genuine ballistic arc from slot to plate.

Three bugs found by driving it rather than trusting the compile:
- valueNoise2D read one past its grid at x=w-1, so heatBias was NaN along an
  edge, which poisoned mean browning to NaN — i.e. every judge score would
  have been NaN. Clamped.
- erode() clamped at the field border, so border texels could never erode out
  of the mask and kept those NaNs in scope. Off-grid now counts as outside.
- Timer.connect(document) zeroes dt whenever document.hidden is true, which
  froze the sim solid in an embedded browser. Dropped; rAF already pauses for
  hidden tabs. App.step(dt) is now exposed so the game can be driven
  deterministically for verification.

Also: screen shake was mutating the camera permanently instead of offsetting
it per-render, and the plate's lathe profile touched the axis (degenerate
triangles -> starburst normals) — rebuilt from primitives.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 21:29:51 +10:00
monster
10adb268a0 M0: scaffold — Vite+TS+Three+Rapier, slice sim core, asset pipeline
The bread slice is the whole game's canvas, so it's built first and properly:

- Field: a 128x128 scalar field with bilinear sample, a mass-conserving brush,
  self-levelling relax, and the masked statistics the judge will read.
- Slice: extruded loaf silhouette (domed top), planar-projected UVs so the sim
  grid lines up with the bread, per-texel silhouette mask, and four fields
  (browning/dryness/spread/damage) packed into one RGBA8 texture per frame.
- Custom shader: browning ramp with blotchy char, crumb/crust/inclusions,
  spread wetting + opacity + thickness relief, gloss, gouge exposure, and
  flat data views for the judge screen.
- bread.ts / spreads.ts: the archetypes and the rheology the difficulty
  curve will live in.
- scripts/gen-assets.sh: reproducible fixed-seed asset generation through
  MODELBEAST (flux_local -> bg_remove_local -> hunyuan3d_mlx). All on-device.

Legibility work that mattered: butter rendered invisible at first. Fixes were
a gamma-style wetting darkening (a multiply *brightens* pale crumb), a normal
perturbation taken in UV units rather than raw texel deltas (~30x too small),
and pulling the lighting back under 1.0 so ACES stops desaturating yellow to
cream.

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