zenno: rebase stamina calculator & format
This commit is contained in:
+1706
-193
File diff suppressed because it is too large
Load Diff
+94
-94
@@ -1,64 +1,64 @@
|
|||||||
import * as math from 'mathjs';
|
import * as math from 'mathjs';
|
||||||
import { Phase, type Stat, type Surface } from "./race";
|
import { Phase, type Stat, type Surface } from './race';
|
||||||
|
|
||||||
export interface Race {
|
export interface Race {
|
||||||
location: RaceLocation;
|
location: RaceLocation;
|
||||||
length: number;
|
length: number;
|
||||||
surface: Surface;
|
surface: Surface;
|
||||||
direction: Direction;
|
direction: Direction;
|
||||||
spans: Span[];
|
spans: Span[];
|
||||||
slopes: Slope[];
|
slopes: Slope[];
|
||||||
thresholds: [] | [Stat] | [Stat, Stat];
|
thresholds: [] | [Stat] | [Stat, Stat];
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum RaceLocation {
|
export enum RaceLocation {
|
||||||
Sapporo = 'Sapporo',
|
Sapporo = 'Sapporo',
|
||||||
Hakodate = 'Hakodate',
|
Hakodate = 'Hakodate',
|
||||||
Niigata = 'Niigata',
|
Niigata = 'Niigata',
|
||||||
Fukushima = 'Fukushima',
|
Fukushima = 'Fukushima',
|
||||||
Nakayama = 'Nakayama',
|
Nakayama = 'Nakayama',
|
||||||
Tokyo = 'Tokyo',
|
Tokyo = 'Tokyo',
|
||||||
Chukyo = 'Chukyo',
|
Chukyo = 'Chukyo',
|
||||||
Kyoto = 'Kyoto',
|
Kyoto = 'Kyoto',
|
||||||
Hanshin = 'Hanshin',
|
Hanshin = 'Hanshin',
|
||||||
Kokura = 'Kokura',
|
Kokura = 'Kokura',
|
||||||
Ooi = 'Ooi',
|
Ooi = 'Ooi',
|
||||||
Kawasaki = 'Kawasaki',
|
Kawasaki = 'Kawasaki',
|
||||||
Funabashi = 'Funabashi',
|
Funabashi = 'Funabashi',
|
||||||
Morioka = 'Morioka',
|
Morioka = 'Morioka',
|
||||||
Longchamp = 'Longchamp',
|
Longchamp = 'Longchamp',
|
||||||
SantaAnitaPark = 'Santa Anita Park',
|
SantaAnitaPark = 'Santa Anita Park',
|
||||||
DelMar = 'Del Mar',
|
DelMar = 'Del Mar',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum Direction {
|
export enum Direction {
|
||||||
Right,
|
Right,
|
||||||
Left,
|
Left,
|
||||||
Stretch,
|
Stretch,
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SpanType {
|
export enum SpanType {
|
||||||
FrontStraight,
|
FrontStraight,
|
||||||
AcrossStraight,
|
AcrossStraight,
|
||||||
FalseStraight,
|
FalseStraight,
|
||||||
Corner1,
|
Corner1,
|
||||||
Corner2,
|
Corner2,
|
||||||
Corner3,
|
Corner3,
|
||||||
Corner4,
|
Corner4,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Span {
|
export interface Span {
|
||||||
type: SpanType;
|
type: SpanType;
|
||||||
span: [number, number];
|
span: [number, number];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Slope {
|
export interface Slope {
|
||||||
per: -2 | -1.5 | -1 | 1 | 1.5 | 2;
|
per: -2 | -1.5 | -1 | 1 | 1.5 | 2;
|
||||||
span: [number, number];
|
span: [number, number];
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortSlopes(slopes: Slope[]): Slope[] {
|
function sortSlopes(slopes: Slope[]): Slope[] {
|
||||||
return slopes.toSorted((a, b) => a.span[0] - b.span[0]);
|
return slopes.toSorted((a, b) => a.span[0] - b.span[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -67,70 +67,70 @@ function sortSlopes(slopes: Slope[]): Slope[] {
|
|||||||
* @returns Quadruple of phase end points, including separate late and last spurt phases
|
* @returns Quadruple of phase end points, including separate late and last spurt phases
|
||||||
*/
|
*/
|
||||||
export function phases(raceLen: number): [number, number, number, number] {
|
export function phases(raceLen: number): [number, number, number, number] {
|
||||||
const sixth = raceLen / 6;
|
const sixth = raceLen / 6;
|
||||||
const half = raceLen / 2;
|
const half = raceLen / 2;
|
||||||
return [sixth, sixth + half, 2*sixth + half, raceLen];
|
return [sixth, sixth + half, 2 * sixth + half, raceLen];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SectionInfo {
|
export interface SectionInfo {
|
||||||
section: number;
|
section: number;
|
||||||
phase: Phase;
|
phase: Phase;
|
||||||
endRate: number;
|
endRate: number;
|
||||||
posKeep: boolean;
|
posKeep: boolean;
|
||||||
rush: boolean;
|
rush: boolean;
|
||||||
spotStruggleEnter: boolean;
|
spotStruggleEnter: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SECTIONS: ReadonlyArray<SectionInfo> = [
|
export const SECTIONS: ReadonlyArray<SectionInfo> = [
|
||||||
{section: 1, phase: Phase.EarlyRace, endRate: 1/24, posKeep: true, rush: false, spotStruggleEnter: false},
|
{ section: 1, phase: Phase.EarlyRace, endRate: 1 / 24, posKeep: true, rush: false, spotStruggleEnter: false },
|
||||||
{section: 2, phase: Phase.EarlyRace, endRate: 2/24, posKeep: true, rush: true, spotStruggleEnter: true},
|
{ section: 2, phase: Phase.EarlyRace, endRate: 2 / 24, posKeep: true, rush: true, spotStruggleEnter: true },
|
||||||
{section: 3, phase: Phase.EarlyRace, endRate: 3/24, posKeep: true, rush: true, spotStruggleEnter: true},
|
{ section: 3, phase: Phase.EarlyRace, endRate: 3 / 24, posKeep: true, rush: true, spotStruggleEnter: true },
|
||||||
{section: 4, phase: Phase.EarlyRace, endRate: 4/24, posKeep: true, rush: true, spotStruggleEnter: true},
|
{ section: 4, phase: Phase.EarlyRace, endRate: 4 / 24, posKeep: true, rush: true, spotStruggleEnter: true },
|
||||||
{section: 5, phase: Phase.MidRace, endRate: 5/24, posKeep: true, rush: true, spotStruggleEnter: true},
|
{ section: 5, phase: Phase.MidRace, endRate: 5 / 24, posKeep: true, rush: true, spotStruggleEnter: true },
|
||||||
{section: 6, phase: Phase.MidRace, endRate: 6/24, posKeep: true, rush: true, spotStruggleEnter: false},
|
{ section: 6, phase: Phase.MidRace, endRate: 6 / 24, posKeep: true, rush: true, spotStruggleEnter: false },
|
||||||
{section: 7, phase: Phase.MidRace, endRate: 7/24, posKeep: true, rush: true, spotStruggleEnter: false},
|
{ section: 7, phase: Phase.MidRace, endRate: 7 / 24, posKeep: true, rush: true, spotStruggleEnter: false },
|
||||||
{section: 8, phase: Phase.MidRace, endRate: 8/24, posKeep: true, rush: true, spotStruggleEnter: false},
|
{ section: 8, phase: Phase.MidRace, endRate: 8 / 24, posKeep: true, rush: true, spotStruggleEnter: false },
|
||||||
{section: 9, phase: Phase.MidRace, endRate: 9/24, posKeep: true, rush: true, spotStruggleEnter: false},
|
{ section: 9, phase: Phase.MidRace, endRate: 9 / 24, posKeep: true, rush: true, spotStruggleEnter: false },
|
||||||
{section: 10, phase: Phase.MidRace, endRate: 10/24, posKeep: true, rush: false, spotStruggleEnter: false},
|
{ section: 10, phase: Phase.MidRace, endRate: 10 / 24, posKeep: true, rush: false, spotStruggleEnter: false },
|
||||||
{section: 11, phase: Phase.MidRace, endRate: 11/24, posKeep: false, rush: false, spotStruggleEnter: false},
|
{ section: 11, phase: Phase.MidRace, endRate: 11 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
||||||
{section: 12, phase: Phase.MidRace, endRate: 12/24, posKeep: false, rush: false, spotStruggleEnter: false},
|
{ section: 12, phase: Phase.MidRace, endRate: 12 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
||||||
{section: 13, phase: Phase.MidRace, endRate: 13/24, posKeep: false, rush: false, spotStruggleEnter: false},
|
{ section: 13, phase: Phase.MidRace, endRate: 13 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
||||||
{section: 14, phase: Phase.MidRace, endRate: 14/24, posKeep: false, rush: false, spotStruggleEnter: false},
|
{ section: 14, phase: Phase.MidRace, endRate: 14 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
||||||
{section: 15, phase: Phase.MidRace, endRate: 15/24, posKeep: false, rush: false, spotStruggleEnter: false},
|
{ section: 15, phase: Phase.MidRace, endRate: 15 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
||||||
{section: 16, phase: Phase.MidRace, endRate: 16/24, posKeep: false, rush: false, spotStruggleEnter: false},
|
{ section: 16, phase: Phase.MidRace, endRate: 16 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
||||||
{section: 17, phase: Phase.LateRace, endRate: 17/24, posKeep: false, rush: false, spotStruggleEnter: false},
|
{ section: 17, phase: Phase.LateRace, endRate: 17 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
||||||
{section: 18, phase: Phase.LateRace, endRate: 18/24, posKeep: false, rush: false, spotStruggleEnter: false},
|
{ section: 18, phase: Phase.LateRace, endRate: 18 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
||||||
{section: 19, phase: Phase.LateRace, endRate: 19/24, posKeep: false, rush: false, spotStruggleEnter: false},
|
{ section: 19, phase: Phase.LateRace, endRate: 19 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
||||||
{section: 20, phase: Phase.LateRace, endRate: 20/24, posKeep: false, rush: false, spotStruggleEnter: false},
|
{ section: 20, phase: Phase.LateRace, endRate: 20 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
||||||
{section: 21, phase: Phase.LateRace, endRate: 21/24, posKeep: false, rush: false, spotStruggleEnter: false},
|
{ section: 21, phase: Phase.LateRace, endRate: 21 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
||||||
{section: 22, phase: Phase.LateRace, endRate: 22/24, posKeep: false, rush: false, spotStruggleEnter: false},
|
{ section: 22, phase: Phase.LateRace, endRate: 22 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
||||||
{section: 23, phase: Phase.LateRace, endRate: 23/24, posKeep: false, rush: false, spotStruggleEnter: false},
|
{ section: 23, phase: Phase.LateRace, endRate: 23 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
||||||
{section: 24, phase: Phase.LateRace, endRate: 24/24, posKeep: false, rush: false, spotStruggleEnter: false},
|
{ section: 24, phase: Phase.LateRace, endRate: 24 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
||||||
];
|
];
|
||||||
|
|
||||||
export interface SectionSlopeSegment extends SectionInfo {
|
export interface SectionSlopeSegment extends SectionInfo {
|
||||||
startDist: number;
|
startDist: number;
|
||||||
endDist: number;
|
endDist: number;
|
||||||
slope: -2 | -1.5 | -1 | 0 | 1 | 1.5 | 2;
|
slope: -2 | -1.5 | -1 | 0 | 1 | 1.5 | 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function splitSectionSlopes(raceLen: number, slopes: Slope[]): SectionSlopeSegment[] {
|
export function splitSectionSlopes(raceLen: number, slopes: Slope[]): SectionSlopeSegment[] {
|
||||||
const ss = sortSlopes(slopes);
|
const ss = sortSlopes(slopes);
|
||||||
return SECTIONS.flatMap<SectionSlopeSegment>((sec) => {
|
return SECTIONS.flatMap<SectionSlopeSegment>((sec) => {
|
||||||
const startDist = (sec.section - 1) * raceLen / 24;
|
const startDist = ((sec.section - 1) * raceLen) / 24;
|
||||||
const endDist = sec.section * raceLen / 24;
|
const endDist = (sec.section * raceLen) / 24;
|
||||||
const s = ss.filter((sl) => sl.span[0] < endDist && sl.span[1] > startDist);
|
const s = ss.filter((sl) => sl.span[0] < endDist && sl.span[1] > startDist);
|
||||||
if (s.length === 0) {
|
if (s.length === 0) {
|
||||||
return [{...sec, startDist, endDist, slope: 0}];
|
return [{ ...sec, startDist, endDist, slope: 0 }];
|
||||||
}
|
}
|
||||||
const r: Array<SectionSlopeSegment> = [{...sec, startDist, endDist: math.min(s[0].span[0], endDist), slope: 0}];
|
const r: Array<SectionSlopeSegment> = [{ ...sec, startDist, endDist: math.min(s[0].span[0], endDist), slope: 0 }];
|
||||||
for (const [i, sl] of s.entries()) {
|
for (const [i, sl] of s.entries()) {
|
||||||
const slopeEnd = math.min(endDist, sl.span[1]);
|
const slopeEnd = math.min(endDist, sl.span[1]);
|
||||||
r.push(
|
r.push(
|
||||||
{...sec, startDist: math.max(startDist, sl.span[0]), endDist: slopeEnd, slope: sl.per},
|
{ ...sec, startDist: math.max(startDist, sl.span[0]), endDist: slopeEnd, slope: sl.per },
|
||||||
{...sec, startDist: slopeEnd, endDist: math.min(s[i+1]?.span[0] ?? endDist, endDist), slope: 0},
|
{ ...sec, startDist: slopeEnd, endDist: math.min(s[i + 1]?.span[0] ?? endDist, endDist), slope: 0 },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return r.filter((sl) => sl.startDist < sl.endDist);
|
return r.filter((sl) => sl.startDist < sl.endDist);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,356 +1,390 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import * as course from '$lib/course';
|
import * as course from '$lib/course';
|
||||||
import * as race from '$lib/race';
|
import * as race from '$lib/race';
|
||||||
|
|
||||||
let rawStats = $state({
|
let rawStats = $state({
|
||||||
[race.Stat.Speed]: 1200,
|
[race.Stat.Speed]: 1200,
|
||||||
[race.Stat.Stamina]: 1200,
|
[race.Stat.Stamina]: 1200,
|
||||||
[race.Stat.Power]: 1200,
|
[race.Stat.Power]: 1200,
|
||||||
[race.Stat.Guts]: 1200,
|
[race.Stat.Guts]: 1200,
|
||||||
[race.Stat.Wit]: 1200,
|
[race.Stat.Wit]: 1200,
|
||||||
});
|
});
|
||||||
let style = $state(race.RunningStyle.FrontRunner);
|
let style = $state(race.RunningStyle.FrontRunner);
|
||||||
let surfaceApt = $state(race.AptitudeLevel.A);
|
let surfaceApt = $state(race.AptitudeLevel.A);
|
||||||
let distanceApt = $state(race.AptitudeLevel.S);
|
let distanceApt = $state(race.AptitudeLevel.S);
|
||||||
let styleApt = $state(race.AptitudeLevel.A);
|
let styleApt = $state(race.AptitudeLevel.A);
|
||||||
let mood = $state(race.Mood.Normal);
|
let mood = $state(race.Mood.Normal);
|
||||||
let isCareer = $state(false);
|
let isCareer = $state(false);
|
||||||
let skillStatBonus = $state({
|
let skillStatBonus = $state({
|
||||||
[race.Stat.Speed]: 0,
|
[race.Stat.Speed]: 0,
|
||||||
[race.Stat.Stamina]: 0,
|
[race.Stat.Stamina]: 0,
|
||||||
[race.Stat.Power]: 0,
|
[race.Stat.Power]: 0,
|
||||||
[race.Stat.Guts]: 0,
|
[race.Stat.Guts]: 0,
|
||||||
[race.Stat.Wit]: 0,
|
[race.Stat.Wit]: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
let raceLen = $state(2000);
|
|
||||||
let slopesRaw: course.Slope[] = $state([]);
|
|
||||||
let surface = $state(race.Surface.Turf);
|
|
||||||
let conditions = $state(race.GroundConditions.Firm);
|
|
||||||
let thresh1: race.Stat | undefined = $state();
|
|
||||||
let thresh2: race.Stat | undefined = $state();
|
|
||||||
|
|
||||||
const rushedOpts = ['No Rushed', 'Rushed Enabled', 'Rushed Forced'] as const;
|
let raceLen = $state(2000);
|
||||||
let rushedType: typeof rushedOpts[number] = $state('No Rushed');
|
let slopesRaw: course.Slope[] = $state([]);
|
||||||
let spotStruggleEnabled = $state(false);
|
let surface = $state(race.Surface.Turf);
|
||||||
|
let conditions = $state(race.GroundConditions.Firm);
|
||||||
|
let thresh1: race.Stat | undefined = $state();
|
||||||
|
let thresh2: race.Stat | undefined = $state();
|
||||||
|
|
||||||
const baseStats = $derived({
|
const rushedOpts = ['No Rushed', 'Rushed Enabled', 'Rushed Forced'] as const;
|
||||||
[race.Stat.Speed]: race.baseStat(mood, rawStats[race.Stat.Speed]),
|
let rushedType: (typeof rushedOpts)[number] = $state('No Rushed');
|
||||||
[race.Stat.Stamina]: race.baseStat(mood, rawStats[race.Stat.Stamina]),
|
let spotStruggleEnabled = $state(false);
|
||||||
[race.Stat.Power]: race.baseStat(mood, rawStats[race.Stat.Power]),
|
|
||||||
[race.Stat.Guts]: race.baseStat(mood, rawStats[race.Stat.Guts]),
|
|
||||||
[race.Stat.Wit]: race.baseStat(mood, rawStats[race.Stat.Wit]),
|
|
||||||
});
|
|
||||||
|
|
||||||
const thresholdMod = $derived.by(() => {
|
const baseStats = $derived({
|
||||||
if (thresh1 == null) {
|
[race.Stat.Speed]: race.baseStat(mood, rawStats[race.Stat.Speed]),
|
||||||
if (thresh2 == null) {
|
[race.Stat.Stamina]: race.baseStat(mood, rawStats[race.Stat.Stamina]),
|
||||||
return 1;
|
[race.Stat.Power]: race.baseStat(mood, rawStats[race.Stat.Power]),
|
||||||
}
|
[race.Stat.Guts]: race.baseStat(mood, rawStats[race.Stat.Guts]),
|
||||||
return race.thresholdMod(baseStats[thresh2]);
|
[race.Stat.Wit]: race.baseStat(mood, rawStats[race.Stat.Wit]),
|
||||||
}
|
});
|
||||||
if (thresh2 == null) {
|
|
||||||
return race.thresholdMod(baseStats[thresh1]);
|
|
||||||
}
|
|
||||||
return race.thresholdMod(baseStats[thresh1], baseStats[thresh2]);
|
|
||||||
});
|
|
||||||
|
|
||||||
const adjStats = $derived({
|
const thresholdMod = $derived.by(() => {
|
||||||
[race.Stat.Speed]: race.adjustedStat(race.Stat.Speed, baseStats[race.Stat.Speed], isCareer, surface, conditions, thresholdMod),
|
if (thresh1 == null) {
|
||||||
[race.Stat.Stamina]: race.adjustedStat(race.Stat.Stamina, baseStats[race.Stat.Stamina], isCareer),
|
if (thresh2 == null) {
|
||||||
[race.Stat.Power]: race.adjustedStat(race.Stat.Power, baseStats[race.Stat.Power], isCareer, surface, conditions),
|
return 1;
|
||||||
[race.Stat.Guts]: race.adjustedStat(race.Stat.Guts, baseStats[race.Stat.Guts], isCareer),
|
}
|
||||||
[race.Stat.Wit]: race.adjustedStat(race.Stat.Wit, baseStats[race.Stat.Wit], isCareer, styleApt),
|
return race.thresholdMod(baseStats[thresh2]);
|
||||||
});
|
}
|
||||||
|
if (thresh2 == null) {
|
||||||
|
return race.thresholdMod(baseStats[thresh1]);
|
||||||
|
}
|
||||||
|
return race.thresholdMod(baseStats[thresh1], baseStats[thresh2]);
|
||||||
|
});
|
||||||
|
|
||||||
const stats = $derived({
|
const adjStats = $derived({
|
||||||
[race.Stat.Speed]: race.finalStat(adjStats[race.Stat.Speed], skillStatBonus[race.Stat.Speed]),
|
[race.Stat.Speed]: race.adjustedStat(
|
||||||
[race.Stat.Stamina]: race.finalStat(adjStats[race.Stat.Stamina], skillStatBonus[race.Stat.Stamina]),
|
race.Stat.Speed,
|
||||||
[race.Stat.Power]: race.finalStat(adjStats[race.Stat.Power], skillStatBonus[race.Stat.Power]),
|
baseStats[race.Stat.Speed],
|
||||||
[race.Stat.Guts]: race.finalStat(adjStats[race.Stat.Guts], skillStatBonus[race.Stat.Guts]),
|
isCareer,
|
||||||
[race.Stat.Wit]: race.finalStat(adjStats[race.Stat.Wit], skillStatBonus[race.Stat.Wit]),
|
surface,
|
||||||
});
|
conditions,
|
||||||
|
thresholdMod,
|
||||||
|
),
|
||||||
|
[race.Stat.Stamina]: race.adjustedStat(race.Stat.Stamina, baseStats[race.Stat.Stamina], isCareer),
|
||||||
|
[race.Stat.Power]: race.adjustedStat(race.Stat.Power, baseStats[race.Stat.Power], isCareer, surface, conditions),
|
||||||
|
[race.Stat.Guts]: race.adjustedStat(race.Stat.Guts, baseStats[race.Stat.Guts], isCareer),
|
||||||
|
[race.Stat.Wit]: race.adjustedStat(race.Stat.Wit, baseStats[race.Stat.Wit], isCareer, styleApt),
|
||||||
|
});
|
||||||
|
|
||||||
const distType = $derived(race.distance(raceLen));
|
const stats = $derived({
|
||||||
const phaseSpeed = $derived({
|
[race.Stat.Speed]: race.finalStat(adjStats[race.Stat.Speed], skillStatBonus[race.Stat.Speed]),
|
||||||
[race.Phase.EarlyRace]: race.sectionSpeed(raceLen, baseStats[race.Stat.Wit], stats[race.Stat.Wit], style, race.Phase.EarlyRace),
|
[race.Stat.Stamina]: race.finalStat(adjStats[race.Stat.Stamina], skillStatBonus[race.Stat.Stamina]),
|
||||||
[race.Phase.MidRace]: race.sectionSpeed(raceLen, baseStats[race.Stat.Wit], stats[race.Stat.Wit], style, race.Phase.MidRace),
|
[race.Stat.Power]: race.finalStat(adjStats[race.Stat.Power], skillStatBonus[race.Stat.Power]),
|
||||||
[race.Phase.LateRace]: [
|
[race.Stat.Guts]: race.finalStat(adjStats[race.Stat.Guts], skillStatBonus[race.Stat.Guts]),
|
||||||
race.spurtSpeed(stats[race.Stat.Speed], stats[race.Stat.Guts], style, distanceApt, raceLen),
|
[race.Stat.Wit]: race.finalStat(adjStats[race.Stat.Wit], skillStatBonus[race.Stat.Wit]),
|
||||||
race.spurtSpeed(stats[race.Stat.Speed], stats[race.Stat.Guts], style, distanceApt, raceLen),
|
});
|
||||||
] as [number, number],
|
|
||||||
});
|
|
||||||
|
|
||||||
const isFront = $derived(style === race.RunningStyle.FrontRunner || style === race.RunningStyle.GreatEscape);
|
const distType = $derived(race.distance(raceLen));
|
||||||
const spotStruggle = $derived(spotStruggleEnabled && isFront);
|
const phaseSpeed = $derived({
|
||||||
|
[race.Phase.EarlyRace]: race.sectionSpeed(
|
||||||
|
raceLen,
|
||||||
|
baseStats[race.Stat.Wit],
|
||||||
|
stats[race.Stat.Wit],
|
||||||
|
style,
|
||||||
|
race.Phase.EarlyRace,
|
||||||
|
),
|
||||||
|
[race.Phase.MidRace]: race.sectionSpeed(raceLen, baseStats[race.Stat.Wit], stats[race.Stat.Wit], style, race.Phase.MidRace),
|
||||||
|
[race.Phase.LateRace]: [
|
||||||
|
race.spurtSpeed(stats[race.Stat.Speed], stats[race.Stat.Guts], style, distanceApt, raceLen),
|
||||||
|
race.spurtSpeed(stats[race.Stat.Speed], stats[race.Stat.Guts], style, distanceApt, raceLen),
|
||||||
|
] as [number, number],
|
||||||
|
});
|
||||||
|
|
||||||
const maxHP = $derived(race.maxHP(raceLen, style, stats[race.Stat.Stamina]));
|
const isFront = $derived(style === race.RunningStyle.FrontRunner || style === race.RunningStyle.Runaway);
|
||||||
|
const spotStruggle = $derived(spotStruggleEnabled && isFront);
|
||||||
|
|
||||||
const sections = $derived.by(() => {
|
const maxHP = $derived(race.maxHP(raceLen, style, stats[race.Stat.Stamina]));
|
||||||
const r = course.splitSectionSlopes(raceLen, slopesRaw)
|
|
||||||
.map((s) => {
|
|
||||||
const uphillMod = s.slope > 0 ? race.uphillMod(stats[race.Stat.Power], s.slope) : 0;
|
|
||||||
const speed = phaseSpeed[s.phase].map((v) => v + uphillMod);
|
|
||||||
const time = [(s.endDist - s.startDist) / speed[1], (s.endDist - s.startDist) / speed[0]];
|
|
||||||
const hp = [
|
|
||||||
time[0] * race.hpPerSecond(raceLen, surface, conditions, stats[race.Stat.Guts], s.phase, speed[0]),
|
|
||||||
time[1] * race.hpPerSecond(raceLen, surface, conditions, stats[race.Stat.Guts], s.phase, speed[1]),
|
|
||||||
];
|
|
||||||
return {...s, speed, time, hp, remain: [0, 0]};
|
|
||||||
});
|
|
||||||
let remain = [maxHP, maxHP];
|
|
||||||
for (const v of r) {
|
|
||||||
remain = [remain[0] - v.hp[0], remain[1] - v.hp[1]];
|
|
||||||
v.remain = [...remain];
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const spurtHPRate = $derived(race.hpPerSecond(raceLen, surface, conditions, stats[race.Stat.Guts], race.Phase.LateRace, phaseSpeed[race.Phase.LateRace][0]));
|
const sections = $derived.by(() => {
|
||||||
const needHP = $derived(race.fullSpurtHP(raceLen, phaseSpeed[race.Phase.LateRace][0], spurtHPRate));
|
const r = course.splitSectionSlopes(raceLen, slopesRaw).map((s) => {
|
||||||
const spurtStartHP = $derived(sections.findLast((s) => s.phase !== race.Phase.LateRace)?.remain);
|
const uphillMod = s.slope > 0 ? race.uphillMod(stats[race.Stat.Power], s.slope) : 0;
|
||||||
|
const speed = phaseSpeed[s.phase].map((v) => v + uphillMod);
|
||||||
|
const time = [(s.endDist - s.startDist) / speed[1], (s.endDist - s.startDist) / speed[0]];
|
||||||
|
const hp = [
|
||||||
|
time[0] * race.hpPerSecond(raceLen, surface, conditions, stats[race.Stat.Guts], s.phase, speed[0]),
|
||||||
|
time[1] * race.hpPerSecond(raceLen, surface, conditions, stats[race.Stat.Guts], s.phase, speed[1]),
|
||||||
|
];
|
||||||
|
return { ...s, speed, time, hp, remain: [0, 0] };
|
||||||
|
});
|
||||||
|
let remain = [maxHP, maxHP];
|
||||||
|
for (const v of r) {
|
||||||
|
remain = [remain[0] - v.hp[0], remain[1] - v.hp[1]];
|
||||||
|
v.remain = [...remain];
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
});
|
||||||
|
|
||||||
let slopeAddParams: course.Slope = $state({per: 1, span: [0, 0]})
|
const spurtHPRate = $derived(
|
||||||
function addSlope() {
|
race.hpPerSecond(
|
||||||
// TODO(zeph): validate no overlap with current slopes
|
raceLen,
|
||||||
slopesRaw.push(slopeAddParams);
|
surface,
|
||||||
slopeAddParams = {per: 1, span: [0, 0]};
|
conditions,
|
||||||
}
|
stats[race.Stat.Guts],
|
||||||
function removeSlope(i: number) {
|
race.Phase.LateRace,
|
||||||
if (i < 0 || i >= slopesRaw.length) {
|
phaseSpeed[race.Phase.LateRace][0],
|
||||||
return;
|
),
|
||||||
}
|
);
|
||||||
slopesRaw.splice(i, 1);
|
const needHP = $derived(race.fullSpurtHP(raceLen, phaseSpeed[race.Phase.LateRace][0], spurtHPRate));
|
||||||
}
|
const spurtStartHP = $derived(sections.findLast((s) => s.phase !== race.Phase.LateRace)?.remain);
|
||||||
|
|
||||||
const phaseNames = {
|
let slopeAddParams: course.Slope = $state({ per: 1, span: [0, 0] });
|
||||||
[race.Phase.EarlyRace]: "Early",
|
function addSlope() {
|
||||||
[race.Phase.MidRace]: "Mid",
|
// TODO(zeph): validate no overlap with current slopes
|
||||||
[race.Phase.LateRace]: "Late",
|
slopesRaw.push(slopeAddParams);
|
||||||
} as const;
|
slopeAddParams = { per: 1, span: [0, 0] };
|
||||||
|
}
|
||||||
|
function removeSlope(i: number) {
|
||||||
|
if (i < 0 || i >= slopesRaw.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
slopesRaw.splice(i, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const phaseNames = {
|
||||||
|
[race.Phase.EarlyRace]: 'Early',
|
||||||
|
[race.Phase.MidRace]: 'Mid',
|
||||||
|
[race.Phase.LateRace]: 'Late',
|
||||||
|
} as const;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1 class="text-4xl">Stamina Calculator</h1>
|
<h1 class="text-4xl">Stamina Calculator</h1>
|
||||||
<div class="mx-auto mt-8 grid max-w-5xl grid-cols-1 rounded-md text-center shadow-md ring md:grid-cols-6">
|
<div class="mx-auto mt-8 grid max-w-5xl grid-cols-1 rounded-md text-center shadow-md ring md:grid-cols-6">
|
||||||
{#each race.StatList as stat (stat)}
|
{#each race.StatList as stat (stat)}
|
||||||
{@const statName = race.Stat[stat]}
|
{@const statName = race.Stat[stat]}
|
||||||
<div class="m-4">
|
<div class="m-4">
|
||||||
<label for={statName}>{statName}</label>
|
<label for={statName}>{statName}</label>
|
||||||
<input type="number" class="w-full" id={statName} required bind:value={rawStats[stat]} />
|
<input type="number" class="w-full" id={statName} required bind:value={rawStats[stat]} />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
<div class="m-4">
|
<div class="m-4">
|
||||||
<label for="mood">Mood</label>
|
<label for="mood">Mood</label>
|
||||||
<select class="w-full" id="mood" required bind:value={mood}>
|
<select class="w-full" id="mood" required bind:value={mood}>
|
||||||
{#each race.MOODS as mood (mood)}
|
{#each race.MOODS as mood (mood)}
|
||||||
<option value={mood}>{race.Mood[mood]}</option>
|
<option value={mood}>{race.Mood[mood]}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="m-4">
|
<div class="m-4">
|
||||||
<label for="style">Style</label>
|
<label for="style">Style</label>
|
||||||
<select class="w-full" id="style" required bind:value={style}>
|
<select class="w-full" id="style" required bind:value={style}>
|
||||||
{#each race.RUNNING_STYLES as [name, style] (style)}
|
{#each race.RUNNING_STYLES as [name, style] (style)}
|
||||||
<option value={style}>{name}</option>
|
<option value={style}>{name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="m-4">
|
<div class="m-4">
|
||||||
<label for="distanceApt">{race.Distance[distType]} Aptitude</label>
|
<label for="distanceApt">{race.Distance[distType]} Aptitude</label>
|
||||||
<select class="w-full" id="distanceApt" required bind:value={distanceApt}>
|
<select class="w-full" id="distanceApt" required bind:value={distanceApt}>
|
||||||
{#each race.APTITUDE_LEVELS as apt (apt)}
|
{#each race.APTITUDE_LEVELS as apt (apt)}
|
||||||
<option value={apt}>{race.AptitudeLevel[apt]}</option>
|
<option value={apt}>{race.AptitudeLevel[apt]}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="m-4">
|
<div class="m-4">
|
||||||
<label for="surfaceApt">{race.Surface[surface]} Aptitude</label>
|
<label for="surfaceApt">{race.Surface[surface]} Aptitude</label>
|
||||||
<select class="w-full" id="surfaceApt" required bind:value={surfaceApt}>
|
<select class="w-full" id="surfaceApt" required bind:value={surfaceApt}>
|
||||||
{#each race.APTITUDE_LEVELS as apt (apt)}
|
{#each race.APTITUDE_LEVELS as apt (apt)}
|
||||||
<option value={apt}>{race.AptitudeLevel[apt]}</option>
|
<option value={apt}>{race.AptitudeLevel[apt]}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="m-4">
|
<div class="m-4">
|
||||||
<label for="styleApt">{race.RUNNING_STYLES[style != race.RunningStyle.GreatEscape ? style : race.RunningStyle.FrontRunner][0]}</label>
|
<label for="styleApt"
|
||||||
<select class="w-full" id="styleApt" required bind:value={styleApt}>
|
>{race.RUNNING_STYLES[style != race.RunningStyle.Runaway ? style : race.RunningStyle.FrontRunner][0]}</label
|
||||||
{#each race.APTITUDE_LEVELS as apt (apt)}
|
>
|
||||||
<option value={apt}>{race.AptitudeLevel[apt]}</option>
|
<select class="w-full" id="styleApt" required bind:value={styleApt}>
|
||||||
{/each}
|
{#each race.APTITUDE_LEVELS as apt (apt)}
|
||||||
</select>
|
<option value={apt}>{race.AptitudeLevel[apt]}</option>
|
||||||
</div>
|
{/each}
|
||||||
|
</select>
|
||||||
|
</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>
|
||||||
<input type="checkbox" id="isCareer" role="switch" bind:checked={isCareer} class="min-h-6 min-w-6 align-middle" />
|
<input type="checkbox" id="isCareer" role="switch" bind:checked={isCareer} class="min-h-6 min-w-6 align-middle" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex col-start-1 col-span-full">
|
<div class="col-span-full col-start-1 flex">
|
||||||
<div class="grow h-px border-t place-self-center"></div>
|
<div class="h-px grow place-self-center border-t"></div>
|
||||||
<span class="flex-none mx-4">Skill Passives</span>
|
<span class="mx-4 flex-none">Skill Passives</span>
|
||||||
<div class="grow h-px border-t place-self-center"></div>
|
<div class="h-px grow place-self-center border-t"></div>
|
||||||
</div>
|
</div>
|
||||||
{#each race.StatList as stat (stat)}
|
{#each race.StatList as stat (stat)}
|
||||||
{@const statName = race.Stat[stat]}
|
{@const statName = race.Stat[stat]}
|
||||||
<div class="m-4">
|
<div class="m-4">
|
||||||
<label for={`${statName}Bonus`}>{statName}</label>
|
<label for={`${statName}Bonus`}>{statName}</label>
|
||||||
<input type="number" class="w-full" id={`${statName}Bonus`} required bind:value={skillStatBonus[stat]} />
|
<input type="number" class="w-full" id={`${statName}Bonus`} required bind:value={skillStatBonus[stat]} />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
<div class="mx-auto mt-8 grid gap-4 max-w-5xl grid-cols-1 rounded-md text-center shadow-md ring md:grid-cols-6">
|
<div class="mx-auto mt-8 grid max-w-5xl grid-cols-1 gap-4 rounded-md text-center shadow-md ring md:grid-cols-6">
|
||||||
<div class="m-4 md:col-span-2 flex">
|
<div class="m-4 flex md:col-span-2">
|
||||||
<input class="col-span-2 my-auto w-full justify-self-center-safe" type="range" id="raceLen" min="1000" max="3600" step="100" bind:value={raceLen} />
|
<input
|
||||||
<span class="my-auto pl-2 text-sm">{raceLen}m</span>
|
class="col-span-2 my-auto w-full justify-self-center-safe"
|
||||||
</div>
|
type="range"
|
||||||
<select id="surface" class="m-4" bind:value={surface}>
|
id="raceLen"
|
||||||
<option value={race.Surface.Turf}>Turf</option>
|
min="1000"
|
||||||
<option value={race.Surface.Dirt}>Dirt</option>
|
max="3600"
|
||||||
</select>
|
step="100"
|
||||||
<select id="conditions" class="m-4" bind:value={conditions}>
|
bind:value={raceLen}
|
||||||
{#each race.GROUND_CONDITONS as cond (cond)}
|
/>
|
||||||
<option value={cond}>{race.GroundConditions[cond]}</option>
|
<span class="my-auto pl-2 text-sm">{raceLen}m</span>
|
||||||
{/each}
|
</div>
|
||||||
</select>
|
<select id="surface" class="m-4" bind:value={surface}>
|
||||||
<div class="m-4">
|
<option value={race.Surface.Turf}>Turf</option>
|
||||||
<label for="thresh1">Threshold 1</label>
|
<option value={race.Surface.Dirt}>Dirt</option>
|
||||||
<select id="thresh1" class="w-full" bind:value={thresh1}>
|
</select>
|
||||||
<option value={undefined}></option>
|
<select id="conditions" class="m-4" bind:value={conditions}>
|
||||||
{#each race.StatList as stat (stat)}
|
{#each race.GROUND_CONDITONS as cond (cond)}
|
||||||
<option value={stat}>{race.Stat[stat]}</option>
|
<option value={cond}>{race.GroundConditions[cond]}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
<div class="m-4">
|
||||||
<div class="m-4">
|
<label for="thresh1">Threshold 1</label>
|
||||||
<label for="thresh2">Threshold 2</label>
|
<select id="thresh1" class="w-full" bind:value={thresh1}>
|
||||||
<select id="thresh2" class="w-full" bind:value={thresh2}>
|
<option value={undefined}></option>
|
||||||
<option value={undefined}></option>
|
{#each race.StatList as stat (stat)}
|
||||||
{#each race.StatList as stat (stat)}
|
<option value={stat}>{race.Stat[stat]}</option>
|
||||||
<option value={stat}>{race.Stat[stat]}</option>
|
{/each}
|
||||||
{/each}
|
</select>
|
||||||
</select>
|
</div>
|
||||||
</div>
|
<div class="m-4">
|
||||||
|
<label for="thresh2">Threshold 2</label>
|
||||||
|
<select id="thresh2" class="w-full" bind:value={thresh2}>
|
||||||
|
<option value={undefined}></option>
|
||||||
|
{#each race.StatList as stat (stat)}
|
||||||
|
<option value={stat}>{race.Stat[stat]}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mx-auto mt-8 p-4 grid grid-cols-2 gap-4 max-w-lg rounded-md text-center shadow-md ring">
|
<div class="mx-auto mt-8 grid max-w-lg grid-cols-2 gap-4 rounded-md p-4 text-center shadow-md ring">
|
||||||
<select id="rushed" class="w-full" bind:value={rushedType}>
|
<select id="rushed" class="w-full" bind:value={rushedType}>
|
||||||
{#each rushedOpts as opt (opt)}
|
{#each rushedOpts as opt (opt)}
|
||||||
<option>{opt}</option>
|
<option>{opt}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
<div class="place-self-center">
|
<div class="place-self-center">
|
||||||
<label for="spotStruggle">Spot Struggle</label>
|
<label for="spotStruggle">Spot Struggle</label>
|
||||||
<input type="checkbox" class="min-h-6 min-w-6 align-middle" id="rushed" role="switch" bind:checked={spotStruggleEnabled} />
|
<input type="checkbox" class="min-h-6 min-w-6 align-middle" id="rushed" role="switch" bind:checked={spotStruggleEnabled} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mx-auto mt-8 p-4 max-w-4xl rounded-md text-center shadow-md ring">
|
<div class="mx-auto mt-8 max-w-4xl rounded-md p-4 text-center shadow-md ring">
|
||||||
<!-- TODO(zeph): this doesn't work for mobile -->
|
<!-- TODO(zeph): this doesn't work for mobile -->
|
||||||
<table class="w-full table-fixed border-spacing-4">
|
<table class="w-full table-fixed border-spacing-4">
|
||||||
<caption>Slopes</caption>
|
<caption>Slopes</caption>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">Slope</th>
|
<th scope="col">Slope</th>
|
||||||
<th scope="col">Start</th>
|
<th scope="col">Start</th>
|
||||||
<th scope="col">End</th>
|
<th scope="col">End</th>
|
||||||
<th scope="col"><!-- empty --></th>
|
<th scope="col"><!-- empty --></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each slopesRaw as slope, i (slope.span[0])}
|
{#each slopesRaw as slope, i (slope.span[0])}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{slope.per}</td>
|
<td>{slope.per}</td>
|
||||||
<td>{slope.span[0]}</td>
|
<td>{slope.span[0]}</td>
|
||||||
<td>{slope.span[1]}</td>
|
<td>{slope.span[1]}</td>
|
||||||
<td>
|
<td>
|
||||||
<button type="button" class="p-2 w-24 bg-mist-300 hover:cursor-pointer dark:bg-mist-900" onclick={() => removeSlope(i)}>
|
<button
|
||||||
Remove
|
type="button"
|
||||||
</button>
|
class="w-24 bg-mist-300 p-2 hover:cursor-pointer dark:bg-mist-900"
|
||||||
</td>
|
onclick={() => removeSlope(i)}
|
||||||
</tr>
|
>
|
||||||
{/each}
|
Remove
|
||||||
<tr>
|
</button>
|
||||||
<td>
|
</td>
|
||||||
<select id={`slopeAddPer`} bind:value={slopeAddParams.per}>
|
</tr>
|
||||||
<option value={2}>Uphill +2</option>
|
{/each}
|
||||||
<option value={1.5}>Uphill +1.5</option>
|
<tr>
|
||||||
<option value={1}>Uphill +1</option>
|
<td>
|
||||||
<option value={-1}>Downhill -1</option>
|
<select id={`slopeAddPer`} bind:value={slopeAddParams.per}>
|
||||||
<option value={-1.5}>Downhill -1.5</option>
|
<option value={2}>Uphill +2</option>
|
||||||
<option value={-2}>Downhill -2</option>
|
<option value={1.5}>Uphill +1.5</option>
|
||||||
</select>
|
<option value={1}>Uphill +1</option>
|
||||||
</td>
|
<option value={-1}>Downhill -1</option>
|
||||||
<td>
|
<option value={-1.5}>Downhill -1.5</option>
|
||||||
<input type="number" id="slopeAddStart" bind:value={slopeAddParams.span[0]} />
|
<option value={-2}>Downhill -2</option>
|
||||||
</td>
|
</select>
|
||||||
<td>
|
</td>
|
||||||
<input type="number" id="slopeAddEnd" bind:value={slopeAddParams.span[1]} />
|
<td>
|
||||||
</td>
|
<input type="number" id="slopeAddStart" bind:value={slopeAddParams.span[0]} />
|
||||||
<td>
|
</td>
|
||||||
<button type="button" class="p-2 w-24 bg-mist-300 hover:cursor-pointer dark:bg-mist-900" onclick={addSlope}>
|
<td>
|
||||||
Add
|
<input type="number" id="slopeAddEnd" bind:value={slopeAddParams.span[1]} />
|
||||||
</button>
|
</td>
|
||||||
</td>
|
<td>
|
||||||
</tr>
|
<button type="button" class="w-24 bg-mist-300 p-2 hover:cursor-pointer dark:bg-mist-900" onclick={addSlope}>
|
||||||
</tbody>
|
Add
|
||||||
</table>
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-8 flex mx-auto max-w-3xl">
|
<div class="mx-auto mt-8 flex max-w-3xl">
|
||||||
<div class="m-2 flex-1 rounded-md border text-center shadow-sm transition-shadow hover:shadow-md">
|
<div class="m-2 flex-1 rounded-md border text-center shadow-sm transition-shadow hover:shadow-md">
|
||||||
<span class="block text-lg">Max HP</span>
|
<span class="block text-lg">Max HP</span>
|
||||||
<span class="block text-2xl">{Math.floor(maxHP)}</span>
|
<span class="block text-2xl">{Math.floor(maxHP)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="m-2 flex-1 rounded-md border text-center shadow-sm transition-shadow hover:shadow-md">
|
<div class="m-2 flex-1 rounded-md border text-center shadow-sm transition-shadow hover:shadow-md">
|
||||||
<span class="block text-lg">Spurt Threshold</span>
|
<span class="block text-lg">Spurt Threshold</span>
|
||||||
<span class="block text-2xl">{Math.ceil(needHP)}</span>
|
<span class="block text-2xl">{Math.ceil(needHP)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="m-2 flex-1 rounded-md border text-center shadow-sm transition-shadow hover:shadow-md">
|
<div class="m-2 flex-1 rounded-md border text-center shadow-sm transition-shadow hover:shadow-md">
|
||||||
<span class="block text-lg">Late Race Start</span>
|
<span class="block text-lg">Late Race Start</span>
|
||||||
{#if spurtStartHP != null}
|
{#if spurtStartHP != null}
|
||||||
<span class="block text-2xl">{Math.ceil(spurtStartHP[1])} – {Math.ceil(spurtStartHP[0])}</span>
|
<span class="block text-2xl">{Math.ceil(spurtStartHP[1])} – {Math.ceil(spurtStartHP[0])}</span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="mt-8 w-full border text-center">
|
<table class="mt-8 w-full border text-center">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="colgroup" colspan="4" class="border-x">Section Data</th>
|
<th scope="colgroup" colspan="4" class="border-x">Section Data</th>
|
||||||
<th scope="colgroup" colspan={spotStruggle ? 2 : 1} class="border-x">Section State</th>
|
<th scope="colgroup" colspan={spotStruggle ? 2 : 1} class="border-x">Section State</th>
|
||||||
<th scope="colgroup" colspan="4" class="border-x">Fastest</th>
|
<th scope="colgroup" colspan="4" class="border-x">Fastest</th>
|
||||||
<th scope="colgroup" colspan="4" class="border-x">Slowest</th>
|
<th scope="colgroup" colspan="4" class="border-x">Slowest</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">Section</th>
|
<th scope="col">Section</th>
|
||||||
<th scope="col">Phase</th>
|
<th scope="col">Phase</th>
|
||||||
<th scope="col" colspan="2">Span</th>
|
<th scope="col" colspan="2">Span</th>
|
||||||
<th scope="col">Slope</th>
|
<th scope="col">Slope</th>
|
||||||
<th scope="col" class:hidden={!spotStruggle}>Spot Struggle</th>
|
<th scope="col" class:hidden={!spotStruggle}>Spot Struggle</th>
|
||||||
<th scope="col">Speed</th>
|
<th scope="col">Speed</th>
|
||||||
<th scope="col">Time</th>
|
<th scope="col">Time</th>
|
||||||
<th scope="col">HP</th>
|
<th scope="col">HP</th>
|
||||||
<th scope="col">Remaining</th>
|
<th scope="col">Remaining</th>
|
||||||
<th scope="col">Speed</th>
|
<th scope="col">Speed</th>
|
||||||
<th scope="col">Time</th>
|
<th scope="col">Time</th>
|
||||||
<th scope="col">HP</th>
|
<th scope="col">HP</th>
|
||||||
<th scope="col">Remaining</th>
|
<th scope="col">Remaining</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each sections as s (s.startDist)}
|
{#each sections as s (s.startDist)}
|
||||||
<tr class="even:bg-mist-300 dark:even:bg-mist-900" class:font-bold={s.phase === race.Phase.LateRace}>
|
<tr class="even:bg-mist-300 dark:even:bg-mist-900" class:font-bold={s.phase === race.Phase.LateRace}>
|
||||||
<td>{s.section}</td>
|
<td>{s.section}</td>
|
||||||
<td>{phaseNames[s.phase]}</td>
|
<td>{phaseNames[s.phase]}</td>
|
||||||
<td>{s.startDist.toFixed(1)} m</td>
|
<td>{s.startDist.toFixed(1)} m</td>
|
||||||
<td>{s.endDist.toFixed(1)} m</td>
|
<td>{s.endDist.toFixed(1)} m</td>
|
||||||
<td>{s.slope}</td>
|
<td>{s.slope}</td>
|
||||||
<td class:hidden={!spotStruggle}></td>
|
<td class:hidden={!spotStruggle}></td>
|
||||||
<td>{s.speed[1].toFixed(3)} m/s</td>
|
<td>{s.speed[1].toFixed(3)} m/s</td>
|
||||||
<td>{s.time[1].toFixed(3)} s</td>
|
<td>{s.time[1].toFixed(3)} s</td>
|
||||||
<td>{s.hp[1].toFixed(1)}</td>
|
<td>{s.hp[1].toFixed(1)}</td>
|
||||||
<td>{s.remain[0].toFixed(1)}</td>
|
<td>{s.remain[0].toFixed(1)}</td>
|
||||||
<td>{s.speed[0].toFixed(3)} m/s</td>
|
<td>{s.speed[0].toFixed(3)} m/s</td>
|
||||||
<td>{s.time[0].toFixed(3)} s</td>
|
<td>{s.time[0].toFixed(3)} s</td>
|
||||||
<td>{s.hp[0].toFixed(1)}</td>
|
<td>{s.hp[0].toFixed(1)}</td>
|
||||||
<td>{s.remain[1].toFixed(1)}</td>
|
<td>{s.remain[1].toFixed(1)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
Reference in New Issue
Block a user