Desktop mascots have a special place in computing history, small,
animated characters living on your screen, offering a bit of company
during long coding sessions. The most famous is Neko, the little cat
that chases your mouse cursor, originally written for the PC-9801 in
1989. For a recent project, I wanted to bring this concept to modern
macOS, creating not just a faithful recreation of Neko but also a second
mascot that explores physics-based animation. The result is two
open-source Swift applications: xNeko, a modern implementation of the
classic behavior with multiple character skins, and xMascot, a swinging
pendant simulation that reacts to gravity and momentum. The code is
available on GitHub here for xNeko and here for xMascot.
Both projects contain releases, but because they have not been compiled
in a while, the certificates have expired and the releases cannot be
run; they need to be recompiled to function correctly.
Window Management &
Rendering
The biggest trick in a desktop mascot isn’t the animation itself;
it’s the window management. To make a character look like it’s living on
your wallpaper, you have to strip away everything that makes a window a
window. Building desktop overlays on macOS sits in an interesting
intersection of technologies. You need the low-level control of AppKit
to manage the window’s behavior, but you want modern tools for
rendering. I configured the window to be borderless, transparent, and
floating. The key property here is ignoring mouse events, which allows
clicks to pass right through the mascot to the windows behind it,
ensuring the mascot acts as a companion rather than an obstruction. Even
the rendering engine choice depended on the specific needs of each
mascot: for xNeko, I used SwiftUI to handle the simple frame-based
animation loop, while for xMascot, I needed the continuous physics of
SpriteKit to simulate the pendulum motion with gravity and damping.
One specific challenge was ensuring pixel-perfect rendering for the
retro sprites. Modern macOS rendering defaults to smooth interpolation,
which blurs low-resolution pixel art. To solve this, I had to explicitly
configure the interpolation settings, using .none in
SwiftUI’s Image views and .nearest filtering in SpriteKit
textures. This preserved the sharp, crisp edges of the original 32x32
sprites, maintaining the authentic 1990s aesthetic even on high-DPI
Retina displays.
Animation Logic
While they share the same windowing tricks, the two mascots use
fundamentally different logical approaches to feel “alive.” xNeko’s
behavior is driven by a finite state machine with about 18 states. It’s
not generic AI; it’s a specific set of rules that evoke personality.
Every frame, the app calculates the vector from the cat to the mouse
cursor. If the distance is large, it picks a movement state based on the
angle. If it catches the mouse, it enters an “idle” sequence, sitting,
scratching an ear, yawning, and finally falling asleep. The cat doesn’t
teleport; it moves a maximum of 13 pixels per frame toward the target.
This simple logic of moving toward a target, stopping if close, and
cycling animations if idle is all it takes to make the character feel
responsive.
Under the hood, this movement logic relies heavily on custom Swift
extensions to simplify the vector mathematics. I extended
CGVector to conform to AdditiveArithmetic,
allowing standard operators like + and * to
work directly on vector types. This simplified the displacement
calculations significantly; instead of manually managing dx
and dy components, the code can express movement as natural
vector math position += displacement * speed. This cleaner
syntax reduced the likelihood of coordinate errors and made the complex
state transition logic, which determines the cat’s facing direction
based on the velocity vector, much easier to reason about and debug.
xMascot, on the other hand, relies on simulation rather than states.
The mascot hangs from a chain modeled by the standard pendulum
equation:
where angular acceleration depends on gravity and the current angle.
I apply a damping factor every frame to simulate air resistance, causing
the motion to decay naturally over time. To prevent it from becoming a
static image, a background timer occasionally gives the mascot a small
“push” or impulse, starting a swing. This makes it feel like the
character is fidgeting or playing, reacting to a simulated physical
world rather than just following a script.
For this physics simulation, efficiency was a key concern since the
mascot runs continuously in the background. I implemented an
optimization where the physics engine enters a “sleep” state when the
mascot’s velocity drops below a negligible threshold. This prevents the
CPU from wasting cycles solving differential equations for a stationary
pendulum. Additionally, the chain’s motion utilizes a non-linear
smoothing factor, where each link’s damping is scaled by its distance
from the pivot, creating a subtle “wave” effect that makes the chain
appear flexible rather than rigid. Projects like this might seem trivial
compared to “serious” software, but they are great exercises in system
interaction.