What it needs
FISH_STUDIO_APIKEYinv3/.env.local- Node, FFmpeg, FFprobe, and Remotion installed locally
- Serper only when a new story world needs assets
Wiggly / Format Lab /
Familiar characters explain a real idea through their own story world.
The kit contains the renderer, runner, rules, layouts, and assets. Unzip it and tell Claude or Codex to read SKILL.md. It must use the packaged renderer, not rebuild one.
Claude or Codex can download this package, use its existing renderer, check what is missing, plan a lesson, render it, inspect it, and improve it up to two times.
FISH_STUDIO_APIKEY in v3/.env.localNo OpenRouter planner, Replicate, GPU, image generation, or video generation. Secret values never enter the Format files or reports.
The complete loop an agent follows without the user explaining the Format.
SKILL.md--- name: otaku-explainer-format description: Use the Cartoon Explainer Format to make a short story-world lesson video without asking the user to explain how the Format works. --- # Cartoon Explainer Agent Loop You are operating a complete runnable Wiggly Format Kit. The user supplies a topic and chooses a packaged story world. This folder supplies the cast, voices, backgrounds, layouts, renderer, runner, audio rules, and quality checks. ## Use the packaged runtime Do not rebuild, replace, translate, or imitate the renderer. Run the renderer and runner already included in this kit. A different framework or a new scene-timing loop is a failed use of the Format, even when its final video looks similar. If you received the public Wiggly Repo URL, download and unzip the **Runnable Format Kit** first. Open its `v3` folder, run `npm install`, copy `.env.example` to `.env.local`, and add only the missing key values. Then follow the commands below. The renderer deliberately adds a soft glow around the active speaker. This hides rough transparent-image edges and makes the speaker obvious. Preserve that behavior. ## Commands Run commands from the downloaded kit's `v3` folder: ```bash npm run smoke npm run prototype:otaku -- check npm run prototype:otaku -- init --run=<run-id> --topic="<topic>" --world=naruto npm run prototype:otaku -- validate --run=<run-id> npm run prototype:otaku -- render --run=<run-id> --approve-loop npm run prototype:otaku -- inspect --run=<run-id> npm run prototype:otaku -- finalize --run=<run-id> ``` Run `npm run smoke` first. It makes a tiny local test video and verifies the packaged renderer, assets, FFmpeg, Remotion, and audio mix without calling Fish or any paid provider. `--approve-loop` records permission for one initial render and up to two focused improvement renders without storing secrets. If the configured voice model is free and the user already asked the agent to make the video, that request is enough; do not interrupt them for another approval. Ask first when a provider may charge money. ## Required loop 1. Read this file, `requirements.json`, `worlds/<world>.json`, `layouts.json`, `scene-contract.json`, `prompts/script-system.md`, and `quality.json`. 2. Run `npm run smoke`, then run `check`. If either fails, stop and report the exact missing local requirement. If `check` reports a missing key, ask the user to add the named key to `.env.local`. Never ask them to paste a secret into chat and never print its value. 3. Run `init`, then write 12–18 short scene records in the new run's `scene-plan.json`. 4. Use only packaged role names, backgrounds, layout IDs, and assets. Do not invent character coordinates. 5. Run `validate` before any media call. Fix every validation error first. 6. Show the user the scene plan, scene count, cast, and estimated duration. If any media call may cost money, ask once for approval covering no more than three total render attempts. 7. Run `render --approve-loop` for the first attempt. Later attempts use `render` without that flag. 8. Run `inspect`. Look at the full video and contact sheet. Record only concrete problems in `quality-report.json`. 9. Fix only the problems you found. Do not rewrite good scenes. Render and inspect again when needed. 10. Run `finalize` only when every automatic and human review check passes. ## Add a story world When the user asks for a story world that is not packaged: 1. Run `check --needs-new-assets`. Ask for missing key names without exposing their values. 2. Research the show's characters, relationships, locations, and lore. Record the sources you used. 3. Map one character to each lesson role: learner, guide, and challenger. Keep the renderer, layouts, and scene contract unchanged. 4. Search Fish Audio's public models for each character, audition available samples, and record the chosen model IDs and why they fit. 5. Use Serper to find full-body character cutouts and wide backgrounds. Follow `prompts/image-search.md`; prefer existing transparent assets and use local cleanup when needed. 6. Source one quiet instrumental track using the music procedure below. 7. Add the inspected files and provenance to `assets.json`, then create `worlds/<world>.json` with the cast, voices, backgrounds, useful lore, claims to avoid, and selected music. 8. Validate the new world before rendering. If it requires special renderer, runner, layout, or schema code, stop: the Format is not portable yet. Do not generate images unless the user separately approves it. Do not add an automated world-building workflow; the agent performs and documents this work. ### Source story-world music 1. Search Serper for `"<story world> background music instrumental"` and inspect the top three credible results. 2. Prefer an exact official or studio instrumental over a cover, remix, or re-orchestration unless the Format calls for one. 3. Download audio with `uvx yt-dlp --no-playlist -x --audio-format mp3 --audio-quality 0 <url>`. 4. Use FFprobe and FFmpeg to check duration, loudness, clipping, and trailing silence. Remove dead air, normalize with `loudnorm=I=-14:TP=-1.5:LRA=11`, and use a one-second crossfade when looping the track to the video length. 5. Verify that the loop has no silent seam and dialogue remains clear. If you cannot hear the candidates, do not claim that you auditioned them: use metadata and technical checks, then show the user playable options when taste affects the decision. 6. Record `localPath`, `sourceUrl`, `volume`, and `selectionReason` in the world pack. Keep the Format's default music when no candidate passes. ## Good result A passing result: - teaches the topic accurately in language a nontechnical viewer can repeat; - sounds like a natural conversation, not a lecture pasted into character mouths; - uses a story-world analogy that stays consistent; - keeps every visible character grounded; - fits every line inside the speech bubble; - assigns the right voice to the active speaker; - uses a clean music loop that stays below the dialogue; and - leaves a clear final takeaway. ## Failure rules - Never render an invalid plan. - Never rebuild the packaged renderer or write a substitute timing/rendering pipeline. - Scene duration comes from the actual voice file. Never add arbitrary silence after a voice clip. - Use the full-length music bed prepared by the runner. Never loop a short track with a hard seam. - Never exceed three attempts for one run. - Never silently change providers or generate replacement images or video. - If the packaged world lacks an asset, stop and explain what is missing. Use Serper only after the user approves sourcing a new asset. - If attempt three still fails, stop and explain the blocker. Do not mark the run finished. - Do not expose, copy, log, or store API key values.
The key names and local tools needed, without any secret values.
requirements.json{
"environment": [
{
"name": "FISH_STUDIO_APIKEY",
"requiredFor": ["render"],
"description": "Generates one free S2.1 Pro voice clip for each scene."
},
{
"name": "SERPER_API_KEY",
"requiredFor": ["new-story-world-assets"],
"description": "Finds character, background, and instrumental music candidates only when a story world is not already packaged."
}
],
"localTools": [
{ "name": "node", "requiredFor": ["check", "init", "validate", "render", "inspect", "finalize"] },
{ "name": "ffmpeg", "requiredFor": ["render", "inspect", "new-story-world-assets"] },
{ "name": "ffprobe", "requiredFor": ["render", "inspect", "new-story-world-assets"] },
{ "name": "uvx", "requiredFor": ["new-story-world-assets"] },
{ "name": "remotion", "requiredFor": ["render"] }
],
"notUsed": [
"OpenRouter",
"Replicate",
"GPU",
"image generation",
"video generation"
],
"secretPolicy": "Read keys from the downloaded kit's v3/.env.local. Never write secret values to plans, logs, reports, or run records."
}
Lesson roles mapped to characters, voices, backgrounds, lore, and music.
worlds/danny-phantom.json{
"id": "danny-phantom",
"label": "Danny Phantom",
"music": {
"localPath": "assets/audio/danny-phantom-background.mp3",
"volume": 0.16,
"sourceUrl": "https://www.youtube.com/watch?v=s8N1O07Lxpw",
"selectionReason": "The top exact-match search result is the official instrumental rather than a cover or remix. Its trailing silence was removed before making a crossfaded loop."
},
"roles": {
"learner": {
"character": "danny-phantom",
"voice": "14f06ac475944bb7a0ef5cc958f07462",
"behavior": "Curious, brave, and restates the lesson in plain words."
},
"guide": {
"character": "tucker-foley",
"voice": "e3e1212180ee4f87ab0730db54d1a8e2",
"behavior": "Tech-obsessed friend who explains the mechanism clearly."
},
"challenger": {
"character": "vlad-plasmius",
"voice": "23231b8547f04d79bebc51bb2a23b5ab",
"behavior": "Confident antagonist who adds a warning, edge case, or correction."
}
},
"backgrounds": ["amity-park", "fenton-lab", "ghost-zone"],
"lore": [
"the Fenton Portal connects Earth to the separate Ghost Zone",
"portal controls send specific commands across that boundary",
"Tucker is Danny's tech-obsessed best friend",
"Vlad is an experienced half-ghost and Danny's antagonist",
"Amity Park is Danny's home and the place he protects",
"ghost-hunting equipment follows specific controls and limits"
],
"avoid": [
"claiming Tucker built the Fenton Portal",
"treating Vlad as a trusted teacher",
"invented ghost powers or portal rules",
"random technical words that do not map to the story-world action"
],
"researchSources": [
"https://en.wikipedia.org/wiki/Danny_Phantom",
"https://dannyphantom.fandom.com/wiki/Danny_Phantom_(character)",
"https://dannyphantom.fandom.com/wiki/Tucker_Foley",
"https://dannyphantom.fandom.com/wiki/Vlad_Plasmius",
"https://dannyphantom.fandom.com/wiki/Fenton_Portal",
"https://dannyphantom.fandom.com/wiki/Ghost_Zone"
],
"voiceResearch": {
"danny-phantom": {
"query": "Danny Phantom",
"selectionReason": "Highest-use English result with a youthful, energetic sample and 1,700 prior tasks."
},
"tucker-foley": {
"query": "Tucker Foley",
"selectionReason": "English public model with an available energetic, friendly sample."
},
"vlad-plasmius": {
"query": "Vlad Plasmius",
"selectionReason": "Only exact English public result; its sample has the confident antagonist delivery the role needs."
}
}
}
Lesson roles mapped to characters, voices, backgrounds, lore, and music.
worlds/naruto.json{
"id": "naruto",
"label": "Naruto",
"roles": {
"learner": { "character": "naruto", "voice": "c658e20af0364df6b73bdcef998b6a8b", "behavior": "Curious, impatient, and repeats the lesson in plain words." },
"guide": { "character": "kakashi", "voice": "6fb91544d42b4d57a5b408c398459237", "behavior": "Calm teacher who makes the main idea accurate." },
"challenger": { "character": "orochimaru", "voice": "8ecce156f83e4fa0bb75f99b30894e63", "behavior": "Adds tension, a sharper example, or a correction." }
},
"backgrounds": ["konoha", "naruto-forest", "training-ground"],
"lore": ["ninja villages", "missions", "scrolls", "jutsu", "summoning contracts", "messenger birds"],
"avoid": ["invented canon claims", "modern apps appearing inside the story world"]
}
Lesson roles mapped to characters, voices, backgrounds, lore, and music.
worlds/spongebob.json{
"id": "spongebob",
"label": "SpongeBob SquarePants",
"music": {
"localPath": "assets/audio/spongebob-background.mp3",
"volume": 0.14,
"sourceUrl": "https://www.youtube.com/watch?v=3xWZB1SoWE8",
"selectionReason": "Hawaiian Cocktail is an exact SpongeBob production instrumental and is calm enough to sit below dialogue. Its leading and trailing silence were removed before loudness normalization."
},
"roles": {
"learner": {
"character": "spongebob",
"voice": "9845e056f37b470d9a1005e41c864e25",
"behavior": "Cheerful and curious; repeats the mechanism in simple words to prove he understands it."
},
"guide": {
"character": "sandy-cheeks",
"voice": "783d32b03d0c4ff28dd66455364d8665",
"behavior": "A practical scientist who explains each electric vehicle part accurately and plainly."
},
"challenger": {
"character": "plankton",
"voice": "fc33048c58604823aa19ace52e4842c9",
"behavior": "An overconfident inventor who suggests one tempting shortcut or misconception for Sandy to correct."
}
},
"backgrounds": ["bikini-bottom", "sandy-treedome", "boating-school"],
"lore": [
"Bikini Bottom is SpongeBob's underwater home",
"Sandy is an inventor and scientist who lives in a treedome",
"Mrs. Puff's Boating School is where SpongeBob practices driving boatmobiles",
"Plankton is an overconfident inventor and SpongeBob's recurring rival",
"Sandy can test an electric boatmobile as a lesson device without claiming it is show canon"
],
"avoid": [
"claiming the lesson's electric boatmobile is a canon invention from the show",
"pretending an electric vehicle battery connects directly to the wheels",
"saying regenerative braking recovers all energy or replaces the regular brakes",
"turning the explanation into unrelated SpongeBob catchphrases",
"invented character relationships or locations"
],
"researchSources": [
"https://afdc.energy.gov/vehicles/how-do-all-electric-cars-work.html",
"https://www.energy.gov/cmei/femp/articles/electric-vehicle-technology-overview-federal-fleet-training",
"https://spongebob.fandom.com/wiki/Bikini_Bottom",
"https://spongebob.fandom.com/wiki/Sandy_Cheeks",
"https://spongebob.fandom.com/wiki/Mrs._Puff%27s_Boating_School",
"https://spongebob.fandom.com/wiki/Sheldon_J._Plankton"
],
"voiceResearch": {
"spongebob": {
"query": "Spongebob Squarepants",
"selectionReason": "Highest-use exact English public result with an available sample and more than 31,000 prior tasks."
},
"sandy-cheeks": {
"query": "Sandy Cheeks",
"selectionReason": "Highest-use exact English public result with an available sample and more than 4,000 prior tasks."
},
"plankton": {
"query": "Plankton",
"selectionReason": "Highest-use exact English public result that also provides an audition sample."
}
}
}
Lesson roles mapped to characters, voices, backgrounds, lore, and music.
worlds/yugioh.json{
"id": "yugioh",
"label": "Yu-Gi-Oh",
"roles": {
"learner": { "character": "yugi", "voice": "a27f617599df41f9aa6e95bb57ac69ba", "behavior": "Asks the clear question and states the final lesson." },
"guide": { "character": "kaiba", "voice": "710a3478dcc4477d8aae292c2de85964", "behavior": "Confident expert who explains the rule precisely." },
"challenger": { "character": "joey", "voice": "2ef97f8ba13a4f71a7e291a3998af362", "behavior": "Adds a practical example, joke, or objection." }
},
"backgrounds": ["duel-arena", "duel-club", "domino-city"],
"lore": ["decks", "cards", "duels", "turns", "combos", "trap cards"],
"avoid": ["invented card rules", "switching the learner and guide roles without a reason"]
}
Reusable two- and three-character positions. Scene writers do not invent coordinates.
layouts.json{
"layouts": {
"two-balanced": [
{ "x": 8, "bottom": 2, "width": 52, "rotate": -1 },
{ "x": 56, "bottom": 3, "width": 41, "rotate": 0 }
],
"two-first-focus": [
{ "x": 8, "bottom": 2, "width": 58, "rotate": -2 },
{ "x": 60, "bottom": 3, "width": 36, "rotate": 0 }
],
"two-second-focus": [
{ "x": 8, "bottom": 3, "width": 45, "rotate": 0 },
{ "x": 52, "bottom": 2, "width": 47, "rotate": 1 }
],
"three-balanced": [
{ "x": 2, "bottom": 2, "width": 38, "rotate": -2 },
{ "x": 33, "bottom": 3, "width": 37, "rotate": 0 },
{ "x": 64, "bottom": 2, "width": 35, "rotate": 2 }
]
}
}
What this Format does and how to run it.
README.md# Cartoon Explainer Turn a real lesson into a short conversation between familiar characters. A curious lead asks the obvious questions, an expert explains the idea through the story world, and a third character adds tension or a joke. This package is the first Wiggly Format repository experiment. It contains the instructions, inputs, assets, prompts, scene slots, renderer, audio rules, quality checks, and exact run records needed to recreate its outputs. An agent can operate the package without an OpenRouter planning call. Start with `SKILL.md`; it explains how to check requirements, create and validate a scene plan, request one render-loop approval, inspect the result, and stop after three attempts. ## Start from the public Wiggly Repo 1. Download and unzip the **Runnable Format Kit**. 2. Open its `v3` folder and run `npm install`. 3. Run `npm run smoke`. This free local test proves the renderer, assets, FFmpeg, Remotion, and audio mix work. 4. Copy `.env.example` to `.env.local` and add the requested key values. 5. Tell Claude or Codex: “Read `public/format-repositories/otaku-explainer-v1/SKILL.md` and use the packaged renderer. Do not rebuild it.” The kit contains the real runner, renderer, dependencies, rules, layouts, and required assets. A fresh agent may write a new scene plan or story-world pack, but it must not recreate the renderer or timing pipeline. ## Formula 1. Open with a question that names the confusing idea. 2. Explain it one step at a time through story-world objects and actions. 3. Let the challenger introduce a believable mistake, warning, or joke. 4. Correct the mistake and complete the lesson. 5. End with the lead character saying the core idea back in one simple line. Each scene uses the same visual grammar: a slowly panning story-world background, two or three grounded character cutouts, one visible speaker, one speech bubble, and an optional short callout. Scene writers choose lesson roles and an approved layout. `worlds/*.json` maps the roles to characters and voices, while `layouts.json` owns the coordinates. This keeps a new topic from inventing a new renderer. ## Agent commands From `v3`: ```bash npm run smoke npm run prototype:otaku -- check npm run prototype:otaku -- init --run=<run-id> --topic="<topic>" --world=naruto npm run prototype:otaku -- validate --run=<run-id> npm run prototype:otaku -- render --run=<run-id> --approve-loop npm run prototype:otaku -- inspect --run=<run-id> npm run prototype:otaku -- finalize --run=<run-id> ``` The Fish key stays in `v3/.env.local`. Serper is needed only when a run adds a new story world. `SKILL.md` tells the agent how to research lore, choose voices, source and inspect assets, and create the world pack without adding another command or changing the renderer. The package never needs OpenRouter, Replicate, a GPU, image generation, or video generation. ## Proof runs The Format page discovers every packaged world from `worlds/*.json`, every scene plan from `scenes/*.json`, and every finished proof from `outputs/*.run.json`. These files are the source of truth; the README and page do not maintain separate lists. Every proof uses the same scene contract and `renderer/OtakuFormatRenderer.tsx`. The tracked videos, contact sheets, run records, and quality reports show whether the agent loop works across topics and story worlds. ## Editing Assets and deterministic scene values can be replaced without regenerating the script. Changing a prompt or instruction marks the package **Needs rerun**, because an old output must never pretend to reflect a new prompt.
The topic and story world the Format needs. The world pack supplies the cast.
inputs.json{
"fields": [
{
"id": "topic",
"label": "What should the characters explain?",
"type": "text",
"required": true
},
{
"id": "storyWorld",
"label": "Which story world should teach it?",
"type": "story-world",
"required": true,
"source": "worlds/*.json"
}
],
"defaults": {
"topic": "Compilers versus interpreters",
"storyWorld": "naruto"
}
}

