sandoniette/OPUS-BUILD-BRIEF.md
type-two da840b531a M1: the marionette — dangle, walk, sticky grip
- sim/marionette.ts: control bar + 6 soft strings (head/back/2 hands/2 knees)
  over a 9-segment silhouette. Bar snaps to mouse; the felt lag is string
  stiffness<1 (worldConstraints), not an input buffer.
- Feet planted on floor + head+back strings keep the torso upright (no topple);
  earlier pure-pendulum rig spun past 360deg — the back string fixed it.
- Sticky grip: while gripped, any empty hand latches the nearest target on
  contact (puppet.update() each step). Reach = LMB drops the bar.
- Harness verbs: bar/sway/grip/pose/settle + dangle/walk/pickupTest + reset.
- Exit-bar measured: dangle settles 87 frames upright (lean 4.6); walk 8.84
  units upright (>6 req, lean 30<45); grip 10/10. Verified in-browser + shot.

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

13 KiB
Raw Blame History

サンドニエット 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, harness (window.__s), typecheck green. Box drops → settles 68 frames, rests flush on floor.
  • M1 sim/marionette.ts — control bar + 6 strings (head, back, 2 hands, 2 knees) over a 9-segment silhouette. Bar snaps to mouse; lag comes from soft worldConstraints (stiffness<1). Feet planted on floor + head/back strings = stable upright stand, no topple. Grip is sticky (latches on contact via puppet.update() each step). Exit-bar measured: dangle settles 87 frames upright (lean 4.6°); walk 8.84 units upright (>6 req, lean 30° < 45 topple); grip 10/10. Verified in-browser + walk screenshot.
  • Stack: Phaser Matter, autoUpdate:false — sim steps only via stage.stepSim(dt) (which also calls puppet.update()). Render contract: Matter body = truth, flat silhouette Prop.view follows. Rng = mulberry32. Tuning knobs: K at top of marionette.ts. Harness reset(homeX) teleports to a known pose for back-to-back tests.
  • Assets: all 20 2D props rendered on MODELBEAST → public/assets/img/ (samurai×5, bg_washi, floor_boards, title, tickets, slash, 5 food faces, splat). Not wired in yet — procedural silhouettes carry gameplay; art is backdrop/faces/juice (M4/M6).
  • Next: M2 cutting (sim/food.ts + the cleaver + cutStats) — the clean chop.
  • Run: npm run dev (:5173). Harness: __s.dangle() __s.walk() __s.pickupTest().

"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.