+145
-2
@@ -107,6 +107,42 @@ const speedStrategyPhaseCoeff = [
|
||||
|
||||
const distanceProficiencyMod = [0.1, 0.2, 0.4, 0.6, 0.8, 0.9, 1.0, 1.05] as const;
|
||||
|
||||
/**
|
||||
* Get a horse's base target speed outside of late race.
|
||||
* @param raceLen Length of the race in meters
|
||||
* @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;
|
||||
/**
|
||||
* Get a horse's base target speed when not spurting during late race.
|
||||
* @param raceLen Length of the race in meters
|
||||
* @param style Horse's running style
|
||||
* @param phase Phase to calculate for
|
||||
* @param speedStat Final speed stat
|
||||
* @param distanceApt Horse's aptitude for the distance
|
||||
*/
|
||||
export function baseTargetSpeed(
|
||||
raceLen: number,
|
||||
style: RunningStyle,
|
||||
phase: Phase.LateRace,
|
||||
speedStat: number,
|
||||
distanceApt: AptitudeLevel,
|
||||
): number;
|
||||
export function baseTargetSpeed(
|
||||
raceLen: number,
|
||||
style: RunningStyle,
|
||||
phase: Phase,
|
||||
speedStat?: number,
|
||||
distanceApt?: number,
|
||||
): number {
|
||||
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;
|
||||
return baseTarget + late;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the range of section speed values for a horse in early or mid race.
|
||||
* @param raceLen Length of the race in meters
|
||||
@@ -224,6 +260,25 @@ export function inverseSpurtSpeed(
|
||||
return Math.round(r);
|
||||
}
|
||||
|
||||
const hpStrategyCoeff = {
|
||||
[RunningStyle.FrontRunner]: 0.95,
|
||||
[RunningStyle.PaceChaser]: 0.89,
|
||||
[RunningStyle.LateSurger]: 1.0,
|
||||
[RunningStyle.EndCloser]: 0.995,
|
||||
[RunningStyle.GreatEscape]: 0.86,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Calculate a horse's max HP (starting HP) for a race.
|
||||
* @param raceLen Length of the race in meters
|
||||
* @param style Horse's running style
|
||||
* @param stamStat Final stamina stat
|
||||
* @returns Max HP
|
||||
*/
|
||||
export function maxHP(raceLen: number, style: RunningStyle, stamStat: number): number {
|
||||
return 0.8 * hpStrategyCoeff[style] * stamStat + raceLen;
|
||||
}
|
||||
|
||||
/** Meters per horse length (馬身). */
|
||||
export const HORSE_LENGTH = 2.5;
|
||||
/** Meters per course width (a constant unit of measure). */
|
||||
@@ -295,6 +350,57 @@ export function deceleration(phase: Phase, pdm?: boolean, dead?: boolean): numbe
|
||||
return phaseDecel[phase];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate a horse's lane change target speed before the first move lane point.
|
||||
* @param powerStat Final power stat
|
||||
* @param maxLane Max lane value on the race track in CW
|
||||
* @param currentLane Current lane on the race track in CW
|
||||
* @returns Lane change speed in CW/s
|
||||
*/
|
||||
export function laneChangeSpeed(powerStat: number, maxLane: number, currentLane: number): number;
|
||||
/**
|
||||
* Calculate a horse's lane change target speed during late race and final spurt phase.
|
||||
* @param powerStat Final power stat
|
||||
* @param order Current order on the field
|
||||
* @returns Lane change speed in CW/s
|
||||
*/
|
||||
export function laneChangeSpeed(powerStat: number, order: number): number;
|
||||
/**
|
||||
* Calculate a horse's lane change target speed between the first move lane point and late race.
|
||||
* @param powerStat Final power stat
|
||||
* @returns Lane change speed in CW/s
|
||||
*/
|
||||
export function laneChangeSpeed(powerStat: number): number;
|
||||
export function laneChangeSpeed(powerStat: number, maxLaneOrOrder?: number, currentLane?: number): number {
|
||||
const laneMod = currentLane != null ? 1 + (currentLane / maxLaneOrOrder!) * 0.05 : 1;
|
||||
const orderMod = currentLane == null ? 1 + (maxLaneOrOrder ?? 0) * 0.05 : 1;
|
||||
return 0.02 * (0.3 + 0.001 * powerStat) * laneMod * orderMod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Acceleration of lane change (horizontal) current speed.
|
||||
*/
|
||||
export const LANE_CHANGE_ACCEL = 0.03;
|
||||
|
||||
/**
|
||||
* Calculate a horse's minimum speed for a race.
|
||||
* @param raceLen Length of the race in meters
|
||||
* @param gutsStat Final guts stat
|
||||
* @returns Minimum speed in m/s
|
||||
*/
|
||||
export function minSpeed(raceLen: number, gutsStat: number): number {
|
||||
return 0.85 * baseSpeed(raceLen) + Math.sqrt(200 * gutsStat) * 0.001;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate a horse's HP consumption multiplier for late race and last spurt phase.
|
||||
* @param gutsStat Final guts stat
|
||||
* @returns HP consumption multiplier
|
||||
*/
|
||||
export function spurtHPRateMod(gutsStat: number): number {
|
||||
return 1 + 200 / Math.sqrt(600 * gutsStat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the speed boost gained from spot struggle.
|
||||
* @param gutsStat Final guts stat
|
||||
@@ -319,6 +425,24 @@ export function spotStruggleDuration(gutsStat: number, frontAptitude: AptitudeLe
|
||||
return Math.sqrt(700 * gutsStat) * 0.012 * strategyProficiencyMod[frontAptitude];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the target speed bonus of dueling.
|
||||
* @param gutsStat Final guts stat
|
||||
* @returns Modifier to target speed while dueling in m/s
|
||||
*/
|
||||
export function duelSpeedMod(gutsStat: number): number {
|
||||
return Math.pow(200 * gutsStat, 0.708) * 0.0001;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the acceleration bonus of dueling.
|
||||
* @param gutsStat Final guts stat
|
||||
* @returns Modifier to acceleration while dueling in m/s²
|
||||
*/
|
||||
export function duelAccelMod(gutsStat: number): number {
|
||||
return Math.pow(160 * gutsStat, 0.59) * 0.0001;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the speed modifier for running uphill.
|
||||
* Contrary to the race mechanics document, this is expressed as a negative number.
|
||||
@@ -348,7 +472,7 @@ export function moveLaneModifier(powerStat: number): number {
|
||||
* @returns Probability of exactly n skills out of N passing wit checks
|
||||
*/
|
||||
export function skillWitCheck(baseWit: number, N?: number, n?: number): number {
|
||||
const p = Math.max(0.2, 1 - 90 / baseWit);
|
||||
const p = Math.ceil(Math.max(20, 100 - 9000 / baseWit)) / 100;
|
||||
return binomPMF(p, N ?? 1, n ?? 1);
|
||||
}
|
||||
|
||||
@@ -389,13 +513,22 @@ export function speedGain(speedBonus: number, dur: number, accel: number | null,
|
||||
return speedBonus * (dur + 0.5 * (decelTime - accelTime));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the probability of accepting a reduced spurt candidate.
|
||||
* @param witStat Final wit stat
|
||||
* @returns Probability per candidate to accept
|
||||
*/
|
||||
export function reducedSpurtChance(witStat: number): number {
|
||||
return Math.ceil(15 + 0.05 * witStat) / 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the chance to enter downhill accel mode each second while running downhill.
|
||||
* @param witStat Final wit stat, including style aptitude modifier
|
||||
* @returns Probability each eligible tick to enter downhill accel mode
|
||||
*/
|
||||
export function downhillAccelEnterChance(witStat: number): number {
|
||||
return witStat * 0.0004;
|
||||
return Math.ceil(witStat * 0.04) / 100;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -415,3 +548,13 @@ export function frontModeEnterChance(witStat: number): number {
|
||||
export function paceUpEnterChance(witStat: number): number {
|
||||
return 0.15 * math.log10(witStat) - 0.15;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the chance for a horse to become rushed during a given race.
|
||||
* @param witStat Final wit stat, including style aptitude modifier
|
||||
* @returns Probability to become rushed during the race
|
||||
*/
|
||||
export function rushedChance(witStat: number): number {
|
||||
const r = 6.5 / Math.log10(0.1 * witStat + 1);
|
||||
return Math.ceil(r * r) / 100;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,389 @@
|
||||
<script lang="ts">
|
||||
import type { ComputedAreas, ComputedSeries } from '$lib/chart';
|
||||
import Article from '../Article.svelte';
|
||||
import Sec from '../Sec.svelte';
|
||||
import * as race from '$lib/race';
|
||||
import StatChart from '$lib/StatChart.svelte';
|
||||
import { resolve } from '$app/paths';
|
||||
|
||||
function modemean(x: number, bonus: number, p: number): number {
|
||||
return x * (1 - p) + x * bonus * p;
|
||||
}
|
||||
|
||||
let style = $state(race.RunningStyle.FrontRunner);
|
||||
let surfaceApt = $state(race.AptitudeLevel.A);
|
||||
let distanceApt = $state(race.AptitudeLevel.A);
|
||||
let raceLen = $state(2000);
|
||||
const raceLenType = $derived.by(() => {
|
||||
if (raceLen < 1500) {
|
||||
return 'Sprint';
|
||||
}
|
||||
if (raceLen < 1900) {
|
||||
return 'Mile';
|
||||
}
|
||||
if (raceLen < 2500) {
|
||||
return 'Medium';
|
||||
}
|
||||
return 'Long';
|
||||
});
|
||||
|
||||
const spurtSpeed: Array<ComputedSeries | null> = $derived([
|
||||
{ label: `Guts 1200`, y: (x) => race.spurtSpeed(x, 1200, style, distanceApt, raceLen) },
|
||||
{ label: `Guts 600`, y: (x) => race.spurtSpeed(x, 600, style, distanceApt, raceLen) },
|
||||
distanceApt !== race.AptitudeLevel.A
|
||||
? { label: `${raceLenType} A, Guts 1200`, y: (x) => race.spurtSpeed(x, 1200, style, race.AptitudeLevel.A, raceLen) }
|
||||
: null,
|
||||
]);
|
||||
const nonSpurtSpeed: Array<ComputedSeries | null> = $derived([
|
||||
{ label: `Base Target Speed`, y: (x) => race.baseTargetSpeed(raceLen, style, race.Phase.LateRace, x, distanceApt) },
|
||||
distanceApt !== race.AptitudeLevel.A
|
||||
? {
|
||||
label: `${raceLenType} A`,
|
||||
y: (x) => race.baseTargetSpeed(raceLen, style, race.Phase.LateRace, x, race.AptitudeLevel.A),
|
||||
}
|
||||
: null,
|
||||
]);
|
||||
const hp: ComputedSeries[] = $derived([
|
||||
{ label: 'Front Runner', y: (x) => race.maxHP(raceLen, race.RunningStyle.FrontRunner, x) },
|
||||
{ label: 'Pace Chaser', y: (x) => race.maxHP(raceLen, race.RunningStyle.PaceChaser, x) },
|
||||
{ label: 'Late Surger', y: (x) => race.maxHP(raceLen, race.RunningStyle.LateSurger, x) },
|
||||
{ label: 'End Closer', y: (x) => race.maxHP(raceLen, race.RunningStyle.EndCloser, x) },
|
||||
{ label: 'Runaway', y: (x) => race.maxHP(raceLen, race.RunningStyle.GreatEscape, x) },
|
||||
]);
|
||||
const moveLane: ComputedSeries[] = [{ label: 'Move Lane Modifier', y: (x) => race.moveLaneModifier(x) }];
|
||||
const uphill: ComputedSeries[] = [
|
||||
{ label: '+2 Hill', y: (x) => race.uphillMod(x, 2.0) },
|
||||
{ label: '+1.5 Hill', y: (x) => race.uphillMod(x, 1.5) },
|
||||
{ label: '+1 Hill', y: (x) => race.uphillMod(x, 1.0) },
|
||||
];
|
||||
const accel: Array<ComputedSeries | null> = $derived([
|
||||
{ label: 'Early Race', y: (x) => race.acceleration(x, style, surfaceApt, race.Phase.EarlyRace) },
|
||||
{ label: 'Mid Race', y: (x) => race.acceleration(x, style, surfaceApt, race.Phase.MidRace) },
|
||||
{ label: 'Late Race', y: (x) => race.acceleration(x, style, surfaceApt, race.Phase.LateRace) },
|
||||
{
|
||||
label: 'Early Race Uphill',
|
||||
y: (x) => race.acceleration(x, style, surfaceApt, race.Phase.EarlyRace, race.AptitudeLevel.A, true),
|
||||
},
|
||||
]);
|
||||
const laneChange: ComputedSeries[] = [{ label: 'Lane Change Target Speed', y: (x) => race.laneChangeSpeed(x) }];
|
||||
const gutsSpurt: Array<ComputedSeries | null> = $derived([
|
||||
{ label: `Speed 1200`, y: (x) => race.spurtSpeed(1200, x, style, distanceApt, raceLen) },
|
||||
{ label: `Speed 600`, y: (x) => race.spurtSpeed(600, x, style, distanceApt, raceLen) },
|
||||
distanceApt !== race.AptitudeLevel.A
|
||||
? { label: `${raceLenType} A, Speed 1200`, y: (x) => race.spurtSpeed(1200, x, style, race.AptitudeLevel.A, raceLen) }
|
||||
: null,
|
||||
]);
|
||||
const minSpeed: ComputedSeries[] = $derived([{ label: 'Minimum Speed', y: (x) => race.minSpeed(raceLen, x) }]);
|
||||
const gutsHPRate: ComputedSeries[] = [{ label: 'HP Consumption Rate Multiplier', y: (x) => race.spurtHPRateMod(x) }];
|
||||
const ssBoost: ComputedSeries[] = [{ label: 'Target Speed Boost', y: (x) => race.spotStruggleSpeed(x) }];
|
||||
const ssDur: ComputedSeries[] = [
|
||||
{ label: 'Front Runner S', y: (x) => race.spotStruggleDuration(x, race.AptitudeLevel.S) },
|
||||
{ label: 'Front Runner A', y: (x) => race.spotStruggleDuration(x, race.AptitudeLevel.A) },
|
||||
];
|
||||
const duelSpeed: ComputedSeries[] = [{ label: 'Dueling Speed Boost', y: (x) => race.duelSpeedMod(x) }];
|
||||
const duelAccel: ComputedSeries[] = [{ label: 'Dueling Acceleration Boost', y: (x) => race.duelAccelMod(x) }];
|
||||
const styleIsFront = $derived(style === race.RunningStyle.FrontRunner || style === race.RunningStyle.GreatEscape);
|
||||
const secSpeed: Array<ComputedAreas | null> = $derived([
|
||||
{
|
||||
label: 'Early Race',
|
||||
y1: (x) => race.sectionSpeed(raceLen, x, x, style, race.Phase.EarlyRace)[0],
|
||||
y2: (x) => race.sectionSpeed(raceLen, x, x, style, race.Phase.EarlyRace)[1],
|
||||
},
|
||||
{
|
||||
label: 'Mid Race',
|
||||
y1: (x) => race.sectionSpeed(raceLen, x, x, style, race.Phase.MidRace)[0],
|
||||
y2: (x) => race.sectionSpeed(raceLen, x, x, style, race.Phase.MidRace)[1],
|
||||
},
|
||||
styleIsFront
|
||||
? {
|
||||
label: 'Early Race + Mean Speed-Up Mode',
|
||||
y1: (x) =>
|
||||
modemean(race.sectionSpeed(raceLen, x, x, style, race.Phase.EarlyRace)[0], 1.04, race.frontModeEnterChance(x)),
|
||||
y2: (x) =>
|
||||
modemean(race.sectionSpeed(raceLen, x, x, style, race.Phase.EarlyRace)[1], 1.04, race.frontModeEnterChance(x)),
|
||||
}
|
||||
: {
|
||||
label: 'Early Race + Mean Pace-Up Mode',
|
||||
y1: (x) => modemean(race.sectionSpeed(raceLen, x, x, style, race.Phase.EarlyRace)[0], 1.04, race.paceUpEnterChance(x)),
|
||||
y2: (x) => modemean(race.sectionSpeed(raceLen, x, x, style, race.Phase.EarlyRace)[1], 1.04, race.paceUpEnterChance(x)),
|
||||
},
|
||||
styleIsFront
|
||||
? {
|
||||
label: 'Mid Race + Mean Speed-Up Mode',
|
||||
y1: (x) => modemean(race.sectionSpeed(raceLen, x, x, style, race.Phase.MidRace)[0], 1.04, race.frontModeEnterChance(x)),
|
||||
y2: (x) => modemean(race.sectionSpeed(raceLen, x, x, style, race.Phase.MidRace)[1], 1.04, race.frontModeEnterChance(x)),
|
||||
}
|
||||
: {
|
||||
label: 'Mid Race + Mean Pace-Up Mode',
|
||||
y1: (x) => modemean(race.sectionSpeed(raceLen, x, x, style, race.Phase.MidRace)[0], 1.04, race.paceUpEnterChance(x)),
|
||||
y2: (x) => modemean(race.sectionSpeed(raceLen, x, x, style, race.Phase.MidRace)[1], 1.04, race.paceUpEnterChance(x)),
|
||||
},
|
||||
]);
|
||||
const downhill: ComputedSeries[] = [
|
||||
{ label: 'Style S', y: (x) => race.downhillAccelEnterChance(x * 1.1) * 100 },
|
||||
{ label: 'Style A', y: (x) => race.downhillAccelEnterChance(x) * 100 },
|
||||
];
|
||||
const reducedSpurt: ComputedSeries[] = [
|
||||
{ label: 'Style S', y: (x) => race.reducedSpurtChance(x * 1.1) * 100 },
|
||||
{ label: 'Style A', y: (x) => race.reducedSpurtChance(x) * 100 },
|
||||
];
|
||||
const skillChance: ComputedSeries[] = [
|
||||
{ label: 'Per Skill', y: (x) => race.skillWitCheck(x) * 100 },
|
||||
{ label: '1 of 2 or 2 of 2', y: (x) => (race.skillWitCheck(x, 2, 1) + race.skillWitCheck(x, 2, 2)) * 100 },
|
||||
{ label: '2 of 2', y: (x) => race.skillWitCheck(x, 2, 2) * 100 },
|
||||
{ label: '3 of 3', y: (x) => race.skillWitCheck(x, 3, 3) * 100 },
|
||||
// TODO(zeph): why is this wrong?
|
||||
{ label: '3 of 3 or 3 of 4', y: (x) => (race.skillWitCheck(x, 4, 3) + race.skillWitCheck(x, 3, 3)) * 100 },
|
||||
];
|
||||
const poskeep: ComputedSeries[] = [
|
||||
{ label: 'Front Runner S', y: (x) => 100 * race.frontModeEnterChance(x * 1.1) },
|
||||
{ label: 'Front Runner A', y: (x) => 100 * race.frontModeEnterChance(x) },
|
||||
{ label: 'Style S', y: (x) => 100 * race.paceUpEnterChance(x * 1.1) },
|
||||
{ label: 'Style A', y: (x) => 100 * race.paceUpEnterChance(x) },
|
||||
];
|
||||
const rushed: ComputedSeries[] = [
|
||||
{ label: 'Style S', y: (x) => race.rushedChance(x * 1.1) * 100 },
|
||||
{ label: 'Style A', y: (x) => race.rushedChance(x) * 100 },
|
||||
];
|
||||
</script>
|
||||
|
||||
{#snippet statChart(
|
||||
stat: race.Stat,
|
||||
y: Array<ComputedSeries | null>,
|
||||
yLabel: string,
|
||||
range?: [number, number],
|
||||
pick?: { len?: boolean; style?: boolean; dist?: boolean; surf?: boolean },
|
||||
)}
|
||||
{#if !pick?.len && !pick?.style && !pick?.dist && !pick?.surf}
|
||||
<div class="mb-4 h-60 w-full md:mb-20 md:h-96">
|
||||
<StatChart
|
||||
class="mx-auto mb-12 h-full w-full max-w-3xl md:mb-10"
|
||||
{stat}
|
||||
{y}
|
||||
{yLabel}
|
||||
{range}
|
||||
plotOptions={{ color: { legend: true } }}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="mb-24 h-60 w-full md:mb-28 md:h-96">
|
||||
<StatChart
|
||||
class="mx-auto mb-12 h-full w-full max-w-3xl md:mb-10"
|
||||
{stat}
|
||||
{y}
|
||||
{yLabel}
|
||||
{range}
|
||||
plotOptions={{ color: { legend: true } }}
|
||||
/>
|
||||
<div class="mx-auto flex w-full md:max-w-2xl">
|
||||
{#if pick?.len}
|
||||
<input
|
||||
class="my-auto max-w-40 flex-1 md:flex-2"
|
||||
type="range"
|
||||
id="raceLen"
|
||||
min="1000"
|
||||
max="3600"
|
||||
step="100"
|
||||
bind:value={raceLen}
|
||||
/>
|
||||
<span class="my-auto flex-1 pl-2 text-sm">{raceLen}m</span>
|
||||
{/if}
|
||||
{#if pick?.style}
|
||||
<label class="my-auto hidden flex-1 pr-2 text-right text-sm md:inline" for="stylePick">Style</label>
|
||||
<select class="my-auto flex-1 text-sm" id="stylePick" bind:value={style}>
|
||||
{#each race.RUNNING_STYLES as [name, style] (style)}
|
||||
<option value={style}>{name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{/if}
|
||||
{#if pick?.dist}
|
||||
<label class="my-auto hidden flex-1 pr-2 text-right text-sm md:inline" for="distPick">{raceLenType}</label>
|
||||
<select class="my-auto flex-1 text-sm" id="distPick" bind:value={distanceApt}>
|
||||
{#each race.APTITUDE_LEVELS as l (l)}
|
||||
<option value={l}>{race.AptitudeLevel[l]}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{/if}
|
||||
{#if pick?.surf}
|
||||
<label class="my-auto hidden flex-1 pr-2 text-right text-sm md:inline" for="surfPick">{raceLenType}</label>
|
||||
<select class="my-auto flex-1 text-sm" id="surfPick" bind:value={surfaceApt}>
|
||||
{#each race.APTITUDE_LEVELS as l (l)}
|
||||
<option value={l}>{l}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
<Article>
|
||||
{#snippet head()}
|
||||
<Sec h={1} id="top" class="text-center">Race Mechanics Charts</Sec>
|
||||
<p>
|
||||
This article is an interactive version of <a
|
||||
href="https://docs.google.com/document/d/15VzW9W2tXBBTibBRbZ8IVpW6HaMX8H0RP03kq6Az7Xg/edit"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">KuromiAK's race mechanics documentation</a
|
||||
>
|
||||
to give a better sense of how mechanics scale with stats and aptitudes.
|
||||
</p>
|
||||
<p>
|
||||
Mechanics are organized in this article according to the stats that determine them. Those that vary with multiple stats,
|
||||
e.g. spurt speed, appear in all relevant sections with differing horizontal axes. Mechanics that vary with race distance,
|
||||
distance aptitudes, and surface aptitudes have controls to change each by their charts.
|
||||
</p>
|
||||
<p>
|
||||
Only numeric mechanics that vary with stats and aptitudes are shown. Descriptions of concepts and conditions are left to the
|
||||
original source.
|
||||
</p>
|
||||
<p>
|
||||
Given the recent discovery that rushed chance uses integer RNG, I am assuming that all wit check mechanics use integer RNG
|
||||
until shown otherwise.
|
||||
</p>
|
||||
{/snippet}
|
||||
|
||||
<Sec h={2} id="speed">Speed</Sec>
|
||||
|
||||
<Sec h={3} id="spurt-speed">Spurt Speed</Sec>
|
||||
<p>Target speed during the Uma's last spurt. See also <a href="#spurt-speed-guts">the effect of Guts</a>.</p>
|
||||
{@render statChart(race.Stat.Speed, spurtSpeed, 'Spurt Speed (m/s)', [20, 26], { len: true, style: true, dist: true })}
|
||||
|
||||
<Sec h={3} id="late-race-speed">Late Race Speed</Sec>
|
||||
<p>Base target speed during late race when the Uma is not spurting (but not dead yet).</p>
|
||||
{@render statChart(race.Stat.Speed, nonSpurtSpeed, 'Base Target Speed (m/s)', [18, 23], { len: true, style: true, dist: true })}
|
||||
|
||||
<Sec h={2} id="stamina">Stamina</Sec>
|
||||
|
||||
<Sec h={3} id="hp">HP</Sec>
|
||||
<p>Max HP, i.e. starting HP.</p>
|
||||
{@render statChart(race.Stat.Stamina, hp, 'HP', [1000, 5000])}
|
||||
|
||||
<Sec h={2} id="power">Power</Sec>
|
||||
|
||||
<Sec h={3} id="move-lane-mod">Move Lane Modifier</Sec>
|
||||
<p>
|
||||
Target speed bonus when changing lanes while under the effect of a lane change speed skill. See <a
|
||||
href={resolve('/doc/frbm#lane-combo')}>lane combo</a
|
||||
>.
|
||||
</p>
|
||||
{@render statChart(race.Stat.Power, moveLane, 'Target Speed Modifier (m/s)', [0.2, 0.5])}
|
||||
|
||||
<Sec h={3} id="uphill">Uphill Target Speed Loss</Sec>
|
||||
<p>Target speed modifier while running uphill.</p>
|
||||
{@render statChart(race.Stat.Power, uphill, 'Target Speed Modifier (m/s)', [-2, 0])}
|
||||
|
||||
<Sec h={3} id="acceleration">Acceleration</Sec>
|
||||
<p>Acceleration.</p>
|
||||
{@render statChart(race.Stat.Power, accel, 'Acceleration (m/s²)', [0.1, 0.5])}
|
||||
<div class="mb-4 h-60 w-full md:h-96">
|
||||
<StatChart
|
||||
class="mx-auto mb-12 h-full w-full max-w-3xl md:mb-10"
|
||||
stat={race.Stat.Power}
|
||||
y={accel}
|
||||
yLabel="Acceleration (m/s²)"
|
||||
range={[0.1, 0.5]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Sec h={3} id="lane-change-target-speed">Lane Change Target Speed</Sec>
|
||||
<p>Horizontal (rather than forward) target speed of changing lanes.</p>
|
||||
<div class="mb-4 h-60 w-full md:h-96">
|
||||
<StatChart
|
||||
class="mx-auto mb-12 h-full w-full max-w-3xl md:mb-10"
|
||||
stat={race.Stat.Power}
|
||||
y={laneChange}
|
||||
yLabel="Lane Change Target Speed (CW/s)"
|
||||
range={[0.01, 0.03]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Sec h={2} id="guts">Guts</Sec>
|
||||
|
||||
<Sec h={3} id="spurt-speed-guts">Spurt Speed</Sec>
|
||||
<p>Target speed during the Uma's last spurt. See also <a href="#spurt-speed">the effect of Speed</a>.</p>
|
||||
{@render statChart(race.Stat.Guts, gutsSpurt, 'Spurt Speed (m/s)', [20, 26], { len: true, style: true, dist: true })}
|
||||
|
||||
<Sec h={3} id="min-speed">Minimum Speed</Sec>
|
||||
<p>Forced minimum speed, as well as target speed when out of HP.</p>
|
||||
{@render statChart(race.Stat.Guts, minSpeed, 'Target Speed (m/s)', [15.8, 18.4], { len: true })}
|
||||
|
||||
<Sec h={3} id="hp-rate-mod">Late Race HP Consumption Rate Modifier</Sec>
|
||||
<p>Multiplier on HP consumption rate during late race and last spurt phase.</p>
|
||||
{@render statChart(race.Stat.Guts, gutsHPRate, 'HP Consumption Multiplier', [1.2, 1.6])}
|
||||
|
||||
<Sec h={3} id="spot-struggle">Spot Struggle</Sec>
|
||||
<p>Speed boost and duration of spot struggle.</p>
|
||||
<div class="mb-4 grid h-96 w-full md:grid-cols-2">
|
||||
<StatChart stat={race.Stat.Guts} y={ssBoost} yLabel="Speed Bonus (m/s)" range={[0, 0.3]} />
|
||||
<StatChart stat={race.Stat.Guts} y={ssDur} yLabel="Duration (s)" range={[0, 12]} />
|
||||
</div>
|
||||
|
||||
<Sec h={3} id="dueling">Dueling</Sec>
|
||||
<p>Speed boost and acceleration boost of dueling.</p>
|
||||
<div class="mb-4 grid h-96 w-full md:grid-cols-2">
|
||||
<StatChart stat={race.Stat.Guts} y={duelSpeed} yLabel="Speed Bonus (m/s)" range={[0, 0.65]} />
|
||||
<StatChart stat={race.Stat.Guts} y={duelAccel} yLabel="Acceleration Bonus (m/s²)" range={[0, 0.14]} />
|
||||
</div>
|
||||
|
||||
<Sec h={2} id="wit">Wit</Sec>
|
||||
|
||||
<Sec h={3} id="section-speed">Section Speed</Sec>
|
||||
<p>Random variance in target speed per race section.</p>
|
||||
<div class="mb-24 h-60 w-full md:mb-20 md:h-96">
|
||||
<StatChart
|
||||
class="mx-auto mb-12 h-full w-full max-w-3xl md:mb-10"
|
||||
stat={race.Stat.Wit}
|
||||
yArea={secSpeed}
|
||||
yLabel="Section Speed (m/s)"
|
||||
range={[17.5, 22.5]}
|
||||
plotOptions={{ color: { legend: true } }}
|
||||
/>
|
||||
<div class="mx-auto flex w-full md:max-w-2xl">
|
||||
<label class="my-auto hidden flex-1 pr-2 text-right text-sm md:inline" for="secSpeedRaceLen">Race Length</label>
|
||||
<input
|
||||
class="my-auto max-w-40 flex-1 md:flex-2"
|
||||
type="range"
|
||||
id="secSpeedRaceLen"
|
||||
min="1000"
|
||||
max="3600"
|
||||
step="100"
|
||||
bind:value={raceLen}
|
||||
/>
|
||||
<span class="my-auto flex-1 pl-2 text-sm">{raceLen}m</span>
|
||||
<label class="my-auto hidden flex-1 pr-2 text-right text-sm md:inline" for="secSpeedStyle">Style</label>
|
||||
<select class="my-auto flex-1 text-sm" id="secSpeedStyle" bind:value={style}>
|
||||
{#each race.RUNNING_STYLES as [name, style] (style)}
|
||||
<option value={style}>{name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Sec h={3} id="downhill">Downhill Accel Mode Chance</Sec>
|
||||
<p>Chance each second to enter downhill accel mode when eligible.</p>
|
||||
{@render statChart(race.Stat.Wit, downhill, 'Entry Chance (% each second)', [0, 60])}
|
||||
|
||||
<Sec h={3} id="spurt-accept">Reduced Spurt Accept Chance</Sec>
|
||||
<p>Chance to accept each checked spurt delay and speed when not full spurting.</p>
|
||||
{@render statChart(race.Stat.Wit, reducedSpurt, 'Accept Chance (% each candidate)', [0, 100])}
|
||||
|
||||
<Sec h={3} id="skill">Skill Activation Chance</Sec>
|
||||
<p>
|
||||
Chance that each skill that has a wit check will be eligible to activate. This uses base wit, so style aptitude and passive
|
||||
wit skills do not affect the chance, although mood does.
|
||||
</p>
|
||||
<p>TODO(zeph): Why is the 3 of 4 series getting values over 100%? Is it not just binom(3, 4, p) + binom(4, 4, p)?</p>
|
||||
{@render statChart(race.Stat.Wit, skillChance, 'Skill Activation Chance (%)', [0, 100])}
|
||||
|
||||
<Sec h={3} id="poskeep">Position Keep Mode Chance</Sec>
|
||||
<p>
|
||||
Chance for front runners to enter speed-up or overtake mode, or for non-front runners to enter pace-up mode (but no effect on
|
||||
pace-down mode), each 2 seconds when eligible.
|
||||
</p>
|
||||
{@render statChart(race.Stat.Wit, poskeep, 'Mode Entry Chance (%)', [0, 50])}
|
||||
|
||||
<Sec h={3} id="rushed">Rushed Chance</Sec>
|
||||
<p>Chance for runners to become rushed at some point during the race.</p>
|
||||
{@render statChart(race.Stat.Wit, rushed, 'Rushed Chance (%)', [0, 30])}
|
||||
</Article>
|
||||
Reference in New Issue
Block a user