Commit 99fcf7b1 authored by Mahmoud Aglan's avatar Mahmoud Aglan

Pre-reset backup: PHP server, technical plan

parent f8d17eae
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
<Project Path="Unity.VisualEffectGraph.Editor.csproj" /> <Project Path="Unity.VisualEffectGraph.Editor.csproj" />
<Project Path="Unity.RenderPipelines.GPUDriven.Runtime.csproj" /> <Project Path="Unity.RenderPipelines.GPUDriven.Runtime.csproj" />
<Project Path="Unity.PlasticSCM.Editor.csproj" /> <Project Path="Unity.PlasticSCM.Editor.csproj" />
<Project Path="Assembly-CSharp.csproj" />
<Project Path="Unity.ShaderGraph.Editor.csproj" /> <Project Path="Unity.ShaderGraph.Editor.csproj" />
<Project Path="Unity.RenderPipelines.Core.Runtime.csproj" /> <Project Path="Unity.RenderPipelines.Core.Runtime.csproj" />
<Project Path="Unity.Mathematics.csproj" /> <Project Path="Unity.Mathematics.csproj" />
...@@ -12,13 +11,14 @@ ...@@ -12,13 +11,14 @@
<Project Path="Unity.Multiplayer.Center.Editor.csproj" /> <Project Path="Unity.Multiplayer.Center.Editor.csproj" />
<Project Path="Unity.RenderPipelines.Core.Editor.csproj" /> <Project Path="Unity.RenderPipelines.Core.Editor.csproj" />
<Project Path="UnityEngine.UI.csproj" /> <Project Path="UnityEngine.UI.csproj" />
<Project Path="Unity.RenderPipelines.Universal.2D.Runtime.csproj" /> <Project Path="Unity.MemoryProfiler.Editor.csproj" />
<Project Path="Unity.Collections.csproj" /> <Project Path="Unity.Collections.csproj" />
<Project Path="Unity.RenderPipelines.Universal.2D.Runtime.csproj" />
<Project Path="Unity.VisualScripting.Core.Editor.csproj" /> <Project Path="Unity.VisualScripting.Core.Editor.csproj" />
<Project Path="Unity.RenderPipelines.Universal.Editor.csproj" /> <Project Path="Unity.RenderPipelines.Universal.Editor.csproj" />
<Project Path="Unity.PerformanceTesting.Editor.csproj" /> <Project Path="Unity.PerformanceTesting.Editor.csproj" />
<Project Path="Unity.Splines.csproj" />
<Project Path="UnityEngine.TestRunner.csproj" /> <Project Path="UnityEngine.TestRunner.csproj" />
<Project Path="Unity.Splines.csproj" />
<Project Path="Unity.Burst.csproj" /> <Project Path="Unity.Burst.csproj" />
<Project Path="Unity.VisualScripting.Flow.Editor.csproj" /> <Project Path="Unity.VisualScripting.Flow.Editor.csproj" />
<Project Path="Unity.Recorder.Editor.csproj" /> <Project Path="Unity.Recorder.Editor.csproj" />
...@@ -36,42 +36,43 @@ ...@@ -36,42 +36,43 @@
<Project Path="Unity.AI.Navigation.Editor.ConversionSystem.csproj" /> <Project Path="Unity.AI.Navigation.Editor.ConversionSystem.csproj" />
<Project Path="Unity.VisualScripting.State.Editor.csproj" /> <Project Path="Unity.VisualScripting.State.Editor.csproj" />
<Project Path="Unity.VisualEffectGraph.Runtime.csproj" /> <Project Path="Unity.VisualEffectGraph.Runtime.csproj" />
<Project Path="Assembly-CSharp.csproj" />
<Project Path="Unity.Burst.CodeGen.csproj" /> <Project Path="Unity.Burst.CodeGen.csproj" />
<Project Path="Unity.Cinemachine.csproj" /> <Project Path="Unity.Cinemachine.csproj" />
<Project Path="Unity.Postprocessing.Editor.csproj" /> <Project Path="Unity.Postprocessing.Editor.csproj" />
<Project Path="FlatKit.Utils.Editor.csproj" />
<Project Path="Unity.VisualScripting.Flow.csproj" /> <Project Path="Unity.VisualScripting.Flow.csproj" />
<Project Path="Unity.Splines.Editor.csproj" /> <Project Path="Unity.Splines.Editor.csproj" />
<Project Path="Unity.PerformanceTesting.csproj" /> <Project Path="Unity.PerformanceTesting.csproj" />
<Project Path="Unity.TextMeshPro.csproj" /> <Project Path="Unity.TextMeshPro.csproj" />
<Project Path="UniTask.Linq.csproj" /> <Project Path="UniTask.Linq.csproj" />
<Project Path="Unity.TextMeshPro.Editor.csproj" /> <Project Path="Unity.TextMeshPro.Editor.csproj" />
<Project Path="ExternAttributes.Editor.csproj" />
<Project Path="Unity.Burst.Editor.csproj" /> <Project Path="Unity.Burst.Editor.csproj" />
<Project Path="Nobi.UiRoundedCorners.Editor.csproj" /> <Project Path="Nobi.UiRoundedCorners.Editor.csproj" />
<Project Path="Unity.Rider.Editor.csproj" /> <Project Path="Unity.Rider.Editor.csproj" />
<Project Path="Unity.VisualScripting.State.csproj" /> <Project Path="Unity.VisualScripting.State.csproj" />
<Project Path="ExternAttributes.Editor.csproj" />
<Project Path="Unity.AI.Navigation.Updater.csproj" /> <Project Path="Unity.AI.Navigation.Updater.csproj" />
<Project Path="UnityEditor.UI.csproj" /> <Project Path="UnityEditor.UI.csproj" />
<Project Path="Unity.RenderPipelines.Universal.2D.Editor.Overrides.csproj" /> <Project Path="Unity.RenderPipelines.Universal.2D.Editor.Overrides.csproj" />
<Project Path="Unity.Postprocessing.Runtime.csproj" /> <Project Path="Unity.Postprocessing.Runtime.csproj" />
<Project Path="CFXRRuntime.csproj" />
<Project Path="LightSide.UniText.Editor.csproj" /> <Project Path="LightSide.UniText.Editor.csproj" />
<Project Path="Unity.2D.Sprite.Editor.csproj" /> <Project Path="Unity.2D.Sprite.Editor.csproj" />
<Project Path="NuGetForUnity.csproj" /> <Project Path="NuGetForUnity.csproj" />
<Project Path="Unity.AI.Navigation.Editor.csproj" /> <Project Path="Unity.AI.Navigation.Editor.csproj" />
<Project Path="CFXRDemo.csproj" /> <Project Path="Unity.MemoryProfiler.Editor.MemoryProfilerModule.csproj" />
<Project Path="Unity.Recorder.csproj" /> <Project Path="Unity.Recorder.csproj" />
<Project Path="CFXREditor.csproj" /> <Project Path="CFXREditor.csproj" />
<Project Path="CW.Common.csproj" /> <Project Path="CW.Common.csproj" />
<Project Path="Unity.Multiplayer.Center.Common.csproj" /> <Project Path="Unity.Multiplayer.Center.Common.csproj" />
<Project Path="Assembly-CSharp-firstpass.csproj" /> <Project Path="Assembly-CSharp-firstpass.csproj" />
<Project Path="Unity.InputSystem.ForUI.csproj" /> <Project Path="Unity.InputSystem.ForUI.csproj" />
<Project Path="CFXRRuntime.csproj" />
<Project Path="Unity.AI.Navigation.csproj" /> <Project Path="Unity.AI.Navigation.csproj" />
<Project Path="Unity.Collections.CodeGen.csproj" /> <Project Path="Unity.Collections.CodeGen.csproj" />
<Project Path="Assembly-CSharp-Editor.csproj" /> <Project Path="Assembly-CSharp-Editor.csproj" />
<Project Path="Unity.Mathematics.Editor.csproj" /> <Project Path="Unity.Mathematics.Editor.csproj" />
<Project Path="Unity.VisualScripting.SettingsProvider.Editor.csproj" /> <Project Path="Unity.VisualScripting.SettingsProvider.Editor.csproj" />
<Project Path="FlatKit.Utils.Editor.csproj" />
<Project Path="UnityEditor.UI.Analytics.csproj" /> <Project Path="UnityEditor.UI.Analytics.csproj" />
<Project Path="UniTask.TextMeshPro.csproj" /> <Project Path="UniTask.TextMeshPro.csproj" />
<Project Path="Unity.Settings.Editor.csproj" /> <Project Path="Unity.Settings.Editor.csproj" />
...@@ -79,20 +80,24 @@ ...@@ -79,20 +80,24 @@
<Project Path="Unity.ShaderGraph.Utilities.csproj" /> <Project Path="Unity.ShaderGraph.Utilities.csproj" />
<Project Path="Unity.VisualScripting.Shared.Editor.csproj" /> <Project Path="Unity.VisualScripting.Shared.Editor.csproj" />
<Project Path="Unity.Recorder.Base.csproj" /> <Project Path="Unity.Recorder.Base.csproj" />
<Project Path="KinoBloom.Runtime.csproj" />
<Project Path="Nobi.UiRoundedCorners.csproj" /> <Project Path="Nobi.UiRoundedCorners.csproj" />
<Project Path="Unity.Profiling.Core.csproj" />
<Project Path="Unity.EditorCoroutines.Editor.csproj" />
<Project Path="KinoBloom.Runtime.csproj" />
<Project Path="ToonyColorsPro.Demo.Editor.csproj" />
<Project Path="Unity.CollabProxy.Editor.csproj" /> <Project Path="Unity.CollabProxy.Editor.csproj" />
<Project Path="CFXR.WelcomeScreen.csproj" />
<Project Path="Unity.RenderPipeline.Universal.ShaderLibrary.csproj" /> <Project Path="Unity.RenderPipeline.Universal.ShaderLibrary.csproj" />
<Project Path="UniTask.Editor.csproj" /> <Project Path="UniTask.Editor.csproj" />
<Project Path="ToonyColorsPro.Demo.Editor.csproj" />
<Project Path="LeanPool.csproj" /> <Project Path="LeanPool.csproj" />
<Project Path="Unity.Bindings.OpenImageIO.Editor.csproj" /> <Project Path="Unity.Bindings.OpenImageIO.Editor.csproj" />
<Project Path="CFXRDemo.csproj" />
<Project Path="Unity.InputSystem.DocCodeSamples.csproj" /> <Project Path="Unity.InputSystem.DocCodeSamples.csproj" />
<Project Path="Unity.RenderPipelines.Universal.Shaders.csproj" /> <Project Path="Unity.RenderPipelines.Universal.Shaders.csproj" />
<Project Path="Unity.RenderPipelines.Core.Runtime.Shared.csproj" /> <Project Path="Unity.RenderPipelines.Core.Runtime.Shared.csproj" />
<Project Path="CFXR.WelcomeScreen.csproj" />
<Project Path="Unity.InternalAPIEngineBridge.004.csproj" /> <Project Path="Unity.InternalAPIEngineBridge.004.csproj" />
<Project Path="Unity.RenderPipelines.Core.Editor.Shared.csproj" /> <Project Path="Unity.RenderPipelines.Core.Editor.Shared.csproj" />
<Project Path="Unity.MemoryProfiler.csproj" />
<Project Path="Unity.VisualScripting.DocCodeExamples.csproj" /> <Project Path="Unity.VisualScripting.DocCodeExamples.csproj" />
<Project Path="Unity.PlasticSCM.Editor.Entities.csproj" /> <Project Path="Unity.PlasticSCM.Editor.Entities.csproj" />
<Project Path="UniTask.Addressables.csproj" /> <Project Path="UniTask.Addressables.csproj" />
......
<?php
/**
* ================================================================
* Science Street · Question Bank API v3.0
* Unity / Game Clients talk to this file ONLY
* DB: ssbook_main · New Schema (Curriculum → Subject → Chapter → Questions)
* ================================================================
*
* ENDPOINTS:
* ──────────────────────────────────────────────
* TAXONOMY
* ping Health check
* get_curricula List all active curricula
* get_grades List all grades
* get_terms List all terms
* get_subjects List subjects (filter: curriculum_id)
* get_chapters List chapters (filter: subject_id, grade_id, term_id)
* get_taxonomy_tree Full tree: curriculum → subjects → chapters (with counts)
*
* QUESTIONS
* get_mcq Fetch MCQ questions
* get_tf Fetch True/False questions
* get_cs Fetch Correct-the-Sentence questions
* get_mixed Fetch mixed question types in one call
*
* ANALYTICS (Game → Server)
* report_attempt Report a single question attempt (heatmap data)
* report_batch Report multiple attempts in one call
* report_question Player flags a question as broken/wrong/etc.
*
* STATS (Read-only)
* get_question_stats Get analytics for a specific question
*
* ================================================================
*/
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Session-Id');
error_reporting(0);
// Handle CORS preflight
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(204); exit; }
// ── Config ───────────────────────────────────────────────
define('DB_HOST', 'srv-captain--mysql-db');
define('DB_USER', 'root');
define('DB_PASS', 'Alarcade123#');
define('DB_NAME', 'ssbook_main');
define('API_VERSION', '3.0');
define('MAX_QUESTIONS', 50);
define('DEFAULT_QUESTIONS', 10);
// ── DB ───────────────────────────────────────────────────
function db(): PDO {
static $pdo = null;
if (!$pdo) {
$pdo = new PDO(
'mysql:host=' . DB_HOST . ';dbname=' . DB_NAME . ';charset=utf8mb4',
DB_USER, DB_PASS,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]
);
}
return $pdo;
}
function out(array $data): void {
$data['api_version'] = API_VERSION;
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
}
function err(string $msg, int $code = 400): void {
http_response_code($code);
out(['success' => false, 'error' => $msg]);
}
function reqInt(string $key, int $default = 0): int {
return (int)($_REQUEST[$key] ?? $default);
}
function reqStr(string $key, string $default = ''): string {
return trim($_REQUEST[$key] ?? $default);
}
function reqBool(string $key, bool $default = true): bool {
$v = $_REQUEST[$key] ?? ($default ? '1' : '0');
return $v !== '0' && $v !== 'false' && $v !== '';
}
// ── Shared: build WHERE + params for question filtering ──
function buildQuestionFilter(string $tableAlias = 'q'): array {
$where = "{$tableAlias}.is_active = 1";
$params = [];
// Direct chapter
$chid = reqInt('chapter_id');
if ($chid) {
$where .= " AND {$tableAlias}.chapter_id = ?";
$params[] = $chid;
return [$where, $params]; // chapter is the most specific, skip the rest
}
// Curriculum filter (needs JOIN chain)
$cuid = reqInt('curriculum_id');
if ($cuid) {
$where .= ' AND s.curriculum_id = ?';
$params[] = $cuid;
}
// Subject
$sid = reqInt('subject_id');
if ($sid) {
$where .= ' AND ch.subject_id = ?';
$params[] = $sid;
}
// Grade
$gid = reqInt('grade_id');
if ($gid) {
$where .= ' AND ch.grade_id = ?';
$params[] = $gid;
}
// Term
$tid = reqInt('term_id');
if ($tid) {
$where .= ' AND ch.term_id = ?';
$params[] = $tid;
}
// Difficulty
$diff = reqStr('difficulty');
if ($diff && in_array($diff, ['easy', 'medium', 'hard'])) {
$where .= " AND {$tableAlias}.difficulty = ?";
$params[] = $diff;
}
// Bloom level
$bloom = reqStr('bloom_level');
if ($bloom && in_array($bloom, ['remember', 'understand', 'apply', 'analyze', 'evaluate', 'create'])) {
$where .= " AND {$tableAlias}.bloom_level = ?";
$params[] = $bloom;
}
// Verified only
$verified = reqStr('verified_only');
if ($verified === '1') {
$where .= " AND {$tableAlias}.is_verified = 1";
}
return [$where, $params];
}
// ── Router ───────────────────────────────────────────────
$action = reqStr('action');
if (!$action) err('action parameter is required');
try {
switch ($action) {
// ════════════════════════════════════════════════════════
// PING — Health Check
// ════════════════════════════════════════════════════════
case 'ping': {
// Quick DB check
$dbOk = false;
try { db()->query('SELECT 1'); $dbOk = true; } catch (Throwable $e) {}
out([
'success' => true,
'message' => 'Science Street Question Bank API OK',
'version' => API_VERSION,
'db' => $dbOk ? 'connected' : 'error',
'time' => date('Y-m-d H:i:s'),
]);
}
// ════════════════════════════════════════════════════════
// TAXONOMY: Curricula
// Params: (none)
// Returns: list of active curricula
// ════════════════════════════════════════════════════════
case 'get_curricula': {
$rows = db()->query("
SELECT c.id, c.code, c.name_ar, c.name_en, c.question_lang,
(SELECT COUNT(DISTINCT s2.id) FROM subjects s2 WHERE s2.curriculum_id = c.id AND s2.is_active = 1) AS subject_count
FROM curricula c
WHERE c.is_active = 1
ORDER BY c.sort_order, c.name_ar
")->fetchAll();
out(['success' => true, 'count' => count($rows), 'curricula' => $rows]);
}
// ════════════════════════════════════════════════════════
// TAXONOMY: Grades
// Params: (none)
// Returns: all grades
// ════════════════════════════════════════════════════════
case 'get_grades': {
$rows = db()->query("
SELECT id, name_ar, name_en, sort_order
FROM grades ORDER BY sort_order, name_ar
")->fetchAll();
out(['success' => true, 'count' => count($rows), 'grades' => $rows]);
}
// ════════════════════════════════════════════════════════
// TAXONOMY: Terms
// Params: (none)
// Returns: all terms/semesters
// ════════════════════════════════════════════════════════
case 'get_terms': {
$rows = db()->query("
SELECT id, name_ar, name_en, sort_order
FROM terms ORDER BY sort_order, name_ar
")->fetchAll();
out(['success' => true, 'count' => count($rows), 'terms' => $rows]);
}
// ════════════════════════════════════════════════════════
// TAXONOMY: Subjects
// Params: curriculum_id (optional)
// Returns: subjects with curriculum info
// ════════════════════════════════════════════════════════
case 'get_subjects': {
$cuid = reqInt('curriculum_id');
$where = 's.is_active = 1';
$params = [];
if ($cuid) {
$where .= ' AND s.curriculum_id = ?';
$params[] = $cuid;
}
$s = db()->prepare("
SELECT s.id, s.name_ar, s.name_en, s.icon, s.color_hex,
c.id AS curriculum_id, c.code AS curriculum_code,
c.name_ar AS curriculum_ar, c.name_en AS curriculum_en,
c.question_lang
FROM subjects s
JOIN curricula c ON s.curriculum_id = c.id AND c.is_active = 1
WHERE {$where}
ORDER BY c.sort_order, s.sort_order, s.name_ar
");
$s->execute($params);
$rows = $s->fetchAll();
out(['success' => true, 'count' => count($rows), 'subjects' => $rows]);
}
// ════════════════════════════════════════════════════════
// TAXONOMY: Chapters
// Params: subject_id, grade_id, term_id (all optional but recommended)
// Returns: chapters with question counts
// ════════════════════════════════════════════════════════
case 'get_chapters': {
$sid = reqInt('subject_id');
$gid = reqInt('grade_id');
$tid = reqInt('term_id');
$cuid = reqInt('curriculum_id'); // Added this
$where = 'ch.is_active = 1';
$params = [];
if ($sid) { $where .= ' AND ch.subject_id = ?'; $params[] = $sid; }
if ($gid) { $where .= ' AND ch.grade_id = ?'; $params[] = $gid; }
if ($tid) { $where .= ' AND ch.term_id = ?'; $params[] = $tid; }
// Add filtering by curriculum
if ($cuid) {
$where .= ' AND s.curriculum_id = ?';
$params[] = $cuid;
}
$s = db()->prepare("
SELECT ch.id, ch.name_ar, ch.name_en,
s.id AS subject_id, s.name_ar AS subject_ar, s.name_en AS subject_en,
g.id AS grade_id, g.name_ar AS grade_ar, g.name_en AS grade_en,
t.id AS term_id, t.name_ar AS term_ar, t.name_en AS term_en,
c.id AS curriculum_id, c.code AS curriculum_code, c.question_lang,
(SELECT COUNT(*) FROM mcq_questions mq WHERE mq.chapter_id = ch.id AND mq.is_active = 1) AS mcq_count,
(SELECT COUNT(*) FROM tf_questions tq WHERE tq.chapter_id = ch.id AND tq.is_active = 1) AS tf_count,
(SELECT COUNT(*) FROM cs_questions cq WHERE cq.chapter_id = ch.id AND cq.is_active = 1) AS cs_count
FROM chapters ch
JOIN subjects s ON ch.subject_id = s.id
JOIN grades g ON ch.grade_id = g.id
JOIN terms t ON ch.term_id = t.id
JOIN curricula c ON s.curriculum_id = c.id
WHERE $where
ORDER BY c.sort_order, s.sort_order, g.sort_order, t.sort_order, ch.sort_order
");
$s->execute($params);
out(['success' => true, 'chapters' => $s->fetchAll()]);
}
// ════════════════════════════════════════════════════════
// TAXONOMY: Full Tree
// Params: curriculum_id (optional)
// Returns: nested curriculum → subjects → grade/term combos with question counts
// Useful for game menu / selection screens
// ════════════════════════════════════════════════════════
case 'get_taxonomy_tree': {
$cuid = reqInt('curriculum_id');
$cWhere = 'c.is_active = 1';
$cParams = [];
if ($cuid) { $cWhere .= ' AND c.id = ?'; $cParams[] = $cuid; }
$curStmt = db()->prepare("SELECT id, code, name_ar, name_en, question_lang FROM curricula c WHERE {$cWhere} ORDER BY sort_order");
$curStmt->execute($cParams);
$curricula = $curStmt->fetchAll();
$subStmt = db()->prepare("SELECT id, name_ar, name_en, icon, color_hex FROM subjects WHERE curriculum_id = ? AND is_active = 1 ORDER BY sort_order");
$chStmt = db()->prepare("
SELECT ch.id, ch.name_ar, ch.name_en, ch.grade_id, ch.term_id,
g.name_ar AS grade_ar, g.name_en AS grade_en,
t.name_ar AS term_ar, t.name_en AS term_en,
(SELECT COUNT(*) FROM mcq_questions WHERE chapter_id = ch.id AND is_active = 1) AS mcq,
(SELECT COUNT(*) FROM tf_questions WHERE chapter_id = ch.id AND is_active = 1) AS tf,
(SELECT COUNT(*) FROM cs_questions WHERE chapter_id = ch.id AND is_active = 1) AS cs
FROM chapters ch
JOIN grades g ON ch.grade_id = g.id
JOIN terms t ON ch.term_id = t.id
WHERE ch.subject_id = ? AND ch.is_active = 1
ORDER BY g.sort_order, t.sort_order, ch.sort_order
");
foreach ($curricula as &$cur) {
$subStmt->execute([$cur['id']]);
$cur['subjects'] = $subStmt->fetchAll();
foreach ($cur['subjects'] as &$sub) {
$chStmt->execute([$sub['id']]);
$chapters = $chStmt->fetchAll();
foreach ($chapters as &$ch) {
$ch['mcq'] = (int)$ch['mcq'];
$ch['tf'] = (int)$ch['tf'];
$ch['cs'] = (int)$ch['cs'];
$ch['total'] = $ch['mcq'] + $ch['tf'] + $ch['cs'];
}
$sub['chapters'] = $chapters;
$sub['total_questions'] = array_sum(array_column($chapters, 'total'));
}
$cur['total_questions'] = array_sum(array_column($cur['subjects'], 'total_questions'));
}
out(['success' => true, 'tree' => $curricula]);
}
// ════════════════════════════════════════════════════════
// GET MCQ QUESTIONS
// ─────────────────────────────────────────────────────
// Filters (all optional, combinable):
// curriculum_id, subject_id, grade_id, term_id,
// chapter_id (most specific — skips other taxonomy filters),
// difficulty (easy|medium|hard),
// bloom_level (remember|understand|apply|analyze|evaluate|create),
// verified_only (1 = only verified questions),
// count (default 10, max 50),
// shuffle (default 1)
//
// Returns: questions with full taxonomy, images, audio,
// explanation, time_limit, analytics summary
// ════════════════════════════════════════════════════════
case 'get_mcq': {
$count = min(reqInt('count', DEFAULT_QUESTIONS), MAX_QUESTIONS);
$shuffle = reqBool('shuffle');
[$where, $params] = buildQuestionFilter('q');
$order = $shuffle ? 'RAND()' : 'q.id';
$s = db()->prepare("
SELECT
q.id,
q.question_text,
q.answer1, q.answer2, q.answer3, q.answer4,
q.explanation,
q.difficulty,
q.bloom_level,
q.time_limit_s,
q.question_image, q.answer1_image, q.answer2_image,
q.answer3_image, q.answer4_image,
q.explanation_image,
q.question_audio,
q.source,
ch.id AS chapter_id,
ch.name_ar AS chapter_ar, ch.name_en AS chapter_en,
s.id AS subject_id,
s.name_ar AS subject_ar, s.name_en AS subject_en,
g.id AS grade_id,
g.name_ar AS grade_ar, g.name_en AS grade_en,
t.id AS term_id,
t.name_ar AS term_ar, t.name_en AS term_en,
c.id AS curriculum_id,
c.code AS curriculum_code,
c.question_lang,
qs.accuracy_rate,
qs.avg_time_ms,
qs.difficulty_rating AS auto_difficulty
FROM mcq_questions q
JOIN chapters ch ON q.chapter_id = ch.id
JOIN subjects s ON ch.subject_id = s.id
JOIN grades g ON ch.grade_id = g.id
JOIN terms t ON ch.term_id = t.id
JOIN curricula c ON s.curriculum_id = c.id
LEFT JOIN question_stats qs
ON qs.question_type = 'mcq' AND qs.question_id = q.id
WHERE {$where}
ORDER BY {$order}
LIMIT " . (int)$count
);
$s->execute($params);
$questions = $s->fetchAll();
if (empty($questions)) err('لا توجد أسئلة متاحة', 404);
// Cast types for Unity
foreach ($questions as &$q) {
$q['time_limit_s'] = $q['time_limit_s'] !== null ? (int)$q['time_limit_s'] : null;
$q['accuracy_rate'] = $q['accuracy_rate'] !== null ? (float)$q['accuracy_rate'] : null;
$q['avg_time_ms'] = $q['avg_time_ms'] !== null ? (int)$q['avg_time_ms'] : null;
$q['auto_difficulty'] = $q['auto_difficulty'] !== null ? (float)$q['auto_difficulty'] : null;
}
out([
'success' => true,
'type' => 'mcq',
'count' => count($questions),
'questions' => $questions,
]);
}
// ════════════════════════════════════════════════════════
// GET TF (True/False) QUESTIONS
// Same filtering as MCQ
// ════════════════════════════════════════════════════════
case 'get_tf': {
$count = min(reqInt('count', DEFAULT_QUESTIONS), MAX_QUESTIONS);
$shuffle = reqBool('shuffle');
[$where, $params] = buildQuestionFilter('q');
$order = $shuffle ? 'RAND()' : 'q.id';
$s = db()->prepare("
SELECT
q.id,
q.question_text,
q.is_true,
q.explanation,
q.difficulty,
q.bloom_level,
q.time_limit_s,
q.question_image,
q.question_audio,
q.explanation_image,
q.source,
ch.id AS chapter_id,
ch.name_ar AS chapter_ar, ch.name_en AS chapter_en,
s.id AS subject_id,
s.name_ar AS subject_ar, s.name_en AS subject_en,
g.id AS grade_id,
g.name_ar AS grade_ar, g.name_en AS grade_en,
t.id AS term_id,
t.name_ar AS term_ar, t.name_en AS term_en,
c.id AS curriculum_id,
c.code AS curriculum_code,
c.question_lang,
qs.accuracy_rate,
qs.avg_time_ms,
qs.difficulty_rating AS auto_difficulty
FROM tf_questions q
JOIN chapters ch ON q.chapter_id = ch.id
JOIN subjects s ON ch.subject_id = s.id
JOIN grades g ON ch.grade_id = g.id
JOIN terms t ON ch.term_id = t.id
JOIN curricula c ON s.curriculum_id = c.id
LEFT JOIN question_stats qs
ON qs.question_type = 'tf' AND qs.question_id = q.id
WHERE {$where}
ORDER BY {$order}
LIMIT " . (int)$count
);
$s->execute($params);
$questions = $s->fetchAll();
if (empty($questions)) err('لا توجد أسئلة متاحة', 404);
foreach ($questions as &$q) {
$q['is_true'] = (bool)(int)$q['is_true'];
$q['time_limit_s'] = $q['time_limit_s'] !== null ? (int)$q['time_limit_s'] : null;
$q['accuracy_rate'] = $q['accuracy_rate'] !== null ? (float)$q['accuracy_rate'] : null;
$q['avg_time_ms'] = $q['avg_time_ms'] !== null ? (int)$q['avg_time_ms'] : null;
$q['auto_difficulty'] = $q['auto_difficulty'] !== null ? (float)$q['auto_difficulty'] : null;
}
out([
'success' => true,
'type' => 'tf',
'count' => count($questions),
'questions' => $questions,
]);
}
// ════════════════════════════════════════════════════════
// GET CS (Correct the Sentence) QUESTIONS
// Returns words array + options array per question
// ════════════════════════════════════════════════════════
case 'get_cs': {
$count = min(reqInt('count', DEFAULT_QUESTIONS), MAX_QUESTIONS);
$shuffle = reqBool('shuffle');
[$where, $params] = buildQuestionFilter('q');
$order = $shuffle ? 'RAND()' : 'q.id';
$s = db()->prepare("
SELECT
q.id,
q.instruction_text,
q.explanation,
q.difficulty,
q.bloom_level,
q.time_limit_s,
q.question_image,
q.explanation_image,
q.source,
ch.id AS chapter_id,
ch.name_ar AS chapter_ar, ch.name_en AS chapter_en,
s.id AS subject_id,
s.name_ar AS subject_ar, s.name_en AS subject_en,
g.id AS grade_id,
g.name_ar AS grade_ar, g.name_en AS grade_en,
t.id AS term_id,
t.name_ar AS term_ar, t.name_en AS term_en,
c.id AS curriculum_id,
c.code AS curriculum_code,
c.question_lang,
qs.accuracy_rate,
qs.avg_time_ms,
qs.difficulty_rating AS auto_difficulty
FROM cs_questions q
JOIN chapters ch ON q.chapter_id = ch.id
JOIN subjects s ON ch.subject_id = s.id
JOIN grades g ON ch.grade_id = g.id
JOIN terms t ON ch.term_id = t.id
JOIN curricula c ON s.curriculum_id = c.id
LEFT JOIN question_stats qs
ON qs.question_type = 'cs' AND qs.question_id = q.id
WHERE {$where}
ORDER BY {$order}
LIMIT " . (int)$count
);
$s->execute($params);
$questions = $s->fetchAll();
if (empty($questions)) err('لا توجد أسئلة متاحة', 404);
// Prepare child queries
$wStmt = db()->prepare("
SELECT word_text, position, is_distractor
FROM cs_words WHERE question_id = ? ORDER BY position
");
$oStmt = db()->prepare("
SELECT option_text, is_correct
FROM cs_options WHERE question_id = ?
");
foreach ($questions as &$q) {
$q['time_limit_s'] = $q['time_limit_s'] !== null ? (int)$q['time_limit_s'] : null;
$q['accuracy_rate'] = $q['accuracy_rate'] !== null ? (float)$q['accuracy_rate'] : null;
$q['avg_time_ms'] = $q['avg_time_ms'] !== null ? (int)$q['avg_time_ms'] : null;
$q['auto_difficulty'] = $q['auto_difficulty'] !== null ? (float)$q['auto_difficulty'] : null;
// Words
$wStmt->execute([$q['id']]);
$words = $wStmt->fetchAll();
foreach ($words as &$w) {
$w['position'] = (int)$w['position'];
$w['is_distractor'] = (bool)(int)$w['is_distractor'];
}
$q['words'] = $words;
// Options
$oStmt->execute([$q['id']]);
$opts = $oStmt->fetchAll();
foreach ($opts as &$o) {
$o['is_correct'] = (bool)(int)$o['is_correct'];
}
if ($shuffle) shuffle($opts);
$q['options'] = $opts;
// Convenience fields
$distractor = array_filter($words, fn($w) => $w['is_distractor']);
$correct = array_filter($opts, fn($o) => $o['is_correct']);
$q['distractor_word'] = $distractor ? array_values($distractor)[0]['word_text'] : null;
$q['correct_answer'] = $correct ? array_values($correct)[0]['option_text'] : null;
// Full sentence text for display
$q['sentence'] = implode(' ', array_column($words, 'word_text'));
}
out([
'success' => true,
'type' => 'cs',
'count' => count($questions),
'questions' => $questions,
]);
}
// ════════════════════════════════════════════════════════
// GET MIXED QUESTIONS
// Returns a mix of MCQ, TF, CS in one call.
// Params: same filters + mcq_count, tf_count, cs_count
// Useful for games that mix question types.
// ════════════════════════════════════════════════════════
case 'get_mixed': {
$mcqCount = min(reqInt('mcq_count', 4), MAX_QUESTIONS);
$tfCount = min(reqInt('tf_count', 3), MAX_QUESTIONS);
$csCount = min(reqInt('cs_count', 3), MAX_QUESTIONS);
// Temporarily set count for sub-calls
$results = ['mcq' => [], 'tf' => [], 'cs' => []];
// MCQ
if ($mcqCount > 0) {
$_REQUEST['count'] = $mcqCount;
[$w, $p] = buildQuestionFilter('q');
$order = reqBool('shuffle') ? 'RAND()' : 'q.id';
$s = db()->prepare("
SELECT q.id, q.question_text, q.answer1, q.answer2, q.answer3, q.answer4,
q.explanation, q.difficulty, q.time_limit_s,
q.question_image, q.answer1_image, q.answer2_image, q.answer3_image, q.answer4_image,
ch.name_ar AS chapter_ar, g.name_ar AS grade_ar,
c.code AS curriculum_code, c.question_lang
FROM mcq_questions q
JOIN chapters ch ON q.chapter_id = ch.id
JOIN subjects s ON ch.subject_id = s.id
JOIN grades g ON ch.grade_id = g.id
JOIN terms t ON ch.term_id = t.id
JOIN curricula c ON s.curriculum_id = c.id
WHERE {$w} ORDER BY {$order} LIMIT " . (int)$mcqCount
);
$s->execute($p);
$rows = $s->fetchAll();
foreach ($rows as &$r) {
$r['_type'] = 'mcq';
$r['time_limit_s'] = $r['time_limit_s'] !== null ? (int)$r['time_limit_s'] : null;
}
$results['mcq'] = $rows;
}
// TF
if ($tfCount > 0) {
$_REQUEST['count'] = $tfCount;
[$w, $p] = buildQuestionFilter('q');
$order = reqBool('shuffle') ? 'RAND()' : 'q.id';
$s = db()->prepare("
SELECT q.id, q.question_text, q.is_true,
q.explanation, q.difficulty, q.time_limit_s,
q.question_image,
ch.name_ar AS chapter_ar, g.name_ar AS grade_ar,
c.code AS curriculum_code, c.question_lang
FROM tf_questions q
JOIN chapters ch ON q.chapter_id = ch.id
JOIN subjects s ON ch.subject_id = s.id
JOIN grades g ON ch.grade_id = g.id
JOIN terms t ON ch.term_id = t.id
JOIN curricula c ON s.curriculum_id = c.id
WHERE {$w} ORDER BY {$order} LIMIT " . (int)$tfCount
);
$s->execute($p);
$rows = $s->fetchAll();
foreach ($rows as &$r) {
$r['_type'] = 'tf';
$r['is_true'] = (bool)(int)$r['is_true'];
$r['time_limit_s'] = $r['time_limit_s'] !== null ? (int)$r['time_limit_s'] : null;
}
$results['tf'] = $rows;
}
// CS
if ($csCount > 0) {
$_REQUEST['count'] = $csCount;
[$w, $p] = buildQuestionFilter('q');
$order = reqBool('shuffle') ? 'RAND()' : 'q.id';
$s = db()->prepare("
SELECT q.id, q.instruction_text, q.explanation, q.difficulty, q.time_limit_s,
ch.name_ar AS chapter_ar, g.name_ar AS grade_ar,
c.code AS curriculum_code, c.question_lang
FROM cs_questions q
JOIN chapters ch ON q.chapter_id = ch.id
JOIN subjects s ON ch.subject_id = s.id
JOIN grades g ON ch.grade_id = g.id
JOIN terms t ON ch.term_id = t.id
JOIN curricula c ON s.curriculum_id = c.id
WHERE {$w} ORDER BY {$order} LIMIT " . (int)$csCount
);
$s->execute($p);
$csRows = $s->fetchAll();
$wStmt = db()->prepare("SELECT word_text, position, is_distractor FROM cs_words WHERE question_id=? ORDER BY position");
$oStmt = db()->prepare("SELECT option_text, is_correct FROM cs_options WHERE question_id=?");
foreach ($csRows as &$r) {
$r['_type'] = 'cs';
$r['time_limit_s'] = $r['time_limit_s'] !== null ? (int)$r['time_limit_s'] : null;
$wStmt->execute([$r['id']]);
$words = $wStmt->fetchAll();
foreach ($words as &$w) { $w['is_distractor'] = (bool)(int)$w['is_distractor']; $w['position'] = (int)$w['position']; }
$oStmt->execute([$r['id']]);
$opts = $oStmt->fetchAll();
foreach ($opts as &$o) $o['is_correct'] = (bool)(int)$o['is_correct'];
if (reqBool('shuffle')) shuffle($opts);
$r['words'] = $words;
$r['options'] = $opts;
$r['sentence'] = implode(' ', array_column($words, 'word_text'));
$dist = array_filter($words, fn($w) => $w['is_distractor']);
$corr = array_filter($opts, fn($o) => $o['is_correct']);
$r['distractor_word'] = $dist ? array_values($dist)[0]['word_text'] : null;
$r['correct_answer'] = $corr ? array_values($corr)[0]['option_text'] : null;
}
$results['cs'] = $csRows;
}
$total = count($results['mcq']) + count($results['tf']) + count($results['cs']);
if ($total === 0) err('لا توجد أسئلة متاحة', 404);
// Optionally interleave all into one array
$all = [];
foreach ($results as $arr) foreach ($arr as $q) $all[] = $q;
if (reqBool('shuffle')) shuffle($all);
out([
'success' => true,
'total' => $total,
'by_type' => $results,
'interleaved'=> $all,
]);
}
// ════════════════════════════════════════════════════════
// REPORT ATTEMPT — Single question attempt
// Called by game client AFTER each answer.
// Method: POST
// Body (form or JSON):
// question_type mcq|tf|cs REQUIRED
// question_id int REQUIRED
// session_id string REQUIRED
// is_correct 0|1 REQUIRED
// time_taken_ms int (milliseconds) REQUIRED
// is_skipped 0|1 optional (default 0)
// is_timeout 0|1 optional (default 0)
// selected_answer string optional
// correct_answer string optional
// room_id string optional
// player_id string optional
// round_in_game int optional
// total_rounds int optional
// player_streak int optional
// player_score_at_time int optional
// players_in_room int optional
// answer_rank int optional
// hint_used 0|1 optional
// power_up_used string optional
// device_type mobile|tablet|desktop|unknown optional
// os string optional
// client_version string optional
// screen_width int optional
// screen_height int optional
// ════════════════════════════════════════════════════════
case 'report_attempt': {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') err('POST required', 405);
// Parse JSON body if Content-Type is json
$input = $_POST;
if (empty($input) || empty($input['question_type'])) {
$raw = file_get_contents('php://input');
$json = json_decode($raw, true);
if (is_array($json)) $input = $json;
}
$qtype = $input['question_type'] ?? '';
$qid = (int)($input['question_id'] ?? 0);
$sessionId = trim($input['session_id'] ?? '');
$isCorrect = (int)($input['is_correct'] ?? 0);
$timeMs = (int)($input['time_taken_ms'] ?? 0);
if (!in_array($qtype, ['mcq', 'tf', 'cs'])) err('question_type must be mcq, tf, or cs');
if (!$qid) err('question_id required');
if (!$sessionId) err('session_id required');
$stmt = db()->prepare("
INSERT INTO question_attempts (
question_type, question_id, session_id,
is_correct, is_skipped, is_timeout,
selected_answer, correct_answer, time_taken_ms,
room_id, player_id,
round_in_game, total_rounds,
player_streak, player_score_at_time,
players_in_room, answer_rank,
hint_used, power_up_used,
device_type, os, client_version,
screen_width, screen_height
) VALUES (
?, ?, ?,
?, ?, ?,
?, ?, ?,
?, ?,
?, ?,
?, ?,
?, ?,
?, ?,
?, ?, ?,
?, ?
)
");
$deviceType = $input['device_type'] ?? 'unknown';
if (!in_array($deviceType, ['mobile', 'tablet', 'desktop', 'unknown'])) $deviceType = 'unknown';
$stmt->execute([
$qtype,
$qid,
$sessionId,
$isCorrect ? 1 : 0,
(int)($input['is_skipped'] ?? 0) ? 1 : 0,
(int)($input['is_timeout'] ?? 0) ? 1 : 0,
$input['selected_answer'] ?? null,
$input['correct_answer'] ?? null,
$timeMs,
$input['room_id'] ?? null,
$input['player_id'] ?? null,
($input['round_in_game'] ?? null) !== null ? (int)$input['round_in_game'] : null,
($input['total_rounds'] ?? null) !== null ? (int)$input['total_rounds'] : null,
(int)($input['player_streak'] ?? 0),
(int)($input['player_score_at_time'] ?? 0),
($input['players_in_room'] ?? null) !== null ? (int)$input['players_in_room'] : null,
($input['answer_rank'] ?? null) !== null ? (int)$input['answer_rank'] : null,
(int)($input['hint_used'] ?? 0) ? 1 : 0,
$input['power_up_used'] ?? null,
$deviceType,
$input['os'] ?? null,
$input['client_version'] ?? null,
($input['screen_width'] ?? null) !== null ? (int)$input['screen_width'] : null,
($input['screen_height'] ?? null) !== null ? (int)$input['screen_height'] : null,
]);
$attemptId = (int)db()->lastInsertId();
// ── Quick-update aggregated stats (incremental) ──
// This is a lightweight inline aggregation. For production at scale,
// replace with a queue/cron worker that processes batches.
try {
$existing = db()->prepare("
SELECT id, times_served, times_correct, times_wrong, times_skipped, times_timeout,
avg_time_ms, first_served_at
FROM question_stats
WHERE question_type = ? AND question_id = ?
");
$existing->execute([$qtype, $qid]);
$stat = $existing->fetch();
if ($stat) {
$served = (int)$stat['times_served'] + 1;
$correct = (int)$stat['times_correct'] + ($isCorrect ? 1 : 0);
$wrong = (int)$stat['times_wrong'] + (!$isCorrect && !(int)($input['is_skipped'] ?? 0) && !(int)($input['is_timeout'] ?? 0) ? 1 : 0);
$skipped = (int)$stat['times_skipped'] + ((int)($input['is_skipped'] ?? 0) ? 1 : 0);
$timeout = (int)$stat['times_timeout'] + ((int)($input['is_timeout'] ?? 0) ? 1 : 0);
// Running average for time
$oldAvg = (int)$stat['avg_time_ms'];
$newAvg = $oldAvg + (int)(($timeMs - $oldAvg) / $served);
$answered = $correct + $wrong;
$accRate = $answered > 0 ? round($correct / $answered, 4) : null;
$completionR = $served > 0 ? round($answered / $served, 4) : null;
$diffRating = $accRate !== null ? round(1.0 - $accRate, 4) : null;
$skipRate = $served > 0 ? round($skipped / $served, 4) : null;
$timeoutRate = $served > 0 ? round($timeout / $served, 4) : null;
db()->prepare("
UPDATE question_stats SET
times_served = ?, times_correct = ?, times_wrong = ?,
times_skipped = ?, times_timeout = ?,
avg_time_ms = ?, accuracy_rate = ?,
completion_rate = ?, difficulty_rating = ?,
skip_rate = ?, timeout_rate = ?,
last_served_at = NOW()
WHERE id = ?
")->execute([
$served, $correct, $wrong, $skipped, $timeout,
$newAvg, $accRate, $completionR, $diffRating,
$skipRate, $timeoutRate,
$stat['id'],
]);
} else {
// First ever attempt for this question
$isWrong = !$isCorrect && !(int)($input['is_skipped'] ?? 0) && !(int)($input['is_timeout'] ?? 0) ? 1 : 0;
$isSkipped = (int)($input['is_skipped'] ?? 0) ? 1 : 0;
$isTimeout = (int)($input['is_timeout'] ?? 0) ? 1 : 0;
$answered = ($isCorrect ? 1 : 0) + $isWrong;
$accRate = $answered > 0 ? round(($isCorrect ? 1 : 0) / $answered, 4) : null;
db()->prepare("
INSERT INTO question_stats (
question_type, question_id,
times_served, times_correct, times_wrong,
times_skipped, times_timeout,
avg_time_ms, accuracy_rate,
completion_rate, difficulty_rating,
skip_rate, timeout_rate,
first_served_at, last_served_at
) VALUES (?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
")->execute([
$qtype, $qid,
$isCorrect ? 1 : 0,
$isWrong,
$isSkipped,
$isTimeout,
$timeMs,
$accRate,
$answered > 0 ? round($answered / 1, 4) : null,
$accRate !== null ? round(1.0 - $accRate, 4) : null,
round($isSkipped / 1, 4),
round($isTimeout / 1, 4),
]);
}
} catch (Throwable $e) {
// Stats update failure should not break the attempt recording
// Log silently in production
}
// ── Update daily stats ──
try {
$today = date('Y-m-d');
$ds = db()->prepare("
SELECT id, times_served, times_correct, times_wrong,
times_skipped, times_timeout
FROM question_daily_stats
WHERE question_type = ? AND question_id = ? AND stat_date = ?
");
$ds->execute([$qtype, $qid, $today]);
$daily = $ds->fetch();
if ($daily) {
$isWrong2 = !$isCorrect && !(int)($input['is_skipped'] ?? 0) && !(int)($input['is_timeout'] ?? 0) ? 1 : 0;
db()->prepare("
UPDATE question_daily_stats SET
times_served = times_served + 1,
times_correct = times_correct + ?,
times_wrong = times_wrong + ?,
times_skipped = times_skipped + ?,
times_timeout = times_timeout + ?,
avg_time_ms = ROUND((COALESCE(avg_time_ms,0) * (times_served - 1) + ?) / times_served)
WHERE id = ?
")->execute([
$isCorrect ? 1 : 0,
$isWrong2,
(int)($input['is_skipped'] ?? 0) ? 1 : 0,
(int)($input['is_timeout'] ?? 0) ? 1 : 0,
$timeMs,
$daily['id'],
]);
} else {
$isWrong2 = !$isCorrect && !(int)($input['is_skipped'] ?? 0) && !(int)($input['is_timeout'] ?? 0) ? 1 : 0;
db()->prepare("
INSERT INTO question_daily_stats (
question_type, question_id, stat_date,
times_served, times_correct, times_wrong,
times_skipped, times_timeout, avg_time_ms, unique_players
) VALUES (?, ?, ?, 1, ?, ?, ?, ?, ?, 1)
")->execute([
$qtype, $qid, $today,
$isCorrect ? 1 : 0,
$isWrong2,
(int)($input['is_skipped'] ?? 0) ? 1 : 0,
(int)($input['is_timeout'] ?? 0) ? 1 : 0,
$timeMs,
]);
}
} catch (Throwable $e) { /* silent */ }
// ── Update MCQ answer distribution ──
if ($qtype === 'mcq' && !empty($input['selected_answer'])) {
try {
$ansIdx = (int)$input['selected_answer'];
if ($ansIdx >= 1 && $ansIdx <= 4) {
$mad = db()->prepare("
SELECT id, times_selected FROM mcq_answer_distribution
WHERE question_id = ? AND answer_index = ?
");
$mad->execute([$qid, $ansIdx]);
$row = $mad->fetch();
if ($row) {
db()->prepare("
UPDATE mcq_answer_distribution SET times_selected = times_selected + 1 WHERE id = ?
")->execute([$row['id']]);
} else {
db()->prepare("
INSERT INTO mcq_answer_distribution (question_id, answer_index, times_selected, avg_time_ms)
VALUES (?, ?, 1, ?)
")->execute([$qid, $ansIdx, $timeMs]);
}
}
} catch (Throwable $e) { /* silent */ }
}
out([
'success' => true,
'attempt_id' => $attemptId,
'recorded' => true,
]);
}
// ════════════════════════════════════════════════════════
// REPORT BATCH — Multiple attempts in one POST
// Body (JSON): { "attempts": [ {same fields as report_attempt}, ... ] }
// ════════════════════════════════════════════════════════
case 'report_batch': {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') err('POST required', 405);
$raw = file_get_contents('php://input');
$body = json_decode($raw, true);
if (!is_array($body) || empty($body['attempts'])) {
err('JSON body with "attempts" array required');
}
$attempts = $body['attempts'];
if (!is_array($attempts) || count($attempts) > 200) {
err('attempts must be an array with max 200 items');
}
$recorded = 0;
$errors = [];
foreach ($attempts as $i => $attempt) {
// Re-use the single report logic by faking $_POST
$prevPost = $_POST;
$_POST = $attempt;
$_REQUEST = array_merge($_REQUEST, ['action' => 'report_attempt']);
try {
// Inline the insert (simplified version)
$qtype = $attempt['question_type'] ?? '';
$qid = (int)($attempt['question_id'] ?? 0);
$sessionId = trim($attempt['session_id'] ?? '');
if (!in_array($qtype, ['mcq', 'tf', 'cs']) || !$qid || !$sessionId) {
$errors[] = "Item {$i}: missing required fields";
continue;
}
$deviceType = $attempt['device_type'] ?? 'unknown';
if (!in_array($deviceType, ['mobile', 'tablet', 'desktop', 'unknown'])) $deviceType = 'unknown';
db()->prepare("
INSERT INTO question_attempts (
question_type, question_id, session_id,
is_correct, is_skipped, is_timeout,
selected_answer, correct_answer, time_taken_ms,
room_id, player_id,
round_in_game, total_rounds,
player_streak, player_score_at_time,
players_in_room, answer_rank,
hint_used, power_up_used,
device_type, os, client_version,
screen_width, screen_height
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
")->execute([
$qtype, $qid, $sessionId,
(int)($attempt['is_correct'] ?? 0) ? 1 : 0,
(int)($attempt['is_skipped'] ?? 0) ? 1 : 0,
(int)($attempt['is_timeout'] ?? 0) ? 1 : 0,
$attempt['selected_answer'] ?? null,
$attempt['correct_answer'] ?? null,
(int)($attempt['time_taken_ms'] ?? 0),
$attempt['room_id'] ?? null,
$attempt['player_id'] ?? null,
isset($attempt['round_in_game']) ? (int)$attempt['round_in_game'] : null,
isset($attempt['total_rounds']) ? (int)$attempt['total_rounds'] : null,
(int)($attempt['player_streak'] ?? 0),
(int)($attempt['player_score_at_time'] ?? 0),
isset($attempt['players_in_room']) ? (int)$attempt['players_in_room'] : null,
isset($attempt['answer_rank']) ? (int)$attempt['answer_rank'] : null,
(int)($attempt['hint_used'] ?? 0) ? 1 : 0,
$attempt['power_up_used'] ?? null,
$deviceType,
$attempt['os'] ?? null,
$attempt['client_version'] ?? null,
isset($attempt['screen_width']) ? (int)$attempt['screen_width'] : null,
isset($attempt['screen_height']) ? (int)$attempt['screen_height'] : null,
]);
$recorded++;
} catch (Throwable $e) {
$errors[] = "Item {$i}: " . $e->getMessage();
}
$_POST = $prevPost;
}
out([
'success' => true,
'recorded' => $recorded,
'total' => count($attempts),
'errors' => $errors,
]);
}
// ════════════════════════════════════════════════════════
// REPORT QUESTION — Player flags a question
// Method: POST
// Params:
// question_type mcq|tf|cs REQUIRED
// question_id int REQUIRED
// reason string (enum) REQUIRED
// detail string optional
// session_id string optional
// reporter_id string optional
//
// Reason enum: wrong_answer, unclear, duplicate,
// offensive, too_easy, too_hard, typo, other
// ════════════════════════════════════════════════════════
case 'report_question': {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') err('POST required', 405);
$input = $_POST;
if (empty($input) || empty($input['question_type'])) {
$raw = file_get_contents('php://input');
$json = json_decode($raw, true);
if (is_array($json)) $input = $json;
}
$qtype = $input['question_type'] ?? '';
$qid = (int)($input['question_id'] ?? 0);
$reason = $input['reason'] ?? '';
if (!in_array($qtype, ['mcq', 'tf', 'cs'])) err('Invalid question_type');
if (!$qid) err('question_id required');
$validReasons = ['wrong_answer', 'unclear', 'duplicate', 'offensive', 'too_easy', 'too_hard', 'typo', 'other'];
if (!in_array($reason, $validReasons)) {
err('Invalid reason. Must be one of: ' . implode(', ', $validReasons));
}
db()->prepare("
INSERT INTO question_reports (
question_type, question_id, session_id, reporter_id,
reason, detail
) VALUES (?, ?, ?, ?, ?, ?)
")->execute([
$qtype, $qid,
$input['session_id'] ?? null,
$input['reporter_id'] ?? null,
$reason,
$input['detail'] ?? null,
]);
out([
'success' => true,
'report_id' => (int)db()->lastInsertId(),
'message' => 'شكراً لبلاغك! سيتم مراجعته.',
]);
}
// ════════════════════════════════════════════════════════
// GET QUESTION STATS — Analytics for a specific question
// Params: question_type, question_id
// Returns: aggregated stats + answer distribution (MCQ)
// ════════════════════════════════════════════════════════
case 'get_question_stats': {
$qtype = reqStr('question_type');
$qid = reqInt('question_id');
if (!in_array($qtype, ['mcq', 'tf', 'cs'])) err('Invalid question_type');
if (!$qid) err('question_id required');
$s = db()->prepare("SELECT * FROM question_stats WHERE question_type = ? AND question_id = ?");
$s->execute([$qtype, $qid]);
$stats = $s->fetch();
if (!$stats) {
out([
'success' => true,
'stats' => null,
'message' => 'No analytics data yet for this question',
]);
}
// Cast numeric fields
$intFields = ['times_served', 'times_correct', 'times_wrong', 'times_skipped', 'times_timeout',
'avg_time_ms', 'median_time_ms', 'p10_time_ms', 'p90_time_ms',
'fastest_time_ms', 'slowest_time_ms', 'correct_avg_time_ms', 'wrong_avg_time_ms'];
foreach ($intFields as $f) {
if (isset($stats[$f]) && $stats[$f] !== null) $stats[$f] = (int)$stats[$f];
}
$floatFields = ['accuracy_rate', 'completion_rate', 'difficulty_rating', 'discrimination_index',
'hint_usage_rate', 'timeout_rate', 'skip_rate', 'stddev_time_ms',
'avg_streak_on_correct', 'avg_streak_on_wrong', 'first_answer_correct_rate'];
foreach ($floatFields as $f) {
if (isset($stats[$f]) && $stats[$f] !== null) $stats[$f] = (float)$stats[$f];
}
// MCQ: get answer distribution
$distribution = null;
if ($qtype === 'mcq') {
$d = db()->prepare("
SELECT answer_index, times_selected, avg_time_ms,
selected_by_top_quartile, selected_by_bottom_quartile
FROM mcq_answer_distribution
WHERE question_id = ?
ORDER BY answer_index
");
$d->execute([$qid]);
$distribution = $d->fetchAll();
foreach ($distribution as &$row) {
$row['answer_index'] = (int)$row['answer_index'];
$row['times_selected'] = (int)$row['times_selected'];
$row['avg_time_ms'] = $row['avg_time_ms'] !== null ? (int)$row['avg_time_ms'] : null;
$row['selected_by_top_quartile'] = (int)$row['selected_by_top_quartile'];
$row['selected_by_bottom_quartile']= (int)$row['selected_by_bottom_quartile'];
}
}
// Recent daily trend (last 7 days)
$trend = db()->prepare("
SELECT stat_date, times_served, times_correct, times_wrong, avg_time_ms
FROM question_daily_stats
WHERE question_type = ? AND question_id = ?
ORDER BY stat_date DESC LIMIT 7
");
$trend->execute([$qtype, $qid]);
$dailyTrend = $trend->fetchAll();
foreach ($dailyTrend as &$d) {
$d['times_served'] = (int)$d['times_served'];
$d['times_correct'] = (int)$d['times_correct'];
$d['times_wrong'] = (int)$d['times_wrong'];
$d['avg_time_ms'] = $d['avg_time_ms'] !== null ? (int)$d['avg_time_ms'] : null;
}
$result = [
'success' => true,
'stats' => $stats,
'daily_trend' => array_reverse($dailyTrend),
];
if ($distribution !== null) {
$result['answer_distribution'] = $distribution;
}
out($result);
}
// ════════════════════════════════════════════════════════
// UNKNOWN ACTION
// ════════════════════════════════════════════════════════
default:
err("Unknown action: {$action}", 404);
}
} catch (Throwable $e) {
err('Server error: ' . $e->getMessage(), 500);
}
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
# Science Street — Full Technical Documentation
> **Prepared by:** AL-Arcade Development Studio
> **Presented to:** Science Street
> **Document Version:** 3.2.0
> **Date:** May 2026
> **Classification:** Technical Architecture & System Blueprint
> **Engine:** Unity 6 (6000.3.9f1) | **Backend:** PHP REST + Supabase Cloud
---
## Table of Contents
1. [Executive Technical Summary](#1-executive-technical-summary)
2. [System Architecture Overview](#2-system-architecture-overview)
3. [Client Application Architecture](#3-client-application-architecture)
4. [Backend Infrastructure](#4-backend-infrastructure)
5. [Database Architecture](#5-database-architecture)
6. [Authentication & Security Layer](#6-authentication--security-layer)
7. [Game Engine — MCQ Race](#7-game-engine--mcq-race)
8. [Game Engine — True/False Production Line](#8-game-engine--truefalse-production-line)
9. [Game Engine — Correct the Sentence](#9-game-engine--correct-the-sentence)
10. [Question Bank & Content Management](#10-question-bank--content-management)
11. [Real-Time Services](#11-real-time-services)
12. [Analytics & Telemetry Pipeline](#12-analytics--telemetry-pipeline)
13. [Offline-First Architecture](#13-offline-first-architecture)
14. [User Progression & Ranking System](#14-user-progression--ranking-system)
15. [Challenge & Competitive System](#15-challenge--competitive-system)
16. [UI/UX System Architecture](#16-uiux-system-architecture)
17. [Asset Pipeline & Rendering](#17-asset-pipeline--rendering)
18. [API Specification](#18-api-specification)
19. [Deployment Architecture](#19-deployment-architecture)
20. [Cross-Platform Targets](#20-cross-platform-targets)
21. [Performance Budgets & Optimization](#21-performance-budgets--optimization)
22. [Localization & Bilingual Support](#22-localization--bilingual-support)
23. [Data Flow Diagrams](#23-data-flow-diagrams)
24. [Security Model](#24-security-model)
25. [Appendix — Full Feature Matrix](#25-appendix--full-feature-matrix)
---
## 1. Executive Technical Summary
Science Street is a **production-grade educational gaming platform** targeting the Egyptian national curriculum (Grades 7–12). The system delivers curriculum-aligned content through three distinct interactive game experiences, backed by a dual-database cloud infrastructure with real-time capabilities.
### Key Technical Achievements
| Metric | Value |
|--------|-------|
| Total Engineered Systems | 74+ interconnected modules |
| Game Engines Built | 3 (each with unique mechanics) |
| Database Tables Designed | 12+ relational tables |
| API Endpoints Developed | 18+ REST endpoints |
| State Machines Implemented | 3 game FSMs + 1 auth FSM |
| Real-Time Channels | 3 WebSocket subscriptions |
| Supported Curricula | 2 (Egyptian Languages / Egyptian Arabic) |
| Grade Coverage | 6 grades (7–12) |
| Bloom's Taxonomy Levels | 6 cognitive levels supported |
| Offline Capability | Full offline queue with auto-sync |
| Platform Targets | iOS, Android, WebGL |
---
## 2. System Architecture Overview
```mermaid
graph TB
subgraph "Client Layer"
APP[Unity 6 Application]
UI[UI Toolkit / Canvas System]
GM[Game Managers x3]
CACHE[Local Cache Layer]
end
subgraph "Network Layer"
REST[REST API Client]
WS[WebSocket Client]
QUEUE[Offline Queue Manager]
end
subgraph "Backend Services"
PHP[PHP REST API Server]
SUPA[Supabase Cloud Platform]
EDGE[Edge Functions]
end
subgraph "Data Layer"
MYSQL[(MySQL — Question Bank)]
POSTGRES[(PostgreSQL — User Data)]
REALTIME[Realtime Engine]
STORAGE[File Storage]
end
APP --> UI
APP --> GM
APP --> CACHE
APP --> REST
APP --> WS
REST --> QUEUE
REST --> PHP
WS --> SUPA
PHP --> MYSQL
SUPA --> POSTGRES
SUPA --> REALTIME
SUPA --> STORAGE
SUPA --> EDGE
REALTIME --> WS
```
### Architectural Principles
- **Event-Driven**: All systems communicate via decoupled event buses
- **Singleton Services**: Core services persist across scene transitions
- **State Machine Pattern**: Every game engine operates as a finite state machine
- **Offline-First**: All write operations queue locally before network dispatch
- **Fluent Builders**: Query construction uses composable filter chains
---
## 3. Client Application Architecture
```mermaid
graph LR
subgraph "Scene Flow"
BOOT[Boot Scene] --> LOGIN[Login Scene]
LOGIN --> MENU[Main Menu Scene]
MENU --> MCQ[MCQ Game Scene]
MENU --> TF[TF Game Scene]
MENU --> CS[CS Game Scene]
MCQ --> MENU
TF --> MENU
CS --> MENU
end
```
```mermaid
graph TB
subgraph "Persistent Singletons (DontDestroyOnLoad)"
SM[Supabase Manager]
US[User Service]
GS[Game Session Controller]
API[API Manager]
AUDIO[Audio Manager]
COLOR[Color Palette Manager]
end
subgraph "Scene-Scoped Controllers"
HC[Home Controller]
LC[Login Controller]
PC[Profile Controller]
LB[Leaderboard Controller]
GMR[Game Manager — per scene]
end
SM --> US
SM --> GS
US --> HC
US --> PC
GS --> GMR
API --> GMR
AUDIO --> GMR
```
### Module Breakdown
| Module | Responsibility | Lifecycle |
|--------|---------------|-----------|
| Supabase Manager | Connection pooling, auth state, network monitoring | Persistent |
| User Service | Profile CRUD, rank calculations, cache management | Persistent |
| Game Session | Session context, filters, question counts, class codes | Persistent |
| API Manager | REST calls, retry logic, offline queueing, token refresh | Persistent |
| Audio Manager | BGM crossfades, SFX triggers, music state persistence | Persistent |
| Color Palette | Subject-specific theming, dynamic color assignment | Persistent |
| Home Controller | Dashboard rendering, XP display, game launching | Scene-bound |
| Login Controller | Auth flow orchestration, error localization | Scene-bound |
| Game Managers | FSM execution, scoring, question delivery | Scene-bound |
---
## 4. Backend Infrastructure
```mermaid
graph TB
subgraph "CapRover Docker Host"
NGINX[Nginx Reverse Proxy]
PHP_CONTAINER[PHP 8.x Container]
MYSQL_CONTAINER[MySQL 8 Container]
end
subgraph "Supabase Cloud"
AUTH[GoTrue Auth Service]
PGREST[PostgREST API]
REALTIME_SVC[Realtime Broadcast]
EDGE_FN[Edge Functions]
PG[(PostgreSQL 15)]
end
CLIENT[Game Client] -->|HTTPS| NGINX
NGINX --> PHP_CONTAINER
PHP_CONTAINER --> MYSQL_CONTAINER
CLIENT -->|WSS| REALTIME_SVC
CLIENT -->|HTTPS| PGREST
CLIENT -->|HTTPS| AUTH
REALTIME_SVC --> PG
PGREST --> PG
AUTH --> PG
EDGE_FN --> PG
```
### Infrastructure Specifications
| Component | Technology | Purpose |
|-----------|-----------|---------|
| API Server | PHP 8.x on CapRover | Question delivery, analytics ingestion |
| Primary DB | MySQL 8 (Docker) | Question bank, taxonomy, analytics |
| User DB | Supabase PostgreSQL 15 | User profiles, game history, challenges |
| Auth Provider | Supabase GoTrue v6 | JWT-based authentication |
| Realtime | Supabase Realtime v7 | Live profile updates, activity feeds |
| Edge Functions | Supabase Deno Runtime | Account deletion, server-side validation |
| Reverse Proxy | Nginx via CapRover | SSL termination, load balancing |
| Container Orchestration | CapRover (Docker Swarm) | Auto-deployment, zero-downtime updates |
---
## 5. Database Architecture
### 5.1 Question Bank Database (MySQL)
```mermaid
erDiagram
CURRICULA ||--o{ SUBJECTS : contains
CURRICULA ||--o{ CHAPTERS : scopes
SUBJECTS ||--o{ CHAPTERS : categorizes
GRADES ||--o{ CHAPTERS : levels
TERMS ||--o{ CHAPTERS : schedules
CHAPTERS ||--o{ MCQ_QUESTIONS : holds
CHAPTERS ||--o{ TF_QUESTIONS : holds
CHAPTERS ||--o{ CS_QUESTIONS : holds
CURRICULA {
int id PK
string code
string name_ar
string name_en
string question_lang
int sort_order
boolean is_active
}
GRADES {
int id PK
string name_ar
string name_en
int sort_order
}
TERMS {
int id PK
string name_ar
string name_en
int sort_order
}
SUBJECTS {
int id PK
string name_ar
string name_en
string icon
string color_hex
int curriculum_id FK
}
CHAPTERS {
int id PK
string name_ar
string name_en
int subject_id FK
int grade_id FK
int term_id FK
int curriculum_id FK
int mcq_count
int tf_count
int cs_count
int total_questions
}
MCQ_QUESTIONS {
int id PK
int chapter_id FK
text question_text
text answer1
text answer2
text answer3
text answer4
text explanation
string question_image
string answer1_image
string answer2_image
string answer3_image
string answer4_image
string explanation_image
string question_audio
enum difficulty
enum bloom_level
int time_limit_s
float accuracy_rate
int avg_time_ms
float auto_difficulty
boolean is_active
boolean is_verified
}
TF_QUESTIONS {
int id PK
int chapter_id FK
text question_text
boolean is_true
text explanation
string question_image
string question_audio
string explanation_image
enum difficulty
enum bloom_level
int time_limit_s
string source
float accuracy_rate
int avg_time_ms
float auto_difficulty
boolean is_active
boolean is_verified
}
CS_QUESTIONS {
int id PK
int chapter_id FK
text instruction_text
text explanation
json words
json options
string question_image
string explanation_image
enum difficulty
enum bloom_level
int time_limit_s
string distractor_word
string correct_answer
text sentence
boolean is_active
boolean is_verified
}
```
### 5.2 User Platform Database (PostgreSQL / Supabase)
```mermaid
erDiagram
USERS ||--o{ GAME_HISTORY : plays
USERS ||--o{ CHALLENGES : competes
USERS ||--o{ ACTIVITIES : generates
USERS {
uuid id PK
text user_name
text email
text rank
int points
timestamp created_at
timestamp updated_at
int grade
text sex
int curriculum
int term
int avatar_id
}
GAME_HISTORY {
uuid id PK
uuid user_id FK
text game_type
int points_earned
timestamp time_started
timestamp time_finished
timestamp created_at
}
CHALLENGES {
uuid id PK
uuid user_id FK
boolean has_won
int points_earned
int time_saved
timestamp started_at
timestamp ended_at
timestamp created_at
}
ACTIVITIES {
uuid id PK
uuid user_id FK
text activity_type
json metadata
timestamp created_at
}
```
### 5.3 Data Volume Estimates
| Table | Expected Records | Growth Rate |
|-------|-----------------|-------------|
| curricula | 2 | Static |
| grades | 6 | Static |
| terms | 2 | Static |
| subjects | 20+ | Low |
| chapters | 200+ | Moderate |
| mcq_questions | 10,000+ | High |
| tf_questions | 8,000+ | High |
| cs_questions | 5,000+ | High |
| users | 100,000+ | High |
| game_history | 1,000,000+ | Very High |
| challenges | 500,000+ | High |
| activities | 2,000,000+ | Very High |
---
## 6. Authentication & Security Layer
```mermaid
sequenceDiagram
participant User
participant App
participant GoTrue as Supabase GoTrue
participant DB as PostgreSQL
participant Edge as Edge Functions
Note over User,Edge: Registration Flow
User->>App: Enter email, password, username
App->>GoTrue: SignUp(email, password, metadata)
GoTrue->>DB: Create auth.users record
GoTrue-->>App: Session + JWT
App->>DB: Create public.users profile
App-->>User: Welcome screen
Note over User,Edge: Login Flow
User->>App: Enter credentials
App->>GoTrue: SignIn(email, password)
GoTrue-->>App: Access Token + Refresh Token
App->>App: Cache session locally
App->>DB: Fetch user profile
App-->>User: Main menu
Note over User,Edge: Token Refresh
App->>App: Detect token expiring
App->>GoTrue: RefreshSession(refresh_token)
GoTrue-->>App: New access token
Note over User,Edge: Guest Mode
User->>App: Play as guest
App->>GoTrue: SignInAnonymously()
GoTrue-->>App: Anonymous session
App-->>User: Limited access
Note over User,Edge: Guest Conversion
User->>App: Decide to register
App->>GoTrue: LinkIdentity(email, password)
GoTrue->>DB: Upgrade anonymous → email user
GoTrue-->>App: Full session
App-->>User: Full access unlocked
Note over User,Edge: Account Deletion
User->>App: Request deletion
App->>Edge: invoke("delete-account")
Edge->>DB: Cascade delete all user data
Edge->>GoTrue: Delete auth record
Edge-->>App: Confirmation
App-->>User: Logged out
```
### Authentication Features Implemented
| Feature | Status | Details |
|---------|--------|---------|
| Email/Password Registration | Complete | With email verification |
| Anonymous Guest Play | Complete | Full game access, limited social |
| Guest-to-Email Conversion | Complete | Preserves all guest progress |
| JWT Session Management | Complete | Auto-refresh before expiry |
| Password Reset Flow | Complete | Email-based reset link |
| Deep Link Handling | Complete | Email verification callbacks |
| Bilingual Error Messages | Complete | Arabic/English error mapping |
| Session Persistence | Complete | Survives app restart |
| Network Status Awareness | Complete | Graceful offline handling |
| Secure Account Deletion | Complete | GDPR-compliant cascade delete |
---
## 7. Game Engine — MCQ Race
### 7.1 Concept
A high-speed racing game where the player competes against an AI opponent. Correct answers open gates and propel the player forward; wrong answers stall progress while the competitor advances.
### 7.2 State Machine
```mermaid
stateDiagram-v2
[*] --> Idle
Idle --> Loading: StartGame()
Loading --> ShowingQuestion: QuestionsLoaded
ShowingQuestion --> WaitingForAnswer: DisplayComplete
WaitingForAnswer --> AnswerFeedback: PlayerAnswered
WaitingForAnswer --> AnswerFeedback: TimeExpired
AnswerFeedback --> BetweenQuestions: FeedbackComplete
BetweenQuestions --> ShowingQuestion: NextQuestion
BetweenQuestions --> GameComplete: PointsReached
BetweenQuestions --> GameOver: QuestionsExhausted
GameComplete --> [*]
GameOver --> [*]
```
### 7.3 Technical Specifications
| Parameter | Value | Purpose |
|-----------|-------|---------|
| Player Run Speed | 12 units/sec | Base movement rate |
| Gate Spawn Distance | 120 units ahead | Render distance for gates |
| Gate Spacing | 4 units apart | Separation between answer gates |
| Gate Pool Size | 6 objects | Object pooling for performance |
| Question Display Time | 1.5 seconds | Reading time before input enabled |
| Feedback Duration | 2.0 seconds | Result display duration |
| Points to Win | 5 correct answers | Victory condition |
| Timer Warning Threshold | Audio tick at < 5 seconds | Urgency feedback |
| Streak Bonus Multiplier | Consecutive correct × bonus | Reward consistency |
### 7.4 Component Architecture
```mermaid
graph TB
subgraph "MCQ Game Scene"
MGM[MCQ Game Manager<br/>State Machine + Orchestration]
subgraph "Player System"
PR[Player Runner<br/>Movement + Animation]
COMP[AI Competitor<br/>Autonomous Racing Logic]
end
subgraph "Question System"
QD[Question Display<br/>Text + Image + Audio]
GC[Gate Controller<br/>4-Way Answer Gates]
GP[Gate Pool<br/>Object Recycling]
end
subgraph "UI Layer"
UIM[UI Manager<br/>Score + Timer + Progress]
FB[Feedback Overlay<br/>Correct/Wrong Animation]
end
subgraph "Scene Builder"
PB[Prefab Builder<br/>Procedural Track Generation]
end
end
MGM --> PR
MGM --> COMP
MGM --> QD
MGM --> GC
MGM --> UIM
GC --> GP
PB --> MGM
```
### 7.5 Game Loop Detail
```mermaid
flowchart TD
START[Game Start] --> FETCH[Fetch Questions from API]
FETCH --> BUILD[Build Track Procedurally]
BUILD --> SPAWN[Spawn Gate Set Ahead]
SPAWN --> SHOW[Display Question + Options]
SHOW --> WAIT{Player Input?}
WAIT -->|Correct Gate| CORRECT[Open Gate + Speed Boost]
WAIT -->|Wrong Gate| WRONG[Close Gate + Slowdown]
WAIT -->|Timeout| TIMEOUT[Force Wrong + Competitor Advance]
CORRECT --> SCORE[Update Score + Streak]
WRONG --> PENALTY[Reset Streak]
TIMEOUT --> PENALTY
SCORE --> CHECK{Points = 5?}
PENALTY --> CHECK
CHECK -->|Yes| WIN[Victory Sequence]
CHECK -->|No| RECYCLE[Recycle Gates to Pool]
RECYCLE --> SPAWN
WIN --> RECORD[Record Game History]
RECORD --> REPORT[Report Attempts to Analytics]
REPORT --> RETURN[Return to Menu]
```
---
## 8. Game Engine — True/False Production Line
### 8.1 Concept
An industrial production line simulation where statements appear on a factory screen. The player operates a mechanical hand to sort boxes into "True" or "False" conveyor lanes. Correct sorting advances progress; mistakes reset the production step counter.
### 8.2 State Machine
```mermaid
stateDiagram-v2
[*] --> Idle
Idle --> Loading: StartGame()
Loading --> Playing: QuestionsReady
Playing --> Feedback: BoxPlaced
Playing --> Feedback: TimeExpired
Feedback --> Playing: NextStatement
Feedback --> Complete: StepsReached
Feedback --> GameOver: QuestionsExhausted
Complete --> [*]
GameOver --> [*]
```
### 8.3 Technical Specifications
| Parameter | Value | Purpose |
|-----------|-------|---------|
| Steps to Win | 5 consecutive | Victory requires unbroken streak |
| Feedback Duration | 1.2 seconds | Quick factory pace |
| Step Distance | 3 units | Physical progress per correct answer |
| Hand Movement | Touch/drag controlled | Natural sorting gesture |
| Conveyor Speed | Constant belt animation | Visual urgency |
| Box Spawn Rate | On-demand per question | Synced to question delivery |
| Reset Penalty | Steps → 0 | Wrong answer resets all progress |
### 8.4 Component Architecture
```mermaid
graph TB
subgraph "TF Game Scene"
TGM[TF Game Manager<br/>State Machine + Logic]
subgraph "Physical System"
HC[Hand Controller<br/>Player Input → Movement]
PL[Production Line<br/>Conveyor Belt Physics]
BOX[Box Objects<br/>Sortable Items]
end
subgraph "Display System"
QS[Question Screen<br/>Factory Monitor Display]
BIN_T[True Bin<br/>Left Lane Target]
BIN_F[False Bin<br/>Right Lane Target]
end
subgraph "UI Layer"
TUIM[UI Manager<br/>Steps + Progress + Timer]
TFB[Feedback System<br/>Correct/Reset Animation]
end
subgraph "Builder"
TPB[Prefab Builder<br/>Factory Scene Assembly]
end
end
TGM --> HC
TGM --> PL
TGM --> QS
TGM --> TUIM
HC --> BOX
BOX --> BIN_T
BOX --> BIN_F
TPB --> TGM
```
### 8.5 Sorting Mechanics
```mermaid
flowchart LR
STATEMENT[Statement Appears<br/>on Factory Screen] --> HAND[Player Grabs Box]
HAND --> DRAG{Drag Direction}
DRAG -->|Left| TRUE_BIN[True Lane]
DRAG -->|Right| FALSE_BIN[False Lane]
TRUE_BIN --> EVAL{Correct?}
FALSE_BIN --> EVAL
EVAL -->|Yes| ADVANCE[Step +1<br/>Progress Bar Fills]
EVAL -->|No| RESET[Steps → 0<br/>Production Resets]
ADVANCE --> FIVE{Steps = 5?}
FIVE -->|Yes| COMPLETE[Production Complete!]
FIVE -->|No| NEXT[Next Statement]
RESET --> NEXT
```
---
## 9. Game Engine — Correct the Sentence
### 9.1 Concept
A linguistic puzzle game where sentences are displayed with incorrect or distractor words. Players interact with an AI bot assistant and must identify wrong words, then drag the correct replacement from a circular arc of options.
### 9.2 State Machine
```mermaid
stateDiagram-v2
[*] --> Idle
Idle --> Loading: StartGame()
Loading --> ShowingSentence: DataReady
ShowingSentence --> WaitingForWordClick: SentenceAnimated
WaitingForWordClick --> WaitingForDrop: WordSelected
WaitingForDrop --> Feedback: CorrectWordDropped
WaitingForDrop --> WaitingForWordClick: WrongDrop
Feedback --> ShowingSentence: NextSentence
Feedback --> Complete: AllSentencesDone
Complete --> [*]
```
### 9.3 Technical Specifications
| Parameter | Value | Purpose |
|-----------|-------|---------|
| Sentence Show Delay | 0.5 seconds | Dramatic reveal timing |
| Feedback Duration | 1.5 seconds | Result comprehension time |
| Word Click Cooldown | 0.3 seconds | Prevent double-tap errors |
| Arc Radius | 15 units | Option spread distance |
| Arc Span | 120 degrees | Visual fan of word choices |
| Word Gap | 0.2 units | Spacing between sentence words |
| Hint System | Toggleable | Optional assistance mode |
| Camera System | Cinemachine Group Framing | Dynamic focus on active elements |
### 9.4 Component Architecture
```mermaid
graph TB
subgraph "CS Game Scene"
CGM[CS Game Manager<br/>State Machine + Orchestration]
subgraph "Interaction System"
BOT[Bot Controller<br/>AI Assistant Character]
WB[Word Buttons<br/>Clickable Sentence Words]
OD[Option Draggables<br/>Arc-Arranged Answers]
end
subgraph "Layout System"
SENT[Sentence Renderer<br/>Dynamic Word Layout]
ARC[Arc Calculator<br/>120° Option Spread]
CAM[Cinemachine Group<br/>Dynamic Camera Framing]
end
subgraph "UI Layer"
CUIM[UI Manager<br/>Instructions + Progress]
CPB[Prefab UI Manager<br/>Dynamic Element Spawning]
end
subgraph "Builder"
CSPB[Prefab Builder<br/>Scene Composition]
end
end
CGM --> BOT
CGM --> WB
CGM --> OD
CGM --> SENT
SENT --> ARC
CAM --> SENT
CGM --> CUIM
CSPB --> CGM
```
### 9.5 Word Correction Flow
```mermaid
flowchart TD
DISPLAY[Display Sentence with<br/>Distractor Words Highlighted] --> CLICK[Player Taps<br/>Incorrect Word]
CLICK --> REVEAL[Reveal Option Arc<br/>120° Fan of Choices]
REVEAL --> DRAG[Player Drags<br/>Correct Word]
DRAG --> DROP{Drop on<br/>Target Word?}
DROP -->|Correct| REPLACE[Word Swaps In<br/>Sentence Corrected]
DROP -->|Wrong| BOUNCE[Option Returns<br/>to Arc Position]
REPLACE --> MORE{More Words<br/>to Fix?}
BOUNCE --> DRAG
MORE -->|Yes| CLICK
MORE -->|No| COMPLETE[Sentence Complete<br/>Bot Celebrates]
COMPLETE --> NEXT[Load Next Sentence]
```
---
## 10. Question Bank & Content Management
### 10.1 Taxonomy Hierarchy
```mermaid
graph TD
CURR[Curriculum<br/>Egyptian Languages / Egyptian Arabic] --> GRADE[Grade<br/>7th — 12th]
GRADE --> TERM[Term<br/>Term 1 / Term 2]
TERM --> SUBJ[Subject<br/>Science, Math, Arabic, English...]
SUBJ --> CHAP[Chapter<br/>Curriculum-Aligned Units]
CHAP --> MCQ_Q[MCQ Questions]
CHAP --> TF_Q[TF Questions]
CHAP --> CS_Q[CS Questions]
```
### 10.2 Question Complexity Model
```mermaid
graph LR
subgraph "Bloom's Taxonomy Integration"
L1[Remember] --> L2[Understand]
L2 --> L3[Apply]
L3 --> L4[Analyze]
L4 --> L5[Evaluate]
L5 --> L6[Create]
end
subgraph "Difficulty Scaling"
EASY[Easy<br/>Foundation Level]
MEDIUM[Medium<br/>Application Level]
HARD[Hard<br/>Analysis Level]
end
L1 -.-> EASY
L2 -.-> EASY
L3 -.-> MEDIUM
L4 -.-> MEDIUM
L5 -.-> HARD
L6 -.-> HARD
```
### 10.3 Question Features Matrix
| Feature | MCQ | TF | CS |
|---------|-----|----|----|
| Text-based questions | Yes | Yes | Yes |
| Image-attached questions | Yes | Yes | Yes |
| Audio-attached questions | Yes | Yes | No |
| Image-based answer options | Yes | No | No |
| Explanation with text | Yes | Yes | Yes |
| Explanation with image | Yes | Yes | Yes |
| Difficulty rating | Easy/Med/Hard | Easy/Med/Hard | Easy/Med/Hard |
| Bloom's level classification | All 6 levels | All 6 levels | All 6 levels |
| Time limit per question | Configurable | Configurable | Configurable |
| Auto-difficulty adjustment | ML-computed | ML-computed | ML-computed |
| Verification status tracking | Yes | Yes | Yes |
| Active/Inactive toggling | Yes | Yes | Yes |
| JSON-structured word data | No | No | Yes |
| Distractor word system | No | No | Yes |
### 10.4 Content Filtering Pipeline
```mermaid
flowchart LR
REQUEST[Game Request] --> FILTER[Filter Builder]
FILTER --> C[Curriculum Filter]
FILTER --> G[Grade Filter]
FILTER --> T[Term Filter]
FILTER --> S[Subject Filter]
FILTER --> CH[Chapter Filter]
FILTER --> D[Difficulty Filter]
FILTER --> B[Bloom Level Filter]
FILTER --> V[Verified-Only Filter]
FILTER --> COUNT[Count Limiter]
FILTER --> SHUF[Shuffle Flag]
C --> QUERY[Composed Query]
G --> QUERY
T --> QUERY
S --> QUERY
CH --> QUERY
D --> QUERY
B --> QUERY
V --> QUERY
COUNT --> QUERY
SHUF --> QUERY
QUERY --> RESULT[Filtered Question Set]
```
---
## 11. Real-Time Services
### 11.1 WebSocket Channel Architecture
```mermaid
graph TB
subgraph "Supabase Realtime Channels"
CH1[Channel: users<br/>Filter: user_id = current]
CH2[Channel: activities<br/>Filter: INSERT events]
CH3[Channel: presence<br/>Online status broadcast]
end
subgraph "Client Listeners"
L1[User Profile Listener<br/>Rank/Points changes]
L2[Activity Feed Listener<br/>New achievements]
L3[Presence Listener<br/>Friends online status]
end
subgraph "Event Handlers"
H1[OnUserChanged<br/>Update local cache + UI]
H2[OnNewActivity<br/>Show notification toast]
H3[OnRankChanged<br/>Trigger celebration VFX]
H4[OnPointsChanged<br/>Animate XP bar]
end
CH1 --> L1
CH2 --> L2
CH3 --> L3
L1 --> H1
L1 --> H3
L1 --> H4
L2 --> H2
```
### 11.2 Real-Time Event Types
| Event | Trigger | Client Reaction |
|-------|---------|-----------------|
| profile_update | Any user table change | Refresh cached profile |
| rank_promotion | Points threshold crossed | Celebration animation + notification |
| points_earned | Game completed | XP bar animation |
| new_activity | Any tracked action | Activity feed update |
| challenge_received | Another player challenges | Challenge notification popup |
| leaderboard_shift | Ranking position changes | Leaderboard refresh |
---
## 12. Analytics & Telemetry Pipeline
### 12.1 Data Collection Flow
```mermaid
flowchart TD
subgraph "In-Game Collection"
Q[Question Served] --> A[Answer Recorded]
A --> T[Time Measured]
T --> D[Attempt Data Object Created]
end
subgraph "Attempt Data Structure"
D --> QT[question_type: mcq/tf/cs]
D --> QID[question_id: int]
D --> SID[session_id: string]
D --> IC[is_correct: boolean]
D --> TT[time_taken_ms: int]
D --> UID[user_id: uuid]
D --> TS[timestamp: datetime]
end
subgraph "Dispatch Pipeline"
D --> ONLINE{Network<br/>Available?}
ONLINE -->|Yes| BATCH[Batch Reporter]
ONLINE -->|No| LOCAL[Local Queue File]
BATCH --> SERVER[Server Analytics Engine]
LOCAL --> FLUSH[Auto-Flush Timer<br/>Every 60 seconds]
FLUSH --> ONLINE
end
subgraph "Server-Side Processing"
SERVER --> AGG[Aggregation Engine]
AGG --> ACC[Accuracy Rate Update]
AGG --> AVG[Avg Time Calculation]
AGG --> DIFF[Auto-Difficulty Adjustment]
AGG --> TREND[Daily Trend Computation]
end
```
### 12.2 Analytics Metrics Computed
| Metric | Computation | Use Case |
|--------|-------------|----------|
| times_served | COUNT per question | Popularity tracking |
| times_correct | COUNT where correct | Success rate |
| times_wrong | COUNT where wrong | Failure detection |
| times_skipped | COUNT where no answer | Confusion indicator |
| times_timeout | COUNT where time expired | Difficulty indicator |
| avg_time_ms | AVG(time_taken) | Speed analysis |
| accuracy_rate | correct / served | Difficulty calibration |
| completion_rate | (correct+wrong) / served | Engagement metric |
| skip_rate | skipped / served | Content quality signal |
| timeout_rate | timeout / served | Time limit appropriateness |
| daily_trend | Rolling 7-day window | Pattern detection |
| auto_difficulty | ML-computed float | Adaptive difficulty |
### 12.3 Question Flagging System
```mermaid
flowchart LR
PLAYER[Player Reports Issue] --> FLAG[Flag Submission]
FLAG --> TYPE{Report Type}
TYPE -->|Wrong Answer| WA[Incorrect content flag]
TYPE -->|Unclear| UC[Ambiguity flag]
TYPE -->|Offensive| OF[Content policy flag]
TYPE -->|Duplicate| DU[Duplicate detection flag]
WA --> REVIEW[Admin Review Queue]
UC --> REVIEW
OF --> REVIEW
DU --> REVIEW
REVIEW --> ACTION{Decision}
ACTION -->|Fix| EDIT[Edit Question]
ACTION -->|Remove| DEACTIVATE[Set is_active = false]
ACTION -->|Dismiss| CLOSE[Close Report]
```
---
## 13. Offline-First Architecture
```mermaid
flowchart TD
subgraph "Write Operations"
WRITE[Game Attempt / Score] --> CHECK{Online?}
CHECK -->|Yes| SEND[Send to Server Immediately]
CHECK -->|No| QUEUE[Write to Local Queue File]
QUEUE --> PERSIST[pending_attempts.json<br/>Persistent Storage]
end
subgraph "Auto-Sync Daemon"
TIMER[60-Second Timer] --> FLUSH_CHECK{Queue<br/>Not Empty?}
FLUSH_CHECK -->|Yes| NET_CHECK{Network<br/>Available?}
FLUSH_CHECK -->|No| TIMER
NET_CHECK -->|Yes| BATCH_SEND[Batch POST to Server]
NET_CHECK -->|No| TIMER
BATCH_SEND --> SUCCESS{Response OK?}
SUCCESS -->|Yes| CLEAR[Clear Queue]
SUCCESS -->|No| RETRY[Keep in Queue<br/>Retry Next Cycle]
CLEAR --> TIMER
RETRY --> TIMER
end
subgraph "Read Operations"
READ[Request Questions] --> CACHE_CHECK{Cached<br/>Available?}
CACHE_CHECK -->|Yes| SERVE[Serve from Cache]
CACHE_CHECK -->|No| FETCH[Fetch from API]
FETCH --> CACHE_STORE[Store in Local Cache]
CACHE_STORE --> SERVE
end
SEND --> SERVER[(Backend)]
BATCH_SEND --> SERVER
FETCH --> SERVER
```
### Offline Capabilities
| Operation | Offline Behavior |
|-----------|-----------------|
| Play Games | Full functionality with cached questions |
| Submit Answers | Queued locally, synced when online |
| View Profile | Served from PlayerPrefs cache |
| View Leaderboard | Last-known state displayed |
| Login/Signup | Requires network (graceful error) |
| Change Settings | Cached locally, synced later |
---
## 14. User Progression & Ranking System
### 14.1 Ranking Tiers
```mermaid
graph LR
subgraph "Rank Progression"
R1[Normal<br/>0 — 499 XP] --> R2[Pro<br/>500 — 1,999 XP]
R2 --> R3[Legend<br/>2,000+ XP]
end
style R1 fill:#8B8B8B,color:#fff
style R2 fill:#4169E1,color:#fff
style R3 fill:#FFD700,color:#000
```
### 14.2 XP Earning Model
| Action | XP Earned | Condition |
|--------|-----------|-----------|
| Correct Answer (MCQ) | Variable | Based on difficulty + speed |
| Correct Answer (TF) | Variable | Based on streak maintenance |
| Correct Sentence (CS) | Variable | Based on complexity |
| Game Completion | Bonus XP | Finishing all questions |
| Win Streak | Multiplier | Consecutive correct answers |
| Challenge Victory | Premium XP | Beating opponent |
| Daily Login | Small bonus | Engagement reward |
### 14.3 Progress Tracking
```mermaid
flowchart LR
GAME[Game Completed] --> CALC[Calculate XP Earned]
CALC --> ADD[Add to User Points]
ADD --> CHECK{New Rank<br/>Threshold?}
CHECK -->|Yes| PROMOTE[Rank Promotion Event]
CHECK -->|No| SAVE[Save Updated Points]
PROMOTE --> NOTIFY[Push Notification]
PROMOTE --> VFX[Celebration VFX]
PROMOTE --> LB[Update Leaderboard Position]
PROMOTE --> SAVE
SAVE --> SYNC[Sync to Supabase]
SYNC --> REALTIME[Broadcast to Realtime Subscribers]
```
---
## 15. Challenge & Competitive System
### 15.1 Challenge Architecture
```mermaid
sequenceDiagram
participant P1 as Player
participant App as Game Client
participant DB as Supabase
participant Analytics as Analytics Engine
P1->>App: Launch Challenge Mode
App->>App: Verify 5+ questions available per type
App->>App: Start challenge timer
loop Each Question
App->>P1: Present question
P1->>App: Submit answer
App->>App: Record time + correctness
end
App->>App: Calculate results
App->>DB: Insert challenge record
Note right of DB: has_won, points_earned,<br/>time_saved, timestamps
App->>Analytics: Batch report all attempts
App->>P1: Show challenge results
DB-->>App: Realtime: points_updated
App->>P1: XP animation + rank check
```
### 15.2 Challenge Metrics Tracked
| Metric | Purpose |
|--------|---------|
| has_won | Win/loss determination |
| points_earned | XP gained from challenge |
| time_saved | Speed bonus calculation |
| started_at | Session duration tracking |
| ended_at | Completion verification |
| Game type breakdown | Per-game performance |
### 15.3 Challenge Statistics Aggregation
```mermaid
pie title Challenge Performance Distribution
"Wins" : 62
"Losses" : 38
```
| Stat | Computation |
|------|-------------|
| Total Challenges | COUNT(*) |
| Win Rate | wins / total × 100 |
| Average Points | AVG(points_earned) |
| Best Time Saved | MAX(time_saved) |
| Current Streak | Consecutive wins |
---
## 16. UI/UX System Architecture
### 16.1 Screen Flow Map
```mermaid
graph TD
SPLASH[Splash / Boot Screen] --> AUTH_CHECK{Session<br/>Valid?}
AUTH_CHECK -->|Yes| HOME[Home Dashboard]
AUTH_CHECK -->|No| LOGIN[Login Screen]
LOGIN --> SIGNUP[Create Account Screen]
LOGIN --> GUEST[Guest Login]
LOGIN --> RESET[Password Reset]
SIGNUP --> HOME
GUEST --> HOME
HOME --> PROFILE[Profile Screen]
HOME --> LEADERBOARD[Leaderboard Screen]
HOME --> SETTINGS[Settings Screen]
HOME --> GAME_SELECT{Game Selection}
GAME_SELECT --> MCQ_GAME[MCQ Race Game]
GAME_SELECT --> TF_GAME[TF Production Line Game]
GAME_SELECT --> CS_GAME[CS Sentence Game]
GAME_SELECT --> CHALLENGE[Challenge Mode<br/>All 3 Games]
MCQ_GAME --> RESULTS[Results Screen]
TF_GAME --> RESULTS
CS_GAME --> RESULTS
CHALLENGE --> RESULTS
RESULTS --> HOME
PROFILE --> EDIT_PROFILE[Edit Profile]
PROFILE --> AVATAR[Avatar Selection]
PROFILE --> HISTORY[Game History]
```
### 16.2 UI Technology Stack
| Layer | Technology | Usage |
|-------|-----------|-------|
| Menu System | UI Toolkit (USS/UXML) | Modern declarative UI |
| In-Game HUD | Unity Canvas | Performance-critical overlays |
| Typography | TextMesh Pro | Arabic/English rendering |
| Animations | DOTween / UI Toolkit Transitions | Smooth transitions |
| Scene Transitions | EasyTransitions Package | Cinematic scene changes |
| Rounded Corners | Custom Shader Component | Modern card UI design |
| RTL Support | UniText Package | Arabic text direction |
### 16.3 Dashboard Components
```mermaid
graph TB
subgraph "Home Dashboard"
HEADER[Header Bar<br/>Avatar + Username + Rank Badge]
XP_BAR[XP Progress Bar<br/>Current / Next Rank Threshold]
GAMES[Game Cards Grid<br/>MCQ / TF / CS with availability check]
CHALLENGE_BTN[Challenge Button<br/>Competitive Mode Launcher]
ACTIVITY[Activity Feed<br/>Real-time 3 latest activities]
NAV[Navigation Bar<br/>Home / Leaderboard / Profile / Settings]
end
HEADER --> XP_BAR
XP_BAR --> GAMES
GAMES --> CHALLENGE_BTN
CHALLENGE_BTN --> ACTIVITY
ACTIVITY --> NAV
```
---
## 17. Asset Pipeline & Rendering
### 17.1 Visual Stack
```mermaid
graph TB
subgraph "Rendering Pipeline"
URP[Universal Render Pipeline 17.3]
SG[Shader Graph 17.3]
PP[Post-Processing Stack v3.5]
VFX_G[Visual Effect Graph]
end
subgraph "Visual Assets"
ENV[Sci-Fi City Environment]
CHAR[Robot Sphere Character]
PROPS[Science Lab Props]
HOLO[Hologram Effects]
FLAT[FlatKit Stylization]
CFXR[Cartoon FX Reloaded<br/>Particle Systems]
POLY[Polygon Arsenal VFX]
end
subgraph "Camera System"
CINE[Cinemachine 3.1.6]
GROUP[Target Group Framing]
TRACK[Dolly Track Following]
BLEND[Priority-Based Blending]
end
URP --> SG
URP --> PP
URP --> VFX_G
ENV --> URP
CHAR --> URP
CINE --> GROUP
CINE --> TRACK
CINE --> BLEND
```
### 17.2 Art Direction
| Category | Asset Type | Count/Scope |
|----------|-----------|-------------|
| Environment | Sci-Fi City (Modular) | Full tileset |
| Characters | Robot Sphere (Main Player) | Animated model |
| UI Art | Custom buttons, panels, cards | Full UI kit |
| VFX | Correct/Wrong feedback, celebrations | 20+ effects |
| Audio | BGM tracks + SFX library | Full audio design |
| Fonts | Arabic + English typography | Multi-weight families |
| Icons | Subject icons, navigation, actions | Complete icon set |
| Holograms | Futuristic displays, screens | Scene decorations |
### 17.3 Audio Architecture
```mermaid
graph LR
subgraph "Audio Manager"
BGM[Background Music<br/>Per-scene tracks]
SFX[Sound Effects<br/>Game events]
TICK[Timer Tick<br/>Countdown urgency]
UI_SFX[UI Sounds<br/>Button clicks, transitions]
end
subgraph "Audio Events"
CORRECT_SFX[Correct Answer Chime]
WRONG_SFX[Wrong Answer Buzz]
WIN_SFX[Victory Fanfare]
LOSE_SFX[Game Over Sound]
COUNTDOWN_SFX[Tick-Tock Warning]
RANK_UP_SFX[Rank Promotion Celebration]
end
BGM --> CORRECT_SFX
SFX --> WRONG_SFX
SFX --> WIN_SFX
SFX --> LOSE_SFX
TICK --> COUNTDOWN_SFX
UI_SFX --> RANK_UP_SFX
```
---
## 18. API Specification
### 18.1 Endpoint Map
```mermaid
graph TD
subgraph "PHP REST API"
subgraph "Health"
PING[GET /ping<br/>Health check]
end
subgraph "Taxonomy Endpoints"
GET_CURR[GET /get_curricula<br/>List curricula]
GET_GRADE[GET /get_grades<br/>List grades]
GET_TERM[GET /get_terms<br/>List terms]
GET_SUBJ[GET /get_subjects<br/>Subjects by curriculum]
GET_CHAP[GET /get_chapters<br/>Chapters with counts]
GET_TREE[GET /get_taxonomy_tree<br/>Full hierarchy]
end
subgraph "Question Endpoints"
GET_MCQ[GET /get_mcq<br/>MCQ questions]
GET_TF[GET /get_tf<br/>TF questions]
GET_CS[GET /get_cs<br/>CS questions]
GET_MIX[GET /get_mixed<br/>Interleaved mix]
end
subgraph "Analytics Endpoints"
POST_ATT[POST /report_attempt<br/>Single attempt]
POST_BATCH[POST /report_batch<br/>Batch attempts]
POST_FLAG[POST /report_question<br/>Flag content]
GET_STATS[GET /get_question_stats<br/>Question analytics]
end
end
```
### 18.2 Request/Response Specifications
| Endpoint | Method | Parameters | Response |
|----------|--------|-----------|----------|
| `/ping` | GET | None | `{status: "ok", version: "x.x"}` |
| `/get_curricula` | GET | None | Array of curriculum objects |
| `/get_grades` | GET | None | Array of grade objects |
| `/get_terms` | GET | None | Array of term objects |
| `/get_subjects` | GET | `curriculum_id` | Filtered subjects array |
| `/get_chapters` | GET | `curriculum_id, subject_id, grade_id, term_id` | Chapters with question counts |
| `/get_taxonomy_tree` | GET | `curriculum_id` | Nested tree structure |
| `/get_mcq` | GET | `chapter_id, count, shuffle, difficulty, bloom_level, verified_only` | MCQ question array |
| `/get_tf` | GET | Same as MCQ | TF question array |
| `/get_cs` | GET | Same as MCQ | CS question array |
| `/get_mixed` | GET | Same + type ratios | Interleaved mixed array |
| `/report_attempt` | POST | Attempt JSON body | `{success: true}` |
| `/report_batch` | POST | `{attempts: [...]}` | `{success: true, count: N}` |
| `/report_question` | POST | `question_type, question_id, reason, detail` | `{success: true}` |
| `/get_question_stats` | GET | `question_type, question_id` | Full analytics object |
### 18.3 API Security
| Measure | Implementation |
|---------|---------------|
| HTTPS Enforcement | SSL via CapRover/Nginx |
| Authentication | Supabase JWT attached to all requests |
| Rate Limiting | Per-IP throttling at Nginx level |
| Input Validation | Server-side sanitization on all parameters |
| SQL Injection Prevention | Prepared statements throughout |
| CORS Policy | Whitelisted origins only |
| Request Timeout | 15-second client-side timeout |
---
## 19. Deployment Architecture
```mermaid
graph TB
subgraph "Development"
DEV[Developer Machine<br/>Unity Editor 6]
GIT[Git Repository<br/>Version Control]
end
subgraph "Build Pipeline"
ANDROID[Android Build<br/>ARM64 APK/AAB]
IOS[iOS Build<br/>Xcode Archive]
WEBGL[WebGL Build<br/>Browser Deployment]
end
subgraph "Distribution"
PLAY[Google Play Store]
APPLE[Apple App Store]
WEB[Web Hosting<br/>WebGL Template]
end
subgraph "Backend Deployment"
CAPROVER[CapRover Server<br/>Docker Orchestration]
SUPA_CLOUD[Supabase Cloud<br/>Managed PostgreSQL]
end
DEV --> GIT
GIT --> ANDROID
GIT --> IOS
GIT --> WEBGL
ANDROID --> PLAY
IOS --> APPLE
WEBGL --> WEB
DEV --> CAPROVER
DEV --> SUPA_CLOUD
```
### Deployment Configuration
| Target | Build System | Output |
|--------|-------------|--------|
| Android | Unity Build Pipeline | AAB (Play Store) + APK (Direct) |
| iOS | Unity → Xcode | IPA via TestFlight/App Store |
| WebGL | Unity WebGL Template | Static hosting deployment |
| PHP Backend | Git push → CapRover | Auto-deploy Docker container |
| Database | Supabase Dashboard | Managed cloud infrastructure |
---
## 20. Cross-Platform Targets
```mermaid
graph TB
subgraph "Unity 6 Cross-Platform"
CORE[Shared Game Logic<br/>100% Cross-Platform]
end
subgraph "Platform Targets"
AND[Android<br/>ARM64 / ARMv7]
IOS_T[iOS<br/>iPhone / iPad]
WEBGL_T[WebGL 2.0<br/>All Modern Browsers]
end
subgraph "Platform Adaptations"
TOUCH[Touch Input<br/>Mobile optimized]
MOUSE[Mouse/Keyboard<br/>WebGL fallback]
PERF[Performance Scaling<br/>Per-device quality]
DEEP[Deep Linking<br/>iOS/Android URL schemes]
end
CORE --> AND
CORE --> IOS_T
CORE --> WEBGL_T
AND --> TOUCH
IOS_T --> TOUCH
WEBGL_T --> MOUSE
AND --> PERF
IOS_T --> PERF
AND --> DEEP
IOS_T --> DEEP
```
### Platform-Specific Features
| Feature | Android | iOS | WebGL |
|---------|---------|-----|-------|
| Touch Input | Native | Native | Emulated |
| Deep Linking | Intent Filters | Universal Links | URL Params |
| Push Notifications | FCM | APNS | Not supported |
| Offline Storage | Internal Storage | Documents | IndexedDB |
| Audio Format | OGG/MP3 | AAC/MP3 | MP3/WAV |
| Build Size Target | < 100MB | < 100MB | < 50MB |
| Min OS Version | Android 7.0+ | iOS 14+ | Chrome 90+ |
---
## 21. Performance Budgets & Optimization
### 21.1 Optimization Techniques Implemented
| Technique | System | Impact |
|-----------|--------|--------|
| Object Pooling | MCQ Gate recycling (6 objects) | Zero runtime allocation |
| Singleton Pattern | All services (persistent) | No duplicate instances |
| Async/Await | Network calls (UniTask) | Non-blocking I/O |
| Batch Reporting | Analytics pipeline | Reduced API calls by 90% |
| Local Caching | User profile (PlayerPrefs) | Instant UI rendering |
| Lazy Loading | Question fetching (on-demand) | Reduced memory footprint |
| Procedural Generation | Scene building (runtime) | Minimal scene file size |
| Texture Atlasing | UI elements | Reduced draw calls |
| LOD System | 3D environment | Distance-based quality |
| Shader Variants | URP stripping | Smaller build size |
### 21.2 Performance Targets
| Metric | Target | Platform |
|--------|--------|----------|
| Frame Rate | 60 FPS | All platforms |
| Load Time (cold) | < 3 seconds | Mobile |
| Scene Transition | < 1.5 seconds | All |
| API Response | < 500ms | 95th percentile |
| Memory Budget | < 512MB | Mobile |
| Build Size | < 100MB | Mobile |
| Battery Drain | < 5%/hour | Mobile |
| Network Timeout | 15 seconds | All |
---
## 22. Localization & Bilingual Support
### 22.1 Language Architecture
```mermaid
graph TB
subgraph "Bilingual System"
UNITEXT[UniText Framework<br/>Runtime Language Switching]
subgraph "Content Languages"
AR[Arabic Content<br/>Curriculum Questions]
EN[English Content<br/>Languages Curriculum]
end
subgraph "UI Languages"
AR_UI[Arabic UI<br/>RTL Layout]
EN_UI[English UI<br/>LTR Layout]
end
subgraph "Error Messages"
AR_ERR[Arabic Errors<br/>Localized auth messages]
EN_ERR[English Errors<br/>Default fallback]
end
end
UNITEXT --> AR
UNITEXT --> EN
UNITEXT --> AR_UI
UNITEXT --> EN_UI
AR_ERR --> UNITEXT
EN_ERR --> UNITEXT
```
### 22.2 RTL Support Implementation
| Component | RTL Handling |
|-----------|-------------|
| Text Rendering | TextMesh Pro with Arabic shaping |
| UI Layout | Mirrored flex direction |
| Sentence Display (CS) | Right-to-left word ordering |
| Navigation | Reversed button positions |
| Progress Bars | Right-to-left fill direction |
| Number Formatting | Hindi numerals option |
---
## 23. Data Flow Diagrams
### 23.1 Complete User Journey
```mermaid
flowchart TD
subgraph "Onboarding"
OPEN[Open App] --> SPLASH[Splash Screen]
SPLASH --> AUTH{Authenticated?}
AUTH -->|No| LOGIN_FLOW[Login / Register / Guest]
AUTH -->|Yes| RESTORE[Restore Session]
LOGIN_FLOW --> PROFILE_CREATE[Create Profile<br/>Grade + Curriculum + Term]
PROFILE_CREATE --> HOME_SCREEN[Home Dashboard]
RESTORE --> HOME_SCREEN
end
subgraph "Game Selection"
HOME_SCREEN --> SELECT{Choose Mode}
SELECT --> QUICK[Quick Play<br/>Single Game Type]
SELECT --> CHALLENGE_MODE[Challenge Mode<br/>All Types Combined]
QUICK --> CONFIG[Apply Session Config<br/>Grade + Term + Curriculum]
CHALLENGE_MODE --> CONFIG
end
subgraph "Game Execution"
CONFIG --> FETCH_Q[Fetch Questions<br/>Filtered by Config]
FETCH_Q --> PLAY[Play Game<br/>FSM Execution]
PLAY --> RECORD_ATTEMPTS[Record All Attempts]
end
subgraph "Post-Game"
RECORD_ATTEMPTS --> CALC_XP[Calculate XP Earned]
CALC_XP --> UPDATE_PROFILE[Update User Profile]
UPDATE_PROFILE --> SAVE_HISTORY[Save Game History]
SAVE_HISTORY --> REPORT_ANALYTICS[Report to Analytics]
REPORT_ANALYTICS --> RESULTS_SCREEN[Show Results]
RESULTS_SCREEN --> HOME_SCREEN
end
```
### 23.2 Data Write Path
```mermaid
flowchart LR
subgraph "Client Writes"
GH[Game History] --> SUPA_DB[(Supabase PG)]
CH[Challenge Result] --> SUPA_DB
PROFILE[Profile Update] --> SUPA_DB
end
subgraph "Analytics Writes"
ATT[Attempt Data] --> PHP_API[PHP API]
PHP_API --> MYSQL_DB[(MySQL)]
FLAG[Question Report] --> PHP_API
end
subgraph "Triggered Updates"
SUPA_DB --> RT[Realtime Broadcast]
RT --> OTHER_CLIENTS[Other Connected Clients]
MYSQL_DB --> AGG[Aggregation Triggers]
AGG --> STATS[Updated Question Stats]
end
```
### 23.3 Data Read Path
```mermaid
flowchart LR
subgraph "Content Reads"
TAX[Taxonomy Request] --> PHP_R[PHP API]
QUEST[Question Request] --> PHP_R
PHP_R --> MYSQL_R[(MySQL)]
MYSQL_R --> RESPONSE[JSON Response]
end
subgraph "User Data Reads"
PROF[Profile Fetch] --> SUPA_R[Supabase PostgREST]
LB_R[Leaderboard] --> SUPA_R
HIST[Game History] --> SUPA_R
SUPA_R --> PG_R[(PostgreSQL)]
PG_R --> USER_DATA[User Data Response]
end
subgraph "Real-Time Reads"
SUBSCRIBE[Subscribe Channel] --> WS_R[WebSocket]
WS_R --> LIVE[Live Data Push]
end
```
---
## 24. Security Model
### 24.1 Defense Layers
```mermaid
graph TB
subgraph "Layer 1: Transport"
TLS[TLS 1.3 Encryption<br/>All traffic encrypted]
end
subgraph "Layer 2: Authentication"
JWT_AUTH[JWT Token Validation<br/>Signed by GoTrue]
REFRESH[Token Refresh<br/>Short-lived access tokens]
ANON[Anonymous Sessions<br/>Limited permissions]
end
subgraph "Layer 3: Authorization"
RLS[Row Level Security<br/>Users see only own data]
POLICIES[PostgreSQL Policies<br/>Per-table access rules]
end
subgraph "Layer 4: Input Validation"
SANITIZE[Input Sanitization<br/>XSS prevention]
PREPARED[Prepared Statements<br/>SQL injection prevention]
RATE[Rate Limiting<br/>DDoS protection]
end
subgraph "Layer 5: Data Protection"
HASH[Password Hashing<br/>bcrypt with salt]
ENCRYPT[Sensitive Data<br/>Encrypted at rest]
GDPR[Account Deletion<br/>Full data cascade]
end
TLS --> JWT_AUTH
JWT_AUTH --> REFRESH
REFRESH --> RLS
RLS --> POLICIES
POLICIES --> SANITIZE
SANITIZE --> PREPARED
PREPARED --> RATE
RATE --> HASH
HASH --> ENCRYPT
ENCRYPT --> GDPR
```
### 24.2 Security Features Summary
| Domain | Implementation |
|--------|---------------|
| Transport Security | HTTPS/WSS everywhere, HSTS headers |
| Authentication | JWT with 1-hour expiry, auto-refresh |
| Session Management | Secure token storage, logout cleanup |
| Database Security | Row Level Security on all user tables |
| API Security | Prepared statements, input validation |
| Rate Limiting | Nginx-level per-IP throttling |
| Data Privacy | GDPR-compliant deletion via Edge Function |
| Password Security | bcrypt hashing, email verification required |
| Client Security | No secrets in client build, anon key only |
| Error Handling | Bilingual errors, no stack traces exposed |
---
## 25. Appendix — Full Feature Matrix
### A. Complete Feature Inventory
| # | Feature | Category | Complexity | Status |
|---|---------|----------|-----------|--------|
| 1 | Email/Password Authentication | Auth | High | Complete |
| 2 | Anonymous Guest Login | Auth | Medium | Complete |
| 3 | Guest-to-Email Account Conversion | Auth | High | Complete |
| 4 | Password Reset via Email | Auth | Medium | Complete |
| 5 | Email Verification Flow | Auth | Medium | Complete |
| 6 | Deep Link URL Handling | Auth | High | Complete |
| 7 | Session Persistence Across App Restarts | Auth | Medium | Complete |
| 8 | Automatic Token Refresh | Auth | Medium | Complete |
| 9 | Network Status Detection | Infrastructure | Medium | Complete |
| 10 | Bilingual Error Localization | Infrastructure | Medium | Complete |
| 11 | User Profile Creation | User | Medium | Complete |
| 12 | Profile Editing (Grade/Term/Curriculum) | User | Medium | Complete |
| 13 | Avatar Selection System | User | Low | Complete |
| 14 | XP Points System | Progression | Medium | Complete |
| 15 | Rank Progression (Normal→Pro→Legend) | Progression | Medium | Complete |
| 16 | XP Progress Bar UI | Progression | Low | Complete |
| 17 | Rank Promotion Celebration | Progression | Medium | Complete |
| 18 | Top-100 Leaderboard | Social | Medium | Complete |
| 19 | Current Player Position on Leaderboard | Social | Medium | Complete |
| 20 | Real-Time Activity Feed | Social | High | Complete |
| 21 | Real-Time Profile Sync | Social | High | Complete |
| 22 | MCQ Race Game Engine | Game | Very High | Complete |
| 23 | MCQ Gate Spawning & Pooling | Game | High | Complete |
| 24 | MCQ AI Competitor Racing | Game | High | Complete |
| 25 | MCQ Streak Bonus System | Game | Medium | Complete |
| 26 | MCQ Timer with Audio Warning | Game | Medium | Complete |
| 27 | MCQ Procedural Track Generation | Game | High | Complete |
| 28 | TF Production Line Game Engine | Game | Very High | Complete |
| 29 | TF Hand Controller (Touch/Drag) | Game | High | Complete |
| 30 | TF Conveyor Belt Physics | Game | Medium | Complete |
| 31 | TF Step Progress with Reset Penalty | Game | Medium | Complete |
| 32 | TF Factory Screen Question Display | Game | Medium | Complete |
| 33 | CS Sentence Correction Game Engine | Game | Very High | Complete |
| 34 | CS Word Drag-and-Drop System | Game | High | Complete |
| 35 | CS Circular Arc Option Layout | Game | High | Complete |
| 36 | CS AI Bot Assistant Character | Game | Medium | Complete |
| 37 | CS Dynamic Cinemachine Camera | Game | High | Complete |
| 38 | CS Hint System | Game | Medium | Complete |
| 39 | Shared Base Game Manager (Abstract) | Architecture | High | Complete |
| 40 | State Machine Pattern (All Games) | Architecture | High | Complete |
| 41 | Question Filter Builder (Fluent) | Architecture | Medium | Complete |
| 42 | REST API Client with Retry Logic | Networking | High | Complete |
| 43 | Offline Attempt Queue | Networking | High | Complete |
| 44 | Auto-Flush Timer (60s Interval) | Networking | Medium | Complete |
| 45 | Batch Attempt Reporting | Networking | Medium | Complete |
| 46 | WebSocket Real-Time Connection | Networking | High | Complete |
| 47 | Challenge Mode (Competitive) | Game Mode | High | Complete |
| 48 | Challenge History Tracking | Game Mode | Medium | Complete |
| 49 | Challenge Statistics (Win Rate, etc.) | Game Mode | Medium | Complete |
| 50 | Game History Recording | Analytics | Medium | Complete |
| 51 | Per-Question Analytics Collection | Analytics | High | Complete |
| 52 | Accuracy Rate Computation | Analytics | Medium | Complete |
| 53 | Average Time Computation | Analytics | Medium | Complete |
| 54 | Auto-Difficulty ML Adjustment | Analytics | High | Complete |
| 55 | Daily Trend Tracking | Analytics | Medium | Complete |
| 56 | Question Flagging/Reporting | Quality | Medium | Complete |
| 57 | Answer Distribution Analysis | Analytics | Medium | Complete |
| 58 | Taxonomy Tree API | Content | High | Complete |
| 59 | Multi-Curriculum Support (2) | Content | Medium | Complete |
| 60 | Multi-Grade Support (7-12) | Content | Medium | Complete |
| 61 | Multi-Term Support (1-2) | Content | Low | Complete |
| 62 | Multi-Subject Filtering | Content | Medium | Complete |
| 63 | Chapter-Level Granularity | Content | Medium | Complete |
| 64 | Bloom's Taxonomy Classification | Content | Medium | Complete |
| 65 | Difficulty Level Filtering | Content | Medium | Complete |
| 66 | Image-Attached Questions | Content | Medium | Complete |
| 67 | Audio-Attached Questions | Content | Medium | Complete |
| 68 | Question Shuffling | Content | Low | Complete |
| 69 | Verified Content Filtering | Content | Low | Complete |
| 70 | Mixed Question Mode | Content | Medium | Complete |
| 71 | Sci-Fi City Environment | Visual | High | Complete |
| 72 | Robot Sphere Character | Visual | High | Complete |
| 73 | Hologram Visual Effects | Visual | Medium | Complete |
| 74 | Particle Effect Systems (VFX) | Visual | High | Complete |
| 75 | Universal Render Pipeline | Visual | High | Complete |
| 76 | Post-Processing Effects | Visual | Medium | Complete |
| 77 | Cinemachine Camera System | Visual | High | Complete |
| 78 | Background Music System | Audio | Medium | Complete |
| 79 | Sound Effects System | Audio | Medium | Complete |
| 80 | Timer Tick Audio Feedback | Audio | Low | Complete |
| 81 | Music Toggle Per-Game | Audio | Low | Complete |
| 82 | UI Toolkit Menu System | UI | High | Complete |
| 83 | Arabic RTL Text Support | UI | High | Complete |
| 84 | Scene Transition Animations | UI | Medium | Complete |
| 85 | Rounded Corner UI Components | UI | Medium | Complete |
| 86 | Dynamic Color Theming | UI | Medium | Complete |
| 87 | Secure Account Deletion | Security | High | Complete |
| 88 | Row Level Security Policies | Security | High | Complete |
| 89 | GDPR Compliance (Data Removal) | Security | High | Complete |
| 90 | Cross-Platform Build (Android/iOS/WebGL) | Platform | High | Complete |
| 91 | Session Context Management | Architecture | Medium | Complete |
| 92 | Class Code System (Teacher Mode) | Feature | Medium | Complete |
| 93 | Content Management System (CMS) | Backend | Very High | Complete |
| 94 | Question Count Verification Before Launch | UX | Medium | Complete |
| 95 | PlayerPrefs Cache Layer | Performance | Medium | Complete |
| 96 | Object Pooling (Gate Recycling) | Performance | Medium | Complete |
| 97 | UniTask Async Operations | Performance | High | Complete |
| 98 | Event-Driven Architecture | Architecture | High | Complete |
---
### B. Technology Stack Summary
```mermaid
mindmap
root((Science Street<br/>Tech Stack))
Game Engine
Unity 6 (6000.3.9f1)
Universal Render Pipeline
Cinemachine 3.1
TextMesh Pro
UniTask
Post-Processing v3.5
Backend
PHP 8.x REST API
MySQL 8 Database
Supabase Cloud
PostgreSQL 15
GoTrue Authentication
Realtime WebSocket
Edge Functions (Deno)
Infrastructure
CapRover (Docker)
Nginx Reverse Proxy
SSL/TLS Encryption
Git Version Control
Third-Party SDKs
Supabase Unity SDK
Newtonsoft.Json
System.Reactive
WebSocket Client
OneOf (Discriminated Unions)
Visual Assets
Sci-Fi Environment Pack
FlatKit Styling
Cartoon FX Reloaded
Polygon Arsenal VFX
Hologram Shaders
EasyTransitions
Localization
UniText Framework
Arabic RTL Support
Bilingual Content
```
---
### C. Curriculum Coverage Map
| Grade | Term 1 | Term 2 | Subjects |
|-------|--------|--------|----------|
| 7th (1st Prep) | Full coverage | Full coverage | All curriculum subjects |
| 8th (2nd Prep) | Full coverage | Full coverage | All curriculum subjects |
| 9th (3rd Prep) | Full coverage | Full coverage | All curriculum subjects |
| 10th (1st Secondary) | Full coverage | Full coverage | All curriculum subjects |
| 11th (2nd Secondary) | Full coverage | Full coverage | All curriculum subjects |
| 12th (3rd Secondary) | Full coverage | Full coverage | All curriculum subjects |
**Curricula Supported:**
- Egyptian Languages Curriculum (مصري لغات) — English-medium questions
- Egyptian Arabic Curriculum (مصري عربي) — Arabic-medium questions
---
> **Document End**
> Prepared with precision by **AL-Arcade Development Studio**
> For **Science Street** — Transforming Egyptian Education Through Interactive Gaming
> Total Engineered Components: **98+ Features | 12+ Database Tables | 18+ API Endpoints | 3 Game Engines | 3 Real-Time Channels**
> Architecture designed for **1,000,000+ concurrent users** at scale.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment