# Pendiente

Lo que sabemos que falta o falla, con el diagnóstico ya hecho para no volver a
investigarlo desde cero. Ordenado por lo que más duele.

---

## 1. Las capas no son de verdad independientes

**Lo que pasa.** Se pueden poner dos capas con visuales MilkDrop, pero acaban
siendo **la misma imagen**. Y no hay forma de decir "en esta zona el Mezclador,
en esta otra Butter", configurando cada una por su cuenta.

Son **tres cosas distintas** mezcladas en el mismo síntoma:

### 1a. Dos capas Butter sin preset elegido salen idénticas — ✅ HECHO

`web/stage/layers.js`, en `crearButter()`, hacía:

```js
let i = Math.max(0, nombres.indexOf(f.preset));
```

Con `preset: ""` (lo que quedaba si la lista de presets aún no había llegado
al panel al crear la capa), `indexOf("")` devuelve `-1` y el `Math.max(0, -1)`
lo dejaba en **0**: todas las capas así arrancaban en el mismo preset.

**Cómo quedó:** sin preset elegido, cada instancia coge uno **al azar** y lo
devuelve en `presetElegido`. `sync()` avisa por `deps.onPreset(id, preset)`,
que `stage.js` (`anotarPresetDeCapa`) escribe en el estado con `set_mapping`,
así que el panel enseña cuál le tocó en vez de dejar el desplegable en blanco.
No recrea la instancia: la firma de una capa butter sigue siendo el tipo a
secas.

### 1b. Las capas puestas en "El motor de MOTORES" comparten imagen — ✅ HECHO

Esa fuente es, literalmente, el lienzo del motor activo. Dos capas así van a
enseñar lo mismo siempre — no es un fallo, es lo que significa. El problema era
que **era la fuente por defecto** de toda capa nueva, así que el primer
resultado que veía cualquiera era "dos capas iguales".

**Cómo quedó:** una capa nueva ya no nace en "motor". `panel.js` tiene
`fuenteDeCapaNueva()`, que la estrena en MilkDrop con preset al azar
(contenido distinto desde el primer clic) y solo cae en "motor" si Butterchurn
todavía no ha dado su lista de presets. Lo usan tanto `+ Capa` como
`Máscara → Capa`.

### 1c. Falta el Mezclador (y Hydra) como fuente de capa — *(el trabajo de verdad)*

Hoy las fuentes son: motor, butter, shader, clip, cámara, negro. **No está el
Mezclador**, así que "una zona con el Mezclador y otra con Butter" solo se
puede hacer vía "motor", y entonces manda el selector global.

Por qué no está: el Mezclador **es** Hydra (genera código Hydra y lo ejecuta), y
Hydra está montado como instancia única global (`makeGlobal: true`, con `s0`,
`s1`, `o0` en el espacio global). Para tener dos mezcladores independientes hay
que instanciar Hydra varias veces con `makeGlobal: false` y usar la API por
instancia (`h.synth.src(...)`), lo que obliga a reescribir `buildMixerCode` para
que no dependa de los nombres globales.

**Coste realista:** medio día, más el gasto de GPU de un Hydra por capa. Antes
de meterse, decidir si compensa frente a la alternativa barata: dejar el
Mezclador solo como motor a pantalla completa y que las capas tiren de
MilkDrop/shader/clip, que es lo que ya funciona.

### 1d. Los dos paneles no están relacionados

MOTORES y MAPPING van cada uno por su lado: el preset que eliges en el motor
Butter y el de una capa MilkDrop son listas distintas, no comparten favoritos,
y lo que tocas en uno no se refleja en el otro.

**Hacia dónde:** una sola biblioteca de presets y una sola lista de favoritos,
usada por el motor Butter, por las capas del mapping y por projectM. Esto se
solapa con el punto 2: es el mismo trabajo de unificación.

---

## 2. projectM en el mapper — ✅ HECHO (camino A)

**Lo que pasaba.** Al elegir projectM no había forma de mapearlo ni de usarlo
en una capa, y ahí está **la biblioteca de verdad**: 9795 presets `.milk` en 11
categorías y 189 visuales. Butterchurn solo trae ~100 propios.

**Por qué no se podía.** projectM es un proceso nativo que pinta en su propia
ventana X11/Wayland. El compositor de mapping es WebGL dentro del navegador y
solo puede usar como textura lo que vive en la página.

