7 Commits
Author SHA1 Message Date
zephyr 56bd5b460d zenno/doc/race: better section speed charts
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
Fixes #49.
2026-11-05 14:45:59 -05:00
zephyr ddaf100069 meta: update go version in ci container
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline was successful
2026-11-03 09:09:36 -05:00
zephyr f013aa4765 umadump/ism: remove old field 2026-11-03 09:09:36 -05:00
zephyr 2f676f0a62 umadump/ism: resparking info is populated now 2026-11-03 09:09:36 -05:00
zephyr 04cb6388c3 zenno/race/skillzone: indicate inherits
ci/woodpecker/push/horsebot Pipeline failed
ci/woodpecker/push/zenno Pipeline was successful
2026-10-31 21:12:17 -04:00
zephyr de2b871862 zenno: link up race/skillzone
ci/woodpecker/push/horsebot Pipeline failed
ci/woodpecker/push/zenno Pipeline was successful
2026-10-31 13:46:50 -04:00
zephyr c95bffbcb8 zenno/doc/race: disclaimer and bug reporting info 2026-10-31 13:45:13 -04:00
5 changed files with 72 additions and 59 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ when:
steps: steps:
- name: test - name: test
image: golang:1.26-alpine image: golang:1.27-alpine
commands: commands:
- go vet ./... - go vet ./...
- go test ./... - go test ./...
+1 -2
View File
@@ -323,7 +323,7 @@ type End struct {
StoryEventMissions []umadump.Never `json:"story_event_mission_list"` StoryEventMissions []umadump.Never `json:"story_event_mission_list"`
StoryEventCharaBonuses []umadump.Never `json:"story_event_chara_bonus_list"` StoryEventCharaBonuses []umadump.Never `json:"story_event_chara_bonus_list"`
StartDressInfo []umadump.Never `json:"start_dress_info"` StartDressInfo []umadump.Never `json:"start_dress_info"`
ResumeFactorSelect *umadump.Never `json:"resume_factor_select"` ResumeFactorSelect any `json:"resume_factor_select"` // don't care yet
RaceRandomPrograms []umadump.Never `json:"race_random_program_array"` RaceRandomPrograms []umadump.Never `json:"race_random_program_array"`
RaceRewardLimitMore []umadump.Never `json:"race_reward_limit_more_list"` RaceRewardLimitMore []umadump.Never `json:"race_reward_limit_more_list"`
SkillFilterSettings []umadump.Never `json:"skill_filter_setting_array"` SkillFilterSettings []umadump.Never `json:"skill_filter_setting_array"`
@@ -344,6 +344,5 @@ type WorkIdleSingleMode struct {
ProgressInfo ProgressInfo `json:"progress_info"` ProgressInfo ProgressInfo `json:"progress_info"`
ProgressLog IdleSingleModeProgressLog `json:"progress_log_info"` ProgressLog IdleSingleModeProgressLog `json:"progress_log_info"`
End End `json:"end_info"` End End `json:"end_info"`
OldChara *SingleModeChara `json:"chara_info"`
Extra map[string]any `json:"extra_data"` Extra map[string]any `json:"extra_data"`
} }
+5
View File
@@ -54,6 +54,11 @@ export const PAGES = {
name: 'Stamina Calculator', name: 'Stamina Calculator',
description: 'Compute stamina requirements and recovery probability distributions.', description: 'Compute stamina requirements and recovery probability distributions.',
}, },
{
route: resolve('/race/skillzone'),
name: 'Skill Activation Zones',
description: 'View locations where skills can activate on a given race course.',
},
] satisfies Page[], ] satisfies Page[],
career: [ career: [
{ {
+57 -53
View File
@@ -7,10 +7,6 @@
import { resolve } from '$app/paths'; import { resolve } from '$app/paths';
import RaceLenSlider from '$lib/RaceLenSlider.svelte'; import RaceLenSlider from '$lib/RaceLenSlider.svelte';
function modemean(x: number, bonus: number, p: number): number {
return x * (1 - p) + x * bonus * p;
}
let style = $state(race.RunningStyle.FrontRunner); let style = $state(race.RunningStyle.FrontRunner);
let surfaceApt = $state(race.AptitudeLevel.A); let surfaceApt = $state(race.AptitudeLevel.A);
let distanceApt = $state(race.AptitudeLevel.A); let distanceApt = $state(race.AptitudeLevel.A);
@@ -84,39 +80,39 @@
]; ];
const duelSpeed: ComputedSeries[] = [{ label: 'Dueling Speed Boost', y: (x) => race.duelSpeedMod(x) }]; 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 duelAccel: ComputedSeries[] = [{ label: 'Dueling Acceleration Boost', y: (x) => race.duelAccelMod(x) }];
const secSpeed: Array<ComputedAreas | null> = $derived([ const secSpeedEarly: Array<ComputedAreas | null> = $derived([
{ {
label: 'Early Race', label: styleIsFront ? 'Front S' : 'Style S',
y1: (x) => race.sectionSpeed(raceLen, x * 1.1, style, race.Phase.EarlyRace)[0],
y2: (x) => race.sectionSpeed(raceLen, x * 1.1, style, race.Phase.EarlyRace)[1],
},
{
label: styleIsFront ? 'Front A' : 'Style A',
y1: (x) => race.sectionSpeed(raceLen, x, style, race.Phase.EarlyRace)[0], y1: (x) => race.sectionSpeed(raceLen, x, style, race.Phase.EarlyRace)[0],
y2: (x) => race.sectionSpeed(raceLen, x, style, race.Phase.EarlyRace)[1], y2: (x) => race.sectionSpeed(raceLen, x, style, race.Phase.EarlyRace)[1],
}, },
{ {
label: 'Mid Race', label: styleIsFront ? 'Front A Speed-Up Mode' : 'Style A Pace-Up Mode',
y1: (x) => race.sectionSpeed(raceLen, x, style, race.Phase.EarlyRace)[0] * 1.04,
y2: (x) => race.sectionSpeed(raceLen, x, style, race.Phase.EarlyRace)[1] * 1.04,
},
]);
const secSpeedMid: Array<ComputedAreas | null> = $derived([
{
label: styleIsFront ? 'Front S' : 'Style S',
y1: (x) => race.sectionSpeed(raceLen, x * 1.1, style, race.Phase.MidRace)[0],
y2: (x) => race.sectionSpeed(raceLen, x * 1.1, style, race.Phase.MidRace)[1],
},
{
label: styleIsFront ? 'Front A' : 'Style A',
y1: (x) => race.sectionSpeed(raceLen, x, style, race.Phase.MidRace)[0], y1: (x) => race.sectionSpeed(raceLen, x, style, race.Phase.MidRace)[0],
y2: (x) => race.sectionSpeed(raceLen, x, style, race.Phase.MidRace)[1], y2: (x) => race.sectionSpeed(raceLen, x, style, race.Phase.MidRace)[1],
}, },
styleIsFront {
? { label: styleIsFront ? 'Front A Speed-Up Mode' : 'Style A Pace-Up Mode',
label: 'Early Race + Mean Speed-Up Mode', y1: (x) => race.sectionSpeed(raceLen, x, style, race.Phase.MidRace)[0] * 1.04,
y1: (x) => modemean(race.sectionSpeed(raceLen, x, style, race.Phase.EarlyRace)[0], 1.04, race.frontModeEnterChance(x)), y2: (x) => race.sectionSpeed(raceLen, x, style, race.Phase.MidRace)[1] * 1.04,
y2: (x) => modemean(race.sectionSpeed(raceLen, 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, style, race.Phase.EarlyRace)[0], 1.04, race.paceUpEnterChance(x)),
y2: (x) => modemean(race.sectionSpeed(raceLen, 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, style, race.Phase.MidRace)[0], 1.04, race.frontModeEnterChance(x)),
y2: (x) => modemean(race.sectionSpeed(raceLen, 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, style, race.Phase.MidRace)[0], 1.04, race.paceUpEnterChance(x)),
y2: (x) => modemean(race.sectionSpeed(raceLen, x, style, race.Phase.MidRace)[1], 1.04, race.paceUpEnterChance(x)),
},
]); ]);
const downhill: ComputedSeries[] = [ const downhill: ComputedSeries[] = [
{ label: 'Style S', y: (x) => race.downhillAccelEnterChance(x * 1.1) * 100 }, { label: 'Style S', y: (x) => race.downhillAccelEnterChance(x * 1.1) * 100 },
@@ -396,34 +392,42 @@
>Section Speed</Sec >Section Speed</Sec
> >
<p>Random variance in target speed per race section.</p> <p>Random variance in target speed per race section.</p>
<div class="mb-24 h-60 w-full md:mb-20 md:h-96"> <div class="mb-10 grid h-96 w-full md:grid-cols-2">
<StatChart <StatChart
class="mx-auto mb-12 h-full w-full max-w-3xl md:mb-10"
stat={race.Stat.Wit} stat={race.Stat.Wit}
yArea={secSpeed} yArea={secSpeedEarly}
yLabel="Section Speed (m/s)" yLabel="Early Race Section Speed (m/s)"
range={[17, 23]} range={[17, 23.5]}
plotOptions={{ color: { legend: true } }} plotOptions={{ color: { legend: true } }}
class="h-96 max-h-96"
/> />
<div class="mx-auto flex w-full md:max-w-2xl"> <StatChart
<label class="my-auto hidden flex-1 pr-2 text-right text-sm md:inline" for="secSpeedRaceLen">Race Length</label> stat={race.Stat.Wit}
<input yArea={secSpeedMid}
class="my-auto max-w-40 flex-1 md:flex-2" yLabel="Mid Race Section Speed (m/s)"
type="range" range={[17, 23.5]}
id="secSpeedRaceLen" plotOptions={{ color: { legend: true } }}
min="1000" class="h-96 max-h-96"
max="3600" />
step="100" </div>
bind:value={raceLen} <div class="mx-auto mb-4 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>
<span class="my-auto flex-1 pl-2 text-sm">{raceLen}m</span> <input
<label class="my-auto hidden flex-1 pr-2 text-right text-sm md:inline" for="secSpeedStyle">Style</label> class="my-auto max-w-40 flex-1 md:flex-2"
<select class="my-auto flex-1 text-sm" id="secSpeedStyle" bind:value={style}> type="range"
{#each race.RUNNING_STYLES as [name, style] (style)} id="secSpeedRaceLen"
<option value={style}>{name}</option> min="1000"
{/each} max="3600"
</select> step="100"
</div> 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 <Sec
+8 -3
View File
@@ -67,7 +67,8 @@
return { id: c.race_course_id, name: racecourse.referName(c) }; return { id: c.race_course_id, name: racecourse.referName(c) };
} }
function skillKey(sk: skill.Skill) { function skillKey(sk: skill.Skill) {
return { id: sk.skill_id, name: sk.name }; const name = sk.unique_owner != null && sk.rarity === 1 ? `${sk.name} (Inherited)` : sk.name;
return { id: sk.skill_id, name };
} }
let skillToAdd: skill.Skill | undefined = $state(); let skillToAdd: skill.Skill | undefined = $state();
@@ -126,7 +127,7 @@
{#snippet option(sk)} {#snippet option(sk)}
<span> <span>
<SkillIcon icon_id={sk.icon_id} size={16} /> <SkillIcon icon_id={sk.icon_id} size={16} />
{sk.name} {skillKey(sk).name}
</span> </span>
{/snippet} {/snippet}
</Pick> </Pick>
@@ -173,7 +174,7 @@
{#snippet option(sk)} {#snippet option(sk)}
<span> <span>
<SkillIcon icon_id={sk.icon_id} size={16} /> <SkillIcon icon_id={sk.icon_id} size={16} />
{sk.name} {skillKey(sk).name}
</span> </span>
{/snippet} {/snippet}
</Pick> </Pick>
@@ -237,6 +238,10 @@
from the gate. In practice, ignoring the start dash this way will tend to overestimate the starting position of the condition from the gate. In practice, ignoring the start dash this way will tend to overestimate the starting position of the condition
especially for backline running styles, and it may underestimate it for runaways with full gate accels. especially for backline running styles, and it may underestimate it for runaways with full gate accels.
</p> </p>
<p>
Condition zones were implemented by hand, and some combinations may have been missed. If you suspect a bug, please
@zephyrtronium on the GameTora, Hakuraku, or uma.moe Discord servers.
</p>
</div> </div>
{#snippet pending()} {#snippet pending()}