176 lines
7.3 KiB
Svelte
176 lines
7.3 KiB
Svelte
<script lang="ts">
|
|
import type { ComputedSeries, HorizontalRule } 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), accel[0], decel[0], spotStruggleDuration(x, AptitudeLevel.S)) / HORSE_LENGTH,
|
|
},
|
|
{
|
|
label: 'Aptitude A',
|
|
y: (x) => speedGain(spotStruggleSpeed(x), accel[0], decel[0], spotStruggleDuration(x, AptitudeLevel.A)) / HORSE_LENGTH,
|
|
},
|
|
frontApt < AptitudeLevel.A
|
|
? {
|
|
label: `Aptitude ${AptitudeLevel[frontApt]}`,
|
|
y: (x) => speedGain(spotStruggleSpeed(x), accel[0], decel[0], spotStruggleDuration(x, frontApt)) / HORSE_LENGTH,
|
|
}
|
|
: null,
|
|
]);
|
|
const lcY: Array<ComputedSeries | null> = $derived([
|
|
{ label: 'Ideal Lane Combo', y: (x) => speedGain(moveLaneModifier(x), accel[0], decel[0], lcDur) / HORSE_LENGTH },
|
|
]);
|
|
const yRule: HorizontalRule[] = $derived([
|
|
{ y: speedGain(0.35, accel[1], decel[1], skillDuration(2.4, raceLen)) / HORSE_LENGTH, label: 'Professor of Curvature' },
|
|
]);
|
|
</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 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 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</SpeedDur>
|
|
</div>
|
|
<span class="mt-8 block w-full text-center text-lg">Unique Skills</span>
|
|
<div class="mx-auto flex flex-col 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 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 py-4 h-60 md:h-96 md:w-3xl">
|
|
<StatChart class="flex-1" stat={Stat.Guts} y={ssY} yLabel="Lengths Gained" range={[0, 1.5, 2.5]} xRule={gutsStat} {yRule} />
|
|
</div>
|
|
<div class="mx-auto mt-4 md:mt-0 py-4 h-60 md:h-96 md:w-3xl">
|
|
<StatChart class="flex-1" stat={Stat.Power} y={lcY} yLabel="Lengths Gained" range={[0, 1.5, 2.5]} xRule={powerStat} {yRule} />
|
|
</div>
|
|
<div class="mx-auto mt-12 md:mt-8 w-full max-w-4xl border-t">
|
|
<ul class="list-disc ml-4">
|
|
<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 and the horse is never blocked.
|
|
<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>
|