Engine logo

Engine

Community
illodev
engine

Technical reference for the drawn-by-code engine. Use it when writing or debugging a scene's code (scene.js), when rendering to MP4 or stills, when touching engine/ or a style kit, or when something jitters, runs slow or doesn't load. Covers the Motion.scene contract, determinism, units, caching and commands.

Overview

Publisherillodev
Repositorydrawn-by-code
Skill nameengine
Stars
31
Forks
3
Bundled files
Instructions only
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by illodev on GitHub. Read the source before you install it.

Installation

Install the Engine AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/illodev/drawn-by-code/tree/main/.claude/skills/engine
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/illodev/drawn-by-code.git /tmp/drawn-by-code
mkdir -p .claude/skills
cp -r /tmp/drawn-by-code/.claude/skills/engine .claude/skills/engine
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Engine in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Engine on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Engine is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Engine

2D canvas in headless Chromium, frame by frame. No animation libraries: time is a variable t and every frame is a pure function of t.

Scene contract

js
Motion.scene({
    fps: 24, duration: 6.5,
    logical: [1600, 900],          // logical units; the output is scaled (k = px/unit)
    uses: ['styles/paper-cutout/paper.js', 'styles/paper-cutout/kit.js', 'sandbox/x/cast.js'],
    fonts: [{ family: 'Hand', src: 'fonts/PatrickHand-Regular.ttf' },
            { family: 'Shrikhand', src: 'fonts/Shrikhand-latin.woff2' }],
    bpm: 120, beatOffset: 0,       // musical grid (review.mjs checks the cuts)
    shots: [[0, 4, 'Workshop'], [4, 8, 'Box']],
    audio: { mix: 'mix.wav' },  // render.mjs adds it automatically
    setup(env) { return { kit: PaperKit.make(env) }; },  // → env.state
    draw(g, t, env) { ... },       // logical coordinates; the canvas comes already scaled
    post(ctx, t, env) { ... },     // optional, in PIXELS: grain, vignette
});

env: W, H (logical), k, fps, duration, total, px: [width, height], state. Paths in uses and fonts are relative to the repo root. An entry can be optional ({ src: 'sandbox/x/private/brand.js', optional: true }, fonts { family, src, optional: true }): it is skipped when missing. That is how a client's brand material stays in the uncommitted sandbox/*/private/ while the committed code still runs on a placeholder brand.

Scenes outside this repo. A client's pieces can live in the client's own (private) repo and still use this engine. Mark the root of that folder with a drawn-by-code.json (its presence is the contract; {} is enough). Every tool then takes a scene path under it: the server mounts that root at /@ext/, so inside it paths from the root are written '@ext/…' (const DIR = '@ext/pieces/x/', uses: ['@ext/brand/brand.js'], fonts '@ext/brand/fonts/…'), while this repo's kits, fonts and effects keep their paths ('styles/…', 'fonts/…'). Nothing outside the two roots is served. new.mjs --in <dir> creates the experiment there (and logs it in that folder's INDEX.md); the preview lists those scenes with node engine/serve.mjs --ext <root>. Lessons that generalize still come back here, to the skills; only the client's material stays there.

Utilities in engine/core.js: Motion.rng(seed), noise1, sprite(key, box, scale, draw), shotAt, pulse(t, bpm), beatIndex, onBeat, cam(g, env, cx, cy, zoom, rot), shake, keys([[t, v], …], t); and Ease.seg/inOut/out/in/back/elastic/bump/ pop/lerp/lerpPt. Motion.layer(env, name, fn) paints a shot on a separate canvas (at output size) and Motion.drawLayer pastes it: for transitions and mirrors. The local-time pattern is const u = Ease.out(Ease.seg(t, 2.0, 2.6)). Motion.quad(g, src, [tl, tr, br, bl], n, box, { bend }) maps an image onto any quadrilateral (triangle-subdivided affine), with an optional bend: paper in perspective, a page turning, a note flipping over.

Animating on twos: hand animation changes drawing every 1/12 s. Index per-drawing tables with const d = Math.floor((t - t0) * 12 + 1e-6) and hold the drawing between; smooth curves look wrong next to a reference animated on twos.

