Saltar al contenido
Tórculo1.0 · borrador de trabajo

FORK.md · generada al construir

El fork de ManimGX en Tórculo

Qué cambia el fork de ManimGX que estampa los pliegos y dónde engancha cada pieza del estándar.

Repositorio: ~/Developer/torculo/manimgx (remoto upstream = academa-labs/manimgx, base 9be4111). Ramas: seams (enganches genéricos, candidatos a contribuir al original) y saorin (lo artístico). seams es la rama del motor: el 14-10-2026 se le fusionaron seams-post, seams-materia y seams-plataforma (06bba13), y saorin se adelantó hasta ella. Los worktrees wt/post, wt/materia y wt/plataforma se quitaron; queda wt/materia-scratch/base (9be4111, con base_hashes.json y corpus_hashes.py) para comparar el corpus. Compilar y probar: uv sync (unos 4 min la primera vez), uv run manimgx render x.py, just test-rust, just test, just bench main (requiere que no haya regresión cuando lo nuevo está apagado). Lee AGENTS.md del repo antes de tocar nada.

Reglas del fork

  • La lógica nueva vive en ficheros nuevos (post.rs, material.rs, vt.rs, pick.rs, *.wgsl.tmpl, paquete Python manimgx/saorin/ o similar). En render.rs, export.rs, player.rs, vector.wgsl, feed.py solo unas pocas líneas de enganche. Upstream reescribe esos ficheros a menudo.
  • Las tomas del fork usan un rango propio de versión: hoy 0x8000_0002 (take::VERSION; registros de 424 B, POST = 15, MATERIAL = 16). Se siguen leyendo las de 0x8000_0001 de seams-materia (MATERIAL era el 15).
  • Claves de canalización de la composición (vector.rs): LINEAR = 32, BLENDS = 64, MATERIALS = 128, KEYS = 256.
  • Todo lo nuevo apagado por defecto no puede cambiar ni un píxel del corpus de referencia.
  • Determinismo: el tiempo que ven los shaders es el de la escena (o el índice de fotograma), nunca el reloj.
  • Commits en español, sin emojis, terminados en «Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com».
  • Canon de la casa: ~/Developer/torculo/canon/gen/canon.{wgsl,py,rs}. Papel #F5F0E8, tinta #2C1810.
  • Materias WGSL compartidas: ~/Developer/torculo/materias (convención de pasada: entrada rgba16float lineal premultiplicada, sampler, uniformes {resolucion, fotograma, tiempo, semilla, params[4]}, salida).

