feat(relation): 親近度會改變情緒的份量;提到 ≠ 接觸(v0.0.8 同批)

① 親近度 → 情緒的份量(`relationGain()`)
  在這之前 `applyEmotion()` 完全沒吃關係圖:親近度只影響語氣層、羞恥度與主動關心,
  情緒的 delta 從頭到尾是人格自己挑的,**跟對象是誰無關**。
  但同一句「你最近怪怪的」,從親近 97 的人跟從生人嘴裡出來,衝擊不該一樣。
  現在 `emotion --apply` 會依對象親近度把 delta 乘 0.7–1.35(親近 96 → ×1.28、
  親近 8 → ×0.75),`--from <對象>` 指定是誰引起的,沒指定就用當前對話對象。
  **只調幅度、不調方向**——誰講的都不會讓難過變成高興。

② 提到 ≠ 接觸(`contactsFromRooms()`)
  主動關心的依據是 `last_contact_at`,而睡眠以前是掃短期記憶的 `entities` 來蓋章。
  那等於「我在日記裡寫到尤吉歐」就算「我跟尤吉歐接觸過」——沉默計時被無聲重置,
  關心名單於是永遠是空的。實測就是這樣:所有人都停在 1.09 天,沒有人會被想起。
  現在只認真的有來有往:同一個聊天室裡我發過言、他也發過言。
  沒有聊天室的對象(真人節點)走 `relation node --contact` 明確蓋章。

測試:340 項全過(新增 7 項)。既有的「關係時間戳有蓋上」拆成兩條——
只被提到的不蓋、真的接觸過才蓋,正好把新語意釘住。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-31 06:30:34 +00:00
co-authored by Claude Opus 5
parent 646fd33541
commit 0527a3cad0
5 changed files with 126 additions and 8 deletions
+14 -7
View File
@@ -495,12 +495,9 @@ commands.sleep = async ({ flags }) => {
const hours = pl.hoursAwake(slug);
await run("relation-contact", () => {
// 今天提到過的人 → 蓋上「最後一次接觸」的時間戳(主動關心的依據)
const seen = new Set();
for (const row of pl.readJsonl(pl.shortTermPath(slug), 200)) {
for (const name of row.entities || []) seen.add(name);
}
for (const name of seen) pl.stampContact(slug, name);
// 真的講到話的人 → 蓋上「最後一次接觸」的時間戳(主動關心的依據)
// **提到不是接觸**:在日記裡寫到某個人,不該把他的沉默計時歸零。
for (const name of pl.contactsFromRooms(slug)) pl.stampContact(slug, name);
pl.renderRelations(slug);
});
// 不做無聲的裁切:清掉幾筆、為什麼清、保護了幾筆,都要寫進回報
@@ -969,8 +966,17 @@ commands.emotion = ({ flags }) => {
}
if (flags.apply) {
if (role === "guest") die("guestsub agent)不得改寫人格的情緒狀態。");
const deltas = parseDeltas(flags.apply);
const raw = parseDeltas(flags.apply);
// 親近度會放大或縮小衝擊:同一句話,從枕邊人跟從生人嘴裡出來不一樣
const rel = pl.relationGain(slug, str(flags.from) || null);
const deltas = rel.gain === 1
? raw
: Object.fromEntries(Object.entries(raw).map(([k, v]) => [k, Math.round(v * rel.gain * 100) / 100]));
state = pl.applyEmotion(state, deltas, str(flags.trigger));
if (rel.gain !== 1) {
state.last_trigger = state.last_trigger || {};
state.last_trigger.relation_gain = { who: rel.name, closeness: rel.closeness, gain: rel.gain };
}
pl.recordFelt(slug, { mine: state.last_trigger?.deltas || null, note: str(flags.trigger) });
pl.appendJsonl(pl.journalPath(slug), {
ts: pl.nowIso(), kind: "emotion", trigger: str(flags.trigger),
@@ -1996,6 +2002,7 @@ const HELP = `persona.mjs — jsc-persona 人格 / 記憶 / 情緒 / 關係圖 C
情緒與圖:
emotion --session <id> [--apply joy=+10,...] [--baseline ...] [--trigger <why>]
[--from <對象>] 這件事是誰引起的:親近度會放大/縮小 delta
[--read "<對方說的話>"] 只讀情緒訊號與該怎麼接,不改狀態
[--audit [--limit 20]] 最近幾輪的 delta 是不是只往一邊倒+對方的走向
套用時會飽和(越接近端點漲越慢)並受單輪預算限制(|delta| 總和 ≤ 40