# Pixelbook · guía corta para IAs de chat

Crea un cartucho libre en español y entrégalo en https://pixelbook.videolibros.app/crear/pegar/. Consola 1.69.5.

Canales: https://pixelbook.videolibros.app/crear/rutas.md.

## La plantilla

Parte de esta plantilla válida y conserva su formato.

```text
PIXELBOOK-PAQUETE 1
=== cart.json ===
{
 "api": 1,
 "id": "volcanes-de-canarias",
 "version": "0.1.0",
 "series": "modo-ciencia",
 "cast": "modo-ciencia",
 "title": "Volcanes de Canarias",
 "fps": 30,
 "boss": false,
 "xpBudget": 30,
 "stars": {
  "three": 1,
  "two": 4
 },
 "flags": [],
 "look": true,
 "audio": {
  "music": [
   "quiz"
  ],
  "sfx": [
   "correct",
   "no"
  ],
  "amb": []
 },
 "voices": {
  "presentadora": "sofia",
  "ayudante": "luca"
 }
}
=== script.json ===
{
 "scenes": [
  {
   "id": "s00-pregunta",
   "sec": "Reto",
   "hud": 1,
   "steps": [
    {
     "music": "quiz"
    },
    {
     "say": "v001",
     "who": "presentadora",
     "text": "¿Qué instrumento mide volúmenes exactos gracias a una marca llamada aforo?"
    },
    {
     "gate": "aforo"
    },
    {
     "say": "v002",
     "who": "presentadora",
     "text": "Eso es: el matraz aforado.",
     "gap": 0.4
    }
   ]
  }
 ],
 "clips": [
  {
   "say": "x-aforo-1",
   "who": "ayudante",
   "text": "La probeta mide volúmenes, pero no tiene aforo: tiene muchas divisiones."
  },
  {
   "say": "x-aforo-2",
   "who": "ayudante",
   "text": "El vaso de precipitado solo da volúmenes aproximados."
  },
  {
   "say": "h-aforo",
   "who": "ayudante",
   "text": "Pista: busca el que tiene una sola marca en el cuello."
  },
  {
   "say": "r-first",
   "who": "ayudante",
   "text": "¡A la primera!"
  },
  {
   "say": "r-ok1",
   "who": "ayudante",
   "text": "¡Correcto!"
  },
  {
   "say": "r-ok2",
   "who": "ayudante",
   "text": "¡Bien visto!"
  }
 ]
}
=== nivel.json ===
{
 "format": 1,
 "level": {
  "title": "El matraz aforado",
  "sub": "Plantilla · Medir con precisión",
  "info": [
   "Elige el instrumento que mide",
   "volúmenes exactos"
  ],
  "icon": "flask",
  "reward": {
   "card": "vdc-matraz"
  }
 },
 "cards": [
  {
   "id": "vdc-matraz",
   "name": "Matraz aforado",
   "sub": "Material de laboratorio",
   "who": "Recipiente con una sola marca",
   "fact": "Su marca, el aforo, indica un volumen exacto. Se usa para preparar disoluciones.",
   "art": {
    "kind": "icon",
    "name": "flask",
    "ramp": "teal"
   }
  }
 ],
 "badges": [
  {
   "id": "vdc-aforo",
   "name": "Ojo de aforo",
   "desc": "Supera el nivel con tres estrellas.",
   "icon": "check",
   "goal": 1,
   "rule": {
    "level": "volcanes-de-canarias",
    "stars": 3
   }
  }
 ],
 "questions": [
  {
   "id": "vdc1",
   "type": "choice",
   "q": "¿Qué instrumento mide volúmenes exactos gracias a su aforo?",
   "opts": [
    "El matraz aforado.",
    "La probeta.",
    "El vaso de precipitado.",
    "El embudo."
   ],
   "a": 0,
   "why": "El matraz aforado tiene una sola marca, el aforo, que indica un volumen exacto."
  },
  {
   "id": "vdc2",
   "type": "tf",
   "q": "La probeta tiene una sola marca.",
   "a": false,
   "why": "La probeta tiene muchas divisiones: mide volúmenes, pero no tan exactos como el matraz aforado."
  }
 ]
}
=== soluciones.json ===
{
 "format": 1,
 "gates": {
  "aforo": [{"tap":"opt","data":0}]
 },
 "expect": {"stars":3,"mistakes":0,"xpMin":30}
}
=== src/00-plantilla.js ===
// Plantilla de Pixelbook: una escena con una pregunta de opción múltiple, hecha con el kit de retos. Es un punto de
// partida: cámbialo todo. El guion (script.json) dice qué se oye y cuándo; este código dibuja cada escena del guion y
// dice cómo funciona cada reto (cada { "gate" } del guion). Todo se prueba con la API, sin instalar nada: guarda los
// ficheros (PUT …/files), lee los errores (POST …/validate) y juega el nivel con el enlace de una vista previa
// instantánea (POST …/previews). Las funciones del motor, con su firma, están en la referencia:
// https://pixelbook.videolibros.app/crear/referencia.md (y en la guía para IAs, crear/guia-ia.md).

// El marcador de arriba (una franja de unos 16 px de alto) sale en las escenas con "hud": 1 en script.json. A la
// izquierda, name y title: el texto que quieras, en mayúsculas («CASO 1», «MISIÓN 2»). A la derecha, la XP del nivel.
HUD_LEVELS[1] = { name: 'RETO', title: 'MEDIR VOLÚMENES', ramp: C.teal };

// Las opciones del reto, en su orden: la buena es la 0 (correct: 0). En pantalla salen barajadas, en otro orden en cada
// partida: lo que se toca es el índice de la opción, nunca su puesto.
const OPCIONES = ['MATRAZ AFORADO', 'PROBETA', 'VASO DE PRECIPITADO'];

// El reto «aforo» ({ "gate": "aforo" } en script.json) es una pregunta de opción múltiple (choiceIX). Cada fallo dice su
// explicación (wrong: opción → línea suelta de clips en script.json) y deja la opción apagada. Tras dos fallos llega la
// pista (hint). xp: los puntos de acertar a la primera, a la segunda y después; xpBudget (cart.json) es el tope de todo
// el nivel. Al acertar, el kit dice r-first, r-ok1 o r-ok2 (también en clips), cierra el reto y la historia sigue.
defInteraction('aforo', choiceIX({ n: 3, correct: 0, wrong: { 1: 'x-aforo-1', 2: 'x-aforo-2' }, hint: 'h-aforo', xp: [30, 15, 5], label: 'Aforo' }));

// La escena «s00-pregunta» del guion. t es el tiempo de la historia; lt, el de la escena. La pantalla mide 480 × 270:
// la pregunta arriba, las opciones en el centro (drawChoice pone sus zonas táctiles y su resalte con el ratón encima),
// la pista escrita encima de los subtítulos (drawHint), el retrato de quien habla abajo a la izquierda (speakerDock: en
// allow, los personajes de voices en cart.json) y Bit, el robot de quien juega, abajo a la derecha, con su color y su
// accesorio (look: true en cart.json). Los subtítulos y el marcador los pone la consola: no los dibujes.
// Las opciones entran desde la derecha, una cada 0,08 s y en 0,25 s cada una, desde t0 (en el tiempo de la historia, que
// se para al abrirse el reto): empiezan 0,6 s antes de la marca del reto, así ya están quietas cuando se abre.
defScene('s00-pregunta', (t, lt) => {
  cls(C.ink[1]);
  ui(() => {
    panel(92, 30, 296, 30, C.ink[2], { r: 4 });
    drawText('¿Qué mide volúmenes exactos?', 240, 41, { c: K.white, align: 'center' });
    drawChoice(ixState('aforo'), OPCIONES, { x: 92, y: 80, w: 296, h: 30, t0: M('s00-pregunta', 'aforo') - 0.6, now: t });
    drawHint(ixState('aforo'), 'h-aforo');
  });
  speakerDock(t, { allow: ['presentadora', 'ayudante'] });
  bit(430, 206, { t, expr: 'happy', color: C[PLAYER.look.color] || C.teal, acc: PLAYER.look.acc });
}, { in: 'cut' });
=== src/90-retrato.js ===
// El retrato del nivel (obligatorio), 64 × 64 desde (x, y): cámbialo por una escena de tu tema que llene el cuadro.
defLevelArt((x, y) => {
  vgrad(x, y, 64, 46, [C.blue[1], C.blue[2], C.teal[2]]);
  rect(x, y + 14, 64, 2, C.wood[3]);
  for (const [bx, r] of [[6, C.red], [14, C.yellow], [50, C.pink]]) { rect(x + bx, y + 6, 5, 8, r[3]); rect(x + bx + 1, y + 4, 3, 2, C.gray[4]); }
  rect(x, y + 46, 64, 18, C.wood[2]); hline(x, x + 63, y + 46, C.wood[4]);
  rect(x + 6, y + 20, 8, 27, C.gray[4]); rect(x + 7, y + 21, 6, 25, C.blue[1]); rect(x + 7, y + 34, 6, 12, C.teal[3]);
  rect(x + 46, y + 28, 13, 19, C.gray[4]); rect(x + 47, y + 29, 11, 17, C.blue[1]); rect(x + 47, y + 37, 11, 9, C.orange[3]);
  rect(x + 28, y + 6, 8, 22, C.gray[4]); rect(x + 29, y + 7, 6, 21, C.blue[1]); rect(x + 27, y + 4, 10, 3, C.wood[3]);
  circ(x + 32, y + 36, 12, C.gray[4]); circ(x + 32, y + 36, 11, C.blue[1]); circ(x + 32, y + 37, 10, C.lime[3]);
  rect(x + 29, y + 20, 6, 9, C.lime[3]); hline(x + 27, x + 37, y + 20, K.white); vline(x + 25, y + 31, y + 39, C.gray[5]);
});
=== FIN ===
```

