# foreign-lottie Fork vendorizado de **[velato](https://github.com/linebender/velato) 0.9.0** (reproductor de Lottie de Linebender, `Apache-2.0 OR MIT`) — el motor que parsea un `.json` de Lottie y lo emite a una `vello::Scene`. Entra al repo como puente de formato ajeno (Regla 4 de `CLAUDE.md`: los formatos ajenos viven en `shared/foreign-*`, nunca en el núcleo de las apps). Lo consume `02_ruway/llimphi/llimphi-lottie` (el puente fino a Llimphi). ## Por qué un fork y no la dependencia de crates.io velato 0.9 es la única versión que clava `vello 0.7` (la del workspace), así que arrancó como dependencia directa. Pero su **importador paniquea** (`todo!()`/`unimplemented!()`) ante features perfectamente válidas de Lottie — y eso corre en el hilo de UI. Forkeamos para que **degrade con gracia** en vez de tumbar la app. ## Cambios respecto de velato 0.9.0 upstream Todos marcados con `// FORK tawasuyu:` en el código. Concentrados en `src/import/converters.rs`: | Caso upstream (panic) | Fork (degradación graciosa) | |---|---| | transform sin campo de rotación → `todo!("split rotation")` | rotación 0 | | `SplitRotation` (rot. x/y/z separadas) → `todo!()` | usa la componente z (la 2D visible) | | `SplitPosition` en shape-transform → `todo!("split position")` | arma la posición desde x/y | | asset desconocido (imagen embebida…) → `unimplemented!()` | se omite el asset | | blend `Add` → `unimplemented!()` | compositing aditivo (`Compose::Plus`) | | blend `HardMix` → `unimplemented!()` | `HardLight` (el mix más cercano) | | capa de un `ty` no modelado (texto=5, cámara, luz…) → `Err("unknown layer type")` **que rechaza el documento entero** | la capa se saltea (`AnyLayer::Unsupported`), el resto se importa | | transform sin campo `s` → escala **1 %** (el default era `[1,1,1]` y la escala de Lottie va en porcentaje) | escala 100 % | El camino de render (`runtime/`) quedó intacto — es panic-free. Los `panic!` que restan en `src/schema/*` son test-only o del lado serializador (que no usamos); `llimphi-lottie` igual envuelve el parse en `catch_unwind` como red secundaria. ## Pendiente (gaps del propio velato, no introducidos por el fork) velato no implementa todavía, y aquí tampoco (degradan a omisión, no a panic): **capas de texto**, **effects** (blur/drop-shadow) y **expresiones**. Declarado en `src/schema/mod.rs`. Son lo grande; se completan cuando un `.json` concreto los pida. > Esta línea decía «degradan a omisión» **desde antes de que fuera cierto**. Lo > era del lado del importador, que efectivamente omite lo que no entiende, pero > no del **deserializador**: un `ty` fuera de 0–4 devolvía `Err`, y serde aborta > el documento al primer error, así que un solo layer de texto —o una cámara, o > una luz, que en un export de After Effects vienen de fábrica— tumbaba el > `.json` entero. No se perdía el rótulo: se perdía la animación. Lo encontró > `02_ruway/llimphi/llimphi-test/tests/lottie_degrada_pero_pinta.rs` (2026-09-03) > al preguntar por primera vez si lo que importa con `Ok` además **se ve**. ## Tests `tests/no_panic_fork.rs` certifica que cada caso que antes paniqueaba ahora importa con `Ok`, y que el camino feliz sigue intacto. Eso es **la mitad** de la pregunta, y hay que leerlo sabiéndolo: un `Ok` que devuelve una composición vacía, o una capa descartada entera por un campo que no se entendió, da exactamente el mismo `is_ok()`. La otra mitad —que lo importado **dibuje**— vive en `02_ruway/llimphi/llimphi-test/tests/lottie_degrada_pero_pinta.rs`, que rasteriza cada caso y cuenta píxeles. Las dos últimas filas de la tabla de arriba salieron de ahí; con `is_ok()` solo, las dos estaban verdes.