thu.ndero.us Experiments in engine, systems, and iteration
Devlog

Measuring the wrong thing

I sat down to run one performance measurement and got up fourteen hours later with a sword that hurts things, a campfire you build a log at a time, and a panel that floats a note in front of me so I don't have to take th…

I sat down to run one performance measurement and got up fourteen hours later with a sword
that hurts things, a campfire you build a log at a time, and a panel that floats a note in
front of me so I don't have to take the headset off to be told what to try next.

The through-line, which I didn't notice until I wrote the report, is that almost everything
that went wrong today was a measurement problem. Not a bug — measurements that worked
perfectly and measured the wrong thing.

The sweep said MSAA was free. It isn't.

The frame-cost sweep runs the scene through eight phases, switching content off in each, and
captures a Perfetto trace inside every one. I'd run it before. It told me MSAA 4×→2× changed
nothing and that shadow distance was contradictory enough to ignore.

Both wrong, and for the same reason: the GPU clock changes between phases. This run split
into 599 MHz for two phases and 545 for the other six. Compare raw milliseconds across that and
you get nonsense — literally, in this case. Normalising by ms × MHz flips the sign on three of
eight phases. Raw numbers say that switching off the enclosure ring makes the scene more
expensive, which isn't a thing that can happen.

Corrected: MSAA 4×→2× is worth 1.68 ms, the second largest saving available in the whole
scene. Shadows cost 2.6% total and shadow distance buys nothing at all — which retires the
"obvious untried candidate" I've been carrying since the first device run.

The scene also fits the frame now. Every phase held 72.5 FPS with headroom, where the previous
run sat pinned at maximum GPU clock and dipped to 60. That's not one fix being credited — the
clearing was rebuilt in between — but it's nice to have the budget back.

The fire lit itself and the code was correct

The new thing is a firepit. You gather firewood, walk to an empty ring of stones, press A three
times to lay the logs, press once more to light it.

The first version used proximity and a dwell: stand within 1.6 m for 0.8 seconds and it catches.
I put the headset on and it lit itself while I was picking up the wood.

The dwell worked exactly as written. The problem is that one of the firewood piles sits 0.92 m
from the pit, so gathering it requires standing there for seconds — and no dwell length
distinguishes "standing near a thing" from "wanting to use it". I could have tuned that number
forever. A button press is the smallest thing that carries intent, so it's a button now, with a
prompt showing the A glyph and a counter that goes 0 / 3 in red and 3 / 3 in green.

That took six headset passes to get right, and only one of them was about the mechanic. The
rest were legibility: the pit I picked had wood baked into the mesh so it could never be
empty; the ring read as a ritual circle until it had sixteen varied stones; it was too small
to look like an objective until I doubled it. Every one of those I could only find by wearing it.

A held sword has no velocity

Making the sword damage things looked like a five-minute job. It wasn't, and the reason is the
best example of the day's theme.

Strikeable scores a blow from collision.relativeVelocity. That works for a thrown rock,
because a released rock goes dynamic and physics knows exactly how fast it's moving. But the
interaction SDK locks a held prop kinematic for the duration of the grab, and a kinematic body
moved by its transform carries no velocity at all. A sword swung through a monster reports about
zero, fails the impact floor, and does nothing.

If I'd shipped that, it would have read as "the sword doesn't work". It wasn't broken; it wasn't
being measured. The weapon samples its own blade tip now and hands the speed in.

While I was at it I found out why I kept slowly floating into the air while holding it.
Everything in the project is on the Default layer, so the sword's grab collider was pushing my
character capsule — and the locomotor sets my height from that capsule every frame. Hold a sword
near your chest, rise gently off the ground.

The audio was fine the entire time

I added the first sound in the project — an impact noise when something gets hit. Silence.

I checked everything: the clips load, they're forced to mono (a stereo clip can't be
spatialised, it just plays flat in both ears), the AudioSource is properly 3D, audio isn't
disabled project-wide. Then the AudioListener, which a text search of the scene file says
doesn't exist — because it's nested inside the camera rig prefab, and a nested prefab's
components live in its file. I nearly added a second one.

None of it was the problem. My PC was routing audio somewhere other than the headset. Horizon
OS plays its own sounds on-device regardless, so "I can hear the system but not the game" reads
exactly like the game being broken.

Still broken

The sword's grip is tuned for my right hand and sits wrong in my left, and a close-range grab
ignores the tuned pose entirely — you just catch the box around the handle at whatever angle
your hand was at. I've got an agent digging through the SDK on that one.

And lighting a fire still doesn't do anything. I built a whole gather-and-spend loop that
terminates in a nice visual. The next piece is making a room able to say what opens its gate —
kill everything, or light the braziers, or both — so the fire is sometimes the way out instead
of decoration.