Procedurally Generate Tracks - #21
Conversation
Stages 1-3: perturbed radial control points, periodic cubic spline, arc-length reparameterization, harmonic width, and the validate/repair loop producing the immutable Track. All tunables live in config.py. Pure NumPy/SciPy, no MuJoCo. Deterministic from one seed (T3). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stage 4: wall_params places K overlapping capsules along each boundary; build_model compiles the car XML once with a fixed 2K-capsule pool (fixed topology every episode -> MJX-ready). Midphase disabled so repositioned static walls still generate contacts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stage 6a projection: stateful forward-biased local projection onto the centerline (never global). Stages 5-7 env: reset (fixed RNG order: track -> DR -> spawn), 50 Hz step, reward (progress - wall - action rate + lap), E1-E6 termination, and the 23-dim Frenet observation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pure-pursuit + speed-PD reference controller (drives T1/T5/T7 tests), plus view.py viewer autopilot and demo.py guided tour. Package exports, requirements (scipy), and README. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Self-contained explanation.html covering the full pipeline with code snippets, prose, and math derivations. No external deps. Includes a sample generated-track render. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…b.com/Neobotics-Foundation-Inc/neoracer-mujoco into amoghmpanhale/Procedurally-Gen-Tracks
Convert wall capsules to thin vertical boxes and fix the orientation bug
that made them render/collide as a comb of axis-aligned slabs.
Parked walls compile at identity orientation, so MuJoCo sets geom_sameframe
("same rotation as world body") on them. mj_kinematics then copies the world
body's identity rotation into geom_xmat every step and ignores the per-reset
geom_quat writes -- position landed but yaw was dropped, so every wall pointed
along world-x. Clear the flag in build_model (same stale-compile-cache trap
already handled for the midphase BVH) so geom_xmat tracks geom_quat.
Add test_wall_orientation_is_live: test_T4 only checked wall_params math, never
the live geom_xmat, which is why it passed while the sim was wrong.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Port the procedural track generator out of proc_track into a standalone, package-ready module. Generation only: walls, env, projection, drive and viewer stay behind, since only the track geometry is in scope. One entry point, generate_track(seed, difficulty), returning a Track with centerline, tangent, left normal, curvature, half-width, arc length and corridor edges. Structure is one file per stage so no file is a wall of math: control_points, centerline, width, validation, repair, settings, with track.py holding the Track and the attempt/repair loop. Names are spelled out (center/tangent/left_normal/curvature/half_width instead of C/T/Nrm/kappa/w) and each function's docstring explains its own math, so no function needs another one read first. Math is unchanged: output is bit-identical to proc_track.sample_valid_track across 4 difficulties x 5 seeds. Only default changed is max_attempts 20 -> 60; at difficulty 0, 20 attempts left 7 of 20 seeds with no valid track, 60 leaves none. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The five generation steps now live in stages/ named for the order they run (s1_control_points ... s5_repair), so the pipeline is readable from the file listing instead of only from track.py's header. track.py, settings.py section comments, and the package __init__ point at the new paths. Rewrote each stage's module docstring to lead with the file's contents and the entry point, then the decisions that file actually made and the alternatives rejected, replacing the narrative prose that restated the pipeline. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Not ready to approve
There are confirmed runtime/documentation issues (e.g., unsafe CLI parsing and missing/undeclared generator dependency plus docs contradicting wall geometry implementation) that should be corrected before approval.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Pull request overview
Adds multiple procedural track-generation implementations under users/amoghmpanhale/, including a full deterministic MuJoCo RL environment (proc_track) with walls/projection/reward/obs pipeline, plus scripts/tests/docs to validate and visualize generated tracks (Issue #14).
Changes:
- Introduces
proc_trackenvironment: deterministic track generator (NumPy/SciPy), live-updated fixed-topology wall pool, projection, reward/termination, scripted driver, and extensive pytest coverage. - Adds a separate
procedural_track/set of standalone generators + an interactivepygametest driver and minimap output. - Adds a refactored/packaged generator variant in
proc_track_final/with staged modules and unit tests.
File summaries
| File | Description |
|---|---|
| users/amoghmpanhale/track.xml | Sample/generated MJCF track XML. |
| users/amoghmpanhale/procedural_track/test_drive.py | Pygame-based manual driving harness + minimap saving for generated tracks. |
| users/amoghmpanhale/procedural_track/03_random_points.py | Closed-loop “random points + convex hull + spline” generator with pitlane + minimap. |
| users/amoghmpanhale/procedural_track/02_connect_check.py | Random-walk generator with “connected walls” fix + self-test. |
| users/amoghmpanhale/procedural_track/01_test.py | Baseline random-walk generator producing MJCF with simple walls. |
| users/amoghmpanhale/proc_track/walls.py | Fixed-topology wall pool construction + per-reset wall parameterization. |
| users/amoghmpanhale/proc_track/view.py | Passive MuJoCo viewer script to watch autopilot lap generated tracks. |
| users/amoghmpanhale/proc_track/tests/test_walls.py | Wall coverage + build_model contract + live orientation tests. |
| users/amoghmpanhale/proc_track/tests/test_wallride.py | Wall-ride penalty calibration test. |
| users/amoghmpanhale/proc_track/tests/test_projection.py | Projection correctness tests on an analytic circle. |
| users/amoghmpanhale/proc_track/tests/test_generator.py | Generator determinism/robustness + validity predicate tests. |
| users/amoghmpanhale/proc_track/tests/test_env.py | Env spawn/rollout determinism + obs/flag semantics tests. |
| users/amoghmpanhale/proc_track/tests/test_drivability.py | Scripted drivability + projection consistency integration test. |
| users/amoghmpanhale/proc_track/tests/conftest.py | Pytest path setup for proc_track imports. |
| users/amoghmpanhale/proc_track/tests/init.py | Marks tests as a package. |
| users/amoghmpanhale/proc_track/requirements.txt | Adds SciPy requirement for the generator stack. |
| users/amoghmpanhale/proc_track/README.md | Overview, invariant (INV-1), run instructions, layout, and gotchas. |
| users/amoghmpanhale/proc_track/projection.py | Stateful local projection onto centerline (reward/obs backbone). |
| users/amoghmpanhale/proc_track/generator.py | Core deterministic generate→validate→repair loop producing Track. |
| users/amoghmpanhale/proc_track/explanation.html | Long-form technical explanation of the proc_track pipeline. |
| users/amoghmpanhale/proc_track/env.py | ProcTrackEnv reset/step/reward/termination/obs implementation. |
| users/amoghmpanhale/proc_track/drive.py | Scripted pure-pursuit + speed controller used by tests/demos. |
| users/amoghmpanhale/proc_track/demo.py | End-to-end demo: generate/plot/drive/print observation breakdown. |
| users/amoghmpanhale/proc_track/config.py | Centralized configs for generator/walls/projection/env/drive. |
| users/amoghmpanhale/proc_track/init.py | Package exports for proc_track. |
| users/amoghmpanhale/proc_track_final/track.py | Packaged staged generator loop and Track definition. |
| users/amoghmpanhale/proc_track_final/test_track.py | Unit tests for proc_track_final generator properties/determinism. |
| users/amoghmpanhale/proc_track_final/stages/s5_repair.py | Repair stage: smooth corners then push apart near-touches. |
| users/amoghmpanhale/proc_track_final/stages/s4_validation.py | Validation stage: curvature/proximity/length checks. |
| users/amoghmpanhale/proc_track_final/stages/s3_width.py | Width stage: harmonic half-width profile generation. |
| users/amoghmpanhale/proc_track_final/stages/s2_centerline.py | Centerline stage: periodic spline + arc-length resampling. |
| users/amoghmpanhale/proc_track_final/stages/s1_control_points.py | Control-point stage: polar ring sampling + smoothing/jitter. |
| users/amoghmpanhale/proc_track_final/stages/init.py | Stage module overview. |
| users/amoghmpanhale/proc_track_final/settings.py | All tunable generator settings + difficulty presets. |
| users/amoghmpanhale/proc_track_final/init.py | Package exports for proc_track_final. |
Review details
Suppressed comments (2)
users/amoghmpanhale/procedural_track/test_drive.py:238
- CLI parsing for --gen will throw IndexError when --gen is the last argument (no filename provided). This makes the tool fail with a confusing traceback instead of a user-facing error.
users/amoghmpanhale/proc_track/README.md:41 - The layout table repeats the "capsule" wording for walls.py, but walls are implemented as boxes (mjGEOM_BOX). Keeping this consistent helps avoid confusion about contact geometry and the midphase notes.
| `config.py` | ALL tunables (Gen/Wall/Proj/Env/Drive configs) + `*_for_phase` |
| `generator.py` | centerline, arc-length reparam, width, validate, repair → `Track`, `sample_valid_track` |
| `walls.py` | wall capsule params + `build_model` (compiled once at startup) |
| `projection.py` | stateful forward-biased local projection onto the centerline |
- Files reviewed: 34/41 changed files
- Comments generated: 4
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
| spec = importlib.util.spec_from_file_location("track_gen", HERE / filename) | ||
| module = importlib.util.module_from_spec(spec) | ||
| spec.loader.exec_module(module) | ||
| return module |
| import cv2 | ||
| import numpy as np | ||
|
|
| Every episode-dependent quantity lives in mutable fields of a fixed-topology | ||
| `MjModel` or in Python-side state. The model is compiled **once** — floor + car + | ||
| a fixed pool of 512 wall capsules with placeholder poses. `reset` rewrites | ||
| `geom_pos/quat/size` and domain-randomization fields in place; no XML generation, | ||
| no recompilation, ever. That is what makes the later MJX port a mechanical array | ||
| replace. |
| <div class="key"><b>INV-1 — the architecture invariant</b> | ||
| The compiled MuJoCo model has <b>identical topology for every episode and every seed</b>: | ||
| floor + car + a fixed pool of <b>512 wall capsules</b>. A new track never recompiles anything — | ||
| <code>reset</code> only rewrites array fields (<code>geom_pos</code>, <code>geom_quat</code>, | ||
| <code>geom_size</code>) in place. That is the single rule that makes the later GPU (MJX) port a | ||
| mechanical array-swap instead of a rewrite. Hold onto it; it explains half the design choices below.</p></div> |
Add `neoracer_mujoco.track_generation`: `generate_track(seed, difficulty)` returns a `Track` of pure geometry (NumPy/SciPy, no MuJoCo), built by an attempt/repair loop that draws a candidate, checks it for undrivable corners, self-touching, and out-of-range length, then nudges the control points behind each complaint rather than redrawing the whole loop. Five files, split by pipeline stage: track.py (the `Track` and the loop, read first), shape.py (control points to spline to even resample to width), reject.py (what makes a candidate invalid and how to nudge it), config.py (the knobs), mjcf.py (scenery-only MJCF walls and floor, the only module touching MuJoCo). `assets.compose(track, car=None)` attaches the car to a generated `Track`, MJCF text, a track path, or a bare name from `assets/tracks/`. It lives in assets.py, not the subpackage, because it also serves hand-written tracks that never touch the generator. Adds a scipy dependency (CubicSpline for the centerline, cKDTree for the self-touch check, replacing an O(N^2) pass that ran on every repair). validation/test_track_generation.py covers the generator's rules (validity, determinism, difficulty ordering, impossible limits) and the MJCF layer's conventions (wall material name, spawn clearance, geom budget). Also gitignore .DS_Store, which had been committed under users/. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three overlapping scratch generators under users/amoghmpanhale/ -- proc_track/, proc_track_final/, and procedural_track/ -- plus their rendered PNGs and a standalone explanation.html. All of it is superseded by src/neoracer_mujoco/track_generation/. Recoverable from history at aa95c34. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The README lists the directory layout, the examples, and the validation suite by name, and none of them mentioned track generation. Adds a section covering the generate_track/compose API, what a Track holds, the attempt/repair loop, and the role of each of the five files, plus the matching entries in the layout tree, the examples list, and the validation list. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Addresses #14