fractafrag/FRACTAFRAG FULL SPEC & SUMMARY.md
2026-03-24 20:36:53 -05:00

62 KiB
Raw Blame History

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
  2. Tech Stack Decision
  3. Docker Compose Architecture
  4. Service Specs
    • 4a. Frontend
    • 4b. API
    • 4c. MCP Server
    • 4d. Renderer
    • 4e. Worker
  5. Database Schema
  6. Authentication & Security
  7. AI Generation Pipeline
  8. Recommendation Engine
  9. Desire Queue & Bounty Board
  10. Monetization & Payment Tiers
  11. Creator Economy — Future Rollout
  12. MCP Server Tool Definitions
  13. API Endpoint Reference
  14. Parallel Build Tracks
  15. Milestone Roadmap
  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
fragfragColor, 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)

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:

// 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:

{
  "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.

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

-- 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

-- 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

{
  "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):

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:

{
  "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

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).

# 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):

# 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

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 M0M5. 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 310x 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 (M0M5)

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:

-- 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.

{
  "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.

{
  "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.

{
  "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.

{
  "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 currently trending shaders and platform stats for context.

{
  "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).

{
  "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).

{
  "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: 12 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: 23 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: 12 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 530 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