Fact-Challenge Anatomy
How facts become challenges in Eko. Six concepts across five layers.
Overview
CONTENT LAYER what we know about the world
|
CHALLENGE CONTENT pre-generated AI challenge material per style
|
PRESENTATION LAYER how we frame the challenge
|
JUNCTION LAYER what goes with what (curated links)
|
SESSION LAYER the actual gameplay
The Six Concepts
1. Fact Record
The atomic unit. A single piece of knowledge stored in fact_records.
| Field | Purpose | Example |
|---|---|---|
title | Descriptive, factual label. Internal reference and feed card heading | "Prince's Multi-Instrument Mastery at Paisley Park" |
facts | Structured JSONB data. Schema-validated key-value pairs | { instruments: 27, studio: "Paisley Park", album: "Purple Rain" } |
context | 4-8 sentence narrative following Hook, Story, Connection structure | "Prince didn't just sing Purple Rain..." |
challenge_title | Theatrical, cinematic curiosity hook. What the user sees | "Twenty-Seven Instruments, One Take, Zero Help" |
notability_score | 0.0-1.0 AI-assessed score of how notable this fact is | 0.85 |
notability_reason | One-sentence explanation of why this fact matters | "Multi-instrumentalist who played every part on debut album" |
Title vs Challenge Title: The title is Wikipedia-style (factual, searchable). The challenge_title is movie-poster-style (theatrical, curiosity-provoking). Both describe the same fact; they serve different audiences.
2. Context (The Setup Layer)
The context field is the most important piece of writing on each fact. It follows the structure defined in CHALLENGE_TONE.md:
- Hook (1-2 sentences) - A surprising, specific detail that draws you in
- Story (2-4 sentences) - The backstory with specific numbers, dates, places, names
- Connection (1-2 sentences) - Links the fact to something the reader already knows
Good context reads like a passionate friend sharing something at dinner. Bad context reads like a textbook definition.
2b. Pre-Generated Challenge Content
Stored in fact_challenge_content. AI-generated content per fact-style combination, following the voice rules in challenge-content.md.
| Field | Purpose | Example |
|---|---|---|
challenge_title | Per-challenge theatrical hook (may differ from fact-level) | "The Studio Where One Man Became an Orchestra" |
setup_text | Backstory that shares context freely (2-4 sentences) | "Prince didn't just perform Purple Rain — he built every layer..." |
challenge_text | Invitation to answer, second-person address | "But do you know how many instruments he actually played?" |
reveal_correct | Warm celebration when they know it (1-3 sentences) | "That's it — and what makes it even more remarkable..." |
reveal_wrong | Kind teaching when they don't (1-3 sentences) | "Not quite — the answer is 27, and here's why that matters..." |
correct_answer | Rich narrative answer for streaming display (3-6 sentences) | "Twenty-seven instruments. Prince played every single instrument on his debut album..." |
context | 1-2 sentence preview for feed cards | "One musician played every instrument on a debut album..." |
style_data | Style-specific structured data (options, gaps, clues) | { options: [...], is_correct: true } |
target_fact_key | Which fact field this challenge tests | "instruments" |
difficulty | 1 (easy recall) to 5 (expert synthesis) | 2 |
image_url | Per-challenge image (inherited or independently resolved) | "https://..." |
image_source | Image provider name | "wikipedia" |
Why a separate table? Challenge content is large (~500 chars x 5 fields x 6 styles per fact), not needed for feed queries, and can be regenerated independently of fact_records. Each challenge row can also carry its own image (inherited from the parent fact or independently resolved via RESOLVE_CHALLENGE_IMAGE).
The correct_answer field is the storytelling payoff — a multi-sentence narrative designed for animated streaming display on challenge detail pages. It goes beyond stating the answer to tell the story of why the answer matters. See CQ-008 in challenge-content.md.
3. Challenge Format (8 Game Modes)
Stored in challenge_formats. Each format defines a type of knowledge test with its own tone, personality, and display configuration.
| Slug | Display Name | Knowledge Type | Description |
|---|---|---|---|
big_fan_of | Big Fan Of | Depth | Prove deep knowledge of a subject |
know_a_lot_about | Know A Lot About | Breadth | Demonstrate wide-ranging knowledge |
repeat_after_me | Repeat After Me | Recall | Fill in missing details from memory |
good_with_dates | Good With Dates | Temporal | Date and timeline challenges |
degrees_of_separation | Degrees of Separation | Connections | Link seemingly unrelated facts |
used_to_work_there | Used To Work There | Insider | Industry/organizational knowledge |
partial_pictures | Partial Pictures | Visual | Identify from progressive image reveals |
originators | Originators | Attribution | Match creations to their creators |
Each format row also carries: tagline, tone, supportsConversational, isLongForm, iconName, color, displayOrder, and isActive — providing full UI configurability without code changes.
Formats map to URL routes: /challenges/{formatSlug}/{topicSlug}.
4. Challenge Style (8 UI Mechanics)
Stored as values in challenge_format_styles. Each style defines how the question is physically presented.
| Style | UI Mechanic |
|---|---|
multiple_choice | Pick from A/B/C/D options |
free_text | Type your answer |
fill_the_gap | Complete the blank in a sentence |
direct_question | Answer a specific question |
statement_blank | Fill in a statement ("Prince played ___ instruments") |
reverse_lookup | Identify who/what from a description |
conversational | Multi-turn AI dialogue |
progressive_image_reveal | Image unblurs progressively |
Format vs Style: Format is the game concept (what kind of knowledge?). Style is the UI pattern (how do we ask?). One format supports multiple styles. big_fan_of works as multiple choice, free text, or conversational. partial_pictures only works with progressive_image_reveal.
5. Challenge Session
The gameplay instance. Created when a user starts a challenge. Stored in challenge_sessions.
| Field | Purpose |
|---|---|
challenge_format_id | Which format (game mode) |
topic_category_id | Which topic |
conversation_history | Array of turns: { role, content, factRecordId?, score?, timestamp } |
cumulative_score | Running score across turns |
turn_count / max_turns | Progress through the session |
Individual responses are tracked in card_interactions with a continuous score (0.0-1.0).
Junction Tables (Curated Links)
Two junction tables control which combinations are valid. These are intentionally selective, not universal.
challenge_format_styles
Links formats to their supported styles. Each format has a default style.
big_fan_of -> [multiple_choice*, free_text, conversational]
know_a_lot_about -> [free_text*, multiple_choice, conversational]
repeat_after_me -> [fill_the_gap*, statement_blank, multiple_choice]
good_with_dates -> [direct_question*, multiple_choice, free_text]
degrees_of_sep... -> [conversational*, free_text]
used_to_work_... -> [multiple_choice*, reverse_lookup, free_text]
partial_pictures -> [progressive_image_reveal*, multiple_choice]
originators -> [reverse_lookup*, multiple_choice, free_text]
(* = default style)
challenge_format_topics
Links formats to topic categories. Not every format works for every topic.
Examples:
partial_pictureslinks to visual topics (art, architecture, nature)good_with_dateslinks to temporal topics (history, science, sports)big_fan_oflinks to most topics (broad applicability)
User Flow
When a user navigates to /challenges/big-fan-of/music:
- Format resolves from URL slug:
big_fan_of - Topic resolves from URL slug:
musiccategory - Facts are pulled where
topic_category_idmatches music AND the format is linked to music viachallenge_format_topics - Style is chosen (format's default or user-selected from
challenge_format_styles) - Session is created in
challenge_sessions - The UI renders: challenge_title as the hook, context as the backstory, and the style determines the interaction pattern
Schema Reference
| Table | Purpose |
|---|---|
fact_records | Content: the facts themselves |
fact_challenge_content | Pre-generated challenge content per fact-style-difficulty (with per-challenge images) |
fact_record_schemas | Defines valid fact_keys per topic schema |
topic_categories | Hierarchical topic tree |
news_sources | Raw articles from news APIs (with optional full_content from v2 providers) |
stories | Clustered article groups representing a single event |
challenge_formats | The 8 game modes |
challenge_format_styles | Format-to-style junction (many-to-many) |
challenge_format_topics | Format-to-topic junction (many-to-many) |
challenge_sessions | Active/completed gameplay sessions |
card_interactions | Individual fact responses with scores |
score_disputes | AI-judged score dispute resolutions |
topic_category_aliases | Maps external provider category slugs to internal topics |
unmapped_category_log | Audit trail for unresolved category slugs during ingestion |
Related
- CHALLENGE_TONE.md - Voice spec for challenge titles, context, and scoring feedback
- Challenge Content Rules - System design and quality rules for pre-generated content
- Glossary - Authoritative term definitions