Background
v0.7.1 (#12) added a defensive cap to setPathWithMapping{,Unmanaged}: the next-hop reconstruction loop was infinite-looping because the next matrix contained a cycle that never reaches the goal, growing the path list unbounded → hard hang + OOM. Surfaced loading a 1000-worker flying-platform colony; a stack sample pinned it to getPath looping with ensureTotalCapacity climbing every iteration.
The cap makes reconstruction safe (returns NoPathFound past size hops), but it papers over the real defect:
The real question
A correct Floyd-Warshall next matrix must never contain a cycle for a pair where hasPath == true. next[u][v] is the first hop on a shortest path u→v; following it must strictly make progress toward v and terminate in ≤ size hops. A cycle means dist and next are mutually inconsistent — i.e. generate() produced a corrupt matrix, OR the input graph has a property the optimized variant mishandles.
Hypotheses to investigate
- Optimized (SIMD/parallel)
generate() bug — the next update during relaxation is wrong/raced for some node ordering, producing a next that doesn't match the final dist. Compare FloydWarshallOptimized vs the scalar FloydWarshall on the same graph and diff the next matrices.
- Equal-cost / zero-weight edges — ties in relaxation (
dist[i][k]+dist[k][j] == dist[i][j]) updating next inconsistently, creating a 2-cycle (next[u][v]=w, next[w][v]=u at equal cost).
INF / disconnected handling — a pair marked reachable (hasPath) whose next chain routes through a wrongly-relaxed entry.
- Mapping layer (
nextWithMapping/reverse_ids) — a stale/duplicate id mapping after the graph is rebuilt (the flying-platform graph is rebuilt on scene load), so the index→id translation yields a cycle even if the raw matrix is fine.
Repro lead
Deterministic: load big_colony (1000 workers) in flying-platform on the bgfx backend; the post-load repath storm hits the cyclic pair. Better: capture the exact (start, goal) node ids + the graph (edges) at the moment the guard fires, then reproduce in a zig-utils unit test by building that graph and asserting generate() yields an acyclic next.
Acceptance
- A minimal failing case (graph + start/goal) reproduced in a zig-utils test.
- Root cause identified (one of the above or new).
generate() fixed so next is always consistent with dist; the v0.7.1 cap stays as defense-in-depth.
Related: #12 (the guard), flying-platform#540 (pin bump).
Background
v0.7.1 (#12) added a defensive cap to
setPathWithMapping{,Unmanaged}: the next-hop reconstruction loop was infinite-looping because thenextmatrix contained a cycle that never reaches the goal, growing the path list unbounded → hard hang + OOM. Surfaced loading a 1000-worker flying-platform colony; a stack sample pinned it togetPathlooping withensureTotalCapacityclimbing every iteration.The cap makes reconstruction safe (returns
NoPathFoundpastsizehops), but it papers over the real defect:The real question
A correct Floyd-Warshall
nextmatrix must never contain a cycle for a pair wherehasPath == true.next[u][v]is the first hop on a shortest path u→v; following it must strictly make progress toward v and terminate in ≤sizehops. A cycle meansdistandnextare mutually inconsistent — i.e.generate()produced a corrupt matrix, OR the input graph has a property the optimized variant mishandles.Hypotheses to investigate
generate()bug — thenextupdate during relaxation is wrong/raced for some node ordering, producing anextthat doesn't match the finaldist. CompareFloydWarshallOptimizedvs the scalarFloydWarshallon the same graph and diff thenextmatrices.dist[i][k]+dist[k][j] == dist[i][j]) updatingnextinconsistently, creating a 2-cycle (next[u][v]=w, next[w][v]=u at equal cost).INF/ disconnected handling — a pair marked reachable (hasPath) whosenextchain routes through a wrongly-relaxed entry.nextWithMapping/reverse_ids) — a stale/duplicate id mapping after the graph is rebuilt (the flying-platform graph is rebuilt on scene load), so the index→id translation yields a cycle even if the raw matrix is fine.Repro lead
Deterministic: load
big_colony(1000 workers) in flying-platform on the bgfx backend; the post-load repath storm hits the cyclic pair. Better: capture the exact (start, goal) node ids + the graph (edges) at the moment the guard fires, then reproduce in a zig-utils unit test by building that graph and assertinggenerate()yields an acyclicnext.Acceptance
generate()fixed sonextis always consistent withdist; the v0.7.1 cap stays as defense-in-depth.Related: #12 (the guard), flying-platform#540 (pin bump).