init with react+axum+spacetimedb
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-04-26 18:06:23 +08:00
commit cbc27bad4a
20199 changed files with 883714 additions and 0 deletions

View File

@@ -0,0 +1,230 @@
import {
buildCarrierNarrativeDescription,
buildCarrierNarrativeName,
buildRuntimeItemStoryFingerprint,
} from '../services/storyEngine/carrierNarrativeCompiler';
import type {
DirectedRuntimeReward,
InventoryItem,
RuntimeItemAiIntent,
RuntimeItemAiPromptInput,
RuntimeItemGenerationContext,
RuntimeItemPlan,
RuntimeRelationAnchor,
} from '../types';
function sanitizeFragment(value: string | null | undefined, maxLength = 4) {
return (value ?? '')
.replace(/[^\u4e00-\u9fa5a-z0-9]/giu, '')
.slice(0, maxLength);
}
function resolveAnchorLabel(anchor: RuntimeRelationAnchor) {
switch (anchor.type) {
case 'npc':
return anchor.npcName;
case 'scene':
return anchor.sceneName;
case 'landmark':
return anchor.landmarkName;
case 'monster':
return anchor.monsterName;
case 'faction':
return anchor.factionName;
case 'quest':
return anchor.questName;
default:
return '无名之地';
}
}
export function buildRuntimeItemAiPromptInput(
context: RuntimeItemGenerationContext,
plan: RuntimeItemPlan,
): RuntimeItemAiPromptInput {
const storyGraph = context.customWorldProfile?.storyGraph;
const activeThreadSummary = (context.activeThreadIds ?? [])
.map((threadId) =>
[...(storyGraph?.visibleThreads ?? []), ...(storyGraph?.hiddenThreads ?? [])]
.find((thread) => thread.id === threadId)?.title ?? threadId,
)
.join('、');
return {
worldSummary: context.customWorldProfile?.summary ?? context.worldType ?? '未知世界',
sceneSummary: [context.sceneName, context.sceneDescription].filter(Boolean).join(' / '),
encounterSummary: [context.encounterNpcName, context.encounterContextText].filter(Boolean).join(' / '),
relatedNpcSummary: context.relatedNpcNarrativeProfile
? `${context.encounterNpcName ?? '相关人物'}:公开面 ${context.relatedNpcNarrativeProfile.publicMask};当前压力 ${context.relatedNpcNarrativeProfile.immediatePressure}`
: context.relatedNpcState
? `${context.encounterNpcName ?? '相关人物'} 当前好感 ${context.relatedNpcState.affinity}`
: '暂无明确人物关系',
recentStorySummary: context.recentStorySummary,
activeThreadSummary,
generationChannel: context.generationChannel,
playerBuildDirection: context.playerBuildTags,
playerBuildGaps: context.playerBuildGaps,
desiredItemKind: plan.itemKind,
permanence: plan.permanence,
};
}
export function buildRuntimeItemAiIntent(
context: RuntimeItemGenerationContext,
plan: RuntimeItemPlan,
): RuntimeItemAiIntent {
const anchorLabel = resolveAnchorLabel(plan.relationAnchor);
const sourceSeed = sanitizeFragment(context.sceneName, 4)
|| sanitizeFragment(context.customWorldProfile?.name, 4)
|| sanitizeFragment(anchorLabel, 4)
|| '旧誓';
const functionalBias: RuntimeItemAiIntent['desiredFunctionalBias'] = [];
if (plan.permanence === 'timed') {
functionalBias.push(context.playerBuildGaps.includes('survival_gap') ? 'heal' : 'cooldown');
}
if (context.playerBuildGaps.includes('mana_gap')) functionalBias.push('mana');
if (context.playerBuildGaps.includes('survival_gap')) functionalBias.push('guard');
if (
functionalBias.length <= 0
|| context.playerBuildGaps.includes('finisher_gap')
|| plan.itemKind === 'equipment'
) {
functionalBias.push('damage');
}
return {
shortNameSeed: sourceSeed,
sourcePhrase: anchorLabel,
reasonToAppear: context.generationChannel === 'monster_drop'
? `${anchorLabel}倒下后,${context.sceneName ?? '这片战场'}里最值得带走的残留被翻了出来。`
: `${anchorLabel}与最近局势把它推到了你面前。`,
relationHooks: [
context.encounterContextText ?? context.sceneName ?? anchorLabel,
...context.recentActions,
].filter(Boolean).slice(0, 2) as string[],
desiredBuildTags: [...new Set([
...plan.targetBuildDirection,
...context.playerBuildTags.slice(0, 2),
])].slice(0, 3),
desiredFunctionalBias: [...new Set(functionalBias)].slice(0, 2),
tone: context.generationChannel === 'monster_drop'
? 'grim'
: context.generationChannel === 'quest_reward'
? 'ritual'
: context.playerBuildGaps.includes('survival_gap')
? 'survival'
: 'martial',
visibleClue:
context.relatedNpcNarrativeProfile?.visibleLine
?? `${anchorLabel}身上留下的旧痕`,
witnessMark:
context.relatedNpcNarrativeProfile?.debtOrBurden
?? `${anchorLabel}尚未散尽的使用痕`,
unfinishedBusiness:
context.relatedNpcNarrativeProfile?.contradiction
?? `${anchorLabel}背后还有没说完的问题`,
hiddenHook:
context.relatedNpcNarrativeProfile?.taboo
?? `${anchorLabel}为什么会在此刻重新出现`,
reactionHooks: [
...(context.relatedNpcNarrativeProfile?.reactionHooks ?? []),
...(context.activeThreadIds ?? []),
].slice(0, 4),
namingPattern:
plan.itemKind === 'quest'
? 'quest_evidence'
: plan.itemKind === 'material'
? 'scene_relic'
: plan.relationAnchor.type === 'monster'
? 'monster_trophy'
: plan.relationAnchor.type === 'npc'
? 'npc_relic'
: 'faction_issue',
};
}
export function applyRuntimeItemNarrative(params: {
item: InventoryItem;
context: RuntimeItemGenerationContext;
plan: RuntimeItemPlan;
intent: RuntimeItemAiIntent;
}) {
const fingerprint = buildRuntimeItemStoryFingerprint(params);
const runtimeMetadata =
params.item.runtimeMetadata ?? {
origin: 'ai_compiled' as const,
generationChannel: params.context.generationChannel,
seedKey: `${params.context.generationChannel}:${params.item.id}`,
sourceReason: params.intent.reasonToAppear,
};
return {
...params.item,
name: buildCarrierNarrativeName(params),
description: buildCarrierNarrativeDescription(params),
runtimeMetadata: {
...runtimeMetadata,
storyFingerprint: fingerprint,
},
} satisfies InventoryItem;
}
export function applyRuntimeItemNarrativeToExistingItem(params: {
item: InventoryItem;
context: RuntimeItemGenerationContext;
plan: RuntimeItemPlan;
intent: RuntimeItemAiIntent;
preserveName?: boolean;
}) {
const fingerprint = buildRuntimeItemStoryFingerprint(params);
const runtimeMetadata =
params.item.runtimeMetadata ?? {
origin: 'procedural' as const,
generationChannel: params.context.generationChannel,
relationAnchor: params.plan.relationAnchor,
seedKey: `${params.context.generationChannel}:${params.item.id}`,
sourceReason: params.intent.reasonToAppear,
};
return {
...params.item,
name: params.preserveName
? params.item.name
: buildCarrierNarrativeName(params),
description: buildCarrierNarrativeDescription(params),
runtimeMetadata: {
...runtimeMetadata,
relationAnchor: runtimeMetadata.relationAnchor ?? params.plan.relationAnchor,
sourceReason: params.intent.reasonToAppear,
storyFingerprint: fingerprint,
},
} satisfies InventoryItem;
}
export function describeRuntimeRelationAnchor(anchor: RuntimeRelationAnchor | undefined) {
if (!anchor) return '无明确锚点';
return `${anchor.type}:${resolveAnchorLabel(anchor)}`;
}
export function flattenDirectedRuntimeRewardItems(reward: DirectedRuntimeReward) {
return [
...(reward.primaryItem ? [reward.primaryItem] : []),
...reward.supportItems,
];
}
export function buildRuntimeRewardStoryHint(reward: DirectedRuntimeReward) {
const primaryItem = reward.primaryItem;
const fingerprint = primaryItem?.runtimeMetadata?.storyFingerprint;
if (!primaryItem) {
return reward.storyHint ?? '你得到了一件与当前局势相关的物品。';
}
if (reward.storyHint) {
return reward.storyHint;
}
if (fingerprint) {
return `${primaryItem.name}先露出的是“${fingerprint.visibleClue}”,但它背后还压着“${fingerprint.unresolvedQuestion}”。`;
}
return `这次得到的核心物件是 ${primaryItem.name}`;
}