VIPER 11 · NotesFly it →

Note 15 of 15

The bobblehead on the dash

A spring that feels every g the jet pulls, six stock passengers, and a workshop that makes your own while you watch.

On the glareshield of VIPER 11’s F-16, just left of the HUD, there is room for one small thing. It’s twelve centimetres tall, it stands on a black disc, and its head sits on a spring. Through the takeoff roll it nods at every joint in the concrete. In a hard turn it leans, slows, and then flops over and stays there until you let go of the stick. It’s the least necessary thing in the cockpit, and it may be the thing people mention most.

Who flies with you

There are six stock bobbleheads: a tiny pilot with his visor up, a hula dancer, a shiba, a cat in goggles, an astronaut and a snake in shades. Or none, which is the default. Each was modelled once from four consistent reference views (front, both sides, back) by an image-to-3D model, then cleaned up and cut in two when the game loads it: every triangle above the neck goes on the spring, everything below stays on the base, and a small steel coil covers the cut. Each one also has a fallback built from spheres, capsules and cones, so if a model file fails to load there’s still a figure on the dash.

The Bobblehead page of the game's menu: a list of the six stock figures on the left, the shiba turning on a turntable on the right
The Bobblehead page, from the start menu or the pause. ←/→ on the Bobblehead row swaps the figure in place without opening the page; Enter opens it, and the one you pick wobbles on its turntable.

The pilot under them can be dressed, too. Hangar, Pilot has six outfits, from the reference clip’s near-black Nomex to international orange. They are colours and materials on one shared body: the suit, the G-suit chaps (which the GoPro sees more than anything else, over the knees), the gloves and the boots. The colours were taken from daylight photographs of issue kit, such as sage green CWU-27/P suits and olive G-suits, and converted to linear albedo so the cockpit’s sun lights them like cloth rather than paint.

Figure 1 · Six outfits, one pilot

The pilot's knees and hands from the GoPro, in the selected outfit
The same frame on the runway at golden hour, one outfit at a time. Black Nomex is the clip’s own look: near-black suit, charcoal G-suit, bare hands.

A spring that feels the jet

The obvious way to animate a bobblehead is to wiggle its head on a timer. That looks like a timer. What makes a real one funny is that it answers to the car, and here it answers to the jet. Every frame the game asks the flight model how the jet’s velocity and rotation changed, and turns that into what a passenger would feel: the specific force, which is 1 g pressing down when you sit still, 9 g pressing down in a 9 g pull, a shove aft when the afterburner lights and a jolt up at touchdown. The head’s spring is driven by that, plus the jet’s angular acceleration, so in a snap roll the head lags behind the cockpit and then catches up.

The head is a top-heavy mass on a coil spring, so it’s an upside-down pendulum. The spring pulls it upright; gravity, or whatever is standing in for gravity, tries to tip it over. At 1 g the spring wins comfortably and the head nods at 2.7 times a second. As the load rises, the effective stiffness falls. At 3 g the nod has slowed to 1.8 Hz, at 4 g to 1 Hz, and at 4.5 g the spring can no longer hold it up at all. The head buckles: it flops over onto its stop, a soft one that starts 24° from upright, and wobbles there until the pull ends. That threshold is a number chosen for the toy, not measured from one. A real spring bobblehead would buckle at some load too, and 4.5 g puts the flop where pilots start straining.

Figure 2 · The bobble, live

The game’s spring (src/cockpit/bobble.ts), stepped here at its own fixed 240 Hz with the same constants, in side view with the pilot on the right. The glareshield slopes 0.2 rad toward you, so the head’s weight already leans it back a little at 1 g. Slide the load past the spring’s buckling point and it flops back onto its stop; bring it back below and it springs up. Buffet is the noise the game adds in cloud (1.0) and on the runway (up to 0.5); in clear air it is 0.06. Touchdown is the kick a firm landing gives it. The trace underneath is the tilt over the last six seconds.

