This commit is contained in:
2026-04-22 23:44:57 +08:00
parent 76ac9d22a5
commit 84dc92646a
484 changed files with 9598 additions and 9135 deletions

View File

@@ -5,7 +5,11 @@ import type { UpstreamLlmClient } from './llmClient.js';
import { CustomWorldAgentFoundationDraftService } from './customWorldAgentFoundationDraftService.js';
import { normalizeFoundationDraftProfile } from './customWorldAgentDraftCompiler.js';
function createFoundationDraftLlmClient(): UpstreamLlmClient {
function createFoundationDraftLlmClient(
options: {
omitStoryOptionalVisualFields?: boolean;
} = {},
): UpstreamLlmClient {
let roleOutlineBatch = 0;
let landmarkSeedBatch = 0;
let landmarkNetworkBatch = 0;
@@ -68,9 +72,15 @@ function createFoundationDraftLlmClient(): UpstreamLlmClient {
title: '旧友兼宿敌',
role: '沉船商盟引路人',
description: '他像旧友,也像最早知道假航灯秘密的人。',
visualDescription: '衣角总带着潮水味,像是刚从夜雾里走出来。',
actionDescription: '会不断试探玩家到底愿不愿意回到旧航路。',
sceneVisualDescription: '总在钟声停下后的空隙里现身。',
...(options.omitStoryOptionalVisualFields
? {}
: {
visualDescription:
'衣角总带着潮水味,像是刚从夜雾里走出来。',
actionDescription:
'会不断试探玩家到底愿不愿意回到旧航路。',
sceneVisualDescription: '总在钟声停下后的空隙里现身。',
}),
initialAffinity: 6,
relationshipHooks: ['和玩家共享一段无法翻篇的旧灯塔往事'],
tags: ['旧友', '宿敌'],
@@ -80,9 +90,14 @@ function createFoundationDraftLlmClient(): UpstreamLlmClient {
title: '守灯会巡夜官',
role: '守灯会前台接口人',
description: '她负责把守灯会的怀疑与命令直接压到玩家面前。',
visualDescription: '披着潮湿斗篷,眼神总先看灯芯再看人。',
actionDescription: '要求玩家立刻证明自己还配站回灯塔。',
sceneVisualDescription: '总把巡夜灯举得很高,不给人躲闪空间。',
...(options.omitStoryOptionalVisualFields
? {}
: {
visualDescription: '披着潮湿斗篷,眼神总先看灯芯再看人。',
actionDescription: '要求玩家立刻证明自己还配站回灯塔。',
sceneVisualDescription:
'总把巡夜灯举得很高,不给人躲闪空间。',
}),
initialAffinity: 6,
relationshipHooks: ['会逼玩家更早站队'],
tags: ['守灯会', '巡夜'],
@@ -198,9 +213,15 @@ function createFoundationDraftLlmClient(): UpstreamLlmClient {
title: '旧友兼宿敌',
role: '沉船商盟引路人',
description: '他像旧友,也像最早知道假航灯秘密的人。',
visualDescription: '衣角总带着潮水味,像是刚从夜雾里走出来。',
actionDescription: '会不断试探玩家到底愿不愿意回到旧航路。',
sceneVisualDescription: '总在钟声停下后的空隙里现身。',
...(options.omitStoryOptionalVisualFields
? {}
: {
visualDescription:
'衣角总带着潮水味,像是刚从夜雾里走出来。',
actionDescription:
'会不断试探玩家到底愿不愿意回到旧航路。',
sceneVisualDescription: '总在钟声停下后的空隙里现身。',
}),
relationshipHooks: ['和玩家共享一段无法翻篇的旧灯塔往事'],
tags: ['旧友', '宿敌'],
},
@@ -209,9 +230,14 @@ function createFoundationDraftLlmClient(): UpstreamLlmClient {
title: '守灯会巡夜官',
role: '守灯会前台接口人',
description: '她负责把守灯会的怀疑与命令直接压到玩家面前。',
visualDescription: '披着潮湿斗篷,眼神总先看灯芯再看人。',
actionDescription: '要求玩家立刻证明自己还配站回灯塔。',
sceneVisualDescription: '总把巡夜灯举得很高,不给人躲闪空间。',
...(options.omitStoryOptionalVisualFields
? {}
: {
visualDescription: '披着潮湿斗篷,眼神总先看灯芯再看人。',
actionDescription: '要求玩家立刻证明自己还配站回灯塔。',
sceneVisualDescription:
'总把巡夜灯举得很高,不给人躲闪空间。',
}),
relationshipHooks: ['会逼玩家更早站队'],
tags: ['守灯会', '巡夜'],
},
@@ -228,9 +254,15 @@ function createFoundationDraftLlmClient(): UpstreamLlmClient {
title: '旧友兼宿敌',
role: '沉船商盟引路人',
description: '他像旧友,也像最早知道假航灯秘密的人。',
visualDescription: '衣角总带着潮水味,像是刚从夜雾里走出来。',
actionDescription: '会不断试探玩家到底愿不愿意回到旧航路。',
sceneVisualDescription: '总在钟声停下后的空隙里现身。',
...(options.omitStoryOptionalVisualFields
? {}
: {
visualDescription:
'衣角总带着潮水味,像是刚从夜雾里走出来。',
actionDescription:
'会不断试探玩家到底愿不愿意回到旧航路。',
sceneVisualDescription: '总在钟声停下后的空隙里现身。',
}),
relationshipHooks: ['和玩家共享一段无法翻篇的旧灯塔往事'],
tags: ['旧友', '宿敌'],
},
@@ -239,9 +271,14 @@ function createFoundationDraftLlmClient(): UpstreamLlmClient {
title: '守灯会巡夜官',
role: '守灯会前台接口人',
description: '她负责把守灯会的怀疑与命令直接压到玩家面前。',
visualDescription: '披着潮湿斗篷,眼神总先看灯芯再看人。',
actionDescription: '要求玩家立刻证明自己还配站回灯塔。',
sceneVisualDescription: '总把巡夜灯举得很高,不给人躲闪空间。',
...(options.omitStoryOptionalVisualFields
? {}
: {
visualDescription: '披着潮湿斗篷,眼神总先看灯芯再看人。',
actionDescription: '要求玩家立刻证明自己还配站回灯塔。',
sceneVisualDescription:
'总把巡夜灯举得很高,不给人躲闪空间。',
}),
relationshipHooks: ['会逼玩家更早站队'],
tags: ['守灯会', '巡夜'],
},
@@ -322,3 +359,49 @@ test('foundation draft service builds draft fields directly from framework inste
assert.equal(legacyStoryNpcs[0]?.name, '沈砺');
assert.equal(legacyStoryNpcs[0]?.backstory, undefined);
});
test('foundation draft service tolerates missing optional scene role visual fields', async () => {
const service = new CustomWorldAgentFoundationDraftService(
createFoundationDraftLlmClient({
omitStoryOptionalVisualFields: true,
}),
);
const draft = await service.generate({
creatorIntent: {
sourceMode: 'freeform',
rawSettingText: '被海雾反复切开的列岛世界。',
worldHook: '旧灯塔、假航灯与失控航路重新把列岛撕开。',
themeKeywords: ['海岛', '悬疑'],
toneDirectives: ['冷峻', '潮湿'],
playerPremise: '玩家是被迫返乡的失职守灯人',
openingSituation: '开局时正站在即将熄灭的旧灯塔上',
coreConflicts: ['守灯会与沉船商盟争夺旧航路解释权'],
keyFactions: [],
keyCharacters: [],
keyLandmarks: [],
iconicElements: ['潮雾钟声', '盐火灯塔'],
forbiddenDirectives: [],
},
anchorPack: {
creatorIntentSummary: '潮雾、旧灯塔、假航灯和被迫返乡的守灯人。',
},
});
const normalized = normalizeFoundationDraftProfile(draft);
const legacyResultProfile = (draft as Record<string, unknown>)
.legacyResultProfile as Record<string, unknown> | undefined;
const legacyStoryNpcs = Array.isArray(legacyResultProfile?.storyNpcs)
? (legacyResultProfile?.storyNpcs as Array<Record<string, unknown>>)
: [];
assert.ok(normalized);
assert.equal(normalized?.storyNpcs.length, 2);
assert.equal(normalized?.storyNpcs[0]?.name, '沈砺');
assert.equal(
normalized?.storyNpcs[0]?.publicMask,
'他像旧友,也像最早知道假航灯秘密的人。',
);
assert.ok((normalized?.storyNpcs[0]?.currentPressure ?? '').trim());
assert.equal(legacyStoryNpcs[0]?.visualDescription, undefined);
});

View File

@@ -61,8 +61,8 @@ function toRecord(value: unknown) {
: null;
}
function clampText(value: string, maxLength: number) {
const normalized = value.replace(/\s+/gu, ' ').trim();
function clampText(value: unknown, maxLength: number) {
const normalized = toText(value).replace(/\s+/gu, ' ').trim();
if (!normalized) {
return '';
}