@mathscan/geometry-engine 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -7
- package/dist/core.cjs +1 -1
- package/dist/core.mjs +4 -4
- package/dist/entries/core.d.cts +1 -1
- package/dist/entries/core.d.ts +1 -1
- package/dist/entries/core.d.ts.map +1 -1
- package/dist/exercise.schema-Bma3qN9P.js +251 -0
- package/dist/exercise.schema-Bma3qN9P.js.map +1 -0
- package/dist/exercise.schema-Oj5ZH-yO.cjs +2 -0
- package/dist/exercise.schema-Oj5ZH-yO.cjs.map +1 -0
- package/dist/exerciseLoad-Db9Cigxi.cjs +2 -0
- package/dist/exerciseLoad-Db9Cigxi.cjs.map +1 -0
- package/dist/{geometryAttemptStatus-5L_q6-9l.js → exerciseLoad-DgeDXYbf.js} +84 -71
- package/dist/exerciseLoad-DgeDXYbf.js.map +1 -0
- package/dist/geometry/entities/angle.d.cts +56 -0
- package/dist/geometry/entities/angle.d.ts +57 -0
- package/dist/geometry/entities/angle.d.ts.map +1 -0
- package/dist/geometry/entities/circle.d.ts.map +1 -1
- package/dist/geometry/entities/index.d.cts +8 -4
- package/dist/geometry/entities/index.d.ts +8 -4
- package/dist/geometry/entities/index.d.ts.map +1 -1
- package/dist/geometry/entities/line.d.ts.map +1 -1
- package/dist/geometry/entities/point.d.ts.map +1 -1
- package/dist/geometry/entities/relations.d.cts +8 -1
- package/dist/geometry/entities/relations.d.ts +8 -1
- package/dist/geometry/entities/relations.d.ts.map +1 -1
- package/dist/geometry/entities/segment.d.ts.map +1 -1
- package/dist/geometry/entityModel.d.cts +21 -3
- package/dist/geometry/entityModel.d.ts +21 -3
- package/dist/geometry/entityModel.d.ts.map +1 -1
- package/dist/geometryState.schema-B5ccMKDa.cjs +2 -0
- package/dist/geometryState.schema-B5ccMKDa.cjs.map +1 -0
- package/dist/geometryState.schema-CKFPlX8j.js +1177 -0
- package/dist/geometryState.schema-CKFPlX8j.js.map +1 -0
- package/dist/goals-BlSeniyt.js +563 -0
- package/dist/goals-BlSeniyt.js.map +1 -0
- package/dist/goals-DLbmBZD8.cjs +2 -0
- package/dist/goals-DLbmBZD8.cjs.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +8 -8
- package/dist/measure/angleType.d.cts +2 -0
- package/dist/measure/angleType.d.ts +3 -0
- package/dist/measure/angleType.d.ts.map +1 -0
- package/dist/measure/geometryAdapter.d.cts +3 -0
- package/dist/measure/geometryAdapter.d.ts +3 -0
- package/dist/measure/geometryAdapter.d.ts.map +1 -1
- package/dist/react-B26_e52I.cjs +2 -0
- package/dist/react-B26_e52I.cjs.map +1 -0
- package/dist/{react-BEcdx1f6.js → react-B5cGaxH8.js} +738 -513
- package/dist/react-B5cGaxH8.js.map +1 -0
- package/dist/react.cjs +1 -1
- package/dist/react.mjs +1 -1
- package/dist/renderer/GeometryCanvas.d.ts.map +1 -1
- package/dist/renderer/StudentGeometryCanvas.d.ts.map +1 -1
- package/dist/renderer/Toolbar.d.ts.map +1 -1
- package/dist/renderer/angleMeasure.d.cts +14 -0
- package/dist/renderer/angleMeasure.d.ts +15 -0
- package/dist/renderer/angleMeasure.d.ts.map +1 -0
- package/dist/renderer/deriveRenderableGeometry.d.cts +1 -0
- package/dist/renderer/deriveRenderableGeometry.d.ts +1 -0
- package/dist/renderer/deriveRenderableGeometry.d.ts.map +1 -1
- package/dist/renderer/deriveSemanticScene.d.ts.map +1 -1
- package/dist/renderer/dragSnap.d.cts +14 -0
- package/dist/renderer/dragSnap.d.ts +15 -0
- package/dist/renderer/dragSnap.d.ts.map +1 -0
- package/dist/renderer/shapes/AngleShape.d.cts +19 -0
- package/dist/renderer/shapes/AngleShape.d.ts +20 -0
- package/dist/renderer/shapes/AngleShape.d.ts.map +1 -0
- package/dist/renderer/shapes/EntityLabel.d.cts +9 -1
- package/dist/renderer/shapes/EntityLabel.d.ts +9 -1
- package/dist/renderer/shapes/EntityLabel.d.ts.map +1 -1
- package/dist/renderer/shapes/EntityShape.d.cts +2 -1
- package/dist/renderer/shapes/EntityShape.d.ts +2 -1
- package/dist/renderer/shapes/EntityShape.d.ts.map +1 -1
- package/dist/renderer/useConnectionPreview.d.cts +12 -1
- package/dist/renderer/useConnectionPreview.d.ts +12 -1
- package/dist/renderer/useConnectionPreview.d.ts.map +1 -1
- package/dist/renderer/usePointerGestures.d.cts +1 -1
- package/dist/renderer/usePointerGestures.d.ts +1 -1
- package/dist/renderer/usePointerGestures.d.ts.map +1 -1
- package/dist/schemas/entityDefinition.schema.d.cts +7 -0
- package/dist/schemas/entityDefinition.schema.d.ts +7 -0
- package/dist/schemas/entityDefinition.schema.d.ts.map +1 -1
- package/dist/schemas/entityDocument.d.cts +34 -13
- package/dist/schemas/entityDocument.d.ts +34 -13
- package/dist/schemas/entityDocument.d.ts.map +1 -1
- package/dist/schemas/entityPresentation.schema.d.cts +3 -0
- package/dist/schemas/entityPresentation.schema.d.ts +3 -0
- package/dist/schemas/entityPresentation.schema.d.ts.map +1 -1
- package/dist/schemas/exercise.schema.d.cts +80 -2
- package/dist/schemas/exercise.schema.d.ts +80 -2
- package/dist/schemas/exercise.schema.d.ts.map +1 -1
- package/dist/schemas/exerciseV2.schema.d.cts +57 -0
- package/dist/schemas/exerciseV2.schema.d.ts +57 -0
- package/dist/schemas/exerciseV2.schema.d.ts.map +1 -1
- package/dist/schemas/exerciseV4.schema.d.cts +168 -0
- package/dist/schemas/exerciseV4.schema.d.ts +169 -0
- package/dist/schemas/exerciseV4.schema.d.ts.map +1 -0
- package/dist/schemas/geometryState.schema.d.cts +8 -0
- package/dist/schemas/geometryState.schema.d.ts +8 -0
- package/dist/schemas/geometryState.schema.d.ts.map +1 -1
- package/dist/schemas/goals/angleBisectorGoal.schema.d.cts +15 -0
- package/dist/schemas/goals/angleBisectorGoal.schema.d.ts +16 -0
- package/dist/schemas/goals/angleBisectorGoal.schema.d.ts.map +1 -0
- package/dist/schemas/goals/angleCriterion.schema.d.cts +28 -0
- package/dist/schemas/goals/angleCriterion.schema.d.ts +29 -0
- package/dist/schemas/goals/angleCriterion.schema.d.ts.map +1 -0
- package/dist/schemas/goals/angleMeasureGoal.schema.d.cts +26 -0
- package/dist/schemas/goals/angleMeasureGoal.schema.d.ts +27 -0
- package/dist/schemas/goals/angleMeasureGoal.schema.d.ts.map +1 -0
- package/dist/schemas/goals/createGoal.schema.d.cts +1 -0
- package/dist/schemas/goals/createGoal.schema.d.ts +1 -0
- package/dist/schemas/goals/createGoal.schema.d.ts.map +1 -1
- package/dist/schemas/goals/geometryGoal.schema.d.cts +61 -0
- package/dist/schemas/goals/geometryGoal.schema.d.ts +61 -0
- package/dist/schemas/goals/geometryGoal.schema.d.ts.map +1 -1
- package/dist/schemas/goals/givenAngleGoal.schema.d.cts +35 -0
- package/dist/schemas/goals/givenAngleGoal.schema.d.ts +36 -0
- package/dist/schemas/goals/givenAngleGoal.schema.d.ts.map +1 -0
- package/dist/schemas/goals/goalKindDeclaration.d.cts +7 -0
- package/dist/schemas/goals/goalKindDeclaration.d.ts +7 -0
- package/dist/schemas/goals/goalKindDeclaration.d.ts.map +1 -1
- package/dist/schemas/goals/parallelGoal.schema.d.cts +1 -0
- package/dist/schemas/goals/parallelGoal.schema.d.ts +1 -0
- package/dist/schemas/goals/parallelGoal.schema.d.ts.map +1 -1
- package/dist/schemas/goals/perpendicularGoal.schema.d.cts +1 -0
- package/dist/schemas/goals/perpendicularGoal.schema.d.ts +1 -0
- package/dist/schemas/goals/perpendicularGoal.schema.d.ts.map +1 -1
- package/dist/schemas/goals/throughPointsGoal.schema.d.cts +1 -0
- package/dist/schemas/goals/throughPointsGoal.schema.d.ts +1 -0
- package/dist/schemas/goals/throughPointsGoal.schema.d.ts.map +1 -1
- package/dist/schemas/objectTypeName.schema.d.cts +1 -0
- package/dist/schemas/objectTypeName.schema.d.ts +1 -0
- package/dist/schemas/objectTypeName.schema.d.ts.map +1 -1
- package/dist/schemas/v2ToV3.d.cts +15 -1
- package/dist/schemas/v2ToV3.d.ts +15 -1
- package/dist/schemas/v2ToV3.d.ts.map +1 -1
- package/dist/schemas/v3ToV4.d.cts +2 -1
- package/dist/schemas/v3ToV4.d.ts +2 -1
- package/dist/schemas/v3ToV4.d.ts.map +1 -1
- package/dist/schemas/v4ToV5.d.cts +7 -0
- package/dist/schemas/v4ToV5.d.ts +8 -0
- package/dist/schemas/v4ToV5.d.ts.map +1 -0
- package/dist/schemas.cjs +1 -1
- package/dist/schemas.mjs +3 -3
- package/dist/snapping/computeDragSnap.d.cts +30 -0
- package/dist/snapping/computeDragSnap.d.ts +31 -0
- package/dist/snapping/computeDragSnap.d.ts.map +1 -0
- package/dist/state/geometryContext.d.ts.map +1 -1
- package/dist/store/attemptChange.d.cts +1 -1
- package/dist/store/attemptChange.d.ts +1 -1
- package/dist/store/attemptChange.d.ts.map +1 -1
- package/dist/store/constructionCommand.d.cts +14 -2
- package/dist/store/constructionCommand.d.ts +14 -2
- package/dist/store/constructionCommand.d.ts.map +1 -1
- package/dist/store/constructionIdentity.d.cts +9 -0
- package/dist/store/constructionIdentity.d.ts +9 -0
- package/dist/store/constructionIdentity.d.ts.map +1 -1
- package/dist/store/studentStore.d.cts +7 -0
- package/dist/store/studentStore.d.ts +7 -0
- package/dist/store/studentStore.d.ts.map +1 -1
- package/dist/store/teacherStore.d.cts +16 -0
- package/dist/store/teacherStore.d.ts +16 -0
- package/dist/store/teacherStore.d.ts.map +1 -1
- package/dist/{studentStore-CEKTtMCh.js → studentStore-CyYa0N93.js} +502 -370
- package/dist/studentStore-CyYa0N93.js.map +1 -0
- package/dist/studentStore-ubldy4PJ.cjs +2 -0
- package/dist/studentStore-ubldy4PJ.cjs.map +1 -0
- package/dist/validation/getGeometryExerciseWarnings.d.cts +3 -2
- package/dist/validation/getGeometryExerciseWarnings.d.ts +3 -2
- package/dist/validation/getGeometryExerciseWarnings.d.ts.map +1 -1
- package/dist/validation/goals/angleBisector.d.cts +3 -0
- package/dist/validation/goals/angleBisector.d.ts +4 -0
- package/dist/validation/goals/angleBisector.d.ts.map +1 -0
- package/dist/validation/goals/angleCriterion.d.cts +12 -0
- package/dist/validation/goals/angleCriterion.d.ts +13 -0
- package/dist/validation/goals/angleCriterion.d.ts.map +1 -0
- package/dist/validation/goals/angleMeasure.d.cts +9 -0
- package/dist/validation/goals/angleMeasure.d.ts +10 -0
- package/dist/validation/goals/angleMeasure.d.ts.map +1 -0
- package/dist/validation/goals/create.d.cts +2 -2
- package/dist/validation/goals/create.d.ts +2 -2
- package/dist/validation/goals/create.d.ts.map +1 -1
- package/dist/validation/goals/givenAngle.d.cts +11 -0
- package/dist/validation/goals/givenAngle.d.ts +12 -0
- package/dist/validation/goals/givenAngle.d.ts.map +1 -0
- package/dist/validation/goals/goalKindHandler.d.cts +38 -1
- package/dist/validation/goals/goalKindHandler.d.ts +38 -1
- package/dist/validation/goals/goalKindHandler.d.ts.map +1 -1
- package/dist/validation/goals/index.d.cts +9 -2
- package/dist/validation/goals/index.d.ts +9 -2
- package/dist/validation/goals/index.d.ts.map +1 -1
- package/dist/validation/goals/midpoint.d.cts +2 -2
- package/dist/validation/goals/midpoint.d.ts +2 -2
- package/dist/validation/goals/midpoint.d.ts.map +1 -1
- package/dist/validation/goals/parallel.d.cts +2 -2
- package/dist/validation/goals/parallel.d.ts +2 -2
- package/dist/validation/goals/parallel.d.ts.map +1 -1
- package/dist/validation/goals/perpendicular.d.cts +2 -2
- package/dist/validation/goals/perpendicular.d.ts +2 -2
- package/dist/validation/goals/perpendicular.d.ts.map +1 -1
- package/dist/validation/goals/segmentLength.d.cts +2 -2
- package/dist/validation/goals/segmentLength.d.ts +2 -2
- package/dist/validation/goals/segmentLength.d.ts.map +1 -1
- package/dist/validation/goals/throughPoints.d.cts +2 -2
- package/dist/validation/goals/throughPoints.d.ts +2 -2
- package/dist/validation/goals/throughPoints.d.ts.map +1 -1
- package/dist/validation/types.d.cts +5 -1
- package/dist/validation/types.d.ts +5 -1
- package/dist/validation/types.d.ts.map +1 -1
- package/dist/validation/validateGeometryState.d.ts.map +1 -1
- package/dist/validation/validateGeometryStateInput.d.ts.map +1 -1
- package/dist/validation.cjs +1 -1
- package/dist/validation.cjs.map +1 -1
- package/dist/validation.mjs +106 -85
- package/dist/validation.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/exercise.schema-BV2b7rtN.cjs +0 -2
- package/dist/exercise.schema-BV2b7rtN.cjs.map +0 -1
- package/dist/exercise.schema-ckVBW9XA.js +0 -212
- package/dist/exercise.schema-ckVBW9XA.js.map +0 -1
- package/dist/geometryAttemptStatus-5L_q6-9l.js.map +0 -1
- package/dist/geometryAttemptStatus-N_XHIkiS.cjs +0 -2
- package/dist/geometryAttemptStatus-N_XHIkiS.cjs.map +0 -1
- package/dist/geometryState.schema-D0ixyGYE.js +0 -955
- package/dist/geometryState.schema-D0ixyGYE.js.map +0 -1
- package/dist/geometryState.schema-DKQ-durx.cjs +0 -2
- package/dist/geometryState.schema-DKQ-durx.cjs.map +0 -1
- package/dist/goals-C3ZwbEh_.js +0 -451
- package/dist/goals-C3ZwbEh_.js.map +0 -1
- package/dist/goals-CxR5qPDC.cjs +0 -2
- package/dist/goals-CxR5qPDC.cjs.map +0 -1
- package/dist/react-BEcdx1f6.js.map +0 -1
- package/dist/react-Pp_VSk--.cjs +0 -2
- package/dist/react-Pp_VSk--.cjs.map +0 -1
- package/dist/studentStore-CEKTtMCh.js.map +0 -1
- package/dist/studentStore-DLftfFAl.cjs +0 -2
- package/dist/studentStore-DLftfFAl.cjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"geometryState.schema-B5ccMKDa.cjs","names":[],"sources":["../src/geometry/point.ts","../src/numerics/policy.ts","../src/geometry/distance.ts","../src/measure/angleType.ts","../src/measure/kernel.ts","../src/measure/geometryAdapter.ts","../src/geometry/entities/angle.ts","../src/geometry/entities/circle.ts","../src/geometry/entities/relations.ts","../src/geometry/entities/line.ts","../src/geometry/entities/point.ts","../src/geometry/entities/segment.ts","../src/geometry/entities/index.ts","../src/geometry/entityRecord.ts","../src/schemas/identifier.schema.ts","../src/schemas/entityDocument.ts","../src/schemas/objectTypeName.schema.ts","../src/schemas/goals/angleBisectorGoal.schema.ts","../src/schemas/goals/angleCriterion.schema.ts","../src/schemas/goals/angleMeasureGoal.schema.ts","../src/schemas/goals/createGoal.schema.ts","../src/schemas/goals/givenAngleGoal.schema.ts","../src/schemas/goals/midpointGoal.schema.ts","../src/schemas/goals/parallelGoal.schema.ts","../src/schemas/goals/perpendicularGoal.schema.ts","../src/schemas/goals/segmentLengthGoal.schema.ts","../src/schemas/goals/throughPointsGoal.schema.ts","../src/schemas/goals/goalKindDeclaration.ts","../src/schemas/exerciseProjection.ts","../src/schemas/point.schema.ts","../src/schemas/entityDefinition.schema.ts","../src/schemas/entityPresentation.schema.ts","../src/schemas/migrate.ts","../src/geometry/pointOrigin.ts","../src/geometry/v1Bridge.ts","../src/schemas/v1ToV2.ts","../src/schemas/geometryState.schema.ts"],"sourcesContent":["// Shape defined in schemas/point.schema.ts; re-exported here next to the math.\nexport type { Point, GivenPoint } from '../schemas/point.schema'\nimport type { Point } from '../schemas/point.schema'\n\nexport type PointId = string\n\n// Elementary coordinate arithmetic: pure by design. Measurements that need\n// real geometric machinery (angles, intersections, incidence) belong in\n// measure/geometryAdapter.ts instead — wrapping arithmetic like\n// this in the adapter would only add a board dependency it does not need.\nexport function distanceBetween(a: Point, b: Point): number {\n return Math.hypot(b.x - a.x, b.y - a.y)\n}\n\nexport function midpointOf(a: Point, b: Point): Point {\n return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 }\n}\n","// The engine's numerical policy: every epsilon that governs a geometric\n// decision, named once, in one place.\n//\n// Three kinds of slack live here and must not be confused:\n//\n// * **Calculation epsilon** — floating-point noise. A quantity that is\n// mathematically exact arrives a few ulps off because it travelled through\n// a square root, a projective meet, or a trigonometric round trip. These\n// values are properties of IEEE 754 arithmetic, not of geometry, and they\n// are never authored.\n// * **Grading tolerance** — how wrong a student is allowed to be. Always\n// authored, always stored in the exercise (`toleranceUnits`,\n// `toleranceDegrees`), never a constant in this file. What lives here is\n// only the calculation epsilon added to an authored tolerance so an exact\n// boundary answer is not rejected by noise.\n// * **Interaction tolerance** — how close a pointer has to be for a snap to\n// fire. Authored in pixels because it is about fingers and mice, then\n// converted to math units against the current placement.\n//\n// Pure arithmetic: no DOM, no board, no Konva, no JSXGraph. Everything else in\n// the engine imports its epsilons from here rather than writing a literal.\nimport { distanceBetween, type Point } from '../geometry/point'\n\n// --- Calculation epsilons -------------------------------------------------\n\n// Absolute slack, in math units, for comparing two quantities that ought to be\n// equal but were computed by different routes. Sized for the coordinate ranges\n// schemas allow (viewports span at most 100 units): well above double-precision\n// noise at that scale, far below anything a student could aim at.\nexport const CALCULATION_EPSILON = 1e-9\n\n// Two coordinate pairs name the same location. Same magnitude as\n// CALCULATION_EPSILON and for the same reason, but kept separate because it\n// answers a geometric question (\"is this one point or two?\") rather than an\n// arithmetic one.\nexport const COORDINATE_EPSILON = 1e-9\n\n// Slack on a parameter along an object. Parameters are unitless — 0 is one\n// defining point, 1 is the other — so this is far tighter than the coordinate\n// epsilons and does not scale with the viewport.\nexport const PARAMETER_EPSILON = 1e-12\n\n// Below this, a magnitude is treated as zero *relative to the scale of the\n// object it came from*: a homogeneous weight against the coordinates it\n// divides, a squared length against the extent of its own endpoints. Relative\n// rather than absolute because the same construction at 1e-3 and at 1e3 must\n// degenerate at the same place. See `isDegenerateAtScale`.\nexport const RELATIVE_DEGENERACY_EPSILON = 1e-12\n\n// Two positions arrived at by different constructions count as the same\n// existing point. Deliberately far looser than COORDINATE_EPSILON: a revealed\n// answer synthesized from trigonometry and a point already on canvas from a\n// projective intersection agree to about a dozen digits, not to the last bit,\n// and treating them as two points would stack a duplicate exactly where the\n// engine is trying to reuse one.\nexport const POINT_REUSE_EPSILON = 1e-6\n\n// --- Grading -------------------------------------------------------------\n\n// Added to an authored tolerance before comparing. Answers exactly on the\n// boundary are a legitimate way to be right, and floating-point arithmetic can\n// put an exact boundary a few quadrillionths outside the tolerance the author\n// wrote. This is the *only* constant grading is allowed to use — the tolerance\n// itself always comes from the exercise. Applies to degrees as readily as to\n// math units: at these magnitudes the unit does not change the answer.\nexport const GRADING_BOUNDARY_EPSILON = 1e-9\n\n// --- Interaction ---------------------------------------------------------\n\n// Pixel radius of the placement snap target. Deliberately tighter than the\n// connection radius: placement competes with grid crossings and whole objects\n// across the entire canvas, so a wide radius would pull almost every click\n// somewhere the student did not aim. Still larger than any drawn point, so\n// touch input can reach it.\nexport const PLACEMENT_SNAP_RADIUS_PX = 24\n\n// Pixel radius while dragging a connection. Wider than placement because it\n// only competes with existing points, and a finger-sized target is what makes\n// the preview commit visibly before release.\nexport const CONNECTION_SNAP_RADIUS_PX = 32\n\n// --- Helpers -------------------------------------------------------------\n\n// Equality up to calculation noise. `epsilon` is absolute and in the same unit\n// as the operands.\nexport function approximatelyEqual(a: number, b: number, epsilon = CALCULATION_EPSILON): boolean {\n return Math.abs(a - b) <= epsilon\n}\n\n// Whether a magnitude is negligible against the scale it was measured on. Pass\n// `scale` in the same unit as `magnitude` — a length against a length, a\n// homogeneous weight against the size of the coordinates it divides. A scale of\n// zero makes any magnitude but zero itself non-negligible, which is what keeps\n// a genuinely zero-sized object from being compared against nothing.\nexport function isDegenerateAtScale(magnitude: number, scale: number): boolean {\n return Math.abs(magnitude) <= Math.abs(scale) * RELATIVE_DEGENERACY_EPSILON\n}\n\n// Whether two coordinate pairs name the same location.\nexport function coordinatesCoincide(a: Point, b: Point, epsilon = COORDINATE_EPSILON): boolean {\n return distanceBetween(a, b) <= epsilon\n}\n\n// Whether a position should reuse an existing point rather than create one.\nexport function isSameStoredPoint(a: Point, b: Point): boolean {\n return distanceBetween(a, b) <= POINT_REUSE_EPSILON\n}\n\n// Whether a parameter lies on a bounded object — 0 and 1 included, with slack\n// for the arithmetic that produced it.\nexport function isWithinSegmentParameter(t: number): boolean {\n return t >= -PARAMETER_EPSILON && t <= 1 + PARAMETER_EPSILON\n}\n\n// A measured quantity is close enough to the value the author asked for.\n// `authoredTolerance` comes from the exercise; only the boundary epsilon is\n// ours.\nexport function withinAuthoredTolerance(\n measured: number,\n target: number,\n authoredTolerance: number,\n): boolean {\n return Math.abs(measured - target) <= authoredTolerance + GRADING_BOUNDARY_EPSILON\n}\n\n// A measured deviation from an ideal of zero — an angle already folded to \"how\n// far from parallel\", a distance from a target point — is inside the authored\n// tolerance.\nexport function withinAuthoredDeviation(deviation: number, authoredTolerance: number): boolean {\n return deviation <= authoredTolerance + GRADING_BOUNDARY_EPSILON\n}\n\n// A pixel radius in math units. `pixelsPerMathUnit` is the placement's scale;\n// callers in the renderer reach this through\n// `coordinates/transform.ts` → `screenLengthToMath`, which knows how to derive\n// that scale from a Placement.\nexport function pixelRadiusToMathTolerance(pixels: number, pixelsPerMathUnit: number): number {\n return pixels / pixelsPerMathUnit\n}\n","// Point-to-object distances in math space. Elementary coordinate arithmetic,\n// so it lives here as pure functions rather than behind the JSXGraph adapter\n// (see FUTURE.md 1.1) — no board, no DOM, usable from the renderer.\nimport { isDegenerateAtScale } from '../numerics/policy'\nimport { distanceBetween, type Point } from './point'\n\n// Parameter t of the projection of `point` onto the infinite line through\n// a and b, where t = 0 is a and t = 1 is b. Null when a and b define no usable\n// line: either they coincide outright, or they are so close together relative\n// to the distance being measured that the direction between them is rounding\n// error (numerics/policy.ts, `isDegenerateAtScale`).\nexport function projectionParameter(point: Point, a: Point, b: Point): number | null {\n const dx = b.x - a.x\n const dy = b.y - a.y\n // Deliberately not (dx² + dy²): squaring a short separation underflows to a\n // subnormal, which carries only a handful of significant bits, and dividing\n // by it puts the projection somewhere off the line entirely. Math.hypot\n // scales internally, so the direction stays exact and the division by the\n // length happens once, in a normal range. Found by the numerical property\n // tests — see tests/numerics/properties.test.ts.\n const length = Math.hypot(dx, dy)\n // Degenerate relative to the query, not just exactly zero. Two endpoints\n // that are a trillionth of the distance being measured apart define a\n // direction made entirely of rounding error, and dividing by that length\n // overflows the parameter to Infinity. `isDegenerateAtScale` is the policy\n // that decides how small \"too small\" is; see numerics/policy.ts.\n if (length === 0 || isDegenerateAtScale(length, distanceBetween(point, a))) return null\n const unitX = dx / length\n const unitY = dy / length\n return ((point.x - a.x) * unitX + (point.y - a.y) * unitY) / length\n}\n\n// The point at parameter t along a -> b: t = 0 is a, t = 1 is b, and values\n// outside [0, 1] continue along the infinite line.\nexport function pointAlong(a: Point, b: Point, t: number): Point {\n return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t }\n}\n\n// Foot of the perpendicular from `point` to the infinite line through a and b.\nexport function closestPointOnLine(point: Point, a: Point, b: Point): Point {\n const t = projectionParameter(point, a, b)\n if (t === null) return a\n return pointAlong(a, b, t)\n}\n\n// Same, bounded to the segment: past either end the nearest place is that\n// endpoint.\nexport function closestPointOnSegment(point: Point, a: Point, b: Point): Point {\n const t = projectionParameter(point, a, b)\n if (t === null) return a\n return pointAlong(a, b, Math.min(1, Math.max(0, t)))\n}\n\n// Distance to the infinite line through a and b. Degenerate input (a === b)\n// falls back to the distance to that single point.\nexport function distancePointToLine(point: Point, a: Point, b: Point): number {\n return distanceBetween(point, closestPointOnLine(point, a, b))\n}\n\n// Distance to the bounded segment [a, b]: beyond either end the nearest place\n// on the segment is that endpoint, which is what makes a segment hit-test\n// differently from the line through the same two points.\nexport function distancePointToSegment(point: Point, a: Point, b: Point): number {\n return distanceBetween(point, closestPointOnSegment(point, a, b))\n}\n\n// Distance to the circle itself — the curve, not the disc. A point at the\n// centre is `radius` away from every part of it, which is what makes a hit test\n// on a circle ignore its interior the way a hit test on a segment ignores the\n// plane around it.\nexport function distancePointToCircle(point: Point, center: Point, radius: number): number {\n return Math.abs(distanceBetween(point, center) - radius)\n}\n\n// The nearest place on the circle: the radial projection outward from the\n// centre. Null at the centre itself, where every direction is equally near and\n// picking one would invent an answer the construction never contained —\n// degenerate relative to the radius, not merely exactly zero, for the reason\n// `projectionParameter()` is.\nexport function closestPointOnCircle(point: Point, center: Point, radius: number): Point | null {\n const dx = point.x - center.x\n const dy = point.y - center.y\n const length = Math.hypot(dx, dy)\n if (length === 0 || isDegenerateAtScale(length, radius)) return null\n return { x: center.x + (dx / length) * radius, y: center.y + (dy / length) * radius }\n}\n","// The angle-type classifier (ADR 0017), internal and imported directly.\n//\n// Its own module rather than a function in `geometryAdapter.ts`, where RFC 0006\n// first placed it (human decision, M3.5.a, 2026-09-16): the adapter is bundled\n// into the chunk `./schemas` shares, and that entry point never classifies an\n// angle. Still inside the measurement seam, and still plain arithmetic.\nimport { GRADING_BOUNDARY_EPSILON } from '../numerics/policy'\n\nexport type AngleType = 'acute' | 'right' | 'obtuse' | 'straight' | 'reflex'\n\n// The type of a folded measure in degrees, [0, 360), per ADR 0017's table. The\n// **real** measure is classified, never a rounded one: 89.6° is acute whatever a\n// label drawn to whole degrees reads. `GRADING_BOUNDARY_EPSILON` absorbs float\n// noise only, so a sweep resolved from a snapped 90° arm is right. Null for 0°\n// (and its float neighbours just under a full turn), which has no type.\n//\n// One classifier for display and grading (M3.5.a draws the right-angle square\n// through it; M3.6 grades through it), so the two never disagree about \"right\".\nexport function angleTypeOf(degrees: number): AngleType | null {\n const e = GRADING_BOUNDARY_EPSILON\n if (degrees <= e || degrees >= 360 - e) return null\n if (Math.abs(degrees - 90) <= e) return 'right'\n if (Math.abs(degrees - 180) <= e) return 'straight'\n if (degrees < 90) return 'acute'\n if (degrees < 180) return 'obtuse'\n return 'reflex'\n}\n","// The JSXGraph boundary proper: the only file that imports JXG, holds a\n// stdform array, or constructs a JXG.Coords.\n//\n// Everything here consumes undocumented internal API — `JXG.Math.Geometry`'s\n// meet functions and the `[c, b0, b1, a, k, r, q0, q1]` standard form they\n// take. None of that is in JSXGraph's public typings or its documented\n// contract, so `tests/measure/kernelContract.test.ts` pins the behavior this\n// engine relies on and **must be re-run on every JSXGraph upgrade**.\n//\n// No board. JSXGraph's meet functions need a board only to hand their result\n// back as a JXG.Coords, which reads three fields off it; KERNEL_CONTEXT\n// supplies exactly those. Every function here unwraps `usrCoords` before\n// returning, so no Coords object, stdform array, or context detail leaves this\n// module.\nimport JXG from 'jsxgraph'\nimport type { Point } from '../geometry/point'\n\n// A homogeneous point as the kernel reports it: `w` is the projective weight,\n// zero for an ideal (infinitely distant) point. Callers divide by `w` — or\n// reject the point — themselves, because what a weightless result means\n// depends on the question that was asked.\nexport interface HomogeneousPoint {\n w: number\n x: number\n y: number\n}\n\n// A line as the kernel takes it: the two points that define it, unbounded.\nexport interface KernelLine {\n from: Point\n to: Point\n}\n\nexport interface KernelCircle {\n center: Point\n radius: number\n}\n\n// The board-shaped argument the meet functions pass to JXG.Coords. Coords\n// reads `origin.scrCoords`, `unitX`, and `unitY` and nothing else, and only to\n// derive screen coordinates this engine never looks at. A unit context makes\n// that derivation the identity; freezing it — deeply, including the array —\n// makes it impossible for a kernel call to acquire state through it.\nconst KERNEL_CONTEXT = Object.freeze({\n origin: Object.freeze({ scrCoords: Object.freeze([1, 0, 0]) }),\n unitX: 1,\n unitY: 1,\n})\n\n// JSXGraph ships these kernel functions but no types for them, so the shapes\n// are declared here, at the one module allowed to know JSXGraph at all.\ninterface MeetKernel {\n meetSegmentSegment(\n p1: number[],\n p2: number[],\n q1: number[],\n q2: number[],\n ): [number[], number, number]\n meetLineLine(l1: number[], l2: number[], i: number, board: unknown): { usrCoords: number[] }\n meetLineCircle(lin: number[], circ: number[], i: number, board: unknown): { usrCoords: number[] }\n meetCircleCircle(\n circ1: number[],\n circ2: number[],\n i: number,\n board: unknown,\n ): { usrCoords: number[] }\n}\n\ninterface NormalizeKernel {\n normalize(stdform: number[]): number[]\n crossProduct(a: number[], b: number[]): number[]\n}\n\n// M1b.1 externalized `jsxgraph` instead of vendoring it, which exposed an\n// interop asymmetry: JSXGraph's CommonJS entry is transpiled ESM, so\n// `require('jsxgraph')` hands back `{ default: JXG, ...named }` without an\n// `__esModule` marker. The bundler's Node interop then wraps that object a\n// second time, and `JXG.Math` is undefined in the CommonJS build while it is\n// correct in the ESM one. Unwrapping one optional layer makes both builds\n// reach the same object; `scripts/verify-entry-points.mjs` loads each format\n// and would fail again if this were removed.\nconst kernel = (JXG as { default?: typeof JXG }).default ?? JXG\n\nconst meet = kernel.Math.Geometry as unknown as MeetKernel\nconst linearAlgebra = kernel.Math as unknown as NormalizeKernel\n\n// A point as the kernel wants it: homogeneous, weight first.\nexport function homogeneous(point: Point): [number, number, number] {\n return [1, point.x, point.y]\n}\n\n// The normalized standard form of the line through two points.\n//\n// Slot 4 must be seeded to 1, not left absent. `JXG.Math.normalize` derives a\n// radius as `stdform[4] / (2 * stdform[3])`, and a line's `stdform[3]` is 0:\n// seeding 1 makes that radius `Infinity` — a line is a circle of infinite\n// radius — where an absent slot would make it `NaN`, and `meetLineLine`\n// short-circuits to the invalid sentinel whenever either operand's radius is\n// NaN.\nexport function lineStdform(from: Point, to: Point): number[] {\n const [w, x, y] = linearAlgebra.crossProduct(homogeneous(from), homogeneous(to))\n return linearAlgebra.normalize([w ?? 0, x ?? 0, y ?? 0, 0, 1, 0, 0, 0])\n}\n\n// The normalized standard form of a circle. `normalize` recomputes slots 0 and\n// 4..7 from the center and radius it finds in slots 1..3, so only those need\n// to be supplied: slot 3 is the fixed quadratic coefficient of a circle.\nexport function circleStdform(circle: KernelCircle): number[] {\n return linearAlgebra.normalize([\n 0,\n -circle.center.x,\n -circle.center.y,\n 0.5,\n circle.radius,\n 0,\n 0,\n 0,\n ])\n}\n\nfunction unwrap(coords: { usrCoords: number[] }): HomogeneousPoint {\n const [w, x, y] = coords.usrCoords\n return { w: w ?? Number.NaN, x: x ?? Number.NaN, y: y ?? Number.NaN }\n}\n\n// Meet of two segments, treated as the infinite lines through their defining\n// points, plus the parameter of the meet along each. A parameter is 0 at the\n// first defining point and 1 at the second, so bounding a segment is a test on\n// the parameter, not a different kernel call. Parallel and collinear operands\n// report infinite parameters.\nexport function meetSegmentSegment(\n a1: Point,\n a2: Point,\n b1: Point,\n b2: Point,\n): { point: HomogeneousPoint; t: number; u: number } {\n const [coordinates, t, u] = meet.meetSegmentSegment(\n homogeneous(a1),\n homogeneous(a2),\n homogeneous(b1),\n homogeneous(b2),\n )\n return { point: unwrap({ usrCoords: coordinates }), t, u }\n}\n\n// Meet of two lines. Parallel lines return a genuine ideal point — `w` is 0\n// but `x` and `y` carry the shared direction — whereas identical lines return\n// the invalid sentinel, all three components 0.\nexport function meetLineLine(a: KernelLine, b: KernelLine): HomogeneousPoint {\n return unwrap(\n meet.meetLineLine(lineStdform(a.from, a.to), lineStdform(b.from, b.to), 0, KERNEL_CONTEXT),\n )\n}\n\n// One of the two meets of a line and a circle. `index` selects the root: 0 the\n// positive square root, 1 the negative. A tangent line returns the same point\n// at both indices; a line that misses returns the invalid sentinel.\nexport function meetLineCircle(\n line: KernelLine,\n circle: KernelCircle,\n index: 0 | 1,\n): HomogeneousPoint {\n return unwrap(\n meet.meetLineCircle(\n lineStdform(line.from, line.to),\n circleStdform(circle),\n index,\n KERNEL_CONTEXT,\n ),\n )\n}\n\n// One of the two meets of two circles, by the same index convention. The\n// kernel reduces this to a line-circle meet against the radical axis, so\n// disjoint, strictly contained, and *coincident* circles all return the same\n// invalid sentinel: the kernel cannot tell \"no shared point\" from \"every point\n// shared\".\nexport function meetCircleCircle(a: KernelCircle, b: KernelCircle, index: 0 | 1): HomogeneousPoint {\n return unwrap(meet.meetCircleCircle(circleStdform(a), circleStdform(b), index, KERNEL_CONTEXT))\n}\n","// The engine's measurement seam: the named measurements the rest of the engine\n// is allowed to ask for. Goal handlers, snapping, the point resolver, and\n// renderers pass plain coordinates and get plain answers back, so swapping the\n// geometry kernel stays a change inside `src/measure/`.\n//\n// Two kinds of measurement live here, and the split is deliberate:\n//\n// * **Coordinate arithmetic** — coincidence, angles. Plain math under the\n// numerical policy. Wrapping these in the kernel bought nothing but a\n// `Math.hypot` and an `atan2` at the cost of a DOM.\n// * **Projective geometry** — intersections. Real machinery, delegated to\n// `kernel.ts`, which is the only file that imports JSXGraph.\n//\n// Nothing here needs a board, a DOM, or any shared state, which is what\n// lets snapping call `intersectionsOf()` on every pointer move and lets a\n// server grade an attempt in bare Node.\nimport { projectionParameter } from '../geometry/distance'\nimport { distanceBetween, type Point } from '../geometry/point'\nimport {\n coordinatesCoincide,\n isDegenerateAtScale,\n isWithinSegmentParameter,\n} from '../numerics/policy'\nimport {\n meetCircleCircle,\n meetLineCircle,\n meetSegmentSegment,\n type HomogeneousPoint,\n} from './kernel'\n\nexport function pointsCoincide(a: Point, b: Point): boolean {\n return coordinatesCoincide(a, b)\n}\n\n// The direction of the line through two points, as an angle from the positive\n// x-axis in (-pi, pi].\n//\n// A line has no direction, so this angle is only meaningful modulo pi — see\n// `linesAngleBetweenDegrees`, its only caller, which folds it away. A\n// degenerate object (both points at the same place) defines no line and yields\n// NaN rather than an arbitrary angle, so that a comparison against it is false\n// rather than accidentally true.\nexport function lineAngleRadians(from: Point, to: Point): number {\n if (distanceBetween(from, to) === 0) return Number.NaN\n return Math.atan2(to.y - from.y, to.x - from.x)\n}\n\n// The direction from one point to another as a turn from the positive x-axis,\n// in [0, 1) — the unit `OnEntityOrigin.t` on a circle already uses, so an angle\n// and a point on a circle cannot drift apart (ADR 0010 § 2).\n//\n// Null where the two points coincide under the numerical policy: a zero-length\n// arm defines no direction, and answering 0 would make a degenerate angle look\n// like a valid 0° one.\nexport function directionTurn(from: Point, to: Point): number | null {\n if (coordinatesCoincide(from, to)) return null\n return foldTurn(Math.atan2(to.y - from.y, to.x - from.x) / (2 * Math.PI))\n}\n\n// Counter-clockwise from `from` to `to`, in [0, 1). Total: reversing the two is\n// exactly `1 - sweep`, with 0 fixed, which is the identity ADR 0010 § 2 requires\n// of arm reversal.\nexport function sweepTurn(from: number, to: number): number {\n return foldTurn(to - from)\n}\n\n// Into [0, 1). `-0` and a value that rounds up to exactly 1 both fold to 0, so a\n// full turn is spelled one way only.\nfunction foldTurn(turn: number): number {\n const folded = turn % 1\n const positive = folded < 0 ? folded + 1 : folded\n return positive >= 1 || positive === 0 ? 0 : positive\n}\n\nexport function linesAngleBetweenDegrees(\n from1: Point,\n to1: Point,\n from2: Point,\n to2: Point,\n): number {\n const angle1 = lineAngleRadians(from1, to1)\n const angle2 = lineAngleRadians(from2, to2)\n const angleDiffDegrees = ((angle1 - angle2) * 180) / Math.PI\n // Fold into [0, 180) (a line has no direction), then into [0, 90]\n // (parallel lines at 179° are really 1° apart, not 179° apart).\n const modAngle = ((angleDiffDegrees % 180) + 180) % 180\n return Math.min(modAngle, 180 - modAngle)\n}\n\n// How far apart two directed angles in degrees are, the shorter way round the\n// circle, in [0, 180]: a measured 359° is 2° from a target of 1°, not 358°\n// (RFC 0003 § Decision 6). Arithmetic, beside `linesAngleBetweenDegrees` for the\n// same reason — nothing projective, so the kernel is not involved.\nexport function angleDifferenceDegrees(a: number, b: number): number {\n const difference = (((a - b) % 360) + 360) % 360\n return Math.min(difference, 360 - difference)\n}\n\n// A segment or line reduced to the two points that define it, so the adapter\n// can be called with plain coordinates from anywhere — including the renderer,\n// which has no ids to hand and no validation context.\n//\n// The two linear kinds share one member rather than getting one each: they\n// differ only in whether the parameter is bounded, which is what `withinObject`\n// asks and the only place the distinction is read.\nexport interface MeasuredLinear {\n type: 'segment' | 'line'\n from: Point\n to: Point\n}\n\n// A circle as the seam takes it (M2.8): centre and radius, the same reduction\n// `ResolvedCircle` holds, because the kernel's standard form is built from\n// exactly those two and a through-point would have to be turned back into a\n// radius here anyway.\nexport interface MeasuredCircle {\n type: 'circle'\n center: Point\n radius: number\n}\n\nexport type MeasuredObject = MeasuredLinear | MeasuredCircle\n\n// Whether a parameter along the defining points lies on the object: a line\n// extends forever in both directions, a segment stops at its endpoints.\nfunction withinObject(object: MeasuredLinear, t: number): boolean {\n if (object.type === 'line') return true\n return isWithinSegmentParameter(t)\n}\n\n/**\n * The same object with its bounds removed: a segment becomes the line through\n * its endpoints, everything else is already unbounded and is returned as it is.\n *\n * For the one caller that has to tell \"these never meet\" from \"they meet past\n * the end of a segment\" — `evaluateGraph()`'s failure reason — and it lives\n * here because which kinds have an unbounded form is a fact about the measured\n * vocabulary, not about the graph.\n */\nexport function withoutBounds(object: MeasuredObject): MeasuredObject {\n return object.type === 'segment' ? { type: 'line', from: object.from, to: object.to } : object\n}\n\n// A kernel answer turned into a location, or null when it carries none. The\n// kernel encodes \"no meet\" three different ways — the all-zero sentinel, an\n// ideal point with weight zero, and NaN components for a zero-radius circle off\n// the other operand — and every one of them has to be rejected here rather than\n// divided through into a plausible-looking coordinate.\n//\n// The weight comes back normalized, so the division is the identity in practice;\n// it is written anyway because \"already normalized\" is an undocumented kernel\n// habit and `kernelContract.test.ts` is what would notice it changing.\nfunction locationOf(point: HomogeneousPoint): Point | null {\n const { w, x, y } = point\n if (!Number.isFinite(w) || !Number.isFinite(x) || !Number.isFinite(y)) return null\n // The weight is normalized, so 1 is the scale it has to be negligible\n // against. A weight that small means x and y carry no location at all, only\n // the direction of an ideal point.\n if (isDegenerateAtScale(w, 1)) return null\n return { x: withoutNegativeZero(x / w), y: withoutNegativeZero(y / w) }\n}\n\n// Negative zero is a sign the kernel's arithmetic happened to produce, never a\n// location: the axis a circle's lower root sits on comes back as -0 where the\n// upper one comes back as 0. It compares equal under `===` and `<` but not under\n// `Object.is`, `toEqual` or a JSON round trip through a store that pretty-prints,\n// so it is folded away once, here, rather than surprising a consumer that\n// compares two coordinates it obtained by different routes.\nfunction withoutNegativeZero(value: number): number {\n return value === 0 ? 0 : value\n}\n\n// Both roots of a conic meet, deduplicated and put in a fixed order.\n//\n// **Deduplication is tolerant, not exact.** A tangency is a double root, so the\n// kernel answers with the same point at both indices — but \"the same point\"\n// arrives as `0` at one index and `-0` at the other often enough that `===` on\n// the coordinates would report two intersections where the geometry has one.\n// `pointsCoincide` asks the question the numerical policy exists to answer, and\n// signed zero falls out of it because the distance between +0 and -0 is 0.\n//\n// **The order is the adapter's, not the kernel's.** An index there is the sign\n// of a square root, and which sign lands where flips when the operands are\n// given the other way round (`kernelContract.test.ts` pins that). A stored\n// `intersectionOf: [a, b]` with `which: 1` must survive the day something\n// reverses that pair, so the results are sorted by coordinate: the same two\n// points in the same order however they were asked for.\nfunction conicMeets(roots: readonly (Point | null)[]): Point[] {\n const meets: Point[] = []\n for (const root of roots) {\n if (!root) continue\n if (meets.some((existing) => pointsCoincide(existing, root))) continue\n meets.push(root)\n }\n // Two points at most, so a comparison sort is the whole of it. Signed zero\n // needs no special case: `0 - -0` is 0, which is the tie the second key\n // breaks.\n return meets.sort((a, b) => a.x - b.x || a.y - b.y)\n}\n\n// Where a linear object meets a circle, bounded by the linear object's own\n// domain. The meets lie on the line through `from` and `to` by construction, so\n// their parameter along it is their projection onto it — the same test a\n// segment/segment meet applies, asked of a root the kernel found rather than\n// one it reported a parameter for.\nfunction meetLinearCircle(linear: MeasuredLinear, circle: MeasuredCircle): Point[] {\n const line = { from: linear.from, to: linear.to }\n const kernelCircle = { center: circle.center, radius: circle.radius }\n const meets = conicMeets([\n locationOf(meetLineCircle(line, kernelCircle, 0)),\n locationOf(meetLineCircle(line, kernelCircle, 1)),\n ])\n if (linear.type === 'line') return meets\n return meets.filter((meet) => {\n const t = projectionParameter(meet, linear.from, linear.to)\n return t !== null && withinObject(linear, t)\n })\n}\n\n// Where two circles meet.\n//\n// **Coincidence is decided here, before the kernel is asked.** The kernel\n// reduces this to a line-circle meet against the radical axis, and two circles\n// that share every point have no radical axis: it returns the same invalid\n// sentinel for coincident circles as for disjoint ones. Concentric circles of\n// different radii are the same call and genuinely share nothing. Both answer\n// \"no intersection point\", because a construction that names one of infinitely\n// many shared points has not picked a point at all — but only one of the two is\n// a question the kernel could have answered, so the engine must not be the\n// thing that finds out by asking.\n//\n// Two zero-radius circles at one centre fall in here too, and share exactly that\n// centre. They answer `[]` with the rest, deliberately: a zero-radius circle is\n// the degenerate construction `entityModel.ts` names, no kind resolves to one,\n// and a meet between two of them is a point neither circle could have been\n// built to define.\nfunction meetCircles(a: MeasuredCircle, b: MeasuredCircle): Point[] {\n if (pointsCoincide(a.center, b.center)) return []\n return conicMeets([locationOf(meetCircleCircle(a, b, 0)), locationOf(meetCircleCircle(a, b, 1))])\n}\n\nfunction meetLinear(a: MeasuredLinear, b: MeasuredLinear): Point[] {\n const { point, t, u } = meetSegmentSegment(a.from, a.to, b.from, b.to)\n // Parallel or collinear: the kernel reports an ideal point with infinite\n // parameters. Collinear overlap is a shared stretch, not a snap target.\n if (!Number.isFinite(t) || !Number.isFinite(u)) return []\n const location = locationOf(point)\n if (!location) return []\n if (!withinObject(a, t) || !withinObject(b, u)) return []\n return [location]\n}\n\n// Intersection points of two objects. Real geometric machinery (the kernel's\n// projective meet), not coordinate arithmetic, so it belongs here per\n// FUTURE.md 1.1.\n//\n// Returns an empty array when the two never meet, are parallel, collinear or\n// coincident, or meet only outside a segment's bounds. A pair involving a\n// circle returns up to two points, in a fixed order; a linear pair still\n// returns at most one.\n//\n// The four cases are written as one nested pair of tests rather than a\n// kind-keyed table: the operands are symmetric, so a table would hold each\n// entry twice and the pairing is the seam's own business, not a dispatch\n// consumers extend. The registry's table over *entity* kinds\n// (`entities/relations.ts`) is the one that has to be exhaustive.\nexport function intersectionsOf(a: MeasuredObject, b: MeasuredObject): Point[] {\n if (a.type === 'circle') {\n return b.type === 'circle' ? meetCircles(a, b) : meetLinearCircle(b, a)\n }\n return b.type === 'circle' ? meetLinearCircle(a, b) : meetLinear(a, b)\n}\n","// Angle operations (M3.4) — ADR 0010.\n//\n// Three point parents in reading order, `[arm, vertex, arm]`, and nothing else.\n// The visible curved mark is this kind's shape descriptor rather than a stored\n// arc, and the arms a figure shows are ordinary segments the author draws to the\n// same points (M3.4 decision, 2026-09-15): dragging a shared arm point moves the\n// segment and the angle together, because both are built on it.\n//\n// Nothing here is measured by the kernel. An angle is `atan2` arithmetic, which\n// belongs to `geometryAdapter.ts`'s coordinate half, so the JSXGraph boundary\n// gains nothing from this kind.\nimport type { AngleDefinition, EntityOperations, ResolvedAngle } from '../entityModel'\nimport { distanceBetween, type Point } from '../point'\nimport { distancePointToSegment } from '../distance'\nimport { angleTypeOf } from '../../measure/angleType'\nimport { directionTurn, sweepTurn } from '../../measure/geometryAdapter'\n\n/**\n * How far out the mark sits, as a fraction of the shorter arm.\n *\n * Tuned in M3.5.a against rendered figures at phone (390 px) and desktop widths,\n * under both the `contain` and `stretch` projections. At 0.25 a mark on a short\n * arm drew under the vertex point's own dot, so a 135° or right-angle mark was\n * not visible at all; at 0.4 every mark clears the dot, and a narrow angle on\n * long arms still keeps its mark near the vertex. A fraction rather than a\n * screen constant because `distanceTo()` and `hitTest()` take no viewport: a\n * mark measured in pixels would be hit where it is not drawn.\n */\nexport const ANGLE_MARK_RADIUS_FRACTION = 0.4\n\nexport const angleOperations = {\n kind: 'angle',\n\n parents: (definition) => definition.parents,\n\n // Degeneracy is a property of the arms' existence, never of their directions\n // (ADR 0010 § 3): an arm of zero length resolves to nothing, while arms that\n // point the same way (0°) or opposite ways (180°) are real angles.\n resolve: (_definition, [firstArm, vertex, secondArm]) => {\n if (firstArm?.kind !== 'point' || vertex?.kind !== 'point' || secondArm?.kind !== 'point') {\n return null\n }\n const startTurn = directionTurn(vertex.at, firstArm.at)\n const endTurn = directionTurn(vertex.at, secondArm.at)\n if (startTurn === null || endTurn === null) return null\n const markRadius =\n Math.min(distanceBetween(vertex.at, firstArm.at), distanceBetween(vertex.at, secondArm.at)) *\n ANGLE_MARK_RADIUS_FRACTION\n return {\n kind: 'angle',\n vertex: vertex.at,\n startTurn,\n sweepTurn: sweepTurn(startTurn, endTurn),\n markRadius,\n }\n },\n\n toShape: (resolved) => ({\n shape: 'angle',\n vertex: resolved.vertex,\n startTurn: resolved.startTurn,\n sweepTurn: resolved.sweepTurn,\n radius: resolved.markRadius,\n }),\n\n // Nothing meets an angle. A point is already an entity nothing meets, so this\n // is not a new kind of exception — `relations.ts` carries the `NO_MEET` rows.\n toMeasured: () => null,\n\n // To the drawn mark, the way a circle measures to its curve rather than to its\n // disc: the arm directions are the angle, but the arc is what a pointer can\n // aim at, and the arms are separate entities with their own targets.\n // A right angle's mark is its square (M3.5.a), so distance follows the square:\n // a pointer on a side must hit what is drawn there, not the arc it replaced.\n distanceTo: (resolved, at) => {\n const square = rightAngleSquareOf(resolved)\n if (!square) return distanceToArc(resolved, at)\n const [first, corner, second] = square\n return Math.min(\n distancePointToSegment(at, first, corner),\n distancePointToSegment(at, corner, second),\n )\n },\n\n describe: (definition, nameOf) => {\n const [firstArm, vertex, secondArm] = definition.parents\n return `angle at ${nameOf(vertex)} from ${nameOf(firstArm)} to ${nameOf(secondArm)}`\n },\n\n // The rank a segment and a circle already hold: the mark is bounded, so it\n // does not shadow the canvas the way a line does.\n hitPriority: 1,\n\n // **Unsorted and not reversal-folded** (ADR 0010 § 1). ∠ABC and ∠CBA are\n // opposite sweeps, and a figure may legitimately mark both the minor and the\n // reflex angle at one vertex, which sorting the parents would refuse.\n // Parent ids are JSON-quoted, never joined: an id may contain `:`, and a\n // joined key made `a:b` + `c` and `a` + `b:c` one construction (decision A,\n // 2026-09-17).\n identityKey: (definition) => `angle:${JSON.stringify(definition.parents)}`,\n\n // No `parameterAt`: an angle is not a locus a point may lie on, which is also\n // why a document defining a point on one is refused at parse.\n} satisfies EntityOperations<AngleDefinition, ResolvedAngle>\n\n/**\n * The right-angle square's three drawn corners — on the first arm, the far\n * corner, on the second arm — or null when the sweep is not right under\n * `angleTypeOf()` (ADR 0017), in which case the mark is the arc.\n *\n * One definition for what `AngleShape` draws and what `distanceTo()` measures,\n * so a hit lands where the mark is. The side is `markRadius·√½`, which puts the\n * far corner at `markRadius` from the vertex, where the arc would have been.\n */\nexport function rightAngleSquareOf(\n resolved: Pick<ResolvedAngle, 'vertex' | 'startTurn' | 'sweepTurn' | 'markRadius'>,\n): readonly [Point, Point, Point] | null {\n const { vertex, startTurn, sweepTurn: sweep, markRadius } = resolved\n if (angleTypeOf(sweep * 360) !== 'right') return null\n const side = markRadius * Math.SQRT1_2\n const first = arcEnd(vertex, startTurn, side)\n const second = arcEnd(vertex, startTurn + sweep, side)\n return [first, { x: first.x + second.x - vertex.x, y: first.y + second.y - vertex.y }, second]\n}\n\n// The arc runs counter-clockwise from `startTurn` through `sweepTurn` at\n// `markRadius`. Inside that span the nearest place on the mark is radially\n// across from the query; outside it, it is whichever end of the arc is nearer —\n// the same shape of answer `closestPointOnSegment` gives past an endpoint.\nfunction distanceToArc(resolved: ResolvedAngle, at: Point): number {\n const { vertex, startTurn, sweepTurn: sweep, markRadius } = resolved\n const turn = directionTurn(vertex, at)\n // At the vertex every direction is equally near, so the distance to the mark\n // is the radius whichever way the arc runs.\n if (turn === null) return markRadius\n const fromStart = (((turn - startTurn) % 1) + 1) % 1\n if (fromStart <= sweep) return Math.abs(distanceBetween(vertex, at) - markRadius)\n return Math.min(\n distanceBetween(at, arcEnd(vertex, startTurn, markRadius)),\n distanceBetween(at, arcEnd(vertex, startTurn + sweep, markRadius)),\n )\n}\n\nfunction arcEnd(vertex: Point, turn: number, radius: number): Point {\n return {\n x: vertex.x + radius * Math.cos(2 * Math.PI * turn),\n y: vertex.y + radius * Math.sin(2 * Math.PI * turn),\n }\n}\n","// Circle operations (M2.8).\n//\n// The first kind that is not two points on a straight object, and the reason\n// M2.8 exists: everything a circle needs is here, in one file, and no consumer\n// was told about it. Its two parents are a centre and a point it passes\n// through — different roles, unlike a segment's interchangeable endpoints,\n// which is why nothing about this kind sorts them.\nimport { distancePointToCircle } from '../distance'\nimport type { CircleDefinition, EntityOperations, ResolvedCircle } from '../entityModel'\nimport { distanceBetween } from '../point'\n\nexport const circleOperations = {\n kind: 'circle',\n\n parents: (definition) => definition.parents,\n\n // The radius is measured, never stored: dragging either parent changes the\n // circle, and a stored radius would be the cached copy that goes stale.\n //\n // A circle through its own centre is the degenerate case `entityModel.ts`\n // names — a zero radius is a point, not a circle — and `null` puts it on the\n // same footing as parallel parents: nothing is drawn, and the graph reports\n // `degenerate` rather than inventing a circle nothing can be built on.\n resolve: (_definition, [center, through]) => {\n if (center?.kind !== 'point' || through?.kind !== 'point') return null\n const radius = distanceBetween(center.at, through.at)\n if (radius === 0) return null\n return { kind: 'circle', center: center.at, radius }\n },\n\n toShape: (resolved) => ({\n shape: 'circle',\n center: resolved.center,\n radius: resolved.radius,\n }),\n\n // The seam takes a circle as a centre and a radius, which is exactly what was\n // resolved: the through-point was consumed in measuring the radius and says\n // nothing further about where the circle is.\n toMeasured: (resolved) => ({\n type: 'circle',\n center: resolved.center,\n radius: resolved.radius,\n }),\n\n // To the curve, not to the disc: the centre of a large circle is nowhere near\n // it, so clicking the middle of one selects whatever is drawn there instead.\n distanceTo: (resolved, at) => distancePointToCircle(at, resolved.center, resolved.radius),\n\n // The two parents have different roles, so the sentence gives them different\n // words — the same asymmetry `identityKey` refuses to sort away.\n describe: (definition, nameOf) =>\n `circle centred on ${nameOf(definition.parents[0])} through ${nameOf(definition.parents[1])}`,\n\n // Ranked with the segment: both are bounded, so neither shadows the canvas\n // the way a line does, and a tie between them falls through to distance.\n hitPriority: 1,\n\n // **Not sorted**, unlike a segment's — RFC 0001 § Q9's example of why the key\n // is the kind's own. The circle centred on A through B is a different circle\n // from the one centred on B through A, and sorting the parents here would\n // report the second as a duplicate of the first and refuse to store it.\n // Parent ids are JSON-quoted, never joined: an id may contain `:`, and a\n // joined key made `a:b` + `c` and `a` + `b:c` one construction (decision A,\n // 2026-09-17).\n identityKey: (definition) => `circle:${JSON.stringify(definition.parents)}`,\n} satisfies EntityOperations<CircleDefinition, ResolvedCircle>\n","// Relations between entities, as opposed to behaviour of one kind (M2.3).\n//\n// Two of the operations a point's resolution needs are not properties of the\n// point at all: where two entities meet, and where a parameter t lands on a\n// host. Both are questions about *another* entity, and an intersection is a\n// question about two of them at once — which is why RFC 0001's scope for M2.3\n// asks for \"intersections through a central multimethod/adapter\" rather than\n// for a method on either operand. A method on the first operand would have to\n// know every kind the second could be, which is the switch the registry\n// exists to delete.\n//\n// Every table below is a mapped type, so they are exhaustive the same way\n// `EntityRegistry` is: the circle arriving at M2.8 added `circle:circle`,\n// `circle:line`, `line:circle`, `circle:segment`, `segment:circle` and a\n// `circle` host, and every one of them was a compile error here before it was\n// written rather than a runtime surprise. A ray or an arc arrives the same way.\n//\n// A pair that means nothing — where a *point* meets a segment, where a point\n// is parameterized — answers `null`. That is the acceptance criterion\n// \"unsupported operation pairs fail clearly rather than returning misleading\n// geometry\": nothing is invented, and the caller's existing \"not on screen\"\n// path takes over.\nimport { closestPointOnCircle, pointAlong, projectionParameter } from '../distance'\nimport type {\n EntityKind,\n ResolvedCircle,\n ResolvedEntity,\n ResolvedEntityOf,\n ResolvedLine,\n ResolvedSegment,\n} from '../entityModel'\nimport type { Point } from '../point'\nimport { intersectionsOf, type MeasuredObject } from '../../measure/geometryAdapter'\n\n// Each entry knows both of its operand types statically, so the two\n// conversions below are not a dispatch — the entry that calls one has already\n// been selected by kind.\nconst asMeasuredSegment = (resolved: ResolvedSegment): MeasuredObject => ({\n type: 'segment',\n from: resolved.from,\n to: resolved.to,\n})\n\nconst asMeasuredLine = (resolved: ResolvedLine): MeasuredObject => ({\n type: 'line',\n from: resolved.through[0],\n to: resolved.through[1],\n})\n\nconst asMeasuredCircle = (resolved: ResolvedCircle): MeasuredObject => ({\n type: 'circle',\n center: resolved.center,\n radius: resolved.radius,\n})\n\n// `which` selects between the two places a circle meets a line; a pair of\n// linear objects meets at most once, so anything but the first meet is empty.\n// The seam orders its answers by coordinate rather than by the kernel's root\n// index, so `which` names the same meet however the operands were paired — which\n// is what lets a stored `intersectionOf` survive being read back the other way\n// round.\nfunction meet(first: MeasuredObject, second: MeasuredObject, which: 0 | 1): Point | null {\n return intersectionsOf(first, second)[which] ?? null\n}\n\ntype EntityPair = `${EntityKind}:${EntityKind}`\n\ntype OperandsOf<P extends EntityPair> =\n P extends `${infer First extends EntityKind}:${infer Second extends EntityKind}`\n ? [ResolvedEntityOf<First>, ResolvedEntityOf<Second>]\n : never\n\ntype IntersectionAdapter = {\n readonly [P in EntityPair]: (operands: OperandsOf<P>, which: 0 | 1) => Point | null\n}\n\nconst NO_MEET = (): null => null\n\nconst intersectionAdapter: IntersectionAdapter = {\n // A point is a location, not a locus: nothing meets it, and answering with\n // its own coordinates would hand back geometry the construction never asked\n // for.\n 'point:point': NO_MEET,\n 'point:segment': NO_MEET,\n 'point:line': NO_MEET,\n 'segment:point': NO_MEET,\n 'line:point': NO_MEET,\n\n 'point:circle': NO_MEET,\n 'circle:point': NO_MEET,\n\n // An angle is a measure between two arms, not a locus: nothing meets it, and\n // a construction that wants the arms meets the segments drawn along them\n // (ADR 0010 § 1).\n 'point:angle': NO_MEET,\n 'angle:point': NO_MEET,\n 'segment:angle': NO_MEET,\n 'angle:segment': NO_MEET,\n 'line:angle': NO_MEET,\n 'angle:line': NO_MEET,\n 'circle:angle': NO_MEET,\n 'angle:circle': NO_MEET,\n 'angle:angle': NO_MEET,\n\n 'segment:segment': ([first, second], which) =>\n meet(asMeasuredSegment(first), asMeasuredSegment(second), which),\n 'segment:line': ([first, second], which) =>\n meet(asMeasuredSegment(first), asMeasuredLine(second), which),\n 'line:segment': ([first, second], which) =>\n meet(asMeasuredLine(first), asMeasuredSegment(second), which),\n 'line:line': ([first, second], which) =>\n meet(asMeasuredLine(first), asMeasuredLine(second), which),\n\n // A circle meets a bounded object only where the bound allows: the seam\n // filters the roots by the segment's own parameter, so a chord that leaves\n // the segment is not a meet the way it is for the line through it.\n 'segment:circle': ([first, second], which) =>\n meet(asMeasuredSegment(first), asMeasuredCircle(second), which),\n 'circle:segment': ([first, second], which) =>\n meet(asMeasuredCircle(first), asMeasuredSegment(second), which),\n 'line:circle': ([first, second], which) =>\n meet(asMeasuredLine(first), asMeasuredCircle(second), which),\n 'circle:line': ([first, second], which) =>\n meet(asMeasuredCircle(first), asMeasuredLine(second), which),\n 'circle:circle': ([first, second], which) =>\n meet(asMeasuredCircle(first), asMeasuredCircle(second), which),\n}\n\n/**\n * Where two resolved entities meet, or null when they do not meet at all,\n * meet only outside a segment's bounds, or are a pair no intersection is\n * defined for.\n *\n * The lookup erases the pair's concrete operand types at one explicit\n * boundary, exactly as `getGoalKindHandler()` erases a goal's subtype: the\n * key is built from the operands' own `kind` fields, so the entry selected is\n * by construction the one whose signature they satisfy.\n */\nexport function intersectEntities(\n first: ResolvedEntity,\n second: ResolvedEntity,\n which: 0 | 1 = 0,\n): Point | null {\n const entry = intersectionAdapter[`${first.kind}:${second.kind}`] as (\n operands: [ResolvedEntity, ResolvedEntity],\n which: 0 | 1,\n ) => Point | null\n return entry([first, second], which)\n}\n\ntype ParameterizationAdapter = {\n readonly [K in EntityKind]: (host: ResolvedEntityOf<K>, t: number) => Point | null\n}\n\n// Parameterization, both directions, keyed on the host's kind. It sits here\n// rather than on `EntityOperations` because it asks the same question\n// intersection does — *where on that other entity* — and because the host's\n// kind is the only thing that decides the answer.\n//\n// **`EntityOperations.parameterAt` stays unimplemented, deliberately** (M2.3.b).\n// Its frozen signature returns `number`, and the forward direction genuinely\n// has no answer for a host whose two defining points are degenerate at the\n// scale of the query (`numerics/policy.ts`): `snapToObject()` and\n// `moveStudentPoint()` both decline to place a point in that case, and the only\n// values a `number` could carry it with are NaN or a plausible-looking 0 that\n// would be written into a document as a real parameter. Widening that member to\n// `number | null` is a *reshape* of the frozen vocabulary, which the freeze note\n// in `entityModel.ts` excludes from its additive exception, so M2.3.b chose the\n// option that does not touch it.\n//\n// **M2.8 left it alone**, and the circle is the reason: a circle's parameter is\n// a turn, not an arc length, and it is answered here like every other host's\n// without the member being needed. Removing a declared-but-unused member is a\n// reshape of a frozen file, and the point of this slice is that a new kind cost\n// no reshape. It stays for RFC 0001's next opening, or M1b.5.\n//\n// t is not clamped. A segment's stored parameter is kept within [0, 1] by the\n// schema and by snapping; clamping here would silently repair a tampered\n// document into a plausible-looking point instead of placing it where the\n// document says.\n\n// Shared by every kind that cannot be travelled along, so `isParameterizableKind`\n// can recognize them by identity rather than by calling each entry and hoping a\n// null answer means the kind rather than the arguments.\nconst NO_PARAMETER = (): null => null\n\nconst parameterizationAdapter: ParameterizationAdapter = {\n // A point has no interior to walk along.\n point: NO_PARAMETER,\n // Neither has an angle: it is the measure between two arms, and the places a\n // point could lie belong to the segments a figure draws along them. This\n // entry is what `objectEntityKinds` reads to leave the angle out of snapping.\n angle: NO_PARAMETER,\n segment: (host, t) => pointAlong(host.from, host.to, t),\n line: (host, t) => pointAlong(host.through[0], host.through[1], t),\n // A turn rather than a run between two points: t is the fraction of a full\n // revolution from the positive x-axis, so 0.25 is a quarter turn whatever\n // defined the circle. Measured from the axis and not from the through-point,\n // because a resolved circle is a centre and a radius — two definitions of the\n // same circle must place a point at t in the same place, and one of them can\n // name a through-point the other never mentions.\n //\n // Nothing is clamped or wrapped: every real number is on the circle, and t\n // and t + 1 are the same place. Which is why a circle needs no counterpart to\n // the segment's [0, 1] rule in the schemas.\n circle: (host, t) => ({\n x: host.center.x + host.radius * Math.cos(2 * Math.PI * t),\n y: host.center.y + host.radius * Math.sin(2 * Math.PI * t),\n }),\n}\n\n/**\n * Whether a position can lie on this kind at all — whether it is a locus.\n *\n * Read off the same table that answers `pointOnEntity()`, so \"what can a point\n * be placed on\" has one answer rather than a list kept in step by hand.\n */\nexport function isParameterizableKind(kind: EntityKind): boolean {\n return parameterizationAdapter[kind] !== NO_PARAMETER\n}\n\n/**\n * The position at parameter `t` along `host`, or null when the host is not\n * something a parameter can travel along.\n */\nexport function pointOnEntity(host: ResolvedEntity, t: number): Point | null {\n const entry = parameterizationAdapter[host.kind] as (\n host: ResolvedEntity,\n t: number,\n ) => Point | null\n return entry(host, t)\n}\n\ntype ProjectionAdapter = {\n readonly [K in EntityKind]: (host: ResolvedEntityOf<K>, at: Point) => number | null\n}\n\n// The forward direction, and the one place the difference between a bounded and\n// an unbounded host is written down: a segment's parameter is clamped into\n// [0, 1] because past either end the nearest place *on the segment* is that\n// endpoint, and a line's is not because it has no end to stop at. This is the\n// `object.type === 'segment' ? clamp : raw` branch that `computeSnap()` and\n// `moveStudentPoint()` each carried a copy of.\n//\n// Null where the host has no interior to project onto, and where its two\n// defining points are too close together relative to the query to define a\n// direction at all — `projectionParameter()` decides the second, and dividing\n// by that length anyway would put the parameter at Infinity.\nconst projectionAdapter: ProjectionAdapter = {\n point: () => null,\n // Nothing projects onto an angle, for the reason its parameterization is\n // absent: it is not a locus.\n angle: () => null,\n segment: (host, at) => {\n const t = projectionParameter(at, host.from, host.to)\n return t === null ? null : Math.min(1, Math.max(0, t))\n },\n line: (host, at) => projectionParameter(at, host.through[0], host.through[1]),\n // The radial projection, reported in the same turn the parameterization\n // takes, and folded into [0, 1) so that a point placed by snapping stores the\n // parameter a reader would write by hand. Null at the centre, where every\n // direction is equally near: `closestPointOnCircle()` decides how close to the\n // centre is too close, on the same relative-degeneracy rule a segment uses.\n circle: (host, at) => {\n const nearest = closestPointOnCircle(at, host.center, host.radius)\n if (!nearest) return null\n const turn = Math.atan2(nearest.y - host.center.y, nearest.x - host.center.x) / (2 * Math.PI)\n return turn < 0 ? turn + 1 : turn\n },\n}\n\n/**\n * The parameter at which `at` projects onto `host`, in the domain the host\n * actually occupies — or null when the host cannot be parameterized, or is\n * degenerate at the scale of the query.\n *\n * `pointOnEntity(host, parameterOnEntity(host, at))` is the closest place on\n * `host` to `at`, which is why the two live side by side.\n */\nexport function parameterOnEntity(host: ResolvedEntity, at: Point): number | null {\n const entry = projectionAdapter[host.kind] as (host: ResolvedEntity, at: Point) => number | null\n return entry(host, at)\n}\n","// Line operations (M2.3).\n//\n// The unbounded two-point object. It resolves and identifies exactly as a\n// segment does — the two defining points — and differs in the two places the\n// boundedness shows: distance is to the infinite line, and it ranks last on a\n// tie because it crosses the whole viewport and would otherwise shadow\n// everything drawn near it.\nimport { distancePointToLine } from '../distance'\nimport type { EntityOperations, LineDefinition, ResolvedLine } from '../entityModel'\n\nexport const lineOperations = {\n kind: 'line',\n\n parents: (definition) => definition.parents,\n\n resolve: (_definition, [from, to]) => {\n if (from?.kind !== 'point' || to?.kind !== 'point') return null\n return { kind: 'line', through: [from.at, to.at] }\n },\n\n toShape: (resolved) => ({ shape: 'line', through: resolved.through }),\n toMeasured: (resolved) => ({\n type: 'line',\n from: resolved.through[0],\n to: resolved.through[1],\n }),\n\n distanceTo: (resolved, at) => distancePointToLine(at, resolved.through[0], resolved.through[1]),\n\n // \"through\", not \"from … to …\": the two points define it without bounding\n // it, and a listener who hears \"from A to B\" hears a segment.\n describe: (definition, nameOf) =>\n `line through ${nameOf(definition.parents[0])} and ${nameOf(definition.parents[1])}`,\n\n hitPriority: 2,\n\n // Parent ids are JSON-quoted, never joined: an id may contain `:`, and a\n // joined key made `a:b` + `c` and `a` + `b:c` one construction (decision A,\n // 2026-09-17).\n identityKey: (definition) => `line:${JSON.stringify([...definition.parents].sort())}`,\n} satisfies EntityOperations<LineDefinition, ResolvedLine>\n","// Point operations (M2.3).\n//\n// A point's three origins — a stored position, a parameter along a host, the\n// meeting of two entities — are one kind with three shapes, exactly as v1's\n// `PointOrigin` is, and they narrow on their own keys rather than on a tag\n// (see the freeze note in `entityModel.ts`). What made them look like three\n// kinds in v1 was that resolution, the deletion cascade and validation each\n// re-derived the branch for themselves; here it is written once.\n//\n// The two derived origins delegate to `relations.ts`, because neither answer\n// is a property of the point: both are questions about the entities it is\n// built on.\nimport { intersectEntities, pointOnEntity } from './relations'\nimport type { EntityId, EntityOperations, PointDefinition, ResolvedPoint } from '../entityModel'\nimport { distanceBetween } from '../point'\n\nconst NO_PARENTS: readonly EntityId[] = Object.freeze([])\n\n// Two decimals, trailing zeros dropped: a free point's coordinates are the one\n// number a description ever carries, and `2` reads better than `2.00` while\n// `2.35` still distinguishes two points a student placed a grid step apart.\nfunction spoken(value: number): string {\n return String(Number(value.toFixed(2)))\n}\n\nexport const pointOperations = {\n kind: 'point',\n\n // Free first: it is the majority of the points in any figure, and each of\n // these key tests is a lookup on an object whose shape the engine cannot\n // predict.\n parents: (definition) => {\n if ('at' in definition) return NO_PARENTS\n if ('on' in definition) return [definition.on]\n return definition.intersectionOf\n },\n\n resolve: (definition, parents) => {\n // A free point's coordinates *are* its construction (RFC 0001 § Q1), which\n // is why they are the only coordinates a document holds and why this is\n // the one origin that resolves without looking at anything else.\n if ('at' in definition) return { kind: 'point', at: definition.at }\n\n if ('on' in definition) {\n const host = parents[0]\n if (!host) return null\n const at = pointOnEntity(host, definition.t)\n return at ? { kind: 'point', at } : null\n }\n\n const [first, second] = parents\n if (!first || !second) return null\n const at = intersectEntities(first, second, definition.which ?? 0)\n return at ? { kind: 'point', at } : null\n },\n\n toShape: (resolved) => ({ shape: 'point', at: resolved.at }),\n\n // The measurement seam takes objects with two defining coordinates; a point\n // is not one, and inventing a degenerate pair for it would let a caller\n // measure an angle against nothing.\n toMeasured: () => null,\n\n distanceTo: (resolved, at) => distanceBetween(at, resolved.at),\n\n // The three origins, in the three sentences they are (M1b.3). A free point is\n // the only one that says where it is, because for a free point the\n // coordinates *are* the construction; the other two say what they were built\n // on, which is what a student needs to hear to know why it moved.\n describe: (definition, nameOf) => {\n if ('at' in definition)\n return `point at (${spoken(definition.at.x)}, ${spoken(definition.at.y)})`\n if ('on' in definition) return `point on ${nameOf(definition.on)}`\n const [first, second] = definition.intersectionOf\n return `point where ${nameOf(first)} and ${nameOf(second)} meet`\n },\n\n // Lowest, so a point wins every tie: the smaller the target, the more\n // deliberate a hit on it is.\n hitPriority: 0,\n\n // Parent order is kept, not sorted. `intersectionOf: [a, b]` and\n // `[b, a]` pick the same place only while a pair meets at most once; once a\n // circle arrives, `which` is read against the operand order, so sorting here\n // would call two different points the same construction.\n //\n // The document check never compares points — two points may coincide — but\n // construction commands do, to reuse a point rather than invent its twin\n // (RFC 0004 § Decision 1, `constructionIdentity.ts`).\n //\n // An intersection's parent ids are JSON-quoted, not joined, as every kind's\n // are (decision A, 2026-09-17): an id is any non-empty trimmed string, so\n // `a:b` + `c` and `a` + `b:c` joined with `:` were one key. `on` needs no\n // quoting: its one id is followed only by `t`, a number, which never holds a\n // `:`.\n identityKey: (definition) => {\n if ('at' in definition) return `point:at:${definition.at.x}:${definition.at.y}`\n if ('on' in definition) return `point:on:${definition.on}:${definition.t}`\n return `point:meet:${JSON.stringify(definition.intersectionOf)}:${definition.which ?? 0}`\n },\n} satisfies EntityOperations<PointDefinition, ResolvedPoint>\n","// Segment operations (M2.3).\n//\n// The bounded two-point object. Everything that distinguishes it from a line\n// is here: distance stops at the endpoints, and a parameter that leaves\n// [0, 1] leaves the object.\nimport { distancePointToSegment } from '../distance'\nimport type { EntityOperations, ResolvedSegment, SegmentDefinition } from '../entityModel'\n\nexport const segmentOperations = {\n kind: 'segment',\n\n parents: (definition) => definition.parents,\n\n // Parents arrive already resolved, in the order `parents()` gave. A caller\n // that could not resolve one passes a short list rather than a hole, so the\n // guard below is what \"a parent that did not resolve\" looks like from here.\n resolve: (_definition, [from, to]) => {\n if (from?.kind !== 'point' || to?.kind !== 'point') return null\n return { kind: 'segment', from: from.at, to: to.at }\n },\n\n toShape: (resolved) => ({ shape: 'segment', from: resolved.from, to: resolved.to }),\n toMeasured: (resolved) => ({ type: 'segment', from: resolved.from, to: resolved.to }),\n\n distanceTo: (resolved, at) => distancePointToSegment(at, resolved.from, resolved.to),\n\n // \"from … to …\", where a line says \"through\": the endpoints are the whole\n // difference between the two kinds, so the sentence names them as ends.\n describe: (definition, nameOf) =>\n `segment from ${nameOf(definition.parents[0])} to ${nameOf(definition.parents[1])}`,\n\n hitPriority: 1,\n\n // Sorted, because a segment from a to b is the same segment as one from b to\n // a. This is the key v1's `superRefine` already builds; the sort is what\n // makes it right here and wrong for a ray (RFC 0001 § Q9).\n // Parent ids are JSON-quoted, never joined: an id may contain `:`, and a\n // joined key made `a:b` + `c` and `a` + `b:c` one construction (decision A,\n // 2026-09-17).\n identityKey: (definition) => `segment:${JSON.stringify([...definition.parents].sort())}`,\n} satisfies EntityOperations<SegmentDefinition, ResolvedSegment>\n","// The entity operations registry (M2.3) — RFC 0001 § Q7.\n//\n// One place per kind to declare its own behaviour, so that adding a kind is\n// one new file plus one line here rather than an edit to every consumer that\n// happened to know what a segment was made of. `EntityRegistry` is a mapped\n// type over `EntityKind`, so the circle arriving at M2.8 is a compile error\n// until its operations exist.\n//\n// **Initialization is a frozen object literal, not a registration pass**, so\n// \"registry initialization is deterministic and does not depend on import\n// order\" holds by construction: there is no order to depend on. The goal\n// registry (`validation/goals/index.ts`) was a `registerGoalKind()` side\n// effect when this was written and became the same frozen literal at M2.5.\n//\n// The dispatchers below erase each kind's concrete definition and resolved\n// types at one explicit boundary, the same trade `getGoalKindHandler()` makes.\n// Lookup stays sound because the key is read off the value's own `kind` field:\n// the entry selected is by construction the one whose signature the value\n// satisfies.\nimport { angleOperations } from './angle'\nimport { circleOperations } from './circle'\nimport { isParameterizableKind } from './relations'\nimport { lineOperations } from './line'\nimport { pointOperations } from './point'\nimport { segmentOperations } from './segment'\nimport type {\n EntityDefinition,\n EntityId,\n EntityKind,\n EntityOperations,\n EntityRegistry,\n ResolvedEntity,\n ResolvedEntityOf,\n ShapeDescriptor,\n} from '../entityModel'\nimport type { Point } from '../point'\nimport type { MeasuredObject } from '../../measure/geometryAdapter'\n\n// The cross-kind operations, re-exported so that the registry has one front\n// door: a consumer asking what two entities do together imports it from the\n// same place it imports what one entity does alone.\nexport { intersectEntities, parameterOnEntity, pointOnEntity } from './relations'\n\nexport const entityRegistry: EntityRegistry = Object.freeze({\n point: pointOperations,\n segment: segmentOperations,\n line: lineOperations,\n circle: circleOperations,\n angle: angleOperations,\n})\n\n/**\n * Every kind the registry declares. Read off the registry rather than written\n * out again, so a consumer that has to enumerate kinds — a snap that offers\n * every locus, a toolbar that offers every construction — grows with it.\n */\nexport const entityKinds: readonly EntityKind[] = Object.freeze(\n Object.keys(entityRegistry) as EntityKind[],\n)\n\n/**\n * The loci: the entities a position can lie *on*.\n *\n * A point is a location, an angle is a measure between two arms, and the rest\n * are loci — which is why `parameterAt` has no meaning for either and why\n * snapping asks these kinds and not those. Read off `pointOnEntity()` rather\n * than listed here, so a kind that cannot be travelled along is excluded by the\n * same answer that makes it unparameterizable: left in, an angle's mark would\n * win `snapToObject`'s hit test and then decline to place the point, taking the\n * on-object snap to whatever is drawn under the mark with it.\n */\nexport const objectEntityKinds: readonly EntityKind[] = Object.freeze(\n entityKinds.filter((kind) => kind !== 'point' && isParameterizableKind(kind)),\n)\n\n// The registry's entries are declared per kind; every dispatcher below reads\n// one through this, which widens them to the union at a single named place.\n// It needs no assertion: `EntityOperations`' members are methods, so the\n// narrower parameter types are accepted here, and the lookup key is always\n// read off the value's own `kind`.\ntype ErasedOperations = EntityOperations<EntityDefinition, ResolvedEntity>\n\nfunction operationsFor(kind: EntityKind): ErasedOperations {\n return entityRegistry[kind]\n}\n\n/** Every entity `definition` is built on, in declaration order. */\nexport function parentsOfDefinition(definition: EntityDefinition): readonly EntityId[] {\n return operationsFor(definition.kind).parents(definition)\n}\n\n/**\n * Evaluates `definition` against its already-resolved parents, which must be\n * in the order `parentsOfDefinition()` gave. `null` means \"not on screen\".\n *\n * A caller that could not resolve every parent should not call this at all:\n * passing a short list would silently slide the parents that did resolve into\n * the wrong positions.\n */\nexport function resolveDefinition<D extends EntityDefinition>(\n definition: D,\n parents: readonly ResolvedEntity[],\n): ResolvedEntityOf<D['kind']> | null {\n return operationsFor(definition.kind).resolve(definition, parents) as ResolvedEntityOf<\n D['kind']\n > | null\n}\n\n/**\n * The resolved form of `definition` against points that have already been\n * resolved for the scene it belongs to — `null` when it is not on screen.\n *\n * For the consumers that hold a definition and a point map rather than a\n * context: `hitTest()` and `computeSnap()` are handed a scene assembled by a\n * renderer, and a preview scene has no context to ask. A caller holding an\n * `IndexedGeometryContext` asks that instead, because its answers are cached\n * and these are computed per call.\n */\nexport function resolveAgainstPoints(\n definition: EntityDefinition,\n points: ReadonlyMap<EntityId, Point>,\n): ResolvedEntity | null {\n const parents: ResolvedEntity[] = []\n for (const parentId of parentsOfDefinition(definition)) {\n const at = points.get(parentId)\n // The registry reads its parents positionally, so a short list would slide\n // the ones that did resolve into the wrong slots. An entity missing a\n // parent is not on screen either.\n if (!at) return null\n parents.push({ kind: 'point', at })\n }\n return resolveDefinition(definition, parents)\n}\n\n/** What a renderer draws. */\nexport function shapeOfResolved(resolved: ResolvedEntity): ShapeDescriptor {\n return operationsFor(resolved.kind).toShape(resolved)\n}\n\n/** The two-point reduction the measurement seam accepts, or null. */\nexport function measuredOfResolved(resolved: ResolvedEntity): MeasuredObject | null {\n return operationsFor(resolved.kind).toMeasured(resolved)\n}\n\n/** Distance to the drawn object, in math space. */\nexport function distanceToResolved(resolved: ResolvedEntity, at: Point): number {\n return operationsFor(resolved.kind).distanceTo(resolved, at)\n}\n\n/** Rank at equal distance, lower wins. */\nexport function hitPriorityOf(kind: EntityKind): number {\n return operationsFor(kind).hitPriority\n}\n\n/** The key deciding whether two definitions are the same construction. */\nexport function identityKeyOf(definition: EntityDefinition): string {\n return operationsFor(definition.kind).identityKey(definition)\n}\n\n/**\n * One sentence saying what this entity is, for the semantic companion (M1b.3).\n *\n * `nameOf` resolves a parent id to the name that surface calls it — the\n * renderer passes `entityDisplayName()`, so \"segment from A to B\" names the\n * two points the canvas has labelled `A` and `B`. A caller with no naming of\n * its own may pass the identity function and get ids.\n */\nexport function describeDefinition(\n definition: EntityDefinition,\n nameOf: (id: EntityId) => string,\n): string {\n return operationsFor(definition.kind).describe(definition, nameOf)\n}\n","// Reading and building the two records both v2 documents are made of.\n//\n// `entities` and `presentation` are keyed by ids that arrive from untrusted\n// JSON, and a JavaScript object answers for names nobody stored in it. That is\n// two separate problems, and this file is both halves (M2.G.1).\n//\n// **Reading.** `record[id]` resolves through the prototype chain, so an id like\n// `constructor` or `toString` reads as a *declared* entity whose `kind` is\n// `undefined`. That is worse than a false positive: `parentsOfDefinition()`\n// indexes the entity registry by that `undefined` and throws, out of a\n// validator whose contract is to return errors. `entryOf()` is the own-property\n// read every dynamic lookup uses instead, so an undeclared id is `undefined`\n// wherever it is asked about.\n//\n// **Writing.** `__proto__` is the one id no record can hold: assigning it on an\n// object literal reaches `Object.prototype`'s setter and creates no own\n// property, and `z.record()` drops the entry while reporting success. So it is\n// refused where a record is built — at the parse boundary and in the migration\n// steps — rather than silently dropped, because an entity that vanishes without\n// a message is data loss the host cannot even detect.\n//\n// Every *other* prototype name is an ordinary id. `constructor`, `toString`,\n// `valueOf` and `hasOwnProperty` survive `z.record()` as own properties and\n// `entryOf()` reads them correctly; `prototype` is not inherited from\n// `Object.prototype` at all. The engine has no opinion about any of them.\nimport type { EntityId } from './entityModel'\n\n/**\n * The one id a record cannot carry, and therefore the one the parse boundary\n * and the migration steps refuse.\n */\nexport const unstorableEntityId = '__proto__'\n\nexport function isUnstorableEntityId(id: string): boolean {\n return id === unstorableEntityId\n}\n\n/** The message every refusal of it uses, so the two boundaries agree. */\nexport function unstorableEntityIdMessage(id: string): string {\n return `${id} cannot be used as an entity id`\n}\n\n/**\n * A record entry, read as a declaration rather than as a property.\n *\n * The `undefined` this returns for an undeclared id is what the callers already\n * expect under `noUncheckedIndexedAccess`; the difference is that they now get\n * it for `constructor` too.\n */\nexport function entryOf<T>(record: Record<EntityId, T>, id: EntityId): T | undefined {\n return Object.prototype.hasOwnProperty.call(record, id) ? record[id] : undefined\n}\n\n/**\n * An accumulator for a record being built from untrusted input.\n *\n * Null-prototype, so nothing inherited can be mistaken for an entry while the\n * record is being filled and so `__proto__` would be stored rather than\n * swallowed — the migration steps refuse that key outright, and this is what\n * makes the refusal a decision instead of a description of what the literal\n * would have done anyway.\n */\nexport function emptyEntityRecord<T>(): Record<EntityId, T> {\n return Object.create(null) as Record<EntityId, T>\n}\n","import { z } from 'zod'\n\nexport const geometryIdSchema = z\n .string()\n .min(1)\n .refine((id) => id === id.trim(), {\n message: 'id must not have leading or trailing spaces',\n })\n","import { z } from 'zod'\nimport { identityKeyOf, parentsOfDefinition } from '../geometry/entities'\nimport { entryOf, unstorableEntityId, unstorableEntityIdMessage } from '../geometry/entityRecord'\nimport type { EntityDefinition, EntityId, EntityPresentation } from '../geometry/entityModel'\nimport { geometryIdSchema } from './identifier.schema'\n\n// The structural rules ADR 0008 states over an `entities` record, in one place\n// because both v2 documents are the same record and had the same rules written\n// twice in v1 — once in `exercise.schema.ts` over `givens[]` and once in\n// `geometryState.schema.ts` over `points` and `constructions[]`, in two\n// vocabularies, with two sets of messages that had drifted.\n//\n// What is *not* here is as deliberate as what is:\n//\n// * **cycles**, which `geometryContext.ts` already reports with the ring they\n// run through. A parse that refused them would take the authoring warnings\n// away from the one document that needs them most — a broken exercise must\n// be describable, not merely rejected — and the walk is where the ring is\n// known;\n// * **cross-document rules**. A schema sees one document. Which declaration\n// wins a contested id, and whether an attempt entity may name a given, are\n// questions only something holding both can answer, and they stay in\n// `validateGeometryStateInput()` and the context (ADR 0008 § Amendment).\n\n/**\n * The `entities` / `presentation` record shape, guarded before `z.record()`\n * sees it.\n *\n * `z.record()` reports success and drops a `__proto__` entry, so a v2 document\n * this engine wrote and read back loses that entity at load with no error at\n * all. Two tidier places to catch it do not work, and were measured rather than\n * argued (M2.G.1): a key schema is never asked, because zod skips a `__proto__`\n * key *before* validating it, and a `superRefine` on the record runs after the\n * entry is already gone. `z.preprocess` is the one hook that still sees the own\n * property.\n *\n * One helper for all four records — both records of both documents — because a\n * guard copied four times is a guard three of them will eventually not have.\n */\nexport function entityRecordSchema<Value extends z.ZodType>(\n value: Value,\n): z.ZodType<Record<EntityId, z.output<Value>>, unknown> {\n return z.preprocess(\n (raw, ctx) => {\n if (\n typeof raw === 'object' &&\n raw !== null &&\n Object.prototype.hasOwnProperty.call(raw, unstorableEntityId)\n ) {\n ctx.addIssue({\n code: 'custom',\n path: [unstorableEntityId],\n message: unstorableEntityIdMessage(unstorableEntityId),\n })\n return z.NEVER\n }\n return raw\n },\n z.record(geometryIdSchema, value),\n )\n}\n\n/** A structural fault, at a path relative to the document that produced it. */\nexport interface EntityDocumentIssue {\n path: Array<string | number>\n message: string\n}\n\n/** The half of either v2 document these rules read. */\nexport interface EntityDocumentShape {\n entities: Record<EntityId, EntityDefinition>\n presentation: Record<EntityId, EntityPresentation>\n}\n\nexport interface EntityDocumentRules {\n /**\n * Whether a parent this document does not declare is an error.\n *\n * True for an exercise, which is self-contained. **False for an attempt**: a\n * student's point sits on a given segment, and since ADR 0008's amendment a\n * v2 attempt no longer copies the givens, so most of its parents live in the\n * other document. `validateGeometryStateInput()` completes the check once it\n * has the exercise — the same staging v1 used for point origins.\n */\n parentsMustBeDeclared: boolean\n}\n\n/** \"a segment\", \"an angle\" — the kinds are data in these messages. */\nfunction aKind(kind: EntityDefinition['kind']): string {\n return kind === 'angle' ? `an ${kind}` : `a ${kind}`\n}\n\n// Only a locus can be lain on or intersected, and only a point can define a\n// segment, a line, a circle or an angle. Checked per parent rather than per kind\n// because the answer needs the *parent's* kind, which is why v1 could only ask\n// it of the declarations it happened to hold.\nfunction parentKindIssue(\n id: EntityId,\n definition: EntityDefinition,\n parentId: EntityId,\n parent: EntityDefinition,\n): string | null {\n if (definition.kind !== 'point') {\n return parent.kind === 'point'\n ? null\n : `${id} references ${parentId}, which is ${aKind(parent.kind)} and not a point`\n }\n // A point lies on a locus. A point is a location and an angle is the measure\n // between two arms, so neither can host one or be met by one (ADR 0010 § 1 and\n // its amendment 3): the places along an arm belong to the segment a figure\n // draws there. Refused at parse, because such a point parses cleanly, never\n // resolves, and is then reported as `degenerate` — which misdescribes it.\n if (parent.kind !== 'point' && parent.kind !== 'angle') return null\n return 'on' in definition\n ? `${id} is on ${parentId}, which is ${aKind(parent.kind)}`\n : `${id} intersects ${parentId}, which is ${aKind(parent.kind)}`\n}\n\n/**\n * Every structural fault a single v2 document can see in itself.\n *\n * Returned rather than reported, so both `superRefine` blocks map them onto\n * their own issue paths and neither owns the rules.\n */\nexport function entityDocumentIssues(\n document: EntityDocumentShape,\n rules: EntityDocumentRules,\n): EntityDocumentIssue[] {\n const issues: EntityDocumentIssue[] = []\n const entities = Object.entries(document.entities)\n const declares = (id: EntityId): boolean =>\n Object.prototype.hasOwnProperty.call(document.entities, id)\n\n // Per kind, because `${kind}:${sorted parents}` is only right where parent\n // order is insignificant (RFC 0001 § Q9), and the key already carries the\n // kind. Points are skipped exactly as v1 skips them: two points at the same\n // coordinates are two points a goal may name separately.\n const firstIdByIdentityKey = new Map<string, EntityId>()\n\n for (const [id, definition] of entities) {\n const parents = parentsOfDefinition(definition)\n const path = ['entities', id]\n\n if (parents.includes(id)) {\n issues.push({ path, message: `${id} is its own parent` })\n }\n if (new Set(parents).size !== parents.length) {\n // Arity-neutral since the angle arrived with three parents (ADR 0010).\n issues.push({ path, message: `${id} must reference distinct entities` })\n }\n\n for (const parentId of parents) {\n const parent = entryOf(document.entities, parentId)\n if (!parent) {\n if (rules.parentsMustBeDeclared) {\n issues.push({ path, message: `${id} references missing entity ${parentId}` })\n }\n continue\n }\n const message = parentKindIssue(id, definition, parentId, parent)\n if (message) issues.push({ path, message })\n // The [0, 1] rule needs the host's kind, which is why it is here and not\n // in the definition schema. A line takes any t, and on a circle every\n // real value is a turn that lands on it.\n if (\n 'on' in definition &&\n parent.kind === 'segment' &&\n (definition.t < 0 || definition.t > 1)\n ) {\n issues.push({\n path: [...path, 't'],\n message: `${id} must have t within [0, 1] on segment ${parentId}`,\n })\n }\n }\n\n if (definition.kind === 'point') continue\n const identityKey = identityKeyOf(definition)\n const firstId = firstIdByIdentityKey.get(identityKey)\n if (firstId !== undefined) {\n issues.push({ path, message: `${id} duplicates the geometry of ${firstId}` })\n } else {\n firstIdByIdentityKey.set(identityKey, id)\n }\n }\n\n // Presentation is a sibling record, so it can name an entity that is not\n // there — a label left behind by a deletion. It is reported rather than\n // ignored: an orphaned entry is how a document starts disagreeing with itself.\n for (const [id, entry] of Object.entries(document.presentation)) {\n if (!declares(id)) {\n issues.push({\n path: ['presentation', id],\n message: `presentation entry for unknown entity ${id}`,\n })\n continue\n }\n // Only an angle has a measure (ADR 0010 amendment 1). This is the one place\n // holding both records, so it is the only one that can see the entity's kind\n // beside its presentation entry; without the rule a strict document could\n // hang a degree readout on a point, a line or a circle, and every renderer\n // would have to decide independently what to do with it.\n if (entry.measureLabel !== undefined && entryOf(document.entities, id)?.kind !== 'angle') {\n issues.push({\n path: ['presentation', id],\n message: `presentation entry for ${id} sets measureLabel, which only an angle has`,\n })\n }\n }\n\n return issues\n}\n\n/**\n * The parent rules a v2 attempt cannot check alone, completed against the\n * exercise it is an attempt at.\n *\n * `entityDocumentIssues()` skips a parent an attempt does not declare, because\n * since ADR 0008 § Amendment most of them are given entities living in the\n * other document. This is the other half, and the reason it is here rather than\n * in `validateGeometryStateInput()`: a parent that turns out to be a given is\n * judged by exactly the rules a declared one is, in exactly the same words.\n *\n * The *kind* rules are asked only of the parents the attempt does not declare —\n * the rest have already been reported by the schema, and reporting them twice\n * would make one fault look like two. One rule reaches the declared ones as\n * well, because the schema cannot ask it: an entity **the exercise declares**\n * may not be defined on an entity it does not (M2.G.5), and a parent the\n * attempt declares is exactly the case that violates it.\n */\nexport function crossDocumentParentIssues(\n attempt: EntityDocumentShape,\n given: Record<EntityId, EntityDefinition>,\n): EntityDocumentIssue[] {\n const issues: EntityDocumentIssue[] = []\n for (const [id, definition] of Object.entries(attempt.entities)) {\n const path = ['entities', id]\n // A re-declared draggable given is still the *exercise's* entity, so its\n // parents must be too. Defined on student work instead, it would outlive\n // the entity it stands on: deletion never removes a given, whatever the\n // cascade says, so the given would be left with a parent that is gone and\n // the attempt would fail the check it just passed (M2.G.5).\n const reDeclaresGiven = entryOf(given, id) !== undefined\n for (const parentId of parentsOfDefinition(definition)) {\n if (Object.prototype.hasOwnProperty.call(attempt.entities, parentId)) {\n if (reDeclaresGiven && !entryOf(given, parentId)) {\n issues.push({\n path,\n message: `${id} is declared by the exercise and cannot be defined on ${parentId}, which it does not declare`,\n })\n }\n continue\n }\n const parent = entryOf(given, parentId)\n if (!parent) {\n issues.push({ path, message: `${id} references missing entity ${parentId}` })\n continue\n }\n const message = parentKindIssue(id, definition, parentId, parent)\n if (message) issues.push({ path, message })\n // A given re-declared off its segment is already a collision, and one\n // fault is reported once.\n if (\n !reDeclaresGiven &&\n 'on' in definition &&\n parent.kind === 'segment' &&\n (definition.t < 0 || definition.t > 1)\n ) {\n issues.push({\n path: [...path, 't'],\n message: `${id} must have t within [0, 1] on segment ${parentId}`,\n })\n }\n }\n }\n return issues\n}\n\n/** The two exercise fields the override invariant reads (ADR 0016). */\nexport interface GivenAuthority {\n entities: Record<EntityId, EntityDefinition>\n draggableEntityIds: readonly EntityId[]\n}\n\n/**\n * Whether the student may drag this given (ADR 0016, RFC 0006): listed in\n * `draggableEntityIds` **and** a free or `on` point. An intersection may be\n * listed — the schema checks only that the id names a point — but it sits\n * where its parents put it, so no drag moves it.\n *\n * One predicate for the goal rule, the re-declaration classes below, the\n * context and the student store, so none of them can disagree about which\n * givens an attempt has authority over.\n */\nexport function isStudentDraggableGiven(exercise: GivenAuthority, id: EntityId): boolean {\n const definition = entryOf(exercise.entities, id)\n if (definition?.kind !== 'point' || 'intersectionOf' in definition) return false\n return exercise.draggableEntityIds.includes(id)\n}\n\n/**\n * What an id both documents declare means (ADR 0016 § The invariant). An\n * attempt may override only the *position* of a point the student may drag;\n * it never redefines a given's dependencies.\n *\n * - `ignored` — a given point the student may not drag, re-declared as any\n * point: unused, and dropped in silence on load, because every migrated v1\n * attempt carries such re-declarations (ADR 0008 § Amendment);\n * - `override` — a student-draggable given in its own origin form: free stays\n * free, and `on H` stays on the same `H` with only `t` changed, within\n * `[0, 1]` on a segment. The attempt's position wins;\n * - `collision` — every other pair: a draggable given re-parented or given\n * another form, or any pair that is not point and point. The loader refuses\n * it and the context reports `duplicate-id`.\n *\n * An override has exactly the exercise's parents, and the exercise is acyclic,\n * so no loadable attempt can close a dependency cycle through a given.\n */\nexport type GivenRedeclaration = 'ignored' | 'override' | 'collision'\n\nexport function classifyGivenRedeclaration(\n exercise: GivenAuthority,\n id: EntityId,\n given: EntityDefinition,\n declared: EntityDefinition,\n): GivenRedeclaration {\n if (given.kind !== 'point' || declared.kind !== 'point') return 'collision'\n if (!isStudentDraggableGiven(exercise, id)) return 'ignored'\n if ('at' in given) return 'at' in declared ? 'override' : 'collision'\n if (!('on' in given) || !('on' in declared) || declared.on !== given.on) return 'collision'\n // The host is the exercise's, since `given` is. A slide never leaves a\n // segment, so a position off one is not a position a drag could produce.\n const host = entryOf(exercise.entities, given.on)\n if (host?.kind === 'segment' && !(declared.t >= 0 && declared.t <= 1)) return 'collision'\n return 'override'\n}\n\n/**\n * The collisions of `classifyGivenRedeclaration()`, as load errors.\n *\n * The context reports these as `duplicate-id` issues and then resolves the id\n * to the *exercise's* declaration, so an attempt declaring segment `A` over\n * given point `A` used to load clean and then have the given graded as the\n * student's construction (M2.G.2). Refusing the document is the fix: nothing\n * downstream has to decide what a contested id means, because it never gets\n * one.\n */\nexport function crossDocumentIdCollisionIssues(\n attempt: EntityDocumentShape,\n exercise: GivenAuthority,\n): EntityDocumentIssue[] {\n const issues: EntityDocumentIssue[] = []\n for (const [id, definition] of Object.entries(attempt.entities)) {\n const declared = entryOf(exercise.entities, id)\n if (!declared) continue\n if (classifyGivenRedeclaration(exercise, id, declared, definition) !== 'collision') continue\n issues.push({\n path: ['entities', id],\n message:\n declared.kind === 'point' && definition.kind === 'point'\n ? `${id} is a point the exercise declares, and an attempt may change only its position`\n : `${id} is already declared by the exercise as a ${declared.kind}`,\n })\n }\n return issues\n}\n\n/** `answerEntityIds` and `draggableEntityIds` are the same list rule twice. */\nexport function entityIdListIssues(\n ids: readonly EntityId[],\n field: string,\n document: EntityDocumentShape,\n options: { pointsOnly?: boolean } = {},\n): EntityDocumentIssue[] {\n const issues: EntityDocumentIssue[] = []\n const seen = new Set<EntityId>()\n ids.forEach((id, index) => {\n const path = [field, index]\n const definition = entryOf(document.entities, id)\n if (!definition) {\n issues.push({ path, message: `${field} names unknown entity ${id}` })\n } else if (options.pointsOnly && definition.kind !== 'point') {\n issues.push({ path, message: `${field} names ${id}, which is ${aKind(definition.kind)}` })\n }\n if (seen.has(id)) issues.push({ path, message: `duplicate ${field} entry: ${id}` })\n seen.add(id)\n })\n return issues\n}\n","import { z } from 'zod'\n\n// What an exercise may allow a student to construct, and what a stored object\n// declares itself to be. One name per entity kind, spelled the way the stored\n// document spells it — `EntityKind` is the same list in the M2 vocabulary.\nexport const objectTypeNameSchema = z.enum(['point', 'segment', 'line', 'circle', 'angle'])\n\nexport type ObjectTypeName = z.infer<typeof objectTypeNameSchema>\n","import { z } from 'zod'\nimport { geometryIdSchema } from '../identifier.schema'\nimport { objectTypeNameSchema } from '../objectTypeName.schema'\n\nexport const angleBisectorGoalSchema = z\n .object({\n id: geometryIdSchema,\n type: z.literal('angleBisector'),\n // Constrained to segment or line by the declaration.\n objectType: objectTypeNameSchema,\n // Must resolve to a given angle; checked in exercise.schema.ts.\n referenceAngleId: geometryIdSchema,\n toleranceDegrees: z.number().positive().max(90),\n })\n .strict()\n\nexport type AngleBisectorGoal = z.infer<typeof angleBisectorGoalSchema>\n","import { z } from 'zod'\n\n// The five angle types ADR 0017 classifies a real measure into. The same names\n// as `AngleType` in `measure/angleType.ts`, which this layer may not import.\nexport const angleTypeSchema = z.enum(['acute', 'right', 'obtuse', 'straight', 'reflex'])\n\n/**\n * What an angle goal asks of a measure (RFC 0006 § Decision 2): a degree with a\n * tolerance, or a named type. A type takes no tolerance — \"right, ± 2\" is\n * already `degrees` 90 ± 2 — so the strict member refuses one.\n */\nexport const angleCriterionSchema = z.discriminatedUnion('kind', [\n z\n .object({\n kind: z.literal('degrees'),\n // `[0, 360)`: 360° and 0° are the same directed angle (ADR 0010 amendment 2).\n targetDegrees: z.number().min(0).lt(360),\n toleranceDegrees: z.number().positive().max(180),\n })\n .strict(),\n z.object({ kind: z.literal('type'), angleType: angleTypeSchema }).strict(),\n])\n\nexport type AngleCriterion = z.infer<typeof angleCriterionSchema>\n","import { z } from 'zod'\nimport { geometryIdSchema } from '../identifier.schema'\nimport { angleCriterionSchema } from './angleCriterion.schema'\n\nexport const angleMeasureGoalSchema = z\n .object({\n id: geometryIdSchema,\n type: z.literal('angleMeasure'),\n // The student answers with an angle entity.\n objectType: z.literal('angle'),\n // The angle must be marked at this given point.\n vertexPointId: geometryIdSchema,\n interpretation: z.enum(['directed', 'undirected']),\n criterion: angleCriterionSchema,\n })\n .strict()\n\nexport type AngleMeasureGoal = z.infer<typeof angleMeasureGoalSchema>\n","import { z } from 'zod'\nimport { geometryIdSchema } from '../identifier.schema'\nimport { objectTypeNameSchema } from '../objectTypeName.schema'\n\nexport const createGoalSchema = z\n .object({\n id: geometryIdSchema,\n type: z.literal('create'),\n objectType: objectTypeNameSchema,\n })\n .strict()\n\nexport type CreateGoal = z.infer<typeof createGoalSchema>\n","import { z } from 'zod'\nimport { geometryIdSchema } from '../identifier.schema'\nimport { angleCriterionSchema } from './angleCriterion.schema'\n\n/**\n * The first **figure goal** (ADR 0016): it judges a given angle as the evaluated\n * figure resolves it, rather than an entity the student built.\n *\n * An exercise that pins an angle's vertex and first arm and lets the student\n * drag the second has no candidate to grade — a given is never a candidate\n * (ADR 0008 § Amendment) — so its only subject is the figure itself. What makes\n * that safe is the override invariant: an attempt may move an authorized point\n * and nothing else, so the figure judged here is the exercise's.\n */\nexport const givenAngleGoalSchema = z\n .object({\n id: geometryIdSchema,\n type: z.literal('givenAngle'),\n // A given angle, and one the student can move: both checked in the\n // declaration, where the rest of the exercise is visible.\n angleId: geometryIdSchema,\n interpretation: z.enum(['directed', 'undirected']),\n criterion: angleCriterionSchema,\n })\n .strict()\n\nexport type GivenAngleGoal = z.infer<typeof givenAngleGoalSchema>\n","import { z } from 'zod'\nimport { geometryIdSchema } from '../identifier.schema'\n\nexport const midpointGoalSchema = z\n .object({\n id: geometryIdSchema,\n type: z.literal('midpoint'),\n // The student answers with a constructed point, never a segment or line.\n objectType: z.literal('point'),\n // Must resolve to a given segment; checked in exercise.schema.ts.\n referenceObjectId: geometryIdSchema,\n // Same units as the viewport, not pixels.\n toleranceUnits: z.number().positive().finite(),\n })\n .strict()\n\nexport type MidpointGoal = z.infer<typeof midpointGoalSchema>\n","import { z } from 'zod'\nimport { geometryIdSchema } from '../identifier.schema'\nimport { objectTypeNameSchema } from '../objectTypeName.schema'\n\nexport const parallelGoalSchema = z\n .object({\n id: geometryIdSchema,\n type: z.literal('parallel'),\n objectType: objectTypeNameSchema,\n referenceObjectId: geometryIdSchema,\n throughPointId: geometryIdSchema,\n toleranceDegrees: z.number().positive().max(90),\n })\n .strict()\n\nexport type ParallelGoal = z.infer<typeof parallelGoalSchema>\n","import { z } from 'zod'\nimport { geometryIdSchema } from '../identifier.schema'\nimport { objectTypeNameSchema } from '../objectTypeName.schema'\n\nexport const perpendicularGoalSchema = z\n .object({\n id: geometryIdSchema,\n type: z.literal('perpendicular'),\n objectType: objectTypeNameSchema,\n referenceObjectId: geometryIdSchema,\n throughPointId: geometryIdSchema,\n toleranceDegrees: z.number().positive().max(90),\n })\n .strict()\n\nexport type PerpendicularGoal = z.infer<typeof perpendicularGoalSchema>\n","import { z } from 'zod'\nimport { geometryIdSchema } from '../identifier.schema'\n\nexport const segmentLengthGoalSchema = z\n .object({\n id: geometryIdSchema,\n type: z.literal('segmentLength'),\n // Only a segment has a length; a line is infinite. Fixed here rather than\n // rejected later so the impossible exercise cannot be authored at all.\n objectType: z.literal('segment'),\n targetLength: z.number().positive().finite(),\n // Same units as the viewport, not pixels.\n toleranceUnits: z.number().positive().finite(),\n })\n .strict()\n\nexport type SegmentLengthGoal = z.infer<typeof segmentLengthGoalSchema>\n","import { z } from 'zod'\nimport { geometryIdSchema } from '../identifier.schema'\nimport { objectTypeNameSchema } from '../objectTypeName.schema'\n\nexport const throughPointsGoalSchema = z\n .object({\n id: geometryIdSchema,\n type: z.literal('throughPoints'),\n objectType: objectTypeNameSchema,\n pointIds: z.array(geometryIdSchema).min(1),\n })\n .strict()\n\nexport type ThroughPointsGoal = z.infer<typeof throughPointsGoalSchema>\n","// The half of a goal kind's definition the schema layer is allowed to see:\n// what it is called, what shape it has, which givens it names, and what makes\n// it unauthorable (M2.5).\n//\n// A goal kind is declared once, in two co-located halves, because the two\n// layers that need it cannot both import the other:\n//\n// * **here** — kind, schema, referenced ids, cross-reference rules. Pure\n// structure, no measurement, so `exercise.schema.ts` can run the rules\n// while parsing without dragging the measurement seam (and JSXGraph\n// underneath it) into the schema layer;\n// * **`validation/goals/<kind>.ts`** — candidate compatibility, satisfaction,\n// relevance, revealed answer. It spreads the declaration below into itself,\n// so a consumer still asks one object — `getGoalKindHandler()` — for\n// everything a kind can answer.\n//\n// Both halves are frozen records keyed by kind rather than registration\n// passes, so a kind added to `geometryGoalSchema` without an entry here is a\n// compile error, duplicate registration cannot be expressed, and completeness\n// does not depend on which module was imported first.\nimport type { z } from 'zod'\nimport { parentsOfDefinition } from '../../geometry/entities'\nimport type { EntityDefinition } from '../../geometry/entityModel'\nimport type { ObjectTypeName } from '../objectTypeName.schema'\nimport { angleBisectorGoalSchema, type AngleBisectorGoal } from './angleBisectorGoal.schema'\nimport { angleMeasureGoalSchema, type AngleMeasureGoal } from './angleMeasureGoal.schema'\nimport { createGoalSchema } from './createGoal.schema'\nimport { givenAngleGoalSchema, type GivenAngleGoal } from './givenAngleGoal.schema'\nimport { midpointGoalSchema, type MidpointGoal } from './midpointGoal.schema'\nimport { parallelGoalSchema, type ParallelGoal } from './parallelGoal.schema'\nimport { perpendicularGoalSchema, type PerpendicularGoal } from './perpendicularGoal.schema'\nimport { segmentLengthGoalSchema } from './segmentLengthGoal.schema'\nimport { throughPointsGoalSchema, type ThroughPointsGoal } from './throughPointsGoal.schema'\nimport type { GeometryGoal } from './geometryGoal.schema'\n\n/**\n * The kinds a goal means when it says \"segment or line\": the objects that have\n * a direction.\n *\n * `throughPoints`, `perpendicular` and `parallel` all reduce their subject to\n * an endpoint pair and measure that pair. A circle is an object with two\n * parents too — its centre and its through-point — so without this test it is\n * measured as the segment between them (M2.G.3).\n */\nexport function isDirectedObjectType(name: ObjectTypeName): boolean {\n return name === 'segment' || name === 'line'\n}\n\n/** An authoring error, at a path relative to the goal that produced it. */\nexport interface GoalAuthoringIssue {\n path: Array<string | number>\n message: string\n}\n\n/**\n * What a cross-reference rule may ask about the exercise around its goal.\n *\n * One member, in the M2 vocabulary: a rule asking \"is this a point?\" and a rule\n * asking \"is this a segment?\" are the same question, and answering both from\n * `kind` is what lets one set of rules serve a v1 exercise (through\n * `definitionOfGiven()`) and a v2 one (whose `entities` record already holds\n * definitions) without a second copy.\n */\nexport interface GoalAuthoringContext {\n definitionOf(id: string): EntityDefinition | undefined\n /**\n * Whether the student may drag this given (ADR 0016) — listed in\n * `draggableEntityIds` *and* a free or `on` point. A figure goal needs it:\n * definitions alone cannot say whether the figure it judges can move, and the\n * rule is `isStudentDraggableGiven()`'s, asked here rather than restated.\n */\n isStudentDraggable(id: string): boolean\n}\n\nexport interface GoalKindDeclaration<TGoal extends GeometryGoal = GeometryGoal> {\n kind: TGoal['type']\n schema: z.ZodType<TGoal>\n /**\n * Every given the goal names, points and objects alike. Removing any of them\n * orphans the goal, which is the teacher editor's question and not a list of\n * type tests it has to keep in step with the goal union.\n */\n referencedGivenIds(goal: TGoal): readonly string[]\n /**\n * What this kind requires of the exercise around it. Shape is the schema's\n * job; this is only what needs the other givens to answer.\n */\n crossReferenceIssues(goal: TGoal, exercise: GoalAuthoringContext): GoalAuthoringIssue[]\n}\n\nexport type GoalKindDeclarationRegistry = {\n [K in GeometryGoal['type']]: GoalKindDeclaration<Extract<GeometryGoal, { type: K }>>\n}\n\nconst noReferences = (): readonly string[] => []\nconst noCrossReferences = (): GoalAuthoringIssue[] => []\n\n// Perpendicular and parallel ask the same questions of the exercise; only the\n// degeneracy rule differs, so it is passed in rather than branched on.\nfunction angleGoalIssues(\n goal: PerpendicularGoal | ParallelGoal,\n exercise: GoalAuthoringContext,\n degenerateThroughPoint: (reference: EntityDefinition | undefined) => boolean,\n): GoalAuthoringIssue[] {\n const issues: GoalAuthoringIssue[] = []\n if (!isDirectedObjectType(goal.objectType)) {\n issues.push({ path: ['objectType'], message: `${goal.type} requires segment or line` })\n }\n const reference = exercise.definitionOf(goal.referenceObjectId)\n if (!reference) {\n issues.push({\n path: ['referenceObjectId'],\n message: `unknown reference object ${goal.referenceObjectId}`,\n })\n } else if (!isDirectedObjectType(reference.kind)) {\n // A reference is measured as the direction through its endpoint pair, so a\n // circle is refused here rather than read as its centre-to-through segment.\n issues.push({\n path: ['referenceObjectId'],\n message: `${goal.type} requires a segment or line reference, got ${reference.kind} ${goal.referenceObjectId}`,\n })\n }\n if (exercise.definitionOf(goal.throughPointId)?.kind !== 'point') {\n issues.push({ path: ['throughPointId'], message: `unknown point ${goal.throughPointId}` })\n }\n if (degenerateThroughPoint(reference)) {\n issues.push({\n path: ['throughPointId'],\n message: `parallel through ${goal.throughPointId} is degenerate: it defines ${goal.referenceObjectId}`,\n })\n }\n return issues\n}\n\n// An undirected measure is folded into [0, 180], so a target past it or a reflex\n// type is a goal nothing can satisfy (RFC 0003 § Decision 6, ADR 0017). Every\n// goal that carries a criterion asks this.\nfunction angleCriterionIssues({\n interpretation,\n criterion,\n}: Pick<AngleMeasureGoal | GivenAngleGoal, 'interpretation' | 'criterion'>): GoalAuthoringIssue[] {\n if (interpretation !== 'undirected') return []\n if (criterion.kind === 'degrees' && criterion.targetDegrees > 180) {\n return [\n {\n path: ['criterion', 'targetDegrees'],\n message: 'an undirected angle cannot measure more than 180 degrees',\n },\n ]\n }\n if (criterion.kind === 'type' && criterion.angleType === 'reflex') {\n return [{ path: ['criterion', 'angleType'], message: 'an undirected angle cannot be reflex' }]\n }\n return []\n}\n\n/**\n * Whether anything the entity stands on can be moved by the student (RFC 0006 §\n * Decision 1, rule 2): a walk up the parent edges of the exercise, asking the\n * one draggability predicate at each id.\n *\n * **Accepted limitation (human, 2026-09-16):** this proves only that *some*\n * transitive parent is genuinely student-draggable. It proves neither that the\n * goal is reachable nor that every allowed movement changes the measure — an\n * arm authored `on` a line through the vertex moves and stays at 0° or 180°.\n * Both need measurement, which the schema layer does not have.\n */\nfunction hasStudentDraggableParent(id: string, exercise: GoalAuthoringContext): boolean {\n const seen = new Set<string>([id])\n const pending = [id]\n while (pending.length > 0) {\n const current = pending.pop()\n if (current === undefined) continue\n if (current !== id && exercise.isStudentDraggable(current)) return true\n const definition = exercise.definitionOf(current)\n if (!definition) continue\n for (const parent of parentsOfDefinition(definition)) {\n if (seen.has(parent)) continue\n seen.add(parent)\n pending.push(parent)\n }\n }\n return false\n}\n\nexport const goalKindDeclarations: GoalKindDeclarationRegistry = Object.freeze({\n create: {\n kind: 'create',\n schema: createGoalSchema,\n // A create goal names no given: it asks for an object of a type.\n referencedGivenIds: noReferences,\n // `objectTypeNameSchema` is deliberately the same list as `EntityKind`, so\n // the angle that arrived at M3.4 became authorable here for nothing — and\n // the create handler grades by kind alone, so any angle would be reported\n // correct. An angle is asked for by its measure, through `angleMeasure`\n // (M3.6), so `create` keeps refusing one.\n crossReferenceIssues(goal) {\n return goal.objectType === 'angle'\n ? [{ path: ['objectType'], message: 'create does not support angle; use angleMeasure' }]\n : []\n },\n },\n throughPoints: {\n kind: 'throughPoints',\n schema: throughPointsGoalSchema,\n referencedGivenIds: (goal: ThroughPointsGoal) => goal.pointIds,\n crossReferenceIssues(goal: ThroughPointsGoal, exercise: GoalAuthoringContext) {\n const issues: GoalAuthoringIssue[] = []\n if (!isDirectedObjectType(goal.objectType)) {\n issues.push({ path: ['objectType'], message: 'throughPoints requires segment or line' })\n }\n if (goal.pointIds.length !== 2 || new Set(goal.pointIds).size !== 2) {\n issues.push({ path: ['pointIds'], message: 'throughPoints requires two distinct points' })\n }\n for (const id of goal.pointIds) {\n if (exercise.definitionOf(id)?.kind !== 'point') {\n issues.push({ path: ['pointIds'], message: `unknown point ${id}` })\n }\n }\n return issues\n },\n },\n perpendicular: {\n kind: 'perpendicular',\n schema: perpendicularGoalSchema,\n referencedGivenIds: (goal: PerpendicularGoal) => [goal.throughPointId, goal.referenceObjectId],\n crossReferenceIssues: (goal: PerpendicularGoal, exercise: GoalAuthoringContext) =>\n // A perpendicular at an endpoint of the reference is a legitimate\n // exercise, so nothing here is degenerate.\n angleGoalIssues(goal, exercise, () => false),\n },\n parallel: {\n kind: 'parallel',\n schema: parallelGoalSchema,\n referencedGivenIds: (goal: ParallelGoal) => [goal.throughPointId, goal.referenceObjectId],\n crossReferenceIssues: (goal: ParallelGoal, exercise: GoalAuthoringContext) =>\n // A parallel through a point of the reference itself is degenerate: the\n // reference redrawn is then a correct answer.\n angleGoalIssues(\n goal,\n exercise,\n (reference) =>\n reference !== undefined &&\n isDirectedObjectType(reference.kind) &&\n parentsOfDefinition(reference).includes(goal.throughPointId),\n ),\n },\n segmentLength: {\n kind: 'segmentLength',\n schema: segmentLengthGoalSchema,\n // A segmentLength goal names no given, so nothing can orphan it.\n referencedGivenIds: noReferences,\n crossReferenceIssues: noCrossReferences,\n },\n midpoint: {\n kind: 'midpoint',\n schema: midpointGoalSchema,\n referencedGivenIds: (goal: MidpointGoal) => [goal.referenceObjectId],\n crossReferenceIssues(goal: MidpointGoal, exercise: GoalAuthoringContext) {\n const reference = exercise.definitionOf(goal.referenceObjectId)\n // A line has no midpoint, so the reference must be a segment.\n if (!reference || reference.kind !== 'segment') {\n return [\n {\n path: ['referenceObjectId'],\n message: `midpoint requires a given segment, got ${goal.referenceObjectId}`,\n },\n ]\n }\n return []\n },\n },\n angleMeasure: {\n kind: 'angleMeasure',\n schema: angleMeasureGoalSchema,\n referencedGivenIds: (goal: AngleMeasureGoal) => [goal.vertexPointId],\n crossReferenceIssues(goal: AngleMeasureGoal, exercise: GoalAuthoringContext) {\n const issues: GoalAuthoringIssue[] = []\n if (exercise.definitionOf(goal.vertexPointId)?.kind !== 'point') {\n issues.push({ path: ['vertexPointId'], message: `unknown point ${goal.vertexPointId}` })\n }\n issues.push(...angleCriterionIssues(goal))\n return issues\n },\n },\n angleBisector: {\n kind: 'angleBisector',\n schema: angleBisectorGoalSchema,\n referencedGivenIds: (goal: AngleBisectorGoal) => [goal.referenceAngleId],\n crossReferenceIssues(goal: AngleBisectorGoal, exercise: GoalAuthoringContext) {\n const issues: GoalAuthoringIssue[] = []\n if (!isDirectedObjectType(goal.objectType)) {\n issues.push({ path: ['objectType'], message: 'angleBisector requires segment or line' })\n }\n // The same shape as midpoint's rule: the reference must be a given angle.\n if (exercise.definitionOf(goal.referenceAngleId)?.kind !== 'angle') {\n issues.push({\n path: ['referenceAngleId'],\n message: `angleBisector requires a given angle, got ${goal.referenceAngleId}`,\n })\n }\n return issues\n },\n },\n givenAngle: {\n kind: 'givenAngle',\n schema: givenAngleGoalSchema,\n referencedGivenIds: (goal: GivenAngleGoal) => [goal.angleId],\n crossReferenceIssues(goal: GivenAngleGoal, exercise: GoalAuthoringContext) {\n const issues: GoalAuthoringIssue[] = []\n // The same shape as midpoint's and angleBisector's rule.\n if (exercise.definitionOf(goal.angleId)?.kind !== 'angle') {\n issues.push({\n path: ['angleId'],\n message: `givenAngle requires a given angle, got ${goal.angleId}`,\n })\n } else if (!hasStudentDraggableParent(goal.angleId, exercise)) {\n // A figure goal over a figure nothing can move is a constant of the\n // document — correct or incorrect before the student touches anything.\n issues.push({\n path: ['angleId'],\n message: `goal ${goal.id} grades ${goal.angleId}, and nothing it is built on can be dragged`,\n })\n }\n issues.push(...angleCriterionIssues(goal))\n return issues\n },\n },\n} satisfies GoalKindDeclarationRegistry)\n\n// The one place a kind's concrete goal type is erased, so that a consumer\n// holding any `GeometryGoal` can ask one question of it. It needs no\n// assertion: the members above are methods, so their narrower parameters are\n// accepted here, and the entry is selected by the goal's own discriminant —\n// by construction the one whose signature that goal satisfies. Consumers that\n// know their kind read the typed record directly.\nexport function declarationFor(goal: GeometryGoal): GoalKindDeclaration {\n return goalKindDeclarations[goal.type]\n}\n","/**\n * The exercise as grading and the student runtime see it: without the authored\n * solution (ADR 0015). The solution is client-visible by product decision, but\n * it is never student state, a malformed one must not disable Check or the\n * canvas, and changing only the solution must not look like a different\n * exercise. Works on a parsed exercise and on a raw migrated document alike.\n * Returns the input itself when there is nothing to remove, so a solution-free\n * exercise keeps its identity.\n */\nexport function withoutSolution<T extends object>(exercise: T): Omit<T, 'solution'> {\n if (!('solution' in exercise)) return exercise\n const projection: Partial<Record<'solution', unknown>> = { ...exercise }\n delete projection.solution\n return projection as Omit<T, 'solution'>\n}\n","import { z } from 'zod'\nimport { geometryIdSchema } from './identifier.schema'\n\nexport const pointSchema = z\n .object({\n x: z.number().finite(),\n y: z.number().finite(),\n })\n .strict()\n\nexport type Point = z.infer<typeof pointSchema>\n\nexport const givenPointSchema = z\n .object({\n id: geometryIdSchema,\n type: z.literal('point'),\n x: z.number().finite(),\n y: z.number().finite(),\n label: z.string().min(1).optional(),\n draggable: z.boolean().optional(),\n })\n .strict()\n\nexport type GivenPoint = z.infer<typeof givenPointSchema>\n","import { z } from 'zod'\nimport { geometryIdSchema } from './identifier.schema'\nimport { pointSchema } from './point.schema'\nimport type { EntityDefinition } from '../geometry/entityModel'\n\n// The v2 persisted entity, transcribed from ADR 0008 (docs/adr/0008-v2-persisted-shape.md).\n//\n// The persisted shape and the in-memory vocabulary are the same types, so a\n// document that parses is already a graph: every schema below is annotated\n// `satisfies z.ZodType<T, T>` against the type `entityModel.ts` froze, which\n// makes drift between the two a compile error rather than a runtime surprise\n// somewhere downstream. There is no second definition of an entity here.\n//\n// **An entity carries no `id`** — the record key is the id, so an entity cannot\n// disagree with its own key — and **no coordinates except a free point's own**.\n// Both are enforced by `.strict()`, which is also what rejects a point carrying\n// two origins at once: the union members are untagged beyond `kind`, and a\n// literal with both `at` and `on` matches none of them.\n\n// Free: the coordinates *are* the construction (RFC 0001 § Q1), which is why\n// they are the only coordinates a document holds.\nexport const freePointDefinitionSchema = z\n .object({\n kind: z.literal('point'),\n at: pointSchema,\n })\n .strict()\n\n// On a host. `t` is deliberately unbounded here: the [0, 1] rule holds only\n// when the host is a segment, which needs the host's kind and so belongs to the\n// document's own refinement. On a circle `t` is a turn from the positive x-axis\n// and every real value is on the circle (M2.8).\nexport const onEntityPointDefinitionSchema = z\n .object({\n kind: z.literal('point'),\n on: geometryIdSchema,\n t: z.number().finite(),\n })\n .strict()\n\n// `which` selects between the two places a circle meets a line; absent means\n// the first meet in `intersectionsOf()`'s ordering, which is what keeps the\n// v1 → v2 migration of an intersection origin a rename rather than a decision.\nexport const intersectionPointDefinitionSchema = z\n .object({\n kind: z.literal('point'),\n intersectionOf: z.tuple([geometryIdSchema, geometryIdSchema]).readonly(),\n which: z.union([z.literal(0), z.literal(1)]).optional(),\n })\n .strict()\n\n// Untagged beyond `kind`, exactly as v1's `PointOrigin` is: each member's\n// required keys are unique, so a stored point needs no second discriminant.\n// A free point therefore has no `parents` key, not even `[]` — free is read off\n// the shape, and `parents: []` on a point is rejected here.\nexport const pointDefinitionSchema = z.union([\n freePointDefinitionSchema,\n onEntityPointDefinitionSchema,\n intersectionPointDefinitionSchema,\n])\n\n// One references tuple for every kind that is built on two entities. v1 spelled\n// them per kind (`from`/`to`, `through`, `center`/`through`); `parents` is the\n// general reference list `parentsOfDefinition()` already walks, and the reason a\n// circle needed no new document vocabulary.\nconst parentPairSchema = z.tuple([geometryIdSchema, geometryIdSchema]).readonly()\n\n// An angle's references, in reading order: `[arm, vertex, arm]`. A sibling of\n// the pair rather than a widening of it, so segment, line and circle keep being\n// refused a third parent at parse time (RFC 0003 § Compatibility).\nconst parentTripleSchema = z\n .tuple([geometryIdSchema, geometryIdSchema, geometryIdSchema])\n .readonly()\n\nexport const segmentDefinitionSchema = z\n .object({ kind: z.literal('segment'), parents: parentPairSchema })\n .strict()\n\nexport const lineDefinitionSchema = z\n .object({ kind: z.literal('line'), parents: parentPairSchema })\n .strict()\n\n// Centre first, then a point the circle passes through — the order\n// `CircleDefinition` documents and the order `identityKey` refuses to sort.\nexport const circleDefinitionSchema = z\n .object({ kind: z.literal('circle'), parents: parentPairSchema })\n .strict()\n\n// The three points ∠ABC names, in that order — arm, vertex, arm. The order\n// carries meaning and is never sorted: ∠ABC and ∠CBA are opposite sweeps, and a\n// figure may mark both at one vertex (ADR 0010 § 1).\nexport const angleDefinitionSchema = z\n .object({ kind: z.literal('angle'), parents: parentTripleSchema })\n .strict()\n\nexport const entityDefinitionSchema = z.union([\n pointDefinitionSchema,\n segmentDefinitionSchema,\n lineDefinitionSchema,\n circleDefinitionSchema,\n angleDefinitionSchema,\n]) satisfies z.ZodType<EntityDefinition, EntityDefinition>\n","import { z } from 'zod'\nimport type { EntityPresentation } from '../geometry/entityModel'\n\n// How an entity appears, persisted as a **sibling** record of `entities` in\n// both documents and never as a field inside a definition (RFC 0001 § Q11,\n// ADR 0008). Presentation decides what is hit-testable and selectable, so\n// folding it into a definition would put engine behaviour inside untrusted\n// attempt data.\n//\n// The capability triple `{ rendered, selectable, snapTarget }` is derived from\n// the role and is never persisted, so it is absent here by design.\nexport const entityRoleSchema = z.enum(['implicit', 'explicit'])\n\n// The default for an entity with no entry is per document — `explicit` in the\n// exercise, `implicit` in the attempt — and lives in the context that reads\n// both, not here: this schema describes a stored entry, and an absent entry has\n// nothing to describe.\nexport const entityPresentationSchema = z\n .object({\n role: entityRoleSchema,\n // Only meaningful for an explicit entity. Empty is rejected rather than\n // stored, matching the v1 given label rule.\n label: z.string().min(1).optional(),\n // The degree readout beside an angle's mark (ADR 0010 § 4). Only an angle\n // has a measure, and that rule needs the entity's kind, so it is checked in\n // `entityDocumentIssues()` rather than here.\n measureLabel: z.enum(['degrees']).optional(),\n })\n .strict() satisfies z.ZodType<EntityPresentation, EntityPresentation>\n","// Version migration for stored documents. Built before a second version\n// exists, on purpose: the moment one is needed, the alternative is inventing\n// this machinery under pressure while old attempts are already in a database.\n//\n// A step is one hop. Chains are walked in order, so v1 -> v3 is two steps and\n// each step only has to know about its own neighbour.\nexport interface MigrationStep {\n from: number\n to: number\n migrate: (document: Record<string, unknown>) => Record<string, unknown>\n}\n\nexport type MigrationOutcome =\n { success: true; data: unknown } | { success: false; message: string }\n\nfunction readVersion(document: Record<string, unknown>): number | null {\n const version = document.schemaVersion\n return typeof version === 'number' && Number.isInteger(version) ? version : null\n}\n\n/**\n * Brings a stored document up to `latestVersion` by applying steps in order.\n *\n * Failure is a message, never a throw: the input is untrusted JSON from a\n * database or a request body, and the caller wants to report it rather than\n * crash. A document from a *newer* version fails too — downgrading by guessing\n * which fields to drop would silently destroy student work.\n */\nexport function applyMigrations(\n input: unknown,\n steps: readonly MigrationStep[],\n latestVersion: number,\n): MigrationOutcome {\n if (typeof input !== 'object' || input === null || Array.isArray(input)) {\n return { success: false, message: 'expected an object' }\n }\n let document = { ...(input as Record<string, unknown>) }\n const version = readVersion(document)\n if (version === null) {\n return { success: false, message: 'missing or invalid schemaVersion' }\n }\n if (version > latestVersion) {\n return {\n success: false,\n message: `schemaVersion ${version} is newer than the supported version ${latestVersion}`,\n }\n }\n\n let current = version\n while (current < latestVersion) {\n const step = steps.find((candidate) => candidate.from === current)\n if (!step) {\n return {\n success: false,\n message: `no migration from schemaVersion ${current} to ${latestVersion}`,\n }\n }\n // Checked before the step runs, not after: a step that does not advance\n // would loop forever, and there is no reason to let it touch the document\n // on the way to being rejected.\n if (step.to <= current) {\n return { success: false, message: `migration from ${current} does not advance the version` }\n }\n // A step is ordinary code operating on untrusted JSON, so it can throw on\n // a shape it did not expect. This function promises a message instead —\n // see above — and that promise has to cover the steps too.\n try {\n document = { ...step.migrate(document), schemaVersion: step.to }\n } catch (error) {\n return {\n success: false,\n message: `migration from ${current} to ${step.to} failed: ${\n error instanceof Error ? error.message : String(error)\n }`,\n }\n }\n current = step.to\n }\n\n return { success: true, data: document }\n}\n","// Shape defined in schemas/pointOrigin.schema.ts; re-exported here next to the\n// predicates, the same way point.ts sits next to its math.\nexport type {\n PointOrigin,\n FreePointOrigin,\n OnObjectPointOrigin,\n IntersectionPointOrigin,\n} from '../schemas/pointOrigin.schema'\nimport type {\n FreePointOrigin,\n IntersectionPointOrigin,\n OnObjectPointOrigin,\n PointOrigin,\n} from '../schemas/pointOrigin.schema'\n\nexport function isFreeOrigin(origin: PointOrigin): origin is FreePointOrigin {\n return 'x' in origin\n}\n\nexport function isOnObjectOrigin(origin: PointOrigin): origin is OnObjectPointOrigin {\n return 'onObject' in origin\n}\n\nexport function isIntersectionOrigin(origin: PointOrigin): origin is IntersectionPointOrigin {\n return 'intersectionOf' in origin\n}\n","// The v1 → v2 translation, in one place (M2.3).\n//\n// **Nothing stored is v1 any more.** Both documents reached v2 at M2.6.c, so the\n// only caller left is `v1ToV2.ts`: this is where a v1 `type` discriminant or a\n// `PointOrigin`'s shape is read, once, on the way in. It stays the only such\n// place, which is what keeps every consumer above it reading definitions.\n//\n// It lived inside `geometryContext.ts` at M2.3.a, when the context was its only\n// consumer, and grew callers in hit testing and snapping while those still took\n// v1 shapes; a second copy of the translation would have been exactly the\n// per-kind knowledge the registry exists to hold once.\n//\n// Adaptation, never behaviour: what a segment *does* is in `entities/`. Since\n// M2.6.c.3 neither this file nor the v1 shapes it reads — `givenElement.schema.ts`,\n// `geometryObject.schema.ts`, `pointOrigin.schema.ts` — are on the published\n// surface: a host loads a document and the loader migrates it, so nothing\n// outside `v1ToV2.ts` has a v1 object to hand to any of them.\nimport { identityKeyOf, parentsOfDefinition, resolveDefinition } from './entities'\nimport type { EntityDefinition, PointDefinition, ResolvedEntity } from './entityModel'\n// Straight from the schema rather than through `geometryObject.ts`, which now\n// builds its own compatibility helper on this module.\nimport type { GeometryObject } from '../schemas/geometryObject.schema'\nimport type { GivenElement } from '../schemas/givenElement.schema'\nimport type { Point, PointId } from './point'\nimport { isFreeOrigin, isOnObjectOrigin, type PointOrigin } from './pointOrigin'\n\n// Keyed rather than written as a ternary so that the translation is as\n// exhaustive as the registry it feeds: a v1 object type added without one is a\n// compile error here.\nconst definitionOfObjectByType: {\n readonly [T in GeometryObject['type']]: (\n object: Extract<GeometryObject, { type: T }>,\n ) => EntityDefinition\n} = {\n segment: (object) => ({ kind: 'segment', parents: [object.from, object.to] }),\n line: (object) => ({ kind: 'line', parents: object.through }),\n // Centre first, then the point on the circumference — the order\n // `CircleDefinition` documents, and the order `identityKey` refuses to sort.\n circle: (object) => ({ kind: 'circle', parents: [object.center, object.through] }),\n}\n\n/** The definition a stored v1 construction or given object stands for. */\nexport function definitionOfObject(object: GeometryObject): EntityDefinition {\n const translate = definitionOfObjectByType[object.type] as (\n object: GeometryObject,\n ) => EntityDefinition\n return translate(object)\n}\n\n// v1 origins are untagged exactly as M2's are, so this narrows on the keys\n// through the same predicates rather than on a discriminant.\n/** The definition a stored v1 point origin stands for. */\nexport function definitionOfOrigin(origin: PointOrigin): PointDefinition {\n // The stored origin *is* the coordinate pair, so it is used as one rather\n // than copied: a definition is read-only to everything that receives it, and\n // this is on the per-pointer-event path where a copy per point in the figure\n // is worth avoiding.\n if (isFreeOrigin(origin)) return { kind: 'point', at: origin }\n if (isOnObjectOrigin(origin)) return { kind: 'point', on: origin.onObject, t: origin.t }\n return { kind: 'point', intersectionOf: origin.intersectionOf }\n}\n\n/**\n * The definition a stored v1 given stands for, point or object.\n *\n * A given point's coordinates are its origin, exactly as a free attempt point's\n * are — v1 spells them as two fields on the given rather than as a `PointOrigin`,\n * which is the only reason this is not `definitionOfOrigin()`. The v1 → v2\n * exercise migration and the goal authoring rules both need it.\n */\nexport function definitionOfGiven(given: GivenElement): EntityDefinition {\n return given.type === 'point'\n ? { kind: 'point', at: { x: given.x, y: given.y } }\n : definitionOfObject(given)\n}\n\n/**\n * Every point a stored v1 object is built on, in declaration order — the\n * general reference API `endpointsOf()` used to be.\n *\n * A caller that only asks *which points does this name* wants this: it stays\n * correct for a kind that names one parent or three, where a pair does not.\n */\nexport function parentsOfObject(object: GeometryObject): readonly PointId[] {\n return parentsOfDefinition(definitionOfObject(object))\n}\n\n/**\n * The key deciding whether two stored v1 objects are the same construction —\n * `${type}:${sorted endpoints}` as both `superRefine` blocks built inline,\n * except that the sort is now the kind's own decision (RFC 0001 § Q9).\n */\nexport function identityKeyOfObject(object: GeometryObject): string {\n return identityKeyOf(definitionOfObject(object))\n}\n\n/**\n * The resolved form of a v1 object, evaluated against points that have already\n * been resolved for the scene it belongs to — `null` when it is not on screen.\n *\n * For the consumers that still hold a v1 object and a point map instead of a\n * context; a caller holding an `IndexedGeometryContext` asks that, because its\n * answers are cached and these are computed per call.\n */\nexport function resolvedOfObject(\n object: GeometryObject,\n points: ReadonlyMap<PointId, Point>,\n): ResolvedEntity | null {\n const definition = definitionOfObject(object)\n const parents: ResolvedEntity[] = []\n for (const parentId of parentsOfDefinition(definition)) {\n const at = points.get(parentId)\n // The registry reads its parents positionally, so a short list would slide\n // the ones that did resolve into the wrong slots. An object missing an\n // endpoint is not on screen either, which is what both callers already did.\n if (!at) return null\n parents.push({ kind: 'point', at })\n }\n return resolveDefinition(definition, parents)\n}\n","// The one-way v1 → v2 translation of both stored documents (M2.6.b).\n//\n// ADR 0008 § \"v1 → v2 mapping\" is normative here, row by row; nothing in this\n// file re-derives the shape from RFC 0001. Two steps, one per document, because\n// `applyMigrations()` walks one document at a time and a step never sees the\n// other one — which is why a migrated attempt still carries copies of the given\n// points it was seeded with, and why removing them is a load-time job that\n// holds both documents (ADR 0008 § Amendment).\n//\n// **A step translates; it does not validate.** The per-kind knowledge stays in\n// `v1Bridge.ts`, the value rules stay in the schemas, and this file only decides\n// which of them applies. So a field that is *copied* — a coordinate, a label, a\n// goal, `constructedPointIds` — is copied verbatim and judged afterwards by the\n// v2 schema, which reports it with a document path. A field the translation has\n// to *branch on* has nowhere to defer to and is checked here: an origin with no\n// recognizable shape, an object with an unknown `type`, a given whose id is not\n// a string, a `draggable` that is not a boolean. Those throw, and\n// `applyMigrations()` turns the throw into a message naming the v1 path.\n//\n// Aliasing: a migrated document shares nested values with its input, exactly as\n// the v1 pass-through through `applyMigrations()` already does — the contract is\n// that migration does not *mutate* its input, not that it deep-copies it.\nimport type { MigrationStep } from './migrate'\nimport { definitionOfGiven, definitionOfObject, definitionOfOrigin } from '../geometry/v1Bridge'\nimport {\n emptyEntityRecord,\n isUnstorableEntityId,\n unstorableEntityIdMessage,\n} from '../geometry/entityRecord'\nimport type { EntityDefinition, EntityId, EntityPresentation } from '../geometry/entityModel'\nimport type { GeometryObject } from './geometryObject.schema'\nimport type { GivenElement } from './givenElement.schema'\nimport type { PointOrigin } from './pointOrigin.schema'\n\n/** The version both steps below produce. */\nexport const v2SchemaVersion = 2\n\ntype StoredDocument = Record<string, unknown>\n\nfunction storedObjectAt(value: unknown, path: string): StoredDocument {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new Error(`${path} is not an object`)\n }\n return value as StoredDocument\n}\n\nfunction storedArrayAt(document: StoredDocument, field: string): readonly unknown[] {\n const value = document[field]\n if (!Array.isArray(value)) throw new Error(`${field} is not an array`)\n return value\n}\n\nfunction idAt(declaration: StoredDocument, path: string): EntityId {\n const id = declaration.id\n if (typeof id !== 'string') throw new Error(`${path}.id is not a string`)\n requireStorableId(id, `${path}.id`)\n return id\n}\n\n/**\n * The one id a v2 record cannot hold, refused here because the schema cannot\n * reach it: migration runs *before* `safeParse()`, so a v1 document carrying\n * `__proto__` would have lost the entity to an object literal's prototype\n * setter long before `entityRecordSchema()` had anything to guard.\n *\n * A branch the step has to take is a branch the step checks (see this file's\n * header): whether the key can be stored at all is not something the v2 schema\n * can be deferred to.\n */\nfunction requireStorableId(id: EntityId, path: string): void {\n if (isUnstorableEntityId(id)) throw new Error(`${path}: ${unstorableEntityIdMessage(id)}`)\n}\n\n/**\n * A v1 list becoming a v2 record is the one translation that can *lose* data:\n * two declarations claiming one id are two entries in an array and one key in a\n * record, so the second would silently overwrite the first.\n *\n * Both v1 schemas already reject a duplicate id — but migration runs before the\n * schema, so by the time the schema could see it there is nothing left to see.\n * A step that has to branch checks what it branches on (M2.6.b), and this is\n * the branch: whether the key is already taken.\n */\nfunction claimId(taken: Set<EntityId>, id: EntityId, path: string, firstPath: string): void {\n if (taken.has(id)) throw new Error(`${path}.id duplicates ${firstPath}`)\n taken.add(id)\n}\n\n// The three v1 object types, listed rather than inferred from the schema: this\n// is the set `definitionOfObject()` can translate, and a `type` outside it is\n// the one thing the cast below would otherwise hide.\nconst storedObjectTypes: readonly string[] = ['segment', 'line', 'circle']\n\nfunction definitionOfStoredObject(declaration: StoredDocument, path: string): EntityDefinition {\n const type = declaration.type\n if (typeof type !== 'string' || !storedObjectTypes.includes(type)) {\n throw new Error(`${path} has unknown type ${JSON.stringify(type)}`)\n }\n return definitionOfObject(declaration as unknown as GeometryObject)\n}\n\n// v1 origins are untagged, so the branch is on keys — the same three predicates\n// `definitionOfOrigin()` narrows with, asked here of untrusted JSON. A shape\n// matching none of them cannot be translated at all, so it is the throw rather\n// than a definition the v2 schema would reject with \"invalid input\".\nfunction definitionOfStoredOrigin(id: EntityId, origin: unknown): EntityDefinition {\n const value = storedObjectAt(origin, `points.${id}`)\n if (!('x' in value) && !('onObject' in value) && !('intersectionOf' in value)) {\n throw new Error(`points.${id} is not a free, on-object or intersection origin`)\n }\n return definitionOfOrigin(value as PointOrigin)\n}\n\n/**\n * A stored v1 attempt as a v2 one: `points` and `constructions` become the\n * single `entities` record, `constructedPointIds` becomes `answerEntityIds`\n * **plus every construction id**, and every entity gets an explicit\n * presentation entry.\n *\n * The presentation row is the only line here that is not a rename. A v1 attempt\n * has no concept of an implicit entity — everything in it came from a student\n * gesture and is drawn — so `presentation: {}` plus the attempt's `implicit`\n * default would hide every construction a student ever made (ADR 0008).\n *\n * `answerEntityIds` gaining the construction ids reproduces v1 grading exactly:\n * v1 keeps a candidacy list for points only and treats every construction as a\n * candidate.\n */\nexport const geometryStateV1ToV2: MigrationStep = {\n from: 1,\n to: v2SchemaVersion,\n migrate: (document) => {\n const points = storedObjectAt(document.points, 'points')\n const constructions = storedArrayAt(document, 'constructions')\n\n const entities = emptyEntityRecord<EntityDefinition>()\n const presentation = emptyEntityRecord<EntityPresentation>()\n // Copied verbatim, unknown members and all: a `constructedPointIds` that is\n // not a list of declared ids is `answerEntityIds`' problem to report.\n const answerEntityIds: unknown[] = [...storedArrayAt(document, 'constructedPointIds')]\n\n const claimedBy = new Map<EntityId, string>()\n const taken = new Set<EntityId>()\n for (const [id, origin] of Object.entries(points)) {\n requireStorableId(id, `points.${id}`)\n entities[id] = definitionOfStoredOrigin(id, origin)\n presentation[id] = { role: 'explicit' }\n taken.add(id)\n claimedBy.set(id, `points.${id}`)\n }\n constructions.forEach((construction, index) => {\n const path = `constructions[${index}]`\n const declaration = storedObjectAt(construction, path)\n const id = idAt(declaration, path)\n claimId(taken, id, path, claimedBy.get(id) ?? path)\n claimedBy.set(id, path)\n entities[id] = definitionOfStoredObject(declaration, path)\n presentation[id] = { role: 'explicit' }\n answerEntityIds.push(id)\n })\n\n // A fresh document rather than a spread of the old one: `points`,\n // `constructions` and `constructedPointIds` are gone in v2, and the strict\n // schema would reject them as leftovers.\n return { schemaVersion: v2SchemaVersion, entities, presentation, answerEntityIds }\n },\n}\n\n/**\n * A stored v1 exercise as a v2 one: `givens[]` becomes the same `entities`\n * record the attempt uses, `label` moves into `presentation` and `draggable`\n * becomes the `draggableEntityIds` list.\n *\n * `id`, `viewport`, `showGrid`, `allowedObjects`, `tools` and `goals` are\n * carried over untouched — the goals array migrates by identity — and anything\n * else a stored document carries is carried over too, so that the strict v2\n * schema rejects an unknown field rather than this step silently eating it.\n *\n * An unlabelled given gets **no** presentation entry: the exercise's default\n * role is `explicit`, so `{ role: 'explicit' }` and no entry mean the same\n * thing, and the smaller document is the one that stays true if a given is\n * later relabelled.\n *\n * A duplicate id is refused rather than collapsed — see `claimId()`.\n */\nexport const geometryExerciseV1ToV2: MigrationStep = {\n from: 1,\n to: v2SchemaVersion,\n migrate: (document) => {\n const givens = storedArrayAt(document, 'givens')\n\n const entities = emptyEntityRecord<EntityDefinition>()\n const presentation = emptyEntityRecord<unknown>()\n const draggableEntityIds: EntityId[] = []\n\n const taken = new Set<EntityId>()\n const claimedBy = new Map<EntityId, string>()\n givens.forEach((given, index) => {\n const path = `givens[${index}]`\n const declaration = storedObjectAt(given, path)\n const id = idAt(declaration, path)\n claimId(taken, id, path, claimedBy.get(id) ?? path)\n claimedBy.set(id, path)\n entities[id] =\n declaration.type === 'point'\n ? definitionOfGiven(declaration as unknown as GivenElement)\n : definitionOfStoredObject(declaration, path)\n if (declaration.label !== undefined) {\n presentation[id] = { role: 'explicit', label: declaration.label }\n }\n // Unlike a label, draggability is not copied anywhere — it decides\n // membership of a list — so a non-boolean has nothing left to reject it.\n if (declaration.draggable !== undefined) {\n if (typeof declaration.draggable !== 'boolean') {\n throw new Error(`${path}.draggable is not a boolean`)\n }\n if (declaration.draggable) draggableEntityIds.push(id)\n }\n })\n\n // Everything but `givens`, which the entity record replaces.\n const carried = { ...document }\n delete carried.givens\n return {\n ...carried,\n schemaVersion: v2SchemaVersion,\n entities,\n presentation,\n draggableEntityIds,\n }\n },\n}\n","import { z } from 'zod'\nimport { geometryIdSchema } from './identifier.schema'\nimport { entityDefinitionSchema } from './entityDefinition.schema'\nimport { entityPresentationSchema } from './entityPresentation.schema'\nimport { entityDocumentIssues, entityIdListIssues, entityRecordSchema } from './entityDocument'\nimport { applyMigrations, type MigrationOutcome, type MigrationStep } from './migrate'\nimport { geometryStateV1ToV2 } from './v1ToV2'\n\n// A student attempt is stored and restored (resume after a refresh, teacher\n// review, analytics), so it carries its own version exactly as the exercise\n// does. Keeping the field on the state itself — rather than wrapping it in a\n// serialization envelope — means the version travels with every snapshot the\n// undo stack holds, and migration reads it straight off the document.\n//\n// Raised to 2 at M2.6.c.2, with the consumers that read what the step below\n// produces. A stored v1 attempt is migrated on the way in and no longer has a\n// schema of its own, because nothing parses one — `geometryStateV1ToV2` reads\n// the raw JSON.\nexport const latestGeometryStateVersion = 2\n\n// One entry per version hop.\nexport const geometryStateMigrations: readonly MigrationStep[] = [geometryStateV1ToV2]\n\n// The attempt document ADR 0008 froze: one `entities` record where v1 kept a\n// `points` record and a `constructions` array, a sibling `presentation` record,\n// and `answerEntityIds` where v1 kept `constructedPointIds`. Order is not\n// persisted — the graph is the order.\n//\n// Since ADR 0008 § Amendment an attempt carries **only student work**: it no\n// longer re-declares the exercise's given points, and the load pass in\n// `validateGeometryStateInput()` drops one that does.\nexport const geometryStateSchema = z\n .object({\n schemaVersion: z.literal(latestGeometryStateVersion),\n entities: entityRecordSchema(entityDefinitionSchema),\n presentation: entityRecordSchema(entityPresentationSchema),\n // Answer candidacy, not graph data — which is why it stays a list beside\n // the entities rather than a flag on them. It covers every kind now, so a\n // constructed segment is a candidate in the same way a constructed point is.\n answerEntityIds: z.array(geometryIdSchema),\n })\n .strict()\n .superRefine((geometry, context) => {\n // Parents are *not* required to be declared here: a point on a given\n // segment names an id that lives in the exercise.\n // `validateGeometryStateInput()` finishes the check with both documents.\n for (const issue of entityDocumentIssues(geometry, { parentsMustBeDeclared: false })) {\n context.addIssue({ code: 'custom', path: issue.path, message: issue.message })\n }\n // An answer, unlike a parent, must be the student's own: grading reads it\n // out of this document and a given is not an answer to anything.\n for (const issue of entityIdListIssues(geometry.answerEntityIds, 'answerEntityIds', geometry)) {\n context.addIssue({ code: 'custom', path: issue.path, message: issue.message })\n }\n })\n\nexport type GeometryState = z.infer<typeof geometryStateSchema>\n\n// Brings a stored attempt up to the current version. Structure is checked\n// afterwards by the schema; this only moves the shape forward, and it never\n// mutates its input.\nexport function migrateGeometryState(input: unknown): MigrationOutcome {\n return applyMigrations(input, geometryStateMigrations, latestGeometryStateVersion)\n}\n"],"mappings":"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"}
|