diff --git a/zenno/src/lib/race.ts b/zenno/src/lib/race.ts new file mode 100644 index 0000000..d6b2ba1 --- /dev/null +++ b/zenno/src/lib/race.ts @@ -0,0 +1,103 @@ +// Umamusume race mechanics adapted from KuromiAK's doc: +// https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit?usp=sharing + +export enum RunningStyle { + FrontRunner, + PaceChaser, + LateSurger, + EndCloser, + GreatEscape, +} + +export enum AptitudeLevel { + G, + F, + E, + D, + C, + B, + A, + S, +} + +export enum Phase { + EarlyRace, + MidRace, + LateRace, +} + +function baseSpeed(raceLen: number): number { + return 20 - (raceLen - 2000) / 1000; +} + +const strategyPhaseCoeff = [ + [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], +] as const; + +const distanceProficiencyMod = [0.1, 0.2, 0.4, 0.6, 0.8, 0.9, 1.0, 1.05] as const; + +/** + * Calculate an Uma's last spurt target speed. + * @param rawSpeed Uma's speed stat. No accounting for mood lol. + * @param gutsStat Uma's guts stat. + * @param style Running style + * @param distance Distance aptitude + * @param raceLen Length of the race + * @returns Target speed in the last spurt in m/s + */ +export function spurtSpeed( + rawSpeed: number, + gutsStat: number, + style: RunningStyle, + distance: AptitudeLevel, + raceLen: number, +): number { + const speedStat = rawSpeed <= 1200 ? rawSpeed : 1200 + (rawSpeed - 1200) * 0.5; + const spc = strategyPhaseCoeff[style][Phase.LateRace]; + const dpm = distanceProficiencyMod[distance]; + const base = baseSpeed(raceLen); + // Expand and rearrange terms from the doccy to make solving for the inverse easier. + // (lateBaseTarget + 0.01*base) * 1.05 + sqrt(500*speedStat) * dpm * 0.002 + // = (base * spc + sqrt(500*speedStat) * dpm * 0.002 + 0.01 * base) * 1.05 + sqrt(500*speedStat) * dpm * 0.002 + // = 1.05*base*spc + 1.05*sqrt(500*speedStat)*dpm*0.002 + 0.0105*base + sqrt(500*speedStat)*dpm*0.002 + // = base * (1.05*spc + 0.0105) + 0.0041 * sqrt(500*speedStat) * dpm + // plus the guts component. + // TODO(zephyr): numerical precision? + return base * (1.05 * spc + 0.0105) + 0.0041 * Math.sqrt(500 * speedStat) * dpm + Math.pow(450 * gutsStat, 0.597) * 0.0001; +} + +/** + * Calculate the speed stat which produces a given target speed in the last spurt. + * Inverse of spurtSpeed. + * @param spurtSpeed Target speed in the last spurt in m/s + * @param gutsStat Uma's guts stat. No accounting for mood or stat thresholds. + * @param style Running style + * @param distance Distance aptitude + * @param raceLen Length of the race in meters + * @returns Speed stat which produces the target speed + */ +export function inverseSpurtSpeed( + spurtSpeed: number, + gutsStat: number, + style: RunningStyle, + distance: AptitudeLevel, + raceLen: number, +): number { + // spurtSpeed = base * (1.05*spc + 0.0105) + 0.0041*sqrt(500*speedStat)*dpm + pow(450*gutsStat, 0.597)*0.0001 + // spurtSpeed - base * (1.05*spc + 0.0105) - pow(450*gutsStat, 0.597)*0.0001 = 0.0041*sqrt(500*speedStat)*dpm + // (spurtSpeed - base * (1.05*spc + 0.0105) - pow(450*gutsStat, 0.597)*0.0001)²/(0.0041²*dpm²*500) = speedStat + // (spurtSpeed - base * (1.05*spc + 0.0105) - pow(450*gutsStat, 0.597)*0.0001)²/(0.008405*dpm²) = speedStat + const spc = strategyPhaseCoeff[style][Phase.LateRace]; + const dpm = distanceProficiencyMod[distance]; + const base = baseSpeed(raceLen); + const nr = spurtSpeed - base * (1.05 * spc + 0.0105) - Math.pow(450 * gutsStat, 0.597) * 0.0001; + const r = (nr * nr) / (0.008405 * dpm * dpm); + if (r > 1200) { + return Math.round(2 * r - 1200); + } + return Math.round(r); +} diff --git a/zenno/src/routes/+layout.svelte b/zenno/src/routes/+layout.svelte index a93c82e..27ae514 100644 --- a/zenno/src/routes/+layout.svelte +++ b/zenno/src/routes/+layout.svelte @@ -21,6 +21,7 @@ Inheritance Chance Spark Chance My Veterans + Spurt Speed Lobby Conversations diff --git a/zenno/src/routes/+page.svelte b/zenno/src/routes/+page.svelte index 65b71f8..b74b2c6 100644 --- a/zenno/src/routes/+page.svelte +++ b/zenno/src/routes/+page.svelte @@ -18,6 +18,10 @@ My Veterans — Not yet implemented — Set up and track your veterans for Zenno Rob Roy's inspiration and spark calculators. +