assets/images/naruto-clean.png
assets/images/kakashi-alpha.png
assets/images/orochimaru-clean.png
assets/images/yugi-clean.png
assets/images/kaiba-clean.png
assets/images/joey.webp
assets/images/danny-phantom.webp
assets/images/tucker-foley.webp
assets/images/vlad-plasmius.webp
assets/images/spongebob.webp
assets/images/sandy-cheeks.png
assets/images/plankton.webp
assets/images/bg-konoha.jpg
assets/images/bg-naruto-forest.jpg
assets/images/bg-training-ground.jpg
assets/images/bg-duel-arena.jpg
assets/images/bg-duel-club.jpg
assets/images/bg-domino-city.jpg
assets/images/bg-amity-park.webp
assets/images/bg-fenton-lab.webp
assets/images/bg-ghost-zone.jpg
assets/images/bg-bikini-bottom.webp
assets/images/bg-sandy-treedome.webp
assets/images/bg-boating-school.webpCharacter cutouts, backgrounds, source links, and local files.
assets.json{
"characters": [
{
"id": "naruto",
"label": "Naruto",
"localPath": "assets/images/naruto-clean.png",
"postprocess": "remove-white-and-trim",
"searchQuery": "Naruto Uzumaki full body transparent png render",
"sourcePage": "https://www.pngitem.com/middle/hJmTRwx_naruto-naruto-shippuden-anime-naruto-render-hd-png/",
"sourceUrl": "https://www.pngitem.com/pimgs/m/487-4873619_naruto-naruto-shippuden-anime-naruto-render-hd-png.png"
},
{
"id": "kakashi",
"label": "Kakashi",
"localPath": "assets/images/kakashi-alpha.png",
"searchQuery": "Kakashi Hatake full body transparent png render",
"sourcePage": "https://freepngimg.com/png/32495-kakashi-hd",
"sourceUrl": "https://freepngimg.com/save/32495-kakashi-hd/900x2332"
},
{
"id": "orochimaru",
"label": "Orochimaru",
"localPath": "assets/images/orochimaru-clean.png",
"postprocess": "remove-white-and-trim",
"searchQuery": "Orochimaru full body transparent png render",
"sourcePage": "https://www.pngitem.com/middle/iJbimTT_renders-de-naruto-y-naruto-shippuden-naruto-orochimaru/",
"sourceUrl": "https://www.pngitem.com/pimgs/m/282-2823667_renders-de-naruto-y-naruto-shippuden-naruto-orochimaru.png"
},
{
"id": "yugi",
"label": "Yugi",
"localPath": "assets/images/yugi-clean.png",
"postprocess": "remove-checkerboard-and-trim",
"searchQuery": "Yami Yugi full body transparent png render",
"sourcePage": "https://www.pngaaa.com/detail/4492094",
"sourceUrl": "https://image.pngaaa.com/94/4492094-middle.png"
},
{
"id": "kaiba",
"label": "Kaiba",
"localPath": "assets/images/kaiba-clean.png",
"postprocess": "remove-checkerboard-and-trim",
"searchQuery": "Seto Kaiba full body transparent png render",
"sourcePage": "https://www.pngaaa.com/detail/4354853",
"sourceUrl": "https://image.pngaaa.com/853/4354853-middle.png"
},
{
"id": "joey",
"label": "Joey",
"localPath": "assets/images/joey.webp",
"searchQuery": "Joey Wheeler full body transparent png render",
"sourcePage": "https://all-worlds-alliance.fandom.com/wiki/Joey_Wheeler",
"sourceUrl": "https://static.wikia.nocookie.net/all-worlds-alliance/images/d/d2/JoeyWheelerMD.png/revision/latest/scale-to-width-down/340?cb=20200516064401"
},
{
"id": "danny-phantom",
"label": "Danny Phantom",
"localPath": "assets/images/danny-phantom.webp",
"searchQuery": "Danny Phantom full body transparent png render",
"sourcePage": "https://dannyphantom.fandom.com/wiki/Danny_Phantom_(character)",
"sourceUrl": "https://static.wikia.nocookie.net/dpwikia/images/3/3c/Render-_Danny_Phantom_stock_image.png/revision/latest?cb=20250529003148"
},
{
"id": "tucker-foley",
"label": "Tucker Foley",
"localPath": "assets/images/tucker-foley.webp",
"searchQuery": "Tucker Foley full body transparent png render",
"sourcePage": "https://dannyphantom.fandom.com/wiki/Tucker_Foley",
"sourceUrl": "https://static.wikia.nocookie.net/dpwikia/images/a/ac/Render-_Tucker_Foley.png/revision/latest?cb=20250529003317"
},
{
"id": "vlad-plasmius",
"label": "Vlad Plasmius",
"localPath": "assets/images/vlad-plasmius.webp",
"searchQuery": "Vlad Plasmius full body transparent png render",
"sourcePage": "https://dannyphantom.fandom.com/wiki/Vlad_Plasmius",
"sourceUrl": "https://static.wikia.nocookie.net/dpwikia/images/6/67/Render-_Vlad_Plasmius_pose.png/revision/latest?cb=20260610224348"
},
{
"id": "spongebob",
"label": "SpongeBob SquarePants",
"localPath": "assets/images/spongebob.webp",
"searchQuery": "SpongeBob full body transparent PNG stock art",
"sourcePage": "https://spongebob.fandom.com/wiki/SpongeBob_SquarePants_(character)/gallery",
"sourceUrl": "https://static.wikia.nocookie.net/spongebob/images/5/5f/SpongeBobStockArt-2D.png/revision/latest?cb=20251102074746"
},
{
"id": "sandy-cheeks",
"label": "Sandy Cheeks",
"localPath": "assets/images/sandy-cheeks.png",
"searchQuery": "Sandy Cheeks full body spacesuit transparent PNG render",
"sourcePage": "https://commons.wikimedia.org/wiki/File:Sandy_Cheeks_(transparent).png",
"sourceUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Sandy_Cheeks_%28transparent%29.png/500px-Sandy_Cheeks_%28transparent%29.png"
},
{
"id": "plankton",
"label": "Plankton",
"localPath": "assets/images/plankton.webp",
"searchQuery": "Plankton full body transparent PNG stock art",
"sourcePage": "https://spongebob.fandom.com/wiki/Sheldon_J._Plankton/gallery",
"sourceUrl": "https://static.wikia.nocookie.net/spongebob/images/7/7a/Laughton.png/revision/latest?cb=20221113005908"
}
],
"backgrounds": [
{
"id": "konoha",
"label": "Konoha",
"localPath": "assets/images/bg-konoha.jpg",
"searchQuery": "Naruto Konoha village background no characters",
"sourcePage": "https://www.peakpx.com/en/hd-wallpaper-desktop-fzdbf",
"sourceUrl": "https://w0.peakpx.com/wallpaper/589/265/HD-wallpaper-konohagakure-anime-naruto-the-leaf-village.jpg"
},
{
"id": "naruto-forest",
"label": "Forest",
"localPath": "assets/images/bg-naruto-forest.jpg",
"searchQuery": "Naruto forest background no characters",
"sourcePage": "https://www.pinterest.com/ideas/naruto-backgrounds/941180977477/",
"sourceUrl": "https://s-media-cache-ak0.pinimg.com/originals/ed/ed/05/eded05d47a6e14ef62a7c275589f16bb.jpg"
},
{
"id": "training-ground",
"label": "Training ground",
"localPath": "assets/images/bg-training-ground.jpg",
"searchQuery": "Naruto training ground background no characters",
"sourcePage": "https://sketchfab.com/3d-models/training-field-naruto-stage-glb-c6cd7082ee864ec984b11f8cdaeefa64",
"sourceUrl": "https://media.sketchfab.com/models/c6cd7082ee864ec984b11f8cdaeefa64/thumbnails/f612af92dae545d2be1ec358e21e2b23/1e62e315d3684beab34923a07f51edd6.jpeg"
},
{
"id": "duel-arena",
"label": "Duel arena",
"localPath": "assets/images/bg-duel-arena.jpg",
"searchQuery": "Yu-Gi-Oh duel arena background no characters",
"sourcePage": "https://yugioh.fandom.com/wiki/Dueling_Arena",
"sourceUrl": "https://static.wikia.nocookie.net/yugioh/images/c/ce/DuelingArena-EN-Anime-DM.png/revision/latest?cb=20080930233413"
},
{
"id": "duel-club",
"label": "Duel club",
"localPath": "assets/images/bg-duel-club.jpg",
"searchQuery": "Yu-Gi-Oh classroom background no characters",
"sourcePage": "https://yugioh.fandom.com/wiki/Duel_Club",
"sourceUrl": "https://static.wikia.nocookie.net/yugioh/images/d/d0/Dc.png/revision/latest/scale-to-width-down/1200?cb=20170903092444"
},
{
"id": "domino-city",
"label": "Domino City",
"localPath": "assets/images/bg-domino-city.jpg",
"searchQuery": "Yu-Gi-Oh city background no characters",
"sourcePage": "https://backgrounds.gallery/backgrounds/753/1150640-Paradise%20City",
"sourceUrl": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/items/1150640/cacb64095456a03c8e04e9e886d938c1f504d29c.jpg"
},
{
"id": "amity-park",
"label": "Amity Park",
"localPath": "assets/images/bg-amity-park.webp",
"searchQuery": "Danny Phantom Amity Park street background no characters wide",
"sourcePage": "https://dannyphantom.fandom.com/wiki/Amity_Park",
"sourceUrl": "https://static.wikia.nocookie.net/dpwikia/images/c/c0/S01e16_AP_welcome_sign.png/revision/latest?cb=20160627025704"
},
{
"id": "fenton-lab",
"label": "Fenton lab",
"localPath": "assets/images/bg-fenton-lab.webp",
"searchQuery": "Danny Phantom Fenton lab background no characters",
"sourcePage": "https://dannyphantom.fandom.com/wiki/Fenton_Portal",
"sourceUrl": "https://static.wikia.nocookie.net/dpwikia/images/f/fe/S01e04_Fenton_Ghost_Portal.png/revision/latest?cb=20160428225359"
},
{
"id": "ghost-zone",
"label": "Ghost Zone",
"localPath": "assets/images/bg-ghost-zone.jpg",
"searchQuery": "Danny Phantom Ghost Zone landscape ground background no characters wide",
"sourcePage": "https://www.ign.com/wikis/nick-all-star-brawl/Ghost_Zone",
"sourceUrl": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/nick-all-star-brawl/1/19/Ghost_Zone.jpg"
},
{
"id": "bikini-bottom",
"label": "Bikini Bottom",
"localPath": "assets/images/bg-bikini-bottom.webp",
"searchQuery": "SpongeBob Bikini Bottom city background no characters wide",
"sourcePage": "https://spongebob.fandom.com/wiki/Bikini_Bottom",
"sourceUrl": "https://static.wikia.nocookie.net/spongebob/images/2/26/Bubbletown_002.png/revision/latest/scale-to-width-down/1200?cb=20181028171458"
},
{
"id": "sandy-treedome",
"label": "Sandy's treedome",
"localPath": "assets/images/bg-sandy-treedome.webp",
"searchQuery": "Sandy treedome exterior background no characters wide",
"sourcePage": "https://spongebob.fandom.com/wiki/Sandy_Cheeks%27_treedome",
"sourceUrl": "https://static.wikia.nocookie.net/spongebob/images/f/f2/Sandy%2C_Help_Us%21_002.png/revision/latest/scale-to-width-down/1200?cb=20231007030731"
},
{
"id": "boating-school",
"label": "Mrs. Puff's Boating School",
"localPath": "assets/images/bg-boating-school.webp",
"searchQuery": "Mrs. Puff Boating School exterior background no characters wide",
"sourcePage": "https://spongebob.fandom.com/wiki/Mrs._Puff%27s_Boating_School",
"sourceUrl": "https://static.wikia.nocookie.net/spongebob/images/1/12/Lighthouse_Louie_001.png/revision/latest?cb=20200527190216"
}
]
}
How the lesson becomes dialogue that fits the story world.
prompts/script-system.md# Script instruction Write a 45–70 second conversation that teaches one confusing idea through the selected story world. - The curious lead asks plain questions. - The expert gives the accurate explanation. - The third character adds tension, a useful correction, or one joke. - Use short sentences and familiar words. - Keep each speech bubble below 95 characters when possible. - Use real story-world objects and character behavior. Do not paste technical words into random lore. - End with the curious lead saying the core lesson back correctly. - Do not use generic hype, fake stakes, or filler.
How to find grounded backgrounds and clean character art.
prompts/image-search.md# Image search instruction Find full-body transparent character renders and wide story-world backgrounds with no foreground characters. Prefer clean, recognizable art that matches the original show. Record the search query, source page, and direct image URL. Reject any background that makes the characters float or clips them at an impossible ground line.
A validated scene-by-scene lesson plan.
scenes/danny-apis.json{
"id": "danny-phantom-how-apis-work",
"title": "Danny Phantom explains how APIs work",
"input": {
"topic": "How APIs work",
"storyWorld": "danny-phantom"
},
"scenes": [
{
"id": "api-01",
"speakerRole": "learner",
"visibleRoles": ["learner", "guide"],
"layout": "two-balanced",
"dialogue": "Tucker, how do two apps talk without giving each other full access?",
"background": "fenton-lab",
"estimatedDurationMs": 3700,
"callout": { "label": "API?", "theme": "question" }
},
{
"id": "api-02",
"speakerRole": "guide",
"visibleRoles": ["guide", "learner"],
"layout": "two-second-focus",
"dialogue": "They use an API, like the Fenton Portal between Earth and the Ghost Zone.",
"background": "fenton-lab",
"estimatedDurationMs": 4000,
"callout": { "label": "SAFE DOORWAY", "theme": "cool" }
},
{
"id": "api-03",
"speakerRole": "learner",
"visibleRoles": ["learner", "guide"],
"layout": "two-first-focus",
"dialogue": "The worlds stay separate, but the portal lets something cross between them.",
"background": "ghost-zone",
"estimatedDurationMs": 3900
},
{
"id": "api-04",
"speakerRole": "guide",
"visibleRoles": ["guide", "learner"],
"layout": "two-balanced",
"dialogue": "Right. An app sends a request, like a command entered into the portal controls.",
"background": "fenton-lab",
"estimatedDurationMs": 4100,
"callout": { "label": "REQUEST", "theme": "warm" }
},
{
"id": "api-05",
"speakerRole": "learner",
"visibleRoles": ["learner", "guide"],
"layout": "two-first-focus",
"dialogue": "And I need the correct control for the job, not just any button.",
"background": "fenton-lab",
"estimatedDurationMs": 3500,
"callout": { "label": "WHICH CONTROL?", "theme": "question" }
},
{
"id": "api-06",
"speakerRole": "guide",
"visibleRoles": ["guide", "learner"],
"layout": "two-second-focus",
"dialogue": "Exactly. That specific place to send a request is called an endpoint.",
"background": "fenton-lab",
"estimatedDurationMs": 3700,
"callout": { "label": "ENDPOINT", "theme": "violet" }
},
{
"id": "api-07",
"speakerRole": "guide",
"visibleRoles": ["guide", "learner"],
"layout": "two-balanced",
"dialogue": "The system does the work, then sends a response back through the doorway.",
"background": "ghost-zone",
"estimatedDurationMs": 3800,
"callout": { "label": "RESPONSE", "theme": "cool" }
},
{
"id": "api-08",
"speakerRole": "learner",
"visibleRoles": ["learner", "guide"],
"layout": "two-first-focus",
"dialogue": "Can the request say which ghost file or location I need?",
"background": "amity-park",
"estimatedDurationMs": 3300
},
{
"id": "api-09",
"speakerRole": "guide",
"visibleRoles": ["guide", "learner"],
"layout": "two-balanced",
"dialogue": "Yes. Those extra details are parameters. They make the request specific.",
"background": "fenton-lab",
"estimatedDurationMs": 3900,
"callout": { "label": "DETAILS", "theme": "warm" }
},
{
"id": "api-10",
"speakerRole": "challenger",
"visibleRoles": ["challenger", "learner", "guide"],
"layout": "three-balanced",
"dialogue": "Surely you would not let every foolish child control such a powerful portal.",
"background": "ghost-zone",
"estimatedDurationMs": 4100,
"callout": { "label": "WHO GETS IN?", "theme": "gold" }
},
{
"id": "api-11",
"speakerRole": "guide",
"visibleRoles": ["guide", "learner", "challenger"],
"layout": "three-balanced",
"dialogue": "We would not. Many APIs check a secret key or token before they answer.",
"background": "fenton-lab",
"estimatedDurationMs": 3900,
"callout": { "label": "AUTH", "theme": "violet" }
},
{
"id": "api-12",
"speakerRole": "challenger",
"visibleRoles": ["challenger", "learner"],
"layout": "two-first-focus",
"dialogue": "Send the wrong command or key, and the system returns an error instead.",
"background": "ghost-zone",
"estimatedDurationMs": 3900,
"callout": { "label": "ERROR", "theme": "warm" }
},
{
"id": "api-13",
"speakerRole": "guide",
"visibleRoles": ["guide", "learner"],
"layout": "two-second-focus",
"dialogue": "Both sides follow the same rules, so each request has a clear meaning.",
"background": "fenton-lab",
"estimatedDurationMs": 3700,
"callout": { "label": "SHARED RULES", "theme": "cool" }
},
{
"id": "api-14",
"speakerRole": "learner",
"visibleRoles": ["learner", "guide"],
"layout": "two-first-focus",
"dialogue": "So an API is a controlled doorway where software can ask and get an answer.",
"background": "amity-park",
"estimatedDurationMs": 4000
},
{
"id": "api-15",
"speakerRole": "guide",
"visibleRoles": ["guide", "learner", "challenger"],
"layout": "three-balanced",
"dialogue": "You got it. Separate systems can work together without exposing everything.",
"background": "fenton-lab",
"estimatedDurationMs": 3800,
"callout": { "label": "ASK + ANSWER", "theme": "gold" }
}
]
}
A validated scene-by-scene lesson plan.
scenes/naruto-apis.json{
"id": "naruto-how-apis-work",
"title": "Naruto explains how APIs work",
"input": {
"topic": "How APIs work",
"storyWorld": "naruto"
},
"scenes": [
{
"id": "api-01",
"speakerRole": "learner",
"visibleRoles": ["learner", "guide"],
"layout": "two-balanced",
"dialogue": "Kakashi Sensei, how do two apps talk without sharing everything?",
"background": "konoha",
"estimatedDurationMs": 3600,
"callout": { "label": "API?", "theme": "question" }
},
{
"id": "api-02",
"speakerRole": "guide",
"visibleRoles": ["guide", "learner"],
"layout": "two-balanced",
"dialogue": "They use an API, like a mission desk with a clear list of requests.",
"background": "training-ground",
"estimatedDurationMs": 3900,
"callout": { "label": "MISSION DESK", "theme": "cool" }
},
{
"id": "api-03",
"speakerRole": "learner",
"visibleRoles": ["learner", "guide"],
"layout": "two-first-focus",
"dialogue": "So I ask the desk instead of running into the secret scroll room?",
"background": "konoha",
"estimatedDurationMs": 3900
},
{
"id": "api-04",
"speakerRole": "guide",
"visibleRoles": ["guide", "learner"],
"layout": "two-balanced",
"dialogue": "Right. Your app sends a request that says exactly what it needs.",
"background": "training-ground",
"estimatedDurationMs": 3600,
"callout": { "label": "REQUEST", "theme": "warm" }
},
{
"id": "api-05",
"speakerRole": "guide",
"visibleRoles": ["guide", "learner"],
"layout": "two-first-focus",
"dialogue": "The other app does the work and sends back a response.",
"background": "naruto-forest",
"estimatedDurationMs": 3400,
"callout": { "label": "RESPONSE", "theme": "cool" }
},
{
"id": "api-06",
"speakerRole": "learner",
"visibleRoles": ["learner", "guide"],
"layout": "two-balanced",
"dialogue": "Where do I send the request?",
"background": "konoha",
"estimatedDurationMs": 2500,
"callout": { "label": "WHERE?", "theme": "question" }
},
{
"id": "api-07",
"speakerRole": "guide",
"visibleRoles": ["guide", "learner"],
"layout": "two-balanced",
"dialogue": "To an endpoint. Think of it as the right window at the mission desk.",
"background": "training-ground",
"estimatedDurationMs": 3900,
"callout": { "label": "ENDPOINT", "theme": "violet" }
},
{
"id": "api-08",
"speakerRole": "challenger",
"visibleRoles": ["challenger", "learner", "guide"],
"layout": "three-balanced",
"dialogue": "And the desk should not obey every stranger who walks in.",
"background": "naruto-forest",
"estimatedDurationMs": 3500,
"callout": { "label": "AUTH", "theme": "gold" }
},
{
"id": "api-09",
"speakerRole": "guide",
"visibleRoles": ["guide", "learner"],
"layout": "two-balanced",
"dialogue": "That is why many APIs check a key or token before helping.",
"background": "training-ground",
"estimatedDurationMs": 3600
},
{
"id": "api-10",
"speakerRole": "learner",
"visibleRoles": ["learner", "guide"],
"layout": "two-first-focus",
"dialogue": "Can my request include details, like which mission I want?",
"background": "konoha",
"estimatedDurationMs": 3500
},
{
"id": "api-11",
"speakerRole": "guide",
"visibleRoles": ["guide", "learner"],
"layout": "two-balanced",
"dialogue": "Yes. Those details are parameters, like a mission name and rank.",
"background": "training-ground",
"estimatedDurationMs": 3700,
"callout": { "label": "DETAILS", "theme": "warm" }
},
{
"id": "api-12",
"speakerRole": "challenger",
"visibleRoles": ["challenger", "learner"],
"layout": "two-balanced",
"dialogue": "If you ask for a missing scroll, the response returns an error.",
"background": "naruto-forest",
"estimatedDurationMs": 3600,
"callout": { "label": "404", "theme": "warm" }
},
{
"id": "api-13",
"speakerRole": "guide",
"visibleRoles": ["guide", "learner"],
"layout": "two-balanced",
"dialogue": "APIs can also limit requests so one ninja cannot block the whole desk.",
"background": "training-ground",
"estimatedDurationMs": 3900,
"callout": { "label": "RATE LIMIT", "theme": "violet" }
},
{
"id": "api-14",
"speakerRole": "learner",
"visibleRoles": ["learner", "guide"],
"layout": "two-first-focus",
"dialogue": "So the apps stay separate, but the request rules let them work together!",
"background": "konoha",
"estimatedDurationMs": 4000
},
{
"id": "api-15",
"speakerRole": "guide",
"visibleRoles": ["guide", "learner", "challenger"],
"layout": "three-balanced",
"dialogue": "Exactly. An API is a safe, agreed way for software to ask and answer.",
"background": "training-ground",
"estimatedDurationMs": 3900,
"callout": { "label": "ASK + ANSWER", "theme": "gold" }
}
]
}
A validated scene-by-scene lesson plan.
scenes/naruto-compilers.json{
"id": "naruto-compilers",
"title": "Naruto explains compilers versus interpreters",
"input": {
"topic": "Compilers versus interpreters",
"storyWorld": "naruto"
},
"scenes": [
{
"id": "nc-01",
"speakerRole": "learner",
"visibleRoles": [
"learner",
"guide"
],
"layout": "two-balanced",
"dialogue": "Kakashi Sensei... if compilers and interpreters both translate code...",
"background": "konoha",
"estimatedDurationMs": 3900,
"callout": {
"label": "?",
"theme": "question"
}
},
{
"id": "nc-02",
"speakerRole": "learner",
"visibleRoles": [
"learner",
"guide"
],
"layout": "two-first-focus",
"dialogue": "Why do Python and C++ work so differently?!",
"background": "naruto-forest",
"estimatedDurationMs": 3100,
"callout": {
"label": "?",
"theme": "question"
}
},
{
"id": "nc-03",
"speakerRole": "guide",
"visibleRoles": [
"guide",
"learner"
],
"layout": "two-balanced",
"dialogue": "Because they execute jutsu differently, Naruto.",
"background": "training-ground",
"estimatedDurationMs": 3000
},
{
"id": "nc-04",
"speakerRole": "challenger",
"visibleRoles": [
"challenger",
"guide"
],
"layout": "two-balanced",
"dialogue": "A compiler prepares the entire forbidden scroll before the battle begins.",
"background": "naruto-forest",
"estimatedDurationMs": 4300,
"callout": {
"label": "FULL SCROLL",
"theme": "warm"
}
},
{
"id": "nc-05",
"speakerRole": "learner",
"visibleRoles": [
"learner",
"guide"
],
"layout": "two-first-focus",
"dialogue": "So C++ works like a prepared jutsu?",
"background": "konoha",
"estimatedDurationMs": 3000,
"callout": {
"label": "FULL SCROLL",
"theme": "warm"
}
},
{
"id": "nc-06",
"speakerRole": "guide",
"visibleRoles": [
"guide",
"learner"
],
"layout": "two-balanced",
"dialogue": "Exactly. The compiler checks the whole program first.",
"background": "training-ground",
"estimatedDurationMs": 3200,
"callout": {
"label": "FULL SCROLL",
"theme": "warm"
}
},
{
"id": "nc-07",
"speakerRole": "guide",
"visibleRoles": [
"guide",
"learner"
],
"layout": "two-balanced",
"dialogue": "It turns everything into machine code, then runs it fast later.",
"background": "konoha",
"estimatedDurationMs": 3800,
"callout": {
"label": "FULL SCROLL",
"theme": "warm"
}
},
{
"id": "nc-08",
"speakerRole": "challenger",
"visibleRoles": [
"challenger",
"guide"
],
"layout": "two-balanced",
"dialogue": "An interpreter is different. It reads instructions during battle...",
"background": "naruto-forest",
"estimatedDurationMs": 3900,
"callout": {
"label": "ONE STEP",
"theme": "cool"
}
},
{
"id": "nc-09",
"speakerRole": "challenger",
"visibleRoles": [
"challenger",
"guide"
],
"layout": "two-balanced",
"dialogue": "One step at a time.",
"background": "training-ground",
"estimatedDurationMs": 2300,
"callout": {
"label": "ONE STEP",
"theme": "cool"
}
},
{
"id": "nc-10",
"speakerRole": "guide",
"visibleRoles": [
"guide",
"learner"
],
"layout": "two-first-focus",
"dialogue": "Yes. Python often runs line by line with an interpreter.",
"background": "konoha",
"estimatedDurationMs": 3600,
"callout": {
"label": "ONE STEP",
"theme": "cool"
}
},
{
"id": "nc-11",
"speakerRole": "learner",
"visibleRoles": [
"learner",
"guide"
],
"layout": "two-first-focus",
"dialogue": "Then why use interpreters at all?",
"background": "naruto-forest",
"estimatedDurationMs": 2700,
"callout": {
"label": "?",
"theme": "question"
}
},
{
"id": "nc-12",
"speakerRole": "challenger",
"visibleRoles": [
"challenger",
"learner"
],
"layout": "two-balanced",
"dialogue": "Because feedback is instant. If one hand sign fails, you see the error.",
"background": "training-ground",
"estimatedDurationMs": 4400,
"callout": {
"label": "ONE STEP",
"theme": "cool"
}
},
{
"id": "nc-13",
"speakerRole": "guide",
"visibleRoles": [
"guide",
"learner"
],
"layout": "two-balanced",
"dialogue": "Compilers catch many mistakes before the program runs.",
"background": "konoha",
"estimatedDurationMs": 3400,
"callout": {
"label": "FULL SCROLL",
"theme": "warm"
}
},
{
"id": "nc-14",
"speakerRole": "challenger",
"visibleRoles": [
"challenger",
"learner"
],
"layout": "two-balanced",
"dialogue": "Interpreters find problems while the jutsu is already running.",
"background": "naruto-forest",
"estimatedDurationMs": 3600,
"callout": {
"label": "ONE STEP",
"theme": "cool"
}
},
{
"id": "nc-15",
"speakerRole": "learner",
"visibleRoles": [
"learner",
"guide"
],
"layout": "two-first-focus",
"dialogue": "So compile errors are failed hand signs before battle...",
"background": "training-ground",
"estimatedDurationMs": 3500,
"callout": {
"label": "FULL SCROLL",
"theme": "warm"
}
},
{
"id": "nc-16",
"speakerRole": "learner",
"visibleRoles": [
"learner",
"guide"
],
"layout": "two-first-focus",
"dialogue": "And runtime errors are jutsu failing mid-fight?!",
"background": "konoha",
"estimatedDurationMs": 3200,
"callout": {
"label": "ONE STEP",
"theme": "cool"
}
},
{
"id": "nc-17",
"speakerRole": "challenger",
"visibleRoles": [
"challenger",
"learner"
],
"layout": "two-balanced",
"dialogue": "A compiler leaves you with one completed forbidden scroll.",
"background": "naruto-forest",
"estimatedDurationMs": 3400,
"callout": {
"label": "FULL SCROLL",
"theme": "warm"
}
},
{
"id": "nc-18",
"speakerRole": "learner",
"visibleRoles": [
"learner",
"guide"
],
"layout": "two-first-focus",
"dialogue": "Dattebayo... programming languages are basically ninja fighting styles!",
"background": "training-ground",
"estimatedDurationMs": 4200,
"callout": {
"label": "DUEL",
"theme": "gold"
}
}
]
}
A validated scene-by-scene lesson plan.
scenes/naruto-mcp.json{
"id": "naruto-mcp",
"title": "Naruto explains MCP",
"input": {
"topic": "Model Context Protocol",
"storyWorld": "naruto"
},
"scenes": [
{
"id": "nm-01",
"speakerRole": "learner",
"visibleRoles": [
"learner",
"guide"
],
"layout": "two-first-focus",
"dialogue": "Kakashi Sensei... why can't every AI tool just share its stuff?",
"background": "konoha",
"estimatedDurationMs": 3900,
"callout": {
"label": "?",
"theme": "question"
}
},
{
"id": "nm-02",
"speakerRole": "guide",
"visibleRoles": [
"guide",
"learner"
],
"layout": "two-balanced",
"dialogue": "Each tool guards its data like a different ninja village.",
"background": "training-ground",
"estimatedDurationMs": 3400,
"callout": {
"label": "MCP",
"theme": "violet"
}
},
{
"id": "nm-03",
"speakerRole": "learner",
"visibleRoles": [
"learner",
"guide"
],
"layout": "two-first-focus",
"dialogue": "So Claude needs a new jutsu for every app?",
"background": "naruto-forest",
"estimatedDurationMs": 3000,
"callout": {
"label": "?",
"theme": "question"
}
},
{
"id": "nm-04",
"speakerRole": "guide",
"visibleRoles": [
"guide",
"learner"
],
"layout": "two-balanced",
"dialogue": "Exactly. One for files. One for Slack. One for a database.",
"background": "konoha",
"estimatedDurationMs": 3900,
"callout": {
"label": "MCP",
"theme": "violet"
}
},
{
"id": "nm-05",
"speakerRole": "challenger",
"visibleRoles": [
"challenger",
"learner"
],
"layout": "two-balanced",
"dialogue": "That is where MCP becomes interesting.",
"background": "naruto-forest",
"estimatedDurationMs": 2900,
"callout": {
"label": "MCP",
"theme": "violet"
}
},
{
"id": "nm-06",
"speakerRole": "challenger",
"visibleRoles": [
"challenger",
"guide"
],
"layout": "two-balanced",
"dialogue": "Think of MCP as one scroll format every village can read.",
"background": "training-ground",
"estimatedDurationMs": 3600,
"callout": {
"label": "MCP",
"theme": "violet"
}
},
{
"id": "nm-07",
"speakerRole": "learner",
"visibleRoles": [
"learner",
"guide"
],
"layout": "two-first-focus",
"dialogue": "The same scroll can ask for tools, files, and data?",
"background": "konoha",
"estimatedDurationMs": 3300,
"callout": {
"label": "?",
"theme": "question"
}
},
{
"id": "nm-08",
"speakerRole": "guide",
"visibleRoles": [
"guide",
"learner"
],
"layout": "two-balanced",
"dialogue": "Yes. The AI is the shinobi asking for help.",
"background": "naruto-forest",
"estimatedDurationMs": 3200,
"callout": {
"label": "MCP",
"theme": "violet"
}
},
{
"id": "nm-09",
"speakerRole": "guide",
"visibleRoles": [
"guide",
"learner"
],
"layout": "two-balanced",
"dialogue": "An MCP server is the village gate offering approved tools.",
"background": "training-ground",
"estimatedDurationMs": 3500,
"callout": {
"label": "MCP",
"theme": "violet"
}
},
{
"id": "nm-10",
"speakerRole": "challenger",
"visibleRoles": [
"challenger",
"learner"
],
"layout": "two-balanced",
"dialogue": "It tells the AI what exists and how to use it.",
"background": "konoha",
"estimatedDurationMs": 3300,
"callout": {
"label": "MCP",
"theme": "violet"
}
},
{
"id": "nm-11",
"speakerRole": "learner",
"visibleRoles": [
"learner",
"guide"
],
"layout": "two-first-focus",
"dialogue": "So I don't rebuild the road every time?",
"background": "naruto-forest",
"estimatedDurationMs": 2800,
"callout": {
"label": "?",
"theme": "question"
}
},
{
"id": "nm-12",
"speakerRole": "guide",
"visibleRoles": [
"guide",
"learner"
],
"layout": "two-balanced",
"dialogue": "Right. Add a server, and the AI gets new abilities.",
"background": "training-ground",
"estimatedDurationMs": 3400,
"callout": {
"label": "MCP",
"theme": "violet"
}
},
{
"id": "nm-13",
"speakerRole": "challenger",
"visibleRoles": [
"challenger",
"guide"
],
"layout": "two-balanced",
"dialogue": "But the village gate still decides what the AI may touch.",
"background": "konoha",
"estimatedDurationMs": 3600,
"callout": {
"label": "MCP",
"theme": "violet"
}
},
{
"id": "nm-14",
"speakerRole": "learner",
"visibleRoles": [
"learner",
"guide"
],
"layout": "two-first-focus",
"dialogue": "One common rulebook... and many powerful jutsu.",
"background": "naruto-forest",
"estimatedDurationMs": 3100,
"callout": {
"label": "MCP",
"theme": "violet"
}
},
{
"id": "nm-15",
"speakerRole": "learner",
"visibleRoles": [
"learner",
"guide"
],
"layout": "two-first-focus",
"dialogue": "Dattebayo... MCP is basically the ninja network for AI!",
"background": "training-ground",
"estimatedDurationMs": 3700,
"callout": {
"label": "DUEL",
"theme": "gold"
}
}
]
}
A validated scene-by-scene lesson plan.
scenes/spongebob-evs.json{
"id": "spongebob-how-evs-work",
"title": "SpongeBob explains how electric vehicles work",
"input": {
"topic": "How electric vehicles work",
"storyWorld": "spongebob"
},
"scenes": [
{
"id": "ev-01",
"speakerRole": "learner",
"visibleRoles": ["learner", "guide"],
"layout": "two-first-focus",
"dialogue": "Sandy, why doesn't your new boatmobile need gasoline?",
"background": "boating-school",
"estimatedDurationMs": 3400,
"callout": { "label": "NO GAS?", "theme": "question" }
},
{
"id": "ev-02",
"speakerRole": "guide",
"visibleRoles": ["guide", "learner"],
"layout": "two-second-focus",
"dialogue": "Because it is electric. A battery stores the energy that moves it.",
"background": "boating-school",
"estimatedDurationMs": 3900,
"callout": { "label": "BATTERY", "theme": "cool" }
},
{
"id": "ev-03",
"speakerRole": "learner",
"visibleRoles": ["learner", "guide"],
"layout": "two-first-focus",
"dialogue": "So the battery is like a lunch box packed with electricity?",
"background": "sandy-treedome",
"estimatedDurationMs": 3600,
"callout": { "label": "STORED ENERGY", "theme": "warm" }
},
{
"id": "ev-04",
"speakerRole": "guide",
"visibleRoles": ["guide", "learner"],
"layout": "two-second-focus",
"dialogue": "Yep. Press the pedal, and a controller decides how much power to send.",
"background": "sandy-treedome",
"estimatedDurationMs": 4100,
"callout": { "label": "CONTROLLER", "theme": "violet" }
},
{
"id": "ev-05",
"speakerRole": "guide",
"visibleRoles": ["guide", "learner"],
"layout": "two-balanced",
"dialogue": "An inverter prepares that electricity for the motor.",
"background": "sandy-treedome",
"estimatedDurationMs": 3400,
"callout": { "label": "INVERTER", "theme": "cool" }
},
{
"id": "ev-06",
"speakerRole": "learner",
"visibleRoles": ["learner", "guide"],
"layout": "two-first-focus",
"dialogue": "Then the motor turns the wheels instead of burning gas?",
"background": "boating-school",
"estimatedDurationMs": 3600,
"callout": { "label": "MOTOR", "theme": "question" }
},
{
"id": "ev-07",
"speakerRole": "guide",
"visibleRoles": ["guide", "learner"],
"layout": "two-second-focus",
"dialogue": "Exactly. Electric energy becomes motion, so the boatmobile moves.",
"background": "boating-school",
"estimatedDurationMs": 3900,
"callout": { "label": "WHEELS TURN", "theme": "gold" }
},
{
"id": "ev-08",
"speakerRole": "challenger",
"visibleRoles": ["challenger", "learner", "guide"],
"layout": "three-balanced",
"dialogue": "Bah! Connect the battery straight to the wheels. Maximum power!",
"background": "bikini-bottom",
"estimatedDurationMs": 3700,
"callout": { "label": "BAD SHORTCUT", "theme": "warm" }
},
{
"id": "ev-09",
"speakerRole": "guide",
"visibleRoles": ["guide", "learner", "challenger"],
"layout": "three-balanced",
"dialogue": "That would be unsafe. The controller keeps the power smooth and controlled.",
"background": "bikini-bottom",
"estimatedDurationMs": 4300,
"callout": { "label": "CONTROL", "theme": "violet" }
},
{
"id": "ev-10",
"speakerRole": "learner",
"visibleRoles": ["learner", "guide"],
"layout": "two-first-focus",
"dialogue": "What happens when the battery runs low?",
"background": "sandy-treedome",
"estimatedDurationMs": 3100,
"callout": { "label": "LOW BATTERY", "theme": "question" }
},
{
"id": "ev-11",
"speakerRole": "guide",
"visibleRoles": ["guide", "learner"],
"layout": "two-second-focus",
"dialogue": "Plug it in. The charger moves electricity from the grid into the battery.",
"background": "sandy-treedome",
"estimatedDurationMs": 4200,
"callout": { "label": "CHARGE", "theme": "cool" }
},
{
"id": "ev-12",
"speakerRole": "challenger",
"visibleRoles": ["challenger", "learner"],
"layout": "two-first-focus",
"dialogue": "Braking wastes all that beautiful speed!",
"background": "boating-school",
"estimatedDurationMs": 3100,
"callout": { "label": "WASTED?", "theme": "question" }
},
{
"id": "ev-13",
"speakerRole": "guide",
"visibleRoles": ["guide", "learner"],
"layout": "two-second-focus",
"dialogue": "Not always. Regenerative braking lets the motor send some energy back.",
"background": "boating-school",
"estimatedDurationMs": 4100,
"callout": { "label": "ENERGY BACK", "theme": "gold" }
},
{
"id": "ev-14",
"speakerRole": "learner",
"visibleRoles": ["learner", "guide"],
"layout": "two-first-focus",
"dialogue": "It still uses regular brakes, and it cannot recover every bit of energy.",
"background": "boating-school",
"estimatedDurationMs": 4200,
"callout": { "label": "SOME, NOT ALL", "theme": "neutral" }
},
{
"id": "ev-15",
"speakerRole": "learner",
"visibleRoles": ["learner", "guide", "challenger"],
"layout": "three-balanced",
"dialogue": "Got it! Store, control, spin, recharge, and recover some energy when slowing.",
"background": "bikini-bottom",
"estimatedDurationMs": 4300,
"callout": { "label": "EV COMPLETE", "theme": "gold" }
}
]
}
A validated scene-by-scene lesson plan.
scenes/yugioh-compilers.json{
"id": "yugioh-compilers",
"title": "Yu-Gi-Oh explains compilers versus interpreters",
"input": {
"topic": "Compilers versus interpreters",
"storyWorld": "yugioh"
},
"scenes": [
{
"id": "yc-01",
"speakerRole": "learner",
"visibleRoles": [
"learner",
"guide"
],
"layout": "two-balanced",
"dialogue": "Kaiba... if compilers and interpreters both run code...",
"background": "duel-arena",
"estimatedDurationMs": 3500,
"callout": {
"label": "?",
"theme": "question"
}
},
{
"id": "yc-02",
"speakerRole": "learner",
"visibleRoles": [
"learner",
"guide"
],
"layout": "two-balanced",
"dialogue": "Why do C++ and Python duel so differently?",
"background": "domino-city",
"estimatedDurationMs": 3000,
"callout": {
"label": "?",
"theme": "question"
}
},
{
"id": "yc-03",
"speakerRole": "guide",
"visibleRoles": [
"guide",
"learner"
],
"layout": "two-balanced",
"dialogue": "One prepares the whole deck. The other draws turn by turn.",
"background": "duel-club",
"estimatedDurationMs": 3700,
"callout": {
"label": "DUEL",
"theme": "gold"
}
},
{
"id": "yc-04",
"speakerRole": "guide",
"visibleRoles": [
"guide",
"learner"
],
"layout": "two-balanced",
"dialogue": "A compiler checks every card before the duel begins.",
"background": "duel-arena",
"estimatedDurationMs": 3400,
"callout": {
"label": "FULL SCROLL",
"theme": "warm"
}
},
{
"id": "yc-05",
"speakerRole": "challenger",
"visibleRoles": [
"challenger",
"guide"
],
"layout": "two-balanced",
"dialogue": "So C++ enters with the whole combo ready?",
"background": "domino-city",
"estimatedDurationMs": 3000,
"callout": {
"label": "?",
"theme": "question"
}
},
{
"id": "yc-06",
"speakerRole": "guide",
"visibleRoles": [
"guide",
"challenger"
],
"layout": "two-balanced",
"dialogue": "Finally, Wheeler understands something.",
"background": "duel-club",
"estimatedDurationMs": 2600
},
{
"id": "yc-07",
"speakerRole": "guide",
"visibleRoles": [
"guide",
"learner"
],
"layout": "two-balanced",
"dialogue": "The compiler turns the full program into machine code.",
"background": "duel-arena",
"estimatedDurationMs": 3400,
"callout": {
"label": "FULL SCROLL",
"theme": "warm"
}
},
{
"id": "yc-08",
"speakerRole": "guide",
"visibleRoles": [
"guide",
"learner"
],
"layout": "two-balanced",
"dialogue": "Then it can run that prepared deck very fast.",
"background": "domino-city",
"estimatedDurationMs": 3100,
"callout": {
"label": "FULL SCROLL",
"theme": "warm"
}
},
{
"id": "yc-09",
"speakerRole": "learner",
"visibleRoles": [
"learner",
"guide"
],
"layout": "two-balanced",
"dialogue": "And an interpreter?",
"background": "duel-club",
"estimatedDurationMs": 2200,
"callout": {
"label": "?",
"theme": "question"
}
},
{
"id": "yc-10",
"speakerRole": "challenger",
"visibleRoles": [
"challenger",
"learner"
],
"layout": "two-balanced",
"dialogue": "It reads one move at a time, like drawing every turn?",
"background": "duel-arena",
"estimatedDurationMs": 3500,
"callout": {
"label": "ONE STEP",
"theme": "cool"
}
},
{
"id": "yc-11",
"speakerRole": "guide",
"visibleRoles": [
"guide",
"learner"
],
"layout": "two-balanced",
"dialogue": "Correct. Python often runs instructions line by line.",
"background": "domino-city",
"estimatedDurationMs": 3300,
"callout": {
"label": "ONE STEP",
"theme": "cool"
}
},
{
"id": "yc-12",
"speakerRole": "learner",
"visibleRoles": [
"learner",
"guide"
],
"layout": "two-balanced",
"dialogue": "That sounds slower. Why use it?",
"background": "duel-club",
"estimatedDurationMs": 2700,
"callout": {
"label": "?",
"theme": "question"
}
},
{
"id": "yc-13",
"speakerRole": "challenger",
"visibleRoles": [
"challenger",
"learner"
],
"layout": "two-balanced",
"dialogue": "Because when a card fails, ya know right away.",
"background": "duel-arena",
"estimatedDurationMs": 3100,
"callout": {
"label": "ONE STEP",
"theme": "cool"
}
},
{
"id": "yc-14",
"speakerRole": "guide",
"visibleRoles": [
"guide",
"learner"
],
"layout": "two-balanced",
"dialogue": "Compilers catch many errors before the duel starts.",
"background": "domino-city",
"estimatedDurationMs": 3200,
"callout": {
"label": "FULL SCROLL",
"theme": "warm"
}
},
{
"id": "yc-15",
"speakerRole": "guide",
"visibleRoles": [
"guide",
"challenger"
],
"layout": "two-balanced",
"dialogue": "Interpreters reveal errors while the program is running.",
"background": "duel-club",
"estimatedDurationMs": 3400,
"callout": {
"label": "ONE STEP",
"theme": "cool"
}
},
{
"id": "yc-16",
"speakerRole": "learner",
"visibleRoles": [
"learner",
"guide"
],
"layout": "two-balanced",
"dialogue": "So compile errors are illegal cards before the duel...",
"background": "duel-arena",
"estimatedDurationMs": 3500,
"callout": {
"label": "FULL SCROLL",
"theme": "warm"
}
},
{
"id": "yc-17",
"speakerRole": "challenger",
"visibleRoles": [
"challenger",
"learner"
],
"layout": "two-balanced",
"dialogue": "And runtime errors are your combo exploding mid-turn!",
"background": "domino-city",
"estimatedDurationMs": 3300,
"callout": {
"label": "ONE STEP",
"theme": "cool"
}
},
{
"id": "yc-18",
"speakerRole": "learner",
"visibleRoles": [
"learner",
"guide"
],
"layout": "two-balanced",
"dialogue": "Programming languages are just different duel strategies!",
"background": "duel-club",
"estimatedDurationMs": 3400,
"callout": {
"label": "DUEL",
"theme": "gold"
}
}
]
}
The visual rules: moving background, characters, bubble, props, and active speaker.
renderer/OtakuFormatRenderer.tsximport type { CSSProperties } from "react";
export type OtakuCharacterPlacement = {
asset: string;
x: number;
bottom: number;
width: number;
rotate?: number;
};
export type OtakuScene = {
id: string;
speaker: string;
dialogue: string;
background: string;
estimatedDurationMs: number;
durationMs?: number;
audioPath?: string;
characters: OtakuCharacterPlacement[];
callout?: {
label: string;
theme: "neutral" | "question" | "warm" | "cool" | "violet" | "gold";
};
};
export type OtakuAsset = {
id: string;
label: string;
localPath: string;
};
export type OtakuAssetLibrary = {
characters: OtakuAsset[];
backgrounds: OtakuAsset[];
};
export type OtakuFormatRendererProps = {
assets: OtakuAssetLibrary;
durationMs: number;
resolveAsset?: (localPath: string) => string;
scene: OtakuScene;
timeInSceneMs: number;
};
const packageBase = "/format-repositories/otaku-explainer-v1/";
const defaultResolveAsset = (localPath: string) => `${packageBase}${localPath}`;
type CalloutTheme = NonNullable<OtakuScene["callout"]>["theme"];
const calloutColors: Record<CalloutTheme, [string, string]> = {
neutral: ["#ffffff", "#64748b"],
question: ["#ffe66d", "#ff7b00"],
warm: ["#ff5d4a", "#7d160b"],
cool: ["#55d6ff", "#1154b7"],
violet: ["#c98cff", "#5a2ad1"],
gold: ["#fff06a", "#6c41ff"],
};
const speechFontSize = (dialogue: string) => {
if (dialogue.length > 82) return 30;
if (dialogue.length > 64) return 34;
if (dialogue.length > 46) return 38;
return 44;
};
function Callout({ callout }: { callout?: OtakuScene["callout"] }) {
if (!callout) return null;
const [bright, dark] = calloutColors[callout.theme];
return (
<div style={{
position: "absolute",
zIndex: 2,
left: "42%",
top: "57%",
width: 145,
padding: "18px 14px",
border: `5px solid ${dark}`,
borderRadius: 24,
background: `linear-gradient(135deg, ${bright}, ${dark})`,
boxShadow: `0 10px 30px ${dark}88, inset 0 0 0 3px rgba(255,255,255,.35)`,
color: "white",
fontFamily: "Arial Black, sans-serif",
fontSize: 20,
lineHeight: 1,
textAlign: "center",
transform: "rotate(-7deg)",
}}>{callout.label}</div>
);
}
export function OtakuFormatRenderer({
assets,
durationMs,
resolveAsset = defaultResolveAsset,
scene,
timeInSceneMs,
}: OtakuFormatRendererProps) {
const progress = Math.max(0, Math.min(1, timeInSceneMs / Math.max(1, durationMs)));
const background = assets.backgrounds.find((asset) => asset.id === scene.background);
const [bright] = scene.callout ? calloutColors[scene.callout.theme] : ["#ffffff"];
return (
<div style={{
position: "relative",
width: 720,
height: 1280,
overflow: "hidden",
background: "#101827",
color: "#111",
fontFamily: "Arial Rounded MT Bold, Arial, sans-serif",
}}>
{background ? (
<img
src={resolveAsset(background.localPath)}
alt=""
style={{
position: "absolute",
inset: "-5% auto -5% -3%",
width: "116%",
height: "110%",
objectFit: "cover",
objectPosition: "center bottom",
transform: `translate3d(${-8 * progress}%, 0, 0) scale(1.04)`,
filter: "saturate(.9) contrast(1.02) brightness(.83)",
}}
/>
) : null}
<div style={{
position: "absolute",
inset: 0,
background: "linear-gradient(180deg, rgba(7,12,24,.34) 0%, rgba(7,12,24,0) 32%, rgba(7,12,24,.12) 68%, rgba(7,12,24,.58) 100%)",
}} />
<Callout callout={scene.callout} />
{scene.characters.map((placement, index) => {
const character = assets.characters.find((asset) => asset.id === placement.asset);
if (!character) return null;
const isSpeaking = placement.asset === scene.speaker;
const transform = [
`rotate(${placement.rotate || 0}deg)`,
`scale(${isSpeaking ? 1.035 : 1})`,
].join(" ");
const style: CSSProperties = {
position: "absolute",
zIndex: isSpeaking ? 4 : 3,
left: `${placement.x}%`,
bottom: `${placement.bottom}%`,
width: `${placement.width}%`,
maxHeight: "73%",
objectFit: "contain",
objectPosition: "center bottom",
transform,
transformOrigin: "center bottom",
filter: isSpeaking
? `drop-shadow(0 0 7px ${bright}) drop-shadow(0 12px 8px rgba(0,0,0,.55))`
: "drop-shadow(0 11px 7px rgba(0,0,0,.55)) brightness(.9)",
opacity: isSpeaking ? 1 : 0.94,
};
return <img key={`${scene.id}-${placement.asset}-${index}`} src={resolveAsset(character.localPath)} alt={character.label} style={style} />;
})}
<div style={{
position: "absolute",
zIndex: 6,
top: "5.2%",
left: "9%",
width: "72%",
minHeight: 206,
padding: "38px 38px 34px",
border: "7px solid #101010",
borderRadius: "48% 48% 44% 46% / 50% 46% 52% 48%",
background: "#fff",
boxShadow: `0 13px 0 rgba(0,0,0,.26), 0 0 0 7px ${bright}33`,
display: "flex",
alignItems: "center",
justifyContent: "center",
}}>
<div style={{
color: "#0a0a0a",
fontFamily: "Arial Rounded MT Bold, Arial, sans-serif",
fontSize: speechFontSize(scene.dialogue),
fontWeight: 800,
lineHeight: 1.06,
letterSpacing: -1.2,
textAlign: "center",
overflowWrap: "anywhere",
}}>{scene.dialogue}</div>
<div style={{
position: "absolute",
left: "24%",
bottom: -28,
width: 54,
height: 54,
borderRight: "7px solid #101010",
borderBottom: "7px solid #101010",
background: "white",
transform: "rotate(38deg) skew(8deg, 8deg)",
}} />
</div>
<div style={{
position: "absolute",
zIndex: 8,
left: 24,
bottom: 18,
color: "rgba(255,255,255,.88)",
fontSize: 20,
fontWeight: 700,
letterSpacing: 0.3,
textShadow: "0 2px 8px rgba(0,0,0,.8)",
}}>@Wiggly_Format_Lab</div>
</div>
);
}
Shared Fish model, speaking speed, and default music. World packs own their voices.
audio.json{
"provider": "fish-audio",
"model": "s2.1-pro-free",
"dialogue": {
"format": "wav",
"sampleRate": 44100,
"speed": 1.08
},
"music": {
"localPath": "assets/audio/background-music.mp3",
"volume": 0.1,
"loop": false,
"preparation": "The runner trims or crossfades the packaged track into one full-length music bed for each video."
}
}
The checks every rerun should pass.
quality.json{
"checks": [
{ "id": "same-renderer", "label": "Every proof uses the same renderer and scene contract." },
{ "id": "bubble-fit", "label": "Every line fits inside its speech bubble." },
{ "id": "grounded-characters", "label": "Feet or lower body meet a believable ground line." },
{ "id": "background-pan", "label": "Every scene has visible right-to-left background movement." },
{ "id": "voice-match", "label": "The active speaker uses the assigned Fish Audio voice." },
{ "id": "music-balance", "label": "Music has no vocals, clipping, or dead loop gap and stays below the dialogue." },
{ "id": "simple-language", "label": "A nontechnical viewer can repeat the lesson after watching." },
{ "id": "world-fit", "label": "The analogy uses objects and behavior from the selected story world." }
]
}
danny-phantom · 15 scenes
naruto · 15 scenes
naruto · 18 scenes
Naruto · 15 scenes
spongebob · 15 scenes
Yu-Gi-Oh · 18 scenes
Drafts, the latest contact sheet, quality report, and final video appear here after refresh.