You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
428 lines
65 KiB
428 lines
65 KiB
|
23 hours ago
|
# Perfil multidispositivo del juego — propuesta de contrato v2
|
||
|
|
|
||
|
|
**Estado:** especificación de diseño para implementar y revisar con el equipo. No está soportada por el runtime, los esquemas ejecutables ni la API actual de `games2`. El [formato y protocolo v1](format-and-protocol.md) permanece sin cambios para los juegos existentes. Esta propuesta requiere versiones nuevas de manifiesto, SDK de reglas, presentación y mensajes; no se añaden campos a v1 fingiendo compatibilidad.
|
||
|
|
|
||
|
|
## 1. Decisión de modelo
|
||
|
|
|
||
|
|
El juego declara sus **superficies** y **métodos de entrada**. Un televisor, proyector o navegador conectado por HDMI puede ser una `shared-display`: muestra una proyección común de la partida, pero no ocupa plaza, no es un jugador, no recibe una mano privada y no envía acciones de juego. Un móvil, tableta u ordenador usado por una persona es una superficie `player`: su sesión autenticada se vincula a un asiento y puede mostrar datos privados y acciones autorizadas. Un jugador puede usar el móvil como mando sin que eso convierta al sensor en fuente de verdad.
|
||
|
|
|
||
|
|
El usuario concreta tres **perfiles de presentación** que cada juego declara en sus propiedades:
|
||
|
|
|
||
|
|
| Perfil | Vista y controles | Creación y entrada a la mesa |
|
||
|
|
| --- | --- | --- |
|
||
|
|
| `TVBoard` | La TV muestra el desarrollo público completo: tablero, fase, turno, resultados y eventos. El móvil ofrece los controles específicos de cada etapa y los datos privados necesarios. | Al crear una mesa presencial, entrada por QR visible en TV, sin invitaciones. Al acceder con una invitación a una sala convencional compatible, QR personal para enlazar móvil y TV del jugador. |
|
||
|
|
| `Desktop` | Vista completa autorizada del jugador, con estado y controles integrados para ordenador. | Flujo convencional de creación/admisión de sala; invitaciones cuando correspondan a su política. |
|
||
|
|
| `Mobil` | Vista completa autorizada del jugador, con estado y controles adaptados al móvil. | El mismo flujo convencional, presentado para móvil. |
|
||
|
|
|
||
|
|
«Completa» se refiere a la vista que corresponde a su destinatario: la TV no revela manos ni roles ocultos. `Mobil` y el mando móvil de `TVBoard` tienen responsabilidades distintas; el primero permite jugar con su propia vista completa, mientras el segundo acompaña al tablero común.
|
||
|
|
|
||
|
|
Los controles móviles pueden usar **la web de la plataforma o una futura app independiente Flutter solo de controles**. Perfil visual y runtime son ejes distintos: la web representa `TVBoard`, `Desktop` y `Mobil`; Flutter únicamente el mando `player-controller` de `TVBoard`, con sus datos privados necesarios. El tablero común/personal y la vista completa siguen en la web. El [perfil de comunicación web/nativa](../platform/client-communication-profile.md) define transporte bidireccional, autenticación por cliente, compatibilidad y recuperación. No exige instalar la app para jugar desde web ni traslada las reglas al móvil.
|
||
|
|
|
||
|
|
El **modo de sala** (`modeId`) fija interacción, requisitos de pantalla y método de entrada; el **perfil visual** (`presentationProfile`) elige cómo se representa una superficie compatible. Crear una mesa presencial desde `TVBoard` selecciona el modo de mesa con QR. Crear desde `Desktop` o `Mobil` selecciona el flujo personal convencional. Un jugador que ya recibió una invitación también puede usar `TVBoard` si el modo existente lo admite, sin crear otra mesa ni cambiar su entrada. Cambiar entre perfiles visuales admitidos conserva asiento, reglas y estado; no convierte una mesa con QR en una sala de invitaciones ni cambia su admisión durante la partida.
|
||
|
|
|
||
|
|
La pantalla común de una mesa usa `displayScope: room`. La TV personal de un jugador invitado usa `displayScope: participant`, vinculada a su membresía y a esa partida: muestra la misma proyección pública de tablero, pero su permiso de entrega depende del acceso vigente de ese jugador. No ocupa otra plaza ni es un espectador remoto independiente. Su mando móvil recibe la información privada del asiento. Varias personas pueden usar su propia TV en una sala compatible; no comparten la sesión personal de pantalla.
|
||
|
|
|
||
|
|
Se mantiene fuera del alcance el espectador remoto con cuenta propia. Una pantalla compartida es una sesión de dispositivo vinculada expresamente a **una sala** y con alcance de lectura mínimo. Cualquier persona físicamente delante de ella podría ver sus píxeles; por eso su contenido se diseña como visible para todos los presentes, especialmente en salas protegidas.
|
||
|
|
|
||
|
|
Ejemplos de adaptación, no juegos anunciados ni licencias de marcas ajenas:
|
||
|
|
|
||
|
|
| Tipo de juego | Pantalla compartida | Superficie personal |
|
||
|
|
| --- | --- | --- |
|
||
|
|
| Preguntas por equipos | Pregunta, tiempo y marcador una vez autorizados para todos. | Selección de respuesta; confirmación privada hasta la revelación. |
|
||
|
|
| Tablero económico | Tablero, turnos, movimientos y resultados públicos. | Cartas, decisiones, saldo o recursos que las reglas mantengan ocultos. |
|
||
|
|
| Rol | Mapa revelado, escena y tiradas públicas. | Ficha y secretos de cada personaje; la dirección de juego conserva una proyección privada propia. |
|
||
|
|
| Dados con cubilete | Animación del resultado **confirmado** por servidor. | Botón de lanzar o gesto de agitar para solicitar exactamente la misma acción. |
|
||
|
|
|
||
|
|
## 2. Extensión versionada del paquete
|
||
|
|
|
||
|
|
El perfil se introduce en `game.json` con `formatVersion: 2`, `engineApiVersion: 2` y `presentation.apiVersion: 2`; `protocolVersion: 2` identifica los sobres de red. Estas versiones no son alias entre sí. Los paquetes v1 no adquieren pantalla compartida por inferencia. Un paquete v2 sin `sharedDisplay` conserva solo la superficie `player`.
|
||
|
|
|
||
|
|
Cada juego publica uno o más **modos de interacción** con identificadores estables y los perfiles de presentación permitidos en ellos. La sala elige `modeId` al crearse y lo fija para toda la partida, junto a la versión/digest del paquete y su `entryMethod`. El ejemplo usa `personal` para el flujo convencional con vistas completas o TV personal opcional, y `mesa` para el tablero común con entrada QR; esos identificadores de modo son propios del juego. No se cambia de modo al perder conexión ni por preferencia local de un navegador: un cambio de reglas, visibilidad o método de entrada requiere una transición explícita, versionada y autorizada.
|
||
|
|
|
||
|
|
Forma propuesta del fragmento nuevo (los nombres se fijarán al generar los esquemas v2):
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"interaction": {
|
||
|
|
"profileVersion": 1,
|
||
|
|
"modes": [
|
||
|
|
{
|
||
|
|
"id": "personal",
|
||
|
|
"entryMethod": "standard",
|
||
|
|
"presentationProfiles": ["Desktop", "Mobil", "TVBoard"],
|
||
|
|
"defaultPresentationProfile": "Desktop",
|
||
|
|
"sharedDisplay": "optional",
|
||
|
|
"displayScope": "participant",
|
||
|
|
"onLoss": "continue",
|
||
|
|
"fallbackPresentationProfile": "Mobil"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "mesa",
|
||
|
|
"entryMethod": "screen-qr",
|
||
|
|
"presentationProfiles": ["TVBoard"],
|
||
|
|
"defaultPresentationProfile": "TVBoard",
|
||
|
|
"sharedDisplay": "required-at-start",
|
||
|
|
"displayScope": "room",
|
||
|
|
"onLoss": "pause-with-timeout",
|
||
|
|
"lossTimeoutSeconds": 120,
|
||
|
|
"onTimeout": "cancel-without-result"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"sharedDisplay": {
|
||
|
|
"projection": "public-board"
|
||
|
|
},
|
||
|
|
"inputBindings": [
|
||
|
|
{
|
||
|
|
"id": "shake-cup",
|
||
|
|
"modes": ["personal", "mesa"],
|
||
|
|
"surface": "player",
|
||
|
|
"input": "motion.shake",
|
||
|
|
"actionType": "roll-dice",
|
||
|
|
"fallbacks": ["button", "keyboard"]
|
||
|
|
}
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"rules": {
|
||
|
|
"projections": {
|
||
|
|
"player-full": {
|
||
|
|
"viewSchemaVersion": 1,
|
||
|
|
"eventSchemaVersion": 1,
|
||
|
|
"viewSchema": "schemas/player-full-view.json",
|
||
|
|
"contextSchema": "schemas/player-full-context.json",
|
||
|
|
"visibleEventSchema": "schemas/player-full-event.json"
|
||
|
|
},
|
||
|
|
"player-controller": {
|
||
|
|
"viewSchemaVersion": 1,
|
||
|
|
"eventSchemaVersion": 1,
|
||
|
|
"viewSchema": "schemas/controller-view.json",
|
||
|
|
"contextSchema": "schemas/controller-context.json",
|
||
|
|
"visibleEventSchema": "schemas/controller-event.json"
|
||
|
|
},
|
||
|
|
"public-board": {
|
||
|
|
"viewSchemaVersion": 1,
|
||
|
|
"eventSchemaVersion": 1,
|
||
|
|
"viewSchema": "schemas/shared-view.json",
|
||
|
|
"contextSchema": "schemas/shared-context.json",
|
||
|
|
"visibleEventSchema": "schemas/shared-event.json"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
"presentation": {
|
||
|
|
"apiVersion": 2,
|
||
|
|
"profiles": [
|
||
|
|
{
|
||
|
|
"id": "TVBoard",
|
||
|
|
"labelKey": "presentation.tvBoard",
|
||
|
|
"surfaces": {
|
||
|
|
"shared-display": {
|
||
|
|
"projection": "public-board",
|
||
|
|
"renderers": {
|
||
|
|
"web": {
|
||
|
|
"rendererId": "preguntas-equipo.web.tv-board",
|
||
|
|
"rendererVersion": "1.0.0",
|
||
|
|
"rendererApiVersion": 2,
|
||
|
|
"entry": "presentation/tv-board.ts"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
"player": {
|
||
|
|
"projection": "player-controller",
|
||
|
|
"renderers": {
|
||
|
|
"web": {
|
||
|
|
"rendererId": "preguntas-equipo.web.controller",
|
||
|
|
"rendererVersion": "1.0.0",
|
||
|
|
"rendererApiVersion": 2,
|
||
|
|
"entry": "presentation/mobile-controller.ts"
|
||
|
|
},
|
||
|
|
"flutter": {
|
||
|
|
"rendererId": "preguntas-equipo.flutter.controller",
|
||
|
|
"rendererVersion": "1.0.0",
|
||
|
|
"rendererApiVersion": 1
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "Desktop",
|
||
|
|
"labelKey": "presentation.desktop",
|
||
|
|
"surfaces": {
|
||
|
|
"player": {
|
||
|
|
"projection": "player-full",
|
||
|
|
"renderers": {
|
||
|
|
"web": {
|
||
|
|
"rendererId": "preguntas-equipo.web.desktop",
|
||
|
|
"rendererVersion": "1.0.0",
|
||
|
|
"rendererApiVersion": 2,
|
||
|
|
"entry": "presentation/desktop.ts"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "Mobil",
|
||
|
|
"labelKey": "presentation.mobil",
|
||
|
|
"surfaces": {
|
||
|
|
"player": {
|
||
|
|
"projection": "player-full",
|
||
|
|
"renderers": {
|
||
|
|
"web": {
|
||
|
|
"rendererId": "preguntas-equipo.web.mobile-full",
|
||
|
|
"rendererVersion": "1.0.0",
|
||
|
|
"rendererApiVersion": 2,
|
||
|
|
"entry": "presentation/mobile-full.ts"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Cada modo declara `sharedDisplay: off|optional|required-at-start`. Solo `optional` y `required-at-start` pueden vincular una pantalla. El último debe anunciarse en el catálogo y no inicia sin una pantalla vinculada y activa. Si el modo admite pantalla, declara `onLoss: continue|pause-with-timeout`; el segundo pausa la admisión de nuevas acciones, sin adjudicar derrota por un corte de red. Para `pause-with-timeout` son obligatorios `lossTimeoutSeconds` acotado y `onTimeout: continue-without-display|cancel-without-result`, fijados al crear la sala. Los plazos de juego se ajustan o reprograman de forma transaccional si una pausa está permitida. Al vencer, el servidor ejecuta la salida declarada; si se permite continuar sin pantalla, ese fallback forma parte del mismo modo y no cambia el `modeId`. La transición exacta requiere pruebas de abuso antes de publicar el modo. En `off` no se incluyen reglas de pérdida de pantalla; en `optional` sin pantalla vinculada al empezar tampoco se inicia una pausa.
|
||
|
|
|
||
|
|
`sharedDisplay` existe solo si al menos un modo la permite; en ese caso sus esquemas y funciones son obligatorios. Los contratos de vista/contexto/eventos pertenecen a `rules.projections` y los renderers a `presentation.profiles[].surfaces.<surface>.renderers`; las referencias deben coincidir, sin duplicar definiciones que puedan discrepar. `inputBindings.modes` es una lista no vacía de modos publicados donde el gesto es válido. El juego puede omitir por completo `motion.shake`; una categoría como `board` o `roleplaying` no activa automáticamente sensores ni TV. En el catálogo se ofrecen perfiles, modos, runtimes compatibles y requisitos por juego, y la sala conserva su selección para autorización, reconexión y replay.
|
||
|
|
|
||
|
|
Cada perfil tiene identificador único, nombre traducible y renderers reales/versionados para sus superficies y runtimes ofrecidos. `TVBoard` exige `shared-display` y `player`; `Desktop` y `Mobil` exigen `player` y representan estado más controles. Los perfiles de un modo y su predeterminado deben existir y ser compatibles con sus superficies. Un juego publica solo combinaciones que implemente y verifique. Un renderer web declara su `entry`; uno Flutter referencia controles registrados en la app, o un modelo declarativo versionado que esa app admite. Flutter solo se admite en `TVBoard` / `player` / `player-controller`, nunca en `player-full` o `public-board`; publicador y negociación comprueban esa restricción. Las versiones/API del renderer son independientes del protocolo de red; una entrada `.ts` no prueba soporte Flutter. Pueden compartir datos/recursos, pero los comportamientos se prueban por cliente. El fragmento es ilustrativo, incluidos sus controles nativos, y no constituye un paquete instalable; los plazos se validan por juego antes de publicar.
|
||
|
|
|
||
|
|
El modo de creación de mesa presencial `TVBoard` usa `entryMethod: screen-qr`, `displayScope: room` y requiere pantalla al inicio. El flujo convencional `standard` puede admitir `Desktop`, `Mobil` y `TVBoard` con pantalla personal opcional, sin modificar cómo se entra en la sala. El juego declara las combinaciones; no se infieren al detectar el dispositivo. En el ejemplo, la mesa común pausa con plazo y cancela sin resultado al perder TV; el modo personal continúa y permite fallback a la vista completa `Mobil`, previa resincronización y conservación de recibos pendientes. El fallback solo afecta al jugador que perdió su TV.
|
||
|
|
|
||
|
|
La ausencia temporal de TV no autoriza a cambiar de modo. Un perfil visual solo cambia por elección compatible o por un fallback publicado explícitamente. Continuar sin pantalla exige una presentación completa alternativa y una transición declaradas y probadas. `fallbackPresentationProfile` debe existir, ofrecer `player` con estado y controles completos y pertenecer al mismo modo; nunca concede permiso para nuevas superficies.
|
||
|
|
|
||
|
|
La selección de `Desktop` o `Mobil` puede sugerirse según las capacidades del dispositivo, respetando la elección explícita y las opciones del juego. `TVBoard` se elige expresamente al preparar la mesa. Cambiar un perfil personal compatible resincroniza su presentación y mantiene los comandos pendientes con sus identificadores. Si también cambia el cliente activo, la [barrera de mando](../platform/client-communication-profile.md#continuidad-del-mando-entre-conexiones) resuelve los ingresos anteriores y excluye nuevos comandos de la generación sustituida antes de admitir acciones del destino. El redimensionado no cambia reglas, identidad ni método de admisión.
|
||
|
|
|
||
|
|
En el acceso convencional con invitación, TV personal y móvil mando, **«Jugar solo en el móvil» es una opción obligatoria**. Un paquete que ofrezca ese recorrido debe incluir `Mobil` web en el mismo modo, con `player-full`, recursos y traducciones correspondientes. Desde Flutter se abre la web, se autentica al mismo principal y se prepara la suscripción destino antes de confirmar transferencia de mando/presentación; no se comparte implícitamente la sesión ni se exporta su credencial permanente. No depende de que falle la TV: el jugador puede solicitarlo por elección propia. El servidor conserva sala, membresía, asiento y recibos, sustituye la suscripción del mando por la vista completa y revoca el vínculo personal de TV al completar el cambio. Un paso fallido no revoca anticipadamente la TV ni crea otra plaza. No consume otra invitación ni altera las pantallas de otros jugadores. Volver a TV exige un vínculo autorizado nuevo. Esta exigencia pertenece al recorrido de TV personal; las mesas presenciales pueden declarar otros requisitos.
|
||
|
|
|
||
|
|
El [perfil de sincronización](synchronization-profile.md) fija cuándo se aplica el cambio: en juegos por turnos puede completarse con la barrera habitual; en una ronda de reacción se solicita en cualquier momento y se aplica entre rondas, conservando la fuente de presentación y mediciones fijadas para la ronda actual. Si esa fuente se pierde y ya no permite jugar, la política temporal resuelve la ronda sin penalización por red antes de completar el cambio. Cambiar de vista nunca reinicia el cronómetro ni concede otra respuesta.
|
||
|
|
|
||
|
|
`inputBindings` se refiere a entradas registradas por la plataforma, no a nombres de sensores arbitrarios ni a código ejecutable del manifiesto. Cada binding identifica un tipo de acción existente y alternativas de botón y teclado obligatorias. El publicador comprueba que la presentación solo puede activar una oferta vigente del servidor y que no hay dos bindings ambiguos para la misma acción y modo. La ausencia de permiso o hardware elimina el gesto, **no** la acción. Las categorías de juego (`board`, `quiz`, `roleplaying`, etc.) sirven para catálogo; no sustituyen estas declaraciones concretas.
|
||
|
|
|
||
|
|
Cada superficie tiene renderers y recursos propios; los assets públicos pueden descargarse, así que su nombre o ruta no contiene secretos de una partida. El renderer nativo registrado y el web implementan contratos compatibles sin compartir código ejecutable por inferencia. Traducciones, texto alternativo, contraste, sonido y movimiento reducido se validan por superficie/cliente. La presentación compartida no importa el módulo de reglas privadas ni recibe datos para filtrarlos en el navegador.
|
||
|
|
|
||
|
|
## 3. Contrato del motor y privacidad
|
||
|
|
|
||
|
|
El SDK v2 distingue tres proyecciones con esquemas propios:
|
||
|
|
|
||
|
|
| Proyección | Funciones propuestas | Destinatarios y contenido |
|
||
|
|
| --- | --- | --- |
|
||
|
|
| `player-full` | `project(position, recipient)` y `projectEvent(...)` | `Desktop` y `Mobil`: estado completo autorizado del jugador, contexto, resultado y eventos personales. |
|
||
|
|
| `player-controller` | `projectController(position, recipient)` y `projectControllerEvent(...)` | Móvil `TVBoard`: etapa, contexto necesario para actuar, datos privados de su plaza y eventos pertinentes al mando. No se envía todo el tablero común para que el móvil lo descarte. |
|
||
|
|
| `public-board` | `projectSharedDisplay(position)` y `projectSharedEvent(...)` | TV común o personal: desarrollo público, fase, marcador, resultados y efectos públicos. Sin secretos, chat, perfiles ni comandos. |
|
||
|
|
|
||
|
|
Todas las funciones de proyección son puras y tienen contratos versionados en `rules.projections`. `availableActions(position, recipient)` sigue siendo la autoridad de ofertas personales para `player-full` y `player-controller`; las acciones habilitadas y sus precondiciones conservan la misma semántica en ambas. El mando no habilita un movimiento distinto por disponer de menos datos de presentación. Para una pantalla `public-board` las ofertas son siempre vacías y no se autoriza ejecutar comandos. Un juego no declara `TVBoard` sin ambas proyecciones, esquemas, entradas y pruebas.
|
||
|
|
|
||
|
|
La salida compartida no se obtiene de `project(position, { kind: "observer" })` ni de copiar la vista de un anfitrión. El observador de v1 representaba una eventual persona espectadora y no establecía la política de una pantalla accesible a presentes. Tampoco se calcula simplemente como intersección JSON de vistas personales: claves opacas, tiempos y metadatos podrían seguir filtrando información. Se exige una proyección positiva que incluya solo campos explícitamente públicos.
|
||
|
|
|
||
|
|
Una pantalla no modifica `Position`, `Flow`, `actorRevisions`, asientos ni aleatoriedad. El resultado de dados, cartas y ruletas lo determina el servidor; el gesto físico solo solicita una acción. Con la misma posición, acción y traza de azar, los efectos de reglas son iguales con independencia de si se invocó mediante toque, teclado o movimiento.
|
||
|
|
|
||
|
|
## 4. Mensajes de partida v2
|
||
|
|
|
||
|
|
`game.sync` solicita `surface: "player" | "shared-display"` y `presentationProfile: "TVBoard" | "Desktop" | "Mobil"`, y declara la tupla exacta de reglas, proyección, versiones de vista/eventos y presentación de esa combinación. El servidor deriva `projection` de la combinación publicada y valida el decodificador declarado por el cliente; no acepta una proyección arbitraria como permiso. El snapshot identifica el `modeId` fijado en la sala y el perfil/proyección aceptados; un cliente no propone el modo como autoridad. El servidor deriva el asiento del contexto autenticado `player`, o la sala, alcance y permiso de lectura del vínculo de pantalla. En `displayScope: participant` revalida también la membresía propietaria; ese vínculo no entrega su vista privada a la TV. Perfil y superficie nunca conceden acceso por sí mismos. Se rechazan perfiles ausentes/incompatibles y, por ejemplo, `Desktop` con `shared-display`, antes de crear suscripción. El cliente no envía `actor`, datos de estado ni `displayId` como autoridad. El contexto de pantalla negocia además un binding de plataforma v2 restringido, separado del `platform.*` v1 de cuentas.
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"protocolVersion": 2,
|
||
|
|
"type": "game.sync",
|
||
|
|
"matchId": "cc749a64-c8e4-4d74-a913-54b39f4c9089",
|
||
|
|
"requestId": "226b3650-0abd-43ae-b49a-11db88bfb6e2",
|
||
|
|
"surface": "shared-display",
|
||
|
|
"presentationProfile": "TVBoard",
|
||
|
|
"knownRevision": null,
|
||
|
|
"client": {
|
||
|
|
"engineApiVersion": 2,
|
||
|
|
"presentationApiVersion": 2,
|
||
|
|
"clientRuntime": "web",
|
||
|
|
"presentations": [{
|
||
|
|
"id": "preguntas-equipo",
|
||
|
|
"rulesVersion": "1.0.0",
|
||
|
|
"surface": "shared-display",
|
||
|
|
"presentationProfile": "TVBoard",
|
||
|
|
"projection": "public-board",
|
||
|
|
"rendererId": "preguntas-equipo.web.tv-board",
|
||
|
|
"rendererVersion": "1.0.0",
|
||
|
|
"rendererApiVersion": 2,
|
||
|
|
"viewSchemaVersion": 1,
|
||
|
|
"eventSchemaVersion": 1
|
||
|
|
}]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
`game.snapshot` devuelve `surface`, `presentationProfile`, `projection`, `subscriptionId`, revisión, estado, `view` y eventos de **esa** proyección, junto a su tupla de contrato. Para `shared-display` identifica también el `displayScope` derivado del vínculo; `context` solo contiene datos públicos y `availableActions` es siempre `[]`. El servidor rechaza cualquier `game.command` o `game.command-status` de esa sesión. `Desktop`/`Mobil` reciben la vista `player-full` y el mando `TVBoard` recibe `player-controller` de su propio asiento; cambiar la forma visual no expone el estado global. Se mantienen las barreras de sincronización, cursores, detección de huecos e idempotencia del protocolo v1. Al reconectar una pantalla se entrega una instantánea actual y se omiten animaciones históricas; no se reproducen sonidos o efectos antiguos. Una revocación cierra la suscripción y borra la proyección local.
|
||
|
|
|
||
|
|
`game.command` personal conserva `commandId`, precondición y `action`, y añade `controlGeneration` en v2. No incluye un campo confiable de «se agitó» ni aceleraciones. El servidor valida asiento, mando activo/generación, turno, oferta, esquema y cuota; responde con el mismo recibo tanto si el origen fue botón como gesto. Busca primero el recibo propio ya confirmado: recuperar una jugada no exige conservar el mando. El estado pendiente aparece en el móvil hasta el acuse; la pantalla anima solo después de la revisión confirmada. Ninguna animación ni temporizador local decide el resultado.
|
||
|
|
|
||
|
|
Los eventos visibles de cada superficie llevan la revisión confirmada y una referencia de evento para deduplicar; la presentación puede escalonar introducción, transición y reposo, pero nunca retrasa la autorización ni convierte un efecto en estado de partida. El momento de revelar una respuesta, carta o escena pública lo decide la transición de reglas y su proyección, no la duración de una animación local. Un cliente que entra tarde o reconecta toma la vista actual y puede omitir efectos anteriores sin perder información necesaria para jugar.
|
||
|
|
|
||
|
|
### Distribución de mensajes y vínculo entre dispositivos
|
||
|
|
|
||
|
|
El servidor mantiene un registro duradero de vínculos: pantalla, sala, finalidad/alcance, membresía propietaria cuando sea personal, generación, caducidad y revocación. El móvil conserva su sesión humana y membresía; un vínculo de mando no transmite sus credenciales a la TV ni permite que esta lo impersonifique. Una TV de mesa se vincula a la sala y acompaña a varios mandos; una TV personal se vincula al acceso de una sola membresía. Los campos de destino se derivan de esos registros, no de un `targetDeviceId`, `actor` o `displayScope` enviado en una jugada.
|
||
|
|
|
||
|
|
| Conexión | Contrato de salida | Puede enviar acciones |
|
||
|
|
| --- | --- | --- |
|
||
|
|
| Jugador `Desktop` o `Mobil` | `surface: player`, `projection: player-full`, perfil seleccionado y contrato personal. | Solo si es el mando activo de su asiento, con generación vigente. |
|
||
|
|
| Móvil mando `TVBoard` | `surface: player`, `projection: player-controller`, perfil `TVBoard` y contrato del mando. | Solo como mando activo del mismo asiento humano; no representa otro jugador. |
|
||
|
|
| TV de mesa presencial | `surface: shared-display`, `projection: public-board`, perfil `TVBoard`, `displayScope: room`. | No. |
|
||
|
|
| TV personal del invitado | `surface: shared-display`, `projection: public-board`, perfil `TVBoard`, `displayScope: participant`. | No; su entrega depende también de la membresía propietaria. |
|
||
|
|
|
||
|
|
Después de una acción confirmada se distribuye la revisión por **suscripción autorizada y contrato**, no mediante un broadcast del estado completo a la sala. Desde la misma transición se construyen y validan las proyecciones necesarias: cada plaza recibe su vista/acciones propias y cada TV la vista pública. El recibo se dirige al principal que emitió el comando; la TV se actualiza por su snapshot, no recibe recibos personales ni manos privadas. Un agente virtual usa el servicio interno y su propia vista autorizada, sin pantalla, perfil visual o enlace de mando.
|
||
|
|
|
||
|
|
Estado, recibo y outbox se confirman juntos. La outbox conserva las salidas autorizadas de esa revisión, sus contratos y destinos internos, sin reconstruir después un estado nuevo etiquetado con una revisión antigua. Antes de entregar se revalidan permisos, generación del vínculo, identidad de la suscripción y contrato seleccionado. Un cambio de perfil cierra la suscripción anterior y crea otra mediante la barrera de sincronización: las entregas pendientes del contrato anterior se descartan para esa suscripción y el cliente recibe un snapshot actual del nuevo contrato. No mezcla eventos del mando con los de vista completa ni reproduce secretos desde caché.
|
||
|
|
|
||
|
|
La autorización final de entrega aplica también el mando activo y la fuente de estímulo fijada por ronda. Las ofertas de reglas son necesarias pero no suficientes: solo la conexión con `context.control.canSend: true` recibe ofertas ejecutables. Otras conexiones del mismo principal conservan consultas de recibos y lecturas permitidas, sin robar el mando con `game.sync`. Durante reacción no reciben el estímulo por una ruta alternativa a la fuente congelada; si no existe una proyección segura, se rechaza esa suscripción hasta el punto permitido. Cambios de mando/fuente invalidan entregas pendientes y exigen una barrera nueva, no reetiquetar una salida antigua con otra generación.
|
||
|
|
|
||
|
|
Las referencias de evento son estables por proyección/destinatario/contrato y revisión, con orden local; nunca se revela el índice interno de un evento oculto. Todas las suscripciones de una partida comparten la revisión autoritativa y detectan huecos, aunque reciban contenidos distintos. El cliente valida `subscriptionId`, perfil, proyección y versión antes de aplicar un mensaje. La identidad de recibos sigue siendo partida + principal autorizado + `commandId`, independiente de móvil, TV, perfil o conexión: cambiar presentación o reconectar no repite una jugada ya confirmada.
|
||
|
|
|
||
|
|
La revisión global y el ritmo de entrega pueden revelar **que hubo actividad**, aunque no su contenido. Este perfil no promete ocultar esos metadatos. Un juego cuyas reglas requieran ocultar incluso la existencia o el número de acciones necesita otro contrato probado de cursores/agrupación por proyección antes de publicarse; borrar campos privados por sí solo no cumple esa garantía.
|
||
|
|
|
||
|
|
Los mensajes de preparación/QR pertenecen al protocolo de plataforma. El QR de altas solo puede generar membresías por la ruta `screen-qr`; el QR del mando personal solo continúa una admisión convencional y vincula dispositivos. Ninguno es un `game.command`, una oferta del motor ni permiso para suscribirse a otra proyección. Las rutas/bindings y esquemas v2 deberán materializar estas distinciones; el protocolo v1 permanece sin cambios.
|
||
|
|
|
||
|
|
`game.sync.client` declara además `clientRuntime` y, en cada tupla, `rendererId`, `rendererVersion` y `rendererApiVersion`. El servidor comprueba esa implementación publicada antes de entregar vista y devuelve la tupla aceptada. Web y Flutter reciben el mismo contrato semántico y envían `game.command` por la conexión autenticada al servidor; los controles no envían mensajes directamente a la TV. La [comunicación bidireccional](../platform/client-communication-profile.md#5-acciones-e-información-en-ambos-sentidos) concreta el envío, recibo, actualización y recuperación por UUID. Una app sin renderer compatible no obtiene datos para improvisar otra presentación.
|
||
|
|
|
||
|
|
El [contrato temporal v2 propuesto](synchronization-profile.md) añade sincronización de reloj y rondas a esta distribución. Todos los destinos comparten `clockEpoch`, `roundId`, generación y tiempos autoritativos, pero reciben solo el contenido de su proyección y fuente autorizada. `game.snapshot.context.timing` representa el estado actual; `round.*` son eventos de ese snapshot y `time.*` mensajes técnicos. Preparación, revelación, recogida y resolución son fases distintas; el orden de llegada de un paquete o de un commit no define por sí solo quién respondió primero. Los acuses de reloj/preparación de una TV no le conceden acciones de juego. Una transición de perfil conserva el reloj de partida y cambia la suscripción, no la identidad del comando. En una ronda temporizada sin resolver prevalece la política de incidentes temporales sobre `sharedDisplay.onLoss`; no se continúa una carrera sin su fuente fijada.
|
||
|
|
|
||
|
|
## 5. Vinculación de pantalla a sala
|
||
|
|
|
||
|
|
El alta de pantalla es un flujo de **plataforma**, no una acción de juego. Una TV sin cuenta abre una página HTTPS de emparejamiento y obtiene un desafío de un solo uso, caducidad corta y código legible/QR. No recibe `roomId`, nombres ni partida antes de vincularse. Un anfitrión o persona con permiso explícito introduce o escanea el código desde su sesión de jugador, elige la sala y confirma el vínculo. El servidor crea una sesión de pantalla limitada a esa sala y la TV muestra un estado de conexión reconocible. Código y QR no contienen credenciales permanentes; se limitan intentos, se excluyen de logs y el desafío se consume atómicamente.
|
||
|
|
|
||
|
|
Las [rutas propuestas](../platform/api-and-events.md#pantallas-compartidas-extensión-futura-del-perfil-multidispositivo) concretan desafío, consulta, vinculación, listado y revocación. Todas las mutaciones aplican idempotencia y revalidación de permisos; el adaptador web exige origen/CSRF y el nativo su sesión emitida por plataforma, según el [perfil de comunicación](../platform/client-communication-profile.md). La ausencia de `Origin` no autoriza cookies ni convierte un navegador en app. La sesión de pantalla no se convierte en cuenta, invitación, membresía, presencia, chat ni dispositivo de administración. Al cerrar sala, caducar el vínculo o perder la autorización responsable en una sala protegida se anula su acceso; el código de emparejamiento no puede reusarse. La política propuesta limita a una pantalla común por sala y una pantalla personal por membresía humana en modos compatibles, con cuotas globales propias; las suscripciones conservan el mismo alcance de lectura restringido.
|
||
|
|
|
||
|
|
La TV no debe almacenar la sesión para partidas futuras. La pantalla de espera, error o reconexión elimina datos de la sala tras revocación y no expone información privada a través de notificaciones del navegador, URL, caché o historial. En salas protegidas la proyección omite alias/avatares identificables, chat, perfiles y texto libre; el anfitrión responsable debe iniciar expresamente la pantalla. Estas condiciones se comprueban en servidor y en revisión de publicación de cada juego, no solo con CSS.
|
||
|
|
|
||
|
|
Una pantalla personal requiere petición expresa del jugador propietario y, en sala protegida, autorización vigente del responsable. La retirada, pérdida de acceso o cambio de identidad de ese jugador revoca su TV, además de los motivos generales. Vincular una TV personal no permite gestionar la pantalla común ni la mesa; el servidor deriva la membresía de quien confirma, sin aceptar un asiento elegido por la pantalla.
|
||
|
|
|
||
|
|
### Creación y entrada por QR en TVBoard
|
||
|
|
|
||
|
|
En una mesa presencial con `entryMethod: screen-qr`, el QR visible en la TV identifica **el juego/versiones elegidos y una mesa concreta**, no una ficha genérica del catálogo. Abrirlo lleva al flujo de entrada y después al mando `TVBoard`. No se crean registros `Invitación`, no se envían invitaciones individuales ni se habilitan sus operaciones en esa mesa. `entryMethod` es distinto de `admissionMode: individual|adult|protected`: escanear no acredita identidad, tutela o permiso para jugar.
|
||
|
|
|
||
|
|
La selección puede empezar en la TV con contenido público apto: elegir juego y `TVBoard` prepara un contexto temporal sin sala activa, membresías ni autoridad de juego. La TV muestra un QR de preparación con esa selección. Una persona habilitada lo escanea, se autentica si procede y confirma expresamente crear/gestionar la mesa y su política. El servidor crea la sala `waiting`, su membresía de anfitrión, el vínculo de pantalla y el acceso QR de jugadores en una transacción. En salas protegidas esa persona debe ser el responsable adulto autorizado. Conectarse primero no concede anfitrión sin esta confirmación y comprobación de permisos.
|
||
|
|
|
||
|
|
Una vez creada, la TV sustituye el QR de preparación por el **QR de entrada a la mesa**. Las demás personas lo escanean y solicitan plaza mediante el flujo específico de QR, con sesión y comprobaciones actuales. La preparación no consume plazas, no recibe estado privado y caduca si nadie autorizado confirma. También puede prepararse la mesa desde el móvil del anfitrión y vincular después la TV; la entrada de los demás sigue siendo el QR visible en pantalla. Son fases del mismo flujo de producto, no invitaciones.
|
||
|
|
|
||
|
|
El acceso QR usa un token opaco de finalidad `tvboard-entry`, impredecible, con revisión/generación, caducidad, revocación y límites de intentos/uso. Está ligado a una sala, al contexto de pantalla autorizado y a su paquete; no reutiliza el desafío de vinculación de un solo uso ni tokens de cuenta/invitación. La ruta propuesta es `/join#tvboard=TOKEN`; el token se pasa a memoria y por POST, con `no-store`, `no-referrer` y cuerpos excluidos de logs/analítica. La ficha de resolución presenta solo el juego público apto y requisitos de entrada permitidos, sin participantes ni secretos. El QR contiene una referencia verificable; el juego anunciado lo determina el servidor, no datos editables de la URL.
|
||
|
|
|
||
|
|
El QR de entrada admite a varias personas durante la espera, hasta las plazas humanas disponibles, descontando las virtuales configuradas. Cada alta, consumo de uso, validación de token/generación, capacidad, bloqueos, protección y estado se confirma bajo la misma unidad de trabajo. Reintentar desde un miembro actual devuelve su membresía sin ocupar otra plaza. Se proponen vencimiento máximo de 5 minutos y rotación autorizada al caducar; el valor final y cuotas deben fijarse en los contratos ejecutables. Mostrar el QR requiere una copia cifrada recuperable solo por el contexto autorizado mientras siga vigente; guardar únicamente el hash no permite volver a representarlo.
|
||
|
|
|
||
|
|
La mesa se crea privada, fuera del listado de descubrimiento. Su QR queda inutilizable para nuevas altas al iniciar, cerrar, revocar/expirar la pantalla o cambiar anfitrión; un QR nuevo necesita autorización vigente. Tras transferir anfitrión en espera se revalida el vínculo de TV y se rota el acceso bajo bloqueo. Quien ya participa recupera su plaza por sesión/membresía, sin necesitar un QR válido ni recibir otra invitación. Una revancha prepara otra mesa y otro QR, con entrada explícita; no incorpora automáticamente a participantes anteriores.
|
||
|
|
|
||
|
|
El anfitrión confirma capacidad y opciones; cada persona confirma «listo» en el móvil y la plataforma inicia solo al cumplir las condiciones del juego, la protección y la pantalla requerida. La TV muestra el juego, QR y progreso agregado apto de preparación; durante la partida recibe únicamente la proyección pública. Un agente virtual ocupa su plaza desde servidor y no escanea un QR.
|
||
|
|
|
||
|
|
### Acceso con invitación desde Smart TV y móvil como mando
|
||
|
|
|
||
|
|
Un jugador puede abrir la plataforma en su Smart TV e introducir la invitación recibida para una sala convencional `standard` cuyo juego/modo permita `TVBoard`. La plataforma resuelve una ficha apta y prepara un enlace temporal entre ese contexto de TV y el futuro mando. Introducir la invitación no crea otra mesa, no ocupa otra plaza y no autentica una cuenta en la TV.
|
||
|
|
|
||
|
|
La TV muestra un **QR personal de enlace del mando**, distinto del QR de altas de una mesa presencial. El móvil lo escanea, recupera el flujo pendiente, autentica al jugador si procede y le pide confirmar el acceso y el uso de esa pantalla. El servidor revalida/consume la invitación al admitirlo, crea o recupera una sola membresía y vincula la TV a ella en una transacción. Una invitación agotada o revocada no se renueva por preparar el QR; los reintentos recuperan el mismo recibo/membresía y no consumen usos adicionales.
|
||
|
|
|
||
|
|
El desafío de enlace es de un solo uso, de caducidad corta y ligado al contexto que lo emitió, con finalidad separada del acceso `tvboard-entry`. No contiene credenciales humanas ni el token de invitación en claro: el servidor conserva el contexto pendiente cifrado con retención limitada. El QR permite continuar ese flujo tras autenticar y confirmar, no concede acceso a otra persona por sí solo. Se aplican fragmento de URL, POST, autenticación según adaptador (CSRF/origen en web), límites, `no-store`, ausencia de terceros y recuperación cifrada acotada igual que en la vinculación general.
|
||
|
|
|
||
|
|
Tras confirmar, la TV recibe la proyección pública del estado y desarrollo del juego; el móvil carga los controles de cada etapa y la información privada del asiento. Todos los comandos parten del contexto autenticado del móvil. La pantalla mantiene `surface: shared-display`, `presentationProfile: TVBoard`, `displayScope: participant`, sin cuenta, acciones ni rol de anfitrión. Otros participantes de esa misma sala pueden usar `Desktop`, `Mobil` o su propia TV si el paquete lo declara. Una segunda pantalla personal reemplaza la anterior mediante confirmación autorizada, sin duplicar plaza ni conservar dos vínculos ocultos.
|
||
|
|
|
||
|
|
Un fallo de vinculación revierte la admisión coordinada si aún no se confirmó; si el cliente perdió la respuesta después del commit, consulta/reintenta el mismo comando. Para un miembro ya admitido el enlace solo vincula su TV y no vuelve a consumir la invitación. Al reconectar se recupera la misma plaza por su sesión y la pantalla por su contexto acotado, sin necesitar otra invitación. El QR del mando se inutiliza al consumirse o caducar; se revalidan permisos también al ejecutar tareas y entregar vistas. La protección, incluida la aprobación del responsable para la TV personal, se mantiene en este recorrido.
|
||
|
|
|
||
|
|
## 6. Móvil como mando y sensores
|
||
|
|
|
||
|
|
En un modo de mesa, la superficie personal funciona como mando y espacio privado. Su presentación pertenece al paquete de juego y adapta controles a fase, rol y ofertas confirmadas: espera, selección, turno, acción pendiente y resultado. Las etiquetas describen acciones («Jugar carta», «Elegir columna», «Lanzar dado»), con áreas táctiles amplias y alternativas accesibles. La TV muestra el estado público a distancia; cartas y roles secretos solo se entregan al asiento autorizado. La dirección estética sigue pendiente.
|
||
|
|
|
||
|
|
Una pulsación puede recibir feedback local inmediato y mostrar «pendiente», pero el tablero compartido solo cambia con la transición confirmada. Cambiar la vista del mando no crea permisos: las acciones proceden de las ofertas del servidor. Una vista confirmada identifica a qué plaza corresponde la sesión, con alias/presets seguros según protección. Perder conexión, bloquear el teléfono o volver al navegador exige resincronización antes de admitir una nueva acción; no reorganiza asientos ni añade un participante nuevo.
|
||
|
|
|
||
|
|
`motion.shake` es un detector local de intención mientras una oferta compatible está visible y la persona ha activado «Agitar para lanzar». La aplicación solicita permiso **solo tras esa acción**, usa HTTPS y comprueba soporte real. [Device Orientation and Motion del W3C](https://www.w3.org/TR/orientation-event/) define contexto seguro, permiso explícito y dependencia de una activación del usuario; la compatibilidad concreta sigue variando. La medición se detiene al salir de la vista, perder foco, terminar el turno o revocar permiso. No se envían ni conservan muestras crudas de acelerómetro/giroscopio.
|
||
|
|
|
||
|
|
El adaptador de plataforma aplica umbral calibrado y acotado, histéresis, una activación por oferta, enfriamiento y rechazo de eventos cuando la app está oculta. Una sacudida accidental no debe lanzar dos comandos. El gesto puede producir una animación local del cubilete, pero el dado que se muestra al final procede del recibo/estado confirmado por servidor. Siempre hay un botón y operación de teclado equivalentes; se respeta movimiento reducido, y háptica/sonido son opcionales. Un juego de habilidad basado en sensores requeriría reglas y garantías propias, no se infiere de este perfil de dados.
|
||
|
|
|
||
|
|
## 7. Compatibilidad y conformidad
|
||
|
|
|
||
|
|
### Gráficos y requisitos por juego
|
||
|
|
|
||
|
|
**El alcance decidido por el usuario es 2D, incluida perspectiva, con soporte de sprites y animaciones sencillas; no se contempla un motor 3D ni Three.js, iluminación o efectos gráficos avanzados. WebGL no es un requisito global de la plataforma ni del protocolo.** Tras considerar Phaser, el usuario cuestiona su peso y pide comparar Konva con Motion y su biblioteca propia de `vicen`: la prioridad es reducir dependencias y consumo, conservando imágenes/sprites, capas, reproducción por fotogramas, movimientos y secuencias coordinadas. Antes de incorporar otra dependencia, evaluar las capacidades ya disponibles en el motor propio; **Konva queda como candidato condicionado a necesitar dibujo y gestión de objetos Canvas**, y Phaser como alternativa de mayor alcance. Esta evaluación no constituye una selección o integración aprobada. HTML/SVG/CSS pueden representar el tablero junto con una capa de animación/sprites; no se exige Canvas por ser un juego. La demo aislada de PixiJS no aprueba dependencias ni demuestra soporte en TV.
|
||
|
|
|
||
|
|
**Inspección del código de `vicen`, 6 de octubre:** `src/libs/motion` contiene tipos y resolución de preferencias (`allow`/`reduce`/`system`); el motor está en `src/arts/motion`. `EngineMotion` registra presets y ejecuta sobre `HTMLElement` los drivers `waapi`, `spring` y `rect` (FLIP), con handles `finished`, cancelación/`AbortSignal`, traspaso de posición/velocidad entre muelles y preferencia reducida leída por ejecución. Los presets CSS dependen de generación/aplicación declarativa externa; no son animaciones ejecutadas por el motor. No se encontró un gestor dedicado de spritesheets, objetos/capas Canvas o timeline general en esos módulos. Las capacidades de [Motion para JavaScript](https://motion.dev/docs/animate) incluyen animación de HTML/SVG, valores/objetos y secuencias; animar valores de un objeto no implementa por sí mismo un renderer Canvas. Konva aporta ese renderer, su árbol de objetos/capas y detección de eventos dentro de Canvas, además de sprites por fotogramas. Para cartas/fichas DOM, empezar por una prueba con el motor propio y un controlador de fotogramas solo si se necesita; adaptar SVG explícitamente, ya que el contrato actual usa `HTMLElement`. Reutilizar requiere concretar exportación/aliases, puerto DOM y presets, sin asumir que todo UIX deba importarse.
|
||
|
|
|
||
|
|
La revisión identifica una limitación temporal del driver propio `spring`: integra un paso fijo `1/60` por callback de `requestFrame` sin usar tiempo transcurrido; el puerto DOM existente delega en `requestAnimationFrame`. La animación puede cambiar de ritmo con la cadencia de pantalla o frames perdidos. **El usuario propone añadir una función nueva para no interferir en la actual:** se recomienda `springTimed(config: SpringConfig): MotionRun`, conservando `spring()` y los presets actuales. La nueva función usaría timestamps, acumulador y pasos físicos constantes con trabajo acotado; conservaría `finished`, cancelación/`AbortSignal`, movimiento reducido y traspaso de velocidad. Se registraría como preset de la familia `spring` con una función `MotionRun` distinta, sin cambiar por inferencia `EngineMotion` o el driver existente. Probar a 30/60/120 Hz, con jitter, interrupciones, cancelación e inversión, antes de una migración optativa. Para el juego, el coordinador recupera el estado confirmado tras una interrupción larga; el reloj visual no sustituye la autoridad temporal del servidor. **Es una propuesta pendiente, no una función implementada.** La inspección fue de solo lectura: no se modificó `vicen`, no se ejecutaron sus tests y no se probó el motor propio en TV/Cast.
|
||
|
|
|
||
|
|
**Versiones aclaradas por el usuario y comprobadas el 6 de octubre de 2026:** **Phaser 4** es el motor; el [archivo oficial](https://phaser.io/download/archive) y una consulta directa al [registro npm](https://registry.npmjs.org/phaser) devuelven **4.2.1** como `latest`. [Phaser Editor 5](https://phaser.io/news/2026/04/phaser-editor-v5-release) es la herramienta de autoría visual compatible con ese motor. El editor se evalúa por separado como herramienta de trabajo: no es una dependencia obligatoria del juego, no cambia los requisitos del cliente publicado ni resuelve por sí mismo la compatibilidad Cast. Si se prueba Phaser, fijar una versión verificable de la v4; esta aclaración no modifica la prioridad de evaluar las capacidades propias existentes.
|
||
|
|
|
||
|
|
| Capa propuesta | Responsabilidad |
|
||
|
|
| --- | --- |
|
||
|
|
| Presentación DOM, con el motor propio como candidato a reutilización | Svelte/HTML representa fichas/cartas/tablero; presets y drivers del motor animan elementos. Evaluar movimientos, cancelación/recuperación y controlador de fotogramas si hace falta. No se asume portabilidad automática de `vicen` ni se promete rendimiento sin prueba. |
|
||
|
|
| Biblioteca Canvas 2D opcional, con Konva como candidato | Si una escena justifica Canvas: capas/grupos, imágenes, sprites por fotogramas, transformaciones y tweens. La carga/organización de recursos y la coordinación con el juego se evalúan en la prueba; no se asume un sistema completo de paquetes de juego o importación automática de atlas. |
|
||
|
|
| Svelte + HTML/CSS/SVG | Estructura de la plataforma, controles, etiquetas, accesibilidad y elementos de interfaz; no necesita ejecutar un motor gráfico en cada mando. |
|
||
|
|
| Adaptador del coordinador propio de presentación | Traduce eventos confirmados a efectos de la biblioteca elegida, limita colas/duración, cancela y libera recursos al cambiar de generación; aplica movimiento reducido y recuperación. Reutiliza los sistemas de sprites/animación; conserva las barreras del protocolo, sin decidir reglas, azar, recibos o plazos de servidor. |
|
||
|
|
|
||
|
|
La documentación oficial de Konva confirma [Canvas 2D, capas y transformaciones](https://konvajs.org/docs/overview.html), [sprites por fotogramas](https://konvajs.org/docs/shapes/Sprite.html) y [tweens](https://konvajs.org/docs/tweens/Linear_Easing.html). Si una escena justifica Canvas, permite evaluar estas funciones sin exigir WebGL ni añadir GSAP/PixiJS. Reutilizar los sistemas disponibles de dibujo y animación; no construir un renderer general por inferencia. El adaptador debe respetar los contratos del [coordinador visual](../game-engine-proposal.md#13-presentación-antes-durante-y-después): callbacks ligados a generación/`AbortSignal`, limpieza y asentamiento en la proyección vigente. No se reescriben ahora los contratos v1 ni se introducen objetos de la biblioteca en mensajes JSON. Go conserva reglas, validación, azar y sincronización autoritativos; la app Flutter sigue siendo solo de controles.
|
||
|
|
|
||
|
|
**Ligereza es un criterio que se mide, no una garantía derivada de usar Canvas.** Comparar bundle de producción comprimido, tiempo hasta el primer dibujo, memoria, fluidez y actividad en reposo con las mismas escenas/recursos; separar el coste de imágenes del JavaScript. La primera prueba debe incluir tablero estático, movimiento/reparto de fichas o cartas, un sprite animado, perspectiva 2D, cancelación/recuperación y movimiento reducido. Revisar el [coste de capas y detección de eventos en Konva](https://konvajs.org/docs/performance/All_Performance_Tips.html): evitar capas innecesarias, desactivar escucha en el tablero público pasivo y detener animaciones cuando la vista esté estática u oculta. El soporte Canvas no acredita por sí solo compatibilidad con navegadores antiguos o dispositivos de TV/Cast; comprobar bundle, APIs y rendimiento en cada vía real.
|
||
|
|
|
||
|
|
Phaser 4 permanece como alternativa si aparecen necesidades que justifiquen un motor más completo. Sus sistemas de [animaciones](https://docs.phaser.io/phaser/concepts/animations), [tweens](https://docs.phaser.io/phaser/concepts/tweens) y [escenas](https://docs.phaser.io/phaser/concepts/scenes) reúnen esas capacidades, pero no se prioriza su integración en el alcance sencillo actual. La [revisión oficial de su renderer](https://phaser.io/news/2026/04/phaser-4-renderer-faster-cleaner-and-built-for-modern-games) indica que Canvas se conserva **deprecado** y que las funciones gráficas nuevas dependen de WebGL. Si se vuelve a evaluar para Cast directo, probar expresamente sprites, texto, orden de capas, transformaciones y tweens sin WebGL; no prometer equivalencia de filtros/shaders ni soporte futuro de Canvas por existir hoy.
|
||
|
|
|
||
|
|
La perspectiva visual se resuelve en la presentación 2D: disposición isométrica o de mesa, escala, orden de superposición, sombras e imágenes/transformaciones de fichas y cartas. No implica cámaras, geometría o iluminación de un motor 3D. Las coordenadas/identificadores lógicos del tablero y las acciones semánticas siguen siendo independientes de su dibujo; si hay interacción sobre una vista transformada, el renderer convierte la selección a la casilla/acción correspondiente. La perspectiva no cambia qué información está autorizada a mostrar cada proyección.
|
||
|
|
|
||
|
|
Cada renderer debe publicar sus requisitos gráficos y ofrecer únicamente combinaciones comprobadas. Un juego que necesite WebGL puede ofrecer una presentación equivalente sin WebGL si la implementa y prueba. Se comprueban el bundle/versiones y las funciones concretas antes de prometer fallback: atlas, sprites animados, texto, máscaras, perspectiva y secuencias, sin asumir que filtros/shaders tienen equivalente. No hay conversión automática a SVG. Cambiar renderer conserva proyección, reglas, resultados y barreras temporales. Un fallo de contexto gráfico requiere recuperación o alternativa explícita, sin dejar una pantalla obsoleta aparentando estar en directo. Estos requisitos/alternativas necesitan esquemas y negociación antes de habilitarse.
|
||
|
|
|
||
|
|
### Televisores y dispositivos externos
|
||
|
|
|
||
|
|
El usuario requiere contemplar **TV no smart con Chromecast, Fire TV u otro dispositivo conectado por HDMI**. La TV física puede ser solo la pantalla: el navegador, receptor Cast o aplicación del dispositivo conectado ejecuta el renderer y mantiene la conexión. Este eje de acceso es distinto de `presentationProfile`, `clientRuntime`, proyección y biblioteca gráfica. Elegirlo no crea otra plaza, cambia el modo de sala ni convierte un enlace personal en entrada de mesa.
|
||
|
|
|
||
|
|
| Vía de acceso prevista | Cliente que ejecuta la pantalla | Trabajo pendiente |
|
||
|
|
| --- | --- | --- |
|
||
|
|
| Navegador de Smart TV | Web `public-board` en el motor del modelo concreto. | Compatibilidad, navegación con mando, QR, memoria, suspensión y reconexión. |
|
||
|
|
| Fire TV con Silk | Web `public-board` en Silk; Amazon documenta [Silk para Fire TV](https://docs.aws.amazon.com/silk/latest/developerguide/what-is-silk.html). | Prueba por modelo/versión; una app de TV posterior es una entrega distinta. |
|
||
|
|
| Google TV / Android TV, incluido Chromecast con Google TV | Aplicación de TV futura con renderer compatible; navegador solo donde se verifique. | Instalación, mando/foco, ciclo de vida y adaptación de sesión de pantalla. No es la app Flutter de controles. |
|
||
|
|
| Chromecast clásico mediante Google Cast | [Custom Web Receiver](https://developers.google.com/cast/docs/web_receiver/basic) HTML5 propio, alojado por HTTPS y registrado con App ID, iniciado por un emisor compatible. | Renderer sin WebGL, SDK emisor/receptor, registro, vinculación y pruebas reales. No basta enviar la URL de la plataforma a un receptor multimedia genérico. |
|
||
|
|
| Duplicación de una pestaña pública hacia Chromecast | El navegador de origen ejecuta `public-board`; el dispositivo presenta su imagen. | Compatibilidad y demora de la ruta completa; solo duplicar la página dedicada de tablero público. |
|
||
|
|
| Ordenador conectado por HDMI | Web `public-board` en el navegador del ordenador. | Pantalla completa, audio, resolución y recuperación en equipos reales. |
|
||
|
|
|
||
|
|
El receptor Cast inicia su propio contexto de preparación/vinculación de pantalla y muestra el QR/código correspondiente. El emisor permite lanzarlo y continuar la confirmación autorizada; no reenvía cookies/tokens de cuenta, vistas privadas o comandos de juego. Tras vincular, el receptor obtiene directamente del servidor Go su `public-board` por el binding HTTPS/WSS restringido; el canal Cast se limita al lanzamiento/vinculación y gestión de esa pantalla. App ID, descubrimiento o pertenecer a la misma WiFi no acreditan permisos de sala. Las rutas y el adaptador de sesión/Origin del receptor siguen pendientes; se conserva la restricción de que omitir `Origin` no autoriza una cookie web.
|
||
|
|
|
||
|
|
La [guía oficial de restricciones de Google Cast](https://developers.google.com/cast/docs/ux_guidelines#considerations), consultada el 6 de octubre de 2026 y actualizada el 24 de octubre de 2024, indica que el **Web Receiver no admite WebGL**. Por tanto, esa vía exige una presentación comprobada sin WebGL, aunque el juego sea 2D. Duplicar una pestaña es distinto: el renderer se ejecuta en el ordenador que genera la imagen y el Chromecast la presenta, con la demora de duplicación. Una app instalada en Google TV/Android TV tiene otro entorno, cuyo soporte gráfico se verifica por separado; no hereda una garantía por compartir dispositivo con Cast.
|
||
|
|
|
||
|
|
La [documentación del emisor web de Google](https://developers.google.com/cast/docs/web_sender) distingue navegadores compatibles y SDK nativos; Chrome en iOS no admite ese envío web. Se comprueba cada vía y se ofrece una alternativa compatible, sin suponer que un botón Cast web funciona en todos los móviles. Finalizar o sustituir la sesión Cast, suspender el dispositivo y perder el emisor se tratan como eventos de ciclo de vida de pantalla; no equivalen por sí solos a retirar al jugador. La pérdida de la fuente visual aplica la política del modo y de la ronda temporal. Al cambiar a `Mobil` se revoca también el acceso de su receptor personal.
|
||
|
|
|
||
|
|
La matriz de soporte incluye modelo, sistema, navegador/app/receptor y versión, renderer, memoria, resolución, audio, gráficos y HTTPS/WSS. [Samsung](https://developer.samsung.com/smarttv/develop/specifications/web-engine-specifications.html) y [LG webOS](https://webostv.developer.lge.com/develop/specifications/web-api-and-web-engine) documentan motores distintos según modelo/año. No se promete un mismo bundle para todos. Sin WebSocket compatible se ofrece un fallback de lectura con sincronización acotada **solo si** se diseña y prueba. Primero se propone verificar web/HDMI y Silk; receptor Cast y apps de TV se habilitan en cortes específicos. Ninguna de estas vías está implementada o certificada hoy.
|
||
|
|
|
||
|
|
### Pruebas de conformidad
|
||
|
|
|
||
|
|
Para aceptar un juego en este perfil se prueban: privacidad de `sharedView` y de todos sus eventos/errores; cero ofertas y comandos desde TV; autorización de vinculación/revocación con carreras y reconexión; sala protegida; varias pestañas/dispositivos del jugador; pérdida y retorno de pantalla; gesto denegado/ausente; botón alternativo; duplicados/idempotencia; resultados de azar iguales entre modalidades; idioma, sonido y movimiento reducido en todas las presentaciones. Se comprueba `TVBoard` con sus dos entradas, `Desktop`/`Mobil` con estado y controles completos, selección/cambio de perfil sin cambiar permisos ni perder comandos, y rechazo de combinaciones no declaradas. El verificador de paquetes debe rechazar soporte sin funciones, esquemas, presentación, recursos, traducciones y pruebas correspondientes.
|
||
|
|
|
||
|
|
La entrada `TVBoard` necesita además fixtures de preparación sin cuenta/sala, confirmación autorizada del anfitrión, creación/vinculación/QR atómicas, dos escaneos que compiten por la última plaza, duplicados, caducidad/rotación, tokens de otra finalidad, QR de otro juego/paquete, revocación concurrente e inicio que cierra altas. En una mesa con QR, crear o consumir una invitación se rechaza incluso por llamada directa a API. Se comprueba que QR, errores y estados de preparación no revelen personas ni contenido privado.
|
||
|
|
|
||
|
|
El recorrido de invitación desde Smart TV se prueba aparte: preparación sin consumir usos, confirmación móvil y consumo/vínculo atómicos, invitación que caduca o se revoca entre escaneo y confirmación, dos móviles que intentan consumir el mismo desafío, miembro ya admitido, respuesta perdida tras commit, varias TVs personales con aislamiento por membresía, cambio de cuenta/retirada/revocación, protección y fallback personal a `Mobil`. El QR de entrada de mesa y el de enlace del mando no se aceptan en rutas de otra finalidad.
|
||
|
|
|
||
|
|
También se prueba «Jugar solo en el móvil» por elección voluntaria con TV funcionando: nueva proyección completa, revocación de su TV, recibo pendiente recuperado y ausencia de otra plaza/uso de invitación. Un paquete que permita el acceso convencional por TV personal sin `Mobil` se rechaza. En juegos de reacción se comprueba la solicitud pendiente entre rondas, el reloj sin reinicio y la salida neutral ante pérdida de la fuente temporal.
|
||
|
|
|
||
|
|
La conformidad compara mando web y futuro Flutter: mismos contratos de `player-controller`, acciones y resultados, controles declarados, QR con/sin app, sesiones propias revocables, suspensión/reconexión y aislamiento por cuenta. Comprueba rechazo de Flutter con `player-full`/`public-board` y transferencia a `Mobil` web conservando plaza/recibos. No se presupone ejecutar el bundle web en nativo. Los SDK TypeScript/Dart comparten fixtures; una combinación Flutter ausente ofrece solo una alternativa web autorizada y comprobada.
|
||
|
|
|
||
|
|
Las pruebas de distribución comparan la misma revisión entre `player-full`, `player-controller` y `public-board`, con fixtures de esquemas y privacidad por destinatario. Se verifica que una acción privada no se filtre en otra TV/mando, que un recibo no llegue a pantalla, que mensajes tardíos de un perfil anterior se descarten y que duplicados/reconexión/cambio de dispositivo mantengan un solo efecto. Revocar el vínculo personal de una TV corta únicamente sus entregas autorizadas; revocar la membresía corta también su mando y todas las vistas que dependían de ella.
|
||
|
|
|
||
|
|
Se añade al futuro entorno de desarrollo un simulador de una pantalla común y varios mandos, con identidades de prueba separadas. Sirve para comprobar vistas, privacidad, fases y reconexión; la conformidad requiere también teléfonos y pantallas reales. Se ensayan red lenta/inestable, bloqueo y retorno del móvil, recarga de TV, dispositivos extra sin plaza, intentos de menú sin permisos y recuperación sin repetir movimientos. La UI explica cuántas plazas faltan y por qué un dispositivo no puede jugar, sin mostrar datos identificables en una sala protegida.
|
||
|
|
|
||
|
|
## 8. Referencia de interacción: AirConsole
|
||
|
|
|
||
|
|
El usuario fija [AirConsole](https://www.airconsole.com/) como referente para usar la TV como tablero y los móviles como mandos. Revisión de documentación oficial: **6 de octubre de 2026**. Es una referencia funcional para el perfil propio; no implica integrar su SDK, adoptar su alojamiento o aprobar una identidad visual. No se ha probado una sesión de su juego ni verificado compatibilidad de nuestra implementación, que sigue pendiente.
|
||
|
|
|
||
|
|
Su guía actual de [juego en TV](https://airconsole.zendesk.com/hc/en-us/articles/4405279964946-Play-on-TV) recomienda la app para Android TV, Google TV y Fire TV, contempla PC por HDMI y no garantiza los navegadores integrados de Smart TV. Su [guía de Chromecast](https://airconsole.zendesk.com/hc/en-us/articles/360015065059-Chromecast), actualizada en 2021, separa duplicación desde PC en modelos antiguos de app instalada en modelos con tienda. Es una referencia para distinguir vías, no evidencia de que AirConsole use un receptor Cast propio ni una medida de latencia válida para nuestros equipos.
|
||
|
|
|
||
|
|
Su [explicación del producto](https://www.airconsole.com/info) presenta una pantalla compartida, un móvil por jugador y vinculación mediante código. Indica que los dispositivos no necesitan la misma WiFi. Para `games2`, ese patrón requiere acceso HTTPS al servidor, autorización y pruebas de conectividad; no significa que podamos prometer juego sin Internet o soporte de todos los navegadores de TV.
|
||
|
|
|
||
|
|
| Referencia publicada | Aplicación al perfil propio |
|
||
|
|
| --- | --- |
|
||
|
|
| Las [guías del mando](https://developers.airconsole.com/smartphones-as-controllers) recomiendan controles grandes, etiquetas por función, vistas según situación e información individual. | La presentación personal del juego adapta las acciones vigentes a móvil y puede mostrar la mano privada; la TV recibe la proyección común. Se prototipa esta interacción desde el primer juego multidispositivo. |
|
||
|
|
| El [inicio rápido](https://developers.airconsole.com/quick-start) separa `screen.html` y `controller.html` y ejemplifica mensajes entre dispositivos. | Conservamos entradas de presentación diferenciadas dentro del paquete. Los móviles envían comandos al servidor Go y la TV recibe vistas confirmadas; las reglas no se trasladan al navegador de la pantalla. |
|
||
|
|
| La [guía de identidades y estado](https://developers.airconsole.com/device-ids-and-state) distingue identificadores de dispositivo y número de jugador, y advierte que los estados personalizados son legibles por otros dispositivos. | Sesión, dispositivo, membresía y asiento son conceptos separados. Se recupera el asiento autenticado tras un corte. Manos, roles y decisiones privadas nunca viajan por un estado difundido a todos. |
|
||
|
|
| Su [checklist](https://developers.airconsole.com/airconsole-checklist) incluye latencia, conexiones/desconexiones, límites de jugadores, permisos de menú y controles adaptados. | Se amplían los escenarios de aceptación sin copiar sus mínimos de navegador o sus políticas comerciales. Menús y configuración usan permisos del anfitrión; el primer móvil conectado no obtiene ese rol por orden de llegada. |
|
||
|
|
| Su [simulador](https://developers.airconsole.com/testing-your-game) permite probar pantalla y mandos en una ventana y añadir teléfonos reales. | Se propone un simulador propio con cuentas sintéticas, más verificación real. Una URL `localhost` en el teléfono apunta al propio teléfono: las pruebas multidispositivo requieren un origen HTTPS accesible y autorizado. |
|
||
|
|
|
||
|
|
Flujo previsto de `games2`, aplicando las autorizaciones ya definidas:
|
||
|
|
|
||
|
|
1. Para crear una mesa presencial, la TV permite elegir juego público y `TVBoard` y muestra el QR de preparación, sin datos privados de partida.
|
||
|
|
2. El anfitrión autorizado confirma desde el móvil la creación de una mesa `TVBoard` para el juego seleccionado y su vínculo de TV. Modo y entrada `screen-qr` quedan fijados; la pantalla obtiene el QR de entrada de esa mesa.
|
||
|
|
3. Las demás personas escanean el QR de juego/mesa, pasan las comprobaciones de admisión, reciben su plaza y confirman «listo». No se envían ni crean invitaciones. El desafío de vinculación de TV no se reutiliza como acceso de jugadores.
|
||
|
|
4. La TV muestra tablero, estado y eventos públicos. Cada móvil muestra su información privada y controles contextuales; un agente virtual admitido actúa desde su instancia en servidor sin necesitar móvil.
|
||
|
|
5. Ante cortes, cada cliente recupera su proyección actual y los comandos conservan recibos/idempotencia. Una TV revocada vuelve a una pantalla sin datos.
|
||
|
|
|
||
|
|
En el recorrido alternativo, una persona introduce su invitación en la Smart TV y escanea el QR personal desde el móvil. La sala conserva su entrada convencional; se vinculan exclusivamente la pantalla y el mando de su plaza, sin activar el QR de altas de una mesa presencial.
|
||
|
|
|
||
|
|
La división pública/privada se define por juego: Conecta 4 muestra el tablero común y selección de columna en el móvil; Brisca muestra la mesa pública y conserva la mano en el mando. En Hundido solo se comparte lo que autoricen sus reglas, nunca posiciones ocultas; la existencia de una TV no convierte toda la partida en información pública. Estos ejemplos son criterios del perfil propuesto, no juegos ya instalados.
|