API reference

Runtime facades

class TrackGenerator[source]

Bases: object

Top-level facade: run the pure-Warp track-generation pipeline and return a Track.

Generation, resample, relaxation and inflation are all expressed as NVIDIA Warp kernels (via _run_pipeline), runnable on the Warp cpu and cuda devices. config.generator must be a registered generator (see generator_registry.available()).

The generator is fixed-batch: it always operates on exactly config.num_envs environments. The output Track is pre-allocated once in __init__ (via _inflate_warp_alloc) and reused across calls: generate() always returns the same self._track instance, writing new results into the same wp.array buffers in place. This ensures stable .ptr pointers so downstream CUDA graph consumers can bake the device addresses once. Use Track.clone() to obtain an independent snapshot.

On a CUDA device the pipeline is auto-captured into a wp.Graph on the first generate() call and replayed on every subsequent call. On the Warp cpu device the pipeline runs eagerly.

Only relax_solver="xpbd" and smooth_finish=False are supported; construction raises AssertionError for other combinations.

__init__(config, rng)[source]
Parameters:
  • config (TrackGenConfig) – The pipeline configuration. config.generator must be a registered generator (see generator_registry.available()).

  • rng – A PerEnvSeededRNG instance; its per-env seed values seed the pipeline’s built-in Warp RNG (one base seed per env).

Raises:
  • ValueError – if rng is None.

  • AssertionError – if config.relax_solver is not "xpbd", or if config.smooth_finish is True.

Return type:

None

generate(num_or_ids=None)[source]

Generate a batch of tracks for the fixed configured batch.

This generator is fixed-batch: it always operates on exactly config.num_envs environments. If integer num_or_ids is provided it is validated against the configured batch size and a ValueError is raised if they disagree (so existing call sites passing E == num_envs continue to work unchanged). Explicit environment-id sequences are rejected with TypeError because selected-env execution is not part of the fixed-batch graph-captured contract.

On the first CUDA call: warms up kernel loading then captures the pipeline into a wp.Graph (via wp.ScopedCapture), then immediately replays it. On subsequent CUDA calls: writes new seeds into the seed buffer in place and replays the graph. On cpu: runs _run() eagerly every call.

Writes results into self._track in place and returns the SAME instance every call (stable .ptr pointers). Use Track.clone() to obtain an independent copy.

Determinism: generate() re-copies the rng’s CURRENT seeds each call, so repeated calls with an unchanged rng return the IDENTICAL batch. To vary the batch between calls, reseed the rng first (e.g. rng.set_seeds_warp(new_seeds, None)).

Parameters:

num_or_ids – Optional. Either None or the integer batch size. When an integer is provided, it must equal config.num_envs. Explicit environment-id sequences are not supported by this fixed-batch, graph-capturable facade.

Returns:

self._track — the same Track instance every call (stable pointers).

Raises:
  • TypeError – if num_or_ids is an explicit environment-id sequence.

  • ValueError – if an integer num_or_ids differs from config.num_envs.

Return type:

Track

class GateGenerator[source]

Bases: object

Facade for fixed-batch native gate sequence generation.

Mirrors the fixed-batch contract of TrackGenerator: construction allocates persistent output and scratch buffers once (via warp_gate._gate_warp_alloc), and generate() refreshes the per-env seed buffer then writes results into the same GateSequence instance on every call (stable .ptr pointers). Use GateSequence.clone() when an independent snapshot is needed.

On a CUDA device the gate pipeline is auto-captured into a wp.Graph on the first generate() call and replayed on subsequent calls. On the Warp cpu device the pipeline runs eagerly.

__init__(config, rng)[source]
Parameters:
  • config (GateGenConfig) – The gate generation configuration. config.generator must be a registered gate generator; config.gate_ordering must be supported by that generator; and config.max_gates must satisfy the generator’s capacity requirements.

  • rng – A PerEnvSeededRNG instance with one seed per configured env.

Raises:
  • ValueError – if rng is None.

  • ValueError – if the number of seeds in rng does not match config.num_envs.

  • ValueError – if config.gate_ordering is not supported by the selected gate generator.

  • ValueError – if config.min_gates exceeds the maximum gate count the generator can produce, or if config.max_gates is too small for the generator’s required capacity.

Return type:

None

generate(num_or_ids=None)[source]

Generate gates for the fixed configured batch.

GateGenerator always operates on exactly config.num_envs environments. Passing an integer is accepted only when it matches that batch size. Explicit environment-id sequences are rejected because selected-env execution is outside the fixed-batch graph-capturable contract.

Writes results into self._gate_sequence in place and returns the SAME instance every call (stable .ptr pointers). Use GateSequence.clone() to obtain an independent snapshot.

Parameters:

num_or_ids – Optional. Either None or the integer batch size. When an integer is provided, it must equal config.num_envs. Explicit environment-id sequences are not supported.

Returns:

self._gate_sequence — the same GateSequence instance every call (stable pointers).

Raises:
  • TypeError – if num_or_ids is not an int (i.e. an explicit environment-id sequence was passed).

  • ValueError – if an integer num_or_ids differs from config.num_envs.

Return type:

GateSequence

Seeded RNG

class PerEnvSeededRNG[source]

Bases: object

Per-environment seeded random number generator backed by NVIDIA Warp kernels.

Each of the num_envs environments owns an independent RNG state so that parallel sampling yields reproducible, environment-diverse results. A scalar seeds int is expanded to seed + arange(num_envs) (diversity expansion): because Warp seeds its state purely from the seed value, a flat broadcast would make every environment identical; the offset guarantees each env starts from a distinct stream. Pass a wp.array of int32 to control every env’s seed explicitly.

After construction the states are immediately initialised from the seeds, so no separate set_seeds_warp call is required before the first sample.

__init__(seeds, num_envs, device)[source]

Initialize the random number generator.

Parameters:
  • seeds (int | array) – Per-env seeds. An int scalar seeds the whole BATCH: it expands to distinct per-env seeds seed + arange(num_envs) so the batch is reproducible AND diverse. (Each env’s RNG state is wp.rand_init(seed) with no env-index folding, so identical per-env seeds would yield identical tracks — hence the expansion.) Pass a wp.array of int32 to control every env’s seed explicitly.

  • num_envs (int) – The number of environments.

  • device (str) – The device to use.

property seeds_warp: array

The [num_envs] int32 Warp array of per-environment base seeds.

property states_warp: array

The [num_envs] uint32 Warp array of live per-environment RNG states.

static to_tuple(shape)[source]

Casts to a tuple.

Parameters:

shape (int | tuple[int])

Return type:

tuple

set_seeds_warp(seeds, ids)[source]

Set the seeds for each environment. :param seeds: The seeds for each environment. :param ids: The ids of the environments.

Parameters:
  • seeds (array)

  • ids (array | None)

Return type:

None

sample_uniform_warp(low, high, shape, ids=None)[source]

Sample from a uniform distribution. Warp implementation.

If low and high are arrays, their shapes need to match that of the ids.

Parameters:
  • low (float | array) – The lower bound of the distribution.

  • high (float | array) – The upper bound of the distribution.

  • shape (tuple | int) – The shape of the output tensor.

  • ids (array | None) – The ids of the environments.

Returns:

The sampled values.

Return type:

array

sample_sign_warp(dtype, shape, ids=None)[source]

Sample a sign. Warp implementation. :param dtype: The data type of the output tensor. :param shape: The shape of the output tensor. :param ids: The ids of the environments.

Returns:

The sampled values.

Parameters:
Return type:

array

sample_integer_warp(low, high, shape, ids=None)[source]

Sample for a random integer. Warp implementation. :param low: The lower bound of the distribution. :param high: The upper bound of the distribution. :param shape: The shape of the output tensor. :param ids: The ids of the environments.

Returns:

The sampled values.

Return type:

wp.array

Parameters:
  • low (int | array)

  • high (int | array)

  • shape (tuple | int)

  • ids (array | None)

sample_normal_warp(mean, std, shape, ids=None)[source]

Sample from a normal distribution. Warp implementation. :param mean: The mean of the distribution. :param std: The standard deviation of the distribution. :param shape: The shape of the output tensor. :param ids: The ids of the environments.

Returns:

The sampled values.

Return type:

wp.array

Parameters:
sample_poisson_warp(lam, shape, ids=None)[source]

Sample from a poisson distribution. Warp implementation. :param lam: The rate of the distribution. :param shape: The shape of the output tensor. :param ids: The ids of the environments.

Returns:

The sampled values.

Parameters:
Return type:

array

sample_quaternion_warp(shape, ids=None)[source]

Sample a quaternion. Warp implementation. :param shape: The shape of the output tensor. :param ids: The ids of the environments.

Returns:

The sampled values.

Return type:

wp.array

Parameters:
  • shape (tuple | int)

  • ids (array | None)

Result types

class Track[source]

Bases: object

Final batched result of the track generation pipeline.

All boundary arrays are index-aligned: outer[i], center[i] and inner[i] share a single cross-section normal. Half-width is not stored; recover it as the outer-center norm along dim=-1. Fields are wp.array; convert at the boundary via the wp bridge.

Warning

TrackGenerator.generate() returns the SAME Track instance on every call and overwrites its buffers in place. A reference held across two generate() calls will see mutated data. Call Track.clone() to obtain a fully-owned deep copy before the next call.

outer

Flat [E * N_max] vec3f outer boundary points. Each real point’s z follows the configured TrackGenConfig.z_profile — 0 only for the default "flat" profile at z_base=0 — and shares the center point’s altitude at the same index (level cross-sections; see 2.5D tracks). Reshape via wp.to_torch(...).view(E, N_max, 3). Points at i >= count[e] are NaN-padded (NaN xy; z carries the flat path’s z_base, 0 by default).

