zenno: use a real last spurt phase

This commit is contained in:
2026-10-10 13:37:12 -04:00
parent 3a39794312
commit cf301745f1
4 changed files with 67 additions and 63 deletions
+23 -23
View File
@@ -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<SectionInfo> = [
{ 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 {
+38 -38
View File
@@ -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<Record<AptitudeLevel, number>> = {
@@ -471,7 +472,7 @@ const distanceProficiencyMod: Readonly<Record<AptitudeLevel, number>> = {
* @param style Horse's running style
* @param phase Phase to calculate for
*/
export function baseTargetSpeed(raceLen: number, style: RunningStyle, phase: Exclude<Phase, Phase.LateRace>): number;
export function baseTargetSpeed(raceLen: number, style: RunningStyle, phase: Exclude<Phase, Phase.LateRace | Phase.LastSpurt>): 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, Phase.LateRace>,
phase: Exclude<Phase, Phase.LateRace | Phase.LastSpurt>,
): [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<Record<AptitudeLevel, number>> = {
@@ -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];
}