Files
jsc-marketplace/plugins/jsc-mod-companion/hooks/companion/rules.ts
T
adminandClaude Sonnet 5.5 7e6e323473 feat: companion 作息、主動發話、適應度與外部素材
- 作息與疲勞、想念累積與時間感,主動發話沿用 care.ts 的冷卻、日限、靜音
- 想家與適應度(9.13)
- 獨立腳本 fetch-materials.mjs 由系統排程寫入快取,hook 只讀、不連網
- 新增 /jsc-mod-companion:materials 與 setup location=、feeds=
- 天氣微調心情基準,新聞依適應度每天最多 2 則並寫入情節記憶
- 版本 0.0.5

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 09:29:46 +00:00

148 lines
5.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 規則表:標籤 → 數值增減。所有數值集中在這裡;模型只給標籤,不給數值。
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<Tag, Pad>
// 記憶權重 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<string, number> = {
晴: 0.04,
多雲: 0.01,
陰: -0.01,
霧: -0.02,
毛毛雨: -0.02,
雨: -0.04,
雪: 0.01,
雷雨: -0.06,
}