Skip to content

Ev/coarse lane view - WIP DO NOT REVIEW#464

Open
eugenevinitsky wants to merge 7 commits into
emerge/temp_trainingfrom
ev/coarse-lane-view
Open

Ev/coarse lane view - WIP DO NOT REVIEW#464
eugenevinitsky wants to merge 7 commits into
emerge/temp_trainingfrom
ev/coarse-lane-view

Conversation

@eugenevinitsky
Copy link
Copy Markdown

No description provided.

Eugene Vinitsky and others added 7 commits May 31, 2026 13:47
Adds a per-agent top-K coarse-view observation built from samples spaced
along all drivable lanes, augmented with Dijkstra distance to the agent's
current goal (absolute + min-anchored relative). Replaces close-range
lane centerline obs slots in the paper-aligned configuration.

For each agent each step, emit obs_slots_coarse_n slots × 6 floats:
ego-frame position (2), ego-relative heading (2), abs and min-anchored
relative Dijkstra distance to the current goal (2). The lane graph is
directed, so unreachable sample→goal pairs fall back to the closest
spatially-reachable coarse sample (euclidean leg + graph leg) — this
encodes a U-turn / lane-change cost without requiring undirected graph
preprocessing. Off by default (obs_slots_coarse_n = 0).

Map-load: build per-lane cumulative arclength on RoadMapElement, the
road_to_lane_graph reverse map, and the coarse_samples array. All live
on SharedMapData so use_map_cache shares them across envs.

Goal projection (goal_lane_graph_idx + goal_along_s) refreshes at every
goal-set / goal-advance site (compute_goals success path, set_start_position
replay branch, c_step goal-advance block). Removed from reset_agent_state
since goal projection is owned by the goal pipeline and outlives the
generic per-episode state reset.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Lane slots now carry coarse-view samples directly: the close-range
lane-segment branch of write_road_obs is replaced with a top-K nearest
coarse-sample scan (40m-spaced points along all drivable lanes within
obs_range_coarse_m), and the existing 7-wide ROAD_FEATURES layout is
reinterpreted in-place as
[rel_x, rel_y, rel_z, dist_abs, dist_rel_min, cos_dh, sin_dh].
Boundary slots are untouched and still carry close-range ROAD_EDGE
polyline segments via the grid map.

Side effects of folding coarse into lane: obs_slots_coarse_n and
COARSE_FEATURES disappear (lane slot count + ROAD_FEATURES already do
that job), and the policy's existing lane_encoder consumes the new
content with zero changes — same slot width, same plumbing.

viz.py plot_observation: lane drawing reinterprets the 7 floats as
coarse content, scatters dots colored by min-anchored dist_rel via
RdYlGn_r (routing-best slot gets a lime ring), draws a short heading
tick per dot. Padding check switched to the PADDED_OBSERVATION_VALUE
sentinel. Fixes a pre-existing _img_from_fig DPI-mismatch crash on
HiDPI displays by switching to buffer_rgba.

scripts/render_coarse_obs.py: steps a Drive env and dumps the
plot_observation render at configurable intervals so we can inspect
how the coarse view evolves with the agent's position.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces the obs_html eval at interval=250 (~ once per 1B-step run) with the
egl mp4 backend at interval=20 (~ every 50M env steps), capturing both bev
(top-down following the agent) and sim_state (chase) views per scenario.
render_num_scenarios drops to 4 so each eval cycle stays cheap.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… args

submit_cluster.py serializes the YAML list ["bev","sim_state"] as a Python
list repr ('[\"bev\",\"sim_state\"]') and passes it to argparse as a single
shell token, which trips the inner shell. The two views we want are already
the drive.ini default for validation_gigaflow.render_views, so the override
was redundant anyway.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
pufferl's argparser only generates --eval.<name>.<key> CLI flags for keys
already present in the [eval.<name>] ini section. interval is set in
[eval.validation_defaults] but NOT in [eval.validation_gigaflow], so
--eval.validation-gigaflow.interval was an unrecognized arg. Override on
the parent defaults section instead; validation_gigaflow inherits and the
other inheriting evals are all disabled in this yaml.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Before this, --eval.<name>.<key> CLI flags only existed for keys
physically present in the [eval.<name>] ini section. Overriding a key
that only lived in the parent (inherits = "validation_defaults") meant
either adding placeholder lines to the child section or routing the
override through the parent (which then affects every sibling that
inherits the same parent). Either way pollutes config.

