Procedural and programmatic animation for pixel-art games
Scope: generating or enhancing pixel-art animation by code, for a plain-JS phone-browser engine (internal resolution about 160x90 to 640x360, deterministic fixed step, per-frame drawing limited to fillRect/drawImage/alpha/blend modes/transforms/gradients, offscreen canvases allowed for pre-baking). Researched 2026-11-02. A note on the method: about 25 searches and fetches were made. Several primary pages were not reachable: Sonic Retro’s RotSprite page returned a bot wall, no GDC Vault recordings could be fetched, and YouTube videos can’t be read. So where an algorithm is described from general engineering knowledge rather than from a fetched source, it is placed under Inferences and marked as such.
1. Procedural character locomotion (walk cycles, IK, creatures, skeletal-to-pixel, pose-to-sprite)
Takeaway
The proven pixel-game approaches are (a) code-driven limbs over a hand-drawn or simple body, as in Rain World, which mixes code with traditional animation, and (b) 3D or skeletal rigs rendered at very low resolution without smoothing, then shown as pixel art, as in Dead Cells. For a small JS engine, the cheap, robust core is a distance-constrained point chain (the “follow-the-leader” spine) with FABRIK or two-bone analytic IK for legs, drawn with nearest-pixel snapping. Rotating sprites at runtime is avoided.
Cited Findings
- Rain World’s creator, Joar Jakobsson: “I was posed with the problem of making the slugcat’s limbs move, and since code is how you make things move in a computer game, that’s what I did.” The developer site describes its creatures as animated with “a combination of code and traditional animation.” — Unity blog, “Exploring procedural design in Rain World: The Watcher”
- A GDC 2016 Animation Bootcamp session had Rain World’s Joar Jakobsson and James Therrien breaking down the game’s animation process. The full title and content couldn’t be fetched. A separate (non-GDC) retrospective talk by Jakobsson centers on “procedural animation, creature AI, and the intersection between the two.” — gameanim.com presentations listing; ti.to event page; also see Game Developer: Crafting the complex, chaotic ecosystem of Rain World
- David Rosen (Wolfire), GDC 2014: “simple procedural techniques to achieve interactive and fluid animations using very few key frames.” This is the canonical talk on blending a few keyframes with procedural layers. — gameanim.com (listing referenced in search results)
- The distance constraint is defined as limiting the relative distance between points so they stay within a set distance. Standard solvers are FK, IK, CCD, Jacobian inverse and FABRIK. — ITB student paper on procedural animation, 2024
- A chain of nodes for a snake or creature uses two constraint types: the distance between nodes, and the degree of bending freedom between nodes (an angle limit). — Tuts+: Create a Mechanical Snake With Inverse Kinematics
- Alan Zucconi’s multi-part IK series covers forward kinematics, gradient-descent IK, robotic arms, tentacles and spider legs. — alanzucconi.com
- The widely shared 2024 YouTube video “A simple procedural animation technique” (“Let’s design some procedurally animated animals!”) has inspired open-source demos, e.g. humensmoc’s itch.io tech demo and its Unity repo. The channel attribution (“argonaut”) could not be verified from a fetched page. — humensmoc.itch.io/procedural-anim; github.com/humensmoc/procedural-animation
- Dead Cells (Motion Twin, 2017–2018). Thomas Vasseur: “To make up for the lack of bandwidth and still deliver on quality, we had to find a pipeline that could give us great looking pixel art, without having to hand draw each and every retake.” He was the sole artist in the first year. He modeled and rigged characters in 3DS Max, exported FBX, and used a private in-house tool to render each frame at very low resolution with no smoothing (no antialiasing). Each frame was saved as a PNG plus a normal map so a toon shader could light the sprites. Edits did not require redrawing sequences. — Game Developer: “Art Design Deep Dive: Using a 3D pipeline for 2D animation in Dead Cells” (title and quote only; full text not fetched); 80.lv case study: Dead Cells character art pipeline; wnhub summary
- Community recreations of that pipeline: a Houdini “pixelart renderer” whose aim is to make the pipeline usable “without them having to code custom tools,” and a Blender + Godot + VRoid student project with normal-map lighting. — fatalexit.itch.io; Devpost: The Dead Cells Animation Pipeline
- t3ssel8r-style 3D-rendered pixel art depends on snapping the camera (or origin) to the texel grid. This works cleanly only with orthographic projection, “where one snap corrects every pixel.” Perspective needs other methods, e.g. Dylan Ebert’s “Texel Splatting” (arXiv, March 2026), which renders into a cubemap from a grid-snapped probe origin. — arXiv 2603.14587; Fab plugin listing replicating the look
- A practical anti-jitter recommendation from practitioners: snap each moving sprite and the camera relative to the parent transform. Rendering at 2x internal resolution reduces sub-pixel drift on angled edges. — Godot Q&A; r/gamemaker thread
Inferences
These are implementation sketches synthesized from the techniques above and from standard practice. They are not quotes from the sources.
- Spine chain (follow-the-leader). Store points
p[0..n]and a rest lengthL[i]. Each fixed step, move the head toward its target. Then for i = 1..n:d = p[i] - p[i-1], andp[i] = p[i-1] + normalize(d) * L[i]. For an angle limit, clamp the angle between(p[i-1]-p[i-2])and(p[i]-p[i-1])to ±maxBend before placing. A body outline comes from a radius per joint: left and right points at ±radius along each segment’s normal, then a polygon fill. Paint into a small offscreen Ink buffer with nearest-pixel rasterization (or a run of fillRects per scanline), then drawImage. Never use antialiased canvas paths on the main frame, because they create off-palette colors. - Legs (lizards, spiders). Each foot has a planted world position. When the ideal foot spot (hip + forward offset in the body’s frame) gets farther from the planted spot than a step threshold, start a step. Ease the foot to the new spot over k ticks, lifting by
h*sin(pi*t). Alternate diagonal pairs: never step while the paired leg is mid-step. Solve the knee with two-bone analytic IK (law of cosines:a=thigh, b=shin, c=|hip-foot|clamped toa+b; knee angle fromacos((a²+c²-b²)/(2ac)), with the bend side fixed per leg) or with 2–3 FABRIK iterations for more joints. Everything is integer-snapped at draw time, not in the simulation, so motion stays smooth while pixels stay crisp. - Parametric biped walk cycle. Phase
φ = (distance traveled / stride) mod 1. Tie it to distance, not time, so feet don’t skate. Foot x =±stride/2 * cos(2πφ). Foot lift =max(0, sin(2πφ)) * lift. Body bob =|sin(2πφ)|-shaped, two bobs per cycle, 1 px at 16–32 px tall characters. Lean = speed × k. Quantize φ to 4, 6 or 8 poses: a coded “sprite” with 8 discrete poses reads as pixel art, while a continuously sliding limb reads as vector or Flash. - Skeletal (Spine/DragonBones-style) rendering to pixels risks “mixels”: rotated or scaled bone images sampled at sub-pixel positions, with pixel sizes that no longer match. The safe route in this engine’s constraints is to pre-bake: run the skeleton offline or at load time, render each pose at native resolution with nearest sampling (or RotSprite or cleanEdge for the rotated parts), optionally hand-clean, and store as a sheet. That is effectively the Dead Cells approach in 2D.
Gaps
- No primary transcript or slides of the GDC 2016 Rain World Animation Bootcamp talk could be fetched. Specifics (e.g., how Rain World blends code-driven limbs with drawn body sprites, and its tile-based leg placement) are unverified here.
- The “argonaut” channel attribution of “A simple procedural animation technique” and its exact algorithm (circles along a spine, angle constraints, outline) couldn’t be confirmed from a fetchable source.
- No source found on how Spine or DragonBones pixel games specifically configure mixel avoidance (e.g., integer-snapped bone translation, disabling bone scaling). This rests on common practice only.
- Celeste’s hair implementation details (node count, follow logic) couldn’t be sourced. Only a Godot forum recreation was found, which describes it as “several circles that follow a path.” — Godot forums
2. Rotating and scaling pixel sprites without destroying them
Takeaway
Never rotate or scale pixel sprites per frame with filtered or nearest transforms in the main renderer. Pre-bake rotations offscreen with an edge-aware algorithm. RotSprite (Scale2x-family upscale, rotate with nearest, downsample) is the classic. torcado’s cleanEdge (MIT, GLSL) is the modern one. MMPX (McGuire and Gagiu 2021, JS reference code) is the best palette-preserving 2x magnifier. All of these keep the exact palette, which matters for an engine that stores pictures as palette indexes.
Cited Findings
- RotSprite was made by Xenowhirl (Sonic hacking community). It preserves features by “guessing” pixel patterns instead of blurring, and can also resize. — Sonic Retro wiki: RotSprite (blocked on fetch; summary via search)
- The Rust
rotspritecrate (0.1.4) upscales with the Scale2x algorithm first, then rotates. Right angles (0/90/180/270) use a fast path. — docs.rs rotsprite source - A Godot GPU shader does a “RotSprite-like” rotation with a Scale2x upscale. Its author calls it “not perfect but a lot better than the regular rotation.” — godotshaders.com
- Scale2x rules (3×3 neighborhood A..I, center E, outputs E0..E3):
E0 = (D==B && B!=H && D!=F) ? D : E,E1 = (B==F && B!=H && D!=F) ? F : E,E2 = (D==H && B!=H && D!=F) ? D : E,E3 = (H==F && B!=H && D!=F) ? F : E. Optimized form: ifB!=H && D!=F, apply the rules, else all four outputs are E. Scale3x has a 9-output rule set. Scale4x = Scale2x twice. — scale2x.it/algorithm - Rotation by any angle can be done with three shears. This is the general-editor approach, which works without resampling blur but still breaks single-pixel lines. — setsideb.com, Sept 2022
- Nearest neighbor is fastest and only looks right at integer multiples in both X and Y. Photo scalers blur “where every dot matters.” — setsideb.com
- cleanEdge (torcado): an MIT-licensed pixel-art scaling and rotation algorithm in GLSL and Godot shader language. It treats pixel art “as if it were a vector drawing,” trying to keep lines and edges “as close to solid 1-pixel thick lines” by expanding those regions into neighboring pixels and vice versa. Its parameters: a “highest color” (wins overlap priority; set it to the outline color), a line width (≈0.707 aligns slopes to the diagonal), and optional 2:1 slopes in scale mode (rotation mode always uses slopes). It runs in-browser and sends no data. A CLEANUP flag matters mainly for slope transitions and can be disabled for rotation-only speed. — torcado.com/cleanEdge; shader gist via dacap
- Aseprite tooling (Astropulse’s “Clean Rotate”) calls cleanEdge “a massive step up” from RotSprite. That is vendor language. Users report fine details can get muddled, and that repeated rotation is destructive (always rotate from the original). — astropulse.itch.io/clean-rotate-for-aseprite; Krita Artists thread; Playdate devforum; MakeCode forum port
- MMPX (Morgan McGuire and Mara Gagiu, Journal of Computer Graphics Techniques, 2021): a 2x filter that tries to predict what the artist would have drawn at larger scale under the same constraints. It rebuilds curves, diagonals and sharp corners “while preserving the exact palette, transparency, and single-pixel features.” It processes a 64k-pixel screen in under 0.5 ms on GPU or CPU. Reference code comes in C++, JavaScript and GLSL ES. The authors say EPX or basic xBR may be better for content with only rounded corners, and HQX or antialiased xBR for large palettes and gradients. — Paper PDF; Roblox publication page
Inferences
- Pre-bake recipe for this engine (offscreen canvases may use anything): for each sprite needing rotation, generate N angles (16 is usually enough at ≤32 px. A 22.5° step at 16 px is under 1 px of arc change near the center, so finer steps read as jitter, not smoothness). Upscale 8x with Scale2x applied three times (or MMPX at the first 2x), rotate with nearest sampling about the sprite’s pivot, then downsample 8x by taking the most frequent non-transparent color in each 8×8 block (or the center sample). Store as palette-index arrays so
Inklimits still hold. Cache per (sprite, angle). Per frame: drawImage only, no transforms. This keeps the per-frame renderer within the WebGL stand-in rules. - Per-frame transforms that are safe: horizontal or vertical flips (scale -1 at integer positions) and exact 90° turns. Integer scales (2x, 3x) are safe only if every sprite in the scene uses the same pixel size. Otherwise you get mixels: neighboring pixels of different sizes, which break the 320×180 “one pixel grid” look.
- Squash and stretch without mixels: instead of
ctx.scale(1.2, 0.8), pre-bake 2–3 squash variants by row and column duplication or removal (drop every k-th row and duplicate every m-th column). Or treat squash as a separate drawn frame. Both keep the global pixel size. - Palette preservation: RotSprite, Scale2x, MMPX and nearest all pick existing colors, so their output stays inside the 8-ink limit. Bilinear or canvas
imageSmoothingEnabledmust stay off everywhere (ctx.imageSmoothingEnabled = falseon every offscreen and onscreen context, and CSSimage-rendering: pixelatedfor the final upscale).
Gaps
- No fetched primary description of RotSprite’s exact steps (the commonly cited “8x upscale with a similarity-tolerant Scale2x variant, then rotation, then downscale and single-pixel cleanup” could not be verified, because the Sonic Retro page blocked access).
- No benchmark comparing cleanEdge, RotSprite and MMPX on speed or quality was found.
- Publication date of cleanEdge isn’t on its page.
3. Frame-level techniques (squash and stretch, smears, anticipation, secondary motion, idle oscillation, palette animation, juice, hit flash)
Takeaway
Most “juice” at low resolution is cheap, deterministic code layered on few drawn frames. That means integer-pixel offsets driven by sines and springs, swapped silhouette or palette sprites for flashes, verlet or spring chains for hair, capes and tails, and drawn (not computed) smears. The rule at low res is that every procedural offset must quantize to whole pixels and move rarely. One pixel of bob is a lot at 16 px tall.
Cited Findings
- Smear frames are most powerful combined with squash and stretch, anticipation and motion blur. Squash and stretch is the foundation of tricks like smears. — GarageFarm: Smear frames; Kirupa: Principles of Animation
- Academic: “Interactive Art-Directable Smear Frame Stylization” (AFIG 2023) generates smears procedurally from motion. It targets 3D or high res but shows smear generation from velocity is tractable. — HAL hal-04285524
- Saint11 (Pedro Medeiros, Celeste and TowerFall artist) has a numbered pixel-art tutorial series that includes “#22 Animation Easing” and “#12 Seamless animation”. No Saint11 page specifically on smears or squash could be fetched. — Lemmy mirror listing Saint11 tutorials; a blogger reproducing saint11 tutorials: blog.0x17.de
- Palette animation: Mark Ferrari’s color cycling animates water and fire by rotating palette entries, with no layers or alpha: one flat image and a 256-color palette. His “BlendShift” mode fades colors between palette positions for in-between frames, giving smoother motion and the illusion of more colors at the same cycle speed. — 80.lv, 28 July 2017
Inferences
These are engineering sketches, not from fetched sources.
- Idle and breathing:
dy = round(A * sin(2π * tick / P + phase))with A = 1 px and P ≈ 60–120 ticks at 60 Hz. Apply it to the upper body only, by drawing the sprite in two horizontal slices with the top slice offset (two drawImage calls with source rects). Seed a phase per NPC fromctx.rngso crowds don’t breathe in sync. - Spring “juice” (scale or offset pop on landing, hit, UI): a critically or under-damped spring per value,
v += (k*(target-x) - c*v)*dt; x += v*dt, stepped in the fixed loop and rounded at draw time. It’s deterministic by construction. - Secondary motion (hair, capes, tails, antennae): a verlet chain.
pos_new = pos + (pos - prev)*damping + gravity*dt², then 2–4 iterations of the distance constraint (as in section 1), with the root pinned to an anchor on the sprite per frame. Draw each node as a filled 1–3 px block or circle in palette colors, largest near the root. That is the “circles that follow a path” look attributed to Celeste’s hair. Keep node count small (4–6) at 320×180. Change the hair’s palette index for state (a Celeste-style dash indicator is a palette swap, not new art). - Hit flash: pre-bake a solid white (or palette-index “highlight”) silhouette of each frame offscreen once (
source-infill on an offscreen canvas is allowed there), then draw it in place of the sprite for 2–4 ticks. This avoids needing a per-frame shader or composite op outside the allowed blend set. - Palette swaps for status or variety: since
Inkkeeps pictures as palette indexes, a re-tint is a re-render of the index buffer through a different palette, done once offscreen and cached. The per-frame cost is zero. - Smears: at ≤32 px, code-generated motion blur (alpha-trailed copies) looks like a different art style. A drawn smear frame (stretched silhouette in the dominant color, 1 frame) or “multiples” (2–3 offset copies of the limb, opaque, palette colors) matches the frogs’ rule better. A procedural fallback is a stretched silhouette drawn along the velocity vector, made from the pre-baked silhouette by column duplication, shown for exactly one frame.
- Anticipation and follow-through as timing, not art: hold the anticipation pose 1–3 extra ticks before the action and add an overshoot frame (or a 1 px overshoot offset via the spring) after it. These cost no new frames.
- Shaders vs pre-baked: with a renderer limited to drawImage, alpha, blends and transforms, anything per-pixel (palette shift, flash, outline, dissolve) must be pre-baked as extra images. Budget memory: a 32×32 frame is 4 KB as RGBA, so 8 palette variants × 32 frames × 20 sprites ≈ 20 MB. That is fine on desktop but worth caching lazily on phones.
Gaps
- No primary written source from Celeste’s team (Maddy Thorson, Noel Berry, Pedro Medeiros) on the hair or squash and stretch code was fetched. “The Art of Celeste” specifics are unverified here.
- No source quantifying how many pixels of squash or bob read well at given sprite heights. The 1 px guidance above is practitioner convention.
4. Generating sprite sheets automatically (walk frames from one sprite, 4/8-direction generation, tweening, AI tools in 2026)
Takeaway
Rule-based derivation from one static sprite works for small, simple sprites (bob plus leg-column shifts, mirrored directions), but in-betweens tweened at low res produce mush and need hand cleanup. As of late 2026 the commercial AI tools (PixelLab, Retro Diffusion) generate frame sets and directional rotations, PixelLab via a poseable skeleton. They are useful for drafts but third-party comparisons are the main evidence, and output still needs palette locking and cleanup.
Cited Findings
- PixelLab: described by a 2026 comparison guide as the best choice for animated characters because it models a sprite as a poseable, rotatable skeleton. It offers skeleton animation, 4- and 8-direction rotation, tilesets and an Aseprite plugin. Free tier: 40 generations. Paid from $12/month (prices as reviewed Sept 2026). — Cinevva: Best AI Pixel Art and Sprite Generators for Games in 2026
- Retro Diffusion (by Astropulse): an Aseprite extension sold as a one-time purchase ($20 Lite / $65 full). “RD Animation specializes in generating animated pixel art sprites and sprite sheets” (Scenario help article, April 2026). Its MCP server offers sprite, animation and tileset generation and a free
fix_pixel_arttool that recovers the native pixel grid from upscaled, blurred or compressed pixel art. — Scenario help: Retro Diffusion Models; mcpservers.org: Retro Diffusion MCP; Cinevva - The same guide claims only PixelLab, Retro Diffusion and Ludo produce frame sets, and only PixelLab does so from a skeleton you pose. This is a single-source claim. — Cinevva
- 3D-to-sprite remains the proven route to many directions and frames from one source (Dead Cells). Off-the-shelf Blender courses teach toon-shaded low-res renders to sprite sheets. — gamedev.tv Blender 2D Sprites; 80.lv Dead Cells case study
Inferences
- Walk frames from one sprite, rule-based (for 8–24 px characters): split the sprite at the hip row. A 4-frame cycle is: contact (legs spread: shift the left-leg columns −1 px and the right-leg columns +1 px), passing (legs together, body +1 px up), contact mirrored, passing. The torso bobs 1 px on passing frames. Arms swing opposite the legs by shifting arm pixels ±1 px. This is how many tiny-sprite generators work. It holds up at ≤16 px and fails beyond about 24 px, where limb shape must change.
- Direction generation: left and right are free (flip). For an isometric 2:1 game like Elsewhere, 4 diagonal directions need only 2 drawn (SE, NE) plus flips. Generating back views from front views can’t be done by rule; a 3D or skeleton source or an AI rotation tool is needed.
- Tweening between keyframes at low res: linear interpolation of pixel positions creates unstable 1 px wobble and breaks clusters. Tween the parameters (bone angles, offsets) and re-rasterize each in-between with nearest sampling. Or don’t tween at all and use holds (section 6). Optical-flow or AI interpolators (RIFE-style) are designed for continuous-tone video and produce off-palette blends on pixel art. Re-quantize to the palette and clean by hand.
- Palette lock any AI output: map every pixel to the nearest palette index (in a perceptual space such as OKLab) and re-derive outlines. That fits the
Inkmodel of index buffers.
Gaps
- No independent (non-vendor, non-aggregator) evaluation of PixelLab or Retro Diffusion animation quality was found. No 2026 source specifically evaluating AI frame interpolation (RIFE, FILM, etc.) on pixel art was found.
- No published, citable algorithm for “deriving walk frames from a static sprite.” The leg-shift rules above are practitioner convention.
5. Environmental animation (foliage, water, fire, smoke, flags, particles, cellular automata, palette cycling)
Takeaway
Two families dominate. Palette cycling (Mark Ferrari, Living Worlds) animates large areas almost for free by rotating palette entries. In a canvas engine, the efficient trick is to pre-index only the pixels that use cycling colors, or, under this renderer’s limits, to pre-bake the K cycle states as offscreen canvases. Cellular automata (Noita) simulate every pixel but need dirty-rect chunking and bottom-up updates. Particles and foliage should snap to the pixel grid and draw from the palette.
Cited Findings
- Color cycling: Ferrari’s scenes are Amiga IFF/ILBM files from Deluxe Paint with a 256-color palette and cycle ranges. Effect Games (Joseph Huckaby) converted them with a C++ tool to gzipped JSON (~100 KB per scene) of pixels, palette and cycle data. Modes: Standard (whole-step shifts) and BlendShift (fades between positions, Ferrari’s invention). Redrawing all 307,200 px of 640×480 per frame in JS was too slow on some browsers, so the engine pre-computes the offsets of only the pixels using animated palette entries and redraws only those. That ran smoothly on an iPhone 3GS. — 80.lv, 28 July 2017; Canvas Cycle on Google Experiments (2010)
- Mark Ferrari’s GDC 2016 talk “8-Bit & ‘8-Bitish’” covers his color cycling and palette-shifting methods. — GDC news
- Critics note canvas has no hardware palette, so the web demo is a software emulation of cycling in JS. — HN discussion
- Noita (Nolla Games; Petri Purho; engine “Falling Everything”): the world updates bottom-up so falling rows stay consistent. Liquids check down, then left and right, and swap by density. Gases are the inverse (up first). Fire picks a random neighbor to ignite (oil burns, water becomes steam). Wood burns for a set time, then is destroyed. Explosions turn static pixels into falling sand. The world is split into 64×64 chunks, each with a dirty rect so only active pixels are iterated. Multithreading uses four checkerboard passes: a pixel may move within its chunk plus 32 px each way, so no two threads touch the same pixel. Rigid bodies store a body ID and local position per pixel and are recomputed (possibly split) when pixels are destroyed. — 80.lv interview, 5 April 2019; Checkpoint Gaming 2017
- Petri Purho’s GDC 2019 talk “Exploring the Tech and Design of Noita” gives the technical details. — Gorilla Newsletter 53; Noita wiki: Official Interviews and Videos
Inferences
- Palette cycling inside this renderer: per-frame
putImageDataor per-pixel work isn’t in the allowed op set. So pre-bake: for a background with a cycle range of length K, render K offscreen canvases (one per rotation) throughInkonce, then drawImage the current one each frame, indexed byfloor(tick / ticksPerStep) % K. If only part of a scene cycles, bake just that region (the waterfall’s bounding box) as K small canvases layered over the static background. That mirrors Huckaby’s “only animated pixels” optimization at the image level. BlendShift needs extra in-between colors, which violates a strict 8-ink limit, so use standard shifting, or count the blend colors in the frame’s eight. - Grass and foliage sway: pre-bake 3–4 lean frames per tuft (top pixels shifted 0, 1, 2 px, bottom row pinned), choose a frame per tuft from
round(A*sin(ω*t + k*x + seedPhase)), and add a traveling “gust” termg(x - windSpeed*t)so waves cross the field. Or slice a sprite into rows and offset the upper rows by integers (a row-shear) using source-rect drawImage calls. That is cheap and stays on-grid. - Water: palette-cycled highlight bands, scrolling a tiled ripple texture by whole pixels at a slow rate (1 px every N ticks), or a 1-D spring height field along the surface line (each column’s height is a damped spring coupled to its neighbors), drawn with fillRect columns.
- Fire and smoke particles: integer positions, colors chosen by age from a 3–5-entry palette ramp (white, yellow, orange, red, dark), sizes 1–3 px squares (fillRect), lifetimes in ticks. Flicker uses
Tycho.seed.fxRand(not saved). Anything that affects gameplay usesctx.rng. Avoid alpha fades (off-palette). Switch to a darker palette entry or dither via a checkerboard instead. - Flags and cloth: a 2-D verlet grid (e.g., 6×4 nodes) rasterized as flat quads or columns, or simply a per-column sine
dy = round(A*sin(ω t − k x))*(x/width)that pins the pole edge. - Falling sand on a phone: a 160×90 grid is 14,400 cells. With a typed array and dirty chunks (say 16×16) that is trivially cheap in JS at 60 Hz. Render by writing indexes into an offscreen ImageData once per tick and drawing it, because offscreen work is allowed. Alternate the left/right scan direction per row each tick to avoid directional bias, while staying deterministic.
Gaps
- Mark Ferrari’s GDC 2016 talk content wasn’t fetched. Details like his cycle speeds and dithering into cycles are unverified.
- No sourced specifics on how Owlboy, Eastward, Sea of Stars or Blasphemous implement foliage, water or fire (shader vs baked) were found in this session.
6. Timing (on 2s/3s, holds, frame rates, deterministic fixed step)
Takeaway
Run simulation on a fixed step (accumulator loop) and drive every animation from the integer tick count, not wall time. Pixel sprites usually change pose only every few ticks (“on 2s/3s” or slower at 60 Hz), while procedural values update every tick but are quantized to pixels. Render-time interpolation (alpha) should not move pixel sprites to fractional positions.
Cited Findings
- Glenn Fiedler, “Fix Your Timestep!” (10 June 2004): accumulate real frame time, step the simulation by a constant
dtwhileaccumulator >= dt, and carry the remainder. Render withalpha = accumulator/dtto interpolate between previous and current states (state = current*alpha + previous*(1-alpha)). Clamp frame time (e.g., 0.25 s) to avoid a “spiral of death.” — gafferongames.com - Pixel-art camera and sprite stability depends on snapping positions to the texel grid. Sub-pixel drift causes edge jitter. — arXiv 2603.14587; r/gamemaker thread
Inferences
- Frame timing by ticks: store animations as lists of
{frame, ticks}(per-frame holds, as in Aseprite’s per-frame durations). Typical at 60 Hz: walk 6–10 ticks per pose, idle 10–30, attack wind-up long (anticipation), strike 2–4, recovery 6–12. Uneven holds read as “snappy” and are the main lever pixel animators use instead of more frames. - Determinism:
frameIndex = f(tick - animStartTick)makes animation reproducible and saveable as two integers. Procedural oscillators usetickand per-entity phases seeded fromctx.rng(name). Avoidperformance.now()in anything visual except the accumulator. - Interpolation vs pixel snapping: if the renderer interpolates with alpha, round the interpolated position to whole internal pixels before drawing. Otherwise, at 60 Hz render on 120 Hz phone displays, sprites shimmer between two pixel positions. An alternative is to render without interpolation and accept up to one tick of latency. At 60 Hz simulation that is usually invisible in pixel art.
- “On 2s” for procedural layers: updating a procedural value (sway, bob) only every 2nd–4th tick and holding it gives a hand-animated cadence. Updating every tick at 1 px resolution reads as “smooth/vector.”
Gaps
- No primary source (talk or blog) giving recommended per-action frame counts or holds for pixel art was fetched. The numbers above are convention.
7. Notable games and talks (reference list)
Takeaway
The confirmed references are Rain World (code-driven limbs, GDC 2016 Animation Bootcamp), Dead Cells (3D-to-pixel pipeline with normal maps), Noita (per-pixel falling-sand simulation, GDC 2019), Mark Ferrari’s color cycling (GDC 2016; Canvas Cycle web demo 2010/2012), and t3ssel8r (grid-snapped 3D pixel rendering). Celeste, Hyper Light Drifter, Owlboy, Blasphemous, Sea of Stars, Eastward and Animal Well are frequently cited, but primary technical sources for their animation could not be fetched this session.
Cited Findings
- Rain World: code plus traditional animation. GDC 2016 Animation Bootcamp talk by Joar Jakobsson and James Therrien. Unity blog on the procedural design of the 2025 DLC “The Watcher.” — Unity blog; gameanim.com listing
- Dead Cells: 3DS Max → FBX → private low-res, no-AA renderer → PNG + normal map → toon shader. — 80.lv; Game Developer archive title
- Noita: Falling Everything engine, 64×64 dirty-rect chunks, checkerboard threading, pixel rigid bodies. — 80.lv 2019
- Mark Ferrari: GDC 2016 “8-Bit & ‘8-Bitish’”; Canvas Cycle by Joseph Huckaby (2010, expanded 2012 with 35 scenes; source free to reuse). — GDC news; Google Experiments; 80.lv
- Wolfire / David Rosen, GDC 2014: procedural animation from very few keyframes. — gameanim.com
- MMPX (2021) and cleanEdge (torcado) as the current reference pixel-art scalers and rotators. — MMPX PDF; cleanEdge
Inferences
- For Elsewhere (isometric 320×180, eight inks,
Inkpalette-index pictures, deterministic loop), the highest-value, lowest-risk techniques are: palette cycling pre-baked into K canvases (water, fire, glows); tick-driven holds with uneven timing; 1 px integer bobs and breathing with seeded phases; verlet chains for hair, scarves and tails drawn as palette blocks; a follow-the-leader spine with two-bone IK for any creature quest (a Rain World-like world); and RotSprite or cleanEdge-style pre-baked rotations only where rotation is unavoidable (thrown objects, spinning props). Falling-sand CA is a strong fit for one self-contained quest world at 160×90 or so, run on an offscreen buffer.
Gaps
- No primary sources fetched for: Celeste’s hair and squash and stretch code or “The Art of Celeste”; Hyper Light Drifter’s animation pipeline (Alx Preston, frame counts, the 2D/3D mix); Owlboy’s hand animation and rotation handling; Blasphemous’s frame counts; Sea of Stars’ dynamic lighting on sprites; Eastward’s animation; Animal Well’s custom engine effects (Billy Basso). These are commonly discussed and worth a dedicated follow-up search (GDC Vault, developer blogs).
- The Rain World GDC 2016 talk’s exact title and Vault URL couldn’t be confirmed.