import type { AttributeVector, Character, CombatActionAttributeProfile, CustomWorldProfile, RoleActionDefinition, RoleAttributeProfile, RoleRelationState, WorldAttributeSchema, WorldAttributeSlot, WorldType, } from '../types'; import {WORLD_ATTRIBUTE_SLOT_IDS} from '../types'; import { resolveRelationStanceFromAffinity } from './affinityLevels'; import {normalizeAttributeVector, roundNumber} from './attributeValidation'; import {getWorldAttributeSchema} from './worldAttributeSchemas'; export function resolveRelationStance(affinity: number): RoleRelationState['stance'] { return resolveRelationStanceFromAffinity(affinity); } export function buildRelationState(affinity: number): RoleRelationState { return { affinity, stance: resolveRelationStance(affinity), }; } export function resolveAttributeSchema( worldType: WorldType | null | undefined, customWorldProfile?: CustomWorldProfile | null, ) { return getWorldAttributeSchema(worldType, customWorldProfile); } export function resolveCharacterAttributeProfile( character: Character, worldType: WorldType | null | undefined, customWorldProfile?: CustomWorldProfile | null, ) { void customWorldProfile; if (worldType && character.attributeProfiles?.[worldType]) { return character.attributeProfiles[worldType] ?? character.attributeProfile; } if (worldType === 'CUSTOM' && character.attributeProfiles?.CUSTOM) { return character.attributeProfiles.CUSTOM; } return character.attributeProfile; } export function getAttributeSlotValue(profile: RoleAttributeProfile | null | undefined, slotId: string) { return profile?.values?.[slotId] ?? 0; } export function getNormalizedAttributeWeights( profile: RoleAttributeProfile | null | undefined, schema: WorldAttributeSchema, ) { return normalizeAttributeVector(profile?.values ?? {}, schema.slots.map(slot => slot.slotId)); } export function scoreAttributeFit( profile: RoleAttributeProfile | null | undefined, vector: AttributeVector | null | undefined, schema: WorldAttributeSchema, ) { const weights = getNormalizedAttributeWeights(profile, schema); const normalizedVector = normalizeAttributeVector(vector ?? {}, schema.slots.map(slot => slot.slotId)); return roundNumber( schema.slots.reduce( (sum, slot) => sum + (weights[slot.slotId] ?? 0) * (normalizedVector[slot.slotId] ?? 0), 0, ), 4, ); } export function scoreActionMatch( actorProfile: RoleAttributeProfile | null | undefined, action: | Pick | Pick, schema: WorldAttributeSchema, options: { targetProfile?: RoleAttributeProfile | null; actorCoefficient?: number; targetCoefficient?: number; relationModifier?: number; contextModifier?: number; } = {}, ) { const actorFit = scoreAttributeFit(actorProfile, action.intentVector, schema); const targetResistance = action.resistVector ? scoreAttributeFit(options.targetProfile, action.resistVector, schema) : 0; const actorCoefficient = options.actorCoefficient ?? 1; const targetCoefficient = options.targetCoefficient ?? 1; const baseScore = 'baseScore' in action ? action.baseScore : 0; return roundNumber( baseScore + actorFit * actorCoefficient - targetResistance * targetCoefficient + (options.relationModifier ?? 0) + (options.contextModifier ?? 0), 4, ); } export function getSortedAttributeEntries( profile: RoleAttributeProfile | null | undefined, schema: WorldAttributeSchema, ) { return [...schema.slots] .map(slot => ({ slot, value: getAttributeSlotValue(profile, slot.slotId), })) .sort((left, right) => right.value - left.value); } export function describeTopAttributes( profile: RoleAttributeProfile | null | undefined, schema: WorldAttributeSchema, limit = 3, ) { return getSortedAttributeEntries(profile, schema) .slice(0, limit) .map(entry => `${entry.slot.name}${entry.value}`); } export function formatAttributeList( profile: RoleAttributeProfile | null | undefined, schema: WorldAttributeSchema, limit = schema.slots.length, ) { return getSortedAttributeEntries(profile, schema) .slice(0, limit) .map(entry => ({ slot: entry.slot, value: entry.value, })); } export function getLeadingAttributeSlot( profile: RoleAttributeProfile | null | undefined, schema: WorldAttributeSchema, ) { return getSortedAttributeEntries(profile, schema)[0]?.slot ?? schema.slots[0] ?? null; } export function buildSchemaSummary(schema: WorldAttributeSchema, limit = 6) { return schema.slots.slice(0, limit).map(slot => ({ name: slot.name, })); } export function getSlotById(schema: WorldAttributeSchema, slotId: string): WorldAttributeSlot | null { return schema.slots.find(slot => slot.slotId === slotId) ?? null; } export function buildDefaultAxisVector(overrides: Partial>) { return WORLD_ATTRIBUTE_SLOT_IDS.reduce((result, slotId) => { result[slotId] = overrides[slotId] ?? 0; return result; }, {}); }