import { describe, expect, it } from 'vitest'; import { BUMP_MOP_CHANCE, KAYDEN_HOLD_COOLDOWN_MS, KAYDEN_HOLD_MS, ORDER_LAG_MS, RADIO_COOLDOWN_MS, SHAN_CLEAN_CHANCE, SHAN_MIN_INTOX, orderLag, rollBumpMops, rollShanClean, } from '../src/rules/staff'; import { drunkStage } from '../src/rules/drunk'; import { SeededRNG } from '../src/core/SeededRNG'; import { VENUES } from '../src/data/venues'; describe('radio command', () => { it('is a Head of Security-tier verb: Elevate and ROOM only', () => { expect(VENUES.map((v) => v.radioCommand === true)).toEqual([false, false, true, true]); }); it('staff competence is real but imperfect — that IS the staff', () => { const rng = new SeededRNG(3).stream('s'); let bump = 0; let shan = 0; for (let i = 0; i < 2000; i++) { if (rollBumpMops(rng)) bump++; if (rollShanClean(rng)) shan++; } expect(bump / 2000).toBeGreaterThan(BUMP_MOP_CHANCE - 0.05); expect(bump / 2000).toBeLessThan(BUMP_MOP_CHANCE + 0.05); expect(shan / 2000).toBeGreaterThan(SHAN_CLEAN_CHANCE - 0.05); expect(shan / 2000).toBeLessThan(SHAN_CLEAN_CHANCE + 0.05); }); it('orders take a real walk and cool down longer than they take', () => { const rng = new SeededRNG(4).stream('s'); for (let i = 0; i < 100; i++) { const lag = orderLag(rng); expect(lag).toBeGreaterThanOrEqual(ORDER_LAG_MS[0]); expect(lag).toBeLessThanOrEqual(ORDER_LAG_MS[1]); } expect(RADIO_COOLDOWN_MS.mess).toBeGreaterThan(ORDER_LAG_MS[1]); expect(RADIO_COOLDOWN_MS.water).toBeGreaterThan(ORDER_LAG_MS[1]); }); it("Shan's floor starts at loose — sober people are not a radio matter", () => { expect(drunkStage(SHAN_MIN_INTOX)).toBe('loose'); }); it('the rope hold ends before it can be re-ordered', () => { expect(KAYDEN_HOLD_COOLDOWN_MS).toBeGreaterThan(KAYDEN_HOLD_MS); }); });