import { describe, expect, it } from 'vitest'; import { SeededRNG } from '../src/core/SeededRNG'; import { dollPlan, DOLL_SIZES } from '../src/patrons/dollPlan'; import { generatePatron, _resetPatronSerial } from '../src/patrons/generator'; import type { Patron } from '../src/data/types'; const NIGHT = new Date('2026-07-18T00:00:00'); const somePatrons = (seed: number, n: number): Patron[] => { _resetPatronSerial(); const ctx = { rng: new SeededRNG(seed), nightDate: NIGHT }; return Array.from({ length: n }, () => generatePatron(ctx, 0)); }; describe('dollPlan', () => { it('is deterministic per (patron, pose)', () => { for (const p of somePatrons(5, 20)) { expect(dollPlan(p, 'queue')).toEqual(dollPlan(p, 'queue')); expect(dollPlan(p, 'idPhoto')).toEqual(dollPlan(p, 'idPhoto')); expect(dollPlan(p, 'floorTopDown')).toEqual(dollPlan(p, 'floorTopDown')); } }); it('respects the pose size contract and stays in bounds', () => { for (const p of somePatrons(9, 30)) { for (const pose of ['queue', 'idPhoto', 'floorTopDown'] as const) { const plan = dollPlan(p, pose); expect(plan.width).toBe(DOLL_SIZES[pose].width); expect(plan.height).toBe(DOLL_SIZES[pose].height); for (const r of plan.rects) { expect(r.x).toBeGreaterThanOrEqual(-2); // jacket edges may hug the border expect(r.x + r.w).toBeLessThanOrEqual(plan.width + 2); expect(r.y).toBeGreaterThanOrEqual(0); expect(r.y + r.h).toBeLessThanOrEqual(plan.height); } } } }); it('drunk stages produce sway + bloodshot tells; sober does not', () => { const [p] = somePatrons(3, 1); const sober = dollPlan({ ...p!, intoxication: 0.1 }, 'queue'); const maggot = dollPlan({ ...p!, intoxication: 0.95 }, 'queue'); expect(sober.swayPx).toBe(0); expect(maggot.swayPx).toBe(3); expect(maggot.rects.length).toBeGreaterThan(sober.rects.length); // eye pixels }); it('photoMismatch renders the ID photo from the WRONG seed (visible tell)', () => { const [p] = somePatrons(21, 1); const honest: Patron = { ...p!, idCard: { ...p!.idCard, photoSeed: p!.dollSeed, fake: undefined } }; const mismatched: Patron = { ...p!, idCard: { ...p!.idCard, photoSeed: p!.dollSeed + 7777, fake: { photoMismatch: true } }, }; expect(dollPlan(mismatched, 'idPhoto')).not.toEqual(dollPlan(honest, 'idPhoto')); // and the patron themselves renders identically — only the card lies expect(dollPlan(mismatched, 'queue')).toEqual(dollPlan(honest, 'queue')); }); it('outfit data drives visible pixels: white vs black sneakers differ', () => { const [p] = somePatrons(33, 1); const white: Patron = { ...p!, outfit: p!.outfit.map((l) => (l.slot === 'shoes' ? { ...l, type: 'sneaker', colour: 'white' } : l)), }; const black: Patron = { ...p!, outfit: p!.outfit.map((l) => (l.slot === 'shoes' ? { ...l, type: 'sneaker', colour: 'black' } : l)), }; expect(dollPlan(white, 'queue')).not.toEqual(dollPlan(black, 'queue')); }); });