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