diff --git a/zenno/src/lib/race.ts b/zenno/src/lib/race.ts index 8fb8fa2..2af2974 100644 --- a/zenno/src/lib/race.ts +++ b/zenno/src/lib/race.ts @@ -642,6 +642,49 @@ export function laneChangeSpeed(powerStat: number, maxLaneOrOrder?: number, curr */ export const LANE_CHANGE_ACCEL = 0.03; +const conserveStratDistCoef = { + [RunningStyle.FrontRunner]: { + [Distance.Sprint]: 1, + [Distance.Mile]: 1, + [Distance.Medium]: 1, + [Distance.Long]: 1, + }, + [RunningStyle.PaceChaser]: { + [Distance.Sprint]: 0.7, + [Distance.Mile]: 0.8, + [Distance.Medium]: 0.9, + [Distance.Long]: 0.9, + }, + [RunningStyle.LateSurger]: { + [Distance.Sprint]: 0.75, + [Distance.Mile]: 0.7, + [Distance.Medium]: 0.875, + [Distance.Long]: 1, + }, + [RunningStyle.EndCloser]: { + [Distance.Sprint]: 0.7, + [Distance.Mile]: 0.75, + [Distance.Medium]: 0.86, + [Distance.Long]: 0.9, + }, + [RunningStyle.GreatEscape]: { + [Distance.Sprint]: 1, + [Distance.Mile]: 1, + [Distance.Medium]: 1, + [Distance.Long]: 1, + }, +} as const; + +export function conservePowerAccel(distanceType: Distance, style: RunningStyle, rawPower: number, powerSkillBonus?: number, spotStruggled?: boolean, rushed?: boolean): number { + const power = rawPower + (powerSkillBonus ?? 0); + if (power <= 1200) { + return 0; + } + const sdc = conserveStratDistCoef[style][distanceType]; + const activity = rushed ? 0.8 : (spotStruggled ? 0.98 : 1); + return Math.sqrt((power - 1200) * 130) * 0.001 * sdc * activity; +} + /** * Calculate a horse's minimum speed for a race. * @param raceLen Length of the race in meters diff --git a/zenno/src/routes/doc/race/+page.svelte b/zenno/src/routes/doc/race/+page.svelte index 603a7b5..97c7563 100644 --- a/zenno/src/routes/doc/race/+page.svelte +++ b/zenno/src/routes/doc/race/+page.svelte @@ -14,18 +14,9 @@ let surfaceApt = $state(race.AptitudeLevel.A); let distanceApt = $state(race.AptitudeLevel.A); let raceLen = $state(2000); - const raceLenType = $derived.by(() => { - if (raceLen < 1500) { - return 'Sprint'; - } - if (raceLen < 1900) { - return 'Mile'; - } - if (raceLen < 2500) { - return 'Medium'; - } - return 'Long'; - }); + const styleIsFront = $derived(style === race.RunningStyle.FrontRunner || style === race.RunningStyle.GreatEscape); + const distanceType = $derived(race.distance(raceLen)); + const raceLenType = $derived(race.Distance[distanceType]); const spurtSpeed: Array = $derived([ { label: `Guts 1200`, y: (x) => race.spurtSpeed(x, 1200, style, distanceApt, raceLen) }, @@ -66,6 +57,11 @@ }, ]); const laneChange: ComputedSeries[] = [{ label: 'Lane Change Target Speed', y: (x) => race.laneChangeSpeed(x) }]; + const conservePower: Array = $derived([ + { label: 'Conserved Power', y: (_raw, x) => race.conservePowerAccel(distanceType, style, x, 0, false, false)}, + { label: 'Rushed', y: (_raw, x) => race.conservePowerAccel(distanceType, style, x, 0, false, true)}, + styleIsFront ? { label: 'Spot Struggled', y: (_raw, x) => race.conservePowerAccel(distanceType, style, x, 0, true, false)} : null, + ]); const gutsSpurt: Array = $derived([ { label: `Speed 600`, y: (x) => race.spurtSpeed(600, x, style, distanceApt, raceLen) }, { label: `Speed 1200`, y: (x) => race.spurtSpeed(1200, x, style, distanceApt, raceLen) }, @@ -85,7 +81,6 @@ ]; const duelSpeed: ComputedSeries[] = [{ label: 'Dueling Speed Boost', y: (x) => race.duelSpeedMod(x) }]; const duelAccel: ComputedSeries[] = [{ label: 'Dueling Acceleration Boost', y: (x) => race.duelAccelMod(x) }]; - const styleIsFront = $derived(style === race.RunningStyle.FrontRunner || style === race.RunningStyle.GreatEscape); const secSpeed: Array = $derived([ { label: 'Early Race', @@ -286,6 +281,11 @@ /> + Conserve Power Acceleration +

Bonus acceleration at spurt start from the Conserve Power mechanic.

+

This mechanic does not appear to be well understood, in particular how long the acceleration bonus lasts and how the rushed and spot struggle multipliers apply.

+ {@render statChart(race.Stat.Power, conservePower, 'Acceleration (m/s²)', [0, 0.35], {len: true, style: true})} + Guts Spurt Speed