What’s that gray block…
After wrapping up the strand rendering improvements, I thought the biggest issue of the day was finally behind me.
It wasn’t.
Me: This looks strangely familiar. That gray block around the face… didn’t we already fix this once?
Claude: We did. We fixed a bug where the face removal area was being filled back in by the front hair enhancement logic.
That immediately brought back the memory of that fix.
Back then, we introduced a dedicated array called faceExcludeMask. Its only job was to permanently protect pixels that had already been classified as facial skin.
if (faceExcludeMask[i]) continue;
The idea was straightforward.
Once a pixel is identified as part of the face, nothing later in the hair enhancement pipeline should ever paint over it again.
So my first assumption was obvious.
“The old bug is back.”
But after walking through the code…
it wasn’t.
faceExcludeMask was still there, and every related enhancement routine was still checking it correctly.
That meant the gray rectangle I was seeing had to be coming from an entirely different code path.
Me: If faceExcludeMask is still doing its job, then what’s drawing that rectangle?
Claude: faceExcludeMask only protects the mask generation pipeline. Anything rendered outside that pipeline won’t even know it exists.
That completely changed the direction of the investigation.
Instead of questioning the segmentation mask, I started tracing the rendering pipeline itself.
The new suspects became:
- The extended strand (
ext) rendering - Thick section layers
- Any rendering path that draws directly without referencing the hair mask
Same visual symptom.
Completely different cause.
Gravity becomes the prime suspect
While tracing those rendering paths, another possibility came to mind.
A few days earlier, I had added a small gravity system to make long hair fall more naturally.
gravityPull
Later, I also introduced a direction limiter.
MAX_DIR_DEVIATION
Neither feature looked particularly risky on its own.
They were just small tweaks to improve realism.
But together…
they might have been amplifying an existing weakness in the rendering logic.
Claude: Think about the columns where the face mask removed almost all of the hair.
Those columns still contain interpolated scalp positions.
Without gravity, the generated strands naturally spread out because of curl and wiggle.
Once gravity and direction clamping are applied, however, those strands begin pointing in almost the same direction.
Instead of appearing as scattered noise, they overlap into what looks like one dense rectangular block.
That explanation matched what I had been seeing surprisingly well.
It also explained why the artifact only became noticeable after adding gravity.
Gravity probably didn’t create a new bug.
It simply made an existing flaw impossible to ignore.
Sometimes a new feature doesn’t introduce a bug.
It just exposes one that has been quietly sitting there all along.
To verify the theory, I asked Claude to disable only the gravity system.
Leave the clamp alone.
Well…
Claude got a little overenthusiastic and disabled both. 😂
After restoring the clamp, we finally built a clean test version with just one change:
const DEBUG_DISABLE_GRAVITY = true;
One variable.
One hypothesis.
No other modifications.
That’s exactly how debugging experiments should be done.
We didn’t reach a final answer that day.
But we did change the question we were asking.
Instead of wondering,
“Why is the face mask broken?”
we started asking,
“Which rendering stage is bypassing
faceExcludeMaskaltogether?”
Sometimes that’s all it takes.
A single better question can completely change the direction of a debugging session and get everything moving again.
Leave a Reply