zenno/doc/frbm: update with charts &c.

This commit is contained in:
2026-05-23 16:18:24 -04:00
parent 3ab17cf9b0
commit bd99cfaa6d
7 changed files with 678 additions and 171 deletions

View File

@@ -0,0 +1,27 @@
<script lang="ts">
import { skills, ZERO_SKILL, type Skill } from "./data/skill";
interface CommonProps {
hint?: string;
mention?: boolean;
}
type Props = CommonProps & ({skill: number, name?: never} | {name: string, skill?: never});
let {hint, mention, skill, name}: Props = $props();
const s: Readonly<Skill> = $derived.by(() => {
const l = skill != null ? skills.global.filter((s) => s.skill_id === skill) : skills.global.filter((s) => s.name.includes(name!));
if (name != null) {
console.warn(`skills specified as ${name} (${hint}):`, l);
}
if (l.length === 0) {
return ZERO_SKILL;
}
return l[0];
});
const spanClass = $derived(mention ? 'italic' : 'font-bold')
</script>
<span class={spanClass}>{s.name}</span>

173
zenno/src/lib/data/skill.ts Normal file
View File

@@ -0,0 +1,173 @@
import skillGlobal from '../../../../global/skill.json'
import groupGlobal from '../../../../global/skill-group.json'
/**
* Skill data.
*/
export interface Skill {
/**
* Skill ID.
*/
skill_id: number;
/**
* Regional skill name.
*/
name: string;
/**
* Regional skil description.
*/
description: string;
/**
* Skill group ID.
*/
group: number;
/**
* Skill rarity. 3-5 are uniques for various star levels.
*/
rarity: 1 | 2 | 3 | 4 | 5;
/**
* Upgrade position within the skill's group.
* -1 is for negative (purple) skills.
*/
group_rate: 1 | 2 | 3 | -1;
/**
* Grade value, or the amount of rating gained for having the skill with
* appropriate aptitude.
*/
grade_value?: number;
/**
* Whether the skill requires a wit check.
*/
wit_check: boolean;
/**
* Conditions and results of skill activation.
*/
activations: Activation[];
/**
* Name of the Uma which owns this skill as a unique, if applicable.
*/
unique_owner?: string;
/**
* SP cost to purchase the skill, if applicable.
*/
sp_cost?: number;
/**
* Skill icon ID.
*/
icon_id: number;
}
/**
* Conditions and results of skill activation.
*/
export interface Activation {
/**
* Precondition which must be satisfied before the condition is checked.
*/
precondition?: string;
/**
* Activation conditions.
*/
condition: string;
/**
* Skill duration in ten thousandths of a second.
* Generally undefined for activations which only affect HP.
*/
duration?: number;
/**
* Special skill duration scaling mode.
*/
dur_scale: 1 | 2 | 3 | 4 | 5 | 7;
/**
* Skill cooldown in ten thousandths of a second.
* A value of 5000000 indicates that the cooldown is forever.
* Generally undefined for passive skills.
*/
cooldown?: number;
/**
* Results applied when the skill's conditions are met.
*/
abilities: Ability[];
}
/**
* Effects applied when a skill activates.
*/
export interface Ability {
/**
* Race mechanic affected by the ability.
*/
type: 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 13 | 21 | 27 | 28 | 31 | 35;
/**
* Special scaling type of the skill value.
*/
value_usage: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 19 | 20 | 22 | 23 | 24 | 25;
/**
* Amount that the skill modifies the race mechanic in ten thousandths of
* whatever is the appropriate unit.
*/
value: number;
/**
* Selector for horses targeted by the ability.
*/
target: 1 | 2 | 4 | 7 | 9 | 10 | 11 | 18 | 19 | 20 | 21 | 22 | 23;
/**
* Argument value for the ability target, when appropriate.
*/
target_value?: number;
}
/**
* Skill groups.
* Skills in a skill group replace each other when purchased.
*
* As a special case, horsegen lists both unique skills and their inherited
* versions in the skill groups for both.
*/
export interface SkillGroup {
/**
* Skill group ID.
*/
skill_group: number;
/**
* Base skill in the skill group, if any.
* Either a common (white) skill or an Uma's own unique.
*
* Some skill groups, e.g. for G1 Averseness, have no base skill.
*/
skill1?: number;
/**
* First upgraded version of a skill, if any.
* A rare (gold) skill, double circle skill, or an inherited unique skill.
*/
skill2?: number;
/**
* Highest upgraded version of a skill, if any.
* Gold version of a skill with a double circle version.
*/
skill3?: number;
/**
* Negative (purple) version of a skill, if any.
*/
skill_bad?: number;
}
export const skills = {
global: skillGlobal as Skill[],
} as const;
export const skillGroups = {
global: groupGlobal as SkillGroup[],
} as const;
export const ZERO_SKILL: Readonly<Skill> = {
skill_id: 0,
name: "invalid skill",
description: "an invalid skill was specified",
group: 0,
rarity: 1,
group_rate: 1,
wit_check: false,
activations: [],
icon_id: 0,
} as const;

9
zenno/src/lib/prob.ts Normal file
View File

@@ -0,0 +1,9 @@
import * as math from "mathjs";
export function binomPMF(p: number, n: number, k: number): number {
// Operate in log domain for precision.
const lc = math.lgamma(n+1) - math.lgamma(k+1) - math.lgamma(n-k+1);
const lpk = k * math.log(p);
const lr = (n - k) * math.log(1 - p);
return math.exp(lc + lpk + lr);
}

View File

@@ -1,6 +1,8 @@
// Umamusume race mechanics adapted from KuromiAK's doc:
// https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?usp=sharing
import { binomPMF } from "./prob";
/**
* Fundamental stats of umas.
*/
@@ -246,6 +248,17 @@ export function spotStruggleDuration(gutsStat: number, frontAptitude: AptitudeLe
return Math.sqrt(700 * gutsStat) * 0.012 * strategyProficiencyMod[frontAptitude];
}
/**
* Calculate the speed modifier for running uphill.
* Contrary to the race mechanics document, this is expressed as a negative number.
* @param powerStat Final power stat
* @param slopePer Slope percentage, generally one of 0.5, 1.0, 1.5, or 2.0
* @returns Speed modifier for running uphill, a negative value
*/
export function uphillMod(powerStat: number, slopePer: number): number {
return slopePer * -200/powerStat;
}
/**
* Calculate the forward speed boost given when moving lanewise while a skill
* that grants a lane change speed boost is active.
@@ -256,6 +269,18 @@ export function moveLaneModifier(powerStat: number): number {
return Math.sqrt(0.0002 * powerStat);
}
/**
* Calculate the probability of n of N skills activating.
* @param baseWit Base wit stat
* @param N Number of skills available, default 1
* @param n Number of skills activating, default 1
* @returns Probability of exactly n skills out of N passing wit checks
*/
export function skillWitCheck(baseWit: number, N?: number, n?: number): number {
const p = Math.max(0.2, 1 - 90/baseWit);
return binomPMF(p, N ?? 1, n ?? 1);
}
/**
* Calculate a skill's actual duration scaled to race length.
* @param baseDur Skill's listed duration in s
@@ -292,3 +317,12 @@ export function speedGain(speedBonus: number, dur: number, accel: number | null,
// speedBonus*(dur-accelTime) + speedBonus*accelTime/2 + speedBonus*decelTime/2
return speedBonus * (dur + 0.5 * (decelTime - accelTime));
}
/**
* Calculate the chance to enter downhill accel mode each second while running downhill.
* @param witStat Final wit stat, including style aptitude modifier
* @returns Probability each eligible tick to enter downhill accel mode
*/
export function downhillAccelEnterChance(witStat: number): number {
return witStat * 0.0004;
}