Cosmo: The Replit Prompt I Used to Polish a 3D Robot Companion (and What Actually Shipped)
Overview
Cosmo is a small floating robot that sits on a stand in the middle of your browser. You can boop its head, pet it, grab it and fling it into the edge of the screen, and it remembers how you treated it. If you are gentle for long enough its eyes turn pink. If you keep hitting it, it stops talking to you until you apologise with enough head pats. Leave it alone for half a minute and it falls asleep. Turn on Mirror mode and it watches you through the webcam: it copies your smile, waves back when you wave, and dances when you hold up a peace sign.
Try it: poddy-animated-robot-companion.replit.app. It started out named Poddy, which is why the URL says so.
I built it with Replit Agent as one self-contained HTML file: Three.js, a rigged GLB model embedded as base64, and every sound made live with the Web Audio API. Once the behaviour worked, I sent the agent one long prompt asking it to improve only the look. This post has that prompt, what the agent built, and the part worth knowing before you copy it: most of what I asked for didn't end up in the final build, and the robot looks better for it.
What it is: a full-viewport Three.js scene with one rigged robot on a three-tier stand, 11 moods with their own eye shapes and LED colours, spring-driven physics for grabbing and throwing, a typed-out speech bubble, synthesised sound effects, and an optional webcam Mirror mode that uses MediaPipe face and gesture tracking.
Stack: Three.js r169, GLTFLoader, MediaPipe Tasks Vision 0.10.14 (loaded from a CDN only when you turn on Mirror mode), Web Audio API. The downloadable version is one ~11 MB HTML file with the model inside it. The robot is Poddy M1 by Exmoor beast on Sketchfab (CC-BY-4.0).
What Cosmo does
Before the polish pass, it's worth seeing how much behaviour is here. The prompt below says "keep all interactions, mood logic, and Mirror mode exactly as they are", and this table is everything that sentence protects. I pulled the numbers from the shipped code.
| You do | Cosmo does |
|---|---|
| Tap the head | Boops and blinks: "boop!", "beep", "*blink*". Mood: happy. |
| Tap the body | Flinches: "hey!", "that tickles". Mood: startled. |
| Double-tap | A burst of hearts and "come here!". Mood: smitten. |
| Slowly stroke the head | A pet happens every ~90px of slow drag. It purrs, tilts its head toward your cursor, and builds affection. |
| Press and hold (260 ms) | Picks it up: "put me down!". Let go fast enough and it spins off with "wheeeee!". |
| Swipe fast across it | Counts as a hit. It gets knocked back in the direction of your swipe, and heavier hits send it spinning. |
| Throw it into the edge of the screen | Bounces off with "ow, wall" or "who put that there", then gets dizzy. |
| Poke 5 times in 3 seconds | "quit poking!", "i will bite". Mood: furious. |
| Hit it 6 times in 14 seconds | Sulks and turns away: "i'm not talking to you". Pet it 5 times to be forgiven, or it gets bored of sulking after 22 seconds. |
| Ignore it | Curious after 18 seconds, asleep with snoring after 32. |
| Come back after 5 seconds away | Waves at you from the side your cursor came in on. |
| Keys W D T G S N | Wave, Dance, Ta-da, Gasp, Sulk, Nap (same as the bottom dock). |
| Key H | Hides every control so you can record a clean video. |
The two hidden meters
Two numbers, patience and affection, sit
underneath all of that, both between 0 and 1. Hits lower both. Pets
raise both. Patience slowly refills and affection slowly fades, so you
have to keep being nice to it. When no emote is playing, the resting
mood comes from the meters: patience under 0.3 makes Cosmo angry,
affection over 0.75 makes it smitten, otherwise calm. That's why it
feels like it has a memory: the same poke gets a different reaction
depending on how the last minute went.
The 11 moods
Each mood has a colour, an eye shape (width, height, tilt, and a lid curve) and a glow strength. The mood chip in the top-left shows the name Cosmo would use for it, not the internal one.
| Mood | Chip says | LED |
|---|---|---|
| calm | calm | #3dff5a |
| happy | happy | #3dffc0 |
| love | smitten | #ff4fb3 |
| surprised | startled | #ffd43f |
| hurt | ouch | #ff5a3a |
| angry | furious | #ff2d24 |
| sad | sulking | #4a86ff |
| dizzy | dizzy | #b26bff |
| sleepy | asleep | #2dbfa3 |
| excited | wheee | #ffe14a |
| curious | curious | #5ae8ff |
Mirror mode
Mirror mode turns on the webcam and loads MediaPipe's face landmarker and gesture recogniser. They take turns on alternate frames, so neither one slows the render loop much. From the face, Cosmo gets your head's yaw, pitch and roll (from a handful of landmarks around the cheeks, nose, eyes and chin) and the blendshape scores for smiling, jaw open, brows and blinks. It then copies your expression:
- Mouth open or brows raised → startled
- Smiling → happy
- Brows down → furious
Hands go through the gesture recogniser. A gesture has to hold for 5 detections at over 60% confidence before Cosmo reacts, with a 2.5-second cooldown, so it doesn't react to a hand just passing through the frame:
- Thumbs up: "we did it", happy
- Thumbs down: "harsh", sad
- Peace sign: "party time!" and a dance
- I-love-you sign: hearts
- Closed fist: flinches, "don't hit me!"
- Pointing up: looks up, "what? where?"
Waving isn't a MediaPipe gesture, so it's detected by hand. The code keeps 1.3 seconds of wrist positions and counts how often the horizontal direction reverses. Three or more reversals with your hand in the top three-quarters of the frame counts as a wave, and Cosmo waves back with the arm on the same side.
The polish prompt
This is the prompt I sent once the behaviour was finished. It's written as a wishlist from a motion designer, and it's worth reading to the end before you paste it, because the next section is about how much of it Replit Agent (correctly) dropped.
What shipped, and what didn't
I read through the final build's source to check this, not the
agent's summary. There's no EffectComposer, bloom pass,
SMAA, HDRI or reflector anywhere in it. Replit Agent tried several
background ideas, then settled on something much quieter, and every
version with more effects looked worse. The robot is the only thing
on screen that should be moving; fog, dust and bloom all took
attention away from it.
| Asked for | In the final build |
|---|---|
| Springs instead of linear damping | Shipped. A small spring integrator (acceleration = −stiffness × offset − damping × velocity) drives the joints, and the robot's position springs back to the stand after a throw. The physics runs in fixed steps of at most 1/30 of a second, so a dropped frame doesn't make it jump. |
| Cap pixel ratio at 1.75 | Shipped, exactly as written. |
| Frosted HUD with a 1px inner highlight, more letter-spacing |
Shipped. backdrop-filter: blur(10px)
plus inset 0 1px 1px rgba(255,255,255,.15) on every
panel.
|
| A mood LED that actually glows |
Shipped. A 9px square with a double
box-shadow in the mood colour, and the COSMO
wordmark gets a faint text-shadow in the same colour.
|
| Speech bubble types one letter at a time | Shipped, at 40 ms per character. Longer lines stay on screen longer. |
| Mood-coloured light | Partly. One point light takes the mood colour. The rest is a warm key from the top-left, a cool light from behind, a hemisphere fill and, added later, a soft overhead spotlight on the stand. |
| Respect prefers-reduced-motion | Shipped, though with no intro, camera drift or shake in the final build, there's little left for it to switch off. |
| Volumetric fog background | Replaced with a navy gradient drawn onto a 2D canvas: a 48px grid at 8.5% opacity, two thin orbital circles around the robot and four crosshair ticks. It costs nothing to render and makes the scene look like a design tool's canvas. |
| Floating dust with depth of field | Removed. The dust and particle layers were taken out entirely. |
| Reflective floor with a glowing ring | Replaced with a three-tier metal stand (roughness 0.38, metalness 0.55). The accent ring was pink at first and ended up muted blue. |
| Bloom, chromatic aberration, grain, SMAA, HDRI | Not used. Standard antialiasing and tone mapping at 1.05 exposure. |
| Camera drift, zoom punch-in, intro sequence | Not used. The camera stays still. It loads, says "hi!" and waves; that's the whole intro. |
| Automatic quality fallback below 45fps | Not needed, since there's no bloom or reflection pass to turn off. |
What I take from this: the lines that actually improved the build were the ones about how things move (springs, typing speed) and how the UI feels (glass, the glowing LED). The ambitious rendering lines produced experiments that got deleted. If you write a prompt like this, put the motion and UI lines first and treat the rendering lines as optional extras.
The follow-up: two bugs and a rename
The second pass renamed Poddy to Cosmo everywhere: the header, the
page title, the loading screen ("BOOTING COSMO…"), the iframe's
accessible title, and the download filename, which is now
cosmo.html. It also fixed two bugs that are common in
any rigged character in Three.js, so they're worth knowing about.
The tail sank into the stand
Cosmo's tail swings with its animation, and at the bottom of the swing it went through the top of the stand. Moving the whole model up doesn't fix this, because the tail's lowest point changes every frame. The fix is to check the vertices themselves. When the model loads, the code runs every animation clip once, records the model's bounding box, and keeps a list of every vertex within 0.65 units of the lowest point. Each frame it moves just those vertices into world space, finds the lowest one near the stand, and if it's closer than 0.07 units to the stand's top, lifts the robot by that amount and cancels any downward velocity. Checking only a short list of vertices keeps this cheap.
Wave and Dance ended almost immediately
Replit's changelog says the durations were being applied incorrectly.
In the fixed build, every action stores its duration in milliseconds
(dur * 1000) because the clock it's compared against is
performance.now(). A Wave now lasts 2 seconds and a Dance
4.8. The action's strength also eases in over the first 0.3 seconds
and out over the last 0.45, so the arms don't snap into or out of the
pose. If your animations finish instantly, mixing up seconds and
milliseconds is the first thing to check.
Verified in a browser with software rendering
The agent tested this in a headless browser with software WebGL, not
just by reading the code. That's possible because the page exposes a
small debug object, window.__poddy, with
emote(), bonk(), pet(), a
ready flag and tailClearance. A test can
wait until the robot is ready, trigger a Dance, and read back how
close the tail got to the stand, all without a GPU. If you want an
agent to check its own work on a WebGL project, give it a hook like
this.
Details worth taking from it
-
Every sound is synthesised. A boop is two sine
sweeps. A bonk is a falling 210→55 Hz sine plus a short burst
of bandpass-filtered noise. A snore is 1.1 seconds of noise through
a filter that sweeps down. There are no audio files, so it works
offline, and the
AudioContextis only created on your first tap. -
The model is inside the page. The GLB is stored as
base64 in a
<script type="text/plain">tag and decoded withatobon load. One file, with no loader URLs and no CORS problems, and the download button still works. - Only the eyes glow. A small change to the material shader uses the emissive texture as a mask. The screen area goes near-black and the eyes take the mood colour, so the LED face works without bloom.
- Mobile adjusts the field of view. On a portrait screen the FOV widens up to 1.55×, so the robot and stand stay in frame instead of being cropped.
- MediaPipe only loads when you ask for it. The vision bundle and its WASM files come from jsDelivr only when you press Mirror. Visitors who never do don't download it.
Recap
- Cosmo is a single-file Three.js robot with 11 moods, two hidden meters (patience and affection), spring physics, synthesised sound and a MediaPipe Mirror mode.
- The polish prompt asked for a lot of rendering effects. What shipped were the motion and UI changes. The rendering effects were tried and removed because they took attention away from the robot.
- The follow-up fixed a tail that clipped into the stand (with a per-frame check of the lowest vertices) and emotes that ended too early (seconds vs milliseconds), and renamed Poddy to Cosmo.
- Try it at poddy-animated-robot-companion.replit.app, and use the download button in the top-right to get the whole thing as one HTML file.
More Replit builds in this shape: a floor lamp hero with a brass switch that lights the page, an interactive light gallery with pull-chain physics, and ASCII Studio, which turns images, 3D models and your webcam into ASCII art.