## Cómo se trabaja

1. Copia la plantilla entera.
2. Cambia solo los textos, la pregunta, las opciones y el dibujo, y pon tu id y tu prefijo (regla 2). Deja igual los nombres de los campos, las funciones y la forma de cada fichero.
3. Entrega el cartucho completo en un solo bloque de código («Cómo entregar», al final).
4. Tu persona lo pega y pulsa «Enviar a revisión». Si falla, te devuelve el informe.
5. Corrige el informe y devuelve otra vez el cartucho completo. Tras el recibo, tu persona pulsa «Probar ahora». Recibido no significa publicado. Sin ejecución, no afirmes que está validado ni inventes enlaces.

No intentes la API ni pidas capturas: la comprobación la hace la página.

## Las reglas, con ejemplos

1. **`cast` es un texto, nunca una lista.** Bien: `"series": "modo-ciencia", "cast": "modo-ciencia"`. Mal: `"cast": ["presentadora", "ayudante"]` o `"cast": ["modo-ciencia"]`. Quien habla va en `who`: usa `presentadora` y `ayudante`, que ya tienen su voz en `voices`, sin inventar otras.
2. **Tu id y tu prefijo.** El id va en minúsculas, cifras y guiones; el prefijo son tres letras minúsculas. Conserva los que te dé tu persona; si no, sácalos del título: «Desafíos de lógica» da `desafios-de-logica` y `ddl`. En cart.json, bien: `"id": "desafios-de-logica"`; mal: `"id": ["desafios-de-logica"]`. En la regla del logro de nivel.json: `"rule": { "level": "desafios-de-logica", "stars": 3 }`.
3. **El prefijo en nivel.json y `flags` de cart.json.** Sustituye `vdc` por el tuyo en todos los ids.

