# Shader DAG Lab — Arquitectura y hoja de ruta

Este documento resume el diseño acumulado tras iterar en artifacts desde
"composición funcional sobre listas" hasta "DAG de shaders WebGPU con fan-in".
Sirve como contexto para retomar el proyecto en Claude Code sin perder las
decisiones de diseño.

## El problema que resuelve

Un entorno para componer fragment shaders WGSL visualmente: el usuario arrastra
"nodos" (primitivas shader) desde una paleta a un pipeline, configura
parámetros con sliders / XY pads / color pickers, y el sistema compila el DAG
resultante a un único fragment shader ejecutado en WebGPU.

Las dos vistas — grafo y código — son proyecciones del mismo modelo interno.
El usuario casual arrastra cajas; el usuario avanzado lee el WGSL generado.

## Arquitectura en una frase

```
Pipeline state (árbol JSON) 
    ↓ compileDagToWGSL()
WGSL source
    ↓ device.createShaderModule()
GPU pipeline
    ↓ render loop, escribe uniforms cada frame
Canvas
```

Separación crítica: la **topología** del DAG (qué nodos, en qué orden, con qué
aristas) dispara recompilación del shader. Los **valores de parámetros** NO
disparan recompilación — solo se escriben al uniform buffer cada frame. Esto
hace que mover un slider sea instantáneo mientras que añadir/quitar nodos
paga el coste de compilar un nuevo pipeline.

## Modelo de datos

Un `step` del pipeline es:

```ts
type Step = {
  id: string;           // UUID estable (sobrevive reorders)
  name: string;         // clave en el catálogo NODES
  params: {             // valores de parámetros editables
    [key: string]: number
  };
  meta?: {              // metadatos que afectan compilación
    sourceId?: string;  // para blends: id del otro nodo fuente
  };
};
```

El `topologyKey` que dispara recompilación se computa como:
```
pipeline.map(s => `${s.name}:${s.meta?.sourceId ?? ''}`).join('|')
```

## Catálogo de nodos

Cada entrada en `NODES` declara:

```ts
{
  kind: 'gen' | 'op' | 'blend' | 'warp' | 'sdf' | 'filter' | 'modulator',
  label: string,
  desc: string,
  params: [{ k: string, d: number }],  // hasta 4 slots del vec4<f32>
  controls: Control[],                  // descriptores de UI
  body: (idx: number) => string,        // emite el cuerpo WGSL
}
```

### Tipos de control UI actualmente soportados
- `slider`: rango numérico con thumb
- `xy`: pad 2D que controla dos params contiguos
- `color`: picker RGB que controla tres params contiguos
- `select`: dropdown con opciones discretas (valor = índice)
- `source`: selector del nodo fuente para blends (escribe a `meta.sourceId`)

## Compilación del DAG a WGSL

`compileDagToWGSL(pipeline)` emite un shader con esta estructura:

```wgsl
struct Uniforms {
  time: f32,
  _pad: f32,
  resolution: vec2<f32>,
  params: array<vec4<f32>, 16>,
};

@group(0) @binding(0) var<uniform> u: Uniforms;

@vertex fn vs(...) { /* fullscreen triangle */ }

// Una función por nodo, nombrada node_<idx>
fn node_0(c: vec4<f32>, uv: vec2<f32>) -> vec4<f32> { ... }
fn node_1(a: vec4<f32>, b: vec4<f32>, uv: vec2<f32>) -> vec4<f32> { ... } // blend

@fragment fn fs(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> {
  let uv = pos.xy / u.resolution;
  let out_0 = node_0(vec4<f32>(0.0), uv);
  let out_1 = node_1(out_0, out_0, uv);  // blend con source=out_0
  return out_1;
}
```

Los outputs intermedios `out_<i>` se preservan como variables locales para
que los blends puedan referenciar nodos anteriores arbitrarios. Esto es lo
que hace que el DAG sea más que un pipeline lineal.

## Estado actual de tipos de nodo

**Implementados (lab 004):**
- `gen`: solid, gradient, plasma, checker, circle, stripes, noise (hash)
- `op`: invert, gamma, contrast, saturate, hueShift, tint, posterize, vignette, ripple, pulse
- `blend`: mix, multiply, screen, add, difference, darken, lighten, mask

**Pendientes de implementar (ver hoja de ruta abajo):**
- `warp`: distorsiones de UV (twirl, polar, kaleidoscope, pixelate, chromatic)
- `sdf`: campos de distancia con compositing (smooth_union, subtract, intersect)
- `modulator`: LFOs que producen escalares animados para alimentar parámetros
- Ruidos procedurales reales: perlin, simplex, worley, fbm
- Filtros de luminancia: threshold, levels, duotone, channel_swap
- Inputs externos: mouse como uniform adicional

## Hoja de ruta post-artifact

### Lab 005 — Warps + Ruidos + Filtros de luma + Mouse
Cambios que requiere:
- **Refactor de compilación**: generadores pasan a ser `fn sample_gen_<i>(uv) -> vec4<f32>`
  para que los warps puedan modificar uv antes del muestreo.
- La cadena main mantiene dos estados: `uv` (mutable por warps) y `c` (color).
- Nuevo kind `warp` con snippets que transforman `uv`.
- Nuevo uniform `mouse: vec2<f32>` (coords 0..1) actualizado desde pointer events.
- Perlin/FBM como snippets WGSL copiados de implementaciones conocidas
  (hash-based gradient noise).