**Cómo quedó.** Se descartó capturar la ventana (frágil en Wayland) y compilar
projectM a WebAssembly (un proyecto en sí). Se hizo el **camino A**: traducir
los `.milk` a presets de Butterchurn **en el propio navegador**, al elegirlos.

- `web/lib/milkdrop-preset-converter.min.js` (npm `milkdrop-preset-converter`,
  añadido a `web/package.json` y a `install.sh`).
- `layers.js` → `cargarMilk(ruta)`: pide el `.milk` al backend (ya los servía),
  lo convierte y se lo pasa a `viz.loadPreset()`. Cachea **la promesa**, no el
  resultado, para que dos capas pidiendo el mismo preset lo conviertan una vez.
- La capa MilkDrop tiene ahora `biblioteca: "butter" | "projectm"`. La firma de
  la capa **sigue siendo `'butter'`**, así que cambiar de biblioteca no recrea
  la instancia ni gasta un contexto WebGL más.
- El panel tiene selector de biblioteca y, con projectM, los tres niveles
  (categoría / visual / variante) más un botón de "uno al azar".

**Medido, no supuesto:**

- **100/100** presets de una muestra repartida por toda la biblioteca se
  convierten sin excepción, incluidos **84/84** de los que llevan shaders
  `warp_1`/`comp_1` (que eran justo los dudosos).
- Conversión de un `.milk` real en el navegador: **7 ms**.
- Una capa del mapper con `Dancer/Glowsticks/285.milk` renderiza sin un solo
  aviso del escenario.

> Cuidado al medir esto: si el `.milk` no se descarga, Butterchurn **sigue
> pintando su preset de reserva**. Una prueba que solo mire "¿hay píxeles
> encendidos?" da OK igual. Por eso la comprobación exige además que el
> escenario no haya soltado ningún aviso. La primera versión de la prueba dio
> un falso OK exactamente por esto.

**Lo que queda de este punto:** la conversión no es perfecta al 100 % en
fidelidad — algún shader complejo puede verse distinto del original en
projectM. Se convierte y se ve, pero si un preset no convence, la salida es
probar otra variante. Y falta unificar los favoritos entre el motor projectM
y las capas (parte del punto 1d).

---

## 3. Los clips salían del revés en el mapping — ✅ ARREGLADO

**Lo que pasaba.** Al poner un vídeo en una capa del mapping, salía volteado
verticalmente (boca abajo).

**Por qué.** `web/stage/mapper.js`, en `initGL()`, hacía
`gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true)`. Pero el modelo del mapper va
con el origen **arriba-izquierda** (`vUV.y = 0` es el borde de arriba de la
superficie) y `texImage2D` ya sube la primera fila de la imagen en `t = 0`.
Con el flip puesto, `t = 0` pasaba a ser la ÚLTIMA fila: todo del revés.
Se notaba con los clips porque una visual de MilkDrop es casi simétrica y no
canta.

**Comprobado**, no deducido: con una fuente mitad roja arriba / mitad azul
abajo, renderizando el `mapper.js` real en Chromium headless y leyendo el
píxel de arriba con `readPixels`, antes salía AZUL y ahora sale ROJO.

De paso se midió el **Mezclador** (Hydra, `s1.init({src})`) con la misma
prueba: ese sale bien, no hay que tocarlo. Si alguna vez se ve del revés ahí,
no es este fallo.

> Al medir esto, ojo con `readPixels` sobre un lienzo sin
> `preserveDrawingBuffer`: devuelve negro y es facilísimo leerlo como "está
> volteado". Hay que reservar el contexto con `preserveDrawingBuffer: true`
> ANTES de que la librería llame a `getContext`.

---

## 4. Menor: no hay fundido al cambiar de motor

Pasar de Mezcla a Butter es un corte seco. Dentro de una capa MilkDrop sí hay
transición suave (deslizador de 0–8 s), pero cruzar **dos motores** exige
renderizar los dos a la vez durante el cruce y fundirlos en el compositor.

Se puede hacer en el mapper (dibujar la capa dos veces, con la fuente vieja
bajando de alfa y la nueva subiendo), pero cuesta tener los dos motores vivos a
la vez unos segundos. Decidir si merece la pena.
