1
This commit is contained in:
@@ -156,7 +156,6 @@ export function getLeadingAttributeSlot(
|
||||
export function buildSchemaSummary(schema: WorldAttributeSchema, limit = 6) {
|
||||
return schema.slots.slice(0, limit).map(slot => ({
|
||||
name: slot.name,
|
||||
definition: slot.definition,
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -50,18 +50,6 @@ function toText(value: unknown, fallback: string) {
|
||||
return normalized || fallback;
|
||||
}
|
||||
|
||||
function toStringArray(value: unknown, fallback: string[]) {
|
||||
if (!Array.isArray(value)) {
|
||||
return [...fallback];
|
||||
}
|
||||
|
||||
const normalized = value
|
||||
.map(item => toOptionalText(item))
|
||||
.filter(Boolean);
|
||||
|
||||
return normalized.length > 0 ? [...new Set(normalized)] : [...fallback];
|
||||
}
|
||||
|
||||
export function coerceWorldAttributeSchema(
|
||||
raw: unknown,
|
||||
fallback: WorldAttributeSchema,
|
||||
@@ -79,7 +67,6 @@ export function coerceWorldAttributeSchema(
|
||||
schemaVersion: typeof raw.schemaVersion === 'number' && Number.isFinite(raw.schemaVersion) && raw.schemaVersion > 0
|
||||
? Math.max(1, Math.round(raw.schemaVersion))
|
||||
: fallback.schemaVersion,
|
||||
schemaName: toOptionalText(raw.schemaName) || fallback.schemaName,
|
||||
generatedFrom: {
|
||||
...fallback.generatedFrom,
|
||||
worldName: toText(rawGeneratedFrom.worldName, fallback.generatedFrom.worldName),
|
||||
@@ -93,12 +80,6 @@ export function coerceWorldAttributeSchema(
|
||||
...fallbackSlot,
|
||||
slotId: fallbackSlot.slotId,
|
||||
name: toText(rawSlot.name, fallbackSlot.name),
|
||||
definition: toText(rawSlot.definition, fallbackSlot.definition),
|
||||
positiveSignals: toStringArray(rawSlot.positiveSignals, fallbackSlot.positiveSignals),
|
||||
negativeSignals: toStringArray(rawSlot.negativeSignals, fallbackSlot.negativeSignals),
|
||||
combatUseText: toText(rawSlot.combatUseText, fallbackSlot.combatUseText),
|
||||
socialUseText: toText(rawSlot.socialUseText, fallbackSlot.socialUseText),
|
||||
explorationUseText: toText(rawSlot.explorationUseText, fallbackSlot.explorationUseText),
|
||||
};
|
||||
}),
|
||||
};
|
||||
@@ -151,17 +132,6 @@ export function validateWorldAttributeSchema(schema: WorldAttributeSchema) {
|
||||
issues.push(`attribute name "${trimmedName}" contains banned legacy term`);
|
||||
}
|
||||
|
||||
if (!slot.definition.trim()) {
|
||||
issues.push(`slot ${slot.slotId} is missing a definition`);
|
||||
}
|
||||
|
||||
if (/攻击力|防御力|生命值|法力值/u.test(slot.definition)) {
|
||||
issues.push(`slot ${slot.slotId} definition is too derivative`);
|
||||
}
|
||||
|
||||
if (!slot.combatUseText.trim() || !slot.socialUseText.trim() || !slot.explorationUseText.trim()) {
|
||||
issues.push(`slot ${slot.slotId} must describe combat, social, and exploration usage`);
|
||||
}
|
||||
});
|
||||
|
||||
return issues;
|
||||
|
||||
@@ -85,7 +85,6 @@ export type BuildDamageBreakdown = {
|
||||
export type BuildContributionAttributeRow = {
|
||||
slotId: string;
|
||||
label: string;
|
||||
definition: string;
|
||||
similarity: number;
|
||||
weight: number;
|
||||
value: number;
|
||||
@@ -104,7 +103,6 @@ export type OutgoingDamageResult = {
|
||||
type BuildContributionTarget = {
|
||||
slotId: string;
|
||||
label: string;
|
||||
definition: string;
|
||||
};
|
||||
|
||||
type ResolvedTagAffinity = {
|
||||
@@ -312,7 +310,6 @@ function resolveContributionTargets(
|
||||
return schema.slots.map((slot) => ({
|
||||
slotId: slot.slotId,
|
||||
label: slot.name,
|
||||
definition: slot.definition,
|
||||
})) satisfies BuildContributionTarget[];
|
||||
}
|
||||
|
||||
@@ -550,7 +547,6 @@ export function getBuildContributionAttributeRows(
|
||||
return {
|
||||
slotId: target.slotId,
|
||||
label: target.label,
|
||||
definition: target.definition,
|
||||
similarity: roundNumber(
|
||||
row.attributeSimilarities?.[target.slotId] ?? 0,
|
||||
4,
|
||||
|
||||
@@ -60,12 +60,6 @@ function buildSlotSemanticVector(slot: WorldAttributeSlot, index: number) {
|
||||
const sourceText = [
|
||||
slot.slotId,
|
||||
slot.name,
|
||||
slot.definition,
|
||||
slot.combatUseText,
|
||||
slot.socialUseText,
|
||||
slot.explorationUseText,
|
||||
...(slot.positiveSignals ?? []),
|
||||
...(slot.negativeSignals ?? []),
|
||||
].join(' ');
|
||||
|
||||
const semanticVector: AttributeVector = {};
|
||||
|
||||
@@ -5,7 +5,8 @@ import type { FunctionDocumentationEntry } from '../types';
|
||||
* camp_travel_home_scene
|
||||
*
|
||||
* 从营地与同伴对话结束后,正式前往角色主线场景的控制 function。
|
||||
* 这里除了元信息,也直接收口了它的按钮构造与判定 helper。
|
||||
* 中文注释:前端只保留按钮构造、判定 helper 和视觉元信息;
|
||||
* 正式场景迁移、遭遇预览与快照写入统一由后端 resolver 承接。
|
||||
*/
|
||||
export const CAMP_TRAVEL_HOME_OPTION_VISUALS: StoryOption['visuals'] = {
|
||||
playerAnimation: AnimationState.RUN,
|
||||
@@ -41,10 +42,10 @@ export const CAMP_TRAVEL_HOME_FUNCTION: FunctionDocumentationEntry = {
|
||||
source: 'src/data/functionCatalog/flow/campTravelHomeScene.ts',
|
||||
summary: '营地开场后的专用旅行控制项。',
|
||||
detailedDescription:
|
||||
'它负责把开局同伴营地流程平稳切到角色真正的起始场景,并清理当前营地 encounter、战斗态和镜头残留状态。',
|
||||
'它负责把开局同伴营地流程平稳切到角色真正的起始场景;正式目标场景、encounter 清理、战斗态清理和镜头残留状态由后端 resolver 写入。',
|
||||
trigger: '常见于开局同伴营地对话后的跟进选项。',
|
||||
execution:
|
||||
'点击后不会走普通 state function 结算,而是执行一次定制的场景迁移和历史写入。',
|
||||
'点击后作为服务端 runtime function id 提交到 /api/runtime/story/actions/resolve,由后端执行定制场景迁移和历史写入。',
|
||||
result: '玩家会离开营地进入角色主场景,正式开始该角色的冒险线。',
|
||||
active: true,
|
||||
runtime: {
|
||||
@@ -52,11 +53,11 @@ export const CAMP_TRAVEL_HOME_FUNCTION: FunctionDocumentationEntry = {
|
||||
uiMode: 'none',
|
||||
visuals: CAMP_TRAVEL_HOME_OPTION_VISUALS,
|
||||
executor:
|
||||
'src/hooks/rpg-runtime-story/choiceActions.ts -> handleCampTravelHome',
|
||||
'server-rs/crates/api-server/src/runtime_story/compat.rs -> resolve_camp_travel_home_scene_action',
|
||||
animationNote:
|
||||
'先播放营地离场的 run 演出,再切到正式场景并生成 encounter preview。',
|
||||
'前端保留 run 视觉元信息;正式状态以服务端 hydrated snapshot 为准。',
|
||||
storyNote:
|
||||
'通过 commitGeneratedStateWithEncounterEntry 写入离营结果,并在新场景继续后续剧情。',
|
||||
'后端写入离营结果、生成 encounter preview,并在新场景继续后续剧情。',
|
||||
uiNote: '这是专用旅行流程,不会打开 modal。',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { existsSync } from 'node:fs';
|
||||
|
||||
import { SERVER_RUNTIME_FUNCTION_IDS } from '../../../packages/shared/src/contracts/rpgRuntimeStoryAction';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { SERVER_RUNTIME_FUNCTION_IDS } from '../../../packages/shared/src/contracts/rpgRuntimeStoryAction';
|
||||
import type { Encounter, GameState, InventoryItem } from '../../types';
|
||||
import {
|
||||
ALL_FUNCTION_DOCUMENTATION,
|
||||
buildCampTravelHomeOption,
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
buildNpcPreviewTalkOption,
|
||||
buildNpcRecruitModalState,
|
||||
buildNpcTradeModalState,
|
||||
CAMP_TRAVEL_HOME_FUNCTION,
|
||||
CONTINUE_ADVENTURE_FUNCTION,
|
||||
getFunctionDocumentationById,
|
||||
isNpcPreviewTalkOption,
|
||||
@@ -18,7 +20,6 @@ import {
|
||||
shouldNpcRecruitOpenModal,
|
||||
} from './index';
|
||||
import { RPG_FUNCTION_RUNTIME_OVERVIEW } from './runtimeIndex';
|
||||
import type { Encounter, GameState, InventoryItem } from '../../types';
|
||||
|
||||
function createEncounter(overrides: Partial<Encounter> = {}): Encounter {
|
||||
return {
|
||||
@@ -103,6 +104,12 @@ describe('functionCatalog', () => {
|
||||
expect(campTravelOption.functionId).toBe('camp_travel_home_scene');
|
||||
expect(campTravelOption.actionText).toBe('前往 竹林古道');
|
||||
expect(campTravelOption.detailText).toBe('离开营地,前往 竹林古道。');
|
||||
expect(CAMP_TRAVEL_HOME_FUNCTION.runtime?.executor).toContain(
|
||||
'server-rs/crates/api-server/src/runtime_story/compat.rs',
|
||||
);
|
||||
expect(CAMP_TRAVEL_HOME_FUNCTION.detailedDescription).toContain(
|
||||
'后端 resolver',
|
||||
);
|
||||
});
|
||||
|
||||
it('builds npc preview talk options from the current encounter', () => {
|
||||
|
||||
@@ -248,7 +248,7 @@ export function buildEncounterAttributeRumors(
|
||||
|
||||
return getSortedAttributeEntries(profile, schemaContext.schema)
|
||||
.slice(0, options.limit ?? 2)
|
||||
.map(entry => `${entry.slot.name}:${entry.slot.definition}`);
|
||||
.map(entry => entry.slot.name);
|
||||
}
|
||||
|
||||
export function buildGiftAffinityInsight(
|
||||
|
||||
@@ -10,7 +10,6 @@ export const WORLD_TEMPLATE_ATTRIBUTE_SCHEMAS: Record<
|
||||
id: 'schema:wuxia:v1',
|
||||
worldId: WorldType.WUXIA,
|
||||
schemaVersion: 1,
|
||||
schemaName: '江湖六脉',
|
||||
generatedFrom: {
|
||||
worldType: WorldType.WUXIA,
|
||||
worldName: '武侠',
|
||||
@@ -22,62 +21,26 @@ export const WORLD_TEMPLATE_ATTRIBUTE_SCHEMAS: Record<
|
||||
{
|
||||
slotId: 'axis_a',
|
||||
name: '骨势',
|
||||
definition: '扛压、顶冲、硬吃风险也不退的势头。',
|
||||
positiveSignals: ['扛压', '硬桥硬马', '稳住正面'],
|
||||
negativeSignals: ['虚浮', '怯退', '一碰就散'],
|
||||
combatUseText: '顶住正面压力、换伤不退、撑住阵线。',
|
||||
socialUseText: '在强压场面里不露怯,给人可靠或强硬之感。',
|
||||
explorationUseText: '穿越险路、硬顶机关、承受高压环境。',
|
||||
},
|
||||
{
|
||||
slotId: 'axis_b',
|
||||
name: '身法',
|
||||
definition: '腾挪、抢位、换线、把握出手节奏的能力。',
|
||||
positiveSignals: ['快', '轻灵', '抢位'],
|
||||
negativeSignals: ['迟缓', '失位', '笨重'],
|
||||
combatUseText: '切线换位、闪转腾挪、争夺先手。',
|
||||
socialUseText: '应变快,擅长观察气口并顺势接话。',
|
||||
explorationUseText: '攀越、潜入、追踪与复杂地形穿行。',
|
||||
},
|
||||
{
|
||||
slotId: 'axis_c',
|
||||
name: '眼脉',
|
||||
definition: '看破破绽、拆招、识局、看穿人心的能力。',
|
||||
positiveSignals: ['识局', '洞察', '拆招'],
|
||||
negativeSignals: ['迟钝', '误判', '看不透'],
|
||||
combatUseText: '抓破绽、拆套路、找出最该切入的位置。',
|
||||
socialUseText: '判断弦外之音、试探真假、识别来意。',
|
||||
explorationUseText: '识破机关、辨认痕迹、看懂异状。',
|
||||
},
|
||||
{
|
||||
slotId: 'axis_d',
|
||||
name: '心焰',
|
||||
definition: '决断、压迫、胆气、在局面中立住自身意志的能力。',
|
||||
positiveSignals: ['胆气', '决断', '压迫'],
|
||||
negativeSignals: ['犹疑', '软弱', '易被动摇'],
|
||||
combatUseText: '逼迫对手、强行推进、在关键时刻拍板。',
|
||||
socialUseText: '立威、定调、在谈判里压住场子。',
|
||||
explorationUseText: '在未知风险前保持决断,不被局势拖死。',
|
||||
},
|
||||
{
|
||||
slotId: 'axis_e',
|
||||
name: '尘缘',
|
||||
definition: '与人事、情面、承诺、牵引关系打交道的能力。',
|
||||
positiveSignals: ['通人情', '会安抚', '懂交换'],
|
||||
negativeSignals: ['生硬', '失礼', '不近人情'],
|
||||
combatUseText: '借势协同、读懂同伴与对手的关系脉络。',
|
||||
socialUseText: '安抚、求助、结盟、维系承诺与信任。',
|
||||
explorationUseText: '从传闻、人脉和地方关系里打开线索。',
|
||||
},
|
||||
{
|
||||
slotId: 'axis_f',
|
||||
name: '玄息',
|
||||
definition: '调息、稳态、久战、把自身维持在可用状态的能力。',
|
||||
positiveSignals: ['稳', '续战', '调息'],
|
||||
negativeSignals: ['紊乱', '易崩', '续不上'],
|
||||
combatUseText: '续战、回气、稳住节奏与状态。',
|
||||
socialUseText: '遇事不乱,语气和姿态都更沉稳可信。',
|
||||
explorationUseText: '长线跋涉、在恶劣环境下维持专注与状态。',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -85,7 +48,6 @@ export const WORLD_TEMPLATE_ATTRIBUTE_SCHEMAS: Record<
|
||||
id: 'schema:xianxia:v1',
|
||||
worldId: WorldType.XIANXIA,
|
||||
schemaVersion: 1,
|
||||
schemaName: '灵界六轴',
|
||||
generatedFrom: {
|
||||
worldType: WorldType.XIANXIA,
|
||||
worldName: '仙侠',
|
||||
@@ -97,62 +59,26 @@ export const WORLD_TEMPLATE_ATTRIBUTE_SCHEMAS: Record<
|
||||
{
|
||||
slotId: 'axis_a',
|
||||
name: '道骨',
|
||||
definition: '承载道压与高强度冲击的底子。',
|
||||
positiveSignals: ['承压', '根基稳', '扛得住'],
|
||||
negativeSignals: ['根基浅', '易溃', '承载不足'],
|
||||
combatUseText: '扛住灵压、正面承受高强度对撞。',
|
||||
socialUseText: '让人感到根基扎实,值得托付重事。',
|
||||
explorationUseText: '承受秘境、禁制与裂隙带来的压迫。',
|
||||
},
|
||||
{
|
||||
slotId: 'axis_b',
|
||||
name: '灵行',
|
||||
definition: '位移、御空、转场、抢占天时地利的能力。',
|
||||
positiveSignals: ['位移', '御空', '机动'],
|
||||
negativeSignals: ['迟滞', '失位', '转场慢'],
|
||||
combatUseText: '抢位、御空、快速重整战场位置。',
|
||||
socialUseText: '反应轻快,擅长顺势接住局面的变化。',
|
||||
explorationUseText: '穿越高危地形、裂隙、云海与复杂禁区。',
|
||||
},
|
||||
{
|
||||
slotId: 'axis_c',
|
||||
name: '识海',
|
||||
definition: '解析禁制、洞察因果、识破虚实的能力。',
|
||||
positiveSignals: ['洞察', '解构', '看破'],
|
||||
negativeSignals: ['迷失', '误判', '看不清'],
|
||||
combatUseText: '识破术理、找出因果节点与破绽。',
|
||||
socialUseText: '更容易辨认真话、虚言与隐藏动机。',
|
||||
explorationUseText: '解读阵纹、禁制、旧史与环境异象。',
|
||||
},
|
||||
{
|
||||
slotId: 'axis_d',
|
||||
name: '劫纹',
|
||||
definition: '在高危变化中强行推进、改写局势的能力。',
|
||||
positiveSignals: ['强推', '决断', '逆转'],
|
||||
negativeSignals: ['畏缩', '迟疑', '不敢碰变局'],
|
||||
combatUseText: '在高压窗口里压上去,逼出变化与突破。',
|
||||
socialUseText: '在关键谈判中拍板,推动他人表态。',
|
||||
explorationUseText: '面对异变与风险时敢于推进关键节点。',
|
||||
},
|
||||
{
|
||||
slotId: 'axis_e',
|
||||
name: '心契',
|
||||
definition: '与他者、器物、灵兽、誓约建立共鸣的能力。',
|
||||
positiveSignals: ['共鸣', '结契', '安抚'],
|
||||
negativeSignals: ['隔阂', '生硬', '难以共振'],
|
||||
combatUseText: '与器物、灵兽、同伴形成协同与共鸣。',
|
||||
socialUseText: '建立信任、誓约与更深层的关系连结。',
|
||||
explorationUseText: '借由共鸣打开封印、回应遗物或安抚异兽。',
|
||||
},
|
||||
{
|
||||
slotId: 'axis_f',
|
||||
name: '玄息',
|
||||
definition: '循环灵息、稳住心神、让自身持续在线的能力。',
|
||||
positiveSignals: ['稳态', '回转', '续航'],
|
||||
negativeSignals: ['紊乱', '枯竭', '失衡'],
|
||||
combatUseText: '维持灵息循环、拖住长线压力与消耗。',
|
||||
socialUseText: '气息沉稳,不轻易乱阵脚或露出破绽。',
|
||||
explorationUseText: '在漫长探索与灵潮侵蚀中维持可行动状态。',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user