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.
* @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 style Horse's running style
* @param phase Phase of the current section
*/
export function sectionSpeed(
raceLen: number,
baseWit: number,
witStat: number,
style: RunningStyle,
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.
* @param raceLen Length of the race in meters
* @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 style Horse's running style
* @param distance Hores's distance proficiency aptitude
@@ -529,7 +526,6 @@ export function sectionSpeed(
export function sectionSpeed(
raceLen: number,
speedStat: number,
baseWit: number,
witStat: number,
style: RunningStyle,
distance: AptitudeLevel,
@@ -537,16 +533,14 @@ export function sectionSpeed(
): [number, number];
export function sectionSpeed(
raceLen: number,
speedStatOrBaseWit: number,
baseWitOrWitStat: number,
speedStatOrWitStat: number,
witStatOrStyle: number | RunningStyle,
styleOrPhase: RunningStyle | Phase,
distance?: AptitudeLevel,
lateRace?: Phase.LateRace,
): [number, number] {
const speedStat = lateRace !== undefined ? speedStatOrBaseWit : 0;
const baseWit = lateRace !== undefined ? baseWitOrWitStat : speedStatOrBaseWit;
const witStat = lateRace !== undefined ? witStatOrStyle : baseWitOrWitStat;
const speedStat = lateRace !== undefined ? speedStatOrWitStat : 0;
const witStat = lateRace !== undefined ? witStatOrStyle : speedStatOrWitStat;
const style = lateRace !== undefined ? (styleOrPhase as RunningStyle) : (witStatOrStyle as RunningStyle);
const phase = lateRace !== undefined ? lateRace : (styleOrPhase as Phase);
const base = baseSpeed(raceLen);
@@ -555,7 +549,7 @@ export function sectionSpeed(
phase === Phase.LateRace
? (math.sqrt(500 * speedStat) as number) * distanceProficiencyMod[distance ?? AptitudeLevel.A] * 0.002
: 0;
const u = (witStat / 550000) * math.log10(baseWit * 0.1);
const u = (witStat / 550000) * math.log10(witStat * 0.1);
const l = u - 0.0065;
return [baseTarget + late + base * l, baseTarget + late + base * u];
}
+17 -20
View File
@@ -123,7 +123,7 @@
const uphillSlowPow = 800;
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 = [
{ per: 2, len: 110 },
{ per: 1.5, len: 200 },
@@ -137,13 +137,13 @@
const secSpeedS = 1200;
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 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 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 aSpeedupSpeed = modemean(aSecSpeed, 1.04, aMode);
const r = 2 / (sOvertakeSpeed - aSpeedupSpeed);
@@ -183,35 +183,35 @@
const secSpeedSeries: Array<ComputedAreas | null> = $derived([
{
label: 'Early Race',
y1: (x) => sectionSpeed(raceLen, x, x, secSpeedStyle, Phase.EarlyRace)[0],
y2: (x) => sectionSpeed(raceLen, x, x, secSpeedStyle, Phase.EarlyRace)[1],
y1: (x) => sectionSpeed(raceLen, x, secSpeedStyle, Phase.EarlyRace)[0],
y2: (x) => sectionSpeed(raceLen, x, secSpeedStyle, Phase.EarlyRace)[1],
},
{
label: 'Mid Race',
y1: (x) => sectionSpeed(raceLen, x, x, secSpeedStyle, Phase.MidRace)[0],
y2: (x) => sectionSpeed(raceLen, x, x, secSpeedStyle, Phase.MidRace)[1],
y1: (x) => sectionSpeed(raceLen, x, secSpeedStyle, Phase.MidRace)[0],
y2: (x) => sectionSpeed(raceLen, x, secSpeedStyle, Phase.MidRace)[1],
},
secIsFront
? {
label: 'Early Race + Mean Speed-Up Mode',
y1: (x) => modemean(sectionSpeed(raceLen, x, x, secSpeedStyle, Phase.EarlyRace)[0], 1.04, frontModeEnterChance(x)),
y2: (x) => modemean(sectionSpeed(raceLen, x, x, secSpeedStyle, Phase.EarlyRace)[1], 1.04, frontModeEnterChance(x)),
y1: (x) => modemean(sectionSpeed(raceLen, x, secSpeedStyle, Phase.EarlyRace)[0], 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',
y1: (x) => modemean(sectionSpeed(raceLen, x, x, secSpeedStyle, Phase.EarlyRace)[0], 1.04, paceUpEnterChance(x)),
y2: (x) => modemean(sectionSpeed(raceLen, x, x, secSpeedStyle, Phase.EarlyRace)[1], 1.04, paceUpEnterChance(x)),
y1: (x) => modemean(sectionSpeed(raceLen, x, secSpeedStyle, Phase.EarlyRace)[0], 1.04, paceUpEnterChance(x)),
y2: (x) => modemean(sectionSpeed(raceLen, x, secSpeedStyle, Phase.EarlyRace)[1], 1.04, paceUpEnterChance(x)),
},
secIsFront
? {
label: 'Mid Race + Mean Speed-Up Mode',
y1: (x) => modemean(sectionSpeed(raceLen, x, x, secSpeedStyle, Phase.MidRace)[0], 1.04, frontModeEnterChance(x)),
y2: (x) => modemean(sectionSpeed(raceLen, x, x, secSpeedStyle, Phase.MidRace)[1], 1.04, frontModeEnterChance(x)),
y1: (x) => modemean(sectionSpeed(raceLen, x, secSpeedStyle, Phase.MidRace)[0], 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',
y1: (x) => modemean(sectionSpeed(raceLen, x, x, secSpeedStyle, Phase.MidRace)[0], 1.04, paceUpEnterChance(x)),
y2: (x) => modemean(sectionSpeed(raceLen, x, x, secSpeedStyle, Phase.MidRace)[1], 1.04, paceUpEnterChance(x)),
y1: (x) => modemean(sectionSpeed(raceLen, x, secSpeedStyle, Phase.MidRace)[0], 1.04, paceUpEnterChance(x)),
y2: (x) => modemean(sectionSpeed(raceLen, x, secSpeedStyle, Phase.MidRace)[1], 1.04, paceUpEnterChance(x)),
},
]);
const fcSeries: ComputedSeries[] = $derived([
@@ -649,10 +649,7 @@
ref="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?tab=t.0#heading=h.xdha40q2176g"
>Section Speed</Sec
>
<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>
<p>Each section, each horse gets a random modifier to target speed. The modifier's range is determined by the wit stat.</p>
<div class="mb-24 h-60 w-full md:mb-20 md:h-96">
<StatChart
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([
{
label: 'Early Race',
y1: (x) => race.sectionSpeed(raceLen, x, x, style, race.Phase.EarlyRace)[0],
y2: (x) => race.sectionSpeed(raceLen, x, x, style, race.Phase.EarlyRace)[1],
y1: (x) => race.sectionSpeed(raceLen, x, style, race.Phase.EarlyRace)[0],
y2: (x) => race.sectionSpeed(raceLen, x, style, race.Phase.EarlyRace)[1],
},
{
label: 'Mid Race',
y1: (x) => race.sectionSpeed(raceLen, x, x, style, race.Phase.MidRace)[0],
y2: (x) => race.sectionSpeed(raceLen, x, x, style, race.Phase.MidRace)[1],
y1: (x) => race.sectionSpeed(raceLen, x, style, race.Phase.MidRace)[0],
y2: (x) => race.sectionSpeed(raceLen, x, style, race.Phase.MidRace)[1],
},
styleIsFront
? {
label: 'Early Race + Mean Speed-Up Mode',
y1: (x) =>
modemean(race.sectionSpeed(raceLen, x, x, style, race.Phase.EarlyRace)[0], 1.04, race.frontModeEnterChance(x)),
y2: (x) =>
modemean(race.sectionSpeed(raceLen, x, x, style, race.Phase.EarlyRace)[1], 1.04, race.frontModeEnterChance(x)),
y1: (x) => modemean(race.sectionSpeed(raceLen, 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)),
}
: {
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)),
y2: (x) => modemean(race.sectionSpeed(raceLen, x, x, style, race.Phase.EarlyRace)[1], 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, style, race.Phase.EarlyRace)[1], 1.04, race.paceUpEnterChance(x)),
},
styleIsFront
? {
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)),
y2: (x) => modemean(race.sectionSpeed(raceLen, x, x, style, race.Phase.MidRace)[1], 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, style, race.Phase.MidRace)[1], 1.04, race.frontModeEnterChance(x)),
}
: {
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)),
y2: (x) => modemean(race.sectionSpeed(raceLen, x, x, style, race.Phase.MidRace)[1], 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, style, race.Phase.MidRace)[1], 1.04, race.paceUpEnterChance(x)),
},
]);
const downhill: ComputedSeries[] = [
+1 -1
View File
@@ -74,7 +74,7 @@
const damCarry = $derived(allowDAM && slope < 0 ? race.downhillAccelMod(slope) : 0);
const carry = $derived(skillCarry + damCarry);
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;
return [s[0] + u, s[1] + u] as const;
});
+1 -1
View File
@@ -83,7 +83,7 @@
}
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 startInstant: accel.Instant = { t: 0, x: 0, v: 3 };
+2 -8
View File
@@ -96,14 +96,8 @@
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.EarlyRace]: race.sectionSpeed(raceLen, stats[race.Stat.Wit], style, race.Phase.EarlyRace),
[race.Phase.MidRace]: race.sectionSpeed(raceLen, 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),