import { regexTag, RULES } from './rules' import type { Tag } from './tags' 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) } } // 心情事件即評估標籤,增減量由 rules.ts 的規則表決定 export const MOOD_EVENTS = RULES export type MoodEvent = Tag // 八分區(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)]! } /** 以使用者文字的關鍵字對應心情事件 */ export const keywordEvent = (text: string): MoodEvent | null => regexTag(text)