A rally stage can fit inside a URL: the seed, distance, terrain, and season are enough to describe a run that someone else can open.
That is the core of Dirtbound — The Rally Field Guide, an independent companion site I built for the DirtBound browser rally game. You can configure a stage, launch it in the original game, and copy a link to share the configuration.
DirtBound is made by Luke Baffait and Victor Boutros. My project is the guide and its interactive tools. The driving happens in their original game at dirtbound.fr.
I wanted the site to give players something useful to do alongside reading: set up a run, check the default controls, and keep track of a few beginner exercises.
The stage planner is the main interaction. It offers distances from 1 km through 10 km, plus Endless, three terrain options, four seasons, and a stage seed. Once those choices are made, the site constructs a link such as:
https://dirtbound.fr/play.html?rally=471231&length=5000&landscape=high&season=winter&lang=en This selects seed 471231, a 5 km distance, high mountains, and winter.
I checked the original game's public menu and startup code to understand the parameters. One detail matters: Endless requires omitting length. The planner represents that choice internally as 0, then leaves it out of the URL. The game's default terrain and season can also be omitted.
Here is the URL construction function from the project:
export function stageURL(value) { const stage = normalizeStage(value); const url = new URL('https://dirtbound.fr/play.html'); url.searchParams.set('rally', String(stage.seed)); if (stage.length) { url.searchParams.set('length', String(stage.length)); } if (stage.landscape !== 'varied') { url.searchParams.set('landscape', stage.landscape); } if (stage.season !== 'summer') { url.searchParams.set('season', stage.season); } url.searchParams.set('lang', 'en'); return url.href; } The normalizeStage helper validates inputs against the supported options and supplies defaults. This also handles saved settings that are invalid or no longer usable. The form provides immediate validation, and URL generation applies its own checks.
Sharing needs no server-side record. The configuration travels in the link, so the recipient can open it directly in the game. That keeps this feature small enough to maintain as a static site.
The rest of the stack follows the same approach: a Node.js build script generates seven HTML pages, CSS handles the layout, and browser ES modules handle the interactions. The guide has no account system or backend database.
Stage preferences and the beginner practice checklist use localStorage. Storage access sits behind a small wrapper that catches read, parse, and write failures. If saving is unavailable, the planner still constructs links and explains that preferences could not be saved.
Copying a link has a similar fallback. When clipboard access fails, the page exposes a selected text field so the user can copy it manually. That failure should add one extra step rather than stop the task.
Launching the game required another browser-specific decision. The current launcher requests a separate game window, keeping the guide and its scroll position available. It can reuse the window while the browser allows access to its reference. If opening it is blocked, the guide displays an explanation and an ordinary new-tab link. The browser ultimately decides how to present that separate context.
The keyboard tester also needed a clear start and stop. It activates only after a click, recognizes the default driving keys through KeyboardEvent.code, and stops on Escape or when the window loses focus. It leaves Tab navigation available and stops when focus enters an editable field. This lets someone explore the controls while keeping the rest of the page usable.
For verification, the project includes checks for URL construction, omitted default parameters, invalid saved settings, local links, and game-window behavior. These check the companion site's behavior; they do not establish game performance or multiplayer reliability.
There is a maintenance tradeoff here: the planner depends on the original game's URL behavior. That contract could change. The site keeps links to the original game menu available, and future game updates may require changes to the planner.
You can try the stage planner here. Choose a configuration, copy its link, and open it in the game.
I'd appreciate feedback on that flow: is it clear what the planner controls, and what would make sharing a stage more useful?