### Lab 006 — Multi-pass (convoluciones + feedback)
Cambio arquitectónico mayor: cada nodo puede escribir a una textura offscreen,
el siguiente samplea esa textura. Requiere:
- Render targets intermedios (pool de texturas)
- Múltiples bind groups
- Double buffer para feedback temporal (frame N+1 lee frame N)
- Detección de qué nodos necesitan aislar su pass y cuáles pueden fusionarse

Desbloquea: blur gaussiano, sobel, edge detection, bloom, reaction-diffusion,
trails, motion blur.

### Lab 007 — SDFs tipados
Introduce heterogeneidad de tipos en las aristas del DAG:
- Aristas de tipo `field` (`f32`) vs `color` (`vec4<f32>`)
- Validación de tipos en compilación: un operador de color no acepta un field
- Nodo terminal `render_sdf` que convierte field → color con shading opciones
  (planar, gradient, stroke, inflate/outline)
- Operadores SDF: `smooth_union`, `subtract`, `intersect`, `round`, `onion`

Este es el salto conceptual a "DAG tipado" que formaliza lo que el lab 001
insinuaba (el fold cambiaba de tipo la arista).

### Lab 008 — Bidireccional código ↔ grafo
Hasta ahora solo va grafo → código. El inverso requiere:
- Parser acotado del WGSL que nosotros mismos emitimos (no WGSL general)
- Marcadores en comentarios `// @meta node=<name> id=<id>` para robustez
- Detección de diff estructural para mantener posiciones / parámetros al editar
- Editor de código integrado (CodeMirror) sincronizado con el pipeline

### Lab 009 — Nodos custom definidos por usuario
Modal donde el usuario escribe body WGSL + declara params, y se registra en
la paleta como si viniera del catálogo. Cierra la asimetría código→grafo sin
necesitar parser completo. Persiste en localStorage o export/import JSON.

## Decisiones de diseño que vale la pena recordar

1. **Los IDs de nodo son UUIDs, no índices posicionales**. Las referencias
   de `sourceId` sobreviven a reorderings. Los índices se re-derivan en
   compilación.

2. **El patrón "armed drag"** para el drag handle: el nodo es `draggable=false`
   por defecto y solo se arma a `true` cuando ocurre pointerdown sobre el
   header con el handle. Esto evita que los sliders internos activen drag
   accidentalmente.

3. **Uniform packing**: todos los parámetros de un nodo van en `u.params[idx]`
   (un `vec4<f32>`). Si un nodo necesita más de 4 floats, habría que
   reasignar slots o usar dos slots. No hay nodos hasta ahora que lo pidan.

4. **MAX_NODES = 16** es arbitrario, limitado solo por el tamaño del array
   de params en el uniform buffer. Subir es trivial: cambia la constante.

5. **Las arbitrary values de Tailwind no funcionan** en algunos entornos
   sin JIT. Grid templates, min-h con calc, etc. se escriben con
   `style={{...}}` inline. En el proyecto de Claude Code esto no debería
   ser problema si usas Tailwind 3+ con su compilador.

## Stack sugerido para Claude Code

- **Vite + React + TypeScript**: setup estándar, HMR inmediato
- **Tailwind 3+** con JIT: los arbitrary values funcionarán esta vez
- **Zustand o Jotai** para el pipeline state (se va a hacer más complejo)
- **Biome o ESLint + Prettier** para formato consistente
- Opcional pero recomendado en cuanto crezca:
  - **Vitest** para tests unitarios del compilador (`compileDagToWGSL`)
  - **React Testing Library** si tests de UI
  - **reactflow** si en lab 008 quieres visualizar el DAG como grafo editable

## Organización de archivos sugerida

```
src/
  nodes/
    index.ts              # export del catálogo completo
    generators.ts         # gen kind
    operators.ts          # op kind
    blends.ts             # blend kind
    warps.ts              # (lab 005)
    sdfs.ts               # (lab 007)
    types.ts              # tipos compartidos NodeDef, Control, etc.
  compiler/
    compileDagToWGSL.ts   # la función principal
    uniforms.ts           # packing y escritura del buffer
    validate.ts           # validación de tipos (lab 007+)
  webgpu/
    useWebGPU.ts          # el hook
    renderer.ts           # setup del device, context, pipeline
  ui/
    PipelineNode.tsx
    controls/
      Slider.tsx
      XYPad.tsx
      ColorPicker.tsx
      Select.tsx
      SourceSelector.tsx
    Palette.tsx
    Canvas.tsx
    WGSLView.tsx
  store/
    pipeline.ts           # Zustand store
  App.tsx
  main.tsx
```

## Primeros pasos en Claude Code

1. `npm create vite@latest shader-dag -- --template react-ts`
2. Copiar `shader-dag-blends.jsx` como base monolítica, renombrar a `.tsx`
3. Arreglar los tipos TypeScript (muchas funciones del artifact no están tipadas)
4. Romper el monolito según la estructura de arriba
5. Implementar lab 005: refactor de compilación para habilitar warps

Nota: el artifact de base (`shader-dag-blends.jsx`) funciona pero tiene el
tipado implícito de JSX. Convertir a TS te va a revelar varios tipos que
merece la pena modelar explícitamente (especialmente `Control`, que ahora es
un union discriminado informal).
