76 lines
2.9 KiB
TypeScript
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);
|
|
}
|
|
});
|
|
});
|