Appearance
Changelog
0.3.29 — current
with diametersyntax for circles:circle c with diameter 6is sugar forwith radius 3. Combinable withcenter(circle c with center p and diameter 8) and the bundled-scalar form (circle c with diameter d = 6declaresscalar d = 6— the diameter — and sets the circle's radius to 3). Only literal numbers are accepted today; for scalar-referenced or solver-derived sizes usewith radius.
0.3.28
- Circle as a point-on-locus stand-in:
c on lnow means "c's centre is on line l", and the symmetricl through cmeans "l passes through c's centre". The same applies to segments (c on ab). Useful for classical compass-and-straightedge constructions where the centre's locus is constrained — inscribed circles, tangent circles, Apollonius problems. Two circles can't relate this way:c1 on c2andc1 through c2are now rejected at elaboration (previously they parsed silently with no geometric meaning). - Stricter naming checks: using a circle name in a point position (e.g.
c = (3, 4)whencis a circle) is now an elaboration error rather than silently creating a phantom point. On and through — the matrixdoc section: the constraints docs gain a matrix listing every LHS / RHS element-type pair and what it means (or that it's rejected). The on-circle section also documents thethroughform (circle c through p).
0.3.27
- Circle hover tooltip: hovering over a circle in the playground now shows its label, centre, and radius instead of the stale tooltip text from the previously-hovered element. The hover handler also gains a fallback branch that shows the element's label (or
(no format)) for any kind that doesn't have a custom formatter yet, so similar bugs won't repeat as new shapes are added.
0.3.26
- Underdetermined circles render through their on-circle points: a bare
circle cwith one or two placed points on it (e.g.point p = (1, 2); circle c; p on c) used to throw because the default radius of 1 conflicted with the constraint. The circle now places its anonymous centre canonically — at the origin for a single on-circle point, at the chord midpoint for two — and lets the radius be derived from there. The circle renders as wavy (its centre is a representative choice). Three or more on-circle points continue to determine the circle exactly via the circumcentre rule.
0.3.25
- Multi-target
through:l through p, qandl through p and q(with comma orandseparator) now work as standalone constraint statements, not just inline on a line declaration. Each target desugars to a separate on-line constraint. The same syntax also works for circles:c through a, b, c. throughclause on circle declarations:circle k = (o, 1) through a, b, cworks inline, mirroring the existingline l = (1,) through p, qform. With three placed points the circumcentre rule completes the circle.- Chaining constraint operators is now rejected:
l through p parallel mused to be silently accepted in line declarations (treating it asl through p; l parallel m) but was actually a parse error in standalone constraint statements. The reading is ambiguous (ispparallel tom?), so it's now a parse error everywhere. Each constraint clause needs its own statement. - Shared constraint-body parser: standalone constraint statements (
l through p), line-decl trailing clauses (line l = (1,) through p), and circle-decl trailing clauses now go through one shared parser. Future constraint operators added there automatically work in all three contexts.
0.3.24
- Circles: first-class element declared as
circle c = (p, 3)(named centre + radius),circle c = ((x, y), r)(inline centre), orcircle c(bare — centre at origin, radius 1). with centerandwith radius: alternative property syntax —circle c with center p and radius 3. Each parameter can be specified independently;=is optional.- Bundled forms for circles:
circle c with center p = (5, 3)declares pointpand places it;circle c with radius r = 4declares scalarrwith value 4. - On-circle constraints:
p on cconstrains a point to a circle. Combined with another locus (line or second circle), a point can be placed at the exact intersection. - Three points determine a circle: if three or more placed points lie on the same circle, the solver derives its centre and radius.
- Derived circle radius: a scalar declared without a value and used as a circle's radius is back-propagated when the radius is determined by an on-circle constraint.
0.3.23
- Partial point declarations:
point p = (5,)declares a point withx = 5andyunknown;point p = (,3)declares the symmetricy = 3, xunknown form. The two halves can be combined across multiple statements —point p = (5,)followed bypoint p = (,3)merges to a fully placed point at(5, 3). The syntax mirrors the existing partial-line formsline l = (m,)andline l = (,b). Internally a partial point is treated as a point lying on an axis-aligned line, so existing constraints work without special cases: e.g.point p = (5,); line l = (3, 2); p on lresolves top = (5, 17)automatically.
0.3.22
- Unified solver architecture: the old anchor-then-resolve two-pass solver has been replaced with a single loop that alternates between propagate (apply every forced placement) and pick (place one element by canonical gauge fixing or by a representative choice). Behaviour is identical — the rule-based strategy still passes the same 88 tests — but the internals are cleaner and easier to extend. The playground dropdown now reads
pick: rule/pick: budgetinstead ofanchor: rule/anchor: budget. The solver documentation has been rewritten to match the new structure.
0.3.21
- Fix dof reporting for free 1-locus points: a point with a known distance to a single placed neighbour (and no other constraints) used to be reported as fully determined when it happened to be placed first, even when the neighbour itself was at an arbitrary position. It is now correctly reported as underconstrained — the placement we picked is representative, not canonical. Affects scenes with disconnected components whose canonicalization can't be reached from the anchor (e.g.
point a; segment bc = 5).
0.3.20
- Better anchor accounting for partial lines with a free point:
line l = (1,); point p on l(and the symmetric direction-only / y-intercept-only forms) now resolves fully — the T-gauge that was previously unused is consumed by placing the point at the line's natural position (origin for slope-only and direction-only forms; the line's pinned point for y-intercept-only forms). Both line and point render as solid instead of underconstrained. - R-gauge tracking when canonicalizing line direction: when a connected line gets a canonical default slope filled in (e.g. y-intercept-only line with a free point at the y-intercept), the line is marked as resolved (dof=0) if rotation-gauge was still available, rather than left as underconstrained.
0.3.19
- Optional
=inwithclauses:line l with slope 2 and intercept 1now parses the same asline l with slope=2 and intercept=1. The=between a property name and its value is optional; existing code is unchanged. - Bundled scalar declarations in
withclauses:line l with slope m = 2is sugar forscalar m = 2; line l with slope=m— declares a named scalar and uses it as the slope in one statement. Double-=forms likewith slope = m = 2are rejected to enforce one binding per name.
0.3.18
- Solver-derived scalars: scalars can now be declared without a value (
scalar m) and derived by the solver from geometric constraints. For example,scalar m; line l = (m, -1, 0); l through (0,0); l through (1,3)determinesm = 3. Multiple scalars can be derived from the same element. - Bare scalar declarations:
scalar m(no= value) declares an unknown scalar whose value is determined by the solver.
0.3.17
- Scalar declarations:
scalar m = 3declares a named constant usable anywhere a number is expected — coordinates, line equations, length constraints, inline tuples. Scalars support forward references and can reference other scalars. Scalartype: geometry primitives (Point,Line) are now defined in terms ofScalar(=number), documenting the relationship to the language concept.
0.3.16
- Inline tuple refs: numeric tuples can now appear wherever a name is expected —
line l perpendicular m at (1, -1)places the intersection inline,line l parallel (1, -1, 0)references a line by equation,p on (1, -1, 0)constrains a point to an inline line. Optionalpoint/linekeyword disambiguates when the tuple length is ambiguous for the context. - Templatized element declarations:
LineDeclandPointDeclnow share anElementDecl<K, T>template with aparams: Nullable<T>field, ready for future element types (e.g. circles). - Declarations no longer carry constraints: inline sugar (
through,parallel,perpendicular,= 5) is expanded by the parser into separate constraint statements. Declarations are pure declarations.
0.3.15
- Line anchoring: disconnected bare lines now correctly render as underconstrained when other elements (e.g. fixed points) consume the global symmetries that would otherwise absorb the line's degrees of freedom. Previously, a bare line always rendered as fully determined regardless of surrounding constraints.
0.3.14
- Solver interface: introduced a shared
Solverinterface (ConstraintSet→SolveResult) enabling swappable solver backends. - Elaboration layer: new
elaborate.tstransforms the AST into a solver-agnosticConstraintSet, separating semantic analysis (ref resolution, unit conversion, shape expansion) from solving. - Geometric solver encapsulated: existing solver code moved into
solver/geometric/with a single public entry point (GeometricSolver). - Shared geometry types:
PointandLinedefined once in the solver interface, used everywhere. - Removed
freeCoefs: line tooltips now always show concrete coefficients.
0.3.13
- Line rendering by certainty: underconstrained lines now render with a squiggly stroke; two-solution lines (e.g.
l parallel m at 3) render with a jagged stroke. Both use the same colour coding as points and segments. - Line labels coloured by certainty: label colour now matches the line's constraint state.
pickworks for lines:pick l 1/pick l 2selects one solution from a two-solution line, rendering it as fully resolved.- Line labels: always visible, placed inset from the viewport edge with a small perpendicular offset so they stay inside the canvas at all orientations.
- Line hover: hovering over a line now shows a tooltip with its name and constraint state.
0.3.12
- Parallel lines:
line l parallel mconstrainslto have the same direction asm. Can appear inline on thelinedeclaration or as a standalone statement. Optionallinehints accepted on both sides (line l parallel line m). - Perpendicular lines:
line l perpendicular msetsl's direction perpendicular tom. - Intersection point shorthand:
line l perpendicular m at pdeclaresl, marks it perpendicular tom, and places pointpat their intersection — sugar for addingpto both lines'throughlists. - Parallel distance:
line l parallel m at 3constrainslto be exactly 3 units fromm, producing two symmetric solutions (one on each side).
0.3.11
- Partial line declarations: a line can now be declared with one parameter unknown —
(m,)for slope-only,(, k)for y-intercept-only,(a, b,)for direction-only. If a placed point lies on the line the missing parameter is solved exactly; otherwise a canonical default is used. A line resolved by default is rendered as underconstrained (like a free point), one resolved by constraint is fully crisp.
0.3.10
- Anchor bug fix: a free point with a length constraint to an already-fixed point is no longer selected as the translation anchor — anchoring it at the origin would violate the distance constraint
0.3.9
- Test suite: Vitest added;
src/tests/solver.test.tscovers bare segment/triangle, subscript triangle, length constraints, explicit point placement, contradictory-position errors, and line-intersection placement
0.3.8
- Subscript shape support: shapes in subscript mode (
triangle t,segment s) now register vertices (t_1,t_2,t_3) and edges in the solver - Subscript segment syntax:
t_1_2(double underscore) is the canonical edge ref for subscript shapes — unambiguous regardless of vertex count - Unified ref system: parser now produces only
NameRef | SubscriptRef; all semantic resolution (line vs segment vs vertex) moved to the solver where the symbol table is available p on t_1_2works: on-constraints now accept subscript segment targetspoint aaftersegment ab: re-declaring an implicitly created vertex is now allowed — coordinates are applied as a position constraint rather than throwing- Internal:
MeasureConstraintsplit intoLengthConstraintandAngleConstraint;PointCoincidencemerged intoEqualityConstraint
0.3.7
- Any-case identifiers: names can now be any mix of upper and lowercase (
MyTriangle,Seg1,hello) — the previous all-upper / all-lower restriction is removed - Flexible shape naming: exact-length all-distinct lowercase names decompose into vertices (
triangle abc→ verticesa,b,c); any other name uses subscript mode (triangle t→t_1,t_2,t_3) - Repeated characters in shape names route to subscript mode instead of erroring (
segment ss→ss_1,ss_2) - Position constraint inline:
a = (1, 2)in awithclause or as a standalone statement places a vertex at exact coordinates; errors if already placed at a different position - Language reference: new
/referencepage with complete syntax listing, marking unimplemented features
0.3.6
- Line ∩ Line constraint: a point constrained to two named lines is placed at their intersection (
point p on a; p on b) - Constraining a point to three or more lines checks that all lines share a common point, throwing a constraint error if not
- Bare point declaration:
let point anow works without coordinates — declares a free vertex the solver places normally - Bare line declaration:
let line lnow works without an equation — defaults toy = x
0.3.5
- Docs: expanded solver internals into five pages — overview, unit resolution, constraint model, anchor, and placement loop
- Docs: introduced locus-intersection model as the unifying framing for the placement algorithm
- Docs: added Mermaid flow diagrams throughout solver internals
0.3.4
- Docs: fixed incorrect example in certainty page (underconstrained example now correctly shows a point on a circle, not a fully free point)
- Docs: removed internal terminology (
infinite,one,heading,P2) from user-facing pages - Docs: fixed zoom levels on pick and certainty examples
0.3.3
- Length constraint
ab = 5now implicitly declares and renders the segment - Segments where both endpoints are explicitly placed render crisp (not wavy)
- Docs: settings page updated with unit documentation, stale entries removed
- Added
CLAUDE.mdwith project conventions
0.3.2
- Length units:
set unit cm(ormm,m,in,inches) sets the default unit for the program - Mixed units convert automatically —
50mmand5cmare the same length whenset unit cm - Without
set unit, the first unit used in any constraint becomes the default; fully unitless programs stay abstract setstatements are now validated to appear before any geometry declarations- Removed
set anchor(use explicit point coordinates instead)
0.3.1
- Fixed isolated disconnected components (e.g. two unconnected segments) stacking both vertices at the same point — each component now seeds separately so constraint propagation resolves it correctly
0.3.0
- Multiple solutions: ambiguous placements now render all discrete solutions simultaneously as jagged numbered alternatives
pick v Nstatement to select a specific solution- On-segment constraint:
p on abplaces a point on a segment (alwaysinfinite) - Multiple underconstrained points on the same segment distribute evenly
solutionsmodel replaces oldcertaintymodel:one | multiple | infinite- Jagged line and circle style for
multiplestate (amber)
0.2.0
- Equation-defined lines:
line l = (a, b, c)orline l = (m, k) - On-line constraint:
b on l,point b on line l,b on line l - Explicit point declaration:
point p = (x, y) - Circle-line intersection solver (P1b priority)
- Name collision detection — reusing a name across shapes/lines/points is a runtime error
- Vertex-centric fixpoint solver replaces old per-shape placement
- Rotating heading (90° CCW per P2 placement) prevents collinear degeneracy in underconstrained cycles
0.1.0
- Initial language:
segment,triangle - Length constraints:
ab = 5 - Point coincidence:
a = b - Two-circle intersection solver (SSS triangles)
set grid on/off- Canvas2D renderer with pan, zoom, resize
- Wavy circle / squiggly line for underconstrained (
infinite) vertices - Semicolons as optional statement terminators
- LocalStorage persistence in playground