---
name: quest-challenge-types
desc: "Bảng chọn DẠNG thử thách quest theo token rubric 17 + schema chính xác từng dạng (trích từ code EngramQuest, không phải phỏng đoán)"
llm_managed: true
---

# Dạng thử thách quest — chọn theo TOKEN, không mặc định `quiz`

> Lazy-load từ `curriculum` (scaffold quest) và `study` §QUEST-UPDATE. Schema dưới đây **trích thẳng từ code** `src/quest/helpers.js` + `src/quest/modal.js` của EngramQuest — sai một field là challenge render ra **màn hình trắng KHÔNG báo lỗi**.

## Vì sao không dùng mãi `quiz`

Plugin hỗ trợ 17 dạng; quest thật trước 2026-07-20 dùng **11/11 node đều `quiz`**. Hai cái giá:

1. **Nhàm** — cùng một nhịp bấm A/B/C/D suốt.
2. **Đo sai** — trắc nghiệm 4 đáp án là **nhận diện** (đáp án nằm sẵn trước mắt), không phải **truy hồi**. Nó là bài DỄ NHẤT trong 17 dạng. Tick `mechanism` vì chọn đúng 1 trong 4 là tick non.

## Bảng chọn dạng theo token

| Token cần đo | Dạng nên dùng | Vì sao hợp |
|---|---|---|
| `mechanism` | `order`, `chain`, `timeline` | sắp đúng chuỗi bước mới là hiểu cơ chế |
| `discrimination` | `match`, `truefalse` | nối/soi cặp dễ lẫn ép phân biệt thật |
| `essence`, `definition` | `cloze` | điền chỗ trống = truy hồi, không nhận diện |
| `teach-back`, `application` | `text` / `input` | tự gõ câu trả lời bằng lời mình |
| `application` (áp lực) | `countdown` | dùng được dưới sức ép mới là dùng được |
| `metacognition` | **`auction`** | cược trước khi trả lời = tự đánh giá độ chắc; cược to mà sai = đang ảo tưởng. **Đây là cách duy nhất đo được token này.** |
| `consequences`, `root-cause` | `quiz` (có `explanation` tốt) | vẫn hợp — quiz không xấu, chỉ đừng dùng độc canh |

**Luật chọn:** mỗi node nên có **≥2 dạng khác nhau** giữa các câu; cả quest **không quá 60% câu là `quiz`**.

## ⚠️ Ràng buộc CỨNG: dạng nào khai được token ở đâu

Đây là chỗ dễ sai nhất, và nó quyết định cách viết quest:

| Nhóm | Dạng | Khai `token` ở đâu |
|---|---|---|
| **Multi-question** (dùng `questions_json`) | `quiz` `truefalse` `countdown` `auction` `cloze` `text`/`input` `image-quiz` | trong **từng item JSON**: `{"token":"mechanism", "q":...}` |
| **Single-question** (dựa mảng riêng, KHÔNG chạy qua `questions_json`) | `order` `match` `chain` `timeline` `snapshot` `memory-palace` | ở **cấp challenge**: dòng `token: mechanism` |

Nhóm 2 không dùng được `questions_json` vì `singleChallenge` không sinh lại `items`/`pairs`/`slots`/`chain_items` cho từng câu con — mọi câu sẽ dùng chung mảng cấp challenge, vô nghĩa.

> **Bẫy lịch sử (đã sửa 2026-07-20):** parser YAML là parser **thủ công dòng-đơn**, chỉ nhận đúng danh sách prefix trong `challengeKeys`. Trước đây `token:` KHÔNG có trong danh sách → khai nó ở cấp challenge làm `inChallenge = false`, **mọi dòng sau bị hiểu thành field của node** → challenge mất `items`/`answer` → render trắng. Đã thêm `token:` vào parser + test hồi quy. Bài học chung: **đừng thêm field lạ vào block `challenge:`** — field không có trong `challengeKeys` sẽ ngắt block.

