Quiz Content Pipeline — S3 Setup, Generation & Custom Trivia¶
This guide walks through the end-to-end content pipeline for quizzes in the IntelliVerseX SDK: how quiz JSON is structured, where it lives on S3, how the SDK resolves it at runtime, and how to wire up your own S3 bucket for a new game with custom daily, weekly, or entirely new quiz types.
Architecture Overview¶
flowchart LR
A[Quiz Generator<br/>Script / LLM / CMS] -->|Upload JSON| B[AWS S3 Bucket]
B -->|HTTPS GET| C[SDK Quiz Provider<br/>IVXS3QuizProvider]
C -->|Parse & Validate| D[IVXQuizSessionManager]
D -->|Events| E[Your Game UI]
B -.->|Optional CDN| F[CloudFront]
F -.->|HTTPS GET| C | Component | Role |
|---|---|
| S3 Bucket | Public (or CloudFront-fronted) object store holding quiz JSON files |
| Quiz Generator | Python script, LLM API, or CMS that produces quiz JSON and uploads to S3 |
| IVXS3QuizProvider | SDK class that fetches quiz JSON by date/id from your S3 URL |
| IVXDailyQuizService / IVXWeeklyQuizService | Higher-level services with caching, fallback, and localization |
| IVXQuizSessionManager | Drives the quiz gameplay loop (questions, answers, scoring) |
Step 1 — Create Your S3 Bucket¶
1a. Create the bucket¶
1b. Enable public read (or use CloudFront)¶
- Create a CloudFront distribution pointed at the S3 bucket.
- Create an Origin Access Identity (OAI) and attach it.
- Restrict the bucket policy to the OAI only.
- Use the CloudFront domain as your
S3_BASE_URL.
1c. Organize the folder structure¶
my-game-quiz-content/
├── daily/
│ ├── dailyquiz-2026-04-01.json
│ ├── dailyquiz-2026-04-02.json
│ ├── dailyquiz-prem-en-2026-04-01.json
│ └── dailyquiz-prem-hi-2026-04-01.json
├── weekly/
│ ├── 2026-1-14-fortune_en.json
│ ├── 2026-1-14-emoji_en.json
│ └── 2026-1-14-health_hi.json
└── custom/
├── halloween-trivia.json
├── sports-pack-1.json
└── science-challenge.json
Step 2 — Understand the JSON Schemas¶
Daily Quiz JSON¶
File naming: dailyquiz-{YYYY}-{MM}-{DD}.json
Example: dailyquiz-2026-04-01.json
{
"quizId": "dailyquiz-2026-04-01",
"generated_at": "2026-04-01T00:00:00Z",
"today_quiz": {
"topic": "General Knowledge",
"difficulty": "medium",
"generated_at": "2026-04-01T00:00:00Z",
"questions": [
{
"id": "q1",
"question": "What is the capital of France?",
"options": ["London", "Paris", "Berlin", "Madrid"],
"correct_answer": 1,
"explanation": "Paris has been the capital of France since the 10th century.",
"hints": {
"text": "It's known as the City of Light",
"audio_url": "https://my-bucket.s3.amazonaws.com/audio/hint-q1.mp3"
},
"languages": {
"en": {
"question": "What is the capital of France?",
"options": ["London", "Paris", "Berlin", "Madrid"],
"explanation": "Paris has been the capital since the 10th century.",
"hints": { "text": "City of Light" },
"question_audio_url": "",
"options_audio_urls": []
},
"hi": {
"question": "फ्रांस की राजधानी क्या है?",
"options": ["लंदन", "पेरिस", "बर्लिन", "मैड्रिड"],
"explanation": "पेरिस 10वीं शताब्दी से फ्रांस की राजधानी रहा है।",
"hints": { "text": "इसे रोशनी का शहर कहा जाता है" },
"question_audio_url": "",
"options_audio_urls": []
}
}
}
]
}
}
Fields reference
| Field | Type | Required | Description |
|---|---|---|---|
quizId | string | Yes | Unique identifier, typically dailyquiz-YYYY-MM-DD |
today_quiz.questions | array | Yes | Array of question objects (minimum 1) |
questions[].id | string | Yes | Unique question id |
questions[].question | string | Yes | Default (English) question text |
questions[].options | string[] | Yes | 2-6 answer options |
questions[].correct_answer | int | Yes | 0-based index of the correct option |
questions[].explanation | string | No | Shown after answering |
questions[].hints | object | No | { text, audio_url } |
questions[].languages | object | No | Keyed by locale code, each with localized question, options, explanation, hints |
Premium Daily Quiz JSON¶
File naming: dailyquiz-prem-{lang}-{YYYY}-{MM}-{DD}.json
{
"quizId": "dailyquiz-prem-en-2026-04-01",
"topic": "Advanced Science",
"difficulty": "hard",
"generated_at": "2026-04-01T00:00:00Z",
"premium_questions": [
{
"id": "pq1",
"question": "What particle mediates the strong nuclear force?",
"options": ["Photon", "Gluon", "W boson", "Graviton"],
"correct_answer": 1,
"explanation": "Gluons bind quarks together inside protons and neutrons."
}
]
}
Weekly Quiz JSON¶
File naming: {YYYY}-{D}-{WW}-{type}_{lang}.json
Where D = day-of-week uploaded (1-7), WW = ISO week number, type = quiz type.
Example: 2026-2-14-fortune_en.json = Week 14 of 2026, uploaded Tuesday, Fortune type, English.
{
"quizId": "fortune-2026-W14",
"topic": {
"en": "What kind of leader are you?",
"hi": "आप किस प्रकार के नेता हैं?"
},
"today_quiz": {
"questions": [
{
"id": "fq1",
"question": "When a team disagrees, you usually...",
"options": [
"Listen to all sides then decide",
"Let the majority rule",
"Step back and observe",
"Take charge immediately"
],
"correct_answer": 0,
"explanation": "Active listening is a hallmark of servant leadership.",
"languages": {
"hi": {
"question": "जब टीम असहमत होती है, तो आप आमतौर पर...",
"options": ["सबकी सुनें", "बहुमत", "देखें", "कमान लें"]
}
}
}
]
}
}
Weekly quiz types: fortune, emoji, prediction, health (or define your own).
Custom / Generic Quiz JSON¶
For any non-daily, non-weekly quiz (trivia packs, seasonal events, etc.), use the generic IVXQuizData format:
{
"quizId": "halloween-trivia-2026",
"title": "Halloween Trivia",
"description": "Spooky knowledge challenge!",
"language": "en",
"questions": [
{
"questionText": "What country originated Halloween?",
"options": ["USA", "Ireland", "Mexico", "Romania"],
"correctAnswerIndex": 1,
"category": "History",
"difficulty": 2,
"explanation": "Halloween originated from the ancient Celtic festival of Samhain in Ireland.",
"hint": "Think Celtic traditions"
}
]
}
Step 3 — Generate Quiz Content¶
Option A: LLM-based generation (recommended)¶
Use any LLM API (OpenAI, Anthropic, etc.) to auto-generate quiz content, then upload to S3.
#!/usr/bin/env python3
"""generate_daily_quiz.py — Generate and upload daily quiz JSON to S3."""
import json
import boto3
from datetime import datetime, timedelta
from openai import OpenAI
client = OpenAI() # uses OPENAI_API_KEY env var
s3 = boto3.client("s3")
BUCKET = "my-game-quiz-content"
LANGUAGES = ["en", "hi", "es", "fr"]
def generate_questions(topic: str, count: int = 10, lang: str = "en") -> list:
"""Generate quiz questions via LLM."""
prompt = f"""Generate {count} multiple-choice trivia questions about "{topic}".
Return JSON array. Each object must have:
- "id": unique string (e.g. "q1")
- "question": the question text in {lang}
- "options": exactly 4 answer strings
- "correct_answer": 0-based index of the correct option
- "explanation": 1-sentence explanation
Return ONLY the JSON array, no markdown."""
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}],
temperature=0.7,
)
return json.loads(response.choices[0].message.content)
def build_daily_quiz(date: datetime, topic: str = "General Knowledge") -> dict:
"""Build the full daily quiz JSON structure."""
questions = generate_questions(topic, count=10)
return {
"quizId": f"dailyquiz-{date:%Y-%m-%d}",
"generated_at": date.isoformat() + "Z",
"today_quiz": {
"topic": topic,
"difficulty": "medium",
"generated_at": date.isoformat() + "Z",
"questions": questions,
},
}
def upload_to_s3(quiz: dict, key: str):
"""Upload quiz JSON to S3."""
s3.put_object(
Bucket=BUCKET,
Key=key,
Body=json.dumps(quiz, indent=2, ensure_ascii=False),
ContentType="application/json",
CacheControl="public, max-age=86400",
)
print(f"Uploaded: s3://{BUCKET}/{key}")
if __name__ == "__main__":
today = datetime.utcnow()
# Generate for the next 7 days
for offset in range(7):
date = today + timedelta(days=offset)
quiz = build_daily_quiz(date, topic="General Knowledge")
key = f"daily/dailyquiz-{date:%Y-%m-%d}.json"
upload_to_s3(quiz, key)
print("Done! 7 days of quizzes uploaded.")
Option B: Nakama server-side generation¶
Add a scheduled RPC on your Nakama server that generates quizzes and stores them in S3:
// server/rpcs/quiz_generate.go
func QuizGenerateDaily(ctx context.Context, logger runtime.Logger,
db *sql.DB, nk runtime.NakamaModule,
payload string) (string, error) {
// 1. Call your LLM endpoint to generate questions
// 2. Format as the daily quiz JSON schema
// 3. Upload to S3 using AWS SDK
// 4. Return success
return `{"status":"ok","quizId":"dailyquiz-2026-04-01"}`, nil
}
Register a CRON trigger in your Nakama server config to call this RPC daily at midnight UTC.
Option C: Manual / CMS upload¶
For small studios, create quizzes manually in a spreadsheet or CMS, export to JSON, and upload via the AWS CLI:
# Upload a single daily quiz
aws s3 cp dailyquiz-2026-04-01.json \
s3://my-game-quiz-content/daily/dailyquiz-2026-04-01.json \
--content-type application/json \
--cache-control "public, max-age=86400"
# Bulk upload a folder
aws s3 sync ./quizzes/ s3://my-game-quiz-content/ \
--content-type application/json
Step 4 — Wire Your S3 URL into the SDK¶
Option A: Use IVXS3QuizProvider (generic quizzes)¶
Point the provider at your bucket's HTTPS URL:
using IntelliVerseX.Quiz;
string myBucketUrl = "https://my-game-quiz-content.s3.us-east-1.amazonaws.com/daily";
var provider = new IVXS3QuizProvider(myBucketUrl);
IVXQuizSessionManager.Instance.Initialize(provider);
// Fetch today's daily quiz
await IVXQuizSessionManager.Instance.StartDailyQuizAsync();
The provider constructs the URL as {baseUrl}/dailyquiz-{yyyy-MM-dd}.json and tries a 7-day fallback window automatically.
Option B: Use IVXDailyQuizService (daily + premium)¶
The IVXDailyQuizService has a hardcoded S3_BASE_URL. To use your own bucket, subclass or modify the constant:
using IntelliVerseX.Quiz.DailyQuiz;
// Option 1: Fork the service and change S3_BASE_URL
// In your copy of IVXDailyQuizService.cs:
private const string S3_BASE_URL = "https://my-game-quiz-content.s3.us-east-1.amazonaws.com/daily/";
// Option 2: Use IVXS3QuizProvider + IVXQuizSessionManager (recommended)
var provider = new IVXS3QuizProvider(
"https://my-game-quiz-content.s3.us-east-1.amazonaws.com/daily"
);
IVXQuizSessionManager.Instance.Initialize(provider);
Option C: Use IVXWeeklyQuizService (weekly quiz types)¶
Same approach — the service's S3_BASE_URL points to a specific bucket. Override it for your game:
// In your copy of IVXWeeklyQuizService.cs:
private const string S3_BASE_URL = "https://my-game-quiz-content.s3.us-east-1.amazonaws.com/weekly/";
Option D: Use IVXHybridQuizProvider (S3 + local fallback)¶
For offline-first experiences, use a hybrid provider:
var s3Provider = new IVXS3QuizProvider(
"https://my-game-quiz-content.s3.us-east-1.amazonaws.com/daily"
);
var localProvider = new IVXLocalQuizProvider("Quizzes/fallback_quiz");
var hybridProvider = new IVXHybridQuizProvider(s3Provider, localProvider);
IVXQuizSessionManager.Instance.Initialize(hybridProvider);
CloudFront recommended
For production, put CloudFront in front of your S3 bucket. This gives you:
- Global edge caching (faster loads worldwide)
- HTTPS with custom domain (
quiz.mygame.com) - No direct S3 public access needed
- Request analytics and WAF protection
Step 5 — Create Custom Quiz Types¶
The SDK's quiz system is designed to be extensible. You can create entirely new quiz types beyond daily and weekly.
5a. Implement IIVXQuizProvider¶
using IntelliVerseX.Quiz;
/// <summary>
/// Custom provider for themed trivia packs stored on S3.
/// </summary>
public class MyTriviaPackProvider : IIVXQuizProvider
{
private readonly string _s3BaseUrl;
public MyTriviaPackProvider(string s3BaseUrl)
{
_s3BaseUrl = s3BaseUrl.TrimEnd('/');
}
public async Task<IVXQuizResult> FetchQuizAsync(string quizId)
{
string url = $"{_s3BaseUrl}/custom/{quizId}.json";
var result = await IVXNetworkRequest.GetAsync(url, IVXRetryPolicy.Default, 15);
if (!result.IsSuccess)
return IVXQuizResult.FailureResult(result.ErrorMessage);
var quizData = JsonConvert.DeserializeObject<IVXQuizData>(result.Data);
return IVXQuizResult.SuccessResult(quizData, url);
}
public Task<IVXQuizResult> FetchQuizAsync(DateTime date)
{
string quizId = $"trivia-{date:yyyy-MM-dd}";
return FetchQuizAsync(quizId);
}
}
5b. Use it in your game¶
var provider = new MyTriviaPackProvider(
"https://my-game-quiz-content.s3.us-east-1.amazonaws.com"
);
IVXQuizSessionManager.Instance.Initialize(provider);
// Load a specific trivia pack
await IVXQuizSessionManager.Instance.StartSessionAsync("halloween-trivia-2026");
5c. Example quiz types for games¶
| Quiz Type | Naming Convention | Use Case |
|---|---|---|
| Daily trivia | dailyquiz-{YYYY-MM-DD}.json | New questions every day |
| Weekly themed | {YYYY}-{D}-{WW}-{type}_{lang}.json | Fortune, emoji, prediction, health |
| Seasonal event | event-{event-id}.json | Halloween, Christmas, game anniversary |
| Category pack | pack-{category}-{number}.json | Science Pack 1, History Pack 3 |
| Tournament | tournament-{id}.json | Competitive fixed-question set |
| User-generated | ugc-{userId}-{quizId}.json | Community-created quizzes |
Step 6 — Automate with CI/CD¶
GitHub Actions example¶
name: Generate Daily Quiz
on:
schedule:
- cron: '0 0 * * *' # Midnight UTC daily
workflow_dispatch: {}
jobs:
generate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: pip install openai boto3
- name: Generate and upload quizzes
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: us-east-1
run: python scripts/generate_daily_quiz.py
Scheduled Nakama CRON¶
If you prefer server-side generation, add a CRON job to your Nakama server that calls the generation RPC at midnight UTC daily. See the Nakama runtime docs for scheduling.
Step 7 — Validate Your Setup¶
Checklist¶
- S3 bucket created and accessible via HTTPS
- JSON files follow the correct naming convention for their quiz type
- JSON validates against the schema (use
jsonlintor a test script) - Quiz has at least 1 question with valid
correct_answerindex - All
optionsarrays have 2+ entries -
correct_answerindex is within theoptionsarray bounds - Languages object (if present) uses supported locale codes
- CloudFront distribution set up (for production)
- SDK provider points to the correct base URL
- Fallback works — delete today's file and verify the SDK loads yesterday's quiz
Quick validation script¶
#!/usr/bin/env python3
"""validate_quiz.py — Validate quiz JSON files before upload."""
import json
import sys
def validate(filepath: str) -> list[str]:
errors = []
with open(filepath) as f:
data = json.load(f)
if "quizId" not in data and "today_quiz" not in data and "questions" not in data:
errors.append("Missing quizId, today_quiz, or questions at root level")
return errors
questions = []
if "today_quiz" in data and "questions" in data["today_quiz"]:
questions = data["today_quiz"]["questions"]
elif "premium_questions" in data:
questions = data["premium_questions"]
elif "questions" in data:
questions = data["questions"]
if not questions:
errors.append("No questions found")
return errors
for i, q in enumerate(questions):
prefix = f"Question {i+1}"
if "question" not in q and "questionText" not in q:
errors.append(f"{prefix}: missing question text")
opts = q.get("options", [])
if len(opts) < 2:
errors.append(f"{prefix}: needs at least 2 options, has {len(opts)}")
correct = q.get("correct_answer", q.get("correctAnswerIndex", -1))
if correct < 0 or correct >= len(opts):
errors.append(f"{prefix}: correct_answer {correct} out of range [0, {len(opts)-1}]")
return errors
if __name__ == "__main__":
for path in sys.argv[1:]:
errs = validate(path)
if errs:
print(f"FAIL {path}:")
for e in errs:
print(f" - {e}")
sys.exit(1)
else:
print(f"OK {path}")
URL Resolution Reference¶
Daily Quiz¶
| Property | Value |
|---|---|
| Base URL | https://{bucket}.s3.{region}.amazonaws.com/{game}/daily/ |
| File pattern | dailyquiz-{YYYY}-{MM}-{DD}.json |
| Premium pattern | dailyquiz-prem-{lang}-{YYYY}-{MM}-{DD}.json |
| Fallback window | 7 days (tries previous dates) |
| Language fallback | Falls back to en if locale not found |
| SDK class | IVXS3QuizProvider or IVXDailyQuizService |
Weekly Quiz¶
| Property | Value |
|---|---|
| Base URL | https://{bucket}.s3.{region}.amazonaws.com/{game}/weekly/ |
| File pattern | {YYYY}-{D}-{WW}-{type}_{lang}.json |
| D | Day of week uploaded (1=Mon … 7=Sun) |
| WW | ISO 8601 week number (1-53) |
| type | fortune, emoji, prediction, health, or custom |
| Fallback window | 8 weeks; tries all 7 upload days per week |
| SDK class | IVXWeeklyQuizService |
Custom Quiz¶
| Property | Value |
|---|---|
| Base URL | https://{bucket}.s3.{region}.amazonaws.com/{game}/custom/ |
| File pattern | {quizId}.json (any string you pass to FetchQuizAsync) |
| SDK class | IVXS3QuizProvider or custom IIVXQuizProvider |
Supported Locales¶
The SDK normalizes language codes to one of these supported values:
en, es, ar, fr, de, pt, hi, zh, ja, ko, id, ru
Provide translations in the languages object of each question. The SDK resolves the player's language from PlayerPrefs["SelectedLanguage"] and falls back to en.
Troubleshooting¶
| Symptom | Cause | Fix |
|---|---|---|
| "No quiz available for the past 7 days" | No JSON files in the S3 path for the date range | Upload quiz files matching the naming convention |
| 403 Forbidden | Bucket policy doesn't allow public read | Update bucket policy or use CloudFront + OAI |
| 404 Not Found | Wrong file name or folder path | Verify naming convention matches exactly |
| "Failed to parse quiz JSON" | JSON is malformed or uses wrong schema | Run the validation script above |
| Wrong language shown | PlayerPrefs["SelectedLanguage"] not set or locale code mismatch | Set the language pref before loading; use supported codes |
| Quiz loads but shows 0 questions | questions array empty or nested under wrong key | Check today_quiz.questions vs questions vs premium_questions |
See Also¶
- Quiz API Reference — Full API for
IVXQuizSessionManager, providers, and data models - Quiz Module — Module overview
- Daily Quiz Demo — Sample daily quiz scene
- Weekly Quiz Demo — Sample weekly quiz scene