The game integrates two tilt angles (sideways and fore-aft), a bounce along the neck and a little twist, at a fixed 240 steps a second whatever the frame rate, so the bob is the same on a phone as on a desktop. The bounce is preloaded for 1 g: under load the spring compresses, up to its full 6 mm at 9 g, and it rings at 11 Hz when the wheels hit. Each stock figure carries its own spring. The hula dancer’s is in her hips, softer (1.7 Hz) and quicker to give (3.2 g), because that is the whole point of a hula dancer.

Figure 3 · Softer with every g

The nod frequency against load for the four springs the game uses. Each line ends where its spring buckles: past that point there is no upright to nod around. Move across the chart to read a load.

Real accelerations come with real noise. The flight model steps once a frame, so the velocity difference between frames is lightly smoothed (over about 35 ms). Some things that look like accelerations aren’t: a restart, or the autopilot taking you home, moves the jet instantly, and differentiated naively that would be an acceleration of thousands of g. Anything over 30 g is ignored, and the drive is clamped to 12 g either way. A few events are added on purpose, as one-off kicks: the touchdown thump, scaled by how hard you sank; the nosewheel coming down; each expansion joint in the runway; the gear going up.

The takeoff roll at golden hour, full afterburner, through rotation at about 165 mph. The head rocks back as the burner pushes, nods at the runway’s joints, and settles as the wheels leave the ground.
The whole GoPro view on the runway at golden hour: the shiba stands on the glareshield to the left of the HUD
Where it sits, from the GoPro. The mount is on the hood’s rising top, left of the HUD and outboard of the AOA indexer, chosen so the figure never covers an instrument or the HUD combiner. It casts and receives the cockpit’s sun shadow like everything else in there.

Making your own

The stock figures were always meant as a start. Under the list there is a row called Make your own: describe a bobblehead in a few words, or add a photo of yourself, and it is made for you, there and then, on your glareshield.

This is the first part of VIPER 11 that needs an account. Flying never does, and someone who has never signed in costs nothing: the sign-in library isn’t even loaded. Making a bobblehead is different, because each one costs real money to make and has to belong to someone. Sign-in is WorkOS AuthKit, and the backend is Convex, a hosted database whose queries are live: the browser subscribes to your bobblehead and gets every change the moment it is written. The account is what lets the game count your builds, keep what you made private, and give it back to you on another device.

The Make your own panel: a text box, a photo button, quick or high detail, and a figure half made on the turntable above, amber wireframe over its first parts
Make your own, mid-build: quick or high detail, words or a photo, and the figure being machined on the turntable while the same one appears on the glareshield. Captured on the game’s local test builder, which streams a hand-written figure (a crew chief) line by line instead of calling any service, so no one’s face is in this picture.

Words in, parts out

The build runs on the server, in steps:

Figure 4 · A figure, line by line

The format the model writes (shared/bobble/schema.ts), using the hand-written crew chief the game’s tests stream, with each part’s finish, decals and outlines left out. Each line is one part; g says whether it rides the spring (head) or is fixed (body, base), and mirror makes a pair. On the right, each part’s outline front-on as it arrives. A quick build is 16 to 28 parts and takes about 20 seconds.

A quick build stops there: a figure of a couple of dozen glossy vinyl parts, which is honestly most of the charm. High detail goes on. With the parts done, an image model is asked for a reference sheet of the same figure from four sides, described from your words and from the figure the language model just designed (its name, its paints part by part, the height of its neck), so that the two agree. The sheet is cut into its four views and sent to an image-to-3D model, which returns a textured mesh of about 9,000 triangles a few minutes later. The server packs its textures down to 1,024 pixels and stores it.

Then the browser fits it to the doll. The mesh is scaled to the part-built figure’s height, stood on its base, and cut at the neck: the game looks for the narrowest cross-section near the neck height the language model chose, and hangs everything above it on the spring. A second machining pass swaps one for the other, a cut plane rising through the figure with the sculpt appearing below it and the part-built version eaten away above, so the change is never a pop. If the mesh fails, the part-built figure simply stays.

Private, and affordable

Everything you make is visible only to you. Every query on the server checks that the bobblehead belongs to the player asking; someone else’s reads as not found. The same goes for your photos. A photo for a bobblehead is shrunk in your browser to 768 pixels before it is sent, enough for hair, glasses and colours and no more.

