184
src/data/buildTagAttributeAffinity.ts
Normal file
184
src/data/buildTagAttributeAffinity.ts
Normal file
@@ -0,0 +1,184 @@
|
||||
import type {AttributeVector, WorldAttributeSchema, WorldAttributeSlot} from '../types';
|
||||
import {normalizeAttributeVector} from './attributeValidation';
|
||||
|
||||
type BuildTagAttributeAffinityMap = Record<string, AttributeVector>;
|
||||
|
||||
type SemanticAxisRule = {
|
||||
axisId: string;
|
||||
patterns: RegExp[];
|
||||
};
|
||||
|
||||
const SEMANTIC_SLOT_RULES: SemanticAxisRule[] = [
|
||||
{
|
||||
axisId: 'axis_a',
|
||||
patterns: [/骨|躯|甲|壳|体|锋|承压|抗压|结构|根基|底子|扛住|稳固|硬碰|机锋|潮骨|界躯|道骨|骨势/u],
|
||||
},
|
||||
{
|
||||
axisId: 'axis_b',
|
||||
patterns: [/步|身法|位移|换位|机动|迅|闪|轻灵|抢位|转场|穿梭|步准|浪步|裂步|灵行/u],
|
||||
},
|
||||
{
|
||||
axisId: 'axis_c',
|
||||
patterns: [/识|察|算|谋|阵|符|术|洞察|解析|看穿|辨认|因果|规律|算识|舟识|界识|识海|眼脉/u],
|
||||
},
|
||||
{
|
||||
axisId: 'axis_d',
|
||||
patterns: [/压|魄|焰|胆|威|决|推进|强推|压迫|定调|逼出|逆转|潮压|潮魄|界压|劫纹|心焰/u],
|
||||
},
|
||||
{
|
||||
axisId: 'axis_e',
|
||||
patterns: [/缘|契|盟|信|交|助|协|共鸣|共振|联结|牵引|交换|安抚|协频|契汐|缚契|尘缘|心契/u],
|
||||
},
|
||||
{
|
||||
axisId: 'axis_f',
|
||||
patterns: [/息|稳|续|回|养|持久|恢复|调息|循环|续航|回稳|稳态|续载|回澜|回脉|玄息/u],
|
||||
},
|
||||
];
|
||||
|
||||
function roundNumber(value: number, digits = 4) {
|
||||
const factor = 10 ** digits;
|
||||
return Math.round(value * factor) / factor;
|
||||
}
|
||||
|
||||
function affinity(
|
||||
strength: number,
|
||||
agility: number,
|
||||
intelligence: number,
|
||||
spirit: number,
|
||||
): AttributeVector {
|
||||
return {
|
||||
axis_a: roundNumber(strength * 0.72 + spirit * 0.28),
|
||||
axis_b: roundNumber(agility * 0.88 + intelligence * 0.12),
|
||||
axis_c: roundNumber(intelligence * 0.78 + agility * 0.22),
|
||||
axis_d: roundNumber(strength * 0.62 + agility * 0.18 + intelligence * 0.2),
|
||||
axis_e: roundNumber(spirit * 0.72 + intelligence * 0.28),
|
||||
axis_f: roundNumber(spirit * 0.74 + strength * 0.26),
|
||||
};
|
||||
}
|
||||
|
||||
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 = {};
|
||||
|
||||
SEMANTIC_SLOT_RULES.forEach((rule, ruleIndex) => {
|
||||
let score = 0;
|
||||
|
||||
if (slot.slotId === rule.axisId) {
|
||||
score += 2.2;
|
||||
} else if (slot.slotId === `axis_${String.fromCharCode(97 + ruleIndex)}`) {
|
||||
score += 1.2;
|
||||
}
|
||||
|
||||
score += rule.patterns.reduce((sum, pattern) => sum + (pattern.test(sourceText) ? 1 : 0), 0);
|
||||
|
||||
if (score > 0) {
|
||||
semanticVector[rule.axisId] = roundNumber(score, 4);
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(semanticVector).length === 0) {
|
||||
const fallbackAxisId = SEMANTIC_SLOT_RULES[index]?.axisId ?? slot.slotId;
|
||||
semanticVector[fallbackAxisId] = 1;
|
||||
}
|
||||
|
||||
return normalizeAttributeVector(
|
||||
semanticVector,
|
||||
SEMANTIC_SLOT_RULES.map(rule => rule.axisId),
|
||||
);
|
||||
}
|
||||
|
||||
function calculateVectorSimilarity(left: AttributeVector, right: AttributeVector) {
|
||||
return roundNumber(
|
||||
Object.keys({...left, ...right}).reduce(
|
||||
(sum, key) => sum + ((left[key] ?? 0) * (right[key] ?? 0)),
|
||||
0,
|
||||
),
|
||||
4,
|
||||
);
|
||||
}
|
||||
|
||||
function resolveSchemaAwareAffinity(tagAffinity: AttributeVector, schema: WorldAttributeSchema) {
|
||||
const rawSimilarity = Object.fromEntries(
|
||||
schema.slots.map((slot, index) => [
|
||||
slot.slotId,
|
||||
calculateVectorSimilarity(tagAffinity, buildSlotSemanticVector(slot, index)),
|
||||
]),
|
||||
);
|
||||
|
||||
return {
|
||||
rawSimilarity,
|
||||
normalizedSimilarity: normalizeAttributeVector(
|
||||
rawSimilarity,
|
||||
schema.slots.map(slot => slot.slotId),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export const BUILD_TAG_ATTRIBUTE_AFFINITY: BuildTagAttributeAffinityMap = {
|
||||
quickblade: affinity(0.35, 1, 0.1, 0.05),
|
||||
combo: affinity(0.3, 0.92, 0.18, 0.08),
|
||||
dash: affinity(0.45, 0.95, 0, 0),
|
||||
pursuit: affinity(0.38, 0.88, 0.08, 0.02),
|
||||
swiftstrike: affinity(0.22, 0.98, 0.12, 0.04),
|
||||
ranged: affinity(0.18, 0.82, 0.34, 0.08),
|
||||
guerrilla: affinity(0.24, 0.9, 0.28, 0.12),
|
||||
mobility: affinity(0.18, 1, 0.08, 0.08),
|
||||
windrun: affinity(0.08, 1, 0.1, 0.1),
|
||||
heavyhit: affinity(1, 0.28, 0.02, 0.04),
|
||||
burst: affinity(0.72, 0.58, 0.36, 0.08),
|
||||
armorbreak: affinity(0.92, 0.28, 0.08, 0.02),
|
||||
pressure: affinity(0.62, 0.64, 0.1, 0.08),
|
||||
bloodrush: affinity(0.84, 0.54, 0.04, 0.18),
|
||||
guard: affinity(0.7, 0.18, 0.04, 0.72),
|
||||
barrier: affinity(0.48, 0.08, 0.2, 0.92),
|
||||
heavyarmor: affinity(0.88, 0.04, 0.02, 0.54),
|
||||
counter: affinity(0.66, 0.46, 0.14, 0.36),
|
||||
banish: affinity(0.24, 0.06, 0.54, 0.88),
|
||||
caster: affinity(0, 0.1, 1, 0.6),
|
||||
mana: affinity(0.02, 0.08, 0.94, 0.74),
|
||||
thunder: affinity(0.06, 0.24, 0.96, 0.42),
|
||||
formation: affinity(0.08, 0.12, 0.82, 0.96),
|
||||
control: affinity(0.12, 0.34, 0.78, 0.72),
|
||||
overload: affinity(0.14, 0.18, 0.92, 0.38),
|
||||
heal: affinity(0.02, 0.08, 0.56, 1),
|
||||
support: affinity(0.14, 0.14, 0.58, 0.98),
|
||||
sustain: affinity(0.34, 0.18, 0.22, 0.9),
|
||||
fate: affinity(0.08, 0.22, 0.72, 0.84),
|
||||
fortune: affinity(0.06, 0.34, 0.7, 0.78),
|
||||
cooldown: affinity(0.04, 0.46, 0.82, 0.4),
|
||||
command: affinity(0.38, 0.26, 0.72, 0.82),
|
||||
balanced: affinity(0.58, 0.58, 0.58, 0.58),
|
||||
craft: affinity(0.24, 0.16, 0.74, 0.5),
|
||||
alchemy: affinity(0.08, 0.16, 0.84, 0.76),
|
||||
vanguard: affinity(0.82, 0.44, 0.08, 0.34),
|
||||
berserk: affinity(0.98, 0.42, 0, 0.22),
|
||||
spellblade: affinity(0.42, 0.42, 0.88, 0.38),
|
||||
paladin: affinity(0.58, 0.12, 0.42, 0.96),
|
||||
fortress: affinity(0.94, 0.04, 0.08, 0.82),
|
||||
starter: affinity(0.42, 0.42, 0.42, 0.42),
|
||||
};
|
||||
|
||||
export function getBuildTagAttributeAffinity(tagId: string, schema?: WorldAttributeSchema) {
|
||||
const semanticAffinity = BUILD_TAG_ATTRIBUTE_AFFINITY[tagId] ?? affinity(0.4, 0.4, 0.4, 0.4);
|
||||
|
||||
if (!schema) {
|
||||
return semanticAffinity;
|
||||
}
|
||||
|
||||
return resolveSchemaAwareAffinity(semanticAffinity, schema).normalizedSimilarity;
|
||||
}
|
||||
|
||||
export function getBuildTagAttributeSimilarityProfile(tagId: string, schema: WorldAttributeSchema) {
|
||||
const semanticAffinity = BUILD_TAG_ATTRIBUTE_AFFINITY[tagId] ?? affinity(0.4, 0.4, 0.4, 0.4);
|
||||
return resolveSchemaAwareAffinity(semanticAffinity, schema);
|
||||
}
|
||||
Reference in New Issue
Block a user