not-tonight/tests/dressCodeShuffle.test.ts

76 lines
2.9 KiB
TypeScript

import { afterEach, describe, expect, it } from 'vitest';
import {
ACTIVE_RULE_CAP,
DRESS_CODE_RULES,
SHUFFLE_EVERY_MIN,
SHUFFLE_RULE_COUNT,
activeRules,
announcedRules,
setDressCodeShuffle,
shuffleWindow,
violations,
} from '../src/rules/dressCode';
import { generatePatron, _resetPatronSerial } from '../src/patrons/generator';
import { SeededRNG } from '../src/core/SeededRNG';
// Module-level mode: ALWAYS clear after each test or the whole suite goes to ROOM.
afterEach(() => setDressCodeShuffle(null));
const ids = (clockMin: number): string[] => activeRules(clockMin).map((r) => r.id);
describe('ROOM dress-code shuffle', () => {
it('windows turn every SHUFFLE_EVERY_MIN', () => {
expect(shuffleWindow(0)).toBe(0);
expect(shuffleWindow(SHUFFLE_EVERY_MIN - 1)).toBe(0);
expect(shuffleWindow(SHUFFLE_EVERY_MIN)).toBe(1);
expect(shuffleWindow(SHUFFLE_EVERY_MIN * 4 + 3)).toBe(4);
});
it('serves SHUFFLE_RULE_COUNT real rules, stable within a window', () => {
setDressCodeShuffle(4207);
const a = ids(0);
expect(a).toHaveLength(SHUFFLE_RULE_COUNT);
expect(SHUFFLE_RULE_COUNT).toBeLessThanOrEqual(ACTIVE_RULE_CAP);
for (const id of a) expect(DRESS_CODE_RULES.some((r) => r.id === id)).toBe(true);
// re-reading the board mid-window must not reroll it
expect(ids(SHUFFLE_EVERY_MIN - 1)).toEqual(a);
});
it('rewrites across windows and is deterministic per seed', () => {
setDressCodeShuffle(4207);
const windows = Array.from({ length: 8 }, (_, w) => ids(w * SHUFFLE_EVERY_MIN));
// Some consecutive pair must differ — a board that never changes isn't ROOM.
expect(windows.some((w, i) => i > 0 && w.join() !== windows[i - 1]!.join())).toBe(true);
// Same seed replays the same night.
setDressCodeShuffle(4207);
expect(ids(SHUFFLE_EVERY_MIN * 3)).toEqual(windows[3]);
// A different seed is a different board.
setDressCodeShuffle(999);
const other = Array.from({ length: 8 }, (_, w) => ids(w * SHUFFLE_EVERY_MIN));
expect(other.join('|')).not.toBe(windows.join('|'));
});
it('announcedRules mirrors the live set — nothing greyed, nothing historical', () => {
setDressCodeShuffle(4207);
expect(announcedRules(200).map((r) => r.id)).toEqual(ids(200));
});
it('clearing the mode restores the Dazza chronology', () => {
setDressCodeShuffle(4207);
setDressCodeShuffle(null);
expect(activeRules(0)).toEqual(DRESS_CODE_RULES.filter((r) => r.activeFrom <= 0).slice(-ACTIVE_RULE_CAP));
});
it('violations() judges against the shuffled set', () => {
_resetPatronSerial();
const rng = new SeededRNG(7);
const ctx = { rng, nightDate: new Date('2026-07-16T00:00:00') };
setDressCodeShuffle(4207);
for (let i = 0; i < 40; i++) {
const p = generatePatron(ctx, 30);
const live = new Set(ids(30));
for (const v of violations(p, 30)) expect(live.has(v.id)).toBe(true);
}
});
});