diff --git a/zenno/src/routes/race/stam/+page.svelte b/zenno/src/routes/race/stam/+page.svelte index 80f6f56..136ddb7 100644 --- a/zenno/src/routes/race/stam/+page.svelte +++ b/zenno/src/routes/race/stam/+page.svelte @@ -30,6 +30,10 @@ let thresh1: race.Stat | undefined = $state(); let thresh2: race.Stat | undefined = $state(); + const rushedOpts = ['No Rushed', 'Rushed Enabled', 'Rushed Forced'] as const; + let rushedType: typeof rushedOpts[number] = $state('No Rushed'); + let spotStruggleEnabled = $state(false); + const baseStats = $derived({ [race.Stat.Speed]: race.baseStat(mood, rawStats[race.Stat.Speed]), [race.Stat.Stamina]: race.baseStat(mood, rawStats[race.Stat.Stamina]), @@ -67,20 +71,286 @@ [race.Stat.Wit]: race.finalStat(adjStats[race.Stat.Wit], skillStatBonus[race.Stat.Wit]), }); - let pdmTime = $state(0); - let rushedEnabled = $state(true); - let spotStruggleEnabled = $state(true); - - const phases = $derived(course.phases(raceLen)); - const sectionSlopes = $derived(course.splitSectionSlopes(raceLen, slopesRaw)); - - const sectionLen = $derived(raceLen / course.SECTIONS.length); + const distType = $derived(race.distance(raceLen)); 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 spurtSpeed = $derived(race.spurtSpeed(stats[race.Stat.Speed], stats[race.Stat.Guts], style, distanceApt, raceLen)); - const spurtHPRate = $derived(race.hpPerSecond(raceLen, surface, conditions, stats[race.Stat.Guts], race.Phase.LateRace, spurtSpeed)); - const needHP = $derived(race.fullSpurtHP(raceLen, spurtSpeed, spurtHPRate)); - \ No newline at end of file + const isFront = $derived(style === race.RunningStyle.FrontRunner || style === race.RunningStyle.GreatEscape); + const spotStruggle = $derived(spotStruggleEnabled && isFront); + + const maxHP = $derived(race.maxHP(raceLen, style, stats[race.Stat.Stamina])); + + const sections = $derived.by(() => { + 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 needHP = $derived(race.fullSpurtHP(raceLen, phaseSpeed[race.Phase.LateRace][0], spurtHPRate)); + const spurtStartHP = $derived(sections.findLast((s) => s.phase !== race.Phase.LateRace)?.remain); + + let slopeAddParams: course.Slope = $state({per: 1, span: [0, 0]}) + function addSlope() { + // TODO(zeph): validate no overlap with current slopes + slopesRaw.push(slopeAddParams); + 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; + + +

Stamina Calculator

+
+ {#each race.StatList as stat (stat)} + {@const statName = race.Stat[stat]} +
+ + +
+ {/each} +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ Skill Passives +
+
+ {#each race.StatList as stat (stat)} + {@const statName = race.Stat[stat]} +
+ + +
+ {/each} +
+
+
+ + {raceLen}m +
+ + +
+ + +
+
+ + +
+
+
+ +
+ + +
+
+
+ + + + + + + + + + + + + {#each slopesRaw as slope, i (slope.span[0])} + + + + + + + {/each} + + + + + + + +
Slopes
SlopeStartEnd
{slope.per}{slope.span[0]}{slope.span[1]} + +
+ + + + + + + +
+
+ +
+
+ Max HP + {Math.floor(maxHP)} +
+
+ Spurt Threshold + {Math.ceil(needHP)} +
+
+ Late Race Start + {#if spurtStartHP != null} + {Math.ceil(spurtStartHP[1])} – {Math.ceil(spurtStartHP[0])} + {/if} +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + {#each sections as s (s.startDist)} + + + + + + + + + + + + + + + + + {/each} + +
Section DataSection StateFastestSlowest
SectionPhaseSpanSlopeSpot StruggleSpeedTimeHPRemainingSpeedTimeHPRemaining
{s.section}{phaseNames[s.phase]}{s.startDist.toFixed(1)} m{s.endDist.toFixed(1)} m{s.slope}{s.speed[1].toFixed(3)} m/s{s.time[1].toFixed(3)} s{s.hp[1].toFixed(1)}{s.remain[0].toFixed(1)}{s.speed[0].toFixed(3)} m/s{s.time[0].toFixed(3)} s{s.hp[0].toFixed(1)}{s.remain[1].toFixed(1)}
\ No newline at end of file