Between the swept floor and the paperwork. Cash counts, the phone slides into a lost-property shoebox, curios JAM the machine, and the bag stops it dead with a different line depending on whether you pocketed it hours ago. Ledger builds down the left as a receipt for the night. Sequence runs off update(), not tween callbacks — a dropped callback would have stranded the player short of the paperwork. Desk also stops itself on handoff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
77 lines
3.1 KiB
TypeScript
77 lines
3.1 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { REFUSE_QUIP, REFUSE_BINNED_QUIP, tallyHaul, sorterVerdict } from '../src/rules/haul';
|
|
import { rollDrops } from '../src/rules/floorScore';
|
|
import { SeededRNG } from '../src/core/SeededRNG';
|
|
import type { Drop } from '../src/rules/floorScore';
|
|
|
|
const d = (kind: Drop['kind'], value = 0, label = kind): Drop => ({ kind, value, label });
|
|
|
|
describe('the coin sorter', () => {
|
|
it('counts money and nothing else', () => {
|
|
const t = tallyHaul([d('note', 20), d('coins', 7), d('curio'), d('phone'), d('baggie')], true);
|
|
expect(t.counted).toBe(27);
|
|
});
|
|
|
|
it('jams on the objects, and that is the whole joke', () => {
|
|
const t = tallyHaul([d('curio'), d('curio'), d('note', 5)], false);
|
|
expect(t.jams).toBe(2);
|
|
expect(t.lines.filter((l) => l.fate === 'jammed')).toHaveLength(2);
|
|
expect(t.lines.find((l) => l.fate === 'counted')?.value).toBe(5);
|
|
});
|
|
|
|
it('the phone goes sideways into lost property, never into the total', () => {
|
|
const t = tallyHaul([d('phone')], false);
|
|
expect(t.lines[0]!.fate).toBe('rejected');
|
|
expect(t.counted).toBe(0);
|
|
});
|
|
|
|
it('the bag gets a different line depending on what you did with it', () => {
|
|
expect(tallyHaul([d('baggie')], true).lines[0]!.quip).toBe(REFUSE_QUIP);
|
|
expect(tallyHaul([d('baggie')], false).lines[0]!.quip).toBe(REFUSE_BINNED_QUIP);
|
|
});
|
|
|
|
it('keeps the order you picked things up in — the desk replays your night', () => {
|
|
const haul: Drop[] = [
|
|
{ kind: 'note', value: 10, label: 'a' },
|
|
{ kind: 'curio', value: 0, label: 'b' },
|
|
{ kind: 'coins', value: 3, label: 'c' },
|
|
];
|
|
expect(tallyHaul(haul, false).lines.map((l) => l.label)).toEqual(['a', 'b', 'c']);
|
|
});
|
|
|
|
it('is deterministic — the same night tells the same joke', () => {
|
|
const haul = rollDrops(new SeededRNG(4207).stream('sweep'), 40);
|
|
expect(tallyHaul(haul, true)).toEqual(tallyHaul(haul, true));
|
|
});
|
|
|
|
it('every line has words, whatever the machine made of it', () => {
|
|
const haul = rollDrops(new SeededRNG(9).stream('sweep'), 60);
|
|
for (const line of tallyHaul(haul, true).lines) {
|
|
expect(line.quip.length).toBeGreaterThan(5);
|
|
expect(line.label.length).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
|
|
it('an empty haul is handled, and says so', () => {
|
|
const t = tallyHaul([], false);
|
|
expect(t).toMatchObject({ counted: 0, jams: 0 });
|
|
expect(t.lines).toEqual([]);
|
|
expect(sorterVerdict(t)).toContain('nothing in the pockets');
|
|
});
|
|
|
|
it('the sign-off escalates with how badly you treated the machine', () => {
|
|
const clean = sorterVerdict(tallyHaul([d('note', 5)], false));
|
|
const rough = sorterVerdict(tallyHaul(Array(5).fill(d('curio')), false));
|
|
expect(clean).not.toBe(rough);
|
|
expect(rough).toContain('looked at');
|
|
});
|
|
|
|
it('a real swept night always gives the desk something to do', () => {
|
|
for (const seed of [4207, 4208, 99]) {
|
|
const t = tallyHaul(rollDrops(new SeededRNG(seed).stream('sweep'), 40), true);
|
|
expect(t.lines.length).toBeGreaterThan(0);
|
|
expect(t.counted).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
});
|