Cosmo: The Replit Prompt I Used to Polish a 3D Robot Companion (and What Actually Shipped)

Published September 2026 · avivashishta.com

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 doCosmo 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.

MoodChip saysLED
calmcalm#3dff5a
happyhappy#3dffc0
lovesmitten#ff4fb3
surprisedstartled#ffd43f
hurtouch#ff5a3a
angryfurious#ff2d24
sadsulking#4a86ff
dizzydizzy#b26bff
sleepyasleep#2dbfa3
excitedwheee#ffe14a
curiouscurious#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:

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:

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.

Upgrade the visual polish of poddy-local.html (Three.js interactive robot hero). Keep all interactions, mood logic, and Mirror mode exactly as they are. Only improve how it looks. Scene and lighting - Add post-processing with EffectComposer: UnrealBloomPass so only the emissive parts glow (threshold ~0.85, strength ~0.6, radius ~0.4), a subtle chromatic aberration and film grain pass, and SMAA. - Swap RoomEnvironment for a moody studio HDRI (dark, with soft rectangular key lights) so the chrome shows crisp highlight strips instead of flat grey reflections. - Build a three-point light rig: warm soft key from top-left, a cool rim light from behind, and a mood-colored rim from below. Blend every light to the new color over 0.4s when the mood changes. - Make the body read as deep gunmetal: lower albedo, roughness ~0.18, clearcoat on the head. Background - Replace the flat gradient with a slow volumetric-fog look: layered noise planes at different depths, tinted by the mood color and lit most strongly behind Poddy. - Make the floor a dark, glossy, reflective surface (MeshReflectorMaterial-style, blurred reflection) with a soft contact shadow and a glowing hover ring under Poddy that pulses with his float. - Give the floating dust depth of field: near particles large and blurry, far ones tiny and sharp, drifting in slow parallax with the cursor. Motion feel - Make everything move with springs (stiffness/damping) instead of linear damping, so the head turn, arm swings and knockbacks overshoot slightly. - Add squash and stretch on hits and landings (scale the body 0.9/1.1 for ~120ms). - Add a camera that drifts subtly with the pointer (±2° orbit), and a quick zoom punch-in on big bonks. - When the mood changes, run a soft color wave across the scene from Poddy outward. FX - Replace the flat sprites with glowing particle bursts: sparks with motion-blur streaks on hits, hearts that float up and fade with a soft bloom, and dizzy stars that leave short trails. - Make the eyes feel like a real LED screen: add scanline/pixel-grid shader and a faint screen flicker on the face glass. UI - Make the HUD more refined: frosted glass with a 1px inner highlight, smaller type, more letter-spacing, and a mood chip whose LED actually glows (box-shadow in the mood color). - Style the speech bubble as a small LED-matrix panel that types its text out one letter at a time. - Add a short intro on load: the camera starts close and dark, Poddy's eyes power on, the lights sweep in, then he waves. Performance - Keep 60fps on a MacBook Air: cap pixel ratio at 1.75, and if FPS drops below 45 for 2s, automatically turn off bloom and the reflections. - Respect prefers-reduced-motion by skipping the camera drift, shake and intro. Deliver it as a single self-contained HTML file with the GLB still embedded.

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 forIn 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

Recap

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.

Robot model: "Poddy M1 – Stylized Floating Robot" by Exmoor beast on Sketchfab, licensed CC-BY-4.0.

← Portfolio · Blog