puck / frontend /src /engine /speech.ts
vu1n's picture
Puck — desktop fairy familiar (HF Build Small)
3c124f3
Raw
History Blame Contribute Delete
2.04 kB
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;
}