Each build has a worst-case cost, reserved when you ask for it: 25 cents for a quick one, and $1.80 for a high-detail one, which adds the image and the mesh. Two limits keep that under control. Each player gets three quick builds and one high-detail build a day (UTC), keeps at most 24 bobbleheads, and makes one at a time. And the whole game has a daily budget, $10 unless the deployment says otherwise, shared by everyone; once it is spent, the workshop is closed until tomorrow. A build that fails is refunded, both to you and to the day’s budget. Most quick builds cost a fraction of their reservation, about six cents, so the budget is cautious, not tight.

Table · The limits

Per player, per dayKept at onceReserved per build
Quick bobblehead324 bobbleheads$0.25
High-detail bobblehead1$1.80
Photo upload1012 photos—
The whole game$10 a day of builds, then closed until midnight UTC
From convex/lib.ts. At the default budget that is 40 quick builds, or five high-detail ones, across every player in a day.

Photos on the console

The same account brings one more thing: photos. Pilots tuck pictures into the cockpit, so the game does too. Upload one and it becomes a small print, the size of a wallet photo (8.9 cm), with a white border, a caption in pen along the bottom, a strip of tape across the top and a slight curl, tucked in at the front of the canopy sill, left or right. They are ordinary lit meshes, so the sun and its shadows fall across them and the canopy reflects them. Before a photo leaves your browser it is re-encoded as a JPEG of at most 1,600 pixels, which also strips its metadata, location included; afterwards it is cached in the browser so it downloads once. Like the bobbleheads, your photos are yours alone.

A small print taped to the front of the left canopy sill, catching the low sun
A print on the left sill. The picture in it is one of these notes’ own game stills (the Nellis bank, from the airfield note), put there with a capture-only switch, not a player’s photo.

A hand that presses the right key

One of the quieter fixes that shipped with all this was to the pilot’s left hand. Press I after takeoff and the pilot squawks IDENT: the hand leaves the throttle, reaches to the keypad under the HUD, presses the key and comes back. It had been doing that for a while, but badly. The hand was one rigid piece from the knuckles to a stub of forearm, so when it turned to point at the key, the stub broke away from the sleeve at about 55°.

The hand now has a real wrist, with anatomical limits: 70° of flex either way, 25° of sideways deviation and no twist, because forearm roll is carried by the whole forearm and twisting at the wrist is what collapses a skinned mesh like a candy wrapper. The forearm is aimed at the elbow that the arm’s solver puts it at, so sleeve, cuff and wrist line up. The reach is a minimum-jerk arc that lifts off the throttle first and then goes forward, taking 0.95 s, with the hand turning a little behind its position, the way an arm leads a hand. On the way it lets go of the grip and pre-shapes: index straight, the others loosely curled, the thumb resting on the middle finger. The fingertip closes the last 15 mm straight along the key’s normal, pushes it in 3 mm with a slight nod of the wrist, and the hand goes home and closes on the throttle again. The press still bottoms out at 1.13 s, as before, so the radio call and its caption still line up with it.

IDENT at 6,000 ft: 0.95 s out, the press, 0.97 s back, about 2.3 s in all. The fingers are a signed-distance surface; another part of the fix stopped the index and middle fingers fusing into a fin whenever one curled and the other did not.

What this is, and isn’t

The bobble is a two-degree-of-freedom toy model with a bounce and a twist bolted on, not a simulation of a vinyl figure. Its numbers (2.7 Hz, a damping ratio of 0.07, buckling at 4.5 g) were tuned by watching it, not measured from a real one. What is real is what drives it: the same accelerations the flight model computes for the jet and for your vision in a hard turn. If the head flops over, you pulled more than four and a half g. That turns out to be the most useful instrument on the glareshield.

Fly it

viperoneone.com

VIPER 11 is a browser flight game: three.js, WebGL2 and Web Audio. Every figure on this page is drawn from the game’s own code and numbers. Made by apek. Follow me @apekshik for what’s next.