| Qué | Forma | Ejemplo con `ddl` |
|---|---|---|
| Cromos (`cards`) y premio (`reward`) | prefijo, guion y palabras | `"ddl-patron"` |
| Logros (`badges`) | prefijo, guion y palabras | `"ddl-perfecto"` |
| Preguntas (`questions`) | prefijo y una cifra | `"ddl1"`, `"ddl2"` |
| Logros del código (`flags`) | prefijo y una mayúscula | `"ddlPerfecto"` |

4. **Cada reto, completo.** Un reto es `{ "gate": "aforo" }` en el guion y lleva siempre:
   - su `defInteraction('aforo', choiceIX({ … }))` en `src/`, con el mismo id;
   - su lista en soluciones.json, que siempre tiene esta forma: `{ "format": 1, "gates": { "aforo": [{ "tap": "opt", "data": 0 }] }, "expect": { "stars": 3, "mistakes": 0, "xpMin": 30 } }`. `data` es la posición de la opción buena en tu lista, contando desde 0 (el mismo número que `correct`);
   - las líneas de cada fallo (`x-…`) y de la pista (`h-…`) en `clips` de script.json, y además, una vez para todo el nivel, `r-first`, `r-ok1` y `r-ok2`, lo que se dice al acertar.

   Cada escena del guion tiene también su `defScene` con el mismo id, y cada `say` es distinto en todo el guion.
