calesthio/procedural-canvas-animation
Provider-independent production guidance for deterministic Canvas 2D and p5.js animation. Use for particles, fields, trails, weather, procedural textures, generative geometry, and lightweight 2D simulations that need fixed media dimensions, seeded repeatability, transparent compositing, aspect variants, performance QA, or frame-addressable rendering.
npx skills add https://github.com/calesthio/generative-media-skills --skill procedural-canvas-animation
Use this skill when a 2D bitmap canvas is the appropriate drawing surface for procedural motion. It covers authored particles, vector/noise fields, trails, weather, textures, generative geometry, and lightweight simulations for rendered media.
It does not cover Three.js scene graphs, WebGL/WebGPU shaders, D3 data semantics, game-engine architecture, or a generic frontend Canvas tutorial.
Facts were verified 2026-07-12. Canvas, browser, p5.js, OffscreenCanvas, and encoding behavior can change; pin versions and record the render environment.
Define:
Documented facts: Canvas has an intrinsic resolution separate from CSS sizing. Its default bitmap is 300 by 150. Setting its width or height clears the bitmap and resets context state. Canvas 2D begins with source-over and globalAlpha = 1 and uses premultiplied alpha. A context requested with {alpha:false} is opaque and unsuitable for transparent delivery.
Set backing dimensions explicitly. Do not stretch a low-resolution backing bitmap with CSS and call it high resolution. For fixed video output, choose exact pixel dimensions and an explicit density; p5.js pixelDensity(1) is often appropriate for predictable output.
Changing size is a reconstruction event: rebuild transforms, state, buffers, and layout rather than assuming pixels or context settings survive.
Design around:
renderFrame(frameIndex, variant, accessibilityMode)
with composition time:
$$
t = \frac{frameIndex}{fps}
$$
Keep requestAnimationFrame() outside production state. Documented fact: rAF is one-shot, follows browser display scheduling, may pause in hidden contexts, and does not guarantee 60 fps. p5.js frameRate() requests a target but does not define offline media time.
Do not use Date.now(), performance.now(), millis(), deltaTime, or mutable frameCount as final-render truth.
Calculate every object directly from (seed, objectId, frameIndex, channel). Best for periodic fields, analytic particles, procedural lines, and arbitrary-frame access.
Reset to deterministic initial state and advance exactly $\Delta t = 1/fps$ in a documented order until the requested frame. Suitable for stateful snow, flocking, or cellular systems when duration is manageable.
Persist deterministic state at fixed frames and replay from the nearest earlier checkpoint. Useful for long stateful simulations. Version checkpoint schema and reject it when parameters/runtime change.
History-dependent persistent-buffer trails are not random-access. Replay history, store checkpoints, or draw the previous $K$ analytic positions into each frame.
Documented fact: ECMAScript Math.random() has an implementation-defined algorithm and no seed control. p5.js randomSeed() and noiseSeed() can repeat their respective sequences, but that does not guarantee pixel identity across p5/browser versions.
Record seed and generator implementation. Prefer keyed random values where arbitrary access matters, so rendering frame 200 does not depend on how many random values earlier frames consumed.
Never reseed every frame unless the intended algorithm is explicitly frame-keyed; that often freezes or correlates motion.
For each system define:
Keep simulation state independent of pixels. Aspect variants should share the event while changing framing, count, density, line width, or field bias intentionally.
For weather, distinguish visual plausibility from physical simulation. Do not claim meteorological accuracy unless the model and inputs support it.
Set globalCompositeOperation deliberately and restore context state. Test source-over, additive/lighten effects, masks, and erasure over black, white, and checkerboard backgrounds.
Transparent delivery requirements:
JPEG cannot preserve alpha. WebCodecs encoder alpha behavior is configuration- and codec-dependent; verify capability and output instead of assuming support.
Documented fact: drawing cross-origin media without valid CORS can make a canvas non-origin-clean, causing pixel reads and serialization to throw SecurityError.
Freeze production assets locally where possible. Record URLs, licenses, hashes, CORS policy, and failure behavior. Do not discover a tainted canvas after a long render.
For deterministic offline work:
function setup() {
pixelDensity(1);
createCanvas(outputWidth, outputHeight, P2D);
noLoop();
initialize(seed);
}
function renderFrame(frame) {
resetOrRestore(frame);
advanceTo(frame, 1 / fps);
drawCurrentState();
}
This is an architectural example. advanceTo() must not advance once too many; define whether frame zero represents initial state before any step.
Avoid allowing window size, input events, or live device density to alter final state. resizeCanvas() clears output and typically triggers redraw, so rebuild the declared variant explicitly.
Profile before moving work to a worker. First reduce:
Documented fact: OffscreenCanvas is transferable and can run in workers, but worker use does not guarantee lower total render time. It can improve main-thread responsiveness and pipeline separation. Verify APIs available in the worker and account for transfer/serialization cost.
Measure warm and cold frame time, p50/p95, peak memory, output readback, and teardown. Promise repeatability only for a pinned environment; fonts, antialiasing, filtering, floating point, and color conversion can differ.
Meaningful non-text content needs an equivalent alternative. Color cannot be the sole carrier of meaning, and meaningful graphics/control indicators need appropriate contrast.
WCAG 2.2 limits flashing above three times in one second unless below thresholds. Test rendered loops while looping. Qualifying automatic motion on interactive surfaces needs pause/stop/hide behavior.
Reduced motion should alter motion language: static representative frame, slower drift, smaller displacement, fewer particles, or user-controlled playback. Lowering FPS alone may increase jerk without reducing motion extent.
For decorative canvas, hide implementation details from assistive technology and expose meaning in the host. Canvas text or a description is not a substitute for keyboard-operable semantic controls.
Record seed, generator, algorithm/library versions, parameters, dimensions, fps, frame range, color space, density, source assets/licenses/hashes, runtime/browser, and output hashes.
QA:
This is a complete example, not a mandatory formula.
Intent: eight-second, 30 fps, 1920x1080 transparent overlay of 600 luminous wind traces.
Approach: use seed 42719, normalized coordinates, and 36-frame analytic trails. Immutable particle parameters come from a keyed generator. Each position is a pure periodic function of absolute time. Frame $f$ clears transparent and draws segments from $f-36$ through $f$ with bounded age-controlled alpha/width. Use source-over for base ribbons and a separately tested additive highlight.
QA: direct versus sequential hashes for frames 0, 1, 137, and 239; alpha histogram/corners; black/white/checkerboard composites; p50/p95; flash test.
Likely failures: mutable PRNG consumption, {alpha:false}, background fill, clipped additive values, or wrapped-trail jumps.
Variation: 9:16 lowers count and biases vertical flow; reduced-motion output is an approved static frame.
This is a complete example, not a mandatory formula.
Intent: 12-second snowfall at 30 fps in 9:16 and 16:9, plus static reduced-motion output.
Approach: pixelDensity(1), noLoop(), seed random/noise with 8301, use normalized state and fixed $1/30$ steps. renderFrame(n) resets, steps 0..n-1, then draws. Dimensions affect projection and composition margins, not simulation state. Respawns consume values only in deterministic replay order.
QA: replay frame 240 twice; compare normalized state before projection; reject NaN/out-of-bounds growth; test crops, performance, pause behavior, alternatives, and flashing.
Likely failures: deltaTime, millis(), frameCount, or window dimensions leak into state; resizing without reconstruction; assuming seed output is version-stable.
Verified 2026-07-12:
Math.random: https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-math.randomTake calesthio/procedural-canvas-animation from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.