fix: skip single_equation_pass during drag to prevent stale constraints #37

Merged
forbes merged 1 commits from fix/planar-drag-prepass into main 2026-02-25 19:02:51 +00:00
Owner

Problem

Planar distance=0 constraints weren't holding during interactive drag operations.

Root Cause

single_equation_pass in pre_drag() analytically solves variables from upstream constraints (e.g. Coincident fixes bracket/tz = 0) and bakes their values as Const() nodes into downstream residual expressions. It also calls params.fix() on those variables, removing them from the free list.

During drag, the cached residuals use these stale constants even though part positions have changed via set_value(). The fixed variables aren't in Newton's free list, so they can't be corrected. Downstream constraints (like Planar) that had upstream values baked in silently stop being enforced.

Fix

Skip single_equation_pass in the pre_drag() path. Only substitution_pass (which replaces genuinely grounded/immutable parameters) is safe to cache across drag steps. Newton-Raphson converges in 1-2 iterations from a nearby initial guess anyway, so the prepass optimization is unnecessary for drag performance.

The static solve() path continues to use single_equation_pass as before.

Testing

  • 5 new regression tests in tests/test_drag.py
  • Full suite: 291 passed, 0 failures
## Problem Planar distance=0 constraints weren't holding during interactive drag operations. ## Root Cause `single_equation_pass` in `pre_drag()` analytically solves variables from upstream constraints (e.g. Coincident fixes `bracket/tz = 0`) and bakes their values as `Const()` nodes into downstream residual expressions. It also calls `params.fix()` on those variables, removing them from the free list. During drag, the cached residuals use these stale constants even though part positions have changed via `set_value()`. The fixed variables aren't in Newton's free list, so they can't be corrected. Downstream constraints (like Planar) that had upstream values baked in silently stop being enforced. ## Fix Skip `single_equation_pass` in the `pre_drag()` path. Only `substitution_pass` (which replaces genuinely grounded/immutable parameters) is safe to cache across drag steps. Newton-Raphson converges in 1-2 iterations from a nearby initial guess anyway, so the prepass optimization is unnecessary for drag performance. The static `solve()` path continues to use `single_equation_pass` as before. ## Testing - 5 new regression tests in `tests/test_drag.py` - Full suite: 291 passed, 0 failures
forbes added 1 commit 2026-02-25 18:58:33 +00:00
single_equation_pass analytically solves variables and bakes their values
as Const() nodes into downstream residual expressions. During drag, the
cached residuals use these stale constants even though part positions have
changed, causing constraints like Planar distance=0 to silently stop
being enforced.

Skip single_equation_pass in the pre_drag() path. Only substitution_pass
(which replaces genuinely grounded parameters) is safe to cache across
drag steps. Newton-Raphson converges in 1-2 iterations from a nearby
initial guess anyway, so the prepass optimization is unnecessary for drag.

Add regression tests covering the bug scenario and the fix.
forbes merged commit 6c2ddb6494 into main 2026-02-25 19:02:51 +00:00
forbes deleted branch fix/planar-drag-prepass 2026-02-25 19:02:51 +00:00
Sign in to join this conversation.