5. **La ficha del nivel.** En nivel.json, `level` lleva `title` (25 caracteres como mucho, el mismo que `title` de cart.json), `sub` (de qué va, en 50 como mucho), `info` (exactamente dos renglones de 35 como mucho), `icon` y `reward`. Bien: `"level": { "title": "Desafíos de lógica", "sub": "Acertijos, patrones y lenguaje", "info": ["Resuelve acertijos", "con lógica y paciencia"], "icon": "bulb", "reward": { "card": "ddl-patron" } }`. Mal: `"info": "Resuelve acertijos"` (son dos textos en una lista) o `title` fuera de `level`.
6. **El retrato.** `src/90-retrato.js` conserva `defLevelArt((x, y) => { … })`: portada de 64 × 64 desde (`x`, `y`), con una escena de tu tema, sin letras. Es obligatoria.
7. **El texto.** Sin raya larga ni comillas tipográficas, tampoco en comentarios. Usa puntuación normal, «» y apóstrofo recto. En textos visibles, sin enlaces, correos, teléfonos ni @usuarios. Español de España con tildes.
8. **Solo lo que existe.** Iconos (`icon` de la ficha, de los logros y de los cromos): trophy, eye, chart, flask, bulb, heart, stopwatch, book, check, cross, star, sun, lock, magnifier, floppy, warning, loop, question, note, thermo, gear, microscope, ruler, pendulum, megaphone, flame, chest, shield, crown, bolt, cards, casa, flecha, pata, pause, speaker, telescope, robot. Colores: `K.white`, `K.black` o `C.<color>[tono]`, del tono más oscuro (0) al más claro: `ink` y `sky`, de 0 a 3; `red`, `orange`, `yellow`, `lime`, `green`, `teal`, `blue`, `purple` y `pink`, de 0 a 4; `gray` y `wood`, de 0 a 5. Para dibujar, lo que usa la plantilla (`rect`, `circ`, `vgrad`, `drawText`…) y `line(x0, y0, x1, y1, c)`, `poly([[x, y], …], c)` e `icon(nombre, cx, cy, tamaño, C.teal)`. La pantalla mide 480 × 270. Nada de `setTimeout`, `fetch`, `window` ni `document`, y no llames `top`, `self` ni `location` a tus variables.

9. **El audio son listas de nombres, no diccionarios.** Bien: `"audio": { "music": [], "sfx": ["correct", "no", "tap"], "amb": [] }`. Mal: `"sfx": { "correct": "correct" }`. Las frases van en `clips` de script.json: lista de objetos `say`, `who`, `text`. Nunca en `audio.clips` ni como `{ "r-first": "texto" }`. Conserva las líneas de la plantilla.
10. **No omitas los datos del manifiesto ni los del capítulo.** Conserva los campos de cart.json de la plantilla, incluidos `title`, `series`, `cast`, `fps: 30`, `stars` y `flags`. Cada escena lleva `id`, `sec` y `steps`. El título también va en nivel.json.
11. **Comprueba la lógica antes de entregar.** Cada pregunta tiene respuesta única y explicación. `correct` y `data` son el mismo índice desde cero y coinciden con el orden visible. Los puntos suman `xpBudget`. Sin ejecución, no afirmes haberlo probado.

