zenno/lib: legacy and affinity stuff

This commit is contained in:
2026-04-03 11:42:13 -04:00
parent c14850a048
commit 9532e0ec89
3 changed files with 182 additions and 0 deletions

View File

@@ -0,0 +1,35 @@
import globalJSON from '../../../../global/affinity.json';
/**
* Precomputed character pair and trio affinity.
*/
export interface Affinity {
/**
* First character in the relation.
*/
chara_a: number;
/**
* Second character in the relation.
* chara_a < chara_b is an invariant.
*/
chara_b: number;
/**
* Third character in the relation, if it is a trio relation.
* If defined, chara_b < chara_c is an invariant.
*/
chara_c?: number;
/**
* Total base compatibility between characters in the relation.
*/
affinity: number;
}
export const affinity = {
global: globalJSON as Affinity[],
} as const;
export function lookup(aff: Affinity[], chara_a: number, chara_b: number, chara_c?: number): number {
const [a, b, c] = [chara_a, chara_b, chara_c ?? Infinity].sort((a, b) => a - b);
const r = aff.find((v) => v.chara_a === a && v.chara_b === b && (v.chara_c ?? Infinity) === c);
return r?.affinity ?? 0;
}

75
zenno/src/lib/data/uma.ts Normal file
View File

@@ -0,0 +1,75 @@
import globalJSON from '../../../../global/uma.json';
/**
* Uma or character card definitions.
*/
export interface Uma {
/**
* Uma ID.
*/
chara_card_id: number;
/**
* Character ID that the Uma is a variant of.
*/
chara_id: number;
/**
* Regional name of the Uma, comprised of the variant name and the character name.
* E.g. "[Special Dreamer] Special Week".
*/
name: string;
/**
* Regional variant name.
* E.g. "[Special Dreamer]".
*/
variant: string;
sprint: AptitudeLevel;
mile: AptitudeLevel;
medium: AptitudeLevel;
long: AptitudeLevel;
front: AptitudeLevel;
pace: AptitudeLevel;
late: AptitudeLevel;
end: AptitudeLevel;
turf: AptitudeLevel;
dirt: AptitudeLevel;
/**
* ID of the Uma's unique skill.
*/
unique: number;
/**
* ID of the Uma's first built-in skill.
*/
skill1: number;
/**
* ID of the Uma's second built-in skill.
*/
skill2: number;
/**
* ID of the Uma's third built-in skill.
*/
skill3: number;
/**
* ID of the skill unlocked at potential level 2.
*/
skill_pl2: number;
/**
* ID of the skill unlocked at potential level 3.
*/
skill_pl3: number;
/**
* ID of the skill unlocked at potential level 4.
*/
skill_pl4: number;
/**
* ID of the skill unlocked at potential level 5.
*/
skill_pl5: number;
}
export type AptitudeLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
export const uma = {
global: globalJSON as Uma[],
};

72
zenno/src/lib/legacy.ts Normal file
View File

@@ -0,0 +1,72 @@
import { affinity, lookup } from './data/affinity';
import { uma, type Uma } from './data/uma';
/**
* A legacy, or parents and grandparents.
* Defined as an applicative over generic types to reduce the API surface.
* @see mapLegacy
*/
export interface Legacy<T> {
p1: T;
s11: T;
s12: T;
p2: T;
s21: T;
s22: T;
}
/**
* Applicative fmap for legacies.
* @param l Input legacy
* @param f Mapping function
* @returns Transformed legacy
*/
export function mapLegacy<U, V>(l: Legacy<U>, f: (u: U) => V): Legacy<V> {
return {
p1: f(l.p1),
s11: f(l.s11),
s12: f(l.s12),
p2: f(l.p2),
s21: f(l.s21),
s22: f(l.s22),
}
}
/**
* A veteran, or the result of a completed career.
*/
export interface Veteran {
uma: number;
sparks: number[];
saddles: number[];
}
function findUma(umas: Uma[], u: number): Uma | null {
return umas.find((v) => v.chara_card_id === u) ?? null;
}
/**
* Compute individual affinities for a legacy using the global region ruleset.
* @param trainee Uma (not character) ID of the trainee Uma
* @param legacy Legacy veterans
* @returns Individual affinities
*/
export function globalAffinity(trainee: number, legacy: Legacy<Veteran>): Legacy<number> {
const t = findUma(uma.global, trainee)?.chara_id ?? 0;
const charas = mapLegacy(legacy, (u) => findUma(uma.global, u.uma)?.chara_id ?? 0);
const saddles = mapLegacy(legacy, (u) => new Set(u.saddles));
const aff = affinity.global;
const pp = lookup(aff, charas.p1, charas.p2);
const s11 = lookup(aff, t, charas.p1, charas.s11) + saddles.p1.intersection(saddles.s11).size;
const s12 = lookup(aff, t, charas.p1, charas.s12) + saddles.p1.intersection(saddles.s12).size;
const s21 = lookup(aff, t, charas.p2, charas.s21) + saddles.p2.intersection(saddles.s21).size;
const s22 = lookup(aff, t, charas.p2, charas.s22) + saddles.p2.intersection(saddles.s22).size;
return {
p1: lookup(aff, t, charas.p1) + pp + s11 + s12,
s11,
s12,
p2: lookup(aff, t, charas.p2) + pp + s21 + s22,
s21,
s22,
};
}