140 lines
4.7 KiB
TypeScript
140 lines
4.7 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import {
|
|
CORRECT_LINE,
|
|
MAGGOT_GRACE_MIN,
|
|
judgeEscalation,
|
|
maggotOverdue,
|
|
} from '../../src/scenes/floor/escalation';
|
|
import type { DrunkStage } from '../../src/data/types';
|
|
import {
|
|
CUTOFF_REPLY_CLEAN,
|
|
CUTOFF_REPLY_SCENE,
|
|
CUTOFF_REPLY_SOFT,
|
|
ESCALATION_LINES,
|
|
type EscalationLine,
|
|
} from '../../src/data/strings/floor';
|
|
|
|
const STAGES: readonly DrunkStage[] = ['sober', 'tipsy', 'loose', 'messy', 'maggot'];
|
|
const LINES: readonly EscalationLine[] = ['water', 'done', 'escort'];
|
|
|
|
describe('judgeEscalation matrix', () => {
|
|
it('returns a coherent result for every (stage, line) pair', () => {
|
|
for (const stage of STAGES) {
|
|
for (const line of LINES) {
|
|
const r = judgeEscalation(stage, line);
|
|
expect(['clean', 'scene', 'staggerFree']).toContain(r.outcome);
|
|
expect(r.line).toBe(ESCALATION_LINES[line]);
|
|
expect(r.line.length).toBeGreaterThan(0);
|
|
expect(r.reply.length).toBeGreaterThan(0);
|
|
expect(Object.values(CUTOFF_REPLY_CLEAN)
|
|
.concat(Object.values(CUTOFF_REPLY_SCENE), Object.values(CUTOFF_REPLY_SOFT)))
|
|
.toContain(r.reply);
|
|
expect(r.aggroDelta).toBeGreaterThanOrEqual(0);
|
|
if (r.eject) expect(r.outcome).toBe('clean');
|
|
if (!r.resolved) expect(r.outcome).toBe('staggerFree');
|
|
}
|
|
}
|
|
});
|
|
|
|
it('pulls the reply from the bank matching its outcome', () => {
|
|
for (const stage of STAGES) {
|
|
for (const line of LINES) {
|
|
const r = judgeEscalation(stage, line);
|
|
const bank =
|
|
r.outcome === 'clean' ? CUTOFF_REPLY_CLEAN
|
|
: r.outcome === 'scene' ? CUTOFF_REPLY_SCENE
|
|
: CUTOFF_REPLY_SOFT;
|
|
expect(r.reply).toBe(bank[stage]);
|
|
}
|
|
}
|
|
});
|
|
|
|
it('is clean whenever the line matches the correct rung', () => {
|
|
for (const stage of STAGES) {
|
|
const r = judgeEscalation(stage, CORRECT_LINE[stage]);
|
|
expect(r.outcome).toBe('clean');
|
|
expect(r.vibeDelta).toBeGreaterThan(0);
|
|
expect(r.aggroDelta).toBe(0);
|
|
expect(r.resolved).toBe(true);
|
|
}
|
|
});
|
|
|
|
it('keeps the clean reward modest — never a jackpot for being right', () => {
|
|
for (const stage of STAGES) {
|
|
expect(judgeEscalation(stage, CORRECT_LINE[stage]).vibeDelta).toBeLessThanOrEqual(5);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('over-escalating', () => {
|
|
it('makes a scene with positive aggro on a loose patron', () => {
|
|
const r = judgeEscalation('loose', 'done');
|
|
expect(r.outcome).toBe('scene');
|
|
expect(r.aggroDelta).toBeGreaterThan(0);
|
|
expect(r.vibeDelta).toBeLessThan(0);
|
|
expect(r.eject).toBe(false);
|
|
expect(r.resolved).toBe(true);
|
|
});
|
|
|
|
it('scales aggro and vibe loss with the distance overreached', () => {
|
|
const oneRung = judgeEscalation('loose', 'done');
|
|
const twoRungs = judgeEscalation('loose', 'escort');
|
|
expect(twoRungs.aggroDelta).toBe(oneRung.aggroDelta * 2);
|
|
expect(twoRungs.vibeDelta).toBe(oneRung.vibeDelta * 2);
|
|
});
|
|
|
|
it('costs more than restraint does, so aggression never pays', () => {
|
|
const scene = judgeEscalation('sober', 'escort');
|
|
const soft = judgeEscalation('maggot', 'water');
|
|
expect(scene.vibeDelta).toBeLessThan(soft.vibeDelta);
|
|
});
|
|
});
|
|
|
|
describe('under-escalating', () => {
|
|
it('lets a maggot stagger free and leaves the infraction open', () => {
|
|
const r = judgeEscalation('maggot', 'water');
|
|
expect(r.outcome).toBe('staggerFree');
|
|
expect(r.resolved).toBe(false);
|
|
expect(r.eject).toBe(false);
|
|
expect(r.aggroDelta).toBe(0);
|
|
expect(r.vibeDelta).toBeLessThan(0);
|
|
});
|
|
|
|
it('does not eject or resolve when you water a messy patron', () => {
|
|
const r = judgeEscalation('messy', 'water');
|
|
expect(r.outcome).toBe('staggerFree');
|
|
expect(r.eject).toBe(false);
|
|
expect(r.resolved).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('ejection', () => {
|
|
it('escorts a maggot out', () => {
|
|
const r = judgeEscalation('maggot', 'escort');
|
|
expect(r.outcome).toBe('clean');
|
|
expect(r.eject).toBe(true);
|
|
});
|
|
|
|
it('walks a messy patron when you call them done', () => {
|
|
expect(judgeEscalation('messy', 'done').eject).toBe(true);
|
|
});
|
|
|
|
it('never ejects someone who only needed water', () => {
|
|
for (const stage of ['sober', 'tipsy', 'loose'] as const) {
|
|
expect(judgeEscalation(stage, 'water').eject).toBe(false);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('maggotOverdue', () => {
|
|
it('is false at the grace boundary and true one minute past', () => {
|
|
expect(maggotOverdue(100, 100 + MAGGOT_GRACE_MIN)).toBe(false);
|
|
expect(maggotOverdue(100, 100 + MAGGOT_GRACE_MIN + 1)).toBe(true);
|
|
});
|
|
|
|
it('is false before the grace window elapses', () => {
|
|
expect(maggotOverdue(100, 100)).toBe(false);
|
|
expect(maggotOverdue(100, 110)).toBe(false);
|
|
});
|
|
});
|