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>
140 lines
4.3 KiB
TypeScript
140 lines
4.3 KiB
TypeScript
/**
|
|
* Bread archetypes. The three numbers that matter for the toasting sim:
|
|
* thickness — thermal lag (slow to brown) but holds warmth longer, which
|
|
* matters later because warm toast is what softens hard butter.
|
|
* moisture — water must boil off before the crust can brown, so a wet crumb
|
|
* stalls, then catches up fast once it dries. Sourdough's trap.
|
|
* sugar — browns early and burns early. Raisin bread is a countdown.
|
|
*/
|
|
export interface Bread {
|
|
id: BreadId;
|
|
name: string;
|
|
blurb: string;
|
|
thickness: number; // world units
|
|
width: number;
|
|
height: number;
|
|
domeH: number; // how much of the top is the loaf's dome
|
|
moisture: number; // 0..1
|
|
sugar: number; // 0..1
|
|
crumb: [number, number, number];
|
|
crust: [number, number, number];
|
|
crumbScale: number; // crumb noise frequency
|
|
crumbContrast: number;
|
|
/** Speckles baked into the crumb: raisins, grain kibble. */
|
|
inclusions: number;
|
|
inclusionColor: [number, number, number];
|
|
}
|
|
|
|
export type BreadId = 'white' | 'sourdough' | 'multigrain' | 'raisin';
|
|
|
|
export const BREADS: Record<BreadId, Bread> = {
|
|
white: {
|
|
id: 'white',
|
|
name: 'Supermarket White',
|
|
blurb: 'Forgiving. Browns like it wants to be liked.',
|
|
thickness: 0.13,
|
|
width: 1.0,
|
|
height: 1.0,
|
|
domeH: 0.26,
|
|
moisture: 0.18,
|
|
sugar: 0.22,
|
|
crumb: [0.95, 0.9, 0.78],
|
|
crust: [0.78, 0.6, 0.36],
|
|
crumbScale: 34,
|
|
crumbContrast: 0.07,
|
|
inclusions: 0,
|
|
inclusionColor: [0, 0, 0],
|
|
},
|
|
sourdough: {
|
|
id: 'sourdough',
|
|
name: 'Thick-Cut Sourdough',
|
|
blurb: 'Wet, dense, and stubborn. Stalls, then catches you out.',
|
|
thickness: 0.23,
|
|
width: 1.06,
|
|
height: 1.02,
|
|
domeH: 0.3,
|
|
moisture: 0.72,
|
|
sugar: 0.1,
|
|
crumb: [0.93, 0.87, 0.72],
|
|
crust: [0.62, 0.42, 0.22],
|
|
crumbScale: 16,
|
|
crumbContrast: 0.16,
|
|
inclusions: 0,
|
|
inclusionColor: [0, 0, 0],
|
|
},
|
|
multigrain: {
|
|
id: 'multigrain',
|
|
name: 'Multigrain',
|
|
blurb: 'The seeds catch first. They always catch first.',
|
|
thickness: 0.15,
|
|
width: 1.0,
|
|
height: 0.98,
|
|
domeH: 0.24,
|
|
moisture: 0.34,
|
|
sugar: 0.3,
|
|
crumb: [0.82, 0.72, 0.55],
|
|
crust: [0.55, 0.4, 0.24],
|
|
crumbScale: 30,
|
|
crumbContrast: 0.13,
|
|
inclusions: 0.55,
|
|
inclusionColor: [0.36, 0.26, 0.15],
|
|
},
|
|
raisin: {
|
|
id: 'raisin',
|
|
name: 'Raisin Loaf',
|
|
blurb: 'Sugar. Everywhere. You have less time than you think.',
|
|
thickness: 0.14,
|
|
width: 0.98,
|
|
height: 0.98,
|
|
domeH: 0.26,
|
|
moisture: 0.3,
|
|
sugar: 0.92,
|
|
crumb: [0.93, 0.84, 0.66],
|
|
crust: [0.7, 0.48, 0.26],
|
|
crumbScale: 26,
|
|
crumbContrast: 0.09,
|
|
inclusions: 0.85,
|
|
inclusionColor: [0.22, 0.11, 0.12],
|
|
},
|
|
};
|
|
|
|
export const BREAD_ORDER: BreadId[] = ['white', 'multigrain', 'raisin', 'sourdough'];
|
|
|
|
/**
|
|
* Brands. The same style of bread exists at very different altitudes, and the
|
|
* altitude is mechanical, not cosmetic: `quality` scales how patchily the slice
|
|
* takes heat and how easily the crumb tears. Cheap bread is a lottery you can
|
|
* partially outplay; good bread does what you tell it.
|
|
*
|
|
* `loaf: true` means it only exists unsliced — the bakery does not pre-slice,
|
|
* and that's where the bread knife (and The Best Thing) earn their keep.
|
|
*/
|
|
export interface BrandDef {
|
|
name: string;
|
|
/** 0..1 — evenness of crumb, structural integrity, general dignity. */
|
|
quality: number;
|
|
/** Comes as a whole loaf: you slice it yourself. */
|
|
loaf?: boolean;
|
|
blurb: string;
|
|
}
|
|
|
|
export const BRANDS: Record<BreadId, BrandDef[]> = {
|
|
white: [
|
|
{ name: 'WONDERSLICE', quality: 0.25, blurb: 'Every slice identical. None of them good.' },
|
|
{ name: 'Crustworthy', quality: 0.8, blurb: 'White bread with references.' },
|
|
],
|
|
multigrain: [
|
|
{ name: 'Seedy Business', quality: 0.55, blurb: 'The seeds are load-bearing.' },
|
|
{ name: 'Grain Bill', quality: 0.85, blurb: 'Serious grains, alphabetised.' },
|
|
],
|
|
raisin: [{ name: 'Sun Chariot', quality: 0.6, blurb: 'The raisins travel in packs.' }],
|
|
sourdough: [
|
|
{ name: 'Longlife Sour-ish', quality: 0.35, blurb: 'Sourdough the way a photocopy is a painting.' },
|
|
{ name: 'the bakery loaf', quality: 1.0, loaf: true, blurb: 'Loaf only. The bakery does not pre-slice. The bakery is right.' },
|
|
],
|
|
};
|
|
|
|
export function brandOf(bread: BreadId, name: string): BrandDef {
|
|
return BRANDS[bread].find((b) => b.name === name) ?? BRANDS[bread][0];
|
|
}
|