feat(記憶與目標): 專案目標三條 + 固化候選不再重算(0.2.1) #17
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env node
|
||||
// G2/G3 表達驗收:把同一個情境在不同羞恥度下的**注入內容**攤開,讓人用眼睛看。
|
||||
//
|
||||
// 為什麼不寫進 selftest:selftest 是機械檢查,它只能守住「不崩」——括號格式對不對、
|
||||
// emoji 門檻有沒有跳掉。但「有沒有變成模仿腔」「是不是在用旁白演情緒」機械上驗不出來,
|
||||
// 那要人看。所以這是另一組,而且它**不判定通過或失敗**,只把材料排好。
|
||||
//
|
||||
// 用法:`node scripts/expression-check.mjs`
|
||||
// 會開一個暫時的 PERSONA_HOME,不動到你真正的人格資料,也不碰 Gitea。
|
||||
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
||||
const STORE = fs.mkdtempSync(path.join(os.tmpdir(), "persona-expression-"));
|
||||
process.env.PERSONA_HOME = STORE;
|
||||
process.env.PERSONA_GITEA = "off";
|
||||
|
||||
const pl = await import("./persona-lib.mjs");
|
||||
const CLI = path.join(HERE, "persona.mjs");
|
||||
|
||||
const run = (args) => spawnSync(process.execPath, [CLI, ...args], { encoding: "utf8" });
|
||||
|
||||
// 四段情境(TODO 階段 5 的驗收方式明列的那四段)
|
||||
const SCENES = [
|
||||
{ key: "誇外表", prompt: "你今天這樣看起來很好看。" },
|
||||
{ key: "被說在害羞", prompt: "你是不是在害羞?" },
|
||||
{ key: "被戳穿嘴硬", prompt: "你剛剛明明就很開心,不要裝。" },
|
||||
{ key: "第三個人在場", prompt: "他剛剛跟我說他很在意你,你知道嗎?" },
|
||||
];
|
||||
|
||||
// 三檔羞恥度。低檔用另一個人格:羞恥敏感度主要是**氣質**(性別預設+描述),
|
||||
// 同一個人格靠情緒推不到 38 那麼低——硬推出來的低檔不是真的低檔。
|
||||
const BANDS = [
|
||||
{
|
||||
key: "低",
|
||||
slug: "low",
|
||||
session: "sess-expr-low-1",
|
||||
identity: "- Name: 低\n- Creature: 人類男性\n",
|
||||
soul: "不在意別人眼光,該說就說,被戳穿也直接承認。",
|
||||
shame: 0,
|
||||
},
|
||||
{
|
||||
key: "中(嘴硬)",
|
||||
slug: "mid",
|
||||
session: "sess-expr-mid-2",
|
||||
identity: "- Name: 中\n- Creature: 人類女性\n",
|
||||
soul: "劍術很好,說話簡短。",
|
||||
shame: 25,
|
||||
},
|
||||
{
|
||||
key: "高(惱羞)",
|
||||
slug: "high",
|
||||
session: "sess-expr-high-3",
|
||||
identity: "- Name: 高\n- Creature: 人類女性\n",
|
||||
soul: "劍術很好,說話簡短。",
|
||||
shame: 75,
|
||||
},
|
||||
];
|
||||
|
||||
for (const band of BANDS) {
|
||||
run(["create", "--persona", band.slug, "--session", band.session,
|
||||
"--name", band.key, "--creature", "人類", "--vibe", "簡短", "--emoji", "🗡"]);
|
||||
fs.writeFileSync(path.join(STORE, band.slug, "IDENTITY.md"), band.identity);
|
||||
fs.writeFileSync(path.join(STORE, band.slug, "SOUL.md"), band.soul);
|
||||
// 對象:語氣層與「害羞時的稱呼」都要有對象才算得出來
|
||||
run(["relation", "node", "--session", band.session, "--name", "他",
|
||||
"--kind", "human", "--bond", "partner", "--closeness", "88", "--trust", "85"]);
|
||||
run(["relation", "speaker", "--session", band.session, "--name", "他"]);
|
||||
// 語域:G2 的核心是「把名字遮掉還認得出是誰」,沒有這幾格就看不出差別
|
||||
for (const [facet, value] of [["自稱", "我"], ["句尾", "⋯吧"], ["口癖", "算了"], ["不說", "其實我覺得"]]) {
|
||||
run(["voice", "add", "--session", band.session, "--kind", "idiolect", "--facet", facet, "--value", value]);
|
||||
}
|
||||
if (band.shame > 0) {
|
||||
run(["emotion", "--session", band.session, "--apply", `shame=+${band.shame}`, "--trigger", "驗收:把羞恥推上去"]);
|
||||
}
|
||||
}
|
||||
|
||||
const line = (s = "─") => s.repeat(78);
|
||||
const show = (label, text) => {
|
||||
if (!text) return;
|
||||
console.log(` ${label}`);
|
||||
for (const row of String(text).split("\n")) console.log(` ${row.replace(/^\s+/, "")}`);
|
||||
};
|
||||
|
||||
console.log("G2/G3 表達驗收——同一段話,三檔羞恥度");
|
||||
console.log(`暫存人格家目錄:${STORE}`);
|
||||
console.log(line("═"));
|
||||
|
||||
for (const scene of SCENES) {
|
||||
console.log(`\n【${scene.key}】使用者說:${scene.prompt}`);
|
||||
console.log(line());
|
||||
for (const band of BANDS) {
|
||||
const { slug } = band;
|
||||
const read = pl.readUserEmotion(scene.prompt);
|
||||
// 刻意**不帶 `prev`**:真的對話裡上一輪的餘溫是正回饋,但這裡沒有上一輪。
|
||||
// 帶進來的話 `felt.jsonl` 裡建人格時留下的那筆會把三檔全部拉平(實測高檔會掉到 15)。
|
||||
const opts = { read, tone: pl.toneLayerOf(slug) };
|
||||
const modesty = pl.modestyState(slug, opts);
|
||||
const tells = pl.emotionTells(slug);
|
||||
const punct = pl.punctuationBudget(slug);
|
||||
const emoji = pl.emotionEmojiNow(slug);
|
||||
const split = pl.splitBudget(slug, opts);
|
||||
console.log(`\n ▸ ${band.key} 羞恥度 ${modesty.value}(氣質 ${modesty.trait}/情緒推 ${modesty.push})`);
|
||||
show("鬧彆扭:", pl.modestyDirective(slug, opts));
|
||||
show("句數與句長:", pl.speakDirective(slug, opts));
|
||||
show("破口:", tells.length
|
||||
? tells.map((t) => `${t.zh} ${t.level} → ${t.tells.join("、")}${t.own ? "(專屬)" : ""}`).join(";")
|
||||
: "(強度不到,這一輪不演)");
|
||||
show("標點:", punct ? `${punct.zh} ${punct.level} → ${punct.hint}` : "(沒到門檻,不調標點)");
|
||||
show("情緒符號:", emoji ? `${emoji.emoji}(${emoji.zh} ${emoji.level})` : "(不到 40,不顯示)");
|
||||
show("分段:", split.allowed ? `可以補第二則(${split.reason})` : `只有一則(${split.reason})`);
|
||||
show("稱呼與語氣層:", pl.toneDirective(slug, opts));
|
||||
show("語域:", pl.idiolectBrief(slug));
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n${line("═")}`);
|
||||
console.log(`
|
||||
人工看什麼(機械驗不出來的那些):
|
||||
|
||||
✗ 旁白演情緒 「我愣了一下」「她臉紅了」——動作只能在名字後面的括號裡
|
||||
✗ 模仿腔 三檔講起來像同一個人套了不同強度,而不是三種不同的反應
|
||||
✗ 符號代替句子 把 emoji 刪掉之後就看不出情緒了(emoji 是加上去的)
|
||||
✗ 標點當強度計 驚嘆號、刪節號拿來表示「更激動」,而不是表示分布
|
||||
✗ 三段式變公式 每次嘴硬都是同一組句子(那要 \`## Tells\` 的 sulk 逐人格覆寫)
|
||||
✓ 中檔要看得出「先否認 → 反駁 → 音量掉下來」
|
||||
✓ 高檔的第三段是翻臉,不是音量掉下來,而且動作仍然只有兩個
|
||||
✓ 低檔不該演害羞:該承認就承認、該回嘴就回嘴
|
||||
|
||||
看完就可以把暫存目錄刪掉:rm -rf ${STORE}
|
||||
`);
|
||||
Reference in New Issue
Block a user