Type:

wp.array

center

Flat [E * N_max] vec3f centerline points. z follows the configured TrackGenConfig.z_profile (see outer). Reshape via wp.to_torch(...).view(E, N_max, 3). Index-aligned with outer and inner; ‖outer[i] - center[i]‖ gives the per-point half-width (an XY-plane distance: level cross-sections share a Z, so the vertical component cancels). Points at i >= count[e] are NaN-padded.

Type:

wp.array

inner

Flat [E * N_max] vec3f inner boundary points. z follows the configured TrackGenConfig.z_profile (see outer). Reshape via wp.to_torch(...).view(E, N_max, 3). Points at i >= count[e] are NaN-padded.

Type:

wp.array

tangent

Flat [E * N_max] vec3f unit tangent vectors at each centerline point. For the default "flat" profile this is the planar tangent (z = 0) from central differences over the 2D centerline; for a non-flat z_profile it is instead a true 3D central difference over the LIFTED centerline (see 2.5D tracks). NaN-padded past count[e].

Type:

wp.array

normal

Flat [E * N_max] vec3f unit left-normals at each centerline point — still the planar left-normal, always z = 0, computed as normal = (-tangent2d.y, tangent2d.x, 0) (the PLAN-VIEW tangent rotated +90° about +z) BEFORE the elevation lift. For a non-flat z_profile this predates the 3D recompute described under tangent: the two fields are no longer literally related by that rotation formula (normal’s XY direction still matches tangent’s XY projection, but tangent is unit-length in 3D, so its XY projection alone is sub-unit and rotating it will not reproduce a unit normal). Which boundary it faces is winding-dependent (see winding): it points toward outer for clockwise loops and toward inner for counter-clockwise loops. The outer/inner split itself is winding-agnostic (assigned by signed-area magnitude), so use outer/inner — not the normal’s sign — to identify the boundaries. NaN-padded past count[e].

Type:

wp.array

arclen

Flat [E * N_max] float32 cumulative arc length along the centerline at each point. For the default "flat" profile this is the plan-view (2D) arc length; for a non-flat z_profile it is the true 3D arc length of the LIFTED centerline (strictly >= the plan-view length whenever the profile is non-constant; see 2.5D tracks). Reshape via wp.to_torch(...).view(E, N_max). NaN-padded past count[e].

Type:

wp.array

length

[E] float32 total arc length (perimeter) of each track’s centerline loop — plan-view for the default "flat" profile, true 3D length otherwise (see arclen).

Type:

wp.array

valid

[E] int32 environment validity flags (0 or 1). A track is valid when: turning number ≈ 1, no NaN points, width floor exceeded everywhere, minimum thickness ≥ (1 − relax_tol) * half_width, and (when validity_border_check=True) zero border self-intersections. Convert via wp.to_torch(...).bool().

Type:

wp.array

count

[E] int32 real point counts, derived from constant-spacing resampling as count[e] = floor(perimeter / spacing) + 1, capped at N_max. All other arrays are NaN-padded for indices i >= count[e].

Type:

wp.array

winding

[E] float32 signed loop winding: +1.0 for a counter-clockwise centerline, -1.0 for clockwise (the sign of the loop’s signed area), and 0.0 for degenerate/empty loops. Winding is generator-dependent (bezier and hull wind clockwise; polar, voronoi and checkpoint wind counter-clockwise), so this field lets a consumer orient itself: for a CCW loop outer lies to the right of increasing arc length, for a CW loop to the left.

Type:

wp.array

clone()[source]

Return a fully-owned deep copy of this Track.

Each field is cloned via wp.clone (torch-free), so the returned Track owns independent buffers unaffected by future generate() calls that overwrite this instance in place.

Return type:

Track

__init__(outer, center, inner, tangent, normal, arclen, length, valid, count, winding)
Parameters:
  • outer (array)

  • center (array)

  • inner (array)

  • tangent (array)

  • normal (array)

  • arclen (array)

  • length (array)

  • valid (array)

  • count (array)

  • winding (array)

Return type:

None

classmethod __new__(*args, **kwargs)
class GateSequence[source]

Bases: object

Batched fixed-stride gate result returned by GateGenerator.

Gate pose arrays are flat [E * max_gates] vec3f buffers; reshape via wp.to_torch(...).view(E, max_gates, 3). count[e] gives the real gate count for environment e and slots i >= count[e] are NaN-padded (any NaN component marks a padded slot). Each real slot’s z follows the configured GateGenConfig.z_profile; it is 0 only for the default "flat" profile at z_base=0.

Warning

GateGenerator.generate() returns the SAME GateSequence instance on every call and overwrites its buffers in place. A reference held across two generate() calls will see mutated data. Call GateSequence.clone() to obtain a fully-owned deep copy before the next call.

