Dystrail Docs
Welcome to the Dystrail documentation hub. These pages collect specs, reference notes, and public-facing messaging for the game.
What you can do
- Understand the gameplay loop and controller flows via the Journey pages (System Overview, Daily Flow, Balance, and Data Packs).
- Grab ready-to-share messaging from the press kit and Discord post templates.
- Follow along with implementation details as the web and engine evolve.
- Remix the game by editing the bundled data packs (see Journey → Data Packs & Modding for JSON locations and knobs).
Quick links
- Play the game: /play
- Source code: github.com/VannaDii/Dystrail
Journey System Overview
Purpose: Explain the moving parts of a Dystrail run: how configs are layered, how determinism is enforced, and which data packs drive behavior.
Core architecture
- One JourneyController simulates every day from mile 0 to the boss gate (default route length
2100.0inboss::ROUTE_LEN_MILES). - Each day emits a
DayRecord(kind:Travel,Partial,NonTravel) with miles already partial-adjusted and tagged. - Game mode → policy family mapping:
- Classic family (GameMode::Classic) — baseline Oregon Trail pacing.
- Deep family (GameMode::Deep) — same pacing bands, higher variance/quirks.
- Strategy overlay: Balanced, Aggressive, Conservative, ResourceManager. The overlay only changes numbers; code paths stay uniform.
Determinism & RNG streams
- Seeds derive from HMAC-SHA256(master_seed, domain_tag) → 64-bit seeds (see
journey::RngBundle). - Independent streams:
rng_travel,rng_breakdown,rng_crossing,rng_encounter. - Atomic crossings: exactly one RNG draw per crossing event regardless of outcome branch, preventing seed drift.
- Replay guarantee: same seed + same data packs = identical simulation trace (used by tester suites).
Data packs and source of truth
All policy and tuning data live in dystrail-web/static/assets/data/ and are bundled into the shipped WASM build.
- Journey families:
journey/classic.json,journey/deep.json. - Strategy overlays:
journey/overlays/*.json(merged on top of family data per field). - Supporting packs:
boss.json(distance required, weights, rounds, min/max chance, biases)crossings.json,camp.json,exec_orders.json,endgame.jsonpacing.json,personas.json,result.json,store.json,vehicle.json,weather.json,game.json
Effective config = family ⊕ overlay. Examples (Classic + Balanced overlay):
- Victory miles: 2100 → 2400 (overlay).
- Partial ratio: 0.50 → 0.51.
- Travel base mpd: 14.6 → 15.6; clamp remains 10.0–22.0.
- Wear base/fatigue: 0.07 / 0.08 → 0.073 / 0.245.
- Breakdown base/beta: 0.01 / 0.10 → 0.015 / 0.185.
- Crossing probs: pass/detour/terminal 0.72 / 0.16 / 0.12 → 0.66 / 0.24 / 0.10; bribe/permit tuning updated accordingly.
Daily Flow & Mechanics
This page walks through the exact order of operations for a single day, with the key formulas and thresholds used in engine code.
Step-by-step day
- Player inputs: choose pace (
PaceId: Steady, Heated, Blitz) and diet (DietId). - Travel miles
- Start from
travel.mpd_base(family/overlay). - Multiply by pace factor (Classic: Steady 1.0, Heated 1.12, Blitz 1.3).
- Multiply by weather factor (Classic: Clear 1.0, Storm 0.82, HeatWave 0.78, ColdSnap 0.9, Smoke 0.86).
- Apply exec-order/weather/diet modifiers if configured.
- Clamp to
[mpd_min, mpd_max](Classic: 10.0–22.0).
- Start from
- Partial travel (detour/repair/etc.): miles ×
partial_ratio(Classic 0.50; Balanced overlay 0.51). - Wear accumulation
- Classic baseline:
wear = base (0.07) + fatigue_k (0.08) * fatigue(distance, comfort_miles=1250). - Balanced overlay: base 0.073, fatigue_k 0.245, comfort_miles 1750.
- Classic baseline:
- Breakdown roll
- Classic base:
p = breakdown.base (0.01) * pace_factor * weather_factor + beta (0.10) * wear_component. - Overlay tweaks: base 0.015, beta 0.185; pace multipliers Steady 0.94, Heated 1.08, Blitz 1.28; weather multipliers Clear 1.0, Storm 1.25, HeatWave 1.35, ColdSnap 1.1, Smoke 1.15.
- Targeted part weights (Classic): tire 44, battery 22, alternator 18, pump 16.
- Classic base:
- Crossing resolver (if scheduled for the day)
- Classic probs: pass 0.72, detour 0.16, terminal 0.12; detour costs 1–3 days.
- Bribe: pass bonus 0.2, terminal penalty 0.2, diminishing returns 0.4 (Balanced overlay adjusts to 0.192 / 0.045 / 0.38).
- Permit can disable terminal for eligible tags (default
["checkpoint"]).
- Encounter resolver
- Uses cooldowns and history windows (
constants.rs: cooldown 1 day, history window 10, repeat window 6, soft cap threshold 5).
- Uses cooldowns and history windows (
- Daily tick (supplies/sanity/HP)
- For each channel:
base × pace × diet × weather × exec(seeDailyTickConfig). - Rounded to integers and clamped to stat caps. Classic family uses zeros; overlays add decay (e.g., Balanced
health.decay = 0.12). - Rest requests can heal HP if
rest_healis set (overlays can enable).
- For each channel:
- Endgame/boss gates
- Endgame (Deep) from
endgame.json: starts at 1750 mi, guard at 1950 mi, health floors 45–50 HP, wear multipliers 0.6–0.7, stop caps (window 10, max 2 full stops), wear shave 0.7. - Boss gate: requires distance ≥
distance_required(defaults to 2100 mi). Boss chance is weighted by supplies, sanity, allies, pants penalty, and policy bias (BalancedBossBiasinboss.json: Classic bonus 0.30, Deep multiplier 1.1, Deep bonus 0.08). Outcomes: PassedCloture, SurvivedFlood, PantsEmergency, Exhausted.
- Endgame (Deep) from
Day record semantics
Travel: full mileage credit.Partial: mileage multiplied bypartial_ratio(detours, repairs, shared travel).NonTravel: camps, blockers, endgame/boss-only days.- Travel ratio uses
(Travel + Partial) / total_daysand is checked against guards.
Balance Targets & Acceptance Guards
These are the numeric rails that keep runs inside the intended “Oregon Trail” feel and the gates the tester enforces.
Global pacing targets
- Route length target: ~2,000–2,400 mi (boss gate at
distance_required, default 2,100; overlays may raise victory miles). - Day window: 84–180 days.
- Mean miles per day: 10–20.
- Travel ratio: ≥ 0.90 (guards block configs that drop below this).
Classic family expectations
- Boss reach: 30–50% of runs.
- Boss win: ~20–35%.
- Survival (any non-early wipe ending): 60–80%.
- Failure mix: no single family (vehicle, sanity, exposure, crossings) > 50% of failures over large samples.
- Aggressive: lower survival and more terminal crossings than Balanced.
- Conservative / ResourceManager: higher survival and boss reach but boss win should stay ≲ 40%.
Deep family expectations
- Same distance/duration/mpd bands as Classic.
- Higher per-run variance is allowed; means must still satisfy the Classic bands.
- Crossings/failures may be a bit harsher or stranger, but not arcade-short or ultra-long.
Guard rails (from journey config)
guards.min_travel_ratio: Classic 0.90; overlays may adjust.guards.target_distance: Classic 2000.0; overlay can raise (e.g., 2400.0).guards.target_days_min/max: 84 / 180.- Tester sweeps (
dystrail-tester) validate these across many seeds; failing guards fails CI.
Crossing & breakdown reference numbers
- Classic crossings: pass 0.72, detour 0.16, terminal 0.12; detour days 1–3.
- Bribe: pass bonus 0.2, terminal penalty 0.2, diminishing returns 0.4 (Balanced overlay tweaks).
- Breakdown (Classic base):
p = base 0.01 * pace_factor * weather_factor + beta 0.10 * wear_component. - Overlay example (Balanced): base 0.015, beta 0.185; pace multipliers 0.94 / 1.08 / 1.28; weather multipliers 1.0 / 1.25 / 1.35 / 1.1 / 1.15.
Data Packs & Modding Guide
Everything that shapes a run lives in JSON under dystrail-web/static/assets/data/ and ships with the WASM build. Editing these files lets you reskin or satirize the game without touching Rust.
Where things live
journey/classic.json,journey/deep.json: base family configs (mpd ranges, partial ratio, wear/breakdown, crossings, guards).journey/overlays/*.json: strategy overlays that override family fields (Balanced, Aggressive, Conservative, ResourceManager).boss.json: distance gate, round count, stat weights, min/max chance, balanced bias (Classic bonus, Deep multiplier/bonus).crossings.json,camp.json,exec_orders.json,endgame.json: crossings odds/detours, camp actions, executive orders, endgame behavior.pacing.json,weather.json,vehicle.json: pace multipliers, weather impacts, vehicle wear/parts weights.personas.json,store.json,result.json,game.json: flavor, pricing, outcomes, and high-level game toggles.
How to make a new variant
- Copy and edit JSON in
static/assets/data/. Change numbers, names, or odds to your liking. - Run tests:
just lintorcargo test --workspace --all-features --lockedto ensure acceptance guards still pass. - Build the web client:
just build-release(or let the CIbuildjob run). The JSON is bundled intodystrail-web/dist. - Ship it: host
dist(GitHub Pages via included workflows) and share the Play link/download.
Knobs to twist for satire
- Crossings: raise
terminal, shrinkpass, or makedetour_days.maxhuge; tweak bribe bonuses/penalties. - Travel feel: drop
mpd_base, shrinkmpd_min, or slashpartial_ratioto 0.2 for a slog; invert for speed-runs. - Breakdowns: spike
breakdown.baseandbeta, or overweight a single part inpart_weights. - Endgame: set
wear_multiplierto 0 to make finale trivial, or raisehealth_floorto punish. - Boss: rename outcomes in
boss.json, pushdistance_requiredup/down, or skew stat weights to reward pants hoarding. - Economy/Flavor: rewrite
store.jsonprices,camp.jsonactions,personas.jsonlines,weather.jsonnames to match your satire.
Notes
- Keep
guardsreasonable if you want CI to stay green; otherwise relax them knowing tests may fail. - Determinism is preserved as long as you keep the same RNG structure—changing JSON won’t break replays for a given seed, it only changes the outcomes via new numbers.