Add a second pass that walks the `inherits` chain of each [eval.<name>]
section and registers --eval.<name>.<inherited_key> flags backed by
argparse.SUPPRESS, so the value is only carried into the nested config
dict when the user actually passes the flag. Inheritance merge in
EvalManager continues to use the parent value otherwise.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
_resolve_inherits_chain returns [] for validation_defaults (no `inherits`
key), so the inner loop already does nothing for it. The explicit skip
was misleading — it suggested some special semantic for that section
when there isn't one.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 31, 2026 21:01
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR is marked "WIP DO NOT REVIEW". It replaces the lane channel of the drive env observation with a GIGAFLOW "W_lane" coarse view: every drivable lane is sampled every 40 m at map load, and per step the top-K nearest samples within obs_range_coarse_m are emitted with absolute + min-anchored Dijkstra distances to the agent's projected goal. Boundary slots remain ROAD_EDGE polylines. Supporting changes wire new config knobs through the Python env, viz, an inherits-aware CLI override path for pufferl, and a debug rendering script.

Changes:

  • New C-side coarse-view machinery: per-lane arclengths, CoarseSample array, road↔lane-graph map, and a rewritten write_road_obs lane branch using top-K + lane-graph distances.
  • Plumbed coarse_sample_spacing_m, obs_range_coarse_m, obs_norm_coarse_dist_m through drive.py / binding.c / drive.ini, plus an eval.<name>.<inherited_key> override path in pufferl.py.
  • Viz updates: _img_from_fig switched to buffer_rgba; plot_observation lane branch redrawn for coarse samples; added scripts/render_coarse_obs.py driver and updated single-agent eval YAML.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
pufferlib/ocean/drive/drive.h Core coarse-view build + per-step top-K and Dijkstra distance emission
pufferlib/ocean/drive/datatypes.h CoarseSample struct, cumulative_s on RoadMapElement, goal projection fields on Agent
pufferlib/ocean/drive/drive.py Surfaces new coarse-view kwargs to the env constructor and kwargs dict
pufferlib/ocean/drive/binding.c Unpacks new coarse-view kwargs into the C struct
pufferlib/config/ocean/drive.ini Adds coarse-view INI keys and comments
pufferlib/viz.py RGBA capture fix; redrew lane slot rendering for coarse samples
pufferlib/pufferl.py Adds CLI override flags for inherited eval section keys
scripts/render_coarse_obs.py New helper to dump per-frame coarse-view obs renders
scripts/cluster_configs/single_agent_speed_run.yaml Switches validation_gigaflow to egl renders with new interval override

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +4826 to +4839
char reachable[env->n_coarse_samples];
for (int s = 0; s < env->n_coarse_samples; s++) {
int slg = env->coarse_samples[s].lane_graph_idx;
if (slg < 0 || goal_lg < 0) {
reachable[s] = 0;
continue;
}
if (slg == goal_lg) {
reachable[s] = 1;
continue;
}
float gd = env->lane_graph.distances[slg * n_lanes + goal_lg];
reachable[s] = (isfinite(gd) && gd < COARSE_DIST_MAX) ? 1 : 0;
}
Comment on lines +4823 to +4875
// Per-sample reachability to goal lane via the directed lane graph.
int n_lanes = env->lane_graph.n_lanes;
int goal_lg = ego->goal_lane_graph_idx;
char reachable[env->n_coarse_samples];
for (int s = 0; s < env->n_coarse_samples; s++) {
int slg = env->coarse_samples[s].lane_graph_idx;
if (slg < 0 || goal_lg < 0) {
reachable[s] = 0;
continue;
}
if (slg == goal_lg) {
reachable[s] = 1;
continue;
}
float gd = env->lane_graph.distances[slg * n_lanes + goal_lg];
reachable[s] = (isfinite(gd) && gd < COARSE_DIST_MAX) ? 1 : 0;
}

