merge: 合併 origin/main(M0 cockpit 改名)並整合 companion

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 13:35:19 +08:00
co-authored by Claude Sonnet 5.5
22 changed files with 71 additions and 45 deletions
+60
View File
@@ -0,0 +1,60 @@
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)]!
}