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.

FieldPurposeExample
titleDescriptive, factual label. Internal reference and feed card heading"Prince's Multi-Instrument Mastery at Paisley Park"
factsStructured JSONB data. Schema-validated key-value pairs{ instruments: 27, studio: "Paisley Park", album: "Purple Rain" }
context4-8 sentence narrative following Hook, Story, Connection structure"Prince didn't just sing Purple Rain..."
challenge_titleTheatrical, cinematic curiosity hook. What the user sees"Twenty-Seven Instruments, One Take, Zero Help"
notability_score0.0-1.0 AI-assessed score of how notable this fact is0.85
notability_reasonOne-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:

  1. Hook (1-2 sentences) - A surprising, specific detail that draws you in
  2. Story (2-4 sentences) - The backstory with specific numbers, dates, places, names
  3. 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.

FieldPurposeExample
challenge_titlePer-challenge theatrical hook (may differ from fact-level)"The Studio Where One Man Became an Orchestra"
setup_textBackstory that shares context freely (2-4 sentences)"Prince didn't just perform Purple Rain — he built every layer..."
challenge_textInvitation to answer, second-person address"But do you know how many instruments he actually played?"
reveal_correctWarm celebration when they know it (1-3 sentences)"That's it — and what makes it even more remarkable..."
reveal_wrongKind teaching when they don't (1-3 sentences)"Not quite — the answer is 27, and here's why that matters..."
correct_answerRich narrative answer for streaming display (3-6 sentences)"Twenty-seven instruments. Prince played every single instrument on his debut album..."
context1-2 sentence preview for feed cards"One musician played every instrument on a debut album..."
style_dataStyle-specific structured data (options, gaps, clues){ options: [...], is_correct: true }
target_fact_keyWhich fact field this challenge tests"instruments"
difficulty1 (easy recall) to 5 (expert synthesis)2
image_urlPer-challenge image (inherited or independently resolved)"https://..."
image_sourceImage 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.

SlugDisplay NameKnowledge TypeDescription
big_fan_ofBig Fan OfDepthProve deep knowledge of a subject
know_a_lot_aboutKnow A Lot AboutBreadthDemonstrate wide-ranging knowledge
repeat_after_meRepeat After MeRecallFill in missing details from memory
good_with_datesGood With DatesTemporalDate and timeline challenges
degrees_of_separationDegrees of SeparationConnectionsLink seemingly unrelated facts
used_to_work_thereUsed To Work ThereInsiderIndustry/organizational knowledge
partial_picturesPartial PicturesVisualIdentify from progressive image reveals
originatorsOriginatorsAttributionMatch 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.

StyleUI Mechanic
multiple_choicePick from A/B/C/D options
free_textType your answer
fill_the_gapComplete the blank in a sentence
direct_questionAnswer a specific question
statement_blankFill in a statement ("Prince played ___ instruments")
reverse_lookupIdentify who/what from a description
conversationalMulti-turn AI dialogue
progressive_image_revealImage 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.

FieldPurpose
challenge_format_idWhich format (game mode)
topic_category_idWhich topic
conversation_historyArray of turns: { role, content, factRecordId?, score?, timestamp }
cumulative_scoreRunning score across turns
turn_count / max_turnsProgress through the session

Individual responses are tracked in card_interactions with a continuous score (0.0-1.0).

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_pictures links to visual topics (art, architecture, nature)
  • good_with_dates links to temporal topics (history, science, sports)
  • big_fan_of links to most topics (broad applicability)

User Flow

When a user navigates to /challenges/big-fan-of/music:

  1. Format resolves from URL slug: big_fan_of
  2. Topic resolves from URL slug: music category
  3. Facts are pulled where topic_category_id matches music AND the format is linked to music via challenge_format_topics
  4. Style is chosen (format's default or user-selected from challenge_format_styles)
  5. Session is created in challenge_sessions
  6. The UI renders: challenge_title as the hook, context as the backstory, and the style determines the interaction pattern

Schema Reference

TablePurpose
fact_recordsContent: the facts themselves
fact_challenge_contentPre-generated challenge content per fact-style-difficulty (with per-challenge images)
fact_record_schemasDefines valid fact_keys per topic schema
topic_categoriesHierarchical topic tree
news_sourcesRaw articles from news APIs (with optional full_content from v2 providers)
storiesClustered article groups representing a single event
challenge_formatsThe 8 game modes
challenge_format_stylesFormat-to-style junction (many-to-many)
challenge_format_topicsFormat-to-topic junction (many-to-many)
challenge_sessionsActive/completed gameplay sessions
card_interactionsIndividual fact responses with scores
score_disputesAI-judged score dispute resolutions
topic_category_aliasesMaps external provider category slugs to internal topics
unmapped_category_logAudit trail for unresolved category slugs during ingestion