221 lines
8.3 KiB
Svelte
221 lines
8.3 KiB
Svelte
<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>
|