Concepts and terms

TrackGen builds many tracks (or gate sequences) at once, one per simulated environment, and then answers per-step questions about them inside your simulator. The whole library follows one data flow:

seeds [E]
  │
  ▼
generator ── a pipeline of stages: generate a loop, resample, relax, build the road
  │          band, check it (a spec, usually from a preset)
  ▼
result ───── Track or GateSequence: flat device arrays, valid[E], fail_mask[E]
  │
  ▼
course ───── components: checkpoints, progress, collision, localization, props, yours
  │
  ▼
your sim ─── binds its buffers, calls step() / reset(mask) / regenerate(mask=...)

The batch. Everything is batched over a fixed number E of envs. Each env gets its own seed, and its own epoch that advances when it needs a new track. The batch size, the device and the per-env capacity (N_max points per track, max_gates slots per gate sequence) are fixed when the generator is built, so every buffer is allocated once and keeps its address: a CUDA graph recorded over it stays valid.

The generator. A TrackGenerator or GateGenerator runs a pipeline of stages described by a spec. A preset builds the usual spec from a few keywords; each stage takes typed Params, and its dynamic fields hold one value per env, which you can change between generations. Stages pass data to each other through channels. One run of the pipeline over the batch is a generation; construction runs the first one.

The result. A generation writes a Track (a road: centerline, borders, frames) or a GateSequence (gate poses) as flat device arrays, [E * N_max] per point and [E] per env. Every env passes through the pipeline’s checks: valid says whether it passed, and the fail bits say which checks failed. An invalid env stays in the batch until you regenerate it.

The course. A course drives one generator and its runtime components for the whole batch: it binds your simulator’s buffers (agent poses), steps every component (progress events, collision contacts), respawns envs on their course, and regenerates selected envs. All of it runs on the device, so you can record a whole sim step into your own CUDA graph (a capture); with conditional nodes, a masked regeneration with nothing to do is skipped on the device.

The Quickstart shows each step in code.

Glossary

env

One slot of the batch, indexed e = 0 .. E - 1: one track or one gate sequence, with its own seed and epoch, and the course one agent of your simulator drives. Every per-env array has E entries.

E
num_envs

The batch size: the number of envs a generator builds in every generation (num_envs= in the presets and configs). It is fixed when the generator is built.

N_max

A Track’s per-env point capacity (n_max= in presets.track, N_max in TrackGenConfig; 384 by default): every per-point array has E * N_max rows, and env e uses the first count[e]. A track that needs more points is cut short and marked invalid (fail bit truncation): keep N_max >= max perimeter / spacing + 1.

spacing

The distance between consecutive points of a track’s centerline: every track is resampled to a constant spacing (by default 0.6 * half_width). With N_max, it bounds the longest track a batch can hold.

size

A per-env multiplier of a course’s extent in the plane (the generator’s dynamic size field, 1 by default, from 0.1 to 10): at size 2 a course is twice as large, with the same road width. Larger sizes need a larger N_max.

seed

A per-env int32 that, with the env’s epoch, determines its track or gate sequence. seeds=k gives env e the seed k + e; a PerEnvSeededRNG or an [E] int32 array sets them one by one.

epoch

A per-env counter (gen.seeds.epoch, 0 at construction). generate(advance=True) and regenerate(advance=True) add one to every env, or to the envs of a mask, so they get a new track from the same seed. For a given generator, the same (seed, epoch) always rebuilds the same output.

generation

One run of a generator’s pipeline over the whole batch: construction runs one, and so does every generate() / regenerate() call (a masked one changes only the masked envs). The result’s generation field holds the epoch each env was built at.

valid

valid[e] ([E] int32, on every result and on a course’s StepResult) is 1 when env e passed every check of its pipeline, 0 when it did not (an invalid env).

fail bits

fail_mask[e] has one bit set per check env e failed (0 exactly when it is valid); result.fail_bits maps each check’s name to its bit, for a track generator, turning, finite, thickness, w_floor, border, grade and truncation.

invalid env

An env with valid[e] == 0. By default its geometry stays in the result, but every runtime utility answers for it as for an env with no track (off the road, no checkpoints, frozen progress). Gate rewards, observations and episode ends on valid, and give the env a new course with a masked regeneration (Invalid envs).

preset

A function that builds a standard configuration from keyword arguments: presets.track and presets.gates return a spec, Course.track and Course.gates build a course with the usual components.

spec
PipelineSpec

The description, as data, of a generator’s pipeline: the batch (E, device, N_max, …) and the ordered stages with their params. It is immutable; edit it into a new one (replace, insert_after, remove), save it with to_dict(), and build it with TrackGenerator(spec) or GateGenerator(spec).

stage

One step of a pipeline, selected by its kind and name (for example the xpbd stage of kind relax) and configured by its Params. Plugins can provide their own generators and stages (Writing a Generator).

Params

A stage’s typed parameters, a frozen dataclass per stage (for example the bezier generator’s BezierParams), checked when the generator is built. Each field is static (fixed for the generator’s lifetime) or dynamic.

dynamic field

A Params field the generator holds as a live [E] device array, one value per env (gen.params[stage_id][field]): set it from the host or from your own kernels, or draw it per env with randomize=, and the next generation uses the new values without rebuilding anything (Domain randomization and curricula). Changing a static field needs a new generator.

channel

A named device buffer that the stages of a pipeline pass to each other (for example cl.xy, the centerline points while the track is being built). The pipeline allocates each channel once, when it is built.

component

A runtime piece that a course drives over the generator’s result: checkpoints, progress, collision, localization, props, or your own (Writing a Component). It refreshes on every new course, steps with your simulator and resets on respawn.

course

A Course: one generator plus its components, driven as one object for the whole batch (bind, step, reset, regenerate); see Courses. Also, in prose, the track or gate sequence one env’s agent drives.

capture

Recording device work into a CUDA graph instead of running it (wp.ScopedCapture, torch.cuda.graph), to replay it later with one launch. Generator and course calls made while their stream is capturing are recorded into your graph; building a generator or a course inside a capture raises an error. Warp also records and replays captures on the cpu device.

conditional node

A CUDA graph node that runs its body only when a value on the device says so. A masked regeneration recorded with one is skipped on the device when no env is masked; recorded without, it runs a full generation on every replay. It needs a capture Warp registered and CUDA 12.4 or newer in the driver and in Warp’s build (wp.is_conditional_graph_supported()).