feat: companion 核心的人設注入、語氣、心情標記與互動記憶
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
import { clamp } from './types.ts'
|
||||
|
||||
export const GAIN = 0.01
|
||||
export const DAILY_CAP = 0.05
|
||||
export const LOSS = 0.03
|
||||
export const IDLE_DAYS = 7
|
||||
export const HALF_LIFE_DAYS = 30
|
||||
const DAY = 86_400_000
|
||||
|
||||
export type Stage = 0 | 1 | 2 | 3
|
||||
export const STAGE_NAMES = ['陌生', '熟悉', '親近', '特別'] as const
|
||||
|
||||
// 升階門檻;降階門檻 = 升階 − 0.05(遲滯)
|
||||
const UP = [0.1, 0.3, 0.6]
|
||||
const HYST = 0.05
|
||||
|
||||
export type Affinity = {
|
||||
value: number
|
||||
stage: Stage
|
||||
/** 今日已增加量與其日期 */
|
||||
gainDay: string
|
||||
gainToday: number
|
||||
lastSeen: number
|
||||
}
|
||||
|
||||
export const initialAffinity = (now: number): Affinity => ({ value: 0, stage: 0, gainDay: dayKey(now), gainToday: 0, lastSeen: now })
|
||||
|
||||
export const dayKey = (t: number) => new Date(t).toISOString().slice(0, 10)
|
||||
|
||||
export function stageFor(value: number, prev: Stage): Stage {
|
||||
let s = prev
|
||||
while (s < 3 && value >= UP[s]!) s++
|
||||
while (s > 0 && value < UP[s - 1]! - HYST) s--
|
||||
return s as Stage
|
||||
}
|
||||
|
||||
/** 讀取時計算:離開超過 7 天後以 30 天半衰期回到 0 */
|
||||
export function settle(a: Affinity, now: number): Affinity {
|
||||
const idleDays = (now - a.lastSeen) / DAY
|
||||
if (idleDays <= IDLE_DAYS) return a
|
||||
const f = Math.pow(0.5, (idleDays - IDLE_DAYS) / HALF_LIFE_DAYS)
|
||||
const value = a.value * f
|
||||
return { ...a, value, stage: stageFor(value, a.stage), lastSeen: now }
|
||||
}
|
||||
|
||||
export function meaningful(a: Affinity, now: number): Affinity {
|
||||
const s = settle(a, now)
|
||||
const today = dayKey(now)
|
||||
const gainToday = s.gainDay === today ? s.gainToday : 0
|
||||
const gain = clamp(Math.min(GAIN, DAILY_CAP - gainToday), 0, GAIN)
|
||||
const value = clamp(s.value + gain, -1, 1)
|
||||
return { value, stage: stageFor(value, s.stage), gainDay: today, gainToday: gainToday + gain, lastSeen: now }
|
||||
}
|
||||
|
||||
export function negative(a: Affinity, now: number): Affinity {
|
||||
const s = settle(a, now)
|
||||
const value = clamp(s.value - LOSS, -1, 1)
|
||||
const today = dayKey(now)
|
||||
return { ...s, value, stage: stageFor(value, s.stage), gainDay: today, gainToday: s.gainDay === today ? s.gainToday : 0, lastSeen: now }
|
||||
}
|
||||
Reference in New Issue
Block a user