position

Flat [E * max_gates] vec3f gate centres. Reshape via wp.to_torch(...).view(E, max_gates, 3). Slots i >= count[e] are NaN-padded.

Type:

wp.array

tangent

Flat [E * max_gates] vec3f unit tangent vectors at each gate centre. NaN-padded past count[e].

Type:

wp.array

forward

Flat [E * max_gates] vec3f unit pose forward (x-axis of orientation): the physical gate-plane normal used for pass detection; equals tangent for full_tangent and for planar yaw_only gates, the horizontal projection of tangent for sloped yaw_only gates; NaN-padded past count[e].

Type:

wp.array

orientation

Flat [E * max_gates] quatf gate poses (wp.quatf(x, y, z, w)): the roll-free frame with local x = gate forward, y = left, z = up. NaN-padded past count[e].

Type:

wp.array

half_size

Flat [E * max_gates] float32 square-opening half-extents (0.5 * gate_width). NaN-padded past count[e].

Type:

wp.array

left

Flat [E * max_gates] vec3f left gate endpoints (position + half_size * left_axis). NaN-padded past count[e].

Type:

wp.array

right

Flat [E * max_gates] vec3f right gate endpoints (position - half_size * left_axis). NaN-padded past count[e].

Type:

wp.array

valid

[E] int32 environment validity flags (0 or 1). Convert to a boolean tensor via wp.to_torch(...).bool().

Type:

wp.array

count

[E] int32 real gate counts per env. All other arrays are NaN-padded for slots i >= count[e].

Type:

wp.array

clone()[source]

Return a deep copy whose Warp buffers do not alias this sequence.

Return type:

GateSequence

__init__(position, tangent, forward, orientation, half_size, left, right, valid, count)
Parameters:
  • position (array)

  • tangent (array)

  • forward (array)

  • orientation (array)

  • half_size (array)

  • left (array)

  • right (array)

  • valid (array)

  • count (array)

Return type:

None

classmethod __new__(*args, **kwargs)

Collision queries

Box-vs-track out-of-bounds checks with full contact info. See track_gen.collision for backend trade-offs (exact segments scan vs baked sdf grids).

Cones and walls placed by track_gen.props, spacing comparison, and the collision SDF field with boxes classified by the exact backend.

The query/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 bound Track buffers directly (fresh after every generate()).

  • method="sdf": bakes per-env signed-distance grids for O(1) queries; approximate within one grid cell near boundaries and requires bake() after each generate(). Memory ~ E * sdf_resolution**2 * 5 bytes.

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: object

Batched box-vs-track out-of-bounds checker bound to a Track.

Because TrackGenerator.generate() overwrites its Track buffers in place, the segments backend always reads the CURRENT track batch with no rebind step. The sdf backend requires bake() after each generate() call.

query() is allocation-free and, under graph capture (enabled via track_gen.set_capturing), host-sync-free (CUDA-graph capturable). In normal (non-capturing) use a wp.synchronize() follows the launch, per the codebase idiom. query() returns the same preallocated BoxContact on every call.

__init__(track, max_boxes, method='segments', sdf_resolution=128, sdf_padding=None, position=None, orientation=None, half_extents=None)[source]

Bind to a Track batch and allocate the result buffers.

Parameters:
  • track (Track) – the bound track batch; the segments backend reads its buffers directly on every query() (no rebind needed after generate()).

  • 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

bake()[source]

(Re)bake the per-env SDF grids from the bound Track.

Required after every TrackGenerator.generate() call when method="sdf" (the segments backend needs no rebake). Pure kernel launches — CUDA-graph capturable.

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 .ptr for the binding’s lifetime.

Parameters:
  • position (array)

  • orientation (array)

  • half_extents (array)

Return type:

None

query(position=None, orientation=None, half_extents=None)[source]

Compute contact info for E * max_boxes oriented boxes.

Two modes:

  • Bound mode: after bind_inputs(), call query() with no arguments; it reads the bound arrays in place each call (the arrays must keep the same .ptr for 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, orientation and half_extents explicitly on every call.

Parameters:
  • position (array | None) – [E*max_boxes] vec3f box 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] quatf box 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] vec2f per-box half sizes (planar boxes). Required in per-call mode; omit in bound mode.

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:

BoxContact

class BoxContact[source]

Bases: object

Batched box-vs-track contact result, flat [E * max_boxes] per field.

Warning

CollisionChecker.query() returns the SAME BoxContact instance on every call and overwrites its buffers in place. Call clone() for a fully-owned snapshot.

oob

int32 — 1 if the box crosses a boundary or lies outside the band.

Type:

wp.array

distance

float32 signed 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). 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

vec2f nearest point on the boundary polylines to the box.

Type:

wp.array

normal

vec2f boundary normal at nearest, pointing INTO the band.

Type:

wp.array

boundary

