zenno: categorize tools
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
</script>
|
||||
|
||||
<h1 class="m-8 text-center text-7xl">Zenno Rob Roy — Character Tools</h1>
|
||||
<p>Tools related to understanding Umamusume characters.</p>
|
||||
|
||||
<ul class="mb-4 list-disc pl-4">
|
||||
<li>
|
||||
<a href={resolve('/race/spurt')}>Spurt Speed</a> — Calculate a horse's target speed in the last spurt and compare to other
|
||||
distance aptitudes and running styles.
|
||||
</li>
|
||||
<li>
|
||||
<a href={resolve('/race/mspeed')}>Front Runner Mechanical Speed Comparator</a> — Compare spot struggle and lane combo to the
|
||||
effects of individual skills.
|
||||
</li>
|
||||
</ul>
|
||||
@@ -0,0 +1,220 @@
|
||||
<script lang="ts">
|
||||
import type { ComputedSeries } from '$lib/chart';
|
||||
import {
|
||||
acceleration,
|
||||
APTITUDE_LEVELS,
|
||||
AptitudeLevel,
|
||||
deceleration,
|
||||
HORSE_LENGTH,
|
||||
moveLaneModifier,
|
||||
Phase,
|
||||
RunningStyle,
|
||||
skillDuration,
|
||||
speedGain,
|
||||
spotStruggleDuration,
|
||||
spotStruggleSpeed,
|
||||
Stat,
|
||||
} from '$lib/race';
|
||||
import StatChart from '$lib/StatChart.svelte';
|
||||
import SpeedDur from './SpeedDur.svelte';
|
||||
|
||||
let rawPower = $state(1200);
|
||||
let rawGuts = $state(1200);
|
||||
let raceLen = $state(1600);
|
||||
let surfApt = $state(AptitudeLevel.A);
|
||||
let frontApt = $state(AptitudeLevel.A);
|
||||
let isRunaway = $state(false);
|
||||
let isCareer = $state(false);
|
||||
|
||||
const careerMod = $derived(isCareer ? 400 : 0);
|
||||
const powerStat = $derived(rawPower + careerMod);
|
||||
const gutsStat = $derived(rawGuts + careerMod);
|
||||
const style = $derived(isRunaway ? RunningStyle.GreatEscape : RunningStyle.FrontRunner);
|
||||
|
||||
const phases = [Phase.EarlyRace, Phase.MidRace, Phase.LateRace] as const;
|
||||
const accel = $derived(phases.map((p) => acceleration(powerStat, style, surfApt, p)));
|
||||
const decel = phases.map((p) => deceleration(p));
|
||||
|
||||
const ssBoost = $derived(spotStruggleSpeed(gutsStat));
|
||||
const ssDur = $derived(spotStruggleDuration(gutsStat, frontApt));
|
||||
|
||||
const lcBoost = $derived(moveLaneModifier(powerStat));
|
||||
const lcDur = $derived(Math.min(skillDuration(3, raceLen), 6));
|
||||
|
||||
const uniques = [
|
||||
['Operation Cacao', 0.35, 5, Phase.MidRace],
|
||||
["All Charged! It's Go Time! (Tokyo turf)", 0.45, 5, Phase.LateRace],
|
||||
] as const;
|
||||
const skills = [
|
||||
['Fast-Paced', 0.15, 3, Phase.MidRace],
|
||||
['Professor of Curvature (mid race)', 0.35, 2.4, Phase.MidRace],
|
||||
["All Charged! It's Go Time! (inherited)", 0.25, 3, Phase.LateRace],
|
||||
] as const;
|
||||
|
||||
const ssY: Array<ComputedSeries | null> = $derived([
|
||||
{
|
||||
label: 'Aptitude S',
|
||||
y: (x) => speedGain(spotStruggleSpeed(x), spotStruggleDuration(x, AptitudeLevel.S), accel[0], decel[0]) / HORSE_LENGTH,
|
||||
},
|
||||
{
|
||||
label: 'Aptitude A',
|
||||
y: (x) => speedGain(spotStruggleSpeed(x), spotStruggleDuration(x, AptitudeLevel.A), accel[0], decel[0]) / HORSE_LENGTH,
|
||||
},
|
||||
frontApt < AptitudeLevel.A
|
||||
? {
|
||||
label: `Aptitude ${AptitudeLevel[frontApt]}`,
|
||||
y: (x) => speedGain(spotStruggleSpeed(x), spotStruggleDuration(x, frontApt), accel[0], decel[0]) / HORSE_LENGTH,
|
||||
}
|
||||
: null,
|
||||
]);
|
||||
const lcY: Array<ComputedSeries | null> = $derived([
|
||||
{
|
||||
label: 'Ideal Lane Combo',
|
||||
y: (x) => speedGain(moveLaneModifier(x), lcDur, acceleration(x, style, surfApt, Phase.EarlyRace), decel[0]) / HORSE_LENGTH,
|
||||
},
|
||||
]);
|
||||
const pcRuler = $derived({
|
||||
y: speedGain(0.35, skillDuration(2.4, raceLen), accel[1], decel[1]) / HORSE_LENGTH,
|
||||
label: 'Professor of Curvature',
|
||||
});
|
||||
const ssYRule = $derived([pcRuler, { y: speedGain(lcBoost, lcDur, accel[0], decel[0]) / HORSE_LENGTH, label: 'Lane Combo' }]);
|
||||
const lcYRule = $derived([
|
||||
pcRuler,
|
||||
{ y: speedGain(ssBoost, ssDur, accel[0], decel[1]) / HORSE_LENGTH, label: 'Spot Struggle' },
|
||||
]);
|
||||
</script>
|
||||
|
||||
<h1 class="text-4xl">Front Runner Mechanical Speed Comparator</h1>
|
||||
<div class="mx-auto mt-8 grid max-w-4xl grid-cols-1 rounded-md text-center shadow-md ring md:grid-cols-8">
|
||||
<div class="m-4 md:col-span-2">
|
||||
<label for="powerStat">Power Stat</label>
|
||||
<input type="number" id="powerStat" bind:value={rawPower} class="w-full" />
|
||||
</div>
|
||||
<div class="m-4 md:col-span-2">
|
||||
<label for="gutsStat">Guts Stat</label>
|
||||
<input type="number" id="gutsStat" bind:value={rawGuts} class="w-full" />
|
||||
</div>
|
||||
<div class="m-4 md:col-span-2">
|
||||
<label for="surfaceApt">Surface Aptitude</label>
|
||||
<select id="surfaceApt" required bind:value={surfApt} class="w-full">
|
||||
{#each APTITUDE_LEVELS as apt (apt)}
|
||||
<option value={apt}>{AptitudeLevel[apt]}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
<div class="m-4 md:col-span-2">
|
||||
<label for="frontApt">Front Runner Aptitude</label>
|
||||
<select id="frontApt" required bind:value={frontApt} class="w-full">
|
||||
{#each APTITUDE_LEVELS as apt (apt)}
|
||||
<option value={apt}>{AptitudeLevel[apt]}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
<div class="m-4 md:col-span-2 md:col-start-2">
|
||||
<label for="raceLen">Race Distance</label>
|
||||
<input type="number" id="raceLen" required min="1000" max="3600" step="100" bind:value={raceLen} class="w-full" />
|
||||
</div>
|
||||
<div class="m-4 self-center md:col-span-2">
|
||||
<label for="isRunaway" class="mr-1 align-middle">Runaway</label>
|
||||
<input type="checkbox" id="isRunaway" role="switch" bind:checked={isRunaway} class="min-h-6 min-w-6 align-middle" />
|
||||
</div>
|
||||
<div class="m-4 self-center md:col-span-2">
|
||||
<label for="isCareer" class="mr-1 align-middle">In Career</label>
|
||||
<input type="checkbox" id="isCareer" role="switch" bind:checked={isCareer} class="min-h-6 min-w-6 align-middle" />
|
||||
</div>
|
||||
</div>
|
||||
<span class="mt-8 block w-full text-center text-lg">Mechanics</span>
|
||||
<div class="mx-auto flex w-full flex-col place-items-center md:flex-row md:justify-center">
|
||||
<SpeedDur speed={ssBoost} dur={ssDur} accel={accel[0]} decel={[decel[0], decel[1]]}>Spot Struggle</SpeedDur>
|
||||
<SpeedDur speed={lcBoost} dur={lcDur} accel={accel[0]} decel={decel[0]}>Idealized Lane Combo (DDPP)</SpeedDur>
|
||||
</div>
|
||||
<table class="mx-auto mt-1 table-fixed rounded-b-md border border-t-0 text-sm">
|
||||
<caption class="rounded-t-md border border-b-0">Base Acceleration</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-32" scope="col">Early Race</th>
|
||||
<th class="w-32" scope="col">Mid Race</th>
|
||||
<th class="w-32" scope="col">Late Race</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
{#each accel as v, i (i)}
|
||||
<td class="text-center">{v.toFixed(3)} m/s²</td>
|
||||
{/each}
|
||||
</tr>
|
||||
<tr>
|
||||
{#each decel as v, i (i)}
|
||||
<td class="text-center">{v.toFixed(3)} m/s²</td>
|
||||
{/each}
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<span class="mt-8 block w-full text-center text-lg">Unique Skills</span>
|
||||
<div class="mx-auto flex flex-col place-items-center md:flex-row md:justify-center">
|
||||
{#each uniques as [name, boost, dur, phase] (name)}
|
||||
<SpeedDur speed={boost} dur={skillDuration(dur, raceLen)} accel={accel[phase]} decel={decel[phase]}>{name}</SpeedDur>
|
||||
{/each}
|
||||
</div>
|
||||
<span class="mt-8 block w-full text-center text-lg">Inherited Uniques & Other Skills</span>
|
||||
<div class="mx-auto flex flex-col place-items-center md:flex-row md:justify-center">
|
||||
{#each skills as [name, boost, dur, phase] (name)}
|
||||
<SpeedDur speed={boost} dur={skillDuration(dur, raceLen)} accel={accel[phase]} decel={decel[phase]}>{name}</SpeedDur>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="mx-auto grid h-96 grid-cols-1 py-4 md:grid-cols-2">
|
||||
<StatChart
|
||||
class="flex-1"
|
||||
stat={Stat.Guts}
|
||||
y={ssY}
|
||||
yLabel="Lengths Gained"
|
||||
range={[0, 1.5, 2.5]}
|
||||
xRule={gutsStat}
|
||||
yRule={ssYRule}
|
||||
/>
|
||||
<StatChart
|
||||
class="flex-1"
|
||||
stat={Stat.Power}
|
||||
y={lcY}
|
||||
yLabel="Lengths Gained"
|
||||
range={[0, 1.5, 2.5]}
|
||||
xRule={powerStat}
|
||||
yRule={lcYRule}
|
||||
/>
|
||||
</div>
|
||||
<div class="mx-auto mt-12 w-full max-w-4xl border-t pt-4 md:mt-8">
|
||||
<ul class="ml-4 list-disc">
|
||||
<li>All lengths gained include acceleration at the beginning of each speed boost and deceleration after its end.</li>
|
||||
<li>Each effect is assumed to be isolated and executed on level ground.</li>
|
||||
<li>
|
||||
Spot struggle has two numbers to distinguish ending in early race versus ending in mid race, which gives different
|
||||
deceleration values. Since spot struggle duration does not scale with race length, it is more likely to end in mid race on
|
||||
shorter races.
|
||||
</li>
|
||||
<li>
|
||||
Lane combo is idealized in the sense of assuming second lane change speed skill executes immediately after Dodging Danger
|
||||
completes, the horse is never blocked, and the horse returns to the rail in early race before the first corner.
|
||||
<ul class="ml-8 list-[revert]">
|
||||
<li>
|
||||
The move lane modifier is capped to 6 seconds, which is the approximate observed time to move from the Dodging Danger
|
||||
fixed lane back to the rail under the effect of Prudent Positioning.
|
||||
</li>
|
||||
<li>
|
||||
On medium+ tracks, with a proper gate acceleration build, Dodging Danger should realize some lane movement speed
|
||||
modifier, so the actual benefit will be more than the idealized number.
|
||||
</li>
|
||||
<li>
|
||||
Ignited Spirit WIT has a longer duration and lower lane change speed boost than Prudent Positioning, so it is likely to
|
||||
give more benefit than the idealized number.
|
||||
</li>
|
||||
<li>
|
||||
For full simulated analysis of lane combo, see <a
|
||||
href="https://lanecalc.hf-uma.net/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">危険回避シミュ</a
|
||||
>.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -0,0 +1,33 @@
|
||||
<script lang="ts">
|
||||
import { HORSE_LENGTH, speedGain } from '$lib/race';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
children: Snippet;
|
||||
speed: number;
|
||||
dur: number;
|
||||
accel: number;
|
||||
decel: number | number[];
|
||||
}
|
||||
|
||||
function fmtp(x: number): string {
|
||||
return x >= 0 ? '+' + x.toFixed(3) : x.toFixed(3);
|
||||
}
|
||||
|
||||
const { children, speed, dur, accel, decel }: Props = $props();
|
||||
const decels = $derived([decel].flat(1));
|
||||
|
||||
const gain = $derived(decels.map((d) => speedGain(speed, dur, accel, d) / HORSE_LENGTH));
|
||||
const text = $derived(gain.map(fmtp).join(' – '));
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="m-2 flex h-full w-full max-w-80 flex-1 flex-col rounded-md border p-2 text-center shadow-sm transition-shadow hover:shadow-md"
|
||||
>
|
||||
<div class="block">{@render children()}</div>
|
||||
<span class="block text-xl">{text} L</span>
|
||||
<div class="flex flex-row">
|
||||
<span class="flex-1 text-xs">{fmtp(speed)} m/s</span>
|
||||
<span class="flex-1 text-xs">{dur.toFixed(3)} s</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,141 @@
|
||||
<script lang="ts">
|
||||
import type { ComputedSeries } from '$lib/chart';
|
||||
import { AptitudeLevel, inverseSpurtSpeed, RunningStyle, spurtSpeed, Stat } from '$lib/race';
|
||||
import StatChart from '$lib/StatChart.svelte';
|
||||
|
||||
const aptsList = Object.entries(AptitudeLevel).filter(([, val]) => typeof val === 'number');
|
||||
const stylesList = [
|
||||
['Front Runner', RunningStyle.FrontRunner],
|
||||
['Pace Chaser', RunningStyle.PaceChaser],
|
||||
['Late Surger', RunningStyle.LateSurger],
|
||||
['End Closer', RunningStyle.EndCloser],
|
||||
['Great Escape', RunningStyle.GreatEscape],
|
||||
] as const;
|
||||
const skillSpeeds = [0.45, 0.35, 0.25, 0.15] as const;
|
||||
|
||||
let rawSpeed: number = $state(1200);
|
||||
let rawGuts: number = $state(1200);
|
||||
let style: RunningStyle = $state(RunningStyle.FrontRunner);
|
||||
let opponentStyle: RunningStyle = $state(RunningStyle.PaceChaser);
|
||||
let distanceApt: AptitudeLevel = $state(AptitudeLevel.S);
|
||||
let raceLen: number = $state(2000);
|
||||
let isCareer: boolean = $state(false);
|
||||
|
||||
const careerMod = $derived(isCareer ? 400 : 0);
|
||||
const speedStat = $derived(rawSpeed + careerMod);
|
||||
const gutsStat = $derived(rawGuts + careerMod);
|
||||
const speed = $derived(spurtSpeed(speedStat, gutsStat, style, distanceApt, raceLen));
|
||||
|
||||
const sProf = $derived([
|
||||
inverseSpurtSpeed(speed, gutsStat, RunningStyle.FrontRunner, AptitudeLevel.S, raceLen) - careerMod,
|
||||
inverseSpurtSpeed(speed, gutsStat, RunningStyle.PaceChaser, AptitudeLevel.S, raceLen) - careerMod,
|
||||
inverseSpurtSpeed(speed, gutsStat, RunningStyle.LateSurger, AptitudeLevel.S, raceLen) - careerMod,
|
||||
inverseSpurtSpeed(speed, gutsStat, RunningStyle.EndCloser, AptitudeLevel.S, raceLen) - careerMod,
|
||||
inverseSpurtSpeed(speed, gutsStat, RunningStyle.GreatEscape, AptitudeLevel.S, raceLen) - careerMod,
|
||||
]);
|
||||
const aProf = $derived([
|
||||
inverseSpurtSpeed(speed, gutsStat, RunningStyle.FrontRunner, AptitudeLevel.A, raceLen) - careerMod,
|
||||
inverseSpurtSpeed(speed, gutsStat, RunningStyle.PaceChaser, AptitudeLevel.A, raceLen) - careerMod,
|
||||
inverseSpurtSpeed(speed, gutsStat, RunningStyle.LateSurger, AptitudeLevel.A, raceLen) - careerMod,
|
||||
inverseSpurtSpeed(speed, gutsStat, RunningStyle.EndCloser, AptitudeLevel.A, raceLen) - careerMod,
|
||||
inverseSpurtSpeed(speed, gutsStat, RunningStyle.GreatEscape, AptitudeLevel.A, raceLen) - careerMod,
|
||||
]);
|
||||
const skillProf = $derived(
|
||||
skillSpeeds.map((v) => [v, inverseSpurtSpeed(speed + v, gutsStat, opponentStyle, AptitudeLevel.S, raceLen) - careerMod]),
|
||||
);
|
||||
|
||||
const y: Array<ComputedSeries | null> = $derived([
|
||||
{ label: 'Aptitude S', y: (x) => spurtSpeed(x, gutsStat, style, AptitudeLevel.S, raceLen) },
|
||||
{ label: 'Aptitude A', y: (x) => spurtSpeed(x, gutsStat, style, AptitudeLevel.A, raceLen) },
|
||||
distanceApt < AptitudeLevel.A
|
||||
? { label: `Aptitude ${AptitudeLevel[distanceApt]}`, y: (x) => spurtSpeed(x, gutsStat, style, distanceApt, raceLen) }
|
||||
: null,
|
||||
]);
|
||||
|
||||
const range: [number, number] = $derived([
|
||||
spurtSpeed(200, gutsStat, RunningStyle.GreatEscape, AptitudeLevel.C, raceLen),
|
||||
spurtSpeed(2000, gutsStat, RunningStyle.EndCloser, AptitudeLevel.S, raceLen),
|
||||
]);
|
||||
</script>
|
||||
|
||||
<h1 class="text-4xl">Spurt Speed Calculator</h1>
|
||||
<div class="mx-auto mt-8 grid max-w-4xl grid-cols-1 rounded-md text-center shadow-md ring md:grid-cols-4">
|
||||
<div class="m-4">
|
||||
<label for="speedStat">Speed Stat</label>
|
||||
<input type="number" id="speedStat" required bind:value={rawSpeed} class="w-full" />
|
||||
</div>
|
||||
<div class="m-4">
|
||||
<label for="gutsStat">Guts Stat</label>
|
||||
<input type="number" id="gutsStat" required bind:value={rawGuts} class="w-full" />
|
||||
</div>
|
||||
<div class="m-4">
|
||||
<label for="style">Style</label>
|
||||
<select id="style" required bind:value={style} class="w-full">
|
||||
{#each stylesList as [name, style] (style)}
|
||||
<option value={style}>{style === RunningStyle.GreatEscape ? 'Great Escape (Runaway)' : name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
<div class="m-4">
|
||||
<label for="distanceApt">Distance Aptitude</label>
|
||||
<select id="distanceApt" required bind:value={distanceApt} class="w-full">
|
||||
{#each aptsList.toReversed() as [name, val] (val)}
|
||||
<option value={val}>{name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
<div class="m-4 md:col-start-2">
|
||||
<label for="raceLen">Race Distance</label>
|
||||
<input type="number" id="raceLen" required min="1000" max="3600" step="100" bind:value={raceLen} class="w-full" />
|
||||
</div>
|
||||
<div class="m-4 self-center">
|
||||
<label for="isCareer" class="mr-1 align-middle">In Career</label>
|
||||
<input type="checkbox" id="isCareer" role="switch" bind:checked={isCareer} class="min-h-6 min-w-6 align-middle" />
|
||||
</div>
|
||||
</div>
|
||||
<span class="mt-8 block w-full text-center text-lg">
|
||||
Target spurt speed: {speed.toFixed(3)} m/s
|
||||
</span>
|
||||
{#each [[AptitudeLevel.A, aProf] as const, [AptitudeLevel.S, sProf] as const] as [level, inv] (level)}
|
||||
<div class="mx-auto max-w-3xl">
|
||||
<span class="mt-8 block w-full">
|
||||
With {AptitudeLevel[level]} proficiency, the equivalent speed is
|
||||
</span>
|
||||
<div class="flex flex-col md:flex-row">
|
||||
{#each stylesList as [styleName, s] (s)}
|
||||
<div
|
||||
class={['m-2 flex-1 rounded-md border shadow-sm transition-shadow hover:shadow-md', s === style ? 'border-2' : null]}
|
||||
>
|
||||
<div class="h-full w-full flex-col text-center">
|
||||
<span class="block text-lg">{styleName}</span>
|
||||
<span class="block text-2xl">{inv[s]}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
<div class="mx-auto max-w-3xl">
|
||||
<span class="mt-8 block w-full">
|
||||
While a speed skill is active, the equivalent speed for a distance S
|
||||
<select id="opponentStyle" required bind:value={opponentStyle}>
|
||||
{#each stylesList as [name, style] (style)}
|
||||
<option value={style}>{style === RunningStyle.GreatEscape ? 'Great Escape (Runaway)' : name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
is
|
||||
</span>
|
||||
<div class="flex flex-col md:flex-row">
|
||||
{#each skillProf as [v, inv] (v)}
|
||||
<div class="m-2 flex-1 rounded-md border shadow-sm transition-shadow hover:shadow-md">
|
||||
<div class="h-full w-full flex-col text-center">
|
||||
<span class="block text-lg">+{v.toFixed(2)}</span>
|
||||
<span class="block text-2xl">{inv}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mx-auto h-60 max-w-3xl place-content-center py-4 md:h-96">
|
||||
<StatChart stat={Stat.Speed} {y} yLabel="Spurt Speed (m/s)" xRule={speedStat} {range} />
|
||||
</div>
|
||||
Reference in New Issue
Block a user