zenno: format & lint
This commit is contained in:
@@ -73,7 +73,9 @@
|
||||
const series = $derived([y].flat(1).filter((s) => s != null));
|
||||
const areas = $derived([yArea].flat(1).filter((s) => s != null));
|
||||
const vals = $derived(series.flatMap(({ y, label }) => xVal.map((x) => ({ x, y: y(adj(x), x), label }))));
|
||||
const areaVals = $derived(areas.flatMap(({ y1, y2, label }) => xVal.map((x) => ({ x, y1: y1(adj(x), x), y2: y2(adj(x), x), label }))));
|
||||
const areaVals = $derived(
|
||||
areas.flatMap(({ y1, y2, label }) => xVal.map((x) => ({ x, y1: y1(adj(x), x), y2: y2(adj(x), x), label }))),
|
||||
);
|
||||
const yRange: [number, number] = $derived.by(() => {
|
||||
if (range != null) {
|
||||
if (range.length === 2) {
|
||||
|
||||
+60
-29
@@ -34,13 +34,7 @@ export enum Mood {
|
||||
/**
|
||||
* Moods as a descending list for easy iteration.
|
||||
*/
|
||||
export const MOODS = [
|
||||
Mood.Great,
|
||||
Mood.Good,
|
||||
Mood.Normal,
|
||||
Mood.Bad,
|
||||
Mood.Awful,
|
||||
] as const;
|
||||
export const MOODS = [Mood.Great, Mood.Good, Mood.Normal, Mood.Bad, Mood.Awful] as const;
|
||||
|
||||
const MOOD_COEF = {
|
||||
[Mood.Awful]: 0.96,
|
||||
@@ -61,7 +55,7 @@ function statClamp(x: number): number {
|
||||
* @returns Base stat value
|
||||
*/
|
||||
export function baseStat(mood: Mood, rawStat: number): number {
|
||||
const r = (math.min(rawStat, 1200) + math.max(rawStat - 1200, 0)*0.5) * MOOD_COEF[mood];
|
||||
const r = (math.min(rawStat, 1200) + math.max(rawStat - 1200, 0) * 0.5) * MOOD_COEF[mood];
|
||||
return statClamp(r);
|
||||
}
|
||||
|
||||
@@ -72,7 +66,7 @@ export enum Distance {
|
||||
Sprint,
|
||||
Mile,
|
||||
Medium,
|
||||
Long
|
||||
Long,
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,15 +98,15 @@ export function phases(raceLen: number) {
|
||||
[Phase.EarlyRace]: sixth,
|
||||
[Phase.MidRace]: 4 * sixth,
|
||||
[Phase.LateRace]: raceLen,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Race surfaces.
|
||||
*/
|
||||
export enum Surface {
|
||||
Turf,
|
||||
Dirt,
|
||||
Turf,
|
||||
Dirt,
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,46 +163,66 @@ const strategyProficiencyMod = [0.1, 0.2, 0.4, 0.6, 0.75, 0.85, 1.0, 1.1] as con
|
||||
|
||||
/**
|
||||
* Compute adjusted speed for a race.
|
||||
* @param speed
|
||||
* @param speed
|
||||
* @param baseSpeed Base speed stat as computed from raw speed with baseStat
|
||||
* @param career Whether the horse is currently training in career
|
||||
* @param surface Race surface type
|
||||
* @param conditions Race ground conditions
|
||||
* @param thresholdMod Achieved stat threshold modifier for the race track
|
||||
*/
|
||||
export function adjustedStat(speed: Stat.Speed, baseSpeed: number, career: boolean, surface: Surface, conditions: GroundConditions, thresholdMod: number): number;
|
||||
export function adjustedStat(
|
||||
speed: Stat.Speed,
|
||||
baseSpeed: number,
|
||||
career: boolean,
|
||||
surface: Surface,
|
||||
conditions: GroundConditions,
|
||||
thresholdMod: number,
|
||||
): number;
|
||||
/**
|
||||
* Compute adjusted stamina for a race.
|
||||
* @param stam
|
||||
* @param stam
|
||||
* @param baseStam Base stamina stat as computed from raw stamina with baseStat
|
||||
* @param career Whether the horse is currently training in career
|
||||
*/
|
||||
export function adjustedStat(stam: Stat.Stamina, baseStam: number, career: boolean): number;
|
||||
/**
|
||||
* Compute adjusted power for a race.
|
||||
* @param power
|
||||
* @param power
|
||||
* @param basePower Base power stat as computed from raw power with baseStat
|
||||
* @param career Whether the horse is currently training in career
|
||||
* @param surface Race surface type
|
||||
* @param conditions Race ground conditions
|
||||
*/
|
||||
export function adjustedStat(power: Stat.Power, basePower: number, career: boolean, surface: Surface, conditions: GroundConditions): number;
|
||||
export function adjustedStat(
|
||||
power: Stat.Power,
|
||||
basePower: number,
|
||||
career: boolean,
|
||||
surface: Surface,
|
||||
conditions: GroundConditions,
|
||||
): number;
|
||||
/**
|
||||
* Compute adjusted guts for a race.
|
||||
* @param guts
|
||||
* @param guts
|
||||
* @param baseGuts Base guts stat as computed from raw guts with baseStat
|
||||
* @param career Whether the horse is currently training in career
|
||||
*/
|
||||
export function adjustedStat(guts: Stat.Guts, baseGuts: number, career: boolean): number;
|
||||
/**
|
||||
* Compute adjusted wit for a race.
|
||||
* @param wit
|
||||
* @param wit
|
||||
* @param baseWit Base wit stat as computed from raw wit with baseStat
|
||||
* @param career Whether the horse is currently training in career
|
||||
* @param styleApt Aptitude for the horse's current running style
|
||||
*/
|
||||
export function adjustedStat(wit: Stat.Wit, baseWit: number, career: boolean, styleApt: AptitudeLevel): number;
|
||||
export function adjustedStat(stat: Stat, baseStat: number, career: boolean, surfaceOrStyle?: Surface | AptitudeLevel, conditions?: GroundConditions, thresholdMod?: number): number {
|
||||
export function adjustedStat(
|
||||
stat: Stat,
|
||||
baseStat: number,
|
||||
career: boolean,
|
||||
surfaceOrStyle?: Surface | AptitudeLevel,
|
||||
conditions?: GroundConditions,
|
||||
thresholdMod?: number,
|
||||
): number {
|
||||
const careerMod = career ? 400 : 0;
|
||||
switch (stat) {
|
||||
case Stat.Speed:
|
||||
@@ -229,13 +243,13 @@ export function thresholdMod(stat1: number, stat2?: number): number {
|
||||
return 0.5 * (thresholdMod(stat1) + thresholdMod(stat2));
|
||||
}
|
||||
if (stat1 > 900) {
|
||||
return 1.20;
|
||||
return 1.2;
|
||||
}
|
||||
if (stat1 > 600) {
|
||||
return 1.15;
|
||||
}
|
||||
if (stat1 > 300) {
|
||||
return 1.10;
|
||||
return 1.1;
|
||||
}
|
||||
return 1.05;
|
||||
}
|
||||
@@ -337,7 +351,7 @@ const groundHPRateMod = {
|
||||
[GroundConditions.Good]: 1,
|
||||
[GroundConditions.Soft]: 1.01,
|
||||
[GroundConditions.Heavy]: 1.02,
|
||||
}
|
||||
},
|
||||
} as const;
|
||||
|
||||
/**
|
||||
@@ -352,14 +366,24 @@ const groundHPRateMod = {
|
||||
* @param pdm Whether pace-down mode is active (default false)
|
||||
* @param rushed Whether the horse is currently rushed (default false)
|
||||
*/
|
||||
export function hpPerSecond(raceLen: number, surface: Surface, conditions: GroundConditions, gutsStat: number, phase: Phase, currentSpeed: number, dam?: boolean, pdm?: boolean, rushed?: boolean): number {
|
||||
export function hpPerSecond(
|
||||
raceLen: number,
|
||||
surface: Surface,
|
||||
conditions: GroundConditions,
|
||||
gutsStat: number,
|
||||
phase: Phase,
|
||||
currentSpeed: number,
|
||||
dam?: boolean,
|
||||
pdm?: boolean,
|
||||
rushed?: boolean,
|
||||
): number {
|
||||
const rushedMod = rushed ? 1.6 : 1;
|
||||
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;
|
||||
return (20 / 144) * sp * sp * rushedMod * pdmMod * damMod * groundMod * gutsMod;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -695,13 +719,20 @@ const conserveStratDistCoef = {
|
||||
},
|
||||
} as const;
|
||||
|
||||
export function conservePowerAccel(distanceType: Distance, style: RunningStyle, rawPower: number, powerSkillBonus?: number, spotStruggled?: boolean, rushed?: boolean): number {
|
||||
export function conservePowerAccel(
|
||||
distanceType: Distance,
|
||||
style: RunningStyle,
|
||||
rawPower: number,
|
||||
powerSkillBonus?: number,
|
||||
spotStruggled?: boolean,
|
||||
rushed?: boolean,
|
||||
): number {
|
||||
const power = rawPower + (powerSkillBonus ?? 0);
|
||||
if (power <= 1200) {
|
||||
return 0;
|
||||
}
|
||||
const sdc = conserveStratDistCoef[style][distanceType];
|
||||
const activity = rushed ? 0.8 : (spotStruggled ? 0.98 : 1);
|
||||
const activity = rushed ? 0.8 : spotStruggled ? 0.98 : 1;
|
||||
return Math.sqrt((power - 1200) * 130) * 0.001 * sdc * activity;
|
||||
}
|
||||
|
||||
@@ -781,7 +812,7 @@ export function uphillMod(powerStat: number, slopePer: number): number {
|
||||
* @returns Speed modifier for downhill accel mode
|
||||
*/
|
||||
export function downhillAccelMod(slopePer: number): number {
|
||||
return 0.3 - slopePer/10;
|
||||
return 0.3 - slopePer / 10;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,22 +2,22 @@
|
||||
* Instantaneous characteristics of a horse in motion.
|
||||
*/
|
||||
export interface Instant {
|
||||
/** Timestamp of the instant. */
|
||||
t: number;
|
||||
/** Forward position along the course. */
|
||||
x: number;
|
||||
/** Actual speed. */
|
||||
v: number;
|
||||
/** Timestamp of the instant. */
|
||||
t: number;
|
||||
/** Forward position along the course. */
|
||||
x: number;
|
||||
/** Actual speed. */
|
||||
v: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward travel characteristics under the effects of acceleration.
|
||||
*/
|
||||
export interface Travel {
|
||||
start: Instant;
|
||||
end: Instant;
|
||||
/** Actual acceleration value applied between start and end. */
|
||||
a: number;
|
||||
start: Instant;
|
||||
end: Instant;
|
||||
/** Actual acceleration value applied between start and end. */
|
||||
a: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -30,31 +30,38 @@ export interface Travel {
|
||||
* @param decel Negative acceleration value for cases where target speed is below start speed
|
||||
* @returns Travel segment after reaching the first of the given limits
|
||||
*/
|
||||
export function travelSegment(start: Instant, targetSpeed: number, totalAccel: number, maxDur?: number, maxDist?: number, decel?: number): Travel {
|
||||
const a = start.v < targetSpeed ? totalAccel : decel ?? -1;
|
||||
const dvt = (targetSpeed - start.v) / a;
|
||||
const dxt = maxDist != null ? (-start.v + Math.sqrt(start.v*start.v + 4*a*maxDist)) / (2*a) : dvt;
|
||||
const time = maxDur != null ? Math.min(dvt, dxt, maxDur) : Math.min(dvt, dxt);
|
||||
// To address numerical imprecision, if accel time comes from velocity or distance,
|
||||
// set the reached values directly.
|
||||
const v = time === dvt ? targetSpeed : start.v + time * a;
|
||||
const dist = maxDist != null && time === dxt ? maxDist : (start.v + 0.5 * a * time) * time;
|
||||
const end = { t: start.t + time, x: start.x + dist, v };
|
||||
return {start, end, a};
|
||||
export function travelSegment(
|
||||
start: Instant,
|
||||
targetSpeed: number,
|
||||
totalAccel: number,
|
||||
maxDur?: number,
|
||||
maxDist?: number,
|
||||
decel?: number,
|
||||
): Travel {
|
||||
const a = start.v < targetSpeed ? totalAccel : (decel ?? -1);
|
||||
const dvt = (targetSpeed - start.v) / a;
|
||||
const dxt = maxDist != null ? (-start.v + Math.sqrt(start.v * start.v + 4 * a * maxDist)) / (2 * a) : dvt;
|
||||
const time = maxDur != null ? Math.min(dvt, dxt, maxDur) : Math.min(dvt, dxt);
|
||||
// To address numerical imprecision, if accel time comes from velocity or distance,
|
||||
// set the reached values directly.
|
||||
const v = time === dvt ? targetSpeed : start.v + time * a;
|
||||
const dist = maxDist != null && time === dxt ? maxDist : (start.v + 0.5 * a * time) * time;
|
||||
const end = { t: start.t + time, x: start.x + dist, v };
|
||||
return { start, end, a };
|
||||
}
|
||||
|
||||
/**
|
||||
* Acceleration bonus from skills.
|
||||
*/
|
||||
export interface Skill {
|
||||
/**
|
||||
* Acceleration modifier in m/s².
|
||||
*/
|
||||
accel: number;
|
||||
/**
|
||||
* Remaining actual duration in seconds.
|
||||
*/
|
||||
dur: number;
|
||||
/**
|
||||
* Acceleration modifier in m/s².
|
||||
*/
|
||||
accel: number;
|
||||
/**
|
||||
* Remaining actual duration in seconds.
|
||||
*/
|
||||
dur: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,25 +73,25 @@ export interface Skill {
|
||||
* @returns Acceleration segments and remainders of skills upon reaching the target speed
|
||||
*/
|
||||
export function up(start: Instant, targetSpeed: number, baseAccel: number, skills: Skill[]) {
|
||||
const segments: Travel[] = [];
|
||||
const sk = skills.map((s) => ({...s})).filter((s) => s.dur > 0);
|
||||
while (start.v < targetSpeed) {
|
||||
const active = sk.filter((s) => s.dur > 0);
|
||||
if (active.length === 0) {
|
||||
segments.push(travelSegment(start, targetSpeed, baseAccel));
|
||||
break;
|
||||
}
|
||||
const boost = active.reduce((a, s) => a + s.accel, 0);
|
||||
const time = Math.min(...active.map((s) => s.dur));
|
||||
const seg = travelSegment(start, targetSpeed, baseAccel + boost, time);
|
||||
segments.push(seg);
|
||||
start = seg.end
|
||||
for (const s of active) {
|
||||
s.dur -= seg.end.t - seg.start.t;
|
||||
if (s.dur < 1e-5) {
|
||||
s.dur = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
return {segments, skills: sk}
|
||||
const segments: Travel[] = [];
|
||||
const sk = skills.map((s) => ({ ...s })).filter((s) => s.dur > 0);
|
||||
while (start.v < targetSpeed) {
|
||||
const active = sk.filter((s) => s.dur > 0);
|
||||
if (active.length === 0) {
|
||||
segments.push(travelSegment(start, targetSpeed, baseAccel));
|
||||
break;
|
||||
}
|
||||
const boost = active.reduce((a, s) => a + s.accel, 0);
|
||||
const time = Math.min(...active.map((s) => s.dur));
|
||||
const seg = travelSegment(start, targetSpeed, baseAccel + boost, time);
|
||||
segments.push(seg);
|
||||
start = seg.end;
|
||||
for (const s of active) {
|
||||
s.dur -= seg.end.t - seg.start.t;
|
||||
if (s.dur < 1e-5) {
|
||||
s.dur = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return { segments, skills: sk };
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ declare const __stage: unique symbol;
|
||||
|
||||
export type Stage = 'raw' | 'base' | 'adjusted' | 'final' | 'skills';
|
||||
|
||||
export type StatValue<T extends Stage> = number & {[__stage]: T};
|
||||
export type StatValue<T extends Stage> = number & { [__stage]: T };
|
||||
|
||||
export interface Stats<T extends Stage> {
|
||||
speed: StatValue<T>;
|
||||
|
||||
Reference in New Issue
Block a user