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,261 @@
import type {
AttributeMigrationTrace,
AttributeVector,
Character,
CharacterSkillDefinition,
CustomWorldItem,
CustomWorldNpc,
CustomWorldPlayableNpc,
InventoryItem,
ItemAttributeResonance,
LegacyAttributeSet,
RoleAttributeEvidence,
WorldAttributeSchema,
} from '../types';
import {WORLD_ATTRIBUTE_SLOT_IDS} from '../types';
import {buildDefaultAxisVector} from './attributeResolver';
import {ensureRoleAttributeProfile} from './attributeValidation';
const AXIS_KEYWORD_RULES: Array<{slotId: string; patterns: RegExp[]; weight: number; reason: string}> = [
{ slotId: 'axis_a', patterns: [/||||||||||/u], weight: 16, reason: '' },
{ slotId: 'axis_b', patterns: [/|||||||||/u], weight: 16, reason: '' },
{ slotId: 'axis_c', patterns: [/|||||||||/u], weight: 16, reason: '' },
{ slotId: 'axis_d', patterns: [/|||||||||/u], weight: 16, reason: '' },
{ slotId: 'axis_e', patterns: [/|||||||||/u], weight: 16, reason: '' },
{ slotId: 'axis_f', patterns: [/|||||||||/u], weight: 16, reason: '' },
];
const SKILL_STYLE_VECTORS: Record<CharacterSkillDefinition['style'], AttributeVector> = {
burst: buildDefaultAxisVector({ axis_a: 0.18, axis_c: 0.2, axis_d: 0.46, axis_f: 0.16 }),
steady: buildDefaultAxisVector({ axis_a: 0.16, axis_c: 0.18, axis_e: 0.14, axis_f: 0.52 }),
mobility: buildDefaultAxisVector({ axis_b: 0.52, axis_c: 0.12, axis_d: 0.16, axis_f: 0.2 }),
finisher: buildDefaultAxisVector({ axis_a: 0.3, axis_b: 0.22, axis_c: 0.2, axis_d: 0.28 }),
projectile: buildDefaultAxisVector({ axis_b: 0.26, axis_c: 0.34, axis_d: 0.1, axis_f: 0.3 }),
};
function applyKeywordWeights(
seed: AttributeVector,
sourceText: string,
evidence: RoleAttributeEvidence[],
schema: WorldAttributeSchema,
) {
AXIS_KEYWORD_RULES.forEach(rule => {
const matches = rule.patterns.reduce((count, pattern) => count + (pattern.test(sourceText) ? 1 : 0), 0);
if (matches <= 0) return;
seed[rule.slotId] = (seed[rule.slotId] ?? 0) + rule.weight * matches;
const slot = schema.slots.find(item => item.slotId === rule.slotId);
if (slot) {
evidence.push({
slotId: slot.slotId,
reason: `${slot.name}${rule.reason}`,
});
}
});
}
function buildLegacyAttributeSeed(attributes: LegacyAttributeSet) {
return buildDefaultAxisVector({
axis_a: attributes.strength * 8 + attributes.spirit * 2,
axis_b: attributes.agility * 9 + attributes.intelligence * 1,
axis_c: attributes.intelligence * 8 + attributes.agility * 2,
axis_d: attributes.spirit * 5 + attributes.strength * 4 + attributes.agility * 1,
axis_e: attributes.spirit * 4 + attributes.intelligence * 4 + attributes.agility * 1,
axis_f: attributes.spirit * 7 + attributes.strength * 3,
});
}
function uniqueEvidence(evidence: RoleAttributeEvidence[]) {
const seen = new Set<string>();
return evidence.filter(entry => {
const key = `${entry.slotId}:${entry.reason}`;
if (seen.has(key)) return false;
seen.add(key);
return true;
});
}
export function buildRoleAttributeProfileFromLegacyData({
entityId,
schema,
legacyAttributes,
textBlocks,
extraWeights,
}: {
entityId: string;
schema: WorldAttributeSchema;
legacyAttributes?: LegacyAttributeSet | null;
textBlocks?: Array<string | null | undefined>;
extraWeights?: AttributeVector;
}) {
const evidence: RoleAttributeEvidence[] = [];
const seed = legacyAttributes
? buildLegacyAttributeSeed(legacyAttributes)
: buildDefaultAxisVector({
axis_a: 58,
axis_b: 58,
axis_c: 58,
axis_d: 58,
axis_e: 58,
axis_f: 58,
});
const sourceText = (textBlocks ?? []).filter(Boolean).join(' ');
if (sourceText) {
applyKeywordWeights(seed, sourceText, evidence, schema);
}
WORLD_ATTRIBUTE_SLOT_IDS.forEach(slotId => {
seed[slotId] = (seed[slotId] ?? 0) + (extraWeights?.[slotId] ?? 0);
});
const fallbackEvidence = uniqueEvidence(evidence).slice(0, 4);
const profile = ensureRoleAttributeProfile(
{
schemaId: schema.id,
values: seed,
},
schema,
{
fallbackValues: seed,
fallbackEvidence,
},
);
const trace: AttributeMigrationTrace = {
sourceCharacterId: entityId,
schemaId: schema.id,
oldAttributes: legacyAttributes ?? undefined,
inferredReasons: fallbackEvidence.map(entry => entry.reason),
fallbackUsed: false,
};
return {
profile,
trace,
};
}
export function buildCharacterAttributeProfile(character: Character, schema: WorldAttributeSchema) {
return buildRoleAttributeProfileFromLegacyData({
entityId: character.id,
schema,
legacyAttributes: character.attributes,
textBlocks: [
character.title,
character.description,
character.backstory,
character.personality,
...(character.combatTags ?? []),
...character.skills.map(skill => `${skill.name} ${skill.style} ${skill.delivery ?? ''}`),
],
}).profile;
}
export function buildCustomWorldPlayableNpcAttributeProfile(
npc: CustomWorldPlayableNpc,
schema: WorldAttributeSchema,
templateAttributes?: LegacyAttributeSet,
) {
return buildRoleAttributeProfileFromLegacyData({
entityId: npc.id,
schema,
legacyAttributes: templateAttributes,
textBlocks: [
npc.title,
npc.role,
npc.description,
npc.backstory,
npc.personality,
npc.motivation,
npc.combatStyle,
...(npc.relationshipHooks ?? []),
...(npc.tags ?? []),
],
}).profile;
}
export function buildCustomWorldStoryNpcAttributeProfile(npc: CustomWorldNpc, schema: WorldAttributeSchema) {
return buildRoleAttributeProfileFromLegacyData({
entityId: npc.id,
schema,
textBlocks: [
npc.title,
npc.role,
npc.description,
npc.backstory,
npc.personality,
npc.motivation,
npc.combatStyle,
...(npc.relationshipHooks ?? []),
...(npc.tags ?? []),
],
}).profile;
}
export function buildMonsterAttributeProfile(
monster: {
id: string;
name: string;
description: string;
introAction: string;
combatTags?: string[];
habitatTags?: string[];
baseStats: { attackRange: number; speed: number; maxHp: number };
},
schema: WorldAttributeSchema,
) {
return buildRoleAttributeProfileFromLegacyData({
entityId: monster.id,
schema,
textBlocks: [
monster.name,
monster.description,
monster.introAction,
...(monster.combatTags ?? []),
...(monster.habitatTags ?? []),
],
extraWeights: buildDefaultAxisVector({
axis_a: monster.baseStats.maxHp >= 150 ? 24 : 0,
axis_b: monster.baseStats.speed >= 7 ? 22 : 0,
axis_d: monster.baseStats.attackRange >= 1.5 ? 18 : 6,
axis_f: monster.baseStats.maxHp >= 180 ? 26 : 10,
}),
}).profile;
}
export function buildItemAttributeResonance(
item: Pick<InventoryItem | CustomWorldItem, 'category' | 'name' | 'description'> & {
tags?: string[];
buildProfile?: { resonanceVector?: AttributeVector | null } | null;
},
): ItemAttributeResonance {
const directVector = item.buildProfile?.resonanceVector;
if (directVector) {
return {
resonanceVector: directVector,
explanation: `${item.name}显式声明了属性共振向量。`,
};
}
const source = `${item.category} ${item.name} ${item.description ?? ''} ${(item.tags ?? []).join(' ')}`;
const vector = buildDefaultAxisVector({
axis_a: /||||||/u.test(source) ? 0.28 : 0.08,
axis_b: /||||||/u.test(source) ? 0.26 : 0.08,
axis_c: /||||||/u.test(source) ? 0.26 : 0.08,
axis_d: /|||||/u.test(source) ? 0.24 : 0.08,
axis_e: /||||||/u.test(source) ? 0.22 : 0.08,
axis_f: /||||||/u.test(source) ? 0.24 : 0.08,
});
return {
resonanceVector: vector,
explanation: `${item.name}的共振由品类与文本语义推断。`,
};
}
export function buildSkillAttributeProfile(skill: CharacterSkillDefinition) {
return {
intentVector: SKILL_STYLE_VECTORS[skill.style] ?? SKILL_STYLE_VECTORS.steady,
};
}