Compare commits
9 Commits
7600c48cc7
...
3ab17cf9b0
| Author | SHA1 | Date | |
|---|---|---|---|
| 3ab17cf9b0 | |||
| 4bd7962182 | |||
| 09f099171b | |||
| 4fff7069a8 | |||
| 3e2153b39c | |||
| d0fa6ab15c | |||
| 9f8024d488 | |||
| 1df3bc1db9 | |||
| a8c1b9c754 |
@@ -43,10 +43,6 @@
|
|||||||
let height = $state(0);
|
let height = $state(0);
|
||||||
|
|
||||||
let expand = $state(false);
|
let expand = $state(false);
|
||||||
const expandIcon = $derived(expand ? '◀' : '▶');
|
|
||||||
function clickExpand() {
|
|
||||||
expand = !expand;
|
|
||||||
}
|
|
||||||
|
|
||||||
const xLabel = $derived(Stat[stat]);
|
const xLabel = $derived(Stat[stat]);
|
||||||
const xLines = $derived([xRule].flat(1));
|
const xLines = $derived([xRule].flat(1));
|
||||||
@@ -85,6 +81,7 @@
|
|||||||
Plot.plot({
|
Plot.plot({
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
clip: true,
|
||||||
...plotOptions,
|
...plotOptions,
|
||||||
x: {
|
x: {
|
||||||
domain: [xMin, xMax],
|
domain: [xMin, xMax],
|
||||||
@@ -117,10 +114,7 @@
|
|||||||
|
|
||||||
<div bind:clientWidth={width} bind:clientHeight={height} class={['flex h-full w-full flex-col md:flex-row', className]}>
|
<div bind:clientWidth={width} bind:clientHeight={height} class={['flex h-full w-full flex-col md:flex-row', className]}>
|
||||||
<div role="img" {@attach makeChart}>
|
<div role="img" {@attach makeChart}>
|
||||||
<span>Loading chart!</span>
|
<span>the chart seems to have didn't</span>
|
||||||
</div>
|
|
||||||
<div class="my-5 flex h-full flex-row place-content-end md:flex-col md:place-content-start">
|
|
||||||
<button class="h-8 rounded border px-2 pb-1 align-middle" onclick={clickExpand}>{expandIcon}</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { AptitudeLevel, Mood, RunningStyle } from './race';
|
import { AptitudeLevel, Mood, RunningStyle } from './race';
|
||||||
import type { Runner } from './runner';
|
import type { Runner } from './runner';
|
||||||
|
|
||||||
const aptitude_map = {
|
const aptMap = {
|
||||||
G: AptitudeLevel.G,
|
G: AptitudeLevel.G,
|
||||||
F: AptitudeLevel.F,
|
F: AptitudeLevel.F,
|
||||||
E: AptitudeLevel.E,
|
E: AptitudeLevel.E,
|
||||||
@@ -11,9 +11,9 @@ const aptitude_map = {
|
|||||||
A: AptitudeLevel.A,
|
A: AptitudeLevel.A,
|
||||||
S: AptitudeLevel.S,
|
S: AptitudeLevel.S,
|
||||||
} as const;
|
} as const;
|
||||||
type AptitudeString = keyof typeof aptitude_map;
|
type AptitudeString = keyof typeof aptMap;
|
||||||
|
|
||||||
const style_map = {
|
const styleMap = {
|
||||||
Nige: RunningStyle.FrontRunner,
|
Nige: RunningStyle.FrontRunner,
|
||||||
Sentou: RunningStyle.PaceChaser,
|
Sentou: RunningStyle.PaceChaser,
|
||||||
Sasi: RunningStyle.LateSurger,
|
Sasi: RunningStyle.LateSurger,
|
||||||
@@ -29,7 +29,7 @@ export interface ImportUma {
|
|||||||
power: number;
|
power: number;
|
||||||
guts: number;
|
guts: number;
|
||||||
wisdom: number;
|
wisdom: number;
|
||||||
strategy: keyof typeof style_map;
|
strategy: keyof typeof styleMap;
|
||||||
distanceAptitude: AptitudeString;
|
distanceAptitude: AptitudeString;
|
||||||
surfaceAptitude: AptitudeString;
|
surfaceAptitude: AptitudeString;
|
||||||
strategyAptitude: AptitudeString;
|
strategyAptitude: AptitudeString;
|
||||||
@@ -55,23 +55,23 @@ export function load(obj: ImportUma, name?: string): Runner {
|
|||||||
return {
|
return {
|
||||||
name: name ?? '',
|
name: name ?? '',
|
||||||
chara_card_id: obj.outfitId !== '' ? parseInt(obj.outfitId) : 0,
|
chara_card_id: obj.outfitId !== '' ? parseInt(obj.outfitId) : 0,
|
||||||
style: style_map[obj.strategy],
|
style: styleMap[obj.strategy],
|
||||||
mood: obj.mood,
|
mood: obj.mood,
|
||||||
speed: obj.speed,
|
speed: obj.speed,
|
||||||
stamina: obj.stamina,
|
stamina: obj.stamina,
|
||||||
power: obj.power,
|
power: obj.power,
|
||||||
guts: obj.guts,
|
guts: obj.guts,
|
||||||
wit: obj.wisdom,
|
wit: obj.wisdom,
|
||||||
sprint: aptitude_map[obj.aptitudes[0]],
|
sprint: aptMap[obj.aptitudes[0]],
|
||||||
mile: aptitude_map[obj.aptitudes[1]],
|
mile: aptMap[obj.aptitudes[1]],
|
||||||
medium: aptitude_map[obj.aptitudes[2]],
|
medium: aptMap[obj.aptitudes[2]],
|
||||||
long: aptitude_map[obj.aptitudes[3]],
|
long: aptMap[obj.aptitudes[3]],
|
||||||
front: aptitude_map[obj.aptitudes[4]],
|
front: aptMap[obj.aptitudes[4]],
|
||||||
pace: aptitude_map[obj.aptitudes[5]],
|
pace: aptMap[obj.aptitudes[5]],
|
||||||
late: aptitude_map[obj.aptitudes[6]],
|
late: aptMap[obj.aptitudes[6]],
|
||||||
end: aptitude_map[obj.aptitudes[7]],
|
end: aptMap[obj.aptitudes[7]],
|
||||||
turf: aptitude_map[obj.aptitudes[8]],
|
turf: aptMap[obj.aptitudes[8]],
|
||||||
dirt: aptitude_map[obj.aptitudes[9]],
|
dirt: aptMap[obj.aptitudes[9]],
|
||||||
skills: obj.skills.map((s) => parseInt(s)),
|
skills: obj.skills.map((s) => parseInt(s)),
|
||||||
unique_level: obj.uniqueLv,
|
unique_level: obj.uniqueLv,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -95,21 +95,20 @@ const distanceProficiencyMod = [0.1, 0.2, 0.4, 0.6, 0.8, 0.9, 1.0, 1.05] as cons
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate an Uma's last spurt target speed.
|
* Calculate an Uma's last spurt target speed.
|
||||||
* @param rawSpeed Uma's speed stat. No accounting for mood lol.
|
* @param speedStat Adjusted speed stat
|
||||||
* @param gutsStat Uma's guts stat.
|
* @param gutsStat Adjusted guts stat
|
||||||
* @param style Running style
|
* @param style Running style
|
||||||
* @param distance Distance aptitude
|
* @param distance Distance aptitude
|
||||||
* @param raceLen Length of the race
|
* @param raceLen Length of the race
|
||||||
* @returns Target speed in the last spurt in m/s
|
* @returns Target speed in the last spurt in m/s
|
||||||
*/
|
*/
|
||||||
export function spurtSpeed(
|
export function spurtSpeed(
|
||||||
rawSpeed: number,
|
speedStat: number,
|
||||||
gutsStat: number,
|
gutsStat: number,
|
||||||
style: RunningStyle,
|
style: RunningStyle,
|
||||||
distance: AptitudeLevel,
|
distance: AptitudeLevel,
|
||||||
raceLen: number,
|
raceLen: number,
|
||||||
): number {
|
): number {
|
||||||
const speedStat = rawSpeed <= 1200 ? rawSpeed : 1200 + (rawSpeed - 1200) * 0.5;
|
|
||||||
const spc = speedStrategyPhaseCoeff[style][Phase.LateRace];
|
const spc = speedStrategyPhaseCoeff[style][Phase.LateRace];
|
||||||
const dpm = distanceProficiencyMod[distance];
|
const dpm = distanceProficiencyMod[distance];
|
||||||
const base = baseSpeed(raceLen);
|
const base = baseSpeed(raceLen);
|
||||||
@@ -149,9 +148,6 @@ export function inverseSpurtSpeed(
|
|||||||
const base = baseSpeed(raceLen);
|
const base = baseSpeed(raceLen);
|
||||||
const nr = spurtSpeed - base * (1.05 * spc + 0.0105) - Math.pow(450 * gutsStat, 0.597) * 0.0001;
|
const nr = spurtSpeed - base * (1.05 * spc + 0.0105) - Math.pow(450 * gutsStat, 0.597) * 0.0001;
|
||||||
const r = (nr * nr) / (0.008405 * dpm * dpm);
|
const r = (nr * nr) / (0.008405 * dpm * dpm);
|
||||||
if (r > 1200) {
|
|
||||||
return Math.round(2 * r - 1200);
|
|
||||||
}
|
|
||||||
return Math.round(r);
|
return Math.round(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,19 +270,25 @@ export function skillDuration(baseDur: number, raceLen: number): number {
|
|||||||
* Calculate the distance gained from a target speed boost, including
|
* Calculate the distance gained from a target speed boost, including
|
||||||
* acceleration to the boosted target speed and deceleration back to baseline.
|
* acceleration to the boosted target speed and deceleration back to baseline.
|
||||||
* @param speedBonus Difference between baseline and boosted speed in m/s
|
* @param speedBonus Difference between baseline and boosted speed in m/s
|
||||||
* @param accel Current acceleration value in m/s²
|
* @param accel Current acceleration value in m/s², or null for instant acceleration
|
||||||
* @param decel Current phase-based deceleration value in m/s², a negative value.
|
* @param decel Current phase-based deceleration value in m/s², a negative value; or null for instant deceleration
|
||||||
* @param dur Duration of the boosted speed
|
* @param dur Duration of the boosted speed
|
||||||
* @returns Distance gained from the speed boost in m
|
* @returns Distance gained from the speed boost in m
|
||||||
*/
|
*/
|
||||||
export function speedGain(speedBonus: number, accel: number, decel: number, dur: number): number {
|
export function speedGain(speedBonus: number, dur: number, accel: number | null, decel: number | null): number {
|
||||||
// Actual effect of a target speed bonus looks like
|
// Actual effect of a target speed bonus looks like
|
||||||
// speed: __/-----\__
|
// speed: __/-----\__
|
||||||
// bonus: ======
|
// bonus: ======
|
||||||
// I.e., the speed bonus duration includes acceleration to the new speed
|
// I.e., the speed bonus duration includes acceleration to the new speed
|
||||||
// and does not include the acceleration back to baseline after it ends.
|
// and does not include the acceleration back to baseline after it ends.
|
||||||
const accelTime = speedBonus / accel;
|
const accelTime = accel !== null ? speedBonus / accel : 0;
|
||||||
const decelTime = -speedBonus / decel;
|
const decelTime = decel !== null ? -speedBonus / decel : 0;
|
||||||
|
if (accelTime >= dur) {
|
||||||
|
// Acceleration is so low that the horse won't reach the boosted target
|
||||||
|
// speed before the effect ends. E.g., G surface aptitude.
|
||||||
|
const peakSpeed = (accel ?? 0) * dur;
|
||||||
|
return 0.5 * (peakSpeed * dur - peakSpeed / (decel ?? 0));
|
||||||
|
}
|
||||||
// speedBonus*(dur-accelTime) + speedBonus*accelTime/2 + speedBonus*decelTime/2
|
// speedBonus*(dur-accelTime) + speedBonus*accelTime/2 + speedBonus*decelTime/2
|
||||||
return speedBonus * (dur + 0.5 * (decelTime - accelTime));
|
return speedBonus * (dur + 0.5 * (decelTime - accelTime));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export interface Runner {
|
|||||||
* @param base_uma Character card (trainee or uma) to use for aptitudes; otherwise all aptitudes are A
|
* @param base_uma Character card (trainee or uma) to use for aptitudes; otherwise all aptitudes are A
|
||||||
* @returns Baseline runner
|
* @returns Baseline runner
|
||||||
*/
|
*/
|
||||||
export function new_runner(name?: string, base_uma?: Uma): Runner {
|
export function newRunner(name?: string, base_uma?: Uma): Runner {
|
||||||
return {
|
return {
|
||||||
name: name ?? '',
|
name: name ?? '',
|
||||||
chara_card_id: base_uma?.chara_card_id ?? 0,
|
chara_card_id: base_uma?.chara_card_id ?? 0,
|
||||||
@@ -72,7 +72,7 @@ export function new_runner(name?: string, base_uma?: Uma): Runner {
|
|||||||
* @param name Name or memo to apply to the runner
|
* @param name Name or memo to apply to the runner
|
||||||
* @returns Imported runner, or null if import was not possible
|
* @returns Imported runner, or null if import was not possible
|
||||||
*/
|
*/
|
||||||
export function import_runner(obj: unknown, name?: string): Runner | null {
|
export function importRunner(obj: unknown, name?: string): Runner | null {
|
||||||
// TODO(zeph): check for keys that identify the uma source
|
// TODO(zeph): check for keys that identify the uma source
|
||||||
if (typeof obj === 'object') {
|
if (typeof obj === 'object') {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -54,24 +54,30 @@
|
|||||||
const ssY: Array<ComputedSeries | null> = $derived([
|
const ssY: Array<ComputedSeries | null> = $derived([
|
||||||
{
|
{
|
||||||
label: 'Aptitude S',
|
label: 'Aptitude S',
|
||||||
y: (x) => speedGain(spotStruggleSpeed(x), accel[0], decel[0], spotStruggleDuration(x, AptitudeLevel.S)) / HORSE_LENGTH,
|
y: (x) => speedGain(spotStruggleSpeed(x), spotStruggleDuration(x, AptitudeLevel.S), accel[0], decel[0]) / HORSE_LENGTH,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Aptitude A',
|
label: 'Aptitude A',
|
||||||
y: (x) => speedGain(spotStruggleSpeed(x), accel[0], decel[0], spotStruggleDuration(x, AptitudeLevel.A)) / HORSE_LENGTH,
|
y: (x) => speedGain(spotStruggleSpeed(x), spotStruggleDuration(x, AptitudeLevel.A), accel[0], decel[0]) / HORSE_LENGTH,
|
||||||
},
|
},
|
||||||
frontApt < AptitudeLevel.A
|
frontApt < AptitudeLevel.A
|
||||||
? {
|
? {
|
||||||
label: `Aptitude ${AptitudeLevel[frontApt]}`,
|
label: `Aptitude ${AptitudeLevel[frontApt]}`,
|
||||||
y: (x) => speedGain(spotStruggleSpeed(x), accel[0], decel[0], spotStruggleDuration(x, frontApt)) / HORSE_LENGTH,
|
y: (x) => speedGain(spotStruggleSpeed(x), spotStruggleDuration(x, frontApt), accel[0], decel[0]) / HORSE_LENGTH,
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
]);
|
]);
|
||||||
const lcY: Array<ComputedSeries | null> = $derived([
|
const lcY: Array<ComputedSeries | null> = $derived([
|
||||||
{ label: 'Ideal Lane Combo', y: (x) => speedGain(moveLaneModifier(x), accel[0], decel[0], lcDur) / HORSE_LENGTH },
|
{ label: 'Ideal Lane Combo', y: (x) => speedGain(moveLaneModifier(x), lcDur, accel[0], decel[0]) / HORSE_LENGTH },
|
||||||
]);
|
]);
|
||||||
const yRule: HorizontalRule[] = $derived([
|
const pcRuler = $derived({ y: speedGain(0.35, skillDuration(2.4, raceLen), accel[1], decel[1]) / HORSE_LENGTH, label: 'Professor of Curvature' });
|
||||||
{ y: speedGain(0.35, accel[1], decel[1], skillDuration(2.4, raceLen)) / 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>
|
</script>
|
||||||
|
|
||||||
@@ -103,7 +109,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="m-4 md:col-span-2 md:col-start-2">
|
<div class="m-4 md:col-span-2 md:col-start-2">
|
||||||
<label for="raceLen">Race Distance</label>
|
<label for="raceLen">Race Distance</label>
|
||||||
<input type="number" id="raceLen" required bind:value={raceLen} class="w-full" />
|
<input type="number" id="raceLen" required min="1000" max="3600" step="100" bind:value={raceLen} class="w-full" />
|
||||||
</div>
|
</div>
|
||||||
<div class="m-4 self-center md:col-span-2">
|
<div class="m-4 self-center md:col-span-2">
|
||||||
<label for="isRunaway" class="mr-1 align-middle">Runaway</label>
|
<label for="isRunaway" class="mr-1 align-middle">Runaway</label>
|
||||||
@@ -117,7 +123,7 @@
|
|||||||
<span class="mt-8 block w-full text-center text-lg">Mechanics</span>
|
<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">
|
<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={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>
|
<SpeedDur speed={lcBoost} dur={lcDur} accel={accel[0]} decel={decel[0]}>Idealized Lane Combo (DDPP)</SpeedDur>
|
||||||
</div>
|
</div>
|
||||||
<span class="mt-8 block w-full text-center text-lg">Unique Skills</span>
|
<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">
|
<div class="mx-auto flex flex-col md:flex-row md:justify-center">
|
||||||
@@ -132,10 +138,10 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
<div class="mx-auto h-60 py-4 md:h-96 md:w-3xl">
|
<div class="mx-auto h-60 py-4 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} />
|
<StatChart class="flex-1" stat={Stat.Guts} y={ssY} yLabel="Lengths Gained" range={[0, 1.5, 2.5]} xRule={gutsStat} yRule={ssYRule} />
|
||||||
</div>
|
</div>
|
||||||
<div class="mx-auto mt-4 h-60 py-4 md:mt-0 md:h-96 md:w-3xl">
|
<div class="mx-auto mt-4 h-60 py-4 md:mt-0 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} />
|
<StatChart class="flex-1" stat={Stat.Power} y={lcY} yLabel="Lengths Gained" range={[0, 1.5, 2.5]} xRule={powerStat} yRule={lcYRule} />
|
||||||
</div>
|
</div>
|
||||||
<div class="mx-auto mt-12 w-full max-w-4xl border-t md:mt-8">
|
<div class="mx-auto mt-12 w-full max-w-4xl border-t md:mt-8">
|
||||||
<ul class="ml-4 list-disc">
|
<ul class="ml-4 list-disc">
|
||||||
@@ -148,7 +154,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Lane combo is idealized in the sense of assuming second lane change speed skill executes immediately after Dodging Danger
|
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.
|
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]">
|
<ul class="ml-8 list-[revert]">
|
||||||
<li>
|
<li>
|
||||||
The move lane modifier is capped to 6 seconds, which is the approximate observed time to move from the Dodging Danger
|
The move lane modifier is capped to 6 seconds, which is the approximate observed time to move from the Dodging Danger
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
const { children, speed, dur, accel, decel }: Props = $props();
|
const { children, speed, dur, accel, decel }: Props = $props();
|
||||||
const decels = $derived([decel].flat(1));
|
const decels = $derived([decel].flat(1));
|
||||||
|
|
||||||
const gain = $derived(decels.map((d) => speedGain(speed, accel, d, dur) / HORSE_LENGTH));
|
const gain = $derived(decels.map((d) => speedGain(speed, dur, accel, d) / HORSE_LENGTH));
|
||||||
const text = $derived(gain.map(fmtp).join(' – '));
|
const text = $derived(gain.map(fmtp).join(' – '));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="m-4 md:col-start-2">
|
<div class="m-4 md:col-start-2">
|
||||||
<label for="raceLen">Race Distance</label>
|
<label for="raceLen">Race Distance</label>
|
||||||
<input type="number" id="raceLen" required bind:value={raceLen} class="w-full" />
|
<input type="number" id="raceLen" required min="1000" max="3600" step="100" bind:value={raceLen} class="w-full" />
|
||||||
</div>
|
</div>
|
||||||
<div class="m-4 self-center">
|
<div class="m-4 self-center">
|
||||||
<label for="isCareer" class="mr-1 align-middle">In Career</label>
|
<label for="isCareer" class="mr-1 align-middle">In Career</label>
|
||||||
|
|||||||
Reference in New Issue
Block a user