Courses and runtime utilities¶
The objects that run alongside your simulator: the Course (one generator plus its
components, see Courses), the component protocol and its
adapters (Components), and the runtime utilities the adapters wrap
(collision, props, checkpoints, progress, localization, heightfields). Each one reads a
generator’s result and answers for every env at once; an invalid env
reads as an env with no track (Invalid envs). import track_gen
imports all of these modules.
Courses¶
The composition root: a generator (any Producer) plus an
ordered list of components, with the presets Course.track /
Course.gates and the 0.x Course(CourseConfig) (Course.from_config;
config= is the 0.x keyword of the constructor). See Courses for the lifecycle
and Composing a course for composing your own.
Public course facade: a generator and its runtime components in one object.
Course is the composition root: Course(generator,
components) over any producer and any track_gen.tools components, the presets
Course.track / Course.gates, and Course.from_config(CourseConfig) (the 0.x
Course(CourseConfig), unchanged). See Course for the lifecycle (construct ->
bind -> step/reset, with regenerate() / generate(seeds=...)). step(),
reset(), regenerate() and generate() can be recorded into a caller’s CUDA
graph as-is; StepResult gives the 0.x fields plus read-only access to every
per-step component’s result by name; set_capturing is a deprecated no-op kept for
compatibility.
- class Course[source]¶
Bases:
objectThe composition root: one producer and its runtime components.
Three ways to build one (all run
__init__, so a subclass initializes normally):Course(generator, components=(), *, stream=None, prepare=True):generatoris any producer (aTrackGenerator, aGateGenerator, a builttrack_gen.pipeline.Program, or an object with thetrack_gen.pipeline.Producersurface);componentsaretrack_gen.toolscomponents (built-in adapters or your own), sorted by theirafter/sourcedependencies (stable: an ordered list is kept as given);the presets
track()/gates()(the 0.x component sets over a config, aPipelineSpecor a built generator, plusextracomponents);from_config()(Course(cfg)andCourse(config=cfg), the 0.x forms, are its aliases): the 0.x course, byte for byte and launch for launch.
Lifecycle: construct ->
bind()(stable sim buffers; required forstep()when a component declares a required input) -> per-stepstep()/ per-envreset(), plusregenerate()/generate()for new courses. Construction attaches every component to the producer’s current batch (their auto-derivations may read it back on the host once), then, withprepare=True, runs one real warm-up regeneration; all of it must happen outside any CUDA graph capture. The 0.x sub-tool attributes are set from the components namedprogress,checkpoints,collision,heightfield,centerlineandlocalizer(Nonewhen absent):progress,checkpoints(the checkpoint component’s result),checkpoint_sampler,collision,heightfield,course_line,localizer; withgenerator,rng,result,components,inputs,seeds.Regeneration recomputes the whole batch (fixed-batch pipelines); per-env NEW courses come from
regenerate()’smask+advance(only the masked envs change and are reset), per-env respawns on the SAME course fromreset()’s mask.Execution order:
attach,on_generateandstepin the sorted order,resetin reverse order (a component is reset before the components it reads; resets read only their own state and the mask).on_generateandresetrun in the masked regeneration body, under a CUDA conditional node where the capture allows one: they read no bound input and emit only kernels, fills and device-to-device copies on the call’s stream (track_gen.testing.check_componentchecks both).One course per producer: building a Course over a producer that a live Course drives makes the older course raise
RuntimeErroron its nextstep/reset/regenerate/enqueue_generate/generate(its components would read a source the newer course regenerates). Regenerate through the course: calling the producer directly regenerates the source without refreshing the components.Lifetime: the course holds the producer, every component and the
StepResult; recording a call into a Warp-registered CUDA capture anchors the course on that graph, with the arrays bound whenstep()was recorded and themask/seedsarrays the recorded call reads (dropping every reference, or rebinding, while the graph lives keeps every buffer it reads). A CPU graph (wp.ScopedCapture("cpu")) retains the arrays its recorded launches touch itself. Under a plaintorch.cuda.graph, keep the course and those arrays alive (track_gen.interop.torch.retain(g, course, ...)).Invalid envs (
valid[e] == 0oncourse.result): every built-in component answers as for a poisoned env (course.checkpoints.count = 0with NaN slots,oob = 1with NaN geometry, no post hit, frozen progress withdist_to_next = 0, NaNs/n, a NaN heightfield with the box(-1, -1)-(1, 1)).StepResult.validcarries the mask: setdone |= 1 - validand give those envs a NEW course withregenerate(mask=done, advance=True)(resetwould respawn them on the same invalid course).- Parameters:
generator – The producer (see above), or a
CourseConfig(the 0.x form, thencomponents/stream/prepare=FalseraiseTypeError).components – The components (unattached instances; each belongs to one course).
stream – The course’s default stream: calls that pass no
stream=(and run outside an ambient scope) run on it. Default: the producer’s (TrackGenerator(..., stream=s)), else resolved per call. Such a course refuses (CaptureError) a call withoutstream=while another stream is capturing: pass the capturing stream (or"torch") when recording.prepare – Run one warm-up regeneration at construction (loads every module the regeneration body uses).
Falsedefers it:prepare()runs it, as does the first regeneration call outside a capture; recording one first raisesCaptureError.config – The 0.x keyword (
Course(config=cfg)).
- Raises:
TypeError – a config or spec given to the generic form (use the presets), a
CourseConfigwith components /stream/prepare=False, a producer missing a required member, a component missing a method.ValueError – duplicate component names, a dependency that is not a component, a cycle, an attached component, an input declared with two specs or by a component and the generator.
track_gen.exec.CaptureError – a capture is open (before anything is allocated).
- __init__(generator=None, components=(), *, stream=None, prepare=True, config=None)[source]¶
- Parameters:
prepare (bool)
config (CourseConfig | None)
- Return type:
None
- classmethod from_config(config)[source]¶
The 0.x course of a
CourseConfig(Course(config)is an alias): the generator and the components the config selects. For what changed since 0.1 (invalid envs, the default checkpoint capacity), see Course presets.- Raises:
TypeError –
configis not aCourseConfig.- Parameters:
config (CourseConfig)
- Return type:
- classmethod track(source, *, checkpoint_spacing, collision=None, max_checkpoints=None, extra=(), seeds=None, stream=None, prepare=True)[source]¶
A track course:
[Checkpoints, Progress, collision] + extra.- Parameters:
source – A
TrackGenConfigor a trackPipelineSpec(the preset buildsTrackGenerator(source, seeds=seeds, stream=stream)), or a builtTrackGenerator/ trackProgram(used as is:seeds=/stream=raiseTypeError).checkpoint_spacing (float) – Centerline checkpoint spacing (a number
> 0).collision (str | object | None) –
None(default, asCourseConfig.collision),"segments"/"sdf"(atools.BandCollisionof that method), or a component named"collision"(it fillsStepResult.contactsandcourse.collision; e.g.tools.BandCollision("sdf", sdf_resolution=256, max_boxes=2)).max_checkpoints (int | None) – The checkpoint capacity per env (an int
>= 3);Noneor"auto": sized for the longest track the generator can produce, else the readback of the construction batch (tools.Checkpoints(max_checkpoints="auto")), with aUserWarningwhen it clips a valid env of the first batch.extra – More components, a sequence (sorted with the preset’s by their dependencies).
seeds – The generator’s seeds (anything
TrackGenerator(seeds=)takes); aPerEnvSeededRNGbecomescourse.rng(generate(seeds=)then reseeds through it, as 0.x).stream (Stream | None) – The default stream of the generator and the course.
- Raises:
ValueError – a bad spacing / collision / capacity, a
collision=component not named"collision", anextrathat clashes with the preset’s names or dependencies (all before anything is built).TypeError – a source of the other family or of another type,
seeds=/stream=with a built generator, a single component asextra.
- Return type:
- classmethod gates(source, *, collision=None, samples_per_gate=8, localize_window=None, extra=(), seeds=None, stream=None, prepare=True)[source]¶
A gate course:
[GateCheckpoints, CenterlineFromGates, Progress, collision, Localizer] + extra(the localizer on the gates’ centerline).- Parameters:
source – A
GateGenConfigor a gatePipelineSpec(the preset buildsGateGenerator(source, seeds=seeds, stream=stream)), or a builtGateGenerator/ gateProgram(used as is:seeds=/stream=raiseTypeError). Its constructiongate_frames.gate_widthmust be> 0, or, when a randomizer draws the width (with_randomize({"gate_frames.gate_width": (lo, hi)})), that randomizer’s constructionlo(checked before anything is built); the preset then refuses a later scalarsetof a width<= 0on the generator it uses (a given one included).collision (float | object | None) –
None, a post radius> 0(atools.DiscCollision), or a component named"collision"(e.g.tools.FrameCollision(radius, thickness, depth),tools.DiscCollision(r, max_boxes=2)).samples_per_gate (int) – Centerline samples per gate (
>= 2).localize_window (int | None) – The localizer’s
warm_window(None: full scans).extra – More components, a sequence (sorted with the preset’s by their dependencies).
seeds – As
track().stream (Stream | None) – As
track().
- Raises:
ValueError – a bad radius, a
collision=component not named"collision",gate_width <= 0(or a width randomizer’slo <= 0), anextrathat clashes (all before anything is built).TypeError – a source of the other family or of another type,
seeds=/stream=with a built generator, a single component asextra.
- Return type:
- property graph: Graph | None¶
The course’s own regeneration graph (producer + every component’s refresh as ONE graph) for a plain
regenerate()/generate(): captured on the first such CUDA call made outside any capture, replayed by later ones.Noneon CPU, for a producer with HOST stages, and before that call. Each(advance, mask)variant ofregenerate()caches one more graph.
- property seeds¶
The producer’s
SeedState(the per-env seeds, bound by pointer or owned, plus the per-env epoch);Nonefor a producer without one.
- property inputs: MappingProxyType¶
Persistent device inputs (stable pointers, allocated at construction):
"regen_mask":[E]int32, zero-initialised. Write the envs to regenerate from your own kernels (e.g.doneflags inside your sim graph) and pass it asregenerate(mask=course.inputs["regen_mask"], advance=True). Its pointer never changes, so the course’s own CUDA graph for it is captured once."reset_mask":[E]int32, zero-initialised: the envs to respawn on their current course, passed asreset(course.inputs["reset_mask"]).
The generator’s own inputs stay at
course.generator.inputs: write them in place (they are read by every regeneration and every replay).
- property components: MappingProxyType¶
The components,
name -> component, in execution order (read-only).
- bind(position=<required on a CourseConfig course>, orientation=None, half_extents=None, box_position=None, **arrays)[source]¶
Bind stable sim buffers to the components that declare them (required before
step()when one declares a required input).Every argument is routed by name to every component whose
inputsdeclare it (Progress,Localizerand your own all takeposition), or that names it as an input’sdefault(box_positiondefaults topositionwith one box per env; an explicitly given name wins over the default). The four 0.x names (position,orientation,half_extents,box_position) are accepted on every course and ignored when no component declares them.positionis the[E]vec3f agent-position buffer. With a box collision component,orientation([E * max_boxes]quatf box poses) andhalf_extents([E * max_boxes]vec2f, planar boxes) are required too; withmax_boxes == 1the samepositionbuffer serves as the box positions, otherwise pass a separatebox_position[E * max_boxes]vec3f buffer. May be called any time after construction (before or after a regeneration).A bind is complete and atomic: every route is validated before anything is rebound, and a rebind replaces every binding; when a component refuses (
on_bindraising part-way), the components already rebound by the call and the one that refused are bound again to their previous arrays, their rebind-after-capture probes are restored, and the error propagates (every previous binding stays in place, and a later rebind after a capture still warns).Do NOT rebind after capturing
step()into a sim graph: the captured graph replays against the buffer pointers live at capture time, so a later rebind leaves it reading the old buffers (silently divergent results) — keep writing into the SAME bound buffers and rebind only before (re)capturing. Such a rebind (other arrays, afterstep()ran while its stream or a CPU graph capture was capturing) warns once,RebindAfterCaptureWarning; rebinding the same arrays is silent. The recipe “rebind, then record again” warns once too: filter the category when you re-record on purpose. The course’s own regeneration graphs never read bound inputs, so they stay valid.The generator’s inputs (
course.generator.inputs, e.g."centerline.xy"ofgenerator="external") are not bound here: write them in place.- Raises:
TypeError – an unknown name, a generator input name (with the in-place recipe), a missing required input; on a config course,
bind()withoutposition.ValueError – an array of the wrong shape, dtype or device (
position=Noneon a config course: the 0.x message).RuntimeError – a config course with a box collision checker bound without
orientation/half_extents(orbox_positionwithmax_boxes > 1).
- Parameters:
position (array)
orientation (array | None)
half_extents (array | None)
box_position (array | None)
arrays (array)
- Return type:
None
- prepare()[source]¶
Load every module the regeneration bodies use, outside any capture, and wait for it: a no-op once prepared (construction with
prepare=Trueincluded).For a course built with
prepare=False: one real masked regeneration on a private all-zero mask (an exact identity: no env’s course or runtime state changes), synchronized. The first regeneration call outside a capture runs it too; recording one before it raisesCaptureError.- Raises:
track_gen.exec.CaptureError – a capture is open.
- Return type:
None
- regenerate(*, mask=None, advance=False, stream=None, sync=True)[source]¶
Regenerate courses plus a coherent refresh of every component; returns
result.mask=None(whole batch): the producer’s generation from the current seeds (after advancing EVERY env’s epoch whenadvance=True), then every component’son_generate(checkpoint resample / sdf and heightfield bake / posts and line rebuild as applicable, your components’ per-generation work) and a FULL reset of every component (progress, localizer, …) — every course changed (or, withoutadvance, was rebuilt identically).maskgiven ([E]int32 on the course device, read on device): envs withmask[e] != 0advance their epoch (whenadvance=True) and get the course of their live(seed, epoch), and their runtime state is reset (every component’sreset(ctx, mask), in reverse order); envs withmask[e] == 0keep their course and runtime state. The pipeline is fixed-shape, so it still recomputes every env: unmasked envs keep the effective seed their current course was built from and are recomputed to identical values, even if their seed or epoch changed since (rng.set_seeds_warp,seeds.reset_epoch(), a write to the seed array): such a change reaches an env at its next masked regeneration, or every env at the next one without a mask. The generator’s commit also restores an unmasked env’sTrackrows (on a gate course itsGateSequencerows) bit for bit after the recompute (track_gen.stages.commit). Withoutadvancea mask respawns the masked envs (asreset()) on the course of their current(seed, epoch): the same one unless their seed was rewritten.Conditional skip: the masked body runs after a device-side
any_set(mask)undertrack_gen.exec.LaunchContext.skip_unless(). Inside a Warp-registered capture (WarpScopedCapture, the course’s own CUDA graph,capture_begin(external=True), ortrack_gen.interop.torch.registered(conditional=True)undertorch.cuda.graph) with CUDA Toolkit and driver >= 12.4, it is skipped on device when no env is masked; the caller’s graph then holds a conditional node, so it can no longer be used as a CUDA child graph, andwp.capture_ifends and restarts the capture: join every side stream before this call (or wrap it intrack_gen.exec.unconditional()). Every component’son_generateandresetrun inside that conditional body (kernels, fills and device-to-device copies only; a refused body raisesCaptureErrornaming the components). Elsewhere it runs unconditionally, which is correct because an all-zero mask makes it a no-op: nothing advances, nothing is reset, and every env is recomputed from the effective seed its current course was built from (the producer’s commit restores every row; the built-in components recompute from the unchanged result).Execution (stream resolved per
track_gen.exec):inside a capture on the resolved stream: RECORDED into the caller’s graph (e.g. next to
step()/reset()); each replay regenerates from the seeds, epochs and mask at replay time (and, withadvance, advances again);outside a capture on CUDA: replay of the course’s own CUDA graph (producer + refresh as ONE graph, captured on the first call of each
(advance, mask)variant;graphis the plain one);on CPU, or with a producer that has HOST stages: eager (a Course does not segment).
A course built with
prepare=Falseprepares first (seeprepare()).- Parameters:
mask (array | None) – optional contiguous
[E]int32 regeneration mask. Pass a STABLE buffer (e.g.inputs["regen_mask"]): outside a capture each distinct mask pointer captures, and caches, one more graph of the course’s own (up to a bound, past which new pointers run eagerly).advance (bool) – advance the epoch (of every env, or of the masked envs) on device first, so those envs get new courses. Default
False(rebuild from unchanged seeds and epochs).stream – optional
wp.Stream(or"torch") to run on.sync (bool) – block the host until the regeneration finished on the resolved stream (default
True; never while capturing, nor while torch is capturing in the calling thread).
- Raises:
ValueError –
maskis not a contiguous[E]int32 array on the course device (before any launch).RuntimeError – a newer Course drives this course’s producer.
track_gen.exec.NotCapturableError – recording a course whose producer has HOST stages (before any launch).
track_gen.exec.CaptureError – recording a course that was never prepared; a
stream=course called withoutstream=under another stream’s capture; a component’son_generate/resetrefused in a conditional body.
In track mode, also check
course.checkpoint_sampler.truncatedafterwards: a per-env[E]int32 flag (1 ifmax_checkpointsclipped that env’s checkpoint ring), mirroringPropSet.truncatedfor prop rings.
- enqueue_generate(*, stream=None, mask=None, advance=False)[source]¶
Enqueue one regeneration (as
regenerate(), samemask/advance) without building the course’s own CUDA graph or blocking: recorded into the caller’s capture when the resolved stream is capturing, otherwise eager on that stream. Returnsresult(stream-ordered).- Parameters:
mask (array | None)
advance (bool)
- generate(seeds=None, *, stream=None, sync=True)[source]¶
Optional reseed, then
regenerate(). Returnsresult.seedsintkreseeds every env withk + e(the same expansion as constructing a freshPerEnvSeededRNG(seeds=k)), expanded device-side into a persistent buffer: no allocation. Awp.arraymust be[E]int32 on the course device. The reseed and the regeneration run on the SAME resolved stream, so this whole call can be recorded into a caller’s capture; note that an intkis then BAKED into the caller’s graph (every replay reseeds withk), while awp.arrayis read at replay time (write new seeds into it to vary replays).Determinism contract: the generators are deterministic under an unchanged
(seed, epoch). Callinggenerate()again WITHOUTseeds=reproduces the identical batch (plus a full reset); passseeds=to reseed and get new courses. A reseed also zeroes every env’s epoch (on device, in the same unit), sogenerate(seeds=s)reproduces a fresh course built withseeds=swhatever was advanced before.Who writes the seeds follows their owner’s rule: with
rng(a config course, or a generator built with aPerEnvSeededRNG) the reseed goes throughrng.set_seeds_warp, which overwritesrng.seeds_warpin place: whenCourseConfig.seedswas awp.array, that IS your array (the rng binds it without a copy), sogenerate(seeds=...)rewrites it. Without an rng, owned seeds are set withSeedState.set(a copy, epochs zeroed); seeds bound to your array are never written (RuntimeError: write the array, thenregenerate()).- Parameters:
- Raises:
TypeError – the producer has no
SeedState(before any launch).RuntimeError – seeds bound to a caller array with no rng; a newer Course drives this course’s producer.
- step(stream=None)[source]¶
Every component’s
step(progress update, collision query, localization, your components), in execution order, on the bound buffers.Returns the same
StepResultinstance every call (its entries: the per-step components’ results).- Parameters:
stream – optional wp.Stream (or ‘torch’) to run on; by default the stream is resolved per track_gen.exec (an enclosing torch.cuda.graph capture is adopted, else the course’s default stream, else Warp’s current stream). Every component launch of this call goes to that one stream.
- Raises:
RuntimeError – a component declares a required input and
bind()was not called; a newer Course drives this course’s producer.track_gen.exec.CaptureError – a
stream=course called withoutstream=under another stream’s capture.
- Return type:
- reset(mask=None, stream=None)[source]¶
Per-env respawn on the SAME course: every component’s
reset(progress state, the localizer’s warm-start memory, your stateful components), in reverse execution order, wheremask[e] != 0. Collision, checkpoints and bakes derive from the course geometry and are unaffected by respawns.- Parameters:
mask (array | None) –
[E]int32 respawn mask (e.g.inputs["reset_mask"]);None: every env.stream – optional wp.Stream (or ‘torch’) to run on; by default the stream is resolved per track_gen.exec (an enclosing torch.cuda.graph capture is adopted, else the course’s default stream, else Warp’s current stream). Every component launch of this call goes to that one stream.
- Return type:
None
- static set_capturing(flag)¶
Deprecated no-op, kept for compatibility. Removed in 1.0.
Utilities no longer host-synchronize, so
query()/update()/reset()/sample()/bake()/refresh()andCourse.step()/reset()can be recorded into a CUDA graph without any flag.- Parameters:
flag (bool)
- Return type:
None
- class CourseConfig[source]¶
Bases:
objectConfiguration for
Course. Strict option applicability: inapplicable options raise instead of being silently ignored.- gen¶
Generator config; its type must match
mode.num_envsanddeviceare taken from here. Gates mode requiresgen.gate_width > 0(a width-0 gate can never be crossed). The width is per env (gate_frames.gate_width): a scalarsetof a value<= 0is refused, but an[E]array written after construction is not range-checked (a value<= 0is used as 0): keep it> 0, or those envs’ gates can never be passed.- Type:
- collision¶
Track mode only:
"segments","sdf", orNone(no out-of-bounds checking — progress-only bundles are legal).- Type:
str or None
- sdf_resolution¶
Track mode with
collision="sdf"only;None-> 128. The facade always usesCollisionChecker’s AUTO SDF padding (sdf_padding=None, i.e. 10% of each env’s larger AABB extent);sdf_paddingitself is not exposed here.- Type:
int or None
- heightfield_resolution¶
Track mode only; when set (
>= 8), builds aHeightFieldBakerexposed ascourse.heightfieldand re-baked on everygenerate()(nearest-edge road-surface grid for external physics solvers). Uses the baker’s AUTO padding (10% of each env’s larger AABB extent).None(default) means no heightfield.- Type:
int or None
- frame_collision¶
Gates mode only:
TrueenablesFrameCheckersphere-vs-gate-frame collision (each square gate = four thin oriented frame members). Mutually exclusive withpost_radius > 0(choose discs OR frames); requiresframe_thickness,frame_depthandagent_radiusall > 0.- Type:
- frame_thickness¶
Gates mode with
frame_collisiononly (required, > 0): thickness of the frame members (their extent across the opening plane).- Type:
- frame_depth¶
Gates mode with
frame_collisiononly (required, > 0): depth of the frame members along the gate-forward axis.- Type:
- agent_radius¶
Gates mode with
frame_collisiononly (required, > 0): the agent sphere radius for frame collision (posts are exclusive, so this cannot ridepost_radius).- Type:
- checkpoint_spacing¶
Track mode only (required there): centerline checkpoint spacing.
- Type:
float or None
- max_checkpoints¶
Track mode only: the checkpoint capacity per env (
>= 3), forwarded toCheckpointSampler.None(default): the plan’s size-independent boundmax(3, ceil(max_loop_length / checkpoint_spacing))withmax_loop_length = 1.5 * N_max * spacing + dZ(track_gen.tools.ComponentContext.for_producer(); 70 at theTrackGenConfigdefaults andcheckpoint_spacing=0.5), or, when the plan declares none (an elevation whosez_min/z_maxdeclare no range, a variable width, a plugin geometry stage), the construction batch’s readback (ceil(1.5 * max valid centerline perimeter / checkpoint_spacing)). A defaulted capacity that clips a valid env of the first batch warns (UserWarning) at construction. To keep a fixed shape (the capacity an earlier release derived from the batch, for example), pass it explicitly (course.checkpoints.position.shape[0] // num_envsreads the current one).- Type:
int or None
- samples_per_gate¶
Gates mode only: centerline samples per gate for the
CourseLinespline (>= 2). Default 8.- Type:
- localize_window¶
Gates mode only: forwarded as
TrackLocalizer(warm_window=...);None(default) means full cold scans every query.- Type:
int or None
- __init__(mode, gen, seeds=0, collision=None, sdf_resolution=None, heightfield_resolution=None, post_radius=0.0, frame_collision=False, frame_thickness=0.0, frame_depth=0.0, agent_radius=0.0, checkpoint_spacing=None, max_checkpoints=None, max_boxes=1, samples_per_gate=8, localize_window=None)¶
- Parameters:
mode (str)
gen (TrackGenConfig | GateGenConfig)
seeds (int | array)
collision (str | None)
sdf_resolution (int | None)
heightfield_resolution (int | None)
post_radius (float)
frame_collision (bool)
frame_thickness (float)
frame_depth (float)
agent_radius (float)
checkpoint_spacing (float | None)
max_checkpoints (int | None)
max_boxes (int)
samples_per_gate (int)
localize_window (int | None)
- Return type:
None
- classmethod __new__(*args, **kwargs)¶
- class StepResult[source]¶
Bases:
objectPer-step bundle returned by
Course.step().Holds the components’ preallocated in-place results: the SAME
StepResult(and the same underlying buffers) is returned on everystep(); useclone()for snapshots.It also gives read-only mapping access to the result of every per-step component (one that overrides
Component.step), by component name, in execution order:res["progress"],res.get(name),keys(),values(),items(),name in res,dict(res). Per-generation components (checkpoints, the course line, bakes, props) are read throughcourse.components[name].result. It is not acollections.abc.Mapping: it stays truthy,iter(res)raisesTypeErrorand dataclass utilities see the four 0.x fields only (dataclasses.replacerebuilds the entries from them; therepris the dataclass one). Assigning a 0.x field (res.events = x, as tree utilities that copy a dataclass and set its fields do) updates its entry too (Noneremoves it), without touching the instance it was copied from.- events¶
Progress events for this step:
res["progress"](Noneon a course without a component namedprogress).- Type:
- contacts¶
Collision result (a
BoxContact, aDiscContactor aFrameContact):res["collision"](Nonewhen the course has no component namedcollision).- Type:
object or None
- frame¶
Localization frame:
res["localizer"](Nonewithout a component namedlocalizer: the 0.x track mode).- Type:
TrackFrame or None
- valid¶
[E]int32 validity of each env’s course: an alias ofcourse.result.valid(Noneonly on a hand-built instance, or for a producer whose result has novalid). The other fields of an env withvalid[e] == 0hold the invalid-env sentinels (seeCourse): gate rewards and observations on it with a select (where(valid, x, 0), never a multiply:NaN * 0is NaN) and end such episodes. It is producer state and live: never write into it (exclude it from code that zeroes or poisons the step outputs;dataclasses.fieldslists it, the mapping entries do not), and read it before aregeneratein the same iteration, which rewrites it for the regenerated envs (orclone()the result).- Type:
wp.array or None
- get(name, default=None)[source]¶
The result of the per-step component
name, elsedefault.- Parameters:
name (str)
- clone()[source]¶
Deep-copy every entry (and
valid): awp.arraywithwp.clone, an object with aclone()method with it, a dataclass as a shallow copy whosewp.arrayfields are cloned (cold: allocates).- Raises:
TypeError – an entry is none of these (the message names its component).
- Return type:
- __init__(events, contacts, frame, valid=None)¶
- Parameters:
events (ProgressEvents)
contacts (BoxContact | DiscContact | FrameContact | None)
frame (TrackFrame | None)
valid (array | None)
- Return type:
None
- classmethod __new__(*args, **kwargs)¶
- set_capturing(flag)[source]¶
Deprecated no-op, kept for compatibility. Removed in 1.0.
Utilities no longer host-synchronize, so
query()/update()/reset()/sample()/bake()/refresh()andCourse.step()/reset()can be recorded into a CUDA graph without any flag.- Parameters:
flag (bool)
- Return type:
None
Components¶
The component protocol and the adapters of the runtime utilities; see Writing a Component to write your own.
Components: runtime consumers of a generator’s result.
A Component declares what it reads (requires / optional capability
protocols of its read target, source, topology), what it is bound to (inputs:
InputSpec), and whether it keeps per-env state (stateful); a
ComponentContext checks the declaration and attaches it, with the producer’s
static capacities (ComponentContext.for_producer()). After attach its hot
methods (on_generate, step, reset) launch through the
LaunchContext they are given, allocate nothing and never
synchronize, so a Course (or your sim loop) can record them into a CUDA graph:
import warp as wp
from track_gen import TrackGenerator, tools
from track_gen.exec import LaunchContext
from track_gen.pipeline import presets
E, device = 64, "cuda:0"
gen = TrackGenerator(presets.track(num_envs=E, device=device))
pos = wp.zeros(E, dtype=wp.vec3f, device=device) # your agents' positions
track = gen.generate() # the producer's result
cc = tools.ComponentContext.for_producer(gen) # layout + plan capacities
cps = cc.add(tools.Checkpoints(0.5), track) # plan-bound capacity
prog = cc.add(tools.Progress(), track) # reads cps.result
prog.bind(position=pos)
ctx = LaunchContext.for_call(track.layout.device)
events = prog.step(ctx)
The built-in adapters each wrap one 0.x utility (adapter.utility; the legacy classes
keep every name): BandCollision (CollisionChecker), DiscCollision
(DiscChecker), FrameCollision (FrameChecker), Props
(PropSampler), Checkpoints (CheckpointSampler),
GateCheckpoints (CheckpointSet.from_gates), Progress
(ProgressTracker), Localizer (TrackLocalizer),
HeightFieldBake (HeightFieldBaker) and CenterlineFromGates
(CourseLine: its result is the Centerline). track_gen.testing.check_component
checks a component (yours or a built-in) against the contract.
- class Component[source]¶
Bases:
objectA runtime consumer of a producer’s result, driven by a
Course(or by hand).A plain base class with immutable defaults (subclass it; the library duck-types and never
isinstance-checks a component). Declare, as class attributes or in__init__:name: unique among the components attached to oneComponentContext;source: the read target:Nonefor the producer’s result given toComponentContext.add(), else thenameof the component whoseresultit reads;requires: capability protocols (track_gen.results) of the read target whose members are all read (strict: a missing one raisesTypeError);optional: protocols whose members are read when present (valid,generation: checked like the required ones when present);topology:"closed"(refuses a read target withlayout.closed=False) or"any";stateful: whether it keeps per-env runtime state thatreset()clears;inputs:name -> InputSpecof the arraysbind()takes;after: names of other components it reads (attached first).
ComponentContext.add()setslayout(the read target’s layout) and callsattach(), which setsresult(allocated once, never replaced). Overrideattach(),on_bind()and the hot methods; neverbind(). (The built-in adapters also expose the legacy utility they wrap asutility; it is not part of the component contract.)Contracts (checked by
track_gen.testing.check_component()):attachis cold (once, outside any capture), allocates every buffer, launches throughcc.ctx, ends with oneon_generate(cc.ctx, None)and loads the module of every kernel its hot methods launch; the hot methods launch through thectxthey are given only, never allocate, synchronize, read back or change Python state that alters a later emission;on_generate(ctx, mask)leaves the rows and state of envs withmask[e] == 0bitwise unchanged (a whole-batch recompute qualifies);stepreturnsresult;reset(ctx, mask)clears the per-env runtime state wheremask[e] != 0and nothing else (a stateless component emits nothing).In a
Course:on_generateandresetrun in the masked regeneration body, under a CUDA conditional node where the capture allows one, so they read and write no bound input (stepreads them; a respawn snapshot of an input belongs instep:resetarms a per-env flag that the nextstepconsumes) and emit only kernels,ctx.filland device-to-devicectx.copyonctx’s stream (no copy to or from pageable host memory, no event, no other stream; CUDA accepts a copy from pinned, device-mapped host memory, but keep the body to device memory for portability);resetreads only the component’s own state and the mask (resets run in reverse order, before the components they read); a component that overridesstepis an entry of the course’sStepResult(itsresult), one that keeps the basestep(per-generation work) is read throughcourse.components[name].result.- attach(source, cc)[source]¶
Cold, once: allocate, set
result, end withon_generate(cc.ctx, None)and load the hot kernels’ modules.sourceis the read target, already checked againstrequires/optional.- Parameters:
cc (ComponentContext)
- Return type:
None
- bind(**arrays)[source]¶
Bind the stable input arrays (cold, before recording
step()).Routes
InputSpec.default(an input not given takes the array of itsdefaultname), refuses unknown names and missing required ones (TypeError), checks each array against its spec onlayout(ValueError; a routed input’s message names the argument it was routed from), then callson_bind()with every declared input (Nonefor an absent optional one). A name that is only thedefaultof a declared input is accepted (and routed). The built-in adapters warn once (RebindAfterCaptureWarning) when an array is replaced after a hot method that reads it was recorded into a capture (their wrapped utilities arm that probe); a component of your own warns only if its hot methods do the same: record the graph again after rebinding.- Parameters:
arrays (array)
- Return type:
None
- on_bind(**arrays)[source]¶
Hook: the checked input arrays (pin them; an absent optional input is
None). Default: nothing.- Parameters:
arrays (array)
- Return type:
None
- on_generate(ctx, mask)[source]¶
Hot: after every generation of the source (
mask: its[E]int32 mask, orNonefor the whole batch). Default: nothing.- Parameters:
ctx (LaunchContext)
mask (array | None)
- Return type:
None
- step(ctx)[source]¶
Hot: per sim step; returns
result. Default: returns it.- Parameters:
ctx (LaunchContext)
- reset(ctx, mask)[source]¶
Hot: clear per-env runtime state where
mask[e] != 0. Default: nothing.- Parameters:
ctx (LaunchContext)
mask (array)
- Return type:
None
- class ComponentContext[source]¶
Bases:
objectWhat
Component.attach()is given: the producer’s layout, its static capacities, a cold launch context and the components attached before.- Parameters:
layout – The producer’s
Layout.capacity – Static capacities of the producer’s plan (
name -> number; seefor_producer()). Hand-built sources pass their own.ctx – The launch context of the cold work of
attach(default:LaunchContext.for_call(layout.device): an enclosingctx.scope(), else Warp’s current stream). Attach on the stream where allocations happen (Warp’s current stream:attachallocates and zero-fills there, then launches its firston_generateonctx; a context on another stream races its own allocations), after the producer’s work completed on it (or make that stream wait for the producer’s).bounds – Declared bounds of the producer’s plan (
name -> number; seefor_producer()):size_hi, the largest course size the plan can draw. Read byBandCollision/HeightFieldBakewithsize_hi="auto". Hand-built sources pass their own (default: none).
- property capacity: Mapping¶
The producer’s static capacities (read-only mapping).
max_loop_length: an upper bound, with measured headroom, on the 3D length of the centerline and of either boundary loop of every valid env (seefor_producer()); a consumer sizes from it, clamps to its own capacity and reports truncation.
- property bounds: Mapping¶
The producer’s declared bounds (read-only mapping; see
for_producer()).size_hi: the largest course size the plan can draw (a size randomizer’s constructionhi, else the generator’s constructionsize); absent when the plan declares no size.
- property ctx: LaunchContext¶
The launch context of the cold work of
attach.
- add(component, source)[source]¶
Check
component’s declaration and read target, attach it and register it.The read target is
source(the producer’s result) whencomponent.sourceisNone, else theresultof the attached component it names. The target is checked againstcomponent.requires(every member) andcomponent.optional(the members it has; withoptionalalone and a target without a layout, against the layout its present members give, this context’s layout filling what they do not), withcomponent.topology;component.layoutis set (to this context’s layout when nothing is declared), thencomponent.attach(target, self)runs. A producer-result target (component.source is None) must hold this context’s batch: the samenum_envsand device.- Returns:
component.- Raises:
ValueError – a component named in
after/sourceis not attached, the name is taken, an input name is declared by an attached component with a different(dtype, slots), the target does not match (thecheck_sourcemessages), or a producer-result target holds another batch (num_envsor device) than this context’s layout.TypeError – a malformed declaration, or a required member missing on the target.
- Return type:
- classmethod for_producer(producer, *, ctx=None)[source]¶
The context of a producer’s components: its layout and the static capacities of its plan (no device readback).
produceris aTrackGenerator/GateGenerator(itsprogram), a builtProgram, aCourse(itsgenerator), or any object with thetrack_gen.pipeline.Producersurface that declarescapacityand / orboundsitself (read before the plan); itsspec/params/layoutare read. A result (gen.generate()’sTrack) has no plan: build aComponentContext(track.layout, capacity)for a hand-built source.Bound
size_hi(bounds): the constructionhiof arandomizestage drawingsize(generator.size/normalize.size; the largest of them), else the constructionsizeof the generator (or of thenormalizestage of a ring gate pipeline); omitted when the plan has no size (external, a legacy spec). It is the declared bound, never a current value: a laterset("hi", ...)orset("size", ...)curriculum is not seen.Capacity key
max_loop_length(0.x):1.5 * N_max * spacing + dZfrom the("resample", "constant_spacing")slot’sspacingand the batch’sn_max. A non-truncated env’s plan-view centerline is shorter thanN_max * spacingbefore relax (else the resample truncates it and the env fails validity); the 1.5 covers relax growth and a constant-width boundary’s extra2 pi * half_width(measured, not proven: at most 0.904 ofN_max * spacing).dZbounds the elevation’s total variation from the knobs’ DECLARED bounds (bounds(), never their current values): 0 forelevation.flat;K * (z_max_hi - z_min_lo)foruniform/random_walk(K = z_control_points);min(2 * H * (z_max_hi - z_min_lo), 4 * A_hi * H / H_H)fornoise(H = z_noise_harmonics,H_H = sum_{k<=H} 1/k,A_hithe amplitude’s upper bound);g * 1.5 * N_max * spacingwithvalidity.z_valid_grade = g > 0; the smallest finite term (only the grade of the built-invalidity.defaultcounts: a plugin check promises nothing about the final altitudes). The key is OMITTED (pass explicit capacities) when no term is finite (an elevation whosez_min/z_maxdeclare no range: every elevatedTrackGenConfigtoday; a randomizer’s range does not count: itslo/hiare dynamic), when the altitudes come from the generator (itprovidesz:lift.elevatedwith no elevation stage after the resample) and no grade is declared, when nowidth.constantstage follows the resample (another width stage, or a generator thatprovideshalf_width: an unbounded per-point width), with a plugin stage of kindrelax/width/offset/elevation/lift/resampleafter the constant-spacing resample, and without that resample (gate pipelines).- Raises:
TypeError –
producerhas no plan and declares neithercapacitynorbounds(aTrackor another result, a minimal producer), or it has noLayout.- Parameters:
ctx (LaunchContext | None)
- Return type:
- class InputSpec[source]¶
Bases:
objectOne bound input of a component: a
[E * slots]array ofdtype.- required¶
Whether
Component.bind()must be given it (afterdefaultrouting). An absent optional input reachesComponent.on_bind()asNone(def on_bind(self, position, speed=None)style hooks work, and so doesdef on_bind(self, position, speed)).- Type:
- default¶
Another input name whose array is used when this one is not given (e.g.
box_positiondefaults topositionwith one box per env).- Type:
str | None
- check(name, array, layout, *, consumer)[source]¶
Check
arrayagainst this spec onlayout: a contiguous 1-D[num_envs * slots]array ofdtypeon the layout’s device.- Raises:
ValueError – naming
consumerandname.- Parameters:
- Return type:
None
- __init__(dtype, slots=1, required=True, default=None, doc='')¶
- classmethod __new__(*args, **kwargs)¶
Adapters¶
Each adapter wraps one runtime utility (.utility) and exposes its result
(.result).
- class BandCollision[source]¶
Bases:
ComponentOut-of-bounds boxes against the road band (wraps
CollisionChecker).Read target: the producer’s result (
HasBand). Inputs:box_position(default="position"whenmax_boxes == 1),orientation,half_extents([E * max_boxes]).on_generate: the SDF bake (method="sdf");step:query->BoxContact. Withsize_hi(the largest size a size curriculum will reach) an"sdf"grid belowceil(reference_resolution * size_hi)texels per side warns at attach.size_hi="auto"(the default) reads the producer’s declared bound (cc.bounds["size_hi"]: a size randomizer’s constructionhi, else the constructionsize);Nonedeclares none.- __init__(method='segments', *, max_boxes=1, sdf_resolution=128, sdf_padding=None, size_hi='auto', reference_resolution=128, name='collision')[source]¶
- classmethod __new__(*args, **kwargs)¶
- class DiscCollision[source]¶
Bases:
ComponentBoxes against discs (wraps
DiscChecker).Posts mode (
discs=None): the read target is the producer’s gates (HasGateFrames):on_generateinterleaves theirleft/rightposts into the ownedposts([2 * E * max_gates]) and writespost_count(2 * count); the gates’validmakes an invalid env hit nothing. Raw mode (discs=a[E * D]vec3f array, optionalcount/valid[E]int32): no read target (requires=(),topology="any"), the arrays are aliased. Inputs asBandCollision;step:query->DiscContact.- classmethod __new__(*args, **kwargs)¶
- class FrameCollision[source]¶
Bases:
ComponentA sphere against the gate frames around the current target (wraps
FrameChecker;num_envscomes from the layout).Read target: the producer’s gates (
HasGateFrames);after=(progress,): the query windows on that component’sresult.next_checkpoint. Input:position.step:query->FrameContact.- __init__(radius, frame_thickness, frame_depth, *, window=4, progress='progress', name='collision')[source]¶
- classmethod __new__(*args, **kwargs)¶
- class Props[source]¶
Bases:
ComponentBoundary prop poses (wraps
PropSampler).Read target: the producer’s result (
HasBand). Capacity:max_props, ormax(3, ceil(max_loop_length / spacing))from the producer’s plan (ValueErrorwithout it).on_generate:sample;step: thePropSet.- classmethod __new__(*args, **kwargs)¶
- class Checkpoints[source]¶
Bases:
ComponentTrack checkpoints (wraps
CheckpointSampler).Read target: the producer’s result (
HasBand;generationread when present). Capacity:max_checkpoints(an int>= 3);None:max(3, ceil(max_loop_length / spacing))from the producer’s plan (ValueErrorwithout it);"auto": that bound, else, when the plan declares none (an elevated config, a variable width, a plugin geometry stage), the readback of the batch it attaches to (ceil(1.5 * max valid centerline perimeter / spacing), one cold host read; theCheckpointSampler(max_checkpoints=None)default).defaultedsays how a capacity it chose itself was found (Nonefor an explicit int); the sampler’smax_checkpointsis the capacity used.on_generate:sample(the set’svalid/generationare written with its rows);step: theCheckpointSet.- classmethod __new__(*args, **kwargs)¶
- class GateCheckpoints[source]¶
Bases:
ComponentThe gates as checkpoints (
CheckpointSet.from_gates: a zero-copy view;utilityisNone). Read target: the producer’s gates (HasGateFrames;generationread when present). Every array of its result aliases the gates.- classmethod __new__(*args, **kwargs)¶
- class Progress[source]¶
Bases:
ComponentOrdered progress through checkpoints (wraps
ProgressTracker; stateful).Read target: the
resultof the component namedsource(default"checkpoints";checkpoints=is an alias kept for 0.x) (HasCheckpoints; itsvalid/generationread when present: invalid envs frozen, self-reset on a new generation). Input:position.step:update->ProgressEvents;reset: clears the masked envs’ state.- classmethod __new__(*args, **kwargs)¶
- class Localizer[source]¶
Bases:
ComponentTrack-frame localization (wraps
TrackLocalizer; stateful in both modes: the warm memory and the self-reset state are per env).Read target: the producer’s result, or the
resultof the component namedsource(HasCenterline;valid/generationread when present). Input:position.step:query->TrackFrame;reset: drops the masked envs’ warm memory.- classmethod __new__(*args, **kwargs)¶
- class HeightFieldBake[source]¶
Bases:
ComponentA per-env road heightfield (wraps
HeightFieldBaker).Read target: the producer’s result (
HasBand).on_generate:bake. Withsize_hia grid belowceil(reference_resolution * size_hi)texels per side warns at attach;size_hi="auto"(the default) reads the producer’s declared bound (cc.bounds["size_hi"]),Nonedeclares none.- __init__(resolution, *, padding=None, size_hi='auto', reference_resolution=128, name='heightfield')[source]¶
- classmethod __new__(*args, **kwargs)¶
- class CenterlineFromGates[source]¶
Bases:
ComponentA 3D centerline through the gates (wraps
CourseLine): its result is theCenterline.Read target: the producer’s gates (
HasGates;generationread when present, copied into the line’s own).on_generate:refresh.- classmethod __new__(*args, **kwargs)¶
Collision queries¶
Box-vs-track out-of-bounds checks with full contact info, and the same box query against
disc obstacles (gate posts, physical cones, point props). Collision backends
compares the backends (the exact segments scan and the baked sdf grid), their
accuracy and their cost.
The query and instancing utilities on one generated track: track_gen.props
cones (points mode) and wall pieces (segments mode) on both boundaries,
the effect of the spacing knob, and track_gen.collision’s baked SDF
field with boxes classified by the exact segments backend (green inside,
red out of bounds, dotted lines to the nearest boundary point).
Regenerate with python -m viz.render_utility_assets.¶
Public collision-query API: box-vs-track out-of-bounds checks.
CollisionChecker answers, for batches of oriented boxes against a batch of
generated tracks, whether each box has left the drivable band — with full
contact info (BoxContact): OOB flag, signed clearance, nearest
boundary point, inward normal, and boundary id. It has two Warp backends:
method="segments"(default): exact, zero precompute; reads the boundTrackbuffers directly (fresh after everygenerate()).method="sdf": bakes per-env signed-distance grids; a query takes the exact minimum of the interpolated field over the box outline, at a cost that grows with the grid cells the outline crosses. Approximate within one grid cell near boundaries; requiresbake()after eachgenerate(). Memory ~E * sdf_resolution**2 * 5bytes.
DiscChecker is a sibling checker in this module, not a third backend of
CollisionChecker: it answers the same oriented-box query against disc
obstacles instead of a track (gate posts, physical cones, point props).
This module is the template for future query utilities: each gets its own
public sibling module (flat namespace, no grab-bag utils).
- class CollisionChecker[source]¶
Bases:
objectBatched box-vs-track out-of-bounds checker bound to a
Track.Because
TrackGenerator.generate()overwrites itsTrackbuffers in place, thesegmentsbackend always reads the CURRENT track batch with no rebind step. Thesdfbackend requiresbake()after eachgenerate()call.query()is allocation-free and host-sync-free (CUDA-graph capturable); its launches are stream-ordered, so a consumer on another stream must order itself explicitly.query()returns the same preallocatedBoxContacton every call (result).The source’s
validis read when present (checked at construction like the required members): an invalid env reads as out of bounds with NaN geometry (see the module docstring).- topology = 'closed'¶
The topologies this component supports (closed loops only in 0.x).
- __init__(track, max_boxes, method='segments', sdf_resolution=128, sdf_padding=None, position=None, orientation=None, half_extents=None)[source]¶
Bind to a
Trackbatch and allocate the result buffers.- Parameters:
track (HasBand) – the bound track batch; the
segmentsbackend reads its buffers directly on everyquery()(no rebind needed aftergenerate()).max_boxes (int) – query stride (boxes per env); must be >= 1.
method (str) –
"segments"(exact, default) or"sdf"(baked, approximate near boundaries; see the module docstring).sdf_resolution (int) –
method="sdf"only — grid side length in texels per env (>= 8; default 128). Ignored for"segments".sdf_padding (float | None) –
method="sdf"only — per-side AABB padding in world units the grid is expanded by.None(default) selects AUTO padding: 10% of the larger AABB extent (max(width, height)) of each env’s track, computed at bake time. Pass a positive float to override with a fixed padding for every env.position (array | None) – optional constructor binding (all-or-none); equivalent to calling
bind_inputs()right after construction.orientation (array | None) – optional constructor binding (all-or-none); equivalent to calling
bind_inputs()right after construction.half_extents (array | None) – optional constructor binding (all-or-none); equivalent to calling
bind_inputs()right after construction.
- Return type:
None
- property result: BoxContact¶
The preallocated
BoxContactquery()writes and returns (the same instance for the checker’s lifetime).
- bake(stream=None, *, ctx=None)[source]¶
(Re)bake the per-env SDF grids from the bound Track.
Required after every
TrackGenerator.generate()call whenmethod="sdf"(the segments backend needs no rebake). Pure kernel launches — CUDA-graph capturable.- Parameters:
stream – optional wp.Stream (or ‘torch’) to run on; by default the stream is resolved per track_gen.exec (an enclosing torch.cuda.graph capture is adopted, else Warp’s current stream).
ctx – optional
LaunchContexton the checker’s device to emit on (exclusive withstream=:TypeError).
- Raises:
ValueError – if this checker was constructed with
method="segments".- Return type:
None
- bind_inputs(position, orientation, half_extents)[source]¶
Bind stable per-step input buffers (validated once, here).
After binding,
query()takes no arguments and reads these arrays in place each call — the natural CUDA-graph pattern: the sim writes poses into its stable buffers and replays the captured query. The arrays must keep the same.ptrfor the binding’s lifetime: replacing one afterquery()was recorded into a capture warns (RebindAfterCaptureWarning).- Parameters:
position (array)
orientation (array)
half_extents (array)
- Return type:
None
- query(position=None, orientation=None, half_extents=None, stream=None, *, ctx=None)[source]¶
Compute contact info for
E * max_boxesoriented boxes.Two modes:
Bound mode: after
bind_inputs(), callquery()with no arguments; it reads the bound arrays in place each call (the arrays must keep the same.ptrfor the binding’s lifetime — write new poses into them rather than rebinding, e.g. under CUDA-graph capture).Per-call mode: if not bound, pass
position,orientationandhalf_extentsexplicitly on every call.
- Parameters:
position (array | None) –
[E*max_boxes]vec3fbox centers (the query projects to xy; OOB semantics stay planar). NaN marks an inactive slot (NaN outputs,oob=0,boundary=-1). Required in per-call mode; omit in bound mode.orientation (array | None) –
[E*max_boxes]quatfbox orientations (wp.quatf(x, y, z, w); the query uses the yaw about +z). Required in per-call mode; omit in bound mode.half_extents (array | None) –
[E*max_boxes]vec2fper-box half sizes (planar boxes). Required in per-call mode; omit in bound mode.stream – optional wp.Stream (or ‘torch’) to run on; by default the stream is resolved per track_gen.exec (an enclosing torch.cuda.graph capture is adopted, else Warp’s current stream).
ctx – optional
LaunchContexton the checker’s device to emit on (exclusive withstream=:TypeError).
- Returns:
The checker’s preallocated
BoxContact(same instance every call; buffers overwritten in place).- Raises:
ValueError – on shape/dtype/device mismatch, or on mode misuse (passing arguments while bound, or omitting them while not bound).
- Return type:
- class BoxContact[source]¶
Bases:
objectBatched box-vs-track contact result, flat
[E * max_boxes]per field.Warning
CollisionChecker.query()returns the SAMEBoxContactinstance on every call and overwrites its buffers in place. Callclone()for a fully-owned snapshot.- oob¶
int32— 1 if the box crosses a boundary or lies outside the band.- Type:
wp.array
- distance¶
float32signed clearance: positive = margin from the box to the nearest boundary; negative = deepest corner penetration when OOB (0.0 when the box only edge-crosses a boundary with all corners inside). Withmethod="sdf"it is the minimum of the interpolated signed-distance field over the box outline and centre, so an edge crossing a side reads negative too (oob == (distance < 0)). NaN for inactive slots. Cf.DiscContact.depth, which reports an UNSIGNED penetration depth (0.0 = no hit) rather than a signed clearance.- Type:
wp.array
- nearest¶
vec2fnearest point on the boundary polylines to the box.- Type:
wp.array
- normal¶
vec2fboundary normal atnearest, pointing INTO the band.- Type:
wp.array
- boundary¶
int32— 0 = inner boundary, 1 = outer boundary, -1 = inactive slot, degenerate track (count < 3) or invalid env (valid[e] == 0); withmethod="sdf"also -1 for a box whose corners are not finite (NaN or inf half-extents, yaw or position; reportedoob=1with NaN geometry).- Type:
wp.array
- __init__(oob, distance, nearest, normal, boundary)¶
- Parameters:
oob (array)
distance (array)
nearest (array)
normal (array)
boundary (array)
- Return type:
None
- classmethod __new__(*args, **kwargs)¶
- class DiscChecker[source]¶
Bases:
objectBatched oriented-box vs disc-obstacle checker (collision family).
See the module docstring.
num_envsis required whencountis not given (a flat disc array alone cannot determine the env split).- __init__(discs, radius, max_boxes, num_envs=None, count=None, position=None, orientation=None, half_extents=None, *, valid=None)[source]¶
Bind a flat disc array and allocate the result buffers.
- Parameters:
discs (array) –
[E * D]vec3f disc centers (ALIASED — regenerated buffers are seen automatically). NaN-marked slots are skipped.radius (float) – shared disc radius (> 0); every disc uses the same radius.
max_boxes (int) – query stride (boxes per env); must be >= 1.
num_envs (int | None) – required when
countis not given (a flat disc array alone cannot determine the env split); must matchcount.shape[0]when both are given.count (array | None) – optional
[E]int32 real-disc-count array (aliased, stable user buffer); omit to rely on NaN padding alone.position (array | None) – optional constructor binding (all-or-none); equivalent to calling
bind_inputs()right after construction.orientation (array | None) – optional constructor binding (all-or-none); equivalent to calling
bind_inputs()right after construction.half_extents (array | None) – optional constructor binding (all-or-none); equivalent to calling
bind_inputs()right after construction.valid (array | None) – optional
[E]int32 per-env validity (aliased, stable buffer): an env withvalid[e] == 0has no discs.
- Return type:
None
- bind_inputs(position, orientation, half_extents)[source]¶
Bind (or rebind) stable per-step input buffers (validated once).
After binding,
query()takes no arguments and reads these arrays in place; same-.ptrrule applies under CUDA-graph capture (replacing one afterquery()was recorded warns:RebindAfterCaptureWarning).- Parameters:
position (array)
orientation (array)
half_extents (array)
- Return type:
None
- property result: DiscContact¶
The preallocated
DiscContactquery()writes and returns (the same instance for the checker’s lifetime).
- query(position=None, orientation=None, half_extents=None, stream=None, *, ctx=None)[source]¶
Box-vs-disc contact for
E * max_boxesboxes.Bound mode (inputs bound at construction): call with no arguments. Per-call mode: pass all three arrays; under CUDA-graph capture the SAME arrays must be used at capture and every replay.
- Parameters:
stream – optional wp.Stream (or ‘torch’) to run on; by default the stream is resolved per track_gen.exec (an enclosing torch.cuda.graph capture is adopted, else Warp’s current stream).
ctx – optional
LaunchContexton the checker’s device to emit on (exclusive withstream=:TypeError).position (array | None)
orientation (array | None)
half_extents (array | None)
- Return type:
- class DiscContact[source]¶
Bases:
objectBatched box-vs-disc result, flat
[E * max_boxes]per field.Warning
DiscChecker.query()returns the SAME instance every call and overwrites its buffers in place;clone()for snapshots.- hit¶
int32— 1 iff any disc touches the box (distance <= radius).- Type:
wp.array
- disc¶
int32— index of the deepest-penetrating disc, -1 when none.- Type:
wp.array
- depth¶
float32— penetration depth of that disc (>= 0; 0 on graze or no hit). Saturates atradiusonce the disc center lies inside the box (ties broken by lowest disc index). Cf.BoxContact.distance, which reports a SIGNED clearance (positive margin / negative penetration) rather than an unsigned depth.- Type:
wp.array
- nearest¶
vec2f— point on that disc’s boundary nearest the box (approximate when the disc center lies deep inside the box). NaN when no hit or the box slot is inactive.- Type:
wp.array
- __init__(hit, disc, depth, nearest)¶
- Parameters:
hit (array)
disc (array)
depth (array)
nearest (array)
- Return type:
None
- classmethod __new__(*args, **kwargs)¶
Boundary props¶
Rendering-only instancing poses along track boundaries: cone lines
(mode="points") and wall pieces (mode="segments"). Props never collide. An
invalid env (Track.valid == 0) gets no props (count = 0), like an env with no
track.
Public boundary prop-sampling API: instancing poses along track boundaries.
PropSampler resamples the inner or outer boundary at a set spacing into a
PropSet of per-prop poses (position, tangent, yaw, length) for
rendering-only instancing — cone lines (mode="points") or wall pieces
(mode="segments"). The complement of track_gen.collision: these props
never collide; use CollisionChecker for out-of-bounds queries.
- class PropSampler[source]¶
Bases:
objectResample one track boundary into instancing poses at a set spacing.
One sampler binds one
Track, one boundary curve, one mode, and one spacing, so all output shapes are fixed andsample()is CUDA-graph capturable (allocation-free and host-sync-free). BecauseTrackGenerator.generate()overwrites itsTrackbuffers in place, everysample()reads the CURRENT batch — no rebind after regeneration. Props are rendering-only; they are not colliders. An env withvalid[e] == 0gets no props (count = 0, NaN slots: the sentinel of a poisoned env), whatever the source’son_invalid.PropSet.truncated/PropSet.steplive on the RESULT becausePropSampleris the ONLY producer of aPropSet— unlikeCheckpointSet, which is also produced zero-copy byCheckpointSet.from_gates(no sampling, so no truncation/step to report);track_gen.checkpoints.CheckpointSamplerkeeps its analogous diagnostics on the sampler instead, to avoid meaningless fields on gate-derived checkpoint sets.- topology = 'closed'¶
The topologies this component supports (closed loops only in 0.x).
- __init__(track, spacing, boundary='outer', mode='points', max_props=None)[source]¶
Bind to a
Trackboundary and allocate the result buffers.- Parameters:
track (HasBand) – the bound track batch;
sample()reads its buffers directly on every call (no rebind needed aftergenerate()).spacing (float) – target arc-length spacing between props (> 0); snapped per env so each ring closes without a seam (see module docstring).
boundary (str) –
"inner"or"outer"(default) — which track boundary curve to resample.mode (str) –
"points"(default, one pose per sample) or"segments"(one pose per chord between samples).max_props (int | None) – buffer capacity per env;
None(default) derives it from the CURRENT batch (see_derive_max_props: a construction-time readback, kept as a supported convenience). Must be >= 3 when given explicitly. The size-independent boundmax(3, ceil(max_loop_length / spacing))from the producer’s plan is the default of the component route:track_gen.tools.Props(spacing)attached throughComponentContext.for_producer(generator).
- Return type:
None
- property result: PropSet¶
The preallocated
PropSetsample()refreshes and returns (the same instance for the sampler’s lifetime).
- sample(stream=None, *, ctx=None)[source]¶
Resample the bound boundary; returns the preallocated
PropSet.Two kernel launches (scan + place); reads the Track buffers as they are NOW, so it reflects the latest
generate()with no rebind.- Parameters:
stream – optional wp.Stream (or ‘torch’) to run on; by default the stream is resolved per track_gen.exec (an enclosing torch.cuda.graph capture is adopted, else Warp’s current stream).
ctx – optional
LaunchContexton the sampler’s device to emit on (exclusive withstream=:TypeError).
- Return type:
- class PropSet[source]¶
Bases:
objectBatched prop poses, flat
[E * max_props]per pose field.Warning
PropSampler.sample()returns the SAMEPropSetinstance on every call and overwrites its buffers in place. Callclone()for a fully-owned snapshot.- position¶
vec3fpose positions on the (possibly lifted) boundary: on-curve sample (points mode) or chord midpoint (segments mode); z tracks the boundary elevation (0 on flat tracks). NaN pastcount[e].- Type:
wp.array
- tangent¶
vec2funit directions: curve tangent at the sample (points) or chord direction (segments). NaN pastcount[e].- Type:
wp.array
- yaw¶
float32heading,atan2(tangent.y, tangent.x).- Type:
wp.array
- length¶
float32per-prop extent: effective arc step (points mode) or chord length (segments mode). Both are measured along the (possibly lifted) boundary polyline: on a flat track this is a planar distance, and on a non-flatz_profiletrack it is the true 3D distance (the 3D chord, in segments mode) between samples that may sit at different altitudes.- Type:
wp.array
- count¶
[E]int32real prop counts (0 for degenerate and invalid envs:valid[e] == 0on the source).- Type:
wp.array
- truncated¶
[E]int32— 1 ifmax_propsclipped this env’s ring (the ring still closes, at a coarser effective spacing).- Type:
wp.array
- step¶
[E]float32effective arc spacingperimeter / count.- Type:
wp.array
- __init__(position, tangent, yaw, length, count, truncated, step)¶
- Parameters:
position (array)
tangent (array)
yaw (array)
length (array)
count (array)
truncated (array)
step (array)
- Return type:
None
- classmethod __new__(*args, **kwargs)¶
Checkpoints¶
Ordered course goals from gates (zero-copy) or subsampled track centerlines.
Public checkpoint API: ordered course goals from gates or tracks.
CheckpointSet is the shared contract consumed by
track_gen.progress.ProgressTracker. Build one zero-copy from a
GateSequence (CheckpointSet.from_gates) or by subsampling a track’s
centerline at a coarse spacing (CheckpointSampler — each checkpoint’s
crossing segment is the road cross-section, a “virtual gate”).
- class CheckpointSampler[source]¶
Bases:
objectResample a track’s centerline into coarse course checkpoints.
One sampler binds one
Trackand one spacing;sample()refreshes the ownedCheckpointSetin place from the CURRENT track batch (no rebind aftergenerate(); two kernel launches, allocation-free and host-sync-free, so CUDA-graph capturable as-is). Spacing is expected to be coarse — checkpoint counts similar to gate counts; nothing enforces this. Progress state bound to the sampled set must be reset after resampling a regenerated track.Producer diagnostics live on the sampler (not the set):
truncated([E]int32, 1 ifmax_checkpointsclipped the ring) andstep([E]float32 effective arc spacing). An env withvalid[e] == 0reads like a poisoned one (count = 0,stepNaN,truncated = 0, NaN slots, the set’svalid = 0). They live here rather than onCheckpointSetbecause the set has a SECOND, sampling-free producer (CheckpointSet.from_gates) that has no truncation or step concept — keeping the diagnostics on the sampler avoids meaningless fields on gate-derived sets (contrasttrack_gen.props.PropSet, whose only producer isPropSampler, so its diagnostics live on the result).- topology = 'closed'¶
The topologies this component supports (closed loops only in 0.x).
- __init__(track, spacing, max_checkpoints=None)[source]¶
Bind to a
Trackcenterline and allocate the result buffers.- Parameters:
track (HasBand) – the bound track batch;
sample()reads its centerline buffer directly on every call (no rebind needed aftergenerate()).spacing (float) – target arc-length spacing between checkpoints (> 0); snapped per env so each ring closes without a seam (same snap rule as
track_gen.props).max_checkpoints (int | None) – buffer capacity per env;
None(default) derives it from the CURRENT batch (see_derive_max_checkpoints: a construction-time readback, kept as a supported convenience). Must be >= 3 when given explicitly. The size-independent boundmax(3, ceil(max_loop_length / spacing))from the producer’s plan is the default of the component route (track_gen.tools.Checkpoints(spacing)attached throughComponentContext.for_producer(generator)) and ofCourse.
- Return type:
None
The track’s
generationis read when present (checked like the required members):sample()copies it (andvalid) into the set.
- property max_checkpoints: int¶
The per-env capacity (slots per env) of the
CheckpointSet(read-only).
- property result: CheckpointSet¶
The preallocated
CheckpointSetsample()refreshes and returns (the same instance for the sampler’s lifetime).
- sample(stream=None, *, ctx=None)[source]¶
Refresh the owned CheckpointSet from the bound Track; returns it.
- Parameters:
stream – optional wp.Stream (or ‘torch’) to run on; by default the stream is resolved per track_gen.exec (an enclosing torch.cuda.graph capture is adopted, else Warp’s current stream).
ctx – optional
LaunchContexton the sampler’s device to emit on (exclusive withstream=:TypeError).
- Return type:
- class CheckpointSet[source]¶
Bases:
objectOrdered per-env checkpoints, flat
[E * M]per field.The consumer contract for
track_gen.progress.ProgressTracker. Sets produced byfrom_gates()ALIAS the gate buffers (mutations to the gates are visible here); sets produced byCheckpointSamplerare owned by the sampler and refreshed in place bysample().- position¶
vec3fcheckpoint centers. NaN pastcount[e].- Type:
wp.array
- left¶
vec3fcrossing-segment endpoints (gate left / track inner).- Type:
wp.array
- right¶
vec3fcrossing-segment endpoints (gate right / track outer).- Type:
wp.array
- tangent¶
vec3funit forward (travel) directions. For gate-derived sets this is the gate’sforward(the physical gate-plane normal), not the 3D spline tangent.- Type:
wp.array
- up_half¶
[E * M]float32vertical half-openings for the crossing test:_BIG(unbounded) for track cross-sections, and the gate’shalf_sizefor gate-derived sets (aliasesGateSequence.half_size). NaN pastcount[e].- Type:
wp.array
- count¶
[E]int32real checkpoint counts (a sampler’s set: 0 for an invalid env).- Type:
wp.array
- valid¶
[E]int32validity of the course the rows were built from: owned byCheckpointSampler(written bysample(), socount, the rows,validandgenerationalways describe the same ring), the gates’ ownvalidforfrom_gates().Noneon a hand-built set (then every env is treated as valid).ProgressTrackerfreezes an env whosevalidis 0.- Type:
wp.array or None
- generation¶
[E]int32generation (epoch) of the course the rows were built from: owned byCheckpointSampler(written bysample(), 0 when its track has none), the gates’ owngenerationforfrom_gates().ProgressTrackerresets an env’s state when it changes (self-reset).None: never.- Type:
wp.array or None
- classmethod from_gates(seq)[source]¶
Zero-copy view of a
GateSequenceas checkpoints.Aliases (does not copy)
position,left,right,forward(astangent: the physical gate-plane normal),half_size(asup_half),countand, when the source has them,validandgeneration.seqis anyHasGateFrameswith those members, checked here (track_gen.results.check_source;valid/generationare checked when present). AProgressTrackeron the set resets an env’s state by itself when itsgenerationchanges (a regeneration withadvance); reset it explicitly after a reseed (seeProgressTracker).- Parameters:
seq (HasGateFrames)
- Return type:
- clone()[source]¶
Return a deep copy whose Warp buffers do not alias this set (
valid/generationtoo, when set).- Return type:
- __init__(position, left, right, tangent, up_half, count, valid=None, generation=None)¶
- Parameters:
position (array)
left (array)
right (array)
tangent (array)
up_half (array)
count (array)
valid (array | None)
generation (array | None)
- Return type:
None
- classmethod __new__(*args, **kwargs)¶
Progress tracking¶
Stateful per-env course progress over any CheckpointSet.
Public progress-tracking API: ordered course progress + reward signals.
ProgressTracker consumes any CheckpointSet (gate sequences via
CheckpointSet.from_gates, or subsampled track centerlines via
CheckpointSampler) and emits per-step ProgressEvents: pass events,
laps, wrong-way / wrong-checkpoint flags, and dist_to_next — difference
it across steps for the classic negative-delta-distance reward.
- class ProgressTracker[source]¶
Bases:
objectTrack ordered progress of one agent per env through a CheckpointSet.
See the module docstring for semantics. Construct with
position=(a stable[E]vec3f wp.array owned by your sim) for bound mode —update()then takes no arguments and reads the buffer in place; or leave unbound and pass positions per call. Mixing modes raisesValueError.- __init__(checkpoints, position=None)[source]¶
Bind to a
CheckpointSetand allocate the tracker’s state.- Parameters:
checkpoints (CheckpointSet) – the ordered per-env checkpoint set to track against (from
CheckpointSet.from_gatesorCheckpointSampler.sample(); anyHasCheckpoints). Aliased, not copied — later mutation (e.g. a resampled or regenerated set) is seen on the nextupdate(). Itsvalid/generationare read when present ([E]int32 on the set’s device, checked here): invalid envs are frozen, and an env resets by itself when itsgenerationchanges. The state of that check starts at the set’sgenerationnow (a device copy on the call’s stream: the ambient context or Warp’s current stream), so the firstupdate()does not reset.position (array | None) – optional stable
[E]vec3f buffer to bind at construction (bound mode); equivalent to callingbind()right after construction.
- Return type:
None
- property result: ProgressEvents¶
The preallocated
ProgressEventsupdate()writes and returns (the same instance for the tracker’s lifetime).
- property next_checkpoint_state: array¶
Aliased read-only view of the
[E]int32 current-target state.Exposes the tracker’s live
_nextbuffer (updated in place by everyupdate()) for consumers that window their queries around the current target checkpoint (e.g.FrameChecker.bind_window). Do NOT mutate — the tracker owns this state.
- bind(position)[source]¶
Bind (or rebind) a stable
[E]vec3f position buffer.After binding,
update()takes no arguments and reads the buffer in place. Validation happens here, once; the array must keep the same.ptrfor the binding’s lifetime (CUDA-graph contract): replacing it afterupdate()was recorded into a capture warns (RebindAfterCaptureWarning).- Parameters:
position (array)
- Return type:
None
- update(position=None, stream=None, *, ctx=None)[source]¶
Advance one step; returns the tracker’s preallocated events.
Bound mode (constructed with
position=): call with no arguments. Per-call mode: pass the[E]vec3f position array — the SAME array (identical.ptr) must be used across a CUDA-graph capture and its replays.NaN-position semantics: a NaN
position[e]PAUSES envefor this step rather than erroring — no crossing/wrong-way/wrong-checkpoint event can fire (the crossing test always misses against a NaN endpoint),dist_to_next[e]is NaN, and the NaN gets written intoprev_pos[e], which re-arms the same sentinelreset()uses: the FIRST update after a finite position resumes also reports no event (it only re-primesprev_pos), and normal crossing detection resumes the update after that. Useful for envs mid-teleport or awaiting a fresh spawn without a fullreset().- Parameters:
position (array | None) –
[E]vec3f positions (per-call mode only).stream – optional wp.Stream (or ‘torch’) to run on; by default the stream is resolved per track_gen.exec (an enclosing torch.cuda.graph capture is adopted, else Warp’s current stream).
ctx – optional
LaunchContexton the tracker’s device to emit on (exclusive withstream=:TypeError).
- Return type:
- reset(mask, stream=None, *, ctx=None)[source]¶
Clear state where
mask[e] == 1([E]int32); arms the NaN previous-position sentinel so the next update cannot emit a spurious crossing. Required after a reseed or an epoch roll-back of the bound course, and on respawns; after a regeneration that changes an env’sgenerationthe tracker resets it by itself (byte-identical to this call).- Parameters:
mask (array) –
[E]int32 reset mask.stream – optional wp.Stream (or ‘torch’) to run on; by default the stream is resolved per track_gen.exec (an enclosing torch.cuda.graph capture is adopted, else Warp’s current stream).
ctx – optional
LaunchContexton the tracker’s device to emit on (exclusive withstream=:TypeError).
- Return type:
None
- class ProgressEvents[source]¶
Bases:
objectPer-env progress events, all
[E]; overwritten in place per update.Warning
ProgressTracker.update()returns the SAME instance every call.clone()for snapshots.- passed¶
int32— 1 iff the target checkpoint was crossed forward this step.- Type:
wp.array
- checkpoint_passed¶
int32— index of the checkpoint passed this step, -1 otherwise.- Type:
wp.array
- next_checkpoint¶
int32— current target AFTER any advance this step.- Type:
wp.array
- laps¶
int32— completed laps.- Type:
wp.array
- progress¶
int32— total checkpoints passed since construction/reset.- Type:
wp.array
- wrong_way¶
int32— 1 iff the target was crossed BACKWARD this step.- Type:
wp.array
- wrong_checkpoint¶
int32— index of a non-target checkpoint crossed this step (either direction; first in index order), -1 otherwise.- Type:
wp.array
- dist_to_next¶
float32—|position - next checkpoint center|after any advance. NaN for a (valid) env with no checkpoints; 0 for an invalid env (frozen).- Type:
wp.array
- __init__(passed, checkpoint_passed, next_checkpoint, laps, progress, wrong_way, wrong_checkpoint, dist_to_next)¶
- Parameters:
passed (array)
checkpoint_passed (array)
next_checkpoint (array)
laps (array)
progress (array)
wrong_way (array)
wrong_checkpoint (array)
dist_to_next (array)
- Return type:
None
- classmethod __new__(*args, **kwargs)¶
Track-frame localization¶
Per-env (s, n) centerline projection with an optional warm start, plus the
per-generation curvature and speed-profile helpers.
Public track-frame localization API: (s, n) projection + speed hints.
TrackLocalizer projects one point per env onto the bound Track’s
centerline and returns a TrackFrame: arc length s, signed
lateral offset n (positive to the RIGHT of the direction of travel;
which boundary that is depends on the loop’s generator-dependent winding),
and the nearest segment index — the Frenet-style frame
racing controllers and reward shapers consume every sim step. An optional
warm start (warm_window=) narrows each scan to a window around the
previous result; reset(mask) drops that memory after regenerations and
teleports.
curvature() and speed_profile() are per-generation companions:
per-point signed centerline curvature and the curvature-limited target
speed (steady-state lateral limit, then forward-acceleration and
backward-braking passes over the closed loop).
- class TrackLocalizer[source]¶
Bases:
objectProject one point per env onto the bound Track’s centerline.
Because
TrackGenerator.generate()overwrites itsTrackbuffers in place,query()always reads the CURRENT track batch — but a warm-started localizer remembers segment indices of the OLD geometry. When the source hasgeneration(aTrack, aCourseLinecenterline) the localizer drops that memory by itself for every env whosegenerationchanged; otherwise, and after a reseed or an epoch roll-back (which may leavegenerationunchanged), callers MUSTreset()the affected envs (cold-scan localizers,warm_window=None, need nothing).query()is allocation-free and host-sync-free (CUDA-graph capturable); its launches are stream-ordered, so a consumer on another stream must order itself explicitly.query()returns the same preallocatedTrackFrameon every call.Warm vs cold results are IDENTICAL whenever the nearest centerline segment stays within
warm_windowsegments of the previous query’s result — the usual regime (per-step motion small againstwarm_window * spacing). Small motion alone does NOT guarantee it: where the loop pinches close to itself, the nearest segment can jump half a lap for arbitrarily small motion, and the warm result then stays on the traveled branch (often preferable for racing consumers — s stays continuous — but not the cold answer). Outside the contract (teleports, huge steps), a warm query may likewise lock onto a local minimum; callreset()on the affected envs to force a full rescan.- topology = 'closed'¶
The topologies this component supports (closed loops only in 0.x).
- __init__(track, warm_window=None, position=None)[source]¶
Bind to a
Trackbatch and allocate the result buffers.- Parameters:
track (HasCenterline) – the bound track batch;
query()reads its centerline buffers directly on every call.warm_window (int | None) –
None(default) scans all segments every query; an int >= 1 scans only the2 * warm_window + 1segments centered on the previous result (per env; the first query after construction orreset()is always a full scan).position (array | None) – optional stable
[E]vec3f buffer to bind at construction (bound mode); equivalent to callingbind()right after construction.
- Return type:
None
The source’s
validandgenerationare read when present (checked like the required members): invalid envs get the poisoned-env frame, and an env’s warm memory is dropped when itsgenerationchanges (the check starts from the source’sgenerationnow: a device copy on the call’s stream, the ambient context or Warp’s current stream).
- bind(position)[source]¶
Bind (or rebind) a stable
[E]vec3f position buffer.After binding,
query()takes no arguments and reads the buffer in place. Validation happens here, once; the array must keep the same.ptrfor the binding’s lifetime (CUDA-graph contract): replacing it afterquery()was recorded into a capture warns (RebindAfterCaptureWarning).- Parameters:
position (array)
- Return type:
None
- property result: TrackFrame¶
The preallocated
TrackFramequery()writes and returns (the same instance for the localizer’s lifetime).
- query(position=None, stream=None, *, ctx=None)[source]¶
Localize the batch; returns the localizer’s preallocated frame.
Bound mode (constructed with
position=or afterbind()): call with no arguments. Per-call mode: pass the[E]vec3f position array — the SAME array (identical.ptr) must be used across a CUDA-graph capture and its replays.NaN
position[e]yields NaNs/nandsegment = -1for that env, and drops its warm-start memory (the next finite query does a full scan) — safe for envs mid-teleport or awaiting respawn.- Parameters:
position (array | None) –
[E]vec3f positions (per-call mode only).stream – optional wp.Stream (or ‘torch’) to run on; by default the stream is resolved per track_gen.exec (an enclosing torch.cuda.graph capture is adopted, else Warp’s current stream).
ctx – optional
LaunchContexton the localizer’s device to emit on (exclusive withstream=:TypeError).
- Returns:
The localizer’s preallocated
TrackFrame(same instance every call; buffers overwritten in place).- Raises:
ValueError – on shape/dtype/device mismatch, or on mode misuse (passing a position while bound, or omitting it while not bound).
- Return type:
- reset(mask, stream=None, *, ctx=None)[source]¶
Drop warm-start memory where
mask[e]is nonzero ([E]int32).The next query for those envs performs a full centerline scan. Required, when
warm_windowis set, after regenerating the bound track without agenerationchange the localizer can see (a source withoutgeneration, a reseed, an epoch roll-back), and for any env that teleports further than the warm window covers. A no-op for cold-scan localizers (but valid to call, so regeneration code need not special-case).- Parameters:
mask (array) –
[E]int32 reset mask.stream – optional wp.Stream (or ‘torch’) to run on; by default the stream is resolved per track_gen.exec (an enclosing torch.cuda.graph capture is adopted, else Warp’s current stream).
ctx – optional
LaunchContexton the localizer’s device to emit on (exclusive withstream=:TypeError).
- Return type:
None
- class TrackFrame[source]¶
Bases:
objectPer-env track-frame coordinates, all
[E]; overwritten per query.Warning
TrackLocalizer.query()returns the SAME instance every call.clone()for snapshots.- s¶
float32— arc length along the centerline at the projected point, in[0, Track.length[e]), same units as the track coordinates. NaN for NaN positions, degenerate tracks (count < 3) and invalid envs (valid[e] == 0on a source withvalid).- Type:
wp.array
- n¶
float32— signed lateral offset from the centerline: positive to the RIGHT of the centerline’s direction of travel (the direction of increasings), negative to the left. Whether the right side is the outer or the inner boundary depends on the loop’s winding, which is generator-dependent (CCW loops haveouteron the right, CW loops on the left);|n| <= wmeans on the road either way, wherewisTrack.half_widthinterpolated at the foot point betweensegmentandsegment + 1(at constant width,width.constant, the configuredhalf_width). NaN for NaN positions, degenerate tracks and invalid envs.- Type:
wp.array
- n_up¶
float32— signed vertical offset in the roll-free frame at the foot point; equalsposition.zfor planar tracks. NaN for NaN positions, degenerate tracks and invalid envs.- Type:
wp.array
- segment¶
int32— indexiof the nearest centerline segment (from pointitoi + 1, the closing segment beingcount[e] - 1 -> 0). -1 for NaN positions, degenerate tracks and invalid envs.- Type:
wp.array
- __init__(s, n, n_up, segment)¶
- Parameters:
s (array)
n (array)
n_up (array)
segment (array)
- Return type:
None
- classmethod __new__(*args, **kwargs)¶
- curvature(track, window=2, ctx=None, *, out=None, scratch=None)[source]¶
Per-point signed centerline curvature, flat
[E * N_max]float32.Discrete curvature at point
i: the turn angle between the incoming and outgoing centerline edges over the mean incident edge length,atan2(cross(v1, v2), dot(v1, v2)) / max(0.5 * (|v1| + |v2|), eps), then a wrap-aware moving average over2 * window + 1points. Units are 1 / track units. The sign follows the loop orientation: positive where the centerline turns counter-clockwise — a CCW circle of radiusRgiveskappa ~ +1/Reverywhere. NaN pastcount[e], for degenerate envs (count[e] < 3) and, when the source hasvalid, for invalid envs (valid[e] == 0: the row of a poisoned env).On a flat track (the default
z_profile)v1/v2are planar edge vectors and this is exactly the familiar 2D signed curvature. On a lifted track (a non-flatz_profile),centercarries real per-point altitude, sov1/v2are full 3D edge vectors, and the formula picks up grade in two ways: the numerator (cross(v1, v2)[2]) is still exactly the PLAN-VIEW cross product (unaffected by z on its own), but the denominator — bothdot(v1, v2)inside theatan2and thedsnormalizer — gains a z contribution. Net effect: a genuine plan-view turn taken on a graded section reports a slightly SMALLER magnitude than the same turn taken flat (the added 3D edge length dilutes it); and a perfectly straight-in-plan-view section with a steep enough grade reversal (a crest or dip sharp enough to flip the sign ofdot(v1, v2)) can register as if it were a sharp turn even though the centerline never bends in plan view. Mild grade changes (no sign flip in the dot product) still read as zero curvature, same as flat.Without
outthis ALLOCATES its result (and a scratch whenwindow > 0): call it aftergenerate(), outside capture regions. Withoutit writes the caller’s arrays: allocation-free, host-sync-free and recordable into a capture (e.g. once per regeneration, next to the regeneration in a graph), with bytes identical to the allocating form.- Parameters:
track (HasCenterline) – the track batch to read the centerline from (any
HasCenterline; itsvalidis read when present).window (int) – moving-average half-width in points (>= 0); 0 disables smoothing. Default 2 (a 5-tap average).
ctx – optional track_gen.exec.LaunchContext; default resolves the device’s stream (see track_gen.exec).
out (array | None) – optional contiguous
[E * N_max]float32 array on the track’s device to write (and return).scratch (array | None) – with
outandwindow > 0: a second contiguous[E * N_max]float32 array (the unsmoothed curvature), not overlappingout; ignored whenwindow == 0.
- Returns:
out, or a NEW flat[E * N_max]float32 wp.array of signed curvatures on the track’s device (owned by the caller; not refreshed by latergenerate()calls).- Raises:
ValueError – on
window < 0, an invalid track batch layout (or a malformedvalid), anout/scratchof the wrong shape, dtype, device or layout, a missingscratch(outwithwindow > 0), or ascratchoverlappingout.- Return type:
array
- speed_profile(track, a_lat_max, a_accel, a_brake, v_cap, kappa=None, window=2, ctx=None, *, out=None)[source]¶
Curvature-limited target speed per centerline point,
[E * N_max].Three stages, batched per env (one Warp thread per env; the passes are sequential by nature): the steady-state limit
min(sqrt(a_lat_max / |kappa|), v_cap), a forward pass capping acceleration out of corners (v[i+1]^2 <= v[i]^2 + 2 * a_accel * ds), and a backward pass capping braking into them (v[i]^2 <= v[i+1]^2 + 2 * a_brake * ds) — each run twice around the closed loop so the wrap converges. Speeds are in track units per second when the accelerations are in track units per second squared.ds(fromTrack.arclen/Track.length) is the true 3D segment length on a lifted (non-flatz_profile) track, so the accel/brake ramps size their distance budget off the actual climbing/descending path length, not its plan-view projection. The corner-speed limit still comes fromkappa(curvature()’s default, if not passed in), which on a lifted track can be perturbed by grade as documented there — this function adds no further elevation-specific handling of its own.Without
outthis ALLOCATES its result (and the curvature whenkappais not given): call it aftergenerate(), outside capture regions. Without(kapparequired, e.g. fromcurvature(track, out=k, scratch=s)) it writes the caller’s array: allocation-free, host-sync-free and recordable, with bytes identical to the allocating form.- Parameters:
track (HasCenterline) – the track batch; segment lengths come from
Track.arclenandTrack.length(anyHasCenterline; itsvalidis read when present).a_lat_max (float) – maximum lateral acceleration (> 0).
a_accel (float) – maximum forward acceleration (>= 0; 0 pins the profile to each point’s corner speed going forward).
a_brake (float) – maximum braking deceleration (>= 0).
v_cap (float) – straight-line speed cap (> 0).
kappa (array | None) – optional precomputed flat
[E * N_max]float32 curvature (e.g. fromcurvature(), possibly reused across several profiles).None(default) computes it internally (not without).window (int) – smoothing half-width forwarded to
curvature()whenkappais None; ignored otherwise.ctx – optional track_gen.exec.LaunchContext; default resolves the device’s stream (see track_gen.exec).
out (array | None) – optional contiguous
[E * N_max]float32 array on the track’s device to write (and return); must not overlapkappa.
- Returns:
out, or a NEW flat[E * N_max]float32 wp.array of target speeds on the track’s device, NaN pastcount[e], for degenerate envs (count[e] < 3) and, when the source hasvalid, for invalid envs.- Raises:
ValueError – on a non-positive
a_lat_max/v_cap, a negativea_accel/a_brake, akappashape/dtype/device mismatch, an invalid track batch layout (or a malformedvalid), anoutof the wrong shape, dtype, device or layout,outwithoutkappa, oroutoverlappingkappa.- Return type:
array
Heightfields¶
A per-env road-surface height grid, baked after every generation.
Public heightfield API: per-env road height grids for external physics solvers.
HeightFieldBaker bakes each env’s road surface (from a bound Track
batch) into a square HeightField height grid, for consumers that want
a grid rather than a polyline. Normally driven indirectly through
track_gen.course.Course (set CourseConfig.heightfield_resolution,
track mode only) which builds and re-bakes it on every generate() and
exposes it as course.heightfield.
- class HeightField[source]¶
Bases:
objectBaked per-env height grid; overwritten in place by
bake().- heightwp.array
[E * res * res]float32 road-surface heights (NaN for invalid envs). Reshape via.numpy().reshape(E, res, res)(row-major:[e, y, x]).- lo, hiwp.array
[E]vec2f world AABB corners; texel (x, y) center is atlo + ((x, y) + 0.5) / res * (hi - lo).(-1, -1)/(1, 1)for degenerate and invalid envs.- resint
Grid resolution per side.
- height: array¶
- lo: array¶
- hi: array¶
- class HeightFieldBaker[source]¶
Bases:
objectBake the bound track batch into a per-env heightfield.
Lifecycle mirrors the SDF baker: construct once (allocates),
bake()after every regeneration (kernel launches only; the facade does this in its captured refresh whenCourseConfig.heightfield_resolutionis set). An env withvalid[e] == 0bakes NaN heights with the box(-1, -1)-(1, 1): the bake of a poisoned env.- topology = 'closed'¶
The topologies this component supports (closed loops only in 0.x).
- property result: HeightField¶
The preallocated
HeightFieldbake()refreshes and returns (the same instance for the baker’s lifetime).
- bake(stream=None, *, ctx=None)[source]¶
Re-bake from the CURRENT track batch; returns the same instance.
- Parameters:
stream – optional wp.Stream (or ‘torch’) to run on; by default the stream is resolved per track_gen.exec (an enclosing torch.cuda.graph capture is adopted, else Warp’s current stream).
ctx – optional
LaunchContexton the baker’s device to emit on (exclusive withstream=:TypeError).
- Return type: