@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
@@ -1,24 +1,61 @@
1
+ import { EntityId, GeometryContext } from '../../geometry/entityModel.js';
1
2
  import { GeometryGoal } from '../../schemas/goals/geometryGoal.schema.js';
2
3
  import { GoalKindDeclaration } from '../../schemas/goals/goalKindDeclaration.js';
3
4
  import { GoalCandidate } from '../../geometry/goalCandidate.js';
4
5
  import { RevealContext, RevealedAnswerShape, ValidationContext } from '../types.js';
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 GoalKindHandler<TGoal extends GeometryGoal = GeometryGoal, TCandidate extends GoalCandidate = GoalCandidate> extends GoalKindDeclaration<TGoal> {
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;
@@ -1 +1 @@
1
- {"version":3,"file":"goalKindHandler.d.ts","sourceRoot":"","sources":["../../../src/validation/goals/goalKindHandler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yCAAyC,CAAA;AAC3E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yCAAyC,CAAA;AAClF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAA;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAErF;;;;;;;;;;GAUG;AACH,MAAM,WAAW,eAAe,CAC9B,KAAK,SAAS,YAAY,GAAG,YAAY,EACzC,UAAU,SAAS,aAAa,GAAG,aAAa,CAChD,SAAQ,mBAAmB,CAAC,KAAK,CAAC;IAClC,OAAO,CAAC,SAAS,EAAE,aAAa,EAAE,IAAI,EAAE,KAAK,GAAG,SAAS,IAAI,UAAU,CAAA;IACvE,SAAS,CAAC,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAA;IAC9E,iBAAiB,CAAC,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAA;IACtF,mBAAmB,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,aAAa,GAAG,mBAAmB,GAAG,IAAI,CAAA;CACjF;AAED,MAAM,MAAM,gBAAgB,GAAG;KAC5B,CAAC,IAAI,YAAY,CAAC,MAAM,CAAC,GAAG,eAAe,CAAC,OAAO,CAAC,YAAY,EAAE;QAAE,IAAI,EAAE,CAAC,CAAA;KAAE,CAAC,CAAC;CACjF,CAAA"}
1
+ {"version":3,"file":"goalKindHandler.d.ts","sourceRoot":"","sources":["../../../src/validation/goals/goalKindHandler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAC3E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yCAAyC,CAAA;AAC3E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yCAAyC,CAAA;AAClF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAA;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAErF;;;;;;;GAOG;AACH,MAAM,WAAW,uBAAwB,SAAQ,iBAAiB;IAChE,KAAK,EAAE,eAAe,CAAA;CACvB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,oBAAoB,CACnC,KAAK,SAAS,YAAY,GAAG,YAAY,EACzC,UAAU,SAAS,aAAa,GAAG,aAAa,CAChD,SAAQ,mBAAmB,CAAC,KAAK,CAAC;IAClC,OAAO,EAAE,YAAY,CAAA;IACrB,OAAO,CAAC,SAAS,EAAE,aAAa,EAAE,IAAI,EAAE,KAAK,GAAG,SAAS,IAAI,UAAU,CAAA;IACvE,SAAS,CAAC,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAA;IAC9E,iBAAiB,CAAC,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAA;IACtF,mBAAmB,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,aAAa,GAAG,mBAAmB,GAAG,IAAI,CAAA;CACjF;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,iBAAiB,CAChC,KAAK,SAAS,YAAY,GAAG,YAAY,CACzC,SAAQ,mBAAmB,CAAC,KAAK,CAAC;IAClC,OAAO,EAAE,QAAQ,CAAA;IACjB,wEAAwE;IACxE,iBAAiB,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,uBAAuB,GAAG,OAAO,GAAG,SAAS,CAAA;IACjF,qEAAqE;IACrE,eAAe,CAAC,IAAI,EAAE,KAAK,GAAG,SAAS,QAAQ,EAAE,CAAA;CAClD;AAED,MAAM,MAAM,eAAe,CACzB,KAAK,SAAS,YAAY,GAAG,YAAY,EACzC,UAAU,SAAS,aAAa,GAAG,aAAa,IAC9C,oBAAoB,CAAC,KAAK,EAAE,UAAU,CAAC,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAA;AAEtE,MAAM,MAAM,gBAAgB,GAAG;KAC5B,CAAC,IAAI,YAAY,CAAC,MAAM,CAAC,GAAG,eAAe,CAAC,OAAO,CAAC,YAAY,EAAE;QAAE,IAAI,EAAE,CAAC,CAAA;KAAE,CAAC,CAAC;CACjF,CAAA"}
@@ -1,10 +1,17 @@
1
- import { GoalKindHandler, GoalKindRegistry } from './goalKindHandler.cjs';
1
+ import { FigureGoalHandler, GoalKindHandler, GoalKindRegistry } from './goalKindHandler.cjs';
2
2
  import { GeometryGoal } from '../../schemas/goals/geometryGoal.schema.cjs';
3
3
  import { GoalCandidate } from '../../geometry/goalCandidate.cjs';
4
4
  import { RevealContext, RevealedAnswerShape, ValidationContext } from '../types.cjs';
5
- export type { GoalKindHandler, GoalKindRegistry } from './goalKindHandler.cjs';
5
+ export type { CandidateGoalHandler, FigureGoalHandler, GoalKindHandler, GoalKindRegistry, } from './goalKindHandler.cjs';
6
6
  export declare const goalKindRegistry: GoalKindRegistry;
7
7
  export declare function getGoalKindHandler(kind: string): GoalKindHandler;
8
+ /**
9
+ * The handler for a goal that judges the figure, or null for one that judges
10
+ * candidates (ADR 0016). The one place the subject is read: every dispatcher
11
+ * below answers a candidate question, and a figure goal has no candidates, so
12
+ * asking one of it is not an error but simply false.
13
+ */
14
+ export declare function figureGoalHandlerFor(goal: GeometryGoal): FigureGoalHandler | null;
8
15
  /** Every given the goal names — what removing one of them would orphan. */
9
16
  export declare function referencedGivenIds(goal: GeometryGoal): readonly string[];
10
17
  /** Whether this candidate could satisfy this goal at all, before measuring. */
@@ -1,10 +1,17 @@
1
- import { GoalKindHandler, GoalKindRegistry } from './goalKindHandler.js';
1
+ import { FigureGoalHandler, GoalKindHandler, GoalKindRegistry } from './goalKindHandler.js';
2
2
  import { GeometryGoal } from '../../schemas/goals/geometryGoal.schema.js';
3
3
  import { GoalCandidate } from '../../geometry/goalCandidate.js';
4
4
  import { RevealContext, RevealedAnswerShape, ValidationContext } from '../types.js';
5
- export type { GoalKindHandler, GoalKindRegistry } from './goalKindHandler.js';
5
+ export type { CandidateGoalHandler, FigureGoalHandler, GoalKindHandler, GoalKindRegistry, } from './goalKindHandler.js';
6
6
  export declare const goalKindRegistry: GoalKindRegistry;
7
7
  export declare function getGoalKindHandler(kind: string): GoalKindHandler;
8
+ /**
9
+ * The handler for a goal that judges the figure, or null for one that judges
10
+ * candidates (ADR 0016). The one place the subject is read: every dispatcher
11
+ * below answers a candidate question, and a figure goal has no candidates, so
12
+ * asking one of it is not an error but simply false.
13
+ */
14
+ export declare function figureGoalHandlerFor(goal: GeometryGoal): FigureGoalHandler | null;
8
15
  /** Every given the goal names — what removing one of them would orphan. */
9
16
  export declare function referencedGivenIds(goal: GeometryGoal): readonly string[];
10
17
  /** Whether this candidate could satisfy this goal at all, before measuring. */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/validation/goals/index.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAC1E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yCAAyC,CAAA;AAC3E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAA;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAErF,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAE1E,eAAO,MAAM,gBAAgB,EAAE,gBAO7B,CAAA;AAOF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAIhE;AAED,2EAA2E;AAC3E,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,YAAY,GAAG,SAAS,MAAM,EAAE,CAExE;AAED,+EAA+E;AAC/E,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,aAAa,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAE1F;AAED,wBAAgB,eAAe,CAC7B,SAAS,EAAE,aAAa,EACxB,IAAI,EAAE,YAAY,EAClB,GAAG,EAAE,iBAAiB,GACrB,OAAO,CAGT;AAED,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,aAAa,EACxB,IAAI,EAAE,YAAY,EAClB,GAAG,EAAE,iBAAiB,GACrB,OAAO,CAGT;AAED,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,YAAY,EAClB,GAAG,EAAE,aAAa,GACjB,mBAAmB,GAAG,IAAI,CAE5B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/validation/goals/index.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAEV,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EACjB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yCAAyC,CAAA;AAC3E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAA;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAErF,YAAY,EACV,oBAAoB,EACpB,iBAAiB,EACjB,eAAe,EACf,gBAAgB,GACjB,MAAM,mBAAmB,CAAA;AAE1B,eAAO,MAAM,gBAAgB,EAAE,gBAU7B,CAAA;AAOF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAIhE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,YAAY,GAAG,iBAAiB,GAAG,IAAI,CAGjF;AAOD,2EAA2E;AAC3E,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,YAAY,GAAG,SAAS,MAAM,EAAE,CAExE;AAED,+EAA+E;AAC/E,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,aAAa,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAE1F;AAED,wBAAgB,eAAe,CAC7B,SAAS,EAAE,aAAa,EACxB,IAAI,EAAE,YAAY,EAClB,GAAG,EAAE,iBAAiB,GACrB,OAAO,CAKT;AAED,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,aAAa,EACxB,IAAI,EAAE,YAAY,EAClB,GAAG,EAAE,iBAAiB,GACrB,OAAO,CAOT;AAED,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,YAAY,EAClB,GAAG,EAAE,aAAa,GACjB,mBAAmB,GAAG,IAAI,CAI5B"}
@@ -1,4 +1,4 @@
1
1
  import { CandidatePoint } from '../../geometry/goalCandidate.cjs';
2
2
  import { MidpointGoal } from '../../schemas/goals/midpointGoal.schema.cjs';
3
- import { GoalKindHandler } from './goalKindHandler.cjs';
4
- export declare const midpointGoalHandler: GoalKindHandler<MidpointGoal, CandidatePoint>;
3
+ import { CandidateGoalHandler } from './goalKindHandler.cjs';
4
+ export declare const midpointGoalHandler: CandidateGoalHandler<MidpointGoal, CandidatePoint>;
@@ -1,5 +1,5 @@
1
1
  import { CandidatePoint } from '../../geometry/goalCandidate.js';
2
2
  import { MidpointGoal } from '../../schemas/goals/midpointGoal.schema.js';
3
- import { GoalKindHandler } from './goalKindHandler.js';
4
- export declare const midpointGoalHandler: GoalKindHandler<MidpointGoal, CandidatePoint>;
3
+ import { CandidateGoalHandler } from './goalKindHandler.js';
4
+ export declare const midpointGoalHandler: CandidateGoalHandler<MidpointGoal, CandidatePoint>;
5
5
  //# sourceMappingURL=midpoint.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"midpoint.d.ts","sourceRoot":"","sources":["../../../src/validation/goals/midpoint.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,cAAc,EAEpB,MAAM,8BAA8B,CAAA;AAGrC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yCAAyC,CAAA;AAI3E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAUxD,eAAO,MAAM,mBAAmB,EAAE,eAAe,CAAC,YAAY,EAAE,cAAc,CAqB7E,CAAA"}
1
+ {"version":3,"file":"midpoint.d.ts","sourceRoot":"","sources":["../../../src/validation/goals/midpoint.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,cAAc,EAEpB,MAAM,8BAA8B,CAAA;AAGrC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yCAAyC,CAAA;AAI3E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAU7D,eAAO,MAAM,mBAAmB,EAAE,oBAAoB,CAAC,YAAY,EAAE,cAAc,CAsBlF,CAAA"}
@@ -1,3 +1,3 @@
1
1
  import { ParallelGoal } from '../../schemas/goals/parallelGoal.schema.cjs';
2
- import { GoalKindHandler } from './goalKindHandler.cjs';
3
- export declare const parallelGoalHandler: GoalKindHandler<ParallelGoal>;
2
+ import { CandidateGoalHandler } from './goalKindHandler.cjs';
3
+ export declare const parallelGoalHandler: CandidateGoalHandler<ParallelGoal>;
@@ -1,4 +1,4 @@
1
1
  import { ParallelGoal } from '../../schemas/goals/parallelGoal.schema.js';
2
- import { GoalKindHandler } from './goalKindHandler.js';
3
- export declare const parallelGoalHandler: GoalKindHandler<ParallelGoal>;
2
+ import { CandidateGoalHandler } from './goalKindHandler.js';
3
+ export declare const parallelGoalHandler: CandidateGoalHandler<ParallelGoal>;
4
4
  //# sourceMappingURL=parallel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"parallel.d.ts","sourceRoot":"","sources":["../../../src/validation/goals/parallel.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yCAAyC,CAAA;AAY3E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAQxD,eAAO,MAAM,mBAAmB,EAAE,eAAe,CAAC,YAAY,CA6C7D,CAAA"}
1
+ {"version":3,"file":"parallel.d.ts","sourceRoot":"","sources":["../../../src/validation/goals/parallel.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yCAAyC,CAAA;AAY3E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAQ7D,eAAO,MAAM,mBAAmB,EAAE,oBAAoB,CAAC,YAAY,CA8ClE,CAAA"}
@@ -1,3 +1,3 @@
1
1
  import { PerpendicularGoal } from '../../schemas/goals/perpendicularGoal.schema.cjs';
2
- import { GoalKindHandler } from './goalKindHandler.cjs';
3
- export declare const perpendicularGoalHandler: GoalKindHandler<PerpendicularGoal>;
2
+ import { CandidateGoalHandler } from './goalKindHandler.cjs';
3
+ export declare const perpendicularGoalHandler: CandidateGoalHandler<PerpendicularGoal>;
@@ -1,4 +1,4 @@
1
1
  import { PerpendicularGoal } from '../../schemas/goals/perpendicularGoal.schema.js';
2
- import { GoalKindHandler } from './goalKindHandler.js';
3
- export declare const perpendicularGoalHandler: GoalKindHandler<PerpendicularGoal>;
2
+ import { CandidateGoalHandler } from './goalKindHandler.js';
3
+ export declare const perpendicularGoalHandler: CandidateGoalHandler<PerpendicularGoal>;
4
4
  //# sourceMappingURL=perpendicular.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"perpendicular.d.ts","sourceRoot":"","sources":["../../../src/validation/goals/perpendicular.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8CAA8C,CAAA;AAYrF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAQxD,eAAO,MAAM,wBAAwB,EAAE,eAAe,CAAC,iBAAiB,CA0CvE,CAAA"}
1
+ {"version":3,"file":"perpendicular.d.ts","sourceRoot":"","sources":["../../../src/validation/goals/perpendicular.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8CAA8C,CAAA;AAYrF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAQ7D,eAAO,MAAM,wBAAwB,EAAE,oBAAoB,CAAC,iBAAiB,CA2C5E,CAAA"}
@@ -1,3 +1,3 @@
1
1
  import { SegmentLengthGoal } from '../../schemas/goals/segmentLengthGoal.schema.cjs';
2
- import { GoalKindHandler } from './goalKindHandler.cjs';
3
- export declare const segmentLengthGoalHandler: GoalKindHandler<SegmentLengthGoal>;
2
+ import { CandidateGoalHandler } from './goalKindHandler.cjs';
3
+ export declare const segmentLengthGoalHandler: CandidateGoalHandler<SegmentLengthGoal>;
@@ -1,4 +1,4 @@
1
1
  import { SegmentLengthGoal } from '../../schemas/goals/segmentLengthGoal.schema.js';
2
- import { GoalKindHandler } from './goalKindHandler.js';
3
- export declare const segmentLengthGoalHandler: GoalKindHandler<SegmentLengthGoal>;
2
+ import { CandidateGoalHandler } from './goalKindHandler.js';
3
+ export declare const segmentLengthGoalHandler: CandidateGoalHandler<SegmentLengthGoal>;
4
4
  //# sourceMappingURL=segmentLength.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"segmentLength.d.ts","sourceRoot":"","sources":["../../../src/validation/goals/segmentLength.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8CAA8C,CAAA;AAKrF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAIxD,eAAO,MAAM,wBAAwB,EAAE,eAAe,CAAC,iBAAiB,CAoCvE,CAAA"}
1
+ {"version":3,"file":"segmentLength.d.ts","sourceRoot":"","sources":["../../../src/validation/goals/segmentLength.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8CAA8C,CAAA;AAKrF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAI7D,eAAO,MAAM,wBAAwB,EAAE,oBAAoB,CAAC,iBAAiB,CAqC5E,CAAA"}
@@ -1,5 +1,5 @@
1
1
  import { Point } from '../../geometry/point.cjs';
2
2
  import { ThroughPointsGoal } from '../../schemas/goals/throughPointsGoal.schema.cjs';
3
- import { GoalKindHandler } from './goalKindHandler.cjs';
3
+ import { CandidateGoalHandler } from './goalKindHandler.cjs';
4
4
  export declare function satisfiesThroughPoints(candidatePoints: Point[], goalPoints: Point[]): boolean;
5
- export declare const throughPointsGoalHandler: GoalKindHandler<ThroughPointsGoal>;
5
+ export declare const throughPointsGoalHandler: CandidateGoalHandler<ThroughPointsGoal>;
@@ -1,6 +1,6 @@
1
1
  import { Point } from '../../geometry/point.js';
2
2
  import { ThroughPointsGoal } from '../../schemas/goals/throughPointsGoal.schema.js';
3
- import { GoalKindHandler } from './goalKindHandler.js';
3
+ import { CandidateGoalHandler } from './goalKindHandler.js';
4
4
  export declare function satisfiesThroughPoints(candidatePoints: Point[], goalPoints: Point[]): boolean;
5
- export declare const throughPointsGoalHandler: GoalKindHandler<ThroughPointsGoal>;
5
+ export declare const throughPointsGoalHandler: CandidateGoalHandler<ThroughPointsGoal>;
6
6
  //# sourceMappingURL=throughPoints.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"throughPoints.d.ts","sourceRoot":"","sources":["../../../src/validation/goals/throughPoints.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAA;AACjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8CAA8C,CAAA;AAKrF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAWxD,wBAAgB,sBAAsB,CAAC,eAAe,EAAE,KAAK,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,OAAO,CAW7F;AAED,eAAO,MAAM,wBAAwB,EAAE,eAAe,CAAC,iBAAiB,CA6BvE,CAAA"}
1
+ {"version":3,"file":"throughPoints.d.ts","sourceRoot":"","sources":["../../../src/validation/goals/throughPoints.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAA;AACjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8CAA8C,CAAA;AAKrF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAW7D,wBAAgB,sBAAsB,CAAC,eAAe,EAAE,KAAK,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,OAAO,CAW7F;AAED,eAAO,MAAM,wBAAwB,EAAE,oBAAoB,CAAC,iBAAiB,CA8B5E,CAAA"}
@@ -18,7 +18,7 @@ export interface GeometryValidationFeedback {
18
18
  result: ValidationResult;
19
19
  goals: GoalGeometryFeedback[];
20
20
  }
21
- export type DetailedGoalCode = 'goal-satisfied' | 'no-candidate' | 'unresolved-candidate' | 'goal-not-satisfied';
21
+ export type DetailedGoalCode = 'goal-satisfied' | 'no-candidate' | 'unresolved-candidate' | 'goal-not-satisfied' | 'unresolved-reference';
22
22
  export interface DetailedGoalResult extends GoalResult {
23
23
  code: DetailedGoalCode;
24
24
  matchedEntityIds: EntityId[];
@@ -51,6 +51,10 @@ export type RevealedAnswerShape = {
51
51
  } | {
52
52
  type: 'point';
53
53
  at: Point;
54
+ } | {
55
+ type: 'angle';
56
+ vertex: Point;
57
+ arms: [Point, Point];
54
58
  };
55
59
  export interface RevealedAnswer {
56
60
  id: string;
@@ -18,7 +18,7 @@ export interface GeometryValidationFeedback {
18
18
  result: ValidationResult;
19
19
  goals: GoalGeometryFeedback[];
20
20
  }
21
- export type DetailedGoalCode = 'goal-satisfied' | 'no-candidate' | 'unresolved-candidate' | 'goal-not-satisfied';
21
+ export type DetailedGoalCode = 'goal-satisfied' | 'no-candidate' | 'unresolved-candidate' | 'goal-not-satisfied' | 'unresolved-reference';
22
22
  export interface DetailedGoalResult extends GoalResult {
23
23
  code: DetailedGoalCode;
24
24
  matchedEntityIds: EntityId[];
@@ -51,6 +51,10 @@ export type RevealedAnswerShape = {
51
51
  } | {
52
52
  type: 'point';
53
53
  at: Point;
54
+ } | {
55
+ type: 'angle';
56
+ vertex: Point;
57
+ arms: [Point, Point];
54
58
  };
55
59
  export interface RevealedAnswer {
56
60
  id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/validation/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAClE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAEvD,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,WAAW,CAAA;AAEhD,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,UAAU,CAAA;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,UAAU,CAAA;IAClB,KAAK,EAAE,UAAU,EAAE,CAAA;CACpB;AAED,MAAM,WAAW,oBAAqB,SAAQ,UAAU;IACtD,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB,QAAQ,EAAE,OAAO,EAAE,CAAA;CACpB;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,gBAAgB,CAAA;IACxB,KAAK,EAAE,oBAAoB,EAAE,CAAA;CAC9B;AAUD,MAAM,MAAM,gBAAgB,GAC1B,gBAAgB,GAAG,cAAc,GAAG,sBAAsB,GAAG,oBAAoB,CAAA;AAEnF,MAAM,WAAW,kBAAmB,SAAQ,UAAU;IACpD,IAAI,EAAE,gBAAgB,CAAA;IAEtB,gBAAgB,EAAE,QAAQ,EAAE,CAAA;IAI5B,iBAAiB,EAAE,QAAQ,EAAE,CAAA;IAG7B,mBAAmB,EAAE,QAAQ,EAAE,CAAA;CAChC;AAED,MAAM,MAAM,sBAAsB,GAAG,UAAU,GAAG,mBAAmB,GAAG,eAAe,CAAA;AAEvF,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,sBAAsB,CAAA;IAC5B,SAAS,EAAE,QAAQ,EAAE,CAAA;CACtB;AAED,MAAM,WAAW,wBAAwB;IAGvC,QAAQ,EAAE,0BAA0B,CAAA;IACpC,KAAK,EAAE,kBAAkB,EAAE,CAAA;IAC3B,WAAW,EAAE,kBAAkB,EAAE,CAAA;IAKjC,cAAc,EAAE,QAAQ,EAAE,CAAA;CAC3B;AAKD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,gBAAgB,CAAA;IAO1B,MAAM,EAAE,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;CACpC;AAID,MAAM,MAAM,aAAa,GAAG,iBAAiB,CAAA;AAE7C,MAAM,MAAM,mBAAmB,GAC3B;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,KAAK,CAAC;IAAC,EAAE,EAAE,KAAK,CAAA;CAAE,GAC3C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,KAAK,CAAA;CAAE,CAAA;AAEhC,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,mBAAmB,GAAG,IAAI,CAAA;CAClC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/validation/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAClE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAEvD,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,WAAW,CAAA;AAEhD,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,UAAU,CAAA;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,UAAU,CAAA;IAClB,KAAK,EAAE,UAAU,EAAE,CAAA;CACpB;AAED,MAAM,WAAW,oBAAqB,SAAQ,UAAU;IACtD,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB,QAAQ,EAAE,OAAO,EAAE,CAAA;CACpB;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,gBAAgB,CAAA;IACxB,KAAK,EAAE,oBAAoB,EAAE,CAAA;CAC9B;AAeD,MAAM,MAAM,gBAAgB,GACxB,gBAAgB,GAChB,cAAc,GACd,sBAAsB,GACtB,oBAAoB,GACpB,sBAAsB,CAAA;AAE1B,MAAM,WAAW,kBAAmB,SAAQ,UAAU;IACpD,IAAI,EAAE,gBAAgB,CAAA;IAEtB,gBAAgB,EAAE,QAAQ,EAAE,CAAA;IAI5B,iBAAiB,EAAE,QAAQ,EAAE,CAAA;IAG7B,mBAAmB,EAAE,QAAQ,EAAE,CAAA;CAChC;AAED,MAAM,MAAM,sBAAsB,GAAG,UAAU,GAAG,mBAAmB,GAAG,eAAe,CAAA;AAEvF,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,sBAAsB,CAAA;IAC5B,SAAS,EAAE,QAAQ,EAAE,CAAA;CACtB;AAED,MAAM,WAAW,wBAAwB;IAGvC,QAAQ,EAAE,0BAA0B,CAAA;IACpC,KAAK,EAAE,kBAAkB,EAAE,CAAA;IAC3B,WAAW,EAAE,kBAAkB,EAAE,CAAA;IAKjC,cAAc,EAAE,QAAQ,EAAE,CAAA;CAC3B;AAKD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,gBAAgB,CAAA;IAO1B,MAAM,EAAE,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;CACpC;AAID,MAAM,MAAM,aAAa,GAAG,iBAAiB,CAAA;AAE7C,MAAM,MAAM,mBAAmB,GAC3B;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,KAAK,CAAC;IAAC,EAAE,EAAE,KAAK,CAAA;CAAE,GAC3C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,KAAK,CAAA;CAAE,GAG5B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;CAAE,CAAA;AAE1D,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,mBAAmB,GAAG,IAAI,CAAA;CAClC"}
@@ -1 +1 @@
1
- {"version":3,"file":"validateGeometryState.d.ts","sourceRoot":"","sources":["../../src/validation/validateGeometryState.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAA;AAGpE,OAAO,KAAK,EACV,wBAAwB,EAGxB,0BAA0B,EAG1B,gBAAgB,EACjB,MAAM,SAAS,CAAA;AAGhB,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,gBAAgB,EAC1B,QAAQ,EAAE,aAAa,GACtB,gBAAgB,CAElB;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,gBAAgB,EAC1B,QAAQ,EAAE,aAAa,GACtB,0BAA0B,CAE5B;AAED;;;;;;;;;GASG;AACH,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,gBAAgB,EAC1B,QAAQ,EAAE,aAAa,GACtB,wBAAwB,CAE1B"}
1
+ {"version":3,"file":"validateGeometryState.d.ts","sourceRoot":"","sources":["../../src/validation/validateGeometryState.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAA;AAGpE,OAAO,KAAK,EACV,wBAAwB,EAGxB,0BAA0B,EAG1B,gBAAgB,EACjB,MAAM,SAAS,CAAA;AAQhB,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,gBAAgB,EAC1B,QAAQ,EAAE,aAAa,GACtB,gBAAgB,CAElB;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,gBAAgB,EAC1B,QAAQ,EAAE,aAAa,GACtB,0BAA0B,CAE5B;AAED;;;;;;;;;GASG;AACH,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,gBAAgB,EAC1B,QAAQ,EAAE,aAAa,GACtB,wBAAwB,CAE1B"}
@@ -1 +1 @@
1
- {"version":3,"file":"validateGeometryStateInput.d.ts","sourceRoot":"","sources":["../../src/validation/validateGeometryStateInput.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAClE,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,iCAAiC,CAAA;AACxC,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,4BAA4B,CAAA;AAwDjF,MAAM,MAAM,kCAAkC,GAC1C;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,EAAE,CAAA;CAAE,GAClD;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,+BAA+B,EAAE,CAAA;CAAE,CAAA;AAE7E,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,OAAO,EACd,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kCAAkC,CAgEpC;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kCAAkC,CAepC;AAID,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,CAEtE"}
1
+ {"version":3,"file":"validateGeometryStateInput.d.ts","sourceRoot":"","sources":["../../src/validation/validateGeometryStateInput.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAClE,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,iCAAiC,CAAA;AACxC,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,4BAA4B,CAAA;AAiEjF,MAAM,MAAM,kCAAkC,GAC1C;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,EAAE,CAAA;CAAE,GAClD;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,+BAA+B,EAAE,CAAA;CAAE,CAAA;AAE7E,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,OAAO,EACd,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kCAAkC,CAgEpC;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kCAAkC,CAepC;AAID,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,CAEtE"}
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./geometryState.schema-DKQ-durx.cjs"),t=require("./goals-CxR5qPDC.cjs"),n=require("./geometryAttemptStatus-N_XHIkiS.cjs");function r(e,t){return i(e,t).result}function i(e,t){return o(e,t,!1).feedback}function a(e,t){return o(e,t,!0)}function o(n,r,i){let a=n.goals.length===0,o=n.grading?.extraObjects===`reject`,l=i||o;if(a&&!i)return{feedback:{result:{status:`incorrect`,goals:[]},goals:[]},goals:[],diagnostics:[],extraEntityIds:[]};let u=t.d(n,r),d={exercise:n,points:u.points},f=[],p=[],m=[];for(let t of r.answerEntityIds){if(e.O(n.entities,t))continue;let r=u.definitionOf(t);if(r){if(l&&m.push(t),u.resolvedOf(t)===void 0){l&&p.push({id:t,definition:r});continue}f.push({id:t,definition:r})}}let h=[],g=[],_=new Set;for(let e of n.goals){let n=f.filter(n=>t.r(n,e,d));if(l)for(let e of n)_.add(e.id);let r=n.length>0,a=r&&!i?[]:f.filter(n=>t.i(n,e,d)),o=r?n:a;if(h.push({id:e.id,status:r?`correct`:`incorrect`,constructionIds:o.filter(e=>!t.f(e)).map(e=>e.id),pointIds:o.filter(t.f).map(e=>e.id)}),!i)continue;let s=p.filter(n=>t.n(n,e));g.push({id:e.id,status:r?`correct`:`incorrect`,code:r?`goal-satisfied`:f.some(n=>t.n(n,e))?`goal-not-satisfied`:s.length>0?`unresolved-candidate`:`no-candidate`,matchedEntityIds:c(n),relevantEntityIds:c(a),unresolvedEntityIds:c(s)})}let v=l?s(m,_,u):[],y={status:!a&&h.every(e=>e.status===`correct`)&&!(o&&v.length>0)?`correct`:`incorrect`,goals:h.map(({id:e,status:t})=>({id:e,status:t}))},b=[];return a&&b.push({code:`no-goals`,entityIds:[]}),p.length>0&&b.push({code:`unresolved-entity`,entityIds:c(p)}),o&&v.length>0&&b.push({code:`extra-objects`,entityIds:v}),{feedback:{result:y,goals:h},goals:g,diagnostics:b,extraEntityIds:v}}function s(e,t,n){let r=new Set(t),i=[...t];for(;i.length>0;){let e=i.pop();if(e!==void 0)for(let t of n.parentsOf(e))r.has(t)||(r.add(t),i.push(t))}return[...new Set(e)].filter(e=>!r.has(e))}function c(e){return[...new Set(e.map(e=>e.id))]}function l(e,t){let r=n.r(e);if(!r.success)return{success:!1,data:null,errors:r.errors.map(e=>({...e,source:`exercise`})),warnings:[]};let a=n.u(t,r.data);return a.success?{success:!0,data:i(r.data,a.data),fingerprints:{exercise:n.s(r.data),attempt:n.o(a.data)},errors:[],warnings:r.warnings}:{success:!1,data:null,errors:a.errors.map(e=>({...e,source:`geometry`})),warnings:[]}}function u(e,t){let r=n.r(e);if(!r.success)return{success:!1,data:null,errors:r.errors.map(e=>({...e,source:`exercise`,code:`invalid-exercise`})),warnings:[]};let i=n.u(t,r.data);return i.success?{success:!0,data:a(r.data,i.data),fingerprints:{exercise:n.s(r.data),attempt:n.o(i.data)},errors:[],warnings:r.warnings}:{success:!1,data:null,errors:i.errors.map(e=>({...e,source:`geometry`,code:`invalid-attempt`})),warnings:[]}}function d(e){let n=t.l(e);return e.goals.map(r=>({id:r.id,shape:t.o(r,{exercise:e,points:n})}))}function f(e){return{success:!1,data:null,errors:e}}function p(e){let t=n.r(e);if(!t.success)return f(t.errors.map(e=>({code:`invalid-exercise`,...e})));if(t.solution===void 0)return f([{code:`invalid-canonical-attempt`,path:[`solution`],message:`this exercise has no canonical solution`}]);let i=n.i(t.solution,t.data);return i.success?r(t.data,i.data).status===`correct`?{success:!0,data:i.data,errors:[]}:f([{code:`canonical-attempt-incorrect`,path:[`solution`],message:`the canonical solution does not satisfy this exercise`}]):f(i.errors.map(e=>({code:`invalid-canonical-attempt`,...e})))}function m(e){let t=n.r(e);if(!t.success)return{success:!1,data:null,errors:t.errors,warnings:[]};if(t.solution===void 0)return{success:!0,data:t.data,errors:[],warnings:t.warnings};let r=n.i(t.solution,t.data);return r.success?{success:!0,data:{...t.data,solution:r.data},errors:[],warnings:t.warnings}:{success:!1,data:null,errors:r.errors,warnings:[]}}function h(e){try{return m(JSON.parse(e))}catch(e){return{success:!1,data:null,errors:[{path:[],message:e instanceof Error?e.message:`Invalid JSON syntax`}],warnings:[]}}}exports.checkGeometryAnswer=l,exports.checkGeometryAnswerDetailed=u,exports.createEmptyGeometryAttempt=n.t,exports.deriveGeometryCanvasFeedback=n.c,exports.evaluateGeometryState=i,exports.evaluateGeometryStateDetailed=a,exports.getGeometryExerciseWarnings=n.a,exports.getGoalKindHandler=t.t,exports.hasGeometryWork=n.n,exports.intersectionsOf=e.V,exports.revealGeometryAnswers=d,exports.revealGeometryAttempt=p,exports.serializeGeometryState=n.l,exports.validateGeometryExercise=m,exports.validateGeometryExerciseJson=h,exports.validateGeometryState=r,exports.validateGeometryStateInput=n.u,exports.validateGeometryStateJson=n.d;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./geometryState.schema-B5ccMKDa.cjs"),t=require("./goals-DLbmBZD8.cjs"),n=require("./exerciseLoad-Db9Cigxi.cjs");function r(e,t){return i(e,t).result}function i(e,t){return o(e,t,!1).feedback}function a(e,t){return o(e,t,!0)}function o(n,r,i){let a=n.goals.length===0,o=n.grading?.extraObjects===`reject`,l=i||o;if(a&&!i)return{feedback:{result:{status:`incorrect`,goals:[]},goals:[]},goals:[],diagnostics:[],extraEntityIds:[]};let u=t.f(n,r),d={exercise:n,points:u.points},f=[],p=[],m=[];for(let t of r.answerEntityIds){if(e.M(n.entities,t))continue;let r=u.definitionOf(t);if(r){if(l&&m.push(t),u.resolvedOf(t)===void 0){l&&p.push({id:t,definition:r});continue}f.push({id:t,definition:r})}}let h=[],g=[],_=new Set;for(let e of n.goals){let n=t.t(e);if(n){let t=n.judgedEntityIds(e),r=n.satisfiedByFigure(e,{...d,scene:u}),a=r===void 0?[]:[...t];h.push({id:e.id,status:r===!0?`correct`:`incorrect`,constructionIds:a,pointIds:[]}),i&&g.push({id:e.id,status:r===!0?`correct`:`incorrect`,code:r===void 0?`unresolved-reference`:r?`goal-satisfied`:`goal-not-satisfied`,matchedEntityIds:r===!0?[...t]:[],relevantEntityIds:a,unresolvedEntityIds:[]});continue}let r=f.filter(n=>t.i(n,e,d));if(l)for(let e of r)_.add(e.id);let a=r.length>0,o=a&&!i?[]:f.filter(n=>t.a(n,e,d)),s=a?r:o;if(h.push({id:e.id,status:a?`correct`:`incorrect`,constructionIds:s.filter(e=>!t.p(e)).map(e=>e.id),pointIds:s.filter(t.p).map(e=>e.id)}),!i)continue;let m=p.filter(n=>t.r(n,e));g.push({id:e.id,status:a?`correct`:`incorrect`,code:a?`goal-satisfied`:f.some(n=>t.r(n,e))?`goal-not-satisfied`:m.length>0?`unresolved-candidate`:`no-candidate`,matchedEntityIds:c(r),relevantEntityIds:c(o),unresolvedEntityIds:c(m)})}let v=l?s(m,_,u):[],y={status:!a&&h.every(e=>e.status===`correct`)&&!(o&&v.length>0)?`correct`:`incorrect`,goals:h.map(({id:e,status:t})=>({id:e,status:t}))},b=[];return a&&b.push({code:`no-goals`,entityIds:[]}),p.length>0&&b.push({code:`unresolved-entity`,entityIds:c(p)}),o&&v.length>0&&b.push({code:`extra-objects`,entityIds:v}),{feedback:{result:y,goals:h},goals:g,diagnostics:b,extraEntityIds:v}}function s(e,t,n){let r=new Set(t),i=[...t];for(;i.length>0;){let e=i.pop();if(e!==void 0)for(let t of n.parentsOf(e))r.has(t)||(r.add(t),i.push(t))}return[...new Set(e)].filter(e=>!r.has(e))}function c(e){return[...new Set(e.map(e=>e.id))]}function l(e,t){let r=n.t(e);if(!r.success)return{success:!1,data:null,errors:r.errors.map(e=>({...e,source:`exercise`})),warnings:[]};let a=n.u(t,r.data);return a.success?{success:!0,data:i(r.data,a.data),fingerprints:{exercise:n.s(r.data),attempt:n.o(a.data)},errors:[],warnings:r.warnings}:{success:!1,data:null,errors:a.errors.map(e=>({...e,source:`geometry`})),warnings:[]}}function u(e,t){let r=n.t(e);if(!r.success)return{success:!1,data:null,errors:r.errors.map(e=>({...e,source:`exercise`,code:`invalid-exercise`})),warnings:[]};let i=n.u(t,r.data);return i.success?{success:!0,data:a(r.data,i.data),fingerprints:{exercise:n.s(r.data),attempt:n.o(i.data)},errors:[],warnings:r.warnings}:{success:!1,data:null,errors:i.errors.map(e=>({...e,source:`geometry`,code:`invalid-attempt`})),warnings:[]}}function d(e){let n=t.u(e);return e.goals.map(r=>({id:r.id,shape:t.s(r,{exercise:e,points:n})}))}function f(e){return{success:!1,data:null,errors:e}}function p(e){let t=n.t(e);if(!t.success)return f(t.errors.map(e=>({code:`invalid-exercise`,...e})));if(t.solution===void 0)return f([{code:`invalid-canonical-attempt`,path:[`solution`],message:`this exercise has no canonical solution`}]);let i=n.n(t.solution,t.data);return i.success?r(t.data,i.data).status===`correct`?{success:!0,data:i.data,errors:[]}:f([{code:`canonical-attempt-incorrect`,path:[`solution`],message:`the canonical solution does not satisfy this exercise`}]):f(i.errors.map(e=>({code:`invalid-canonical-attempt`,...e})))}function m(e){let t=n.t(e);if(!t.success)return{success:!1,data:null,errors:t.errors,warnings:[]};if(t.solution===void 0)return{success:!0,data:t.data,errors:[],warnings:t.warnings};let r=n.n(t.solution,t.data);return r.success?{success:!0,data:{...t.data,solution:r.data},errors:[],warnings:t.warnings}:{success:!1,data:null,errors:r.errors,warnings:[]}}function h(e){try{return m(JSON.parse(e))}catch(e){return{success:!1,data:null,errors:[{path:[],message:e instanceof Error?e.message:`Invalid JSON syntax`}],warnings:[]}}}exports.checkGeometryAnswer=l,exports.checkGeometryAnswerDetailed=u,exports.createEmptyGeometryAttempt=n.i,exports.deriveGeometryCanvasFeedback=n.c,exports.evaluateGeometryState=i,exports.evaluateGeometryStateDetailed=a,exports.getGeometryExerciseWarnings=n.r,exports.getGoalKindHandler=t.n,exports.hasGeometryWork=n.a,exports.intersectionsOf=e.J,exports.revealGeometryAnswers=d,exports.revealGeometryAttempt=p,exports.serializeGeometryState=n.l,exports.validateGeometryExercise=m,exports.validateGeometryExerciseJson=h,exports.validateGeometryState=r,exports.validateGeometryStateInput=n.u,exports.validateGeometryStateJson=n.d;
2
2
  //# sourceMappingURL=validation.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"validation.cjs","names":[],"sources":["../src/validation/validateGeometryState.ts","../src/validation/checkGeometryAnswer.ts","../src/validation/revealGeometryAnswers.ts","../src/validation/revealGeometryAttempt.ts","../src/validation/validateGeometryExercise.ts"],"sourcesContent":["import { entryOf } from '../geometry/entityRecord'\nimport type { EntityId } from '../geometry/entityModel'\nimport type { GeometryExercise } from '../schemas/exercise.schema'\nimport type { GeometryState } from '../schemas/geometryState.schema'\nimport { isCandidatePoint, type GoalCandidate } from '../geometry/goalCandidate'\nimport { buildGeometryContext } from '../state/geometryContext'\nimport type {\n DetailedGeometryFeedback,\n DetailedGoalResult,\n GeometryDiagnostic,\n GeometryValidationFeedback,\n GoalGeometryFeedback,\n ValidationContext,\n ValidationResult,\n} from './types'\nimport { goalAcceptsCandidate, goalSatisfiedBy, isRelevantGoalAttempt } from './goals'\n\nexport function validateGeometryState(\n exercise: GeometryExercise,\n geometry: GeometryState,\n): ValidationResult {\n return evaluateGeometryState(exercise, geometry).result\n}\n\nexport function evaluateGeometryState(\n exercise: GeometryExercise,\n geometry: GeometryState,\n): GeometryValidationFeedback {\n return evaluate(exercise, geometry, false).feedback\n}\n\n/**\n * The same grading, reported rather than summarized: why each goal landed where\n * it did, as stable codes and entity ids (RFC 0004 § Decision 5). It measures\n * nothing the plain evaluation does not — the codes are read off the existing\n * goal handlers' own predicates — so the two can never disagree.\n *\n * `feedback` is exactly what `evaluateGeometryState()` returns for the same\n * inputs. The extra work is one relevance pass per satisfied goal and the\n * unresolved bookkeeping, which the plain path still skips.\n */\nexport function evaluateGeometryStateDetailed(\n exercise: GeometryExercise,\n geometry: GeometryState,\n): DetailedGeometryFeedback {\n return evaluate(exercise, geometry, true)\n}\n\nfunction evaluate(\n exercise: GeometryExercise,\n geometry: GeometryState,\n detailed: boolean,\n): DetailedGeometryFeedback {\n // An exercise nobody can answer is incorrect, and the report now says so in\n // one word rather than leaving a host to infer it from an empty goal list.\n // The plain path stops here, as it always has; the report does not, because\n // an unresolved answer entity is reported whatever the goals say — there is\n // simply no goal loop to run below.\n const noGoals = exercise.goals.length === 0\n // Absence means `allow` (ADR 0013), so an exercise authored before v3 keeps\n // exactly the verdict it had. The extra-object sets cost a closure walk, so\n // the plain path computes them only when the policy actually reads them:\n // under `allow` they are reported and never graded on.\n const strict = exercise.grading?.extraObjects === 'reject'\n const needsExtras = detailed || strict\n if (noGoals && !detailed) {\n return {\n feedback: { result: { status: 'incorrect', goals: [] }, goals: [] },\n goals: [],\n diagnostics: [],\n extraEntityIds: [],\n }\n }\n\n // Coordinates come from the dependency graph, not from storage: a derived\n // point is wherever its parents currently put it. A point that cannot be\n // resolved (its object was deleted, its intersection vanished) is absent\n // here and therefore cannot satisfy anything.\n const scene = buildGeometryContext(exercise, geometry)\n // The scene's own index, not a copy: `ValidationContext.points` is read-only\n // by contract (M2.3.c), so there is nothing to defend it against. The copy\n // this replaced was one `Map` allocation and one full rehash per validation.\n const context: ValidationContext = { exercise, points: scene.points }\n // One list for every kind, which is what `answerEntityIds` is: v1 kept a\n // candidacy list for points only and treated every construction as a\n // candidate, and the migration reproduces that by naming both. A goal kind\n // decides for itself which candidates it can be satisfied by (a midpoint goal\n // wants a point, a perpendicular goal wants a segment or line); points the\n // student only used as scaffolding are not named here and are never\n // candidates.\n //\n // An entity that no longer resolves has no coordinates to measure, so it is\n // dropped here rather than left for a handler to trip over: handlers may\n // assume every candidate they are handed can be looked up in `points`. An\n // entity resolves exactly when everything it is built on does, which is the\n // index's answer rather than a second scan.\n const candidates: GoalCandidate[] = []\n // Answer entities that exist but have no coordinates this evaluation. The\n // plain path drops them silently; the report names them, because \"you built\n // it and then deleted what it stood on\" is not the same as \"you built\n // nothing\".\n const unresolved: GoalCandidate[] = []\n // Set `A` of RFC 0004 § Decision 6, in answer order: every id the attempt\n // owns and names as an answer, resolved or not. The extra-object policy\n // subtracts from this list and nothing else, so scaffolding the student never\n // offered as an answer cannot be an extra.\n const answered: EntityId[] = []\n for (const id of geometry.answerEntityIds) {\n // A given is not an answer to anything (ADR 0008 § Amendment), whatever\n // the list says. `validateGeometryStateInput()` already strips these on\n // load, but grading takes both documents directly — a host holding a\n // migrated v1 attempt it never loaded through the engine, or a store still\n // in memory, would otherwise have the exercise's own given graded as the\n // student's construction (M2.G.2).\n if (entryOf(exercise.entities, id)) continue\n const definition = scene.definitionOf(id)\n if (!definition) continue\n if (needsExtras) answered.push(id)\n if (scene.resolvedOf(id) === undefined) {\n if (needsExtras) unresolved.push({ id, definition })\n continue\n }\n candidates.push({ id, definition })\n }\n const goals: GoalGeometryFeedback[] = []\n const detailedGoals: DetailedGoalResult[] = []\n // Set `M`: every candidate that satisfies *some* goal. A union, not an\n // assignment — one entity may answer two goals and two entities may answer\n // one, and this policy deliberately does not count solutions.\n const matched = new Set<EntityId>()\n for (const goal of exercise.goals) {\n const matching = candidates.filter((candidate) => goalSatisfiedBy(candidate, goal, context))\n if (needsExtras) for (const candidate of matching) matched.add(candidate.id)\n const correct = matching.length > 0\n // Highlighting wants relevance only when the goal is unsatisfied; the\n // report wants it either way, so a correct goal still says which other\n // attempts were aimed at it.\n const relevant =\n correct && !detailed\n ? []\n : candidates.filter((candidate) => isRelevantGoalAttempt(candidate, goal, context))\n const shown = correct ? matching : relevant\n goals.push({\n id: goal.id,\n status: correct ? 'correct' : 'incorrect',\n constructionIds: shown.filter((candidate) => !isCandidatePoint(candidate)).map((c) => c.id),\n pointIds: shown.filter(isCandidatePoint).map((candidate) => candidate.id),\n })\n if (!detailed) continue\n const unresolvedForGoal = unresolved.filter((candidate) =>\n goalAcceptsCandidate(candidate, goal),\n )\n detailedGoals.push({\n id: goal.id,\n status: correct ? 'correct' : 'incorrect',\n code: correct\n ? 'goal-satisfied'\n : candidates.some((candidate) => goalAcceptsCandidate(candidate, goal))\n ? 'goal-not-satisfied'\n : unresolvedForGoal.length > 0\n ? 'unresolved-candidate'\n : 'no-candidate',\n matchedEntityIds: distinctIds(matching),\n relevantEntityIds: distinctIds(relevant),\n unresolvedEntityIds: distinctIds(unresolvedForGoal),\n })\n }\n // `A − (M ∪ S)`: answer entities no goal needed, and that nothing a goal\n // needed was built on. Empty unless the policy or the report asked for it.\n const extraEntityIds = needsExtras ? withoutSupport(answered, matched, scene) : []\n const goalsMet = !noGoals && goals.every((goal) => goal.status === 'correct')\n const result: ValidationResult = {\n // `every` on an empty list is vacuously true, and a goalless exercise is\n // incorrect — the one place the two disagree.\n //\n // Under `reject`, an extra answer entity makes the attempt incorrect while\n // every goal stays as its own predicate found it: \"the goals are met and\n // something unused is still on the canvas\" is a different thing from a\n // failed goal, and the diagnostic below is where it is said.\n status: goalsMet && !(strict && extraEntityIds.length > 0) ? 'correct' : 'incorrect',\n goals: goals.map(({ id, status }) => ({ id, status })),\n }\n const diagnostics: GeometryDiagnostic[] = []\n if (noGoals) diagnostics.push({ code: 'no-goals', entityIds: [] })\n // Informational: an unresolved entity does not make a satisfied exercise\n // incorrect. Reporting it regardless is what lets a host say \"this one is\n // no longer on the canvas\" instead of leaving it unexplained.\n if (unresolved.length > 0) {\n diagnostics.push({ code: 'unresolved-entity', entityIds: distinctIds(unresolved) })\n }\n // Only under `reject`: the list is reported either way, but it is a finding\n // about the attempt only where the author asked for strictness. Last, because\n // an unresolved extra is explained by the diagnostic above it.\n if (strict && extraEntityIds.length > 0) {\n diagnostics.push({ code: 'extra-objects', entityIds: extraEntityIds })\n }\n return { feedback: { result, goals }, goals: detailedGoals, diagnostics, extraEntityIds }\n}\n\n/**\n * `answered` minus the matched entities and minus set `S`, their transitive\n * parents — the support closure.\n *\n * Support is exempt because it is not an alternative answer: the two points a\n * correct segment is drawn between, and the helper objects a correct derived\n * point stands on, are the construction, however the student named them. That\n * is why the walk goes *up* the graph from what satisfied a goal rather than\n * down from every answer entity.\n *\n * An unresolved answer entity cannot be in `matched` — nothing measured it —\n * and it can only be support if something that did resolve was built on it,\n * which is impossible. So it stays, which is the reported outcome RFC 0004 §\n * Decision 6 names.\n */\nfunction withoutSupport(\n answered: readonly EntityId[],\n matched: ReadonlySet<EntityId>,\n scene: { parentsOf(id: EntityId): readonly EntityId[] },\n): EntityId[] {\n const exempt = new Set<EntityId>(matched)\n const pending = [...matched]\n // Visited-bounded, because the graph is walked by id: a cycle is\n // unrepresentable in a stored document, but this must not depend on that.\n while (pending.length > 0) {\n const id = pending.pop()\n if (id === undefined) continue\n for (const parent of scene.parentsOf(id)) {\n if (exempt.has(parent)) continue\n exempt.add(parent)\n pending.push(parent)\n }\n }\n return [...new Set(answered)].filter((id) => !exempt.has(id))\n}\n\n/** `answerEntityIds` order, each id once — the reporting order RFC 0004 fixes. */\nfunction distinctIds(candidates: readonly GoalCandidate[]): string[] {\n return [...new Set(candidates.map((candidate) => candidate.id))]\n}\n","import type { DetailedGeometryFeedback, GeometryValidationFeedback } from './types'\nimport type { GeometryExerciseWarning } from './getGeometryExerciseWarnings'\nimport { fingerprintGeometryAttempt, fingerprintGeometryExercise } from './gradingFingerprints'\nimport { loadExerciseDocument } from './exerciseLoad'\nimport type { GeometryExerciseValidationIssue } from './validateGeometryExercise'\nimport { evaluateGeometryState, evaluateGeometryStateDetailed } from './validateGeometryState'\nimport { validateGeometryStateInput } from './validateGeometryStateInput'\n\nexport interface CheckGeometryAnswerIssue extends GeometryExerciseValidationIssue {\n source: 'exercise' | 'geometry'\n}\n\n/**\n * Which grading inputs a successful check described (RFC 0005 § Decision 2).\n * Both are opaque and engine-produced: equal for equal normalized content,\n * whatever the object identity or key order, and the exercise member ignores\n * the authored solution. A host passes the whole result on and never computes\n * or compares these itself.\n */\nexport interface CheckGeometryAnswerFingerprints {\n exercise: string\n attempt: string\n}\n\nexport type CheckGeometryAnswerResult =\n | {\n success: true\n data: GeometryValidationFeedback\n fingerprints: CheckGeometryAnswerFingerprints\n errors: []\n warnings: GeometryExerciseWarning[]\n }\n | {\n success: false\n data: null\n errors: CheckGeometryAnswerIssue[]\n warnings: []\n }\n\n/**\n * Safe public Check entry point for untrusted application data. Structure is\n * validated first; valid geometry is then evaluated by the JSXGraph-backed\n * goal handlers used by validateGeometryState().\n */\nexport function checkGeometryAnswer(\n exerciseInput: unknown,\n geometryInput: unknown,\n): CheckGeometryAnswerResult {\n // The solution-free load: grading ignores the authored solution, so a\n // malformed one cannot fail a check (ADR 0015).\n const exercise = loadExerciseDocument(exerciseInput)\n if (!exercise.success) {\n return {\n success: false,\n data: null,\n errors: exercise.errors.map((issue) => ({ ...issue, source: 'exercise' })),\n warnings: [],\n }\n }\n\n const geometry = validateGeometryStateInput(geometryInput, exercise.data)\n if (!geometry.success) {\n return {\n success: false,\n data: null,\n errors: geometry.errors.map((issue) => ({ ...issue, source: 'geometry' })),\n warnings: [],\n }\n }\n\n return {\n success: true,\n data: evaluateGeometryState(exercise.data, geometry.data),\n fingerprints: {\n exercise: fingerprintGeometryExercise(exercise.data),\n attempt: fingerprintGeometryAttempt(geometry.data),\n },\n errors: [],\n warnings: exercise.warnings,\n }\n}\n\n/**\n * Which document was rejected, as a code rather than a sentence. `message`\n * stays the schema's human-readable detail and is never a reason code: a\n * malformed document is not a student's wrong answer, and nothing here reaches\n * geometry evaluation (RFC 0004 § Decision 5).\n */\nexport type CheckGeometryAnswerErrorCode = 'invalid-exercise' | 'invalid-attempt'\n\nexport interface DetailedCheckGeometryAnswerIssue extends CheckGeometryAnswerIssue {\n code: CheckGeometryAnswerErrorCode\n}\n\nexport type DetailedCheckGeometryAnswerResult =\n | {\n success: true\n data: DetailedGeometryFeedback\n fingerprints: CheckGeometryAnswerFingerprints\n errors: []\n warnings: GeometryExerciseWarning[]\n }\n | {\n success: false\n data: null\n errors: DetailedCheckGeometryAnswerIssue[]\n warnings: []\n }\n\n/**\n * `checkGeometryAnswer()` with the structured report: same untrusted inputs,\n * same success discriminant, fingerprints and warnings, detailed data on\n * success.\n */\nexport function checkGeometryAnswerDetailed(\n exerciseInput: unknown,\n geometryInput: unknown,\n): DetailedCheckGeometryAnswerResult {\n // The solution-free load: grading ignores the authored solution, so a\n // malformed one cannot fail a check (ADR 0015).\n const exercise = loadExerciseDocument(exerciseInput)\n if (!exercise.success) {\n return {\n success: false,\n data: null,\n errors: exercise.errors.map((issue) => ({\n ...issue,\n source: 'exercise',\n code: 'invalid-exercise',\n })),\n warnings: [],\n }\n }\n\n const geometry = validateGeometryStateInput(geometryInput, exercise.data)\n if (!geometry.success) {\n return {\n success: false,\n data: null,\n errors: geometry.errors.map((issue) => ({\n ...issue,\n source: 'geometry',\n code: 'invalid-attempt',\n })),\n warnings: [],\n }\n }\n\n return {\n success: true,\n data: evaluateGeometryStateDetailed(exercise.data, geometry.data),\n fingerprints: {\n exercise: fingerprintGeometryExercise(exercise.data),\n attempt: fingerprintGeometryAttempt(geometry.data),\n },\n errors: [],\n warnings: exercise.warnings,\n }\n}\n","import type { GeometryExercise } from '../schemas/exercise.schema'\nimport { resolveExercisePoints } from '../state/resolvePoints'\nimport { revealedAnswerFor } from './goals'\nimport type { RevealedAnswer } from './types'\n\nexport function revealGeometryAnswers(exercise: GeometryExercise): RevealedAnswer[] {\n // The exercise's own points, resolved: a canonical answer is measured\n // against the givens and nothing else, so there is no attempt to index here.\n const points = resolveExercisePoints(exercise)\n\n return exercise.goals.map((goal) => ({\n id: goal.id,\n shape: revealedAnswerFor(goal, { exercise, points }),\n }))\n}\n","import type { GeometryState } from '../schemas/geometryState.schema'\nimport { loadExerciseDocument, validateEmbeddedSolution } from './exerciseLoad'\nimport { validateGeometryState } from './validateGeometryState'\n\n/**\n * Why no canonical attempt came back, as a code rather than a sentence (RFC\n * 0005 § Decision 4). The three are kept apart because a host acts on them\n * differently: a broken exercise is the whole widget's problem, a missing or\n * malformed solution is an authoring gap, and an incorrect one means the\n * authored answer and the goals disagree.\n */\nexport type RevealGeometryAttemptErrorCode =\n 'invalid-exercise' | 'invalid-canonical-attempt' | 'canonical-attempt-incorrect'\n\nexport interface RevealGeometryAttemptIssue {\n code: RevealGeometryAttemptErrorCode\n path: Array<string | number>\n message: string\n}\n\nexport type RevealGeometryAttemptResult =\n | { success: true; data: GeometryState; errors: [] }\n | { success: false; data: null; errors: RevealGeometryAttemptIssue[] }\n\nfunction refused(errors: RevealGeometryAttemptIssue[]): RevealGeometryAttemptResult {\n return { success: false, data: null, errors }\n}\n\n/**\n * The authored canonical attempt an exercise embeds, for a host's Reveal\n * (ADR 0015). Synchronous and DOM-free, and safe on untrusted input: the\n * exercise is migrated and parsed, then its `solution` is migrated, parsed and\n * normalized as an attempt against that exercise, then graded under the\n * exercise's own policy. Only a solution that grades correct is returned.\n *\n * Nothing is synthesized from goals and nothing is merged with student work:\n * the host replaces its attempt value with `data`. Paths of a malformed\n * solution are rooted at `solution`, so they point into the exercise document\n * the host holds.\n */\nexport function revealGeometryAttempt(exerciseInput: unknown): RevealGeometryAttemptResult {\n // The same two stages `validateGeometryExercise()` runs, kept apart here only\n // so each failure gets its own code.\n const exercise = loadExerciseDocument(exerciseInput)\n if (!exercise.success) {\n return refused(exercise.errors.map((issue) => ({ code: 'invalid-exercise', ...issue })))\n }\n\n if (exercise.solution === undefined) {\n return refused([\n {\n code: 'invalid-canonical-attempt',\n path: ['solution'],\n message: 'this exercise has no canonical solution',\n },\n ])\n }\n\n const attempt = validateEmbeddedSolution(exercise.solution, exercise.data)\n if (!attempt.success) {\n return refused(attempt.errors.map((issue) => ({ code: 'invalid-canonical-attempt', ...issue })))\n }\n\n if (validateGeometryState(exercise.data, attempt.data).status !== 'correct') {\n return refused([\n {\n code: 'canonical-attempt-incorrect',\n path: ['solution'],\n message: 'the canonical solution does not satisfy this exercise',\n },\n ])\n }\n\n return { success: true, data: attempt.data, errors: [] }\n}\n","import type { GeometryExercise } from '../schemas/exercise.schema'\nimport { loadExerciseDocument, validateEmbeddedSolution } from './exerciseLoad'\nimport type { GeometryExerciseWarning } from './getGeometryExerciseWarnings'\n\nexport interface GeometryExerciseValidationIssue {\n path: Array<string | number>\n message: string\n}\n\nexport type GeometryExerciseValidationResult =\n | {\n success: true\n data: GeometryExercise\n errors: []\n warnings: GeometryExerciseWarning[]\n }\n | {\n success: false\n data: null\n errors: GeometryExerciseValidationIssue[]\n warnings: []\n }\n\n/**\n * The whole stored exercise, authored solution included. Stored exercises are\n * brought up to the current version before their structure is checked, exactly\n * as attempts are (M2.6.b), and an embedded `solution` is then migrated and\n * checked against the exercise as an attempt (ADR 0015), with its issues rooted\n * at `solution`. On success `data.solution` is that normalized attempt.\n *\n * Grading does not go through here: Check and the student component load the\n * solution-free projection, so a broken solution never disables them. An\n * authoring tool does, and learns about it at save time.\n */\nexport function validateGeometryExercise(input: unknown): GeometryExerciseValidationResult {\n const loaded = loadExerciseDocument(input)\n if (!loaded.success) {\n return { success: false, data: null, errors: loaded.errors, warnings: [] }\n }\n if (loaded.solution === undefined) {\n return { success: true, data: loaded.data, errors: [], warnings: loaded.warnings }\n }\n\n const solution = validateEmbeddedSolution(loaded.solution, loaded.data)\n if (!solution.success) {\n return { success: false, data: null, errors: solution.errors, warnings: [] }\n }\n return {\n success: true,\n data: { ...loaded.data, solution: solution.data },\n errors: [],\n warnings: loaded.warnings,\n }\n}\n\nexport function validateGeometryExerciseJson(json: string): GeometryExerciseValidationResult {\n try {\n return validateGeometryExercise(JSON.parse(json))\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 warnings: [],\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"validation.cjs","names":[],"sources":["../src/validation/validateGeometryState.ts","../src/validation/checkGeometryAnswer.ts","../src/validation/revealGeometryAnswers.ts","../src/validation/revealGeometryAttempt.ts","../src/validation/validateGeometryExercise.ts"],"sourcesContent":["import { entryOf } from '../geometry/entityRecord'\nimport type { EntityId } from '../geometry/entityModel'\nimport type { GeometryExercise } from '../schemas/exercise.schema'\nimport type { GeometryState } from '../schemas/geometryState.schema'\nimport { isCandidatePoint, type GoalCandidate } from '../geometry/goalCandidate'\nimport { buildGeometryContext } from '../state/geometryContext'\nimport type {\n DetailedGeometryFeedback,\n DetailedGoalResult,\n GeometryDiagnostic,\n GeometryValidationFeedback,\n GoalGeometryFeedback,\n ValidationContext,\n ValidationResult,\n} from './types'\nimport {\n figureGoalHandlerFor,\n goalAcceptsCandidate,\n goalSatisfiedBy,\n isRelevantGoalAttempt,\n} from './goals'\n\nexport function validateGeometryState(\n exercise: GeometryExercise,\n geometry: GeometryState,\n): ValidationResult {\n return evaluateGeometryState(exercise, geometry).result\n}\n\nexport function evaluateGeometryState(\n exercise: GeometryExercise,\n geometry: GeometryState,\n): GeometryValidationFeedback {\n return evaluate(exercise, geometry, false).feedback\n}\n\n/**\n * The same grading, reported rather than summarized: why each goal landed where\n * it did, as stable codes and entity ids (RFC 0004 § Decision 5). It measures\n * nothing the plain evaluation does not — the codes are read off the existing\n * goal handlers' own predicates — so the two can never disagree.\n *\n * `feedback` is exactly what `evaluateGeometryState()` returns for the same\n * inputs. The extra work is one relevance pass per satisfied goal and the\n * unresolved bookkeeping, which the plain path still skips.\n */\nexport function evaluateGeometryStateDetailed(\n exercise: GeometryExercise,\n geometry: GeometryState,\n): DetailedGeometryFeedback {\n return evaluate(exercise, geometry, true)\n}\n\nfunction evaluate(\n exercise: GeometryExercise,\n geometry: GeometryState,\n detailed: boolean,\n): DetailedGeometryFeedback {\n // An exercise nobody can answer is incorrect, and the report now says so in\n // one word rather than leaving a host to infer it from an empty goal list.\n // The plain path stops here, as it always has; the report does not, because\n // an unresolved answer entity is reported whatever the goals say — there is\n // simply no goal loop to run below.\n const noGoals = exercise.goals.length === 0\n // Absence means `allow` (ADR 0013), so an exercise authored before v3 keeps\n // exactly the verdict it had. The extra-object sets cost a closure walk, so\n // the plain path computes them only when the policy actually reads them:\n // under `allow` they are reported and never graded on.\n const strict = exercise.grading?.extraObjects === 'reject'\n const needsExtras = detailed || strict\n if (noGoals && !detailed) {\n return {\n feedback: { result: { status: 'incorrect', goals: [] }, goals: [] },\n goals: [],\n diagnostics: [],\n extraEntityIds: [],\n }\n }\n\n // Coordinates come from the dependency graph, not from storage: a derived\n // point is wherever its parents currently put it. A point that cannot be\n // resolved (its object was deleted, its intersection vanished) is absent\n // here and therefore cannot satisfy anything.\n const scene = buildGeometryContext(exercise, geometry)\n // The scene's own index, not a copy: `ValidationContext.points` is read-only\n // by contract (M2.3.c), so there is nothing to defend it against. The copy\n // this replaced was one `Map` allocation and one full rehash per validation.\n const context: ValidationContext = { exercise, points: scene.points }\n // One list for every kind, which is what `answerEntityIds` is: v1 kept a\n // candidacy list for points only and treated every construction as a\n // candidate, and the migration reproduces that by naming both. A goal kind\n // decides for itself which candidates it can be satisfied by (a midpoint goal\n // wants a point, a perpendicular goal wants a segment or line); points the\n // student only used as scaffolding are not named here and are never\n // candidates.\n //\n // An entity that no longer resolves has no coordinates to measure, so it is\n // dropped here rather than left for a handler to trip over: handlers may\n // assume every candidate they are handed can be looked up in `points`. An\n // entity resolves exactly when everything it is built on does, which is the\n // index's answer rather than a second scan.\n const candidates: GoalCandidate[] = []\n // Answer entities that exist but have no coordinates this evaluation. The\n // plain path drops them silently; the report names them, because \"you built\n // it and then deleted what it stood on\" is not the same as \"you built\n // nothing\".\n const unresolved: GoalCandidate[] = []\n // Set `A` of RFC 0004 § Decision 6, in answer order: every id the attempt\n // owns and names as an answer, resolved or not. The extra-object policy\n // subtracts from this list and nothing else, so scaffolding the student never\n // offered as an answer cannot be an extra.\n const answered: EntityId[] = []\n for (const id of geometry.answerEntityIds) {\n // A given is not an answer to anything (ADR 0008 § Amendment), whatever\n // the list says. `validateGeometryStateInput()` already strips these on\n // load, but grading takes both documents directly — a host holding a\n // migrated v1 attempt it never loaded through the engine, or a store still\n // in memory, would otherwise have the exercise's own given graded as the\n // student's construction (M2.G.2).\n if (entryOf(exercise.entities, id)) continue\n const definition = scene.definitionOf(id)\n if (!definition) continue\n if (needsExtras) answered.push(id)\n if (scene.resolvedOf(id) === undefined) {\n if (needsExtras) unresolved.push({ id, definition })\n continue\n }\n candidates.push({ id, definition })\n }\n const goals: GoalGeometryFeedback[] = []\n const detailedGoals: DetailedGoalResult[] = []\n // Set `M`: every candidate that satisfies *some* goal. A union, not an\n // assignment — one entity may answer two goals and two entities may answer\n // one, and this policy deliberately does not count solutions.\n const matched = new Set<EntityId>()\n for (const goal of exercise.goals) {\n // A figure goal judges a given entity as the figure resolves it, so it runs\n // outside the candidate filter entirely and adds nothing to `matched`: it\n // has no candidates, and ADR 0013's extra-object set is about answers\n // (ADR 0016, RFC 0006 § Reporting).\n const figure = figureGoalHandlerFor(goal)\n if (figure) {\n const judged = figure.judgedEntityIds(goal)\n const satisfied = figure.satisfiedByFigure(goal, { ...context, scene })\n // Unresolved is neither correct nor an answer that measured wrong, so\n // the judged id is reported on neither row.\n const shown = satisfied === undefined ? [] : [...judged]\n goals.push({\n id: goal.id,\n status: satisfied === true ? 'correct' : 'incorrect',\n // Every figure goal judges an angle, which is a construction; a figure\n // goal over a point would report it here too, and no kind has one.\n constructionIds: shown,\n pointIds: [],\n })\n if (detailed) {\n detailedGoals.push({\n id: goal.id,\n status: satisfied === true ? 'correct' : 'incorrect',\n code:\n satisfied === undefined\n ? 'unresolved-reference'\n : satisfied\n ? 'goal-satisfied'\n : 'goal-not-satisfied',\n matchedEntityIds: satisfied === true ? [...judged] : [],\n relevantEntityIds: shown,\n // `no-candidate` is never produced for a figure goal, and neither is\n // an unresolved *candidate*: what did not resolve is the exercise's\n // own entity, which the code above names.\n unresolvedEntityIds: [],\n })\n }\n continue\n }\n const matching = candidates.filter((candidate) => goalSatisfiedBy(candidate, goal, context))\n if (needsExtras) for (const candidate of matching) matched.add(candidate.id)\n const correct = matching.length > 0\n // Highlighting wants relevance only when the goal is unsatisfied; the\n // report wants it either way, so a correct goal still says which other\n // attempts were aimed at it.\n const relevant =\n correct && !detailed\n ? []\n : candidates.filter((candidate) => isRelevantGoalAttempt(candidate, goal, context))\n const shown = correct ? matching : relevant\n goals.push({\n id: goal.id,\n status: correct ? 'correct' : 'incorrect',\n constructionIds: shown.filter((candidate) => !isCandidatePoint(candidate)).map((c) => c.id),\n pointIds: shown.filter(isCandidatePoint).map((candidate) => candidate.id),\n })\n if (!detailed) continue\n const unresolvedForGoal = unresolved.filter((candidate) =>\n goalAcceptsCandidate(candidate, goal),\n )\n detailedGoals.push({\n id: goal.id,\n status: correct ? 'correct' : 'incorrect',\n code: correct\n ? 'goal-satisfied'\n : candidates.some((candidate) => goalAcceptsCandidate(candidate, goal))\n ? 'goal-not-satisfied'\n : unresolvedForGoal.length > 0\n ? 'unresolved-candidate'\n : 'no-candidate',\n matchedEntityIds: distinctIds(matching),\n relevantEntityIds: distinctIds(relevant),\n unresolvedEntityIds: distinctIds(unresolvedForGoal),\n })\n }\n // `A − (M ∪ S)`: answer entities no goal needed, and that nothing a goal\n // needed was built on. Empty unless the policy or the report asked for it.\n const extraEntityIds = needsExtras ? withoutSupport(answered, matched, scene) : []\n const goalsMet = !noGoals && goals.every((goal) => goal.status === 'correct')\n const result: ValidationResult = {\n // `every` on an empty list is vacuously true, and a goalless exercise is\n // incorrect — the one place the two disagree.\n //\n // Under `reject`, an extra answer entity makes the attempt incorrect while\n // every goal stays as its own predicate found it: \"the goals are met and\n // something unused is still on the canvas\" is a different thing from a\n // failed goal, and the diagnostic below is where it is said.\n status: goalsMet && !(strict && extraEntityIds.length > 0) ? 'correct' : 'incorrect',\n goals: goals.map(({ id, status }) => ({ id, status })),\n }\n const diagnostics: GeometryDiagnostic[] = []\n if (noGoals) diagnostics.push({ code: 'no-goals', entityIds: [] })\n // Informational: an unresolved entity does not make a satisfied exercise\n // incorrect. Reporting it regardless is what lets a host say \"this one is\n // no longer on the canvas\" instead of leaving it unexplained.\n if (unresolved.length > 0) {\n diagnostics.push({ code: 'unresolved-entity', entityIds: distinctIds(unresolved) })\n }\n // Only under `reject`: the list is reported either way, but it is a finding\n // about the attempt only where the author asked for strictness. Last, because\n // an unresolved extra is explained by the diagnostic above it.\n if (strict && extraEntityIds.length > 0) {\n diagnostics.push({ code: 'extra-objects', entityIds: extraEntityIds })\n }\n return { feedback: { result, goals }, goals: detailedGoals, diagnostics, extraEntityIds }\n}\n\n/**\n * `answered` minus the matched entities and minus set `S`, their transitive\n * parents — the support closure.\n *\n * Support is exempt because it is not an alternative answer: the two points a\n * correct segment is drawn between, and the helper objects a correct derived\n * point stands on, are the construction, however the student named them. That\n * is why the walk goes *up* the graph from what satisfied a goal rather than\n * down from every answer entity.\n *\n * An unresolved answer entity cannot be in `matched` — nothing measured it —\n * and it can only be support if something that did resolve was built on it,\n * which is impossible. So it stays, which is the reported outcome RFC 0004 §\n * Decision 6 names.\n */\nfunction withoutSupport(\n answered: readonly EntityId[],\n matched: ReadonlySet<EntityId>,\n scene: { parentsOf(id: EntityId): readonly EntityId[] },\n): EntityId[] {\n const exempt = new Set<EntityId>(matched)\n const pending = [...matched]\n // Visited-bounded, because the graph is walked by id: a cycle is\n // unrepresentable in a stored document, but this must not depend on that.\n while (pending.length > 0) {\n const id = pending.pop()\n if (id === undefined) continue\n for (const parent of scene.parentsOf(id)) {\n if (exempt.has(parent)) continue\n exempt.add(parent)\n pending.push(parent)\n }\n }\n return [...new Set(answered)].filter((id) => !exempt.has(id))\n}\n\n/** `answerEntityIds` order, each id once — the reporting order RFC 0004 fixes. */\nfunction distinctIds(candidates: readonly GoalCandidate[]): string[] {\n return [...new Set(candidates.map((candidate) => candidate.id))]\n}\n","import type { DetailedGeometryFeedback, GeometryValidationFeedback } from './types'\nimport type { GeometryExerciseWarning } from './getGeometryExerciseWarnings'\nimport { fingerprintGeometryAttempt, fingerprintGeometryExercise } from './gradingFingerprints'\nimport { loadExerciseDocument } from './exerciseLoad'\nimport type { GeometryExerciseValidationIssue } from './validateGeometryExercise'\nimport { evaluateGeometryState, evaluateGeometryStateDetailed } from './validateGeometryState'\nimport { validateGeometryStateInput } from './validateGeometryStateInput'\n\nexport interface CheckGeometryAnswerIssue extends GeometryExerciseValidationIssue {\n source: 'exercise' | 'geometry'\n}\n\n/**\n * Which grading inputs a successful check described (RFC 0005 § Decision 2).\n * Both are opaque and engine-produced: equal for equal normalized content,\n * whatever the object identity or key order, and the exercise member ignores\n * the authored solution. A host passes the whole result on and never computes\n * or compares these itself.\n */\nexport interface CheckGeometryAnswerFingerprints {\n exercise: string\n attempt: string\n}\n\nexport type CheckGeometryAnswerResult =\n | {\n success: true\n data: GeometryValidationFeedback\n fingerprints: CheckGeometryAnswerFingerprints\n errors: []\n warnings: GeometryExerciseWarning[]\n }\n | {\n success: false\n data: null\n errors: CheckGeometryAnswerIssue[]\n warnings: []\n }\n\n/**\n * Safe public Check entry point for untrusted application data. Structure is\n * validated first; valid geometry is then evaluated by the JSXGraph-backed\n * goal handlers used by validateGeometryState().\n */\nexport function checkGeometryAnswer(\n exerciseInput: unknown,\n geometryInput: unknown,\n): CheckGeometryAnswerResult {\n // The solution-free load: grading ignores the authored solution, so a\n // malformed one cannot fail a check (ADR 0015).\n const exercise = loadExerciseDocument(exerciseInput)\n if (!exercise.success) {\n return {\n success: false,\n data: null,\n errors: exercise.errors.map((issue) => ({ ...issue, source: 'exercise' })),\n warnings: [],\n }\n }\n\n const geometry = validateGeometryStateInput(geometryInput, exercise.data)\n if (!geometry.success) {\n return {\n success: false,\n data: null,\n errors: geometry.errors.map((issue) => ({ ...issue, source: 'geometry' })),\n warnings: [],\n }\n }\n\n return {\n success: true,\n data: evaluateGeometryState(exercise.data, geometry.data),\n fingerprints: {\n exercise: fingerprintGeometryExercise(exercise.data),\n attempt: fingerprintGeometryAttempt(geometry.data),\n },\n errors: [],\n warnings: exercise.warnings,\n }\n}\n\n/**\n * Which document was rejected, as a code rather than a sentence. `message`\n * stays the schema's human-readable detail and is never a reason code: a\n * malformed document is not a student's wrong answer, and nothing here reaches\n * geometry evaluation (RFC 0004 § Decision 5).\n */\nexport type CheckGeometryAnswerErrorCode = 'invalid-exercise' | 'invalid-attempt'\n\nexport interface DetailedCheckGeometryAnswerIssue extends CheckGeometryAnswerIssue {\n code: CheckGeometryAnswerErrorCode\n}\n\nexport type DetailedCheckGeometryAnswerResult =\n | {\n success: true\n data: DetailedGeometryFeedback\n fingerprints: CheckGeometryAnswerFingerprints\n errors: []\n warnings: GeometryExerciseWarning[]\n }\n | {\n success: false\n data: null\n errors: DetailedCheckGeometryAnswerIssue[]\n warnings: []\n }\n\n/**\n * `checkGeometryAnswer()` with the structured report: same untrusted inputs,\n * same success discriminant, fingerprints and warnings, detailed data on\n * success.\n */\nexport function checkGeometryAnswerDetailed(\n exerciseInput: unknown,\n geometryInput: unknown,\n): DetailedCheckGeometryAnswerResult {\n // The solution-free load: grading ignores the authored solution, so a\n // malformed one cannot fail a check (ADR 0015).\n const exercise = loadExerciseDocument(exerciseInput)\n if (!exercise.success) {\n return {\n success: false,\n data: null,\n errors: exercise.errors.map((issue) => ({\n ...issue,\n source: 'exercise',\n code: 'invalid-exercise',\n })),\n warnings: [],\n }\n }\n\n const geometry = validateGeometryStateInput(geometryInput, exercise.data)\n if (!geometry.success) {\n return {\n success: false,\n data: null,\n errors: geometry.errors.map((issue) => ({\n ...issue,\n source: 'geometry',\n code: 'invalid-attempt',\n })),\n warnings: [],\n }\n }\n\n return {\n success: true,\n data: evaluateGeometryStateDetailed(exercise.data, geometry.data),\n fingerprints: {\n exercise: fingerprintGeometryExercise(exercise.data),\n attempt: fingerprintGeometryAttempt(geometry.data),\n },\n errors: [],\n warnings: exercise.warnings,\n }\n}\n","import type { GeometryExercise } from '../schemas/exercise.schema'\nimport { resolveExercisePoints } from '../state/resolvePoints'\nimport { revealedAnswerFor } from './goals'\nimport type { RevealedAnswer } from './types'\n\nexport function revealGeometryAnswers(exercise: GeometryExercise): RevealedAnswer[] {\n // The exercise's own points, resolved: a canonical answer is measured\n // against the givens and nothing else, so there is no attempt to index here.\n const points = resolveExercisePoints(exercise)\n\n return exercise.goals.map((goal) => ({\n id: goal.id,\n shape: revealedAnswerFor(goal, { exercise, points }),\n }))\n}\n","import type { GeometryState } from '../schemas/geometryState.schema'\nimport { loadExerciseDocument, validateEmbeddedSolution } from './exerciseLoad'\nimport { validateGeometryState } from './validateGeometryState'\n\n/**\n * Why no canonical attempt came back, as a code rather than a sentence (RFC\n * 0005 § Decision 4). The three are kept apart because a host acts on them\n * differently: a broken exercise is the whole widget's problem, a missing or\n * malformed solution is an authoring gap, and an incorrect one means the\n * authored answer and the goals disagree.\n */\nexport type RevealGeometryAttemptErrorCode =\n 'invalid-exercise' | 'invalid-canonical-attempt' | 'canonical-attempt-incorrect'\n\nexport interface RevealGeometryAttemptIssue {\n code: RevealGeometryAttemptErrorCode\n path: Array<string | number>\n message: string\n}\n\nexport type RevealGeometryAttemptResult =\n | { success: true; data: GeometryState; errors: [] }\n | { success: false; data: null; errors: RevealGeometryAttemptIssue[] }\n\nfunction refused(errors: RevealGeometryAttemptIssue[]): RevealGeometryAttemptResult {\n return { success: false, data: null, errors }\n}\n\n/**\n * The authored canonical attempt an exercise embeds, for a host's Reveal\n * (ADR 0015). Synchronous and DOM-free, and safe on untrusted input: the\n * exercise is migrated and parsed, then its `solution` is migrated, parsed and\n * normalized as an attempt against that exercise, then graded under the\n * exercise's own policy. Only a solution that grades correct is returned.\n *\n * Nothing is synthesized from goals and nothing is merged with student work:\n * the host replaces its attempt value with `data`. Paths of a malformed\n * solution are rooted at `solution`, so they point into the exercise document\n * the host holds.\n */\nexport function revealGeometryAttempt(exerciseInput: unknown): RevealGeometryAttemptResult {\n // The same two stages `validateGeometryExercise()` runs, kept apart here only\n // so each failure gets its own code.\n const exercise = loadExerciseDocument(exerciseInput)\n if (!exercise.success) {\n return refused(exercise.errors.map((issue) => ({ code: 'invalid-exercise', ...issue })))\n }\n\n if (exercise.solution === undefined) {\n return refused([\n {\n code: 'invalid-canonical-attempt',\n path: ['solution'],\n message: 'this exercise has no canonical solution',\n },\n ])\n }\n\n const attempt = validateEmbeddedSolution(exercise.solution, exercise.data)\n if (!attempt.success) {\n return refused(attempt.errors.map((issue) => ({ code: 'invalid-canonical-attempt', ...issue })))\n }\n\n if (validateGeometryState(exercise.data, attempt.data).status !== 'correct') {\n return refused([\n {\n code: 'canonical-attempt-incorrect',\n path: ['solution'],\n message: 'the canonical solution does not satisfy this exercise',\n },\n ])\n }\n\n return { success: true, data: attempt.data, errors: [] }\n}\n","import type { GeometryExercise } from '../schemas/exercise.schema'\nimport { loadExerciseDocument, validateEmbeddedSolution } from './exerciseLoad'\nimport type { GeometryExerciseWarning } from './getGeometryExerciseWarnings'\n\nexport interface GeometryExerciseValidationIssue {\n path: Array<string | number>\n message: string\n}\n\nexport type GeometryExerciseValidationResult =\n | {\n success: true\n data: GeometryExercise\n errors: []\n warnings: GeometryExerciseWarning[]\n }\n | {\n success: false\n data: null\n errors: GeometryExerciseValidationIssue[]\n warnings: []\n }\n\n/**\n * The whole stored exercise, authored solution included. Stored exercises are\n * brought up to the current version before their structure is checked, exactly\n * as attempts are (M2.6.b), and an embedded `solution` is then migrated and\n * checked against the exercise as an attempt (ADR 0015), with its issues rooted\n * at `solution`. On success `data.solution` is that normalized attempt.\n *\n * Grading does not go through here: Check and the student component load the\n * solution-free projection, so a broken solution never disables them. An\n * authoring tool does, and learns about it at save time.\n */\nexport function validateGeometryExercise(input: unknown): GeometryExerciseValidationResult {\n const loaded = loadExerciseDocument(input)\n if (!loaded.success) {\n return { success: false, data: null, errors: loaded.errors, warnings: [] }\n }\n if (loaded.solution === undefined) {\n return { success: true, data: loaded.data, errors: [], warnings: loaded.warnings }\n }\n\n const solution = validateEmbeddedSolution(loaded.solution, loaded.data)\n if (!solution.success) {\n return { success: false, data: null, errors: solution.errors, warnings: [] }\n }\n return {\n success: true,\n data: { ...loaded.data, solution: solution.data },\n errors: [],\n warnings: loaded.warnings,\n }\n}\n\nexport function validateGeometryExerciseJson(json: string): GeometryExerciseValidationResult {\n try {\n return validateGeometryExercise(JSON.parse(json))\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 warnings: [],\n }\n }\n}\n"],"mappings":"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"}