M5: days + escalation — one new pressure per day
- DAYS table (game/game.ts): 5 days, each introduces exactly one pressure — basics / +1 ingredient / wetter (2x tomato) / a real cut (katsu) / patience ramp (maxSeconds 45->22, angerRamp 1.8). Game.setDay loads order + ramp. - samurai.ramp scales every sin (sheathing unaffected) — day 5 the samurai runs out of patience faster. - Exit-bar: __s.playAllDays() — every day beatable by a scripted-honest run (all S, standing). Ramp bites: day 1 = 7 crime-cuts to strike, day 5 = 4. Verified in-browser. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
58285712ef
commit
cdde03bef4
@ -58,10 +58,18 @@
|
||||
`reset()` restrings in place — Matter's constraint solve-order makes the
|
||||
restrung puppet marginally different but still walks fine (head stays ~400,
|
||||
floor 640).
|
||||
- **Next: M5** orders/days escalation (`game/orders.ts`-ish) — one new pressure
|
||||
per day. Then **M6** juice (title, audio, string unlockables).
|
||||
- Run: `npm run dev` (:5173). Harness: `__s.playCleanRun() __s.playDisasterRun()
|
||||
__s.serve() __s.anger(ev)` + all M1–M3 verbs. Headless: `npm run check`.
|
||||
- **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.
|
||||
- **Next: M6** juice — title screen (「サンドニエット」brush art + SHUBATTO crawl +
|
||||
attract ragdoll), Web Audio (shamisen sting / slash whoosh / wet splat), food
|
||||
faces on pieces, string unlockables (rubber/chain/silk — pre-authorized defer).
|
||||
- Run: `npm run dev` (:5173). Harness: `__s.playDay(n) __s.playAllDays()
|
||||
__s.playCleanRun() __s.playDisasterRun()` + all M1–M3 verbs. Headless: `npm run check`.
|
||||
|
||||
|
||||
|
||||
|
||||
29
src/dev.ts
29
src/dev.ts
@ -1,6 +1,7 @@
|
||||
import type { Stage } from './scenes/stage';
|
||||
import { FLOOR_Y } from './scenes/stage';
|
||||
import { spawnFood } from './sim/food';
|
||||
import { spawnFood, FOODS } from './sim/food';
|
||||
import { DAYS } from './game/game';
|
||||
|
||||
/**
|
||||
* Dev harness — toastsim's single most valuable organ, transplanted.
|
||||
@ -306,6 +307,32 @@ export function installHarness(stage: Stage): void {
|
||||
return { ...v, standing: stage.sando.stats().standing, order: stage.session.order.name };
|
||||
},
|
||||
|
||||
// ---- M5: days + escalation ---------------------------------------------
|
||||
/** Play day `n` with a scripted-but-honest run: cut the filling, stack the
|
||||
* ordered layers, serve in time. Each day must stay beatable (B+). */
|
||||
playDay(n = 0) {
|
||||
this.freshRun();
|
||||
const d = stage.session.setDay(n);
|
||||
const order = stage.session.order;
|
||||
const fill = order.layers.find((k) => k !== 'bread' && k in FOODS) ?? 'tomato';
|
||||
this.chop(fill); // a clean cut feeds THE CUT
|
||||
this.buildSando(order.layers.map((k) => [k, 0] as [string, number]));
|
||||
const v = this.serve(true, Math.max(6, order.maxSeconds - 6));
|
||||
return {
|
||||
day: n + 1,
|
||||
name: order.name,
|
||||
pressure: d.pressure,
|
||||
grade: v.grade,
|
||||
total: v.total,
|
||||
standing: stage.sando.stats().standing,
|
||||
};
|
||||
},
|
||||
|
||||
/** M5 exit-bar: every day introduces its pressure and is beatable (B+). */
|
||||
playAllDays() {
|
||||
return DAYS.map((_, i) => this.playDay(i));
|
||||
},
|
||||
|
||||
/** M4 exit-bar B: anger the samurai to the top → the string cut → ragdoll. */
|
||||
playDisasterRun() {
|
||||
this.freshRun();
|
||||
|
||||
@ -2,11 +2,25 @@ import type { Stage } from '../scenes/stage';
|
||||
import { Samurai } from '../sim/samurai';
|
||||
import { judge, type Order, type RunResult, type Verdict } from './judging';
|
||||
|
||||
/** Day 1: one open tomato sando. Escalation is M5. */
|
||||
export const ORDERS: Order[] = [
|
||||
{ name: 'Open Tomato Sando', layers: ['bread', 'tomato', 'bread'], needsCut: true, maxSeconds: 40 },
|
||||
/** A day introduces exactly ONE new pressure (never two) and stays beatable by
|
||||
* a scripted-but-honest run. angerRamp scales every sin — the samurai's
|
||||
* patience shortens on the rush-hour day. */
|
||||
export interface Day {
|
||||
order: Order;
|
||||
pressure: string;
|
||||
angerRamp: number;
|
||||
}
|
||||
|
||||
export const DAYS: Day[] = [
|
||||
{ order: { name: 'Open Tomato Sando', layers: ['bread', 'tomato', 'bread'], needsCut: true, maxSeconds: 45 }, pressure: 'the basics', angerRamp: 1 },
|
||||
{ order: { name: 'Cheese & Tomato', layers: ['bread', 'cheese', 'tomato', 'bread'], needsCut: true, maxSeconds: 45 }, pressure: '+1 ingredient', angerRamp: 1 },
|
||||
{ order: { name: 'Double Tomato', layers: ['bread', 'tomato', 'tomato', 'bread'], needsCut: true, maxSeconds: 45 }, pressure: 'wetter — twice the tomato', angerRamp: 1 },
|
||||
{ order: { name: 'Katsu Sando', layers: ['bread', 'katsu', 'tomato', 'bread'], needsCut: true, maxSeconds: 45 }, pressure: 'a real cut — the katsu', angerRamp: 1 },
|
||||
{ order: { name: 'Rush Hour', layers: ['bread', 'tomato', 'bread'], needsCut: true, maxSeconds: 22 }, pressure: 'the patience ramp', angerRamp: 1.8 },
|
||||
];
|
||||
|
||||
export const ORDERS: Order[] = DAYS.map((d) => d.order);
|
||||
|
||||
/**
|
||||
* The run controller. Holds the samurai + the current ticket, tallies the floor,
|
||||
* and turns a finished attempt into a Verdict. Kitchen sins feed the anger
|
||||
@ -14,7 +28,8 @@ export const ORDERS: Order[] = [
|
||||
*/
|
||||
export class Game {
|
||||
samurai = new Samurai();
|
||||
order: Order = ORDERS[0];
|
||||
day = 0;
|
||||
order: Order = DAYS[0].order;
|
||||
drops = 0;
|
||||
seconds = 0; // ticket timer (seconds)
|
||||
|
||||
@ -22,7 +37,18 @@ export class Game {
|
||||
this.samurai.onStrike = () => this.stage.stringCut();
|
||||
}
|
||||
|
||||
reset(order: Order = ORDERS[0]): void {
|
||||
/** Load day `n` (clamped): its order + its samurai patience ramp. */
|
||||
setDay(n: number): Day {
|
||||
this.day = Math.max(0, Math.min(DAYS.length - 1, n));
|
||||
const d = DAYS[this.day];
|
||||
this.order = d.order;
|
||||
this.samurai.ramp = d.angerRamp;
|
||||
this.drops = 0;
|
||||
this.seconds = 0;
|
||||
return d;
|
||||
}
|
||||
|
||||
reset(order: Order = this.order): void {
|
||||
this.samurai.reset();
|
||||
this.order = order;
|
||||
this.drops = 0;
|
||||
|
||||
@ -24,6 +24,7 @@ export const PORTRAITS = ['samurai_calm', 'samurai_watch', 'samurai_annoyed', 's
|
||||
|
||||
export class Samurai {
|
||||
anger = 0; // 0..1
|
||||
ramp = 1; // day patience multiplier — sins hurt more on the rush-hour day
|
||||
struck = false;
|
||||
/** Fired once when the meter tops out — the stage wires the string cut here. */
|
||||
onStrike?: () => void;
|
||||
@ -32,10 +33,12 @@ export class Samurai {
|
||||
/** The last event, for barks. */
|
||||
last: AngerEvent | null = null;
|
||||
|
||||
/** Apply an anger event (optionally overriding the amount, e.g. ramped late). */
|
||||
/** Apply an anger event (optionally overriding the amount, e.g. ramped late).
|
||||
* Sins scale by the day ramp; sheathing (a clean serve) does not. */
|
||||
bump(ev: AngerEvent, amount = ANGER[ev]): void {
|
||||
this.last = ev;
|
||||
this.anger = Math.max(0, Math.min(1, this.anger + amount));
|
||||
const scaled = amount > 0 ? amount * this.ramp : amount;
|
||||
this.anger = Math.max(0, Math.min(1, this.anger + scaled));
|
||||
this.onChange?.(this.anger, this.stage);
|
||||
if (this.anger >= 1 && !this.struck) this.strike();
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user