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>
This commit is contained in:
2026-10-14 09:29:46 +00:00
co-authored by Claude Sonnet 5.5
parent eeed12741e
commit 7e6e323473
15 changed files with 1237 additions and 20 deletions
@@ -0,0 +1,42 @@
import { ADAPT as A } from './rules'
const DAY = 86_400_000
export type Adapt = { value: number; day: number; firsts: string[] }
/** 本地日序號(時區換算後的第幾天) */
export const dayNumber = (ms: number, tzOffsetMin: number) => Math.floor((ms + tzOffsetMin * 60_000) / DAY)
export const initialAdapt = (now: number, tzOffsetMin: number): Adapt => ({ value: 0, day: dayNumber(now, tzOffsetMin), firsts: [] })
/** k = 1.5 + 1.5 × (主動度 + 想像力)/2,參數 0–100 */
export const growthRate = (initiative = 50, imagine = 50) => A.kBase + A.kSpan * ((initiative + imagine) / 200)
/** 逐日成長 ΔA = k(1 − A/100),跨多日用閉式:A' = 100 − (100 − A)(1 − k/100)^n;只往前算、不寫回 */
export function grow(a: Adapt, now: number, tzOffsetMin: number, k: number): Adapt {
const n = dayNumber(now, tzOffsetMin) - a.day
if (n <= 0) return a
return { ...a, day: a.day + n, value: 100 - (100 - a.value) * Math.pow(1 - k / 100, n) }
}
/** 本地「第一次」事件(例如第一次讀到天氣、新聞)另加 2;每種事件只算一次 */
export function markFirst(a: Adapt, kind: string): Adapt {
if (a.firsts.includes(kind)) return a
return { ...a, value: Math.min(100, a.value + A.first), firsts: [...a.firsts, kind] }
}
/** 想家基準:越不熟悉越想家 */
export const homesickBase = (value: number) => A.homesickMax * (1 - value / 100)
/** 今天最多聊幾則本地新聞:適應度不夠不聊 */
export function newsQuota(value: number, cap: number): number {
if (value < A.newsMin) return 0
return Math.min(cap, value >= A.newsTwo ? 2 : 1)
}
export function describeAdapt(value: number): string {
if (value < 20) return '初來乍到'
if (value < 40) return '慢慢熟悉'
if (value < 60) return '逐漸適應'
return value < 80 ? '相當熟悉' : '像本地人'
}