Editor de Cinemáticas#
El Editor de Cinemáticas arma listas de pasos ordenadas y no interactivas — un NPC que
camina hacia una puerta, una línea de diálogo, una espera, una escena cinemática — que se
reproducen con el input bloqueado, igual que una cinemática clásica de aventura gráfica. La
mayoría de las cinemáticas están delimitadas a una sola sala: viven en el propio archivo de
reglas de esa sala, junto a sus reacciones de hotspot, watchers y globalWatchers (el mismo
archivo que escribe el Editor de Puzzles). Una cinemática también puede vivir en el bucket
🌐 Global de todo el proyecto (rules/_global.rules.js) para momentos que no están atados a
una sola sala. Se dispara desde cualquier lugar — una reacción de hotspot, una opción de diálogo,
una regla de puzzle — con el token de efecto CUTSCENE:<id>, que resuelve el id contra las
cinemáticas de la sala actual primero y cae al bucket Global si la sala no tiene
coincidencia, y luego entrega el arreglo de pasos a startCutscene().
Llegas a él desde el Hub (con el dev server corriendo). La barra de herramientas tiene un desplegable Sala (con una entrada sintética 🌐 Global arriba), un desplegable Cinemática acotado a esa sala o bucket, y botones para Nuevo (un modal que pide el nombre), Renombrar, Duplicar, Copiar y Pegar pasos (los dos últimos funcionan entre salas y el bucket Global), Eliminar, y Guardar. Bajo la barra hay una franja de miniaturas de salas para saltar de una a otra sin volver al desplegable — el bucket 🌐 Global tiene su propia tarjeta también; viene en tres tamaños y se puede plegar, y tu elección queda recordada. El panel central muestra los pasos de la cinemática cargada en una de dos vistas intercambiables — Visual (tarjetas de nodo amigables) o Avanzado (el editor campo por campo) — y el panel derecho tiene Validar, Vista previa, y una pestaña Exportar oculta (muestra el código de reglas generado, útil para depurar un guardado).
Anatomía de un paso#
Los pasos son una lista ordenada lineal, no una línea de tiempo multi-pista — pasa
exactamente una cosa a la vez, en el orden de la lista, la misma forma que consume
core/cutscene.js. Cada fila tiene un selector de tipo, un cuerpo de campos específicos del
tipo, una insignia bloquea/instantáneo, y controles de reordenar/eliminar. El editor
expone seis tipos de paso tipados más un escape genérico:
- NPCENTER — un NPC aparece ya dentro de la sala actual en el punto de una región (la puerta por la que "entra"). Tanto NPC como Región son pickers tipados (lista de personajes / las regiones de la sala cargada). Instantáneo.
- WALKNPCTO — un NPC camina hacia un objetivo, elegido con un grupo de pestañas
jugador / hotspot / coords:
jugador(donde esté parado el jugador en ese momento), el punto de camino de un hotspot, o X/Y crudas (con un botón "⌖ Elegir X/Y en la sala" que lee un clic sobre la sala). Bloquea hasta que el NPC llega — el runtime lo hace caminar ahí durante los frames que hagan falta. Mirando hacia (opc.) es hacia dónde queda mirando el actor al llegar. Si lo dejas vacío y el objetivo es un hotspot, se usa elfacingconfigurado en ese hotspot desde el editor de salas — así, al llegar a una puerta el personaje se voltea hacia ella, igual que cuando el jugador la clickea. Vacío sobre un objetivojugador/coords deja el rumbo con el que terminó la caminata, que para una puerta a la que acabas de llegar suele ser al revés. Pon un valor explícito para pisar el del hotspot, o para apuntar un objetivo por coordenadas; están las ocho direcciones canónicas, diagonales incluidas. - NPCEXIT — un NPC camina hasta un punto de salida en la sala actual (hotspot o coords,
mismo grupo de pestañas de arriba) y sale hacia una sala destino (un picker de sala
autowired). Este no es en realidad un verbo de
core/cutscene.js— serializa al token de efectoNPCEXITy cae en el catch-all genéricoapplyEffects()del runner, así que se comporta como cualquier otro efecto instantáneo una vez despachado, no como la caminata bloqueante cuadro-a-cuadro deWALKNPCTO. - WAIT — pausa N segundos. Bloquea.
- SAY — una línea de diálogo: un desplegable NPC (vacío = narrador) y un campo de
texto. Bloquea — pero no hay que autorar ningún tiempo manual; el runtime calcula
automáticamente cuánto tiempo queda en pantalla según el largo de la línea (
sayDuration()encore/cutscene.js, entre 1 y 6 segundos aproximadamente). Si la línea tiene voz horneada, el hold se extiende mientras el clip sigue sonando (tope de 30 segundos extra, así una fuente trabada nunca puede colgar la cutscene), y se corta en el momento en que el jugador salta, mantiene pulsado para saltar, o la línea termina y el control avanza — un warp de room incluido. - PLAYSCENE — reproduce una escena cinemática (un picker de escena autowired, conceptualmente
enlazado a los clips del Orquestador de Escenas) y bloquea hasta que termina; el jugador
puede mantener pulsado para saltarla igual dentro del juego. (
REWINDSCENEtambién existe a nivel de runtime, para el efecto de rebobinado estilo VHS, pero no se expone como un tipo de paso propio acá — solo alcanzable desde el slot raw.) - raw (token de efecto) — cualquier otra cosa, a través del mismo widget de efectos
compartido y dirigido por manifiesto que usan las reglas, los diálogos y el Editor de Puzzles
(modo de efecto único): un desplegable de verbo más campos de argumento tipados/buscados,
nunca texto libre para un argumento que porte un ID. Así es como una cinemática dispara
SETFLAG,GIVEITEM:<item>|<char>,SWITCHCHAR,WARPTOROOM,CUTSCENESPEED, o cualquier otra cosa del manifiesto de efectos.
Los pasos se reordenan de dos formas: arrastrando la fila por su encabezado, o con los botones ▲/▼ — ambos llaman a la misma ruta de reordenamiento. Eliminar es un solo × por fila, sin confirmación.
Targeting de party y foco#
Para juegos con party, una cinemática no siempre puede nombrar de forma fija a un personaje — el jugador quizá no lo reclutó. Dos mecanismos cubren eso, y ambos aparecen como pickers, nunca texto libre:
{partySlotN}— una opción de slot 🧑🤝🧑 en cualquier campo de personaje (y en el desplegable de hablante de SAY/NPCSAY) que resuelve al N-ésimo miembro comprometido del party en runtime. Un slot vacío deja el token en no-op; para una línea de SAY/NPCSAY, un hablante vacío simplemente salta la línea en vez de sacar un globo fantasma.IFINPARTY:<charId>— un guard por-paso, un select en el encabezado de cada paso. El paso corre solo cuando ese personaje está en el party activo; de lo contrario se salta de forma transparente. (Sin sistema de party en juego, el guard queda simplemente inerte.)
Un tercer sentinel, {_prevChar}, se ofrece en los pickers de personaje para el patrón
SWITCHCHAR de abajo — representa a quien tenía el control cuando arrancó la cinemática.
Vistas Visual y Avanzado#
El panel central alterna entre dos vistas de la misma lista de pasos — las ediciones en una aparecen en la otra al instante.
- Avanzado es el editor campo por campo descrito arriba: cada campo tipado, select de guard y picker desplegado en detalle. Úsalo cuando estés cableando argumentos exactos.
- Visual renderiza la secuencia como una cadena de tarjetas de nodo amigables — "Dice", "Mueve", "Entra", "Sale", "Espera", "Escena", "Token" — cada una con una insignia BLOQUEA cuando retiene la secuencia. Puedes hacer pan/zoom del diagrama, agregar un nodo soltándolo en la cadena, reordenar, eliminar, y editar una línea de SAY en línea; hacer clic en otra parte del nodo abre el editor completo. Es la vista de leer-de-un-vistazo para ver forma y flujo antes de meterte en los argumentos.
Modelo de secuenciación#
core/cutscene.js avanza el paso activo cada frame y solo mueve el cursor una vez que un paso
se resuelve — no hay paralelismo entre pasos, aunque un paso bloqueante (una caminata, una
espera, una línea de SAY, una escena) no congela el resto del motor; la sala sigue renderizando
y otros actores siguen animando alrededor. WARPTOROOM y SWITCHCHAR también bloquean a nivel
de runtime: ambos son operaciones asíncronas del shell (una carga de sala, en el caso de
SWITCHCHAR posiblemente cruzando de sala), y el runner espera hasta que el shell limpia
state.warpPending para que un paso pensado para la sala nueva no dispare mientras la vieja
sigue en pantalla. Cualquier otro token no cubierto por un paso tipado (todo lo que viene del
slot raw) es instantáneo — se aplica a través del mismo applyEffects() que usan el motor de
reglas y los diálogos, así que hay una sola ruta de despacho para los tokens que mutan estado en
todo el motor.
SWITCHCHAR también puede apuntar a un NPC para un foco momentáneo — una mini-cinemática que
mueve el control a un personaje, reproduce un momento, y devuelve el control. Combínalo con el
sentinel {_prevChar} para volver a quien manejaba cuando arrancó la escena, sin nombrarlo
de forma fija. El foco es autoral, no control del jugador: el switch es skip-safe (el regreso
corre igual con un salto acelerado) y no perturba los saves ni corta la música de fondo si el NPC
no tiene tema propio.
Mientras corre una cinemática, el shell oculta la GUI y bloquea el input del jugador; un clic
del jugador durante un paso SAY salta directo a la siguiente línea, y mantener el mouse
presionado dispara el salto acelerado, adelantando toda la secuencia. El salto colapsa todos
los pasos bloqueantes, no solo los de tiempo: los WAIT y los SAY terminan al instante, un
WALKNPCTO teletransporta al NPC a su destino (con el facing de llegada que le tocaba), un
WARPTOROOM cambia de room sin su fundido, y un PLAYSCENE que todavía no arrancó se saltea —
una escena que ya está corriendo conserva su propio hold-to-skip. El mundo en el que caes es el
mundo que la cinemática habría construido, solo que sin la espera. El cursor del mouse también se oculta — salvo que un paso
SETCURSOR:visible=show lo traiga de vuelta: el input sigue bloqueado igual, así que el cursor
se vuelve un prop que la propia cinemática maneja (qué cursor, qué frame, clavado dónde — ver la
sección SETCURSOR de la referencia de effect-tokens), y el override se limpia solo al terminar
la escena.
Pestaña Validar#
Corre en cada edición, y Guardar se niega a escribir mientras haya un error en pantalla —
NPCs faltantes/desconocidos, un WAIT no positivo y (ver abajo) un token raw sin un argumento
obligatorio cuentan como error; una región vacía en NPCENTER, un objetivo de hotspot/coords
incompleto y texto SAY vacío son avisos, que no bloquean. Un segundo pase asíncrono revisa que
las regiones y hotspots referenciados existan de verdad en los datos de la sala cargada. También
acepta los sentinels dinámicos ({partySlotN}, {_prevChar}) como objetivos válidos, mientras
sigue avisando si un guard de party nombra a un personaje que no reconoce.
El effect token de un paso raw se chequea igual que el Editor de Puzzles chequea los tokens
de una reacción. Un SETFLAG: sin nombre de flag, o cualquier otro token al que le falte un
argumento obligatorio, es un paso que en runtime no va a hacer nada — ahora se marca como error
(bloqueando Guardar) en vez de pasar sin revisión, con el mismo juez (y la misma etiqueta de
argumento que muestra el propio widget) que comparten los chequeos de argumento vacío del Editor
de Puzzles y el Editor de Reacciones.
Uno de esos hallazgos sí tiene algo que decir al guardar. Un SAY vacío el runtime lo salta:
no es una pausa (para eso está WAIT), es un paso que no hace nada. Esa es la conducta segura, y es
justamente por eso que el editor pregunta: como el paso desaparece en silencio, jugando nunca vas
a ver que falta una línea. Así que guardar una cinemática que todavía tiene uno pregunta primero,
nombrando el paso. Es una pregunta y no un rechazo a propósito: un SAY vacío es justo lo que tienes a
mitad de una idea, y tienes que poder guardar el trabajo y volver después.
Eliminar o renombrar una cinemática te avisa primero si algo todavía apunta a su id — un token
CUTSCENE:<id> en otra regla, diálogo o cinemática — para que no dejes huérfano un disparador
en silencio.
Pestaña Vista previa#
Corre el runtime real de core/cutscene.js (startCutscene/tickCutscene) contra un
estado de prueba dentro del propio editor — no una simulación. ▶ Reproducir lo avanza a
60fps vía setInterval, ⏭ Paso avanza un tick a la vez, ↺ Reiniciar limpia la
ejecución. No hay una mini-escena renderizada; en su lugar, un registro de texto reporta
cada paso a medida que se resuelve ([VERBO] arg, inicio/llegada de caminata con coordenadas
redondeadas, cuentas regresivas de espera), lo cual alcanza para confirmar el orden y el
comportamiento de bloqueo sin tener que cablear la cinemática al juego primero. La nota de la
vista previa aclara explícitamente que las caminatas de NPC en preview son en línea recta (sin
el polígono transitable), aunque el ritmo coincide con la velocidad real del motor.
Content i18n#
El texto de SAY se autora monolingüe, en el defaultLocale del proyecto, misma regla que
el Editor de Diálogos. Al guardar, promoteSaySteps() acuña un lid estable para cada línea
nueva (o actualiza el existente en su lugar si el paso ya trae uno) y serializa el paso como
SAY:<npc>|#lid (o SAY:#lid para una línea de narrador) — nunca un par {es, en} inline. El
Editor de Traducción completa después cada otro locale configurado; al volver a cargar una
cinemática se resuelve el lid a su texto en defaultLocale para mostrarlo, así que lo que ves
en el campo siempre es la línea autorada, no un id crudo.
Flujo de trabajo#
- Elige una Sala — la lista cubre cada sala del proyecto (no solo las que ya tienen un archivo de reglas; uno se crea al primer guardado) más el bucket 🌐 Global para momentos entre salas.
- Elige una Cinemática existente del desplegable, o haz clic en Nuevo y nómbrala en el modal.
- + Agregar Paso para añadir un paso (por defecto
SAY), luego elige su tipo y llena los campos específicos — cada argumento que porta un ID es un picker (personaje, región, hotspot, sala, escena), nunca texto libre. - Reordena arrastrando o con ▲/▼; elimina con ×.
- Mantén un ojo en Validar a medida que avanzas.
- Recórrela en Vista previa — Reproducir/Paso/Reiniciar — y lee el registro para confirmar que la secuencia y el bloqueo se comportan como se espera.
- Guardar para escribir el archivo de reglas de la sala. Como ese archivo se comparte con
el Editor de Puzzles, cualquier comentario
//del autor anclado a una reacción o watcher en otro lugar del archivo sobrevive a un guardado desde acá también, aunque este editor no tenga UI para mostrarlos. El punto ciego es un comentario escrito directo dentro de tu propio contenido de cutscene — no tiene nombre al cual anclarse, así que Guardar avisa antes de descartarlo.
CUTSCENE:<id>busca en la sala actual primero, luego en el bucket Global. Una cinemática delimitada a una sala y una Global son dos hogares distintos: el mismo string de id en dos salas distintas son dos cinemáticas sin relación, pero el bucket 🌐 Global es el único espacio de nombres compartido, pensado para momentos que cualquier sala puede disparar. Si una reacción de hotspot o una opción de diálogo disparaCUTSCENE:<id>y no pasa nada, la causa habitual es que el id vive en las reglas de una sala distinta a la que el jugador está pisando — y tampoco está en Global, así que el fallback no encuentra nada. Los momentos entre salas van en Global; los específicos de una sala se quedan con su sala.Guardar reescribe el archivo de reglas entero — incluidas las reacciones y los vigías que no tocaste. Es a propósito (un archivo por sala, un serializador), y por eso la pestaña de exportación sirve para ojear qué va a escribir un guardado. Las notas del autor escritas en las reacciones y los vigías de la sala — las que el Editor de Puzzles y el Editor de Reacciones editan en un campo — sobreviven intactas a esa reescritura, aunque este editor no muestre ningún campo para ellas: viajan con el archivo, ancladas por nombre, y el bloque de la cabecera se preserva como siempre. Lo que el viaje de ida y vuelta no puede cargar es una nota
//escrita adentro de un bloque que se re-emite desde el objeto vivo —cutscenes:es justamente eso, y por eso importa acá —, así que el editor avisa y las lista antes de escribir, y cancelar deja el archivo intacto: no se escribe nada ni se acuña ningúnlid.