toastsim/src/sim
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
..
bread.ts M10 WIP + Build Brief 2: the bakery is code-complete, the prep bench is specced 2026-07-17 16:24:05 +10:00
cutlery.ts M10 WIP + Build Brief 2: the bakery is code-complete, the prep bench is specced 2026-07-17 16:24:05 +10:00
cutting.ts M12 routed + M14 onions: the juice detour and the stop-line 2026-07-17 19:19:49 +10:00
ingredients.ts Bell pepper in the cast; the pantry backlog spec'd in the brief (§7.5) 2026-07-17 18:22:29 +10:00
juicing.ts M12 WIP: oranges and the ribbed pyramid — the reamer twist is causal 2026-07-17 18:48:37 +10:00
mess.ts M11 WIP: the prep bench core — one board, and it remembers 2026-07-17 17:17:47 +10:00
slice.ts M10: the bakery, verified — the wobble follows you into the toaster 2026-07-17 16:58:13 +10:00
slicing.ts M10: the bakery, verified — the wobble follows you into the toaster 2026-07-17 16:58:13 +10:00
spreading.ts M10 WIP + Build Brief 2: the bakery is code-complete, the prep bench is specced 2026-07-17 16:24:05 +10:00
spreads.ts M8: crunchy vs smooth, and the jar that separates 2026-07-16 23:03:57 +10:00
toasting.ts M10: the bakery, verified — the wobble follows you into the toaster 2026-07-17 16:58:13 +10:00