# Formato de paquetes y protocolo de partidas — Juegoland v1
El catálogo se limita a juegos propios publicados por el equipo. La [propuesta de arquitectura Go](../platform/architecture.md) distingue este formato/protocolo de datos del SDK ejecutable TypeScript: el perfil nativo Go y un posible transporte WebSocket requieren contratos versionados propios. Este documento y los esquemas v1 no adquieren soporte Go por esa recomendación.
**Estado:** contrato propuesto para el motor propio, 5 de octubre de 2026. Este documento explica cómo se distribuye un juego y cómo se comunica una partida. El [modelo completo](../game-engine-proposal.md) define reglas, presentación, persistencia y migración. El servidor actual aún requiere el refactor para hablar este protocolo.
**Ampliación multidispositivo:** el [perfil de superficies y controles v2](multi-device-profile.md) declara `TVBoard`, `Desktop` y `Mobil`, proyecciones diferenciadas y distribución por suscripción/contrato. Distingue mesa por QR sin invitaciones y TV personal enlazada tras invitación, con cambio a móvil completo. El [perfil temporal](synchronization-profile.md) propone reloj, rondas y equidad de respuestas. No se modifica silenciosamente este formato/protocolo v1 ni se consideran implementadas estas ampliaciones por existir documentación.
**Clientes web y nativos:** el [perfil común de comunicación v2](../platform/client-communication-profile.md) contempla web Svelte y una app Flutter posterior **solo de controles**: datos/acciones neutrales del mando, SDK TypeScript/Dart, controles por runtime, acceso propio y WSS bidireccional. Tableros y vistas completas permanecen en la web; Flutter solo representa `player-controller` de `TVBoard`. Las firmas/módulos TypeScript y eventos Socket.IO de v1 no se ejecutan en Dart ni se convierten a WebSocket simple automáticamente.
**Jugadores virtuales:** el [perfil de agentes v2](virtual-player-profile.md) propone que cada juego declare esta opción en sus propiedades e incluya las instrucciones versionadas de sus agentes. La plataforma ejecuta los perfiles admitidos y el motor valida sus acciones. Es una ampliación pendiente, sin campos nuevos en los esquemas v1.
Las fuentes verificables del contrato son [schema.ts](schema.ts), los [tipos del SDK](contracts.ts), los [esquemas JSON](package.schema.json) y los [mensajes de ejemplo](examples/wire.json). Los esquemas JSON describen estructura; las comprobaciones entre campos y paquetes están en los validadores de `schema.ts`.
## 1. Un paquete de juego
El manifiesto de distribución es `game.json`. Una biblioteca de componentes o recursos usa `package.json`. Ambos tienen:
Los catálogos declarados en `metadata.locales` forman parte de la distribución versionada. La publicación interna debe exigir cobertura de los idiomas activos de la [plataforma](../platform/localization.md), incluidos reglas, acciones, ayuda, textos alternativos y recursos que contengan texto. El idioma elegido solo modifica presentación; identificadores, acciones y resultados del motor son estables entre lenguas.
| `components` | Mapa por identificador. Cada entrada declara `kind`, `definition` y `copies` (1–100). |
| `rules` | `entry`, `version`, versiones de los esquemas de estado, vista y eventos, y rutas de los esquemas de configuración, datos, vista, contexto, evento interno y evento visible. |
Una biblioteca contiene `exports: { nombre: ruta }`. El código de reglas puede cargar bibliotecas `components`; la presentación puede cargar `assets`. Una biblioteca solo puede depender de otras de su mismo tipo.
### Referencias y recursos
Una definición local usa `{ "source": "local", "path": "components/board.json" }`. Una compartida usa una referencia por paquete y exportación:
```json
{
"source": "dependency",
"packageId": "playing-cards",
"export": "poker52"
}
```
La dependencia fija `playing-cards@1.0.0` en `dependencies`. El cargador rechaza exportaciones ausentes, dependencias circulares y versiones distintas del mismo paquete dentro del árbol de un juego.
Los componentes permitidos son `deck`, `dice`, `wheel`, `board`, `pieces` y `custom`. Su estructura está en [component.schema.json](component.schema.json). Una definición describe lo que existe; cada partida crea instancias con identidad propia. En Solitario, `poker52` define 52 caras y `mainDeck` crea una copia. Sus ilustraciones vienen por separado de `cards-poker/standard`. El [manifiesto de Solitario](examples/solitario.game.json) contiene todos esos campos como ejemplo estructural.
`presentation.resources` es un mapa de entradas con:
| `kind` | `image`, `atlas`, `audio`, `font` o `graphic`. |
| `path` | Ruta relativa dentro de la distribución. |
| `load` | `catalog`, `board`, `phase` o `lazy`; `phase` exige el nombre de fase. |
| `required` | Si el recurso es necesario para representar el juego. |
| `fallback` | `text` con clave traducible, otro `resource` u `omit` si es opcional. |
| `width`, `height`, `regions` | Geometría de imágenes y atlas. |
| `altKey` | Clave de texto alternativo, cuando corresponda. |
`catalog` identifica obligatoriamente `cover`, `thumbnail` e `icon` entre esos recursos. `bindings` asocia componentes con arte local o de biblioteca. Un [conjunto visual](examples/poker.resource-set.json) contiene slots por carta, recursos y reverso; debe cubrir todas las caras que el renderer vaya a mostrar. Un recurso `graphic` implementa `GraphicDefinition` y devuelve un SVG a partir del slot y su definición.
Todos los `entry`, esquemas, recursos y exportaciones son rutas relativas normalizadas. El publicador debe comprobar que cada archivo existe, permanece dentro del paquete, tiene el tipo esperado y coincide con su hash. Los manifiestos de ejemplo describen esas rutas, pero **todavía no contienen los módulos ni los archivos necesarios para instalar los juegos**.
### Lock y versiones
El [lock.schema.json](lock.schema.json) fija raíz, dependencias resueltas y archivos con SHA-256, tamaño, tipo de contenido y URL pública opcional. El código privado de reglas tiene `url: null`. Los hashes se calculan de los bytes reales y de inventarios JSON canónicos; [la especificación general](../game-engine-proposal.md#publicación-y-lock) define el orden y la canonicalización.
Una partida fija `packageDigest`, `dependencyLockDigest`, versión de reglas y versión del esquema persistido. No se interpreta un estado antiguo con una versión nueva por coincidencia de identificador. `bundleVersion` identifica cambios de presentación y `protocolVersion` identifica el contrato de red.
## 2. Mensajes del protocolo
Todos los mensajes llevan `protocolVersion: 1` y `type`. Salvo `game.error` antes de identificar una partida válida, llevan `matchId` UUID. `game.sync` y `game.command-status` llevan `requestId` UUID; los comandos usan `commandId` UUID para idempotencia.
El transporte previsto es Socket.IO sobre el namespace principal. Todos los sobres v1 se envían como un objeto JSON mediante un único evento `game:message`, en ambos sentidos; los recibos usan ese evento, no el callback de acuse de Socket.IO. El campo `type` distingue el mensaje. El adaptador anterior podrá convivir hasta migrar los clientes. El cliente se autentica en la plataforma y el servidor deriva su asiento. **Ningún comando de cliente contiene `actor` ni un estado nuevo.** El límite inicial del sobre es 64 KiB de JSON y profundidad 32, además de los límites específicos de cada juego.
| `game.error` | Servidor → cliente | `code`, `recovery`, `messageKey` y los identificadores disponibles. |
### Sincronización y vista
`game.sync.client` declara `engineApiVersion: 1`, `presentationApiVersion: 1` y al menos un contrato exacto:
```json
{
"id": "solitario",
"rulesVersion": "1.0.0",
"viewSchemaVersion": 1,
"eventSchemaVersion": 1
}
```
El servidor comprueba la tupla completa antes de entregar una vista. Un snapshot de sincronización declara `delivery: { "kind": "sync", "requestId": "…" }` y `events: []`. Su `subscriptionId` identifica el flujo nuevo. El servidor instala primero la suscripción, almacena cambios concurrentes, lee la revisión R confirmada, envía el snapshot R y después entrega los cambios posteriores a R en orden.
Un snapshot vivo declara `delivery: { "kind": "live", "fromRevision": R }` y lleva `revision: R + 1`. Contiene una vista completa autorizada para ese destinatario. `view` y `context` deben cumplir además los esquemas específicos del juego. `game` contiene la tupla de contrato usada. `status` es `active`, `completed` o `cancelled`; `outcome` es `null` exactamente mientras está activo. Las partidas terminadas no ofrecen acciones.
Cada evento visible lleva `eventId` UUID estable, revisión, `order` consecutivo desde 0, tipo y payload. Una regla puede ocultar un evento interno o proyectarlo en varios visibles. La sincronización no reproduce eventos anteriores. Si el cliente pierde una revisión, recibe una revisión duplicada, vuelve al primer plano o ve un cursor por delante de su vista, se resincroniza. Solo acepta la respuesta de su solicitud más reciente y los mensajes de su suscripción actual.
### Acción, precondiciones y recibo
Una acción usa `{ "type": "drop", "payload": { "column": 3 } }`. El módulo del juego valida ese payload. Hay dos precondiciones:
`match` exige la revisión global exacta. `actor` se permite solo para operaciones independientes declaradas por el módulo, como colocar un barco privado. Ambas se revalidan bajo el bloqueo de la partida; al cambiar de fase aumenta `phaseEpoch` y una colocación tardía se rechaza.
El recibo aplicado es `{ "status": "applied", "appliedRevision": 13 }`. El rechazado definitivo es `{ "status": "rejected", "code": "STALE_REVISION", "observedRevision": 13 }`; los códigos permitidos son `STALE_REVISION`, `STALE_PHASE`, `STALE_ACTOR`, `ILLEGAL_ACTION` y `MATCH_FINISHED`. Errores transitorios usan `game.error`, no un rechazo definitivo.
La identidad idempotente es partida + principal autenticado + `commandId`. El servidor guarda el recibo con el estado confirmado. Repetir **el mismo sobre completo**, incluida la precondición, devuelve el mismo recibo con `duplicate: true`; reutilizar el UUID con otro sobre da `COMMAND_ID_REUSED`. El cliente conserva los comandos pendientes y reenvía el mismo UUID después de un corte. `game.command-unknown` significa que no hay recibo almacenado al consultar: un envío retrasado todavía podría llegar.
La vista puede llegar antes o después del recibo. El recibo informa sobre la operación; la vista del snapshot determina qué se muestra. Un cambio confirmado incrementa la revisión una vez; una consulta o rechazo no la incrementa.
### Resultados, privacidad y errores
`outcome` completado contiene `reasonCode` y un mapa por asiento. Cada participante tiene `outcome: win | loss | draw | neutral`, `metrics` y puntuación, puesto o equipo opcionales. Esto admite derrota en Solitario y derrota de todos en un juego cooperativo. La cancelación usa `{ "kind": "cancelled", "reasonCode": "…" }`. El módulo decide cuándo terminan sus reglas y qué resulta de una retirada.
El servidor proyecta por destinatario `view`, `context`, ofertas, resultado y eventos. Ninguno copia automáticamente el estado privado. El esquema de red acepta objetos JSON en esos campos; los esquemas del juego y las pruebas de privacidad completan la validación.
`game.error` usa códigos del [esquema de protocolo](protocol.schema.json) y una recuperación explícita: `none`, `sync`, `retry-same-command`, `reload-client` o `reauthenticate`. No incluye datos privados ni trazas internas.
## 3. Orden de procesamiento y entrega
1. Validar sobre y límites, autenticar, autorizar asiento y buscar un recibo previo.
2. Bloquear la partida, verificar la versión fijada y las precondiciones.
3. Ejecutar reglas síncronas con servicios deterministas; validar estado y proyecciones.
5. Enviar el recibo y publicar snapshots completos en orden de revisión.
6. Ante fallo de envío, reintentar la publicación pendiente. El cliente descarta duplicados y recupera huecos con `game.sync`.
El cursor periódico detecta incluso la última publicación perdida. La frecuencia inicial prevista es 15 segundos mientras existe suscripción. El servidor de producción de varias instancias necesitará coordinación de bloqueo, publicaciones y suscripciones.
El primer comando valida ejemplos, dependencias, invariantes comunes y que los seis JSON Schema coincidan con `schema.ts`. El segundo comprueba las firmas TypeScript. Véanse los [ejemplos de paquetes](examples/README.md) y los [sobres de red](examples/wire.json).
Estas comprobaciones verifican el **diseño del formato**. El compilador de paquetes, los archivos reales de cada ejemplo y las pruebas del servidor, sockets y navegador pertenecen a la implementación del motor.