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>
38 lines
939 B
TypeScript
38 lines
939 B
TypeScript
/** Tiny DOM helpers. The UI is an overlay; the game is the 3D scene. */
|
|
|
|
export function el<K extends keyof HTMLElementTagNameMap>(
|
|
tag: K,
|
|
cls?: string,
|
|
parent?: HTMLElement,
|
|
text?: string,
|
|
): HTMLElementTagNameMap[K] {
|
|
const e = document.createElement(tag);
|
|
if (cls) e.className = cls;
|
|
if (text !== undefined) e.textContent = text;
|
|
if (parent) parent.appendChild(e);
|
|
return e;
|
|
}
|
|
|
|
export function clear(e: HTMLElement): void {
|
|
while (e.firstChild) e.removeChild(e.firstChild);
|
|
}
|
|
|
|
export const ui = document.getElementById('ui') as HTMLElement;
|
|
|
|
/** A panel that owns a chunk of the overlay and can be shown/hidden as a unit. */
|
|
export class Panel {
|
|
readonly root: HTMLElement;
|
|
constructor(cls: string) {
|
|
this.root = el('div', cls, ui);
|
|
}
|
|
show(v = true): void {
|
|
this.root.style.display = v ? '' : 'none';
|
|
}
|
|
hide(): void {
|
|
this.show(false);
|
|
}
|
|
dispose(): void {
|
|
this.root.remove();
|
|
}
|
|
}
|