diff --git a/src/game/game.ts b/src/game/game.ts index 49b0707..32d87f3 100644 --- a/src/game/game.ts +++ b/src/game/game.ts @@ -98,6 +98,8 @@ export class Game { private demoMode = false; /** The Inspector's Shadow: the kitchen's words go dark; you cook by eye and ear. */ private shadowMode = false; + /** Rush hour: ticket one's verdict, parked on the pass while you cook two. */ + private rushFirst: { total: number; seconds: number } | null = null; private demoSnapshot: string | null = null; private rng = new Rng(20260716); private order!: Order; @@ -204,6 +206,12 @@ export class Game { this.beginDay(); return; } + if (this.rushFirst) { + // Ticket two of the rush: same day, different deal, the clock restarts + // but the FIRST ticket's decay is measured by ticket two's service. + this.beginDay(); + return; + } this.save.day++; this.save.maxDay = Math.max(this.save.maxDay, this.save.day); writeSave(this.save); @@ -452,10 +460,6 @@ export class Game { const v = judgeBruschetta(slice, this.order, this.kitchen.toolId, seconds, result); this.lastBruschetta = { result, verdict: v }; this.recordVerdict(v); - this.save.total += v.total; - const key = `day${this.order.day}`; - this.save.best[key] = Math.max(this.save.best[key] ?? 0, v.total); - writeSave(this.save); this.hideTempToggle(); this.hideAllScenes(); @@ -514,6 +518,7 @@ export class Game { /** Test hook: jump straight to a day, through the real order table. */ setDay(day: number): void { + this.rushFirst = null; this.save.day = day; this.save.maxDay = Math.max(this.save.maxDay ?? day, day); writeSave(this.save); @@ -586,6 +591,7 @@ export class Game { } startDaily(): void { + this.rushFirst = null; const date = new Date().toISOString().slice(0, 10); this.dailyDate = date; let h = 2166136261; @@ -642,10 +648,13 @@ export class Game { // re-deals the identical order (no reroll-scumming), and the harness can // reproduce any day by number. const dayRng = new Rng((day * 2654435761) >>> 0); + // Ticket two of a rush re-deals the same day from a twisted seed — a + // different order, deterministically, so reloads still can't scum it. + const rng2 = this.rushFirst ? new Rng(((day * 2654435761) ^ 0x9e3779b9) >>> 0) : dayRng; const o = day <= DAYS.length ? DAYS[day - 1] - : proceduralOrder(day, () => dayRng.next()); + : proceduralOrder(day, () => rng2.next()); if (!o.text) o.text = `${o.browningName}, ${o.amount} ${o.spread === 'mitey' ? 'MITEY' : o.spread}. ${o.noChar ? 'No burnt bits.' : ''}`; this.order = o; this.orderSeconds = 0; @@ -734,10 +743,6 @@ export class Game { const slice = this.kitchen.currentSlice; const v = judgeBenedict(slice, this.order, poach, seconds); this.recordVerdict(v); - this.save.total += v.total; - const key = `day${this.order.day}`; - this.save.best[key] = Math.max(this.save.best[key] ?? 0, v.total); - writeSave(this.save); this.hideAllScenes(); this.judgeView.root.visible = true; this.judgeView.show(slice, v, this.order, this.kitchen.toolId, () => this.rng.next()); @@ -765,10 +770,6 @@ export class Game { const slice = this.kitchen.currentSlice; const v = judgePoach(result, this.order, seconds); this.recordVerdict(v); - this.save.total += v.total; - const key = `day${this.order.day}`; - this.save.best[key] = Math.max(this.save.best[key] ?? 0, v.total); - writeSave(this.save); this.hideAllScenes(); this.judgeView.root.visible = true; this.judgeView.show(slice, v, this.order, this.kitchen.toolId, () => this.rng.next()); @@ -797,10 +798,6 @@ export class Game { const slice = this.kitchen.currentSlice; const v = judgeEggs(result, this.order, seconds); this.recordVerdict(v); - this.save.total += v.total; - const key = `day${this.order.day}`; - this.save.best[key] = Math.max(this.save.best[key] ?? 0, v.total); - writeSave(this.save); this.hideAllScenes(); this.judgeView.root.visible = true; this.judgeView.show(slice, v, this.order, this.kitchen.toolId, () => this.rng.next()); @@ -840,10 +837,6 @@ export class Game { const slice = this.kitchen.currentSlice; const v = judgeSteak(result, this.order, seconds); this.recordVerdict(v); - this.save.total += v.total; - const key = `day${this.order.day}`; - this.save.best[key] = Math.max(this.save.best[key] ?? 0, v.total); - writeSave(this.save); this.hideAllScenes(); this.judgeView.root.visible = true; this.judgeView.show(slice, v, this.order, this.kitchen.toolId, () => this.rng.next()); @@ -874,10 +867,6 @@ export class Game { this.save.fridge = serializeStore(result.store); const v = judgeFridge(result, this.order, seconds); this.recordVerdict(v); - this.save.total += v.total; - const key = `day${this.order.day}`; - this.save.best[key] = Math.max(this.save.best[key] ?? 0, v.total); - writeSave(this.save); this.hideAllScenes(); this.judgeView.root.visible = true; this.judgeView.show(slice, v, this.order, this.kitchen.toolId, () => this.rng.next()); @@ -926,10 +915,6 @@ export class Game { const slice = this.kitchen.currentSlice; const v = judgePan(result, this.order, seconds, this.panIngredient ?? undefined); this.recordVerdict(v); - this.save.total += v.total; - const key = `day${this.order.day}`; - this.save.best[key] = Math.max(this.save.best[key] ?? 0, v.total); - writeSave(this.save); this.hideAllScenes(); this.judgeView.root.visible = true; this.judgeView.show(slice, v, this.order, this.kitchen.toolId, () => this.rng.next()); @@ -957,10 +942,6 @@ export class Game { const slice = this.kitchen.currentSlice; const v = judgeGrill(result, this.order, seconds); this.recordVerdict(v); - this.save.total += v.total; - const key = `day${this.order.day}`; - this.save.best[key] = Math.max(this.save.best[key] ?? 0, v.total); - writeSave(this.save); this.hideAllScenes(); this.judgeView.root.visible = true; @@ -1065,8 +1046,38 @@ export class Game { d[this.dailyDate] = Math.max(d[this.dailyDate] ?? 0, v.total); extras.push('The Daily. Same deal for everyone today — come back tomorrow.'); } + // RUSH HOUR (endless only): the day dealt TWO tickets. The first verdict + // goes on the pass; the second closes the pair — and the first decays for + // every second it sat there while you cooked. One combined line in the book. + if (this.rushDay(this.order.day) && !this.dailyDate) { + if (!this.rushFirst) { + this.rushFirst = { total: v.total, seconds: this.orderSeconds }; + extras.push('Ticket one, on the pass. The second is already up — NEXT. Cook.'); + this.judgeView.extraLines = extras; + this.judgeView.shareText = ''; + return; // nothing is written until the pair closes + } + const passSec = this.orderSeconds; + const decay = Math.min(1.5, (passSec / 60) * 0.5); + const aAdj = Math.max(0, Math.round((this.rushFirst.total - decay) * 10) / 10); + extras.push(`Ticket one sat ${Math.round(passSec)}s on the pass: ${this.rushFirst.total.toFixed(1)} fell to ${aAdj.toFixed(1)}.`); + v.total = Math.round(((aAdj + v.total) / 2) * 10) / 10; + v.grade = gradeOf(v.total); + extras.push('Two tickets, one verdict — the rush is scored as a pair.'); + this.rushFirst = null; + } this.judgeView.extraLines = extras; this.judgeView.shareText = this.shareLine(v); + this.save.total += v.total; + const key = `day${this.order.day}`; + this.save.best[key] = Math.max(this.save.best[key] ?? 0, v.total); + writeSave(this.save); + } + + /** Endless-only, deterministic by day number: ~1 in 4 days past the card + * are a rush, never on a Critic day (one cruelty at a time). */ + private rushDay(day: number): boolean { + return day > DAYS.length && day % 10 !== 0 && ((day * 2654435761) >>> 0) % 4 === 1; } private renderTicket(): void { @@ -1075,6 +1086,7 @@ export class Game { el('div', 'ticket-day', this.ticketEl, `DAY ${o.day}`); el('div', 'ticket-who', this.ticketEl, o.who); if (o.day % 10 === 0 && !this.dailyDate) el('div', 'ticket-critic', this.ticketEl, 'THE CRITIC is in today. The flaw will be the headline.'); + if (this.rushDay(o.day) && !this.dailyDate) el('div', 'ticket-critic', this.ticketEl, this.rushFirst ? 'TICKET TWO — the first is dying on the pass.' : 'RUSH HOUR — two tickets on the rail. The first will wait for the second.'); const demos = (window as unknown as { __t?: { demos?: Record } }).__t?.demos; if (!this.demoMode && demos && demos[o.day]) { const watch = el('button', 'btn ghost watch-btn', this.ticketEl, '▶ watch him do it'); @@ -1150,10 +1162,6 @@ export class Game { const extra = this.juiceResult ? juiceCriteria(this.juiceResult) : []; const v = judge(slice, this.order, this.kitchen.toolId, seconds, this.prepResult ?? undefined, extra); this.recordVerdict(v); - this.save.total += v.total; - const key = `day${this.order.day}`; - this.save.best[key] = Math.max(this.save.best[key] ?? 0, v.total); - writeSave(this.save); this.kitchen.root.visible = false; this.judgeView.root.visible = true;