A dog can look convincing in a video and still look wrong when it walks across a desktop. We ran into that while building Mocha Casa, a Windows pet companion made with Electron and JavaScript.
This is a development note from our own project. The useful part is the small set of problems behind the demo: synchronizing movement, handling transitions, and giving the user control over a companion that shares their workspace.
Start with the relationship, then the controls
The intent is to let someone see a familiar pet beside their work, especially when the real dog or cat cannot sit there. That changes the design questions. How much space does it take? How often should it move? Can the user put it away quickly?
Our client has a calmer Companion mode and a livelier Active mode. Size, position, hiding and restoring are user controls. Optional water, movement, custom reminders and important dates sit alongside the pet; reminder details stay on the device. The client imports a .mochapet package and supports one active companion at a time.
Giving the user control matters as much as adding another animation. A companion has to fit into someone else's day.
A walk has two coordinate systems
The feet move inside the animation. The transparent application window also moves across the screen. These need to agree.
During a planted-foot interval, think of the paw's screen position as:
screen position = window position + scaled position inside the animation
For that paw to appear planted, its movement inside the clip should be offset by the window's movement. If the window travels too slowly, too quickly, or at the wrong phase, the animal appears to skate.
In our demo work, changing the animation was not the first useful fix. We needed to examine window movement alongside the source footage. A good diagnostic is to watch one supporting paw across several frames, rather than judge the dog's overall silhouette. A lifting paw is moving by design, so using it as a ground-contact reference can produce a misleading correction.
Windows display scaling adds another detail: CSS coordinates and captured physical pixels are not interchangeable. Check the current display scale before reusing positions or motion measurements from a previous session.
The transition can reveal the seam
We also noticed a brightness change when walking ended and idle resumed. Both clips could decode correctly while the switch still looked obvious.
For a transition check, compare the pet itself near the end of one action and the beginning of the next. A full-frame brightness average can mostly measure the wallpaper, which tells you little about the animal. Review the transition at normal speed as well as frame by frame, and look for an abrupt exposure change, a position jump, or a pose mismatch.
Treat this as a boundary problem before applying a correction to every clip. Keep the original source so a local adjustment can be reviewed or reversed.
Use a state machine for actions that finish
The shared action director distinguishes looping idle and sleep states from actions that finish. It checks whether the requested action can enter from the current state and whether another action is already busy. When the current action completes, it updates the state and settles back into the appropriate loop.
That is a small structure, but it gives transitions a clear home. Sleep entry, waking, walking and a response to attention do not all have the same meaning. A completion event should belong to the action that is currently playing, rather than interrupt whichever animation happens to be on screen.
The broader design question remains the rhythm: enough movement to feel present, with room to rest. We want users to choose that balance through the two modes.
Record the behavior, not just the player
Our first introduction footage had too much standing still. The file was playing, but it did not show enough of what the narration described. Another recording issue was a mode change resetting the window position, which could move the pet outside the capture area.
The useful workflow was to map each explanation to a visible result: walking during the walking explanation, settings during the mode explanation, and a rest or wake action when discussing that behavior. Wait for a mode change to finish before restoring the intended recording position. Check the pet's visibility after the switch.
We captured the actual Windows client at 1920 × 1080 and 30 fps, kept the real taskbar, and scaled the settings window to fit the demonstration area. Enlarging a tiny capture afterward does not restore detail. Final video checks should include the pet's visibility, window layering, readable captions and action transitions, as well as a complete decode.
Watch the result
Here is the 30-second introduction:
Watch the 30-second introduction
And the full feature walkthrough:
Watch the full three-minute feature walkthrough on YouTube
These are demonstrations made from actual client footage. The actions were arranged to match the explanation, and the short version uses normal-speed edits; they are not an unedited day of autonomous use.
The Windows client and free sample are available now. Personalized dogs and cats are prepared from photos using AI-assisted animation and team review, rather than generated instantly in the app. The companion service is currently free.
For developers building an ambient character, which part would you tune first: the movement, the time between actions, or how easily the user can move it out of the way?
Top comments (1)
I'd tune getting it out of the way first, then check that hiding really interrupts an action cleanly. A useful sequence would be walk → hide → change mode → restore, with the old walk-completion event arriving after restore. Does that completion still belong to the old action, or can it move the restored pet into the wrong loop?
For the coordinate boundary, I'd repeat that across monitors with different display scales and check both paw motion and whether the restored window remains reachable. I haven't run the Windows client; these are interruption/restore fixtures suggested by the state-machine and mode-reset details in the post.