import type { Pad } from './types' export const clamp = (x: number, lo = -1, hi = 1) => Math.max(lo, Math.min(hi, x)) const HOUR = 3_600_000 const MOOD_HALF_LIFE_H = 8 // 高神經質的角色情緒消退較慢:50 為基準,0→0.5 倍,100→1.5 倍 export const halfLife = (neuroticism: number) => MOOD_HALF_LIFE_H * HOUR * (0.5 + neuroticism / 100) // 讀檔時才計算:x = B + (x − B)·0.5^(Δ/τ) export function decay(mood: Pad, baseline: Pad, elapsedMs: number, neuroticism: number): Pad { if (elapsedMs <= 0) return { p: mood.p, a: mood.a, d: mood.d } const k = Math.pow(0.5, elapsedMs / halfLife(neuroticism)) return { p: baseline.p + (mood.p - baseline.p) * k, a: baseline.a + (mood.a - baseline.a) * k, d: baseline.d + (mood.d - baseline.d) * k, } } // 單軸每回合變化上限 ±0.3 export const TURN_CAP = 0.3 export function applyDelta(mood: Pad, delta: Pad): Pad { const step = (x: number, dx: number) => clamp(x + clamp(dx, -TURN_CAP, TURN_CAP)) return { p: step(mood.p, delta.p), a: step(mood.a, delta.a), d: step(mood.d, delta.d) } } export const MOOD_EVENTS = { thanks: { p: 0.15, a: 0.05, d: 0.05 }, scold: { p: -0.2, a: 0.15, d: -0.1 }, success: { p: 0.08, a: 0.03, d: 0.05 }, toolFail: { p: -0.08, a: 0.1, d: -0.08 }, return: { p: 0.1, a: 0.08, d: 0 }, lateNight: { p: -0.05, a: -0.1, d: 0 }, favorite: { p: 0.12, a: 0.08, d: 0.03 }, ignored: { p: -0.05, a: -0.03, d: -0.03 }, } as const satisfies Record export type MoodEvent = keyof typeof MOOD_EVENTS // 八分區(Mehrabian 的 PAD 八分類)→ 情緒標籤與顏文字 const OCTANTS: Record = { '+++': { label: '興奮', kaomoji: '(≧▽≦)' }, '++-': { label: '撒嬌', kaomoji: '(๑>ᴗ<๑)' }, '+-+': { label: '放鬆', kaomoji: '(´ᴗ`)' }, '+--': { label: '溫順', kaomoji: '(︶ᴗ︶)' }, '---': { label: '無聊', kaomoji: '(-_-)' }, '--+': { label: '不屑', kaomoji: '(¬_¬)' }, '-++': { label: '生氣', kaomoji: '(╬`益´)' }, '-+-': { label: '焦慮', kaomoji: '(;´Д`)' }, } const CALM = { label: '平靜', kaomoji: '(・ω・)' } const CALM_RADIUS = 0.1 export function moodLabel(m: Pad): { label: string; kaomoji: string } { if (Math.abs(m.p) < CALM_RADIUS && Math.abs(m.a) < CALM_RADIUS && Math.abs(m.d) < CALM_RADIUS) return CALM const s = (x: number) => (x >= 0 ? '+' : '-') return OCTANTS[s(m.p) + s(m.a) + s(m.d)]! } const KEYWORDS: [MoodEvent, RegExp][] = [ ['scold', /笨|白痴|廢物|爛|你很煩|閉嘴|stupid|useless|idiot/i], ['thanks', /謝謝|感謝|太棒|好棒|厲害|讚|thanks?|thank you|great job|awesome/i], ['success', /成功|完成了|通過|搞定|passed|done|fixed/i], ] /** 以使用者文字的關鍵字對應心情事件 */ export function keywordEvent(text: string): MoodEvent | null { for (const [ev, re] of KEYWORDS) if (re.test(text)) return ev return null }