Editor/Effigy/Sketch/IConstraint.cs
using System;

namespace Effigy;

/// <summary>
/// One residual row produced by a constraint evaluation, plus its derivative against the points it
/// touches. The solver stacks these into r and J and takes a damped Gauss-Newton step.
/// </summary>
public readonly struct ConstraintResult
{
	/// <summary>How far this rule is from being satisfied. Zero means satisfied.</summary>
	public readonly double Residual;

	/// <summary>∂r/∂x and ∂r/∂y for every point the constraint touches.</summary>
	public readonly (int Point, double Gx, double Gy)[] Jacobian;

	public ConstraintResult( double residual, (int Point, double Gx, double Gy)[] jacobian )
	{
		Residual = residual;
		Jacobian = jacobian;
	}
}

/// <summary>
/// A constraint the solver can evaluate. One method, and the solver never switches on a kind — so
/// adding a rule is one new class here and no change at all to SketchSolver.
///
/// Note what this is NOT: it is not the stored form. SketchConstraint is what a sketch holds and
/// what a file round-trips; this is what it becomes for the duration of a solve. Keeping them apart
/// is what lets the stored form stay a plain record of point indices while the evaluated form
/// carries derivatives.
/// </summary>
public interface IConstraint
{
	/// <summary>How many residual rows this contributes. One for most; Coincident is two, because
	/// x and y are independently wrong.</summary>
	int ResidualCount { get; }

	/// <summary>
	/// Evaluate at the given positions, writing ResidualCount entries into <paramref name="output"/>.
	///
	/// Pinned points still appear in the Jacobian — the residual would be wrong without them — and
	/// the solver drops their columns when it assembles J.
	/// </summary>
	void Evaluate( ReadOnlySpan<Vec2> points, Span<ConstraintResult> output );
}