UmaSim is a planned web app for Umamusume: Pretty Derby tools, starting with a race simulator. The first goal is not to make a pretty shell around loose formulas, but to build a modular simulation core that can later support related tools like gacha simulation, guides, tier lists, race planning, build comparisons, and event calculators.
This project is fan-made and is not affiliated with Cygames or Umamusume: Pretty Derby.
UmaSim should eventually feel like a trainer workbench:
- Simulate a race with selected track, conditions, runners, stats, aptitudes, strategies, and skills.
- Explain why the result happened instead of only showing placements.
- Let users compare builds, race plans, and skill packages.
- Keep data and simulation logic reusable so future modules can share the same source of truth.
The first release should prioritize correctness, transparency, and iteration speed over visual spectacle.
See IMPLEMENTATION.md for the working build plan.
Requirements:
- Node.js 20.19 or newer
- npm, pnpm, or another Node package manager
Install dependencies and start the local app:
npm install
npm run devIf you use pnpm:
pnpm install
pnpm devRun checks:
npm test
npm run buildTo log every simulated race to the browser DevTools console while running the
development server, create a local .env.local file containing:
VITE_RACE_DEBUG=true
Restart npm run dev after changing the variable. Batch analysis logs the
setup, every full per-seed RaceResult (including timeline and skill debug
data), and the final aggregate; a single replay logs its setup and result.
Those entries are visible in DevTools and are also appended automatically to
race-logs/debug/race-debug.ndjson while Vite's development server is
running. The switch and local write endpoint are ignored in production builds.
Run the latest completed debug batch through the current local engine:
npm run replay:debugThe command reconstructs the exact logged setup, imported builds, base seed,
and run count, then prints a recorded-versus-current result table. Select a
specific one-based completed batch with npm run replay:debug -- --batch 4,
or a session with --session <id>. Add --write to save the full comparison
under race-logs/replays/ (ignored by Git).
Use controlled mutations to investigate the recorded field without editing the saved setup. Runner references can be an exact build name or runner ID:
# Skill ablation
npm run replay:debug -- --without-skill "Air Groove Build:gt-201601"
# Stat sweep
npm run replay:debug -- --set-stat "Taiki Shuttle Build2:wit:600"
# Stat/position baseline for one build
npm run replay:debug -- --clear-skills "Air Groove Build"
# Remove build and character differences to test engine variance and positioning
npm run replay:debug -- --equal-fieldEach replay includes a Wit/skill audit and average race position at 3, 10, 20, 40, and 60 seconds so a result can be traced to an early, middle, or late-race separation rather than only its final win rate.
For calibration work only, a replay can apply a JSON profile without changing normal app behavior:
npm run replay:debug -- --tuning .agents/traffic-half.profile.jsonProfiles may tune stat contribution, phase/style pacing, traffic penalties, and global skill speed/current-speed/acceleration effects. They are hypotheses, not game settings: keep each profile narrowly scoped, save it with the experiment, and compare it against a known baseline.
UmaSim currently builds as a static Vite app, so it can be hosted on Cloudflare Pages, Netlify, Vercel, GitHub Pages, or any static file host.
Recommended static-host settings:
Build command: npm run build
Output directory: dist
Node version: 20.19 or newer
Environment variables: none required
The app stores local Uma library data and latest race logs in the browser. Exported race logs can be placed in race-logs when you want a workspace copy.
The race simulator should answer questions like:
- How does this Uma perform on this course under these conditions?
- Which stat, aptitude, strategy, or skill change matters most?
- When do skills activate, and how do they alter speed, acceleration, stamina, recovery, positioning, or lane behavior?
- How stable is the result across many runs with randomness?
The first version can be deterministic or near-deterministic while the model is still being validated. Monte Carlo race batches can come after the single-race timeline is trustworthy.
The simulator needs several distinct data families. These should be stored separately from UI code and loaded through typed adapters.
Track/course data should include:
- Track id and display name.
- Surface: turf or dirt.
- Distance in meters.
- Distance category: sprint, mile, medium, long.
- Direction and layout details when available.
- Weather and ground condition modifiers.
- Course segments: straights, corners, final corner, final straight, uphill/downhill, slope grade if available.
- Phase boundaries: early, middle, late, last spurt.
- Lane/positioning metadata if we model blocking and passing.
Prior-art repos commonly treat course data as static JSON or database-backed records. This project should start with importable JSON fixtures, then move toward a database-backed catalog once import/update scripts exist.
Runner data should include:
- Character id and display name.
- Base stats for a trained runner: speed, stamina, power, guts, wit.
- Aptitudes/proficiencies:
- Surface: turf, dirt.
- Distance: sprint, mile, medium, long.
- Strategy: front runner, pace chaser, late surger, end closer.
- Mood/condition.
- Chosen strategy.
- Equipped skills.
- Optional metadata for future modules: rarity, costume/version, unique skill, growth rates, goals, support-card context.
For simulation, we should distinguish long-lived runner configuration from in-race runner state. A trained Uma is an input; an in-race runner has current speed, target speed, stamina budget, phase, lane, active effects, cooldowns, and race position.
Skill data should include:
- Skill id, localized names, rarity, group id, icon id, base cost.
- One or more activation alternatives.
- Conditions and preconditions.
- Duration.
- Effects, each with type, target, and modifier.
- Tags for filtering: surface, distance, strategy, phase/location, recovery, speed, acceleration, debuff, unique, inherited.
The model should allow skills to be interpreted by the simulator instead of hard-coded into UI components. Some skills may need custom effect handlers, but the default path should be data-driven.
The simulation engine should be isolated from the web UI. It should accept a complete race setup and return a timeline plus summary.
Core concepts:
- Race setup: course, conditions, runners, seed/randomness settings.
- Race phases: early, middle, late, last spurt.
- Runner stats: raw stats, mood-adjusted stats, track-adjusted stats.
- Aptitude modifiers: distance, surface, and strategy.
- Strategy coefficients by race phase.
- Base speed, target speed, minimum speed, acceleration, stamina consumption, and recovery.
- Skill system: activation checks, active effects, effect stacking, expiration.
- Positioning: strategy-based placement, passing, blocking, lane movement, and final placement.
- Result explanation: major speed changes, stamina exhaustion, skill activations, and decisive moments.
First engine output should be simple and inspectable:
type RaceResult = {
seed: string;
placements: Placement[];
runners: RunnerSummary[];
timeline: RaceTick[];
skillEvents: SkillEvent[];
warnings: SimulationWarning[];
};The repo should be organized around feature modules and shared domain packages:
src/
app/ # Routing, app shell, providers
features/
race-sim/ # Race simulator pages and controls
skills/ # Skill browser/search, later shared by race sim
guides/ # Future guide/tier list content
gacha/ # Future gacha simulator
domain/
race/ # Pure simulation engine
data/ # Shared data types, loaders, validation schemas
uma/ # Runner/character models
skills/ # Skill models and effect interpreter
data/
fixtures/ # Small checked-in seed data
generated/ # Generated data, if legally usable
scripts/
import-data/ # Data extraction/import/update tooling
validate-data/ # Schema and consistency checks
tests/
race/ # Engine tests and golden scenarios
The simulation core should be usable from tests, scripts, and the web app without depending on React or browser APIs.
Recommended starting point:
- TypeScript for shared types and simulation logic.
- Vite + React for a fast web UI.
- Zod or Valibot for runtime validation of imported data.
- Vitest for deterministic engine tests.
- Playwright later for end-to-end UI checks.
- JSON fixtures first; SQLite or a generated static database once the data surface grows.
This keeps the first implementation lightweight while leaving room for richer data tooling.
Useful references found during initial planning:
kira0x1/kira-uma: TypeScript/Vite app withsrc/dataJSON files for courses and skills, asrc/dblayer, and skill types shaped around alternatives, conditions, durations, effects, and metadata.AZLik/racesim: Small Python WIP that separatestrack.py,racer.py, andconst.py, with concepts for stats, mood, ground condition, aptitudes, strategy, race phases, acceleration, and phase coefficients.- Other public race-sim repos exist but appear smaller or less structured at first glance.
We should use these as architectural inspiration only. Avoid copying implementation details directly unless licensing and attribution are clear.
The app displays the newest bundled Global data snapshot in its header. This is the GameTora retrieval timestamp, not a verified Umamusume client build number: the current source data does not expose an authoritative Global game-version tag.
Each generated GameTora dataset records its server, retrieval time, source
manifest hash, and (for new imports) importer revision. A saved race also records
the engine ID and data snapshot used to create it. Race behavior is frozen under
src/domain/race/engines/vN/; src/config/raceEngine.ts selects the default
engine for new simulations. A future behavior change must start by
copying the previous engine into a new versioned folder rather than modifying an
existing engine, so historical replays remain reproducible. Git tags/releases
can map those engine IDs to published app builds, but they are not required for
the engine or data provenance itself.
The current default is v3. Browser testing can select a version with
?engine=v1; local replay/CLI runs can set VITE_RACE_ENGINE=v1. V2 applies the documented in-race post-1200 stat
conversion before mood for all five displayed build stats. V3 additionally isolates
the phase-aware target-speed, acceleration, Start Dash, and deceleration audit;
v1 and v2 remain frozen.
for baseline replays.
To refresh Global data, run npm run import:character-cards, npm run import:skills, and npm run import:tracks, then review the generated JSON diff
and run the validation suite. Re-importing is mechanically low risk because the
scripts write only generated catalog files, but the resulting changes can be
high impact: changed conditions/effects, new stat-cap rules, and mechanics
changes require engine and regression-test work before the simulator can claim
current behavior.
Before implementing high-confidence race results, we need to answer:
- What is the best legally safe source for game data?
- Which data can be checked into this repo, and which should be generated locally by users?
- How closely do we want to match the game formulas versus provide a transparent approximation?
- Which region/version is the first data target: global English, Japanese, or both?
- How do we handle localization for names, skill text, and conditions?
- How do we validate simulator output against known race behavior?
- Choose web stack and initialize the app.
- Add linting, formatting, and tests.
- Create core domain folders.
- Add a tiny hand-written fixture set: a few tracks, runners, and skills.
- Parse a race setup.
- Compute adjusted stats from mood, surface, distance, strategy, and track condition.
- Simulate one runner over a course timeline.
- Output speed, target speed, stamina, and phase changes per tick.
- Simulate multiple runners.
- Add strategy-based positioning.
- Produce placements and summary explanations.
- Add deterministic seeded randomness.
- Load skill data.
- Evaluate activation conditions.
- Apply timed effects.
- Show skill event logs in the UI.
- Add golden tests for known scenarios.
- Compare output against community formulas and observed race behavior.
- Add warnings when a setup depends on unimplemented mechanics.
- Build race setup UI.
- Add track, runner, and skill selectors.
- Render race timeline charts.
- Add result explanations and build comparison.
The long-term goal is not just a plausible race toy, but a simulator that approaches real in-game behavior closely enough to be trusted for build comparison and race planning. That means treating the race engine as its own product surface.
- Model race state more completely.
- Interpret imported skill data more faithfully.
- Validate behavior against known game outcomes and community research.
Expand the in-race model beyond simple speed, stamina, and order. The engine should explicitly track:
- lane and lateral movement state
- blocked states and crowding
- nearby runner relationships
- overtake events and short-term order history
- trigger anchors for corners, straights, and phase randoms
- start quality, temptation/composure, and other hidden race flags
- race metadata such as season, post number, popularity, and track-specific conditions when relevant
- per-run counters and skill activation history
Split the current simulator into clear subsystems so new mechanics have a stable home:
- positioning
- pathing and lane choice
- movement and stamina
- race event/state tracking
- skill evaluation and effect application
This refactor should happen before we add too many more special cases.
The imported global skill data already exposes a much richer condition language than the current engine supports. We should:
- inventory all condition tokens in the global data set
- classify each token as supported, metadata-only, requires new runtime state, or unknown
- move to a token resolver table with focused tests
- implement exact semantics for trigger-anchor conditions such as random phase/corner/straight activation windows
The current engine handles stat bonuses, speed, acceleration, and recovery. To approach game accuracy, it must also support the missing effect families that influence:
- navigation and lane handling
- debuffs and pressure/intimidation
- field-of-view or awareness style mechanics
- repeatable and cooldown-based activations
Unique skills need separate treatment from inherited versions. The simulator should correctly model:
- owner unique vs inherited unique behavior
- unique skill level scaling
- evolved/alternate unique variants when present in imported data
If a seed is provided, the sim should replay exactly. Random rolls should be structured and stable so engine changes do not silently alter behavior.
Accuracy needs measurement. We should build a validation suite with benchmark scenarios covering:
- standard pace/front/late/end builds
- representative track archetypes
- known unique-skill cases
- crowded-lane and blocked-runner scenarios
- skills with special trigger anchors
The output should let us compare activation timing, activation rate, finish ordering, stamina trends, and speed profiles.
The first serious accuracy pass should happen in this order:
- Define a richer
EngineState - Refactor the simulator into subsystems
- Add lane/pathing state
- Implement navigation-related condition tokens and effects
- Add repeatable trigger infrastructure
- Implement debuff/pressure effects
- Add unique skill level scaling
- Build validation fixtures and regression tests
Right now the simulator is strongest at straightforward speed, acceleration, recovery, and simple position/phase checks. It is still weak anywhere the real game depends on lane movement, navigation, hidden race flags, repeated activation windows, or unique-skill scaling.
The UI should feel like a dense but friendly strategy tool: fast selectors, readable stat tables, clear timelines, and direct comparison views. It should not start as a marketing page. The first screen should be the simulator or a compact dashboard leading directly into it.