Spaces:
Running
Running
| import type { EventDef, FairyState, Rating, Rng, StyleTag, Taste, Tier, Utterance } from "./types"; | |
| import { clamp01, pick } from "./util"; | |
| export const STYLE_TAGS: Record<Tier, StyleTag[]> = { | |
| 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; | |
| } | |