Saltar al contenido
zabloo

El envelope

La unidad que carga un SDK: un objeto JSON con una versión, un diccionario de tokens, una o más views y un manifiesto de assets — más las props que lleva cada nodo.

El envelope —el sobre que el juego descarga— es el único fichero que un juego baja para tener su UI. Imagina la pantalla de tienda del gremio: el panel, las dos filas de objetos, los botones de comprar, el contador de oro. Todo eso viaja en un solo fichero JSON —las cajas y las etiquetas, los colores a los que apuntan, las imágenes que pintan— y el SDK que vive dentro del juego lee ese fichero y dibuja la pantalla. No hay un segundo fichero ni un paso de build en la máquina del jugador.

Formalmente: un objeto JSON con una versión, un diccionario de tokens, una o más views —cada una es una pantalla— y, cuando la UI usa imágenes, un manifiesto de assets. La forma de lo que lleva dentro es la IR, y estas páginas son su referencia.

Hay exactamente un camino de carga. Un fichero importado a mano en el editor y un hot-update —una actualización enviada a un juego ya publicado— son el mismo payload versionado, y se leen igual.

{
  "v": 1,
  "tokens": {
    "color.primary": "#4f46e5",
    "space.3": 12,
    "radius.md": 6
  },
  "views": {
    "hud": { "type": "Container", "children": [] }
  },
  "assets": {
    "icons/coin.png": {
      "hash": "9f2c…",
      "mime": "image/png",
      "size": 1204,
      "width": 32,
      "height": 32,
      "data": "iVBORw0KGgo…"
    }
  }
}

Cuatro campos de primer nivel, y el resto de esta referencia describe lo que vive dentro de uno de ellos:

PropTipoPor defectoDescripción
vnumberLa versión mayor de la IR. Un SDK rechaza un payload cuya mayor no implementa.
tokensRecord<string, string | number>Diccionario plano de tokens de diseño.
viewsRecord<string, ZNode>Documentos (views, escenas) indexados por id de view. Hace falta al menos una view utilizable.
assetsRecord<string, AssetEntry>ausenteManifiesto de assets indexado por id lógico. Los envelopes sin assets lo omiten.

Las views

Una view es una pantalla. Un envelope lleva varias —un HUD, una tienda, una pantalla de ajustes— y el juego elige cuál renderizar. Cada valor es un árbol de nodos, las cajas y etiquetas de las que está hecha la pantalla; la clave es el id que el juego pide.

Los ids de view son cadenas opacas y pueden contener puntos, y por eso los diagnósticos los direccionan entre corchetes: views["shop.main"].children[2].

Se espera que los id de nodo sean únicos dentro de una view. Son lo que el juego nombra cuando maneja un nodo a través del canal del host, y a lo que apunta un Overlay anclado. Dale a un nodo el id audio-section y setOpen("audio-section", true) lo abre desde el código del juego; toda la superficie está en El canal del host. Los duplicados cargan, con un aviso, y resuelven a la primera coincidencia.

Los tokens

Un token es un valor con nombre —color.primary, space.3— al que los estilos apuntan en vez de escribirlo. Los estilos no cuecen valores dentro: referencian tokens, y el SDK los resuelve por nodo en tiempo de render. Cambiar el diccionario re-tematiza toda la UI sin volver a emitir el árbol, y eso es lo que hace que un tema se pueda hot-updatear por su cuenta.

Una referencia a token es una cadena entre llaves: "{color.primary}". Vale en cualquier sitio donde se acepte un Dim o un ColorValue, y el diccionario es plano: la clave es el nombre entero, puntos incluidos, así que buscar es un acceso a la tabla hash y nunca un recorrido:

"tokens": { "color.primary": "#4f46e5", "space.3": 12 }
"style":  { "background": "{color.primary}" },
"layout": { "padding": "{space.3}" }

En la tienda, el botón de comprar pide "background": "{color.primary}", y también lo hace cualquier otro botón relleno del juego. Publica un envelope cuyo diccionario mapee color.primary a otro hexadecimal y todos se repintan a la vez: el árbol de nodos es byte a byte el que era.

No hay cascada ni herencia. Cada nodo lleva su propio estilo ya resuelto; nada se busca en un padre.

Un token que falta nunca rompe el frame

El pase de carga lo reporta una vez, nombrando el nodo y la propiedad (unknown-token), y entonces:

  • Un Dim cae al valor por defecto de su propia propiedad. Un {space.3} que falta da padding: 0, y un {size.card} que falta en un width deja el nodo con tamaño automático.
  • Un ColorValue declarado pinta el magenta de color-ausente. Es una señal deliberada y a gritos: el autor pidió un color y nombró uno que no existe, y un nodo transparente en silencio escondería la errata en vez de enseñarla. Un color ausente no es este caso: simplemente no pinta nada.

Los valores de los tokens son cadenas o números, y la propiedad decide cómo leer uno: un Dim toma el número, un ColorValue toma la cadena. Un token del tipo equivocado se trata exactamente igual que uno que falta.

Los assets

Las imágenes viajan dentro del envelope. Una entrada describe el contenido y, en v1, lleva los bytes:

