// The Cut-Off judgement matrix. Pure โ€” no Phaser, no bus. The scene decides // what to do with the result; this only decides what the room thinks of you. import type { DrunkStage } from '../../data/types'; import { CUTOFF_REPLY_CLEAN, CUTOFF_REPLY_SCENE, CUTOFF_REPLY_SOFT, ESCALATION_LINES, type EscalationLine, } from './strings'; export type EscalationOutcome = 'clean' | 'scene' | 'staggerFree'; export interface EscalationResult { outcome: EscalationOutcome; vibeDelta: number; aggroDelta: number; /** What you said (from ESCALATION_LINES). */ line: string; /** What they said back (from CUTOFF_REPLY_*). */ reply: string; /** Eject: attach them to the escort-out walk. */ eject: boolean; /** Resolved: stop treating them as an open infraction. */ resolved: boolean; } /** Softest first โ€” comparing rungs is the whole judgement. */ const RUNG: Readonly> = { water: 0, done: 1, escort: 2, }; /** The correct rung for each drunk stage. */ export const CORRECT_LINE: Readonly> = { sober: 'water', tipsy: 'water', loose: 'water', messy: 'done', maggot: 'escort', }; // Deliberately small. Being right about a drunk shouldn't feel like a jackpot, // and overreach should sting more than restraint pays (design doc ยง4.3). const CLEAN_VIBE = 4; const SCENE_VIBE_PER_RUNG = -3; const SCENE_AGGRO_PER_RUNG = 5; const SOFT_VIBE = -1; export function judgeEscalation(stage: DrunkStage, line: EscalationLine): EscalationResult { const said = ESCALATION_LINES[line]; const overreach = RUNG[line] - RUNG[CORRECT_LINE[stage]]; if (overreach === 0) { return { outcome: 'clean', vibeDelta: CLEAN_VIBE, aggroDelta: 0, line: said, reply: CUTOFF_REPLY_CLEAN[stage], // Water is a kindness, not an exit โ€” only the top two stages walk. eject: stage === 'messy' || stage === 'maggot', resolved: true, }; } if (overreach > 0) { return { outcome: 'scene', vibeDelta: SCENE_VIBE_PER_RUNG * overreach, aggroDelta: SCENE_AGGRO_PER_RUNG * overreach, line: said, reply: CUTOFF_REPLY_SCENE[stage], // They're not going anywhere with you now, but the moment is spent. eject: false, resolved: true, }; } return { outcome: 'staggerFree', vibeDelta: SOFT_VIBE, aggroDelta: 0, line: said, reply: CUTOFF_REPLY_SOFT[stage], eject: false, resolved: false, }; } /** A maggot left alone too long becomes a deferred heat risk. */ export const MAGGOT_GRACE_MIN = 25; export function maggotOverdue(spottedAtClockMin: number, nowClockMin: number): boolean { return nowClockMin - spottedAtClockMin > MAGGOT_GRACE_MIN; }