toastsim/scripts/gen-assets.sh
monster 10adb268a0 M0: scaffold — Vite+TS+Three+Rapier, slice sim core, asset pipeline
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>
2026-07-16 21:16:18 +10:00

100 lines
5.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# TOASTSIM asset generation — 100% local, 100% free, via MODELBEAST on the M3 Ultra.
#
# ./scripts/gen-assets.sh 3d # product shot -> cutout -> GLB (slow: ~5 min each, gpu lane is serial)
# ./scripts/gen-assets.sh 2d # flux images (judge portraits, backdrops, ui)
# ./scripts/gen-assets.sh all
#
# Seeds are fixed so every asset is re-generatable. Style token is shared for coherence.
set -uo pipefail
MB_DIR="$HOME/Documents/MODELBEAST"
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
OUT_MODELS="$REPO/public/assets/models"
OUT_IMG="$REPO/public/assets/img"
mkdir -p "$OUT_MODELS" "$OUT_IMG"
cd "$MB_DIR" || exit 1
# shellcheck disable=SC1091
source data/agent.env
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"
# run_flux <seed> <prompt> [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
}
# gen_3d <name> <seed> <prompt>
gen_3d() {
local name="$1" seed="$2" prompt="$3"
if [ -f "$OUT_MODELS/$name.glb" ]; then echo " = $name.glb exists, skipping"; return; fi
echo "→ [$name] flux..."
local img cut
img=$(run_flux "$seed" "$prompt, $STYLE, $SHOT")
[ -z "$img" ] && { echo " ! $name: flux failed"; 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)
[ -z "$cut" ] && { echo " ! $name: bg_remove failed"; return 1; }
echo "→ [$name] mesh ($cut) — ~5 min..."
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
local glb; glb=$(find "$tmp" -iname '*.glb' | head -1)
if [ -n "$glb" ]; then mv "$glb" "$OUT_MODELS/$name.glb"; echo "$name.glb"; else echo " ! $name: no glb"; fi
rm -rf "$tmp"
}
# gen_2d <name> <seed> <prompt> [w] [h] [cutout]
gen_2d() {
local name="$1" seed="$2" prompt="$3" w="${4:-1024}" h="${5:-1024}" cutout="${6:-no}"
if [ -f "$OUT_IMG/$name.png" ]; then echo " = $name.png exists, skipping"; return; fi
echo "→ [$name] flux..."
local img; img=$(run_flux "$seed" "$prompt" "$w" "$h")
[ -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)
[ -n "$c" ] && img="$c"
fi
local tmp; tmp=$(mktemp -d)
./mb get "$img" -o "$tmp" >/dev/null 2>&1
local f; f=$(find "$tmp" -iname '*.png' -o -iname '*.jpg' | head -1)
[ -n "$f" ] && { mv "$f" "$OUT_IMG/$name.png"; echo "$name.png"; }
rm -rf "$tmp"
}
# ---- the judge: same person every time, seed fixed, only the expression phrase varies ----
JUDGE="portrait of a stern elderly australian food inspector, deep wrinkles, bushy grey eyebrows, thin wire glasses, grey moustache, wearing a brown cardigan over a shirt and tie, holding a clipboard, plain light grey background, head and shoulders, claymation stop-motion character, soft matte clay texture, warm studio light"
do_3d() {
gen_3d toaster 11 "a retro two-slot pop-up toaster, cream and chrome, with a browning dial"
gen_3d butter_dish 23 "a ceramic butter dish with a thick block of yellow butter on it, lid off"
gen_3d pb_jar 31 "an open glass jar of peanut butter, chunky brown spread visible, cream label"
gen_3d mitey_jar 37 "a small squat glass jar of dark brown yeast extract spread with a bright red and yellow label reading MITEY, lid off"
gen_3d plate 43 "a simple round white ceramic side plate, empty, seen at an angle"
}
do_2d() {
gen_2d judge_neutral 101 "$JUDGE, neutral unimpressed expression" 768 768 cut
gen_2d judge_intrigued 101 "$JUDGE, one eyebrow raised, mildly intrigued expression" 768 768 cut
gen_2d judge_impressed 101 "$JUDGE, small approving smile, impressed expression" 768 768 cut
gen_2d judge_disappointed 101 "$JUDGE, deeply disappointed frowning expression, looking down at clipboard" 768 768 cut
gen_2d judge_horrified 101 "$JUDGE, horrified appalled expression, eyes wide, recoiling" 768 768 cut
gen_2d bench_wood 202 "seamless top-down texture of a warm honey-toned wooden kitchen benchtop, subtle grain and knife marks, claymation soft matte look, flat even lighting, no objects" 1024 1024
gen_2d kitchen_backdrop 205 "blurry cosy 1970s australian kitchen interior background, warm cream tiles, soft bokeh, claymation set, no people, no text" 1280 768
gen_2d ticket_paper 210 "seamless texture of a plain cream paper order docket, faint blue lines, slightly crumpled, flat even lighting, no text" 512 512
gen_2d title_art 220 "a single slice of golden toast with a pat of butter melting on it, dramatic hero shot, claymation stop-motion, soft matte clay, warm rim light, plain dark background" 1024 768 cut
}
case "${1:-all}" in
3d) do_3d ;;
2d) do_2d ;;
*) do_3d; do_2d ;;
esac
echo "done."