diff --git a/.claude/launch.json b/.claude/launch.json index 716823b..7f4be01 100644 --- a/.claude/launch.json +++ b/.claude/launch.json @@ -4,8 +4,8 @@ { "name": "toastsim", "runtimeExecutable": "npm", - "runtimeArgs": ["run", "dev"], - "port": 5173 + "runtimeArgs": ["run", "dev", "--", "--port", "5273"], + "port": 5273 } ] } diff --git a/package-lock.json b/package-lock.json index 165f1b8..6f9fbc5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,12 @@ { "name": "toastsim", - "version": "1.0.0", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "toastsim", - "version": "1.0.0", - "license": "ISC", + "version": "0.1.0", "dependencies": { "@dimforge/rapier3d-compat": "^0.19.3", "three": "^0.185.1" diff --git a/public/assets/models/batard_loaf.glb b/public/assets/models/batard_loaf.glb new file mode 100644 index 0000000..ebba671 Binary files /dev/null and b/public/assets/models/batard_loaf.glb differ diff --git a/public/assets/models/cheese_hard.glb b/public/assets/models/cheese_hard.glb new file mode 100644 index 0000000..f5a3339 Binary files /dev/null and b/public/assets/models/cheese_hard.glb differ diff --git a/public/assets/models/cheese_soft.glb b/public/assets/models/cheese_soft.glb new file mode 100644 index 0000000..4392c66 Binary files /dev/null and b/public/assets/models/cheese_soft.glb differ diff --git a/public/assets/models/cutting_board.glb b/public/assets/models/cutting_board.glb new file mode 100644 index 0000000..e2c9795 Binary files /dev/null and b/public/assets/models/cutting_board.glb differ diff --git a/public/assets/models/roast_tomatoes.glb b/public/assets/models/roast_tomatoes.glb new file mode 100644 index 0000000..e791768 Binary files /dev/null and b/public/assets/models/roast_tomatoes.glb differ diff --git a/public/assets/models/sourdough_loaf.glb b/public/assets/models/sourdough_loaf.glb new file mode 100644 index 0000000..2868c00 Binary files /dev/null and b/public/assets/models/sourdough_loaf.glb differ diff --git a/public/assets/models/tomato_vine.glb b/public/assets/models/tomato_vine.glb new file mode 100644 index 0000000..c66e242 Binary files /dev/null and b/public/assets/models/tomato_vine.glb differ diff --git a/scripts/gen-assets.sh b/scripts/gen-assets.sh index 6c0100c..4ef671e 100755 --- a/scripts/gen-assets.sh +++ b/scripts/gen-assets.sh @@ -9,6 +9,7 @@ set -uo pipefail MB_DIR="$HOME/Documents/MODELBEAST" +[ -d "$MB_DIR" ] || MB_DIR="$HOME/MODELBEAST" REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" OUT_MODELS="$REPO/public/assets/models" OUT_IMG="$REPO/public/assets/img" @@ -16,17 +17,39 @@ mkdir -p "$OUT_MODELS" "$OUT_IMG" cd "$MB_DIR" || exit 1 # shellcheck disable=SC1091 -source data/agent.env +if [ -f data/agent.env ]; then source data/agent.env +else set -a; source "$HOME/Documents/backnforth/.env"; set +a; fi +export MB_HOST="${MB_HOST:-http://100.89.131.57:8777}" STYLE="chunky stylized claymation kitchen prop, soft matte plastic look, warm colors" SHOT="single object centered on plain light grey background, soft studio lighting, 3/4 view, chunky stylized game asset" +# ponytail: m4pro node has dead venvs (2026-07-17) — jobs routed there fail in ~1s, +# so every stage retries up to 10x with a 60s pause until a healthy node takes it. +# Remove the loops once m4pro is fixed or pulled from the pool. +MB_TRIES=10 MB_PAUSE=60 + # run_flux [w] [h] -> asset id on stdout run_flux() { - local seed="$1" prompt="$2" w="${3:-1024}" h="${4:-1024}" - ./mb run flux_local -p prompt="$prompt" -p model=flux2-klein-4b -p steps=4 \ - -p seed="$seed" -p width="$w" -p height="$h" --wait 2>/dev/null \ - | awk '/image/{print $1}' | tail -1 + local seed="$1" prompt="$2" w="${3:-1024}" h="${4:-1024}" i out + for i in $(seq 1 "$MB_TRIES"); do + out=$(./mb run flux_local -p prompt="$prompt" -p model=flux2-klein-4b -p steps=4 \ + -p seed="$seed" -p width="$w" -p height="$h" --wait 2>/dev/null \ + | awk '/image/{print $1}' | tail -1) + [ -n "$out" ] && { echo "$out"; return; } + sleep "$MB_PAUSE" + done +} + +# run_cutout -> asset id on stdout +run_cutout() { + local asset="$1" i out + for i in $(seq 1 "$MB_TRIES"); do + out=$(./mb run bg_remove_local --asset "$asset" -p resolution=2048 --wait 2>/dev/null \ + | awk '/image/{print $1}' | tail -1) + [ -n "$out" ] && { echo "$out"; return; } + sleep "$MB_PAUSE" + done } # gen_3d — each stage retries once (transient rsync/infra errors happen) @@ -42,24 +65,21 @@ gen_3d() { fi [ -z "$img" ] && { echo " ! $name: flux failed twice"; return 1; } echo "→ [$name] cutout ($img)..." - cut=$(./mb run bg_remove_local --asset "$img" -p resolution=2048 --wait 2>/dev/null | awk '/image/{print $1}' | tail -1) - if [ -z "$cut" ]; then - echo " ~ $name: bg_remove failed, retrying once..." - cut=$(./mb run bg_remove_local --asset "$img" -p resolution=2048 --wait 2>/dev/null | awk '/image/{print $1}' | tail -1) - fi - [ -z "$cut" ] && { echo " ! $name: bg_remove failed twice"; return 1; } + cut=$(run_cutout "$img") + [ -z "$cut" ] && { echo " ! $name: bg_remove failed after $MB_TRIES tries"; return 1; } echo "→ [$name] mesh ($cut) — ~5 min..." local try glb - for try in 1 2; do + for try in $(seq 1 "$MB_TRIES"); do local tmp; tmp=$(mktemp -d) ./mb run hunyuan3d_mlx --asset "$cut" -p octree_resolution=256 -p texture_size=1024 \ -p max_num_view=6 --wait --download "$tmp" >/dev/null 2>&1 glb=$(find "$tmp" -iname '*.glb' | head -1) if [ -n "$glb" ]; then mv "$glb" "$OUT_MODELS/$name.glb"; echo " ✓ $name.glb"; rm -rf "$tmp"; return 0; fi rm -rf "$tmp" - [ "$try" = 1 ] && echo " ~ $name: no glb, retrying mesh once..." + echo " ~ $name: no glb (try $try/$MB_TRIES), pausing ${MB_PAUSE}s..." + sleep "$MB_PAUSE" done - echo " ! $name: no glb after retry" + echo " ! $name: no glb after $MB_TRIES tries" return 1 } @@ -72,7 +92,7 @@ gen_2d() { [ -z "$img" ] && { echo " ! $name: flux failed"; return 1; } if [ "$cutout" = "cut" ]; then echo "→ [$name] cutout..." - local c; c=$(./mb run bg_remove_local --asset "$img" -p resolution=2048 --wait 2>/dev/null | awk '/image/{print $1}' | tail -1) + local c; c=$(run_cutout "$img") [ -n "$c" ] && img="$c" fi local tmp; tmp=$(mktemp -d) diff --git a/src/scenes/prep.ts b/src/scenes/prep.ts index c4edd41..f74887c 100644 --- a/src/scenes/prep.ts +++ b/src/scenes/prep.ts @@ -169,6 +169,25 @@ export class PrepView implements View { mesh.scale.set(1, 0.85, 1); break; } + case 'mushroom': { + // Cap on a stem; group extent stays ~size so the shared y-offset works. + mesh = new THREE.Mesh(new THREE.SphereGeometry(r, 24, 16), skin); + mesh.scale.set(1, 0.6, 1); + mesh.position.y = ing.size * 0.15; + const stemMat = new THREE.MeshStandardMaterial({ + color: new THREE.Color().setRGB(...ing.colors.flesh, THREE.SRGBColorSpace), + roughness: 0.7, + }); + const stem = new THREE.Mesh( + new THREE.CylinderGeometry(r * 0.35, r * 0.42, ing.size * 0.6, 16), + stemMat, + ); + stem.position.y = -ing.size * 0.15; + stem.castShadow = true; + stem.receiveShadow = true; + g.add(stem); + break; + } default: mesh = new THREE.Mesh(new THREE.SphereGeometry(r, 28, 20), skin); } diff --git a/src/sim/ingredients.ts b/src/sim/ingredients.ts index 6637937..2a23c51 100644 --- a/src/sim/ingredients.ts +++ b/src/sim/ingredients.ts @@ -7,7 +7,7 @@ * 1 world unit ≈ 11cm, same convention as the loaf. */ -export type IngredientShape = 'sphere' | 'ovoid' | 'block' | 'wedge' | 'bulb'; +export type IngredientShape = 'sphere' | 'ovoid' | 'block' | 'wedge' | 'bulb' | 'mushroom'; export interface Ingredient { id: string; @@ -39,7 +39,10 @@ export type IngredientId = | 'parmesan' | 'brie' | 'cucumber' - | 'avocado'; + | 'avocado' + | 'button_mushroom' + | 'portobello' + | 'king_oyster'; export const INGREDIENTS: Record = { tomato: { @@ -136,4 +139,39 @@ export const INGREDIENTS: Record = { seeds: { count: 1, pocket: 'core' }, colors: { skin: [0.16, 0.2, 0.1], flesh: [0.78, 0.85, 0.5] }, }, + // The mushrooms: dry-slick skin that skates under a pressed blade, soft + // flesh once the edge bites. Three varieties, three different fights. + button_mushroom: { + id: 'button_mushroom', + name: 'button mushroom', + shape: 'mushroom', + size: 0.3, + // Tiny and rolls away from the blade — the slip is the whole challenge. + skin: { resistance: 0.15, slippery: 0.75 }, + flesh: { resistance: 0.15, moisture: 0.35 }, + roastable: true, + colors: { skin: [0.92, 0.88, 0.8], flesh: [0.97, 0.95, 0.9] }, + }, + portobello: { + id: 'portobello', + name: 'portobello', + shape: 'mushroom', + size: 0.85, + skin: { resistance: 0.2, slippery: 0.55 }, + // Gills crumble when mistreated, and it weeps dark juice. + flesh: { resistance: 0.2, moisture: 0.45, layered: true }, + roastable: true, + colors: { skin: [0.45, 0.32, 0.22], flesh: [0.82, 0.76, 0.66] }, + }, + king_oyster: { + id: 'king_oyster', + name: 'king oyster', + shape: 'ovoid', + size: 1.0, + // Mostly one dense smooth stem — cuts like a slippery little log. + skin: { resistance: 0.2, slippery: 0.8 }, + flesh: { resistance: 0.35, moisture: 0.3 }, + roastable: true, + colors: { skin: [0.9, 0.87, 0.78], flesh: [0.96, 0.94, 0.88] }, + }, };