int32 — 0 = inner boundary, 1 = outer boundary, -1 = inactive slot or degenerate track (count < 3).

Type:

wp.array

clone()[source]

Return a deep copy whose Warp buffers do not alias this result.

Return type:

BoxContact

__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: object

Batched oriented-box vs disc-obstacle checker (collision family).

See the module docstring. num_envs is required when count is 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)[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 count is not given (a flat disc array alone cannot determine the env split); must match count.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.

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-.ptr rule applies under CUDA-graph capture.

Parameters:
  • position (array)

  • orientation (array)

  • half_extents (array)

Return type:

None

query(position=None, orientation=None, half_extents=None)[source]

Box-vs-disc contact for E * max_boxes boxes.

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:
  • position (array | None)

  • orientation (array | None)

  • half_extents (array | None)

Return type:

DiscContact

class DiscContact[source]

Bases: object

Batched 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 at radius once 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

clone()[source]

Return a deep copy whose Warp buffers do not alias this result.

Return type:

DiscContact

__init__(hit, disc, depth, nearest)
Parameters:
  • hit (array)

  • disc (array)

  • depth (array)

  • nearest (array)

Return type:

None

classmethod __new__(*args, **kwargs)

Performance

Measured numbers, backend trade-offs, and the reproduce command live in the collision utility page.

Boundary props

Rendering-only instancing poses along track boundaries — cone lines (mode="points") and wall pieces (mode="segments"). Complementary to the collision utility: props never collide. Results for invalid envs (Track.valid == 0) are undefined — always gate on valid.

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: object

Resample 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 and sample() is CUDA-graph capturable (allocation-free; host-sync-free while capturing is enabled via track_gen.set_capturing). Because TrackGenerator.generate() overwrites its Track buffers in place, every sample() reads the CURRENT batch — no rebind after regeneration. Props are rendering-only; they are not colliders. Results for envs with valid[e] == 0 are undefined — an invalid track can still have >= 3 boundary points and will yield well-formed-looking props with no NaN signal; callers must gate on Track.valid, as everywhere in the library.

PropSet.truncated/PropSet.step live on the RESULT because PropSampler is the ONLY producer of a PropSet — unlike CheckpointSet, which is also produced zero-copy by CheckpointSet.from_gates (no sampling, so no truncation/step to report); track_gen.checkpoints.CheckpointSampler keeps its analogous diagnostics on the sampler instead, to avoid meaningless fields on gate-derived checkpoint sets.

__init__(track, spacing, boundary='outer', mode='points', max_props=None)[source]

Bind to a Track boundary and allocate the result buffers.

Parameters:
  • track (Track) – the bound track batch; sample() reads its buffers directly on every call (no rebind needed after generate()).

  • 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()). Must be >= 3 when given explicitly.

Return type:

None

sample()[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.

Return type:

PropSet

class PropSet[source]

Bases: object

Batched prop poses, flat [E * max_props] per pose field.

Warning

PropSampler.sample() returns the SAME PropSet instance on every call and overwrites its buffers in place. Call clone() for a fully-owned snapshot.

position

vec3f pose 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 past count[e].

Type:

wp.array

tangent

vec2f unit directions: curve tangent at the sample (points) or chord direction (segments). NaN past count[e].

Type:

wp.array

yaw

float32 heading, atan2(tangent.y, tangent.x).

Type:

wp.array

length

float32 per-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-flat z_profile track 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] int32 real prop counts (0 for degenerate envs). Meaningful only for envs with valid[e] == 1.

Type:

wp.array

truncated

[E] int32 — 1 if max_props clipped this env’s ring (the ring still closes, at a coarser effective spacing).

Type:

wp.array

step

[E] float32 effective arc spacing perimeter / count.

Type:

wp.array

clone()[source]

Return a deep copy whose Warp buffers do not alias this set.

Return type:

PropSet

__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: object

Resample a track’s centerline into coarse course checkpoints.

One sampler binds one Track and one spacing; sample() refreshes the owned CheckpointSet in place from the CURRENT track batch (no rebind after generate(); two kernel launches, allocation-free, CUDA-graph capturable via the shared capture flag — track_gen.set_capturing). 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 if max_checkpoints clipped the ring) and step ([E] float32 effective arc spacing). They live here rather than on CheckpointSet because 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 (contrast track_gen.props.PropSet, whose only producer is PropSampler, so its diagnostics live on the result).

__init__(track, spacing, max_checkpoints=None)[source]

Bind to a Track centerline and allocate the result buffers.

Parameters:
  • track (Track) – the bound track batch; sample() reads its centerline buffer directly on every call (no rebind needed after generate()).

  • 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()). Must be >= 3 when given explicitly.

Return type:

None

sample()[source]

Refresh the owned CheckpointSet from the bound Track; returns it.

Return type:

CheckpointSet

class CheckpointSet[source]

Bases: object

Ordered per-env checkpoints, flat [E * M] per field.

