PathManager.GetCachedObstacles always empty — CollectObstaclesInPlace never fills _obstacleArrayBuffer #808

Open
opened 2026-07-23 04:27:15 +00:00 by spikerj · 1 comment
Owner

Summary

PathManager.CollectObstaclesInPlace writes obstacles into the caller-provided Span<Obstacle> and updates _obstacleBufferCount, but never copies into _obstacleArrayBuffer.

GetCachedObstacles() returns _obstacleArrayBuffer.AsSpan(0, _obstacleBufferCount), so after a successful CollectObstaclesInPlace the span length is non-zero but every element is default/default(Obstacle) (zeros).

Grep confirms _obstacleArrayBuffer is only declared and read — never written.

Repro

var manager = new PathManager(...planets: () => [planet]...);
Span<Obstacle> buf = new Obstacle[8];
var count = manager.CollectObstaclesInPlace(craft, buf);
// buf has planet; manager.GetCachedObstacles() has length==count but Position/Size all zero

Expected

Either CollectObstaclesInPlace should also populate _obstacleArrayBuffer (so GetCachedObstacles matches), or GetCachedObstacles should expose the same data the in-place path just collected.

Found during coverage wave (PathManager residual).

## Summary `PathManager.CollectObstaclesInPlace` writes obstacles into the caller-provided `Span<Obstacle>` and updates `_obstacleBufferCount`, but never copies into `_obstacleArrayBuffer`. `GetCachedObstacles()` returns `_obstacleArrayBuffer.AsSpan(0, _obstacleBufferCount)`, so after a successful `CollectObstaclesInPlace` the span length is non-zero but every element is default/`default(Obstacle)` (zeros). Grep confirms `_obstacleArrayBuffer` is only declared and read — never written. ## Repro ```csharp var manager = new PathManager(...planets: () => [planet]...); Span<Obstacle> buf = new Obstacle[8]; var count = manager.CollectObstaclesInPlace(craft, buf); // buf has planet; manager.GetCachedObstacles() has length==count but Position/Size all zero ``` ## Expected Either `CollectObstaclesInPlace` should also populate `_obstacleArrayBuffer` (so `GetCachedObstacles` matches), or `GetCachedObstacles` should expose the same data the in-place path just collected. Found during coverage wave (PathManager residual).
Author
Owner

Audited against origin/masterSTILL BROKEN, but the notes need one correction: this is latent, not live.

The defect is exactly as described. SpikerSoft.GameServer/Controllers/PathManager.cs:53 declares _obstacleArrayBuffer; CollectObstaclesInPlace (:294-341) writes only into the caller's output span and _obstacleBufferCount; GetCachedObstacles (:348-350) returns _obstacleArrayBuffer.AsSpan(0, _obstacleBufferCount). The field is still never written, so the accessor returns a window over a zeroed array sized by an unrelated count.

The correction — GetCachedObstacles has no production caller. It appears only at its own declaration (:348) and in PathManagerLifecycleTests.cs:316 and :330. The ticket reads as though pathfinding is currently consuming empty obstacle data, which would be live gameplay corruption. It isn't — nothing calls it outside tests.

That's worth recording for two reasons. It changes the priority (this is a latent trap waiting for the first caller, not an active bug), and it changes the fix: whoever wires up a caller will get silently-empty obstacles unless the buffer is filled first, so the ticket should be resolved before anything starts using it rather than in response to a symptom.

Prioritisation context from the same sweep. I checked reachability across eleven small-bug tickets. Three are latent with no production caller — this one, #792 and #840. The rest are live: #811 (broken AES on a real network path, security-relevant), #793 and #795 (live gameplay/transport), #794, #797, #799. If these are being worked as a batch, the live set is where the user-visible impact is; this one is best treated as cleanup-before-use.

Notes are otherwise accurate — line numbers still match current code.

Audited against `origin/master` — **STILL BROKEN, but the notes need one correction: this is latent, not live.** **The defect is exactly as described.** `SpikerSoft.GameServer/Controllers/PathManager.cs:53` declares `_obstacleArrayBuffer`; `CollectObstaclesInPlace` (`:294-341`) writes only into the caller's `output` span and `_obstacleBufferCount`; `GetCachedObstacles` (`:348-350`) returns `_obstacleArrayBuffer.AsSpan(0, _obstacleBufferCount)`. The field is still never written, so the accessor returns a window over a zeroed array sized by an unrelated count. **The correction — `GetCachedObstacles` has no production caller.** It appears only at its own declaration (`:348`) and in `PathManagerLifecycleTests.cs:316` and `:330`. The ticket reads as though pathfinding is currently consuming empty obstacle data, which would be live gameplay corruption. It isn't — nothing calls it outside tests. That's worth recording for two reasons. It changes the priority (this is a latent trap waiting for the first caller, not an active bug), and it changes the fix: whoever wires up a caller will get silently-empty obstacles unless the buffer is filled first, so the ticket should be resolved *before* anything starts using it rather than in response to a symptom. **Prioritisation context from the same sweep.** I checked reachability across eleven small-bug tickets. Three are latent with no production caller — this one, **#792** and **#840**. The rest are live: **#811** (broken AES on a real network path, security-relevant), **#793** and **#795** (live gameplay/transport), **#794**, **#797**, **#799**. If these are being worked as a batch, the live set is where the user-visible impact is; this one is best treated as cleanup-before-use. Notes are otherwise accurate — line numbers still match current code.
Sign in to join this conversation.