import type { EventDef, FairyState, Rating, Rng, StyleTag, Taste, Tier, Utterance } from "./types"; import { clamp01, pick } from "./util"; export const STYLE_TAGS: Record = { plain: ["useful", "terse"], playful: ["useful", "warm"], mythic: ["theatrical", "warm"], }; const SIDE_QUESTS = [ "Pay me in a snack and I'll watch it for you.", "I left a tiny ✦ on it so I can find it again.", "I also rearranged one desktop icon. You'll never know which.", ] as const; /** Pick the voice tier the taste vector + fairy state favor, with a little caprice. */ export function speak(ev: EventDef, fs: FairyState, taste: Taste, rng: Rng = Math.random): Utterance { const tiers = Object.keys(ev.lines) as Tier[]; let best: Tier = tiers[0]; let bestW = -1e9; for (const tier of tiers) { const tags = STYLE_TAGS[tier]; let w = tags.reduce((a, t) => a + (taste[t] ?? 0.5), 0); if (tier === "mythic") w += (fs.mischief - 50) / 90; // mischief loves drama if (tier === "plain") w += (fs.protectiveness - 50) / 140; w += rng() * 0.5; // a little caprice if (w > bestW) { bestW = w; best = tier; } } let text = ev.lines[best]; // high mischief sometimes tacks on a harmless side-quest if (fs.mischief > 70 && rng() < 0.4) text += ` ${pick(SIDE_QUESTS, rng)}`; return { text, tier: best, tags: STYLE_TAGS[best] }; } /** A rating nudges taste toward / away from the tags that were used. */ export function learn(taste: Taste, tags: StyleTag[], rating: Rating): Taste { const t = { ...taste }; const step = 0.08; if (rating === "helpful") { for (const g of tags) t[g] = clamp01(t[g] + step); t.useful = clamp01(t.useful + step); } if (rating === "annoying") { for (const g of tags) t[g] = clamp01(t[g] - step); t.theatrical = clamp01(t.theatrical - step / 2); } if (rating === "cute") { t.theatrical = clamp01(t.theatrical + step); t.warm = clamp01(t.warm + step / 2); t.useful = clamp01(t.useful - step / 2); } return t; }