Loading
Qué hace un SDK con un payload para el que no fue construido: tres niveles de diagnóstico, un resultado reparado en vez de un rechazo, la tabla estable de códigos y la tolerancia hacia adelante.
Un juego se publica en marzo. En septiembre su pantalla de tienda recibe contenido nuevo, pero la copia del SDK que hay dentro de ese juego sigue siendo la de marzo. Esta página es lo que pasa cuando los dos no acaban de encajar, o cuando la descarga sencillamente llegó rota: el SDK dice lo que se ha encontrado, repara lo que puede y renderiza el resto. Un envelope corrupto no puede tumbar nunca el juego.
Esa es la promesa entera, y las reglas de abajo son cómo se cumple. El contenido se entrega a juegos vivos y se hot-updatea —se actualiza en un juego ya publicado— con independencia del SDK, así que antes o después a un SDK le pondrán delante un payload para el que no fue construido: más nuevo, más viejo, truncado por una descarga mala o sencillamente mal.
Enviar UI a un juego que ya está en manos de los jugadores es para lo que
existe este formato, y hay tres páginas que se lo reparten.
Versionado dice si un SDK y un payload pueden
siquiera encontrarse. Esta página dice qué hace el SDK con el payload una
vez se encuentran: qué repara, qué rechaza y qué le cuenta al juego.
El canal del host es la llamada que entrega
el payload nuevo, reload, y el callback por el que vuelven los diagnósticos.
La política de abajo es una sola política, implementada una vez en
@zabloo/format (readEnvelope) y portada literalmente por cada SDK. Los dos
targets degradan igual ante los mismos bytes, y un consumidor que ha cargado un
envelope puede fiarse de su forma en vez de defenderse en cada nodo y en cada
frame.
Tres niveles
No todo lo que está mal en un payload está igual de mal. Un token de color que falta es una errata que la pantalla sobrevive; un fichero truncado no es una pantalla en absoluto. Clasificar los hallazgos en tres niveles es lo que permite que un juego siga jugándose a través del primero y se entere del segundo, y todos los SDK los clasifican idénticamente.
La raya entre ellos es una sola pregunta: ¿queda todavía un árbol que renderizar?
normativeNiveles de diagnóstico
| Nivel | Qué cubre |
|---|---|
fatal | JSON inválido o truncado, algo que no es un objeto, una v ausente o no numérica, una versión mayor incompatible, un mapa views ausente, y cero views utilizables una vez reparado todo lo demás. |
warn | Todo lo reparable en local: una view que no es un nodo, un nodo malformado, una prop del tipo equivocado, una entrada de asset inválida, una referencia colgada a un token, un asset o un ancla, un id duplicado, un subárbol anidado demasiado hondo. El envelope carga sin ellos. |
| silencio | Propiedades desconocidas y tipos de nodo desconocidos. La tolerancia hacia adelante es una capacidad, no un error. |
Formas, nunca vocabularios
Un conjunto cerrado —Easing, ImageFit, AnchorAt, GroupBehavior,
ScrollAxis— se comprueba que sea una cadena, y nada más.
El vocabulario es justo lo que crece en una versión posterior, y todos los consumidores ya caen a su valor por defecto ante un valor que no conocen. Validar aquí el valor convertiría el contenido de mañana en el error de hoy, que es lo contrario de para lo que está el validador.
El resultado viene reparado, no solo reportado
readEnvelope devuelve una copia con las partes rotas quitadas. El objeto
de quien llama no se muta nunca, y las propiedades desconocidas sobreviven a la
copia intactas.
Dos reparaciones conviene conocerlas:
- Un slot posicional que se cae se sustituye por un
Containervacío e inerte.Collapse,Toggle,Slider,ProgressBaryRepeatleen a sus hijos por posición, así que quitar uno roto renumeraría el resto y cambiaría en silencio lo que significan. Un marcador de posición mantiene honesta la numeración. - Una prop del tipo equivocado cae a su valor por defecto en vez de tirar el nodo entero.
La lectura tiene además un tope de profundidad de 256 niveles. Nada de lo que se autora se acerca, pero todos los pases de más abajo —validación, layout, pintado, hit-testing— son recursivos, así que un árbol que desbordaría la pila deja de ser un árbol en la puerta. El corte es un aviso ordinario: ese subárbol se cae, el resto de la UI carga.
Los diagnósticos
Un diagnóstico es lo que el SDK devuelve tras leer un payload: un objeto por cada cosa que se ha encontrado. Las herramientas casan contra el código y enseñan el mensaje, así que el código es la parte que no cambia nunca.
Cada hallazgo lleva un code estable —ese es el contrato, no la prosa del
mensaje— más un path dentro del envelope
(views["hud"].children[2].text) y un mensaje legible y autocontenido que
nombra el campo y el motivo. Las claves de mapa van entre corchetes porque los
ids de view, los ids de asset y los nombres de token llevan puntos propios.
normativeCódigos de diagnóstico
| Código | Nivel | Significado |
|---|---|---|
| invalid-json | fatal | El payload no es un JSON parseable. |
| not-an-object | fatal | El payload no es un objeto. |
| missing-version | fatal | No hay v, o v no es un número. |
| unsupported-version | fatal | La versión mayor es una que este lector no implementa. |
| missing-views | fatal | No hay mapa views. |
| no-usable-views | fatal | Todas las views se cayeron durante la reparación. |
| invalid-tokens | warn | El diccionario de tokens no es un objeto. |
| invalid-token | warn | El valor de un token no es ni una cadena ni un número. |
| invalid-assets | warn | El manifiesto de assets no es un objeto. |
| invalid-asset | warn | A una entrada le falta hash/mime/size, o data no es base64. |
| invalid-node | warn | Un nodo no es un objeto, o no tiene un type utilizable. |
| invalid-prop | warn | Una propiedad tiene el tipo equivocado; cae a su valor por defecto. |
| invalid-binding | warn | La ruta de un binding está malformada. |
| too-deep | warn | El subárbol supera el tope de profundidad. |
| duplicate-id | warn | Dos nodos de una view comparten id. |
| unknown-token | warn | Un {token} que el diccionario no define. |
| unknown-asset | warn | Una ref asset: sin entrada en el manifiesto. |
| unknown-anchor | warn | Un id de ancla de overlay que no casa con ningún nodo. |
Las entradas de asset se comprueban solo por forma: data no se decodifica
nunca durante la validación, ya que eso pagaría el coste dos veces.
Qué hacen los consumidores con ellos
- Montar lanza ante un diagnóstico fatal. No hay ninguna UI anterior que proteger, y quien llama tiene que enterarse de que su payload nunca llegó a ser una view. El mensaje de error es el del diagnóstico fatal, y arrastra los avisos encontrados por el camino.
- Recargar no lanza nunca. Un hot-update que el validador rechaza se reporta y se descarta: el envelope que hay en pantalla se queda exactamente como está. Una actualización mala le cuesta al jugador una actualización, nunca su sesión.
- La exportación valida antes de escribir. Un diagnóstico fatal aborta la exportación; los avisos van a su resumen. Lo que escribe es el árbol del autor, nunca el reparado: tirar un nodo del artefacto en silencio escondería justo el fallo que el aviso acaba de nombrar.
- Dónde se reportan es cosa de quien montó la view. El renderer web acepta
un callback
onDiagnostic, tanto enmountcomo enreload, que recibe estos objetos —código, ruta y todo— para que un overlay de errores, un servidor de desarrollo o un editor puedan enseñarlos donde el autor está mirando. Sin callback se van a la consola.
Los avisos se emiten una vez, al cargar, no en cada frame.
Correr el contrato tú mismo
zabloo validate [file] aplica exactamente esta política a un envelope en disco
y la reporta como código de salida: 0 cuando un SDK lo cargaría, 1 ante un
fatal, y --strict para fallar también con los avisos reparados. --json da
los diagnósticos como valores —level, code, path, message— para que un
paso de CI pueda anotar el diff en vez de imprimir una línea.
npx zabloo validate --strict
El envelope es un payload que se entrega a juegos vivos y se hot-updatea dentro de ellos, así que esta es la comprobación que merece la pena correr en la pull request y no en el jugador.
La tolerancia hacia adelante
Esta es la regla que permite que el formato crezca. A un juego que corre el SDK de marzo le van a poner delante contenido autorado contra uno posterior, y tiene que renderizar las partes que entiende en vez de rendirse — así que lo que hace con las partes que no entiende se escribe aquí, y no se deja a cada target.
normativeQué hace un SDK con contenido construido para una versión más nueva del formato:
| Situación | Comportamiento |
|---|---|
| Propiedad desconocida | Se ignora, en silencio. Sobrevive intacta a una ida y vuelta por la validación. |
| Tipo de nodo desconocido | Se renderiza como un Container, conservando layout, style, visible, disabled y children. |
| Valor desconocido en un conjunto cerrado | Cae al valor por defecto de esa propiedad. |
Comportamiento de group desconocido | Se ignora — los hijos se maquetan como hermanos ordinarios. |
| Versión mayor incompatible | Se rechaza: unsupported-version, fatal. |
Un primitivo
nuevo aterriza en un SDK viejo como una caja llana con sus hijos dentro: el
layout sobrevive, y lo único que falta es la capacidad nueva. Es también por lo
que cada primitivo se diseña de forma que su
degradación a
Container sea una imagen razonable de él — un Repeat se queda en una copia
estática de su plantilla, un ProgressBar en su pista con un relleno sin
dimensionar, un Spinner en sus cuentas en reposo.
Ver Versionado para saber qué cambios pueden apoyarse en esto y cuáles no.