import { clamp } from './mood' import type { Affinity } from './types' const DAY = 86_400_000 export const GAIN = 0.01 export const DAILY_CAP = 0.05 export const LOSS = 0.03 const IDLE_GRACE_DAYS = 7 const IDLE_HALF_LIFE_DAYS = 30 // 升階門檻;降階門檻 = 升階門檻 − 0.05,形成遲滯 export const UP = [0.3, 0.55, 0.8] export const HYSTERESIS = 0.05 export const dayKey = (ms: number) => new Date(ms).toISOString().slice(0, 10) export const initialAffinity = (now: number): Affinity => ({ value: 0, stage: 0, dayKey: dayKey(now), dayGain: 0, lastSeen: now }) export function stageFor(value: number, current: number): number { let s = current while (s < UP.length && value >= UP[s]!) s++ while (s > 0 && value < UP[s - 1]! - HYSTERESIS) s-- return s } // 離開超過 7 天後,以 30 天半衰期回到 0 export function idleDecay(a: Affinity, now: number): Affinity { const idleDays = (now - a.lastSeen) / DAY if (idleDays <= IDLE_GRACE_DAYS) return a const value = a.value * Math.pow(0.5, (idleDays - IDLE_GRACE_DAYS) / IDLE_HALF_LIFE_DAYS) return { ...a, value, stage: stageFor(value, a.stage) } } export function interact(prev: Affinity, kind: 'meaningful' | 'negative', now: number): Affinity { let a = idleDecay(prev, now) const key = dayKey(now) if (a.dayKey !== key) a = { ...a, dayKey: key, dayGain: 0 } let { value, dayGain } = a if (kind === 'meaningful') { const gain = Math.max(0, Math.min(GAIN, DAILY_CAP - dayGain)) value += gain dayGain += gain } else { value -= LOSS } value = clamp(value) return { value, dayGain, dayKey: key, lastSeen: now, stage: stageFor(value, a.stage) } }