## Schema từng dạng (tối thiểu chạy được)

### `order` — sắp thứ tự · đo `mechanism`
```yaml
challenge:
  type: order
  token: mechanism
  question: Sắp xếp vòng đời request theo đúng thứ tự.
  items: [Response, Request, Routing]
  answer: [1, 2, 0]
```
- `answer` = **thứ tự CLICK theo index của `items`** (ở đây: bấm Request → Routing → Response).
- `items` **KHÔNG bị xáo** khi hiển thị → **phải tự khai `items` đã trộn sẵn**, nếu không đáp án chính là thứ tự nhìn thấy.
- Bấm sai không mất gì, chip vẫn đó, bấm lại được → **luôn kết thúc thành công**; sai chỉ làm mất `firstTryCorrect` (tức mất điểm token).

### `match` — nối cặp · đo `discrimination`
```yaml
challenge:
  type: match
  token: discrimination
  pairs:
    - [HTTP, Giao thức truyền siêu văn bản]
    - [TCP, Giao thức tầng vận chuyển]
```
- **Trường hợp DUY NHẤT dùng list nhiều dòng.** Phải có dòng `pairs:` đứng riêng rồi mỗi cặp một dòng `- [trái, phải]`.
- **Không có `answer`** — đáp án nằm trong cấu trúc cặp. Cột phải tự xáo khi hiển thị.
- `question` **bị bỏ qua** (header cố định), đừng phí công viết.

### `cloze` — điền chỗ trống · đo `essence`/`definition`
```yaml
challenge:
  type: cloze
  token: essence
  sentence: Metadata là dữ liệu {{c1::mô tả}} một đối tượng, không phải bản thân đối tượng.
```
- Cú pháp Anki `{{c1::đáp án}}`. Nhiều `{{cN}}` vẫn chỉ có **một ô nhập**, gõ trúng bất kỳ cái nào là qua → muốn kiểm nhiều chỗ độc lập thì tách thành nhiều câu.

### `text` / `input` — tự gõ · đo `teach-back`/`application`
```yaml
challenge:
  type: text
  token: teach-back
  prompt: Giải thích cho người mới, vì sao property phải nằm ở frontmatter?
  keywords: [máy biết trước vị trí, tên field cố định]
```
- Hai type này **y hệt nhau**.
- **Chấm bằng substring, không phân biệt hoa thường**: đúng nếu câu trả lời **CHỨA** bất kỳ keyword nào. Nên keyword **phải đủ đặc trưng** — `keywords: [ADN]` sẽ chấp nhận cả *"tôi chẳng nhớ gì về ADN"*.
- Không có keyword hợp lệ nào → **không đáp án nào đúng được**, người học kẹt.

### `auction` — cược điểm · đo `metacognition`
```yaml
challenge:
  type: auction
  token: metacognition
  question: Dataview đọc kiểu dữ liệu từ đâu?
  options: [types.json của Obsidian, Format của giá trị thật, Khai báo trong .base]
  answer: 1
  coins: 100
```
- `answer` = **index số**, không phải chuỗi. `coins` mặc định 100, cược từ 5 đến min(số dư, 80).
- Đọc kết quả: cược to + sai = tự tin sai chỗ → đó chính là tín hiệu `metacognition` cần bắt.

### `chain` — chuỗi có áp lực · đo `mechanism`
```yaml
challenge:
  type: chain
  token: mechanism
  chain_items: [Kết tủa, Bay hơi, Ngưng tụ]
  answer: [1, 2, 0]
  timer: 20
```
- Khác `order` ở chỗ **CÓ thất bại thật**: items **được xáo**, sai 3 lần hoặc hết giờ → fail.

### `timeline` — xếp vào mốc · đo `mechanism`/`consequences`
```yaml
challenge:
  type: timeline
  token: mechanism
  slots: [Bước 1, Bước 2, Bước 3]
  events: [Ghi vào đĩa, Nhận request, Dựng index RAM]
  answer: [1, 2, 0]
```
- `answer[i]` = index trong `events` thuộc về `slots[i]`. Chấm một lần, phải đúng **toàn bộ**.

