#!/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} `);