The control was standing in the same room
Three props in my hub looked wrong. Shiny, slightly blue, like they were catching a light that wasn't in the room. Everything else in the game has a flat matte look; these didn't. I fixed it four times. First the metalli…
Three props in my hub looked wrong. Shiny, slightly blue, like they were catching a light that wasn't in the room. Everything else in the game has a flat matte look; these didn't.
I fixed it four times.
First the metallic map, because a shiny prop is usually a metallic one. Then the shader keywords, because URP branches on keywords and the float alone is inert. Then the source atlases themselves — I had the artist re-bake all three, which was real work, on the theory that the palette had been chosen under a metallic render and stripping the metal left the colors reading wrong. Then the hub's ambient color, which was a genuinely cool fill, (0.055, 0.050, 0.062), and in an enclosed room lit by torches that constant is most of what you see.
Every one of those was a correct edit. The metallic value should be zero. The keyword really does gate the float. The atlases really were better afterwards. The ambient really was blue and is now warm. Four changes, four improvements, and the props were still shiny.
The thing that was different was standing next to them
There are braziers in the same room. Same ambient, same torches, same enclosed space, delivered by the same artist in the same week. I'd said out loud, in the same breath as complaining about the other three, that the braziers looked great.
I never asked why.
That's the part I want to write down, because the debugging wasn't lazy — it was four rounds of careful, mechanically sound work. What was missing wasn't rigor. It was that I kept asking is this fix correct? and the answer kept being yes, when the question that would have ended it in five minutes was what is different about the one that looks right?
Two things, as it turned out, and it needed both.
The braziers' source file carries SpecularFactor: 0.0. The workbench and the vault carry 0.25. Unity's FBX importer folds that into the auto-generated material's smoothness, so those two arrive glossy from the moment they're imported.
That alone would be harmless, because I don't use the auto-generated material — every one of these props loads a hand-authored one at runtime, metallic 0, smoothness 0.12. Except that on the workbench and the vault, the code that does the loading never ran.
A filename
The two classes that dress those props were declared inside a file named after a third class. Unity resolves a component through its MonoScript, and it keys MonoScript by file name — so a MonoBehaviour whose class name disagrees with its file simply cannot be referenced. The scene stores m_Script: {fileID: 0}, a null. No error. No warning. Nothing in the console, nothing in a build log, nothing anywhere.
So Awake never ran, the dressing never ran, the matte material was never applied, and what
rendered was the glossy import material — reflecting the default skybox, which is a blue midday sky, indoors.
"Shiny, kind of like it's reflecting another light source" was a literally accurate description of the mechanism. I'd been reading my own bug report as a metaphor for a fortnight.
And every earlier fix had changed something that is never read. The material asset isn't applied. The atlas isn't sampled by the material that's actually rendering. Ambient is the diffuse channel and gloss is specular — a different input entirely. That's why all four agreed with themselves and none of them moved the picture. A fix can be correct, verifiable, and about nothing.
The same day, the same shape
I also couldn't swing a two-handed weapon. I could hold the sword in both hands, and then only wiggle it. Measured: two-handed swings averaged 5.71 m/s against a one-handed median of 66.87. About eight percent of the speed.
The cause is a line in the grip solver that builds the weapon's rotation with the blade pointing along the line between your hands, leaving the dominant wrist only roll about the blade. So a wrist flick moves the blade not at all — the only way to sweep it is to move both arms through space, which is several times slower.
That behavior was tested. There's an assertion that the blade lies along the hand line to within a thousandth, and a comment above it reading "the rule that makes it stable: the long axis is the hands, and nothing else."
The test was right. The comment was right. That rule is exactly why the weapon couldn't be swung.
Every test in that class asked where the blade points given where the hands are. Not one asked whether the blade responds to the hand holding it. A green suite told me the geometry was correct while the thing was unusable, and both statements were true.
There's a nastier detail. The previous build had a bug — the grip flickered between two modes several times a second, which I'd complained about as "glitchiness". Removing that flicker made the weapon worse, because the flicker had been falling back to pure-wrist control on about 30% of frames. It wasn't a better build. It was leaking the correct behavior through its own defect.
And a third, smaller one
Distant lightning in the intro that I never saw fire. Three strikes, fifteen seconds apart, and I missed all three. They were drawn at an offset written in the source as 2.1 m left, 0.9 m down, at 3.8 m.
Nobody had ever converted that to an angle. It's 29 degrees off-axis and 13 down — technically inside the field of view, practically in the corner of your eye while you're looking at something else. Written as meters it looks like a placement. Written as degrees it's obviously a problem.
What I'm taking
All three were verified, and all three verifications were about the wrong quantity. So the habit I want isn't check harder. It's:
When something looks wrong, find the thing that looks right and ask what's different about it. The control was standing in the same room the whole time, and I'd already told myself it was fine.
The tests I added are shaped by that. The one guarding the grip runs its own fixture at the old setting and requires it to fail, so it can't quietly pass against the code it exists to refute. And the one guarding the filename asks Unity's own question rather than a proxy for it — because a proxy that's right most of the time is precisely how you get four correct fixes and no change.
None of this is confirmed yet. It's built, the suite is green, and it's on the headset. The four explanations before this one were internally consistent too.