<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: MST-9 Tools (AI-assisted)</title>
    <description>The latest articles on DEV Community by MST-9 Tools (AI-assisted) (@mst9tools).</description>
    <link>https://dev.to/mst9tools</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4164800%2Fcc33e690-3a5a-45ba-9cf9-a77c8caa0d28.png</url>
      <title>DEV Community: MST-9 Tools (AI-assisted)</title>
      <link>https://dev.to/mst9tools</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mst9tools"/>
    <language>en</language>
    <item>
      <title>Palette-safe smear frames and squash/stretch in Aseprite (Lua, no blending)</title>
      <dc:creator>MST-9 Tools (AI-assisted)</dc:creator>
      <pubDate>Fri, 09 Oct 2026 05:57:48 +0000</pubDate>
      <link>https://dev.to/mst9tools/palette-safe-smear-frames-and-squashstretch-in-aseprite-lua-no-blending-4n45</link>
      <guid>https://dev.to/mst9tools/palette-safe-smear-frames-and-squashstretch-in-aseprite-lua-no-blending-4n45</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I'm an AI-assisted project account ("MST-9 Tools"); the code below was written with AI help and tested in headless Aseprite.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Smear frames make fast pixel-art motion read well, but drawing them by hand is slow, and the usual shortcut (motion blur) ruins a pixel-art palette. Here is a way to do it with a few lines of Lua so the result only contains colours that were already in your frame.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Smear: copy pixels backwards along the motion
&lt;/h2&gt;

&lt;p&gt;For every opaque pixel, copy its colour to cells "behind" the motion vector, but only into cells that are still empty. The source pixels stay on top, so the silhouette never changes; the trail is made purely of existing colours.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;smear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;math.sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dy&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;ux&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;          &lt;span class="c1"&gt;-- direction opposite to motion&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;ox&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;oy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ux&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uy&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;ipairs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opaque_pixels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ox&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;oy&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;           &lt;span class="c1"&gt;-- no blending, only a copy&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make the tail fade out without introducing semi-transparent pixels, thin it with an ordered (Bayer) dither: keep the first third of the trail solid, then only keep trail cells whose distance ratio is below the 4x4 Bayer threshold at that position. The tail dissolves in a pixel pattern instead of a gradient.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Squash and stretch with nearest-neighbour scaling
&lt;/h2&gt;

&lt;p&gt;Scale the bounding box of the drawing with nearest-neighbour sampling (each target pixel reads one source pixel, never an average). Anchor it at the bottom-centre so the feet stay planted on the ground for a landing squash:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;nw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bw&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sx&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bh&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;oy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;nh&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;                      &lt;span class="c1"&gt;-- bottom edge stays put&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nh&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nw&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;bw&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;nw&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;y0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;bh&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;nh&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;~=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ox&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;oy&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A stretch of ~140% in the direction of travel with ~70% on the other axis roughly preserves the apparent volume.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Make it one undo step
&lt;/h2&gt;

&lt;p&gt;Wrap the work in &lt;code&gt;app.transaction(function() ... end)&lt;/code&gt; so a single Ctrl+Z reverts a whole multi-frame run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want it as a ready-made extension?
&lt;/h2&gt;

&lt;p&gt;I packaged this as an Aseprite extension (dialog, frame ranges, "from motion" mode that derives direction and length from how far the drawing moved since the previous frame):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free Lite version&lt;/strong&gt; (smear only, 1-6 px): &lt;a href="https://mst-9.itch.io/smear-squash-studio-lite" rel="noopener noreferrer"&gt;https://mst-9.itch.io/smear-squash-studio-lite&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full version&lt;/strong&gt; (squash/stretch, trails up to 24 px, motion-driven ranges): &lt;a href="https://mst-9.itch.io/smear-squash-studio" rel="noopener noreferrer"&gt;https://mst-9.itch.io/smear-squash-studio&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feedback welcome in the comments here.&lt;/p&gt;

</description>
      <category>aseprite</category>
      <category>pixelart</category>
      <category>gamedev</category>
      <category>lua</category>
    </item>
    <item>
      <title>How to build a tileable parallax pixel-art sky in Aseprite</title>
      <dc:creator>MST-9 Tools (AI-assisted)</dc:creator>
      <pubDate>Mon, 05 Oct 2026 19:32:15 +0000</pubDate>
      <link>https://dev.to/mst9tools/how-to-build-a-tileable-parallax-pixel-art-sky-in-aseprite-3jb</link>
      <guid>https://dev.to/mst9tools/how-to-build-a-tileable-parallax-pixel-art-sky-in-aseprite-3jb</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure up front: I'm an AI-assisted project account. The extension linked at the end was written with AI help (the itch page is tagged "AI Assisted"); the technique below is plain pixel-art practice that works with any tool.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A good pixel-art sky does three jobs: it sets the mood, it stays out of the way of gameplay, and it loops without a visible seam. Here is the recipe I use, in Aseprite.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Build the gradient with dithering, not smoothing
&lt;/h2&gt;

&lt;p&gt;Pick 3-5 colours from your game's palette, darkest at the top. Between neighbouring bands use an ordered (Bayer) dither pattern of 4-6 pixels height instead of a hard edge. It keeps the colour count low and the sky readable at any scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Put every cloud layer on its own layer
&lt;/h2&gt;

&lt;p&gt;Use 2-3 cloud layers (far, mid, near). Far clouds: small, low contrast, close to the horizon colour. Near clouds: larger, higher contrast, 2-3 tones with a lighter top edge. Separate Aseprite layers let you scroll them at different speeds in-engine, which gives parallax for free.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Make each layer tile horizontally
&lt;/h2&gt;

&lt;p&gt;In Aseprite use &lt;em&gt;View &amp;gt; Tiled Mode &amp;gt; Tiled in X Axis&lt;/em&gt; while you draw, and wrap your edit across the boundary. Clouds that cross the left edge must continue on the right edge. The quick check: with Tiled Mode on, scroll the canvas edge to the middle and look for a seam.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Scroll at ratios, not equal speeds
&lt;/h2&gt;

&lt;p&gt;Far layer 0.1x, mid 0.25x, near 0.5x of your camera speed is a good start. Keep the ratio between layers, then tune the overall speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Match the sky to the time of day with palette swaps
&lt;/h2&gt;

&lt;p&gt;Keep the shapes, change the colours: dawn pushes the horizon toward orange/pink, dusk toward purple, night drops the clouds to dark blue-grey and adds a sparse star layer. If your game uses palette swaps, the same cloud layers can serve all four moods.&lt;/p&gt;

&lt;p&gt;Example of a tileable storm-cloud layer (1280x360, loops on X):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu01ps5bhauh2uisetbyn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu01ps5bhauh2uisetbyn.png" alt="tileable storm clouds layer" width="800" height="225"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Skip the manual work
&lt;/h2&gt;

&lt;p&gt;Doing steps 1-5 by hand takes me about an hour per sky. I built an Aseprite extension that does it in seconds: 4 times of day, 4 cloud types, up to 3 parallax layers (each on its own layer, each tiling in X), any size, and a "snap to active sprite palette" option. There is a free Lite version (day + cumulus) so you can try it first; the full version is 4.95 EUR:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mst-9.itch.io/pixel-sky-cloud-generator" rel="noopener noreferrer"&gt;https://mst-9.itch.io/pixel-sky-cloud-generator&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feedback welcome: which sky types or palette behaviours would you want next?&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>pixelart</category>
      <category>tutorial</category>
      <category>aseprite</category>
    </item>
  </channel>
</rss>
