Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 21x 21x 21x 21x 21x 21x 21x 21x 21x 21x 21x 21x 5x 5x 4x 5x 5x 16x 16x 16x 10x 9x 21x 8x 8x 7x 6x 6x 7x 5x 7x 2x 7x 4x 7x 4x 4x 1x 1x 1x 8x 21x 3x 3x 3x 1x 3x 3x 5x 5x 5x 5x 21x 5x 5x 5x 5x 5x 5x | import type {
ConstructionElement,
ConstructionState,
ExpectedAction,
IntersectionCandidate,
} from '../types'
import { isCandidateBeyondPoint } from '../engine/intersections'
import { resolveSelector } from '../engine/selectors'
/**
* Validate whether the last committed element matches the expected action for a step.
* Returns true if the step is satisfied.
*
* For intersection steps, optionally validates that the candidate came from the
* expected pair of elements (ofA/ofB). These use ElementSelectors resolved at
* runtime to decouple propositions from element creation order.
*
* For macro steps, validation is handled externally (the macro commit handler
* validates input points and calls checkStep with a synthetic element).
*/
export function validateStep(
expected: ExpectedAction,
state: ConstructionState,
lastElement: ConstructionElement,
candidate?: IntersectionCandidate
): boolean {
// Observation steps are advanced by the Continue button, not canvas interaction
if (expected.type === 'observation') return false
// Superposition steps are advanced by the interaction hook, not element validation
if (expected.type === 'superposition') return false
if (expected.type === 'compass' && lastElement.kind === 'circle') {
return (
lastElement.centerId === expected.centerId &&
lastElement.radiusPointId === expected.radiusPointId
)
}
if (
expected.type === 'intersection' &&
lastElement.kind === 'point' &&
lastElement.origin === 'intersection'
) {
// If ofA/ofB are specified, resolve selectors and check that the candidate matches
if (expected.ofA != null && expected.ofB != null) {
if (!candidate) return false
const resolvedA = resolveSelector(expected.ofA, state)
const resolvedB = resolveSelector(expected.ofB, state)
if (!resolvedA || !resolvedB) return false
const matchesElements =
(candidate.ofA === resolvedA && candidate.ofB === resolvedB) ||
(candidate.ofA === resolvedB && candidate.ofB === resolvedA)
if (!matchesElements) return false
// If beyondId is specified, candidate must be on the extension past that point
if (expected.beyondId) {
return isCandidateBeyondPoint(
candidate,
expected.beyondId,
candidate.ofA,
candidate.ofB,
state
)
}
return true
}
// Accept any intersection point when ofA/ofB are omitted
return true
}
if (expected.type === 'straightedge' && lastElement.kind === 'segment') {
// Flexible on order: (from, to) or (to, from)
return (
(lastElement.fromId === expected.fromId && lastElement.toId === expected.toId) ||
(lastElement.fromId === expected.toId && lastElement.toId === expected.fromId)
)
}
// Extend steps: verify new point + segment beyond throughId
if (
expected.type === 'extend' &&
lastElement.kind === 'point' &&
lastElement.origin === 'intersection'
) {
return lastElement.label === expected.label
}
// Macro steps are validated and advanced directly by handleCommitMacro
// in EuclidCanvas.tsx, not through this function.
return false
}
|