/** * 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 = { 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 = { 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]; }