PropTipoPor defectoDescripción
hashstringIdentidad del contenido (SHA-256, hex). Hoy deduplica; mañana es la clave del caché direccionado por contenido.
mimestringPor ejemplo "image/png". El formato es genérico: qué tipos MIME se aceptan es asunto de la exportación.
sizenumberTamaño en bytes del contenido decodificado.
widthnumberausenteAncho en píxeles. Permite al layout reservar sitio antes de decodificar los bytes.
heightnumberausenteAlto en píxeles.
datastringausenteEl contenido, codificado en base64.

Los nodos referencian una entrada por su clave en el manifiesto, con prefijo: "asset:icons/coin.png". El prefijo es lo que hace reconocible una referencia a asset sin conocer el manifiesto — isAssetRef y assetIdFromRef en @zabloo/format son los lectores compartidos para eso.

data es opcional solo en el esquema: una exportación v1 siempre lo incrusta. El campo existe para que un futuro camino de entrega pueda enviar un envelope que nombre sus assets por hash y deje que el SDK resuelva los bytes desde un caché o una CDN, sin cambiar el formato. Un SDK que no encuentra data y no puede resolver los bytes pinta el fondo del nodo y nada más — igual que una imagen que todavía se está decodificando.

width y height importan para el layout, no para el pintado: una Image toma el tamaño en píxeles del origen como su tamaño intrínseco, así que un manifiesto que los omite hace que el nodo mida cero hasta que se le dé un tamaño explícito.

Las fuentes no son assets en v1

El manifiesto lleva imágenes y solo imágenes —la exportación acepta .png, .jpg y .jpeg— y ningún nodo tiene una prop que pueda nombrar una fuente, así que no hay con qué referenciarla. Cada target rasteriza en su lugar la misma tipografía incrustada. Las fuentes por proyecto llegan con el trabajo del motor de texto, y aterrizarán aquí: el manifiesto es deliberadamente genérico con los tipos MIME, así que añadir uno es asunto de la exportación y no un cambio de formato.

La base de los nodos

Cada nodo de views es un objeto con un type y los campos de abajo. Estos son los campos que tiene todo nodo, sea lo que sea: un Button, un Text y un ScrollView los llevan los tres. Cada tipo de nodo añade después los suyos —un Text añade text, un Slider añade min y max— y esos se documentan en cada página del catálogo.

PropTipoPor defectoDescripción
typestringLa identidad del nodo. Decide su comportamiento y su pintado por defecto.
idstringausenteNombre direccionable dentro de la view.
visibleBindable<boolean>trueEl único mecanismo para ocultar, con semántica de display:none — un nodo oculto sale del layout por completo.
disabledBindable<boolean>falseSaca este nodo y su subárbol del modelo de interacción.
layoutLayout{}Entradas del flex — dirección, gap, padding, tamaño, grow, wrap.
styleStyle{}Cómo pinta el nodo, resuelto por nodo.
statesPartial<Record<StateName, { style?: Style }>>{}Sobrescrituras de estilo por estado, mezcladas en un único orden normativo.
transitionTransitionausenteInterpola los valores animables de este nodo cuando cambian.
autofocusbooleanfalseEste nodo toma el foco inicial de su ámbito.
clipbooleanfalseRecorta el pintado y el hit-testing de los hijos al rect de este nodo.

Tres de ellos llevan reglas que conviene decir en voz alta.

visible es la única forma de ocultar algo. No hay un segundo mecanismo: ni display, ni hidden, ni un truco de opacidad con consecuencias en el layout. Ocultar un nodo lo saca del pase de layout, así que sus hermanos cierran el hueco; volver a mostrarlo lo trae de vuelta. Como es Bindable, el juego abre y cierra UI moviendo un booleano en sus propios datos.

disabled es la única prop que se hereda. El valor efectivo de un nodo es el suyo o el de cualquier ancestro, así que una sola prop apaga una sección entera de un formulario; un Overlay reinicia la cadena, por ser la cima de su propio ámbito de entrada. Quita foco, hover, pulsación y acciones — no el pintado, y no el scroll — y su aspecto es states.disabled, ya que el formato no trae ningún look apagado de serie. La regla entera está en Input y focus.

clip es configuración de pintado, no estado de ejecución. El rect de recorte efectivo de un nodo es la intersección del suyo con el de todos sus ancestros, y corta la entrada además de los píxeles: un hijo pintado fuera del rect tampoco es pulsable ahí. Un ScrollView recorta siempre e ignora un clip: false explícito.

Qué se puede bindear

Un binding —el enlace entre un dato del juego y un nodo— es una dirección dentro de los datos del propio juego, player.gold, escrita donde iría un valor fijo, para que el nodo enseñe lo que el juego tenga ahí en cada momento.

visible y disabled son las dos props base que aceptan un binding en lugar de un literal, y son las mismas dos en todos los tipos de nodo: el juego empuja un booleano y el nodo entra o sale del layout, o el subárbol entero se cae del modelo de interacción. Todo lo demás que se bindea es propio de cada tipo —el text de un Text, el checked de un Toggle, los items de un Repeat— y vive en Bindings y actions.

Fíjate en que style no es bindeable en ningún sitio. Una barra que cambia de color con su valor se hace con el juego moviendo un token, no con la UI calculando uno.

De dónde sale el árbol

@zabloo/react emite envelopes. zabloo export renderiza las views del proyecto, recoge los assets que referencian y escribe el JSON. Los conceptos de autoría —componentes de usuario, variantes, compuestos— se resuelven durante ese pase y nunca aparecen en la salida.

Leer un envelope de vuelta —parsear, validar, reparar— es Loading.

Relacionado