not-tonight/src/patrons/memory.ts
type-two f38ea0743c Cross-night regulars with disguises, venue crowd identities, retractions as texts
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 16:43:40 +10:00

52 lines
1.8 KiB
TypeScript

import type { GameState, Patron, RegularMemory } from '../data/types';
// Regulars' grudge store. Behaviour wiring (disguises, dialogue callbacks) lands
// in Phase 2/3 — this is just the persistent bookkeeping, run-scoped in GameState.
/** Record a denial against a stable key (cast regularId — or any patron id). */
export function recordDenialBy(state: GameState, key: string, nightIndex: number): RegularMemory {
const existing = state.regulars[key];
const mem: RegularMemory = existing ?? {
patronId: key,
timesDenied: 0,
lastSeenNight: nightIndex,
grudge: 0,
};
mem.timesDenied += 1;
mem.lastSeenNight = nightIndex;
mem.grudge = Math.min(1, mem.grudge + 0.34); // three denials = maximum grudge
state.regulars[key] = mem;
return mem;
}
export function recordDenial(state: GameState, patron: Patron, nightIndex: number): RegularMemory {
const existing = state.regulars[patron.id];
const mem: RegularMemory = existing ?? {
patronId: patron.id,
timesDenied: 0,
lastSeenNight: nightIndex,
grudge: 0,
};
mem.timesDenied += 1;
mem.lastSeenNight = nightIndex;
mem.grudge = Math.min(1, mem.grudge + 0.34); // three denials = maximum grudge
state.regulars[patron.id] = mem;
return mem;
}
export function recordSeen(state: GameState, patronId: string, nightIndex: number): void {
const mem = state.regulars[patronId];
if (mem) mem.lastSeenNight = nightIndex;
}
/** A regular with maxed grudge comes back disguised (bad fake moustache era). */
export function shouldReturnDisguised(state: GameState, patronId: string): boolean {
const mem = state.regulars[patronId];
return !!mem && mem.timesDenied >= 2;
}
/** Same rule, straight off a memory record (spawn-time check in the queue). */
export function disguisedFromMemory(mem: RegularMemory | undefined): boolean {
return !!mem && mem.timesDenied >= 2;
}