@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":"studentStore-ubldy4PJ.cjs","names":[],"sources":["../src/geometry/hitTest.ts","../src/coordinates/transform.ts","../src/coordinates/lineClipping.ts","../src/coordinates/grid.ts","../src/snapping/computeSnap.ts","../src/store/createStore.ts","../src/store/history.ts","../src/store/constructionCommand.ts","../src/store/constructionIdentity.ts","../src/store/teacherStore.ts","../src/store/attemptChange.ts","../src/store/studentStore.ts"],"sourcesContent":["// What is under the pointer, in math space. The single place that answers\n// \"what did the user touch\": selection, snapping (1.3b) and connection\n// previews all consume this instead of each renderer growing its own\n// proximity loop.\n//\n// Tolerance is a math-space distance, deliberately decoupled from how large\n// the object is drawn: on touch devices the finger target must be far larger\n// than the stroke. Callers convert pixels to math units through the viewport\n// placement (coordinates/transform.ts).\nimport { distanceToResolved, hitPriorityOf, resolveAgainstPoints } from './entities'\nimport type { EntityDefinition, EntityId, EntityKind } from './entityModel'\nimport type { Point, PointId } from './point'\n\n// One name per entity kind, because a hit *is* an entity. An alias rather than\n// a second list: a kind added to the registry is hit-testable the moment it\n// declares a distance, and M2.8's circle needed no edit here beyond this line\n// becoming an alias.\nexport type HitTestType = EntityKind\n\nexport interface HitTestResult {\n id: string\n type: HitTestType\n distance: number\n}\n\n// An id and what it is built on, which is what a stored entity is once its\n// document is out of the way (M2.6.c.1). A scene is assembled by a renderer or\n// invented for a preview, so it is a list rather than a context: the entities\n// under the pointer are not always the entities a document declares.\nexport interface SceneEntity {\n id: EntityId\n definition: EntityDefinition\n}\n\nexport interface HitTestGeometry {\n points: ReadonlyMap<PointId, Point>\n objects: readonly SceneEntity[]\n}\n\nexport interface HitTestOptions {\n // Ids that can never be hit — the point a connection started from, the\n // object being dragged, and so on.\n exclude?: readonly string[]\n // Restricts the answer to certain kinds, e.g. ['point'] while connecting.\n types?: readonly HitTestType[]\n // Which ids may be hit at all, asked per entity rather than listed like\n // `exclude`: a scene assembled from a document contains entities that are\n // present and untouchable, and the caller — not this file — knows which\n // (M2.G.4.a, `capabilitiesOf`). Absent means every entity in the scene.\n accepts?: (id: EntityId) => boolean\n}\n\n// Points beat segments beat lines at equal distance, and both halves of that —\n// the ranking and every distance below it — are now the kind's own to declare\n// (`geometry/entities/`). This file decides what counts as a hit and how the\n// hits are ordered; it no longer knows what a segment is made of.\n\nexport function hitTest(\n at: Point,\n geometry: HitTestGeometry,\n tolerance: number,\n options: HitTestOptions = {},\n): HitTestResult[] {\n const excluded = new Set(options.exclude ?? [])\n const allowedTypes = options.types ? new Set(options.types) : null\n const results: HitTestResult[] = []\n\n if (!allowedTypes || allowedTypes.has('point')) {\n for (const [id, point] of geometry.points) {\n if (excluded.has(id)) continue\n if (options.accepts && !options.accepts(id)) continue\n const distance = distanceToResolved({ kind: 'point', at: point }, at)\n if (distance <= tolerance) results.push({ id, type: 'point', distance })\n }\n }\n\n for (const { id, definition } of geometry.objects) {\n if (excluded.has(id)) continue\n if (options.accepts && !options.accepts(id)) continue\n // Filtered before resolving, not after: `computeSnap()` asks for points\n // only twice per pointer move, and resolving every object to discard it\n // would be the most expensive question in the file.\n if (allowedTypes && !allowedTypes.has(definition.kind)) continue\n // An object whose parents are not resolvable is not on screen either.\n const resolved = resolveAgainstPoints(definition, geometry.points)\n if (!resolved) continue\n const distance = distanceToResolved(resolved, at)\n if (distance <= tolerance) results.push({ id, type: definition.kind, distance })\n }\n\n return results.sort(\n (a, b) => hitPriorityOf(a.type) - hitPriorityOf(b.type) || a.distance - b.distance,\n )\n}\n\n// The first hit, or null. Convenience for the common \"what did I just click\"\n// call, keeping the ranking rules in one place.\nexport function firstHit(\n at: Point,\n geometry: HitTestGeometry,\n tolerance: number,\n options: HitTestOptions = {},\n): HitTestResult | null {\n return hitTest(at, geometry, tolerance, options)[0] ?? null\n}\n","// Screen (canvas pixels, y-down) <-> math (y-up) coordinate transform.\n// Knows nothing about Konva or React — mirrors measure/geometryAdapter.ts,\n// which stays equally ignorant of Konva on the validation side. The math\n// range comes from the exercise's viewport; this file only knows how to\n// map that rectangle onto a fixed-size canvas. The default `contain`\n// projection preserves angles; `stretch` maps the exact viewport to every\n// canvas edge for full-screen application layouts.\nimport type { Point } from '../geometry/point'\nimport { pixelRadiusToMathTolerance } from '../numerics/policy'\nimport type { MathViewport } from '../schemas/viewport.schema'\n\nexport interface CanvasSize {\n width: number\n height: number\n projection?: 'contain' | 'stretch'\n}\n\nexport interface Placement {\n scale: number\n scaleX: number\n scaleY: number\n offsetX: number\n offsetY: number\n}\n\n// `contain` uses the shared `scale`; `stretch` uses scaleX/scaleY. Keeping the\n// projection on CanvasSize makes rendering and pointer conversion consume the\n// same source of truth without renderer-specific flags.\nexport function computePlacement(viewport: MathViewport, canvas: CanvasSize): Placement {\n const [xMin, yMin, xMax, yMax] = viewport\n const scaleX = canvas.width / (xMax - xMin)\n const scaleY = canvas.height / (yMax - yMin)\n const scale = Math.min(scaleX, scaleY)\n const appliedScaleX = canvas.projection === 'stretch' ? scaleX : scale\n const appliedScaleY = canvas.projection === 'stretch' ? scaleY : scale\n const offsetX = (canvas.width - (xMax - xMin) * appliedScaleX) / 2\n const offsetY = (canvas.height - (yMax - yMin) * appliedScaleY) / 2\n return { scale, scaleX: appliedScaleX, scaleY: appliedScaleY, offsetX, offsetY }\n}\n\nexport function mathToScreen(\n point: Point,\n viewport: MathViewport,\n canvas: CanvasSize,\n placement: Placement = computePlacement(viewport, canvas),\n): Point {\n const [xMin, yMin] = viewport\n const { scaleX, scaleY, offsetX, offsetY } = placement\n return {\n x: offsetX + (point.x - xMin) * scaleX,\n y: canvas.height - offsetY - (point.y - yMin) * scaleY,\n }\n}\n\nexport function screenToMath(\n point: Point,\n viewport: MathViewport,\n canvas: CanvasSize,\n placement: Placement = computePlacement(viewport, canvas),\n): Point {\n const [xMin, yMin] = viewport\n const { scaleX, scaleY, offsetX, offsetY } = placement\n return {\n x: (point.x - offsetX) / scaleX + xMin,\n y: (canvas.height - offsetY - point.y) / scaleY + yMin,\n }\n}\n\n// A pixel radius (a finger or cursor target) expressed in math units. Under\n// the default `contain` projection both axes share one scale and this is\n// exact; under `stretch` the tighter axis sets it, so the target is never\n// smaller than intended on either axis.\nexport function screenLengthToMath(pixels: number, placement: Placement): number {\n return pixelRadiusToMathTolerance(pixels, Math.min(placement.scaleX, placement.scaleY))\n}\n","import type { Point } from '../geometry/point'\nimport type { MathViewport } from '../schemas/viewport.schema'\n\n// Given two math-space points defining an infinite line, and the exercise\n// viewport, return the two points where the line crosses the viewport\n// boundary — so a bounded Konva Line drawn between them fills the whole\n// visible area and looks infinite. Works in math space only; pixel\n// conversion is a separate step (see coordinates/transform.ts).\n//\n// Standard Liang-Barsky parametric clipping: P(t) = a + t*(b-a), t ranging\n// over all of ℝ (not just [0,1], since the line is infinite in both\n// directions), narrowed against each of the viewport's 4 boundaries.\nexport function clipLineToViewport(\n a: Point,\n b: Point,\n viewport: MathViewport,\n): [Point, Point] | null {\n const dx = b.x - a.x\n const dy = b.y - a.y\n if (dx === 0 && dy === 0) return null\n\n const [xMin, yMin, xMax, yMax] = viewport\n const boundaries: Array<[p: number, q: number]> = [\n [-dx, a.x - xMin],\n [dx, xMax - a.x],\n [-dy, a.y - yMin],\n [dy, yMax - a.y],\n ]\n\n let tEnter = -Infinity\n let tExit = Infinity\n\n for (const [pi, qi] of boundaries) {\n if (pi === 0) {\n // Line is parallel to this boundary: if it's already outside on this\n // axis, it never crosses the viewport at all.\n if (qi < 0) return null\n continue\n }\n const t = qi / pi\n if (pi < 0) {\n tEnter = Math.max(tEnter, t)\n } else {\n tExit = Math.min(tExit, t)\n }\n }\n\n if (tEnter > tExit) return null\n\n return [\n { x: a.x + tEnter * dx, y: a.y + tEnter * dy },\n { x: a.x + tExit * dx, y: a.y + tExit * dy },\n ]\n}\n","// Grid geometry in math space, shared by the drawn grid and by snapping so a\n// snap target can never land somewhere no grid line was rendered.\nimport type { Point } from '../geometry/point'\nimport type { MathViewport } from '../schemas/viewport.schema'\n\n// A \"nice\" step (1, 2, 5 × 10ⁿ) giving roughly 16 divisions across the range.\nexport function gridStep(range: number): number {\n // A unit grid reads best on the small integer viewports exercises usually\n // use — but only while a unit still divides the range into something to aim\n // at. Below about four units across, a step of 1 leaves a viewport holding\n // one or two crossings, and every snap in it lands on the same corner; there\n // the general formula, which scales down past the decimal point, is right.\n if (range >= 4 && range <= 20) return 1\n const roughStep = range / 16\n const magnitude = 10 ** Math.floor(Math.log10(roughStep))\n const normalized = roughStep / magnitude\n if (normalized <= 2) return 2 * magnitude\n if (normalized <= 5) return 5 * magnitude\n return 10 * magnitude\n}\n\nexport function gridSteps(viewport: MathViewport): { x: number; y: number } {\n const [xMin, yMin, xMax, yMax] = viewport\n return { x: gridStep(xMax - xMin), y: gridStep(yMax - yMin) }\n}\n\n// The nearest multiple of `step` that is still inside [min, max]. Rounding\n// alone can leave the range at its edges — with a step of 1 and a maximum of\n// 10.5, anything past 10.5 rounds to 11 — and clamping *that* to the maximum\n// would hand back 10.5, a position no grid line was ever drawn through. So the\n// range is narrowed to the crossings it actually contains before rounding.\n// gridStep() always divides the range into at least four parts, so a viewport\n// always contains a crossing on each axis and this range is never empty.\nfunction nearestMultipleWithin(value: number, step: number, min: number, max: number): number {\n const lowest = Math.ceil(min / step) * step\n const highest = Math.floor(max / step) * step\n return Math.min(highest, Math.max(lowest, Math.round(value / step) * step))\n}\n\n// The grid crossing nearest to `at`: always one the grid renderer drew, and\n// always inside the viewport.\nexport function nearestGridPoint(at: Point, viewport: MathViewport): Point {\n const [xMin, yMin, xMax, yMax] = viewport\n const step = gridSteps(viewport)\n return {\n x: nearestMultipleWithin(at.x, step.x, xMin, xMax),\n y: nearestMultipleWithin(at.y, step.y, yMin, yMax),\n }\n}\n","// Where a placed point should actually land. Sits above geometry/ (hit\n// testing, distances) and the JSXGraph adapter (intersections), below the\n// renderer — so both canvases snap identically and the rules are testable\n// without a Konva stage.\nimport {\n measuredOfResolved,\n objectEntityKinds,\n parameterOnEntity,\n pointOnEntity,\n resolveAgainstPoints,\n} from '../geometry/entities'\nimport { hitTest, type SceneEntity } from '../geometry/hitTest'\nimport { distanceBetween, type Point, type PointId } from '../geometry/point'\nimport type { EntityId, PointDefinition } from '../geometry/entityModel'\nimport { nearestGridPoint } from '../coordinates/grid'\nimport { intersectionsOf, type MeasuredObject } from '../measure/geometryAdapter'\nimport type { MathViewport } from '../schemas/viewport.schema'\n\nexport type SnapKind = 'point' | 'intersection' | 'grid' | 'onObject'\n\nexport interface SnapResult {\n // Exact coordinates to place at — never the raw pointer position.\n position: Point\n kind: SnapKind\n // The existing point snapped to ('point'), or the object the position lies\n // on ('onObject'). Empty for a grid crossing; two ids for an intersection.\n targetIds: string[]\n // Parameter along the object, for 'onObject' only — what a derived point\n // stores so it slides with its parent instead of drifting off it.\n parameter?: number\n distance: number\n}\n\nexport interface SnapScene {\n points: ReadonlyMap<PointId, Point>\n objects: readonly SceneEntity[]\n viewport: MathViewport\n // Grid crossings are only offered when the student can see the grid.\n showGrid?: boolean\n // `capabilitiesOf(role).snapTarget`, asked per entity: the scene a canvas\n // hands over is the whole document, drawn or not, because an implicit entity\n // is invisible and still snappable (ADR 0008 § Presentation). This is the row\n // that decides it, so a role that stops being a snap target stops being one\n // here rather than by being left out of the scene. Absent means every entity\n // in the scene is a target — which is what an invented preview scene wants,\n // since it has no presentation record behind it.\n isSnapTarget?: (id: EntityId) => boolean\n}\n\nexport interface SnapOptions {\n // Math-space radius, converted from a pixel target by the caller.\n tolerance: number\n exclude?: readonly string[]\n kinds?: readonly SnapKind[]\n}\n\n// Priority, strongest first. Reusing an existing point beats inventing one;\n// an intersection is a real construction landmark; the grid is a convenience;\n// landing loosely on an object is the weakest claim of the four.\nconst kindPriority: SnapKind[] = ['point', 'intersection', 'grid', 'onObject']\n\nfunction snapToPoint(at: Point, scene: SnapScene, options: SnapOptions): SnapResult | null {\n const hit = hitTest(at, scene, options.tolerance, {\n exclude: options.exclude,\n types: ['point'],\n accepts: scene.isSnapTarget,\n })[0]\n if (!hit) return null\n const position = scene.points.get(hit.id)\n if (!position) return null\n return { position, kind: 'point', targetIds: [hit.id], distance: hit.distance }\n}\n\nfunction snapToIntersection(at: Point, scene: SnapScene, options: SnapOptions): SnapResult | null {\n const excluded = new Set(options.exclude ?? [])\n // Resolved and reduced once per object, ahead of the scan rather than inside\n // it: the pairing is quadratic and the reduction is not, so the inner loop\n // rebuilding both operands was paying n² times for an answer that changes n\n // times. An object with nowhere to be is dropped here and pairs with nothing.\n const targets: { id: string; measured: MeasuredObject }[] = []\n for (const { id, definition } of scene.objects) {\n if (excluded.has(id)) continue\n // An object that is not a snap target pairs with nothing: the intersection\n // of two entities the student cannot aim at is not a landmark.\n if (scene.isSnapTarget && !scene.isSnapTarget(id)) continue\n const resolved = resolveAgainstPoints(definition, scene.points)\n const measured = resolved && measuredOfResolved(resolved)\n if (measured) targets.push({ id, measured })\n }\n let best: SnapResult | null = null\n // Pairwise over the objects on screen. Exercises hold a handful of objects,\n // and this runs per pointer move; revisit if that stops being true.\n for (let i = 0; i < targets.length; i += 1) {\n const first = targets[i]!\n for (let j = i + 1; j < targets.length; j += 1) {\n const second = targets[j]!\n for (const position of intersectionsOf(first.measured, second.measured)) {\n const distance = distanceBetween(at, position)\n if (distance > options.tolerance) continue\n if (best && best.distance <= distance) continue\n best = {\n position,\n kind: 'intersection',\n targetIds: [first.id, second.id],\n distance,\n }\n }\n }\n }\n return best\n}\n\nfunction snapToGrid(at: Point, scene: SnapScene, options: SnapOptions): SnapResult | null {\n if (!scene.showGrid) return null\n const position = nearestGridPoint(at, scene.viewport)\n const distance = distanceBetween(at, position)\n if (distance > options.tolerance) return null\n return { position, kind: 'grid', targetIds: [], distance }\n}\n\nfunction snapToObject(at: Point, scene: SnapScene, options: SnapOptions): SnapResult | null {\n // Every kind a position can lie on, from the registry rather than a list\n // written here: this was `['segment', 'line']` until M2.8, which is the edit\n // adding a circle must not require.\n const hit = hitTest(at, scene, options.tolerance, {\n exclude: options.exclude,\n types: objectEntityKinds,\n accepts: scene.isSnapTarget,\n })[0]\n if (!hit) return null\n const object = scene.objects.find((candidate) => candidate.id === hit.id)\n if (!object) return null\n const target = resolveAgainstPoints(object.definition, scene.points)\n if (!target) return null\n // Where the parameter is clamped and where it is not is the host's business,\n // not this file's — a segment ends and a line does not. Null is a host that\n // cannot be parameterized at all: two endpoints so close together relative to\n // the pointer that the direction between them is rounding error, which is a\n // placement to decline rather than to guess at.\n const parameter = parameterOnEntity(target, at)\n if (parameter === null) return null\n // The stored parameter and the position it stands for come from the same\n // answer, so a point placed here lands exactly where reopening the document\n // will put it.\n const position = pointOnEntity(target, parameter)\n if (!position) return null\n return { position, kind: 'onObject', targetIds: [object.id], parameter, distance: hit.distance }\n}\n\nconst snapFinders: Record<\n SnapKind,\n (at: Point, scene: SnapScene, options: SnapOptions) => SnapResult | null\n> = {\n point: snapToPoint,\n intersection: snapToIntersection,\n grid: snapToGrid,\n onObject: snapToObject,\n}\n\n// The single snap decision: the strongest kind that has a candidate within\n// tolerance wins outright, so a nearby existing point is never passed over\n// for a marginally closer grid crossing.\nexport function computeSnap(at: Point, scene: SnapScene, options: SnapOptions): SnapResult | null {\n const enabled = options.kinds ? new Set(options.kinds) : null\n for (const kind of kindPriority) {\n if (enabled && !enabled.has(kind)) continue\n const result = snapFinders[kind](at, scene, options)\n if (result) return result\n }\n return null\n}\n\n// Convenience for placement: the snapped coordinates, or the pointer itself\n// when nothing is in range.\nexport function snapPosition(at: Point, scene: SnapScene, options: SnapOptions): Point {\n return computeSnap(at, scene, options)?.position ?? at\n}\n\n// What a snapped placement should be stored as. Returning null means \"place\n// nothing\": the pointer landed on a point that already exists, and a second\n// point stacked on it would be a duplicate, not an answer.\n//\n// This is where snapping stops being cosmetic — an intersection or on-object\n// snap becomes a derived point, so validation can later tell a point that is\n// on the line by construction from one that is there by luck.\n//\n// A whole `PointDefinition`, because that is what an attempt stores since\n// M2.6.c.2: for a point the origin *is* the definition, minus the kind that\n// says which it is.\nexport function originForSnap(placement: {\n position: Point\n snap: SnapResult | null\n}): PointDefinition | null {\n const { snap, position } = placement\n const free: PointDefinition = { kind: 'point', at: { x: position.x, y: position.y } }\n if (!snap) return free\n if (snap.kind === 'point') return null\n if (snap.kind === 'intersection') {\n const [first, second] = snap.targetIds\n if (!first || !second) return free\n return { kind: 'point', intersectionOf: [first, second] }\n }\n if (snap.kind === 'onObject' && snap.parameter !== undefined && snap.targetIds[0]) {\n return { kind: 'point', on: snap.targetIds[0], t: snap.parameter }\n }\n return free\n}\n","// Generic, framework-agnostic store primitive — no React import here, so it\n// stays usable from plain tests and from any future non-React embed.\n// zustand-vanilla's own internal pattern (getState/setState/subscribe),\n// without depending on zustand itself.\nexport interface Store<T> {\n getState(): T\n setState(update: Partial<T> | ((state: T) => Partial<T>)): void\n subscribe(listener: (state: T, previousState: T) => void): () => void\n}\n\nexport function createStore<T extends object>(initialState: T): Store<T> {\n let state = initialState\n const listeners = new Set<(state: T, previousState: T) => void>()\n\n function getState(): T {\n return state\n }\n\n function setState(update: Partial<T> | ((state: T) => Partial<T>)): void {\n const previousState = state\n const partial = typeof update === 'function' ? update(state) : update\n state = { ...state, ...partial }\n for (const listener of listeners) listener(state, previousState)\n }\n\n function subscribe(listener: (state: T, previousState: T) => void): () => void {\n listeners.add(listener)\n return () => listeners.delete(listener)\n }\n\n return { getState, setState, subscribe }\n}\n","// The undo stack's retention limit, shared by both stores.\n//\n// M2.7 decided that history stays snapshot-based rather than becoming inverse\n// commands (ADR 0009 § Q3). A snapshot is structurally shared — each entry is a\n// shallow copy whose entity *definitions* are the same objects as the live\n// document's — so an entry costs one record slot per entity, not a deep copy.\n// Cheap, but not free, and until M2.7 it was also unbounded: every commit\n// pushed an entry and nothing ever dropped one.\n//\n// A cap is what makes \"snapshots, unchanged\" a safe answer to keep as M3\n// multiplies the entity count. The number is a product choice, not a measured\n// one: 100 edits is far past what a school exercise asks a student to undo, and\n// it bounds an attempt's history at roughly a hundred times the entity count in\n// record slots.\n//\n// The consequence is deliberate and is a behaviour change: undo depth is now\n// finite, and the 101st edit drops the oldest entry rather than growing the\n// stack. Redo does not need its own cap — it can only hand back entries undo\n// removed, so it cannot push `past` above a height it already reached.\nexport const MAX_HISTORY_ENTRIES = 100\n\nexport function pushHistory<T>(past: readonly T[], entry: T): T[] {\n const next = [...past, entry]\n return next.length > MAX_HISTORY_ENTRIES ? next.slice(next.length - MAX_HISTORY_ENTRIES) : next\n}\n","import type { EntityId, PointDefinition } from '../geometry/entityModel'\nimport type { PointId } from '../geometry/point'\n\n/**\n * An end of a construction, before the engine has decided what it costs. A\n * gesture reports where it landed; whether that is a point yet is not the\n * gesture's business.\n *\n * These types live apart from the store because the gesture layer speaks them\n * too — `useConnectionPreview` resolves a release into one of these and hands\n * it on, which is how the preview stopped creating entities of its own.\n */\nexport type ConstructionEndpoint =\n | { kind: 'existing'; id: PointId }\n // The origin a snap resolved to — an intersection, a position on an object,\n // or bare coordinates. The point itself does not exist until the command\n // runs, so a gesture that is abandoned leaves nothing behind.\n | { kind: 'create'; origin: PointDefinition }\n\n/**\n * One completed construction gesture. Every member has `from` and `to`; an\n * angle adds its `vertex` between them, in notation order — ∠ABC is\n * `{ from: A, vertex: B, to: C }` (ADR 0009 § Amendment — the angle\n * construction command).\n */\nexport type ConstructionCommand =\n | { tool: 'segment' | 'line'; from: ConstructionEndpoint; to: ConstructionEndpoint }\n | {\n tool: 'angle'\n from: ConstructionEndpoint\n vertex: ConstructionEndpoint\n to: ConstructionEndpoint\n }\n\n// A command's endpoints in parent order, which is the order its entity stores\n// them: `[arm, vertex, arm]` for an angle.\nexport function endpointsOf(command: ConstructionCommand): ConstructionEndpoint[] {\n return command.tool === 'angle'\n ? [command.from, command.vertex, command.to]\n : [command.from, command.to]\n}\n\n/**\n * Why the last construction attempt made nothing new: the document already\n * declares that construction, as `entityId` (RFC 0004 § Decision 1). A code\n * rather than a sentence, so a host can say it in its own language.\n *\n * Transient, like the rest of a store's gesture state: never serialized and\n * never in history.\n */\nexport interface ConstructionNotice {\n code: 'duplicate-construction'\n entityId: EntityId\n}\n","import type { EntityDefinition, EntityId } from '../geometry/entityModel'\nimport type { PointId } from '../geometry/point'\nimport { identityKeyOf } from '../geometry/entities'\nimport { entryOf } from '../geometry/entityRecord'\nimport type { ConstructionEndpoint, ConstructionNotice } from './constructionCommand'\n\n// Whether a creation would repeat something already there (RFC 0004 §\n// Decision 1). \"The same\" is the kind's own `identityKey` — the question the\n// schema's duplicate-geometry check asks when a document is reopened — so a\n// segment drawn B→A repeats A→B, a line through A and B does not repeat the\n// segment, and a free point and an intersection that happen to coincide are\n// two constructions with different futures. Nothing here compares coordinates.\n\ntype EntityRecord = Readonly<Record<EntityId, EntityDefinition>>\n\n// A key is a string built from the definition — for a free point, two numbers\n// formatted — and every creation asks it of the whole document. Definitions are\n// never mutated, and a commit shares every one it did not touch with the\n// snapshot before it, so each is keyed once for as long as it lives rather than\n// once per gesture. Weak, so a definition undo has discarded takes its key with\n// it. The kind is part of the cached value because nothing obliges a kind's key\n// to name the kind.\nconst keys = new WeakMap<EntityDefinition, string>()\n\nfunction keyOf(definition: EntityDefinition): string {\n let key = keys.get(definition)\n if (key === undefined) {\n key = `${definition.kind} ${identityKeyOf(definition)}`\n keys.set(definition, key)\n }\n return key\n}\n\n// The first id per key in one record, built once per record. A record is\n// replaced, never edited, so the index is right for as long as the record is\n// the one in use: the exercise behind a student store for the whole attempt,\n// an authored exercise until its next commit.\nconst indexes = new WeakMap<EntityRecord, ReadonlyMap<string, EntityId>>()\n\nfunction firstIdByKey(entities: EntityRecord): ReadonlyMap<string, EntityId> {\n let index = indexes.get(entities)\n if (!index) {\n const built = new Map<string, EntityId>()\n for (const id of Object.keys(entities)) {\n const definition = entryOf(entities, id)\n if (!definition) continue\n const key = keyOf(definition)\n if (!built.has(key)) built.set(key, id)\n }\n indexes.set(entities, built)\n index = built\n }\n return index\n}\n\n/**\n * The first entity in `entities`, in record order, that declares the same\n * construction as `candidate`, or `null`. `owns` narrows the search to one\n * ownership domain: a student's answer may repeat a given, never their own work.\n */\nexport function findSameConstruction(\n entities: EntityRecord,\n candidate: EntityDefinition,\n owns?: (id: EntityId) => boolean,\n): EntityId | null {\n const key = keyOf(candidate)\n if (!owns) return firstIdByKey(entities).get(key) ?? null\n for (const id of Object.keys(entities)) {\n const definition = entryOf(entities, id)\n if (definition?.kind !== candidate.kind || !owns(id)) continue\n if (keyOf(definition) === key) return id\n }\n return null\n}\n\n/**\n * The points a connection's `create` endpoints should land on instead of new\n * ones, one answer per endpoint (`null` for an `existing` end, or one with\n * nothing to land on): the first point with exactly that origin anywhere on\n * the canvas. Givens come first — read through the attempt, since a moved\n * draggable given is where the attempt says — then the attempt's own points in\n * record order. Both ends in one pass, because a gesture asks for both at once.\n */\nexport function findEndpointsInScene(\n endpoints: readonly ConstructionEndpoint[],\n givens: EntityRecord,\n attempt: EntityRecord = {},\n): (PointId | null)[] {\n const wanted = endpoints.map((endpoint) =>\n endpoint.kind === 'create' ? keyOf(endpoint.origin) : null,\n )\n const found = wanted.map((): PointId | null => null)\n if (wanted.every((key) => key === null)) return found\n let movesAGiven = false\n for (const id of Object.keys(attempt)) {\n const definition = entryOf(attempt, id)\n if (definition?.kind !== 'point') continue\n if (Object.prototype.hasOwnProperty.call(givens, id)) {\n movesAGiven = true\n continue\n }\n const key = keyOf(definition)\n wanted.forEach((want, index) => {\n if (want === key && found[index] === null) found[index] = id\n })\n }\n // The index answers for the givens as the exercise declares them, which is\n // where they are until a draggable one is moved. Once one has been, the\n // attempt's word wins, so the givens are read one by one through it.\n return wanted.map((key, index) => {\n if (key === null) return null\n if (!movesAGiven) return firstIdByKey(givens).get(key) ?? found[index] ?? null\n for (const id of Object.keys(givens)) {\n const definition = entryOf(attempt, id) ?? entryOf(givens, id)\n if (definition && keyOf(definition) === key) return id\n }\n return found[index] ?? null\n })\n}\n\n/**\n * Whether two ends of one command land on the same point once resolved: an id\n * named twice, a `create` end that reuses an `existing` one, two ends reusing\n * one point, or two `create` ends with the same origin — which would otherwise\n * invent twins. `reused` is `findEndpointsInScene()`'s answer. A construction\n * whose ends coincide is degenerate, and an angle document refuses a repeated\n * parent.\n */\nexport function endsRepeat(\n endpoints: readonly ConstructionEndpoint[],\n reused: readonly (PointId | null)[],\n): boolean {\n // Tagged by their first character, so no point id — any non-empty string is\n // one — can equal an origin's key: `p` names a point, `n` a point not yet made.\n const ends = endpoints.map((endpoint, index) => {\n if (endpoint.kind === 'existing') return `p${endpoint.id}`\n const reusedId = reused[index]\n return reusedId ? `p${reusedId}` : `n${keyOf(endpoint.origin)}`\n })\n return new Set(ends).size < ends.length\n}\n\nexport function duplicateOf(entityId: EntityId): ConstructionNotice {\n return { code: 'duplicate-construction', entityId }\n}\n","import type { ExtraObjectPolicy, GeometryExercise, ToolName } from '../schemas/exercise.schema'\nimport type { ObjectTypeName } from '../schemas/objectTypeName.schema'\nimport type { GeometryGoal } from '../schemas/goals/geometryGoal.schema'\nimport type { MathViewport } from '../schemas/viewport.schema'\nimport type { Point, PointId } from '../geometry/point'\nimport { isSameStoredPoint } from '../numerics/policy'\nimport { entryOf } from '../geometry/entityRecord'\nimport type {\n EntityDefinition,\n EntityId,\n EntityPresentation,\n EntityRole,\n PointDefinition,\n} from '../geometry/entityModel'\nimport { exerciseGeometryContext } from '../state/resolvePoints'\nimport { referencedGivenIds } from '../validation/goals'\nimport { createStore, type Store } from './createStore'\nimport { pushHistory } from './history'\nimport {\n endpointsOf,\n type ConstructionCommand,\n type ConstructionEndpoint,\n type ConstructionNotice,\n} from './constructionCommand'\nimport {\n duplicateOf,\n endsRepeat,\n findEndpointsInScene,\n findSameConstruction,\n} from './constructionIdentity'\n\nexport interface TeacherEditorState {\n exercise: GeometryExercise\n pendingObjectType: ObjectTypeName | null\n pendingFirstPointId: PointId | null\n // The angle tool's second pick, between taps two and three. Never set while\n // `pendingFirstPointId` is null, and cleared with it on every path.\n pendingVertexPointId: PointId | null\n selectedGivenId: string | null\n past: GeometryExercise[]\n future: GeometryExercise[]\n // Consecutive commits sharing a tag collapse into a single undo entry. A\n // point drag fires one move per pointer event, and without this the undo\n // stack fills with pixel-sized steps instead of one \"I moved that point\".\n coalesceTag: string | null\n // Moves on whenever something replaces what a pointer gesture was aimed at —\n // a load, a tool change, an undo, a redo — so a canvas can drop a contact\n // pressed under an older value (RFC 0004 § Decision 4). Transient: never\n // serialized, never in history. Optional, as on `StudentState`; absent\n // reads as 0.\n interactionEpoch?: number\n // Why the last authoring attempt made nothing new: the exercise already\n // declares it (RFC 0004 § Decision 1). Replaced by every creation command,\n // cleared by whatever moves `interactionEpoch`. Transient and optional.\n constructionNotice?: ConstructionNotice | null\n}\n\nexport interface GivenRemovalImpact {\n // Every entity that transitively depends on the target, points included: an\n // exercise may author derived givens, so a segment's dependents are not all\n // constructions (M2.G.5).\n dependentEntityIds: string[]\n dependentGoalIds: string[]\n}\n\nexport function createTeacherStore(initialExercise: GeometryExercise): Store<TeacherEditorState> {\n return createStore<TeacherEditorState>({\n exercise: initialExercise,\n pendingObjectType: null,\n pendingFirstPointId: null,\n pendingVertexPointId: null,\n selectedGivenId: null,\n past: [],\n future: [],\n coalesceTag: null,\n interactionEpoch: 0,\n constructionNotice: null,\n })\n}\n\n// For a command that refuses before it writes anything: the attempt still\n// happened, so an older notice no longer describes the last one.\nfunction dismissNotice(store: Store<TeacherEditorState>): void {\n const state = store.getState()\n if (state.constructionNotice || state.pendingFirstPointId) {\n store.setState({ ...NO_PICKS, constructionNotice: null })\n }\n}\n\n// The whole pick sequence at once: the vertex pick means nothing without the\n// first, so no path clears one and keeps the other.\nconst NO_PICKS = { pendingFirstPointId: null, pendingVertexPointId: null } as const\n\nfunction commitExercise(\n store: Store<TeacherEditorState>,\n update: (exercise: GeometryExercise) => GeometryExercise,\n extra: Partial<TeacherEditorState> = {},\n options: { coalesceTag?: string } = {},\n): void {\n store.setState((state) => {\n const exercise = update(state.exercise)\n if (exercise === state.exercise) return extra\n const tag = options.coalesceTag ?? null\n const coalesces = tag !== null && tag === state.coalesceTag\n return {\n exercise,\n past: coalesces ? state.past : pushHistory(state.past, state.exercise),\n future: [],\n coalesceTag: tag,\n ...extra,\n }\n })\n}\n\nfunction pointName(index: number): string {\n let value = index + 1\n let result = ''\n while (value > 0) {\n value -= 1\n result = String.fromCharCode(65 + (value % 26)) + result\n value = Math.floor(value / 26)\n }\n return result\n}\n\nfunction nextPointId(exercise: GeometryExercise): PointId {\n let index = 0\n while (declares(exercise, pointName(index))) index += 1\n return pointName(index)\n}\n\nfunction nextNumberedId(exercise: GeometryExercise, prefix: 'segment' | 'line' | 'angle'): string {\n let index = 1\n while (declares(exercise, `${prefix}-${index}`)) index += 1\n return `${prefix}-${index}`\n}\n\nfunction declares(exercise: GeometryExercise, id: EntityId): boolean {\n return Object.prototype.hasOwnProperty.call(exercise.entities, id)\n}\n\n// One entity added to the record, and the record only. Written as a helper\n// because every authoring command below is this line plus its own definition:\n// `givens: [...exercise.givens, given]` had the id inside the value, and a\n// record puts it in the key.\nfunction withEntity(\n exercise: GeometryExercise,\n id: EntityId,\n definition: EntityDefinition,\n role?: EntityRole,\n): GeometryExercise {\n return {\n ...exercise,\n entities: { ...exercise.entities, [id]: definition },\n presentation: role ? { ...exercise.presentation, [id]: { role } } : exercise.presentation,\n }\n}\n\nexport function loadExercise(store: Store<TeacherEditorState>, exercise: GeometryExercise): void {\n store.setState((state) => ({\n exercise,\n pendingObjectType: null,\n pendingFirstPointId: null,\n pendingVertexPointId: null,\n selectedGivenId: null,\n past: [],\n future: [],\n coalesceTag: null,\n interactionEpoch: (state.interactionEpoch ?? 0) + 1,\n constructionNotice: null,\n }))\n}\n\nexport function undoTeacherChange(store: Store<TeacherEditorState>): void {\n store.setState((state) => {\n const previous = state.past[state.past.length - 1]\n if (!previous) return {}\n return {\n exercise: previous,\n past: state.past.slice(0, -1),\n future: [state.exercise, ...state.future],\n selectedGivenId: null,\n pendingFirstPointId: null,\n pendingVertexPointId: null,\n coalesceTag: null,\n interactionEpoch: (state.interactionEpoch ?? 0) + 1,\n constructionNotice: null,\n }\n })\n}\n\nexport function redoTeacherChange(store: Store<TeacherEditorState>): void {\n store.setState((state) => {\n const next = state.future[0]\n if (!next) return {}\n return {\n exercise: next,\n past: [...state.past, state.exercise],\n future: state.future.slice(1),\n selectedGivenId: null,\n pendingFirstPointId: null,\n pendingVertexPointId: null,\n coalesceTag: null,\n interactionEpoch: (state.interactionEpoch ?? 0) + 1,\n constructionNotice: null,\n }\n })\n}\n\nexport function selectGiven(store: Store<TeacherEditorState>, id: string | null): void {\n store.setState({ selectedGivenId: id })\n}\n\nexport function setViewport(store: Store<TeacherEditorState>, viewport: MathViewport): void {\n commitExercise(store, (exercise) => ({ ...exercise, viewport }))\n}\n\nexport function setShowGrid(store: Store<TeacherEditorState>, showGrid: boolean): void {\n commitExercise(store, (exercise) => ({ ...exercise, showGrid }))\n}\n\nexport function setAllowedObjects(store: Store<TeacherEditorState>, types: ObjectTypeName[]): void {\n commitExercise(store, (exercise) => ({ ...exercise, allowedObjects: types }))\n}\n\nexport function setTools(store: Store<TeacherEditorState>, tools: ToolName[]): void {\n commitExercise(store, (exercise) => ({ ...exercise, tools }))\n}\n\n/**\n * How the attempt is graded for answer entities no goal needed (ADR 0013).\n * Undoable, like every other authoring change.\n *\n * `allow` **removes** the field rather than storing it: absence already means\n * permissive, and an exercise that says so twice would differ from a migrated\n * one for no reason a host could read. So choosing `allow` on an exercise that\n * never stated a policy changes nothing and records no history entry, which is\n * `commitExercise()`'s existing identity rule.\n */\nexport function setExtraObjectPolicy(\n store: Store<TeacherEditorState>,\n policy: ExtraObjectPolicy,\n): void {\n commitExercise(store, (exercise) => {\n if (policy === 'allow') {\n if (exercise.grading === undefined) return exercise\n const next = { ...exercise }\n delete next.grading\n return next\n }\n if (exercise.grading?.extraObjects === policy) return exercise\n return { ...exercise, grading: { extraObjects: policy } }\n })\n}\n\nexport function addGoal(store: Store<TeacherEditorState>, goal: GeometryGoal): void {\n commitExercise(store, (exercise) => ({ ...exercise, goals: [...exercise.goals, goal] }))\n}\n\nexport function replaceGoal(store: Store<TeacherEditorState>, goal: GeometryGoal): void {\n commitExercise(store, (exercise) => ({\n ...exercise,\n goals: exercise.goals.map((current) => (current.id === goal.id ? goal : current)),\n }))\n}\n\nexport function removeGoal(store: Store<TeacherEditorState>, goalId: string): void {\n commitExercise(store, (exercise) => ({\n ...exercise,\n goals: exercise.goals.filter((goal) => goal.id !== goalId),\n }))\n}\n\n/**\n * Adds a point to the figure, from coordinates or from a whole origin. The\n * Point tool passes an origin so a point the author placed on an object or at\n * an intersection is stored as derived (M3.1-6); everything else passes\n * coordinates and gets a free point.\n *\n * A given with exactly this origin is returned instead of stacked, unchanged:\n * `draggable` does not rewrite a point that is already there.\n */\nexport function addGivenPoint(\n store: Store<TeacherEditorState>,\n origin: Point | PointDefinition,\n draggable = false,\n): PointId {\n const { exercise } = store.getState()\n const definition: PointDefinition =\n 'kind' in origin ? origin : { kind: 'point', at: { x: origin.x, y: origin.y } }\n const existingId = findSameConstruction(exercise.entities, definition)\n if (existingId) {\n store.setState({ constructionNotice: duplicateOf(existingId) })\n return existingId\n }\n const id = nextPointId(exercise)\n commitExercise(\n store,\n (exercise) => {\n const next = withEntity(exercise, id, definition)\n // Draggability is a list on the exercise, not a flag on the declaration\n // (ADR 0008): a definition that could carry it would let a tampered attempt\n // declare a pinned given draggable.\n return draggable ? { ...next, draggableEntityIds: [...next.draggableEntityIds, id] } : next\n },\n { constructionNotice: null },\n )\n return id\n}\n\n/**\n * Reveals scaffolding the figure already depends on: an implicit given becomes\n * explicit, so it is drawn and selectable.\n *\n * One-way by design (M3.1-9, amendment 4). There is no demotion: hiding an\n * entity an exercise or a goal refers to would silently change what the\n * student is asked to do, while revealing one only shows what is already load\n * bearing.\n *\n * Any kind, not points only, because an exercise may declare an implicit\n * segment, line or circle — the `presentation` record says nothing about kind.\n */\nexport function promoteGivenEntity(store: Store<TeacherEditorState>, id: EntityId): void {\n const state = store.getState()\n if (!entryOf(state.exercise.entities, id)) return\n if (state.exercise.presentation[id]?.role !== 'implicit') return\n commitExercise(store, (exercise) => ({\n ...exercise,\n presentation: {\n ...exercise.presentation,\n [id]: { ...exercise.presentation[id], role: 'explicit' },\n },\n }))\n}\n\n/**\n * The canvas gesture's promotion: the same rule, refused for anything but a\n * point, because clicking with the Point tool is what calls it.\n */\nexport function promoteGivenPoint(store: Store<TeacherEditorState>, pointId: PointId): void {\n const definition = entryOf(store.getState().exercise.entities, pointId)\n if (definition?.kind !== 'point') return\n promoteGivenEntity(store, pointId)\n}\n\n// `reusedId` is the given with exactly this end's origin, which it lands on\n// rather than invent its twin.\nfunction withGivenEndpoint(\n exercise: GeometryExercise,\n endpoint: ConstructionEndpoint,\n reusedId: PointId | null,\n): [GeometryExercise, PointId] {\n if (endpoint.kind === 'existing') return [exercise, endpoint.id]\n if (reusedId) return [exercise, reusedId]\n const pointId = nextPointId(exercise)\n return [withEntity(exercise, pointId, endpoint.origin, 'implicit'), pointId]\n}\n\n/**\n * Turns one teacher gesture into its endpoints and shape in one undo step. A\n * shape the exercise already declares is not declared twice: the staged\n * endpoints are dropped with it, nothing enters history, and\n * `constructionNotice` names the one that is there.\n */\nexport function executeGivenConstruction(\n store: Store<TeacherEditorState>,\n command: ConstructionCommand,\n): void {\n const state = store.getState()\n const endpoints = endpointsOf(command)\n const existingIds = endpoints.flatMap((endpoint) =>\n endpoint.kind === 'existing' ? [endpoint.id] : [],\n )\n if (existingIds.some((id) => entryOf(state.exercise.entities, id)?.kind !== 'point')) {\n dismissNotice(store)\n return\n }\n\n const reuse = findEndpointsInScene(endpoints, state.exercise.entities)\n if (endsRepeat(endpoints, reuse)) {\n dismissNotice(store)\n return\n }\n let withEnds = state.exercise\n const ids: PointId[] = []\n for (const [index, endpoint] of endpoints.entries()) {\n const [next, id] = withGivenEndpoint(withEnds, endpoint, reuse[index] ?? null)\n withEnds = next\n ids.push(id)\n }\n // `endpointsOf()` gives each tool its own arity, which the type cannot see.\n const definition = { kind: command.tool, parents: ids } as unknown as EntityDefinition\n // A shape on a point this command just invented is new by definition.\n const existingId =\n withEnds === state.exercise ? findSameConstruction(state.exercise.entities, definition) : null\n if (existingId) {\n store.setState({ ...NO_PICKS, constructionNotice: duplicateOf(existingId) })\n return\n }\n const objectId = nextNumberedId(withEnds, command.tool)\n const candidate = withEntity(withEnds, objectId, definition, 'explicit')\n // Every end must resolve, and none may sit on parent 1 — a connection's `to`,\n // an angle's vertex. An angle's two arms may coincide: 0° is an angle.\n const context = exerciseGeometryContext(candidate)\n const positions = ids.map((id) => context.points.get(id))\n const anchor = positions[1]\n if (\n !anchor ||\n !positions.every(\n (position, index) => position && (index === 1 || !isSameStoredPoint(position, anchor)),\n )\n ) {\n dismissNotice(store)\n return\n }\n commitExercise(store, () => candidate, {\n ...NO_PICKS,\n selectedGivenId: objectId,\n constructionNotice: null,\n })\n}\n\nexport function moveGivenPoint(\n store: Store<TeacherEditorState>,\n id: PointId,\n position: Point,\n): void {\n // One undo entry per drag, not per pointer event — see coalesceTag.\n commitExercise(\n store,\n (exercise) => {\n const definition = entryOf(exercise.entities, id)\n // Only a free point has coordinates to move. A derived one is wherever\n // its parents put it, so a drag on it means nothing and changes nothing.\n if (!definition || definition.kind !== 'point' || !('at' in definition)) return exercise\n return withEntity(exercise, id, { kind: 'point', at: { x: position.x, y: position.y } })\n },\n {},\n { coalesceTag: `move:${id}` },\n )\n}\n\n// Whether dragging this point means anything while authoring: only a free\n// point carries coordinates a drag can rewrite, so a derived one sits wherever\n// its parents put it. The rule `moveGivenPoint` already enforces, asked here so\n// the canvas draws no handle the store will refuse.\nexport function isGivenPointDraggable(state: TeacherEditorState, pointId: PointId): boolean {\n const definition = entryOf(state.exercise.entities, pointId)\n return definition?.kind === 'point' && 'at' in definition\n}\n\n// Ends the coalescing window opened by moveGivenPoint, so dragging the same\n// point twice leaves two undo entries rather than one. Renderers call this on\n// drag end; nothing breaks if it is never called, the two drags just merge.\nexport function endGivenPointDrag(store: Store<TeacherEditorState>): void {\n store.setState({ coalesceTag: null })\n}\n\nexport function setGivenPointDraggable(\n store: Store<TeacherEditorState>,\n id: PointId,\n draggable: boolean,\n): void {\n commitExercise(store, (exercise) => {\n const definition = entryOf(exercise.entities, id)\n if (!definition || definition.kind !== 'point') return exercise\n const listed = exercise.draggableEntityIds.includes(id)\n if (listed === draggable) return exercise\n return {\n ...exercise,\n draggableEntityIds: draggable\n ? [...exercise.draggableEntityIds, id]\n : exercise.draggableEntityIds.filter((entityId) => entityId !== id),\n }\n })\n}\n\n/**\n * Names an entity: the label both surfaces draw and the inspector lists, for\n * every kind rather than points only (M3.1-8).\n *\n * An empty or blank label removes the entry's label rather than storing one,\n * which is the rule `entityPresentationSchema` already states — a stored label\n * is `min(1)` — and what `entityDisplayName()` reads as \"no label\".\n *\n * Typing coalesces into one undo entry per label, the way the X and Y inputs\n * already coalesce through `moveGivenPoint`: a keystroke is not an edit a\n * teacher means to undo on its own.\n */\nexport function setGivenLabel(store: Store<TeacherEditorState>, id: EntityId, label: string): void {\n commitExercise(\n store,\n (exercise) => {\n if (!entryOf(exercise.entities, id)) return exercise\n const current = entryOf(exercise.presentation, id)\n const next = label.trim()\n if ((current?.label ?? '') === next) return exercise\n // An entity with no entry is explicit in an exercise, so naming one\n // states the role it already had rather than changing it. The rest of\n // the entry — an angle's `measureLabel` — is kept: renaming an angle\n // must not hide its measure.\n const entry: EntityPresentation = { ...current, role: current?.role ?? 'explicit' }\n if (next) entry.label = next\n else delete entry.label\n return { ...exercise, presentation: { ...exercise.presentation, [id]: entry } }\n },\n {},\n { coalesceTag: `label:${id}` },\n )\n}\n\n/**\n * Shows or hides a given angle's measure beside its mark (M3.5.a), the\n * `measureLabel` entry ADR 0010 § 4 defines. Absent means hidden, so hiding\n * removes the field rather than storing a second spelling of \"none\".\n *\n * Refused for anything but an angle — `entityDocumentIssues()` rejects a\n * `measureLabel` on any other kind — and one undo entry per toggle.\n */\nexport function setGivenMeasureLabel(\n store: Store<TeacherEditorState>,\n id: EntityId,\n shown: boolean,\n): void {\n commitExercise(store, (exercise) => {\n if (entryOf(exercise.entities, id)?.kind !== 'angle') return exercise\n const current = entryOf(exercise.presentation, id)\n if ((current?.measureLabel === 'degrees') === shown) return exercise\n const entry: EntityPresentation = { ...current, role: current?.role ?? 'explicit' }\n if (shown) entry.measureLabel = 'degrees'\n else delete entry.measureLabel\n return { ...exercise, presentation: { ...exercise.presentation, [id]: entry } }\n })\n}\n\nexport function getGivenRemovalImpact(exercise: GeometryExercise, id: string): GivenRemovalImpact {\n if (!entryOf(exercise.entities, id)) return { dependentEntityIds: [], dependentGoalIds: [] }\n\n // The whole dependency closure, not the target and one edge out. Until\n // M2.6.c.1 an exercise's givens were one level of construction and a point\n // was the only target with dependents, so a hand-written pair of type tests\n // was enough; an exercise may now author `{ on, t }` and `{ intersectionOf }`\n // givens, and deleting the segment under one of those left a point with no\n // parent and the exercise failing its own load. This is the walk\n // `getStudentRemovalImpact()` already deletes by.\n const closure = exerciseGeometryContext(exercise).removalClosureOf(id)\n const removedIds = new Set<EntityId>(closure)\n const dependentEntityIds = closure.filter((entityId) => entityId !== id)\n\n // Which goals a removal orphans is each goal kind's own answer (M2.5): a\n // kind that names a given it did not name before, or a new kind entirely,\n // brings its reference list with it rather than needing a clause here.\n const dependentGoalIds = exercise.goals\n .filter((goal) => referencedGivenIds(goal).some((goalId) => removedIds.has(goalId)))\n .map((goal) => goal.id)\n\n return { dependentEntityIds, dependentGoalIds }\n}\n\nexport function removeGiven(store: Store<TeacherEditorState>, id: string): void {\n const { exercise, pendingFirstPointId, pendingVertexPointId } = store.getState()\n // Nothing to remove still drops the selection, as it always has.\n if (!declares(exercise, id)) return selectGiven(store, null)\n const impact = getGivenRemovalImpact(exercise, id)\n const removedIds = new Set([id, ...impact.dependentEntityIds])\n // The presentation record and the draggable list are siblings of the\n // entity record, so a removal has to reach all three: a label or a\n // draggable id left behind names an entity that is gone, which both v2\n // documents refuse.\n commitExercise(\n store,\n () => ({\n ...exercise,\n entities: Object.fromEntries(\n Object.entries(exercise.entities).filter(([entityId]) => !removedIds.has(entityId)),\n ),\n presentation: Object.fromEntries(\n Object.entries(exercise.presentation).filter(([entityId]) => !removedIds.has(entityId)),\n ),\n draggableEntityIds: exercise.draggableEntityIds.filter(\n (entityId) => !removedIds.has(entityId),\n ),\n goals: exercise.goals.filter((goal) => !impact.dependentGoalIds.includes(goal.id)),\n }),\n {\n selectedGivenId: null,\n // Removing either pick ends the whole sequence; an unrelated removal\n // keeps it.\n ...((removedIds.has(pendingFirstPointId ?? '') ||\n removedIds.has(pendingVertexPointId ?? '')) &&\n NO_PICKS),\n },\n )\n}\n\n// Both direct connection commands. `null` is \"nothing was added\": the two ends\n// are one point, or the exercise already declares this construction — in\n// either order, since a segment's identity sorts its ends.\nfunction addGivenConnection(\n store: Store<TeacherEditorState>,\n kind: 'segment' | 'line',\n parents: [PointId, PointId],\n): string | null {\n if (parents[0] === parents[1]) {\n dismissNotice(store)\n return null\n }\n const { exercise } = store.getState()\n const definition: EntityDefinition = { kind, parents }\n const existingId = findSameConstruction(exercise.entities, definition)\n if (existingId) {\n store.setState({ constructionNotice: duplicateOf(existingId) })\n return null\n }\n const id = nextNumberedId(exercise, kind)\n commitExercise(store, (exercise) => withEntity(exercise, id, definition), {\n constructionNotice: null,\n })\n return id\n}\n\nexport function addGivenSegment(\n store: Store<TeacherEditorState>,\n fromId: PointId,\n toId: PointId,\n): string | null {\n return addGivenConnection(store, 'segment', [fromId, toId])\n}\n\nexport function addGivenLine(\n store: Store<TeacherEditorState>,\n throughA: PointId,\n throughB: PointId,\n): string | null {\n return addGivenConnection(store, 'line', [throughA, throughB])\n}\n\nexport function selectPendingObjectType(\n store: Store<TeacherEditorState>,\n type: ObjectTypeName | null,\n): void {\n store.setState((state) => ({\n pendingObjectType: type,\n pendingFirstPointId: null,\n pendingVertexPointId: null,\n selectedGivenId: null,\n interactionEpoch: (state.interactionEpoch ?? 0) + 1,\n constructionNotice: null,\n }))\n}\n\n/**\n * Abandons a click-click construction in progress: the first endpoint is\n * forgotten and the tool stays armed, as does the selection. The authoring\n * sibling of `cancelStudentConstruction()`, called when a canvas cancels a\n * gesture.\n */\nexport function cancelGivenConstruction(store: Store<TeacherEditorState>): void {\n if (store.getState().pendingFirstPointId === null) return\n store.setState(NO_PICKS)\n}\n\n/**\n * One tap of a click-click construction on an existing point. A segment or a\n * line builds on its second pick; an angle keeps its second as the vertex and\n * builds on its third, in notation order (ADR 0009 § Amendment). A tap on a\n * point already picked cancels the whole sequence.\n */\nexport function pickPointForPendingObject(\n store: Store<TeacherEditorState>,\n pointId: PointId,\n): void {\n const {\n pendingObjectType: type,\n pendingFirstPointId: first,\n pendingVertexPointId: vertex,\n } = store.getState()\n if (type !== 'segment' && type !== 'line' && type !== 'angle') return\n if (!first) {\n store.setState({ pendingFirstPointId: pointId, constructionNotice: null })\n } else if (pointId === first || pointId === vertex) {\n store.setState({ ...NO_PICKS, constructionNotice: null })\n } else if (type !== 'angle') {\n const id = addGivenConnection(store, type, [first, pointId])\n // A refused duplicate leaves the selection where it was: the notice says\n // what is already there without taking the author's focus to it.\n store.setState(id ? { ...NO_PICKS, selectedGivenId: id } : NO_PICKS)\n } else if (!vertex) {\n store.setState({ pendingVertexPointId: pointId, constructionNotice: null })\n } else {\n executeGivenConstruction(store, {\n tool: type,\n from: { kind: 'existing', id: first },\n vertex: { kind: 'existing', id: vertex },\n to: { kind: 'existing', id: pointId },\n })\n }\n}\n","import type { EntityId } from '../geometry/entityModel'\nimport { entryOf } from '../geometry/entityRecord'\nimport type { GeometryState } from '../schemas/geometryState.schema'\n\n/**\n * What a completed student action did to the attempt (RFC 0005 § Decision 2).\n * Each code maps to a row of ADR 0009's student-mutation inventory. The list\n * may grow additively, so a consumer treats a value it does not know as a\n * generic change.\n */\nexport type StudentGeometryAction =\n | 'point-placed'\n | 'point-promoted'\n | 'segment-created'\n | 'line-created'\n | 'angle-created'\n | 'point-moved'\n | 'deleted'\n | 'undo'\n | 'redo'\n | 'reset'\n | 'revealed'\n\nexport interface StudentGeometryChangeMeta {\n action: StudentGeometryAction\n // The persisted entities the action changed directly, in a deterministic\n // order: a construction before the endpoints it invented, a deletion's target\n // before the rest of its closure.\n entityIds: string[]\n}\n\n/**\n * The store's record of its last completed action. `sequence` rises by one per\n * action on that store, so two identical actions in a row are still two\n * receipts. Delivery metadata, not a command log: only the latest is kept, and\n * it is never serialized or put in history.\n */\nexport interface StudentAttemptChangeReceipt extends StudentGeometryChangeMeta {\n sequence: number\n}\n\n// Structural equality for plain document data. Snapshots share unchanged\n// definitions by reference, which is the fast path; a definition rebuilt with\n// the same content — a point dragged away and back — is compared by value, so\n// it is not reported as changed.\nexport function sameData(a: unknown, b: unknown): boolean {\n if (a === b) return true\n if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) return false\n if (Array.isArray(a) !== Array.isArray(b)) return false\n const aRecord = a as Record<string, unknown>\n const bRecord = b as Record<string, unknown>\n const keys = Object.keys(aRecord)\n if (keys.length !== Object.keys(bRecord).length) return false\n return keys.every(\n (key) =>\n Object.prototype.hasOwnProperty.call(bRecord, key) && sameData(aRecord[key], bRecord[key]),\n )\n}\n\n/**\n * The ids whose definition, presentation entry or answer membership differs\n * between two attempts — what an undo, a redo, a reset or a reveal changed.\n * Ordered by first appearance: the earlier attempt's entities, presentation and\n * answers, then the later one's.\n */\nexport function changedEntityIds(before: GeometryState, after: GeometryState): EntityId[] {\n const beforeAnswers = new Set(before.answerEntityIds)\n const afterAnswers = new Set(after.answerEntityIds)\n const ids = new Set<EntityId>()\n for (const attempt of [before, after]) {\n for (const id of Object.keys(attempt.entities)) ids.add(id)\n for (const id of Object.keys(attempt.presentation)) ids.add(id)\n for (const id of attempt.answerEntityIds) ids.add(id)\n }\n return [...ids].filter(\n (id) =>\n !sameData(entryOf(before.entities, id), entryOf(after.entities, id)) ||\n !sameData(entryOf(before.presentation, id), entryOf(after.presentation, id)) ||\n beforeAnswers.has(id) !== afterAnswers.has(id),\n )\n}\n","import type { GeometryExercise } from '../schemas/exercise.schema'\nimport { withoutSolution } from '../schemas/exerciseProjection'\nimport { isStudentDraggableGiven } from '../schemas/entityDocument'\nimport { latestGeometryStateVersion, type GeometryState } from '../schemas/geometryState.schema'\nimport type {\n EntityDefinition,\n EntityId,\n EntityRole,\n PointDefinition,\n} from '../geometry/entityModel'\nimport type { ObjectTypeName } from '../schemas/objectTypeName.schema'\nimport type { Point, PointId } from '../geometry/point'\nimport { isSameStoredPoint } from '../numerics/policy'\nimport { parameterOnEntity } from '../geometry/entities'\nimport { entryOf } from '../geometry/entityRecord'\nimport { buildGeometryContext } from '../state/geometryContext'\nimport { resolvePoints } from '../state/resolvePoints'\nimport type { RevealedAnswerShape } from '../validation/types'\nimport {\n endpointsOf,\n type ConstructionCommand,\n type ConstructionEndpoint,\n type ConstructionNotice,\n} from './constructionCommand'\nimport {\n changedEntityIds,\n sameData,\n type StudentAttemptChangeReceipt,\n type StudentGeometryChangeMeta,\n} from './attemptChange'\nimport {\n duplicateOf,\n endsRepeat,\n findEndpointsInScene,\n findSameConstruction,\n} from './constructionIdentity'\nimport { createStore, type Store } from './createStore'\nimport { pushHistory } from './history'\n\nexport type {\n StudentAttemptChangeReceipt,\n StudentGeometryAction,\n StudentGeometryChangeMeta,\n} from './attemptChange'\nexport type {\n ConstructionCommand,\n ConstructionEndpoint,\n ConstructionNotice,\n} from './constructionCommand'\n\nexport interface StudentState {\n exercise: GeometryExercise\n geometry: GeometryState\n pendingObjectType: ObjectTypeName | null\n pendingFirstPointId: PointId | null\n // The angle tool's second pick, between taps two and three. Never set while\n // `pendingFirstPointId` is null, and cleared with it on every path.\n pendingVertexPointId: PointId | null\n selectedObjectId: string | null\n past: GeometryState[]\n future: GeometryState[]\n // Consecutive commits sharing a tag collapse into a single undo entry. A\n // point drag fires one move per pointer event, and without this the undo\n // stack fills with pixel-sized steps instead of one \"I moved that point\".\n coalesceTag: string | null\n // Moves on whenever something replaces what a pointer gesture was aimed at —\n // a tool change, an undo, a redo, a reset. A canvas stamps its contact with\n // the value it pressed under and drops the contact when the two differ, so a\n // release arriving late cannot build on, or drag back, geometry that is no\n // longer there (RFC 0004 § Decision 4). Transient: never serialized, never in\n // history, and meaningful only as \"has it changed\". Optional so a state\n // literal built by hand still type-checks; absent reads as 0.\n interactionEpoch?: number\n // Why the last construction attempt made nothing new: the student already\n // built it (RFC 0004 § Decision 1). Every creation command replaces it, and\n // whatever moves `interactionEpoch` clears it. Transient and optional, for\n // the same reasons as the epoch.\n constructionNotice?: ConstructionNotice | null\n // Locks the attempt against every public student action (RFC 0005 §\n // Decision 3). Host-authority replacement still works, and `store.setState()`\n // is not guarded by anything. Transient and optional; absent reads as false.\n readOnly?: boolean\n // The last completed attempt-changing action. Geometry and its receipt land\n // in the same transition, except a drag: its frames write geometry, and the\n // one `point-moved` receipt lands when the drag is settled. Host replacement\n // leaves it alone. Transient and optional; absent reads as null.\n lastAttemptChange?: StudentAttemptChangeReceipt | null\n}\n\nexport interface StudentRemovalImpact {\n constructionIds: string[]\n // Derived points that lose their parents — an intersection whose object is\n // gone has nowhere to be, so it goes too.\n pointIds: string[]\n}\n\nconst EMPTY_ATTEMPT: GeometryState = {\n schemaVersion: latestGeometryStateVersion,\n entities: {},\n presentation: {},\n answerEntityIds: [],\n}\n\n/**\n * One store per attempt. Pass `savedGeometry` to resume a stored attempt —\n * validate it first with validateGeometryStateJson(), which checks it against\n * this exercise. A resumed attempt starts with empty history: undo must not\n * reach back past what the student can see.\n *\n * The store holds the exercise without its authored `solution` (ADR 0015):\n * the canonical answer is never student state.\n */\nexport function createStudentStore(\n exercise: GeometryExercise,\n savedGeometry?: GeometryState,\n): Store<StudentState> {\n return createStore<StudentState>({\n exercise: withoutSolution(exercise),\n // Empty, not seeded with the givens. A v1 attempt re-declared every given\n // point and `validateGeometryStateInput()` errored if one was missing; since\n // ADR 0008 § Amendment an attempt carries only student work, and a given the\n // student never touched is the exercise's alone (M2.6.c.2). The seeding took\n // `originOfPointDefinition()` with it, and with that the one gap it had: a\n // v1 `PointOrigin` has no `which`, so a derived given copied into the attempt\n // lost it.\n geometry: savedGeometry ?? EMPTY_ATTEMPT,\n pendingObjectType: null,\n pendingFirstPointId: null,\n pendingVertexPointId: null,\n selectedObjectId: null,\n past: [],\n future: [],\n coalesceTag: null,\n interactionEpoch: 0,\n constructionNotice: null,\n readOnly: false,\n lastAttemptChange: null,\n })\n}\n\n// The definition a drag or a draggability question acts on: the attempt's own\n// if it has one, and otherwise the exercise's, because a draggable given is not\n// in the attempt at all until the first time it is moved.\nfunction pointDefinitionOf(state: StudentState, pointId: PointId): PointDefinition | undefined {\n const definition =\n entryOf(state.geometry.entities, pointId) ?? entryOf(state.exercise.entities, pointId)\n return definition?.kind === 'point' ? definition : undefined\n}\n\nfunction receiptAfter(\n state: StudentState,\n meta: StudentGeometryChangeMeta,\n): StudentAttemptChangeReceipt {\n return { ...meta, sequence: (state.lastAttemptChange?.sequence ?? 0) + 1 }\n}\n\nconst MOVE_TAG = 'move:'\n\n// A drag's frames share one undo entry and owe one receipt between them, paid\n// when the drag ends — or when anything else settles it first: another student\n// action, the lock. Null when no drag is open.\n//\n// A drag that came back to where it started changed nothing, so it owes\n// nothing: the snapshot it pushed is restored, which drops its undo entry and\n// any given override its frames wrote. Only this point's entity can differ\n// from that snapshot, because a commit under another tag would have settled\n// the drag first.\nfunction settledMove(state: StudentState): Partial<StudentState> | null {\n const tag = state.coalesceTag\n if (!tag?.startsWith(MOVE_TAG)) return null\n const pointId = tag.slice(MOVE_TAG.length)\n const start = state.past[state.past.length - 1]\n const positionIn = (geometry: GeometryState) =>\n entryOf(geometry.entities, pointId) ?? entryOf(state.exercise.entities, pointId)\n if (start && sameData(positionIn(start), positionIn(state.geometry))) {\n return { coalesceTag: null, geometry: start, past: state.past.slice(0, -1) }\n }\n return {\n coalesceTag: null,\n lastAttemptChange: receiptAfter(state, { action: 'point-moved', entityIds: [pointId] }),\n }\n}\n\n// Called before a student action reads the state it acts on, so that action\n// never builds on a drag's frames that settling is about to take back.\nfunction settleMove(store: Store<StudentState>): void {\n const settled = settledMove(store.getState())\n if (settled) store.setState(settled)\n}\n\n// Every mutation of the student's geometry goes through here, so undo/redo\n// never misses one. Returning the same geometry reference means \"nothing\n// changed\" and leaves the history and the receipt untouched.\nfunction commitGeometry(\n store: Store<StudentState>,\n update: (geometry: GeometryState) => GeometryState,\n options: {\n coalesceTag?: string\n extra?: Partial<StudentState>\n receipt?: (before: GeometryState, after: GeometryState) => StudentGeometryChangeMeta\n } = {},\n): void {\n const before = store.getState().geometry\n const geometry = update(before)\n if (geometry === before) {\n store.setState(options.extra ?? {})\n return\n }\n const tag = options.coalesceTag ?? null\n store.setState((state) => ({\n geometry,\n past: tag !== null && tag === state.coalesceTag ? state.past : pushHistory(state.past, before),\n future: [],\n coalesceTag: tag,\n ...(options.receipt && {\n lastAttemptChange: receiptAfter(state, options.receipt(before, geometry)),\n }),\n ...options.extra,\n }))\n}\n\n// What every host-authority replacement and a reset clear: nothing of the\n// previous session's interaction survives, and the epoch drops a live gesture.\nfunction freshSession(state: StudentState): Partial<StudentState> {\n return {\n pendingObjectType: null,\n pendingFirstPointId: null,\n pendingVertexPointId: null,\n selectedObjectId: null,\n past: [],\n future: [],\n coalesceTag: null,\n interactionEpoch: (state.interactionEpoch ?? 0) + 1,\n constructionNotice: null,\n }\n}\n\n/**\n * Locks or unlocks the attempt (RFC 0005 § Decision 3). While read-only every\n * public student action that could change attempt data does nothing and no\n * tool can be armed; selection stays available for inspection.\n *\n * Locking disarms the tool and clears the pending endpoint, the selection and\n * the notice, and moves the epoch so a late pointer release writes nothing. A\n * drag whose frames already moved a point keeps that position, its one undo\n * entry and its one `point-moved` receipt, all in the locking transition.\n * Unlocking moves the epoch as well, so a contact pressed during the lock\n * cannot start editing once it lifts; no point is draggable while locked.\n * `replaceStudentAttempt()` and `replaceStudentSession()` still work while\n * locked; `store.setState()` is not guarded at all.\n */\nexport function setStudentReadOnly(store: Store<StudentState>, readOnly: boolean): void {\n if ((store.getState().readOnly ?? false) === readOnly) return\n if (!readOnly) {\n // The epoch moves this way too: a contact pressed while locked was aimed\n // at a figure it could not change, and must not start editing once the\n // lock lifts under it.\n store.setState((state) => ({\n readOnly: false,\n interactionEpoch: (state.interactionEpoch ?? 0) + 1,\n }))\n return\n }\n store.setState((state) => ({\n ...settledMove(state),\n readOnly: true,\n pendingObjectType: null,\n pendingFirstPointId: null,\n pendingVertexPointId: null,\n selectedObjectId: null,\n coalesceTag: null,\n interactionEpoch: (state.interactionEpoch ?? 0) + 1,\n constructionNotice: null,\n }))\n}\n\n/**\n * Host authority: installs `attempt` on the same store, as a controlled value\n * that differs from the current one (RFC 0005 § Decision 2). Both history\n * stacks and all interaction state are cleared and the epoch moves, so it\n * cannot be undone and a live gesture is dropped. Works while read-only, and is\n * not a student action: `lastAttemptChange` is left as it was, and a drag it\n * interrupts gets no receipt.\n *\n * The attempt is taken as already parsed and checked against this exercise —\n * validate untrusted input first, as for `createStudentStore()`.\n */\nexport function replaceStudentAttempt(store: Store<StudentState>, attempt: GeometryState): void {\n store.setState((state) => ({ ...freshSession(state), geometry: attempt }))\n}\n\n/**\n * Host authority: installs a different exercise and its attempt together, in\n * one transition, so no subscriber ever sees the new exercise with the old\n * attempt. An absent attempt is a fresh empty one. Otherwise it behaves as\n * `replaceStudentAttempt()` does: same store, history and interaction state\n * cleared, epoch moved, read-only kept, no receipt, and parsed input expected.\n * As at creation, the authored `solution` is left out of the store.\n */\nexport function replaceStudentSession(\n store: Store<StudentState>,\n exercise: GeometryExercise,\n attempt?: GeometryState,\n): void {\n store.setState((state) => ({\n ...freshSession(state),\n exercise: withoutSolution(exercise),\n geometry: attempt ?? { ...EMPTY_ATTEMPT },\n }))\n}\n\n// The student's own work, the domain a student creation is compared with: a\n// given the attempt re-declares is still the exercise's, and an answer that\n// repeats a given is the student's to build under an id of their own.\nfunction studentOwns(exercise: GeometryExercise): (id: EntityId) => boolean {\n return (id) => !Object.prototype.hasOwnProperty.call(exercise.entities, id)\n}\n\n// The whole pick sequence at once: the vertex pick means nothing without the\n// first, so no path clears one and keeps the other.\nconst NO_PICKS = { pendingFirstPointId: null, pendingVertexPointId: null } as const\n\n// For a command that refuses before it writes anything: the attempt still\n// happened, so an older notice no longer describes the last one, and a refused\n// construction ends its pick sequence as a completed one does.\nfunction dismissNotice(store: Store<StudentState>): void {\n const state = store.getState()\n if (state.constructionNotice || state.pendingFirstPointId) {\n store.setState({ ...NO_PICKS, constructionNotice: null })\n }\n}\n\n// Given ids count as taken even though they live in the exercise rather than\n// in the state: an id has to be unique across everything on the canvas, and a\n// given segment happening to be named `student-segment-1` would otherwise be\n// silently shadowed by the student's first one.\nfunction nextId(exercise: GeometryExercise, geometry: GeometryState, prefix: string): string {\n const ids = new Set([...Object.keys(exercise.entities), ...Object.keys(geometry.entities)])\n let index = 1\n while (ids.has(`${prefix}-${index}`)) index += 1\n return `${prefix}-${index}`\n}\n\n// One record write for every entity the store creates, whatever its kind:\n// `points` and `constructions[]` were two of these and `constructedPointIds`\n// covered only the first.\n//\n// The presentation entry is always written, never left to the attempt's\n// `implicit` default (ADR 0008). An entry that says `implicit` and a missing\n// entry resolve the same way, but only the written one survives a round trip\n// through a host that rebuilds the record, and only it can be flipped to\n// `explicit` by the one-field write RFC 0001 § 11 calls promotion.\nfunction withEntity(\n geometry: GeometryState,\n id: EntityId,\n definition: EntityDefinition,\n { role, countsAsAnswer }: { role: EntityRole; countsAsAnswer: boolean },\n): GeometryState {\n return {\n ...geometry,\n entities: { ...geometry.entities, [id]: definition },\n presentation: { ...geometry.presentation, [id]: { role } },\n answerEntityIds: countsAsAnswer ? [...geometry.answerEntityIds, id] : geometry.answerEntityIds,\n }\n}\n\nexport function selectStudentObjectType(\n store: Store<StudentState>,\n type: ObjectTypeName | null,\n): void {\n const { exercise, readOnly } = store.getState()\n if (type && (readOnly || !exercise.allowedObjects.includes(type))) return\n store.setState((state) => ({\n pendingObjectType: type,\n pendingFirstPointId: null,\n pendingVertexPointId: null,\n interactionEpoch: (state.interactionEpoch ?? 0) + 1,\n constructionNotice: null,\n }))\n}\n\n/**\n * Abandons a click-click construction in progress: the first endpoint is\n * forgotten and the tool stays armed. What a canvas calls when a gesture is\n * cancelled — a system gesture, a second finger, a release off the canvas —\n * so the next tap starts a construction rather than finishing a stale one.\n */\nexport function cancelStudentConstruction(store: Store<StudentState>): void {\n if (store.getState().pendingFirstPointId === null) return\n store.setState(NO_PICKS)\n}\n\nexport function undoStudentChange(store: Store<StudentState>): void {\n const { readOnly, past } = store.getState()\n if (readOnly || past.length === 0) return\n settleMove(store)\n store.setState((state) => {\n const previous = state.past[state.past.length - 1]\n if (!previous) return {}\n return {\n geometry: previous,\n past: state.past.slice(0, -1),\n future: [state.geometry, ...state.future],\n pendingFirstPointId: null,\n pendingVertexPointId: null,\n selectedObjectId: null,\n coalesceTag: null,\n interactionEpoch: (state.interactionEpoch ?? 0) + 1,\n constructionNotice: null,\n lastAttemptChange: receiptAfter(state, {\n action: 'undo',\n entityIds: changedEntityIds(state.geometry, previous),\n }),\n }\n })\n}\n\nexport function redoStudentChange(store: Store<StudentState>): void {\n const { readOnly, future } = store.getState()\n if (readOnly || future.length === 0) return\n settleMove(store)\n store.setState((state) => {\n const next = state.future[0]\n if (!next) return {}\n return {\n geometry: next,\n past: [...state.past, state.geometry],\n future: state.future.slice(1),\n pendingFirstPointId: null,\n pendingVertexPointId: null,\n selectedObjectId: null,\n coalesceTag: null,\n interactionEpoch: (state.interactionEpoch ?? 0) + 1,\n constructionNotice: null,\n lastAttemptChange: receiptAfter(state, {\n action: 'redo',\n entityIds: changedEntityIds(state.geometry, next),\n }),\n }\n })\n}\n\n/**\n * Starts the attempt over on the same store (RFC 0004 § Decision 2): the\n * exercise and its givens stay, and everything the student did goes — their\n * constructions, any given they dragged (it returns to where the teacher put\n * it), the tool, the selection, a pending endpoint and the notice. A resumed\n * attempt resets to empty, not to what was saved.\n *\n * Not undoable: both history stacks are emptied, so neither Undo nor Redo can\n * bring the old attempt back. The store object and its subscribers are kept.\n * The epoch moves even when the attempt was already empty, which is how a\n * canvas drops a gesture in flight, and a host clearing checked feedback on a\n * geometry or epoch change sees every reset. Nothing saved outside the store\n * is touched.\n *\n * A student action: it does nothing while read-only, and it records a `reset`\n * receipt even for an attempt that was already empty.\n */\nexport function resetStudentAttempt(store: Store<StudentState>): void {\n if (store.getState().readOnly) return\n settleMove(store)\n store.setState((state) => {\n // A new object every time, so a host watching `geometry` sees even an\n // empty reset.\n const geometry = { ...EMPTY_ATTEMPT }\n return {\n ...freshSession(state),\n geometry,\n lastAttemptChange: receiptAfter(state, {\n action: 'reset',\n entityIds: changedEntityIds(state.geometry, geometry),\n }),\n }\n })\n}\n\n// Takes a whole definition, not just coordinates: `{ kind: 'point', at }` is a\n// free point, while a snapped placement passes `{ on, t }` or `{ intersectionOf }`\n// so the engine remembers why the point is there.\n//\n// A point the student already built with this exact origin is returned instead\n// of stacked, unchanged — its role, label and candidacy are its own, and making\n// a hidden one visible is `promoteStudentPoint()`'s job, not this one's.\n//\n// Null while read-only: nothing was placed, and no id would be honest.\nexport function addStudentPoint(\n store: Store<StudentState>,\n definition: PointDefinition,\n countsAsConstruction = true,\n): PointId | null {\n if (store.getState().readOnly) return null\n settleMove(store)\n const { exercise, geometry } = store.getState()\n const existingId = findSameConstruction(geometry.entities, definition, studentOwns(exercise))\n if (existingId) {\n store.setState({ constructionNotice: duplicateOf(existingId) })\n return existingId\n }\n const pointId = nextId(exercise, geometry, 'student-point')\n commitGeometry(\n store,\n (geometry) =>\n withEntity(geometry, pointId, definition, {\n role: 'explicit',\n countsAsAnswer: countsAsConstruction,\n }),\n {\n extra: { constructionNotice: null },\n receipt: () => ({ action: 'point-placed', entityIds: [pointId] }),\n },\n )\n return pointId\n}\n\n// Making an implicit endpoint visible is a presentation change, not a new\n// answer. The point keeps its id and origin, so every construction already\n// attached to it stays attached.\nexport function promoteStudentPoint(store: Store<StudentState>, pointId: PointId): void {\n if (store.getState().readOnly) return\n settleMove(store)\n const state = store.getState()\n const definition = entryOf(state.geometry.entities, pointId)\n if (definition?.kind !== 'point') return\n if (state.geometry.presentation[pointId]?.role !== 'implicit') return\n commitGeometry(\n store,\n (geometry) => ({\n ...geometry,\n presentation: {\n ...geometry.presentation,\n [pointId]: { ...geometry.presentation[pointId], role: 'explicit' },\n },\n }),\n { receipt: () => ({ action: 'point-promoted', entityIds: [pointId] }) },\n )\n}\n\n/**\n * Drags a point to `point`, respecting what the point is:\n * - free: takes the new coordinates;\n * - on-object: slides along its object, so the drag re-parameterizes rather\n * than detaching it (a point placed on a line stays on that line);\n * - intersection: ignored — its position is its parents' to decide.\n */\nexport function moveStudentPoint(store: Store<StudentState>, pointId: PointId, point: Point): void {\n if (store.getState().readOnly) return\n // Another point's open drag ends here; this point's continues.\n if (store.getState().coalesceTag !== `${MOVE_TAG}${pointId}`) settleMove(store)\n const state = store.getState()\n const definition = pointDefinitionOf(state, pointId)\n if (!definition || 'intersectionOf' in definition) return\n // Enforced here, not only in the renderer: a pinned given is part of the\n // exercise, and the invariant has to hold for every caller of the store, not\n // just for the ones that happen to draw a drag handle.\n if (!isStudentPointDraggable(state, pointId)) return\n\n let moved: PointDefinition = { kind: 'point', at: { x: point.x, y: point.y } }\n if ('on' in definition) {\n // One indexed pass instead of an object scan plus a full resolve: the host\n // is absent both when it was deleted and when its own endpoints no longer\n // resolve, and either way there is nothing to slide along.\n const host = buildGeometryContext(state.exercise, state.geometry).resolvedOf(definition.on)\n if (!host) return\n // Whether the slide stops at an endpoint is the host's own rule, so a\n // segment clamps and a line does not without this knowing which it holds.\n // Null is a host a point cannot travel along at all — an id that names a\n // point, or two endpoints too close together to define a direction.\n const t = parameterOnEntity(host, point)\n if (t === null) return\n moved = { kind: 'point', on: definition.on, t }\n }\n\n // The entity alone, with no `presentation` entry: the first drag of a\n // draggable given is what puts it in the attempt at all, and a label the\n // exercise gave it is still the label to draw.\n //\n // A frame that lands where the point already is writes nothing: no copy of\n // the definition, no undo entry, and no given override at its own position.\n if (sameData(moved, definition)) return\n\n // One undo entry per drag, not per pointer event — see coalesceTag. No\n // receipt per frame either: the drag's one receipt is paid when it settles.\n commitGeometry(\n store,\n (geometry) => ({ ...geometry, entities: { ...geometry.entities, [pointId]: moved } }),\n { coalesceTag: `${MOVE_TAG}${pointId}` },\n )\n}\n\n// Whether dragging this point means anything: an intersection is placed by its\n// parents, so renderers show it as fixed.\nexport function isStudentPointDraggable(state: StudentState, pointId: PointId): boolean {\n // A locked attempt has nothing to drag, so a canvas opens no drag contact.\n if (state.readOnly) return false\n // A point the exercise declares moves only if the exercise's one predicate\n // says so (ADR 0016) — the rule the loader and the context apply to the\n // attempt this drag writes. Anything else is the student's own.\n if (Object.prototype.hasOwnProperty.call(state.exercise.entities, pointId)) {\n return isStudentDraggableGiven(state.exercise, pointId)\n }\n const definition = pointDefinitionOf(state, pointId)\n return definition !== undefined && !('intersectionOf' in definition)\n}\n\n// Ends the coalescing window opened by moveStudentPoint, so dragging the same\n// point twice leaves two undo entries rather than one. Renderers call this on\n// drag end; nothing breaks if it is never called, the two drags just merge.\n//\n// It is also where a drag that moved something records its one `point-moved`\n// receipt. A drag that moved nothing, or was already settled by another\n// action or by locking, records nothing.\nexport function endStudentPointDrag(store: Store<StudentState>): void {\n const state = store.getState()\n if (state.coalesceTag === null) return\n store.setState(settledMove(state) ?? { coalesceTag: null })\n}\n\n// Resolves one end, threading the geometry through so the second endpoint sees\n// the first one's id as taken. `reusedId` is the point already on the canvas\n// with exactly this end's origin, which it lands on, as a snap would have,\n// rather than invent its twin (RFC 0004 § Decision 1).\nfunction withEndpoint(\n exercise: GeometryExercise,\n geometry: GeometryState,\n endpoint: ConstructionEndpoint,\n reusedId: PointId | null,\n): [GeometryState, PointId] {\n if (endpoint.kind === 'existing') return [geometry, endpoint.id]\n if (reusedId) return [geometry, reusedId]\n const pointId = nextId(exercise, geometry, 'student-point')\n // Implicit, and not an answer entity: the student aimed a segment somewhere,\n // and the point that lands it is the engine's bookkeeping. It is still a snap\n // target, so the next segment drawn from there reuses it rather than stacking\n // a second point on the same spot — `entityCapabilities.ts` holds that row.\n return [\n withEntity(geometry, pointId, endpoint.origin, { role: 'implicit', countsAsAnswer: false }),\n pointId,\n ]\n}\n\n/**\n * The one way a gesture becomes geometry (RFC 0001 § 11).\n *\n * Two rules the pre-M2.9 code broke, both of them here now:\n *\n * 1. **One gesture is one commit is one undo.** Every entity the command needs\n * is written inside a single `commitGeometry()`, so a segment that had to\n * invent its endpoint still costs the student exactly one undo — and undoing\n * it takes the invented point with it.\n * 2. **An invented endpoint is not part of the answer.** Only the shape the\n * student drew is, which is what keeps a `create` goal from being satisfied\n * by scaffolding they never chose to make.\n *\n * A third since RFC 0004: **a construction the student already built is not\n * built twice.** The whole staged document is dropped — no invented point, no\n * history entry, the redo stack intact — and `constructionNotice` names the one\n * that is there.\n */\nexport function executeConstruction(\n store: Store<StudentState>,\n command: ConstructionCommand,\n): void {\n if (store.getState().readOnly) return\n settleMove(store)\n const state = store.getState()\n const endpoints = endpointsOf(command)\n const existingIds = endpoints.flatMap((endpoint) =>\n endpoint.kind === 'existing' ? [endpoint.id] : [],\n )\n // An id the caller supplies has to name a point that is really there. The\n // gesture layer resolves ids from the scene it drew, but this is a public\n // entry point, and a parent that resolves to nothing is an invalid document\n // rather than a bad drawing.\n if (\n !state.exercise.allowedObjects.includes(command.tool) ||\n existingIds.some((id) => !pointDefinitionOf(state, id))\n ) {\n dismissNotice(store)\n return\n }\n\n const staged = stageConstruction(state.exercise, state.geometry, command)\n if (!staged) {\n dismissNotice(store)\n return\n }\n if ('duplicateId' in staged) {\n store.setState({ ...NO_PICKS, constructionNotice: duplicateOf(staged.duplicateId) })\n return\n }\n commitGeometry(store, () => staged.geometry, {\n extra: { ...NO_PICKS, constructionNotice: null },\n receipt: () => ({\n action: `${command.tool}-created` as const,\n // The construction first, then the endpoints it invented.\n entityIds: [staged.objectId, ...staged.inventedIds],\n }),\n })\n}\n\ntype StagedConstruction =\n | { duplicateId: EntityId }\n | { geometry: GeometryState; objectId: EntityId; inventedIds: PointId[] }\n | null\n\n/**\n * What a command writes, without writing it: null when its ends repeat, the\n * construction already there, or the staged document — the geometry it was\n * given, unchanged, when the result would not be drawable. Shared by the\n * gesture path and the angle reveal, so both invent endpoints by one rule.\n */\nfunction stageConstruction(\n exercise: GeometryExercise,\n geometry: GeometryState,\n command: ConstructionCommand,\n): StagedConstruction {\n const endpoints = endpointsOf(command)\n const reuse = findEndpointsInScene(endpoints, exercise.entities, geometry.entities)\n if (endsRepeat(endpoints, reuse)) return null\n let withEnds = geometry\n const ids: PointId[] = []\n // Only the endpoints this command invented: a reused or existing endpoint\n // was not changed by it.\n const inventedIds: PointId[] = []\n endpoints.forEach((endpoint, index) => {\n const before = withEnds\n const [next, id] = withEndpoint(exercise, before, endpoint, reuse[index] ?? null)\n withEnds = next\n ids.push(id)\n if (next !== before) inventedIds.push(id)\n })\n // `endpointsOf()` gives each tool its own arity, which the type cannot see.\n const definition = { kind: command.tool, parents: ids } as unknown as EntityDefinition\n // A construction on a point this command just invented is new by definition,\n // so only one whose ends were all already there is compared.\n const duplicateId =\n withEnds === geometry\n ? findSameConstruction(geometry.entities, definition, studentOwns(exercise))\n : null\n if (duplicateId) return { duplicateId }\n\n const objectId = nextId(exercise, withEnds, `student-${command.tool}`)\n const candidate = withEntity(\n withEnds,\n objectId,\n definition,\n // No `constructedConstructionIds` ever existed: v1 treated every\n // construction as a candidate, so every one of them is an answer entity.\n { role: 'explicit', countsAsAnswer: true },\n )\n // Every end must resolve, and none may sit on parent 1 — a connection's `to`,\n // an angle's vertex. An angle's two arms may coincide: 0° is an angle.\n const context = buildGeometryContext(exercise, candidate)\n const positions = ids.map((id) => context.points.get(id))\n const anchor = positions[1]\n const drawable =\n anchor &&\n positions.every(\n (position, index) => position && (index === 1 || !isSameStoredPoint(position, anchor)),\n )\n return { geometry: drawable ? candidate : geometry, objectId, inventedIds }\n}\n\n/**\n * One tap of a click-click construction on an existing point. A segment or a\n * line builds on its second pick; an angle keeps its second as the vertex and\n * builds on its third, in notation order (ADR 0009 § Amendment). A tap on a\n * point already picked cancels the whole sequence.\n */\nexport function pickStudentPoint(store: Store<StudentState>, pointId: PointId): void {\n const state = store.getState()\n if (state.readOnly) return\n const type = state.pendingObjectType\n if (type !== 'segment' && type !== 'line' && type !== 'angle') return\n const first = state.pendingFirstPointId\n const vertex = state.pendingVertexPointId\n if (!first) {\n store.setState({ pendingFirstPointId: pointId, constructionNotice: null })\n } else if (pointId === first || pointId === vertex) {\n store.setState({ ...NO_PICKS, constructionNotice: null })\n } else if (type !== 'angle') {\n // The click-click path and the drag path build the same thing, so they go\n // through the same command — one commit either way.\n executeConstruction(store, {\n tool: type,\n from: { kind: 'existing', id: first },\n to: { kind: 'existing', id: pointId },\n })\n } else if (!vertex) {\n store.setState({ pendingVertexPointId: pointId, constructionNotice: null })\n } else {\n executeConstruction(store, {\n tool: type,\n from: { kind: 'existing', id: first },\n vertex: { kind: 'existing', id: vertex },\n to: { kind: 'existing', id: pointId },\n })\n }\n}\n\nexport function selectStudentObject(store: Store<StudentState>, id: string | null): void {\n store.setState({ selectedObjectId: id })\n}\n\n// Given points live in the exercise, so the student may move a draggable one\n// but never delete it; everything else in GeometryState is their own work.\nexport function isStudentOwned(state: StudentState, id: string): boolean {\n if (!Object.prototype.hasOwnProperty.call(state.geometry.entities, id)) return false\n return !Object.prototype.hasOwnProperty.call(state.exercise.entities, id)\n}\n\n/**\n * Everything that disappears along with `id`, the target included, the same\n * way getGivenRemovalImpact() reports cascades on the teacher side.\n *\n * The cascade follows the dependency graph upwards: a construction needs its\n * endpoints, and a derived point needs its objects, so deleting a segment can\n * strip an intersection point, which can strip a construction that used it,\n * and so on. Since M2.2 that is one walk of the context's reverse edges — the\n * fixpoint loop it replaces rescanned both documents once per entity it\n * removed.\n */\nexport function getStudentRemovalImpact(state: StudentState, id: string): StudentRemovalImpact {\n if (!isStudentOwned(state, id)) return { constructionIds: [], pointIds: [] }\n const removed = new Set<string>(\n buildGeometryContext(state.exercise, state.geometry).removalClosureOf(id),\n )\n\n // Split by kind rather than by which list it came from: one `entities` record\n // holds both, and the two-list shape survives only because callers report the\n // two separately.\n const constructionIds: string[] = []\n const pointIds: string[] = []\n for (const [entityId, definition] of Object.entries(state.geometry.entities)) {\n if (!removed.has(entityId)) continue\n if (definition.kind === 'point') pointIds.push(entityId)\n else constructionIds.push(entityId)\n }\n return { constructionIds, pointIds }\n}\n\nexport function deleteStudentObject(store: Store<StudentState>, id: string): void {\n if (store.getState().readOnly) return\n settleMove(store)\n const state = store.getState()\n if (!isStudentOwned(state, id)) return\n const impact = getStudentRemovalImpact(state, id)\n const removed = new Set<string>([\n ...impact.constructionIds,\n // Given points are never removed, whatever the cascade says. Nothing is\n // left dangling by that: a re-declared given may only be defined on\n // entities the exercise declares, refused at load since M2.G.5, so a given\n // is never downstream of student work in the first place.\n ...impact.pointIds.filter((pointId) => isStudentOwned(state, pointId)),\n ])\n\n // The presentation entry and the answer entry go with the entity: either one\n // left behind names something the document no longer declares, and both v2\n // documents refuse that.\n const without = <T>(record: Record<EntityId, T>): Record<EntityId, T> =>\n Object.fromEntries(Object.entries(record).filter(([entityId]) => !removed.has(entityId)))\n\n commitGeometry(\n store,\n (geometry) => ({\n schemaVersion: geometry.schemaVersion,\n entities: without(geometry.entities),\n presentation: without(geometry.presentation),\n answerEntityIds: geometry.answerEntityIds.filter((entityId) => !removed.has(entityId)),\n }),\n {\n extra: {\n selectedObjectId: null,\n // Removing either pick ends the whole sequence.\n ...((removed.has(state.pendingFirstPointId ?? '') ||\n removed.has(state.pendingVertexPointId ?? '')) &&\n NO_PICKS),\n },\n // The target, then the rest of what went with it in record order.\n receipt: (before) => ({\n action: 'deleted',\n entityIds: [\n id,\n ...Object.keys(before.entities).filter(\n (entityId) => entityId !== id && removed.has(entityId),\n ),\n ],\n }),\n },\n )\n}\n\n// Coordinates come from the resolver now, so a revealed answer can land on a\n// derived point (an intersection, say) and reuse it instead of stacking a\n// second point on the same spot.\n// Point reuse is deliberately looser than coordinate equality; the reason is\n// recorded with POINT_REUSE_EPSILON in numerics/policy.ts.\nfunction findPointAt(resolved: ReadonlyMap<PointId, Point>, position: Point): PointId | null {\n for (const [id, point] of resolved) {\n if (isSameStoredPoint(point, position)) return id\n }\n return null\n}\n\n// What a reveal leaves behind, and the student entity it reused rather than\n// built — the one `constructionNotice` names — if it reused one.\ntype Revealed = [GeometryState, EntityId | null]\n\n// A revealed point counts as the student's own answer, otherwise re-checking\n// would still fail: goals are only ever satisfied by candidates named in\n// `answerEntityIds`. An existing point at that position is promoted rather than\n// duplicated.\nfunction revealPoint(\n exercise: GeometryExercise,\n geometry: GeometryState,\n resolved: ReadonlyMap<PointId, Point>,\n at: Point,\n): Revealed {\n const existingId = findPointAt(resolved, at)\n // Promoted only if the *attempt* declares it and the exercise does not. A\n // given is not an answer to anything (ADR 0008 § Amendment), so a revealed\n // point landing on one is declared as the student's own rather than\n // promoting the exercise's — which is what v1 did, only because its attempt\n // re-declared every given. The exercise half of the test is what a\n // *draggable* given needs (M2.G.2): the attempt does declare it, so naming\n // it as an answer used to be allowed here, and grading now ignores such an\n // entry — leaving the reveal unable to make its own re-check pass.\n if (\n existingId &&\n Object.prototype.hasOwnProperty.call(geometry.entities, existingId) &&\n !Object.prototype.hasOwnProperty.call(exercise.entities, existingId)\n ) {\n if (geometry.answerEntityIds.includes(existingId)) return [geometry, existingId]\n return [{ ...geometry, answerEntityIds: [...geometry.answerEntityIds, existingId] }, existingId]\n }\n const pointId = nextId(exercise, geometry, 'student-point')\n return [\n withEntity(\n geometry,\n pointId,\n { kind: 'point', at },\n { role: 'explicit', countsAsAnswer: true },\n ),\n null,\n ]\n}\n\n// A revealed answer's synthesized endpoints stay **explicit**, unlike the ones\n// `executeConstruction()` invents. A reveal is the engine showing its work, and\n// a shown answer whose endpoints were invisible would be a different picture\n// than the one the student is being asked to compare against. They are still\n// not answer entities: the reveal is not the student's construction.\nconst EXPLICIT_SCAFFOLD = { role: 'explicit', countsAsAnswer: false } as const\n\n// Reveals an answer by adding it as a real construction — reusing an\n// existing given point where the shape's endpoint lands on one (the\n// common case for throughPoints goals) and creating a new point otherwise\n// (e.g. the synthesized second endpoint of a perpendicular goal) — so the\n// student can immediately re-check it like anything they built themselves.\n// The whole reveal is one commit, so one undo takes all of it back.\nexport function revealStudentAnswer(store: Store<StudentState>, shape: RevealedAnswerShape): void {\n if (store.getState().readOnly) return\n settleMove(store)\n const { exercise, geometry } = store.getState()\n const [revealed, reusedId] = revealShape(exercise, geometry, shape)\n commitGeometry(store, () => revealed, {\n extra: { constructionNotice: reusedId ? duplicateOf(reusedId) : null },\n receipt: (before, after) => ({\n action: 'revealed',\n entityIds: changedEntityIds(before, after),\n }),\n })\n}\n\nfunction revealShape(\n exercise: GeometryExercise,\n geometry: GeometryState,\n shape: RevealedAnswerShape,\n): Revealed {\n // A copy: the reveal invents points as it goes and looks them up again a\n // line later, so it needs a map of its own rather than the scene's.\n const resolved = new Map(resolvePoints(exercise, geometry))\n if (shape.type === 'point') return revealPoint(exercise, geometry, resolved, shape.at)\n if (shape.type === 'angle') {\n // Through the angle command, so the arm points are invented implicit by the\n // rule a gesture follows; an end that lands on a point already there uses it.\n const endAt = (at: Point): ConstructionEndpoint => {\n const id = findPointAt(resolved, at)\n return id ? { kind: 'existing', id } : { kind: 'create', origin: { kind: 'point', at } }\n }\n const staged = stageConstruction(exercise, geometry, {\n tool: 'angle',\n from: endAt(shape.arms[0]),\n vertex: endAt(shape.vertex),\n to: endAt(shape.arms[1]),\n })\n if (!staged) return [geometry, null]\n return 'duplicateId' in staged ? [geometry, staged.duplicateId] : [staged.geometry, null]\n }\n\n const [a, b] = shape.type === 'segment' ? [shape.from, shape.to] : shape.through\n let next = geometry\n let fromId = findPointAt(resolved, a)\n if (!fromId) {\n fromId = nextId(exercise, next, 'student-point')\n next = withEntity(next, fromId, { kind: 'point', at: a }, EXPLICIT_SCAFFOLD)\n resolved.set(fromId, a)\n }\n let toId = findPointAt(resolved, b)\n if (!toId) {\n toId = nextId(exercise, next, 'student-point')\n next = withEntity(next, toId, { kind: 'point', at: b }, EXPLICIT_SCAFFOLD)\n resolved.set(toId, b)\n }\n if (fromId === toId) return [geometry, null]\n\n // Same construction, not the same id: a reveal the student already built\n // keeps the one that is there, by the rule every other creation command\n // uses. An id is not part of that answer — a definition carries none — so\n // one is only claimed once the construction is known to be worth adding.\n const definition: EntityDefinition = { kind: shape.type, parents: [fromId, toId] }\n const existingId = findSameConstruction(next.entities, definition, studentOwns(exercise))\n if (existingId) return [geometry, existingId]\n return [\n withEntity(next, nextId(exercise, next, `student-${shape.type}`), definition, {\n role: 'explicit',\n countsAsAnswer: true,\n }),\n null,\n ]\n}\n"],"mappings":"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"}
|
|
@@ -4,8 +4,9 @@ export interface GeometryExerciseWarning {
|
|
|
4
4
|
message: string;
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
|
-
* What is wrong with an exercise without being invalid.
|
|
8
|
-
*
|
|
7
|
+
* What is wrong with an exercise without being invalid. Two rules: a given
|
|
8
|
+
* placed where the viewport cannot show it, and a figure goal the authored
|
|
9
|
+
* figure already satisfies.
|
|
9
10
|
*
|
|
10
11
|
* Only a **free** point is checked. A derived point has no stored coordinates —
|
|
11
12
|
* it is wherever the graph puts it, which depends on the entities it is built
|
|
@@ -4,8 +4,9 @@ export interface GeometryExerciseWarning {
|
|
|
4
4
|
message: string;
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
|
-
* What is wrong with an exercise without being invalid.
|
|
8
|
-
*
|
|
7
|
+
* What is wrong with an exercise without being invalid. Two rules: a given
|
|
8
|
+
* placed where the viewport cannot show it, and a figure goal the authored
|
|
9
|
+
* figure already satisfies.
|
|
9
10
|
*
|
|
10
11
|
* Only a **free** point is checked. A derived point has no stored coordinates —
|
|
11
12
|
* it is wherever the graph puts it, which depends on the entities it is built
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getGeometryExerciseWarnings.d.ts","sourceRoot":"","sources":["../../src/validation/getGeometryExerciseWarnings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;
|
|
1
|
+
{"version":3,"file":"getGeometryExerciseWarnings.d.ts","sourceRoot":"","sources":["../../src/validation/getGeometryExerciseWarnings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAMlE,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAA;IAC5B,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,gBAAgB,GAAG,uBAAuB,EAAE,CAWjG"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { AngleBisectorGoal } from '../../schemas/goals/angleBisectorGoal.schema.js';
|
|
2
|
+
import { CandidateGoalHandler } from './goalKindHandler.js';
|
|
3
|
+
export declare const angleBisectorGoalHandler: CandidateGoalHandler<AngleBisectorGoal>;
|
|
4
|
+
//# sourceMappingURL=angleBisector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"angleBisector.d.ts","sourceRoot":"","sources":["../../../src/validation/goals/angleBisector.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8CAA8C,CAAA;AAYrF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AA0B7D,eAAO,MAAM,wBAAwB,EAAE,oBAAoB,CAAC,iBAAiB,CAkC5E,CAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { AngleDefinition } from '../../geometry/entityModel.cjs';
|
|
2
|
+
import { Point } from '../../geometry/point.cjs';
|
|
3
|
+
import { AngleCriterion } from '../../schemas/goals/angleCriterion.schema.cjs';
|
|
4
|
+
export type AngleInterpretation = 'directed' | 'undirected';
|
|
5
|
+
export declare function measuredDegrees(sweep: number, interpretation: AngleInterpretation): number;
|
|
6
|
+
export declare function meetsAngleCriterion(sweep: number, interpretation: AngleInterpretation, criterion: AngleCriterion): boolean;
|
|
7
|
+
export interface MeasuredAngle {
|
|
8
|
+
vertex: Point;
|
|
9
|
+
startTurn: number;
|
|
10
|
+
sweepTurn: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function measureAngleAt(points: ReadonlyMap<string, Point>, [firstArmId, vertexId, secondArmId]: AngleDefinition['parents']): MeasuredAngle | null;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { AngleDefinition } from '../../geometry/entityModel.js';
|
|
2
|
+
import { Point } from '../../geometry/point.js';
|
|
3
|
+
import { AngleCriterion } from '../../schemas/goals/angleCriterion.schema.js';
|
|
4
|
+
export type AngleInterpretation = 'directed' | 'undirected';
|
|
5
|
+
export declare function measuredDegrees(sweep: number, interpretation: AngleInterpretation): number;
|
|
6
|
+
export declare function meetsAngleCriterion(sweep: number, interpretation: AngleInterpretation, criterion: AngleCriterion): boolean;
|
|
7
|
+
export interface MeasuredAngle {
|
|
8
|
+
vertex: Point;
|
|
9
|
+
startTurn: number;
|
|
10
|
+
sweepTurn: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function measureAngleAt(points: ReadonlyMap<string, Point>, [firstArmId, vertexId, secondArmId]: AngleDefinition['parents']): MeasuredAngle | null;
|
|
13
|
+
//# sourceMappingURL=angleCriterion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"angleCriterion.d.ts","sourceRoot":"","sources":["../../../src/validation/goals/angleCriterion.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AACjE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAA;AAIjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2CAA2C,CAAA;AAG/E,MAAM,MAAM,mBAAmB,GAAG,UAAU,GAAG,YAAY,CAAA;AAI3D,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,mBAAmB,GAAG,MAAM,CAG1F;AAID,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,cAAc,EAAE,mBAAmB,EACnC,SAAS,EAAE,cAAc,GACxB,OAAO,CAQT;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,KAAK,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB;AAKD,wBAAgB,cAAc,CAC5B,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,EAClC,CAAC,UAAU,EAAE,QAAQ,EAAE,WAAW,CAAC,EAAE,eAAe,CAAC,SAAS,CAAC,GAC9D,aAAa,GAAG,IAAI,CAMtB"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { AngleDefinition } from '../../geometry/entityModel.cjs';
|
|
2
|
+
import { GoalCandidate } from '../../geometry/goalCandidate.cjs';
|
|
3
|
+
import { AngleMeasureGoal } from '../../schemas/goals/angleMeasureGoal.schema.cjs';
|
|
4
|
+
import { CandidateGoalHandler } from './goalKindHandler.cjs';
|
|
5
|
+
type AngleCandidate = GoalCandidate & {
|
|
6
|
+
definition: AngleDefinition;
|
|
7
|
+
};
|
|
8
|
+
export declare const angleMeasureGoalHandler: CandidateGoalHandler<AngleMeasureGoal, AngleCandidate>;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AngleDefinition } from '../../geometry/entityModel.js';
|
|
2
|
+
import { GoalCandidate } from '../../geometry/goalCandidate.js';
|
|
3
|
+
import { AngleMeasureGoal } from '../../schemas/goals/angleMeasureGoal.schema.js';
|
|
4
|
+
import { CandidateGoalHandler } from './goalKindHandler.js';
|
|
5
|
+
type AngleCandidate = GoalCandidate & {
|
|
6
|
+
definition: AngleDefinition;
|
|
7
|
+
};
|
|
8
|
+
export declare const angleMeasureGoalHandler: CandidateGoalHandler<AngleMeasureGoal, AngleCandidate>;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=angleMeasure.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"angleMeasure.d.ts","sourceRoot":"","sources":["../../../src/validation/goals/angleMeasure.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAA;AAEjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6CAA6C,CAAA;AAKnF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAG7D,KAAK,cAAc,GAAG,aAAa,GAAG;IAAE,UAAU,EAAE,eAAe,CAAA;CAAE,CAAA;AAMrE,eAAO,MAAM,uBAAuB,EAAE,oBAAoB,CAAC,gBAAgB,EAAE,cAAc,CAkD1F,CAAA"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { CreateGoal } from '../../schemas/goals/createGoal.schema.cjs';
|
|
2
|
-
import {
|
|
3
|
-
export declare const createGoalHandler:
|
|
2
|
+
import { CandidateGoalHandler } from './goalKindHandler.cjs';
|
|
3
|
+
export declare const createGoalHandler: CandidateGoalHandler<CreateGoal>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { CreateGoal } from '../../schemas/goals/createGoal.schema.js';
|
|
2
|
-
import {
|
|
3
|
-
export declare const createGoalHandler:
|
|
2
|
+
import { CandidateGoalHandler } from './goalKindHandler.js';
|
|
3
|
+
export declare const createGoalHandler: CandidateGoalHandler<CreateGoal>;
|
|
4
4
|
//# sourceMappingURL=create.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../src/validation/goals/create.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uCAAuC,CAAA;AAEvE,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../src/validation/goals/create.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uCAAuC,CAAA;AAEvE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAO7D,eAAO,MAAM,iBAAiB,EAAE,oBAAoB,CAAC,UAAU,CAiB9D,CAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { GivenAngleGoal } from '../../schemas/goals/givenAngleGoal.schema.cjs';
|
|
2
|
+
import { FigureGoalHandler } from './goalKindHandler.cjs';
|
|
3
|
+
/**
|
|
4
|
+
* The first figure goal (ADR 0016): the given angle as the evaluated figure
|
|
5
|
+
* resolves it, folded by the goal's interpretation and measured against its
|
|
6
|
+
* criterion. That is the whole predicate — RFC 0006 § Decision 1 § Satisfaction.
|
|
7
|
+
*
|
|
8
|
+
* It reads the scene's resolved form rather than measuring the parents itself,
|
|
9
|
+
* so the sweep graded is exactly the one the canvas drew and the companion read.
|
|
10
|
+
*/
|
|
11
|
+
export declare const givenAngleGoalHandler: FigureGoalHandler<GivenAngleGoal>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { GivenAngleGoal } from '../../schemas/goals/givenAngleGoal.schema.js';
|
|
2
|
+
import { FigureGoalHandler } from './goalKindHandler.js';
|
|
3
|
+
/**
|
|
4
|
+
* The first figure goal (ADR 0016): the given angle as the evaluated figure
|
|
5
|
+
* resolves it, folded by the goal's interpretation and measured against its
|
|
6
|
+
* criterion. That is the whole predicate — RFC 0006 § Decision 1 § Satisfaction.
|
|
7
|
+
*
|
|
8
|
+
* It reads the scene's resolved form rather than measuring the parents itself,
|
|
9
|
+
* so the sweep graded is exactly the one the canvas drew and the companion read.
|
|
10
|
+
*/
|
|
11
|
+
export declare const givenAngleGoalHandler: FigureGoalHandler<GivenAngleGoal>;
|
|
12
|
+
//# sourceMappingURL=givenAngle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"givenAngle.d.ts","sourceRoot":"","sources":["../../../src/validation/goals/givenAngle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2CAA2C,CAAA;AAG/E,OAAO,KAAK,EAAE,iBAAiB,EAA2B,MAAM,mBAAmB,CAAA;AAEnF;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,EAAE,iBAAiB,CAAC,cAAc,CAWnE,CAAA"}
|
|
@@ -1,24 +1,61 @@
|
|
|
1
|
+
import { EntityId, GeometryContext } from '../../geometry/entityModel.cjs';
|
|
1
2
|
import { GeometryGoal } from '../../schemas/goals/geometryGoal.schema.cjs';
|
|
2
3
|
import { GoalKindDeclaration } from '../../schemas/goals/goalKindDeclaration.cjs';
|
|
3
4
|
import { GoalCandidate } from '../../geometry/goalCandidate.cjs';
|
|
4
5
|
import { RevealContext, RevealedAnswerShape, ValidationContext } from '../types.cjs';
|
|
6
|
+
/**
|
|
7
|
+
* What a figure goal is handed: the same context plus the scene it was built
|
|
8
|
+
* from, because a figure goal judges an entity's *resolved* form rather than a
|
|
9
|
+
* candidate's coordinates (RFC 0006 § Handler shape).
|
|
10
|
+
*
|
|
11
|
+
* Internal, unlike `ValidationContext`: no published function takes one, and
|
|
12
|
+
* ADR 0016's only additive public name is the `unresolved-reference` code.
|
|
13
|
+
*/
|
|
14
|
+
export interface FigureValidationContext extends ValidationContext {
|
|
15
|
+
scene: GeometryContext;
|
|
16
|
+
}
|
|
5
17
|
/**
|
|
6
18
|
* Everything a goal kind can answer: its declaration (kind, schema, referenced
|
|
7
19
|
* givens, authoring rules — see `schemas/goals/goalKindDeclaration.ts`) plus
|
|
8
20
|
* how it judges a student's work.
|
|
9
21
|
*
|
|
22
|
+
* A kind judges one of two **subjects** (ADR 0016), which is why this is a
|
|
23
|
+
* union rather than one interface with optional members: either the candidates
|
|
24
|
+
* the student named as answers, or the figure itself. The discriminant is read
|
|
25
|
+
* before anything else is called, so a figure goal can never be handed a
|
|
26
|
+
* candidate and a candidate goal can never be asked about the figure.
|
|
27
|
+
*
|
|
10
28
|
* `accepts()` is the precondition of the three members below it: a candidate a
|
|
11
29
|
* kind cannot be satisfied by is never measured, so `satisfies()` and
|
|
12
30
|
* `isRelevantAttempt()` are written for candidates of the shape the kind
|
|
13
31
|
* expects and must be reached through the registry's dispatchers
|
|
14
32
|
* (`goalSatisfiedBy()`, `isRelevantGoalAttempt()`), which apply it.
|
|
15
33
|
*/
|
|
16
|
-
export interface
|
|
34
|
+
export interface CandidateGoalHandler<TGoal extends GeometryGoal = GeometryGoal, TCandidate extends GoalCandidate = GoalCandidate> extends GoalKindDeclaration<TGoal> {
|
|
35
|
+
subject: 'candidates';
|
|
17
36
|
accepts(candidate: GoalCandidate, goal: TGoal): candidate is TCandidate;
|
|
18
37
|
satisfies(candidate: TCandidate, goal: TGoal, ctx: ValidationContext): boolean;
|
|
19
38
|
isRelevantAttempt(candidate: TCandidate, goal: TGoal, ctx: ValidationContext): boolean;
|
|
20
39
|
buildRevealedAnswer(goal: TGoal, ctx: RevealContext): RevealedAnswerShape | null;
|
|
21
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* A goal that judges a named given entity as the evaluated figure resolves it
|
|
43
|
+
* (ADR 0016). It has no candidates, so none of the four members above applies:
|
|
44
|
+
* there is one predicate over the figure and the ids it judged.
|
|
45
|
+
*
|
|
46
|
+
* `buildRevealedAnswer()` has no figure equivalent — a correct figure is a set
|
|
47
|
+
* of positions, not a shape to synthesize — so a figure goal's per-goal reveal
|
|
48
|
+
* is `null` and the reveal of a drag exercise is the embedded solution
|
|
49
|
+
* (ADR 0015, RFC 0006 § Reporting).
|
|
50
|
+
*/
|
|
51
|
+
export interface FigureGoalHandler<TGoal extends GeometryGoal = GeometryGoal> extends GoalKindDeclaration<TGoal> {
|
|
52
|
+
subject: 'figure';
|
|
53
|
+
/** Undefined when the judged entity does not resolve in this figure. */
|
|
54
|
+
satisfiedByFigure(goal: TGoal, ctx: FigureValidationContext): boolean | undefined;
|
|
55
|
+
/** What the goal judged, for the report and the canvas highlight. */
|
|
56
|
+
judgedEntityIds(goal: TGoal): readonly EntityId[];
|
|
57
|
+
}
|
|
58
|
+
export type GoalKindHandler<TGoal extends GeometryGoal = GeometryGoal, TCandidate extends GoalCandidate = GoalCandidate> = CandidateGoalHandler<TGoal, TCandidate> | FigureGoalHandler<TGoal>;
|
|
22
59
|
export type GoalKindRegistry = {
|
|
23
60
|
[K in GeometryGoal['type']]: GoalKindHandler<Extract<GeometryGoal, {
|
|
24
61
|
type: K;
|