// 規則表:標籤 → 數值增減。所有數值集中在這裡;模型只給標籤,不給數值。 import type { Pad } from './types' import { isTag, type Tag } from './tags' export const RULES = { 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 }, praiseSkill: { p: 0.1, a: 0.04, d: 0.06 }, amused: { p: 0.1, a: 0.06, d: 0.02 }, cared: { p: 0.08, a: -0.02, d: -0.02 }, apology: { p: 0.06, a: -0.06, d: 0.03 }, soothe: { p: 0.05, a: -0.08, d: 0.02 }, brushOff: { p: -0.06, a: 0.02, d: -0.04 }, overstep: { p: -0.15, a: 0.12, d: 0.02 }, remembered: { p: 0.08, a: 0.02, d: 0.02 }, selfDisclosure: { p: 0.04, a: 0.03, d: -0.02 }, gladForYou: { p: 0.08, a: 0.05, d: 0 }, liedCaught: { p: -0.18, a: 0.1, d: -0.05 }, brokenPromise: { p: -0.12, a: 0.04, d: -0.08 }, } as const satisfies Record // 記憶權重 W = I_eff × F × A 與生命週期的數值(第 9.5 章) export const MEMORY = { /** I_eff 下限與上限 */ weightMin: 1, weightMax: 5, /** 編碼時情緒峰值(0–100)除以此值加進 I_eff */ peakDiv: 40, /** 新鮮度半衰的回合數:F = 0.5^(Δseq / 200) */ halfSeq: 200, /** 被檢索到時 F 至少回到此值 */ floor: 0.8, /** 存取加成:A = 1 + step × min(命中次數, cap) */ accessStep: 0.1, accessCap: 5, /** 未解負向事件:每日 +20%,上限 3 倍 */ ruminatePerDay: 0.2, ruminateCap: 3, /** 類別加成的角色參數門檻(0–1) */ bonusThreshold: 0.65, /** 暫時記憶 7 天衰減 */ tempDays: 7, /** 可升級記憶:30 天未升級就降權、90 天未檢索就封存 */ demoteDays: 30, demoteFactor: 0.5, archiveDays: 90, /** 被檢索到這麼多次就升級為長期記憶 */ upgradeHits: 3, /** 開放迴路:她的提問經過這麼多個使用者回合沒有回應就逾時 */ loopTurns: 2, /** RRF:fused = Σ 1 / (k + rank) */ rrfK: 60, /** 心情一致加權 w_m = base + slope × (1 − 釋懷速度) */ moodBase: 0.1, moodSlope: 0.05, } as const // 正則先判明確的情況;順序即優先序 const PATTERNS: [Tag, RegExp][] = [ ['scold', /笨|白痴|廢物|爛|你很煩|閉嘴|stupid|useless|idiot/i], ['thanks', /謝謝|感謝|太棒|好棒|厲害|讚|thanks?|thank you|great job|awesome/i], ['success', /成功|完成了|通過|搞定|passed|done|fixed/i], ] /** 以正則判斷使用者文字的標籤;沒有明確情況回 null */ export function regexTag(text: string): Tag | null { for (const [tag, re] of PATTERNS) if (re.test(text)) return tag return null } /** 正則優先;正則沒判出才用模型補的標籤(須在白名單內),缺失或不合法就是沒有標籤 */ export function classify(text: string, modelTag?: string | null): Tag | null { return regexTag(text) ?? (isTag(modelTag) ? modelTag : null) } // 作息與疲勞(第 9.12 章的簡化):只動精力基準,隨讀取計算、不寫回 export const SCHEDULE = { /** 醒著超過這麼多小時後,每小時精力基準 −perHour,最多 −awakeMax */ awakeFreeH: 12, perHour: 0.02, awakeMax: 0.15, /** 連續工作超過這麼多小時後,每小時 −workPerHour,最多 −workMax */ workFreeH: 1, workPerHour: 0.03, workMax: 0.15, /** 使用者的睡眠時段內仍在線上(熬夜):精力基準再 −lateShift */ lateShift: 0.1, /** 起床後這麼多小時內的「剛起床」微幅回復 */ wakeBoostH: 2, wakeBoost: 0.03, } as const // 想念累積與時間感:想念 = 速度 × max(0, 離開小時 − graceH),上限 max;速度隨「想念累積速度」參數調整 export const MISSING = { graceH: 2, perHour: 0.03, max: 0.8, /** 想念達此值,回來時主動說出口 */ speakAt: 0.5, /** 離開這麼久以上再回來,才算「回來」事件 */ returnH: 6, } as const // 適應度(第 9.13 章):每日 ΔA = k × (1 − A/100),k = 1.5 + 1.5 × (主動度 + 想像力)/2;本地第一次事件 +2 export const ADAPT = { kBase: 1.5, kSpan: 1.5, first: 2, /** 想家基準 = homesickMax × (1 − A/100) */ homesickMax: 0.25, /** 想家達此值,主動說出口 */ homesickSpeakAt: 0.4, /** 適應度達此值才聊本地新聞;達 newsTwo 時每天最多 2 則,否則 1 則 */ newsMin: 40, newsTwo: 70, } as const // 外部素材:hook 不連網,只讀由排程腳本寫入的快取;過期或缺失當作沒查 export const MATERIALS = { weatherTtlMs: 6 * 3_600_000, newsTtlMs: 24 * 3_600_000, /** 讀取快取的間隔,避免 band 每次重繪都讀檔 */ readEveryMs: 60_000, newsPerDay: 2, /** 記住已經提過的新聞連結上限 */ seenMax: 50, /** 天氣只微調心情基準,上限 */ weatherCap: 0.06, } as const /** 天氣狀況(快取的標題)→ 心情基準微調 */ export const WEATHER_MOOD: Record = { 晴: 0.04, 多雲: 0.01, 陰: -0.01, 霧: -0.02, 毛毛雨: -0.02, 雨: -0.04, 雪: 0.01, 雷雨: -0.06, }