← All posts

The pistol was low, not rotated

Yesterday the server started picking the right weapon, the ticket recorded it, and the tests proved it. The client drew a rifle anyway.

The function returning the viewmodel weapon returned the same receiver unconditionally. Every weapon was one weapon.

Three causes, and I predicted one

The client could not know. Weapon state is deliberately not replicated. The snapshot decoder resets it to a default, which is a rifle, so a client asking its own prediction what it is holding always hears "rifle". No wire change was needed, though: the equipment message already carries the sling and the holster. The answer was on the client the whole time, being asked of the wrong thing.

The server never sent it at deploy. Equipment was transmitted only when a client changed something, never on arrival. That is invisible for most of a loadout, which the client does not draw. It is extremely visible for the weapon.

The system was never registered. The rebuild ran zero times, because my edit to the system list matched nothing. The tuple had gained an unrelated entry since I last read it, so the replacement silently did nothing and the build stayed green.

That third one is the one worth keeping. It was found by instrumenting rather than theorising, and the diagnostic printed no lines at all, which is a categorically different failure from printing wrong ones. A wrong number means your logic is wrong. No number means your code is not running, and no amount of staring at the logic will tell you that.

A pistol is one mesh

The art pack ships pistols whole, so the parts list returns a single part and every consumer (viewmodel, body arming, muzzle lookup) sees a shorter list and works unchanged.

Inventing a nine-slot attachment config for a weapon with no slots would have described something that does not exist.

Then it aimed edge-on

With the right gun in hand, aiming down sights showed the pistol turned sideways. Three rounds of tweaking chased that rotation.

There was no rotation.

Aiming drops the whole viewmodel root by 19.33cm, a figure measured to put the rifle's sight line on the eye. The pistol's slide top sits much lower in its own mesh, so that same drop parked its sights 14.5cm below the eye. What was on screen was the top face of the slide, viewed from above, and a flat plane seen edge-on reads, convincingly, as a gun rotated 90 degrees.

The symptom named the wrong axis, and I believed the symptom three times.

The fix lifts the weapon rather than the root, because the arms and the ammo counter are positioned for the hip pose and follow the root. It lifts by the measured difference less 2cm, because a pistol is aimed looking over its sights rather than through them. It blends on the same easing curve as the travel into aim, so the correction cannot arrive before the weapon does.

The slide racks

Carved off that single shipped mesh the same way optic glass is: per triangle, at the horizontal line the vertex histogram names, with straddling triangles staying attached to the frame.

It rides the recoil decay the weapon's own kickback already uses: full travel on the frame the round leaves, home when the kick is. So there is exactly one clock driving both. Two clocks that agree today are two clocks that disagree later.

What is still wrong

Third-person pistol bodies hold a rifle stance.

There are no pistol body animations to map. The shipped rig carries seven rifle-stance clips, and all three source animation packs are rifle-only. This is not a bug with a fix pending; it is an asset that does not exist yet, and pretending otherwise in a devlog would be the same mistake as trusting the symptom about the rotation.