feat: 完成 G 群組 — 角色人格層
- archetype-params.ts:六原型(傲嬌/冷淡/天然呆/元氣/大小姐/三無)參數表, 串接進 D 群組 EmotionService(情緒觸發閾值)與 E 群組 SentimentLedgerService(信任成長速度) - 修正一個真實落差:觸發閾值原本只縮放強度、不影響是否觸發,加入 MIN_TRIGGER_INTENSITY 門檻後「難觸發」原型才會對弱刺激真的沒反應 - expressiveness.ts:G-3 外顯函數,傲嬌反向表達(傲嬌值=內外差值,隨內部親密度先升後降) - language-style.ts:G-2 稱呼系統(陌生→您、認識→名字、摯友→暱稱,含跨階事件)與角色禁則清單, 已串接進 F-3 輸出過濾層與口癖後處理 - contrast-behavior.service.ts:G-4 反差萌,親密度+情緒+機率門檻皆滿足才觸發, 新增 ContrastTriggerLog 資料表記錄冷卻期 - character-creation.service.ts:G-5 由角色設定表一次產生語意記憶/情節記憶/情緒參數/關係初始值 - cuteness-behavior.ts:G-6 撒嬌/鬧彆扭節奏控制/吃醋/笨拙的努力/專屬揭露觸發條件; 記住小事擴充了 C-4 睡眠固化,使用者瑣事即使情緒強度低也會寫入語意記憶 - scripts/smoke/G.mjs:涵蓋六原型差異化反應、稱呼進化、傲嬌外顯曲線、反差冷卻、 可愛行為觸發條件、瑣事固化 npm run restart && npm run smoke -- G 皆通過(G-V),A/B/C/D/E/F 群組冒煙測試無回歸。
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { PrismaService } from "../prisma/prisma.service.js";
|
||||
import { RuleBasedEmotionTagger } from "../emotion/emotion-tagger.js";
|
||||
import { stageForIntimacy } from "../relationship/constants.js";
|
||||
|
||||
export interface InitialRelationshipInput {
|
||||
userId: string;
|
||||
intimacy?: number;
|
||||
trust?: number;
|
||||
}
|
||||
|
||||
export interface CreateCharacterInput {
|
||||
id?: string;
|
||||
source: "ORIGINAL" | "EXISTING_WORK";
|
||||
buildStatus?: "CANDIDATE" | "BUILT";
|
||||
workId?: string;
|
||||
formalName: string;
|
||||
basicInfo: string;
|
||||
backgroundStory: string;
|
||||
personalityArchetype: string;
|
||||
likesDislikes: string;
|
||||
goalsObsessions: string;
|
||||
speechStyle: string;
|
||||
initialRelationValue?: string;
|
||||
initialRelationship?: InitialRelationshipInput;
|
||||
}
|
||||
|
||||
// G-5 角色建立流程:由角色設定表產生初始語意記憶+初始情節記憶+情緒參數+關係初始值。
|
||||
@Injectable()
|
||||
export class CharacterCreationService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly tagger: RuleBasedEmotionTagger,
|
||||
) {}
|
||||
|
||||
async createCharacter(input: CreateCharacterInput, now: Date = new Date()) {
|
||||
const character = await this.prisma.client.character.create({
|
||||
data: {
|
||||
id: input.id,
|
||||
source: input.source,
|
||||
buildStatus: input.buildStatus ?? "CANDIDATE",
|
||||
workId: input.workId,
|
||||
basicInfo: input.basicInfo,
|
||||
backgroundStory: input.backgroundStory,
|
||||
personalityArchetype: input.personalityArchetype,
|
||||
likesDislikes: input.likesDislikes,
|
||||
goalsObsessions: input.goalsObsessions,
|
||||
speechStyle: input.speechStyle,
|
||||
initialRelationValue: input.initialRelationValue,
|
||||
},
|
||||
});
|
||||
|
||||
await this.prisma.client.characterAlias.create({
|
||||
data: { characterId: character.id, formalName: input.formalName },
|
||||
});
|
||||
|
||||
// 初始語意記憶:關於自己的事實。
|
||||
await this.prisma.client.semanticMemory.create({
|
||||
data: { characterId: character.id, fact: input.basicInfo, about: "自己" },
|
||||
});
|
||||
|
||||
// 初始情節記憶:背景故事,重用 D-1 的規則標記器判斷情緒基調(帶情緒標記)。
|
||||
const signal = this.tagger.tag({ text: input.backgroundStory });
|
||||
await this.prisma.client.episodicMemory.create({
|
||||
data: {
|
||||
characterId: character.id,
|
||||
content: input.backgroundStory,
|
||||
occurredAt: now,
|
||||
emotionTag: signal.tag,
|
||||
emotionIntensity: signal.intensity || 0.3,
|
||||
source: "OFFLINE_GENERATED",
|
||||
weight: 2,
|
||||
},
|
||||
});
|
||||
|
||||
// 情緒參數:預設值(calm=100,其餘為 0)。
|
||||
await this.prisma.client.emotionState.create({ data: { characterId: character.id } });
|
||||
|
||||
// 關係初始值(若指定了對象)。
|
||||
if (input.initialRelationship) {
|
||||
const intimacy = input.initialRelationship.intimacy ?? 0;
|
||||
await this.prisma.client.relationship.create({
|
||||
data: {
|
||||
characterId: character.id,
|
||||
userId: input.initialRelationship.userId,
|
||||
intimacy,
|
||||
trust: input.initialRelationship.trust ?? 0,
|
||||
firstMet: now,
|
||||
stage: stageForIntimacy(intimacy),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return character;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user