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; section: number;
phase: Phase; phase: Phase;
endRate: number; endRate: number;
posKeep: boolean; posKeep?: boolean;
rush: boolean; rush?: boolean;
spotStruggleEnter: boolean; spotStruggleEnter?: boolean;
} }
export const SECTIONS: ReadonlyArray<SectionInfo> = [ 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: 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: 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: 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: 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: 6, phase: Phase.MidRace, endRate: 6 / 24, posKeep: true, rush: true },
{ section: 7, phase: Phase.MidRace, endRate: 7 / 24, posKeep: true, rush: true, spotStruggleEnter: false }, { section: 7, phase: Phase.MidRace, endRate: 7 / 24, posKeep: true, rush: true },
{ section: 8, phase: Phase.MidRace, endRate: 8 / 24, posKeep: true, rush: true, spotStruggleEnter: false }, { section: 8, phase: Phase.MidRace, endRate: 8 / 24, posKeep: true, rush: true },
{ section: 9, phase: Phase.MidRace, endRate: 9 / 24, posKeep: true, rush: true, spotStruggleEnter: false }, { section: 9, phase: Phase.MidRace, endRate: 9 / 24, posKeep: true, rush: true },
{ section: 10, phase: Phase.MidRace, endRate: 10 / 24, posKeep: true, rush: false, spotStruggleEnter: false }, { section: 10, phase: Phase.MidRace, endRate: 10 / 24, posKeep: true },
{ section: 11, phase: Phase.MidRace, endRate: 11 / 24, posKeep: false, rush: false, spotStruggleEnter: false }, { section: 11, phase: Phase.MidRace, endRate: 11 / 24 },
{ section: 12, phase: Phase.MidRace, endRate: 12 / 24, posKeep: false, rush: false, spotStruggleEnter: false }, { section: 12, phase: Phase.MidRace, endRate: 12 / 24 },
{ section: 13, phase: Phase.MidRace, endRate: 13 / 24, posKeep: false, rush: false, spotStruggleEnter: false }, { section: 13, phase: Phase.MidRace, endRate: 13 / 24 },
{ section: 14, phase: Phase.MidRace, endRate: 14 / 24, posKeep: false, rush: false, spotStruggleEnter: false }, { section: 14, phase: Phase.MidRace, endRate: 14 / 24 },
{ section: 15, phase: Phase.MidRace, endRate: 15 / 24, posKeep: false, rush: false, spotStruggleEnter: false }, { section: 15, phase: Phase.MidRace, endRate: 15 / 24 },
{ section: 16, phase: Phase.MidRace, endRate: 16 / 24, posKeep: false, rush: false, spotStruggleEnter: false }, { section: 16, phase: Phase.MidRace, endRate: 16 / 24 },
{ section: 17, phase: Phase.LateRace, endRate: 17 / 24, posKeep: false, rush: false, spotStruggleEnter: false }, { section: 17, phase: Phase.LateRace, endRate: 17 / 24 },
{ section: 18, phase: Phase.LateRace, endRate: 18 / 24, posKeep: false, rush: false, spotStruggleEnter: false }, { section: 18, phase: Phase.LateRace, endRate: 18 / 24 },
{ section: 19, phase: Phase.LateRace, endRate: 19 / 24, posKeep: false, rush: false, spotStruggleEnter: false }, { section: 19, phase: Phase.LateRace, endRate: 19 / 24 },
{ section: 20, phase: Phase.LateRace, endRate: 20 / 24, posKeep: false, rush: false, spotStruggleEnter: false }, { section: 20, phase: Phase.LateRace, endRate: 20 / 24 },
{ section: 21, phase: Phase.LateRace, endRate: 21 / 24, posKeep: false, rush: false, spotStruggleEnter: false }, { section: 21, phase: Phase.LastSpurt, endRate: 21 / 24 },
{ section: 22, phase: Phase.LateRace, endRate: 22 / 24, posKeep: false, rush: false, spotStruggleEnter: false }, { section: 22, phase: Phase.LastSpurt, endRate: 22 / 24 },
{ section: 23, phase: Phase.LateRace, endRate: 23 / 24, posKeep: false, rush: false, spotStruggleEnter: false }, { section: 23, phase: Phase.LastSpurt, endRate: 23 / 24 },
{ section: 24, phase: Phase.LateRace, endRate: 24 / 24, posKeep: false, rush: false, spotStruggleEnter: false }, { section: 24, phase: Phase.LastSpurt, endRate: 24 / 24 },
]; ];
export interface SectionSlopeSegment extends SectionInfo { export interface SectionSlopeSegment extends SectionInfo {
+38 -38
View File
@@ -8,11 +8,11 @@ import { binomPMF } from './prob';
* Fundamental stats of umas. * Fundamental stats of umas.
*/ */
export enum Stat { export enum Stat {
Speed, Speed = 1,
Stamina, Stamina = 2,
Power, Power = 3,
Guts, Guts = 4,
Wit, Wit = 5,
} }
/** /**
@@ -118,14 +118,15 @@ export function distance(raceLen: number): Distance {
/** /**
* Get the distances at which each phase of a race ends. * Get the distances at which each phase of a race ends.
* @param raceLen Length of the race in meters * @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) { export function phases(raceLen: number) {
const sixth = raceLen / 6; const sixth = raceLen / 6;
return { return {
[Phase.EarlyRace]: sixth, [Phase.EarlyRace]: sixth,
[Phase.MidRace]: 4 * 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. * Race surfaces.
*/ */
export enum Surface { export enum Surface {
Turf, Turf = 1,
Dirt, Dirt = 2,
} }
/** /**
* Race ground condition types. * Race ground condition types.
*/ */
export enum GroundConditions { export enum GroundConditions {
Firm, Firm = 1,
Good, Good = 2,
Soft, Soft = 3,
Heavy, Heavy = 4,
} }
/** /**
@@ -327,12 +328,12 @@ export const RUNNING_STYLES = [
/** /**
* Race phases. * Race phases.
* While last spurt phase is also a phase, it is not distinguished here.
*/ */
export enum Phase { export enum Phase {
EarlyRace, EarlyRace = 0,
MidRace, MidRace = 1,
LateRace, LateRace = 2,
LastSpurt = 3,
} }
const hpStrategyCoeff = { const hpStrategyCoeff = {
@@ -407,7 +408,7 @@ export function hpPerSecond(
const pdmMod = pdm ? 0.6 : 1; const pdmMod = pdm ? 0.6 : 1;
const damMod = dam ? 0.4 : 1; const damMod = dam ? 0.4 : 1;
const groundMod = groundHPRateMod[surface][conditions]; 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; const sp = currentSpeed - baseSpeed(raceLen) + 12;
return (20 / 144) * sp * sp * rushedMod * pdmMod * damMod * groundMod * gutsMod; return (20 / 144) * sp * sp * rushedMod * pdmMod * damMod * groundMod * gutsMod;
} }
@@ -447,11 +448,11 @@ export function baseSpeed(raceLen: number): number {
} }
const speedStrategyPhaseCoeff = [ const speedStrategyPhaseCoeff = [
[1.0, 0.98, 0.962], [1.0, 0.98, 0.962, 0.962],
[0.978, 0.991, 0.975], [0.978, 0.991, 0.975, 0.975],
[0.938, 0.998, 0.994], [0.938, 0.998, 0.994, 0.994],
[0.931, 1.0, 1.0], [0.931, 1.0, 1.0, 1.0],
[1.063, 0.962, 0.95], [1.063, 0.962, 0.95, 0.95],
] as const; ] as const;
const distanceProficiencyMod: Readonly<Record<AptitudeLevel, number>> = { const distanceProficiencyMod: Readonly<Record<AptitudeLevel, number>> = {
@@ -471,7 +472,7 @@ const distanceProficiencyMod: Readonly<Record<AptitudeLevel, number>> = {
* @param style Horse's running style * @param style Horse's running style
* @param phase Phase to calculate for * @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. * Get a horse's base target speed when not spurting during late race.
* @param raceLen Length of the race in meters * @param raceLen Length of the race in meters
@@ -483,7 +484,7 @@ export function baseTargetSpeed(raceLen: number, style: RunningStyle, phase: Exc
export function baseTargetSpeed( export function baseTargetSpeed(
raceLen: number, raceLen: number,
style: RunningStyle, style: RunningStyle,
phase: Phase.LateRace, phase: Phase.LateRace | Phase.LastSpurt,
speedStat: number, speedStat: number,
distanceApt: AptitudeLevel, distanceApt: AptitudeLevel,
): number; ): number;
@@ -497,7 +498,7 @@ export function baseTargetSpeed(
const base = baseSpeed(raceLen); const base = baseSpeed(raceLen);
const baseTarget = base * speedStrategyPhaseCoeff[style][phase]; const baseTarget = base * speedStrategyPhaseCoeff[style][phase];
const late = 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; return baseTarget + late;
} }
@@ -512,7 +513,7 @@ export function sectionSpeed(
raceLen: number, raceLen: number,
witStat: number, witStat: number,
style: RunningStyle, style: RunningStyle,
phase: Exclude<Phase, Phase.LateRace>, phase: Exclude<Phase, Phase.LateRace | Phase.LastSpurt>,
): [number, number]; ): [number, number];
/** /**
* 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.
@@ -529,7 +530,7 @@ export function sectionSpeed(
witStat: number, witStat: number,
style: RunningStyle, style: RunningStyle,
distance: AptitudeLevel, distance: AptitudeLevel,
phase: Phase.LateRace, phase: Phase.LateRace | Phase.LastSpurt,
): [number, number]; ): [number, number];
export function sectionSpeed( export function sectionSpeed(
raceLen: number, raceLen: number,
@@ -537,7 +538,7 @@ export function sectionSpeed(
witStatOrStyle: number | RunningStyle, witStatOrStyle: number | RunningStyle,
styleOrPhase: RunningStyle | Phase, styleOrPhase: RunningStyle | Phase,
distance?: AptitudeLevel, distance?: AptitudeLevel,
lateRace?: Phase.LateRace, lateRace?: Phase.LateRace | Phase.LastSpurt,
): [number, number] { ): [number, number] {
const speedStat = lateRace !== undefined ? speedStatOrWitStat : 0; const speedStat = lateRace !== undefined ? speedStatOrWitStat : 0;
const witStat = lateRace !== undefined ? witStatOrStyle : speedStatOrWitStat; const witStat = lateRace !== undefined ? witStatOrStyle : speedStatOrWitStat;
@@ -546,7 +547,7 @@ export function sectionSpeed(
const base = baseSpeed(raceLen); const base = baseSpeed(raceLen);
const baseTarget = base * speedStrategyPhaseCoeff[style][phase]; const baseTarget = base * speedStrategyPhaseCoeff[style][phase];
const late = const late =
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(witStat * 0.1); 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; export const LANE_WIDTH = COURSE_WIDTH / 18;
const accelStrategyPhaseCoeff = { const accelStrategyPhaseCoeff = {
[RunningStyle.FrontRunner]: [1.0, 1.0, 0.996], [RunningStyle.FrontRunner]: [1.0, 1.0, 0.996, 0.996],
[RunningStyle.PaceChaser]: [0.985, 1.0, 0.996], [RunningStyle.PaceChaser]: [0.985, 1.0, 0.996, 0.996],
[RunningStyle.LateSurger]: [0.975, 1.0, 1.0], [RunningStyle.LateSurger]: [0.975, 1.0, 1.0, 1.0],
[RunningStyle.EndCloser]: [0.945, 1.0, 0.997], [RunningStyle.EndCloser]: [0.945, 1.0, 0.997, 0.997],
[RunningStyle.Runaway]: [1.17, 0.94, 0.956], [RunningStyle.Runaway]: [1.17, 0.94, 0.956, 0.956],
} as const; } as const;
const surfaceProficiencyMod: Readonly<Record<AptitudeLevel, number>> = { const surfaceProficiencyMod: Readonly<Record<AptitudeLevel, number>> = {
@@ -681,6 +682,7 @@ const phaseDecel = {
[Phase.EarlyRace]: -1.2, [Phase.EarlyRace]: -1.2,
[Phase.MidRace]: -0.8, [Phase.MidRace]: -0.8,
[Phase.LateRace]: -1.0, [Phase.LateRace]: -1.0,
[Phase.LastSpurt]: -1.0,
} as const; } as const;
/** /**
@@ -695,9 +697,7 @@ export function deceleration(phase: Phase, pdm?: boolean, dead?: boolean): numbe
return -1.2; return -1.2;
} }
if (pdm) { if (pdm) {
// This isn't until 1.5anni. return -0.5;
// return -0.5;
return phaseDecel[phase];
} }
return phaseDecel[phase]; return phaseDecel[phase];
} }
+2 -2
View File
@@ -99,8 +99,8 @@
const distance = $derived([last(seggs[0]).end.x, last(seggs[1]).end.x] as const); 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 time = $derived([last(seggs[0]).end.t, last(seggs[1]).end.t] as const);
const finishTime = $derived([ const finishTime = $derived([
time[0] + (phases[race.Phase.LateRace] - distance[0]) / spurtSpeed, time[0] + (raceLen - distance[0]) / spurtSpeed,
time[1] + (phases[race.Phase.LateRace] - distance[1]) / spurtSpeed, time[1] + (raceLen - distance[1]) / spurtSpeed,
] as const); ] as const);
function gainLengths(fast: accel.Instant, slow: accel.Instant): number { function gainLengths(fast: accel.Instant, slow: accel.Instant): number {
const m = fast.v * (slow.t - fast.t) + fast.x - slow.x; const m = fast.v * (slow.t - fast.t) + fast.x - slow.x;
+4
View File
@@ -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),
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], ] 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); // const isFront = $derived(style === race.RunningStyle.FrontRunner || style === race.RunningStyle.Runaway);