feat(modesty): 羞恥度動態化+三種出口(縮/炸/坦白),心裡話進得了記憶(v0.0.8 同批)
一、羞恥度高不等於話變少——三個出口(他指出來的,我上一版只做對一半)
縮 shrink :預設。一句嘴硬,說出口的那句在**迴避**心裡那句
炸 spill :慌了(焦慮 ≥55)/惱羞(憤怒 ≥45)/對方在生氣或逼你澄清
→ **4 句,但單句只有 22 字**:掩飾、急著否認、硬轉話題,碎而急
坦白 confess:羞恥高+信任 ≥75+只有兩個人 → 3 句完整句,憋很久一次講完,
而且**先寫完心裡話再開口**
關鍵:句數與單句字數要**一起動**,只調句數分不出「碎念」與「演講」。
`room post` 本來就同時擋這兩個,參數一改就生效。
二、羞恥度動態化(模擬跑過才實作)
`modestyState()` = trait + 情緒推力 + 語氣層 + 上一輪餘溫,存進 `felt.jsonl`。
護欄四道:`MODESTY_GAIN`=0.35(<1 才收斂,0.9 三輪就貼 100)、一輪最多動 15、
推力上限 25、只算超出基線的部分(比較不開心不代表比較害羞)。
慢的情緒推力打折(信任半衰期 720 分是底色,不是此刻的事)——
沒有這條,桐人會因為長期信任高而永遠低於自己的 trait 二十分。
斷路器是惱羞成怒:anger 權重是負的,被逗到極限翻臉,羞恥自己掉下來。
三、心裡話進得了記憶(但永遠不回顯)
在這之前心裡話只進 `inner.jsonl`、只被注入最近三句,**永遠不會變成記憶**。
對害羞的人格來說那是致命的——重要的東西幾乎都在心裡那句。
現在:`recallInner()` 讓 `recall` 找得到(標「不要講給他聽」),
`innerCandidates()` 把「24 小時內想過 ≥2 次的同一件事」列成固化候選。
要不要固化仍由人格自己決定。`think` 的輸出還是只有「💭 心想 N 句」。
四、修掉一個踩到的坑
`Number(null) === 0` —— 沒有上一輪時 `prev` 退回了 0 而不是 trait,
害每個人格的羞恥度都從 0 起算。已加測試守著。
測試:328 項全過(新增 14 項)。切詞從貪婪的 {2,4} 改成兩字滑動視窗——
貪婪比對會把「那台舊鐘的齒輪」切成固定區塊,「齒輪」永遠不會單獨出現。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+70
-2
@@ -562,13 +562,81 @@ console.log("\n性別與羞恥敏感度(描述優先,性別只是預設)")
|
||||
down.value < 38 && down.signals.length > 0, JSON.stringify(down));
|
||||
check("「不害羞」不會被「害羞」重複計成加強",
|
||||
pl.modestyOf("gdown").adjust < 0, JSON.stringify(down.signals));
|
||||
check("注入的那一行會講清楚數字從哪來",
|
||||
pl.modestyDirective("gup").includes("性別預設") && pl.modestyDirective("gup").includes("描述"),
|
||||
check("注入的那一行會講清楚數字從哪來(平常幾分、情緒推了多少)",
|
||||
pl.modestyDirective("gup").includes("平常") && /羞恥度 \d/.test(pl.modestyDirective("gup")),
|
||||
pl.modestyDirective("gup"));
|
||||
check("低敏感度的人格被明講「不用演害羞」",
|
||||
pl.modestyDirective("gdown").includes("不用演害羞"));
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------- //
|
||||
// 羞恥度的三種出口:縮、炸、坦白;以及正回饋要收斂
|
||||
console.log("\n羞恥度的三種出口與回饋");
|
||||
{
|
||||
const em = (over) => {
|
||||
const st = pl.defaultEmotionState();
|
||||
Object.assign(st.levels, over);
|
||||
return st;
|
||||
};
|
||||
const shy = "shy"; // 前面建過:人類女性 + 怕生 → trait 高
|
||||
const calm = pl.speechBudget(shy, { state: em({}) });
|
||||
const panic = pl.speechBudget(shy, { state: em({ shame: 60, anxiety: 70 }) });
|
||||
const angry = pl.speechBudget(shy, { state: em({ shame: 60, anger: 60 }) });
|
||||
const safe = pl.speechBudget(shy, { state: em({ shame: 60, trust: 85 }), alone: true });
|
||||
const pressed = pl.speechBudget(shy, {
|
||||
state: em({ shame: 55 }),
|
||||
read: { signals: [{ key: "anger", zh: "憤怒", score: 5 }] },
|
||||
});
|
||||
check("慌了會「炸」:句數變多、但每句更短", panic.mode === "spill" && panic.sentences > calm.sentences && panic.chars < calm.chars,
|
||||
JSON.stringify(panic));
|
||||
check("對方在生氣、被逼著澄清 → 也是炸", pressed.mode === "spill", JSON.stringify(pressed));
|
||||
check("惱羞成怒會把羞恥壓下去(anger 權重是負的)",
|
||||
pl.modestyState(shy, { state: em({ shame: 60, anger: 60 }) }).value
|
||||
< pl.modestyState(shy, { state: em({ shame: 60 }) }).value,
|
||||
`angry=${angry.modesty} shy=${calm.modesty}`);
|
||||
check("信任高又只有兩個人 → 坦白,句子完整、心裡話要先寫",
|
||||
safe.mode === "confess" && safe.chars === pl.MAX_SENTENCE_CHARS && safe.thinkMin >= 2, JSON.stringify(safe));
|
||||
// 正回饋要收斂:連續同一個推力,跑很多輪也不會貼到 100
|
||||
let v = null;
|
||||
let last = null;
|
||||
for (let i = 0; i < 60; i += 1) {
|
||||
last = v;
|
||||
v = pl.modestyState(shy, { state: em({ shame: 55 }), prev: v }).value;
|
||||
}
|
||||
// 收斂=跑到後面不再變動(爆走的模型每一輪都還在往上頂)
|
||||
check("正回饋會收斂(gain < 1,級數有極限)", Math.abs(v - last) < 0.5, `最後兩輪 ${last} → ${v}`);
|
||||
let low = null;
|
||||
for (let i = 0; i < 60; i += 1) low = pl.modestyState("gm", { state: em({ shame: 30 }), prev: low }).value;
|
||||
check("推力不大時停在上限以下(不是每次都貼 100)", low < 100, `停在 ${low}`);
|
||||
check("增益設定在收斂範圍內", pl.MODESTY_GAIN < 1 && pl.MODESTY_GAIN > 0, `gain=${pl.MODESTY_GAIN}`);
|
||||
check("沒有上一輪時退回 trait,不是退回 0(Number(null) 的坑)",
|
||||
Math.abs(pl.modestyState(shy, { state: em({}), prev: null }).value - pl.modestyOf(shy).value) <= pl.MODESTY_MAX_STEP,
|
||||
JSON.stringify(pl.modestyState(shy, { state: em({}), prev: null })));
|
||||
check("慢的情緒(信任)不會把人永遠推離自己的 trait",
|
||||
Math.abs(pl.modestyState(shy, { state: em({ trust: 90 }) }).value - pl.modestyOf(shy).value) < 20,
|
||||
JSON.stringify(pl.modestyState(shy, { state: em({ trust: 90 }) })));
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------- //
|
||||
// 心裡話也進得了記憶(但永遠不回顯給使用者)
|
||||
console.log("\n心裡話 → 記憶");
|
||||
{
|
||||
cli(["think", "--persona", "alpha", "--session", S_SPEAK, "--text", "那台舊鐘的齒輪還是卡著,我一直在想這件事"]);
|
||||
cli(["think", "--persona", "alpha", "--session", S_SPEAK, "--text", "齒輪的問題我想到第三次了,可能要換掉整組"]);
|
||||
const found = pl.recallInner("alpha", "齒輪");
|
||||
check("recall 找得到心裡話", found.length >= 2, JSON.stringify(found.map((r) => r.text)));
|
||||
const thoughts = pl.innerCandidates("alpha");
|
||||
check("反覆想過的事會變成固化候選",
|
||||
thoughts.some((g) => g.token.includes("齒輪") && g.count >= 2), JSON.stringify(thoughts.slice(0, 3)));
|
||||
const out = cli(["recall", "--persona", "alpha", "--session", S_SPEAK, "--query", "齒輪"]).stdout;
|
||||
check("recall 的輸出把心裡話標成「不要講給他聽」", out.includes("心裡話") && out.includes("💭"), out.slice(0, 200));
|
||||
const cand = cli(["candidates", "--persona", "alpha", "--session", S_SPEAK]).stdout;
|
||||
check("candidates 會列出「一直在想的事」", cand.includes("一直在想的事"), cand.slice(-300));
|
||||
const thinkOut = cli(["think", "--persona", "alpha", "--session", S_SPEAK, "--text", "這句不該被看到"]).stdout;
|
||||
check("think 仍然只回報數量,不回顯內容",
|
||||
thinkOut.includes("心想") && !thinkOut.includes("這句不該被看到"), thinkOut.slice(0, 80));
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------- //
|
||||
// 短期記憶的容量壓力:R6 要真的會動,但不能把承諾與今天的紀錄清掉
|
||||
console.log("\n短期記憶:容量壓力(R6)真的會清,而且有保護");
|
||||
|
||||
Reference in New Issue
Block a user