zenno: section speed does not use base wit
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful

Per analysis by Aya, only final wit is used for section speed.

Fixes #40.
This commit is contained in:
2026-09-03 15:30:29 -04:00
parent c7a847f677
commit d0c11aa527
6 changed files with 37 additions and 54 deletions
+4 -10
View File
@@ -504,14 +504,12 @@ export function baseTargetSpeed(
/** /**
* Calculate the range of section speed values for a horse in early or mid race. * Calculate the range of section speed values for a horse in early or mid race.
* @param raceLen Length of the race in meters * @param raceLen Length of the race in meters
* @param baseWit Base wit, after mood but before other modifiers
* @param witStat Final wit stat, including strategy proficiency and skills * @param witStat Final wit stat, including strategy proficiency and skills
* @param style Horse's running style * @param style Horse's running style
* @param phase Phase of the current section * @param phase Phase of the current section
*/ */
export function sectionSpeed( export function sectionSpeed(
raceLen: number, raceLen: number,
baseWit: number,
witStat: number, witStat: number,
style: RunningStyle, style: RunningStyle,
phase: Exclude<Phase, Phase.LateRace>, phase: Exclude<Phase, Phase.LateRace>,
@@ -520,7 +518,6 @@ export function sectionSpeed(
* Calculate the range of section speed values for a horse not spurting during late race. * Calculate the range of section speed values for a horse not spurting during late race.
* @param raceLen Length of the race in meters * @param raceLen Length of the race in meters
* @param speedStat Final speed stat * @param speedStat Final speed stat
* @param baseWit Base wit, after mood but before other modifiers
* @param witStat Final wit stat, including strategy proficiency and skills * @param witStat Final wit stat, including strategy proficiency and skills
* @param style Horse's running style * @param style Horse's running style
* @param distance Hores's distance proficiency aptitude * @param distance Hores's distance proficiency aptitude
@@ -529,7 +526,6 @@ export function sectionSpeed(
export function sectionSpeed( export function sectionSpeed(
raceLen: number, raceLen: number,
speedStat: number, speedStat: number,
baseWit: number,
witStat: number, witStat: number,
style: RunningStyle, style: RunningStyle,
distance: AptitudeLevel, distance: AptitudeLevel,
@@ -537,16 +533,14 @@ export function sectionSpeed(
): [number, number]; ): [number, number];
export function sectionSpeed( export function sectionSpeed(
raceLen: number, raceLen: number,
speedStatOrBaseWit: number, speedStatOrWitStat: number,
baseWitOrWitStat: number,
witStatOrStyle: number | RunningStyle, witStatOrStyle: number | RunningStyle,
styleOrPhase: RunningStyle | Phase, styleOrPhase: RunningStyle | Phase,
distance?: AptitudeLevel, distance?: AptitudeLevel,
lateRace?: Phase.LateRace, lateRace?: Phase.LateRace,
): [number, number] { ): [number, number] {
const speedStat = lateRace !== undefined ? speedStatOrBaseWit : 0; const speedStat = lateRace !== undefined ? speedStatOrWitStat : 0;
const baseWit = lateRace !== undefined ? baseWitOrWitStat : speedStatOrBaseWit; const witStat = lateRace !== undefined ? witStatOrStyle : speedStatOrWitStat;
const witStat = lateRace !== undefined ? witStatOrStyle : baseWitOrWitStat;
const style = lateRace !== undefined ? (styleOrPhase as RunningStyle) : (witStatOrStyle as RunningStyle); const style = lateRace !== undefined ? (styleOrPhase as RunningStyle) : (witStatOrStyle as RunningStyle);
const phase = lateRace !== undefined ? lateRace : (styleOrPhase as Phase); const phase = lateRace !== undefined ? lateRace : (styleOrPhase as Phase);
const base = baseSpeed(raceLen); const base = baseSpeed(raceLen);
@@ -555,7 +549,7 @@ export function sectionSpeed(
phase === Phase.LateRace phase === Phase.LateRace
? (math.sqrt(500 * speedStat) as number) * distanceProficiencyMod[distance ?? AptitudeLevel.A] * 0.002 ? (math.sqrt(500 * speedStat) as number) * distanceProficiencyMod[distance ?? AptitudeLevel.A] * 0.002
: 0; : 0;
const u = (witStat / 550000) * math.log10(baseWit * 0.1); const u = (witStat / 550000) * math.log10(witStat * 0.1);
const l = u - 0.0065; const l = u - 0.0065;
return [baseTarget + late + base * l, baseTarget + late + base * u]; return [baseTarget + late + base * l, baseTarget + late + base * u];
} }
+17 -20
View File
@@ -123,7 +123,7 @@
const uphillSlowPow = 800; const uphillSlowPow = 800;
const uphillFastPow = 1200; const uphillFastPow = 1200;
const uphillBaseSpeed = mean2(sectionSpeed(2500, 1200, 1200, RunningStyle.FrontRunner, Phase.MidRace)); const uphillBaseSpeed = mean2(sectionSpeed(2500, 1200, RunningStyle.FrontRunner, Phase.MidRace));
const uphillSlopes = [ const uphillSlopes = [
{ per: 2, len: 110 }, { per: 2, len: 110 },
{ per: 1.5, len: 200 }, { per: 1.5, len: 200 },
@@ -137,13 +137,13 @@
const secSpeedS = 1200; const secSpeedS = 1200;
const secSpeedA = 1000; const secSpeedA = 1000;
const secSpeedExample = $derived(sectionSpeed(raceLen, secSpeedS, secSpeedS * 1.1, RunningStyle.FrontRunner, Phase.EarlyRace)); const secSpeedExample = $derived(sectionSpeed(raceLen, secSpeedS * 1.1, RunningStyle.FrontRunner, Phase.EarlyRace));
const secSpeedInfo = $derived(secSpeedExample.map((x) => x.toFixed(2)).join(' to ')); const secSpeedInfo = $derived(secSpeedExample.map((x) => x.toFixed(2)).join(' to '));
const secSpeedPassTime = $derived.by(() => { const secSpeedPassTime = $derived.by(() => {
const sSecSpeed = mean2(sectionSpeed(raceLen, secSpeedS, secSpeedS * 1.1, RunningStyle.FrontRunner, Phase.MidRace)); const sSecSpeed = mean2(sectionSpeed(raceLen, secSpeedS * 1.1, RunningStyle.FrontRunner, Phase.MidRace));
const sMode = frontModeEnterChance(secSpeedS * 1.1); const sMode = frontModeEnterChance(secSpeedS * 1.1);
const sOvertakeSpeed = modemean(sSecSpeed, 1.05, sMode); const sOvertakeSpeed = modemean(sSecSpeed, 1.05, sMode);
const aSecSpeed = mean2(sectionSpeed(raceLen, secSpeedA, secSpeedA, RunningStyle.FrontRunner, Phase.MidRace)); const aSecSpeed = mean2(sectionSpeed(raceLen, secSpeedA, RunningStyle.FrontRunner, Phase.MidRace));
const aMode = frontModeEnterChance(secSpeedA); const aMode = frontModeEnterChance(secSpeedA);
const aSpeedupSpeed = modemean(aSecSpeed, 1.04, aMode); const aSpeedupSpeed = modemean(aSecSpeed, 1.04, aMode);
const r = 2 / (sOvertakeSpeed - aSpeedupSpeed); const r = 2 / (sOvertakeSpeed - aSpeedupSpeed);
@@ -183,35 +183,35 @@
const secSpeedSeries: Array<ComputedAreas | null> = $derived([ const secSpeedSeries: Array<ComputedAreas | null> = $derived([
{ {
label: 'Early Race', label: 'Early Race',
y1: (x) => sectionSpeed(raceLen, x, x, secSpeedStyle, Phase.EarlyRace)[0], y1: (x) => sectionSpeed(raceLen, x, secSpeedStyle, Phase.EarlyRace)[0],
y2: (x) => sectionSpeed(raceLen, x, x, secSpeedStyle, Phase.EarlyRace)[1], y2: (x) => sectionSpeed(raceLen, x, secSpeedStyle, Phase.EarlyRace)[1],
}, },
{ {
label: 'Mid Race', label: 'Mid Race',
y1: (x) => sectionSpeed(raceLen, x, x, secSpeedStyle, Phase.MidRace)[0], y1: (x) => sectionSpeed(raceLen, x, secSpeedStyle, Phase.MidRace)[0],
y2: (x) => sectionSpeed(raceLen, x, x, secSpeedStyle, Phase.MidRace)[1], y2: (x) => sectionSpeed(raceLen, x, secSpeedStyle, Phase.MidRace)[1],
}, },
secIsFront secIsFront
? { ? {
label: 'Early Race + Mean Speed-Up Mode', label: 'Early Race + Mean Speed-Up Mode',
y1: (x) => modemean(sectionSpeed(raceLen, x, x, secSpeedStyle, Phase.EarlyRace)[0], 1.04, frontModeEnterChance(x)), y1: (x) => modemean(sectionSpeed(raceLen, x, secSpeedStyle, Phase.EarlyRace)[0], 1.04, frontModeEnterChance(x)),
y2: (x) => modemean(sectionSpeed(raceLen, x, x, secSpeedStyle, Phase.EarlyRace)[1], 1.04, frontModeEnterChance(x)), y2: (x) => modemean(sectionSpeed(raceLen, x, secSpeedStyle, Phase.EarlyRace)[1], 1.04, frontModeEnterChance(x)),
} }
: { : {
label: 'Early Race + Mean Pace-Up Mode', label: 'Early Race + Mean Pace-Up Mode',
y1: (x) => modemean(sectionSpeed(raceLen, x, x, secSpeedStyle, Phase.EarlyRace)[0], 1.04, paceUpEnterChance(x)), y1: (x) => modemean(sectionSpeed(raceLen, x, secSpeedStyle, Phase.EarlyRace)[0], 1.04, paceUpEnterChance(x)),
y2: (x) => modemean(sectionSpeed(raceLen, x, x, secSpeedStyle, Phase.EarlyRace)[1], 1.04, paceUpEnterChance(x)), y2: (x) => modemean(sectionSpeed(raceLen, x, secSpeedStyle, Phase.EarlyRace)[1], 1.04, paceUpEnterChance(x)),
}, },
secIsFront secIsFront
? { ? {
label: 'Mid Race + Mean Speed-Up Mode', label: 'Mid Race + Mean Speed-Up Mode',
y1: (x) => modemean(sectionSpeed(raceLen, x, x, secSpeedStyle, Phase.MidRace)[0], 1.04, frontModeEnterChance(x)), y1: (x) => modemean(sectionSpeed(raceLen, x, secSpeedStyle, Phase.MidRace)[0], 1.04, frontModeEnterChance(x)),
y2: (x) => modemean(sectionSpeed(raceLen, x, x, secSpeedStyle, Phase.MidRace)[1], 1.04, frontModeEnterChance(x)), y2: (x) => modemean(sectionSpeed(raceLen, x, secSpeedStyle, Phase.MidRace)[1], 1.04, frontModeEnterChance(x)),
} }
: { : {
label: 'Mid Race + Mean Pace-Up Mode', label: 'Mid Race + Mean Pace-Up Mode',
y1: (x) => modemean(sectionSpeed(raceLen, x, x, secSpeedStyle, Phase.MidRace)[0], 1.04, paceUpEnterChance(x)), y1: (x) => modemean(sectionSpeed(raceLen, x, secSpeedStyle, Phase.MidRace)[0], 1.04, paceUpEnterChance(x)),
y2: (x) => modemean(sectionSpeed(raceLen, x, x, secSpeedStyle, Phase.MidRace)[1], 1.04, paceUpEnterChance(x)), y2: (x) => modemean(sectionSpeed(raceLen, x, secSpeedStyle, Phase.MidRace)[1], 1.04, paceUpEnterChance(x)),
}, },
]); ]);
const fcSeries: ComputedSeries[] = $derived([ const fcSeries: ComputedSeries[] = $derived([
@@ -649,10 +649,7 @@
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.xdha40q2176g" ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.xdha40q2176g"
>Section Speed</Sec >Section Speed</Sec
> >
<p> <p>Each section, each horse gets a random modifier to target speed. The modifier's range is determined by the wit stat.</p>
Each section, each horse gets a random modifier to target speed. The modifier's range is determined by the wit stat.
(Curiously, the calculation uses both wit as modified by style proficiency and green skills as well as base wit.)
</p>
<div class="mb-24 h-60 w-full md:mb-20 md:h-96"> <div class="mb-24 h-60 w-full md:mb-20 md:h-96">
<StatChart <StatChart
class="mx-auto mb-12 h-full w-full max-w-3xl md:mb-10" class="mx-auto mb-12 h-full w-full max-w-3xl md:mb-10"
+12 -14
View File
@@ -86,37 +86,35 @@
const secSpeed: Array<ComputedAreas | null> = $derived([ const secSpeed: Array<ComputedAreas | null> = $derived([
{ {
label: 'Early Race', label: 'Early Race',
y1: (x) => race.sectionSpeed(raceLen, x, x, style, race.Phase.EarlyRace)[0], y1: (x) => race.sectionSpeed(raceLen, x, style, race.Phase.EarlyRace)[0],
y2: (x) => race.sectionSpeed(raceLen, x, x, style, race.Phase.EarlyRace)[1], y2: (x) => race.sectionSpeed(raceLen, x, style, race.Phase.EarlyRace)[1],
}, },
{ {
label: 'Mid Race', label: 'Mid Race',
y1: (x) => race.sectionSpeed(raceLen, x, x, style, race.Phase.MidRace)[0], y1: (x) => race.sectionSpeed(raceLen, x, style, race.Phase.MidRace)[0],
y2: (x) => race.sectionSpeed(raceLen, x, x, style, race.Phase.MidRace)[1], y2: (x) => race.sectionSpeed(raceLen, x, style, race.Phase.MidRace)[1],
}, },
styleIsFront styleIsFront
? { ? {
label: 'Early Race + Mean Speed-Up Mode', label: 'Early Race + Mean Speed-Up Mode',
y1: (x) => y1: (x) => modemean(race.sectionSpeed(raceLen, x, style, race.Phase.EarlyRace)[0], 1.04, race.frontModeEnterChance(x)),
modemean(race.sectionSpeed(raceLen, x, x, style, race.Phase.EarlyRace)[0], 1.04, race.frontModeEnterChance(x)), y2: (x) => modemean(race.sectionSpeed(raceLen, x, style, race.Phase.EarlyRace)[1], 1.04, race.frontModeEnterChance(x)),
y2: (x) =>
modemean(race.sectionSpeed(raceLen, x, x, style, race.Phase.EarlyRace)[1], 1.04, race.frontModeEnterChance(x)),
} }
: { : {
label: 'Early Race + Mean Pace-Up Mode', label: 'Early Race + Mean Pace-Up Mode',
y1: (x) => modemean(race.sectionSpeed(raceLen, x, x, style, race.Phase.EarlyRace)[0], 1.04, race.paceUpEnterChance(x)), y1: (x) => modemean(race.sectionSpeed(raceLen, x, style, race.Phase.EarlyRace)[0], 1.04, race.paceUpEnterChance(x)),
y2: (x) => modemean(race.sectionSpeed(raceLen, x, x, style, race.Phase.EarlyRace)[1], 1.04, race.paceUpEnterChance(x)), y2: (x) => modemean(race.sectionSpeed(raceLen, x, style, race.Phase.EarlyRace)[1], 1.04, race.paceUpEnterChance(x)),
}, },
styleIsFront styleIsFront
? { ? {
label: 'Mid Race + Mean Speed-Up Mode', label: 'Mid Race + Mean Speed-Up Mode',
y1: (x) => modemean(race.sectionSpeed(raceLen, x, x, style, race.Phase.MidRace)[0], 1.04, race.frontModeEnterChance(x)), y1: (x) => modemean(race.sectionSpeed(raceLen, x, style, race.Phase.MidRace)[0], 1.04, race.frontModeEnterChance(x)),
y2: (x) => modemean(race.sectionSpeed(raceLen, x, x, style, race.Phase.MidRace)[1], 1.04, race.frontModeEnterChance(x)), y2: (x) => modemean(race.sectionSpeed(raceLen, x, style, race.Phase.MidRace)[1], 1.04, race.frontModeEnterChance(x)),
} }
: { : {
label: 'Mid Race + Mean Pace-Up Mode', label: 'Mid Race + Mean Pace-Up Mode',
y1: (x) => modemean(race.sectionSpeed(raceLen, x, x, style, race.Phase.MidRace)[0], 1.04, race.paceUpEnterChance(x)), y1: (x) => modemean(race.sectionSpeed(raceLen, x, style, race.Phase.MidRace)[0], 1.04, race.paceUpEnterChance(x)),
y2: (x) => modemean(race.sectionSpeed(raceLen, x, x, style, race.Phase.MidRace)[1], 1.04, race.paceUpEnterChance(x)), y2: (x) => modemean(race.sectionSpeed(raceLen, x, style, race.Phase.MidRace)[1], 1.04, race.paceUpEnterChance(x)),
}, },
]); ]);
const downhill: ComputedSeries[] = [ const downhill: ComputedSeries[] = [
+1 -1
View File
@@ -74,7 +74,7 @@
const damCarry = $derived(allowDAM && slope < 0 ? race.downhillAccelMod(slope) : 0); const damCarry = $derived(allowDAM && slope < 0 ? race.downhillAccelMod(slope) : 0);
const carry = $derived(skillCarry + damCarry); const carry = $derived(skillCarry + damCarry);
const midSpeed = $derived.by(() => { const midSpeed = $derived.by(() => {
const s = race.sectionSpeed(raceLen, baseWit, wit, style, race.Phase.MidRace) satisfies [number, number]; const s = race.sectionSpeed(raceLen, wit, style, race.Phase.MidRace) satisfies [number, number];
const u = slope > 0 ? race.uphillMod(power, slope) : 0; const u = slope > 0 ? race.uphillMod(power, slope) : 0;
return [s[0] + u, s[1] + u] as const; return [s[0] + u, s[1] + u] as const;
}); });
+1 -1
View File
@@ -83,7 +83,7 @@
} }
return [0, 0]; return [0, 0];
}); });
const earlyTarget = $derived(race.sectionSpeed(raceLen, baseWit, wit, style, race.Phase.EarlyRace)); const earlyTarget = $derived(race.sectionSpeed(raceLen, wit, style, race.Phase.EarlyRace));
const speeds: [number, number] = $derived([earlyTarget[0] + hillMod[0], earlyTarget[1] + hillMod[1]]); const speeds: [number, number] = $derived([earlyTarget[0] + hillMod[0], earlyTarget[1] + hillMod[1]]);
const startInstant: accel.Instant = { t: 0, x: 0, v: 3 }; const startInstant: accel.Instant = { t: 0, x: 0, v: 3 };
+2 -8
View File
@@ -96,14 +96,8 @@
const distType = $derived(race.distance(raceLen)); const distType = $derived(race.distance(raceLen));
const phaseSpeed = $derived({ const phaseSpeed = $derived({
[race.Phase.EarlyRace]: race.sectionSpeed( [race.Phase.EarlyRace]: race.sectionSpeed(raceLen, stats[race.Stat.Wit], style, race.Phase.EarlyRace),
raceLen, [race.Phase.MidRace]: race.sectionSpeed(raceLen, stats[race.Stat.Wit], style, race.Phase.MidRace),
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.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),
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),