### `countdown` — trắc nghiệm có giờ · đo `application`
```yaml
challenge:
  type: countdown
  token: application
  question: Cần lọc note theo ngày — property phải kiểu gì?
  options: [Text, Date đúng ISO, Number]
  answer: 1
  timer: 15
```
- `answer` phải **trong phạm vi** `options`, ngoài phạm vi → **TypeError lúc hết giờ**.

### `truefalse` — đúng/sai · đo `discrimination`
```yaml
challenge:
  type: truefalse
  token: discrimination
  statement: Xoá một Base sẽ xoá luôn dữ liệu trong note gốc.
  answer: false
```
- Dùng `statement:` **không phải** `question:` (dùng nhầm → đề trống). `answer` viết thường `true`/`false`.

### `snapshot` — nhìn nhanh rồi nhớ · đo `definition` (bộ danh sách phải thuộc)
```yaml
challenge:
  type: snapshot
  token: definition
  snapshot_items: [Text, Number, Date, Checkbox, List, Link]
  snapshot_time: 5
  question: Obsidian có mấy kiểu dữ liệu property cơ bản?
  options: ["4", "6", "8"]
  answer: 1
```
- Flash `snapshot_items` trong `snapshot_time` giây rồi che, mới hỏi. `snapshot_time` mặc định 4.
- **Hợp khi** bài có bộ danh sách hữu hạn phải thuộc (6 kiểu dữ liệu, 4 loại view…). **Không hợp** với khái niệm trừu tượng — nhớ mặt chữ không phải hiểu.

### `memory-palace` — gán nhãn ↔ vai trò · đo `connection`
```yaml
challenge:
  type: memory-palace
  token: connection
  palace_items: [Frontmatter, Inline field, Index RAM]
  palace_descs: [Metadata cho CẢ note, Metadata cho TỪNG dòng, Nơi Dataview tra cứu nhanh]
  palace_time: 12
  question: Thành phần nào lo việc tra cứu nhanh?
  answer: Index RAM
```
- `answer` là **CHUỖI khớp đúng một phần tử `palace_items`**, KHÔNG phải index (dùng số → không thẻ nào đúng).
- `palace_descs` song song theo index với `palace_items`.

### `image-quiz` / `image` — đọc sơ đồ · đo `mechanism`/`discrimination`
```yaml
challenge:
  type: image-quiz
  token: mechanism
  image: Excalidraw/so-do-luong-du-lieu.png
  question: Ở bước nào dữ liệu được serialize?
  options: [Đĩa → RAM, RAM → Đĩa, Trong RAM]
  answer: 1
```
- Hai type này **y hệt nhau**. Có `options` → trắc nghiệm; có `keywords` → gõ tự do; không có gì → click vùng ảnh (toạ độ **tỉ lệ 0–1**, mặc định 0 nên gần như không bấm trúng — tránh chế độ này).
- ⚠️ **Cần FILE ẢNH THẬT** (.png/.jpg). Vault hiện có **32 file Excalidraw nhưng chỉ 3 ảnh export** — `.excalidraw.md` KHÔNG dùng trực tiếp được, phải export PNG trước. Sơ đồ mermaid trong `so-do-<domain>.md` cũng không phải ảnh.

### `image-occlusion` — che vùng trên sơ đồ · đo `definition`/`mechanism`
```yaml
challenge:
  type: image-occlusion
  token: mechanism
  image: Excalidraw/so-do-luong-du-lieu.png
  prompt: Ô bị che là bước nào?
  region_x: 240
  region_y: 120
  region_width: 180
  region_height: 90
  answer: Deserialize
```
- `region_*` ở đây là **PIXEL** theo kích thước tự nhiên của ảnh (khác `image-quiz` dùng tỉ lệ 0–1 — **cùng tên field, khác đơn vị hoàn toàn**). Hoặc dùng bộ `region_*_pct` (0–100).
- **Đây là dạng hợp nhất với vault này về lý thuyết** — ôn lại chính sơ đồ mình vẽ ở L1. Nhưng chặn bởi ràng buộc ảnh ở trên, và phải đo toạ độ pixel tay.

