- Sando is zone-DETECTION now: the stack = food bodies physically resting in the plate zone, bottom->top. Hand-stacked and harness-stacked sandos are judged identically (sando.kinds() is what the judge reads). - The full live loop: Game.state + updateLive() — ticket clock with late-anger (1 bump/3s overdue), bow-dwell serve (hold still 0.5s over a matching standing sando -> scorecard), click -> next day; string cut -> ragdoll -> fail card -> click retries the same day with fresh strings. setupDay stages bread + fillings per order; sins fire live off cutter.onCut + offstage sweep. - String unlockables: HEMP/RUBBER/CHAIN/SILK picker on the title (stiffness/ damping multipliers, registry-persisted). Rubber visibly stretches. - Days 6-8: Tamago (it rolls), Tofu (fragile), THE TOWER (5 layers) + tofu food. - samurai_bow (same seed): an S serve swaps in the single approving nod. - Battery: M1 87f/8.41u/10-10, M2 cv0 clean, M3 both, days SSSSSSSS, live serve commits at 31f (~0.5s), death->retry + serve->advance verified. - Known: embedded preview pane never fires rAF (visibilityState hidden) — live loop freezes there by browser design; harness steps manually. Real tabs fine. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
342 lines
22 KiB
Markdown
342 lines
22 KiB
Markdown
# サンドニエット SANDONIETTE — Build Brief 1: The Puppet, The Food, The Samurai
|
||
|
||
## STATUS (update this as you go — a cold session reads here first)
|
||
- **LIVE 🌐** https://partly.party/sandoniette/ — `bash deploy.sh` (build base
|
||
`/sandoniette/` → rsync dist/ → docker cp into `forum-nginx:/usr/share/nginx/html/sandoniette`).
|
||
Games VPS `humanjing@100.71.119.27`, Cloudflare-fronted (purge cache after big
|
||
changes). Verified live: index+JS 200, JS is application/javascript, PNGs image/png,
|
||
title renders. `vite.config.ts` bases dev at `/`, build at `/sandoniette/`; asset
|
||
loads use `import.meta.env.BASE_URL`. Local preview needs `vite preview --base=/sandoniette/`.
|
||
|
||
- **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).
|
||
- **M2 ✅** cutting. `sim/geom.ts` (pure convex-split + pieceCV/cvWord), `sim/food.ts`
|
||
(convex polygon foods: bread/tomato/cheese/katsu/egg, material params
|
||
resistance/moisture/wobble), `sim/cutting.ts` (blade pass-through → split food
|
||
into 2 real bodies along the chord, or squash→splat on a slow wet press).
|
||
Honest geometry, no canned slice anim. Traps hit + fixed: (a) cut line through a
|
||
vertex made lopsided halves — split now treats on-line verts as shared (regression
|
||
guarded in `geom.check.ts`); (b) **Matter `setVelocity` is px/STEP not px/s** —
|
||
piece launch was 60× too fast and flung halves offstage. Exit-bar measured:
|
||
**chop** 2 clean pieces CV 0 (<0.1 req); **push** 0 pieces + splat 3.28 (wet crime);
|
||
**wild** swing flings a piece offstage. Verified in-browser (two clean halves rest
|
||
on the board) + `npm run check` (headless geom asserts).
|
||
- **M3 ✅** stacking. `sim/stack.ts` — the sando: ordered layers on a plate zone
|
||
(x=980), honest Matter stacking scored by lean°, COM drift, overhang. `standing`
|
||
is measured by **elevation** (a toppled tower loses height) not lean angle — a
|
||
short 3-layer stack reads 30° from a mere 24px step, so lean alone is a bad
|
||
topple test. Trap fixed while here: **grip pickup was non-deterministic** (trials
|
||
shared puppet state) AND unreliable (yanking the bar sideways just stretches the
|
||
arms — they hang from the shoulders, so the BODY must drift over the crate first;
|
||
`shiftBodyTo` waits for the hips to arrive, then reach). Now a true 10/10.
|
||
Exit-bar: centred 3-layer sando stands + survives quake (lean 0→2.4°); a leaning
|
||
stack stands marginally (lean 44°) then the nudge topples it — the quake
|
||
discriminates good from bad. Verified in-browser (full M1+M2+M3 battery green).
|
||
- **M4 ✅** samurai + full loop. `sim/samurai.ts` (anger meter 0..1, events
|
||
drop/splat/crimeCut/servedWrong/servedLate/offstage/cleanServe, 5 unsheath
|
||
stages → the generated portraits, vignette), `game/judging.ts` (4 groups
|
||
**THE CUT / SANDO / SERVICE / FLOOR**, banded scores, grade S–D), `game/lines.ts`
|
||
(bilingual samurai barks, 8+/group, deterministic pick). **THE STRING CUT**:
|
||
`Marionette.cutStrings()` deletes all 6 strings in one frame → ragdoll (pins
|
||
survive); stage flings loose food with the slash impulse + white slash FX +
|
||
synth gong. Backdrop = `bg_washi`, samurai portrait HUD + anger bar swap with
|
||
the meter. Assets now wired in — the stage reads as the shadow-puppet theatre.
|
||
Exit-bar: clean run **grade S(10)** standing; bad run **grade D**; disaster run
|
||
**strings cut → ragdoll head falls ~180px, samurai fury**. Judge discriminates
|
||
S↔D. Verified in-browser (scorecard + string-cut screenshots) + full battery.
|
||
Trap fixed: field named `game` collided with `Phaser.Scene.game` → renamed
|
||
`session`; walk faceplant test was per-frame lean>45 (a stride swings past 45
|
||
and recovers) → now "did the head hit the floor / end collapsed".
|
||
- Note: a string cut is game-over; real restart should rebuild the scene. Harness
|
||
`reset()` restrings in place — Matter's constraint solve-order makes the
|
||
restrung puppet marginally different but still walks fine (head stays ~400,
|
||
floor 640).
|
||
- **M5 ✅** days + escalation. `DAYS` in `game/game.ts` — 5 days, each ONE new
|
||
pressure: (1) basics, (2) +1 ingredient (4 layers), (3) wetter (2× tomato),
|
||
(4) a real cut (katsu), (5) patience ramp (maxSeconds 45→22, angerRamp 1.8).
|
||
`Game.setDay(n)` loads order + ramp; sins scale by `samurai.ramp` (sheathing
|
||
doesn't). Exit-bar: `__s.playAllDays()` — every day beatable by a scripted-
|
||
honest run (all S, standing). Ramp bites: day 1 needs 7 crime-cuts to strike,
|
||
day 5 only 4. Verified in-browser.
|
||
- **M6 ✅** juice. Title scene (`scenes/title.ts`) — sumi brush `title_art` +
|
||
「サンドニエット」+ pulsing **SHUBATTO!! SANDONIETTE!!!** + click-to-begin;
|
||
launched as an overlay over the live stage so the harness is up from frame one
|
||
(`__s.startGame()` dismisses it). `game/audio.ts` — 100% synth Web Audio SFX
|
||
(gong / slash whoosh / wet splat / shamisen), lazy AudioContext, no-op headless,
|
||
unblocked on first click. Wired: slash+gong on the string cut, splat on wet
|
||
squash, shamisen on title start. **Deferred (pre-authorized judgment):** food
|
||
faces — the generated faces are full-colour cartoons on paper, they clash with
|
||
the coherent black-silhouette look; would need regen as true cutouts. String
|
||
unlockables (rubber/chain/silk) — pre-authorized defer, M5 lands fine without.
|
||
Attract ragdoll → the pulsing title. Verified: title screenshot, click path,
|
||
production `npm run build` clean.
|
||
|
||
## BUILD-OUT PASS 2 ✅ — the playable loop, string unlockables, 8 days
|
||
- **Physical stacking** — `Sando` is now zone-DETECTION: the stack = whatever food
|
||
bodies rest in the plate zone (±82px, below-flight speed), sorted bottom→top.
|
||
A human stacking by marionette hand and the harness are judged identically.
|
||
`sando.kinds()` (strips piece `p` suffixes) is what the judge reads.
|
||
- **The full live loop** — `Game.state` (playing/served/dead) + `updateLive()`:
|
||
ticket clock with late-anger ramp (1 bump / 3s overdue), the **bow-dwell serve**
|
||
(hold the bar still 0.5s over a matching standing sando → scorecard), click →
|
||
next day (`registry sando.day` → `scene.restart`), string cut → ragdoll →
|
||
fail scorecard → click retries the SAME day with fresh strings. Day setup
|
||
(`Stage.setupDay`) stages bread slices + one whole food per non-bread kind on
|
||
the board. Live sins wired off `cutter.onCut` (squash→splat, botch→crimeCut) +
|
||
offstage sweep. (ponytail: no floor-drop sin yet — add when a day needs it.)
|
||
- **String unlockables** — title-screen picker: HEMP/RUBBER/CHAIN/SILK
|
||
(`STRING_STYLES` k/d multipliers on the same rig; registry `sando.style`).
|
||
Rubber visibly stretches — verified.
|
||
- **Days 6–8**: Tamago (egg rolls), Tofu (fragile), THE TOWER (5 layers). All 8
|
||
days S via harness. Live serve: dwell commits at **31 frames ≈ 0.5s**, grade B
|
||
(no cut performed — honest), day 8 same; death→retry + serve→advance both
|
||
close the loop (verified by simulated clicks + manual queue flush).
|
||
- **samurai_bow.png** (same seed 320) wired: an S-grade serve swaps the portrait
|
||
to the single approving nod. counter_wood/day_stamp/face_cut_bread banked in
|
||
`public/assets/img/`, unwired.
|
||
- **Embedded-pane trap (KNOWN, not a bug)**: the preview pane reports
|
||
`document.visibilityState='hidden'` → rAF never fires → live loop + queued
|
||
scene ops freeze; harness drives frames manually, and
|
||
`game.scene.processQueue()` flushes ops by hand. Real visible tabs unaffected.
|
||
- Full battery green: M1 87f/8.41u/10-10 · M2 cv0-clean/splat/wild · M3 ✓✓ ·
|
||
days SSSSSSSS · live served@31f / d8 served / dead CUT · cleaver CUTS.
|
||
|
||
## POLISH PASS (post-M6) ✅ — "make it even better"
|
||
- **Visible strings + control bar** — the biggest missed feel. `Marionette.stringSegments()`
|
||
/ `barSegment()`; `Stage.drawStrings()` redraws the 6 strings + the wooden bar each
|
||
frame (gone when cut). It now unmistakably reads as a marionette.
|
||
- **Cut-out faces** — procedural: two eyes + a worried "o" punched into each whole-food
|
||
silhouette in the bright washi tone (backlit-hole look). Pieces get no face.
|
||
(The generated face PNGs were full-colour cartoons that clashed — this fits.)
|
||
- **Order ticket HUD** (top-left) + **the cleaver**: a grabbable blade; held in a hand,
|
||
its edge drives the live cutter — swinging chops. Proven: `__s.swingCleaver()` cuts
|
||
clean (tip speed ~150, well over threshold). Full manual play (walk to blade, carry,
|
||
chop, stack, serve across the stage) is the intended-hard game; the wiring is there.
|
||
- All M1–M6 harness numbers unchanged (strings/faces/cleaver don't perturb the sim):
|
||
dangle 87f · walk 8.83u up · grip 10/10 · chop cv0 clean · days SSSSS · disaster→cut.
|
||
|
||
## ALL SIX MILESTONES SHIP ✅ — full loop plays, three must-feels landed
|
||
(the dangle M1, the clean chop M2, the string cut M4). Final battery green:
|
||
M1 dangle 97f / walk 8.83u up / grip 10/10 · M2 chop cv0 clean / splat / offstage ·
|
||
M3 good survives + bad toppled · M4 disaster→string-cut ragdoll · M5 days 1-5 all
|
||
beatable. `npm run typecheck` + `npm run check` + `npm run build` all pass.
|
||
Possible next (Brief 2 territory): food faces as true silhouette cutouts, string
|
||
unlockables, more days/orders, real attract-mode, mobile/touch controls.
|
||
|
||
|
||
|
||
*"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 S–D; 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.
|