The consumer contract for track_gen.progress.ProgressTracker. Sets produced by from_gates() ALIAS the gate buffers (mutations to the gates are visible here); sets produced by CheckpointSampler are owned by the sampler and refreshed in place by sample().

position

vec3f checkpoint centers. NaN past count[e].

Type:

wp.array

left

vec3f crossing-segment endpoints (gate left / track inner).

Type:

wp.array

right

vec3f crossing-segment endpoints (gate right / track outer).

Type:

wp.array

tangent

vec3f unit forward (travel) directions. For gate-derived sets this is the gate’s forward (the physical gate-plane normal), not the 3D spline tangent.

Type:

wp.array

up_half

[E * M] float32 vertical half-openings for the crossing test: _BIG (unbounded) for track cross-sections, and the gate’s half_size for gate-derived sets (aliases GateSequence.half_size). NaN past count[e].

Type:

wp.array

count

[E] int32 real checkpoint counts. Meaningful only for envs with valid[e] == 1 on the source batch.

Type:

wp.array

classmethod from_gates(seq)[source]

Zero-copy view of a GateSequence as checkpoints.

Aliases (does not copy) position, left, right, forward (as tangent: the physical gate-plane normal), half_size (as up_half), and count. Progress state bound to this set must be reset after the gates are regenerated.

Parameters:

seq (GateSequence)

Return type:

CheckpointSet

clone()[source]

Return a deep copy whose Warp buffers do not alias this set.

Return type:

CheckpointSet

__init__(position, left, right, tangent, up_half, count)
Parameters:
  • position (array)

  • left (array)

  • right (array)

  • tangent (array)

  • up_half (array)

  • count (array)

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: object

Track 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 raises ValueError.

__init__(checkpoints, position=None)[source]

Bind to a CheckpointSet and allocate the tracker’s state.

Parameters:
  • checkpoints (CheckpointSet) – the ordered per-env checkpoint set to track against (from CheckpointSet.from_gates or CheckpointSampler.sample()). Aliased, not copied — later mutation (e.g. a resampled or regenerated set) is seen on the next update().

  • position (array | None) – optional stable [E] vec3f buffer to bind at construction (bound mode); equivalent to calling bind() right after construction.

Return type:

None

property next_checkpoint_state: array

Aliased read-only view of the [E] int32 current-target state.

Exposes the tracker’s live _next buffer (updated in place by every update()) 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 .ptr for the binding’s lifetime (CUDA-graph contract).

Parameters:

position (array)

Return type:

None

update(position=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 env e for 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 into prev_pos[e], which re-arms the same sentinel reset() uses: the FIRST update after a finite position resumes also reports no event (it only re-primes prev_pos), and normal crossing detection resumes the update after that. Useful for envs mid-teleport or awaiting a fresh spawn without a full reset().

Parameters:

position (array | None)

Return type:

ProgressEvents

reset(mask)[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 regenerating the bound course.

Parameters:

mask (array)

Return type:

None

class ProgressEvents[source]

Bases: object

Per-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 envs with no checkpoints.

Type:

wp.array

clone()[source]

Return a deep copy whose Warp buffers do not alias this result.

Return type:

ProgressEvents

__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: object

Project one point per env onto the bound Track’s centerline.

Because TrackGenerator.generate() overwrites its Track buffers in place, query() always reads the CURRENT track batch — but a warm-started localizer remembers segment indices of the OLD geometry, so callers MUST reset() all envs after regenerating (cold-scan localizers, warm_window=None, need nothing).

query() is allocation-free and, under graph capture (enabled via track_gen.set_capturing), host-sync-free (CUDA-graph capturable). In normal (non-capturing) use a wp.synchronize() follows the launch, per the codebase idiom. query() returns the same preallocated TrackFrame on every call.

Warm vs cold results are IDENTICAL whenever the nearest centerline segment stays within warm_window segments of the previous query’s result — the usual regime (per-step motion small against warm_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; call reset() on the affected envs to force a full rescan.

__init__(track, warm_window=None, position=None)[source]

Bind to a Track batch and allocate the result buffers.

Parameters:
  • track (Track) – 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 the 2 * warm_window + 1 segments centered on the previous result (per env; the first query after construction or reset() is always a full scan).

  • position (array | None) – optional stable [E] vec3f buffer to bind at construction (bound mode); equivalent to calling bind() right after construction.

Return type:

None

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 .ptr for the binding’s lifetime (CUDA-graph contract).

Parameters:

position (array)

Return type:

None

query(position=None)[source]

Localize the batch; returns the localizer’s preallocated frame.

Bound mode (constructed with position= or after bind()): 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 NaN s/n and segment = -1 for that env, and drops its warm-start memory (the next finite query does a full scan) — safe for envs mid-teleport or awaiting respawn.

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).

Parameters:

position (array | None)

Return type:

TrackFrame

reset(mask)[source]

Drop warm-start memory where mask[e] is nonzero ([E] int32).

The next query for those envs performs a full centerline scan. Required for ALL envs after regenerating the bound track when warm_window is set, 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)

Return type:

None

class TrackFrame[source]

Bases: object

Per-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 and degenerate tracks (count < 3).

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 increasing s), 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 have outer on the right, CW loops on the left); |n| <= half_width means on the road either way. NaN for NaN positions and degenerate tracks.

