Files
jsc-marketplace/plugins/jsc-mod-companion/hooks/companion/mood.ts
T
rootandClaude Sonnet 5.5 ea17f1cb2d refactor: jsc-mod 改名 jsc-mod-statusline、jsc-companion 改名 jsc-mod-companion,指令拆成獨立指令
- jsc-mod-statusline:/jsc-mod-statusline:enable、compact、disable
- jsc-mod-companion:/jsc-mod-companion:status、switch、disable、setup、todo、quiet
- 同步更新 plugin.json、marketplace.json 與各 README
- 補上 statusline band 測試缺少的 ui.render 底層處理

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 05:45:52 +00:00

73 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)]!
}
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
}