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 } }