158 lines
6.7 KiB
TypeScript
158 lines
6.7 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
|
|
import { generateCustomWorldEntity } from './customWorldEntityGenerationService.js';
|
|
import type { UpstreamLlmClient } from './llmClient.js';
|
|
|
|
function createProfile() {
|
|
return {
|
|
name: '裂潮边城',
|
|
settingText: '裂潮重新逼近边城,旧封桥令也被重新翻出。',
|
|
summary: '一座在裂潮与旧案之间摇摇欲坠的边城。',
|
|
tone: '紧绷、克制、暗流涌动',
|
|
playerGoal: '查清封桥旧令背后的真正操盘者',
|
|
playableNpcs: [
|
|
{
|
|
id: 'playable-1',
|
|
name: '沈砺',
|
|
title: '灰炬向导',
|
|
role: '边路同行者',
|
|
description: '熟悉裂潮边路的向导。',
|
|
visualDescription: '灰斗篷和旧路标是他最显眼的识别点。',
|
|
actionDescription: '先试探风向,再用短弓牵制。',
|
|
sceneVisualDescription: '他常在旧边路哨点出现。',
|
|
backstory: '曾在旧撤离线里失去整支同行队。',
|
|
personality: '谨慎寡言。',
|
|
motivation: '想查清旧撤离线再次失控的原因。',
|
|
combatStyle: '短弓牵制后贴近补刀。',
|
|
initialAffinity: 18,
|
|
relationshipHooks: ['旧撤离线'],
|
|
tags: ['裂潮', '向导'],
|
|
},
|
|
],
|
|
storyNpcs: [
|
|
{
|
|
id: 'story-1',
|
|
name: '梁砺',
|
|
title: '断桥巡守',
|
|
role: '巡守',
|
|
description: '守着旧桥与哨火的人。',
|
|
visualDescription: '披着旧制巡守外袍,枪柄磨损很重。',
|
|
actionDescription: '先立枪封路,再逼近压线。',
|
|
sceneVisualDescription: '多出现在断桥和潮湿石阶附近。',
|
|
backstory: '旧案爆发时,他是最后一个封桥的人。',
|
|
personality: '直接、警觉。',
|
|
motivation: '不想再让封桥旧案被人利用。',
|
|
combatStyle: '长枪压线。',
|
|
initialAffinity: 6,
|
|
relationshipHooks: ['断桥'],
|
|
tags: ['巡守'],
|
|
},
|
|
],
|
|
landmarks: [
|
|
{
|
|
id: 'landmark-1',
|
|
name: '旧潮栈桥',
|
|
description: '裂潮来时最先响起铁索声的旧栈桥。',
|
|
visualDescription: '铁索、旧桩和盐雾一起压在栈桥上。',
|
|
dangerLevel: 'medium',
|
|
sceneNpcIds: ['story-1'],
|
|
connections: [],
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
test('generateCustomWorldEntity returns role-side visual descriptions from the same model response', async () => {
|
|
const llmClient = {
|
|
requestMessageContent: async () =>
|
|
JSON.stringify({
|
|
playableNpc: {
|
|
name: '顾潮音',
|
|
title: '潮港校灯人',
|
|
role: '边港同行者',
|
|
description: '在港区高处替玩家校正风向与路标的人。',
|
|
visualDescription:
|
|
'深蓝防潮外套压着风痕,腰侧悬着校灯尺和短刃,整个人像常年站在高处看潮线的人。',
|
|
actionDescription:
|
|
'先借高处观察和校灯信号牵制敌人,再突然贴近切断退路。',
|
|
sceneVisualDescription:
|
|
'他第一次出现的高台边缘挂着潮湿风旗,脚下是被盐雾浸白的木板和仍亮着的旧校灯。',
|
|
backstory: '曾负责港区夜航校灯,后被卷进旧案。',
|
|
personality: '沉稳、寡言、观察细。',
|
|
motivation: '想在港区秩序彻底失控前找到还能守住的线。',
|
|
combatStyle: '高差观察后快速切入。',
|
|
initialAffinity: 24,
|
|
relationshipHooks: ['夜航校灯', '旧港案'],
|
|
tags: ['港区', '校灯'],
|
|
publicSummary: '港区里很少有人比他更熟悉夜里的风向。',
|
|
chapterTeasers: ['他盯风向比盯人更久。', '旧港案在他身上没过去。', '他一直在等某个信号。', '他还藏着最后一次校灯记录。'],
|
|
chapterContents: ['他总先校风向。', '旧港案改变了他的站位。', '他真正守的是港区里还没断的线。', '最后那份校灯记录能指向操盘者。'],
|
|
skills: [
|
|
{ name: '校灯试探', summary: '先用灯信号试探敌我位置。', style: '起手压制' },
|
|
{ name: '斜坡切入', summary: '借高差快速贴近改线。', style: '机动周旋' },
|
|
{ name: '潮线封口', summary: '看准潮线后一口气断掉退路。', style: '爆发终结' },
|
|
],
|
|
initialItems: [
|
|
{ name: '校灯尺', category: '武器', quantity: 1, rarity: 'rare', description: '兼具校灯与近战功能。', tags: ['港区'] },
|
|
{ name: '旧港图片', category: '专属物品', quantity: 1, rarity: 'rare', description: '记着他自己的旧线路。', tags: ['旧案'] },
|
|
{ name: '潮雾止血包', category: '消耗品', quantity: 2, rarity: 'uncommon', description: '港区常备。', tags: ['补给'] },
|
|
],
|
|
},
|
|
}),
|
|
} as UpstreamLlmClient;
|
|
|
|
const result = await generateCustomWorldEntity(llmClient, {
|
|
profile: createProfile(),
|
|
kind: 'playable',
|
|
});
|
|
|
|
assert.equal(result.kind, 'playable');
|
|
assert.equal(
|
|
result.entity.visualDescription,
|
|
'深蓝防潮外套压着风痕,腰侧悬着校灯尺和短刃,整个人像常年站在高处看潮线的人。',
|
|
);
|
|
assert.equal(
|
|
result.entity.actionDescription,
|
|
'先借高处观察和校灯信号牵制敌人,再突然贴近切断退路。',
|
|
);
|
|
assert.equal(
|
|
result.entity.sceneVisualDescription,
|
|
'他第一次出现的高台边缘挂着潮湿风旗,脚下是被盐雾浸白的木板和仍亮着的旧校灯。',
|
|
);
|
|
});
|
|
|
|
test('generateCustomWorldEntity returns landmark visual descriptions from the same model response', async () => {
|
|
const llmClient = {
|
|
requestMessageContent: async () =>
|
|
JSON.stringify({
|
|
landmark: {
|
|
name: '回潮观测台',
|
|
description: '能俯瞰旧港和裂潮边缘的新观测点。',
|
|
visualDescription:
|
|
'观测台立在湿冷高处,铁质风标、旧灯架和被潮气侵白的石阶一起构成了压迫感很强的前景。',
|
|
dangerLevel: 'high',
|
|
sceneNpcNames: ['梁砺'],
|
|
connections: [
|
|
{
|
|
targetLandmarkName: '旧潮栈桥',
|
|
relativePosition: 'forward',
|
|
summary: '沿风雨走廊可直接回到旧潮栈桥',
|
|
},
|
|
],
|
|
},
|
|
}),
|
|
} as UpstreamLlmClient;
|
|
|
|
const result = await generateCustomWorldEntity(llmClient, {
|
|
profile: createProfile(),
|
|
kind: 'landmark',
|
|
});
|
|
|
|
assert.equal(result.kind, 'landmark');
|
|
assert.equal(
|
|
result.entity.visualDescription,
|
|
'观测台立在湿冷高处,铁质风标、旧灯架和被潮气侵白的石阶一起构成了压迫感很强的前景。',
|
|
);
|
|
});
|