THE RUSH, made honest: only the pair reaches the book
Adversarial pass over the freshly-threaded recordVerdict pipeline found three real ordering bugs on rush days, all of the same species: ticket one's RAW verdict was reaching systems that should only ever see the pair. The streak moved twice per rush (and an S on ticket one extended it even when the pair collapsed), the regulars' book logged two visits, and — the exploit — the Shadow star was awarded off ticket one's raw grade: ace the first ticket, flub the second, keep the S★ on a day the ledger records as B. The rush stash/combine now sits directly after the per-dish adjustments (Critic and Mother judge each DISH — that part was right) and before streak, book, star and daily, so ticket one returns early having touched nothing, and the pair flows through everything else exactly once. Also fixed the falsy-zero trap the stash simplification introduced: a 0.0 ticket one made `this.rushFirst ?` read as "no rush", re-dealing the SAME order for ticket two — all three checks are `!== null` now. Verified live on day 25: after ticket one, streak/visits/ledger byte- identical to before it; after the pair, one visit, 41s decay 7.6→7.3, one write. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
3ecb6b59a3
commit
9ca713b5c9
@ -98,8 +98,8 @@ export class Game {
|
|||||||
private demoMode = false;
|
private demoMode = false;
|
||||||
/** The Inspector's Shadow: the kitchen's words go dark; you cook by eye and ear. */
|
/** The Inspector's Shadow: the kitchen's words go dark; you cook by eye and ear. */
|
||||||
private shadowMode = false;
|
private shadowMode = false;
|
||||||
/** Rush hour: ticket one's verdict, parked on the pass while you cook two. */
|
/** Rush hour: ticket one's total, parked on the pass while you cook two. */
|
||||||
private rushFirst: { total: number; seconds: number } | null = null;
|
private rushFirst: number | null = null;
|
||||||
private demoSnapshot: string | null = null;
|
private demoSnapshot: string | null = null;
|
||||||
private rng = new Rng(20260716);
|
private rng = new Rng(20260716);
|
||||||
private order!: Order;
|
private order!: Order;
|
||||||
@ -206,7 +206,7 @@ export class Game {
|
|||||||
this.beginDay();
|
this.beginDay();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.rushFirst) {
|
if (this.rushFirst !== null) {
|
||||||
// Ticket two of the rush: same day, different deal, the clock restarts
|
// 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.
|
// but the FIRST ticket's decay is measured by ticket two's service.
|
||||||
this.beginDay();
|
this.beginDay();
|
||||||
@ -650,7 +650,7 @@ export class Game {
|
|||||||
const dayRng = new Rng((day * 2654435761) >>> 0);
|
const dayRng = new Rng((day * 2654435761) >>> 0);
|
||||||
// Ticket two of a rush re-deals the same day from a twisted seed — a
|
// 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.
|
// different order, deterministically, so reloads still can't scum it.
|
||||||
const rng2 = this.rushFirst ? new Rng(((day * 2654435761) ^ 0x9e3779b9) >>> 0) : dayRng;
|
const rng2 = this.rushFirst !== null ? new Rng(((day * 2654435761) ^ 0x9e3779b9) >>> 0) : dayRng;
|
||||||
const o =
|
const o =
|
||||||
day <= DAYS.length
|
day <= DAYS.length
|
||||||
? DAYS[day - 1]
|
? DAYS[day - 1]
|
||||||
@ -1014,6 +1014,28 @@ export class Game {
|
|||||||
extras.push('The starter is lively — the crumb held. (+0.2. She did that, not you.)');
|
extras.push('The starter is lively — the crumb held. (+0.2. She did that, not you.)');
|
||||||
} else if (gap >= 3 && toastDay) extras.push('The jar says nothing today. It is sulking, and the crumb knows.');
|
} else if (gap >= 3 && toastDay) extras.push('The jar says nothing today. It is sulking, and the crumb knows.');
|
||||||
}
|
}
|
||||||
|
// RUSH HOUR (endless only): the day dealt TWO tickets. The first verdict
|
||||||
|
// goes on the pass — no streak, no book, no star, nothing written — and
|
||||||
|
// the second closes the pair. The first decays for every second it sat
|
||||||
|
// while you cooked. Only the PAIR reaches the streak, the regulars'
|
||||||
|
// book, the Shadow star and the ledger: one service, one line.
|
||||||
|
if (this.rushDay(this.order.day) && !this.dailyDate) {
|
||||||
|
if (this.rushFirst === null) {
|
||||||
|
this.rushFirst = v.total;
|
||||||
|
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 - decay) * 10) / 10);
|
||||||
|
extras.push(`Ticket one sat ${Math.round(passSec)}s on the pass: ${this.rushFirst.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;
|
||||||
|
}
|
||||||
// The streak. He notices. He hates that he notices.
|
// The streak. He notices. He hates that he notices.
|
||||||
const prev = this.save.streak ?? 0;
|
const prev = this.save.streak ?? 0;
|
||||||
if (v.grade === 'S') {
|
if (v.grade === 'S') {
|
||||||
@ -1046,26 +1068,6 @@ export class Game {
|
|||||||
d[this.dailyDate] = Math.max(d[this.dailyDate] ?? 0, v.total);
|
d[this.dailyDate] = Math.max(d[this.dailyDate] ?? 0, v.total);
|
||||||
extras.push('The Daily. Same deal for everyone today — come back tomorrow.');
|
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.extraLines = extras;
|
||||||
this.judgeView.shareText = this.shareLine(v);
|
this.judgeView.shareText = this.shareLine(v);
|
||||||
this.save.total += v.total;
|
this.save.total += v.total;
|
||||||
@ -1086,7 +1088,7 @@ export class Game {
|
|||||||
el('div', 'ticket-day', this.ticketEl, `DAY ${o.day}`);
|
el('div', 'ticket-day', this.ticketEl, `DAY ${o.day}`);
|
||||||
el('div', 'ticket-who', this.ticketEl, o.who);
|
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 (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.');
|
if (this.rushDay(o.day) && !this.dailyDate) el('div', 'ticket-critic', this.ticketEl, this.rushFirst !== null ? '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<number, unknown> } }).__t?.demos;
|
const demos = (window as unknown as { __t?: { demos?: Record<number, unknown> } }).__t?.demos;
|
||||||
if (!this.demoMode && demos && demos[o.day]) {
|
if (!this.demoMode && demos && demos[o.day]) {
|
||||||
const watch = el('button', 'btn ghost watch-btn', this.ticketEl, '▶ watch him do it');
|
const watch = el('button', 'btn ghost watch-btn', this.ticketEl, '▶ watch him do it');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user