float abs_dist[K];
float min_abs = FLT_MAX;
for (int k = 0; k < n_sel; k++) {
struct CoarseSample *cs = &env->coarse_samples[sel_idx[k]];
if (reachable[sel_idx[k]]) {
abs_dist[k]
= coarse_lane_dist(env, cs->lane_graph_idx, cs->along_s, goal_lg, ego->goal_along_s);
} else {
float best_d2 = FLT_MAX;
int best_idx = -1;
for (int s = 0; s < env->n_coarse_samples; s++) {
if (!reachable[s]) {
continue;
}
float dx = env->coarse_samples[s].x - cs->x;
float dy = env->coarse_samples[s].y - cs->y;
float d2 = dx * dx + dy * dy;
if (d2 < best_d2) {
best_d2 = d2;
best_idx = s;
}
}
if (best_idx >= 0) {
struct CoarseSample *cr = &env->coarse_samples[best_idx];
float spatial_leg = sqrtf(best_d2);
float graph_leg
= coarse_lane_dist(env, cr->lane_graph_idx, cr->along_s, goal_lg, ego->goal_along_s);
abs_dist[k] = spatial_leg + graph_leg;
if (abs_dist[k] > COARSE_DIST_MAX) {
abs_dist[k] = COARSE_DIST_MAX;
}
} else {
abs_dist[k] = COARSE_DIST_MAX;
}
}
Comment on lines +122 to +124
coarse_sample_spacing_m = 40.0
obs_range_coarse_m = 200.0
obs_norm_coarse_dist_m = 200.0
Comment thread pufferlib/viz.py
count_boundary = 0
for i in range(boundary_obs.shape[0]):
if np.all(boundary_obs[i] == 0):
if np.all(np.isclose(boundary_obs[i], -0.001)) or np.all(boundary_obs[i] == 0):
Comment on lines +1671 to +1701
static void find_nearest_drivable_lane(Drive *env, float x, float y, int *out_road_idx, float *out_along_s) {
int best_road = -1;
float best_along_s = 0.0f;
float best_d2 = FLT_MAX;
for (int i = 0; i < env->num_road_elements; i++) {
RoadMapElement *r = &env->road_elements[i];
if (r->cumulative_s == NULL || env->road_to_lane_graph[i] < 0) {
continue;
}
for (int seg = 0; seg < r->segment_length - 1; seg++) {
float dx = r->x[seg + 1] - r->x[seg];
float dy = r->y[seg + 1] - r->y[seg];
float len2 = dx * dx + dy * dy;
float t = (len2 > 0.0f) ? ((x - r->x[seg]) * dx + (y - r->y[seg]) * dy) / len2 : 0.0f;
if (t < 0.0f) t = 0.0f;
if (t > 1.0f) t = 1.0f;
float px = r->x[seg] + t * dx;
float py = r->y[seg] + t * dy;
float d2 = (px - x) * (px - x) + (py - y) * (py - y);
if (d2 < best_d2) {
best_d2 = d2;
best_road = i;
float seg_s0 = r->cumulative_s[seg];
float seg_s1 = r->cumulative_s[seg + 1];
best_along_s = seg_s0 + t * (seg_s1 - seg_s0);
}
}
}
*out_road_idx = best_road;
*out_along_s = best_along_s;
}
Comment on lines +3759 to +3762
// GIGAFLOW W_lane derived data (depends on road_elements + lane_graph being loaded).
build_lane_arclengths(env);
build_road_to_lane_graph(env);
build_coarse_samples(env);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants