Guide · 02 of 09
Exercises
An exercise is the checkable part of a slide — the thing the learner answers. Every exercise shares four optional lines: ask "..." (the prompt), hint "..." (repeatable — a hint ladder), ! "..." (the explanation shown after checking), and skill: "..." (the skill it tests).
expect#
A correctness check for the author, not the learner. Goes inside a numeric exercise and states, as a formula, why the answer is what it is. The compiler works it out and refuses to build if it disagrees with answer:. It's double-entry bookkeeping for math: writing answer: 0.8 next to expect: 4/sqrt(4^2+9) means a slipped decimal becomes a compile error instead of a wrong answer a student has to argue with. Nothing about it reaches the browser. Only numeric takes it, since it needs one unambiguous value to compare against.
expect: <expression>
| prop | type | description |
|---|---|---|
| expect* | expr | closed-form value the answer must match, within tolerance |
Example
quiz#
Multiple choice. Mark the correct option with * and wrong options with -. Each option may carry a { why: "..." } giving per-option feedback shown when the learner picks it.
quiz { ask "Question?" - "wrong option" { [why: "..."] } * "correct option" [hint "..."] [! "explanation"] }
Example
numeric#
A free-entry numeric answer. The learner types a number and it counts as correct if it lands within tolerance of any listed answer. Answers fold from expressions, so 64/3 or sqrt(2) are fine. tolerance defaults to a tiny epsilon (so type an exact expected value, or widen it for estimates). unit is shown as a suffix in the input.
numeric { ask "..." answer: <number> [answer: <another accepted value>] [tolerance: <n>] [unit: "..."] }
| prop | type | description |
|---|---|---|
| answer | number | an accepted value (repeatable) |
| tolerance | number | how far off is still correct (default ~0) |
| unit | string | label shown next to the input |
Example
build#
Tap tokens from the bank into slots to assemble an answer. List multiple answer: lines to accept equivalent orderings (e.g. commutative forms). Add reusable when a token can be placed more than once. Bank tokens and slots render as rich text, so "$f(a)$" is a perfectly good token.
Without template, the slots are a bare row and slots defaults to the length of the first answer. With template, the slots sit inline in the text you write, which turns the same exercise into an expression builder or a fill-in-the-blank sentence. Mark each blank with ___ (three or more underscores). Underscores inside a $...$ span are left alone, so $x_1$ is safe — which also means a blank cannot go inside a math group like x^{...}; make the whole group a bank token instead. When a template is present it decides the slot count, so leave slots off.
build { ask "..." bank: ["tok", "tok", ...] answer: ["tok", ...] [answer: ["...alt ordering..."]] [template: "text with ___ blanks"] [slots: <n>] [reusable] }
| prop | type | description |
|---|---|---|
| bank | string[] | the tokens the learner can place |
| answer | string[] | an accepted sequence (repeatable) |
| template | string | text the slots sit inside; each ___ outside math is a slot |
| slots | number | number of slots (defaults to first answer length; unused with a template) |
| reusable | flag | allow a token to be used more than once |
Example
hotspot#
Tap the right spot directly on the slide's scene, rather than picking from a list or typing a value. target is a rect or circle region in scene (data) coordinates — a tap inside it is correct. The tapped point is marked on the scene; miss is shown instead of the usual explanation when the tap lands outside the target.
hotspot { ask "..." target rect (x, y) { w: <n>, h: <n> } [target circle (x, y) { r: <n> }] [miss "..."] }
| prop | type | description |
|---|---|---|
| target* | rect | circle | the hit region, in scene coordinates |
| miss | string | feedback shown when the tap misses |
Example
sketch#
Draw directly on the slide's scene — a freehand curve, a handful of tapped points, or a dragged straight line. curve and points check against near landmark points (the drawn shape must pass within tol of each); curve is checked as a connected stroke, points as independent taps. line checks the drawn segment's slope against slope (within slopeTol) and that it passes within tol of through. Pairs naturally with a reveal { ... } block for predict-then-reveal.
sketch curve { ask "..." near (x, y) [near (x, y) ...] [tol: <n>] } sketch points { ask "..." near (x, y) [near (x, y) ...] [tol: <n>] } sketch line { ask "..." through (x, y) slope: <n> [tol: <n>] [slopeTol: <n>] }
| prop | type | description |
|---|---|---|
| near | (number, number) | a landmark point (repeatable) |
| through | (number, number) | point the line must pass near (line mode) |
| slope* | number | expected slope (line mode) |
| tol | number | position tolerance, in scene units |
| slopeTol | number | slope tolerance (line mode, default 0.5) |
Example
match#
Tap-left-then-tap-right pairing. Tap a left item, then tap the right item it belongs with — at least two pair lines are required. decoy adds extra right-side entries that never pair with anything, making the match harder to guess. The right column is shuffled (seeded off the left texts, so it stays put across re-renders of the same exercise).
match { ask "..." pair "left" -> "right" [pair "left2" -> "right2" ...] [decoy "extra wrong right"] [hint "..."] [! "explanation"] }
| prop | type | description |
|---|---|---|
| pair | "left" -> "right" | a correct pairing (repeatable, min 2) |
| decoy | string | an extra unpaired right-side option (repeatable) |
Example
Tap one on the left, then its partner on the right. They turn the same color. Tap either one again to undo.
Match with
order#
Put a shuffled list of items back into the right order. The item lines are declared in their correct sequence — that's both the answer and the source of the tokens shown (shuffled) to the learner. decoy adds extra tokens that don't belong in the sequence at all. Like build, but without the operator/operand token styling and without separate bank:/answer: arrays — there is exactly one correct order.
order { ask "..." item "..." [item "..." ...] [decoy "..."] [hint "..."] [! "explanation"] }
| prop | type | description |
|---|---|---|
| item | string | a step in the correct sequence (repeatable, min 2) |
| decoy | string | an extra token that isn't part of the sequence (repeatable) |
Example
sort#
Drop each item into the bin it belongs to. Declare at least two bin groups; every item listed under a bin is shown in a shuffled tray and has to be put back. Reach for this when the lesson is about telling cases apart (which rule opens a derivative, which kind of discontinuity a function has), where a multiple choice only ever probes one case at a time. An item may appear in exactly one bin, or the compiler refuses to build, since there would be no single right answer. Items render as rich text, so "$x^2\sin x$" is a fine item.
sort { ask "..." bin "Label": ["item", "item", ...] bin "Other label": ["item", ...] [hint "..."] [! "explanation"] }
| prop | type | description |
|---|---|---|
| bin | string: string[] | a labelled bin and the items belonging in it (repeatable, min 2) |
Example
table#
Fill in the blanks of a function table. Each row: line lists one value per column — plain numbers are given, blank(<answer>) marks a cell the learner fills in (the argument is the expected value, and folds from an expression like any other answer). Every row needs the same number of cells; at least one cell across the whole table must be a blank(...). tolerance applies to every blank. Correctness is shown per-blank plus an overall "X / N correct" count — partial credit is surfaced but never blocks Continue, same as every other exercise.
table { ask "..." [header: ["col", "col", ...]] row: <value|blank(answer)>, <value|blank(answer)>, ... [row: ...] [tolerance: <n>] }
| prop | type | description |
|---|---|---|
| header | string[] | column headings (optional) |
| row* | value, value, ... | one row — mix plain numbers and blank(answer) cells (repeatable) |
| tolerance | number | how far off a blank can be (default ~0) |
Example
| x | f(x) |
|---|---|
| -2 | |
| -1 | |
| 0 | |
| 1 | |
| 2 |