Determinism (golden rule)

  • Forbidden: Math.random(), Date, performance.now() and state that accumulates between frames (positions that keep adding up, live particles). Everything is computed from t.
  • Randomness = Motion.rng('stable-seed') or Paper.rng. The seed identifies the piece, never the frame, or the texture boils.
  • Simulations (falls, paper rain): a closed-form formula per particle with its own seed, or a simulation fully precomputed in setup and indexed by frame.
  • review.mjs paints the frames in a different order and compares hashes: if it warns, there is state.

Long or multi-style scenes

One file per segment in segments/ (each adds its function to a global object, e.g. Segment.frog = (g, t, env) => …), loaded from uses together with the kits of every style. scene.js only assembles: it decides which segment or which transition plays at each t. See sandbox/2026-09-24-exquisite-corpse/.

Performance

  • Static things (backgrounds, cutouts, textures) are painted once with Motion.sprite / the kit's sprite, with a key that includes everything that changes their look.
  • Marker texture is expensive: low density on large pieces, and always cached.
  • Target: < 150 ms/frame warm at 1920. review.mjs measures it.

Commands

bash
npm run preview                                           # http://127.0.0.1:5173
node engine/review.mjs sandbox/x/scene.js                 # automatic review + sheet
node engine/render.mjs sandbox/x/scene.js --at 1.5,3.2    # stills at 1920 → out/stills
node engine/render.mjs sandbox/x/scene.js --size 1920     # MP4 → out/x.mp4
node engine/render.mjs sandbox/x/scene.js --size 1080 --from 4 --to 8   # a segment
node engine/render.mjs sandbox/x/scene.js --size 1920 --gpu   # on a machine with a graphics card (prints the GL it got; a laptop's discrete NVIDIA card is tried first, through PRIME offload; MOTION_IGPU=1 skips it)
node engine/mix.mjs sandbox/x/audio.json                  # mix.wav
node engine/strip.mjs sandbox/x/render/x.mp4 sandbox/x/render/strip.jpg --at 1,3,5   # a film strip for READMEs
node engine/gif.mjs sandbox/x/render/x.mp4 sandbox/x/render/x.gif --clips 2-6,10-14   # a looping GIF (< ~5 MB)
# working against a reference video (replicas, style studies)
node engine/reference.mjs sheets ref.mp4 --every 0.25      # labelled contact sheets
node engine/reference.mjs compare sandbox/x/scene.js ref.mp4 --times 2,3.5 --crop 0.3,0.2,0.4,0.4
node engine/reference.mjs colors ref.mp4 3.5 skin=0.42,0.31 # sampled palette
node engine/reference.mjs track ref.mp4 --color '#d2745e' --from 10 --to 12   # per-frame box
node engine/reference.mjs cuts ref.mp4                                         # exact cut frames
node engine/reference.mjs box ref.mp4 1.75 --color '#302222' --region 150,100,850,800
node engine/reference.mjs runs out/stills/t_3.79s.png 0 --row 0.6               # edges along a row
node engine/reference.mjs face ref.mp4 --body '#d2745e' --from 12 --to 12.5    # face per drawing
# a scene outside this repo (a folder marked with drawn-by-code.json)
node engine/new.mjs x --in ../client/marketing/pieces --aspect 9:16
node engine/serve.mjs --ext ../client/marketing
node engine/render.mjs ../client/marketing/pieces/2026-10-01-x/scene.js --at 1

The whole method for replicas is in the replicate skill.

Preview: space = pause, ←/→ one frame, shift+←/→ one second, &t=3.5 in the URL. Chrome: looked up in CHROME_PATH, /opt/pw-browsers, and the usual Linux/macOS paths.

Debugging

  • Black screen or no READY: review.mjs prints the page error. Typically: a wrong uses path, a font that doesn't load or an exception in setup.
  • Text in a system font on the first frame: the font is not in fonts.
  • Everything blurry: the sprite was cached at low resolution (raise res) or is scaled up a lot.

