Skip to content

Fix: zero result buffers in MatrixUtils to prevent neural network output corruption#31

Open
clubsnake wants to merge 1 commit intokeiwando:masterfrom
clubsnake:fix/matrix-accumulation-bug
Open

Fix: zero result buffers in MatrixUtils to prevent neural network output corruption#31
clubsnake wants to merge 1 commit intokeiwando:masterfrom
clubsnake:fix/matrix-accumulation-bug

Conversation

@clubsnake
Copy link
Copy Markdown

Summary

The three matrix multiplication functions in MatrixUtils.cs (MatrixProduct × 2, MatrixProductTranspose) accumulate into a result buffer using += but never zero it first. When a caller passes a pre-allocated buffer — as FeedForwardNetwork.CalculateOutputs() does with tempResults on every physics tick — values from previous forward passes accumulate indefinitely instead of being computed fresh.

Root cause

// FeedForwardNetwork.cs, lines 106-108
float[] tempResultVec = tempResults[i];          // allocated once, reused every tick
result = MatrixUtils.MatrixProductTranspose(
    layerWeights, result, tempResultVec);         // += into stale values

tempResults is allocated in the constructor and never cleared. Each call to MatrixProductTranspose adds the new dot-product results on top of whatever was left from the last tick.

Why it's hard to notice

Sigmoid activation saturates quickly. After ~5-10 ticks of accumulation, every pre-activation value is so large (positive or negative) that sigmoid(x) maps it to ≈0 or ≈1 regardless of the actual inputs. The network still "works" — it just outputs near-constant values that ignore sensory input, which is why creatures converge on simple repetitive motions (e.g., rhythmic flexing) rather than developing adaptive, input-responsive gaits.

Observable impact

  • Creatures plateau early and cannot learn complex behaviors
  • Larger/deeper networks don't improve performance (their extra capacity is wasted)
  • The genetic algorithm struggles because fitness differences between chromosomes are flattened by the saturated outputs

Fix

Zero each result element before the inner accumulation loop — one line per function:

result[i] = 0f;   // added before the k-loop in each function

This is a minimal, safe change. When result is freshly allocated (the null path), the zeroing is redundant but harmless. When result is reused (the bug path), it's essential.

Testing

  • Verified the bug exists in the current master source
  • Confirmed FeedForwardNetwork.CalculateOutputs() passes reused tempResults buffers
  • Tested the fix in a compiled build — creatures now produce input-responsive outputs and evolve more diverse behaviors

Bug discovered and fix authored with assistance from Claude (Anthropic).

MatrixProduct and MatrixProductTranspose accumulate into a result buffer
using +=, but never zero it first. When callers pass a pre-allocated
buffer (as FeedForwardNetwork.CalculateOutputs does with tempResults on
every tick), values from previous forward passes accumulate indefinitely
instead of being computed fresh.

The bug is masked by sigmoid activation saturation — after a few ticks
the accumulated values grow so large that sigmoid maps everything near
0 or 1 regardless. This effectively corrupts the network's ability to
produce nuanced, input-dependent outputs and limits creatures to simple
repetitive motions rather than adaptive behavior.

Fix: zero each result element before the inner accumulation loop.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant