Three issues resolved in the detection explainability system:
- computeExplanation returned early when grid==nil (always the case in the
live fusion loop), causing all explain requests to return empty link
contributions. Removed the unnecessary nil-grid guard since the Fresnel
computation uses only blob/link positions, not the grid.
- Contribution values were raw deltaRMS×weight×zoneDecay scalars, not
percentages. Now normalized so contributing links sum to 1.0, giving the
dashboard a proper confidence breakdown (60% / 40% / etc.).
- Fixed off-by-one in blobHistory eviction (kept 101 instead of 100).
Added 23 table-driven tests covering: nil-grid computation, single/multi-link
normalization, Fresnel zone number geometry, ellipsoid generation, HTTP
handlers (200/400 paths), WebSocket snapshot fields, BLE match inclusion,
zone decay inverse-square law, and history eviction cap.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>