61 lines
2.4 KiB
TypeScript
61 lines
2.4 KiB
TypeScript
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<string, Pad>
|
||
export type MoodEvent = keyof typeof MOOD_EVENTS
|
||
|
||
// 八分區(Mehrabian 的 PAD 八分類)→ 情緒標籤與顏文字
|
||
const OCTANTS: Record<string, { label: string; kaomoji: string }> = {
|
||
'+++': { 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)]!
|
||
}
|