1587 lines
62 KiB
Markdown
1587 lines
62 KiB
Markdown
# Fractafrag — Full Platform Specification
|
||
**Version:** 1.1
|
||
**Date:** March 2026
|
||
**Status:** Pre-build handoff to GSD
|
||
|
||
---
|
||
|
||
## Executive Summary
|
||
|
||
Fractafrag is a self-hosted web platform for creating, browsing, and generating GLSL shaders — real-time 2D and 3D visuals that run on the GPU.
|
||
|
||
It is three things fused: a TikTok-style adaptive feed of living, animated visuals that learns your taste from dwell time, votes, and replays; a Shadertoy-style in-browser code editor where anyone can write, fork, and publish shaders; and an AI generation layer where you describe what you want in plain language and the platform writes and renders the shader for you.
|
||
|
||
When a user wants something that doesn't exist yet — "ragdoll physics but dark and slow" — they post it to the desire queue. Other users or autonomous AI agents can fulfill it, with optional tips attached to high-demand requests.
|
||
|
||
The platform exposes a REST API and an MCP server so AI tools (Claude, GPT agents, custom automations) can browse, generate, and submit shaders programmatically — making Fractafrag a productive target for AI creative pipelines, not just a human browsing experience.
|
||
|
||
Everything runs in a single Docker Compose stack, self-hosted on Linux, no cloud dependencies required.
|
||
|
||
Monetization at launch is subscription tiers and AI generation credits. A full creator economy — revenue share, source licensing, commercial rights — is designed and documented in Section 11, but **deliberately deferred** until the platform has organic community traction. Build the data hooks now. Build the features later.
|
||
|
||
**North star: make it fun first. Everything else follows.**
|
||
|
||
---
|
||
|
||
## Table of Contents
|
||
1. [Vision & Product Summary](#1-vision--product-summary)
|
||
2. [Tech Stack Decision](#2-tech-stack-decision)
|
||
3. [Docker Compose Architecture](#3-docker-compose-architecture)
|
||
4. [Service Specs](#4-service-specs)
|
||
- 4a. Frontend
|
||
- 4b. API
|
||
- 4c. MCP Server
|
||
- 4d. Renderer
|
||
- 4e. Worker
|
||
5. [Database Schema](#5-database-schema)
|
||
6. [Authentication & Security](#6-authentication--security)
|
||
7. [AI Generation Pipeline](#7-ai-generation-pipeline)
|
||
8. [Recommendation Engine](#8-recommendation-engine)
|
||
9. [Desire Queue & Bounty Board](#9-desire-queue--bounty-board)
|
||
10. [Monetization & Payment Tiers](#10-monetization--payment-tiers)
|
||
11. [Creator Economy — Future Rollout](#11-creator-economy--future-rollout)
|
||
12. [MCP Server Tool Definitions](#12-mcp-server-tool-definitions)
|
||
13. [API Endpoint Reference](#13-api-endpoint-reference)
|
||
14. [Parallel Build Tracks](#14-parallel-build-tracks)
|
||
15. [Milestone Roadmap](#15-milestone-roadmap)
|
||
16. [Open Questions & Decisions for GSD](#16-open-questions--decisions-for-gsd)
|
||
|
||
---
|
||
|
||
## 1. Vision & Product Summary
|
||
|
||
**Fractafrag** is a self-hosted, community-driven GLSL shader platform combining:
|
||
|
||
- A TikTok-style adaptive visual feed of live-rendered 2D/3D shaders
|
||
- A Shadertoy-style in-browser editor for creating and submitting shaders
|
||
- An AI generation system allowing users to prompt shaders into existence
|
||
- A desire queue and bounty board where users express what they want to see, and AI agents or human creators fulfill those requests
|
||
- A full REST API and MCP (Model Context Protocol) server enabling AI tools to browse, generate, and submit shaders autonomously
|
||
- A monetization layer built around AI generation credits, BYOK (bring your own key), and bounty facilitation fees
|
||
- A creator economy layer (future rollout — see Section 11) enabling revenue share, source licensing, and commercial rights for established creators
|
||
|
||
**Primary focus for initial development:** Build a platform that is genuinely fun to browse and use. The feed, the editor, the AI generation, and the desire queue are the product. Monetization features beyond the basic subscription and credit tiers are deliberately deferred until the platform has demonstrated organic community growth. All components should be architected with the future creator economy in mind, but not blocked on it.
|
||
|
||
**What makes Fractafrag different from Shadertoy, GLSL Sandbox, and others:**
|
||
- Adaptive feed — learns what you like from dwell time, votes, replays, and explicit desires
|
||
- AI generation built into the platform, not bolted on
|
||
- MCP server that lets any LLM-based agent plug in and autonomously generate or fulfill bounties
|
||
- Bounty/desire economy that closes the loop between user desire and content creation
|
||
- Fully self-hostable, Docker-native, no cloud dependency
|
||
|
||
**Core name etymology:**
|
||
`fracta` — fractal, generative, infinite mathematical form
|
||
`frag` — `fragColor`, the GLSL output variable every shader writes to. An easter egg for shader developers.
|
||
|
||
---
|
||
|
||
## 2. Tech Stack Decision
|
||
|
||
GSD should make final calls on language/framework, but the following is strongly recommended based on the platform's requirements:
|
||
|
||
### Backend API
|
||
**Recommendation: Python + FastAPI**
|
||
|
||
Rationale:
|
||
- AI/ML integrations (pgvector embeddings, LLM clients, numpy for recommendation math) are Python-native
|
||
- FastAPI gives async performance comparable to Node without the callback hell
|
||
- Pydantic models double as API docs (auto-generates OpenAPI spec)
|
||
- Celery + Redis worker ecosystem is mature for the job queue
|
||
|
||
Alternative if team has stronger Node experience: **Hono on Bun** — fast, modern, TypeScript-native, handles the same async workloads well.
|
||
|
||
### Frontend
|
||
**React + Vite + Three.js / raw WebGL**
|
||
|
||
Rationale:
|
||
- Three.js for 3D shader rendering in the editor
|
||
- Raw WebGL via canvas for the feed thumbnails (lighter, faster for many simultaneous renders)
|
||
- React for UI, TanStack Query for server state, Zustand for client state
|
||
- Tailwind CSS for styling
|
||
|
||
### Database
|
||
- **PostgreSQL** — primary datastore (shaders, users, votes, bounties, transactions)
|
||
- **pgvector extension** — taste vectors, style embeddings, desire embeddings (nearest-neighbor queries)
|
||
- **Redis** — session cache, feed cache, rate limiting, BullMQ job queue
|
||
|
||
### Renderer
|
||
- **Headless Chromium via Puppeteer** — runs shaders in an isolated browser context, captures video thumbnails and still previews
|
||
- Alternative worth evaluating: **esbuild + offscreen-canvas in Node** (lighter, but less accurate to browser rendering)
|
||
|
||
### Payments
|
||
- **Stripe** — subscription tiers + one-time bounty payments
|
||
- Stripe Connect for creator payouts (bounty fulfillment)
|
||
|
||
---
|
||
|
||
## 3. Docker Compose Architecture
|
||
|
||
All services run within a single Docker Compose stack. The entire platform is self-contained with no external cloud dependencies required for core functionality (AI provider API keys are user-supplied).
|
||
|
||
```
|
||
fractafrag/
|
||
├── docker-compose.yml
|
||
├── docker-compose.override.yml # local dev overrides
|
||
├── .env.example
|
||
├── services/
|
||
│ ├── frontend/ # React app
|
||
│ ├── api/ # FastAPI backend
|
||
│ ├── mcp/ # MCP server
|
||
│ ├── renderer/ # Headless shader renderer
|
||
│ ├── worker/ # BullMQ/Celery job worker
|
||
│ └── nginx/ # Reverse proxy + SSL termination
|
||
├── db/
|
||
│ └── init.sql # Schema bootstrap
|
||
└── scripts/
|
||
└── seed.py # Dev seed data
|
||
```
|
||
|
||
### docker-compose.yml (structure)
|
||
|
||
```yaml
|
||
version: "3.9"
|
||
services:
|
||
|
||
nginx:
|
||
image: nginx:alpine
|
||
ports: ["80:80", "443:443"]
|
||
volumes: [./nginx/conf:/etc/nginx/conf.d, ./nginx/certs:/etc/ssl]
|
||
depends_on: [frontend, api, mcp]
|
||
|
||
frontend:
|
||
build: ./services/frontend
|
||
environment:
|
||
- VITE_API_URL=https://fractafrag.yourdomain.com/api
|
||
- VITE_MCP_URL=https://fractafrag.yourdomain.com/mcp
|
||
|
||
api:
|
||
build: ./services/api
|
||
environment:
|
||
- DATABASE_URL=postgresql://fracta:${DB_PASS}@postgres:5432/fractafrag
|
||
- REDIS_URL=redis://redis:6379
|
||
- STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY}
|
||
- TURNSTILE_SECRET=${TURNSTILE_SECRET}
|
||
- JWT_SECRET=${JWT_SECRET}
|
||
- RENDERER_URL=http://renderer:3100
|
||
depends_on: [postgres, redis]
|
||
|
||
mcp:
|
||
build: ./services/mcp
|
||
environment:
|
||
- API_BASE_URL=http://api:8000
|
||
- MCP_API_KEY_SALT=${MCP_API_KEY_SALT}
|
||
ports: ["3200:3200"]
|
||
|
||
renderer:
|
||
build: ./services/renderer
|
||
shm_size: "512mb" # Required for Chromium
|
||
environment:
|
||
- MAX_RENDER_DURATION=8 # seconds
|
||
- OUTPUT_DIR=/renders
|
||
volumes: [renders:/renders]
|
||
|
||
worker:
|
||
build: ./services/worker
|
||
environment:
|
||
- DATABASE_URL=postgresql://fracta:${DB_PASS}@postgres:5432/fractafrag
|
||
- REDIS_URL=redis://redis:6379
|
||
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} # platform key for internal generation
|
||
- OPENAI_API_KEY=${OPENAI_API_KEY}
|
||
- RENDERER_URL=http://renderer:3100
|
||
depends_on: [postgres, redis, renderer]
|
||
|
||
postgres:
|
||
image: pgvector/pgvector:pg16
|
||
environment:
|
||
- POSTGRES_USER=fracta
|
||
- POSTGRES_PASSWORD=${DB_PASS}
|
||
- POSTGRES_DB=fractafrag
|
||
volumes: [pgdata:/var/lib/postgresql/data]
|
||
|
||
redis:
|
||
image: redis:7-alpine
|
||
volumes: [redisdata:/data]
|
||
|
||
volumes:
|
||
pgdata:
|
||
redisdata:
|
||
renders:
|
||
```
|
||
|
||
### Nginx Routing
|
||
|
||
```
|
||
/ → frontend (React SPA)
|
||
/api/* → api:8000 (FastAPI)
|
||
/mcp/* → mcp:3200 (MCP Server)
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Service Specs
|
||
|
||
### 4a. Frontend
|
||
|
||
**Stack:** React 18, Vite, Three.js, TanStack Query, Zustand, Tailwind CSS
|
||
|
||
**Pages & Routes:**
|
||
|
||
| Route | Page | Description |
|
||
|-------|------|-------------|
|
||
| `/` | Feed | Infinite scroll adaptive shader feed |
|
||
| `/explore` | Explore | Browse by tag, trending, new, top |
|
||
| `/shader/:id` | Shader detail | Full-screen view, code, comments, vote |
|
||
| `/editor` | Editor | GLSL editor + live WebGL preview |
|
||
| `/editor/:id` | Fork editor | Edit existing shader |
|
||
| `/generate` | AI Generator | Prompt-to-shader interface |
|
||
| `/bounties` | Bounty board | Open desire queue |
|
||
| `/bounties/:id` | Bounty detail | Single desire, stats, submissions |
|
||
| `/profile/:username` | Profile | User shaders, votes, bounties |
|
||
| `/settings` | Settings | Account, API keys, subscription |
|
||
| `/login` | Auth | Login / register with CAPTCHA |
|
||
|
||
**Key Frontend Components:**
|
||
|
||
**ShaderCanvas** — Core WebGL renderer used everywhere. Accepts GLSL code, renders it live in a canvas element using WebGL2. Handles uniform injection (iTime, iResolution, iMouse, etc., Shadertoy-compatible). Used in the editor (full size), the feed (thumbnail/preview), and the shader detail page (full screen).
|
||
|
||
**FeedItem** — A feed card containing a looping ShaderCanvas thumbnail (auto-plays muted), title, author, vote controls (upvote/downvote), and dwell time tracking (IntersectionObserver + timer). Reports dwell time back to the API every 5 seconds while visible.
|
||
|
||
**InfiniteFeed** — Wraps FeedItem in a virtualized scroll list. Fetches next page of feed from `/api/feed` using cursor-based pagination. The feed endpoint returns shaders ranked by the recommendation engine for the authenticated user (or trending/new for anonymous users).
|
||
|
||
**DesireInput** — A natural language text input on the feed page. "Show me something more like X" — submits to the desire queue. Displays in the UI as a pending request with a loading state that resolves when fulfilled.
|
||
|
||
**GLSLEditor** — Monaco Editor (same as VS Code) with GLSL syntax highlighting, autocompletion for uniforms, and error underlining from the GLSL linter. Split pane: editor on left, live preview on right. Submit button triggers the validation + render pipeline.
|
||
|
||
**BountyCard** — Displays a desire/bounty. Shows original request text, heat score (how many users want something similar), tip amount if any, time posted, status (open/fulfilled), and a "Fulfill this" button for creators.
|
||
|
||
**AiGeneratorPanel** — The primary AI generation UI. Text prompt input, style sliders (chaos level, color temperature, motion type), provider selector (platform credits / BYOK), generation status, and the rendered result when complete. Shows remaining free generations or upgrade CTA.
|
||
|
||
**Dwell Time Tracking:**
|
||
The recommendation engine runs on signals. Dwell time is the most valuable. Implementation:
|
||
```javascript
|
||
// FeedItem dwell tracking
|
||
const startTime = useRef(null);
|
||
const reported = useRef(0);
|
||
|
||
// IntersectionObserver fires when card enters/exits viewport
|
||
onVisible: () => { startTime.current = Date.now(); }
|
||
onHidden: () => {
|
||
const dwell = (Date.now() - startTime.current) / 1000;
|
||
if (dwell > 1) { // ignore flicker scrolls
|
||
api.reportDwell({ shaderId, dwell, replayed });
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 4b. API (FastAPI)
|
||
|
||
**Base URL:** `/api/v1`
|
||
|
||
Full endpoint reference in Section 12. High-level structure:
|
||
|
||
```
|
||
api/
|
||
├── main.py
|
||
├── routers/
|
||
│ ├── auth.py # register, login, refresh, logout
|
||
│ ├── shaders.py # CRUD, submit, fork, search
|
||
│ ├── feed.py # personalized feed, trending, new
|
||
│ ├── votes.py # upvote, downvote, dwell report
|
||
│ ├── generate.py # AI generation endpoints
|
||
│ ├── desires.py # desire queue, bounties
|
||
│ ├── bounties.py # bounty creation, fulfillment, payouts
|
||
│ ├── users.py # profiles, settings
|
||
│ ├── payments.py # Stripe webhooks, subscription, credits
|
||
│ └── mcp_keys.py # API key management for MCP clients
|
||
├── services/
|
||
│ ├── glsl_validator.py
|
||
│ ├── renderer_client.py
|
||
│ ├── embedding.py # style vector generation
|
||
│ ├── recommendation.py
|
||
│ ├── ai_generator.py
|
||
│ └── stripe_service.py
|
||
├── models/ # SQLAlchemy ORM models
|
||
├── schemas/ # Pydantic request/response schemas
|
||
└── middleware/
|
||
├── auth.py # JWT verification
|
||
└── rate_limit.py # Redis-backed rate limiting
|
||
```
|
||
|
||
**Key Middleware:**
|
||
|
||
- JWT authentication (all protected routes)
|
||
- Cloudflare Turnstile verification (auth endpoints + shader submit)
|
||
- Redis rate limiting (per IP + per user):
|
||
- Anonymous: 100 req/min
|
||
- Authenticated: 300 req/min
|
||
- MCP API key: configurable per tier
|
||
|
||
---
|
||
|
||
### 4c. MCP Server
|
||
|
||
**Stack:** Python using the official `mcp` SDK, HTTP + SSE transport
|
||
|
||
**Port:** 3200 (exposed via nginx at `/mcp`)
|
||
|
||
**Transport:** Streamable HTTP (production), stdio available for local dev
|
||
|
||
**Auth:** Bearer token (MCP API key) in Authorization header. Keys are issued per user account, managed in Settings → API Keys.
|
||
|
||
**Full tool definitions in Section 11.**
|
||
|
||
```
|
||
mcp/
|
||
├── server.py # MCP server entrypoint
|
||
├── tools/
|
||
│ ├── submit_shader.py
|
||
│ ├── browse_shaders.py
|
||
│ ├── get_desire_queue.py
|
||
│ ├── fulfill_desire.py
|
||
│ ├── get_trending.py
|
||
│ ├── tag_shader.py
|
||
│ └── get_feed_context.py
|
||
└── auth.py # API key verification via internal API call
|
||
```
|
||
|
||
**Connection example for Claude Desktop:**
|
||
```json
|
||
{
|
||
"mcpServers": {
|
||
"fractafrag": {
|
||
"url": "https://fractafrag.yourdomain.com/mcp/sse",
|
||
"headers": {
|
||
"Authorization": "Bearer ff_key_your_api_key_here"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**Trust tiers for MCP clients:**
|
||
|
||
| Tier | Submissions/hour | Auto-publish | fulfill_desire | tag_shader |
|
||
|------|-----------------|--------------|----------------|------------|
|
||
| Probation (new key) | 10 | No (queued for review) | No | No |
|
||
| Trusted | 100 | Yes | Yes | Yes |
|
||
| Premium | 500 | Yes | Yes | Yes |
|
||
|
||
Promotion from Probation → Trusted: automatic after 10 approved submissions with >0.6 avg approval rate.
|
||
|
||
---
|
||
|
||
### 4d. Renderer Service
|
||
|
||
**Stack:** Node.js + Puppeteer (Headless Chromium)
|
||
|
||
**Purpose:** Accepts GLSL code via HTTP POST, renders it in an isolated browser context, returns a preview image and a short looping video (WebM/MP4).
|
||
|
||
**Endpoint:**
|
||
```
|
||
POST /render
|
||
{
|
||
"glsl": "void mainImage(out vec4 fragColor, in vec2 fragCoord) { ... }",
|
||
"duration": 5, // seconds to render
|
||
"width": 640,
|
||
"height": 360,
|
||
"fps": 30
|
||
}
|
||
|
||
Response:
|
||
{
|
||
"thumbnail_url": "/renders/abc123/thumb.jpg",
|
||
"preview_url": "/renders/abc123/preview.webm",
|
||
"duration_ms": 4823,
|
||
"peak_memory_mb": 112,
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
**Safety measures:**
|
||
- Max render duration: 8 seconds (configurable)
|
||
- GPU memory cap: Chromium flag `--max-gum-memory-mb=256`
|
||
- CPU timeout: process.kill after 15s regardless
|
||
- Sandboxed: `--no-sandbox` disabled in prod, use `--sandbox` with user namespace isolation
|
||
- Static analysis before render: detect infinite loops, banned extensions (compute shaders that could be GPU-abusive)
|
||
|
||
**GLSL Validator (pre-render):**
|
||
Run `glslang` (Khronos reference compiler) as a subprocess before sending to renderer. If lint fails, return error to submitter without spending a render.
|
||
|
||
```bash
|
||
echo "$GLSL_CODE" | glslangValidator --stdin -S frag
|
||
```
|
||
|
||
---
|
||
|
||
### 4e. Worker Service
|
||
|
||
**Stack:** Python + Celery (or BullMQ in Node if backend is Node) + Redis as broker
|
||
|
||
**Jobs:**
|
||
|
||
| Job | Trigger | Description |
|
||
|-----|---------|-------------|
|
||
| `render_shader` | On submission | Calls renderer, stores result, updates shader record |
|
||
| `embed_shader` | After render | Generates style embedding vector for recommendation |
|
||
| `process_desire` | New desire posted | Embeds desire, matches to existing shaders, optionally queues AI fulfillment |
|
||
| `ai_generate` | Manual or desire match | Calls LLM to write GLSL, submits via API |
|
||
| `payout_creator` | Bounty fulfilled + settled | Initiates Stripe Connect payout to creator |
|
||
| `expire_bounties` | Cron, daily | Marks old unfulfilled bounties as expired |
|
||
| `rebuild_feed_cache` | Cron, every 15min | Pre-computes trending/new lists for anonymous feed |
|
||
|
||
**AI Generation Worker flow:**
|
||
```
|
||
1. Dequeue job: { desire_id, prompt, style_metadata, provider, api_key }
|
||
2. Build system prompt (see Section 7)
|
||
3. Call LLM API (Anthropic / OpenAI / Ollama)
|
||
4. Extract GLSL from response
|
||
5. POST to /api/v1/shaders/submit (as platform bot user)
|
||
with fulfills_desire_id = desire_id
|
||
6. Wait for render result
|
||
7. If render passes: mark desire fulfilled, notify user
|
||
8. If render fails: retry with error feedback in next prompt (max 3 attempts)
|
||
9. If all retries fail: log failure, re-open desire
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Database Schema
|
||
|
||
### Core Tables
|
||
|
||
```sql
|
||
-- Users
|
||
CREATE TABLE users (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
username TEXT UNIQUE NOT NULL,
|
||
email TEXT UNIQUE NOT NULL,
|
||
password_hash TEXT NOT NULL,
|
||
role TEXT NOT NULL DEFAULT 'user', -- user, moderator, admin
|
||
trust_tier TEXT NOT NULL DEFAULT 'standard', -- standard, creator, trusted_api
|
||
stripe_customer_id TEXT,
|
||
subscription_tier TEXT DEFAULT 'free', -- free, pro, studio
|
||
ai_credits_remaining INTEGER DEFAULT 0,
|
||
taste_vector vector(512), -- pgvector: learned taste embedding
|
||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||
last_active_at TIMESTAMPTZ
|
||
);
|
||
|
||
-- Shaders
|
||
CREATE TABLE shaders (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
author_id UUID REFERENCES users(id),
|
||
title TEXT NOT NULL,
|
||
description TEXT,
|
||
glsl_code TEXT NOT NULL,
|
||
is_public BOOLEAN DEFAULT TRUE,
|
||
is_ai_generated BOOLEAN DEFAULT FALSE,
|
||
ai_provider TEXT, -- anthropic, openai, ollama, null
|
||
thumbnail_url TEXT,
|
||
preview_url TEXT,
|
||
render_status TEXT DEFAULT 'pending', -- pending, rendering, ready, failed
|
||
style_vector vector(512), -- pgvector: visual style embedding
|
||
style_metadata JSONB, -- { chaos_level, color_temp, motion_type, ... }
|
||
tags TEXT[],
|
||
shader_type TEXT DEFAULT '2d', -- 2d, 3d, audio-reactive
|
||
forked_from UUID REFERENCES shaders(id),
|
||
view_count INTEGER DEFAULT 0,
|
||
score FLOAT DEFAULT 0, -- cached hot score for feed ranking
|
||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||
);
|
||
|
||
-- Votes
|
||
CREATE TABLE votes (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
user_id UUID REFERENCES users(id),
|
||
shader_id UUID REFERENCES shaders(id),
|
||
value SMALLINT NOT NULL CHECK (value IN (-1, 1)),
|
||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||
UNIQUE (user_id, shader_id)
|
||
);
|
||
|
||
-- Engagement signals (dwell time, replays, shares)
|
||
CREATE TABLE engagement_events (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
user_id UUID REFERENCES users(id), -- null for anonymous
|
||
session_id TEXT, -- anonymous session token
|
||
shader_id UUID REFERENCES shaders(id),
|
||
event_type TEXT NOT NULL, -- dwell, replay, share, generate_similar
|
||
dwell_secs FLOAT,
|
||
metadata JSONB,
|
||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||
);
|
||
|
||
-- Desires / Bounties
|
||
CREATE TABLE desires (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
author_id UUID REFERENCES users(id),
|
||
prompt_text TEXT NOT NULL,
|
||
prompt_embedding vector(512), -- embedded for similarity grouping
|
||
style_hints JSONB, -- { chaos_level, color_temp, etc }
|
||
tip_amount_cents INTEGER DEFAULT 0,
|
||
status TEXT DEFAULT 'open', -- open, in_progress, fulfilled, expired
|
||
heat_score FLOAT DEFAULT 1, -- updated as similar desires accumulate
|
||
fulfilled_by_shader UUID REFERENCES shaders(id),
|
||
fulfilled_at TIMESTAMPTZ,
|
||
expires_at TIMESTAMPTZ,
|
||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||
);
|
||
|
||
-- Similar desire grouping (many-to-many)
|
||
CREATE TABLE desire_clusters (
|
||
cluster_id UUID,
|
||
desire_id UUID REFERENCES desires(id),
|
||
similarity FLOAT,
|
||
PRIMARY KEY (cluster_id, desire_id)
|
||
);
|
||
|
||
-- Bounty tips (financial layer on top of desires)
|
||
CREATE TABLE bounty_tips (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
desire_id UUID REFERENCES desires(id),
|
||
tipper_id UUID REFERENCES users(id),
|
||
amount_cents INTEGER NOT NULL,
|
||
stripe_payment_intent_id TEXT,
|
||
status TEXT DEFAULT 'held', -- held, released, refunded
|
||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||
);
|
||
|
||
-- Payouts to creators
|
||
CREATE TABLE creator_payouts (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
creator_id UUID REFERENCES users(id),
|
||
desire_id UUID REFERENCES desires(id),
|
||
gross_amount_cents INTEGER,
|
||
platform_fee_cents INTEGER, -- 10%
|
||
net_amount_cents INTEGER, -- 90%
|
||
stripe_transfer_id TEXT,
|
||
status TEXT DEFAULT 'pending',
|
||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||
);
|
||
|
||
-- API Keys (for MCP clients)
|
||
CREATE TABLE api_keys (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
user_id UUID REFERENCES users(id),
|
||
key_hash TEXT UNIQUE NOT NULL, -- bcrypt hash of the actual key
|
||
key_prefix TEXT NOT NULL, -- first 8 chars for display (ff_key_XXXXXXXX)
|
||
name TEXT, -- user-given label
|
||
trust_tier TEXT DEFAULT 'probation',
|
||
submissions_approved INTEGER DEFAULT 0,
|
||
rate_limit_per_hour INTEGER DEFAULT 10,
|
||
last_used_at TIMESTAMPTZ,
|
||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||
revoked_at TIMESTAMPTZ
|
||
);
|
||
|
||
-- AI generation log
|
||
CREATE TABLE generation_log (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
user_id UUID REFERENCES users(id),
|
||
shader_id UUID REFERENCES shaders(id),
|
||
provider TEXT NOT NULL,
|
||
prompt_text TEXT,
|
||
tokens_used INTEGER,
|
||
cost_cents INTEGER, -- platform cost for credit-based generations
|
||
success BOOLEAN,
|
||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||
);
|
||
|
||
-- Comments
|
||
CREATE TABLE comments (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
shader_id UUID REFERENCES shaders(id),
|
||
author_id UUID REFERENCES users(id),
|
||
body TEXT NOT NULL,
|
||
parent_id UUID REFERENCES comments(id),
|
||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||
);
|
||
```
|
||
|
||
### Indexes
|
||
|
||
```sql
|
||
-- Feed performance
|
||
CREATE INDEX idx_shaders_score ON shaders(score DESC) WHERE is_public = TRUE;
|
||
CREATE INDEX idx_shaders_created ON shaders(created_at DESC) WHERE is_public = TRUE;
|
||
CREATE INDEX idx_shaders_tags ON shaders USING GIN(tags);
|
||
|
||
-- Recommendation (pgvector ANN)
|
||
CREATE INDEX idx_shaders_style_vector ON shaders
|
||
USING ivfflat (style_vector vector_cosine_ops) WITH (lists = 100);
|
||
CREATE INDEX idx_users_taste_vector ON users
|
||
USING ivfflat (taste_vector vector_cosine_ops) WITH (lists = 50);
|
||
CREATE INDEX idx_desires_embedding ON desires
|
||
USING ivfflat (prompt_embedding vector_cosine_ops) WITH (lists = 50);
|
||
|
||
-- Engagement
|
||
CREATE INDEX idx_engagement_user ON engagement_events(user_id, created_at DESC);
|
||
CREATE INDEX idx_engagement_shader ON engagement_events(shader_id, event_type);
|
||
|
||
-- Desires / bounties
|
||
CREATE INDEX idx_desires_status ON desires(status, heat_score DESC);
|
||
CREATE INDEX idx_desires_author ON desires(author_id);
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Authentication & Security
|
||
|
||
### User Auth Flow
|
||
|
||
1. **Registration:** User submits email + password + Cloudflare Turnstile token
|
||
2. **Turnstile verification:** API calls Cloudflare's `/siteverify` endpoint server-side. Request rejected if token invalid.
|
||
3. **Password hashing:** bcrypt with cost factor 12
|
||
4. **JWT issuance:** Access token (15 min TTL) + refresh token (30 day TTL, stored in httpOnly cookie)
|
||
5. **Token refresh:** Silent refresh on 401, using refresh token cookie
|
||
6. **Logout:** Refresh token revoked in Redis blocklist
|
||
|
||
### JWT Payload
|
||
```json
|
||
{
|
||
"sub": "user-uuid",
|
||
"username": "johndoe",
|
||
"role": "user",
|
||
"tier": "free",
|
||
"iat": 1234567890,
|
||
"exp": 1234568790
|
||
}
|
||
```
|
||
|
||
### Cloudflare Turnstile
|
||
- Widget type: **Managed** (invisible by default, visible challenge when risk is detected)
|
||
- Endpoints protected: `/register`, `/login`, `/shaders/submit`
|
||
- Server-side verification: POST to `https://challenges.cloudflare.com/turnstile/v0/siteverify`
|
||
- Turnstile is free and does not require Cloudflare proxying — works with direct DNS
|
||
|
||
### MCP API Key Auth
|
||
- Keys generated as: `ff_key_` + 32 random bytes (base58 encoded)
|
||
- Only key prefix stored in plaintext for display; full key bcrypt-hashed in DB
|
||
- Bearer token in Authorization header on every MCP request
|
||
- Rate limiting tracked per key in Redis: `ratelimit:apikey:{key_id}:hour`
|
||
|
||
### GLSL Sandboxing
|
||
- Static lint with glslang before any render
|
||
- Renderer process runs in Docker with no network access
|
||
- Chromium flags: `--disable-web-security=false`, `--no-first-run`, memory limits
|
||
- Max execution time enforced at renderer level (8s) and Puppeteer level (15s)
|
||
- Banned GLSL extensions list: `GL_ARB_compute_shader`, `GL_NV_*` (GPU-specific, untestable)
|
||
|
||
---
|
||
|
||
## 7. AI Generation Pipeline
|
||
|
||
### Providers Supported
|
||
- **Anthropic (Claude)** — claude-sonnet-4-5 recommended for GLSL generation quality
|
||
- **OpenAI** — gpt-4o or gpt-4.1
|
||
- **Ollama (self-hosted)** — any model with sufficient code generation capability (codellama, deepseek-coder recommended)
|
||
|
||
### Provider Selection Logic
|
||
1. If user has BYOK configured for a provider → use their key, no credits consumed
|
||
2. If user has platform credits → use platform key, deduct credits based on tokens used
|
||
3. If user has no credits → show upgrade CTA, block generation
|
||
|
||
### System Prompt (GLSL Generation)
|
||
```
|
||
You are an expert GLSL shader developer specializing in WebGL fragment shaders.
|
||
Generate a single, complete GLSL fragment shader in Shadertoy-compatible format.
|
||
|
||
Rules:
|
||
- Entry point must be: void mainImage(out vec4 fragColor, in vec2 fragCoord)
|
||
- Available uniforms: iTime (float), iResolution (vec3), iMouse (vec4)
|
||
- Output must write to fragColor
|
||
- No external textures unless iChannel0 is specified in the request
|
||
- Shader must complete in under 16ms on mid-range GPU (avoid heavy loops)
|
||
- Do not use compute shaders or platform-specific extensions
|
||
|
||
Style guidance: {style_metadata}
|
||
|
||
User request: {prompt}
|
||
|
||
Respond with ONLY the GLSL code. No explanation, no markdown, no comments unless they
|
||
are GLSL // comments within the code itself.
|
||
```
|
||
|
||
### Generation → Submission Flow
|
||
```
|
||
User submits prompt
|
||
↓
|
||
Credits check / BYOK validation
|
||
↓
|
||
Enqueue ai_generate job (Redis/Celery)
|
||
↓ (async, user sees "generating..." state via SSE or polling)
|
||
Worker: build prompt → call LLM API → extract GLSL
|
||
↓
|
||
Worker: POST /api/v1/shaders/internal-submit (bot token)
|
||
↓
|
||
API: glslang lint
|
||
↓ (fail → retry with error in prompt, max 3 attempts)
|
||
API: POST /render to renderer service
|
||
↓
|
||
Renderer: Headless Chrome renders shader → returns thumbnail + preview video
|
||
↓
|
||
API: store shader record + media URLs + style embedding
|
||
↓
|
||
API: notify user via SSE (or frontend polls /api/v1/generate/status/{job_id})
|
||
↓
|
||
Frontend: shader appears in user's feed + editor
|
||
```
|
||
|
||
### Retry with Error Feedback
|
||
If the generated GLSL fails lint or render, the worker appends the error to the next prompt:
|
||
|
||
```
|
||
Previous attempt failed with this GLSL compiler error:
|
||
{glslang_error_output}
|
||
|
||
Please fix the shader. Here is the failing code:
|
||
{previous_glsl}
|
||
|
||
Corrected shader (GLSL only):
|
||
```
|
||
|
||
### BYOK (Bring Your Own Key)
|
||
Users can store their own API keys in Settings → AI Keys. Keys are encrypted at rest using AES-256-GCM with a server-side key derived from their user ID + a master secret. Decrypted only in the worker context when a job runs. Never returned to the client.
|
||
|
||
---
|
||
|
||
## 8. Recommendation Engine
|
||
|
||
The feed is the heart of the product. Every decision in the recommendation engine should optimize for "shaders this user will watch longer and vote on."
|
||
|
||
### Taste Vector
|
||
|
||
Each user has a 512-dimension `taste_vector` stored in pgvector. It is updated incrementally as they interact with the platform.
|
||
|
||
**Signal weights:**
|
||
| Signal | Weight |
|
||
|--------|--------|
|
||
| Upvote | +1.0 |
|
||
| Downvote | -0.8 |
|
||
| Dwell > 10s (no upvote) | +0.3 |
|
||
| Dwell > 30s (no upvote) | +0.6 |
|
||
| Replay | +0.5 |
|
||
| Explicit desire submission | +1.2 |
|
||
| Skip < 2s | -0.4 |
|
||
|
||
**Update formula (exponential moving average):**
|
||
```python
|
||
new_taste = alpha * shader_style_vector * weight + (1 - alpha) * current_taste_vector
|
||
# alpha = 0.15 (responsive but not volatile)
|
||
```
|
||
|
||
**Style Embedding (per shader):**
|
||
At submission time, the worker calls a lightweight classifier (fine-tuned embedding model or GPT-4o with structured output) to extract style metadata and produce a 512-dim embedding:
|
||
|
||
```json
|
||
{
|
||
"chaos_level": 0.85,
|
||
"color_temperature": "cool",
|
||
"motion_type": "fluid",
|
||
"geometry": "abstract_2d",
|
||
"complexity": "high",
|
||
"palette": ["#1a1a2e", "#0f3460", "#533483"]
|
||
}
|
||
```
|
||
|
||
This metadata is also human-readable and exposed in the UI on each shader card.
|
||
|
||
### Feed Ranking Query
|
||
|
||
```sql
|
||
SELECT s.id, s.title, s.preview_url, s.score,
|
||
1 - (s.style_vector <=> $1::vector) AS similarity
|
||
FROM shaders s
|
||
WHERE s.is_public = TRUE
|
||
AND s.render_status = 'ready'
|
||
AND s.id NOT IN (
|
||
SELECT shader_id FROM engagement_events
|
||
WHERE user_id = $2 AND created_at > NOW() - INTERVAL '30 days'
|
||
)
|
||
ORDER BY
|
||
(similarity * 0.6 + s.score * 0.3 + RANDOM() * 0.1) DESC
|
||
LIMIT 20
|
||
OFFSET $3;
|
||
-- $1 = user's taste_vector
|
||
-- $2 = user_id
|
||
-- $3 = cursor offset
|
||
```
|
||
|
||
The 10% random factor prevents the feed from becoming a filter bubble.
|
||
|
||
**Anonymous users:** Served from a pre-computed trending/new cache (rebuilt every 15 minutes by the worker cron).
|
||
|
||
### Hot Score (for trending)
|
||
|
||
```python
|
||
# Wilson score lower bound for vote confidence + time decay
|
||
def hot_score(upvotes, downvotes, age_hours):
|
||
n = upvotes + downvotes
|
||
if n == 0:
|
||
return 0
|
||
z = 1.96 # 95% confidence
|
||
p = upvotes / n
|
||
wilson = (p + z*z/(2*n) - z * sqrt(p*(1-p)/n + z*z/(4*n*n))) / (1 + z*z/n)
|
||
decay = 1 / (1 + age_hours / 48) # halves every 2 days
|
||
return wilson * decay
|
||
```
|
||
|
||
Score is recomputed on every vote and cached on the shader record.
|
||
|
||
---
|
||
|
||
## 9. Desire Queue & Bounty Board
|
||
|
||
### Desire Submission Flow
|
||
|
||
1. User types a desire in the FeedInput or BountyBoard page
|
||
2. Frontend POST to `/api/v1/desires`
|
||
3. API stores desire, embeds the text (vector), checks for similar existing desires
|
||
4. If similar desires exist (cosine similarity > 0.82): cluster them, update heat score
|
||
5. Enqueue `process_desire` worker job
|
||
6. Worker decides: auto-fulfill via AI immediately (if heat score low, queue not backed up) or list on bounty board for human/AI fulfillment
|
||
|
||
### Desire Status Lifecycle
|
||
```
|
||
open → in_progress → fulfilled
|
||
↓
|
||
expired (if no fulfillment within TTL)
|
||
```
|
||
|
||
### Bounty Board (Human + AI Fulfillment)
|
||
|
||
The bounty board at `/bounties` shows open desires sorted by heat score (demand). Any user or AI client can view and fulfill a bounty.
|
||
|
||
**Human fulfillment:**
|
||
- Creator opens a bounty, opens the editor, writes a shader
|
||
- On submit, selects "fulfills bounty #ID"
|
||
- After render passes, desire is marked fulfilled, tip released to creator (90%) and platform (10%)
|
||
|
||
**AI fulfillment (via MCP):**
|
||
```python
|
||
# Example autonomous agent loop
|
||
desires = mcp.call("get_desire_queue", { "min_heat": 3, "limit": 5 })
|
||
for desire in desires:
|
||
shader_code = llm.generate_glsl(desire["prompt_text"])
|
||
result = mcp.call("submit_shader", {
|
||
"glsl_code": shader_code,
|
||
"title": f"Generated: {desire['prompt_text'][:40]}",
|
||
"tags": ["ai-generated"],
|
||
"fulfills_desire_id": desire["id"]
|
||
})
|
||
```
|
||
|
||
**Tip Payout Timing:**
|
||
- Tips are held in Stripe escrow when the bounty is posted
|
||
- Released 48 hours after fulfillment (dispute window)
|
||
- If user marks as "not what I wanted" within 48h, tip is refunded and desire re-opened
|
||
|
||
### Heat Score
|
||
```python
|
||
def update_heat_score(desire_id):
|
||
# Count similar desires posted in last 7 days
|
||
similar_count = db.query("""
|
||
SELECT COUNT(*) FROM desires
|
||
WHERE status = 'open'
|
||
AND created_at > NOW() - INTERVAL '7 days'
|
||
AND 1 - (prompt_embedding <=> $1) > 0.82
|
||
""", desire.prompt_embedding)
|
||
|
||
# Heat = log(similar_count + 1) * recency_factor
|
||
heat = log(similar_count + 1) * (1 / (1 + age_hours / 24))
|
||
db.update_desire(desire_id, heat_score=heat)
|
||
```
|
||
|
||
---
|
||
|
||
## 10. Monetization & Payment Tiers
|
||
|
||
### Subscription Tiers
|
||
|
||
| Feature | Free | Pro ($12/mo) | Studio ($39/mo) |
|
||
|---------|------|--------------|-----------------|
|
||
| Browse feed | ✓ | ✓ | ✓ |
|
||
| Vote & comment | ✓ | ✓ | ✓ |
|
||
| Manual GLSL submissions | 5/month | Unlimited | Unlimited |
|
||
| AI generations (platform credits) | 0 | 50/month | 200/month |
|
||
| BYOK (own API key) | ✗ | ✓ | ✓ |
|
||
| MCP API access | Read-only | ✓ (Probation tier) | ✓ (Trusted tier) |
|
||
| Bounty posting | ✗ | ✓ | ✓ |
|
||
| Bounty fulfillment (creator payouts) | ✗ | ✓ | ✓ |
|
||
| Private shaders | ✗ | ✓ | ✓ |
|
||
| Commercial license downloads | Per-shader | 10/month included | 50/month included |
|
||
|
||
### AI Credit Packs (one-time, no subscription required)
|
||
- 20 generations — $4
|
||
- 100 generations — $16
|
||
- 500 generations — $60
|
||
|
||
### Bounty Platform Fee
|
||
- 10% of tip amount collected when a bounty is fulfilled
|
||
- Stripe Connect used for creator payouts (handles tax forms, international transfers)
|
||
- Minimum tip: $1.00
|
||
- Minimum payout: $10.00 (held until threshold reached)
|
||
|
||
### BYOK Mechanics
|
||
- Free tier: BYOK not available (prevents platform abuse with no revenue)
|
||
- Pro/Studio: User adds their Anthropic/OpenAI key in Settings
|
||
- Key stored encrypted, used only in worker context
|
||
- No platform credits consumed; user pays their provider directly
|
||
- Usage is still logged (token counts, model) for their own reference
|
||
|
||
### Stripe Integration
|
||
|
||
**Subscription:** Stripe Billing with monthly/annual billing, webhook events handled at `/api/v1/payments/webhook`
|
||
|
||
**One-time credits:** Stripe Payment Intents, credit balance updated on `payment_intent.succeeded` webhook
|
||
|
||
**Creator payouts:** Stripe Connect Express accounts. Creators onboard via Stripe Connect flow. Platform transfers `net_amount` to their Connect account on bounty settlement.
|
||
|
||
**Key webhook events to handle:**
|
||
- `customer.subscription.created` / `updated` / `deleted`
|
||
- `payment_intent.succeeded`
|
||
- `transfer.created` (payout confirmation)
|
||
- `account.updated` (Connect onboarding complete)
|
||
|
||
---
|
||
|
||
## 11. Creator Economy — Future Rollout
|
||
|
||
> **⚠️ Rollout Note — Read Before Building**
|
||
>
|
||
> Everything in this section is **intentionally deferred.** The initial development focus is on building a platform that is genuinely fun to browse, fast to use, and compelling enough that creators want to contribute without any financial incentive. A monetization system sitting on top of a platform nobody enjoys is worthless.
|
||
>
|
||
> **Do not build any of this during M0–M5.** The exception is the data instrumentation noted in section 11f — certain engagement tracking fields and schema stubs should be put in place from day one so the architecture does not need to be reconstructed when the time comes. Build the hooks, not the features.
|
||
>
|
||
> Revisit this section when the platform has reached meaningful organic usage — sustained daily active users, a growing shader library, and real community behavior. That is the signal that monetization will reinforce rather than replace organic growth.
|
||
|
||
---
|
||
|
||
### 11a. Design Philosophy
|
||
|
||
The Fractafrag creator economy is built on one principle: **reward shaders that people actually spend time with.** Not shaders that game an algorithm, not shaders that get one viral spike — shaders with sustained engagement over time.
|
||
|
||
Every signal needed to measure this is already being collected by the recommendation engine (dwell time, replays, votes, desire fulfillments). The creator economy is simply a financial layer on top of data that already exists. This is why the instrumentation can be stubbed now without building the full system.
|
||
|
||
All shaders remain **freely browsable and playable** regardless of monetization status. The rendered output is always public. What creators can optionally monetize is access to the underlying GLSL source code and commercial usage rights. The feed never discriminates on monetization status — a source-locked shader ranks identically to an open one.
|
||
|
||
---
|
||
|
||
### 11b. Shader Access Tiers
|
||
|
||
Creators choose one of three modes per shader, configurable at submit time or changed later from the shader settings page.
|
||
|
||
**Open (default)**
|
||
Full source visible, forkable, free. The community layer. Earns passive revenue share from the subscription pool based on engagement. This should remain the dominant mode — the majority of shaders should always be open.
|
||
|
||
**Source-locked**
|
||
The rendered output plays freely everywhere — feed, detail page, embeds. The GLSL source code tab is replaced with a paywall. Creator sets a one-time unlock price (minimum $2, maximum $200). A buyer pays once and gets permanent personal-use access to the source. They can read it, learn from it, use it in personal projects. They cannot redistribute or resell it.
|
||
|
||
**Commercial license**
|
||
An add-on layer that sits on top of either Open or Source-locked. Creator sets a separate commercial license price. Purchasing it grants rights to use the shader in live performances, music videos, brand installations, broadcast, or any revenue-generating context. Typically priced at 3–10x the source-unlock price by the creator.
|
||
|
||
| | Open | Source-locked | Commercial license |
|
||
|--|------|--------------|-------------------|
|
||
| Render plays in feed | ✓ | ✓ | ✓ |
|
||
| Source code visible | ✓ | Paywall | Paywall (unless also unlocked) |
|
||
| Forkable | ✓ | After unlock | After unlock |
|
||
| Personal use | ✓ | ✓ | ✓ |
|
||
| Commercial use | ✗ | ✗ | ✓ |
|
||
| Platform cut | 0% | 10% | 10% |
|
||
| Creator cut | 100% of rev share | 90% of sale | 90% of sale |
|
||
|
||
---
|
||
|
||
### 11c. Subscription Revenue Share Pool
|
||
|
||
A percentage of monthly subscription revenue is pooled and distributed to creators proportional to their engagement contribution that month.
|
||
|
||
**Pool size:** 20% of gross subscription revenue (Pro + Studio subscribers)
|
||
|
||
**Engagement score per shader per month:**
|
||
```
|
||
score =
|
||
(total_dwell_seconds x 1.0)
|
||
+ (replay_count x 30)
|
||
+ (upvote_count x 60)
|
||
+ (desire_fulfillments x 120)
|
||
+ (source_unlocks x 0) <- already compensated separately
|
||
```
|
||
|
||
**Distribution formula:**
|
||
```
|
||
creator_monthly_payout =
|
||
pool_total x (sum of creator shader scores / sum of all shader scores)
|
||
```
|
||
|
||
**Example at modest scale:**
|
||
- Monthly subscription revenue: $3,000
|
||
- Pool (20%): $600
|
||
- Creator shaders drove 4% of total platform engagement
|
||
- Creator earns: $24
|
||
|
||
Small at first. Meaningful at scale. Fully passive — creators earn it just by making things people watch.
|
||
|
||
Minimum payout threshold: $10 (held until reached, released via Stripe Connect). Paid on the 15th of the following month for the previous full calendar month.
|
||
|
||
---
|
||
|
||
### 11d. Verified Creator Program
|
||
|
||
Unlocks source-lock and commercial license features, plus elevated revenue share weighting.
|
||
|
||
**Thresholds to reach Verified status:**
|
||
- Minimum 10 approved shaders on the platform
|
||
- Minimum 30-day account age
|
||
- Minimum engagement score of 500 in any single month
|
||
- No active moderation flags
|
||
|
||
**Benefits:**
|
||
- Can set source-lock pricing on new and existing shaders
|
||
- Can set commercial license pricing
|
||
- 1.2x multiplier on revenue share pool weighting
|
||
- Featured placement in Explore page creator spotlight (rotates weekly)
|
||
- Verified badge on profile and shader cards
|
||
- Access to creator analytics dashboard
|
||
|
||
Verified status is reviewed quarterly. Accounts with moderation actions or sustained low engagement can be downgraded.
|
||
|
||
---
|
||
|
||
### 11e. Creator Analytics Dashboard
|
||
|
||
A dedicated `/dashboard` route for Verified creators showing:
|
||
|
||
- **This month:** Estimated earnings, source unlock revenue, commercial license revenue, total engagement score, rank percentile
|
||
- **Per shader:** Dwell time, replay count, vote ratio, unlock count, license count, estimated pool contribution
|
||
- **Payout history:** Past payouts, pending balance, Stripe Connect status
|
||
- **Desire fulfillments:** Bounties fulfilled, tips received, tip payout history
|
||
|
||
The dashboard should make the connection between creative output and financial return extremely legible. Creators should be able to see exactly which shaders are earning and why.
|
||
|
||
---
|
||
|
||
### 11f. Data Instrumentation to Build Now (M0–M5)
|
||
|
||
These schema additions and API stubs should be included in the initial build. They are cheap to add early and expensive to retrofit later. None of the actual payment or payout logic should be implemented — only the structure.
|
||
|
||
**Schema stubs to include in initial DB migration:**
|
||
|
||
```sql
|
||
-- Add to shaders table (all nullable/defaulted, not enforced until creator economy launches)
|
||
ALTER TABLE shaders ADD COLUMN access_tier TEXT DEFAULT 'open';
|
||
ALTER TABLE shaders ADD COLUMN source_unlock_price_cents INTEGER;
|
||
ALTER TABLE shaders ADD COLUMN commercial_license_price_cents INTEGER;
|
||
ALTER TABLE shaders ADD COLUMN verified_creator_shader BOOLEAN DEFAULT FALSE;
|
||
|
||
-- Source unlock purchase records (dormant until activated)
|
||
CREATE TABLE source_unlocks (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
shader_id UUID REFERENCES shaders(id),
|
||
buyer_id UUID REFERENCES users(id),
|
||
license_type TEXT NOT NULL, -- personal, commercial
|
||
amount_cents INTEGER NOT NULL,
|
||
platform_fee_cents INTEGER NOT NULL,
|
||
stripe_payment_intent_id TEXT,
|
||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||
);
|
||
|
||
-- Monthly engagement snapshots for revenue share calculation (dormant)
|
||
CREATE TABLE creator_engagement_snapshots (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
creator_id UUID REFERENCES users(id),
|
||
month DATE NOT NULL,
|
||
total_score FLOAT NOT NULL,
|
||
pool_share FLOAT,
|
||
payout_cents INTEGER,
|
||
paid_at TIMESTAMPTZ,
|
||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||
);
|
||
|
||
-- Verified creator status fields
|
||
ALTER TABLE users ADD COLUMN is_verified_creator BOOLEAN DEFAULT FALSE;
|
||
ALTER TABLE users ADD COLUMN verified_creator_at TIMESTAMPTZ;
|
||
ALTER TABLE users ADD COLUMN stripe_connect_account_id TEXT;
|
||
```
|
||
|
||
**Engagement tracking already in the spec — no changes needed:**
|
||
- `engagement_events` table (dwell, replay, share) ✓
|
||
- `votes` table (upvote/downvote) ✓
|
||
- `desires` table (fulfillment tracking) ✓
|
||
|
||
**API endpoints to stub as 501 Not Implemented:**
|
||
```
|
||
GET /api/v1/dashboard <- creator dashboard data
|
||
GET /api/v1/shaders/:id/unlock-status <- check if user has unlocked source
|
||
POST /api/v1/shaders/:id/unlock <- purchase source unlock
|
||
POST /api/v1/shaders/:id/commercial <- purchase commercial license
|
||
POST /api/v1/me/creator/apply <- apply for verified status
|
||
GET /api/v1/me/creator/earnings <- earnings history
|
||
```
|
||
|
||
Stubbing these at 501 means the frontend can reference them, MCP tool specs can document them, and the architecture is coherent — without any payment logic being live.
|
||
|
||
---
|
||
|
||
### 11g. Rollout Trigger Criteria
|
||
|
||
Begin active development of the creator economy when **all three** of the following are true:
|
||
|
||
1. The platform has sustained **500+ daily active users** for at least 30 consecutive days
|
||
2. The shader library has **1,000+ approved shaders** with genuine style diversity
|
||
3. At least **20 creators** have submitted 5 or more shaders organically without financial incentive
|
||
|
||
At that point the community exists, the content exists, and monetization will accelerate existing behavior rather than try to manufacture it.
|
||
|
||
---
|
||
|
||
## 12. MCP Server Tool Definitions
|
||
|
||
All tools communicate via JSON-RPC 2.0 over HTTP+SSE. The MCP server proxies calls to the internal API.
|
||
|
||
### `submit_shader`
|
||
Submit a new GLSL shader to the platform.
|
||
```json
|
||
{
|
||
"name": "submit_shader",
|
||
"description": "Submit a GLSL fragment shader to Fractafrag. The shader will be linted, rendered, and added to the feed if it passes validation.",
|
||
"inputSchema": {
|
||
"type": "object",
|
||
"required": ["glsl_code", "title"],
|
||
"properties": {
|
||
"glsl_code": { "type": "string", "description": "Complete GLSL fragment shader code, Shadertoy-compatible" },
|
||
"title": { "type": "string", "maxLength": 120 },
|
||
"description": { "type": "string", "maxLength": 1000 },
|
||
"tags": { "type": "array", "items": { "type": "string" }, "maxItems": 10 },
|
||
"shader_type": { "type": "string", "enum": ["2d", "3d", "audio-reactive"], "default": "2d" },
|
||
"style_metadata": {
|
||
"type": "object",
|
||
"properties": {
|
||
"chaos_level": { "type": "number", "minimum": 0, "maximum": 1 },
|
||
"color_temperature": { "type": "string", "enum": ["warm", "cool", "neutral", "monochrome"] },
|
||
"motion_type": { "type": "string" }
|
||
}
|
||
},
|
||
"fulfills_desire_id": { "type": "string", "format": "uuid", "description": "If this shader fulfills an open bounty, include the desire ID" }
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### `browse_shaders`
|
||
Search and browse shaders on the platform.
|
||
```json
|
||
{
|
||
"name": "browse_shaders",
|
||
"description": "Browse and search shaders on Fractafrag",
|
||
"inputSchema": {
|
||
"type": "object",
|
||
"properties": {
|
||
"query": { "type": "string" },
|
||
"tags": { "type": "array", "items": { "type": "string" } },
|
||
"sort": { "type": "string", "enum": ["trending", "new", "top", "random"], "default": "trending" },
|
||
"shader_type": { "type": "string", "enum": ["2d", "3d", "audio-reactive", "all"], "default": "all" },
|
||
"limit": { "type": "integer", "minimum": 1, "maximum": 50, "default": 10 },
|
||
"cursor": { "type": "string" }
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### `get_desire_queue`
|
||
Retrieve open desires/bounties from the queue.
|
||
```json
|
||
{
|
||
"name": "get_desire_queue",
|
||
"description": "Get open shader desires/bounties sorted by demand heat score",
|
||
"inputSchema": {
|
||
"type": "object",
|
||
"properties": {
|
||
"min_heat": { "type": "number", "minimum": 0, "default": 0, "description": "Minimum heat score (higher = more demand)" },
|
||
"has_tip": { "type": "boolean", "description": "Filter to only tipped bounties" },
|
||
"min_tip_cents": { "type": "integer" },
|
||
"limit": { "type": "integer", "minimum": 1, "maximum": 20, "default": 10 }
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### `fulfill_desire`
|
||
Mark a desire as fulfilled by an existing shader.
|
||
```json
|
||
{
|
||
"name": "fulfill_desire",
|
||
"description": "Mark an open desire as fulfilled by a shader you've submitted",
|
||
"inputSchema": {
|
||
"type": "object",
|
||
"required": ["desire_id", "shader_id"],
|
||
"properties": {
|
||
"desire_id": { "type": "string", "format": "uuid" },
|
||
"shader_id": { "type": "string", "format": "uuid" }
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### `get_trending`
|
||
Get currently trending shaders and platform stats for context.
|
||
```json
|
||
{
|
||
"name": "get_trending",
|
||
"description": "Get trending shaders and platform activity context",
|
||
"inputSchema": {
|
||
"type": "object",
|
||
"properties": {
|
||
"period": { "type": "string", "enum": ["1h", "24h", "7d"], "default": "24h" },
|
||
"limit": { "type": "integer", "default": 10 }
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### `get_feed_context`
|
||
Get personalized feed context for a user (for AI agents acting on behalf of a user).
|
||
```json
|
||
{
|
||
"name": "get_feed_context",
|
||
"description": "Get the current feed context: what's trending, what style gaps exist, top open desires",
|
||
"inputSchema": {
|
||
"type": "object",
|
||
"properties": {
|
||
"include_taste_profile": { "type": "boolean", "default": false }
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### `tag_shader`
|
||
Add or update tags on a shader (Trusted tier only).
|
||
```json
|
||
{
|
||
"name": "tag_shader",
|
||
"description": "Add tags to a shader. Trusted API clients only.",
|
||
"inputSchema": {
|
||
"type": "object",
|
||
"required": ["shader_id", "tags"],
|
||
"properties": {
|
||
"shader_id": { "type": "string", "format": "uuid" },
|
||
"tags": { "type": "array", "items": { "type": "string" }, "maxItems": 10 }
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 13. API Endpoint Reference
|
||
|
||
### Auth
|
||
| Method | Path | Description | Auth |
|
||
|--------|------|-------------|------|
|
||
| POST | `/api/v1/auth/register` | Register new user | Public + Turnstile |
|
||
| POST | `/api/v1/auth/login` | Login, returns JWT | Public + Turnstile |
|
||
| POST | `/api/v1/auth/refresh` | Refresh access token | Refresh cookie |
|
||
| POST | `/api/v1/auth/logout` | Revoke refresh token | JWT |
|
||
|
||
### Shaders
|
||
| Method | Path | Description | Auth |
|
||
|--------|------|-------------|------|
|
||
| GET | `/api/v1/shaders` | List/search shaders | Public |
|
||
| GET | `/api/v1/shaders/:id` | Get shader | Public |
|
||
| POST | `/api/v1/shaders` | Submit new shader | JWT + Turnstile |
|
||
| PUT | `/api/v1/shaders/:id` | Update shader | JWT (owner) |
|
||
| DELETE | `/api/v1/shaders/:id` | Delete shader | JWT (owner/admin) |
|
||
| POST | `/api/v1/shaders/:id/fork` | Fork shader | JWT |
|
||
|
||
### Feed
|
||
| Method | Path | Description | Auth |
|
||
|--------|------|-------------|------|
|
||
| GET | `/api/v1/feed` | Personalized feed | JWT (or anon) |
|
||
| GET | `/api/v1/feed/trending` | Trending feed | Public |
|
||
| GET | `/api/v1/feed/new` | New shaders feed | Public |
|
||
| POST | `/api/v1/feed/dwell` | Report dwell time signal | JWT / session |
|
||
|
||
### Votes & Engagement
|
||
| Method | Path | Description | Auth |
|
||
|--------|------|-------------|------|
|
||
| POST | `/api/v1/shaders/:id/vote` | Upvote or downvote | JWT |
|
||
| DELETE | `/api/v1/shaders/:id/vote` | Remove vote | JWT |
|
||
| POST | `/api/v1/shaders/:id/replay` | Report replay signal | JWT / session |
|
||
|
||
### AI Generation
|
||
| Method | Path | Description | Auth |
|
||
|--------|------|-------------|------|
|
||
| POST | `/api/v1/generate` | Start AI generation job | JWT |
|
||
| GET | `/api/v1/generate/status/:job_id` | Poll job status | JWT |
|
||
| GET | `/api/v1/generate/credits` | Check credit balance | JWT |
|
||
|
||
### Desires & Bounties
|
||
| Method | Path | Description | Auth |
|
||
|--------|------|-------------|------|
|
||
| GET | `/api/v1/desires` | List open desires | Public |
|
||
| POST | `/api/v1/desires` | Post a desire | JWT (Pro+) |
|
||
| GET | `/api/v1/desires/:id` | Get desire detail | Public |
|
||
| POST | `/api/v1/desires/:id/fulfill` | Fulfill a desire | JWT |
|
||
| POST | `/api/v1/desires/:id/tip` | Add tip to bounty | JWT (Pro+) |
|
||
|
||
### Users & Settings
|
||
| Method | Path | Description | Auth |
|
||
|--------|------|-------------|------|
|
||
| GET | `/api/v1/users/:username` | Public profile | Public |
|
||
| GET | `/api/v1/me` | Own profile + settings | JWT |
|
||
| PUT | `/api/v1/me` | Update settings | JWT |
|
||
| GET | `/api/v1/me/api-keys` | List API keys | JWT |
|
||
| POST | `/api/v1/me/api-keys` | Create API key | JWT (Pro+) |
|
||
| DELETE | `/api/v1/me/api-keys/:id` | Revoke API key | JWT |
|
||
| PUT | `/api/v1/me/ai-keys` | Update BYOK keys | JWT (Pro+) |
|
||
|
||
### Payments
|
||
| Method | Path | Description | Auth |
|
||
|--------|------|-------------|------|
|
||
| POST | `/api/v1/payments/checkout` | Create Stripe checkout session | JWT |
|
||
| POST | `/api/v1/payments/webhook` | Stripe webhook handler | Stripe sig |
|
||
| GET | `/api/v1/payments/portal` | Stripe customer portal URL | JWT |
|
||
| POST | `/api/v1/payments/credits` | Purchase credit pack | JWT |
|
||
| POST | `/api/v1/payments/connect/onboard` | Start creator Connect onboarding | JWT |
|
||
|
||
---
|
||
|
||
## 14. Parallel Build Tracks
|
||
|
||
GSD can run these tracks in parallel. Dependencies are noted — do not start a track that depends on an incomplete track.
|
||
|
||
### Track A: Infrastructure & Data Layer
|
||
**Can start immediately. No dependencies.**
|
||
|
||
- [ ] Docker Compose scaffold with all services defined
|
||
- [ ] PostgreSQL container with pgvector extension
|
||
- [ ] Redis container
|
||
- [ ] Database migrations (schema from Section 5)
|
||
- [ ] `.env.example` with all required vars documented
|
||
- [ ] Nginx reverse proxy config (HTTP + SSL termination)
|
||
- [ ] CI/CD: basic health check endpoints on all services
|
||
|
||
**Deliverable:** `docker compose up` runs all services, DB schema bootstraps, all containers healthy.
|
||
|
||
---
|
||
|
||
### Track B: Auth & User System
|
||
**Depends on:** Track A (DB running)
|
||
**Can run parallel with:** C, D, E
|
||
|
||
- [ ] User registration + bcrypt password hashing
|
||
- [ ] Cloudflare Turnstile integration (server-side verification)
|
||
- [ ] JWT issuance + refresh token flow (httpOnly cookie)
|
||
- [ ] Redis refresh token blocklist (for logout)
|
||
- [ ] `/api/v1/auth/*` endpoints
|
||
- [ ] `/api/v1/me` profile + settings endpoints
|
||
- [ ] API key generation + management (`/api/v1/me/api-keys`)
|
||
|
||
**Deliverable:** Users can register, login, refresh, logout. API keys can be created and listed.
|
||
|
||
---
|
||
|
||
### Track C: Shader Submission & Renderer
|
||
**Depends on:** Track A (DB + Docker network)
|
||
**Can run parallel with:** B, D, E
|
||
|
||
- [ ] GLSL validator (glslang integration)
|
||
- [ ] Renderer service (Puppeteer + headless Chromium)
|
||
- [ ] Render endpoint: POST `/render` → returns thumbnail + preview video
|
||
- [ ] `/api/v1/shaders` CRUD endpoints
|
||
- [ ] Shader submission pipeline: validate → render → store → embed
|
||
- [ ] Style metadata extractor (simple heuristic first, LLM-based later)
|
||
- [ ] Style embedding generation (pgvector insert)
|
||
- [ ] Media storage: rendered thumbnails + previews to Docker volume (or S3-compatible if configured)
|
||
|
||
**Deliverable:** Shaders can be submitted via API, pass lint, render in headless Chromium, and are stored with thumbnails.
|
||
|
||
---
|
||
|
||
### Track D: Frontend Shell
|
||
**Depends on:** Track B (auth endpoints live)
|
||
**Can run parallel with:** C, E
|
||
|
||
- [ ] React + Vite project scaffold
|
||
- [ ] Tailwind CSS setup
|
||
- [ ] Route structure (all pages as stubs)
|
||
- [ ] Auth flows: login, register, JWT storage, refresh interceptor
|
||
- [ ] ShaderCanvas component (WebGL, Shadertoy-compatible uniforms)
|
||
- [ ] GLSL Editor (Monaco + GLSL syntax highlighting)
|
||
- [ ] Basic shader submission UI (editor → submit → status polling)
|
||
- [ ] User settings page (account, subscription placeholder)
|
||
|
||
**Deliverable:** Users can log in, open the editor, write GLSL, see a live preview, and submit.
|
||
|
||
---
|
||
|
||
### Track E: MCP Server
|
||
**Depends on:** Track B (API key auth), Track C (shaders submittable)
|
||
**Can run parallel with:** D, F
|
||
|
||
- [ ] MCP server scaffold (Python `mcp` SDK, HTTP+SSE transport)
|
||
- [ ] API key verification middleware (calls internal API)
|
||
- [ ] `submit_shader` tool
|
||
- [ ] `browse_shaders` tool
|
||
- [ ] `get_trending` tool
|
||
- [ ] `get_desire_queue` tool (returns empty until Track G)
|
||
- [ ] `fulfill_desire` tool (stub until Track G)
|
||
- [ ] `get_feed_context` tool
|
||
- [ ] `tag_shader` tool (trust tier gated)
|
||
- [ ] Rate limiting per key (Redis)
|
||
- [ ] Trust tier enforcement
|
||
|
||
**Deliverable:** Claude Desktop (or any MCP client) can connect, browse shaders, and submit new ones.
|
||
|
||
---
|
||
|
||
### Track F: Feed & Recommendation Engine
|
||
**Depends on:** Track C (style vectors being generated), Track B (users exist)
|
||
**Can run parallel with:** E, G
|
||
|
||
- [ ] Hot score calculation on vote events
|
||
- [ ] Anonymous feed: trending + new (pre-computed cache)
|
||
- [ ] Authenticated feed: taste vector query (pgvector ANN)
|
||
- [ ] Dwell time reporting endpoint + taste vector update logic
|
||
- [ ] Vote endpoints (upvote/downvote) + score recalculation
|
||
- [ ] Feed API endpoint with cursor pagination
|
||
- [ ] Frontend: InfiniteFeed component with virtualization
|
||
- [ ] Frontend: FeedItem with dwell tracking (IntersectionObserver)
|
||
- [ ] Frontend: vote controls
|
||
|
||
**Deliverable:** Logged-in users see a personalized feed that improves with engagement.
|
||
|
||
---
|
||
|
||
### Track G: Desire Queue & Bounty Board
|
||
**Depends on:** Track B (auth), Track C (shader submission), Track F (feed exists)
|
||
**Can run parallel with:** H
|
||
|
||
- [ ] Desire submission endpoint + embedding
|
||
- [ ] Similarity clustering (pgvector cosine search)
|
||
- [ ] Heat score calculation
|
||
- [ ] Bounty board API endpoints
|
||
- [ ] Desire fulfillment flow (link shader → desire → mark fulfilled)
|
||
- [ ] Frontend: DesireInput component on feed page
|
||
- [ ] Frontend: Bounty board page + BountyCard components
|
||
- [ ] Worker: `process_desire` job
|
||
- [ ] Tip holding (basic — Stripe integration in Track H)
|
||
|
||
**Deliverable:** Users can post desires, see the bounty board, and fulfill bounties with manually-written shaders.
|
||
|
||
---
|
||
|
||
### Track H: Payments & Monetization
|
||
**Depends on:** Track B (users), Track G (bounties exist)
|
||
**Can run parallel with:** I
|
||
|
||
- [ ] Stripe account + webhook setup
|
||
- [ ] Subscription checkout flow (Free / Pro / Studio)
|
||
- [ ] Webhook handler for subscription events
|
||
- [ ] Subscription tier enforcement in API middleware
|
||
- [ ] Credit pack purchase flow
|
||
- [ ] Credit deduction on AI generation (Track I)
|
||
- [ ] Stripe Connect creator onboarding flow
|
||
- [ ] Bounty tip escrow on desire post
|
||
- [ ] Bounty tip release on fulfillment (48h settle)
|
||
- [ ] Payout initiation (Stripe Connect transfer)
|
||
- [ ] Frontend: upgrade modals, credit balance display, settings/billing page
|
||
|
||
**Deliverable:** Users can subscribe, purchase credits, and creators can receive payouts for fulfilled bounties.
|
||
|
||
---
|
||
|
||
### Track I: AI Generation
|
||
**Depends on:** Track C (renderer), Track B (auth + BYOK key storage), Track H (credit system)
|
||
**Final track — start last**
|
||
|
||
- [ ] BYOK key storage (encrypted at rest)
|
||
- [ ] AI provider clients: Anthropic, OpenAI, Ollama
|
||
- [ ] System prompt template + style injection
|
||
- [ ] Generation job (Celery/BullMQ worker)
|
||
- [ ] Retry logic with error feedback (up to 3 attempts)
|
||
- [ ] Job status endpoint (SSE or polling)
|
||
- [ ] Frontend: AiGeneratorPanel component
|
||
- [ ] Frontend: generation status UI (streaming if possible)
|
||
- [ ] Credit deduction logic (tokens used → credits consumed)
|
||
- [ ] Generation log for user history
|
||
|
||
**Deliverable:** Users can type a prompt, watch a shader generate, and see it appear in their feed.
|
||
|
||
---
|
||
|
||
## 15. Milestone Roadmap
|
||
|
||
### M0 — Foundation (Tracks A + B)
|
||
**Goal:** Infrastructure live, auth working, can create an account and log in.
|
||
**Estimated effort:** 1–2 weeks parallel dev
|
||
|
||
### M1 — Core Shader Loop (Tracks C + D)
|
||
**Goal:** Can write, submit, and view a GLSL shader. Live feed of manually submitted shaders.
|
||
**Estimated effort:** 2–3 weeks
|
||
**First dogfood milestone** — platform is usable for basic shader sharing.
|
||
|
||
### M2 — Intelligence Layer (Tracks E + F)
|
||
**Goal:** MCP server live (AI clients can connect), personalized feed working.
|
||
**Estimated effort:** 2 weeks
|
||
**Key milestone** — the platform starts feeling different from Shadertoy.
|
||
|
||
### M3 — Desire Economy (Track G)
|
||
**Goal:** Users can post desires, bounty board is live, human fulfillment works.
|
||
**Estimated effort:** 1–2 weeks
|
||
|
||
### M4 — Monetization (Track H)
|
||
**Goal:** Subscriptions, credits, and creator payouts all functional.
|
||
**Estimated effort:** 2 weeks
|
||
**Revenue-ready milestone.**
|
||
|
||
### M5 — AI Generation (Track I)
|
||
**Goal:** Platform-powered AI generation with credits + BYOK. Autonomous MCP agents can fulfill bounties.
|
||
**Estimated effort:** 2 weeks
|
||
**Full product milestone.** Everything is live.
|
||
|
||
---
|
||
|
||
## 16. Open Questions & Decisions for GSD
|
||
|
||
The following require a decision before or during implementation. Flagged here so GSD doesn't have to guess.
|
||
|
||
**Q1: Media storage for renders**
|
||
Render thumbnails and preview videos can be stored on the Docker volume (simple, works for single-server) or in an S3-compatible bucket (Minio self-hosted, or AWS S3). Volume is simpler to start; S3 is better if storage grows large.
|
||
→ *Recommendation: Start with Docker volume, add S3 config flag for later migration.*
|
||
|
||
**Q2: Style embedding model**
|
||
The style vector for each shader needs to be generated. Options:
|
||
- Heuristic classifier (fast, cheap, less accurate) — parse GLSL for math patterns, color functions, etc.
|
||
- LLM with structured output (accurate, slower, costs tokens)
|
||
- Fine-tuned embedding model (best, requires training data we don't have yet)
|
||
→ *Recommendation: Start with heuristic + LLM structured output for M1. Fine-tune later.*
|
||
|
||
**Q3: Renderer fidelity vs. speed**
|
||
Puppeteer/Chromium renders accurately but is heavy. A Node.js offscreen-canvas approach is lighter but may render differently than users' browsers.
|
||
→ *Recommendation: Puppeteer for M1 for accuracy. Profile performance at M2, optimize if needed.*
|
||
|
||
**Q4: SSE vs polling for generation status**
|
||
AI generation takes 5–30 seconds. User needs to see progress. Options:
|
||
- Server-Sent Events (real-time, slightly more complex)
|
||
- Polling every 2 seconds (simpler, slightly worse UX)
|
||
→ *Recommendation: Polling for M5 (simpler), SSE upgrade in a follow-up sprint.*
|
||
|
||
**Q5: Ollama endpoint config**
|
||
Self-hosted Ollama requires the user to provide both their API key (blank for Ollama) and the endpoint URL. The settings UI needs to handle this. Confirm UX approach before building BYOK settings page.
|
||
|
||
**Q6: Comment system scope at M1**
|
||
Comments on shaders are in the schema but not on the milestone list. Should comments be in scope for M1 or deferred to a post-M5 polish sprint?
|
||
→ *Recommendation: Defer to post-M5.*
|
||
|
||
**Q7: Moderation**
|
||
Shaders are auto-published for trusted submitters. Who reviews probation-tier submissions? Is there a mod queue UI needed, or is it email-based for now?
|
||
→ *Recommendation: Simple admin endpoint (`/api/v1/admin/queue`) returning pending shaders + approve/reject actions. No admin UI needed for M4, just API.*
|
||
|
||
---
|
||
|
||
*End of Fractafrag Platform Specification v1.0*
|
||
*Built for GSD handoff — March 2026*
|