# Quiz pattern (tutor-driven, with embedded sandboxes)

**Audience:** course authors who want to add a self-check quiz to a chapter.
**Not student-facing.** Students see the rendered quiz HTML; you see this README.

A "self-check quiz" in the Academy is a single HTML page at `wasm-workbench/static/quiz-<course>-<topic>.html` containing five to seven questions. Each question pairs a prompt with an embedded interactive sandbox (one of the Phase-1/Phase-2 workbench interactives) so the student can verify their hand-worked answer against the live tool.

The two shipped reference quizzes are the canonical examples:

- `quiz-csa101-ch4-encoding.html`, six questions on RV32I-Lite encoding, sandboxing `rv32i-encoder.html`
- `quiz-fnd101-ch1-numbers.html`, five questions on number systems, sandboxing `multi-base-slider.html`

Read both before authoring a new one. They cover all three question types.

---

## The 5-element pattern (per question)

Each question is a `<section>` rendered by `quiz-engine.js`:

1. **Prompt**, the question text (HTML allowed; markup `<code>` for byte values; `<strong>` for emphasis).
2. **Embedded sandbox**, an `<iframe>` pointing at the relevant Phase-1/Phase-2 interactive at `/workbench/static/<name>.html`. Default height 600px desktop, 520px below 800px viewport, `loading="lazy"` so off-screen iframes don't block first paint. The student can collapse the iframe per-question (the "Toggle embedded sandbox" button) or open it in a new tab.
3. **Answer-entry mechanism**, one of three types, declared on the question object:
   - **`type: 'text'`**, single `<input>` for a hex value or assembly string. The engine compares against the canonical `answer` (and optional `answers` alt-form list) after passing both sides through your declared `normalize` function. Case-insensitive by default.
   - **`type: 'choice'`**, 4-5 buttons rendered from the `choices` array. Exactly one carries `right: true`. The engine locks the picks once any are clicked and lights the right one green / the chosen-wrong one red.
   - **`type: 'short'`**, free-text `<textarea>` with no auto-grade. Reveal-only. Student types reasoning, hits Reveal, and self-attests via Mark complete. The model answer in `explanation` is the rubric.
4. **Reveal-explanation**, a `<button>Check answer</button>` (text/choice types) or `<button>Reveal explanation</button>` (all types) opens the `explanation` block, which may itself reference the sandbox ("now use the encoder to verify your answer") and link to the chapter prose or the encoding card. Cite specific chapters / page numbers in explanations the same way the Petzold weaves cite Petzold.
5. **Progress indicator**, a sticky strip at the top of the page renders one numbered dot per question. Dots flip from gray (untouched) to blue (attempted, not yet correct) to green (correct). Click a dot to jump to that question. The "X / N correct" tally and a per-quiz Reset button live in the same strip.

State persists to `localStorage` under key `quiz:<id>:state`. **No backend. No telemetry. No grade-server.** This is non-negotiable; the page must work fully offline once cached, aligned with the "nothing leaves your device" doctrine.

---

## How to clone a quiz (for a new chapter)

### Step 1: copy the closer reference

Pick whichever shipped quiz is structurally closer to what you want to author:

```sh
cp wasm-workbench/static/quiz-csa101-ch4-encoding.html \
   wasm-workbench/static/quiz-<course>-<topic>.html
```

### Step 2: edit the top-of-file metadata

In your copy, replace the `<title>`, the `<meta name="description">`, and the `window.QUIZ` object's `id` / `title` / `subtitle` / `pedagogy` fields. The `id` becomes the localStorage namespace, so make it unique, `csa101-ch7-vm`, `sec101-ch4-xor`, and so on.

### Step 3: pick a sandbox