Type:

wp.array

n_up

float32 — signed vertical offset in the roll-free frame at the foot point; equals position.z for planar tracks. NaN for NaN positions and degenerate tracks.

Type:

wp.array

segment

int32 — index i of the nearest centerline segment (from point i to i + 1, the closing segment being count[e] - 1 -> 0). -1 for NaN positions and degenerate tracks.

Type:

wp.array

clone()[source]

Return a deep copy whose Warp buffers do not alias this result.

Return type:

TrackFrame

__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)[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 over 2 * window + 1 points. Units are 1 / track units. The sign follows the loop orientation: positive where the centerline turns counter-clockwise — a CCW circle of radius R gives kappa ~ +1/R everywhere. NaN past count[e] and for degenerate envs (count[e] < 3); results are undefined for envs with valid[e] == 0.

On a flat track (the default z_profile) v1/v2 are planar edge vectors and this is exactly the familiar 2D signed curvature. On a lifted track (a non-flat z_profile), center carries real per-point altitude, so v1/v2 are 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 — both dot(v1, v2) inside the atan2 and the ds normalizer — 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 of dot(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.

Unlike the per-step utilities, this ALLOCATES its result (and a scratch when window > 0) — a per-generation helper: call it after generate(), outside capture regions.

Parameters:
  • track (Track) – the track batch to read the centerline from.

  • window (int) – moving-average half-width in points (>= 0); 0 disables smoothing. Default 2 (a 5-tap average).

Returns:

A NEW flat [E * N_max] float32 wp.array of signed curvatures on the track’s device (owned by the caller; not refreshed by later generate() calls).

Raises:

ValueError – on window < 0 or an invalid track batch layout.

Return type:

array

speed_profile(track, a_lat_max, a_accel, a_brake, v_cap, kappa=None, window=2)[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 (from Track.arclen/Track.length) is the true 3D segment length on a lifted (non-flat z_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 from kappa (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.

Like curvature(), this ALLOCATES its result — a per-generation helper: call it after generate(), outside capture regions.

Parameters:
  • track (Track) – the track batch; segment lengths come from Track.arclen and Track.length.

  • 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. from curvature(), possibly reused across several profiles). None (default) computes it internally.

  • window (int) – smoothing half-width forwarded to curvature() when kappa is None; ignored otherwise.

Returns:

A NEW flat [E * N_max] float32 wp.array of target speeds on the track’s device, NaN past count[e] and for degenerate envs (count[e] < 3). Undefined for envs with valid[e] == 0.

Raises:

ValueError – on a non-positive a_lat_max / v_cap, a negative a_accel / a_brake, a kappa shape/dtype/device mismatch, or an invalid track batch layout.

Return type:

array

Course facade

One object bundling generation, collision, and progress per mode; see the Course facade page for the lifecycle.

Public course facade: generation + collision + progress in one object.

See Course for the lifecycle (bind -> generate -> step/reset) and set_capturing for one-switch CUDA-graph capture of the step path.

class CourseConfig[source]

Bases: object

Configuration for Course. Strict option applicability: inapplicable options raise instead of being silently ignored.

mode

"track" or "gates".

Type:

str

gen

Generator config; its type must match mode. num_envs and device are taken from here. Gates mode requires gen.gate_width > 0 (a width-0 gate can never be crossed).

Type:

TrackGenConfig or GateGenConfig

seeds

Initial per-env RNG seeding (forwarded to PerEnvSeededRNG).

Type:

int or wp.array

collision

Track mode only: "segments", "sdf", or None (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 uses CollisionChecker’s AUTO SDF padding (sdf_padding=None, i.e. 10% of each env’s larger AABB extent); sdf_padding itself is not exposed here.

Type:

int or None

heightfield_resolution

Track mode only; when set (>= 8), builds a HeightFieldBaker exposed as course.heightfield and re-baked on every generate() (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

post_radius

Gates mode only: > 0 enables DiscChecker gate-post collision.

Type:

float

frame_collision

Gates mode only: True enables FrameChecker sphere-vs-gate-frame collision (each square gate = four thin oriented frame members). Mutually exclusive with post_radius > 0 (choose discs OR frames); requires frame_thickness, frame_depth and agent_radius all > 0.

Type:

bool

frame_thickness

Gates mode with frame_collision only (required, > 0): thickness of the frame members (their extent across the opening plane).

Type:

float

frame_depth

Gates mode with frame_collision only (required, > 0): depth of the frame members along the gate-forward axis.

Type:

float

agent_radius

Gates mode with frame_collision only (required, > 0): the agent sphere radius for frame collision (posts are exclusive, so this cannot ride post_radius).

Type:

float

checkpoint_spacing

Track mode only (required there): centerline checkpoint spacing.

Type:

float or None

max_checkpoints

Track mode only: forwarded to CheckpointSampler.

Type:

int or None

max_boxes

Collision query stride (boxes per env). Must be >= 1.

Type:

int

samples_per_gate

Gates mode only: centerline samples per gate for the CourseLine spline (>= 2). Default 8.

Type:

int

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:
Return type:

None

classmethod __new__(*args, **kwargs)
class Course[source]

Bases: object

One object bundling generation, collision, and progress per mode.

Lifecycle: construct -> bind() (stable sim buffers; required for step()) -> generate() (whole batch) -> per-step step() / per-env reset(). Sub-tools are constructed after the FIRST generate() (their auto-derivations need a real batch) and are reachable as attributes (generator, rng, result, collision, checkpoints, checkpoint_sampler, progress, an optional track-mode heightfield (when heightfield_resolution is set), and — gates mode — course_line, localizer).

generate() is whole-batch by generator design (fixed-batch captured pipelines); per-env respawn control is reset()’s mask. Results are undefined for envs with valid[e] == 0 on result.

__init__(config)[source]
Parameters:

config (CourseConfig)

Return type:

None

bind(position, orientation=None, half_extents=None, box_position=None)[source]

Bind stable sim buffers (required before step()).

position is the [E] vec3f agent-position buffer driving progress. When a box-collision checker is enabled, orientation ([E * max_boxes] quatf box poses) and half_extents ([E * max_boxes] vec2f, planar boxes) are required too; with max_boxes == 1 the same position buffer serves as the box positions, otherwise pass a separate box_position [E * max_boxes] vec3f buffer. May be called before or after the first generate(); rebinding replaces the previous binding.

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.

Parameters:
  • position (array)

  • orientation (array | None)

  • half_extents (array | None)

  • box_position (array | None)

Return type:

None

generate(seeds=None)[source]

Whole-batch (re)generation plus a coherent downstream refresh.

Optional reseed, generator pipeline (its own captured graph on cuda), then: checkpoint resample / sdf bake / posts rebuild as applicable, and a FULL progress reset (every course changed). On cuda the refresh is captured once into a facade-owned graph and replayed on later calls. Returns result.

Determinism contract: the generators are deterministic under an unchanged RNG. Calling generate() again WITHOUT seeds= reproduces the identical batch (plus a full progress reset); pass seeds= to advance the RNG and get new courses.

In track mode, also check course.checkpoint_sampler.truncated after generate(): a per-env [E] int32 flag (1 if max_checkpoints clipped that env’s checkpoint ring), mirroring PropSet.truncated for prop rings.

Parameters:

seeds (int | array | None)

step()[source]

Progress update + collision query on the bound buffers.

Return type:

StepResult

reset(mask)[source]

Per-env respawn on the SAME course: clears progress state (and, in gates mode, the localizer’s warm-start memory) where mask[e] == 1. Collision and checkpoints derive from the course geometry and are unaffected by respawns.

Parameters:

mask (array)

Return type:

None

static set_capturing(flag)

Toggle the family-wide capture flag.

While True, no utility (collision, discs, props, checkpoints, progress, course) performs a host synchronize, so query() / update() / sample() / bake() are safe to record into a CUDA graph. The flag is PROCESS-WIDE: exactly one capturing sim loop at a time is supported by design. Restore it (set_capturing(False) or to the saved previous value) after the capture region.

Parameters:

flag (bool)

Return type:

None

class StepResult[source]

Bases: object

Per-step bundle returned by Course.step().

Holds the sub-tools’ preallocated in-place result instances — the SAME StepResult (and the same underlying buffers) is returned on every step(); use clone() for snapshots.

events

Progress events for this step.

Type:

ProgressEvents

contacts

Collision result (None when the course has no collision checker).

Type:

BoxContact or DiscContact or FrameContact or None

frame

Localization frame on the gates-mode CourseLine (None in track mode).

Type:

TrackFrame or None

clone()[source]

Deep-copy the sub-results.

Return type:

StepResult

__init__(events, contacts, frame)
Parameters:
Return type:

None

classmethod __new__(*args, **kwargs)
set_capturing(flag)[source]

Toggle the family-wide capture flag.

While True, no utility (collision, discs, props, checkpoints, progress, course) performs a host synchronize, so query() / update() / sample() / bake() are safe to record into a CUDA graph. The flag is PROCESS-WIDE: exactly one capturing sim loop at a time is supported by design. Restore it (set_capturing(False) or to the saved previous value) after the capture region.

Parameters:

flag (bool)

Return type:

None