# Creator guide An episode is a script, artwork, sound and connected movement—not engine code. Creators write YAML and local media only. The working product example is `content/series/murim-sega-maknae/`; the smallest reusable test example is `fixtures/content/series/dev-reuse-test/`. Fixtures are never product releases. ## 0. Start in ten minutes ```sh bun install bun run toonpg new-series my-toon --title "My Toon" bun run toonpg new-episode my-toon bun run toonpg validate bun run toonpg verify --series my-toon bun run dev ``` The scaffold includes starter art and a finishable episode. Replace its writing and drawings. Saving rebuilds content and reports `file:line:column` diagnostics. The local series route is `http://localhost:3000/series/my-toon`. | Task | Command | | --- | --- | | New series | `toonpg new-series --title "Title"` | | Next episode | `toonpg new-episode ` | | New character | `toonpg new-character ` | | Read-only validation | `toonpg validate` | | Complete and resume every checkpoint | `toonpg verify [--series s] [--episode e]` | | Inventory | `toonpg list` | | Watch and preview | `bun run dev` | Use `bun run toonpg` before each CLI command. English is the canonical source language. Published reader text supports English, Korean and Japanese through series dictionaries; see [INTERNATIONALIZATION.md](INTERNATIONALIZATION.md). ## 1. Files and ids ```text content/ creators/official/creator.yaml series// series.yaml locales/{en,ko,ja}.json shared-assets/cover.webp characters/hero.yaml characters/hero.webp episodes/001/ episode.yaml stages/yard.yaml story/main.yaml assets/ ``` Ids use lowercase letters, digits, hyphen and underscore, at most40 characters. File/folder and declared id agree. Episode/beat/checkpoint/action ids are permanent even if display numbers change. Quote numeric-looking YAML ids: `id: "001"`. Characters/shared assets are reused within a series, not copied for each episode. Asset paths are episode-relative; `@series/...` refers to `shared-assets/...`. Character media paths are relative to its character file. ## 2. File fields ### Series ```yaml schema: 1 id: my-toon title: My Toon creator: official status: draft # draft → preview → published placeholder: false # true can never publish featured: false # optional editorial TV spotlight; never bypasses publication gates genre: [Action, Drama] synopsis: A short introduction. serialization: ongoing # ongoing | completed | hiatus cover: shared-assets/cover.webp logo: shared-assets/logo.webp # optional; avoid language baked into shared art still: shared-assets/still.webp # optional 16:9 gameplay frame without HUD; the TV home shows it language: en viewport: { width: 480, height: 270 } render: pixel # pixel | smooth motion: drawn # puppet is legacy mesh compatibility dialogueCamera: stage # keep authored framing during dialogue theme: accent: '#7c5cff' dialogue: { background: 'rgba(12,14,22,0.88)', text: '#f5f7ff', border: '#7c5cff' } skin: stickBase: shared-assets/ui/stick-base.webp stickKnob: shared-assets/ui/stick-knob.webp buttonA: shared-assets/ui/btn-coral.webp dialogue: { image: shared-assets/ui/dialogue.webp, slice: [78, 78, 78, 78] } ``` Smaller logical viewport makes characters larger. Default640×360 and16:9 are recommended; wider than2.4:1 warns. Genre supports at most5 entries. Display budgets account for CJK versus Latin width (e.g.60 CJK title characters or a comparable Latin line), rather than assuming English fits in the same raw count. Skin supports stick base/knob, A/B/pause, dialogue, panel, nameplate, chip and bar frame/fill. `buttonFrame` is an optional nine-slice border with a transparent center, so selected colors and localized labels remain live. `panel` supplies the nine-slice material for menus, settings, combat instructions and inventory. Missing pieces use defaults. Labels are rendered by the game. Nine-slice order is top/right/bottom/left in source pixels. `toonpg art ui` cuts a kit. Colors accept only hex3/4/6/8 or comma-form rgb/rgba with valid ranges; CSS names, hsl and malformed hex are rejected before playback. ### Characters ```yaml id: hero name: Hero color: '#ffd166' sprite: image: hero.png frame: { width: 32, height: 48 } layout: standard anchor: { x: 16, y: 44 } scale: 1 height: 44 speed: 84 collider: { width: 14, height: 8 } hand: down: { x: 9, y: -16 } up: { x: -9, y: -16 } left: { x: -10, y: -16 } right: { x: 10, y: -16 } mirror: { left: right } ``` Anchor is the sole/ground reference in source pixels. Collider is a separate foot box; height is actual world body height, not overhead weapon canvas. Custom `clips` override standard layout definitions: ```yaml sprite: clips: idle: { fps: 1, loop: true, frames: { down: [0], up: [8], left: [16], right: [24] } } walk: { fps: 8, loop: true, frames: [1, 2, 3, 4] } ``` Grid frames use numbers. TexturePacker hash/array atlases use names; trimmed frames retain source-frame anchor coordinates. `idle` is required; absent walk warns about sliding. Standard quarter-view art uses front `downRight`, back `upRight` and left mirroring. More specific drawn directions take precedence; legacy four-direction/`any` clips remain supported. Import with `toonpg art character`; see [ART_WORKFLOW.md](ART_WORKFLOW.md). ```yaml carry: bucket: { idle: carry_idle, walk: carry_walk } portraits: default: portraits/hero-default.webp shock: portraits/hero-shock.webp rage: portraits/hero-rage.webp emotes: { shock: surprise, rage: anger, cry: sweat, daze: question } ``` Without carry art, an owned prop attaches to `hand`. Matching carry art or `heldProp:true` bakes it into the pose and hides the duplicate external object. Drawn mode adds no prop sway. Use `retarget:false` for sequential drawn gait; legacy puppet interpolates feet/breathing/nods. Follow SPRITE_GUIDE for full front/back gait, identity, equipment and recovery checks. Intermediate body turns use `turn_`, `frames.any` and matching `turnAngles` (0front,90right,180back). At ordinary quarter angles the base idle/gait returns. Moving turns require phase-matched moving art, not standing frames. Mirror the left. Preserve props with `heldProp` and matching carry aliases. Simulation time pauses turning; attack/hit/KO contacts are not delayed by idle turns. Resume/skip/new placement starts at the final pose. Inspect with `turns`. Portrait mood syntax is `hero@shock`. Unknown moods fail (`UNKNOWN_MOOD`), narration cannot have a portrait mood, and absent default warns. Dialogue places the player's portrait left and others right; timed stage dialogue can reflect the speaker's screen side. Optional mood-emotes add a small expression gesture; do not attach noisy balloons to every ordinary line. ### Episode, inventory and crowds ```yaml schema: 1 id: '001' number: 1 title: The First Day status: draft # publishedAt: 2026-10-10 thumbnail: assets/thumb.png estimatedMinutes: 4 player: hero cast: [hero, guide, rival] crowd: guards: { character: guard, count: 12 } props: token: image: assets/token.png anchor: { x: 8, y: 8 } items: paddle: name: Laundry Paddle slot: weapon prop: paddle text: The familiar paddle she has carried since childhood. power: 1 story: [01-arrival, 02-follow] ``` Published episodes require a date. `cast` includes the player. Crowd guards expand to guards1…12 sharing art but independent state. Place/walk/anim/face/ remove can address a group; `stagger` offsets acting. A beat downloads only used or present actors. Inventory item slots are `weapon`, `outfit`, `trinket`, or absent for carried-only items. `prop` links the world's object and supplies its icon when no icon is set. Weapon power controls damage; character `level` (default1) controls combos. `item:{give:paddle}` shows an acquisition notice (`quiet:true` suppresses it). `waitFor:{equip:paddle}` calls attention to the bag, selects the item and closes the bag after equipping; the bot equips after2.2s. An unavailable item or an equip wait inside cinema fails compilation. The bag pauses the world. Fighting locks equipment. A held linked prop cannot be unequipped; a visibly grounded prop cannot be equipped. Giving a prop to the hero quietly equips it; release equipment when it leaves the hand. Equipment records are tied to the step where the change occurred. Resume replays only records before its checkpoint, discards later records after rewind/skip and invalidates incompatible records after content revision. Story item/equip/level steps rebuild inventory and levels; live/resume equality is tested. ### Stages ```yaml id: yard name: Courtyard size: { width: 960, height: 360 } background: assets/yard-bg.png dust: true tilt: 1 foreground: - { image: assets/tree.png, x: 94, y: 118, sortY: 250 } walkable: - { x: 24, y: 150, width: 912, height: 190 } - poly: [[100, 40], [300, 130], [100, 220]] blockers: - { id: well, x: 452, y: 236, width: 56, height: 26 } anchors: hero_start: { x: 90, y: 292 } zones: approach: { x: 230, y: 160, width: 140, height: 180 } to_lane: { x: 932, y: 150, width: 28, height: 190 } props: - { id: footprint, prop: marker, at: step_1, hidden: true } interactables: - { id: crate, at: crate_spot, radius: 40, label: Inspect } - id: well at: { x: 480, y: 268 } once: true lines: [{ narration: 'A well. The water is clear.' }] exits: - zone: to_lane to: { stage: lane, at: from_yard } facing: right ``` Examples are field references, not a standalone complete episode: define all referenced props/anchors/stages before using them. Coordinates start top-left, y increases downward. Author walkable/blockers yourself; blockers are an object's ground footprint, not its entire painted silhouette. `sortY` puts actors with lower foot y behind foreground. `resume` for zones and `stand` for interactables can choose the nearest legal position; unreachable required positions fail compilation. Quarter view combines an angled diamond background, `tilt` (~0.4–0.5) and front/ back character art. Ground distance divides screen y by tilt, correcting speed, near and inspection radii. Tilt1 is a flat front-view plane. Larger1280×720 stages scroll inside a640×360 logical viewport. Read vertices from the artwork and scale by stageWidth/imageWidth. Polygon vertices follow their perimeter (3–64); self-crossing/flat polygons fail. Oblique walls slide; direct pressure stops. Debug overlays show terrain alignment. ### Connected maps Exiting right must enter the next stage from the left; stairs upward enter at the bottom threshold, and return paths work. Put exits in thin edge strips and arrival anchors just inside, outside the trigger. An arrival lock prevents immediate bouncing until the zone is left. Exits operate only with exploration ownership, fade through black and announce the destination name. Held objects follow; offstage NPCs/props remain prepared there. Compile checks zones, stages, anchors, legal arrival, one-way warnings and unreachable exits. `connected-stages.test.ts` tests all real product stages. `waitFor` with `stage:` requires the action there; no route gives `STAGE_UNREACHABLE`. Bots walk the exit route; manual HUD shows a direction arrow. ```yaml - place: guide: { at: well_spot, facing: left, stage: well } - control: on - objective: Fetch water from the well - waitFor: { interact: well, stage: well } - prop: { id: bucket, show: well_front } ``` `place` and `prop:show` can prepare a different stage explicitly. All stage props exist from the start; hidden ones wait for show. After a stage-qualified wait, subsequent story references use that destination stage. ### Story files ```yaml beats: - id: arrival title: Arrival steps: - stage: { id: yard, fade: 700ms } - place: hero: { at: hero_start, facing: right } guide: { at: guide_start, facing: down } - control: on - objective: Walk to the gate - waitFor: { reach: approach } - control: off - dialogue: - guide: { text: Welcome., to: hero } - hero: { text: Thanks., to: guide } - checkpoint: met ``` ## 3. Story command reference One command per list entry. Durations accept numeric milliseconds, `300ms` or `1.5s`. Chain events on actual arrival/completion; use wait only for intended rhythm. | Command | Shape / behavior | | --- | --- | | stage | `stage:yard` or `{id:yard,fade:600ms}` enters a space | | place | Actor→anchor/point, optional facing/stage; crowd row/grid/scatter layouts | | remove | Actor, list or crowd; remove from the stage | | walk | Actor/to point, waypoints or `{approach:hero,within:40}`; wait:false, speed, leash, backward, crowd except | | face | Actor/toward actor, anchor, point or cardinal direction; ground-space heading; crowd except | | anim | Actor/clip, wait (defaulttrue), loop, hold, facing, crowd stagger; idle ends immediately | | emote | surprise/question/silence/anger/joy/heart/sweat/idea/sad, or hop/jump/bow/nod/shake/tremble/startle/lean;0.2–6s, optional wait | | listen | on/off; nearby listeners visually face the speaker without rewriting world facing | | dialogue | List of speaker→text or `{text,to}`; `narration` is speakerless | | say | `{who:guide,text:'Hello.'}` | | camera | focus/follow, zoom, time, wait, snap; authored from/ease/sway/roll | | control | on/off or `{on:true,facing:right}`; fixed facing permits backward movement | | objective | Text or null; approximately60CJK/120Latin display budget | | waitFor | reach zone, near pair/within, interact id, equip item; optional stage | | give | Prop/from/to/time; transfers hand ownership | | throw | Prop/from/to/time/arc/spin/wait; arc rises at midpoint and lands persistently | | ghost | Actor/alpha/time; persistent transparency | | prop | id/show anchor/stage or id/hide:true | | title | text/sub/time;0.8–12s title card | | grade | color/alpha/blend/time or off; persistent multiply/screen/add/normal | | fx | kind/at/color/scale/time/count/angle; transient seeded effects | | slow | scale/time; up to5s | | cinema | aspect/time/skip or off; bars and film HUD | | subtitle | Text or who@mood/text/name/to/time/wait; timed dialogue/narration | | bubble | who/text/name/to/time/shout/wait; short local speech balloon | | caption | text/sub/time/style; corner time/place or centered technique name | | insert | image/time/move/fade/shake/wait; brief matching-action insert | | ambient | petals/embers/mist/dust/rays/ash/rain0–1 and transition time; off clears | | impact | true or at/time;60–600ms transient contrast frame | | blur | amount/time; transient blur/reveal | | cutin | image/caption/time/full; without time waits for confirmation | | action | id/module and validated module parameters | | item | give/take and quiet | | equip | Item id or `{off:weapon}` | | level | actor/to; level-up notice and longer combos | | music | Path, play/fade/volume, or stop; looped beat music | | sfx | Path or play/volume | | fade | to/from:black,time | | flash / shake | Transient visual effects, reduced by motion preference | | wait | Authored interval | | set | Flag/value | | when | flag/is/then/else; reaction differences only, never plot branches | | parallel | Concurrent commands/lists; all finish before continuation | | checkpoint | Permanent id at beat top level | | complete | Last step of the last beat, once only | Crowd grid uses at/columns and col/row vectors. Scatter uses at/radius/gap/seed, legal ground and deterministic placement. Measure screen body spacing too. Walk leash waits when the player falls too far behind. Backward preserves gaze while feet follow travel. `except` leaves fallen crowd members down. Gesture clips between lines should use wait:false and recover to idle, not hold a pointing pose over other speakers. `glide` is a special effect, not human gait. Use footwork with drawn lift/plant frames. Effects include slash, spark, ring, burst, arrows, glow, smoke, wave, embers, geese and fire. Colored arrows can represent fire arrows. Geese use angle/count/ travel time; fire uses count/scale/lifetime. All transient effects skip on resume. Ghost/grade/ambient/camera state persists. Camera from/ease defines exact timed push/dolly; sway is pixels and roll degrees. Use visible650ms eased transitions, with snap only under black. ### Speech, gaze and framing Named timed speakers use `subtitle:{who:hero@mood,text,to}` with portrait/name and a matching small gesture. Extras use a crowd actor and display name instead of a new character. Balloons appear near their speaker with a short tail: head when above, body midpoint when beside. Do not push them far away with long tails. Keep balloons short (~32CJK-equivalent); longer speech belongs in subtitle/dialogue. **One voice at a time.** New speech clears old speech, so author enough reading time before the next line. Short shouts often need0.6–1.2s, sentences1.5–2.4s, longer lines~3s. Remove excess lines instead of accelerating everything. `overlap --speech` must report0 shortened lines. Combat technique/result text uses floaters, not speech balloons. Every dialogue line faces its recipient using `to:`; compilation inserts the face step, including during resume. Turn back toward the altar after a reply if needed. `overlap --facing` must report0; without explicit to it infers a nearby adjacent speaker, while battle-wide action shouts are excluded. Inserts appear immediately after the matching sprite action, same pose and orientation. Only brief narration can overlay an insert; hidden actors must not speak beneath it. `combat-film` barks have at/who/text/time/name/mood/bubble/shout; a bark starts after the preceding one ends. `dialogueCamera:stage` keeps the author's camera and adds no automatic zoom or box inset. Compatible `speaker` mode frames speaker/listener (previous speaker, otherwise nearest), includes half-visible named bodies, holds framing briefly between lines and returns to follow. The speaker's steady color ring links the box and body. Dialogue always docks below; old top/bottom switching is obsolete. Frame entire heads/feet/props above the box and respect up-to2.4:1 width. Use `shots` through camera motion, not just at the final position. ## 4. Actions ### Musical performance `module: music` performs on the current stage. Legacy free play uses level8; scored charts, native entrances and modern skins need level9. Five lanes use `left`, `down`, `ok`, `up` and `right`. Each cue needs a fresh press through InputRouter; held keys do not repeat. Left/right chooses a low/high arrangement only during its marked rest. The ordinary reader bot earns the same hits and clears; manual takeover never secretly fills missing notes. Required parameters: `bpm` (60-150), `beats` (16-256), `listenBeats`, `chooseBeat`, `choiceBeats` (2-8), `backing` (audio), `lead`, `drummer` and `notes`. `windowMs` defaults to180. Optional `answer` is the audible drum reply after four accepted notes. Ordered notes are `{beat, input?, asset, alternate?}`, at least half a beat apart, outside the selection rest and final two beats. An omitted input means `ok`; `alternate` supplies the high riff's actual audio. Stop ordinary background music first. The backing must omit the playable lead. Optional `passRate` is0..1 (default0, no gate); `level` and `levels` label the progression. A gated chart must have playable notes. Its final accuracy is hits / (playable cues + wrong presses), displayed without rounding a failure up to the threshold. Perfect/good hits grow combo; misses and wrong keys reset it. At the song end, a failure waits for fresh OK and retries only this action. No outcome flag or next checkpoint is written before a pass. Two failed takes widen timing by60ms but retain the pass requirement. Back pauses the song and opens Watch together; returning to automatic watching uses the normal bot. Earlier cleared checkpoints survive retries and resume. Introduce sparse whole beats before half-beat runs; no simultaneous buttons. Neon's three charts use90/105/120BPM,60/70/80percent thresholds and240/200/160ms windows. At120BPM, half-beats leave250ms between presses. Use complete-bar lengths and explain what clearing each rehearsal unlocks in the next dialogue. Optional `skin: {panel, key?, marquee?}` registers images through the validator. The native marquee frames the localized title and can also skin choice/result controls. Call this asset `marquee`, not the translatable text field `title`. Keep live text, arrows, scores and OK in localized DOM, never baked into art. Native sources/prompts are under `fixtures/content/series/dev-band/art-src/`. The selected v4 native graphite arcade cabinet frames five color-coded lanes. Keep OK in the middle, with violet/cyan lanes around its gold lane. Render live notes as luminous horizontal bars, beat lines in simulation time, and combo feedback above the judgment line. Align DOM targets over the five native deck keys; do not stack a second set of generic keycap pictures. Guidance belongs on dark space above the lanes, clear of metal bevels and bolts. Touch keys are at least44px, submit on pointer-down and do not duplicate the following click. Performers bind `{actor, entry?, clip, exit, finish, contactFrame, restFrame}`. `finish` defaults to idle. Each attempt plays the optional complete-body `entry` before starting the chart clock, then selects existing contact/rest drawings. The final800ms plays `exit` and settles to `finish`; keep that clip short enough to reach canonical standing. Never assemble limbs or distort the body. Retain one instrument representation, anchored feet and visible instrument tips. Musical voices follow music volume even when incidental SFX are disabled. `bun scripts/music-challenges.mts` rebuilds the three fixture/product charts, original instrumental audio and manifest. It includes the older proof generator; running `music-proof.mts` alone restores the superseded one-song fixture. The production story uses local asset copies; fixtures never enter its catalog. Music muted/unavailable still permits input and scoring. This is instrumental, not a recorded vocal song. Browser checks do not measure physical Fire TV remote/audio latency; verify that output path before claiming timing quality. ### Basic duel ```yaml - action: id: duel1 module: duel foe: rival arena: arena playerStart: duel_hero foeStart: duel_rival focus: duel_look playerHp: 5 foeHp: 3 telegraph: 600ms assistAfter: 2 skipAfter: 3 ``` A attacks, B dodges briefly; a yellow telegraph signals a strike. Repeated losses provide assistance then skip so readers are not trapped. The permanent action flag records win/assisted/skipped; only reactions may differ afterward. Start anchors must be legal. Frame spectators below HP bars and the referee between them. ### Sword duel: tactics and command ring `mode:sword` uses deliberate turns, automatic drawn combinations and a quarter-view board. One turn permits one move plus one command. The mobile/ desktop ring has attack at top, then skill, ultimate, item, rest, move, flee, scout clockwise. Locked slots show only a lock and accept no selection. Selected sectors pop outward; confirmation animates briefly before execution. Arrow/stick selection has a0.09s dwell; Enter/Space confirms,1–8 select clockwise. TV uses a large D-pad/OK command list with only relevant actions and readable reasons. The board appears while choosing/making a move, not under all combat. Legal tiles are blue; recommendations darker blue never prohibit other blue tiles. Movement follows four shared-edge neighbors, one tile at a time, avoiding the foe's tile. Range is `lightness+1` tiles (default lightness1); TILE32 gives64px screen width. Ordinary starts are four tiles apart; tutorial starts closer. Board speed is1.3×body speed, keeping drawn stride. Keep arrival direction until a strike turns toward the opponent. Adjacent means the four shared-edge cells **plus direct left/right**, six total, excluding direct top/bottom. Finishing a move adjacent immediately opens target selection; otherwise Rest ends the turn. Attack first opens target selection. Only an occupied foe tile is red. Select its tile/body or confirm; an empty tile does nothing; Back/cancel returns to commands. Preview hit chance and damage before committing. Board taps must not leak through top HUD. Combos depend on level:1hit;1–2;2–3;3–4, with varied successive techniques at higher levels. Each landed strike uses weapon power and a damage floater. Behind the opponent's facing, a strike cannot be blocked/dodged and adds one hit. The foe turns on its own turn. Automatic dodge uses `0.15 + 0.05*levelDifference + 0.08*lightnessDifference`, clamped5–60%. Scout spends a turn, shows level/HP/lightness/weapon/note, guarantees the next attack and adds one hit, with30% extra evasion until that attack. Rest heals1; item opens the locked-for-combat bag. The foe moves its own lightness range through red preview tiles, then attacks if adjacent. HP uses continuous gauges and numbers, not tile-like pips. Technique text sits above, damage/results lower. Seeded encounter/attempt randomness makes identical decisions reproducible. `tutorial:true` teaches intro→approach→attack→foe evade→back move→back attack→free. Guarantee the first hit/evade, skip impossible lightness lessons and credit actions already performed. All blue movement stays available. Read cards before bot input (roughly1.7–5.2s); confirming an introduction does not disable autoplay. Cards use the wider lower side without covering feet/ring/preview; during tile selection put guidance in the bottom movement line. Retry does not repeat the first tutorial. Give the foe enough HP for two/three free exchanges afterward. At foeHP1, stop the combo. The foe approaches if needed and winds up a final cleave **where it stands**. Authored finisher/recoil/fall follow there. After a brief hold, fade black0.45s, place both at playerEnd/foeEnd using the same settle as resume, then reveal. Do not walk back to the opening stance. Cancellation must clear the dark overlay. Required/fallback art: duel_ready; thrust/slash/cleave, their wind/strike variants and hitFrame; guard variants, side/back dodge, hurt; carry/fight_walk/walk for board motion. `combat_*` is a compatible fallback. Author finisher, finisherWindup and finisherFall. `talk` event arrays cover start/hit/combo/blocked/hurt/parry/ dodge/back/rest/scout/last. Lines are consumed once in order; exhausted events fall silent. Drop incidental reactions while someone speaks; start/last wait. Suppress reactions at the final HP so last reads clearly. Observers may speak only while present. Keep lines short and actual dialogue; technique names are floaters. `foeWeapon` references an episode item; `note` describes a habit. The bot uses the same commands: back attack when reachable, scout against a stronger adjacent foe, approach when distant, rest when needed. Verification requires a victory; bot skip is FIGHT_SKIPPED and blocks release. ### Self-playing battle `module:battle` is a compatible seeded wave battle with no input or loss. Require control:off. Parameters include hero/post/from/exit, ally/foe crowds, look(game/film), ending(hold/victory), camera(coverage/wide), seconds20–180, waves, ultAt, seed, skill{name,sub,glyph}, moves, labels, sfx and barks. Names come from content, not hardcoded cast names. Hero art includes fight_ready, slash_up/back, thrust, whirl, ult_charge/cut, plant/plant_dead; foes need attack/ hurt/ko and allies guard/hurt. Prefer appropriate held-weapon walking art. The authored HP curve, retreat and climax stay linear. Film look has active ally fronts, enemy density, up-to-five falling/ground corpses and authored coverage. Historical automatic close-shot cuts are legacy; new visible camera moves follow current easing rules. The deterministic settle is the resume world; read current ending parameters before assuming the old standing-death ending. ### Authored combat-film `module:combat-film` schedules contact-frame cues, moves, lanes and barks with no player input. Cue motion between fromFrame/toFrame, arc, trail and sweep keeps ground/body separate. Hit reacts only within55 ground units. Knockout uses the specified reaction and counts falling bodies toward the five-body cap; older corpses clear after3.8s. Lanes `{a,b,until,reverseAt,pressure}` can yield then advance simultaneously. Attack/hurt/walk art connects motion. Sound and slash align with hitFrame. Final actors actually move to finish positions and leave the same world as instant settle. See EXTENDING for detailed parameters. ### Footwork and fall ```yaml - action: id: form1 module: footwork actor: hero facing: right finish: stance4 steps: - { to: step1, clip: kata1, fromFrame: 1, toFrame: 2 } - { to: step2, clip: kata2, fromFrame: 1, toFrame: 2 } - action: id: fall1 module: fall actor: hero edge: cliff_edge landing: cliff_landing seconds: 2.8 slip: slip fall: fall landed: injured ``` Footwork travels only while feet lift/plant, then preserves finish. Fall connects slip to airborne trajectory and landing in one authored shot. Use shadow:false airborne and still:true prone. Both are deterministic, self-playing and resumable. ### Cinema Cinema uses the same live sprite stage, with bars and timed speech instead of player controls. Begin control:off, enable cinema with aspect/time/skip, author stage/grade/ambient/camera, then cinema:off before the target checkpoint. Do not build a slideshow from static scene plates. Wide shots (roughly0.6–0.8zoom) show scale; close shots (~1.6–2.3) show people. Fill empty shots or approach. Buildings/trees/walls give dialogue depth. Let movement create composition changes; use eased camera travel, not visible snaps. Grade/atmosphere mark dawn, morning, fire, night and dawn. Silence before ambush or after a decisive strike is intentional. `timeline` estimates cut/subtitle/sound times (`--only music,sfx,stage`). Unknown walk/action/conditional times are marked≈. Slow motion extends real duration, while music uses audio time, so align final music with **rendered timestamps**. Each beat starts its music at0; a new track after silence limits drift. Skip requires a second confirmation, preloads the target before cancellation, leaves film running on load failure, stops old sounds and restores black checkpoints under black. Skip must target after cinema:off (`CINEMA_SKIP`). Cinema rejects control:on, input actions and equip waits; tap dialogue warns. Conditional branches cannot leave different cinema state (`CINEMA_BRANCH`). Persistent bars/atmosphere/camera roll/sway restore; transient subtitle/impact/ blur/caption/insert do not. For resume-safe crowd facing in parallel, target a fixed anchor rather than an actor still moving at different replay speeds. ## 5. Standard grid (legacy compatibility) Template: [character-sheet-template.png](templates/character-sheet-template.png) and [guide](templates/character-sheet-guide.png). Frame32×48,8×8, total256×384; index=row*8+column, sole y44 and center x16. Same body size in every frame. | Clip | Down | Up | Left | Right | | --- | --- | --- | --- | --- | | Idle |0|8|16|24| | Walk |1–4|9–12|17–20|25–28| | Hurt |5|13|21|29| | Attack |32–35|40–43|48–51|56–59| | Reach |36–37|44–45|52–53|60–61| Use custom clips without layout for other sheets. Draw only used actions. Clip hand coordinates override character defaults; left mirrors x. `hand:null` places an owned prop beside the feet until the pose ends, but does not author the actual pickup/putdown transition. Use give/prop/throw for transfers. ## 6. Asset and content limits | Type | Allowed / limit | | --- | --- | | Images | png/webp/jpg; ≤4096px edge,10MB; warn above4MP/2MB | | Audio | mp3/m4a/aac/ogg/wav;15MB, warn above5MB/large WAV; MP3/M4A preferred for iPhone compatibility | | Paths | Local series-bounded relative ASCII paths; no absolute path, URL or traversal | | YAML / atlas JSON |1MB before read/parse | | Compiled steps |20,000 including expanded set/when/parallel | | Story/stage files |100 each | | Walk waypoints |100 | | Blockers/walkable |500 each per stage | | Zones/anchors |200 each | | Interactables/props/foreground |200 each | | Source text/flag value hard cap |500 characters where enforced by limits.ts; field-specific display budgets also apply | | Stage edge |64–8192px | Limits are defined in `src/content/limits.ts`. A failed file/stage reports located LIMIT_* diagnostics rather than exhausting the compiler. Identical media hashes download once; assets are computed per beat. Optional sound failure does not stop story; browser audio starts after the reader's start gesture. Case matters on the server (`FILE_CASE`). Symlinks outside the content boundary are never read (`FILE_OUTSIDE`), including YAML/JSON/media, and diagnostics do not reveal unread contents. Duplicate story paths fail. Keep YAML anchors/aliases small; undefined or explosive aliases report YAML_SYNTAX. `__proto__` is always reserved; prototype names such as constructor/toString cannot be flags/action ids. ## 7. Preview and verification Open `/studio` after bun run dev. It is absent in production. Start from a checkpoint, autoplay or show collision/zones. `?preview=1&tools=1` opens the DEV panel with beat/step, ownership, coordinates, flags, collision, bot and jumps. Tap the stage to read/copy world coordinates. Preview saves use `toonpg:v1:preview:*` separately from reader saves. Reset them in Studio. Only nonproduction accepts preview/from/fresh/debug/autoplay/tools/controls. Desktop convenience: arrows/WASD, Space/Enter/E/Z for A, Shift/X/Q for B. Sword ring uses arrows+Enter or1–8. TV uses D-pad, OK and Back. Default readers watch automatically with human reading delays, walking, interaction and fights. Stick/action input temporarily takes over; dialogue taps only advance a line. AUTO resumes from the current position. Persisted auto/manual preference differs from temporary takeover. A resting finger is not takeover. Bots must solve every reach/interact/near/equip/leash/action. A stuck bot can stop autoplay after~9s and ask for manual help, but verify must catch such content before release. Development fast autoplay (~0.45s/line) is verification speed, not the normal reader experience. Authored wait/parallel timing stays unchanged. Validate catches references, unplaced actors, control-off deadlocks, unreachable waits and missing completion. Verify runs the **real input bot** to completion and restores every checkpoint. Keep lines short enough to read; English display budgets allow more Latin characters than CJK, but readability remains a visual and pacing judgment in all three languages. | Diagnostic | Resolution | | --- | --- | | UNKNOWN_ANCHOR/ZONE/CLIP | Correct name; use Did you mean hint | | ACTOR_NOT_ON_STAGE | Place before using/speaking | | WAIT_WITHOUT_CONTROL | Enable control before waiting for player | | ZONE_UNREACHABLE / UNREACHABLE_INTERACTABLE | Repair terrain/stand position | | WALK_CROSSES_BLOCKER | Add authored route waypoints | | ANCHOR_BLOCKED | Move out of a wall | | NO_COMPLETE / COMPLETE_EARLY | Complete once, last step of last beat | | LAYOUT_MISMATCH | Correct standard grid or use explicit clips | | PLACEHOLDER_PUBLISHED | Remove test material from shipping | | LIMIT_* | Reduce bounded file/content size | | FILE_OUTSIDE / FILE_CASE | Copy inside boundary / fix exact filename case | | RESERVED_NAME | Avoid prototype keys | | INTERNAL | Report compiler bug with content; episode does not ship | | SERIES_UNREADABLE / EPISODE_UNREADABLE | Repair status/schema/YAML; shipping fails closed | | INVALID_TRANSLATIONS / MISSING_TRANSLATION / UNKNOWN_TRANSLATION | Repair locale JSON/key parity; see internationalization guide | ## 8. Common recipes - Add dialogue: append a speaker/text row; add matching locale entries. - Add NPC: new-character, cast entry, stage anchor, then place/walk/dialogue. - Replace background: change image and realign walkable/blockers with overlays. - Escort: walk waypoints with leash150; use parallel for speech while walking. - Transfer object: define prop, then give from/to; ownership restores after resume. - Reorder events: ids stay permanent; adding a scene must not reinterpret old ids. - Change stage: stage with fade, then place actors in the new space. - Add episode: new-episode, then content only; routes/player/runtime stay generic. ## 9. Publish Move draft→preview→published, with episode no more public than series. Follow [PUBLISHING.md](PUBLISHING.md), including bot, language and channel gates.