Saltar al contenido
zabloo

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.

Las tres páginas que explican el hot-update

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

NivelQué cubre
fatalJSON 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.
warnTodo 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.
silencioPropiedades 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 Container vacío e inerte. Collapse, Toggle, Slider, ProgressBar y Repeat leen 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ódigoNivelSignificado
invalid-jsonfatalEl payload no es un JSON parseable.
not-an-objectfatalEl payload no es un objeto.
missing-versionfatalNo hay v, o v no es un número.
unsupported-versionfatalLa versión mayor es una que este lector no implementa.
missing-viewsfatalNo hay mapa views.
no-usable-viewsfatalTodas las views se cayeron durante la reparación.
invalid-tokenswarnEl diccionario de tokens no es un objeto.
invalid-tokenwarnEl valor de un token no es ni una cadena ni un número.
invalid-assetswarnEl manifiesto de assets no es un objeto.
invalid-assetwarnA una entrada le falta hash/mime/size, o data no es base64.
invalid-nodewarnUn nodo no es un objeto, o no tiene un type utilizable.
invalid-propwarnUna propiedad tiene el tipo equivocado; cae a su valor por defecto.
invalid-bindingwarnLa ruta de un binding está malformada.
too-deepwarnEl subárbol supera el tope de profundidad.
duplicate-idwarnDos nodos de una view comparten id.
unknown-tokenwarnUn {token} que el diccionario no define.
unknown-assetwarnUna ref asset: sin entrada en el manifiesto.
unknown-anchorwarnUn 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 en mount como en reload, 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.

normative

Qué hace un SDK con contenido construido para una versión más nueva del formato:

SituaciónComportamiento
Propiedad desconocidaSe ignora, en silencio. Sobrevive intacta a una ida y vuelta por la validación.
Tipo de nodo desconocidoSe renderiza como un Container, conservando layout, style, visible, disabled y children.
Valor desconocido en un conjunto cerradoCae al valor por defecto de esa propiedad.
Comportamiento de group desconocidoSe ignora — los hijos se maquetan como hermanos ordinarios.
Versión mayor incompatibleSe rechaza: unsupported-version, fatal.
Por qué la regla del tipo desconocido es toda la historia del crecimiento

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.

Relacionado