zenno: update stat charts for new limits

This commit is contained in:
2026-07-01 22:12:10 -04:00
parent 9ce665865e
commit d62e729052
6 changed files with 38 additions and 36 deletions
+9 -11
View File
@@ -55,39 +55,37 @@
let width = $state(0); let width = $state(0);
let height = $state(0); let height = $state(0);
let expand = $state(false); const xLabel = $derived(`Raw ${Stat[stat]}`);
const xLabel = $derived(Stat[stat]);
const xLines = $derived([xRule].flat(1)); const xLines = $derived([xRule].flat(1));
const xMin = $derived(xRange?.[0] ?? 200); const xMin = $derived(xRange?.[0] ?? 200);
const xMax = $derived.by(() => { const xMax = $derived.by(() => {
if (xRange?.[1] != null) { if (xRange?.[1] != null) {
return xRange[1]; return xRange[1];
} }
if (expand) { return 100 * Math.ceil(Math.max(2000, ...xLines) / 100);
return 2000;
}
return 100 * Math.ceil(Math.max(1200, ...xLines) / 100);
}); });
const xVal = $derived(d3.range(xMin, xMax + 1, 5)); // +1 because d3.range does not include the max const xVal = $derived(d3.range(xMin, xMax + 1, 5)); // +1 because d3.range does not include the max
const thrX = 1200; const thrX = 1200;
function adj(x: number): number {
return Math.min(x, 1200) + 0.5 * (Math.max(x, 1200) - 1200);
}
const series = $derived([y].flat(1).filter((s) => s != null)); const series = $derived([y].flat(1).filter((s) => s != null));
const areas = $derived([yArea].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(x), label })))); 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(x), y2: y2(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(() => { const yRange: [number, number] = $derived.by(() => {
if (range != null) { if (range != null) {
if (range.length === 2) { if (range.length === 2) {
return range; return range;
} }
return [range[0], expand ? range[2] : range[1]]; return [range[0], range[2]];
} }
const l = d3.min(vals, ({ y }) => y) ?? 0; const l = d3.min(vals, ({ y }) => y) ?? 0;
const r = d3.max(vals, ({ y }) => y) ?? 1; const r = d3.max(vals, ({ y }) => y) ?? 1;
return [l, r]; return [l, r];
}); });
const yLines = $derived(xLines.flatMap((x) => series.map(({ y, label }) => ({ y: y(x), label })))); const yLines = $derived(xLines.flatMap((x) => series.map(({ y, label }) => ({ y: y(adj(x), x), label }))));
const makeChart: Attachment = (el) => { const makeChart: Attachment = (el) => {
$effect(() => { $effect(() => {
+3 -3
View File
@@ -1,11 +1,11 @@
export interface ComputedSeries { export interface ComputedSeries {
y: (x: number) => number; y: (base: number, raw: number) => number;
label: string; label: string;
} }
export interface ComputedAreas { export interface ComputedAreas {
y1: (x: number) => number; y1: (base: number, raw: number) => number;
y2: (x: number) => number; y2: (base: number, raw: number) => number;
label: string; label: string;
} }
+2 -1
View File
@@ -519,7 +519,8 @@ export function speedGain(speedBonus: number, dur: number, accel: number | null,
* @returns Probability per candidate to accept * @returns Probability per candidate to accept
*/ */
export function reducedSpurtChance(witStat: number): number { export function reducedSpurtChance(witStat: number): number {
return 0.15 + 0.0005 * witStat; // 1900 wit style S exceeds 100% chance if we don't explicitly cap.
return Math.min(0.15 + 0.0005 * witStat, 1);
} }
/** /**
+5 -5
View File
@@ -414,8 +414,8 @@
never happen, its duration also scales with the guts stat. Unlike skills, its duration <i>does not</i> scale with race distance. never happen, its duration also scales with the guts stat. Unlike skills, its duration <i>does not</i> scale with race distance.
</p> </p>
<div class="mb-4 grid h-60 w-full grid-cols-2 md:h-96"> <div class="mb-4 grid h-60 w-full grid-cols-2 md:h-96">
<StatChart stat={Stat.Guts} y={ssBoostSeries} yLabel="Speed Bonus (m/s)" range={[0, 0.3]} /> <StatChart stat={Stat.Guts} y={ssBoostSeries} yLabel="Speed Bonus (m/s)" range={[0, 0.4]} />
<StatChart stat={Stat.Guts} y={ssDurSeries} yLabel="Duration (s)" range={[0, 12]} /> <StatChart stat={Stat.Guts} y={ssDurSeries} yLabel="Duration (s)" range={[0, 14]} />
</div> </div>
<p> <p>
Spot struggle also greatly increases HP consumption. For normal front runners, the rate is slightly less than Rushed. For Spot struggle also greatly increases HP consumption. For normal front runners, the rate is slightly less than Rushed. For
@@ -443,7 +443,7 @@
y={laneComboSeries} y={laneComboSeries}
yLabel="Speed Boost" yLabel="Speed Boost"
yRule={lcYRule} yRule={lcYRule}
range={[0.2, 0.5]} range={[0.2, 0.6]}
/> />
</div> </div>
<p> <p>
@@ -530,7 +530,7 @@
stat={Stat.Wit} stat={Stat.Wit}
y={downhillSeries} y={downhillSeries}
yLabel="Entry Chance (% each second)" yLabel="Entry Chance (% each second)"
range={[0, 60]} range={[0, 75]}
/> />
</div> </div>
<p> <p>
@@ -550,7 +550,7 @@
stat={Stat.Wit} stat={Stat.Wit}
yArea={secSpeedSeries} yArea={secSpeedSeries}
yLabel="Section Speed (m/s)" yLabel="Section Speed (m/s)"
range={[17.5, 22.5]} range={[17, 23]}
plotOptions={{ color: { legend: true } }} plotOptions={{ color: { legend: true } }}
/> />
<div class="mx-auto flex w-full md:max-w-2xl"> <div class="mx-auto flex w-full md:max-w-2xl">
+17 -14
View File
@@ -67,10 +67,13 @@
]); ]);
const laneChange: ComputedSeries[] = [{ label: 'Lane Change Target Speed', y: (x) => race.laneChangeSpeed(x) }]; const laneChange: ComputedSeries[] = [{ label: 'Lane Change Target Speed', y: (x) => race.laneChangeSpeed(x) }];
const gutsSpurt: Array<ComputedSeries | null> = $derived([ 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) }, { label: `Speed 600`, y: (x) => race.spurtSpeed(600, x, style, distanceApt, raceLen) },
{ label: `Speed 1200`, y: (x) => race.spurtSpeed(1200, x, style, distanceApt, raceLen) },
// NOTE(zeph): The label is 1600 despite the input being 1400
// for raw vs final difference.
{ label: `Speed 1600`, y: (x) => race.spurtSpeed(1400, x, style, distanceApt, raceLen) },
distanceApt !== race.AptitudeLevel.A distanceApt !== race.AptitudeLevel.A
? { label: `${raceLenType} A, Speed 1200`, y: (x) => race.spurtSpeed(1200, x, style, race.AptitudeLevel.A, raceLen) } ? { label: `${raceLenType} A, Speed 1600`, y: (x) => race.spurtSpeed(1400, x, style, race.AptitudeLevel.A, raceLen) }
: null, : null,
]); ]);
const minSpeed: ComputedSeries[] = $derived([{ label: 'Minimum Speed', y: (x) => race.minSpeed(raceLen, x) }]); const minSpeed: ComputedSeries[] = $derived([{ label: 'Minimum Speed', y: (x) => race.minSpeed(raceLen, x) }]);
@@ -241,7 +244,7 @@
<Sec h={3} id="spurt-speed">Spurt 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> <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 })} {@render statChart(race.Stat.Speed, spurtSpeed, 'Spurt Speed (m/s)', [20, 26.5], { len: true, style: true, dist: true })}
<Sec h={3} id="late-race-speed">Late Race Speed</Sec> <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> <p>Base target speed during late race when the Uma is not spurting (but not dead yet).</p>
@@ -261,7 +264,7 @@
href={resolve('/doc/frbm#lane-combo')}>lane combo</a href={resolve('/doc/frbm#lane-combo')}>lane combo</a
>. >.
</p> </p>
{@render statChart(race.Stat.Power, moveLane, 'Target Speed Modifier (m/s)', [0.2, 0.5])} {@render statChart(race.Stat.Power, moveLane, 'Target Speed Modifier (m/s)', [0.2, 0.6])}
<Sec h={3} id="uphill">Uphill Target Speed Loss</Sec> <Sec h={3} id="uphill">Uphill Target Speed Loss</Sec>
<p>Target speed modifier while running uphill.</p> <p>Target speed modifier while running uphill.</p>
@@ -269,7 +272,7 @@
<Sec h={3} id="acceleration">Acceleration</Sec> <Sec h={3} id="acceleration">Acceleration</Sec>
<p>Acceleration.</p> <p>Acceleration.</p>
{@render statChart(race.Stat.Power, accel, 'Acceleration (m/s²)', [0.1, 0.5], { style: true })} {@render statChart(race.Stat.Power, accel, 'Acceleration (m/s²)', [0.1, 0.55], { style: true })}
<Sec h={3} id="lane-change-target-speed">Lane Change Target Speed</Sec> <Sec h={3} id="lane-change-target-speed">Lane Change Target Speed</Sec>
<p>Horizontal (rather than forward) target speed of changing lanes.</p> <p>Horizontal (rather than forward) target speed of changing lanes.</p>
@@ -279,7 +282,7 @@
stat={race.Stat.Power} stat={race.Stat.Power}
y={laneChange} y={laneChange}
yLabel="Lane Change Target Speed (CW/s)" yLabel="Lane Change Target Speed (CW/s)"
range={[0.01, 0.03]} range={[0.01, 0.04]}
/> />
</div> </div>
@@ -287,11 +290,11 @@
<Sec h={3} id="spurt-speed-guts">Spurt Speed</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> <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 })} {@render statChart(race.Stat.Guts, gutsSpurt, 'Spurt Speed (m/s)', [20, 26.5], { len: true, style: true, dist: true })}
<Sec h={3} id="min-speed">Minimum Speed</Sec> <Sec h={3} id="min-speed">Minimum Speed</Sec>
<p>Forced minimum speed, as well as target speed when out of HP.</p> <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 })} {@render statChart(race.Stat.Guts, minSpeed, 'Target Speed (m/s)', [15.8, 18.5], { len: true })}
<Sec h={3} id="hp-rate-mod">Late Race HP Consumption Rate Modifier</Sec> <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> <p>Multiplier on HP consumption rate during late race and last spurt phase.</p>
@@ -300,15 +303,15 @@
<Sec h={3} id="spot-struggle">Spot Struggle</Sec> <Sec h={3} id="spot-struggle">Spot Struggle</Sec>
<p>Speed boost and duration of spot struggle.</p> <p>Speed boost and duration of spot struggle.</p>
<div class="mb-4 grid h-96 w-full md:grid-cols-2"> <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={ssBoost} yLabel="Speed Bonus (m/s)" range={[0, 0.4]} />
<StatChart stat={race.Stat.Guts} y={ssDur} yLabel="Duration (s)" range={[0, 12]} /> <StatChart stat={race.Stat.Guts} y={ssDur} yLabel="Duration (s)" range={[0, 14]} />
</div> </div>
<Sec h={3} id="dueling">Dueling</Sec> <Sec h={3} id="dueling">Dueling</Sec>
<p>Speed boost and acceleration boost of dueling.</p> <p>Speed boost and acceleration boost of dueling.</p>
<div class="mb-4 grid h-96 w-full md:grid-cols-2"> <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={duelSpeed} yLabel="Speed Bonus (m/s)" range={[0, 0.8]} />
<StatChart stat={race.Stat.Guts} y={duelAccel} yLabel="Acceleration Bonus (m/s²)" range={[0, 0.14]} /> <StatChart stat={race.Stat.Guts} y={duelAccel} yLabel="Acceleration Bonus (m/s²)" range={[0, 0.16]} />
</div> </div>
<Sec h={2} id="wit">Wit</Sec> <Sec h={2} id="wit">Wit</Sec>
@@ -321,7 +324,7 @@
stat={race.Stat.Wit} stat={race.Stat.Wit}
yArea={secSpeed} yArea={secSpeed}
yLabel="Section Speed (m/s)" yLabel="Section Speed (m/s)"
range={[17.5, 22.5]} range={[17, 23]}
plotOptions={{ color: { legend: true } }} plotOptions={{ color: { legend: true } }}
/> />
<div class="mx-auto flex w-full md:max-w-2xl"> <div class="mx-auto flex w-full md:max-w-2xl">
@@ -347,7 +350,7 @@
<Sec h={3} id="downhill">Downhill Accel Mode Chance</Sec> <Sec h={3} id="downhill">Downhill Accel Mode Chance</Sec>
<p>Chance each second to enter downhill accel mode when eligible.</p> <p>Chance each second to enter downhill accel mode when eligible.</p>
{@render statChart(race.Stat.Wit, downhill, 'Entry Chance (% each second)', [0, 60])} {@render statChart(race.Stat.Wit, downhill, 'Entry Chance (% each second)', [0, 75])}
<Sec h={3} id="spurt-accept">Reduced Spurt Accept Chance</Sec> <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> <p>Chance to accept each checked spurt delay and speed when not full spurting.</p>
+2 -2
View File
@@ -169,7 +169,7 @@
stat={Stat.Guts} stat={Stat.Guts}
y={ssY} y={ssY}
yLabel="Lengths Gained" yLabel="Lengths Gained"
range={[0, 1.5, 2.5]} range={[0, 2]}
xRule={gutsStat} xRule={gutsStat}
yRule={ssYRule} yRule={ssYRule}
/> />
@@ -178,7 +178,7 @@
stat={Stat.Power} stat={Stat.Power}
y={lcY} y={lcY}
yLabel="Lengths Gained" yLabel="Lengths Gained"
range={[0, 1.5, 2.5]} range={[0, 1.1]}
xRule={powerStat} xRule={powerStat}
yRule={lcYRule} yRule={lcYRule}
/> />