Files
jsc-marketplace/plugins/cockpit/companion/affinity.ts
T

61 lines
2.0 KiB
TypeScript

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