// 規則表:標籤 → 數值增減。所有數值集中在這裡;模型只給標籤,不給數值。 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) }