@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.
Files changed (238) hide show
  1. package/README.md +24 -7
  2. package/dist/core.cjs +1 -1
  3. package/dist/core.mjs +4 -4
  4. package/dist/entries/core.d.cts +1 -1
  5. package/dist/entries/core.d.ts +1 -1
  6. package/dist/entries/core.d.ts.map +1 -1
  7. package/dist/exercise.schema-Bma3qN9P.js +251 -0
  8. package/dist/exercise.schema-Bma3qN9P.js.map +1 -0
  9. package/dist/exercise.schema-Oj5ZH-yO.cjs +2 -0
  10. package/dist/exercise.schema-Oj5ZH-yO.cjs.map +1 -0
  11. package/dist/exerciseLoad-Db9Cigxi.cjs +2 -0
  12. package/dist/exerciseLoad-Db9Cigxi.cjs.map +1 -0
  13. package/dist/{geometryAttemptStatus-5L_q6-9l.js → exerciseLoad-DgeDXYbf.js} +84 -71
  14. package/dist/exerciseLoad-DgeDXYbf.js.map +1 -0
  15. package/dist/geometry/entities/angle.d.cts +56 -0
  16. package/dist/geometry/entities/angle.d.ts +57 -0
  17. package/dist/geometry/entities/angle.d.ts.map +1 -0
  18. package/dist/geometry/entities/circle.d.ts.map +1 -1
  19. package/dist/geometry/entities/index.d.cts +8 -4
  20. package/dist/geometry/entities/index.d.ts +8 -4
  21. package/dist/geometry/entities/index.d.ts.map +1 -1
  22. package/dist/geometry/entities/line.d.ts.map +1 -1
  23. package/dist/geometry/entities/point.d.ts.map +1 -1
  24. package/dist/geometry/entities/relations.d.cts +8 -1
  25. package/dist/geometry/entities/relations.d.ts +8 -1
  26. package/dist/geometry/entities/relations.d.ts.map +1 -1
  27. package/dist/geometry/entities/segment.d.ts.map +1 -1
  28. package/dist/geometry/entityModel.d.cts +21 -3
  29. package/dist/geometry/entityModel.d.ts +21 -3
  30. package/dist/geometry/entityModel.d.ts.map +1 -1
  31. package/dist/geometryState.schema-B5ccMKDa.cjs +2 -0
  32. package/dist/geometryState.schema-B5ccMKDa.cjs.map +1 -0
  33. package/dist/geometryState.schema-CKFPlX8j.js +1177 -0
  34. package/dist/geometryState.schema-CKFPlX8j.js.map +1 -0
  35. package/dist/goals-BlSeniyt.js +563 -0
  36. package/dist/goals-BlSeniyt.js.map +1 -0
  37. package/dist/goals-DLbmBZD8.cjs +2 -0
  38. package/dist/goals-DLbmBZD8.cjs.map +1 -0
  39. package/dist/index.cjs +1 -1
  40. package/dist/index.mjs +8 -8
  41. package/dist/measure/angleType.d.cts +2 -0
  42. package/dist/measure/angleType.d.ts +3 -0
  43. package/dist/measure/angleType.d.ts.map +1 -0
  44. package/dist/measure/geometryAdapter.d.cts +3 -0
  45. package/dist/measure/geometryAdapter.d.ts +3 -0
  46. package/dist/measure/geometryAdapter.d.ts.map +1 -1
  47. package/dist/react-B26_e52I.cjs +2 -0
  48. package/dist/react-B26_e52I.cjs.map +1 -0
  49. package/dist/{react-BEcdx1f6.js → react-B5cGaxH8.js} +738 -513
  50. package/dist/react-B5cGaxH8.js.map +1 -0
  51. package/dist/react.cjs +1 -1
  52. package/dist/react.mjs +1 -1
  53. package/dist/renderer/GeometryCanvas.d.ts.map +1 -1
  54. package/dist/renderer/StudentGeometryCanvas.d.ts.map +1 -1
  55. package/dist/renderer/Toolbar.d.ts.map +1 -1
  56. package/dist/renderer/angleMeasure.d.cts +14 -0
  57. package/dist/renderer/angleMeasure.d.ts +15 -0
  58. package/dist/renderer/angleMeasure.d.ts.map +1 -0
  59. package/dist/renderer/deriveRenderableGeometry.d.cts +1 -0
  60. package/dist/renderer/deriveRenderableGeometry.d.ts +1 -0
  61. package/dist/renderer/deriveRenderableGeometry.d.ts.map +1 -1
  62. package/dist/renderer/deriveSemanticScene.d.ts.map +1 -1
  63. package/dist/renderer/dragSnap.d.cts +14 -0
  64. package/dist/renderer/dragSnap.d.ts +15 -0
  65. package/dist/renderer/dragSnap.d.ts.map +1 -0
  66. package/dist/renderer/shapes/AngleShape.d.cts +19 -0
  67. package/dist/renderer/shapes/AngleShape.d.ts +20 -0
  68. package/dist/renderer/shapes/AngleShape.d.ts.map +1 -0
  69. package/dist/renderer/shapes/EntityLabel.d.cts +9 -1
  70. package/dist/renderer/shapes/EntityLabel.d.ts +9 -1
  71. package/dist/renderer/shapes/EntityLabel.d.ts.map +1 -1
  72. package/dist/renderer/shapes/EntityShape.d.cts +2 -1
  73. package/dist/renderer/shapes/EntityShape.d.ts +2 -1
  74. package/dist/renderer/shapes/EntityShape.d.ts.map +1 -1
  75. package/dist/renderer/useConnectionPreview.d.cts +12 -1
  76. package/dist/renderer/useConnectionPreview.d.ts +12 -1
  77. package/dist/renderer/useConnectionPreview.d.ts.map +1 -1
  78. package/dist/renderer/usePointerGestures.d.cts +1 -1
  79. package/dist/renderer/usePointerGestures.d.ts +1 -1
  80. package/dist/renderer/usePointerGestures.d.ts.map +1 -1
  81. package/dist/schemas/entityDefinition.schema.d.cts +7 -0
  82. package/dist/schemas/entityDefinition.schema.d.ts +7 -0
  83. package/dist/schemas/entityDefinition.schema.d.ts.map +1 -1
  84. package/dist/schemas/entityDocument.d.cts +34 -13
  85. package/dist/schemas/entityDocument.d.ts +34 -13
  86. package/dist/schemas/entityDocument.d.ts.map +1 -1
  87. package/dist/schemas/entityPresentation.schema.d.cts +3 -0
  88. package/dist/schemas/entityPresentation.schema.d.ts +3 -0
  89. package/dist/schemas/entityPresentation.schema.d.ts.map +1 -1
  90. package/dist/schemas/exercise.schema.d.cts +80 -2
  91. package/dist/schemas/exercise.schema.d.ts +80 -2
  92. package/dist/schemas/exercise.schema.d.ts.map +1 -1
  93. package/dist/schemas/exerciseV2.schema.d.cts +57 -0
  94. package/dist/schemas/exerciseV2.schema.d.ts +57 -0
  95. package/dist/schemas/exerciseV2.schema.d.ts.map +1 -1
  96. package/dist/schemas/exerciseV4.schema.d.cts +168 -0
  97. package/dist/schemas/exerciseV4.schema.d.ts +169 -0
  98. package/dist/schemas/exerciseV4.schema.d.ts.map +1 -0
  99. package/dist/schemas/geometryState.schema.d.cts +8 -0
  100. package/dist/schemas/geometryState.schema.d.ts +8 -0
  101. package/dist/schemas/geometryState.schema.d.ts.map +1 -1
  102. package/dist/schemas/goals/angleBisectorGoal.schema.d.cts +15 -0
  103. package/dist/schemas/goals/angleBisectorGoal.schema.d.ts +16 -0
  104. package/dist/schemas/goals/angleBisectorGoal.schema.d.ts.map +1 -0
  105. package/dist/schemas/goals/angleCriterion.schema.d.cts +28 -0
  106. package/dist/schemas/goals/angleCriterion.schema.d.ts +29 -0
  107. package/dist/schemas/goals/angleCriterion.schema.d.ts.map +1 -0
  108. package/dist/schemas/goals/angleMeasureGoal.schema.d.cts +26 -0
  109. package/dist/schemas/goals/angleMeasureGoal.schema.d.ts +27 -0
  110. package/dist/schemas/goals/angleMeasureGoal.schema.d.ts.map +1 -0
  111. package/dist/schemas/goals/createGoal.schema.d.cts +1 -0
  112. package/dist/schemas/goals/createGoal.schema.d.ts +1 -0
  113. package/dist/schemas/goals/createGoal.schema.d.ts.map +1 -1
  114. package/dist/schemas/goals/geometryGoal.schema.d.cts +61 -0
  115. package/dist/schemas/goals/geometryGoal.schema.d.ts +61 -0
  116. package/dist/schemas/goals/geometryGoal.schema.d.ts.map +1 -1
  117. package/dist/schemas/goals/givenAngleGoal.schema.d.cts +35 -0
  118. package/dist/schemas/goals/givenAngleGoal.schema.d.ts +36 -0
  119. package/dist/schemas/goals/givenAngleGoal.schema.d.ts.map +1 -0
  120. package/dist/schemas/goals/goalKindDeclaration.d.cts +7 -0
  121. package/dist/schemas/goals/goalKindDeclaration.d.ts +7 -0
  122. package/dist/schemas/goals/goalKindDeclaration.d.ts.map +1 -1
  123. package/dist/schemas/goals/parallelGoal.schema.d.cts +1 -0
  124. package/dist/schemas/goals/parallelGoal.schema.d.ts +1 -0
  125. package/dist/schemas/goals/parallelGoal.schema.d.ts.map +1 -1
  126. package/dist/schemas/goals/perpendicularGoal.schema.d.cts +1 -0
  127. package/dist/schemas/goals/perpendicularGoal.schema.d.ts +1 -0
  128. package/dist/schemas/goals/perpendicularGoal.schema.d.ts.map +1 -1
  129. package/dist/schemas/goals/throughPointsGoal.schema.d.cts +1 -0
  130. package/dist/schemas/goals/throughPointsGoal.schema.d.ts +1 -0
  131. package/dist/schemas/goals/throughPointsGoal.schema.d.ts.map +1 -1
  132. package/dist/schemas/objectTypeName.schema.d.cts +1 -0
  133. package/dist/schemas/objectTypeName.schema.d.ts +1 -0
  134. package/dist/schemas/objectTypeName.schema.d.ts.map +1 -1
  135. package/dist/schemas/v2ToV3.d.cts +15 -1
  136. package/dist/schemas/v2ToV3.d.ts +15 -1
  137. package/dist/schemas/v2ToV3.d.ts.map +1 -1
  138. package/dist/schemas/v3ToV4.d.cts +2 -1
  139. package/dist/schemas/v3ToV4.d.ts +2 -1
  140. package/dist/schemas/v3ToV4.d.ts.map +1 -1
  141. package/dist/schemas/v4ToV5.d.cts +7 -0
  142. package/dist/schemas/v4ToV5.d.ts +8 -0
  143. package/dist/schemas/v4ToV5.d.ts.map +1 -0
  144. package/dist/schemas.cjs +1 -1
  145. package/dist/schemas.mjs +3 -3
  146. package/dist/snapping/computeDragSnap.d.cts +30 -0
  147. package/dist/snapping/computeDragSnap.d.ts +31 -0
  148. package/dist/snapping/computeDragSnap.d.ts.map +1 -0
  149. package/dist/state/geometryContext.d.ts.map +1 -1
  150. package/dist/store/attemptChange.d.cts +1 -1
  151. package/dist/store/attemptChange.d.ts +1 -1
  152. package/dist/store/attemptChange.d.ts.map +1 -1
  153. package/dist/store/constructionCommand.d.cts +14 -2
  154. package/dist/store/constructionCommand.d.ts +14 -2
  155. package/dist/store/constructionCommand.d.ts.map +1 -1
  156. package/dist/store/constructionIdentity.d.cts +9 -0
  157. package/dist/store/constructionIdentity.d.ts +9 -0
  158. package/dist/store/constructionIdentity.d.ts.map +1 -1
  159. package/dist/store/studentStore.d.cts +7 -0
  160. package/dist/store/studentStore.d.ts +7 -0
  161. package/dist/store/studentStore.d.ts.map +1 -1
  162. package/dist/store/teacherStore.d.cts +16 -0
  163. package/dist/store/teacherStore.d.ts +16 -0
  164. package/dist/store/teacherStore.d.ts.map +1 -1
  165. package/dist/{studentStore-CEKTtMCh.js → studentStore-CyYa0N93.js} +502 -370
  166. package/dist/studentStore-CyYa0N93.js.map +1 -0
  167. package/dist/studentStore-ubldy4PJ.cjs +2 -0
  168. package/dist/studentStore-ubldy4PJ.cjs.map +1 -0
  169. package/dist/validation/getGeometryExerciseWarnings.d.cts +3 -2
  170. package/dist/validation/getGeometryExerciseWarnings.d.ts +3 -2
  171. package/dist/validation/getGeometryExerciseWarnings.d.ts.map +1 -1
  172. package/dist/validation/goals/angleBisector.d.cts +3 -0
  173. package/dist/validation/goals/angleBisector.d.ts +4 -0
  174. package/dist/validation/goals/angleBisector.d.ts.map +1 -0
  175. package/dist/validation/goals/angleCriterion.d.cts +12 -0
  176. package/dist/validation/goals/angleCriterion.d.ts +13 -0
  177. package/dist/validation/goals/angleCriterion.d.ts.map +1 -0
  178. package/dist/validation/goals/angleMeasure.d.cts +9 -0
  179. package/dist/validation/goals/angleMeasure.d.ts +10 -0
  180. package/dist/validation/goals/angleMeasure.d.ts.map +1 -0
  181. package/dist/validation/goals/create.d.cts +2 -2
  182. package/dist/validation/goals/create.d.ts +2 -2
  183. package/dist/validation/goals/create.d.ts.map +1 -1
  184. package/dist/validation/goals/givenAngle.d.cts +11 -0
  185. package/dist/validation/goals/givenAngle.d.ts +12 -0
  186. package/dist/validation/goals/givenAngle.d.ts.map +1 -0
  187. package/dist/validation/goals/goalKindHandler.d.cts +38 -1
  188. package/dist/validation/goals/goalKindHandler.d.ts +38 -1
  189. package/dist/validation/goals/goalKindHandler.d.ts.map +1 -1
  190. package/dist/validation/goals/index.d.cts +9 -2
  191. package/dist/validation/goals/index.d.ts +9 -2
  192. package/dist/validation/goals/index.d.ts.map +1 -1
  193. package/dist/validation/goals/midpoint.d.cts +2 -2
  194. package/dist/validation/goals/midpoint.d.ts +2 -2
  195. package/dist/validation/goals/midpoint.d.ts.map +1 -1
  196. package/dist/validation/goals/parallel.d.cts +2 -2
  197. package/dist/validation/goals/parallel.d.ts +2 -2
  198. package/dist/validation/goals/parallel.d.ts.map +1 -1
  199. package/dist/validation/goals/perpendicular.d.cts +2 -2
  200. package/dist/validation/goals/perpendicular.d.ts +2 -2
  201. package/dist/validation/goals/perpendicular.d.ts.map +1 -1
  202. package/dist/validation/goals/segmentLength.d.cts +2 -2
  203. package/dist/validation/goals/segmentLength.d.ts +2 -2
  204. package/dist/validation/goals/segmentLength.d.ts.map +1 -1
  205. package/dist/validation/goals/throughPoints.d.cts +2 -2
  206. package/dist/validation/goals/throughPoints.d.ts +2 -2
  207. package/dist/validation/goals/throughPoints.d.ts.map +1 -1
  208. package/dist/validation/types.d.cts +5 -1
  209. package/dist/validation/types.d.ts +5 -1
  210. package/dist/validation/types.d.ts.map +1 -1
  211. package/dist/validation/validateGeometryState.d.ts.map +1 -1
  212. package/dist/validation/validateGeometryStateInput.d.ts.map +1 -1
  213. package/dist/validation.cjs +1 -1
  214. package/dist/validation.cjs.map +1 -1
  215. package/dist/validation.mjs +106 -85
  216. package/dist/validation.mjs.map +1 -1
  217. package/package.json +1 -1
  218. package/dist/exercise.schema-BV2b7rtN.cjs +0 -2
  219. package/dist/exercise.schema-BV2b7rtN.cjs.map +0 -1
  220. package/dist/exercise.schema-ckVBW9XA.js +0 -212
  221. package/dist/exercise.schema-ckVBW9XA.js.map +0 -1
  222. package/dist/geometryAttemptStatus-5L_q6-9l.js.map +0 -1
  223. package/dist/geometryAttemptStatus-N_XHIkiS.cjs +0 -2
  224. package/dist/geometryAttemptStatus-N_XHIkiS.cjs.map +0 -1
  225. package/dist/geometryState.schema-D0ixyGYE.js +0 -955
  226. package/dist/geometryState.schema-D0ixyGYE.js.map +0 -1
  227. package/dist/geometryState.schema-DKQ-durx.cjs +0 -2
  228. package/dist/geometryState.schema-DKQ-durx.cjs.map +0 -1
  229. package/dist/goals-C3ZwbEh_.js +0 -451
  230. package/dist/goals-C3ZwbEh_.js.map +0 -1
  231. package/dist/goals-CxR5qPDC.cjs +0 -2
  232. package/dist/goals-CxR5qPDC.cjs.map +0 -1
  233. package/dist/react-BEcdx1f6.js.map +0 -1
  234. package/dist/react-Pp_VSk--.cjs +0 -2
  235. package/dist/react-Pp_VSk--.cjs.map +0 -1
  236. package/dist/studentStore-CEKTtMCh.js.map +0 -1
  237. package/dist/studentStore-DLftfFAl.cjs +0 -2
  238. package/dist/studentStore-DLftfFAl.cjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exerciseLoad-Db9Cigxi.cjs","names":[],"sources":["../src/validation/validateGeometryStateInput.ts","../src/validation/deriveGeometryCanvasFeedback.ts","../src/validation/gradingFingerprints.ts","../src/validation/geometryAttemptStatus.ts","../src/validation/getGeometryExerciseWarnings.ts","../src/validation/exerciseLoad.ts"],"sourcesContent":["import type { EntityId } from '../geometry/entityModel'\nimport { entryOf } from '../geometry/entityRecord'\nimport {\n classifyGivenRedeclaration,\n crossDocumentIdCollisionIssues,\n crossDocumentParentIssues,\n} from '../schemas/entityDocument'\nimport type { GeometryExercise } from '../schemas/exercise.schema'\nimport {\n geometryStateSchema,\n migrateGeometryState,\n type GeometryState,\n} from '../schemas/geometryState.schema'\nimport type { GeometryExerciseValidationIssue } from './validateGeometryExercise'\n\n/**\n * The load pass ADR 0008 § Amendment specifies: a v2 attempt carries **only\n * student work**, so a re-declaration of a given the student has no authority\n * over is removed on the way in, and no given at all survives in\n * `answerEntityIds`.\n *\n * Neither drop is an error, because both are what every migrated v1 attempt\n * looks like — v1 seeded the givens into the attempt, required them to be\n * there, and its reveal promoted a given point into `constructedPointIds`,\n * which the migration copies verbatim into `answerEntityIds`. What is dropped\n * is the `ignored` class of `classifyGivenRedeclaration()` (ADR 0016): a\n * pinned given, or a listed intersection no drag could have moved. An\n * `override` is kept, since it is how the student moves a draggable given, and\n * a `collision` is kept to be refused below rather than tidied away here.\n *\n * **Presentation is the exercise's for every id it declares** (ADR 0016), so\n * the attempt's entry for such an id goes, the override's included. Silently,\n * for the same migrated-v1 reason; and before this an attempt could relabel,\n * or hide, a given it had only moved.\n *\n * **`answerEntityIds` loses every id the exercise declares**, not only the\n * dropped ones: a given is not an answer to anything, so a draggable given the\n * student moved is still not something they built. Dropping rather than\n * refusing is decided by what the loader can see — a hand-written document\n * naming a given there is indistinguishable from a migrated v1 one, and\n * refusing would make every stored v1 attempt with a revealed given\n * unloadable. The entry says nothing true either way, so it is removed in\n * silence, exactly as the ignored re-declaration is.\n */\nfunction normalizedAgainstExercise(\n geometry: GeometryState,\n exercise: GeometryExercise,\n): GeometryState {\n const declaredByExercise = (id: EntityId) =>\n Object.prototype.hasOwnProperty.call(exercise.entities, id)\n const dropped = new Set<EntityId>()\n for (const [id, definition] of Object.entries(geometry.entities)) {\n const given = entryOf(exercise.entities, id)\n if (!given) continue\n if (classifyGivenRedeclaration(exercise, id, given, definition) === 'ignored') dropped.add(id)\n }\n const answerEntityIds = geometry.answerEntityIds.filter((id) => !declaredByExercise(id))\n const presentationIds = Object.keys(geometry.presentation)\n if (\n dropped.size === 0 &&\n answerEntityIds.length === geometry.answerEntityIds.length &&\n !presentationIds.some(declaredByExercise)\n ) {\n return geometry\n }\n\n return {\n ...geometry,\n entities: Object.fromEntries(\n Object.entries(geometry.entities).filter(([id]) => !dropped.has(id)),\n ),\n presentation: Object.fromEntries(\n Object.entries(geometry.presentation).filter(([id]) => !declaredByExercise(id)),\n ),\n answerEntityIds,\n }\n}\n\nexport type GeometryStateInputValidationResult =\n | { success: true; data: GeometryState; errors: [] }\n | { success: false; data: null; errors: GeometryExerciseValidationIssue[] }\n\nexport function validateGeometryStateInput(\n input: unknown,\n exercise?: GeometryExercise,\n): GeometryStateInputValidationResult {\n // Stored attempts are brought up to the current version before their\n // structure is checked, so an older saved attempt loads rather than failing\n // as malformed.\n const migrated = migrateGeometryState(input)\n if (!migrated.success) {\n return {\n success: false,\n data: null,\n errors: [{ path: ['schemaVersion'], message: migrated.message }],\n }\n }\n\n const result = geometryStateSchema.safeParse(migrated.data)\n if (!result.success) {\n return {\n success: false,\n data: null,\n errors: result.error.issues.map((issue) => ({\n path: issue.path.map((part) => (typeof part === 'symbol' ? part.toString() : part)),\n message: issue.message,\n })),\n }\n }\n\n const errors: GeometryExerciseValidationIssue[] = []\n let data = result.data\n if (exercise) {\n data = normalizedAgainstExercise(data, exercise)\n\n // An id both documents declare that is neither ignored nor an override\n // (ADR 0016). `buildGeometryContext()` reports these as `duplicate-id` and\n // then resolves the id to the exercise's declaration, so before M2.G.2 an\n // attempt declaring segment `A` over given point `A` loaded clean and had\n // the *given* graded as the student's construction. One rule, shared with\n // the context, and the document is refused rather than reinterpreted.\n for (const issue of crossDocumentIdCollisionIssues(data, exercise)) {\n errors.push(issue)\n }\n\n // The parents the attempt schema deliberately let through: since the\n // amendment a student's point sits on a given segment, so most of an\n // attempt's parents are declared in the other document. This is where the\n // pair is completed, in the schema's own words.\n for (const issue of crossDocumentParentIssues(data, exercise.entities)) {\n errors.push(issue)\n }\n\n // What the student was allowed to draw. Points are not checked: placing one\n // is how every construction starts, and v1 never asked either.\n for (const [id, definition] of Object.entries(data.entities)) {\n if (definition.kind === 'point') continue\n if (!exercise.allowedObjects.includes(definition.kind)) {\n errors.push({\n path: ['entities', id],\n message: `${definition.kind} is not allowed by this exercise`,\n })\n }\n }\n }\n\n return errors.length > 0\n ? { success: false, data: null, errors }\n : { success: true, data, errors: [] }\n}\n\n/**\n * Restores an attempt from stored JSON — the counterpart to\n * validateGeometryExerciseJson(). Pass the exercise to also check the attempt\n * against it — only allowed object types, parents naming entities that exist in\n * one of the two documents — and to normalize it, which is what drops the given\n * points a migrated v1 attempt still carries.\n */\nexport function validateGeometryStateJson(\n json: string,\n exercise?: GeometryExercise,\n): GeometryStateInputValidationResult {\n try {\n return validateGeometryStateInput(JSON.parse(json), exercise)\n } catch (error) {\n return {\n success: false,\n data: null,\n errors: [\n {\n path: [],\n message: error instanceof Error ? error.message : 'Invalid JSON syntax',\n },\n ],\n }\n }\n}\n\n// Saves an attempt. Trivial today, but it is the one place that guarantees the\n// stored document carries its version, so a restore never has to guess.\nexport function serializeGeometryState(geometry: GeometryState): string {\n return JSON.stringify(geometry)\n}\n","import type { GeometryState } from '../schemas/geometryState.schema'\nimport { parentsOfDefinition } from '../geometry/entities'\nimport { entryOf } from '../geometry/entityRecord'\nimport type { GoalStatus, GeometryValidationFeedback } from './types'\n\nexport interface GeometryCanvasFeedback {\n constructions: Record<string, GoalStatus>\n points: Record<string, GoalStatus>\n}\n\nexport function deriveGeometryCanvasFeedback(\n feedback: GeometryValidationFeedback | null,\n geometry: GeometryState,\n focusedGoalId: string | null,\n): GeometryCanvasFeedback {\n const focused = feedback?.goals.find((goal) => goal.id === focusedGoalId)\n if (!focused) return { constructions: {}, points: {} }\n\n const constructions = Object.fromEntries(\n focused.constructionIds.map((id) => [id, focused.status]),\n ) as Record<string, GoalStatus>\n const pointIds = new Set(focused.pointIds)\n for (const [id, definition] of Object.entries(geometry.entities)) {\n if (definition.kind === 'point') continue\n if (!entryOf(constructions, id)) continue\n for (const pointId of parentsOfDefinition(definition)) pointIds.add(pointId)\n }\n const points = Object.fromEntries([...pointIds].map((id) => [id, focused.status])) as Record<\n string,\n GoalStatus\n >\n return { constructions, points }\n}\n","import type { GeometryExercise } from '../schemas/exercise.schema'\nimport { withoutSolution } from '../schemas/exerciseProjection'\nimport type { GeometryState } from '../schemas/geometryState.schema'\n\n// Feedback freshness (RFC 0005 § Decisions 2 and 5, ADR 0014). A check result\n// describes the normalized documents it graded, not the objects a host passed\n// in, so a clone, a JSON round trip or a reordered record fingerprints the same,\n// while any change to what grading reads does not.\n//\n// The value is opaque: only another fingerprint from this engine is ever\n// compared against it. That keeps the format free to change — a result from an\n// older format simply stops matching, which hides feedback instead of showing\n// stale feedback.\n\n// JSON with object keys sorted at every depth. Array order is kept because it\n// is meaningful (answer order is report order); records are the documents'\n// only unordered collections. An `undefined` member is skipped as\n// JSON.stringify skips it, so an absent optional field and an explicit\n// `undefined` agree.\nfunction canonicalJson(value: unknown): string {\n if (Array.isArray(value)) {\n return `[${value.map((item) => (item === undefined ? 'null' : canonicalJson(item))).join(',')}]`\n }\n if (typeof value === 'object' && value !== null) {\n const record = value as Record<string, unknown>\n const members = Object.keys(record)\n .sort()\n .filter((key) => record[key] !== undefined)\n .map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`)\n return `{${members.join(',')}}`\n }\n return JSON.stringify(value)\n}\n\n// cyrb53's two 32-bit lanes, both printed: 16 hex digits. Not cryptographic —\n// a fingerprint detects that grading inputs changed; a host forging a match\n// could only ever redisplay feedback it already holds.\nfunction hash(text: string): string {\n let h1 = 0xdeadbeef\n let h2 = 0x41c6ce57\n for (let index = 0; index < text.length; index++) {\n const code = text.charCodeAt(index)\n h1 = Math.imul(h1 ^ code, 2654435761)\n h2 = Math.imul(h2 ^ code, 1597334677)\n }\n h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507)\n h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909)\n h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507)\n h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909)\n return `${(h2 >>> 0).toString(16).padStart(8, '0')}${(h1 >>> 0).toString(16).padStart(8, '0')}`\n}\n\n// The solution-free grading projection. Checking ignores the authored solution\n// ADR 0015 embeds, so changing only that must not hide feedback.\nexport function fingerprintGeometryExercise(exercise: GeometryExercise): string {\n return hash(canonicalJson(withoutSolution(exercise)))\n}\n\n// Takes the attempt as grading read it: migrated, parsed and normalized against\n// its exercise, so a dropped pinned given or an older version changes nothing.\nexport function fingerprintGeometryAttempt(attempt: GeometryState): string {\n return hash(canonicalJson(attempt))\n}\n","import { latestGeometryStateVersion, type GeometryState } from '../schemas/geometryState.schema'\nimport { validateGeometryStateInput } from './validateGeometryStateInput'\n\n/**\n * A fresh attempt at the current attempt version, for a host starting a student\n * on an exercise (RFC 0005 § Decision 5). It takes no exercise: the two\n * documents version independently, and an empty attempt holds nothing of the\n * exercise. Every call returns a new object, so a host may keep or change one\n * without touching another.\n */\nexport function createEmptyGeometryAttempt(): GeometryState {\n return {\n schemaVersion: latestGeometryStateVersion,\n entities: {},\n presentation: {},\n answerEntityIds: [],\n }\n}\n\n/**\n * Whether a stored attempt holds any student work — a completion hint for a\n * host's \"not started\" state, never a grade. Untrusted input is migrated and\n * parsed first; absent or malformed input is `false` rather than an error.\n *\n * Work is any entity the attempt declares, not only an answer: dragging a given\n * copies it into `entities` while `answerEntityIds` stays empty, and that\n * counts. Reset, or deleting everything the student added, empties `entities`\n * again.\n *\n * No exercise is read, so a pinned given that a migrated v1 attempt still\n * re-declares counts here, although the load pass against its exercise drops it.\n */\nexport function hasGeometryWork(attemptInput: unknown): boolean {\n const attempt = validateGeometryStateInput(attemptInput)\n return attempt.success && Object.keys(attempt.data.entities).length > 0\n}\n","import type { GeometryExercise } from '../schemas/exercise.schema'\nimport { buildGeometryContext } from '../state/geometryContext'\nimport { createEmptyGeometryAttempt } from './geometryAttemptStatus'\nimport { figureGoalHandlerFor } from './goals'\nimport type { FigureValidationContext } from './goals/goalKindHandler'\n\nexport interface GeometryExerciseWarning {\n path: Array<string | number>\n message: string\n}\n\n/**\n * What is wrong with an exercise without being invalid. Two rules: a given\n * placed where the viewport cannot show it, and a figure goal the authored\n * figure already satisfies.\n *\n * Only a **free** point is checked. A derived point has no stored coordinates —\n * it is wherever the graph puts it, which depends on the entities it is built\n * on — so warning about it would mean resolving the whole exercise, and where\n * an authoring warning belongs for a construction that lands off-screen is an\n * M3 question about the authoring UI rather than about the document.\n */\nexport function getGeometryExerciseWarnings(exercise: GeometryExercise): GeometryExerciseWarning[] {\n const [xMin, yMin, xMax, yMax] = exercise.viewport\n const warnings: GeometryExerciseWarning[] = []\n for (const [id, definition] of Object.entries(exercise.entities)) {\n if (definition.kind !== 'point' || !('at' in definition)) continue\n const { x, y } = definition.at\n if (x >= xMin && x <= xMax && y >= yMin && y <= yMax) continue\n warnings.push({ path: ['entities', id], message: `point ${id} is outside the viewport` })\n }\n warnings.push(...satisfiedFigureGoalWarnings(exercise))\n return warnings\n}\n\n/**\n * A figure goal the authored figure already satisfies: an untouched attempt\n * checks correct, which is almost always a mistake (RFC 0006 § Reporting).\n *\n * A warning rather than a refusal, because deciding it needs measurement the\n * schema layer does not have, and because an author may want exactly this — a\n * \"keep it acute while you move the vertex\" exercise.\n *\n * The figure measured is the exercise's own, so the scene is built over an\n * empty attempt: this is the authored document before the student touches it.\n */\nfunction satisfiedFigureGoalWarnings(exercise: GeometryExercise): GeometryExerciseWarning[] {\n // Nothing to measure, and nothing to build, unless a figure goal is authored.\n if (!exercise.goals.some((goal) => figureGoalHandlerFor(goal) !== null)) return []\n\n const scene = buildGeometryContext(exercise, createEmptyGeometryAttempt())\n const context: FigureValidationContext = { exercise, points: scene.points, scene }\n const warnings: GeometryExerciseWarning[] = []\n exercise.goals.forEach((goal, index) => {\n const handler = figureGoalHandlerFor(goal)\n if (handler?.satisfiedByFigure(goal, context) !== true) return\n warnings.push({\n path: ['goals', index],\n message: `goal ${goal.id} is already satisfied by the authored figure`,\n })\n })\n return warnings\n}\n","// The one exercise load every public loader is built on (M3.I.3, option C).\n// Internal: not exported from any entry point.\n//\n// Exercise v4 embeds an authored solution (ADR 0015), and the engine loads an\n// exercise two ways. **Grading** — Check, fingerprints, the student component —\n// must ignore the solution entirely, so a malformed one never disables them.\n// **The full load** — `validateGeometryExercise()` and reveal — must also\n// migrate the solution and check it against the exercise. Both start from\n// `loadExerciseDocument()`, so the exercise half cannot differ between them, and the\n// full load adds `validateEmbeddedSolution()` on top.\nimport { withoutSolution } from '../schemas/exerciseProjection'\nimport {\n geometryExerciseSchema,\n migrateGeometryExercise,\n type GeometryExercise,\n} from '../schemas/exercise.schema'\nimport {\n getGeometryExerciseWarnings,\n type GeometryExerciseWarning,\n} from './getGeometryExerciseWarnings'\nimport type { GeometryExerciseValidationIssue } from './validateGeometryExercise'\nimport {\n validateGeometryStateInput,\n type GeometryStateInputValidationResult,\n} from './validateGeometryStateInput'\n\nexport type ExerciseLoadResult =\n | {\n success: true\n /** The solution-free projection, parsed. */\n data: GeometryExercise\n /** The stored solution, untouched: `undefined` when the document has none. */\n solution: unknown\n warnings: GeometryExerciseWarning[]\n }\n | { success: false; errors: GeometryExerciseValidationIssue[] }\n\nfunction normalizePath(path: PropertyKey[]): Array<string | number> {\n return path.map((segment) =>\n typeof segment === 'symbol' ? (segment.description ?? segment.toString()) : segment,\n )\n}\n\n/**\n * Migrates a stored exercise and parses its solution-free projection. Every\n * refusal the migration stage can produce — an older version with no step, a\n * newer one, something that is not a document at all — is reported under\n * `schemaVersion`, because that is the field the reader consulted.\n */\nexport function loadExerciseDocument(input: unknown): ExerciseLoadResult {\n const migrated = migrateGeometryExercise(input)\n if (!migrated.success) {\n return { success: false, errors: [{ path: ['schemaVersion'], message: migrated.message }] }\n }\n\n const document = migrated.data as Record<string, unknown>\n const result = geometryExerciseSchema.safeParse(withoutSolution(document))\n if (!result.success) {\n return {\n success: false,\n errors: result.error.issues.map((issue) => ({\n path: normalizePath(issue.path),\n message: issue.message,\n })),\n }\n }\n return {\n success: true,\n data: result.data,\n solution: document.solution,\n warnings: getGeometryExerciseWarnings(result.data),\n }\n}\n\n/**\n * The stored solution as an attempt of `exercise`: migrated on the attempt\n * chain, parsed, and normalized and cross-checked against the exercise exactly\n * as a student attempt is. Issue paths are rooted at `solution`, so they point\n * into the exercise document a host holds.\n */\nexport function validateEmbeddedSolution(\n solution: unknown,\n exercise: GeometryExercise,\n): GeometryStateInputValidationResult {\n const attempt = validateGeometryStateInput(solution, exercise)\n if (attempt.success) return attempt\n return {\n ...attempt,\n errors: attempt.errors.map((issue) => ({ ...issue, path: ['solution', ...issue.path] })),\n }\n}\n"],"mappings":"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"}
@@ -1,33 +1,32 @@
1
- import { F as e, O as t, S as n, i as r, n as i, u as a, x as o } from "./geometryState.schema-D0ixyGYE.js";
2
- import { i as s, o as c } from "./exercise.schema-ckVBW9XA.js";
1
+ import { E as e, M as t, T as n, i as r, n as i, u as a, w as o, z as s } from "./geometryState.schema-CKFPlX8j.js";
2
+ import { f as c, t as l } from "./goals-BlSeniyt.js";
3
+ import { i as u, o as d } from "./exercise.schema-Bma3qN9P.js";
3
4
  //#region src/validation/validateGeometryStateInput.ts
4
- function l(e, n) {
5
- let r = /* @__PURE__ */ new Set();
6
- for (let [i, a] of Object.entries(e.entities)) {
7
- let e = t(n.entities, i);
8
- !e || e.kind !== "point" || a.kind !== "point" || n.draggableEntityIds.includes(i) || r.add(i);
5
+ function f(e, n) {
6
+ let r = (e) => Object.prototype.hasOwnProperty.call(n.entities, e), i = /* @__PURE__ */ new Set();
7
+ for (let [r, a] of Object.entries(e.entities)) {
8
+ let e = t(n.entities, r);
9
+ e && o(n, r, e, a) === "ignored" && i.add(r);
9
10
  }
10
- let i = e.answerEntityIds.filter((e) => !Object.prototype.hasOwnProperty.call(n.entities, e));
11
- if (r.size === 0 && i.length === e.answerEntityIds.length) return e;
12
- let a = (e) => Object.fromEntries(Object.entries(e).filter(([e]) => !r.has(e)));
13
- return {
11
+ let a = e.answerEntityIds.filter((e) => !r(e)), s = Object.keys(e.presentation);
12
+ return i.size === 0 && a.length === e.answerEntityIds.length && !s.some(r) ? e : {
14
13
  ...e,
15
- entities: a(e.entities),
16
- presentation: a(e.presentation),
17
- answerEntityIds: i
14
+ entities: Object.fromEntries(Object.entries(e.entities).filter(([e]) => !i.has(e))),
15
+ presentation: Object.fromEntries(Object.entries(e.presentation).filter(([e]) => !r(e))),
16
+ answerEntityIds: a
18
17
  };
19
18
  }
20
- function u(e, t) {
21
- let a = r(e);
22
- if (!a.success) return {
19
+ function p(t, a) {
20
+ let o = r(t);
21
+ if (!o.success) return {
23
22
  success: !1,
24
23
  data: null,
25
24
  errors: [{
26
25
  path: ["schemaVersion"],
27
- message: a.message
26
+ message: o.message
28
27
  }]
29
28
  };
30
- let s = i.safeParse(a.data);
29
+ let s = i.safeParse(o.data);
31
30
  if (!s.success) return {
32
31
  success: !1,
33
32
  data: null,
@@ -36,14 +35,14 @@ function u(e, t) {
36
35
  message: e.message
37
36
  }))
38
37
  };
39
- let c = [], u = s.data;
40
- if (t) {
41
- u = l(u, t);
42
- for (let e of o(u, t.entities)) c.push(e);
43
- for (let e of n(u, t.entities)) c.push(e);
44
- for (let [e, n] of Object.entries(u.entities)) n.kind !== "point" && (t.allowedObjects.includes(n.kind) || c.push({
38
+ let c = [], l = s.data;
39
+ if (a) {
40
+ l = f(l, a);
41
+ for (let e of n(l, a)) c.push(e);
42
+ for (let t of e(l, a.entities)) c.push(t);
43
+ for (let [e, t] of Object.entries(l.entities)) t.kind !== "point" && (a.allowedObjects.includes(t.kind) || c.push({
45
44
  path: ["entities", e],
46
- message: `${n.kind} is not allowed by this exercise`
45
+ message: `${t.kind} is not allowed by this exercise`
47
46
  }));
48
47
  }
49
48
  return c.length > 0 ? {
@@ -52,13 +51,13 @@ function u(e, t) {
52
51
  errors: c
53
52
  } : {
54
53
  success: !0,
55
- data: u,
54
+ data: l,
56
55
  errors: []
57
56
  };
58
57
  }
59
- function d(e, t) {
58
+ function m(e, t) {
60
59
  try {
61
- return u(JSON.parse(e), t);
60
+ return p(JSON.parse(e), t);
62
61
  } catch (e) {
63
62
  return {
64
63
  success: !1,
@@ -70,35 +69,35 @@ function d(e, t) {
70
69
  };
71
70
  }
72
71
  }
73
- function f(e) {
72
+ function h(e) {
74
73
  return JSON.stringify(e);
75
74
  }
76
75
  //#endregion
77
76
  //#region src/validation/deriveGeometryCanvasFeedback.ts
78
- function p(n, r, i) {
79
- let a = n?.goals.find((e) => e.id === i);
80
- if (!a) return {
77
+ function g(e, n, r) {
78
+ let i = e?.goals.find((e) => e.id === r);
79
+ if (!i) return {
81
80
  constructions: {},
82
81
  points: {}
83
82
  };
84
- let o = Object.fromEntries(a.constructionIds.map((e) => [e, a.status])), s = new Set(a.pointIds);
85
- for (let [n, i] of Object.entries(r.entities)) if (i.kind !== "point" && t(o, n)) for (let t of e(i)) s.add(t);
83
+ let a = Object.fromEntries(i.constructionIds.map((e) => [e, i.status])), o = new Set(i.pointIds);
84
+ for (let [e, r] of Object.entries(n.entities)) if (r.kind !== "point" && t(a, e)) for (let e of s(r)) o.add(e);
86
85
  return {
87
- constructions: o,
88
- points: Object.fromEntries([...s].map((e) => [e, a.status]))
86
+ constructions: a,
87
+ points: Object.fromEntries([...o].map((e) => [e, i.status]))
89
88
  };
90
89
  }
91
90
  //#endregion
92
91
  //#region src/validation/gradingFingerprints.ts
93
- function m(e) {
94
- if (Array.isArray(e)) return `[${e.map((e) => e === void 0 ? "null" : m(e)).join(",")}]`;
92
+ function _(e) {
93
+ if (Array.isArray(e)) return `[${e.map((e) => e === void 0 ? "null" : _(e)).join(",")}]`;
95
94
  if (typeof e == "object" && e) {
96
95
  let t = e;
97
- return `{${Object.keys(t).sort().filter((e) => t[e] !== void 0).map((e) => `${JSON.stringify(e)}:${m(t[e])}`).join(",")}}`;
96
+ return `{${Object.keys(t).sort().filter((e) => t[e] !== void 0).map((e) => `${JSON.stringify(e)}:${_(t[e])}`).join(",")}}`;
98
97
  }
99
98
  return JSON.stringify(e);
100
99
  }
101
- function h(e) {
100
+ function v(e) {
102
101
  let t = 3735928559, n = 1103547991;
103
102
  for (let r = 0; r < e.length; r++) {
104
103
  let i = e.charCodeAt(r);
@@ -106,15 +105,29 @@ function h(e) {
106
105
  }
107
106
  return t = Math.imul(t ^ t >>> 16, 2246822507), t ^= Math.imul(n ^ n >>> 13, 3266489909), n = Math.imul(n ^ n >>> 16, 2246822507), n ^= Math.imul(t ^ t >>> 13, 3266489909), `${(n >>> 0).toString(16).padStart(8, "0")}${(t >>> 0).toString(16).padStart(8, "0")}`;
108
107
  }
109
- function g(e) {
110
- return h(m(a(e)));
108
+ function y(e) {
109
+ return v(_(a(e)));
111
110
  }
112
- function _(e) {
113
- return h(m(e));
111
+ function b(e) {
112
+ return v(_(e));
113
+ }
114
+ //#endregion
115
+ //#region src/validation/geometryAttemptStatus.ts
116
+ function x() {
117
+ return {
118
+ schemaVersion: 2,
119
+ entities: {},
120
+ presentation: {},
121
+ answerEntityIds: []
122
+ };
123
+ }
124
+ function S(e) {
125
+ let t = p(e);
126
+ return t.success && Object.keys(t.data.entities).length > 0;
114
127
  }
115
128
  //#endregion
116
129
  //#region src/validation/getGeometryExerciseWarnings.ts
117
- function v(e) {
130
+ function C(e) {
118
131
  let [t, n, r, i] = e.viewport, a = [];
119
132
  for (let [o, s] of Object.entries(e.entities)) {
120
133
  if (s.kind !== "point" || !("at" in s)) continue;
@@ -124,15 +137,29 @@ function v(e) {
124
137
  message: `point ${o} is outside the viewport`
125
138
  });
126
139
  }
127
- return a;
140
+ return a.push(...w(e)), a;
141
+ }
142
+ function w(e) {
143
+ if (!e.goals.some((e) => l(e) !== null)) return [];
144
+ let t = c(e, x()), n = {
145
+ exercise: e,
146
+ points: t.points,
147
+ scene: t
148
+ }, r = [];
149
+ return e.goals.forEach((e, t) => {
150
+ l(e)?.satisfiedByFigure(e, n) === !0 && r.push({
151
+ path: ["goals", t],
152
+ message: `goal ${e.id} is already satisfied by the authored figure`
153
+ });
154
+ }), r;
128
155
  }
129
156
  //#endregion
130
157
  //#region src/validation/exerciseLoad.ts
131
- function y(e) {
158
+ function T(e) {
132
159
  return e.map((e) => typeof e == "symbol" ? e.description ?? e.toString() : e);
133
160
  }
134
- function b(e) {
135
- let t = c(e);
161
+ function E(e) {
162
+ let t = d(e);
136
163
  if (!t.success) return {
137
164
  success: !1,
138
165
  errors: [{
@@ -140,22 +167,22 @@ function b(e) {
140
167
  message: t.message
141
168
  }]
142
169
  };
143
- let n = t.data, r = s.safeParse(a(n));
170
+ let n = t.data, r = u.safeParse(a(n));
144
171
  return r.success ? {
145
172
  success: !0,
146
173
  data: r.data,
147
174
  solution: n.solution,
148
- warnings: v(r.data)
175
+ warnings: C(r.data)
149
176
  } : {
150
177
  success: !1,
151
178
  errors: r.error.issues.map((e) => ({
152
- path: y(e.path),
179
+ path: T(e.path),
153
180
  message: e.message
154
181
  }))
155
182
  };
156
183
  }
157
- function x(e, t) {
158
- let n = u(e, t);
184
+ function D(e, t) {
185
+ let n = p(e, t);
159
186
  return n.success ? n : {
160
187
  ...n,
161
188
  errors: n.errors.map((e) => ({
@@ -165,20 +192,6 @@ function x(e, t) {
165
192
  };
166
193
  }
167
194
  //#endregion
168
- //#region src/validation/geometryAttemptStatus.ts
169
- function S() {
170
- return {
171
- schemaVersion: 2,
172
- entities: {},
173
- presentation: {},
174
- answerEntityIds: []
175
- };
176
- }
177
- function C(e) {
178
- let t = u(e);
179
- return t.success && Object.keys(t.data.entities).length > 0;
180
- }
181
- //#endregion
182
- export { v as a, p as c, d, x as i, f as l, C as n, _ as o, b as r, g as s, S as t, u };
195
+ export { S as a, g as c, m as d, x as i, h as l, D as n, b as o, C as r, y as s, E as t, p as u };
183
196
 
184
- //# sourceMappingURL=geometryAttemptStatus-5L_q6-9l.js.map
197
+ //# sourceMappingURL=exerciseLoad-DgeDXYbf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exerciseLoad-DgeDXYbf.js","names":[],"sources":["../src/validation/validateGeometryStateInput.ts","../src/validation/deriveGeometryCanvasFeedback.ts","../src/validation/gradingFingerprints.ts","../src/validation/geometryAttemptStatus.ts","../src/validation/getGeometryExerciseWarnings.ts","../src/validation/exerciseLoad.ts"],"sourcesContent":["import type { EntityId } from '../geometry/entityModel'\nimport { entryOf } from '../geometry/entityRecord'\nimport {\n classifyGivenRedeclaration,\n crossDocumentIdCollisionIssues,\n crossDocumentParentIssues,\n} from '../schemas/entityDocument'\nimport type { GeometryExercise } from '../schemas/exercise.schema'\nimport {\n geometryStateSchema,\n migrateGeometryState,\n type GeometryState,\n} from '../schemas/geometryState.schema'\nimport type { GeometryExerciseValidationIssue } from './validateGeometryExercise'\n\n/**\n * The load pass ADR 0008 § Amendment specifies: a v2 attempt carries **only\n * student work**, so a re-declaration of a given the student has no authority\n * over is removed on the way in, and no given at all survives in\n * `answerEntityIds`.\n *\n * Neither drop is an error, because both are what every migrated v1 attempt\n * looks like — v1 seeded the givens into the attempt, required them to be\n * there, and its reveal promoted a given point into `constructedPointIds`,\n * which the migration copies verbatim into `answerEntityIds`. What is dropped\n * is the `ignored` class of `classifyGivenRedeclaration()` (ADR 0016): a\n * pinned given, or a listed intersection no drag could have moved. An\n * `override` is kept, since it is how the student moves a draggable given, and\n * a `collision` is kept to be refused below rather than tidied away here.\n *\n * **Presentation is the exercise's for every id it declares** (ADR 0016), so\n * the attempt's entry for such an id goes, the override's included. Silently,\n * for the same migrated-v1 reason; and before this an attempt could relabel,\n * or hide, a given it had only moved.\n *\n * **`answerEntityIds` loses every id the exercise declares**, not only the\n * dropped ones: a given is not an answer to anything, so a draggable given the\n * student moved is still not something they built. Dropping rather than\n * refusing is decided by what the loader can see — a hand-written document\n * naming a given there is indistinguishable from a migrated v1 one, and\n * refusing would make every stored v1 attempt with a revealed given\n * unloadable. The entry says nothing true either way, so it is removed in\n * silence, exactly as the ignored re-declaration is.\n */\nfunction normalizedAgainstExercise(\n geometry: GeometryState,\n exercise: GeometryExercise,\n): GeometryState {\n const declaredByExercise = (id: EntityId) =>\n Object.prototype.hasOwnProperty.call(exercise.entities, id)\n const dropped = new Set<EntityId>()\n for (const [id, definition] of Object.entries(geometry.entities)) {\n const given = entryOf(exercise.entities, id)\n if (!given) continue\n if (classifyGivenRedeclaration(exercise, id, given, definition) === 'ignored') dropped.add(id)\n }\n const answerEntityIds = geometry.answerEntityIds.filter((id) => !declaredByExercise(id))\n const presentationIds = Object.keys(geometry.presentation)\n if (\n dropped.size === 0 &&\n answerEntityIds.length === geometry.answerEntityIds.length &&\n !presentationIds.some(declaredByExercise)\n ) {\n return geometry\n }\n\n return {\n ...geometry,\n entities: Object.fromEntries(\n Object.entries(geometry.entities).filter(([id]) => !dropped.has(id)),\n ),\n presentation: Object.fromEntries(\n Object.entries(geometry.presentation).filter(([id]) => !declaredByExercise(id)),\n ),\n answerEntityIds,\n }\n}\n\nexport type GeometryStateInputValidationResult =\n | { success: true; data: GeometryState; errors: [] }\n | { success: false; data: null; errors: GeometryExerciseValidationIssue[] }\n\nexport function validateGeometryStateInput(\n input: unknown,\n exercise?: GeometryExercise,\n): GeometryStateInputValidationResult {\n // Stored attempts are brought up to the current version before their\n // structure is checked, so an older saved attempt loads rather than failing\n // as malformed.\n const migrated = migrateGeometryState(input)\n if (!migrated.success) {\n return {\n success: false,\n data: null,\n errors: [{ path: ['schemaVersion'], message: migrated.message }],\n }\n }\n\n const result = geometryStateSchema.safeParse(migrated.data)\n if (!result.success) {\n return {\n success: false,\n data: null,\n errors: result.error.issues.map((issue) => ({\n path: issue.path.map((part) => (typeof part === 'symbol' ? part.toString() : part)),\n message: issue.message,\n })),\n }\n }\n\n const errors: GeometryExerciseValidationIssue[] = []\n let data = result.data\n if (exercise) {\n data = normalizedAgainstExercise(data, exercise)\n\n // An id both documents declare that is neither ignored nor an override\n // (ADR 0016). `buildGeometryContext()` reports these as `duplicate-id` and\n // then resolves the id to the exercise's declaration, so before M2.G.2 an\n // attempt declaring segment `A` over given point `A` loaded clean and had\n // the *given* graded as the student's construction. One rule, shared with\n // the context, and the document is refused rather than reinterpreted.\n for (const issue of crossDocumentIdCollisionIssues(data, exercise)) {\n errors.push(issue)\n }\n\n // The parents the attempt schema deliberately let through: since the\n // amendment a student's point sits on a given segment, so most of an\n // attempt's parents are declared in the other document. This is where the\n // pair is completed, in the schema's own words.\n for (const issue of crossDocumentParentIssues(data, exercise.entities)) {\n errors.push(issue)\n }\n\n // What the student was allowed to draw. Points are not checked: placing one\n // is how every construction starts, and v1 never asked either.\n for (const [id, definition] of Object.entries(data.entities)) {\n if (definition.kind === 'point') continue\n if (!exercise.allowedObjects.includes(definition.kind)) {\n errors.push({\n path: ['entities', id],\n message: `${definition.kind} is not allowed by this exercise`,\n })\n }\n }\n }\n\n return errors.length > 0\n ? { success: false, data: null, errors }\n : { success: true, data, errors: [] }\n}\n\n/**\n * Restores an attempt from stored JSON — the counterpart to\n * validateGeometryExerciseJson(). Pass the exercise to also check the attempt\n * against it — only allowed object types, parents naming entities that exist in\n * one of the two documents — and to normalize it, which is what drops the given\n * points a migrated v1 attempt still carries.\n */\nexport function validateGeometryStateJson(\n json: string,\n exercise?: GeometryExercise,\n): GeometryStateInputValidationResult {\n try {\n return validateGeometryStateInput(JSON.parse(json), exercise)\n } catch (error) {\n return {\n success: false,\n data: null,\n errors: [\n {\n path: [],\n message: error instanceof Error ? error.message : 'Invalid JSON syntax',\n },\n ],\n }\n }\n}\n\n// Saves an attempt. Trivial today, but it is the one place that guarantees the\n// stored document carries its version, so a restore never has to guess.\nexport function serializeGeometryState(geometry: GeometryState): string {\n return JSON.stringify(geometry)\n}\n","import type { GeometryState } from '../schemas/geometryState.schema'\nimport { parentsOfDefinition } from '../geometry/entities'\nimport { entryOf } from '../geometry/entityRecord'\nimport type { GoalStatus, GeometryValidationFeedback } from './types'\n\nexport interface GeometryCanvasFeedback {\n constructions: Record<string, GoalStatus>\n points: Record<string, GoalStatus>\n}\n\nexport function deriveGeometryCanvasFeedback(\n feedback: GeometryValidationFeedback | null,\n geometry: GeometryState,\n focusedGoalId: string | null,\n): GeometryCanvasFeedback {\n const focused = feedback?.goals.find((goal) => goal.id === focusedGoalId)\n if (!focused) return { constructions: {}, points: {} }\n\n const constructions = Object.fromEntries(\n focused.constructionIds.map((id) => [id, focused.status]),\n ) as Record<string, GoalStatus>\n const pointIds = new Set(focused.pointIds)\n for (const [id, definition] of Object.entries(geometry.entities)) {\n if (definition.kind === 'point') continue\n if (!entryOf(constructions, id)) continue\n for (const pointId of parentsOfDefinition(definition)) pointIds.add(pointId)\n }\n const points = Object.fromEntries([...pointIds].map((id) => [id, focused.status])) as Record<\n string,\n GoalStatus\n >\n return { constructions, points }\n}\n","import type { GeometryExercise } from '../schemas/exercise.schema'\nimport { withoutSolution } from '../schemas/exerciseProjection'\nimport type { GeometryState } from '../schemas/geometryState.schema'\n\n// Feedback freshness (RFC 0005 § Decisions 2 and 5, ADR 0014). A check result\n// describes the normalized documents it graded, not the objects a host passed\n// in, so a clone, a JSON round trip or a reordered record fingerprints the same,\n// while any change to what grading reads does not.\n//\n// The value is opaque: only another fingerprint from this engine is ever\n// compared against it. That keeps the format free to change — a result from an\n// older format simply stops matching, which hides feedback instead of showing\n// stale feedback.\n\n// JSON with object keys sorted at every depth. Array order is kept because it\n// is meaningful (answer order is report order); records are the documents'\n// only unordered collections. An `undefined` member is skipped as\n// JSON.stringify skips it, so an absent optional field and an explicit\n// `undefined` agree.\nfunction canonicalJson(value: unknown): string {\n if (Array.isArray(value)) {\n return `[${value.map((item) => (item === undefined ? 'null' : canonicalJson(item))).join(',')}]`\n }\n if (typeof value === 'object' && value !== null) {\n const record = value as Record<string, unknown>\n const members = Object.keys(record)\n .sort()\n .filter((key) => record[key] !== undefined)\n .map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`)\n return `{${members.join(',')}}`\n }\n return JSON.stringify(value)\n}\n\n// cyrb53's two 32-bit lanes, both printed: 16 hex digits. Not cryptographic —\n// a fingerprint detects that grading inputs changed; a host forging a match\n// could only ever redisplay feedback it already holds.\nfunction hash(text: string): string {\n let h1 = 0xdeadbeef\n let h2 = 0x41c6ce57\n for (let index = 0; index < text.length; index++) {\n const code = text.charCodeAt(index)\n h1 = Math.imul(h1 ^ code, 2654435761)\n h2 = Math.imul(h2 ^ code, 1597334677)\n }\n h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507)\n h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909)\n h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507)\n h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909)\n return `${(h2 >>> 0).toString(16).padStart(8, '0')}${(h1 >>> 0).toString(16).padStart(8, '0')}`\n}\n\n// The solution-free grading projection. Checking ignores the authored solution\n// ADR 0015 embeds, so changing only that must not hide feedback.\nexport function fingerprintGeometryExercise(exercise: GeometryExercise): string {\n return hash(canonicalJson(withoutSolution(exercise)))\n}\n\n// Takes the attempt as grading read it: migrated, parsed and normalized against\n// its exercise, so a dropped pinned given or an older version changes nothing.\nexport function fingerprintGeometryAttempt(attempt: GeometryState): string {\n return hash(canonicalJson(attempt))\n}\n","import { latestGeometryStateVersion, type GeometryState } from '../schemas/geometryState.schema'\nimport { validateGeometryStateInput } from './validateGeometryStateInput'\n\n/**\n * A fresh attempt at the current attempt version, for a host starting a student\n * on an exercise (RFC 0005 § Decision 5). It takes no exercise: the two\n * documents version independently, and an empty attempt holds nothing of the\n * exercise. Every call returns a new object, so a host may keep or change one\n * without touching another.\n */\nexport function createEmptyGeometryAttempt(): GeometryState {\n return {\n schemaVersion: latestGeometryStateVersion,\n entities: {},\n presentation: {},\n answerEntityIds: [],\n }\n}\n\n/**\n * Whether a stored attempt holds any student work — a completion hint for a\n * host's \"not started\" state, never a grade. Untrusted input is migrated and\n * parsed first; absent or malformed input is `false` rather than an error.\n *\n * Work is any entity the attempt declares, not only an answer: dragging a given\n * copies it into `entities` while `answerEntityIds` stays empty, and that\n * counts. Reset, or deleting everything the student added, empties `entities`\n * again.\n *\n * No exercise is read, so a pinned given that a migrated v1 attempt still\n * re-declares counts here, although the load pass against its exercise drops it.\n */\nexport function hasGeometryWork(attemptInput: unknown): boolean {\n const attempt = validateGeometryStateInput(attemptInput)\n return attempt.success && Object.keys(attempt.data.entities).length > 0\n}\n","import type { GeometryExercise } from '../schemas/exercise.schema'\nimport { buildGeometryContext } from '../state/geometryContext'\nimport { createEmptyGeometryAttempt } from './geometryAttemptStatus'\nimport { figureGoalHandlerFor } from './goals'\nimport type { FigureValidationContext } from './goals/goalKindHandler'\n\nexport interface GeometryExerciseWarning {\n path: Array<string | number>\n message: string\n}\n\n/**\n * What is wrong with an exercise without being invalid. Two rules: a given\n * placed where the viewport cannot show it, and a figure goal the authored\n * figure already satisfies.\n *\n * Only a **free** point is checked. A derived point has no stored coordinates —\n * it is wherever the graph puts it, which depends on the entities it is built\n * on — so warning about it would mean resolving the whole exercise, and where\n * an authoring warning belongs for a construction that lands off-screen is an\n * M3 question about the authoring UI rather than about the document.\n */\nexport function getGeometryExerciseWarnings(exercise: GeometryExercise): GeometryExerciseWarning[] {\n const [xMin, yMin, xMax, yMax] = exercise.viewport\n const warnings: GeometryExerciseWarning[] = []\n for (const [id, definition] of Object.entries(exercise.entities)) {\n if (definition.kind !== 'point' || !('at' in definition)) continue\n const { x, y } = definition.at\n if (x >= xMin && x <= xMax && y >= yMin && y <= yMax) continue\n warnings.push({ path: ['entities', id], message: `point ${id} is outside the viewport` })\n }\n warnings.push(...satisfiedFigureGoalWarnings(exercise))\n return warnings\n}\n\n/**\n * A figure goal the authored figure already satisfies: an untouched attempt\n * checks correct, which is almost always a mistake (RFC 0006 § Reporting).\n *\n * A warning rather than a refusal, because deciding it needs measurement the\n * schema layer does not have, and because an author may want exactly this — a\n * \"keep it acute while you move the vertex\" exercise.\n *\n * The figure measured is the exercise's own, so the scene is built over an\n * empty attempt: this is the authored document before the student touches it.\n */\nfunction satisfiedFigureGoalWarnings(exercise: GeometryExercise): GeometryExerciseWarning[] {\n // Nothing to measure, and nothing to build, unless a figure goal is authored.\n if (!exercise.goals.some((goal) => figureGoalHandlerFor(goal) !== null)) return []\n\n const scene = buildGeometryContext(exercise, createEmptyGeometryAttempt())\n const context: FigureValidationContext = { exercise, points: scene.points, scene }\n const warnings: GeometryExerciseWarning[] = []\n exercise.goals.forEach((goal, index) => {\n const handler = figureGoalHandlerFor(goal)\n if (handler?.satisfiedByFigure(goal, context) !== true) return\n warnings.push({\n path: ['goals', index],\n message: `goal ${goal.id} is already satisfied by the authored figure`,\n })\n })\n return warnings\n}\n","// The one exercise load every public loader is built on (M3.I.3, option C).\n// Internal: not exported from any entry point.\n//\n// Exercise v4 embeds an authored solution (ADR 0015), and the engine loads an\n// exercise two ways. **Grading** — Check, fingerprints, the student component —\n// must ignore the solution entirely, so a malformed one never disables them.\n// **The full load** — `validateGeometryExercise()` and reveal — must also\n// migrate the solution and check it against the exercise. Both start from\n// `loadExerciseDocument()`, so the exercise half cannot differ between them, and the\n// full load adds `validateEmbeddedSolution()` on top.\nimport { withoutSolution } from '../schemas/exerciseProjection'\nimport {\n geometryExerciseSchema,\n migrateGeometryExercise,\n type GeometryExercise,\n} from '../schemas/exercise.schema'\nimport {\n getGeometryExerciseWarnings,\n type GeometryExerciseWarning,\n} from './getGeometryExerciseWarnings'\nimport type { GeometryExerciseValidationIssue } from './validateGeometryExercise'\nimport {\n validateGeometryStateInput,\n type GeometryStateInputValidationResult,\n} from './validateGeometryStateInput'\n\nexport type ExerciseLoadResult =\n | {\n success: true\n /** The solution-free projection, parsed. */\n data: GeometryExercise\n /** The stored solution, untouched: `undefined` when the document has none. */\n solution: unknown\n warnings: GeometryExerciseWarning[]\n }\n | { success: false; errors: GeometryExerciseValidationIssue[] }\n\nfunction normalizePath(path: PropertyKey[]): Array<string | number> {\n return path.map((segment) =>\n typeof segment === 'symbol' ? (segment.description ?? segment.toString()) : segment,\n )\n}\n\n/**\n * Migrates a stored exercise and parses its solution-free projection. Every\n * refusal the migration stage can produce — an older version with no step, a\n * newer one, something that is not a document at all — is reported under\n * `schemaVersion`, because that is the field the reader consulted.\n */\nexport function loadExerciseDocument(input: unknown): ExerciseLoadResult {\n const migrated = migrateGeometryExercise(input)\n if (!migrated.success) {\n return { success: false, errors: [{ path: ['schemaVersion'], message: migrated.message }] }\n }\n\n const document = migrated.data as Record<string, unknown>\n const result = geometryExerciseSchema.safeParse(withoutSolution(document))\n if (!result.success) {\n return {\n success: false,\n errors: result.error.issues.map((issue) => ({\n path: normalizePath(issue.path),\n message: issue.message,\n })),\n }\n }\n return {\n success: true,\n data: result.data,\n solution: document.solution,\n warnings: getGeometryExerciseWarnings(result.data),\n }\n}\n\n/**\n * The stored solution as an attempt of `exercise`: migrated on the attempt\n * chain, parsed, and normalized and cross-checked against the exercise exactly\n * as a student attempt is. Issue paths are rooted at `solution`, so they point\n * into the exercise document a host holds.\n */\nexport function validateEmbeddedSolution(\n solution: unknown,\n exercise: GeometryExercise,\n): GeometryStateInputValidationResult {\n const attempt = validateGeometryStateInput(solution, exercise)\n if (attempt.success) return attempt\n return {\n ...attempt,\n errors: attempt.errors.map((issue) => ({ ...issue, path: ['solution', ...issue.path] })),\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ import { AngleDefinition, ResolvedAngle } from '../entityModel.cjs';
2
+ import { Point } from '../point.cjs';
3
+ /**
4
+ * How far out the mark sits, as a fraction of the shorter arm.
5
+ *
6
+ * Tuned in M3.5.a against rendered figures at phone (390 px) and desktop widths,
7
+ * under both the `contain` and `stretch` projections. At 0.25 a mark on a short
8
+ * arm drew under the vertex point's own dot, so a 135° or right-angle mark was
9
+ * not visible at all; at 0.4 every mark clears the dot, and a narrow angle on
10
+ * long arms still keeps its mark near the vertex. A fraction rather than a
11
+ * screen constant because `distanceTo()` and `hitTest()` take no viewport: a
12
+ * mark measured in pixels would be hit where it is not drawn.
13
+ */
14
+ export declare const ANGLE_MARK_RADIUS_FRACTION = 0.4;
15
+ export declare const angleOperations: {
16
+ kind: "angle";
17
+ parents: (definition: AngleDefinition) => readonly [string, string, string];
18
+ resolve: (_definition: AngleDefinition, [firstArm, vertex, secondArm]: readonly import('../entityModel.cjs').ResolvedEntity[]) => {
19
+ kind: "angle";
20
+ vertex: {
21
+ x: number;
22
+ y: number;
23
+ };
24
+ startTurn: number;
25
+ sweepTurn: number;
26
+ markRadius: number;
27
+ } | null;
28
+ toShape: (resolved: ResolvedAngle) => {
29
+ shape: "angle";
30
+ vertex: {
31
+ x: number;
32
+ y: number;
33
+ };
34
+ startTurn: number;
35
+ sweepTurn: number;
36
+ radius: number;
37
+ };
38
+ toMeasured: () => null;
39
+ distanceTo: (resolved: ResolvedAngle, at: {
40
+ x: number;
41
+ y: number;
42
+ }) => number;
43
+ describe: (definition: AngleDefinition, nameOf: (id: import('../entityModel.cjs').EntityId) => string) => string;
44
+ hitPriority: number;
45
+ identityKey: (definition: AngleDefinition) => string;
46
+ };
47
+ /**
48
+ * The right-angle square's three drawn corners — on the first arm, the far
49
+ * corner, on the second arm — or null when the sweep is not right under
50
+ * `angleTypeOf()` (ADR 0017), in which case the mark is the arc.
51
+ *
52
+ * One definition for what `AngleShape` draws and what `distanceTo()` measures,
53
+ * so a hit lands where the mark is. The side is `markRadius·√½`, which puts the
54
+ * far corner at `markRadius` from the vertex, where the arc would have been.
55
+ */
56
+ export declare function rightAngleSquareOf(resolved: Pick<ResolvedAngle, 'vertex' | 'startTurn' | 'sweepTurn' | 'markRadius'>): readonly [Point, Point, Point] | null;
@@ -0,0 +1,57 @@
1
+ import { AngleDefinition, ResolvedAngle } from '../entityModel.js';
2
+ import { Point } from '../point.js';
3
+ /**
4
+ * How far out the mark sits, as a fraction of the shorter arm.
5
+ *
6
+ * Tuned in M3.5.a against rendered figures at phone (390 px) and desktop widths,
7
+ * under both the `contain` and `stretch` projections. At 0.25 a mark on a short
8
+ * arm drew under the vertex point's own dot, so a 135° or right-angle mark was
9
+ * not visible at all; at 0.4 every mark clears the dot, and a narrow angle on
10
+ * long arms still keeps its mark near the vertex. A fraction rather than a
11
+ * screen constant because `distanceTo()` and `hitTest()` take no viewport: a
12
+ * mark measured in pixels would be hit where it is not drawn.
13
+ */
14
+ export declare const ANGLE_MARK_RADIUS_FRACTION = 0.4;
15
+ export declare const angleOperations: {
16
+ kind: "angle";
17
+ parents: (definition: AngleDefinition) => readonly [string, string, string];
18
+ resolve: (_definition: AngleDefinition, [firstArm, vertex, secondArm]: readonly import('../entityModel.js').ResolvedEntity[]) => {
19
+ kind: "angle";
20
+ vertex: {
21
+ x: number;
22
+ y: number;
23
+ };
24
+ startTurn: number;
25
+ sweepTurn: number;
26
+ markRadius: number;
27
+ } | null;
28
+ toShape: (resolved: ResolvedAngle) => {
29
+ shape: "angle";
30
+ vertex: {
31
+ x: number;
32
+ y: number;
33
+ };
34
+ startTurn: number;
35
+ sweepTurn: number;
36
+ radius: number;
37
+ };
38
+ toMeasured: () => null;
39
+ distanceTo: (resolved: ResolvedAngle, at: {
40
+ x: number;
41
+ y: number;
42
+ }) => number;
43
+ describe: (definition: AngleDefinition, nameOf: (id: import('../entityModel.js').EntityId) => string) => string;
44
+ hitPriority: number;
45
+ identityKey: (definition: AngleDefinition) => string;
46
+ };
47
+ /**
48
+ * The right-angle square's three drawn corners — on the first arm, the far
49
+ * corner, on the second arm — or null when the sweep is not right under
50
+ * `angleTypeOf()` (ADR 0017), in which case the mark is the arc.
51
+ *
52
+ * One definition for what `AngleShape` draws and what `distanceTo()` measures,
53
+ * so a hit lands where the mark is. The side is `markRadius·√½`, which puts the
54
+ * far corner at `markRadius` from the vertex, where the arc would have been.
55
+ */
56
+ export declare function rightAngleSquareOf(resolved: Pick<ResolvedAngle, 'vertex' | 'startTurn' | 'sweepTurn' | 'markRadius'>): readonly [Point, Point, Point] | null;
57
+ //# sourceMappingURL=angle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"angle.d.ts","sourceRoot":"","sources":["../../../src/geometry/entities/angle.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,eAAe,EAAoB,aAAa,EAAE,MAAM,gBAAgB,CAAA;AACtF,OAAO,EAAmB,KAAK,KAAK,EAAE,MAAM,UAAU,CAAA;AAKtD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,0BAA0B,MAAM,CAAA;AAE7C,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyEgC,CAAA;AAE5D;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,IAAI,CAAC,aAAa,EAAE,QAAQ,GAAG,WAAW,GAAG,WAAW,GAAG,YAAY,CAAC,GACjF,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,GAAG,IAAI,CAOvC"}
@@ -1 +1 @@
1
- {"version":3,"file":"circle.d.ts","sourceRoot":"","sources":["../../../src/geometry/entities/circle.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,gBAAgB,EAAoB,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAGxF,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoDiC,CAAA"}
1
+ {"version":3,"file":"circle.d.ts","sourceRoot":"","sources":["../../../src/geometry/entities/circle.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,gBAAgB,EAAoB,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAGxF,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuDiC,CAAA"}
@@ -10,11 +10,15 @@ export declare const entityRegistry: EntityRegistry;
10
10
  */
11
11
  export declare const entityKinds: readonly EntityKind[];
12
12
  /**
13
- * Every kind but the point: the entities a position can lie *on*.
13
+ * The loci: the entities a position can lie *on*.
14
14
  *
15
- * A point is a location and the rest are loci, which is the one distinction
16
- * that survives generalization it is why `parameterAt` has no meaning for a
17
- * point and why snapping asks these kinds and not that one.
15
+ * A point is a location, an angle is a measure between two arms, and the rest
16
+ * are lociwhich is why `parameterAt` has no meaning for either and why
17
+ * snapping asks these kinds and not those. Read off `pointOnEntity()` rather
18
+ * than listed here, so a kind that cannot be travelled along is excluded by the
19
+ * same answer that makes it unparameterizable: left in, an angle's mark would
20
+ * win `snapToObject`'s hit test and then decline to place the point, taking the
21
+ * on-object snap to whatever is drawn under the mark with it.
18
22
  */
19
23
  export declare const objectEntityKinds: readonly EntityKind[];
20
24
  /** Every entity `definition` is built on, in declaration order. */
@@ -10,11 +10,15 @@ export declare const entityRegistry: EntityRegistry;
10
10
  */
11
11
  export declare const entityKinds: readonly EntityKind[];
12
12
  /**
13
- * Every kind but the point: the entities a position can lie *on*.
13
+ * The loci: the entities a position can lie *on*.
14
14
  *
15
- * A point is a location and the rest are loci, which is the one distinction
16
- * that survives generalization it is why `parameterAt` has no meaning for a
17
- * point and why snapping asks these kinds and not that one.
15
+ * A point is a location, an angle is a measure between two arms, and the rest
16
+ * are lociwhich is why `parameterAt` has no meaning for either and why
17
+ * snapping asks these kinds and not those. Read off `pointOnEntity()` rather
18
+ * than listed here, so a kind that cannot be travelled along is excluded by the
19
+ * same answer that makes it unparameterizable: left in, an angle's mark would
20
+ * win `snapToObject`'s hit test and then decline to place the point, taking the
21
+ * on-object snap to whatever is drawn under the mark with it.
18
22
  */
19
23
  export declare const objectEntityKinds: readonly EntityKind[];
20
24
  /** Every entity `definition` is built on, in declaration order. */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/geometry/entities/index.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EACV,gBAAgB,EAChB,QAAQ,EACR,UAAU,EAEV,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,eAAe,EAChB,MAAM,gBAAgB,CAAA;AACvB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAA;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AAKnE,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAEjF,eAAO,MAAM,cAAc,EAAE,cAK3B,CAAA;AAEF;;;;GAIG;AACH,eAAO,MAAM,WAAW,EAAE,SAAS,UAAU,EAE5C,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,EAAE,SAAS,UAAU,EAElD,CAAA;AAaD,mEAAmE;AACnE,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,gBAAgB,GAAG,SAAS,QAAQ,EAAE,CAErF;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,gBAAgB,EAC1D,UAAU,EAAE,CAAC,EACb,OAAO,EAAE,SAAS,cAAc,EAAE,GACjC,gBAAgB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CAIpC;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,gBAAgB,EAC5B,MAAM,EAAE,WAAW,CAAC,QAAQ,EAAE,KAAK,CAAC,GACnC,cAAc,GAAG,IAAI,CAWvB;AAED,6BAA6B;AAC7B,wBAAgB,eAAe,CAAC,QAAQ,EAAE,cAAc,GAAG,eAAe,CAEzE;AAED,qEAAqE;AACrE,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,cAAc,GAAG,cAAc,GAAG,IAAI,CAElF;AAED,mDAAmD;AACnD,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,cAAc,EAAE,EAAE,EAAE,KAAK,GAAG,MAAM,CAE9E;AAED,0CAA0C;AAC1C,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAEtD;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,UAAU,EAAE,gBAAgB,GAAG,MAAM,CAElE;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,gBAAgB,EAC5B,MAAM,EAAE,CAAC,EAAE,EAAE,QAAQ,KAAK,MAAM,GAC/B,MAAM,CAER"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/geometry/entities/index.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EACV,gBAAgB,EAChB,QAAQ,EACR,UAAU,EAEV,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,eAAe,EAChB,MAAM,gBAAgB,CAAA;AACvB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAA;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AAKnE,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAEjF,eAAO,MAAM,cAAc,EAAE,cAM3B,CAAA;AAEF;;;;GAIG;AACH,eAAO,MAAM,WAAW,EAAE,SAAS,UAAU,EAE5C,CAAA;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,EAAE,SAAS,UAAU,EAElD,CAAA;AAaD,mEAAmE;AACnE,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,gBAAgB,GAAG,SAAS,QAAQ,EAAE,CAErF;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,gBAAgB,EAC1D,UAAU,EAAE,CAAC,EACb,OAAO,EAAE,SAAS,cAAc,EAAE,GACjC,gBAAgB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CAIpC;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,gBAAgB,EAC5B,MAAM,EAAE,WAAW,CAAC,QAAQ,EAAE,KAAK,CAAC,GACnC,cAAc,GAAG,IAAI,CAWvB;AAED,6BAA6B;AAC7B,wBAAgB,eAAe,CAAC,QAAQ,EAAE,cAAc,GAAG,eAAe,CAEzE;AAED,qEAAqE;AACrE,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,cAAc,GAAG,cAAc,GAAG,IAAI,CAElF;AAED,mDAAmD;AACnD,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,cAAc,EAAE,EAAE,EAAE,KAAK,GAAG,MAAM,CAE9E;AAED,0CAA0C;AAC1C,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAEtD;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,UAAU,EAAE,gBAAgB,GAAG,MAAM,CAElE;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,gBAAgB,EAC5B,MAAM,EAAE,CAAC,EAAE,EAAE,QAAQ,KAAK,MAAM,GAC/B,MAAM,CAER"}
@@ -1 +1 @@
1
- {"version":3,"file":"line.d.ts","sourceRoot":"","sources":["../../../src/geometry/entities/line.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAoB,cAAc,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAEpF,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2B+B,CAAA"}
1
+ {"version":3,"file":"line.d.ts","sourceRoot":"","sources":["../../../src/geometry/entities/line.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAoB,cAAc,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAEpF,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8B+B,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"point.d.ts","sourceRoot":"","sources":["../../../src/geometry/entities/point.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,QAAQ,EAAoB,eAAe,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAYhG,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;CAsEgC,CAAA"}
1
+ {"version":3,"file":"point.d.ts","sourceRoot":"","sources":["../../../src/geometry/entities/point.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,QAAQ,EAAoB,eAAe,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAYhG,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;CA2EgC,CAAA"}
@@ -1,4 +1,4 @@
1
- import { ResolvedEntity } from '../entityModel.cjs';
1
+ import { EntityKind, ResolvedEntity } from '../entityModel.cjs';
2
2
  import { Point } from '../point.cjs';
3
3
  /**
4
4
  * Where two resolved entities meet, or null when they do not meet at all,
@@ -11,6 +11,13 @@ import { Point } from '../point.cjs';
11
11
  * by construction the one whose signature they satisfy.
12
12
  */
13
13
  export declare function intersectEntities(first: ResolvedEntity, second: ResolvedEntity, which?: 0 | 1): Point | null;
14
+ /**
15
+ * Whether a position can lie on this kind at all — whether it is a locus.
16
+ *
17
+ * Read off the same table that answers `pointOnEntity()`, so "what can a point
18
+ * be placed on" has one answer rather than a list kept in step by hand.
19
+ */
20
+ export declare function isParameterizableKind(kind: EntityKind): boolean;
14
21
  /**
15
22
  * The position at parameter `t` along `host`, or null when the host is not
16
23
  * something a parameter can travel along.
@@ -1,4 +1,4 @@
1
- import { ResolvedEntity } from '../entityModel.js';
1
+ import { EntityKind, ResolvedEntity } from '../entityModel.js';
2
2
  import { Point } from '../point.js';
3
3
  /**
4
4
  * Where two resolved entities meet, or null when they do not meet at all,
@@ -11,6 +11,13 @@ import { Point } from '../point.js';
11
11
  * by construction the one whose signature they satisfy.
12
12
  */
13
13
  export declare function intersectEntities(first: ResolvedEntity, second: ResolvedEntity, which?: 0 | 1): Point | null;
14
+ /**
15
+ * Whether a position can lie on this kind at all — whether it is a locus.
16
+ *
17
+ * Read off the same table that answers `pointOnEntity()`, so "what can a point
18
+ * be placed on" has one answer rather than a list kept in step by hand.
19
+ */
20
+ export declare function isParameterizableKind(kind: EntityKind): boolean;
14
21
  /**
15
22
  * The position at parameter `t` along `host`, or null when the host is not
16
23
  * something a parameter can travel along.
@@ -1 +1 @@
1
- {"version":3,"file":"relations.d.ts","sourceRoot":"","sources":["../../../src/geometry/entities/relations.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAGV,cAAc,EAIf,MAAM,gBAAgB,CAAA;AACvB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAA;AAoFrC;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,cAAc,EACrB,MAAM,EAAE,cAAc,EACtB,KAAK,GAAE,CAAC,GAAG,CAAK,GACf,KAAK,GAAG,IAAI,CAMd;AAqDD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,cAAc,EAAE,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAM3E;AAqCD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,cAAc,EAAE,EAAE,EAAE,KAAK,GAAG,MAAM,GAAG,IAAI,CAGhF"}
1
+ {"version":3,"file":"relations.d.ts","sourceRoot":"","sources":["../../../src/geometry/entities/relations.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EACV,UAAU,EAEV,cAAc,EAIf,MAAM,gBAAgB,CAAA;AACvB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAA;AAiGrC;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,cAAc,EACrB,MAAM,EAAE,cAAc,EACtB,KAAK,GAAE,CAAC,GAAG,CAAK,GACf,KAAK,GAAG,IAAI,CAMd;AA+DD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAE/D;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,cAAc,EAAE,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAM3E;AAwCD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,cAAc,EAAE,EAAE,EAAE,KAAK,GAAG,MAAM,GAAG,IAAI,CAGhF"}
@@ -1 +1 @@
1
- {"version":3,"file":"segment.d.ts","sourceRoot":"","sources":["../../../src/geometry/entities/segment.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAoB,eAAe,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAE1F,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BkC,CAAA"}
1
+ {"version":3,"file":"segment.d.ts","sourceRoot":"","sources":["../../../src/geometry/entities/segment.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAoB,eAAe,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAE1F,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgCkC,CAAA"}