From 9bac52084eb34c14e7febc24fcfccb41933ed979 Mon Sep 17 00:00:00 2001 From: Joseph Spiker Date: Tue, 25 Aug 2026 14:59:14 +0000 Subject: [PATCH] =?UTF-8?q?docs:=20bring=20the=20platform=20README=20curre?= =?UTF-8?q?nt=20=E2=80=94=20Art=20Studio,=20clients,=20tracker,=20and=20st?= =?UTF-8?q?ale-fact=20sweep?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New major sections: Art Studio (pipeline, GPU coordinator, per-model lanes, storage), Asset-to-Game Integration, Photography & Gallery, Native Mobile Apps, Activity Tracker/Routes/Paths, Godot Games Client, Desktop Stacker, Time & Materials, Direct Messages, Ops/Fleet/Status, C/C++/SQL/x86 tracks, sponsorship families/funds, and a More Platform Features roundup. Stale-fact sweep: Angular 22/TS 6/Nx 23, native apps no longer 'Future', PWA shipped, PeerJS replaced by VideoCallHub + coturn TURN, TileServerGL retired for PMTiles-on-MinIO, health-check set corrected, 102 C# lessons, lesson code moved to SpikerSoft.Business.CodeExecution, per-project test projects via SpikerSoft.UnitTests.slnf, library counts 84 across 6 layers, 28 event handlers inventoried, queue lanes updated, hiring page master-detail, data-driven menu bar, Marks CRM vs api/tm relationship. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01DoTHPhXe2qAAeQfs2itQb9 --- README.md | 786 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 666 insertions(+), 120 deletions(-) diff --git a/README.md b/README.md index 4564dfa..51a7eb1 100644 --- a/README.md +++ b/README.md @@ -6,35 +6,46 @@ Think Boy Scouts meets Geek Squad in the jungle. ## Platform Overview -SpikerSoft is a mono-repo whose **product** stack is these two repositories: +SpikerSoft is a multi-repo platform. The full product stack today: ``` -SpikerSoft Solution/ -├── spikersoft-angular/ # Frontend - Angular 21 Nx workspace -└── spikersoft-backend/ # Backend - .NET 10 C# solution +SpikerSoft Platform/ +├── spikersoft-angular/ # Web SPA — Angular 22 Nx workspace (PWA-enabled) +├── spikersoft-backend/ # .NET 10 C# solution — REST API, GameServer, 28 event-handler/worker projects +├── spikersoft-artpipe/ # ProArt ("art_pipe") — Python GPU generation pipeline behind the Art Studio +├── spikersoft-infrastructure/ # Docker Swarm stacks, Traefik, MinIO, OpenBao, runbooks, CI plumbing +├── spikersoft-ios/ # Native SwiftUI client (embedded Godot games, activity tracker) +├── spikersoft-android/ # Native Jetpack Compose client (embedded Godot games, activity tracker) +├── spikersoft-games-godot/ # Unified Godot 4 multi-mode game client (Space MVP; Dungeon/Voxel/Hex modes) +├── spikersoft-stacker/ # Desktop focus-stacking app (Rust + wgpu + egui, fully offline) +└── spikersoft-time-and-materials/ # Field-service Android app (backed by /api/tm in spikersoft-backend) ``` -Other sibling directories in this workspace (if present), such as experimental games or tooling, are **not** part of that core pair unless noted in their own README. +The web pair (`spikersoft-angular` + `spikersoft-backend`) remains the center of gravity; the other repos are real shipping surfaces documented in their own sections below ([Native Mobile Apps](#native-mobile-apps-ios--android), [Godot Games Client](#godot-games-client), [Desktop Stacker](#desktop-stacker-spikersoft-stacker), [Time & Materials](#time--materials-apitm), [Art Studio](#art-studio)). ### Frontend (spikersoft-angular) An Nx monorepo Angular application providing: -- **Integrated Game Ecosystem** - Three interconnected web-based games (Space, Voxel, Dungeon Crawler) forming a single MMO experience, plus standalone learning games (Chess, Fishing, puzzle games) +- **Integrated Game Ecosystem** - Three interconnected web-based games (Space, Voxel, Dungeon Crawler) forming a single MMO experience, a server-authoritative Hex Tower Defence, plus standalone learning games (Chess, Fishing, puzzle games) and a set of routed mini-games +- **Art Studio** - AI-assisted 2D/3D asset generation: prompt-to-3D pipeline (concept → modeling → texturing → rigging → animation → export → enrichment), concept candidate picking/editing, material variants, chess piece sets, a class gallery with moderation, focus stacking, and loading finished assets directly into the games - **Visual Programming Tools** - Blockly and Rete.js node-graph editors for programming in-game robots and spacecraft, doubling as real STEM learning tools - **3D Visualization** - Three.js and WebGL-powered immersive experiences -- **Developer Tools** - JSON editors, encoding utilities, visual flowcharts, SQL trainers, and a 95-lesson C# Coding Curriculum that compiles and grades student code either server-side or fully in-browser via a WebAssembly Roslyn runtime (works offline) -- **Geography & Travel** - Interactive world maps, country exploration, travel planning tied to real-world adventures -- **Sponsorship & Fundraising** - Stripe-powered donation system connecting donors with children's travel experiences -- **Real-time Communication** - PeerJS/WebRTC video calling, OvenPlayer live streaming, SignalR chat and notifications -- **Team & Hiring** - Dynamic staff profiles and data-driven open positions (ambassadors auto-generated per location) +- **Developer Tools** - JSON editors, encoding utilities, visual flowcharts, and coding playgrounds/curricula for **eight tracks** (C#, Python, JavaScript, Regex, C, C++, SQL, x86 assembly) that compile and grade student code either server-side or fully in-browser via WebAssembly runtimes (works offline) +- **Geography & Travel** - Interactive world maps, a shared WebGL geo-globe, country exploration, travel planning tied to real-world adventures +- **Photography** - Personal photo gallery with RAW uploads, burst/stack grouping, AI auto-tagging, and a camera-gear registry derived from EXIF +- **Activity Tracker** - MapLibre + PMTiles GPS tracker with recorded routes, analysis reports, and repeatable paths with attempts +- **Sponsorship & Fundraising** - Stripe-powered donation system connecting donors with individuals, families, family funds, and location funds +- **Real-time Communication** - Authenticated WebRTC video calling (SignalR signaling + coturn TURN), OvenPlayer live streaming, SignalR chat, 1:1 direct messages, and notifications +- **Team & Hiring** - Dynamic staff profiles and a master-detail careers page with a geo-globe (ambassadors auto-generated per location) **Tech Stack:** -- Angular 21 with standalone components +- Angular 22 with standalone components (TypeScript 6, Nx 23) - Three.js for 3D graphics -- RxJS for reactive state management +- RxJS + Signals for reactive state management - SignalR for real-time updates - Keycloak authentication integration +- PWA service worker with offline-first lesson grading ### Backend (spikersoft-backend) @@ -42,8 +53,8 @@ A multi-project .NET 10 solution providing: - **REST API** - Main platform API with CQRS pattern (MediatR) - **Game Server** - Purpose-built real-time multiplayer server -- **Event Handlers** - Distributed microservices for async processing -- **AI Services** - LLamaSharp-powered ML capabilities +- **Event Handlers** - 28 distributed worker projects for async processing (code grading, book pipeline, art pipeline, GPU coordination, photography, notifications, host-fleet monitoring, and more — see [Event Handlers](#event-handlers)) +- **AI Services** - LLamaSharp-powered ML capabilities, GPU-scheduled vision/quiz/embedding workers, and the artpipe model fleet **Tech Stack:** - .NET 10 / C# 14 @@ -63,11 +74,17 @@ A multi-project .NET 10 solution providing: │ CLIENTS │ ├─────────────────────────────────────────────────────────────────────────────┤ │ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ -│ │ Angular Web App │ │ Native iOS / │ │ Mobile PWA │ │ -│ │ (Browser) │ │ Android (Future)│ │ (Future) │ │ +│ │ Angular Web App │ │ Native iOS + │ │ Godot Games │ │ +│ │ (Browser, PWA) │ │ Android apps │ │ Client (embeds │ │ +│ │ │ │ (SwiftUI / │ │ in mobile apps) │ │ +│ │ │ │ Compose) │ │ │ │ │ └────────┬─────────┘ └────────┬─────────┘ └────────┬─────────┘ │ +│ ┌──────────────────┐ ┌──────────────────┐ │ +│ │ Time & Materials│ │ Desktop Stacker │ (Stacker is fully offline — │ +│ │ Android app │ │ (Rust, offline) │ no backend connection) │ +│ └────────┬─────────┘ └──────────────────┘ │ └───────────┼─────────────────────┼─────────────────────┼─────────────────────┘ - │ HTTPS/WSS │ WebSocket │ + │ HTTPS/WSS │ HTTPS + WSS │ WSS (GameServer) ▼ ▼ ▼ ┌─────────────────────────────────────────────────────────────────────────────┐ │ TRAEFIK (Load Balancer) │ @@ -79,15 +96,16 @@ A multi-project .NET 10 solution providing: ├─────────────────────────────────────────────────────────────────────────────┤ │ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ │ │ SpikerSoft.API │ │ SpikerSoft.Game │ │ Event Handlers │ │ -│ │ (REST + SignalR)│ │ Server (30Hz) │ │ (RabbitMQ) │ │ -│ │ │ │ │ │ │ │ -│ │ • Auth │ │ • Zone Manager │ │ • Embeddings │ │ -│ │ • CRUD APIs │ │ • Space Zones │ │ • File Movement │ │ -│ │ • Chat Hubs │ │ • Voxel Zones │ │ • Book Mgmt │ │ -│ │ • Sponsor API │ │ • Camp Zones │ │ • Code Execution│ │ -│ │ • Lessons API │ │ • Robot Engine │ │ (Roslyn) │ │ -│ │ • Geography API │ │ • Combat System │ │ • Image Desc. │ │ -│ │ • Notifications │ │ │ │ │ │ +│ │ (REST + SignalR)│ │ Server (30Hz) │ │ (RabbitMQ, 28 │ │ +│ │ │ │ │ │ worker projects)│ │ +│ │ • Auth │ │ • Zone Manager │ │ • Code Execution│ │ +│ │ • CRUD APIs │ │ • Space Zones │ │ • Book pipeline │ │ +│ │ • Chat + DM Hubs│ │ • Voxel Zones │ │ • ArtPipe (GPU) │ │ +│ │ • Sponsor API │ │ • Camp Zones │ │ • GPU Coordinator│ │ +│ │ • Lessons API │ │ • Robot Engine │ │ • Photography │ │ +│ │ • Geography API │ │ • Combat System │ │ • Notifications │ │ +│ │ • Tracker API │ │ │ │ • Fleet agents │ │ +│ │ • Art Studio API│ │ │ │ • …see inventory│ │ │ └────────┬─────────┘ └────────┬─────────┘ └────────┬─────────┘ │ └───────────┼─────────────────────┼─────────────────────┼─────────────────────┘ │ │ │ @@ -185,7 +203,8 @@ A real-time orbital mechanics simulation where players pilot spacecraft, manage - Server-authoritative physics with thruster simulation, docking, and autopilot - Spacecraft management with ownership enforcement - Beam weapons, mines, projectile combat, and radar/stealth systems -- All UI rendered as native WebGL overlays for performance +- All UI rendered inside the WebGL scene via the shared `@spikersoft/gl-hud` framework (no DOM overlays; see [gl-hud](#gl-hud--in-scene-webgl-ui)) +- In-scene options menu with rebindable keys (`KeybindingManager`, server-synced via `api/game/{gameId}/keybindings`) and persisted graphics settings (bloom, FXAA) - Procedural planet and asteroid generation ### Voxel World (Minecraft Port) @@ -216,6 +235,18 @@ An EverQuest-inspired multiplayer RPG with Ultima Online-style ruleset. Players | Ownership | Spacecraft per player | Robots per player | Characters per player | | Entity filtering | `ListShipsCommand` | `ListUnitsCommand` (voxel zones) | `ListCharactersCommand` (camp/dungeon zones) | +### gl-hud — in-scene WebGL UI + +All game UI for the space game and dungeon crawler renders **inside the Three.js scene** — an orthographic HUD scene composed of `troika-three-text` labels and shader-driven bars, with **zero DOM overlays**. This started as space-game code and is now the shared **`@spikersoft/gl-hud`** library (`spikersoft-angular/libraries/game/gl-hud/`), exporting `HUDScene`, `HUDPanel`, `HUDButton`, `HUDTextInput`, a layout manager, tooltips, prompts, messages, context menus, and a crosshair. New game UI should compose these primitives rather than reaching for HTML. + +### Beyond the MMO trio + +The three MMO games are not the whole games surface: + +- **Hex Tower Defence** — server-authoritative multiplayer tower defence. The TypeScript game reducer is ported to C# (`spikersoft-backend/SpikerSoft.Games.HexTowerDefence/`) and compiled into the API, with its own SignalR hub at `/hubs/hex-tower-defence` (it does **not** use the GameServer). Frontend lib: `spikersoft-angular/libraries/game/hex-tower-defence/`. Its buildings and enemies can be reskinned with Art Studio assets ([Asset-to-Game Integration](#asset-to-game-integration)). +- **Chess** — see [Chess Game](#chess-game); pieces can be Art-Studio-generated sets. +- **Routed mini-games** — `/deploy-defender`, `/github-sweeper`, `/snake-scroller`, `/commit-snake`, `/duality-game`, `/hexatile-game`, and `/perfidy` (an alias of the snake game), plus the fishing and puzzle games. Registered in `projects/spikersoft/src/routes.ts`. + --- ## Robot & Visual Programming @@ -271,11 +302,142 @@ The same Blockly and Rete editors available at `/tools/(tools:blockly)` and `/to --- -## Coding Curriculum & Playground (C#, Python, and JavaScript) +## Art Studio -SpikerSoft's flagship coding-education tools run three parallel tracks: +The **Art Studio** is SpikerSoft's AI-assisted asset creation suite: students and staff generate 2D concept art and full 3D game assets from text prompts and/or reference images, watch them move through a multi-stage GPU pipeline live, curate the results in a personal library and a moderated class gallery, and load finished assets directly into the platform's games. It spans four repos: the Angular feature (`spikersoft-angular/libraries/features/art-studio/`), the .NET orchestration (`spikersoft-backend`, ArtStudio domain + `SpikerSoft.EventHandlers.ArtPipeProcessor`), the Python model pipeline (`spikersoft-artpipe`, aka **ProArt / art_pipe**), and the per-model Swarm stacks (`spikersoft-infrastructure/spikersoft-artpipe-model-*`). -- **C# Playground** — 95-lesson curriculum graded by **Roslyn** server-side and **.NET WebAssembly** in the browser. +### What a user can do + +The shell (`art-pipe-shell`) is a five-view app routed at `/tools/(tools:art-studio)` behind AuthGuard: **Pipeline · Library · Metrics · Builder · Photo Stack**. + +- **Generate an asset** from a text prompt, an uploaded image, or both (`art-prompt-composer`). The composer exposes a per-stage "git rail" editor (concept → … → enrichment) with auto/manual/skip badges, per-stage model pickers, and workflow presets. Generation modes: **Generate**, **Batch**, **Preset Batch**, **Continuous**, and **Coverage Matrix** (the multi-asset modes are client-side fan-out over the single-asset submit endpoint). A distinct **Text → 3D** path skips the concept stage entirely (Shap-E / Hunyuan3D direct). +- **Pick a concept candidate** — the asset parks in `AwaitingSelection`, the concept stage's candidate images (typically 6) render as a grid, and `POST api/artstudio/{id}/select-concept` resumes the pipeline. Unpicked candidates stay reproducible (per-image seed recorded in provenance). +- **Edit a concept candidate by instruction** — `POST {id}/edit-concept`, backed by **FLUX.2 Klein** (fast) or **Qwen-Image-Edit** (precision). The asset re-parks for another pick after the edit completes (or fails), so editing is iterative. +- **Create material variants** — `POST {id}/material-variant` runs a "Material Variation (Paint 2.1)" retexture that preserves the source topology/UVs and produces a **sibling asset** (blobs copied, `VariantOfAssetId` provenance) — e.g. "the same knight, but polished gold". +- **Watch live progress** — stage timeline with state chips, percent, queue position ("you are Nth in line", published by the GPU coordinator), and per-stage phase text. Delivered over SignalR (`hubs/notifications`, `art.asset.lifecycle` events) and degrades to 4-second polling. +- **View results in-browser** — a three.js GLB viewer with skeleton/animation-clip playback and PBR channel switching (`art-asset-viewer`). +- **Manage a personal library** — search/status/kind/tag filters (deep-linkable query params), cancel, delete, **restart from any stage**, download. +- **Export** — per-artifact downloads, a streamed **ZIP bundle** (`GET api/artstudio/{id}/bundle`: mesh + textures + manifest), and a **games-handoff manifest** (`GET {id}/manifest`). +- **Share, remix, rate** — assets can be submitted to a class gallery (`art-gallery`), where staff moderate submissions and a flagged-content review queue (`/admin/art-review`, `/admin/art-gallery-review`). Approved gallery assets can be **remixed** by other users (`RemixOfAssetId` provenance) and rated. +- **Build chess piece sets** — one theme fans out into six per-piece prompts (`api/artstudio/chess-sets`); sets can be shared, moderated, and **equipped** into the chess game. See [Asset-to-Game Integration](#asset-to-game-integration). +- **Photo Stack (focus stacking)** — pick 2–60 frames from the photo gallery (grouped by upload session), choose a fusion engine, watch `develop → align_fuse` run, and get a fused JPEG preview plus 16-bit TIFF downloads. **Photo Touch-Up** then offers Zerene-style retouching: paint aligned source-frame pixels through a soft brush over the fused composite; the result saves back to the photo gallery as a new Touchup photograph (the original is never modified). +- **Staff/admin extras** — a d3 metrics dashboard (`/admin/art-metrics`), a live "Now Running" activity board, a **visual Pipeline Builder** node graph that saves as a workflow preset (`/admin/art-builder`), preset management (`/admin/art-presets`), and a usage meter. + +Two adjacent GPU lanes are *not* Art Studio stages but ride the same worker fleet: **QR Art** (`api/qrart` — stylized QR code generation, `classic` SD1.5 QR Monster v2 and `sdxl` variants; the anonymous client-side QR tool stays anonymous, this authenticated lane adds the AI stylization) and **photo auto-tagging** (Florence-2 tags gallery photographs; see [Photography & Gallery](#photography--gallery)). + +### Guardrails (students are minors) + +- Prompt moderation: blocklist screening, 500-char cap, URL/PII rejection (`Business/Domain/ArtStudio/Services/Guardrails/`). +- Per-student generation quotas and a full audit log (`art-studio-audit` collection). +- `AllowStudentMethodChoice` defaults **false** — model/method choice is staff-first; students get the curated defaults. +- **Fail-closed safety gate**: every output image is classified (resident SafetyCheck model, ~20 ms/check via RPC). Flagged artifacts move to a quarantine bucket with staff-only download; generation parameters pass through an allow-list + clamp policy (`ArtStudioGenerationParamPolicy`). +- No anonymous gallery; all sharing is authenticated and moderated. + +### Pipeline architecture (spikersoft-artpipe) + +The Python side is three processes over HTTP (see `spikersoft-artpipe/CLAUDE.md` for the deep-dive): + +| Process | Role | +|---|---| +| **ArtPipe server** (`src/artpipe/server.py`, :9100) | Stdlib-only dispatcher; discovers model backends by their `models/*/artpipe.json` manifests; never imports model code | +| **Batch orchestrator** (`src/artpipe/batch/`, Flask + SQLite, :5000) | Multi-asset batch/curation workflows | +| **Blender addon** (`src/addons/artpipe/`) | In-Blender integration | + +- **Worker protocol**: one JSON object on stdin, newline-delimited JSON events (`started|progress|result|error`) on stdout; model stdout is redirected to stderr so prints can't corrupt the protocol. +- **Stages** are independent modules (`src/artpipe/batch/stages/`): `concept`, `modeling`, `texturing`, `rigging`, `animation`, `export`, `enrichment`. Stage plans vary by asset type: **character** = all 7; **prop/equipment** skip animation; **environment** skips rigging + animation. +- **Rigging fallback chain**: UniRig → RigNet → Mesh2Rig → smart_rig. +- **Enrichment** normalizes meshes, renders thumbnails / MP4/GIF animation previews / multi-view screenshots, and runs CV analysis (symmetry, silhouette, form, color auto-tags). +- **Blender daemon pool**: persistent headless Blender HTTP servers (ports 19200+) with session leasing, auto-recycle after 50 batches, and stale-lease reclamation. +- Each model backend lives in its own venv with an `artpipe.json` manifest as the contract. Current manifest inventory spans text-to-image (SDXL Lightning/Turbo, Flux Schnell), image editing (FLUX.2 Klein, Qwen-Image-Edit), image→3D (TripoSR, TripoSG, SF3D, InstantMesh, Pixal3D, Hunyuan3D, Hunyuan3D-Omni, Trellis-mac), text→3D (Shap-E, Hunyuan3D), texturing (Hunyuan3D-Paint 2.0/2.1, SD-Turbo-Tex), rigging (UniRig, RigNet), motion (MDM), pose (OpenPose), photo (photostack), QR stylization (QR Monster), tagging (Florence-2), and safety (safety_check). + +### Backend orchestration (SpikerSoft.EventHandlers.ArtPipeProcessor) + +The .NET GPU worker that shells out to art_pipe. Three mutually exclusive execution modes (`ArtPipeConfig`): + +| Mode | Config | Behavior | +|---|---|---| +| **Per-stage** | `ArtPipe:Stages` | Consumes `art.asset.stage.requested.`; per-task GPU lease (acquire → load → run → unload → release) | +| **Resident** | `ArtPipe:ResidentModel` | Keeps one model in VRAM for the container lifetime; consumes `art.model..tasks`; recycles after N jobs (e.g. SDXLLightning 500) | +| **Model-queue** | `ArtPipe:ModelQueue` | Same per-model queue, but a short-lived subprocess + per-job lease — an **idle container holds zero VRAM**. This is the production doctrine | + +Routing is API-side via `ArtStudio:StageModelMap` (production defaults: `modeling → Pixal3D`, `texturing → Hunyuan3DPaint21`) with worker-side `ArtPipe:PublishToModelQueues`. Resilience pieces: a stage-run watchdog, capped failover down the per-stage method list (`ArtStudioStageFailoverOptions` — `PinnedOnly` methods like the edit models and material variation are excluded from failover chains and generation pickers), and display-derivative generation (400 px AVIF thumbnail + display AVIF via the shared `cavif` wrapper; TIFF path for PhotoStack). A separate single-replica `ArtStudioMetrics` handler folds lifecycle events into the metrics read model. + +### GPU coordinator & VRAM leases + +`SpikerSoft.EventHandlers.GpuCoordinator` is a **cluster-wide VRAM lease broker**: every GPU-bound worker (artpipe stages, image description, embeddings, quiz generation) must acquire a lease before touching a CUDA context. + +- RabbitMQ lanes: `gpu.lease.requests` / `.releases` / `.task-complete` / `.queue-status` / `.keepalive`, plus per-node heartbeats. +- VRAM-aware concurrent grants with model-affinity scheduling; **queue-position publishing** feeds the UI's "Nth in line". +- A **durable Redis ledger** reloads booked VRAM + active leases on restart so a coordinator bounce can't double-grant into GPUs workers still hold. +- Deliberately **replicas: 1** (in-memory tracker is authoritative), pinned to node `SERVER`, HTTP `/health` + `/gpu/status` on port 8090 (not exposed through Traefik). +- **Two permanent GPU lanes**: the **4090** node (RTX 4090, 24,576 MB budget, permanent holder of the `artpipe-gpu` placement label — runs the artpipe model stacks and image description) and **SERVER** (RTX 3070 Ti, 8,192 MB — runs embeddings, quiz generation, and the CPU-only PhotoStack lane). Lease requests carry `requiredNodeId` so a grant is budgeted against the requester's own card. + +### Per-model container lanes (spikersoft-infrastructure) + +Each model runs as its own Swarm stack with fully **baked weights** (`HF_HUB_OFFLINE=1`; two-tier image build `artpipe-base` → `artpipe-model-env-` → `artpipe-model-`). Placement rides the movable `node.labels.artpipe-gpu == true` label; restart policy is `condition: any` (a clean exit-0 on broker loss must not wedge a lane at zero consumers). + +| Stack (`spikersoft-artpipe-…`) | Stage / role | VRAM (MB) | +|---|---|---| +| `modeling` (prodstages monolith) | all 7 stages serialized, per-task leases | per-stage | +| `-model-sdxl` | concept (SDXL Lightning, resident) — currently stood down (replicas 0) | — | +| `-model-flux2klein` | concept `edit_image` (FLUX.2 Klein) | 13,000 | +| `-model-qwenedit` | concept `edit_image` (Qwen-Image-Edit; also needs ~15 GB host RAM for the offloaded text encoder) | 16,000 | +| `-model-triposr` | modeling `image_to_3d` | 3,000 | +| `-model-sf3d` | modeling `image_to_3d` | 6,000 | +| `-model-shape` | modeling `text_to_3d` (Shap-E) | 6,000 | +| `-model-instantmesh` | modeling `image_to_3d` | 8,000 | +| `-model-triposg` | modeling `image_to_3d` | 8,000 | +| `-model-hunyuanomni` | modeling `image_to_3d` with **bbox proportion control** (Hunyuan3D-Omni) | 11,000 | +| `-model-textto3d` | modeling (Hunyuan3D text/image→3D) | 14,000 | +| `-model-pixal3d` | modeling `image_to_3d`, PBR output — **production default** | 18,000 | +| `-model-hunyuan` | texturing `texture_mesh` (Hunyuan3D-Paint 2.0, diffuse) | 14,000 | +| `-model-hunyuan21` | texturing `texture_mesh` (Hunyuan3D-Paint **2.1**, full PBR maps) — **production default** | 21,000 | +| `-model-blender` | resident Blender (rig/animate/export/enrich) | ~2,048 | +| `-model-safety` | resident SafetyCheck gate | ~1,024 | +| `-model-qrmonster` | QR Art lane (on-demand, replicas 0 when idle) | 9,500 | +| `-model-florence2` | photo auto-tag lane (pinned to SERVER; replicas 0 when idle) | 2,048 | +| `photostack` | `develop` + `align_fuse`, **CPU-only** (`BypassGpuLease=true`, pinned to SERVER) | 0 | + +VRAM figures are lease sizes (torch *reserved*, not allocated — measured on live runs, not taken from upstream quotes). + +### Storage & data + +- **MinIO buckets**: `art-asset-artifacts` (all pipeline artifacts — concept images, meshes, PBR texture maps, rigs, animations, exports, AVIF derivatives) and `art-asset-quarantine` (safety-flagged, staff-only). Artifacts are referenced by MinIO-native object keys — **not** GridFS. +- **Mongo collections**: `art-assets` (owner, type, prompt, stage plan, artifacts + selections, safety flags, gallery status, remix/variant/chess-set provenance, method selections + provenance, ratings), `art-asset-stage-runs` (per-run state/timings/retries), `chess-piece-sets`, `darkroom-develop-jobs`, `art-variant-upload-jobs` (ClamAV-scanned user uploads), `qr-art-jobs`, `art-studio-audit`, `art-workflow-presets`, `game-art-loadouts`, `art-studio-metrics`. +- Artifact **kinds** are extension-aware (`ArtifactKinds.cs`): images `png|jpeg|webp|tiff`, meshes `glb|gltf|obj|fbx|ply|stl|usdz`, video `mp4|webm`; texturing images classify into a PBR map taxonomy. The only wired export action today is **`export_glb`** (Blender) — the wider mesh-format list is the classifier's vocabulary, not a user-facing export menu. +- Tracing propagates end-to-end: the API's stage-request publish carries trace context through the .NET worker into the Python subprocess (`JAEGER_ENDPOINT`), so one Jaeger trace covers submit → GPU job → artifact upload. + +--- + +## Asset-to-Game Integration + +Finished Art Studio assets don't stop at downloads — they skin the platform's games. + +### The handoff contract + +`GET api/artstudio/{id}/manifest` (and `gallery/{id}/manifest` for gallery assets) returns a **games manifest**: root-relative artifact download routes, rig info, attribution, and provenance. The Angular side consumes it through `art-asset-game-loader.service.ts`, which downloads the game-ready GLB, parses it with `GLTFLoader`, and normalizes it — the returned container Group is scaled so its largest dimension is 1 world unit, XZ-centered, and grounded at y=0, so any game can place it with a single uniform scale + translation. A budget backstop (`ART_ASSET_MODEL_BUDGET`: 192 MB / 2,000,000 triangles) protects the render loop, and blobs are cached in memory + the Cache API. + +### Per-game loadouts + +`api/game/{gameId}/art-loadout` (`GameArtLoadoutController`, Mongo `game-art-loadouts`) persists which asset fills which **slot** per game. The slot registry (`GameArtSlotRegistry.cs`): + +| Game | Slots | +|---|---| +| `chess` | 12 — `piece:{pawn,rook,knight,bishop,queen,king}` for player 1 and `opponent:*` for player 2 | +| `hex-tower-defence` | 9 — `building:{command-center, matter-mine, solar-power-plant, laser-tower, barricade, build-slot}`, `enemy:{normal, gargantuan, zerg}` | + +- **Chess** (`art-studio-chess-piece-model-provider.ts`) diffs slot descriptors, loads sequentially against an aggregate byte budget, hot-swaps piece prototypes before disposing retired ones, and falls back to the bundled classic mesh on `tooLarge`/`loadFailed`. Custom meshes are never tinted — each side is its own set. +- **Chess piece sets** are a first-class domain: `CreateChessPieceSetCommandHandler` fans one theme into six per-piece prompts (`ChessPieceSetPrompts.cs` — composition constraints lead, theme last, shared negative prompt), pieces are modeled (production default Pixal3D), and completed sets can be shared to the gallery, moderated, and **equipped**. +- **Hex Tower Defence** uses a per-slot `art-asset-picker` with lazy `import("@spikersoft/feature-art-studio")`, explicit disposal per slot, and a `tooHeavy` toast. +- The **voxel game is not yet connected** to the Art Studio path, and the older `3d-models` collection (`ThreeDModelController`) is a read-only legacy surface — new modeling goes through the Art Studio. + +--- + +## Coding Curriculum & Playground (Eight Tracks) + +SpikerSoft's flagship coding-education tools run **eight parallel language tracks** — C#, Python, JavaScript, Regex, C, C++, SQL, and x86 assembly (`LessonCatalogService.listByLanguage()` accepts exactly that set). The original four are documented in depth here; the four newer tracks are covered in [C, C++, SQL & x86 tracks](#c-c-sql--x86-tracks) below. + +- **C# Playground** — 102-lesson curriculum graded by **Roslyn** server-side and **.NET WebAssembly** in the browser. - **Python Playground** — ~101-lesson curriculum graded by **CPython subprocess** server-side and **Pyodide** in the browser. - **JavaScript Playground** — **138-lesson** curriculum (10 orientation tutorials + 128 graded challenges across 17 tiers) graded by **Node** server-side and **QuickJS-WASM** in the browser. Same harness contract as Python (`runTests()` returning `PASS:`/`FAIL:`/`ERROR:` strings); offline + Run-locally + batch re-verification all work the same way. Every challenge ships with a reference solution that's executed end-to-end through Node by `JavaScriptChallenge_ReferenceSolutionPasses`. - **Regex Playground** — 12-chapter curriculum graded **fully in-browser** against a pre-computed `RegexLessonPlan` and re-verified server-side through Node (not .NET) to preserve JavaScript regex flavor. See [Regex Playground & Curriculum](#regex-playground--curriculum) below. @@ -286,7 +448,7 @@ Backend dispatch is strategy-based: `ILessonGradingExecutorFactory` picks Roslyn ### Python curriculum tiers -Python lessons live under [`SpikerSoft.Business/Domain/Lessons/Curriculum/Python/`](spikersoft-backend/SpikerSoft.Business/Domain/Lessons/Curriculum/Python/) and mirror the C# tier structure plus a Python-only `TierP1_Pythonic` for idioms that have no C# parallel: +Python lessons live under [`SpikerSoft.Business.CodeExecution/Domain/Lessons/Curriculum/Python/`](spikersoft-backend/SpikerSoft.Business.CodeExecution/Domain/Lessons/Curriculum/Python/) and mirror the C# tier structure plus a Python-only `TierP1_Pythonic` for idioms that have no C# parallel: | Tier | Numbers | Topic | |------|---------|-------| @@ -311,7 +473,7 @@ Python lessons live under [`SpikerSoft.Business/Domain/Lessons/Curriculum/Python ### JavaScript curriculum tiers -JavaScript lessons live under [`SpikerSoft.Business/Domain/Lessons/Curriculum/JavaScript/`](spikersoft-backend/SpikerSoft.Business/Domain/Lessons/Curriculum/JavaScript/) and use the **30000–31999** lesson-number range with `DisplayNumber = LessonNumber - 30000` so the sidebar shows clean 1..N badges. See [`.cursor/rules/javascript-curriculum.mdc`](.cursor/rules/javascript-curriculum.mdc) for the locals-first / Node↔QuickJS parity rules: +JavaScript lessons live under [`SpikerSoft.Business.CodeExecution/Domain/Lessons/Curriculum/JavaScript/`](spikersoft-backend/SpikerSoft.Business.CodeExecution/Domain/Lessons/Curriculum/JavaScript/) and use the **30000–31999** lesson-number range with `DisplayNumber = LessonNumber - 30000` so the sidebar shows clean 1..N badges. See [`.cursor/rules/javascript-curriculum.mdc`](.cursor/rules/javascript-curriculum.mdc) for the locals-first / Node↔QuickJS parity rules: | Tier | Numbers | Topic | |------|---------|-------| @@ -335,24 +497,51 @@ JavaScript lessons live under [`SpikerSoft.Business/Domain/Lessons/Curriculum/Ja ### Pedagogical contract: no concept used before introduction -Every challenge lesson is reachable from "Hello, World" using only language features that were formally introduced (and graded) in an earlier lesson. A student who works the curriculum top-to-bottom never has to leave the platform to look up a syntactic form they have not been taught — if the reference solution uses an `f-string`, decorator, optional chain, or generator expression, an earlier lesson exists that puts that exact construct in front of them as a "recipe to copy". Reference solutions for every gradable challenge are checked in alongside the lesson and executed end-to-end on every CI run (`ReferenceSolutionPasses` in `SpikerSoft.Tests.Unit`), so a contract violation surfaces as a red test, not as a confused student. +Every challenge lesson is reachable from "Hello, World" using only language features that were formally introduced (and graded) in an earlier lesson. A student who works the curriculum top-to-bottom never has to leave the platform to look up a syntactic form they have not been taught — if the reference solution uses an `f-string`, decorator, optional chain, or generator expression, an earlier lesson exists that puts that exact construct in front of them as a "recipe to copy". Reference solutions for every gradable challenge are checked in alongside the lesson and executed end-to-end on every CI run (the `ReferenceSolutionPasses` tests, collected by the `SpikerSoft.UnitTests.slnf` solution filter), so a contract violation surfaces as a red test, not as a confused student. -The audit findings that drove the most recent Python and JavaScript reorderings are living documents in [`docs/curriculum/python-curriculum-audit.md`](docs/curriculum/python-curriculum-audit.md) and [`docs/curriculum/javascript-curriculum-audit.md`](docs/curriculum/javascript-curriculum-audit.md). Re-run the same audit when adding a new tier; the verification gate is `dotnet test SpikerSoft.Tests.Unit --filter "ReferenceSolutionPasses"`. +The audit findings that drove the most recent Python and JavaScript reorderings are living documents in [`docs/curriculum/python-curriculum-audit.md`](docs/curriculum/python-curriculum-audit.md) and [`docs/curriculum/javascript-curriculum-audit.md`](docs/curriculum/javascript-curriculum-audit.md). Re-run the same audit when adding a new tier; the verification gate is `dotnet test SpikerSoft.UnitTests.slnf --filter "ReferenceSolutionPasses"`. ### Regex Playground & Curriculum -The fourth language track is a 12-chapter regular-expressions curriculum under [`SpikerSoft.Business/Domain/Lessons/Curriculum/Regex/`](spikersoft-backend/SpikerSoft.Business/Domain/Lessons/Curriculum/Regex/) (`Chapter01_Literals` → `Chapter12_Recipes`, covering literals, special characters, character classes, quantifiers, anchors, alternation, capturing groups, back-references, replace, lookarounds, named groups + Unicode property escapes, and idiomatic recipes). Lessons are written as `RegexLessonStrategyBase` subclasses that hand the SPA a serialized `RegexLessonPlan` — test text plus expected matches/replacements row-by-row. +The fourth language track is a 12-chapter regular-expressions curriculum under [`SpikerSoft.Business.CodeExecution/Domain/Lessons/Curriculum/Regex/`](spikersoft-backend/SpikerSoft.Business.CodeExecution/Domain/Lessons/Curriculum/Regex/) (`Chapter01_Literals` → `Chapter12_Recipes`, covering literals, special characters, character classes, quantifiers, anchors, alternation, capturing groups, back-references, replace, lookarounds, named groups + Unicode property escapes, and idiomatic recipes). Lessons are written as `RegexLessonStrategyBase` subclasses that hand the SPA a serialized `RegexLessonPlan` — test text plus expected matches/replacements row-by-row. Grading mechanics differ deliberately from the C# / Python / JavaScript tracks: - **Live grading runs entirely in the browser.** [`RegexLessonRunnerService`](spikersoft-angular/libraries/features/dev-tools-reg-ex/src/lib/regex-lesson-runner.service.ts) and [`RegexLessonGraderService`](spikersoft-angular/libraries/features/dev-tools-reg-ex/src/lib/regex-lesson-grader.service.ts) (in `libraries/features/dev-tools-reg-ex`) feed the student's pattern through the browser's native `RegExp` against the plan's test text and emit row-level pass/fail just like the JS / Python harnesses do. -- **Server re-verification routes through Node, not .NET.** When an offline-graded regex submission flushes through `POST /api/Lessons/progress/batch`, [`RegexLessonGradingExecutor`](spikersoft-backend/SpikerSoft.Business/Domain/CodeExecution/Execution/RegexLessonGradingExecutor.cs) builds a JS harness via [`RegexLessonHarnessBuilder`](spikersoft-backend/SpikerSoft.Business/Domain/CodeExecution/Execution/RegexLessonHarnessBuilder.cs) and runs it on the worker's Node subprocess (the same `JavaScriptLessonExecutor` plumbing the JS Playground uses). This is **not** a casual choice — JavaScript regex differs from .NET's `System.Text.RegularExpressions` in ways that matter for the curriculum: the `v`-flag set operations `[[a-z]--[aeiou]]` / `&&`, JS-only replacement patterns `` $` `` / `$'`, and the supported `\p{...}` Unicode property names all diverge between engines. Re-grading through Node guarantees the server records the same pass/fail the student saw locally. +- **Server re-verification routes through Node, not .NET.** When an offline-graded regex submission flushes through `POST /api/Lessons/progress/batch`, [`RegexLessonGradingExecutor`](spikersoft-backend/SpikerSoft.Business.CodeExecution/Domain/CodeExecution/Execution/RegexLessonGradingExecutor.cs) builds a JS harness via [`RegexLessonHarnessBuilder`](spikersoft-backend/SpikerSoft.Business.CodeExecution/Domain/CodeExecution/Execution/RegexLessonHarnessBuilder.cs) and runs it on the worker's Node subprocess (the same `JavaScriptLessonExecutor` plumbing the JS Playground uses). This is **not** a casual choice — JavaScript regex differs from .NET's `System.Text.RegularExpressions` in ways that matter for the curriculum: the `v`-flag set operations `[[a-z]--[aeiou]]` / `&&`, JS-only replacement patterns `` $` `` / `$'`, and the supported `\p{...}` Unicode property names all diverge between engines. Re-grading through Node guarantees the server records the same pass/fail the student saw locally. - **No second runtime in the browser.** Regex lessons need no Pyodide, no QuickJS, no .NET WASM — the SPA already has `RegExp`. Offline support is automatic. Server-side regex re-grading rides the [Offline Lesson Re-Grading via Worker RPC](#offline-lesson-re-grading-via-worker-rpc) pipeline (below), so a code-runner crash does not take the API down. SPA-side details and the in-app "Lesson" pane / cheatsheet UI live in [`libraries/features/dev-tools-reg-ex/README.md`](spikersoft-angular/libraries/features/dev-tools-reg-ex/README.md). > **WASM exclude.** [`SpikerSoft.Wasm.csproj`](spikersoft-backend/SpikerSoft.Wasm/SpikerSoft.Wasm.csproj) excludes `Regex*.cs` from the link-included executor sources — regex re-grading is server-only by design. +### C, C++, SQL & x86 tracks + +Four further curricula live under `SpikerSoft.Business.CodeExecution/Domain/Lessons/Curriculum/{C,Cpp,Sql,X86}/`: + +| Track | Size | Browser runtime | Playground route | +|---|---|---|---| +| **C** | 11 chapters | browser Clang toolchain (see below) | `/tools/(tools:c-playground)` | +| **C++** | 11 chapters | browser Clang toolchain | `/tools/(tools:cpp-playground)` | +| **SQL** | 24 chapters | DuckDB-WASM | `/tools/(tools:sql-playground)` | +| **x86 assembly** | 13 chapters | Blink WASM emulator | `/tools/(tools:x86-playground)` | + +- **Browser Clang toolchain** (`libraries/platform/clang-runtime/`): a worker-hosted Clang + LLD + WASI-libc + libc++ build (browsercc, LLVM 20.1.2) paired with `@bjorn3/browser_wasi_shim` — full C89→C23 and C++98→C++23 entirely client-side, a sibling to the Roslyn-WASM / Pyodide / QuickJS runtimes. Server-side grading routes through `CCodeRunnerController` / `CppCodeRunnerController` / `SqlCodeRunnerController`. +- **x86 playground** (`libraries/features/dev-tools-x86-playground/`): the Blink-based emulator with a GDB-like register/memory/disassembly debugger; supports GNU `as`, FASM, and NASM syntax — all client-side. (This was a TODO item; it shipped.) +- **Pattern courses** also exist per language at `/learn/coding/patterns/{c,cpp,sql,x86}`. + +### Git playground & software-practice tracks + +Version-control and testing are curricula too: `/learn/coding/version-control/git` (lessons) and `/learn/coding/version-control/git/playground` (a real Gitea-backed sandbox — credentials stay server-side behind a broker, `GitPlaygroundController`), plus unit / integration / e2e testing tracks. + +### Playground visual progress trail + +The former standalone "My Journey" page is now the **Progress** tab inside each playground's curriculum pane (`libraries/platform/playground-visual-progress/`): a per-language winding trail of lesson stops with a daily streak, a celebratory sound on completion, and a reduced-motion variant. Opt-in, remembered per playground. + +### Content locale + +`libraries/platform/content-locale/` is the single reactive source of truth for the language that server-localized content arrives in (lesson catalog, journey trail, curriculum chapters, geography facts, attempt instructions). Any surface sending `contentLocale` follows its documented two-obligation contract so UI locale and content locale can't drift apart. + ### Dual execution | Path | Where | When | Latency | @@ -388,9 +577,9 @@ Two startup probes (`PythonInterpreterProbeHostedService` and `NodeInterpreterPr Browser graders (`SpikerSoft.Wasm` for C#, Pyodide for Python, QuickJS for JavaScript, the SPA's native `RegExp` for regex) are convenient and work entirely offline, but they run on the student's machine and are tamperable. Before the platform credits a skill unlock or marks a lesson complete for-real, the *server* has to re-execute the same lesson against the same grader and confirm it actually passed. That's the trust boundary the offline IndexedDB queue crosses every time it flushes through `POST /api/Lessons/progress/batch`. -**The architectural rule:** code execution NEVER runs in the API process. If the code-runner container crashes mid-grade, the API stays up; a fresh client retry round-trips through Rabbit on the next batch flush. This is enforced by the API's DI graph — only [`ILessonRegradeClient`](spikersoft-backend/SpikerSoft.Business/Domain/Lessons/Services/ILessonRegradeClient.cs) is registered there; no `ILessonGradingExecutor` implementation is reachable from `SpikerSoft.Api`. +**The architectural rule:** code execution NEVER runs in the API process. If the code-runner container crashes mid-grade, the API stays up; a fresh client retry round-trips through Rabbit on the next batch flush. This is enforced by the API's DI graph — only [`ILessonRegradeClient`](spikersoft-backend/SpikerSoft.Business.CodeExecution/Domain/Lessons/Services/ILessonRegradeClient.cs) is registered there; no `ILessonGradingExecutor` implementation is reachable from `SpikerSoft.Api`. -**The flow:** [`LessonsController.BatchProgress`](spikersoft-backend/SpikerSoft.Api/Domain/Lessons/LessonsController.cs) builds a [`LessonRegradeRequest`](spikersoft-backend/SpikerSoft.Business/Domain/Lessons/Messaging/LessonRegradeRequest.cs) for each queued submission and hands it to [`LessonRegradeClient`](spikersoft-backend/SpikerSoft.Business/Domain/Lessons/Services/LessonRegradeClient.cs), which publishes it to the `lesson.regrade.requests` exchange and blocks on the worker's reply via RabbitMQ **Direct-Reply-To** (`amq.rabbitmq.reply-to`). The worker's [`LessonRegradeWorkerHostedService`](spikersoft-backend/SpikerSoft.EventHandlers.CodeExecution/Services/LessonRegradeWorkerHostedService.cs) consumes the request, dispatches through the same [`ILessonGradingExecutorFactory`](spikersoft-backend/SpikerSoft.Business/Domain/CodeExecution/Execution/ILessonGradingExecutorFactory.cs) that backs the live "Submit" path (Roslyn / CPython / Node / Regex-via-Node — no code duplication between live-submit and regrade), and publishes a [`LessonRegradeResponse`](spikersoft-backend/SpikerSoft.Business/Domain/Lessons/Messaging/LessonRegradeResponse.cs) back to the API's reply queue. +**The flow:** [`LessonsController.BatchProgress`](spikersoft-backend/SpikerSoft.Api/Domain/Lessons/LessonsController.cs) builds a [`LessonRegradeRequest`](spikersoft-backend/SpikerSoft.Business.CodeExecution/Domain/Lessons/Messaging/LessonRegradeRequest.cs) for each queued submission and hands it to [`LessonRegradeClient`](spikersoft-backend/SpikerSoft.Business.CodeExecution/Domain/Lessons/Services/LessonRegradeClient.cs), which publishes it to the `lesson.regrade.requests` exchange and blocks on the worker's reply via RabbitMQ **Direct-Reply-To** (`amq.rabbitmq.reply-to`). The worker's [`LessonRegradeWorkerHostedService`](spikersoft-backend/SpikerSoft.EventHandlers.CodeExecution/Services/LessonRegradeWorkerHostedService.cs) consumes the request, dispatches through the same [`ILessonGradingExecutorFactory`](spikersoft-backend/SpikerSoft.Business.CodeExecution/Domain/CodeExecution/Execution/ILessonGradingExecutorFactory.cs) that backs the live "Submit" path (Roslyn / CPython / Node / Regex-via-Node — no code duplication between live-submit and regrade), and publishes a [`LessonRegradeResponse`](spikersoft-backend/SpikerSoft.Business.CodeExecution/Domain/Lessons/Messaging/LessonRegradeResponse.cs) back to the API's reply queue. ```mermaid sequenceDiagram @@ -427,11 +616,11 @@ sequenceDiagram ## C# Coding Curriculum & Playground -The C# Playground is SpikerSoft's flagship coding-education tool — a 95-lesson curriculum that teaches C# from "Hello, World" through async/await, LINQ, and operator overloading. It runs both online (server-graded) and offline (browser-graded) with progress preserved across the boundary, so a student can start a lesson at home, finish it on a campus bus with no signal, and see their unlocks update the moment connectivity returns. +The C# Playground is SpikerSoft's flagship coding-education tool — a 102-lesson curriculum that teaches C# from "Hello, World" through async/await, LINQ, and operator overloading. It runs both online (server-graded) and offline (browser-graded) with progress preserved across the boundary, so a student can start a lesson at home, finish it on a campus bus with no signal, and see their unlocks update the moment connectivity returns. ### Curriculum Structure -95 lessons grouped into 17 progressive tiers. Each lesson is gated behind explicit prerequisites and unlocks the next as it is completed: +102 lessons grouped into 17 progressive tiers. Each lesson is gated behind explicit prerequisites and unlocks the next as it is completed: | Tier | Theme | Sample Topics | |------|-------|---------------| @@ -550,7 +739,7 @@ Before the gradable challenge, each lesson can present any number of `TutorialPa ### Hint Analyzer -`HintAnalyzer` (in `SpikerSoft.Business/Domain/CodeExecution/Hints`) runs heuristic pattern checks on student source *before* it reaches Roslyn. Common mistakes (missing `using`, wrong return type, null-check inversions, off-by-one in `for`-loop bounds) surface as friendly hints instead of cryptic compiler errors. The same analyzer runs server- and browser-side because the file is link-included by `SpikerSoft.Wasm`. +`StudentCodeHintAnalyzer` (in `SpikerSoft.Business.CodeExecution/Domain/CodeExecution/Hints` — with Python/JavaScript/SQL siblings picked by `StudentCodeHintAnalyzerFactory`) runs heuristic pattern checks on student source *before* it reaches Roslyn. Common mistakes (missing `using`, wrong return type, null-check inversions, off-by-one in `for`-loop bounds) surface as friendly hints instead of cryptic compiler errors. The same analyzer runs server- and browser-side because the file is link-included by `SpikerSoft.Wasm`. ### Activity Tracking @@ -570,8 +759,8 @@ The publish target can be overridden: `dotnet publish SpikerSoft.Wasm -c Release Rebuild whenever any of the following change: -- A lesson strategy under `SpikerSoft.Business/Domain/Lessons/Curriculum/**` -- `RoslynCodeExecutor` or anything else under `SpikerSoft.Business/Domain/CodeExecution/Execution/` +- A lesson strategy under `SpikerSoft.Business.CodeExecution/Domain/Lessons/Curriculum/**` +- `RoslynCodeExecutor` or anything else under `SpikerSoft.Business.CodeExecution/Domain/CodeExecution/Execution/` - The hint analyzer - `WasmCompilerEntry.cs` or the `[JSExport]` surface - The `Microsoft.CodeAnalysis.CSharp` package version @@ -584,11 +773,11 @@ In CI the bundle is published by `spikersoft-backend/.gitea/workflows/spikersoft | Layer | File | Purpose | |-------|------|---------| -| **Lesson definitions** | `SpikerSoft.Business/Domain/Lessons/Curriculum/Tier{NN}_*/Lesson*.cs` | All 95 lessons (one class per lesson) | -| **Lesson base** | `SpikerSoft.Business/Domain/Lessons/LessonStrategyBase.cs` | Abstract base — StarterCode, TestCode, Prerequisites, TutorialPanels | -| **Catalog hydration** | `SpikerSoft.Business/Domain/Lessons/LessonCatalogHydrationService.cs` | Reads strategies on boot, upserts into Mongo `lessons` | -| **Roslyn executor** | `SpikerSoft.Business/Domain/CodeExecution/Execution/RoslynCodeExecutor.cs` | Shared compile + run engine; sequential build under WASM | -| **Hint analyzer** | `SpikerSoft.Business/Domain/CodeExecution/Hints/HintAnalyzer.cs` | Pre-Roslyn pattern checks | +| **Lesson definitions** | `SpikerSoft.Business.CodeExecution/Domain/Lessons/Curriculum/Tier{NN}_*/Lesson*.cs` | All 102 C# lessons (one class per lesson) | +| **Lesson base** | `SpikerSoft.Business.CodeExecution/Domain/Lessons/LessonStrategyBase.cs` | Abstract base — StarterCode, TestCode, Prerequisites, TutorialPanels | +| **Catalog hydration** | `SpikerSoft.Api/Services/LessonCatalogHydrationService.cs` | Reads strategies on boot, upserts into Mongo `lessons` | +| **Roslyn executor** | `SpikerSoft.Business.CodeExecution/Domain/CodeExecution/Execution/RoslynCodeExecutor.cs` | Shared compile + run engine; sequential build under WASM | +| **Hint analyzer** | `SpikerSoft.Business.CodeExecution/Domain/CodeExecution/Hints/StudentCodeHintAnalyzer.cs` | Pre-Roslyn pattern checks (per-language siblings via `StudentCodeHintAnalyzerFactory`) | | **Server worker** | `SpikerSoft.EventHandlers.CodeExecution/` | RabbitMQ-driven sandboxed grader (4 consumers) | | **WASM project** | `SpikerSoft.Wasm/SpikerSoft.Wasm.csproj` | .NET 10 WebAssembly app; publishes to `assets/dotnet/` | | **JS interop** | `SpikerSoft.Wasm/WasmCompilerEntry.cs` | `[JSExport]` shim for the browser worker | @@ -617,9 +806,153 @@ All endpoints require authentication. **`POST /api/CSharpCodeRunner/*`** and **` --- +## Native Mobile Apps (iOS & Android) + +SpikerSoft ships **native** mobile clients — SwiftUI on iOS (`spikersoft-ios`), Jetpack Compose on Android (`spikersoft-android`) — as one App Store / Play Store listing each. The two apps are deliberate mirrors of each other, and both are **API clients only**: business logic stays server-side. + +> The per-repo READMEs still describe an early "Phase 1" scope; the source is the authority. Everything below ships today. + +### Launcher shell & app catalog + +Instead of a tab bar, both apps present an **OS-style launcher home**: drag-to-reorder app icons, interest-based folders, and an "All Apps" screen. An onboarding wizard has the user rate areas of interest (≥3 required); the ratings seed the default folder layout, and later interest changes only ever *append* folders — user pins and deletions are never undone. Icon order and onboarding completion are persisted on the profile (`Profile/launcher-layout`), so both devices share one home layout. + +The **app catalog is a cross-platform contract**: iOS `Features/Home/AppCatalog.swift` and Android `features/home/AppCatalog.kt` carry identical app ids that must also match the backend allowlist. + +- **Shipping**: `books`, `quizzes`, `practice`, `lessons`, `knots`, `tracker`, `games` +- **Coming soon** (dimmed, non-navigating): `geography`, `chemistry`, `reading-journey`, `our-stack`, `art-studio`, `gallery`, `photo-gallery`, `tools`, `sponsor`, `blog`, `contact` + +### Feature surface + +| Feature | Notes | +|---|---| +| **Keycloak OIDC login** | Public PKCE clients `spikersoft-ios` / `spikersoft-android` via AppAuth; tokens in Keychain / EncryptedSharedPreferences. Redirect URIs differ by one slash: `com.spikersoft.app://oauth2redirect` (iOS) vs `com.spikersoft.app:/oauth2redirect` (Android) | +| **Books + EPUB reader** | Shelf, server-side page API (`Book/{id}/epub-page`), progress sync (`reader/progress`) | +| **"Listen" audiobook mode** | TTS narration of book pages with media-session/background playback. iOS is dual-engine: **Kokoro neural TTS on-device** (`KokoroTTSEngine` + a compact phonemizer) with Apple TTS fallback, chosen by `TTSEngineRouter`; Android uses a `ListenService` foreground service (`android.speech.tts`, MediaSession, audio focus) | +| **Quizzes** | List / start / answer / complete against `Quiz/*` endpoints | +| **C# lessons** | Catalog, tutorial panels, graded challenges, and a native code editor with syntax highlighting (`CodeRunner/lesson` for grading) | +| **Practice** | Deck-based practice packs + UML diagram renderer + the **Knots** catalog (see [Practice](#practice-flashcards-knots-uml)); packs download as versioned immutable MinIO zips, sha256+size-verified client-side | +| **Activity tracker** | The biggest native feature — see [Activity Tracker, Routes & Paths](#activity-tracker-routes--paths) | +| **Embedded Godot games** | See [Godot Games Client](#godot-games-client) | +| **SignalR** | In-app notifications (`hubs/notifications`) and live code-execution results (`codeExecutionHub`) | + +### Backend contract + +Both platforms speak to the same production endpoints (there is no separate dev backend — local work means temporarily pointing at a LAN IP): + +| Service | URL | +|---|---| +| REST API | `https://api.spikersoft.com/api` | +| Keycloak | `https://ids.spikersoft.com`, realm `spikersoft` | +| SignalR hubs | `https://api.spikersoft.com/hubs/notifications`, `/codeExecutionHub` (host only, no `/api`) | +| Static assets (practice packs) | `https://api.spikersoft.com` root | +| GameServer | `wss://gameserver.spikersoft.com` (reached by the embedded Godot client, not native code) | + +### Tech stacks & CI + +- **iOS**: SwiftUI, iOS 17+, Swift strict concurrency `complete`, XcodeGen-generated project. SPM: AppAuth, SignalR-Client-Swift, **SwiftGodotKit**, MapLibre 6.23+. MetalFX links device-only (it's absent from the simulator SDK). CI builds the unsigned **device** slice on the Mac runner *on purpose* — the simulator slice compiles Godot out entirely and wouldn't exercise the shipping SwiftGodotKit path. +- **Android**: Kotlin, Compose Material 3, compileSdk/targetSdk 35, minSdk 26. Hilt, Navigation Compose, Retrofit + OkHttp + kotlinx.serialization, AppAuth, SignalR Java client, Coil (+ AVIF decoder), **MapLibre 13.4.1**, Room (schemas committed), WorkManager, `org.godotengine:godot:4.7.1.stable`. CI runs `assembleDebug` + Robolectric unit tests; both repos have Sonar scans. + +### Practice (flashcards, knots, UML) + +The Practice product spans web and native: deck types + a session engine, per-language Concepts/Architecture decks, **22 GoF UML diagrams × 7 interest themes** with precomputed layouts, and an illustrated **knots catalog**. The Angular lib (`libraries/shared/practice-decks/`) is the source of truth; iOS/Android carry generated vendored copies verified by a CI drift gate. Knot frame assets ship as versioned immutable MinIO packs (`practice-packs/knots-v{N}.zip`, ZIP_STORED, verified by sha256+size on device). + +--- + +## Activity Tracker, Routes & Paths + +A GPS activity tracker for **run / hike / bike / walk / other**, shipping on web (`/tracker`), iOS, and Android, with recorded routes, derived analysis, and repeatable **paths**. + +### Privacy stance + +Location data belongs to minors, so the rules are strict: recorded routes and paths are **owner-scoped everywhere** — requesting another user's id answers **404** (no existence leak), and both mobile recorders carry an explicit `PRIVACY: never log coordinates` rule. iOS records with **When-In-Use** authorization only (a `CLBackgroundActivitySession` keeps recording with the screen locked — no "Always" permission requested). + +### Maps + +All three clients share the same map stack: **six raster base maps** (`osm-standard`, `osm-humanitarian`, `cartodb-light`, `cartodb-dark`, `esri-satellite`, `esri-topo`) + terrain-DEM hillshade + a red GPS path line. The web defines it in `map.service.ts`; the mobile apps bundle a style JSON mirroring it (MapLibre Native). Terrain elevation is served as **PMTiles from MinIO** (`map-tiles/terrain.pmtiles`) over HTTP range requests — the old `tileserver-gl` service was retired 2026-08-13 once all three clients had cut over. + +### Recording (mobile) + +- **iOS** (`Core/Tracking/ActivityRecorder.swift`): the iOS 17 `CLLocationUpdate.liveUpdates(.fitness)` async loop. Sampling gate: ≥2 s since last point AND (first-of-segment OR moved ≥5 m); fixes with horizontal accuracy >50 m are dropped. +- **Android** (`core/tracking/RecordingService.kt`): a foreground `LifecycleService` (`FOREGROUND_SERVICE_LOCATION`) backed by Room. +- **Ghost racing**: race a prior attempt with live gap computation and spoken auto-announcements (off / every minute / every km), optionally triggered by the volume buttons. +- **Return-to-start**: detects a dwell back at the start point and fires a chime + spoken prompt + actionable notification — deliberately **never auto-stops** (it feeds on raw fixes, because the sampling gate starves while stationary). +- **Listen-while-recording**: a companion sheet offers resume-a-book / pick-a-book / device music; audio cues duck the narration. +- **Two-way sync**: uploads pending recordings, flushes delete tombstones, then *pulls down* routes the device lacks — so a reinstall doesn't show an empty history. + +### Routes, analysis, trimming + +- Backend: `api/recorded-routes` (CRUD + `/analysis` + `/trim`) under `SpikerSoft.Api/Domain/Map/RecordedRoutes/`, plus a **trails** corpus (by-state / near / bounds / geometry), directions, and geocoding. +- **Analysis** (walk/run/rest segmentation, splits, rests, sections) is materialized lazily server-side on first read; web renders it at `/tracker/activities/:id`. +- **Trimming** cuts leading/trailing junk with a dual-handle range slider over a dimmed map preview (`POST recorded-routes/{id}/trim`). + +### Paths — repeatable courses with attempts + +A **path** merges recorded routes into a canonical course; routes attach to it as numbered **attempts** (`api/recorded-paths`: CRUD + `/attempts` + `/match-check` + `/analysis`). Features: record-from-path, cross-attempt comparison, and a best-combined "potential" computed across attempts. `match-check` is a **read-only similarity probe** offered at save time — the server never auto-attaches a route to a path. The web per-path report (`/tracker/paths/:id`) renders an inline-SVG mini-map, deliberately maplibre-free. + +--- + +## Godot Games Client + +`spikersoft-games-godot` is the unified **Godot 4.7 (GDScript, mobile renderer)** game client that embeds inside both native mobile apps — one engine, multiple game modes. + +### Modes + +| Mode | Status | Transport | +|---|---|---| +| **Space** | Playable MVP: connect → create/list ship → join zone → 6DOF flight → peers → shoot → radar/survey → HUD | GameServer WSS + MessagePack | +| **Dungeon Crawler** | Menu stub | GameServer (planned) | +| **Voxel** | Menu stub | GameServer (planned) | +| **Hex Tower Defence** | Menu stub | API SignalR `/hubs/hex-tower-defence` (not GameServer) | + +> **Known gap:** both mobile Games hubs present a **Chess** card and launch the Godot host with `mode: "chess"`, but no chess mode exists in the Godot repo yet — the launch lands on the main menu. (Multiplayer chess is already gated behind a "Coming soon" alert; single-player is not.) + +Space-mode client features: Descent-style 6DOF mobile controls (left stick slide, right stick pitch/yaw, roll buttons, thrust/reverse/fire/boost), a mobile-only cruise-throttle latch, a ship-anchored camera rig ported exactly from the web client, off-screen ship indicators at web-HUD parity, unified targeting across ships/radar contacts/celestial bodies, celestial **survey** scanning with a report panel (threat/age/bio/robotic/composition), and a destroyed overlay with ~5 s server auto-respawn. + +### Wire protocol (GameServer) + +`wss://gameserver.spikersoft.com/ws?token=` — Keycloak JWT in the query string; **MessagePack maps, camelCase keys, `type` discriminator**; server `ProtocolVersion = 2`. Full reference: `docs/PROTOCOL.md` in the repo. + +- Single-writer send queue; `Move` commands coalesced at **20 Hz** to match the server's rate limit. +- Buffers are raised before connect (4 MiB inbound) because the server batches a whole tick into one binary frame and the zone-join burst blows past Godot's 64 KiB default. +- Commands: `ListShips`, `CreateShip`, `JoinZone`, `LeaveZone`, `Move`, `Shoot`, `Target`, `ScanBody`, `SetRadarMode`, `ActiveScan`, `ToggleStealth`, `FireWeapon`, `DeployMine`, `Heartbeat` (heartbeat is play-time tracking, **not** keepalive). +- Wire gotchas every client must honor: `Move.direction*` is **ship-local** (the server applies the ship's rotation — sending world vectors rotates thrust twice); velocity is **SET** from `direction * speed * throttle` (coast/drift is produced client-side by decaying throttle); `ZoneJoined` carries `playerId`, not `entityId`; `EntityBatchDelta` uses binary `packedDeltas`; `AsteroidMoved` is a batch; `RadarContactUpdate` is a full snapshot; `ToggleStealth` is currently a server-side no-op. + +### Embedding in the mobile apps + +- **Android**: `GamesActivity extends GodotActivity`; JWT + `mode`/`vs`/`difficulty` are injected through `SpikerSoftHostPlugin` (a `GodotPlugin` exposing `get_access_token()`, `get_mode()`, `request_exit()`, …). A Gradle `syncGodotProject` task copies the sibling `../spikersoft-games-godot` checkout into `app/src/main/assets` on every build. +- **iOS**: **SwiftGodotKit** loads `Resources/games.pck` (exported with `godot --headless --export-pack`). SwiftGodotKit has no plugin channel, so the handoff is **file-based**: Swift writes `host_session.json` (`{token, mode, vs, difficulty}`) *before* engine construction; Godot reads it as a one-shot and deletes it so the JWT never sits on disk. Exit requests flow back the same way (Godot writes `user://host_exit_request.json`; the SwiftUI side polls and dismisses). +- The shells draw **no chrome** over the game (nav/status bars hidden, full-bleed) — the only way out is the in-game `Exit Games`. All 2D UI sits inside a safe-area control inset to `DisplayServer.get_display_safe_area()` for notch/Dynamic-Island handling. +- **Desktop/editor**: paste a Keycloak token on the main menu or pass `--token=`; hosted sessions hide the paste UI. + +Note: this repo currently has **no CI** — in-engine test scripts only (`tests/wire_selftest.gd`, `tests/arena_smoke.gd`). + +--- + +## Desktop Stacker (spikersoft-stacker) + +A **local, fully offline** focus-stacking application (Zerene/Helicon class) for macro photography — one self-contained executable per OS (macOS / Windows / Linux). It ports the platform's PhotoStack pipeline (artpipe `align.py` + `fusion.py`) to a fast desktop workflow; algorithm parity with the Python implementation is verified by a test harness in the repo. + +### Workflow stages + +0. **Camera** — remote capture from OM System / Olympus Wi-Fi cameras: BLE wake-up, live view with tap-to-focus, remote trigger of in-camera Focus Bracketing, auto-download of the new ORFs. +1. **Import** — `.ORF` and other RAW/TIFF/JPEG; instant previews from embedded JPEGs; Olympus `FocusStepCount` ordering and duplicate curation. +2. **Align** — phase-correlation seeding + chained pyramidal ECC-style affine refinement against a reference frame. +3. **Mass crop** — auto intersection rect of all aligned frames, adjustable on the master; "Crop" bakes warp+crop into every frame so no border replication reaches fusion. +4. **Stack** — selectable fusion engines: **Laplacian pyramid** and **depth map**. +5. **Touch up** — dual synced previews (source frame | fused result); a soft brush copies source pixels into the result at full 16-bit resolution, with stroke-replay undo. +6. **Export** — 16-bit TIFF / JPEG + a JSON `.sstack` project sidecar. + +### Architecture + +A Rust workspace of four crates: `stacker-core` (the pipeline — no UI deps; `rawler` RAW decode, `rustfft`, `rayon`, `memmap2`), `stacker-gpu` (**wgpu** compute kernels from one WGSL codebase targeting Metal/Vulkan/DX12, with a CPU fallback for every kernel), `stacker-camera` (Olympus Wi-Fi CGI protocol + UDP live view + optional BLE wake, plus a mock camera for tests), and `stacker-app` (egui/eframe desktop UI). + +**Platform integration: none, by design.** There is no S3 client, no Keycloak, no `api.spikersoft.com` anywhere in the workspace — it shares *algorithms* with the platform's [Photo Stack](#art-studio) lane, not services. Output stays local. + +--- + ## Frontend Architecture: Feature-Oriented Boundaries -The Angular workspace was migrated through a multi-phase refactor (Phases 4–6, completed 2026-05-09 — see [`docs/architecture/inventory.md`](docs/architecture/inventory.md) and [`docs/architecture/boundaries.md`](docs/architecture/boundaries.md)) from a monolithic `libraries/tools/` lib into ~65 buildable libraries grouped by **layer**. Each library has its own `ng-package.json`, `vitest.config.ts`, `project.json`, and tests run isolated. Lazy-loaded routes per feature → smaller initial bundle (the Phase 6 sub-phases collectively brought the gzipped `main.js` to ~302 kB). +The Angular workspace was migrated through a multi-phase refactor (Phases 4–6, completed 2026-05-09 — see [`docs/architecture/inventory.md`](docs/architecture/inventory.md) and [`docs/architecture/boundaries.md`](docs/architecture/boundaries.md)) from a monolithic `libraries/tools/` lib into buildable libraries grouped by **layer** (84 `project.json` libraries today and growing). Each library has its own `ng-package.json`, `vitest.config.ts`, `project.json`, and tests run isolated. Lazy-loaded routes per feature → smaller initial bundle (the Phase 6 sub-phases collectively brought the gzipped `main.js` to ~302 kB). ### Layers and clusters @@ -627,12 +960,12 @@ Every library carries at least one `layer:*` tag in its `project.json`. The boun | Cluster | Layer tag | Count | Purpose | |---|---|---|---| -| [`libraries/domain/`](spikersoft-angular/libraries/domain/) | `layer:domain` | 9 | Pure data models + thin services bound to backend resources, **no UI**: `blog`, `book`, `child-account`, `fundraiser`, `geo`, `mrz`, `pre-registration`, `profile`, `sponsor` | -| [`libraries/features/`](spikersoft-angular/libraries/features/) | `layer:feature` | 24 | Top-level user-facing features that own routes/components/services: the entire `dev-tools-*` suite (21 tools — C#/Python/JavaScript/regex/x86 playgrounds, decompiler, encoding/conversion, image-to-avif/ico, qr-code, voronoi, diff, diagram, blockly, duckdb, ipv4, quick-type, common-commands), `blog`, `child-account-dialog`, `fundraiser`, `games-clue-for-sql`, `parent-dashboard`, `sponsor`, `trellis-3d-generator` | -| [`libraries/platform/`](spikersoft-angular/libraries/platform/) | `layer:platform` | 13 | Cross-cutting infrastructure consumed by features: `language-runner`, `lesson-catalog`, `progress-sync`, `pyodide-runtime`, `monaco-editor`, `tool-storage`, `tool-file-menu`, `intro-tour`, `intro-launching`, `js-step-debugger`, `activity-tracking`, `loading`, `avif-encoder` | -| [`libraries/shared/`](spikersoft-angular/libraries/shared/) | `layer:shared` | 6 | Small reusable units that don't belong to a single feature/domain: `api-config`, `js-formatting-options`, `lesson-panes`, `lesson-platform`, `save-load-dialogs`, `utils/{crc32,file-hash}` | -| [`libraries/ui/`](spikersoft-angular/libraries/ui/) | `layer:ui` | 2 | Pure presentational atoms: `confirm-dialog`, `mrz-crop` | -| [`libraries/game/`](spikersoft-angular/libraries/game/) | `layer:game` | 1 | Game-runtime libs that pair a feature with heavy runtime deps: `wasm-voxel` | +| [`libraries/domain/`](spikersoft-angular/libraries/domain/) | `layer:domain` | 11 | Pure data models + thin services bound to backend resources, **no UI**: `blog`, `book`, `child-account`, `fundraiser`, `geo`, `mrz`, `photograph`, `pre-registration`, `profile`, `sponsor`, `users` | +| [`libraries/features/`](spikersoft-angular/libraries/features/) | `layer:feature` | 32 | Top-level user-facing features that own routes/components/services: the `dev-tools-*` suite (20 tools — C#/Python/JavaScript/regex/x86/C/C++/SQL playgrounds, decompiler, encoding, conversions-monaco, image-to-avif/ico, qr-code, diff, diagram, blockly, duckdb, ipv4, quick-type), plus `art-studio`, `blog`, `child-account-dialog`, `fundraiser`, `games-clue-for-sql`, `geo-globe`, `parent-dashboard`, `photo-gallery`, `sponsor`, `sponsor-cards`, `status`, `trellis-3d-generator` (orphaned — no route; kept for reference, new modeling goes through Art Studio) | +| [`libraries/platform/`](spikersoft-angular/libraries/platform/) | `layer:platform` | 19 | Cross-cutting infrastructure consumed by features: `language-runner`, `lesson-catalog`, `progress-sync`, `pyodide-runtime`, `clang-runtime`, `monaco-editor`, `tool-storage`, `tool-file-menu`, `intro-tour`, `intro-launching`, `js-step-debugger`, `step-debugger-core`, `activity-tracking-api`/`-impl` (port + implementation split), `anon-session`, `content-locale`, `playground-visual-progress`, `loading`, `avif-encoder` | +| [`libraries/shared/`](spikersoft-angular/libraries/shared/) | `layer:shared` | 7 | Small reusable units that don't belong to a single feature/domain: `api-config`, `js-formatting-options`, `lesson-panes`, `lesson-platform`, `practice-decks`, `save-load-dialogs`, `utils/{crc32,file-hash}` | +| [`libraries/ui/`](spikersoft-angular/libraries/ui/) | `layer:ui` | 7 | Pure presentational atoms: `avatar-stack`, `award-chips`, `confirm-dialog`, `destination-chips`, `image-paste`, `mrz-crop`, `user-picker` | +| [`libraries/game/`](spikersoft-angular/libraries/game/) | `layer:game` | 3 | Game-runtime libs that pair a feature with heavy runtime deps: `gl-hud`, `hex-tower-defence`, `wasm-voxel` | | Top-level legacy | various | 5 | Pre-Phase-4 libs that haven't been re-clustered into the new directories yet but already carry the right layer tag: `keycloak-admin` (`layer:feature`), `marks-site-models` + `spikersoft-models` (`layer:domain`), `spikersoft-environment` (`layer:platform`), `spikersoft-theme` (`layer:ui`) | ### Allowed dependency arrows @@ -726,31 +1059,39 @@ spikersoft-angular/ │ ├── position/ # PositionService (open positions + auto ambassadors) │ └── team/ # TeamService (staff member profiles) ├── libraries/ # Buildable Nx libs grouped by layer (see Frontend Architecture section) -│ ├── domain/ # layer:domain — pure data + thin services bound to backend resources (9 libs) -│ │ ├── blog/ book/ child-account/ fundraiser/ geo/ -│ │ └── mrz/ pre-registration/ profile/ sponsor/ -│ ├── features/ # layer:feature — top-level user features w/ routes (24 libs) +│ ├── domain/ # layer:domain — pure data + thin services bound to backend resources (11 libs) +│ │ ├── blog/ book/ child-account/ fundraiser/ geo/ mrz/ +│ │ └── photograph/ pre-registration/ profile/ sponsor/ users/ +│ ├── features/ # layer:feature — top-level user features w/ routes (32 libs) +│ │ ├── art-studio/ # Art Studio shell + all its views (43 subdirs) +│ │ ├── photo-gallery/ # Personal photo gallery (wall/cards/map, bursts, lightbox) +│ │ ├── geo-globe/ sponsor-cards/ status/ │ │ ├── dev-tools-csharp-runner/ # C# Playground wrapping shell (config + adapter) │ │ ├── dev-tools-python-runner/ # Python Playground wrapping shell │ │ ├── dev-tools-javascript-runner/# JS Playground wrapping shell │ │ ├── dev-tools-reg-ex/ # Regex Playground (chapters + cheatsheet) -│ │ ├── dev-tools-x86-playground/ ...# 21 dev-tools-* + blog, sponsor, fundraiser, ... +│ │ ├── dev-tools-x86-playground/ ...# 20 dev-tools-* + blog, sponsor, fundraiser, ... │ │ └── … # see libraries/features/ for full list -│ ├── platform/ # layer:platform — cross-cutting infra (13 libs) -│ │ ├── language-runner/ # Language-agnostic playground SHELL (consumed by 3 wrapping shells) -│ │ ├── lesson-catalog/ # Lesson catalog + prerequisites +│ ├── platform/ # layer:platform — cross-cutting infra (19 libs) +│ │ ├── language-runner/ # Language-agnostic playground SHELL (consumed by the wrapping shells) +│ │ ├── lesson-catalog/ # Lesson catalog + prerequisites (8 languages) │ │ ├── progress-sync/ # Server↔in-browser router + offline IndexedDB queue │ │ ├── pyodide-runtime/ # Pyodide loader (Python in-browser) +│ │ ├── clang-runtime/ # Browser Clang/LLD/WASI toolchain (C and C++ in-browser) +│ │ ├── content-locale/ playground-visual-progress/ anon-session/ │ │ ├── monaco-editor/ tool-storage/ tool-file-menu/ intro-tour/ intro-launching/ -│ │ └── js-step-debugger/ activity-tracking/ loading/ avif-encoder/ -│ ├── shared/ # layer:shared — small reusable units (6 libs) +│ │ └── js-step-debugger/ step-debugger-core/ activity-tracking-api/ activity-tracking-impl/ loading/ avif-encoder/ +│ ├── shared/ # layer:shared — small reusable units (7 libs) │ │ ├── lesson-platform/ # Cross-cluster contracts (PROGRESS_SYNC_PORT, SourceFile, ...) │ │ ├── lesson-panes/ # Lesson + tutorial + sidebar panes +│ │ ├── practice-decks/ # Practice product source of truth (flashcards/knots/UML) │ │ ├── api-config/ js-formatting-options/ save-load-dialogs/ │ │ └── utils/ # crc32, file-hash -│ ├── ui/ # layer:ui — presentational atoms (2 libs) -│ │ └── confirm-dialog/ mrz-crop/ -│ ├── game/ # layer:game — game runtime libs (1 lib) +│ ├── ui/ # layer:ui — presentational atoms (7 libs) +│ │ └── avatar-stack/ award-chips/ confirm-dialog/ destination-chips/ image-paste/ mrz-crop/ user-picker/ +│ ├── game/ # layer:game — game runtime libs (3 libs) +│ │ ├── gl-hud/ # In-scene WebGL HUD framework (troika-three-text, no DOM) +│ │ ├── hex-tower-defence/ # Hex TD client (server-authoritative via SignalR hub) │ │ └── wasm-voxel/ # TypeScript Minecraft port (voxel engine) │ │ │ │ # Legacy top-level libs (pre-Phase-4 boundary refactor; tagged but not re-clustered) @@ -780,16 +1121,17 @@ spikersoft-backend/ │ └── Network/ │ ├── MessagePackSerializer.cs # Binary protocol (v2) │ └── ConnectionManager.cs # WebSocket session management -├── SpikerSoft.Business/ # Business logic (CQRS + MediatR) +├── SpikerSoft.Business/ # Business logic (CQRS + MediatR) — profile, sponsor, art studio, tracker, tm, … +│ └── Domain/GameServer/ # Game domain logic +├── SpikerSoft.Business.CodeExecution/ # Lesson + grading bounded context (split out of Business) │ └── Domain/ -│ ├── GameServer/ # Game domain logic │ ├── Lessons/ -│ │ ├── LessonStrategyBase.cs # Abstract base for lesson definitions -│ │ ├── LessonCatalogHydrationService.cs # Boots Mongo `lessons` from C# strategies -│ │ └── Curriculum/Tier{NN}_*/Lesson*.cs # 95 lessons across 17 tiers +│ │ ├── LessonStrategyBase.cs # Abstract base for lesson definitions +│ │ └── Curriculum/ # Tier{NN}_* (102 C# lessons) + Python/ JavaScript/ Regex/ C/ Cpp/ Sql/ X86/ │ └── CodeExecution/ -│ ├── Execution/RoslynCodeExecutor.cs # Shared compile + run engine (server + WASM) -│ └── Hints/HintAnalyzer.cs # Pre-Roslyn pattern checks +│ ├── Execution/RoslynCodeExecutor.cs # Shared compile + run engine (server + WASM) +│ └── Hints/StudentCodeHintAnalyzer.cs # Pre-Roslyn pattern checks (+ per-language siblings) +├── SpikerSoft.Games.HexTowerDefence/ # Server-authoritative hex TD reducer (C# port of the TS game) ├── SpikerSoft.Wasm/ # .NET 10 WebAssembly bundle of Roslyn + lesson strategies │ ├── WasmCompilerEntry.cs # [JSExport] surface (Ping, GetAttempt, ExecuteLessonCode, ExecuteFreePlayCode, AddReferenceImage) │ └── main.js # Worker boot script — pre-fetches PE bytes, feeds Roslyn @@ -800,8 +1142,9 @@ spikersoft-backend/ │ └── Mongos/ # MongoDB documents (incl. game state, lessons, userLessonProgress) ├── SpikerSoft.Contracts.SignalR/ # SignalR hub contracts ├── SpikerSoft.AI.MCPServer/ # AI/ML model server -├── SpikerSoft.EventHandlers.*/ # Distributed event processors (incl. CodeExecution worker) -├── SpikerSoft.Tests.Unit/ # ~5.1k unit tests (count via `dotnet test SpikerSoft.Tests.Unit --list-tests`) +├── SpikerSoft.EventHandlers.*/ # 28 distributed event processor/worker projects (incl. CodeExecution, ArtPipeProcessor, GpuCoordinator) +├── SpikerSoft.Workers.*/ # Non-handler worker libs (Decompile, Notifications, Ocr) +├── *.Tests/ # Per-project test projects (36), collected by SpikerSoft.UnitTests.slnf └── SpikerSoft.sln ``` @@ -898,7 +1241,8 @@ Both frontend and backend support environment-specific configuration: | Seq | 5341 | Structured log aggregation | | Jaeger | 4317 | Distributed tracing (OTLP gRPC) | | InfluxDB | 8086 | Time-series metrics and dashboards | -| TileServerGL | 443 (external) | Self-hosted terrain tile server for maps | +| MinIO | 9000 | S3 object storage: photos, art-asset artifacts, practice packs, PMTiles terrain (`map-tiles/terrain.pmtiles` — replaced the retired TileServerGL) | +| OpenBao | 8200 | Secrets management (3-node raft HA at `bao.spikersoft.com`; services and CI fetch secrets via AppRole) | | DNS Server | 5380 | Self-hosted DNS management | | Email (SMTP) | 587 | Mail server with DKIM, DMARC, SPF | | Checkr API | N/A (external) | Background checks — USA (optional, falls back to Manual) | @@ -1007,6 +1351,23 @@ All endpoints require authentication and an `Admin` or `Staff` role. ## Profile & Travel +The profile page has grown well beyond Personal + Travel. Current tabs (`projects/spikersoft/src/app/_components/profile/tabs/`), each with its own guided intro.js tour: + +| Tab | Purpose | +|---|---| +| **Personal** | Identity, mailing address, home country, sponsorship toggle | +| **Children** | Child account management (adults only) | +| **Travel** | Travel map, wishlists, documents | +| **Photos** | Personal photo uploads (feeds the [photo gallery](#photography--gallery)) | +| **Photography** | Camera-bag registry derived server-side from EXIF serial numbers of uploads — nothing self-reported | +| **Interests** | Star-rated areas of interest (drives recommendations, mobile launcher folders, geography filters) | +| **Skills** | Earned skill badges (see [Skills & Badges](#skills--badges)) | +| **Schedule** | Weekly availability (feeds the calendar system) | +| **Site Settings** | Appearance + the Offline WASM preload panel | +| **Parental** | Parental controls and permissions (e.g. the Photography permission that gates a minor's photo tagging) | + +Profile forms have been migrated to Angular **Signal Forms** (`personalForm().dirty()` etc.) with an aggregate unsaved-changes guard across tabs. + ### Personal Tab — Mailing Address & Home Country The **Personal** tab includes a structured mailing address section (Street 1, Street 2, City, State/Province, Postal Code, Country) and a **Home Country** dropdown. The home country setting is used by the domestic travel map to automatically load the correct country view. @@ -1215,7 +1576,16 @@ docker build -t spikersoft-angular -f spikersoft-angular/Dockerfile . | Blog System | See main README — [Blog System](#blog-system) | | Reading Journey | See main README — [Reading Journey & Book System](#reading-journey--book-system) | | Real-time Communication | See main README — [Real-time Communication](#real-time-communication) | -| Marks Field Service | See main README — [Marks Field Service](#marks-field-service) | +| Art Studio & artpipe | See main README — [Art Studio](#art-studio); pipeline internals: `spikersoft-artpipe/CLAUDE.md`; Angular lib: `spikersoft-angular/libraries/features/art-studio/README.md` | +| Asset-to-Game Integration | See main README — [Asset-to-Game Integration](#asset-to-game-integration) | +| Photography & Gallery | See main README — [Photography & Gallery](#photography--gallery) | +| Native Mobile Apps | See main README — [Native Mobile Apps](#native-mobile-apps-ios--android); repos: `spikersoft-ios/`, `spikersoft-android/` | +| Activity Tracker | See main README — [Activity Tracker, Routes & Paths](#activity-tracker-routes--paths) | +| Godot Games Client | See main README — [Godot Games Client](#godot-games-client); protocol: `spikersoft-games-godot/docs/PROTOCOL.md` | +| Desktop Stacker | See main README — [Desktop Stacker](#desktop-stacker-spikersoft-stacker); repo: `spikersoft-stacker/README.md` | +| Time & Materials | See main README — [Time & Materials](#time--materials-apitm); repo: `spikersoft-time-and-materials/README.md` | +| Marks Field Service (legacy) | See main README — [Marks Field Service](#marks-field-service-legacy-crm) | +| Ops, Fleet & Status | See main README — [Ops, Fleet & Status](#ops-fleet--status) | | Health & Observability | See main README — [Health Check & Observability](#health-check--observability) | --- @@ -1258,6 +1628,10 @@ Online play uses a dedicated `ChessHub` SignalR hub (`/hubs/chess`) with Keycloa Completed online games (checkmate, resignation, draw, abandonment) are persisted to MongoDB as `ChessGame` documents, storing both players, all moves, result, and timestamps. +### Custom Piece Sets (Art Studio) + +Chess pieces can be replaced with **Art-Studio-generated 3D sets**: one theme prompt fans out into six per-piece generations (`api/artstudio/chess-sets`), the finished set can be shared to the gallery and moderated, and an approved set is **equipped** into the game via the per-game art loadout (`api/game/chess/art-loadout`, 12 slots — 6 per side). The 3D board hot-swaps piece prototypes with byte-budget enforcement and falls back to the bundled classic mesh if a custom model is too large or fails to load. See [Asset-to-Game Integration](#asset-to-game-integration). + ### Key Files | File | Purpose | @@ -1289,13 +1663,19 @@ The `/team` route displays profiles of all SpikerSoft staff members. Profiles ar ## Hiring Page -The `/employment` route lists open positions at SpikerSoft, divided into two sections: +The `/employment` route is a **master-detail careers page** (it replaced a flat grid of ~33 near-identical cards that had no detail view, no filters, and no per-role URL): -1. **Ambassador Positions** — Auto-generated for every active `Location` that does not have an assigned ambassador. These positions are dynamically created by the backend so the hiring page always reflects current staffing gaps. +- **Filter bar** — country / work mode / section, mirrored to query params so filtered views are shareable. +- **Role listbox + routed detail pane** — each role has its own URL (`/employment/:slug`), so a specific opening can be linked directly. +- **Geo-globe** — a WebGL globe (the shared `@spikersoft/feature-geo-globe` library, also used by the sponsor family page and the geography explorer) highlights where SpikerSoft works. + +Position sources remain two-fold: + +1. **Ambassador Positions** — Auto-generated for every active `Location` that does not have an assigned ambassador, so the page always reflects current staffing gaps. 2. **Other Roles** — Manually created positions (developer, designer, teacher, etc.) managed through the admin Location Management page. - **Backend**: `PositionsController` (`GET /api/positions`) merges manually created `OpenPosition` documents with auto-generated ambassador entries for unassigned locations. -- **Frontend**: `EmploymentComponent` uses `PositionService` and displays positions grouped by type with color-coded badges. +- **Frontend**: `EmploymentComponent` + `employment-filters` / `employment-list` / `employment-detail` children, backed by `PositionCatalogService`. ### Ambassador Application Workflow @@ -1331,7 +1711,7 @@ Staff/Admin users access the review panel at `/admin/application-review` (menu i | `OpenPosition.cs` | MongoDB model for manually managed positions | | `position.service.ts` | Frontend service for positions CRUD | | `application.service.ts` | Frontend service for application API calls | -| `employment.component.ts/html/scss` | Hiring page with grouped position cards (clickable for applications) | +| `employment.component.ts/html/scss` | Master-detail careers page (filters + listbox + routed detail + globe) | | `application-dialog.component.ts` | Multi-step application stepper dialog | | `application-review.component.ts` | Admin review panel with status workflow | @@ -1449,13 +1829,13 @@ Staff and admin users can manage locations and open positions via `/admin/locati |------|---------| | `location-management.component.ts/html/scss` | Admin page with tabbed location and position management | | `routes.ts` | Route registered at `admin/location-management` with AuthGuard + RoleGuard | -| `menu-bar.component.html` | "Location Management" added to user dropdown (staff section), "Hiring" added to main nav | +| `menu-bar.menu.ts` | Navigation is **data-driven**: `MENU_SECTIONS` in this one file is the source of truth for both the desktop dropdowns and the mobile drawer, so items can no longer drift between the two surfaces. Sections: Learn (incl. Coding → Languages / Patterns / Version Control / Testing), Games, Art Studio, Development, About, Management. Items carry gates (`auth`/`staff`/`canReviewArt`/`canBlog`/`notChild`/`readingInterest`/`videoCall`), badge ids, and drawer-specific labels | --- ## Sponsorship & Donation System -SpikerSoft is a nonprofit organization. The sponsorship system enables donors to fund travel experiences for approved individuals and children. +SpikerSoft is a nonprofit organization. The sponsorship system enables donors to fund travel experiences for approved **individuals, children, and families** — under the "Sponsor a Journey" banner. The public landing page was rebranded family-first ("Family Exploration & Learning" / "Explore the World, Together"; the stat row leads with the *Families* count) and `/sponsor` now offers three view modes — Families, Families by destination, Individuals by destination — plus a "Location funds" section. The individual flow below came first; families and funds are documented in [Families, Family Funds & Location Funds](#families-family-funds--location-funds). **How It Works:** @@ -1584,6 +1964,16 @@ Staff only: Parent enables sponsorship → Staff approves → Staff sets trip costs → Profile appears on /sponsor → Donors contribute → Stripe processes payment → Webhook confirms → Donation recorded ``` +### Families, Family Funds & Location Funds + +Families are first-class sponsorship subjects alongside individuals: + +- **Family page** — `/sponsor/family/:familyKey` is an anonymous page showing the family's shared travel dream on the geo-globe (shared destinations accented vs. individual wishes) with per-member cost breakdowns. Family cards (`libraries/features/sponsor-cards/`) show a persistent goal bar and a Donate link; hovering a member swaps the card's side panel to that member's tabbed details (Locations / About / Awards). +- **Family funds** — `/sponsor/donate-family/:familyKey` pools gifts into a family fund that staff disburse **only toward the family's shared trip**, never a member's solo trip (`FamilyFundsController`: ledger, disburse, manual adjust — all staff-side). +- **Location funds** — `/sponsor/fund/:code` lets donors fund a *destination* rather than a person. Travelers who dream of that destination **earn from the pool** by completing activities (`/sponsor/earn`): staff define earn rules, travelers submit claims, staff approve/reject completions, and prizes/ledger/reports round out the admin surface (`FundsController`). + +Key files: `SpikerSoft.Api/Domain/Sponsor/{FamilyFundsController,FundsController}.cs`, `spikersoft-angular/projects/spikersoft/src/app/_components/sponsor-family/`, `libraries/features/sponsor/src/lib/components/{family-donate,fund-donate,sponsor-earn}/`, `libraries/features/sponsor-cards/`. + --- ## Fundraiser System @@ -1795,6 +2185,37 @@ Each step updates the media's `ProcessingStatus`, and failures are isolated per- --- +## Photography & Gallery + +A personal photo gallery built for **real camera workflows** — per-frame uploads of RAW `.ORF` plus developed TIFF/JPEG with EXIF retained end-to-end, not just phone snapshots. + +### Gallery + +`libraries/features/photo-gallery/` renders three views: a **thumbnail wall**, **cards**, and a **geotagged map** (`photo-map`), with a lightbox. The gallery is **stack-aware**: frames consumed by a Stacked or Touchup photograph (from the Art Studio [Photo Stack](#art-studio) lane) collapse behind a representative **burst tile** (grouped by upload session), and a provenance endpoint returns the source frames in burst order (`GetPhotographProvenance`). A photo-info dialog exposes sanitized metadata. + +### Tags + +- **Manual tags**: `PUT api/photos/{id}/tags` replaces the owner's tag list (max 50 tags × 64 chars). For minors, tagging is gated on the parental **Photography** permission. +- **AI auto-tags**: the Florence-2 GPU lane tags photographs automatically; `POST photos/{id}/auto-tags/reject` moves a tag into `RejectedAutoTags` so re-tagging can never resurrect it. +- `GET api/photography/tags` returns the caller's tag universe with counts, feeding the filter chips. + +### Gear registry + +The profile's **Photography** tab shows the user's cameras and lenses — derived **server-side from EXIF serial numbers** of their uploads (`GET api/photography/gear`); nothing is self-reported. + +### Pipeline & Darkroom Companion + +- `SpikerSoft.EventHandlers.PhotographProcessor` runs the async photo pipeline: receive → ClamAV scan → metadata extract → move to final storage (MinIO). +- **Darkroom Companion** (`DarkroomController`, Mongo `darkroom-develop-jobs`): a staff-only desktop-agent lane. A machine with Adobe installed polls for parked PhotoStack develop jobs, runs the real **Camera Raw** develop recipe locally, and uploads 16-bit TIFFs back — the pipeline then resumes at `align_fuse`. This exists because faithful RAW development is the one step the server fleet can't do. + +### Clipboard paste + +`@spikersoft/ui-image-paste` provides a document-level `ssImagePaste` directive: Ctrl/Cmd+V with images on the clipboard emits `File[]` into the surface's existing validation funnel (with `accept`-style filtering and auto-renaming of generic `image.png` screenshots). Wired into 11+ upload surfaces — blog posts, info vault, profile Photos tab, photo gallery, art prompt composer, passport scan, QR code, image converters, geography facts, and the child-account dialog. + +Key files: `SpikerSoft.Api/Domain/Photography/PhotographyController.cs`, `SpikerSoft.Business/Domain/Photography/Queries/{GetMyPhotographs,GetPhotographProvenance}/`, `spikersoft-angular/libraries/features/photo-gallery/`, `libraries/ui/image-paste/`. + +--- + ## Reading Journey & Book System The Reading Journey is the platform's digital library and learning hub. Members upload books (PDF or EPUB), which pass through an AI-powered processing pipeline that generates metadata, embeddings, and comprehension quizzes. The reader supports bookmarks, progress tracking, and multiple reading profiles. @@ -1857,6 +2278,14 @@ Book available in library | `GET/POST` | `/api/reader/profiles` | List / create reading profiles | | `DELETE` | `/api/reader/profiles/{profileId}` | Delete a reading profile | +### Semantic search & vector indexes + +`api/books/search` (GET + POST + stats, `BookSearchController`) runs semantic search over the embeddings the pipeline generates, and `api/vector-indexes` (`VectorIndexController`) gives staff CRUD/inspection over the Redis vector indexes backing it. + +### Configurable quiz generation + +Staff tune quiz generation at book-approval time (`quiz-options-dialog` in admin book management): model preset (**Fast / Balanced / Quality**), target difficulty, question style, questions-per-page, and max pages. The options are serialized onto the workflow (`QuizOptionsJson` on `ApproveUpload`) and replayed on retry, so a re-run grades with the same settings. + ### Key Files | File | Purpose | @@ -1871,19 +2300,20 @@ Book available in library ## Real-time Communication -SpikerSoft provides three real-time communication channels: video calling, live streaming, and platform-wide chat. +SpikerSoft provides four real-time communication channels: video calling, live streaming, platform-wide chat, and 1:1 direct messages. ### Video Calling -Peer-to-peer browser video calls powered by **PeerJS** (WebRTC). Signaling uses the PeerJS hosted broker; media flows directly between browsers via Google STUN servers. +Peer-to-peer browser video calls (WebRTC) with **authenticated, first-party signaling** — the old PeerJS public broker (which had no auth and no permission gating) is gone: -- One-on-one video calls with camera/mic controls -- Video call picker dialog for selecting contacts -- No backend server required for media relay +- Signaling runs over the authenticated **`VideoCallHub`** SignalR hub at `/hubs/video-call`: opaque SDP/ICE forwarding, room pairing, and an abandonment grace window. +- `VideoCallController` issues **short-lived coturn TURN credentials** (HMAC `use-auth-secret`, default 600 s TTL) so media can relay through our own TURN server when a direct path fails. +- One-on-one calls with camera/mic controls and a call picker dialog; access is gated by a `videoCall` feature permission. | File | Purpose | |------|---------| -| `call.service.ts` | PeerJS wrapper — peer creation, call/answer lifecycle | +| `SpikerSoft.Contracts.SignalR/VideoCallHub.cs` | Signaling hub — SDP/ICE forwarding, room pairing | +| `SpikerSoft.Api/Domain/VideoCall/VideoCallController.cs` | Short-lived TURN credential issuance | | `video-call.component.ts` | Call UI with local/remote video streams | ### Live Streaming @@ -1914,6 +2344,23 @@ Chat is entirely hub-based with no REST API; all message exchange happens over W | `chat.component.ts` | Angular chat UI | | `chat.service.ts` | SignalR connection and message handling | +### Direct Messages + +Distinct from chat rooms: **1:1 private messaging** with a REST surface plus live delivery. + +- A global chat **FAB + drawer** (`messaging-fab` / `messaging-drawer`) with per-conversation unread counts. +- The contact roster is **relationship-derived** (parent/child family links), and `MessagingAccessRules` enforces a role/family-scoped permission matrix — who may message whom is a policy decision, not an open directory. +- Delivery rides the existing `NotificationsHub` (`ReceiveDirectMessage`), so no extra WebSocket connection. + +| Method | Endpoint | Purpose | +|--------|----------|---------| +| `GET` | `/api/messaging/contacts` | Who the caller may message | +| `GET` | `/api/messaging/conversations` | Conversation list with unread counts | +| `GET` | `/api/messaging/conversations/{id}/messages` | Message history (paged) | +| `POST` | `/api/messaging/messages` | Send a message | +| `POST` | `/api/messaging/conversations/{id}/read` | Mark read | +| `GET` | `/api/messaging/unread-count` | Global unread badge | + ### Notifications A centralized notification system bridges async backend events to real-time client updates via SignalR: @@ -1936,38 +2383,53 @@ SpikerSoft uses a distributed event handler architecture where specialized micro | Handler | Purpose | |---------|---------| | **Infrastructure** | Shared library providing `EventHandlerHostBuilder` with Serilog, MongoDB, RabbitMQ, and OpenTelemetry wiring (not a runnable service) | +| **ArtPipeProcessor** | GPU art-pipeline worker — runs artpipe stages/models under GPU leases (see [Art Studio](#art-studio)); also hosts the QR Art and photo auto-tag lanes | +| **ArtStudioMetrics** | Single-replica CQRS projection folding `art.asset.lifecycle` events into the Art Studio metrics read model | | **BlogMediaProcessor** | Async blog media pipeline: receive → ClamAV scan → metadata extract → strip EXIF → move to final storage | | **BookManagement** | Book lifecycle side effects triggered by processing events | | **CalendarReminders** | Calendar reminder notification dispatch | -| **CodeExecution** | Sandboxed user code execution worker (request/response queues, 4 concurrent consumers) | +| **CodeExecution** | Sandboxed user code execution worker (request/response queues, 4 concurrent consumers; also the lesson-regrade RPC lane) | +| **Decompile** | RabbitMQ direct-reply-to RPC into ICSharpCode.Decompiler (the decompiler dev tool) | | **DockerMonitor** | Docker host monitoring and event publishing to Redis | | **Embeddings** | Vector embedding generation for book text chunks (LLamaSharp, GPU-scheduled) | | **FileMovement** | File transfer between staging and final storage locations | | **GameEvents** | Game state persistence and Redis hot-path dual-writes for the game server | -| **GpuCoordinator** | GPU VRAM lease management, queue scheduling, and `/gpu/status` HTTP API | +| **GpuCoordinator** | Cluster-wide GPU VRAM lease broker, queue scheduling, and `/gpu/status` HTTP API (see [Art Studio](#art-studio)) | +| **ImageDescription.Python** | Vision-model image description for book images (Qwen-VL, GPU-scheduled) | | **InfluxDashboard** | InfluxDB metrics ingestion and `DashboardHub` SignalR streaming | | **KeycloakEvents** | Keycloak identity event synchronization into the platform | +| **LessonVideoProcessor** | Lesson video processing + moderation pipeline | | **MetadataExtractor** | PDF/EPUB metadata extraction (Aspose, VersOne.Epub) | +| **NodeAgent** | One replica per swarm node: tails the systemd journal + samples host health/disk into `system.events` (see [Ops, Fleet & Status](#ops-fleet--status)) | +| **Notifications** | SMS / voice / phone-lookup / verify lanes (`notifications.{sms,voice,verify,phone}.*`) | +| **Ocr** | Passport-OCR RPC lane (`ocr.passport.*`) | +| **PhotographProcessor** | Photo pipeline: receive → scan → metadata extract → move (see [Photography & Gallery](#photography--gallery)) | | **QuizGeneration** | LLM-powered quiz generation from book content (GPU-scheduled) | | **Scheduler** | Scheduled task execution (GeoIP updates, HTTP callbacks) | +| **SecurityMonitor** | Redis-backed stateful detectors over `system.events` producing security alerts | | **SecurityScanner** | Uploaded content security scanning | +| **SystemRemediation** | Rule engine over system events: dedupes incidents, dispatches curated SAFE remediations or alerts operators | | **UploadCoordinator** | Orchestrates the full book/upload lifecycle across processing stages | +Plus three non-handler **worker libraries** consumed by the handlers: `SpikerSoft.Workers.{Decompile,Notifications,Ocr}`. + ### Production Queue Architecture -From the `/healthz` endpoint, the production RabbitMQ instance runs 9 queues: +The queue set is code-derived and grows with the handler fleet — treat the handler inventory above as the map, not a frozen queue list. The major lane families: -| Queue | Consumers | Purpose | -|-------|-----------|---------| -| `calendar.reminders` | 0 | Calendar reminder dispatch | -| `code.execution.requests` | 4 | Sandboxed code execution input | -| `code.execution.responses` | 1 | Code execution results | -| `keycloak.events` | 0 | Keycloak event sync | -| `notifications.signalr` | 1 | Real-time notification bridge to SignalR | -| `notifications.signalr.retry.1` | 0 | First retry tier (delayed) | -| `notifications.signalr.retry.2` | 0 | Second retry tier (longer delay) | -| `notifications.signalr.retry.3` | 0 | Third retry tier (longest delay) | -| `notifications.signalr.dlq` | 0 | Dead letter queue for failed notifications | +| Lane family | Purpose | +|-------------|---------| +| `code.execution.*` + `lesson.regrade.*` | Sandboxed grading (live submit + offline re-grade RPC) | +| `book.create.requests`, `image.extraction`, `metadata.extraction.requests`, `quiz.generation`, `upload.lifecycle`, `file.operations`, `security.scan.requests` | Book/upload processing pipeline | +| `game.events` (+ `game.events.dlx`), `game.persistence.events` (+ dlq) | Game server persistence | +| `art.asset.*`, `art.model.*`, `artpipe.*` | Art Studio stage dispatch + per-model queues + lifecycle events | +| `gpu.lease.*` | GPU coordinator lease protocol | +| `photograph.*` | Photo pipeline | +| `decompile.*`, `ocr.passport.*` | Direct-reply-to RPC lanes | +| `lesson.video.*` | Lesson video processing | +| `notifications.signalr` (+ retry tiers `.retry.1/2/3` + `.dlq`), `notifications.{sms,voice,email,verify,phone}.*` | Notification delivery | +| `system.events`, `security.alert.*` | Host-fleet monitoring and remediation | +| `calendar.reminders`, `keycloak.events` | Calendar + identity sync | ### Key Files @@ -1980,9 +2442,9 @@ From the `/healthz` endpoint, the production RabbitMQ instance runs 9 queues: --- -## Marks Field Service +## Marks Field Service (legacy CRM) -An integrated field-service customer management tool for tracking residential and commercial customers, their equipment (generators, propane tanks), and service history. The feature includes a Google Maps-based workflow for visualizing customer locations. +The original single-tenant field-service CRM for the Mark Wilson generator-service business: residential and commercial customers with embedded addresses, generators, and service history, plus a Google Maps workflow for visualizing customer locations. Its multi-tenant successor is [Time & Materials](#time--materials-apitm) below — the two surfaces **coexist**; this is not a completed migration. ### Features @@ -1992,44 +2454,74 @@ An integrated field-service customer management tool for tracking residential an - **Service contracts** — Track service agreements and maintenance schedules - **Address management** — Structured addresses with postal code lookup integration -### API Endpoints +### Scope -| Method | Endpoint | Purpose | -|--------|----------|---------| -| `GET` | `/api/mark-wilson/customer` | List all customers | -| `POST` | `/api/mark-wilson/customer` | Create customer | -| `GET` | `/api/mark-wilson/customer/{id}` | Get customer detail | -| `PUT` | `/api/mark-wilson/customer/{id}` | Update customer | +The backend surface is far larger than one controller: `MarkWilsonsCustomerController` (route `/api/mark-wilson/customer`) plus supporting domains `ServiceCalls`, `ServiceCallReasons`, `Employees`, `Parts`, `Labors`, `Milages`, `FuelTypes`, `GeneratorBrands`, `AddressTypes`, `PhoneTypes`. The frontend lives under `_components/marks-site/` (`marks-landing-page`, `marks-customer-list`, `marks-generator`, `marks-propane-tank`, `marks-service-call`, `marks-service-contract`, `marks-address`). + +> **Auth note:** because this CRM shares the main Keycloak realm with student/consumer accounts, all of it is gated `Admin/Staff` — a plain `[Authorize]` here once let any platform user read customer PII, fixed in spikersoft-issues#688's follow-ups. ### Key Files | File | Purpose | |------|---------| -| `CustomersController.cs` | API controller for customer CRUD | +| `CustomersController.cs` | API controller for customer CRUD (`MarkWilsonsCustomerController` inside) | | `marks-customer.component.ts` | Customer management UI | | `marks-map.component.ts` | Google Maps customer visualization | | `libraries/marks-site-models/` | Shared TypeScript models | --- +## Time & Materials (`api/tm`) + +The **multi-tenant, multi-asset-type successor** to the Marks CRM: a full field-service/billing bounded context in the backend plus its own native Android app (`spikerj/spikersoft-time-and-materials`). + +### The Android app + +One binary, **two roles** — and the role is *data* (on `TmProfile`), not a Keycloak role, so any authenticated realm user can onboard: + +| Role | Bottom nav | Capabilities | +|---|---|---| +| **Service tech** | Schedule · Jobs · Customers · Map · More | Customers, typed assets, jobs with time/materials/travel entries, availability calendar, route plans, agreements, invoices | +| **Customer** | Appointments · Book · Equipment · More | Own sites/equipment, **self-booking** against a preferred tech or the first open slot, agreements, invoices | + +Tech stack mirrors the main Android app (Compose M3, Hilt, Retrofit, Room, WorkManager, MapLibre 13.4.1, AppAuth), with its own Keycloak PKCE client `spikersoft-time-and-materials` on the same realm. + +### Backend (`SpikerSoft.Api/Domain/TimeAndMaterials/`) + +Seven controllers over MediatR CQRS and Mongo `Tm*` documents (Profile, Business, Customer, Job, Invoice, Agreement, Availability, Material, RoutePlan, AssetType): + +| Area | Controller | Highlights | +|---|---|---| +| Profile & onboarding | `TmProfileController` | onboard, join-business, role change | +| Customers & assets | `TmCustomersController` | customers, **pluggable asset-type catalog** (`TmAssetTypeCatalog` — "generator" is just one entry, with type-specific checklist items like `load_test`/`hours`) | +| Jobs | `TmJobsController` | jobs + time/material/travel entries + attachments | +| Scheduling | `TmSchedulingController` | availability, open slots (`TmSlotCalculator`), appointments with reschedule/cancel | +| Agreements | `TmAgreementsController` | service agreements | +| Route plans | `TmRoutePlansController` | day-route planning (`TmRoutePlanner`) | +| Invoices | `TmInvoicesController` | invoicing + status | + +The app's one non-`/tm` call is the shared `Geocoding` controller. There is no web frontend for `/api/tm` yet — the Android app is the only consumer. + +--- + ## Health Check & Observability The platform exposes a comprehensive `/healthz` endpoint that validates the entire infrastructure stack. This endpoint powers uptime monitoring and aids rapid diagnosis during incidents. ### Health Checks (13 total) -Registered in `SpikerSoft.Api` `AddHealthChecksConfiguration` (order in code: RabbitMQ, MongoDB, Redis Cluster, Vector Search, Keycloak, InfluxDB, Email Server, TileServerGL, DNS Server, DKIM, DMARC, SPF, Postal Code Data). +Registered in `SpikerSoft.Api` `AddHealthChecksConfiguration` (order in code: RabbitMQ, MongoDB, MongoDB Indexes, Redis Cluster, Vector Search, Keycloak, InfluxDB, Email Server, DNS Server, DKIM, DMARC, SPF, Postal Code Data). | Check | What It Validates | |-------|-------------------| | **MongoDB** | Cluster reachability via `mongo-router`, ping latency, database list | +| **MongoDB Indexes** | Expected indexes exist on the collections that need them | | **Redis Cluster** | All 6 nodes connected (3 masters + 3 replicas), cluster state `ok`, slot coverage | | **RabbitMQ** | Queue health, consumer counts, message depth, DLQ status | | **Vector Search** | Redis vector index exists (default name `idx:BookPageChunk`, overridable via `VectorSearch:IndexName`) | | **Keycloak** | Authentication endpoint reachable at `ids.spikersoft.com` | | **InfluxDB** | Ping, bucket existence, write/query round-trip at `influxdb.spikersoft.com` | | **Email Server** | SMTP connection to `mail.spikersoft.com:587`, STARTTLS, authentication | -| **TileServerGL** | Terrain tile fetch from `tiles.spikersoft.com` with latency measurement | | **DNS Server** | Self-hosted DNS at `192.168.0.105:5380` responding | | **DKIM** | DKIM record valid for `spikersoft.com` (selector: `mail`, RSA key) | | **DMARC** | DMARC policy active (`quarantine`) with aggregate reporting | @@ -2069,14 +2561,68 @@ This ensures `@spikersoft.com` emails (child account provisioning, notifications --- +## Ops, Fleet & Status + +Beyond app-level health checks, the platform watches its own **host fleet** and surfaces incidents both to operators and to the public. + +### Host-agent pipeline + +``` +NodeAgent (1 per swarm node) SecurityMonitor SystemRemediation + tails systemd journal → Redis-backed stateful → rule engine over events: + samples host health/disk detectors producing dedupes SystemIncident read-models, + → publishes system.events SecurityAlerts dispatches a curated SAFE remediation + command back to the node, or alerts operators +``` + +- `SpikerSoft.EventHandlers.NodeAgent` — journal tail + host health/disk sampling into `system.events`. +- `SpikerSoft.EventHandlers.SecurityMonitor` — stateful detectors (Redis-backed) that turn raw events into `SecurityAlert`s. +- `SpikerSoft.EventHandlers.SystemRemediation` — the rule engine; only **curated, safe** remediation commands are ever dispatched automatically. + +### Ops console & public status + +- **Ops console** — read-only Mongo read-models over incidents and security alerts (`OpsIncidentsController`, `OpsSecurityAlertsController`). Authorization is deliberately **stricter** than the usual admin tools: `IsDevOpsOrAdmin`, with `staff` explicitly denied. +- **Public status page** — `/status` (`libraries/features/status/`) is a no-auth, read-only page for school operators: current platform state, no actions. Distinct from the admin console. + +--- + +## More Platform Features + +Shorter notes on features that have their own code surface but don't need a full section: + +### Skills & Badges + +Skill definitions with per-user awards (`SkillsController`): award-by-interest lookup, manual awards, and auto-award checking. Surfaced as the profile **Skills** tab, `ui-award-chips` throughout the UI, and `/admin/skill-management`. + +### Calendar & availability + +A `/calendar` route + `CalendarController`, fed by the profile **Schedule** tab's weekly availability; reminders dispatch through the `CalendarReminders` handler. + +### Geography explorer globe + +The geography explorer now lands on the shared WebGL globe (behind `@defer`) with fact-count hover labels; the old sidebar-cards view is one of **four** switchable layouts, and the interest filter seeds from the user's rated profile interests. + +### Tree of Knowledge + +`/tree-of-knowledge` (+ `/tree-of-knowledge/domain/:domainKey`) visualizes per-domain proficiency computed from tracked activity across the platform's learning surfaces. + +### Admin surfaces + +Beyond those documented above: `/admin/activity-coverage`, `/admin/platform-adoption`, `/admin/interests`, `/admin/geography-facts`, `/admin/skill-management`, `/admin/contact-messages`, `/admin/lesson-video-moderation`, and the five `/admin/art-*` routes ([Art Studio](#art-studio)). + +### Assorted routes + +`/ai` (AI assistant), `/classes`, `/links`, `/mission`, `/syllabus`, `/sms-consent`, `/our-hardware` (Dreamstream cluster dashboard), `/organization/confirm`, `/parental-approve/:token`, `/chemistry`, and the exploratory `/brain-forge`, `/cortex-quest`, `/wisdom-well`. + +--- + ## TODO / Future work Planned or exploratory integrations not yet in the product stack: -- **Apache Guacamole (HTML5 remote desktop)** — Run a [Guacamole](https://guacamole.apache.org/) server and pair it with a **custom Angular 21** client UI: a from-scratch or heavy rewrite of the official web client, using the [guacamole-client `next` branch frontend](https://github.com/apache/guacamole-client/tree/next/guacamole/src/main/frontend) as a behavioral and protocol reference (not necessarily a line-for-line port), aligned with SpikerSoft’s stack and theming. +- **Apache Guacamole (HTML5 remote desktop)** — Run a [Guacamole](https://guacamole.apache.org/) server and pair it with a **custom Angular** client UI: a from-scratch or heavy rewrite of the official web client, using the [guacamole-client `next` branch frontend](https://github.com/apache/guacamole-client/tree/next/guacamole/src/main/frontend) as a behavioral and protocol reference (not necessarily a line-for-line port), aligned with SpikerSoft’s stack and theming. - **OpenSC2K-style city sim (optional game)** — Investigate embedding or forking ideas from [OpenSC2K](https://github.com/nicholas-ochoa/OpenSC2K) (WebGL / Phaser) as a **SimCity 2000–style** learning game or creative sandbox, subject to licensing, asset, and product-fit review (the upstream project is GPL-3.0; ship only compliant code and assets). - **PHP in the browser (WASM)** — Add a **PHP learning / playground** path parallel to the existing C# and Python storylines, using [php-wasm](https://github.com/seanmorris/php-wasm) (PHP in the browser via WebAssembly), preloaded and status-tracked like other WASM runtimes in the **Offline** panel where applicable. -- **x86-64 assembly learning (Blink WASM + Angular UI)** — Adopt the **Blink-based emulator** and tooling approach from [x86-64-playground](https://github.com/robalb/x86-64-playground) (assembly editor, emulator, and debugger-style experience for the x86-64 Linux model in the browser). **Reuse their WASM** (the packaged Blink build / integration pattern) as the execution layer; **re-implement the front end natively in Angular 21** (the upstream app is Svelte + Vite) so the experience matches SpikerSoft UI patterns and theming, subject to license review of upstream artifacts. - **TypeScript curriculum (extension of the JavaScript track)** — JavaScript playground + lessons are already shipping (Node server + QuickJS-WASM browser). The follow-on TypeScript curriculum reuses the same runtime via `LessonMetadata.Preprocessor = LessonPreprocessor.TypeScript`: both Node and the QuickJS worker call `ts.transpileModule` before grading. Add a **"Transpile to JavaScript"** action in TypeScript lessons that uses the **TypeScript compiler in the browser** (same API as the canonical [`typescript` package](https://www.npmjs.com/package/typescript) ships in `lib/typescript.js` — the implementation currently referenced at [`unpkg.com/typescript@latest/lib/typescript.js`](https://unpkg.com/typescript@latest/lib/typescript.js)). **Dependency policy:** add `typescript` as a **first-party pnpm dependency** and serve the browser bundle from our own build/static assets (or vendor the built file in-repo), so **no runtime dependency on a third-party CDN** for the compiler; unpkg is only the upstream reference for which artifact to align with. --- -- 2.54.0