zenno/doc/frbm: chart and calcs for section speed
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
import * as Plot from '@observablehq/plot';
|
||||
import * as d3 from 'd3';
|
||||
import { Stat } from './race';
|
||||
import type { ComputedSeries, HorizontalRule } from './chart';
|
||||
import type { ComputedAreas, ComputedSeries, HorizontalRule } from './chart';
|
||||
import type { ClassValue } from 'svelte/elements';
|
||||
import type { Attachment } from 'svelte/attachments';
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
/** The stat the chart shows. */
|
||||
stat: Stat;
|
||||
/** Series to show in the chart. */
|
||||
y: ComputedSeries | Array<ComputedSeries | null>;
|
||||
y?: ComputedSeries | Array<ComputedSeries | null>;
|
||||
/** Areas to show in the chart. */
|
||||
yArea?: ComputedAreas | Array<ComputedAreas | null>;
|
||||
/** Label for the dependent variable. */
|
||||
yLabel: string;
|
||||
/**
|
||||
@@ -37,7 +39,7 @@
|
||||
plotOptions?: Omit<Plot.PlotOptions, 'marks' | 'x' | 'y'>;
|
||||
}
|
||||
|
||||
let { stat, y, yLabel, range, xRange, xRule = [], yRule = [], class: className, plotOptions = {} }: Props = $props();
|
||||
let { stat, y = [], yArea = [], yLabel, range, xRange, xRule = [], yRule = [], class: className, plotOptions = {} }: Props = $props();
|
||||
|
||||
let width = $state(0);
|
||||
let height = $state(0);
|
||||
@@ -60,7 +62,9 @@
|
||||
const thrX = 1200;
|
||||
|
||||
const series = $derived([y].flat(1).filter((s) => s != null));
|
||||
const areas = $derived([yArea].flat(1).filter((s) => s != null));
|
||||
const vals = $derived(series.flatMap(({ y, label }) => xVal.map((x) => ({ x, y: y(x), label }))));
|
||||
const areaVals = $derived(areas.flatMap(({ y1, y2, label }) => xVal.map((x) => ({ x, y1: y1(x), y2: y2(x), label }))));
|
||||
const yRange: [number, number] = $derived.by(() => {
|
||||
if (range != null) {
|
||||
if (range.length === 2) {
|
||||
@@ -103,8 +107,11 @@
|
||||
Plot.ruleY(yRule, { y: 'y', strokeOpacity: 0.75 }),
|
||||
Plot.tip(yRule, { x: xMax, y: 'y', title: 'label', anchor: 'top-right', className: 'plot-tip' }),
|
||||
Plot.frame(),
|
||||
Plot.areaY(areaVals, { x: 'x', y1: 'y1', y2: 'y2', fill: 'label', fillOpacity: 0.5 }),
|
||||
Plot.line(areaVals, { x: 'x', y: 'y1', stroke: 'label' }),
|
||||
Plot.line(areaVals, { x: 'x', y: 'y2', stroke: 'label' }),
|
||||
Plot.line(vals, { x: 'x', y: 'y', stroke: 'label', strokeWidth: 3 }),
|
||||
Plot.tip(vals, Plot.pointerY({ x: 'x', y: 'y', stroke: 'label', className: 'plot-tip' })),
|
||||
vals.length > 0 ? Plot.tip(vals, Plot.pointerY({ x: 'x', y: 'y', stroke: 'label', className: 'plot-tip' })) : null,
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -3,6 +3,12 @@ export interface ComputedSeries {
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface ComputedAreas {
|
||||
y1: (x: number) => number;
|
||||
y2: (x: number) => number;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface HorizontalRule {
|
||||
y: number;
|
||||
label: string;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Umamusume race mechanics adapted from KuromiAK's doc:
|
||||
// https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?usp=sharing
|
||||
|
||||
import * as math from "mathjs";
|
||||
import { binomPMF } from "./prob";
|
||||
|
||||
/**
|
||||
@@ -43,6 +44,17 @@ export enum RunningStyle {
|
||||
GreatEscape,
|
||||
}
|
||||
|
||||
/**
|
||||
* Running styles with their proper names, for easy iterating.
|
||||
*/
|
||||
export const RUNNING_STYLES = [
|
||||
['Front Runner', RunningStyle.FrontRunner],
|
||||
['Pace Chaser', RunningStyle.PaceChaser],
|
||||
['Late Surger', RunningStyle.LateSurger],
|
||||
['End Closer', RunningStyle.EndCloser],
|
||||
['Great Escape', RunningStyle.GreatEscape],
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Aptitude or proficiency levels.
|
||||
*/
|
||||
@@ -95,6 +107,40 @@ const speedStrategyPhaseCoeff = [
|
||||
|
||||
const distanceProficiencyMod = [0.1, 0.2, 0.4, 0.6, 0.8, 0.9, 1.0, 1.05] as const;
|
||||
|
||||
/**
|
||||
* Calculate the range of section speed values for a horse in early or mid race.
|
||||
* @param raceLen Length of the race in meters
|
||||
* @param baseWit Base wit, after mood but before other modifiers
|
||||
* @param witStat Final wit stat, including strategy proficiency and skills
|
||||
* @param style Horse's running style
|
||||
* @param phase Phase of the current section
|
||||
*/
|
||||
export function sectionSpeed(raceLen: number, baseWit: number, witStat: number, style: RunningStyle, phase: Exclude<Phase, Phase.LateRace>): [number, number];
|
||||
/**
|
||||
* Calculate the range of section speed values for a horse not spurting during late race.
|
||||
* @param raceLen Length of the race in meters
|
||||
* @param speedStat Final speed stat
|
||||
* @param baseWit Base wit, after mood but before other modifiers
|
||||
* @param witStat Final wit stat, including strategy proficiency and skills
|
||||
* @param style Horse's running style
|
||||
* @param distance Hores's distance proficiency aptitude
|
||||
* @param phase Phase.LateRace
|
||||
*/
|
||||
export function sectionSpeed(raceLen: number, speedStat: number, baseWit: number, witStat: number, style: RunningStyle, distance: AptitudeLevel, phase: Phase.LateRace): [number, number];
|
||||
export function sectionSpeed(raceLen: number, speedStatOrBaseWit: number, baseWitOrWitStat: number, witStatOrStyle: number | RunningStyle, styleOrPhase: RunningStyle | Phase, distance?: AptitudeLevel, lateRace?: Phase.LateRace): [number, number] {
|
||||
const speedStat = lateRace !== undefined ? speedStatOrBaseWit : 0;
|
||||
const baseWit = lateRace !== undefined ? baseWitOrWitStat : speedStatOrBaseWit;
|
||||
const witStat = lateRace !== undefined ? witStatOrStyle : baseWitOrWitStat;
|
||||
const style = lateRace !== undefined ? styleOrPhase as RunningStyle : witStatOrStyle as RunningStyle;
|
||||
const phase = lateRace !== undefined ? lateRace : styleOrPhase as Phase;
|
||||
const base = baseSpeed(raceLen);
|
||||
const baseTarget = base * speedStrategyPhaseCoeff[style][phase];
|
||||
const late = phase === Phase.LateRace ? (math.sqrt(500 * speedStat) as number) * distanceProficiencyMod[distance ?? AptitudeLevel.A] * 0.002 : 0;
|
||||
const u = witStat / 550000 * math.log10(baseWit * 0.1);
|
||||
const l = u - 0.0065;
|
||||
return [baseTarget + late + base*l, baseTarget + late + base*u];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate an Uma's last spurt target speed.
|
||||
* @param speedStat Adjusted speed stat
|
||||
@@ -326,3 +372,21 @@ export function speedGain(speedBonus: number, dur: number, accel: number | null,
|
||||
export function downhillAccelEnterChance(witStat: number): number {
|
||||
return witStat * 0.0004;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the chance for a front runner to enter speed-up or overtake mode when eligible.
|
||||
* @param witStat Final wit stat, including style aptitude modifier
|
||||
* @returns Probability each eligible tick to enter speed-up or overtake mode
|
||||
*/
|
||||
export function frontModeEnterChance(witStat: number): number {
|
||||
return 0.2 * math.log10(witStat) - 0.2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the chance for a non-front runner to enter pace-up mode when eligible.
|
||||
* @param witStat Final wit stat, including style aptitude modifier
|
||||
* @returns Probability each eligible tick to enter pace-up mode
|
||||
*/
|
||||
export function paceUpEnterChance(witStat: number): number {
|
||||
return 0.15 * math.log10(witStat) - 0.15;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user