### `iframe` — HTML tương tác tự viết · đo `application`/`create`
```yaml
challenge:
  type: iframe
  token: application
  html: engram-quest/html/quest-x/sim-1.html
  height: 520
```
- `html` là **đường dẫn file** trong vault, không phải nội dung HTML. Trang tự chấm rồi gửi `postMessage {type:"engram-quest-solved", score:0..100}`.
- **Mạnh nhất** (làm được sandbox, mô phỏng, kéo thả bất kỳ) nhưng **tốn công nhất**, và phần lớn nhu cầu đó đã có Lesson Academy lo. Chỉ dùng khi bài cần một mô phỏng mà không dạng nào khác thay được.

## Bảng quyết định nhanh — chọn trong 17 dạng

| Nhóm | Dạng | Dùng khi | Chi phí |
|---|---|---|---|
| **Mặc định, luôn dùng được** | `quiz` `truefalse` `order` `match` `cloze` `text` `countdown` `chain` `timeline` `auction` | mọi bài — chỉ cần chữ | thấp |
| **Cần bộ danh sách hữu hạn** | `snapshot` `memory-palace` | bài có 4–8 mục phải thuộc | thấp, nhưng dễ thành học vẹt |
| **Cần FILE ẢNH** | `image-quiz` `image` `image-occlusion` | có sơ đồ đã export PNG | trung bình — phải export + đo toạ độ tay |
| **Cần viết HTML** | `iframe` | cần mô phỏng thật | cao — cân nhắc Lesson Academy trước |

> **Không có dạng nào bị cấm.** 10 dạng nhóm đầu dùng được ngay với mọi bài; 7 dạng còn lại có điều kiện đi kèm — đọc cột "Dùng khi" rồi tự quyết, đừng bỏ qua chỉ vì lạ.

## Cạm bẫy cú pháp chung (mọi dạng)

1. **Mọi field một dòng.** Không block scalar, không YAML list nhiều dòng — **trừ `pairs:`**.
2. **Mảng phải inline `[a, b, c]`**, và giá trị **không được chứa dấu phẩy hay `]`** (parser cắt theo dấu phẩy trong cặp `[]` đầu tiên).
3. **Đừng thêm field lạ vào `challenge:`** — không có trong `challengeKeys` là ngắt block, mọi dòng sau parse sai.
4. `questions_json` phải nằm **trọn trên MỘT dòng**; JSON lỗi → **im lặng** trả `[]` rồi tụt về single-question mode.
5. `answer: 1.5` → thành **chuỗi** `"1.5"`, so index luôn sai. `answer: 007` → thành `7`.
6. Sai guard = **màn hình trắng, không báo lỗi**. Ví dụ `type: quiz` mà quên `options:`.

## Checklist trước khi ghi quest

- [ ] Mỗi câu/challenge có `token` **đúng chỗ** (bảng ràng buộc ở trên)
- [ ] Cả quest **≤60% câu là `quiz`**; mỗi node có ≥2 dạng
- [ ] Có ít nhất 1 câu `text`/`cloze` (truy hồi thật) và 1 câu `auction` (đo metacognition) mỗi khi domain đã có ≥2 bài ≥🟢
- [ ] `order`/`chain`/`timeline`: đã khai `answer`, và `items` đã trộn sẵn (với `order`)
- [ ] `text`/`cloze`: keyword **đặc trưng**, không phải chuỗi con của từ thông dụng
- [ ] Mảng inline, giá trị không chứa dấu phẩy
