authority (eject/pat-down/cut-off/busts) and stations (taps/decks) are now per role. Gated verbs become 'radio it in' — reusing the staff competence roll, so ~2 in 3 calls bring somebody over and the rest just hang in the air. Doorgirl keeps floor authority on purpose: that loop is the shipped game. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
105 lines
4.2 KiB
TypeScript
105 lines
4.2 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { AUTHORITY_VERBS, ROLES, canEnforce, canWork, defaultRoleFor, roleById, rolesFor } from '../src/data/roster';
|
|
import { NO_AUTHORITY } from '../src/data/strings/floor';
|
|
import { VENUES } from '../src/data/venues';
|
|
|
|
describe('the roster board', () => {
|
|
it('every venue has at least one shift pinned to it', () => {
|
|
for (const v of VENUES) {
|
|
expect(rolesFor(v.id).length, `${v.id} board is empty`).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
|
|
it('every role is pinned to a real venue', () => {
|
|
const ids = new Set(VENUES.map((v) => v.id));
|
|
for (const r of ROLES) {
|
|
expect(r.venues.length).toBeGreaterThan(0);
|
|
for (const vid of r.venues) expect(ids.has(vid), `${r.id} references '${vid}'`).toBe(true);
|
|
}
|
|
});
|
|
|
|
it('the ladder pays upward — the boss venues pay the most', () => {
|
|
for (const r of ROLES) expect(r.wage).toBeGreaterThan(0);
|
|
const hos = roleById('hos')!;
|
|
for (const r of ROLES) expect(hos.wage).toBeGreaterThanOrEqual(r.wage);
|
|
});
|
|
|
|
it('posts belong to floor starts — you cannot hold the taps from the door', () => {
|
|
for (const r of ROLES) {
|
|
if (r.post) expect(r.startLoc).toBe('floor');
|
|
}
|
|
});
|
|
|
|
it('defaultRoleFor always resolves (dev routes skip the board)', () => {
|
|
for (const v of VENUES) {
|
|
const d = defaultRoleFor(v);
|
|
expect(d).toBeDefined();
|
|
// the sensible default mans the door, or is the HoS at boss venues
|
|
expect(d.startLoc).toBe('door');
|
|
}
|
|
});
|
|
|
|
it('roleById round-trips and rejects nonsense', () => {
|
|
for (const r of ROLES) expect(roleById(r.id)?.id).toBe(r.id);
|
|
expect(roleById('mascot')).toBeUndefined();
|
|
expect(roleById(undefined)).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('what the shift lets you DO (design §6 role permissions)', () => {
|
|
it('security enforces; service reports', () => {
|
|
// The doorgirl keeps floor authority on purpose: working the rope and
|
|
// stepping inside to sort the floor is the loop the game already shipped.
|
|
expect(roleById('doorgirl')!.authority).toBe(true);
|
|
expect(roleById('bouncer')!.authority).toBe(true);
|
|
expect(roleById('hos')!.authority).toBe(true);
|
|
expect(roleById('glassie')!.authority).toBe(false);
|
|
expect(roleById('bartender')!.authority).toBe(false);
|
|
expect(roleById('dj')!.authority).toBe(false);
|
|
});
|
|
|
|
it('the glassie is the zero-authority fantasy — sees everything, may touch nothing', () => {
|
|
const glassie = roleById('glassie')!;
|
|
expect(canEnforce(glassie)).toBe(false);
|
|
for (const station of ['taps', 'decks'] as const) {
|
|
expect(canWork(glassie, station)).toBe(false);
|
|
}
|
|
// ...and is still paid the least, which is the joke and also the ladder.
|
|
for (const r of ROLES) expect(glassie.wage).toBeLessThanOrEqual(r.wage);
|
|
});
|
|
|
|
it('a station belongs to the role that owns it, plus the boss', () => {
|
|
expect(canWork(roleById('bartender')!, 'taps')).toBe(true);
|
|
expect(canWork(roleById('bartender')!, 'decks')).toBe(false);
|
|
expect(canWork(roleById('dj')!, 'decks')).toBe(true);
|
|
expect(canWork(roleById('dj')!, 'taps')).toBe(false);
|
|
for (const s of ['taps', 'decks'] as const) expect(canWork(roleById('hos')!, s)).toBe(true);
|
|
// Security roles work people, not equipment.
|
|
for (const s of ['taps', 'decks'] as const) {
|
|
expect(canWork(roleById('doorgirl')!, s)).toBe(false);
|
|
expect(canWork(roleById('bouncer')!, s)).toBe(false);
|
|
}
|
|
});
|
|
|
|
it('a role that starts ON a post is allowed to work it', () => {
|
|
// Otherwise the night opens by walking you onto a station you cannot use.
|
|
for (const r of ROLES) {
|
|
if (r.post) expect(canWork(r, r.post), `${r.id} starts on ${r.post}`).toBe(true);
|
|
}
|
|
});
|
|
|
|
it('every authority verb is a real floor interaction', () => {
|
|
// Guards a typo silently un-gating a verb: these strings are matched
|
|
// against InteractTarget.kind in FloorDemoScene.applyRolePermissions.
|
|
expect([...AUTHORITY_VERBS].sort()).toEqual(
|
|
['contraband', 'drunk', 'filming', 'joint', 'noStamp', 'stall'],
|
|
);
|
|
});
|
|
|
|
it('and every authority verb has words for reporting it instead', () => {
|
|
for (const verb of AUTHORITY_VERBS) {
|
|
expect(NO_AUTHORITY[verb], `no report line for '${verb}'`).toBeTruthy();
|
|
}
|
|
});
|
|
});
|