From cf301745f1542cb66c5ef218c9a3bb43e6051847 Mon Sep 17 00:00:00 2001 From: Branden J Brown Date: Sat, 10 Oct 2026 13:31:25 -0400 Subject: [PATCH] zenno: use a real last spurt phase --- zenno/src/lib/course.ts | 46 ++++++------ zenno/src/lib/race.ts | 76 ++++++++++---------- zenno/src/routes/race/carryover/+page.svelte | 4 +- zenno/src/routes/race/stam/+page.svelte | 4 ++ 4 files changed, 67 insertions(+), 63 deletions(-) diff --git a/zenno/src/lib/course.ts b/zenno/src/lib/course.ts index ca7d348..66d1d10 100644 --- a/zenno/src/lib/course.ts +++ b/zenno/src/lib/course.ts @@ -76,36 +76,36 @@ export interface SectionInfo { section: number; phase: Phase; endRate: number; - posKeep: boolean; - rush: boolean; - spotStruggleEnter: boolean; + posKeep?: boolean; + rush?: boolean; + spotStruggleEnter?: boolean; } export const SECTIONS: ReadonlyArray = [ - { section: 1, phase: Phase.EarlyRace, endRate: 1 / 24, posKeep: true, rush: false, spotStruggleEnter: false }, + { section: 1, phase: Phase.EarlyRace, endRate: 1 / 24, posKeep: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 24, phase: Phase.LateRace, endRate: 24 / 24, posKeep: false, rush: false, spotStruggleEnter: false }, + { section: 6, phase: Phase.MidRace, endRate: 6 / 24, posKeep: true, rush: true }, + { section: 7, phase: Phase.MidRace, endRate: 7 / 24, posKeep: true, rush: true }, + { section: 8, phase: Phase.MidRace, endRate: 8 / 24, posKeep: true, rush: true }, + { section: 9, phase: Phase.MidRace, endRate: 9 / 24, posKeep: true, rush: true }, + { section: 10, phase: Phase.MidRace, endRate: 10 / 24, posKeep: true }, + { section: 11, phase: Phase.MidRace, endRate: 11 / 24 }, + { section: 12, phase: Phase.MidRace, endRate: 12 / 24 }, + { section: 13, phase: Phase.MidRace, endRate: 13 / 24 }, + { section: 14, phase: Phase.MidRace, endRate: 14 / 24 }, + { section: 15, phase: Phase.MidRace, endRate: 15 / 24 }, + { section: 16, phase: Phase.MidRace, endRate: 16 / 24 }, + { section: 17, phase: Phase.LateRace, endRate: 17 / 24 }, + { section: 18, phase: Phase.LateRace, endRate: 18 / 24 }, + { section: 19, phase: Phase.LateRace, endRate: 19 / 24 }, + { section: 20, phase: Phase.LateRace, endRate: 20 / 24 }, + { section: 21, phase: Phase.LastSpurt, endRate: 21 / 24 }, + { section: 22, phase: Phase.LastSpurt, endRate: 22 / 24 }, + { section: 23, phase: Phase.LastSpurt, endRate: 23 / 24 }, + { section: 24, phase: Phase.LastSpurt, endRate: 24 / 24 }, ]; export interface SectionSlopeSegment extends SectionInfo { diff --git a/zenno/src/lib/race.ts b/zenno/src/lib/race.ts index c1eccc3..a4a4262 100644 --- a/zenno/src/lib/race.ts +++ b/zenno/src/lib/race.ts @@ -8,11 +8,11 @@ import { binomPMF } from './prob'; * Fundamental stats of umas. */ export enum Stat { - Speed, - Stamina, - Power, - Guts, - Wit, + Speed = 1, + Stamina = 2, + Power = 3, + Guts = 4, + Wit = 5, } /** @@ -118,14 +118,15 @@ export function distance(raceLen: number): Distance { /** * Get the distances at which each phase of a race ends. * @param raceLen Length of the race in meters - * @returns Ends of each phase in meters + * @returns Ends of each phase in meters, including separate late and spurt phases */ export function phases(raceLen: number) { const sixth = raceLen / 6; return { [Phase.EarlyRace]: sixth, [Phase.MidRace]: 4 * sixth, - [Phase.LateRace]: raceLen, + [Phase.LateRace]: 5 * sixth, + [Phase.LastSpurt]: raceLen, }; } @@ -133,18 +134,18 @@ export function phases(raceLen: number) { * Race surfaces. */ export enum Surface { - Turf, - Dirt, + Turf = 1, + Dirt = 2, } /** * Race ground condition types. */ export enum GroundConditions { - Firm, - Good, - Soft, - Heavy, + Firm = 1, + Good = 2, + Soft = 3, + Heavy = 4, } /** @@ -327,12 +328,12 @@ export const RUNNING_STYLES = [ /** * Race phases. - * While last spurt phase is also a phase, it is not distinguished here. */ export enum Phase { - EarlyRace, - MidRace, - LateRace, + EarlyRace = 0, + MidRace = 1, + LateRace = 2, + LastSpurt = 3, } const hpStrategyCoeff = { @@ -407,7 +408,7 @@ export function hpPerSecond( const pdmMod = pdm ? 0.6 : 1; const damMod = dam ? 0.4 : 1; const groundMod = groundHPRateMod[surface][conditions]; - const gutsMod = phase === Phase.LateRace ? 1 + 200 / (math.sqrt(600 * gutsStat) as number) : 1; + const gutsMod = phase >= Phase.LateRace ? 1 + 200 / (math.sqrt(600 * gutsStat) as number) : 1; const sp = currentSpeed - baseSpeed(raceLen) + 12; return (20 / 144) * sp * sp * rushedMod * pdmMod * damMod * groundMod * gutsMod; } @@ -447,11 +448,11 @@ export function baseSpeed(raceLen: number): number { } const speedStrategyPhaseCoeff = [ - [1.0, 0.98, 0.962], - [0.978, 0.991, 0.975], - [0.938, 0.998, 0.994], - [0.931, 1.0, 1.0], - [1.063, 0.962, 0.95], + [1.0, 0.98, 0.962, 0.962], + [0.978, 0.991, 0.975, 0.975], + [0.938, 0.998, 0.994, 0.994], + [0.931, 1.0, 1.0, 1.0], + [1.063, 0.962, 0.95, 0.95], ] as const; const distanceProficiencyMod: Readonly> = { @@ -471,7 +472,7 @@ const distanceProficiencyMod: Readonly> = { * @param style Horse's running style * @param phase Phase to calculate for */ -export function baseTargetSpeed(raceLen: number, style: RunningStyle, phase: Exclude): number; +export function baseTargetSpeed(raceLen: number, style: RunningStyle, phase: Exclude): number; /** * Get a horse's base target speed when not spurting during late race. * @param raceLen Length of the race in meters @@ -483,7 +484,7 @@ export function baseTargetSpeed(raceLen: number, style: RunningStyle, phase: Exc export function baseTargetSpeed( raceLen: number, style: RunningStyle, - phase: Phase.LateRace, + phase: Phase.LateRace | Phase.LastSpurt, speedStat: number, distanceApt: AptitudeLevel, ): number; @@ -497,7 +498,7 @@ export function baseTargetSpeed( const base = baseSpeed(raceLen); const baseTarget = base * speedStrategyPhaseCoeff[style][phase]; const late = - phase === Phase.LateRace ? (math.sqrt(500 * speedStat!) as number) * distanceProficiencyMod[distanceApt!] * 0.002 : 0; + phase >= Phase.LateRace ? (math.sqrt(500 * speedStat!) as number) * distanceProficiencyMod[distanceApt!] * 0.002 : 0; return baseTarget + late; } @@ -512,7 +513,7 @@ export function sectionSpeed( raceLen: number, witStat: number, style: RunningStyle, - phase: Exclude, + phase: Exclude, ): [number, number]; /** * Calculate the range of section speed values for a horse not spurting during late race. @@ -529,7 +530,7 @@ export function sectionSpeed( witStat: number, style: RunningStyle, distance: AptitudeLevel, - phase: Phase.LateRace, + phase: Phase.LateRace | Phase.LastSpurt, ): [number, number]; export function sectionSpeed( raceLen: number, @@ -537,7 +538,7 @@ export function sectionSpeed( witStatOrStyle: number | RunningStyle, styleOrPhase: RunningStyle | Phase, distance?: AptitudeLevel, - lateRace?: Phase.LateRace, + lateRace?: Phase.LateRace | Phase.LastSpurt, ): [number, number] { const speedStat = lateRace !== undefined ? speedStatOrWitStat : 0; const witStat = lateRace !== undefined ? witStatOrStyle : speedStatOrWitStat; @@ -546,7 +547,7 @@ export function sectionSpeed( const base = baseSpeed(raceLen); const baseTarget = base * speedStrategyPhaseCoeff[style][phase]; const late = - phase === Phase.LateRace + phase >= Phase.LateRace ? (math.sqrt(500 * speedStat) as number) * distanceProficiencyMod[distance ?? AptitudeLevel.A] * 0.002 : 0; const u = (witStat / 550000) * math.log10(witStat * 0.1); @@ -620,11 +621,11 @@ export const COURSE_WIDTH = 11.25; export const LANE_WIDTH = COURSE_WIDTH / 18; const accelStrategyPhaseCoeff = { - [RunningStyle.FrontRunner]: [1.0, 1.0, 0.996], - [RunningStyle.PaceChaser]: [0.985, 1.0, 0.996], - [RunningStyle.LateSurger]: [0.975, 1.0, 1.0], - [RunningStyle.EndCloser]: [0.945, 1.0, 0.997], - [RunningStyle.Runaway]: [1.17, 0.94, 0.956], + [RunningStyle.FrontRunner]: [1.0, 1.0, 0.996, 0.996], + [RunningStyle.PaceChaser]: [0.985, 1.0, 0.996, 0.996], + [RunningStyle.LateSurger]: [0.975, 1.0, 1.0, 1.0], + [RunningStyle.EndCloser]: [0.945, 1.0, 0.997, 0.997], + [RunningStyle.Runaway]: [1.17, 0.94, 0.956, 0.956], } as const; const surfaceProficiencyMod: Readonly> = { @@ -681,6 +682,7 @@ const phaseDecel = { [Phase.EarlyRace]: -1.2, [Phase.MidRace]: -0.8, [Phase.LateRace]: -1.0, + [Phase.LastSpurt]: -1.0, } as const; /** @@ -695,9 +697,7 @@ export function deceleration(phase: Phase, pdm?: boolean, dead?: boolean): numbe return -1.2; } if (pdm) { - // This isn't until 1.5anni. - // return -0.5; - return phaseDecel[phase]; + return -0.5; } return phaseDecel[phase]; } diff --git a/zenno/src/routes/race/carryover/+page.svelte b/zenno/src/routes/race/carryover/+page.svelte index 00ba0a4..ea49e56 100644 --- a/zenno/src/routes/race/carryover/+page.svelte +++ b/zenno/src/routes/race/carryover/+page.svelte @@ -99,8 +99,8 @@ const distance = $derived([last(seggs[0]).end.x, last(seggs[1]).end.x] as const); const time = $derived([last(seggs[0]).end.t, last(seggs[1]).end.t] as const); const finishTime = $derived([ - time[0] + (phases[race.Phase.LateRace] - distance[0]) / spurtSpeed, - time[1] + (phases[race.Phase.LateRace] - distance[1]) / spurtSpeed, + time[0] + (raceLen - distance[0]) / spurtSpeed, + time[1] + (raceLen - distance[1]) / spurtSpeed, ] as const); function gainLengths(fast: accel.Instant, slow: accel.Instant): number { const m = fast.v * (slow.t - fast.t) + fast.x - slow.x; diff --git a/zenno/src/routes/race/stam/+page.svelte b/zenno/src/routes/race/stam/+page.svelte index 570235a..7f8e61b 100644 --- a/zenno/src/routes/race/stam/+page.svelte +++ b/zenno/src/routes/race/stam/+page.svelte @@ -103,6 +103,10 @@ 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], + [race.Phase.LastSpurt]: [ + 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 isFront = $derived(style === race.RunningStyle.FrontRunner || style === race.RunningStyle.Runaway);