## Un segundo reto

Añade estas cuatro piezas con ids nuevos y los textos de tu tema:

**1. La escena**, en `scenes` de script.json, detrás de la primera y separada por una coma:

```json
{ "id": "s01-reto2", "sec": "Reto", "hud": 1, "steps": [
  { "say": "v003", "who": "presentadora", "text": "Otra pregunta: ¿con qué se mide la masa?" },
  { "gate": "reto2" },
  { "say": "v004", "who": "presentadora", "text": "Eso es: con la balanza.", "gap": 0.4 } ] }
```

**2. Sus líneas sueltas**, al final de `clips` de script.json, tras una coma:

```json
{ "say": "x-reto2-1", "who": "ayudante", "text": "La probeta mide volúmenes, no masas." },
{ "say": "x-reto2-2", "who": "ayudante", "text": "El termómetro mide temperaturas." },
{ "say": "h-reto2", "who": "ayudante", "text": "Pista: tiene dos platillos o una pantalla con gramos." }
```

**3. Su código**, en un fichero nuevo, `src/10-reto2.js`:

```js
const OPCIONES2 = ['BALANZA', 'PROBETA', 'TERMÓMETRO'];
defInteraction('reto2', choiceIX({ n: 3, correct: 0, wrong: { 1: 'x-reto2-1', 2: 'x-reto2-2' }, hint: 'h-reto2', xp: [30, 15, 5], label: 'Reto 2' }));
defScene('s01-reto2', (t, lt) => {
  cls(C.ink[1]);
  ui(() => {
    panel(92, 30, 296, 30, C.ink[2], { r: 4 });
    drawText('¿Con qué se mide la masa?', 240, 41, { c: K.white, align: 'center' });
    drawChoice(ixState('reto2'), OPCIONES2, { x: 92, y: 80, w: 296, h: 30, t0: M('s01-reto2', 'reto2') - 0.6, now: t });
    drawHint(ixState('reto2'), 'h-reto2');
  });
  speakerDock(t, { allow: ['presentadora', 'ayudante'] });
  bit(430, 206, { t, expr: 'happy', color: C[PLAYER.look.color] || C.teal, acc: PLAYER.look.acc });
}, { in: 'cut' });
```

**4. Su solución**, en `gates` de soluciones.json. El fichero entero queda así:

```json
{ "format": 1,
  "gates": { "aforo": [{ "tap": "opt", "data": 0 }], "reto2": [{ "tap": "opt", "data": 0 }] },
  "expect": { "stars": 3, "mistakes": 0, "xpMin": 60 } }
```

Y suma sus puntos: cada reto da 30 a la primera, así que con dos pon `"xpBudget": 60` en cart.json (y `"xpMin": 60`, como arriba).

Para un tercero usa `reto3`, `s02-reto3`, `v005`, `v006`, `x-reto3-1`, `x-reto3-2`, `h-reto3`, `src/20-reto3.js`, `OPCIONES3` y 90 puntos. Las constantes tienen nombres únicos: los src/ se juntan en uno.

## Cómo entregar

- Un solo bloque de código, de `PIXELBOOK-PAQUETE 1` a `=== FIN ===`, con todos los ficheros: cart.json, script.json, nivel.json, soluciones.json y cada `src/*.js`.
- Cada fichero entero, con su línea `=== ruta ===` delante. Nunca «...», «igual que antes» ni «el resto no cambia»: un fichero a medias no se puede leer.
- Si no te cabe en un mensaje, dalo en partes seguidas: primero los cuatro JSON y después los `src/*.js`, cada parte en su bloque y `=== FIN ===` solo al final de la última. Tu persona las junta en el mismo cuadro.

Lo demás (otros personajes y voces, otros retos, música) está en la [guía completa](https://pixelbook.videolibros.app/crear/guia-ia.md).

Encargos sin registro: https://pixelbook.videolibros.app/crear/pegar/?encargo=enc-0007 (usa el código real).
