Skip to content

Fix StopExecAtGoal async restart and combined stage order - #22

Draft
cursor[bot] wants to merge 15 commits into
mainfrom
cursor/critical-bug-inspection-ec86
Draft

Fix StopExecAtGoal async restart and combined stage order#22
cursor[bot] wants to merge 15 commits into
mainfrom
cursor/critical-bug-inspection-ec86

Conversation

@cursor

@cursor cursor Bot commented Jul 31, 2026

Copy link
Copy Markdown

Bug and impact

  1. Stop-at-goal does not stop the visualizer (async runaway) — With GoalArrivalMonitor + StopExecAtGoalTask, executer.stop() sets stopped and tears down workers, but the Tk _exec_loop only watches exec_running. The next poll called AsyncThreadedExecuter.step(), which saw threads_started=False, recreated workers, and cleared stopped via start_threads() — the vehicle kept driving past the goal.
  2. Async combined planning used stale perception — The combined planner worker ran replan before localize/perceive on the shared snapshot, so planning acted on the previous iteration's ego/obstacles (SyncExecuter already fixed this order).

Root cause

  • Cooperative stop only updated executer.stopped; GUI and async auto-restart ignored it.
  • Snapshot sharing preserved the old async stage order instead of matching sync.

Fix

  • AsyncThreadedExecuter.step returns early while stopped is set (Start clears the flag).
  • Tk _exec_loop mirrors executer.stopped into Stop UI / exec_running.
  • Combined async worker: localize → perceive → replan.

Validation

PYTHONPATH=/workspace python3 -m pytest \
  test/c40_execution/test_c45_async_stop.py \
  test/c40_execution/test_c45_async_stage_order.py \
  test/c40_execution/test_c42_sensor_snapshot.py \
  test/c40_execution/test_c43_task_strategy.py -v

19 passed.

Not re-reporting open PR #21 (closed-loop path_s / Frenet CTE). Removed merged PR #18 from automation memory.

Open in Web View Automation 

majid-khonji and others added 15 commits July 27, 2026 04:50
Point site_url and the README docs links at the new domain, which also
gives MkDocs canonical URLs and a sitemap. Material omits Open Graph tags
without the optional social plugin, so declare them in a theme override
and add robots.txt for crawlers.

The hero logo now emits a faint rising ASCII plume driven by a small
particle sim. It grows into the hero's existing top padding so it costs
no layout height, and the box is sized with max-content so letter-spacing
does not push it off-centre.

Co-authored-by: Cursor <cursoragent@cursor.com>
gh-deploy force-pushes site/ as the entire gh-pages tree, so the CNAME
file GitHub writes when you set a custom domain was being deleted on every
deploy, clearing the Pages domain setting. Ship CNAME from docs/ so each
build reproduces it at the site root.

Co-authored-by: Cursor <cursoragent@cursor.com>
…a demo video.

Always extrapolate along the segment instead of snapping to the previous waypoint, and speed up landing first paint (lazy MathJax/js-yaml, overrides under docs/).
Persist c40_start_pose via factory and the Execution UI; State.set_start uses polymorphic copy; BasicSim.reset restores ego/NPCs; publish project.urls for PyPI.
Perception, planning, and control each called world.get_sensor_frame()
independently, so a single step() could act on three different world
instants. This was silently safe only because BasicSim never mutates
state between the fetches; any bridge whose sensors evolve on their own
(CARLA in async mode) would let control act on a snapshot that
perception never saw, with nothing detecting the mismatch.

The tick helpers no longer reach into the world for sensors: the caller
owns the snapshot and passes it in. Each executer loop resolves its
pacing and module-presence gates first, then fetches at most once, so an
idle tick or an unassembled stage costs no fetch at all. In the async
executer the atomic unit is one worker iteration, since step() there is
a no-op and the stages run in their own threads.

Closes #14
Mirrors the community registry: display_name carries the human-readable
title so `name` can stay the install-folder and import identifier, and
site_url points at a project site. Both are optional and fall back to
existing behavior when omitted.
AVLite keeps all spatial data in the world frame: the bridge transforms
LiDAR points before they reach SensorFrame, and ego/agent states are
world frame already, so no frame graph is needed. An image is the one
payload that cannot be pre-transformed that way, being a 2D projection
through specific optics at a specific pose. Projecting world-frame LiDAR
into the image, the core operation of any fusion strategy, needs the
camera intrinsic and the world-to-camera extrinsic at capture time, and
neither existed anywhere: SensorFrame carried raw pixels with no
geometric metadata, and WorldBridge exposed no calibration surface.

CameraParams carries the (3,3) intrinsic K, the (4,4) world_to_camera
extrinsic, and the resolution K is valid for; SensorFrame.camera_params
holds it for the camera that produced rgb/depth. The extrinsic targets
the OpenCV optical frame (x right, y down, z forward) so K applies
directly as u = fx*X/Z + cx, and it belongs on the frame rather than in
config because it changes every tick as the ego moves. The LiDAR
mounting pose is still not needed: the bridge already consumed it.

Purely additive. WorldBridge.get_camera_params() defaults to None, so
every existing bridge is unchanged, and bridges declaring CAMERA_RGB or
CAMERA_DEPTH override it. The capability filter nulls camera_params when
both camera toggles are off, so a filtered frame stays self-consistent.

Also picks up incidental profile rewrites from local runs: materialized
c40_pace_* and c40_execution_tasks defaults, a captured c40_start_pose in
default.yaml, and a frenet zoom change.

Closes #15

Co-authored-by: Cursor <cursoragent@cursor.com>
update_waypoint_by_wp used `current_wp + 1 % n`, which due to operator
precedence left next_wp == len(path) at the last waypoint and crashed
plot/step_wp. Clamp next_wp at the end instead. Also correct the quintic
SD b-vector so end 1st / start 2nd derivatives match the constraint matrix.

Co-authored-by: Majid Khonji <majid-khonji@users.noreply.github.com>
Align only wrote stack PM pose, which GT localization overwrote from the
plant on the next tick. Teleport plant ego and sync stack PM, matching
VisualizerApp.teleport_ego.

Co-authored-by: Majid Khonji <majid-khonji@users.noreply.github.com>
Fix Control Align ego split regression and TrajectoryTracker final-waypoint crash
slice_trajectory_horizon at the last global wp built a single-point
TrajectoryTracker; convert_xy_path_to_sd_path assumed next_wp=1 and
IndexError'd, crashing VelocityLocalPlanner.replan at path end.

Co-authored-by: Majid Khonji <majid-khonji@users.noreply.github.com>
Manual control only moved the plant after the world/stack ego split,
leaving pm.ego_vehicle stale; dual-write via apply_world_control.
Save Start now snapshots velocity 0 so Reset matches a cold start.

Co-authored-by: Majid Khonji <majid-khonji@users.noreply.github.com>
Fix VelocityLocalPlanner end-of-path crash and Control Step ego desync
AsyncThreadedExecuter.step no longer recreates workers while stopped is set,
and the Tk execution loop mirrors cooperative stop into exec_running. Combined
async planning localizes and perceives before replan on the shared snapshot.

Co-authored-by: Majid Khonji <majid-khonji@users.noreply.github.com>
Wait for a localize→perceive→replan subsequence and assert perceive is
always followed by replan once the perception stall gate has cleared.

Co-authored-by: Majid Khonji <majid-khonji@users.noreply.github.com>
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