6 KiB
Void Constraints V2 Plan (Planegcs-First, Fallback as Safety Net)
Goal
Move sketch solving to a clean planegcs-first architecture, remove dual-solver behavioral drift, and improve drag/tangent stability.
Current Findings
-
Constraint solving is currently dual-mode by default:
enforceWithPlanegcs()runs, then a fallback settle pass is still applied.- This can reintroduce different motion/priority behavior after planegcs already converged.
-
Drag interactions frequently force fallback:
- During drag,
useFallback: tangentDriven || !pointDragis used in pointer drag paths. - This bypasses planegcs exactly where stable incremental behavior matters most.
- During drag,
-
Tangent constraints are not mapped into planegcs in the current mapper:
toPlanegcsConstraint()covers many constraints but not sketchtangent.- Tangency currently depends on fallback heuristics (
constraints_tangent.js).
-
The planegcs wrapper already supports temporary constraints:
- Constraint objects with
temporary: trueare supported by the wrapper path. - This enables proper drag-driving constraints with lower priority solving semantics.
- Constraint objects with
-
angle_via_pointis available in the solver bindings:- Suitable for robust endpoint tangency encoding (angle = 0 at shared endpoint).
- This aligns with FreeCAD guidance for improved stability vs direct tangent formulations in corner cases.
Root Cause Summary
- Two solvers are actively shaping geometry during interaction.
- Drag logic has explicit fallback preference in key paths.
- Tangency is solved outside planegcs, creating inconsistent convergence and corner-case instability.
Target Architecture
- Planegcs is the primary and default solver for all live interaction and final settle.
- Fallback solver is retained only as failure recovery.
- Drag uses temporary constraints in planegcs:
- Temporary point-to-point/point-to-line style guidance constraints to cursor/ghost references.
- No permanent topology mutation from drag constraints.
- Tangency uses planegcs-native representation:
- Shared-endpoint tangent:
angle_via_pointwith angle = 0. - Non-shared cases: use direct tangent primitives where stable (
tangent_la,tangent_aa, etc.), with endpoint-angle fallback where needed.
- Shared-endpoint tangent:
Phased Execution
Phase 1: Instrumentation and Guardrails
- Add solver telemetry per enforce call:
- planegcs used, fallback used, solve status, elapsed time.
- Add debug toggle to display active temporary constraints during drag.
- Add deterministic logs for tangent constraint path selection.
Exit Criteria:
- We can observe when and why fallback is invoked.
Phase 2: Temporary Drag Constraints
- Add drag-time temporary constraints (
temporary: true) in planegcs solve graph. - Remove drag-path forced fallback defaults.
- Keep fallback only if planegcs solve fails or returns non-converged status.
Exit Criteria:
- Drag no longer “snaps back” from dual-pass disagreement.
- Solver path during normal drag is planegcs-only.
Phase 3: Tangent Migration
- Implement tangent mapping in
toPlanegcsConstraint():- line-arc, arc-arc, line-circle as available.
- For shared-endpoint tangent pairs, map to
angle_via_point(angle=0). - Keep old tangent fallback path behind a temporary feature flag for rollback.
Exit Criteria:
- Tangent drag corner cases no longer require tangent-specific fallback aggressiveness.
- Shared-endpoint tangent cases are stable under repeated edits/drag.
Phase 4: Remove Default Dual Settle
- Remove unconditional fallback settle after successful planegcs solve.
- Fallback runs only on explicit planegcs failure paths.
- Keep compatibility switch (
constraints_v2_force_fallback) for emergency rollback.
Exit Criteria:
- Single primary solver behavior in normal operation.
- Fewer constraint jitter/regressions from solver disagreement.
Phase 5: Cleanup
- Simplify pointer drag enforcement call sites.
- Remove tangent-specific fallback tuning knobs that become obsolete.
- Document canonical constraint mapping table and temporary-constraint rules.
Exit Criteria:
- Constraint code paths are materially simpler and easier to reason about.
Proposed Code Touchpoints
-
src/void/sketch/constraints.js- Add temporary constraint plumbing and tangent mapping in
toPlanegcsConstraint(). - Remove unconditional fallback settle on success.
- Add temporary constraint plumbing and tangent mapping in
-
src/void/sketch/pointer.js- Replace drag-time fallback preference with planegcs temporary constraints.
-
src/void/sketch/constraints_actions.js- Ensure apply/edit flows use planegcs-first, fallback-on-failure behavior.
-
src/void/sketch/constraints_tangent.js- Transition from primary solver role to compatibility fallback only.
-
src/void/solver/sketch/gcs_wrapper.js- Confirm temporary constraint lifecycle handling and cleanup.
Risks and Mitigations
-
Risk: Regression in legacy sketches tuned around fallback behavior.
- Mitigation: feature flag + staged rollout + telemetry.
-
Risk: Performance regressions during drag with added temporary constraints.
- Mitigation: limit temporary constraint count to active dragged subset; cap iterations.
-
Risk: Incorrect tangent mapping for mixed entity types.
- Mitigation: explicit mapping matrix tests per constraint subtype.
Validation Plan
-
Unit tests:
- Tangent mapping (shared endpoint and non-shared).
- Temporary constraint injection/removal lifecycle.
- Planegcs success path without fallback pass.
-
Interaction tests:
- Drag with dimensions, coincident, perpendicular, and tangent combos.
- Repeated drag/release cycles without geometric drift.
- Circular/grid/polygon pattern interactions under drag.
-
Regression scenarios:
- Known tangent corner cases.
- Previously flaky dual-solver “snap back” sketches.
Recommendation
Start with Phase 2 (temporary drag constraints + fallback-on-failure only for drag) before full tangent migration. This yields immediate UX improvement and reduces dual-solver interference while keeping rollback safety.