From d62e7290527793910a1555ccd0887d5a6123e99a Mon Sep 17 00:00:00 2001
From: Branden J Brown
Date: Wed, 1 Jul 2026 22:12:10 -0400
Subject: [PATCH] zenno: update stat charts for new limits
---
zenno/src/lib/StatChart.svelte | 20 +++++++--------
zenno/src/lib/chart.ts | 6 ++---
zenno/src/lib/race.ts | 3 ++-
zenno/src/routes/doc/frbm/+page.svelte | 10 ++++----
zenno/src/routes/doc/race/+page.svelte | 31 +++++++++++++----------
zenno/src/routes/race/mspeed/+page.svelte | 4 +--
6 files changed, 38 insertions(+), 36 deletions(-)
diff --git a/zenno/src/lib/StatChart.svelte b/zenno/src/lib/StatChart.svelte
index 8415a90..af94804 100644
--- a/zenno/src/lib/StatChart.svelte
+++ b/zenno/src/lib/StatChart.svelte
@@ -55,39 +55,37 @@
let width = $state(0);
let height = $state(0);
- let expand = $state(false);
-
- const xLabel = $derived(Stat[stat]);
+ const xLabel = $derived(`Raw ${Stat[stat]}`);
const xLines = $derived([xRule].flat(1));
const xMin = $derived(xRange?.[0] ?? 200);
const xMax = $derived.by(() => {
if (xRange?.[1] != null) {
return xRange[1];
}
- if (expand) {
- return 2000;
- }
- return 100 * Math.ceil(Math.max(1200, ...xLines) / 100);
+ return 100 * Math.ceil(Math.max(2000, ...xLines) / 100);
});
const xVal = $derived(d3.range(xMin, xMax + 1, 5)); // +1 because d3.range does not include the max
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 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 areaVals = $derived(areas.flatMap(({ y1, y2, label }) => xVal.map((x) => ({ x, y1: y1(x), y2: y2(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(adj(x), x), y2: y2(adj(x), x), label }))));
const yRange: [number, number] = $derived.by(() => {
if (range != null) {
if (range.length === 2) {
return range;
}
- return [range[0], expand ? range[2] : range[1]];
+ return [range[0], range[2]];
}
const l = d3.min(vals, ({ y }) => y) ?? 0;
const r = d3.max(vals, ({ y }) => y) ?? 1;
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) => {
$effect(() => {
diff --git a/zenno/src/lib/chart.ts b/zenno/src/lib/chart.ts
index d95c04b..1c24387 100644
--- a/zenno/src/lib/chart.ts
+++ b/zenno/src/lib/chart.ts
@@ -1,11 +1,11 @@
export interface ComputedSeries {
- y: (x: number) => number;
+ y: (base: number, raw: number) => number;
label: string;
}
export interface ComputedAreas {
- y1: (x: number) => number;
- y2: (x: number) => number;
+ y1: (base: number, raw: number) => number;
+ y2: (base: number, raw: number) => number;
label: string;
}
diff --git a/zenno/src/lib/race.ts b/zenno/src/lib/race.ts
index 874e340..708d03d 100644
--- a/zenno/src/lib/race.ts
+++ b/zenno/src/lib/race.ts
@@ -519,7 +519,8 @@ export function speedGain(speedBonus: number, dur: number, accel: number | null,
* @returns Probability per candidate to accept
*/
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);
}
/**
diff --git a/zenno/src/routes/doc/frbm/+page.svelte b/zenno/src/routes/doc/frbm/+page.svelte
index 8a1e502..acc7c15 100644
--- a/zenno/src/routes/doc/frbm/+page.svelte
+++ b/zenno/src/routes/doc/frbm/+page.svelte
@@ -414,8 +414,8 @@
never happen, its duration also scales with the guts stat. Unlike skills, its duration does not scale with race distance.
-
-
+
+
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}
yLabel="Speed Boost"
yRule={lcYRule}
- range={[0.2, 0.5]}
+ range={[0.2, 0.6]}
/>
@@ -530,7 +530,7 @@
stat={Stat.Wit}
y={downhillSeries}
yLabel="Entry Chance (% each second)"
- range={[0, 60]}
+ range={[0, 75]}
/>
@@ -550,7 +550,7 @@
stat={Stat.Wit}
yArea={secSpeedSeries}
yLabel="Section Speed (m/s)"
- range={[17.5, 22.5]}
+ range={[17, 23]}
plotOptions={{ color: { legend: true } }}
/>
diff --git a/zenno/src/routes/doc/race/+page.svelte b/zenno/src/routes/doc/race/+page.svelte
index 253604a..603a7b5 100644
--- a/zenno/src/routes/doc/race/+page.svelte
+++ b/zenno/src/routes/doc/race/+page.svelte
@@ -67,10 +67,13 @@
]);
const laneChange: ComputedSeries[] = [{ label: 'Lane Change Target Speed', y: (x) => race.laneChangeSpeed(x) }];
const gutsSpurt: Array
= $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 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
- ? { 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,
]);
const minSpeed: ComputedSeries[] = $derived([{ label: 'Minimum Speed', y: (x) => race.minSpeed(raceLen, x) }]);
@@ -241,7 +244,7 @@
Spurt Speed
Target speed during the Uma's last spurt. See also the effect of Guts.
- {@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 })}
Late Race Speed
Base target speed during late race when the Uma is not spurting (but not dead yet).
@@ -261,7 +264,7 @@
href={resolve('/doc/frbm#lane-combo')}>lane combo.
- {@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])}
Uphill Target Speed Loss
Target speed modifier while running uphill.
@@ -269,7 +272,7 @@
Acceleration
Acceleration.
- {@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 })}
Lane Change Target Speed
Horizontal (rather than forward) target speed of changing lanes.
@@ -279,7 +282,7 @@
stat={race.Stat.Power}
y={laneChange}
yLabel="Lane Change Target Speed (CW/s)"
- range={[0.01, 0.03]}
+ range={[0.01, 0.04]}
/>
@@ -287,11 +290,11 @@
Spurt Speed
Target speed during the Uma's last spurt. See also the effect of Speed.
- {@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 })}
Minimum Speed
Forced minimum speed, as well as target speed when out of HP.
- {@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 })}
Late Race HP Consumption Rate Modifier
Multiplier on HP consumption rate during late race and last spurt phase.
@@ -300,15 +303,15 @@
Spot Struggle
Speed boost and duration of spot struggle.
-
-
+
+
Dueling
Speed boost and acceleration boost of dueling.
-
-
+
+
Wit
@@ -321,7 +324,7 @@
stat={race.Stat.Wit}
yArea={secSpeed}
yLabel="Section Speed (m/s)"
- range={[17.5, 22.5]}
+ range={[17, 23]}
plotOptions={{ color: { legend: true } }}
/>
@@ -347,7 +350,7 @@
Downhill Accel Mode Chance
Chance each second to enter downhill accel mode when eligible.
- {@render statChart(race.Stat.Wit, downhill, 'Entry Chance (% each second)', [0, 60])}
+ {@render statChart(race.Stat.Wit, downhill, 'Entry Chance (% each second)', [0, 75])}
Reduced Spurt Accept Chance
Chance to accept each checked spurt delay and speed when not full spurting.
diff --git a/zenno/src/routes/race/mspeed/+page.svelte b/zenno/src/routes/race/mspeed/+page.svelte
index d879f86..802c9a0 100644
--- a/zenno/src/routes/race/mspeed/+page.svelte
+++ b/zenno/src/routes/race/mspeed/+page.svelte
@@ -169,7 +169,7 @@
stat={Stat.Guts}
y={ssY}
yLabel="Lengths Gained"
- range={[0, 1.5, 2.5]}
+ range={[0, 2]}
xRule={gutsStat}
yRule={ssYRule}
/>
@@ -178,7 +178,7 @@
stat={Stat.Power}
y={lcY}
yLabel="Lengths Gained"
- range={[0, 1.5, 2.5]}
+ range={[0, 1.1]}
xRule={powerStat}
yRule={lcYRule}
/>