Hechos del código (leídos con fichero:línea)

  • GPU singleton: static GPU render.rs:1016, with_gpu :1020, wasm thread-local :1029. Requiere R32Float blending y Depth32FloatStencil8 (render.rs:1070-1086).
  • Composición 2D en 8 bits: COLOR = Rgba8Unorm render.rs:44; solo 3D iluminado en RADIANCE = Rgba16Float :46; la composición escribe texture_storage_2d<rgba8unorm> vector_compute.wgsl:5 vía store()/seen(). Pintura en sRGB premultiplicado (paint.wgsl:49).
  • Pintura por píxel en la composición: painted() vector.wgsl:1301 → paint() :957 → paint_of() :1324; capas con over() :1094; modelo de dos regiones lay*()/split_by() :1149-1233.
  • Ancho de trazo constante por objeto: half = params.z vector.wgsl:803; body_slot/joint_slot/cap_slot :669/:727/:754; raster de trazo a atlas R32Float (vector.rs:722, :1207; fs_stroke vector.wgsl:923).
  • Guiones en espacio de parámetro, no de longitud de arco (vector.wgsl:241-261).
  • Registro de 336 B por objeto y fotograma: render.rs:113-135, take.rs:50, feed.py:79-109, Instance render.rs:91, vector::Object vector.rs:21 (432 B), accesor en vector_buffers.wgsl:31-43. Claves de forma: take.rs:49 KEYS=7, slots() :53; feed.py KEYS (:111). Desalojo KEEP=120, SWEEP=240, BUDGET=768 MB (feed.py:53-56).
  • Fotograma final: encode_frame() render.rs:2061 (devuelve en :2506) a Targets.frame.color; lo leen export.rs:107 (convert), render.rs:1783 (copy_pixels) y player.rs:268 (Screen::composite).
  • Codificación por daño: damage() export.rs:18, whole export.rs:83, NV12 BT.601 limitado nv12.wgsl:3, x264 shim.c:39, MP4 mp4.rs:399; fotogramas retenidos con repeat (film.py:482 _send); el reproductor cachea el fotograma (project.rs:434).
  • Peticiones reproductor→anfitrión: asks() web.rs:141; browser/src/engine.ts.
  • LaTeX mal formado pasa: mitex_math typeset.rs:752; avisos de Typst descartados typeset.rs:568-575; _balanced existe en text.py:1180.
  • Voces: Voice = Callable[[str], Speech] sound.py:404; Scene.speech cae en Fal() de pago sin avisar (scene.py:1683). Word en manimgx.audio.sound. Estimación por letras sound.py:417-450; caché :513-549.
  • Pixels de frames= en RGBA premultiplicado (153,80,28,153 para #FF862F a 0,6).

Plan por punto (resumen; los detalles están en el informe de investigación)

  1. Postproceso: post.rs (modelo bloom.rs), llamado al final de encode_frame; config.post = [Pass(...)]; mensaje de toma POST; en el reproductor sobre el lienzo cacheado (picture() player.rs ~946). Composición lineal opcional en Rgba16Float (config.compositing="linear"), OETF sRGB a nv12.wgsl y al COMPOSITE del reproductor. Daño: pasadas locales dilatan macrobloques; pasadas que esparcen → whole; temporales → expandir retenidos (push_frames export.rs:45 y _send film.py:482).
  2. Materiales: registro + material:u32, blend:u32, user_id:u64, user:[f32;16] (336→416 B); register_material(nombre, wgsl) → mensaje MATERIAL; switch generado inyectado en la composición; clave de caché de pipelines (render.rs:1000); texturas de material en texture_2d_array. Mezcla multiplicar: blend(mode, p, below) en lay, lay_line, split_by; rasters vía lay_raster :1432.
  3. Trazo variable: clave width_rows (KEYS=8), half(u) en stroke_slot, body() :461, joint_record :493, cap_record/end_cap :520/:533; huella con el ancho máximo (render.rs:836); longitud de arco acumulada en add_path (render.rs:2625); textura de pincel en fs_stroke; canal extra de atlas Rg32Float.
  4. Hervor: N poses subidas una vez, cambia key1 (sin fork); versión GPU desplazando puntos en el aplanado (curve()/at() vector.wgsl:187/:200) con hash(objeto, curva, floor(t·fps)).
  5. Biblioteca: crate-type rlib+cdylib, python fuera de default, fachada manimgx-render con Gpu::from_device, Player, Replay, typeset(); reproductor npm de tomas sin Pyodide (<manimgx-film src="x.take">, Player.feed web.rs:69 → project.rs:325); manimgx render --take.
  6. Interactividad: textura de selección R32Uint en composite (vector_compute.wgsl:60) con user_id; eventos en player.rs 695-728 vía asks(); await self.espera(Clic(pieza)).
  7. VideoToolbox: features export-x264 / export-vt; vt.rs con objc2; p010.wgsl BT.709; HEVC Main10, ProRes 422 HQ y 4444 con alfa (desmultiplicar); AVAssetWriter; IOSurface para el laboratorio de película.
  8. Móvil: reproductor de tomas iOS (xcframework, uniffi, CAMetalLayer); Android con camino alternativo de cobertura por compute si falta la mezcla de float32.
  9. Voz: sidecar(dir) que lee WAV + .tiempos.json de .mesa ({beats:{id:{dur,pause,palabras:[{w,t,d}]}}, wavs:{id:path}}); la voz por defecto da error en vez de llamar a Fal.
  10. Errores estrictos: _balanced, \left/\right, ^/_ colgantes, zws como argumento; avisos de Typst devueltos; --strict / MANIMGX_STRICT=1; inspect marca glifos vacíos.

La emulsión: pasadas de clase «grafo» (rama emulsion, 15-10-2026)

  • rust/engine/src/grafo.rs (nuevo): un grafo de pasadas de cómputo de un mismo shader, con texturas propias a fracciones del tamaño; la pasada de la cadena trae el WGSL en source, el grafo en entry (una pasada por línea: punto lee,lee escribe divisor p0 p1 p2 p3), los números en params (búfer de almacenamiento) y la gelatina como volumen. Enganches en post.rs (clase aceptada, Stage::Graph, su encode) y una línea en render.rs.
  • Python: manimgx/saorin/pelicula.py (m.Pelicula), que pide la ficha a torculo_emulsion (TORCULO/emulsion).
  • El mismo grafo lo ejecutan materias/src/grafo.rs (materia revela) y emulsion/web/grafo.js (WebGPU).