sandoniette/OPUS-BUILD-BRIEF.md
type-two 802f738a3d M0: vite+ts+phaser+Matter skeleton, harness, box drops flush
- Phaser 3.90 Matter, autoUpdate:false — deterministic stepSim(dt), harness-driven.
- Render contract: raw Matter body = truth, flat black silhouette follows.
- Rng (mulberry32) ported from toastsim; dev harness on window.__s.
- Exit-bar measured: box drops y=80 -> settles 68 frames -> rests y=608.05,
  gap -0.05px flush on floor, landed:true. Verified in-browser + screenshot.
- scripts/gen-assets.sh (2D-only flux via MODELBEAST) firing in background.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 20:02:27 +10:00

217 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# サンドニエット SANDONIETTE — Build Brief 1: The Puppet, The Food, The Samurai
## STATUS (update this as you go — a cold session reads here first)
- **M0 ✅** vite+ts+phaser(3.90)+Matter skeleton, typecheck green, harness (`window.__s`)
installed. Box drops from y=80, settles in **68 frames**, rests **y=608.05**
flush on the floor (gap 0.05px, `landed:true`). Verified in-browser + screenshot.
- Stack: Phaser Matter, `autoUpdate:false` — sim steps only via `stage.stepSim(dt)`,
so the harness drives frame-by-frame. Render contract: raw Matter body = truth,
flat silhouette `Prop.view` follows it (`Stage.addBody`). `Rng` = mulberry32.
- Assets: `scripts/gen-assets.sh` (2D flux only) firing on MODELBEAST →
`public/assets/img/`. Log: `~/.jobs/sandoniette-assets.log`. Game never blocks on art.
- **Next: M1** puppet (`sim/marionette.ts`) — the dangle.
- Run: `npm run dev` (:5173). Harness verbs live in `src/dev.ts`.
*"SHUBATTO! SANDO-NI-ETTE!!!" — read this file and execute it. It is written to
work cold: a fresh session with no other context should be able to build this
game from here. Sibling project: `~/Documents/toastsim` — do NOT touch it, but
DO steal its discipline (this brief tells you exactly what to steal).*
## 0. What this game is
A 2D shadow-puppet kitchen. You are a **silhouette marionette chef** operated
by strings — and only by strings. You cut comical food, stack it into
sandwiches, carry the dish across the stage, and serve it to your one
customer: a giant, permanently unimpressed **samurai**, glaring down at the
kitchen like a health inspector from hell. Do badly — drop things, mangle the
cut, stack a leaning tower, serve late — and his katana unsheathes a little
more. At maximum: **SHUBATTO** — one lightning slash, your strings are cut,
and the chef collapses into a floppy ragdoll amid flying ingredients. Restart.
The comedy IS the physics: puppeteer lag, pendulum swing, food with mass.
The difficulty is "cunty hard but fair" — Loot Cycle Inc. tension, not
Sekiro. Every failure must be readable as YOUR wobble, never RNG.
Fictional inspiration stack (for taste, not code): Loot Cycle Inc. (physics
triage under a ticking fail-state), Ratatouille (tiny chef, big kitchen),
1997 bargain-bin PS1 import energy (Pepsi Man voice, broken-Engrish UI).
## 1. The design law (inherited from TOASTSIM, adapted)
1. **One game, not minigames in a trench coat.** Cut, carry, stack, serve are
all the same verb — *move a pendulum with intent* — scored by the same
judge. A new mechanic must ride the string sim or improve it for everyone.
2. **The judge is the voice.** The samurai is toastsim's judge generalized:
named criteria with bands, a line bank with 8+ lines per criterion, the
scorecard as the emotional payoff. He speaks subtitled Japanese barks with
deadpan Engrish translations — *"SANDO... ga... yugande iru."* ("The
sandwich. It leans.")
3. **Physics is the comedian, the sim is honest.** No canned animations for
gameplay outcomes — if the tower falls, it falls because the center of
mass left the base. Comedy from real consequences, always.
4. **Nothing ships unverified.** The dev harness drives REAL input
deterministically and every milestone commits with measured numbers.
This is toastsim's single most valuable organ. Transplant it first.
5. **Failure must be readable.** The player must be able to say what they did
wrong from the wreckage alone. If a fail reads as random, it's a bug in
the DESIGN even when the code is correct.
## 2. Tech decisions (made — don't relitigate)
- **Vite + TypeScript + Phaser 3 (Matter physics build)**. One engine
dependency; Matter gives joints/constraints for the marionette and food.
Same repo shape as toastsim: `src/sim/` pure logic, `src/scenes/`
presentation, `src/dev.ts` harness, `npm run typecheck` must stay clean.
- **The strings are Verlet-ish chains of small Matter bodies + pin
constraints** (stiffness < 1 so they stretch visibly under abuse). If
Matter's constraint jitter fights back, fall back to pure-math Verlet
ropes rendered as polylines with only the ENDS as Matter bodies
pre-authorized judgment call.
- **Silhouettes that carry physics are procedural polygons** (flat black
vector shapes with visible pin-joints, like Balinese/Japanese shadow
puppets). Generated art is for backdrops, food faces, the samurai, title
never for collision shapes. (Toastsim law: silhouettes are gameplay.)
- **Determinism**: fixed-dt stepping (`app.step(dt)` equivalent Phaser's
`update` driven manually in harness mode), seeded RNG only. No `Date.now()`
in sim code.
## 3. The bones to build (in order; each is a pure module + a scene + harness verbs)
### 3.1 `sim/marionette.ts` — the puppet
Classic marionette rig: a **control bar** (the player's proxy) + strings to
head, two hands, two knees. Mouse position = bar position; mouse tilt comes
from horizontal velocity; hold LMB = lower the bar (crouch/reach), release =
lift; RMB or Space = the GRIP (both hands clamp). That's the whole control
surface depth comes from the pendulum, not the input map.
- Puppet body: 9 segments (head, torso ×2, upper/lower arms, thighs/shins as
single limbs to start), pin joints with angular limits.
- **The lag is the game**: strings transmit force with ~0.15s of felt delay
(stiffness/damping tuned, not a literal buffer). Walk = rhythmic bar sway
that gets the legs swinging. It must feel like Getting Over It's honesty
with Octodad's dignity.
- Harness: `__s.bar(x,y)`, `__s.sway(freq, amp, secs)`, `__s.grip(on)`,
`__s.pose()` joint angles, `__s.settle()` frames-to-rest.
- **Exit bar (M1)**: pendulum settle time measured; a scripted sway walks the
puppet 6 stage-units without face-planting; grip picks a box off the floor
10/10 times in harness. Feel: dangle a hand over a table edge it should
make you smile before any food exists.
### 3.2 `sim/food.ts` + cutting
Food = 2D convex-ish polygon bodies with mass, friction, a `face` sprite,
and toastsim-style material params: `skin.resistance`, `flesh.moisture`
(wet food SPLATS a 2D mess decal + slippery floor patch), `wobble`
(jelly-class food jiggles as soft constraint clusters).
- **The cut**: cleaver in gripped hand; a cut commits when blade-edge speed ×
angle-through-food crosses threshold (a slow push SQUASHES moisture
splats, no cut). Pieces are real bodies with the parent's face split
across them. Cut quality = blade angle + speed + follow-through, scored as
**clean / ragged / crime** (crime = squashed).
- Harness: `__s.spawnFood(id, x)`, `__s.swing(from, to, secs)`,
`__s.cutStats()` pieces, area CV (toastsim's pieceCV, straight port of
the idea), splat mass.
- **Exit bar (M2)**: a practiced harness swing produces 2 clean pieces with
area CV < 0.1; a slow push produces 0 pieces + splat > 0; a wild fast
swing sends a piece flying offstage (and that's CORRECT).
### 3.3 `sim/stack.ts` — the sando
Sandwich = ordered layers stacked by carrying pieces onto a plate zone.
Stability is honest Matter stacking, but SCORED: center-of-mass drift per
layer, overhang, lean angle. The order ticket names the target
(bread-filling-bread minimum; later: the katsu sando, the "tower" specials).
- Harness: `__s.stack()` → layers, lean°, COM drift; `__s.nudge(force)` for
the earthquake test.
- **Exit bar (M3)**: 3-layer sando stands; lean° reported; nudge test
topples a bad stack and not a good one; toppled food splats/rolls.
### 3.4 `sim/samurai.ts` — the judge + the fail state
The anger meter is toastsim's judge wearing armor. **Anger events**: drop
(+small), splat (+medium), crime-cut (+medium), served-wrong (+large),
served-late (+ramps), offstage food (+large). **Cooldown**: a clean serve
sheathes a little. Meter states = katana unsheath stages (0/25/50/75%:
eyebrow → hand-on-hilt → 10cm of blade → full glare, screen vignettes).
- **THE STRING CUT**: at 100% — freeze 300ms, slash line across the screen,
ALL string constraints deleted in one frame, ragdoll collapse, ingredients
launched with the impulse of the slash, gong. Score card over the wreckage.
It must be fast, unfair-FEELING but earned, and funny every single time.
- Serve = dish carried into the counter zone + a bow (hold still 0.5s — the
dwell commit, straight from toastsim).
- Scorecard groups: **THE CUT / THE SANDO / THE SERVICE / THE FLOOR** (the
floor = mess+drops; the bench criterion reborn). 8+ lines each, bilingual
bark + Engrish subtitle. Grade SD; S = "…umai." and a single approving nod.
- **Exit bar (M4)**: full loop playable — order in, cut, stack, carry, serve,
scored; harness plays a clean run (B or better) and a disaster run (strings
cut) deterministically; numbers in the commit message.
### 3.5 Orders, days, escalation (M5)
Ticket rail like toastsim's days: Day 1 = one open sando. Escalate
ingredient count, wetness (tomato before you've earned it is cruelty),
required cuts, and the samurai's patience ramp. **One new pressure per day,
never two.** Day N bar: each day introduces its thing and is beatable by the
harness with a scripted-but-honest run.
### 3.6 The juice (M6)
Title screen: giant brush-stroke 「サンドニエット」, the voice line (text
crawl + gong if no audio yet: **SHUBATTO!! SANDONIETTE!!!**), attract-mode
ragdoll. Shamisen sting + slash whoosh + wet splats (Web Audio, synthesized
or CC0 — no paid assets). Unlockable strings as difficulty modifiers —
**rubber** (bouncy, +lag +comedy), **chain** (heavy, slow, precise), **silk**
(tighter, for cowards) — pre-authorized to defer if M5 lands better without.
## 4. Assets — MODELBEAST, 2D ONLY (this game needs no meshes)
Steal `scripts/gen-assets.sh` from toastsim as the template, keep only the
flux path (`run_flux` + `run_cutout`). **Machine facts current as of
2026-07-17** (verified the hard way): client lives at `~/MODELBEAST` (NOT
~/Documents), token is `MB_TOKEN` in `~/Documents/backnforth/.env`, export
`MB_HOST=http://100.89.131.57:8777` (m3ultra queue). Node m4's venvs are
dead — every stage retries up to 10× with 60s pauses until a healthy node
takes the job; keep that loop until told m4pro is healed.
Style token (fix it, reuse it everywhere):
`"japanese shadow puppet theatre, flat black silhouette with cut-out
details, warm washi paper backdrop, edo period, high contrast, hand-crafted
paper texture"`.
Batch 1 (fixed seeds, start at 301): washi/shoji stage backdrop, wooden
stage floor boards, the samurai in 5 anger states (SAME seed, expression
phrase varies — the toastsim judge-portrait trick), title brushwork art,
paper order tickets, food FACES (bread, tomato, cheese, katsu, egg —
comical, slightly alarmed), splat decals, the katana slash frame.
Fire asset jobs FIRST, build procedural while they render, `loadProp`-style
catch-fallbacks so the game never blocks on art.
## 5. What to steal from toastsim, literally
Open these files and port the PATTERN (not the code — different engine):
- `src/dev.ts` — the harness: real input driven deterministically, helpers
returning stats objects, `window.__s` installer. THE most important file.
- `src/sim/cutting.ts` — pieceCV and the cvWord ladder; material params.
- `src/game/judging.ts` + `lines.ts` — criteria bands, weighted groups, the
line bank shape and its voice discipline.
- `OPUS-BUILD-BRIEF-2.md §0` — the verify discipline and "known traps" habit:
keep a STATUS section at the top of THIS file updated as you go, so any
session can pick up cold.
## 6. Milestone order and the bar
M0 bootstrap (vite+ts+phaser+harness skeleton, typecheck green, a Matter box
drops onto the stage floor) → M1 puppet → M2 cutting → M3 stacking → M4
samurai + full loop → M5 days → M6 juice. One commit per milestone, message
= what you measured. Screenshots when visuals change. If a feel is wrong,
fixing feel beats advancing — the three feels that must be RIGHT: **the
dangle** (M1), **the clean chop** (M2), **the string cut** (M4).
Pre-authorized judgment calls: all tuning constants; Verlet fallback (§2);
segment count on the puppet; deferring string unlockables; cutting a food
type that won't behave. Do NOT add: paid/cloud dependencies, accounts,
telemetry, or 3D. Do not touch `~/Documents/toastsim` or `~/MODELBEAST`
except through `mb`.
The bet: toastsim proved the bones (harness, honest sim, judge voice, free
assets) make a game feel handmade and alive. Sandoniette bets the same bones
survive transplant into slapstick — that "verified physics comedy" is a
genre, and we can build it in milestones without ever shipping an unfunny
fall or an unfair death.