Capas independientes, projectM en el mapping y catalogo de la galeria

Mapping
- Los clips salian del reves: mapper.js activaba UNPACK_FLIP_Y_WEBGL, pero el
  modelo va con origen arriba-izquierda y texImage2D ya sube la primera fila
  en t=0, asi que el flip la mandaba al final. Comprobado renderizando el
  mapper real en Chromium headless con una fuente mitad roja / mitad azul.
- projectM se puede usar YA en una capa: los .milk de data/presets-projectm se
  traducen a Butterchurn en el navegador al elegirlos (milkdrop-preset-
  converter). La capa MilkDrop gana 'biblioteca' (butter | projectm) sin
  cambiar su firma, para no recrear el contexto WebGL al cambiar de una a otra.
  Medido: 100/100 presets de una muestra convierten, 84/84 de los que llevan
  shaders warp/comp; ~7 ms por preset.
- Cada capa tiene su pestana arriba y su propia vista, y '+ CAPA' crea una y
  entra en ella: con varias capas, ir y volver a MAPPING no era viable.
- Dos capas MilkDrop sin preset ya no salen identicas (cogian el indice 0):
  cada instancia elige uno al azar y lo escribe en el estado.
- Una capa nueva ya no nace en la fuente "motor", que es el lienzo del motor
  activo y hacia que dos capas ensenaran lo mismo.

Galeria de visuales
- scripts/catalogar-visuales.py: ficha de cada clip (pelicula, personajes,
  duracion) y, sobre todo, si es una silueta de verdad y cuanta figura tiene.
  Distingue silueta de corte crudo por el negro puro del fondo: 0,63-0,90
  frente a 0,04-0,06, sin zona gris.
- El panel lista los clips agrupados por pelicula, con nombre legible y aviso
  de los que casi no tienen figura, en vez del nombre del archivo.
- Soporte de siluetas con canal alfa (.webm VP9): transparencia de verdad, sin
  recorte por luminancia. El shader del mapper ya la respeta.

Documentacion
- docs/visuales.md nuevo; pendiente.md al dia con lo hecho y lo que queda.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
hacklab 2026-08-02 14:56:01 +02:00
parent 5d5223db9f
commit f017246f0e
30 changed files with 3771 additions and 292 deletions

162
docs/pendiente.md Normal file
View file

@ -0,0 +1,162 @@
# 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 08 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.