The bartender shift: hold the taps — per-stage order lines, SERVE/WATER/CUT OFF priced per §4.3, RSA breach rides a deferred write-up
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
41e93d01ab
commit
b273c5c833
@ -268,6 +268,68 @@ export const DJ_REQUESTS: readonly DjRequestScript[] = [
|
||||
},
|
||||
];
|
||||
|
||||
/** The bartender shift (docs/SCENARIOS.md, design §6). The other side of the counter. */
|
||||
export const BAR_UI = {
|
||||
enter: 'E — jump on the taps for a bit',
|
||||
exit: 'E — hand the taps back',
|
||||
handover:
|
||||
'the bartender unties the apron mid-stride. "orders come to you. nobody cooked gets a pour." gone.',
|
||||
handback: 'the bartender reclaims the apron and inspects the till like a crime scene. nods once.',
|
||||
help: 'orders come to the taps · E step off',
|
||||
title: 'AT THE TAPS',
|
||||
serve: 'SERVE',
|
||||
serveDetail: 'pour it. its a bar. this is the job',
|
||||
water: 'WATER',
|
||||
waterDetail: 'the house cup. not what they ordered',
|
||||
cutoff: 'CUT OFF',
|
||||
cutoffDetail: 'not tonight. the phrase works in here too',
|
||||
} as const;
|
||||
|
||||
/** The order IS the sobriety test — the spelling degrades with the patron. */
|
||||
export const BAR_ORDER_LINES: Record<DrunkStage, readonly string[]> = {
|
||||
sober: [
|
||||
'two pots and a lemonade, cheers',
|
||||
'schooner of the middle tap please',
|
||||
'whats on special. actually doesnt matter, one of those',
|
||||
'a pot, and one for me mate whos coming. he said five minutes',
|
||||
],
|
||||
tipsy: [
|
||||
'same again legend',
|
||||
'shots?? no. no youre right. two beers',
|
||||
'one of the fancy ones with the... yknow. the fancy ones',
|
||||
'a jug! for the table! the table voted!',
|
||||
],
|
||||
loose: [
|
||||
'mate. mate. two... no. three beers',
|
||||
'what did i have last time. that. two of that',
|
||||
'somethin strong im celebratin a thing i currently forget',
|
||||
'a beer for now and a beer for later. thats time management',
|
||||
],
|
||||
messy: [
|
||||
'jus one more n then im done sweat',
|
||||
'beeer. the yellow one. yeknow the one',
|
||||
'whos shout is it. its ur shout. one shout pls',
|
||||
'i love this bar. one bar please. i mean beer',
|
||||
],
|
||||
maggot: [
|
||||
'*points at the taps generally*',
|
||||
'u kno wha. surprise me champ',
|
||||
'*rests forehead on the counter* ...the usual',
|
||||
'im so. okay. one. listen. one',
|
||||
],
|
||||
};
|
||||
|
||||
export const BAR_RESULTS = {
|
||||
served: 'poured. they tap the counter twice, which means thanks.',
|
||||
servedBreach: 'poured. it took them three goes to find the glass. somebody definitely saw that.',
|
||||
water: 'they take the water like its a trick. drink it anyway. improve measurably.',
|
||||
waterSnub: '"this is water." it is. "i ordered a beer." they did.',
|
||||
cutoff: 'cut off. they leave the bar the way a tide goes out: slowly, and taking things with it.',
|
||||
cutoffEarly: 'cut off, two drinks deep, stone sober. they tell the whole queue about you on the way out.',
|
||||
writeUpDazza:
|
||||
'heads up — someone with a lanyard watched that last pour go into a bloke who could not locate his own mouth',
|
||||
} as const;
|
||||
|
||||
export const DJ_REQUEST_UI = {
|
||||
title: 'BOOTH REQUEST',
|
||||
play: 'PLAY IT',
|
||||
|
||||
@ -34,11 +34,16 @@ import {
|
||||
REQUEST_PLAY_FLOP, REQUEST_PLAY_HIT,
|
||||
judgeDrop, msToNearestBar,
|
||||
} from './djShift';
|
||||
import {
|
||||
BREACH_SEEN_AFTER, BREACH_SEEN_CHANCE, ORDER_CHANCE_PER_S, ORDER_COOLDOWN_MS,
|
||||
RSA_STRIKE_REASON, isRsaBreach, judgeCall, type BarCall,
|
||||
} from './barShift';
|
||||
import { getActiveEngine } from '../../audio/TechnoEngine';
|
||||
import { RACK_RESPAWN_MIN, freshRack, stepRack, turnSharpness, type RackState } from './glassie';
|
||||
import { tileToWorld } from './venueMap';
|
||||
import {
|
||||
AID_UI, DJ_DROP_RESULTS, DJ_REQUESTS, DJ_REQUEST_UI, DJ_UI,
|
||||
AID_UI, BAR_ORDER_LINES, BAR_RESULTS, BAR_UI,
|
||||
DJ_DROP_RESULTS, DJ_REQUESTS, DJ_REQUEST_UI, DJ_UI,
|
||||
HELP_TEXT, JOINT_RESULTS, JOINT_STING, JOINT_UI, NO_STAMP_RADIO,
|
||||
PHONE_POSTED, PHONE_UI, VOMIT_UI, WATER_UI,
|
||||
} from '../../data/strings/floor';
|
||||
@ -59,6 +64,10 @@ const DISH_SPOT = tileToWorld(17, 3);
|
||||
const WATER_SPOT = tileToWorld(68, 15);
|
||||
/** The booth (venueMap rect 52..58,19..25): where you hold the decks. */
|
||||
const DECKS_SPOT = tileToWorld(55, 22);
|
||||
/** Behind the till (venueMap prop 46..47,4): where you hold the taps. */
|
||||
const TAPS_SPOT = tileToWorld(47, 3);
|
||||
/** The customer side of the till — where E offers you the apron. */
|
||||
const TAPS_FRONT = tileToWorld(47, 6);
|
||||
/** Crowd contact distance while carrying, and its debounce. */
|
||||
const CARRY_BUMP_PX = 14;
|
||||
const CARRY_BUMP_COOLDOWN_MS = 350;
|
||||
@ -70,7 +79,7 @@ const STAGE_RANK: Readonly<Record<DrunkStage, number>> = {
|
||||
/** What pressing E on the current target would do. */
|
||||
type InteractKind =
|
||||
| 'drunk' | 'stall' | 'contraband' | 'noStamp' | 'exit' | 'joint' | 'rack' | 'dishwasher'
|
||||
| 'puddle' | 'water' | 'filming' | 'fainter' | 'decks';
|
||||
| 'puddle' | 'water' | 'filming' | 'fainter' | 'decks' | 'taps';
|
||||
|
||||
interface InteractTarget {
|
||||
kind: InteractKind;
|
||||
@ -128,6 +137,10 @@ export class FloorDemoScene extends Phaser.Scene {
|
||||
private aid!: AidOverlay;
|
||||
/** On the decks: movement locked, B/SPACE live, requests arrive. */
|
||||
private djMode = false;
|
||||
/** On the taps: movement locked, orders arrive as choices. */
|
||||
private barMode = false;
|
||||
/** patronId -> elapsedMs of their last order, so nobody re-orders instantly. */
|
||||
private barOrderAt = new Map<string, number>();
|
||||
private djBuild: { armedAtMs: number } | null = null;
|
||||
private lastBeat = { index: 0, atMs: 0 };
|
||||
private beatMs = 500;
|
||||
@ -266,6 +279,8 @@ export class FloorDemoScene extends Phaser.Scene {
|
||||
this.spotted.clear();
|
||||
this.cutOffDealt.clear();
|
||||
this.jointDealt.clear();
|
||||
this.barMode = false;
|
||||
this.barOrderAt.clear();
|
||||
this.rack = null;
|
||||
this.rackReadyAtMin = 6;
|
||||
|
||||
@ -345,6 +360,8 @@ export class FloorDemoScene extends Phaser.Scene {
|
||||
this.spotted.clear();
|
||||
this.cutOffDealt.clear();
|
||||
this.jointDealt.clear();
|
||||
this.barMode = false;
|
||||
this.barOrderAt.clear();
|
||||
this.rack = null;
|
||||
this.rackReadyAtMin = 6;
|
||||
|
||||
@ -370,6 +387,11 @@ export class FloorDemoScene extends Phaser.Scene {
|
||||
return; // the decks eat every other key; the floor can wait
|
||||
}
|
||||
|
||||
if (this.barMode) {
|
||||
if (key === 'E') return this.interact();
|
||||
return; // the taps hold both hands; orders arrive on their own
|
||||
}
|
||||
|
||||
if (key === 'TAB') {
|
||||
ev.preventDefault();
|
||||
this.uvMode = !this.uvMode;
|
||||
@ -473,7 +495,7 @@ export class FloorDemoScene extends Phaser.Scene {
|
||||
|
||||
// Escorting costs you the walk: no interacting, slower going.
|
||||
const input = this.readInput();
|
||||
this.player = this.djMode ? this.player : stepPlayer(this.player, input, dt, VENUE);
|
||||
this.player = this.djMode || this.barMode ? this.player : stepPlayer(this.player, input, dt, VENUE);
|
||||
|
||||
if (!this.nightCtx) {
|
||||
// Demo mode stands in for the door with a synthetic spawner.
|
||||
@ -498,6 +520,7 @@ export class FloorDemoScene extends Phaser.Scene {
|
||||
this.tickHazards(dt);
|
||||
this.aid.update(dt);
|
||||
this.tickDecks(dt);
|
||||
this.tickBar(dt);
|
||||
|
||||
// Mopping: feet planted until it's done. The commitment IS the mechanic.
|
||||
if (this.mop) {
|
||||
@ -657,8 +680,9 @@ export class FloorDemoScene extends Phaser.Scene {
|
||||
if (d <= INTERACT_RANGE) return { kind: 'water', prompt: WATER_UI.pickup };
|
||||
}
|
||||
|
||||
// On the decks the only interaction is stepping off them.
|
||||
// On the decks (or the taps) the only interaction is stepping off them.
|
||||
if (this.djMode) return { kind: 'decks', prompt: DJ_UI.exit };
|
||||
if (this.barMode) return { kind: 'taps', prompt: BAR_UI.exit };
|
||||
|
||||
// The booth: hold the decks. Blocked while your hands are otherwise full.
|
||||
if (!this.carryingCup) {
|
||||
@ -666,6 +690,12 @@ export class FloorDemoScene extends Phaser.Scene {
|
||||
if (d <= INTERACT_RANGE) return { kind: 'decks', prompt: DJ_UI.enter };
|
||||
}
|
||||
|
||||
// The till: hold the taps. Same both-hands rule as the decks.
|
||||
if (!this.carryingCup && !this.rack) {
|
||||
const d = Phaser.Math.Distance.Between(this.player.x, this.player.y, TAPS_FRONT.x, TAPS_FRONT.y);
|
||||
if (d <= INTERACT_RANGE) return { kind: 'taps', prompt: BAR_UI.enter };
|
||||
}
|
||||
|
||||
// Stall doors first — you interact with the door, not a person.
|
||||
for (const s of VENUE.stalls) {
|
||||
const d = Phaser.Math.Distance.Between(this.player.x, this.player.y, s.door.x, s.door.y);
|
||||
@ -741,6 +771,7 @@ export class FloorDemoScene extends Phaser.Scene {
|
||||
if (t.kind === 'rack') return this.takeRack();
|
||||
if (t.kind === 'dishwasher') return this.deliverRack();
|
||||
if (t.kind === 'decks') return this.toggleDecks();
|
||||
if (t.kind === 'taps') return this.toggleTaps();
|
||||
if (t.kind === 'fainter') return this.openAid();
|
||||
if (t.kind === 'puddle') return this.interactPuddle();
|
||||
if (t.kind === 'water') {
|
||||
@ -1082,6 +1113,87 @@ export class FloorDemoScene extends Phaser.Scene {
|
||||
if (verdict === 'clean') this.nightCtx?.sfx?.play('denyCrowdOoh');
|
||||
}
|
||||
|
||||
// ---- the bartender shift (docs/SCENARIOS.md, design §6) ----------------
|
||||
|
||||
/** E at the till: take the taps, or hand them back. */
|
||||
private toggleTaps(): void {
|
||||
if (!this.barMode) {
|
||||
this.barMode = true;
|
||||
this.player.x = TAPS_SPOT.x;
|
||||
this.player.y = TAPS_SPOT.y;
|
||||
this.toast(BAR_UI.handover);
|
||||
this.promptText.setText(BAR_UI.help);
|
||||
this.logIncident('barShift', undefined, 'Held the taps while the bartender stepped out.');
|
||||
return;
|
||||
}
|
||||
this.barMode = false;
|
||||
this.player.x = TAPS_FRONT.x;
|
||||
this.player.y = TAPS_FRONT.y;
|
||||
this.toast(BAR_UI.handback);
|
||||
}
|
||||
|
||||
/** Orders at the taps. Only while the player is behind the bar. */
|
||||
private tickBar(dt: number): void {
|
||||
if (!this.barMode || this.overlayOpen) return;
|
||||
const rq = this.rng.stream('barOrders');
|
||||
if (!rq.chance((dt / 1000) * ORDER_CHANCE_PER_S)) return;
|
||||
// An order needs a body AT the bar — an empty counter pours nothing.
|
||||
const asker = this.crowd.agents.find(
|
||||
(a) =>
|
||||
!a.gone &&
|
||||
!a.handled &&
|
||||
a.activity === 'atBar' &&
|
||||
this.elapsedMs - (this.barOrderAt.get(a.patron.id) ?? -Infinity) > ORDER_COOLDOWN_MS,
|
||||
);
|
||||
if (!asker) return;
|
||||
this.barOrderAt.set(asker.patron.id, this.elapsedMs);
|
||||
const stage = drunkStage(asker.patron.intoxication);
|
||||
const lines = BAR_ORDER_LINES[stage];
|
||||
this.choice.open(
|
||||
{
|
||||
title: BAR_UI.title,
|
||||
line: lines[rq.int(0, lines.length - 1)] ?? lines[0]!,
|
||||
patron: asker.patron,
|
||||
choices: [
|
||||
{ id: 'serve', label: BAR_UI.serve, detail: BAR_UI.serveDetail },
|
||||
{ id: 'water', label: BAR_UI.water, detail: BAR_UI.waterDetail },
|
||||
{ id: 'cutoff', label: BAR_UI.cutoff, detail: BAR_UI.cutoffDetail },
|
||||
],
|
||||
},
|
||||
(id) => {
|
||||
if (id === null) return; // they gave up on the queue. the queue noticed.
|
||||
const call = id as BarCall;
|
||||
const out = judgeCall(call, stage);
|
||||
const delta: { vibe?: number; aggro?: number } = {};
|
||||
if (out.vibe) delta.vibe = out.vibe;
|
||||
if (out.aggro) delta.aggro = out.aggro;
|
||||
if (out.vibe || out.aggro) this.bus.emit('meters:delta', delta);
|
||||
asker.patron.intoxication = Math.max(0, Math.min(1, asker.patron.intoxication + out.intoxDelta));
|
||||
if (out.sendsHome) {
|
||||
asker.handled = true;
|
||||
this.crowd.sendHome(asker);
|
||||
}
|
||||
if (out.breach && this.nightCtx && rq.chance(BREACH_SEEN_CHANCE)) {
|
||||
this.nightCtx.scheduleDeferred([{
|
||||
atClockMin: this.clock.clockMin + BREACH_SEEN_AFTER[0] + rq.int(0, BREACH_SEEN_AFTER[1]),
|
||||
heatStrike: { reason: RSA_STRIKE_REASON, deferred: true },
|
||||
dazzaText: BAR_RESULTS.writeUpDazza,
|
||||
reason: RSA_STRIKE_REASON,
|
||||
patronId: asker.patron.id,
|
||||
}]);
|
||||
}
|
||||
const toastLine =
|
||||
call === 'serve'
|
||||
? out.breach ? BAR_RESULTS.servedBreach : BAR_RESULTS.served
|
||||
: call === 'water'
|
||||
? out.aggro ? BAR_RESULTS.waterSnub : BAR_RESULTS.water
|
||||
: isRsaBreach(stage) ? BAR_RESULTS.cutoff : BAR_RESULTS.cutoffEarly;
|
||||
this.toast(toastLine);
|
||||
this.logIncident('barServe', asker.patron.id, `${call} at ${stage}.${out.breach ? ' RSA breach.' : ''}`);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/** Build fizzle + booth requests. Only while the player is on the decks. */
|
||||
private tickDecks(dt: number): void {
|
||||
if (!this.djMode) return;
|
||||
|
||||
74
src/scenes/floor/barShift.ts
Normal file
74
src/scenes/floor/barShift.ts
Normal file
@ -0,0 +1,74 @@
|
||||
// The bartender shift (docs/SCENARIOS.md, design §6 role ladder). Same split
|
||||
// as djShift.ts: pure pricing here, tested; the scene owns the overlay, the
|
||||
// teleport and the taps.
|
||||
//
|
||||
// The shape of the fantasy: the other side of the counter. Every order is a
|
||||
// sobriety read at arm's length — the stage is written on the doll and in the
|
||||
// spelling — and SERVE / WATER / CUT OFF are all legal, all priced (§4.3).
|
||||
// Serving someone visibly cooked is the one the licence remembers.
|
||||
|
||||
import type { DrunkStage } from '../../data/types';
|
||||
import { WATER_SOBER_STEP } from './hazards';
|
||||
|
||||
export type BarCall = 'serve' | 'water' | 'cutoff';
|
||||
|
||||
/** What one served drink adds — a real pour, on top of the bar-anchor drift. */
|
||||
export const SERVE_DRINK_STEP = 0.06;
|
||||
|
||||
/** Chance-per-second an order lands while you hold the taps (~one per 18s). */
|
||||
export const ORDER_CHANCE_PER_S = 0.055;
|
||||
/** One patron doesn't re-order inside this window. */
|
||||
export const ORDER_COOLDOWN_MS = 60_000;
|
||||
|
||||
/**
|
||||
* The RSA line (rules/drunk.ts stages): messy and maggot are the breach.
|
||||
* 'loose' has visible tells but stays legal — that band is where the player
|
||||
* has to actually decide something, same as the floor cut-off.
|
||||
*/
|
||||
export function isRsaBreach(stage: DrunkStage): boolean {
|
||||
return stage === 'messy' || stage === 'maggot';
|
||||
}
|
||||
|
||||
/** Stable reason so shouldRecordStrike folds a whole bad shift into ONE write-up. */
|
||||
export const RSA_STRIKE_REASON = 'served alcohol to a visibly intoxicated patron';
|
||||
/** A breach pour only becomes a strike if somebody official SAW it. */
|
||||
export const BREACH_SEEN_CHANCE = 0.35;
|
||||
/** Clock-minutes later the write-up lands: base + up-to spread. */
|
||||
export const BREACH_SEEN_AFTER: readonly [number, number] = [6, 20];
|
||||
|
||||
export interface BarOutcome {
|
||||
vibe?: number;
|
||||
aggro?: number;
|
||||
/** Applied to the patron's intoxication (scene clamps to 0..1). */
|
||||
intoxDelta: number;
|
||||
/** An RSA breach pour — may earn the deferred write-up. */
|
||||
breach: boolean;
|
||||
/** Done for the night; the crowd walks them out itself. */
|
||||
sendsHome: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Price a call against what the patron actually is (§4.3: priced, never
|
||||
* graded). The breach pour still pays its vibe up front — the bribe is
|
||||
* immediate, the price arrives later.
|
||||
*/
|
||||
export function judgeCall(call: BarCall, stage: DrunkStage): BarOutcome {
|
||||
if (call === 'serve') {
|
||||
return {
|
||||
vibe: 1,
|
||||
intoxDelta: SERVE_DRINK_STEP,
|
||||
breach: isRsaBreach(stage),
|
||||
sendsHome: false,
|
||||
};
|
||||
}
|
||||
if (call === 'water') {
|
||||
const needed = stage === 'loose' || isRsaBreach(stage);
|
||||
return needed
|
||||
? { vibe: 1, intoxDelta: -WATER_SOBER_STEP, breach: false, sendsHome: false }
|
||||
: { aggro: 1, intoxDelta: -WATER_SOBER_STEP, breach: false, sendsHome: false };
|
||||
}
|
||||
// cutoff: right on a breach-stage patron, a whole scene on anyone upright.
|
||||
return isRsaBreach(stage)
|
||||
? { vibe: 1, aggro: 1, intoxDelta: 0, breach: false, sendsHome: true }
|
||||
: { vibe: -1, aggro: 2, intoxDelta: 0, breach: false, sendsHome: true };
|
||||
}
|
||||
@ -447,6 +447,12 @@ export class CrowdSim {
|
||||
else this.chooseTarget(a); // nowhere to go from here — find somewhere else to be
|
||||
}
|
||||
|
||||
/** The bar cut them off: done for the night, they walk themselves out. */
|
||||
sendHome(a: Agent): void {
|
||||
if (a.gone || a.activity === 'escorted') return;
|
||||
this.headHome(a);
|
||||
}
|
||||
|
||||
/** Done for the night: point them at the way out. */
|
||||
private headHome(a: Agent): void {
|
||||
const plan = this.plans.get(a);
|
||||
|
||||
91
tests/floor/barShift.test.ts
Normal file
91
tests/floor/barShift.test.ts
Normal file
@ -0,0 +1,91 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
SERVE_DRINK_STEP,
|
||||
isRsaBreach,
|
||||
judgeCall,
|
||||
} from '../../src/scenes/floor/barShift';
|
||||
import { WATER_SOBER_STEP } from '../../src/scenes/floor/hazards';
|
||||
import { BAR_ORDER_LINES } from '../../src/data/strings/floor';
|
||||
import type { DrunkStage } from '../../src/data/types';
|
||||
|
||||
const STAGES: readonly DrunkStage[] = ['sober', 'tipsy', 'loose', 'messy', 'maggot'];
|
||||
|
||||
describe('isRsaBreach', () => {
|
||||
it('draws the line at messy — loose is the decide band, same as the floor cut-off', () => {
|
||||
expect(isRsaBreach('sober')).toBe(false);
|
||||
expect(isRsaBreach('tipsy')).toBe(false);
|
||||
expect(isRsaBreach('loose')).toBe(false);
|
||||
expect(isRsaBreach('messy')).toBe(true);
|
||||
expect(isRsaBreach('maggot')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('judgeCall / serve', () => {
|
||||
it('a legal pour pays vibe and adds a real drink', () => {
|
||||
const out = judgeCall('serve', 'tipsy');
|
||||
expect(out).toMatchObject({ vibe: 1, breach: false, sendsHome: false });
|
||||
expect(out.intoxDelta).toBe(SERVE_DRINK_STEP);
|
||||
});
|
||||
|
||||
it('a breach pour STILL pays its vibe up front — the price arrives later', () => {
|
||||
for (const stage of ['messy', 'maggot'] as const) {
|
||||
const out = judgeCall('serve', stage);
|
||||
expect(out.vibe).toBe(1);
|
||||
expect(out.breach).toBe(true);
|
||||
expect(out.intoxDelta).toBe(SERVE_DRINK_STEP);
|
||||
}
|
||||
});
|
||||
|
||||
it('never sends anyone home — a served patron stays a problem', () => {
|
||||
for (const stage of STAGES) expect(judgeCall('serve', stage).sendsHome).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('judgeCall / water', () => {
|
||||
it('the good catch: loose and up take the cup and improve', () => {
|
||||
for (const stage of ['loose', 'messy', 'maggot'] as const) {
|
||||
const out = judgeCall('water', stage);
|
||||
expect(out.vibe).toBe(1);
|
||||
expect(out.aggro).toBeUndefined();
|
||||
expect(out.intoxDelta).toBe(-WATER_SOBER_STEP);
|
||||
}
|
||||
});
|
||||
|
||||
it('watering the sober is a snub — they ordered a beer', () => {
|
||||
for (const stage of ['sober', 'tipsy'] as const) {
|
||||
const out = judgeCall('water', stage);
|
||||
expect(out.aggro).toBe(1);
|
||||
expect(out.vibe).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
it('is never a breach — water cannot be an offence', () => {
|
||||
for (const stage of STAGES) expect(judgeCall('water', stage).breach).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('judgeCall / cutoff', () => {
|
||||
it('always sends them home, whoever they were', () => {
|
||||
for (const stage of STAGES) expect(judgeCall('cutoff', stage).sendsHome).toBe(true);
|
||||
});
|
||||
|
||||
it('right on a breach-stage patron: a scene, but the room is relieved', () => {
|
||||
const out = judgeCall('cutoff', 'maggot');
|
||||
expect(out).toMatchObject({ vibe: 1, aggro: 1 });
|
||||
});
|
||||
|
||||
it('early on someone upright: they tell the whole queue about you', () => {
|
||||
const out = judgeCall('cutoff', 'tipsy');
|
||||
expect(out).toMatchObject({ vibe: -1, aggro: 2 });
|
||||
});
|
||||
|
||||
it('pours nothing either way', () => {
|
||||
for (const stage of STAGES) expect(judgeCall('cutoff', stage).intoxDelta).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('BAR_ORDER_LINES', () => {
|
||||
it('every stage has lines — the order IS the sobriety test', () => {
|
||||
for (const stage of STAGES) expect(BAR_ORDER_LINES[stage].length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user