Set `window.QUIZ.sandboxPath` to one of the shipped interactives. **Use the sibling-relative filename** (e.g. `multi-base-slider.html`), not the deployed absolute URL (`/workbench/static/multi-base-slider.html`). The quiz HTML lives alongside the sandbox HTMLs in `wasm-workbench/static/`, so the sibling-relative form resolves in both the local dev server (where the workbench is served at `/`) and the deployed site (where it is served at `/workbench/static/`).

| Interactive                  | sandboxPath                       | Best-fit topic              |
|------------------------------|-----------------------------------|-----------------------------|
| Multi-base number slider     | `multi-base-slider.html`          | FND-101 number systems      |
| RV32I-Lite encoder/decoder   | `rv32i-encoder.html`              | CSA-101 Ch 4 encoding       |
| VM stack machine             | `vm-stack-machine.html`           | CSA-101 Ch 7 VM I           |
| VM program-flow              | `vm-program-flow.html`            | CSA-101 Ch 8 VM II          |
| Memory-map explorer          | `memory-map-explorer.html`        | CSA-101 Ch 3 / Ch 7 / Ch 8  |
| Boolean logic playground     | `boolean-logic-playground.html`   | CSA-101 Ch 1 / FND-101 Ch 3 |
| Bitplane decomposer          | `bitplane-decomposer.html`        | SPK-101 PPU work            |
| Cipher visualizer            | `cipher-visualizer.html`          | SEC-101 XOR / Caesar        |
| Diffie-Hellman animator| `dh-animator.html`                | SEC-101 key exchange        |

Per-question `sandboxPath` override is supported if a single quiz needs to switch sandboxes mid-stream (e.g. a SEC-101 quiz that alternates between cipher visualizer and DH animator). Default to one sandbox per quiz unless you have a specific reason.

### Step 4: write your questions

Replace the `questions` array. Each entry is one of:

```js
// Hand-encode / hand-convert: text input, exact-match (case-insensitive)
{
  type: 'text',
  prompt: 'Convert <code>0xFF</code> to decimal.',
  meta: 'Method: ...',                  // small italic line under the prompt
  placeholder: 'e.g. 42',
  normalize: window.QuizNorm.hex,       // or .asm, .plain, or your own (s)=>s
  answer: '255',
  answers: ['255', '0xff'],             // optional alt-form list
  explanation: '<p>...HTML...</p>',     // shown after Check or Reveal
},

// Multiple choice: one right answer
{
  type: 'choice',
  prompt: 'Which field disambiguates <code>add</code> from <code>sub</code>?',
  meta: '...',
  choices: [
    {label: 'rd',     right: false},
    {label: 'funct7', right: true},
    {label: 'rs1',    right: false},
  ],
  explanation: '<p>...</p>',
},

// Short-answer: free text, no auto-grade, reveal-only
{
  type: 'short',
  prompt: 'In your own words, why...?',
  meta: 'No auto-grade. Type, then Reveal.',
  explanation: '<p><strong>Model answer:</strong> ...</p>',
},
```

`window.QuizNorm` provides three normalize helpers callable from your question definitions:

- `QuizNorm.hex(s)`, strips `0x`/`0X` prefix and whitespace, lowercases. Use for hex-value answers.
- `QuizNorm.asm(s)`, lowercases, collapses whitespace, normalizes `space-comma-space` to bare comma. Use for assembly mnemonic answers.
- `QuizNorm.plain(s)`, lowercases, trims. Use for short single-token answers.

For exact-case matching (e.g. ASCII character `'A'` vs `'a'`), pass your own `normalize: (s) => String(s).trim()` instead.

### Step 5: write the explanations like chapter prose

The explanation is the teaching moment. Treat it the way the chapter prose treats a worked example:

- Cite specific chapters and page numbers (Petzold weaves, Nand2Tetris references, prior chapter prose) using the same conventions as the rest of the curriculum.
- Cross-link to handouts (encoding card, cheat sheets) using their `https://virtuscyberacademy.org/handouts/...` URLs so the link works from both portal and main site.
- Where the sandbox can verify, tell the student exactly what to type: "type `add x1, x2, x3` into the encoder pane above to verify."
- For short-answer questions, write a model answer that names at least three things the student should have hit; allow partial credit.

### Step 6: add the course-page link

Edit the relevant `courses/<slug>/week-N-<title>.md` and add a "Self-check quiz" entry under the Independent Practice section, pointing at the absolute portal-friendly URL:

```md
- [Self-check quiz: <topic>](https://virtuscyberacademy.org/workbench/static/quiz-<course>-<topic>.html), <N> questions, ~<minutes> minutes; predict-then-verify pattern.
```

Then re-render: `python3 courses/_pipeline/render.py --course <slug>` and rsync.

### Step 7: deploy + verify against the live URL

Per `feedback_validate_before_ship`: curl-200 is not enough. Open the page in a browser (or run a Playwright probe against the live URL) and confirm:

- All N questions render
- Each iframe loads (no broken sandbox; no Mixed-Content blocking)
- Check answer / Reveal both work for at least one question of each type your quiz uses
- localStorage progress persists across a page refresh

---

## Pitfalls and anti-patterns

- **Do not** make a question depend on a backend round-trip (no telemetry, no grade-server). The page must work fully offline.
- **Do not** put more than seven questions on a single quiz; longer quizzes lose the predict-then-verify cadence. Split into multiple quizzes per chapter if you need more coverage.
- **Do not** invent a new question type. If your need is not covered by `text` / `choice` / `short`, write the requirement up as a brief and the engine can be extended in a follow-on round; don't fork the engine inline.
- **Do not** embed the sandbox iframe at the top of the page instead of per-question. The per-question pattern matters: it puts the verification tool right next to the question that needs it, with the student's eye-line in one column.
- **Do not** style the quiz page yourself. Use `quiz-style.css`; the Palette-B chrome stays consistent across quizzes.
- **Do not** auto-grade short-answer questions. They are reveal-only by design. If you want auto-grading, structure the question as `text` (single token expected) or `choice` (constrained options).

---

## Bare-bones single-question stub (for cloning)

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>My Quiz · Virtus Cyber Academy</title>
<link rel="stylesheet" href="quiz-style.css">
</head>
<body>
<main></main>
<script>
window.QUIZ = {
  id: 'mycourse-ch-N',
  title: 'My Quiz: short topic',
  subtitle: 'One-line description.',
  pedagogy: 'Optional top callout. Tell the student how to use the sandbox.',
  sandboxLabel: 'multi-base number slider',
  sandboxPath: 'multi-base-slider.html',
  questions: [
    {
      type: 'text',
      prompt: 'Convert <code>0xFF</code> to decimal.',
      meta: 'Method: column-weights.',
      placeholder: 'e.g. 42',
      normalize: window.QuizNorm.plain,
      answer: '255',
      explanation: '<p><strong>255.</strong> Verify with the slider.</p>',
    },
  ],
  footer: '<p>Reading: <a href="#">link</a>.</p>',
};
</script>
<script src="quiz-engine.js"></script>
</body>
</html>
```

That stub is a working quiz. Save it as `quiz-mycourse-chN-topic.html`, open it in the browser, and you have a one-question self-check with progress saving and an embedded slider. Add questions one at a time from there.

---

## When this pattern is the wrong tool

- **Multi-step worked problems with intermediate state.** Use a lab worksheet instead; quizzes are single-shot.
- **Adaptive difficulty / branching.** Out of scope for Phase 2. Forward-stretch into a Phase 3 tutor-driven implementation.
- **Multi-quiz dashboards / cohort analytics.** Out of scope; aligned with the no-telemetry doctrine. If the program needs cohort analytics, the LMS Pattern-C build is the right vehicle, not the workbench.

---

*Last updated: 2026-05-13 (R-INTERACTIVE-PHASE2-14-QUIZ-WITH-SANDBOXES-2026-05-13).*
