not-tonight/tests/dollplan.test.ts
2026-07-19 16:20:53 +10:00

73 lines
3.0 KiB
TypeScript

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'));
});
});