Document question authoring workflow
This commit is contained in:
@@ -28,12 +28,17 @@ npm run check # проверка типов (svelte-check)
|
|||||||
Чем дороже карточка, тем сложнее вопрос.
|
Чем дороже карточка, тем сложнее вопрос.
|
||||||
3. Ведущий открывает вопрос, затем «Показать ответ», и решает:
|
3. Ведущий открывает вопрос, затем «Показать ответ», и решает:
|
||||||
- **Засчитать +N** — команде идут очки;
|
- **Засчитать +N** — команде идут очки;
|
||||||
|
- **Засчитать частично +N/2** — для «почти правильного» ответа ведущий может
|
||||||
|
дать половину стоимости вопроса;
|
||||||
- **Не засчитывать** — без очков, но вопрос закрыт.
|
- **Не засчитывать** — без очков, но вопрос закрыт.
|
||||||
- Штрафов и минусов нет.
|
- Штрафов и минусов нет.
|
||||||
4. Для творческих вопросов (type `creative`) кнопка мягче:
|
4. У некоторых вопросов есть необязательная **подсказка** 💡. Ведущий может
|
||||||
|
показать её по желанию. Если подсказка показана — ответ засчитывается только
|
||||||
|
за половину стоимости: мягкая помощь без давления.
|
||||||
|
5. Для творческих вопросов (type `creative`) кнопка мягче:
|
||||||
«Засчитать творческий ответ +N».
|
«Засчитать творческий ответ +N».
|
||||||
5. Сыгранный вопрос затемняется и больше не выбирается.
|
6. Сыгранный вопрос затемняется и больше не выбирается.
|
||||||
6. Когда сыграны все вопросы — экран результата с мягкой финальной фразой.
|
7. Когда сыграны все вопросы — экран результата с мягкой финальной фразой.
|
||||||
|
|
||||||
Прогресс сохраняется автоматически — после перезагрузки игра продолжится с того же места.
|
Прогресс сохраняется автоматически — после перезагрузки игра продолжится с того же места.
|
||||||
|
|
||||||
@@ -41,6 +46,11 @@ npm run check # проверка типов (svelte-check)
|
|||||||
|
|
||||||
Архитектура задумана так, чтобы наборы добавлялись без правок UI.
|
Архитектура задумана так, чтобы наборы добавлялись без правок UI.
|
||||||
|
|
||||||
|
**Структура набора:** ровно **5 категорий**, в каждой ровно **5 вопросов** с
|
||||||
|
номиналами **100 / 200 / 300 / 500 / 1000** (от простого к сложному). Вопросы
|
||||||
|
бывают `strict` (однозначный ответ) и `creative` (творческий). У вопроса может
|
||||||
|
быть необязательное поле `optionalHint` — подсказка для ведущего.
|
||||||
|
|
||||||
1. Создайте файл `src/lib/question-sets/<name>.ts`:
|
1. Создайте файл `src/lib/question-sets/<name>.ts`:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
@@ -57,14 +67,14 @@ npm run check # проверка типов (svelte-check)
|
|||||||
title: 'Категория',
|
title: 'Категория',
|
||||||
questions: [
|
questions: [
|
||||||
{ id: 'q1', points: 100, question: '...', answer: '...', type: 'strict' },
|
{ id: 'q1', points: 100, question: '...', answer: '...', type: 'strict' },
|
||||||
{ id: 'q2', points: 200, question: '...', answer: '...', type: 'strict' },
|
{ id: 'q2', points: 200, question: '...', answer: '...', optionalHint: '...', type: 'strict' },
|
||||||
{ id: 'q3', points: 300, question: '...', answer: '...', type: 'strict' },
|
{ id: 'q3', points: 300, question: '...', answer: '...', type: 'strict' },
|
||||||
{ id: 'q4', points: 500, question: '...', answer: '...', type: 'creative' },
|
{ id: 'q4', points: 500, question: '...', answer: '...', type: 'creative' },
|
||||||
{ id: 'q5', points: 1000, question: '...', answer: '...', type: 'strict' }
|
{ id: 'q5', points: 1000, question: '...', answer: '...', type: 'strict' }
|
||||||
// ровно 5 вопросов с очками 100/200/300/500/1000
|
// ровно 5 вопросов с очками 100/200/300/500/1000
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
// …остальные категории
|
// …ещё 4 категории (всего 5)
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ Shared references for Codex and other AI coding agents in this project.
|
|||||||
| Autonomous agent harness | [autonomous-agent-harness.md](autonomous-agent-harness.md) |
|
| Autonomous agent harness | [autonomous-agent-harness.md](autonomous-agent-harness.md) |
|
||||||
| Worker loop command | [scripts/run-agent-loop.mjs](scripts/run-agent-loop.mjs) |
|
| Worker loop command | [scripts/run-agent-loop.mjs](scripts/run-agent-loop.mjs) |
|
||||||
| Pi worker wrapper | [scripts/run-pi-worker.mjs](scripts/run-pi-worker.mjs) |
|
| Pi worker wrapper | [scripts/run-pi-worker.mjs](scripts/run-pi-worker.mjs) |
|
||||||
|
| Question authoring | [question-authoring.md](question-authoring.md) |
|
||||||
|
|
||||||
Use `npm run agent:loop -- --task "<concrete task>"` when Codex delegates
|
Use `npm run agent:loop -- --task "<concrete task>"` when Codex delegates
|
||||||
implementation to Pi. Codex remains the orchestrator, verifier, reviewer, and
|
implementation to Pi. Codex remains the orchestrator, verifier, reviewer, and
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
# Question Authoring Guide (for AI agents)
|
||||||
|
|
||||||
|
How to create or extend a question set for the calm family quiz **without
|
||||||
|
touching code, state, or UI**. Question sets are pure data files.
|
||||||
|
|
||||||
|
> You are a bounded worker. Read [AGENTS.md](../../../AGENTS.md) and
|
||||||
|
> [autonomous-agent-harness.md](autonomous-agent-harness.md) first. This guide
|
||||||
|
> covers **content data only**. Do not change UI components, the game store, the
|
||||||
|
> types (beyond what content needs — usually nothing), or add backend / auth /
|
||||||
|
> editor / multiplayer unless your task explicitly asks.
|
||||||
|
|
||||||
|
## TL;DR workflow
|
||||||
|
|
||||||
|
1. Create `src/lib/question-sets/<name>.ts` exporting a `QuestionSet`.
|
||||||
|
2. Register it in `src/lib/question-sets/index.ts` (import + add to the
|
||||||
|
`questionSets` array).
|
||||||
|
3. Run `npm run check` to confirm types compile.
|
||||||
|
4. (Only if your task asks) smoke-test in the browser with `npm run dev`.
|
||||||
|
|
||||||
|
The selector screen lists sets from `questionSets` automatically — no UI edit
|
||||||
|
needed.
|
||||||
|
|
||||||
|
## Hard structure rules (non-negotiable)
|
||||||
|
|
||||||
|
Every set must match this shape **exactly**:
|
||||||
|
|
||||||
|
- **Exactly 5 categories** per set (`set.categories.length === 5`).
|
||||||
|
- **Exactly 5 questions** per category.
|
||||||
|
- **Fixed point values per category, in order:** `100, 200, 300, 500, 1000`.
|
||||||
|
- These values come from `POINT_VALUES` in `src/lib/types.ts`. Never invent
|
||||||
|
other numbers (no 400, no 750).
|
||||||
|
|
||||||
|
## Exact data shape
|
||||||
|
|
||||||
|
Mirror of `Question` / `Category` / `QuestionSet` in `src/lib/types.ts`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import type { QuestionSet } from '$lib/types';
|
||||||
|
|
||||||
|
export const mySet: QuestionSet = {
|
||||||
|
id: 'my-set', // globally unique, kebab-case, stable forever
|
||||||
|
title: 'Моя тема',
|
||||||
|
description: 'Короткое тёплое описание.',
|
||||||
|
ageRange: '6+ лет',
|
||||||
|
categories: [
|
||||||
|
{
|
||||||
|
id: 'cat-1', // unique WITHIN this set, stable
|
||||||
|
title: 'Категория',
|
||||||
|
questions: [
|
||||||
|
{ id: 'q1', points: 100, question: '…', answer: '…', type: 'strict' },
|
||||||
|
{ id: 'q2', points: 200, question: '…', answer: '…', optionalHint: '…', type: 'strict' },
|
||||||
|
{ id: 'q3', points: 300, question: '…', answer: '…', type: 'strict' },
|
||||||
|
{ id: 'q4', points: 500, question: '…', answer: '…', type: 'creative' },
|
||||||
|
{ id: 'q5', points: 1000, question: '…', answer: '…', type: 'strict' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
// …4 more categories (5 total)
|
||||||
|
]
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Unique ids (stability matters)
|
||||||
|
|
||||||
|
- `set.id` — unique across **all** sets (e.g. `animals`, `cozy`, `space`).
|
||||||
|
Lowercase kebab-case. It is persisted in `localStorage`, so never rename it
|
||||||
|
after release.
|
||||||
|
- `category.id` — unique **within the set**. Stable. Used in the played-question
|
||||||
|
key `categoryId:questionId`.
|
||||||
|
- `question.id` — unique **within the category** (typically `q1..q5`). Stable.
|
||||||
|
|
||||||
|
Changing any id after release breaks saved progress for players mid-game. Treat
|
||||||
|
ids as immutable once shipped.
|
||||||
|
|
||||||
|
## Difficulty progression (100 → 1000)
|
||||||
|
|
||||||
|
Within each category, questions run simple → hard. The number only labels it;
|
||||||
|
the **wording** carries the difficulty:
|
||||||
|
|
||||||
|
- **100** — obvious warm-up; most answer instantly.
|
||||||
|
- **200** — easy; may need a second's thought.
|
||||||
|
- **300** — solid mid-level.
|
||||||
|
- **500** — needs real knowledge or reasoning.
|
||||||
|
- **1000** — the hard capstone; satisfying to land.
|
||||||
|
|
||||||
|
## Question type: `strict` vs `creative`
|
||||||
|
|
||||||
|
From `QuestionType` in `src/lib/types.ts`:
|
||||||
|
|
||||||
|
- `strict` — there is one correct answer. Host button reads «Засчитать».
|
||||||
|
Use for facts: «Какое животное…?», «Где живут белые медведи?».
|
||||||
|
- `creative` — open-ended, many acceptable answers. Host button reads
|
||||||
|
«Засчитать творческий ответ» (softer framing, no "wrong" feeling). Use for
|
||||||
|
imagination prompts: «Придумай…», «Опиши…».
|
||||||
|
|
||||||
|
A category of facts is mostly/entirely `strict`; a category of imagination is
|
||||||
|
mostly/entirely `creative`. A set can mix freely. The example sets
|
||||||
|
(`animals.ts`, `cozy.ts`) keep roughly one `creative` category per set for
|
||||||
|
balance.
|
||||||
|
|
||||||
|
## `optionalHint` — scaffold, don't reveal
|
||||||
|
|
||||||
|
`optionalHint?: string` is an optional nudge the host can show on demand.
|
||||||
|
|
||||||
|
- **Why it costs points:** showing a hint means the answer can then only be
|
||||||
|
awarded at **half** value (see Boundaries). So it's a fair, pressure-free
|
||||||
|
trade — omit it whenever no scaffold genuinely helps.
|
||||||
|
- **Do** point at a category, sense, rhyme, or well-known association:
|
||||||
|
- «Его часто называют царём зверей.» (for «Лев»)
|
||||||
|
- «Мама у него — кобыла, а папа — жеребец.» (for «Жеребёнок»)
|
||||||
|
- **Don't** restate or obviously give away the answer:
|
||||||
|
- ❌ «Это лев.»
|
||||||
|
- ❌ «Ответ начинается на букву „Л“.»
|
||||||
|
- **For `creative` questions**, the hint can loosen imagination instead of
|
||||||
|
narrowing it («Чем нелепее и добрее — тем лучше.»).
|
||||||
|
- Keep it to **one short sentence**. Omit the field entirely when no scaffold
|
||||||
|
helps — it is genuinely optional.
|
||||||
|
|
||||||
|
## Tone: calm, warm, Russian, family-friendly
|
||||||
|
|
||||||
|
All user-facing text (`title`, `description`, `question`, `answer`,
|
||||||
|
`optionalHint`) is in **Russian** and must stay calm and family-safe:
|
||||||
|
|
||||||
|
- Soft, friendly phrasing; no words implying timers, pressure, or rush.
|
||||||
|
- No scary, crude, political, or adult content.
|
||||||
|
- Friendly to kids and grandparents together; avoid slang that ages badly.
|
||||||
|
- Match the cozy register of the existing sets (`animals.ts`, `cozy.ts`).
|
||||||
|
|
||||||
|
When unsure, read the existing sets and mirror their voice. Aim for a warm
|
||||||
|
bedtime-evening mood.
|
||||||
|
|
||||||
|
## Register the set
|
||||||
|
|
||||||
|
In `src/lib/question-sets/index.ts`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { mySet } from './my-set';
|
||||||
|
export const questionSets = [animalsSet, cozySet, mySet];
|
||||||
|
```
|
||||||
|
|
||||||
|
Order in the array is the order on the selector screen — new sets usually go
|
||||||
|
last.
|
||||||
|
|
||||||
|
## Validation checklist (run before reporting done)
|
||||||
|
|
||||||
|
- [ ] File is `src/lib/question-sets/<name>.ts`; its export is a `QuestionSet`.
|
||||||
|
- [ ] `set.id` is unique across all sets and kebab-case.
|
||||||
|
- [ ] Exactly 5 categories; each `category.id` unique within the set.
|
||||||
|
- [ ] Exactly 5 questions per category; each `question.id` unique within its category.
|
||||||
|
- [ ] Each category's points are exactly `100, 200, 300, 500, 1000` in order.
|
||||||
|
- [ ] Every question has `type` of `strict` or `creative`.
|
||||||
|
- [ ] `optionalHint` (if present) scaffolds without revealing the answer; ≤ 1 sentence.
|
||||||
|
- [ ] All copy is Russian, calm, family-friendly; matches the existing sets' tone.
|
||||||
|
- [ ] Set is imported and added to `questionSets` in `index.ts`.
|
||||||
|
- [ ] `npm run check` passes (types + svelte-check).
|
||||||
|
|
||||||
|
## Boundaries — do NOT (unless your task explicitly says so)
|
||||||
|
|
||||||
|
- Add backend, database, auth, accounts, or multiplayer.
|
||||||
|
- Add a question editor UI or any new component / route.
|
||||||
|
- Edit the game store (`src/lib/stores/game.ts`) or the `GameState` shape.
|
||||||
|
- Change scoring, hint, or partial-credit mechanics — they already exist and are
|
||||||
|
fixed by the UI/state contract:
|
||||||
|
- Full points: host «Засчитать +N».
|
||||||
|
- Half points: host «Засчитать частично +N/2» (shown only when the hint was
|
||||||
|
**not** revealed; `halfPoints = Math.round(points / 2)`).
|
||||||
|
- Hint revealed → the answer can only be awarded at half value, and the
|
||||||
|
partial-credit button is hidden.
|
||||||
|
- Refactor `src/lib/types.ts` beyond what content needs (usually nothing).
|
||||||
|
- Commit, push, deploy, or edit `AGENTS.md` and other files under
|
||||||
|
`development/_reference/ai/`.
|
||||||
|
|
||||||
|
If a needed change falls outside these boundaries, stop and report it; do not
|
||||||
|
expand scope on your own.
|
||||||
Reference in New Issue
Block a user