Lessons

  • 2026-09-24 · coffee-first · Everything that bleeds off the frame (backgrounds, floors, tables) must cover the camera's full travel: paperBg(…, { bleed }) and pieces wider than the pan. review.mjs warns about «Transparent holes».
  • 2026-09-24 · coffee-first · To cache a moving piece, draw the sprite at its origin and move it with translate/rotate/scale; the sprite key must not depend on t.
  • 2026-09-24 · what-do-you-love · A text drawn with strokeText on top of fillText only gets bolder; a felt-tip look needs the glyph thinned: fill, then destination-out stroke on an offscreen canvas (cache it per scale from getTransform()).
  • 2026-09-24 · saas-promo · Key tables compared with tq = Math.floor(t*12)/12 must be exact twelfths: 1.67 is not ≥ 20/12, so a drawing silently shifts. Write times as beats/halves/quarters or snap with Math.round(a*12)/12.
  • 2026-09-25 · physics-history v2 · Never wait for a render with until ! pgrep -f "render.mjs …": the waiting shell's own command line contains the pattern, so it waits forever. Run the render itself in the background and let its completion notify you.
  • 2026-09-27 · pyramid-engraving · This container has no GPU: WebGL runs in SwiftShader, which executes every branch of a shader for every pixel (masked). An if never saves time there; what does: fewer call sites of heavy functions, relief gradients from dFdx/dFdy instead of extra evaluations, and shader variants compiled per draw (#ifdef, variantFor in the engraving kit). Profile before guessing: time one still at half size (fragment-bound work drops 4×), then knock features out one at a time. For long films, render with --gpu on a machine that has one.
  • 2026-09-27 · pyramid-engraving · On a laptop with Intel + NVIDIA (Optimus), headless Chrome takes the integrated GPU unless the process is launched with PRIME offload (__NV_PRIME_RENDER_OFFLOAD=1, __VK_LAYER_NV_optimus=NVIDIA_only, __GLX_VENDOR_LIBRARY_NAME=nvidia); ANGLE on Vulkan there was 6× faster than OpenGL on the Intel (0.27 s vs 1.75 s a heavy frame), and only without --enable-features=Vulkan, which left no WebGL2. --gpu now tries it first.
  • 2026-09-27 · pyramid-engraving · The same shader can differ by GPU: in GLSL ES 3.0 a fragment shader's int/uint default to mediump (16 bits on NVIDIA: an integer hash collapsed to a constant), and a float hash like fract(p · 123.34 …) amplifies a compiler's reassociation of (i + 1) · k into a visible jump at every noise lattice line. Declare precision highp int, hash lattices in integers, and check a still on each GPU you render on.
  • 2026-09-30 · external product promo · Motion blur by averaging copies: paint each pass whole into its own layer (Motion.layer) and composite it with alpha 1/(k+1); painted straight onto the canvas, each pass's background washes out the previous ones and objects turn half transparent. Pick the number of samples from the real displacement in pixels, at least 3 as soon as anything moves (switching from 1 to 2 samples changes the edges in one frame, a visible jump), and never let it be 1 in a formula like i / (n − 1): 0/0 gives NaN and the layer silently paints nothing (an empty frame).
  • 2026-10-02 · product videos · A scaled drawImage with a source crop samples one pixel outside the crop (Chrome): a reused temporary canvas is cleared whole, not only the area about to be copied, or leftovers of the previous group show as a line. And ctx.filter = 'blur()' every frame doubled a scene's cost (105 → 47 ms for a frame): blur once into a sprite and stretch it.

Frequently asked questions

What does the Engine AI skill do?

Technical reference for the drawn-by-code engine. Use it when writing or debugging a scene's code (scene.js), when rendering to MP4 or stills, when touching engine/ or a style kit, or when something jitters, runs slow or doesn't load. Covers the Motion.scene contract, determinism, units, caching and commands.

Why use Engine on TypingMind?

Because you install it once and use it with any model. Engine is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Engine in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/illodev/drawn-by-code/tree/main/.claude/skills/engine. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Engine?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Engine?

As many as you like. As long as a model supports skills, you can use Engine with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Engine AI skill free?

It is published on GitHub by illodev. Check the repository for licensing terms. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