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>
145 lines
12 KiB
Markdown
145 lines
12 KiB
Markdown
# TOASTSIM — Build Brief
|
||
|
||
**For: Claude Opus 4.8, executing in Claude Code on this Mac (M3 Ultra, cwd `/Users/m3ultra/Documents/toastsim`).**
|
||
**Approved by the owner. Build the whole thing; commit and push per milestone. Asset generation is free and pre-authorized — see §3.**
|
||
|
||
---
|
||
|
||
## 1. What we're building
|
||
|
||
**TOASTSIM** — a browser game about making toast, where the *inputs are analog* and the *output is judged*. Two connected activities:
|
||
|
||
1. **The Toast Round** — toast bread (timing + power), spread it (pressure/angle physics per spread type), scrape burnt bits (risk/reward), get scored by a deadpan judge.
|
||
2. **The Cutlery Drawer** — a 3D physics minigame: a tangled drawer of *similar-but-not-identical* metal cutlery; fish out the specific requested item against a timer, **while your toast cools** (cold toast = hard butter = harder spread round).
|
||
|
||
Tone: dry, characterful, slightly absurd. PowerWash-Simulator-style satisfaction with a scoring screen that makes you go "one more toast."
|
||
|
||
**Arcade loop:** a customer order appears ("dark toast, thin vegemite, NO burnt bits") → drawer → toast → spread/scrape → judgment → next order, escalating.
|
||
|
||
## 2. Stack & repo
|
||
|
||
- **Vite + TypeScript + Three.js** for everything (both scenes 3D — the toast slice is a real GLB you paint on). **@dimforge/rapier3d-compat** for drawer physics. No React needed; DOM overlay for UI is fine. Target 60fps in Chrome.
|
||
- Repo: the folder is empty. `git init`, build in it, remote is `https://gitea.partly.party/monster/toastsim.git` (exists, empty). Push `main` after each milestone with a clear message.
|
||
- Create `.claude/launch.json` with the Vite dev server so the app is previewable via the Browser pane. Verify visually with screenshots as you go — don't trust "it compiles."
|
||
- Generated assets go in `public/assets/` (committed — they're small GLBs/PNGs and reproducibility matters less than a working repo).
|
||
|
||
## 3. Asset factory — MODELBEAST (free, local, pre-authorized)
|
||
|
||
This Mac runs MODELBEAST, a job server wrapping local ML tools. **You are explicitly allowed to use it. Everything below is $0 (on-device).** Full reference: `~/Documents/MODELBEAST/AGENTS.md` — read it before your first job.
|
||
|
||
### Auth & basics
|
||
```bash
|
||
cd ~/Documents/MODELBEAST
|
||
source data/agent.env # sets MB_HOST + MB_TOKEN (guest: local ops only, free by design)
|
||
./mb ops -v # live operator catalog — trust this over docs
|
||
```
|
||
If the server is down (`curl http://localhost:8777/api/health` fails): `~/Documents/MODELBEAST/scripts/serve.sh`.
|
||
|
||
Guest tokens **cannot** run paid cloud ops (server-enforced 403) — never try to work around that; the local chain below is the intended path.
|
||
|
||
### The two pipelines you'll use
|
||
|
||
**Prompt → 2D image** (UI art, judge portraits, backdrops, labels, title screen):
|
||
```bash
|
||
./mb run flux_local -p prompt="..." -p model=flux2-klein-4b -p steps=4 -p seed=7 --wait --download out/
|
||
```
|
||
Fixed seeds = reproducible. 1024×1024 default; set `width`/`height` for backdrops.
|
||
|
||
**Prompt → 3D GLB** (bread, toaster, cutlery, jars, plate):
|
||
```bash
|
||
# 1. generate a clean "product shot" image of the object
|
||
./mb run flux_local -p prompt="<object>, single object centered on plain light grey background, soft studio lighting, 3/4 view, stylized chunky game asset" -p seed=<n> --wait
|
||
# 2. cut it out (biggest quality lever for image→3D)
|
||
./mb run bg_remove_local --asset <IMG_ID> --wait
|
||
# 3. mesh it (GLB + PBR texture, ~4-5 min, no login needed)
|
||
./mb run hunyuan3d_mlx --asset <CUT_ID> -p octree_resolution=256 -p texture_size=1024 --wait --download public/assets/models/
|
||
```
|
||
`blender_convert` exists if you need format conversion. `mflux_image_edit` for prompt-based image fixes.
|
||
|
||
### Job etiquette (hard rules)
|
||
- **GPU jobs run one at a time** (serial lane). Queue asset jobs early, then *keep writing code while they run* — check with `./mb jobs` / `./mb log <ID> -f`. Never spam retries on `queued`.
|
||
- First run of an operator may download weights (10–30 min). Be patient; don't cancel.
|
||
- Don't delete assets you didn't create. `./mb rm-asset` your own intermediates when done.
|
||
|
||
### Art direction & asset manifest
|
||
|
||
Pick ONE style token and use it in every prompt for coherence. Recommended: **"chunky stylized claymation kitchen, soft matte plastic look, warm colors"** — chunky geometry hides image→3D artifacts, reads well at game scale, and gives cutlery thicker (physics-friendlier) forms.
|
||
|
||
Generate (3D via the pipeline above, one flux seed noted per asset so it's re-runnable):
|
||
|
||
| Asset | Notes |
|
||
|---|---|
|
||
| Bread slices ×4: white, thick-cut sourdough, multigrain, raisin | These are the paint canvases — need a clear flat top face. If Hunyuan's top-face UVs are unusable for painting, **fallback: procedural slice** (rounded-box extrusion in Three.js) with a flux-generated bread-crust texture — the paint tech matters more than the mesh source. |
|
||
| Toaster (2-slot, retro) | Hero prop of the toast scene |
|
||
| Cutlery: butter knife, dinner knife, steak knife, spreader; dinner fork, dessert fork; teaspoon, dessert spoon, soup spoon | The "similar but not identical" cast. If thin tines/blades come out mangled from image→3D, **model cutlery procedurally** (extruded 2D profiles) — silhouettes must be hand-controlled anyway for fair gameplay. Decide after one test generation. |
|
||
| Butter dish, peanut-butter jar, vegemite jar (fictional brand: "MITEY") | Bench props / selection UI |
|
||
| Plate, kitchen bench surface | Bench = one big flux texture on a plane is fine |
|
||
| Judge portraits ×5 expressions (neutral, intrigued, impressed, disappointed, horrified) | 2D, flux + bg_remove. Same character every time: describe them exactly, fix the seed, vary only the expression phrase. A stern older health-inspector type with a clipboard works. |
|
||
| Title art, order-ticket paper texture, UI icons | 2D |
|
||
|
||
**Normalize every GLB on load in code** (bounding-box → unit scale, recenter pivot) — don't hand-fix meshes.
|
||
|
||
## 4. Game design spec
|
||
|
||
### 4.1 Toasting
|
||
- Browning is a **2D scalar field** (~64×64) per slice face, advanced each tick by heat input; render it by compositing into the slice's diffuse texture (white → golden → brown → black, with char speckle past 0.85).
|
||
- Heat input = toaster power dial (1–10) × coil proximity map (hotspots near coil lines, cooler edges, slight random per-run bias so no two runs identical).
|
||
- Per-bread params: `thickness` (thermal lag), `moisture` (delays browning then accelerates — sourdough), `sugar` (raisin browns fast and burns faster).
|
||
- Player controls: power dial + lever down; **no timer readout** — you judge by smell cues (text wisps: "smells warm… toasty… SMOKY") and can pop early anytime. Popping early and re-toasting is allowed but re-toasting browns unevenly (field already has gradient).
|
||
|
||
### 4.2 Spreading & scraping — the star mechanic
|
||
- Knife follows the mouse over the slice's top face (raycast → UV → paint into spread field, a second 2D field with `amount` per texel).
|
||
- **Scroll wheel = knife angle.** Flat (low angle) = wide contact, gentle pressure, spreads. Steep = narrow contact, high pressure: scrapes spread *off* — or drags burnt char off (reduces browning field where >0.85, spawns crumb particles). Too steep + too fast on clean bread = **gouge** (permanent damage decal, integrity penalty). One unified control, three behaviors.
|
||
- Dip into the butter/jar to load the knife (visible blob on blade, depletes as you spread).
|
||
- **Spread rheology** per type: `{ yieldPressure, viscosity, pickupAmount, tempSoftening }`
|
||
- **Butter**: high yieldPressure when cold — below it the butter doesn't spread, it *tears the bread* (drags the surface, integrity damage). `effectiveHardness = f(butterTemp, toastWarmth)`; toast warmth decays in real time (drawer dawdling matters). Soft butter = dream mode.
|
||
- **Peanut butter**: high viscosity, big pickup — goes on thick, hard to get even, drags.
|
||
- **Vegemite**: low viscosity, tiny correct dose — the skill is spreading then **scraping most of it back off**. Judge wants a translucent film; opaque = massive penalty ("This is a hate crime").
|
||
- Wrong tool from the drawer applies modifiers: spoon = blotchy coverage, fork = tears, steak knife = gouge-prone.
|
||
|
||
### 4.3 The drawer
|
||
- Rapier scene: drawer box, 10–14 cutlery pieces spawned tangled (drop-settle at load, cached). Order card shows a **silhouette** of the required item.
|
||
- Mouse grabs via spring joint (raycast to nearest piece, joint to grab point); pieces clatter and snag realistically. Lift the right item fully clear of the drawer rim to win.
|
||
- Timer = your toast cooling. Grabbing the *wrong* item and committing (button) lets you proceed with the wrong-tool modifier — sometimes the smart play.
|
||
- **Colliders: hand-authored compound primitives** (capsules/boxes) per archetype, NOT trimesh — stability and speed. Visual mesh can be the generated GLB.
|
||
- Start with 8 pieces; only raise the count once tangling feels good.
|
||
- Metal-on-metal clatter sounds are non-negotiable for feel (see §4.5).
|
||
|
||
### 4.4 Judging
|
||
Score 0–10, itemized so the player sees exactly why:
|
||
| Criterion | Measures |
|
||
|---|---|
|
||
| Browning match | mean browning vs. order target |
|
||
| Evenness | browning field variance |
|
||
| Coverage | % of face with spread in the target thickness band |
|
||
| Uniformity | spread field variance |
|
||
| Char | texels >0.85 remaining (scraping fixed it? no penalty) |
|
||
| Integrity | gouges, tears, over-scrape |
|
||
| Order compliance | right spread, right amount class (thin/normal/thick) |
|
||
| Time bonus | small — never dominant |
|
||
|
||
Judge delivers 1–2 dry lines keyed to the worst and best criterion (write a bank of ~40; e.g. "The coverage is admirable. The crater is not."). Show the toast rotating on a pedestal with a heatmap toggle. Grade stamp (S/A/B/C/F). **This screen is the game — make it excellent.**
|
||
|
||
### 4.5 Sound & feel
|
||
- Web Audio, synthesized where possible: toaster tick, lever clunk, pop (with slice launch physics!), knife scrape (pitch ∝ pressure), butter-tear rip, cutlery clatter (Rapier collision events → filtered noise bursts, velocity-scaled), judge stamp thunk.
|
||
- Screen-shake on pop, crumb particles on scrape, steam wisps off warm toast.
|
||
|
||
## 5. Milestones (commit + push each; verify in browser before moving on)
|
||
|
||
- **M0 — Scaffold**: Vite+TS+Three+Rapier boots, launch.json, placeholder cube scene, pushed. *Also: fire off the first MODELBEAST asset jobs now so weights download while you build M1.*
|
||
- **M1 — Toasting**: procedural slice + browning field + toaster controls + pop. Playable: make toast of any darkness.
|
||
- **M2 — Spreading**: knife, angle control, butter + vegemite + PB rheology, scraping, gouges. This milestone is where the fun lives — iterate until dragging cold butter genuinely feels bad in a funny way.
|
||
- **M3 — Judging**: full rubric, judge lines, grade screen, "next order" loop with 5 handwritten orders.
|
||
- **M4 — Drawer**: physics drawer with 8 pieces, silhouette orders, spring grab, toast-cooling link, wrong-tool modifiers.
|
||
- **M5 — Content & escalation**: 4 breads, order generator with difficulty curve (day 1: butter on white → day 7: thin vegemite on warm crumpet-thick sourdough with a steak knife because the drawer's a mess), persistent day/score.
|
||
- **M6 — Art & polish pass**: swap placeholders for generated assets, judge portraits, title screen, sound polish, perf check (60fps with drawer active).
|
||
|
||
At each milestone: run it, screenshot it, check the console is clean, then commit. If a milestone's feel is wrong, fixing feel beats advancing.
|
||
|
||
## 6. Judgment calls you're pre-authorized to make
|
||
|
||
- Procedural vs. generated meshes per asset (test generation first, choose per §3 fallbacks).
|
||
- Any tuning of the rheology/browning constants — the spec's numbers are starting points.
|
||
- Cutting scope inside a milestone to protect feel (e.g., 2 spreads that feel great > 3 that don't).
|
||
- Do **not** add paid/cloud dependencies, accounts, or external services. Do not touch anything in `~/Documents/MODELBEAST` except via the `mb` CLI as documented.
|