Conversation
…y, and wall avoidance
Introduce Structure-of-Arrays (SoA) data layout for pathfinding graph nodes
via a new SpotManager, replacing the previous object-scattered Spot storage.
Add grid-aligned spot placement, wall/edge repulsion post-processing, and
improved triangle scoring -- all validated by iterative benchmarking.
=== Architecture: SpotManager (SoA data layout) ===
- NEW PPather/Graph/SpotManager.cs: Central manager storing all spot data
in contiguous parallel arrays (locations, flags, path edges, chunks)
indexed by a cached Spot.ManagerIndex for O(1) access.
- Flattened edge storage in a single float[] with per-spot offset/capacity
tracking, eliminating per-Spot float[] path allocations.
- Edge spot cache (edgeSpotCache[]) avoids costly coordinate->Spot
round-trips in GetPathsToSpots; cache misses resolved lazily.
- Reusable neighborBuffer for single-threaded A* neighbor enumeration.
- Cached triangle scores (short[]) per spot to avoid redundant
closeness+gradient recomputation during a single search.
- NEW PPather/Graph/SearchBuffer.cs: Pooled per-search state buffer
(traceBack indices, G/F scores, closed/scoreSet BitArrays) allocated
from a ConcurrentStack<SearchBuffer> pool and returned after each search.
Replaces search state fields that were previously scattered across
individual Spot objects (searchID, traceBack, score, closed, scoreSet).
~180 KB per search, reused across searches via pool.
- CHANGED PPather/Graph/Spot.cs: Stripped down to a lightweight identifier.
Removed ~170 lines of path management, search state, and scoring methods.
All data now lives in SpotManager arrays. Only Loc, flags, chunk, next
(for spatial hash chaining), and ManagerIndex remain.
Added FLAG_ON_OBJECT (0x0040) to distinguish bridge/ramp surfaces from
indoor corridors.
=== Pathfinding: Grid-based connectivity ===
- CHANGED PPather/Graph/PathGraph.cs:
- Replaced radial spot scanning (FindAllSpots + angle-based loop) with
grid-aligned 8-neighbor connectivity using SpotGridSize (= WantedStepLength).
- SnapToGrid() ensures spots land on predictable, evenly-spaced positions.
- AddAndConnectSpot() now connects only to 8 cardinal+diagonal grid
neighbors instead of all spots within MaxStepLength radius, preventing
long-distance shortcut edges.
- CreateSpotsAroundSpot() uses grid offsets instead of radial angle sweep,
producing uniform coverage with no overlap or gaps.
- Line-of-sight checks now filter by TriangleType (Object|Model only),
preventing terrain slopes from falsely blocking neighbor connections
while still blocking paths through walls and tree trunks.
- Spots standing on Object geometry (bridges, ramps) skip the LoS block
check to avoid the walkable surface's own triangles blocking the ray.
- Ceiling raycast (head->sky at CeilingCheckHeight=20) distinguishes
indoor corridors (FLAG_INDOORS) from open bridges (FLAG_ON_OBJECT only).
- All A* scoring methods (ScoreSpot_A_Star, ScoreSpot_A_Star_With_Model_
And_Gradient_Avoidance, ScoreSpot_Pather) updated to use SpotManager
for distance calculations, flag checks, and search state management.
- CreatePath() now wraps Search() in try/finally to guarantee
SpotManager.CompleteSearch() returns the SearchBuffer to the pool.
- Search start/end spots are now integrated into the grid by calling
CreateSpotsAroundSpot() at path creation time.
=== Wall and edge avoidance ===
- NEW: ApplyWallRepulsion() post-processes path waypoints (2 passes):
- For ground-level waypoints near Model|Object walls: computes XY
repulsion direction from closest wall triangle (filtering floors/
ceilings via normal.Z > 0.7 threshold), pushes waypoint away by
WallAvoidanceDistance (3.0 yards) minus actual wall distance.
- For waypoints on Object surfaces (bridges): probes 8 directions for
drop-off edges, pushes toward center of walkable surface.
- Validates all pushed positions are standable before applying.
- Only applied when SearchStrategy is A_Star_With_Model_Avoidance.
- CHANGED: IsCloseToObjectRange increased from MinStepLength to
MaxStepLength for broader object proximity detection.
- CHANGED Core/GoalsComponent/Navigation.cs: minAngleToStopBeforeTurn
reduced from PI/2 (90 degrees) to PI/3 (60 degrees) for smoother
turning behavior.
=== Triangle geometry improvements ===
- CHANGED PPather/Triangles/ChunkedTriangleCollection.cs:
- NEW: GradiantScoreTiered() -- single-pass tiered gradient scoring at
ranges 1/2/3 instead of 3 separate GradiantScore() calls.
- NEW: ClosestDistanceToType() -- returns continuous float distance to
nearest triangle of given type (replaces boolean IsCloseToType for
closeness scoring, enabling smooth cost gradients).
- NEW: CombinedScoring() -- fused closeness+gradient scoring in one
triangle query pass, eliminating duplicate GetChunkAt/GetTriangleMatrix/
GetAllCloseTo lookups. Filters wall-like triangles (|normal.Z| <= 0.7)
for closeness, ignoring floors and ceilings.
- NEW: LineOfSightExists(a, b, blockingMask) overload -- LoS check
considering only triangles matching a TriangleType mask.
- NEW: TryGetWallRepulsion() -- finds closest wall triangle within range
and returns XY repulsion direction + distance for path smoothing.
- FIX: IsCloseToType() now checks maxZ (not minZ) against toon height,
fixing false negatives for triangles partially above the toon.
- NEW PPather/Triangles/MCNKHelper.cs: MCNK-aligned coordinate conversion
utilities (ADT->MCNK coord mapping, bounds calculation, AABB intersection
tests for Model/WMO instances). Enables MCNK-granularity chunk loading.
- CHANGED PPather/Triangles/MPQTriangleSupplier.cs: NEW GetMCNKTriangles()
loads only a single MCNK's terrain (145 vertices) plus spatially-culled
models/WMOs, ~97% memory reduction vs full ADT loading.
- NEW PPather/Triangles/Utils.cs: Added ClosestPointOnSegment() and
ClosestPointOnTriangle() for wall repulsion distance calculations.
- CHANGED PPather/Triangles/TriangleMatrix.cs: Replaced ArrayPool<int>
rentals with ThreadStatic reusable buffer (t_elementBuffer) to eliminate
per-query pool overhead in hot triangle lookup paths.
=== GraphChunk alignment ===
- CHANGED PPather/Graph/GraphChunk.cs:
- CHUNK_SIZE reduced from 256 to 34 (MCNK-aligned: 33.33 yards),
reducing per-chunk spot array from 65,536 to 1,156 entries (56x smaller).
- Constructor accepts optional SpotManager for eager spot registration.
- Load/Save updated to use SpotManager for path storage instead of
per-Spot float[] paths.
=== Visualization enhancements ===
- CHANGED PathingAPI/Pages/Watch.razor:
- Replaced binary Show3D toggle with 5-mode geometry cycle:
Off -> SparseFromSpots -> WalkableOnly -> ExploredArea -> All
- Each mode has distinct color indicator (cyan/yellow/orange/green).
- Spots are filtered to chunk bounds before mesh generation.
- NEW PPather/Search/MeshFactory.cs: Three sparse mesh creation variants:
- CreateSparseFromSpots: Only triangles intersecting discovered spots.
- CreateWalkableOnly: Pre-filtered walkable vertices near explored spots.
- CreateExploredArea: All triangles within explored bounding box.
- CHANGED PPather/Search/PPatherService.cs: Exposed TriangleWorld property
and GetSpots() method for visualization access.
=== Benchmarks ===
- NEW Benchmarks/PPather/PPather_PathGraph_Performance.cs: BenchmarkDotNet
suite measuring SpotManager operations (location lookups, distance calcs,
flag ops, path reconstruction, A* scoring loop, SearchBuffer alloc/reset).
=== Performance progression (30-route PathingAPI benchmark suite) ===
Before refactor: 297ms avg, 171ms median, 17.86s total
After initial SoA: 357ms avg, 142ms median, 21.45s total (regression)
Manager index opt: 199ms avg, 10ms median, 11.97s total (breakthrough)
Final stabilized: 243ms avg, 74ms median, 14.65s total
Third warm run: 201ms avg, 62ms median, 12.05s total
Median improved 64% (171ms -> 62ms). Key route improvements:
- "Coldridge->gnome": 950ms -> 187ms (80% faster)
- "Redridge Grave->North": 570ms -> 248ms (56% faster)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TLDR:
Introduce Structure-of-Arrays (SoA) data layout for pathfinding graph nodes via a new SpotManager, replacing the previous object-scattered Spot storage. Add grid-aligned spot placement, wall/edge repulsion post-processing, and improved triangle scoring -- all validated by iterative benchmarking.
=== Architecture: SpotManager (SoA data layout) ===
NEW PPather/Graph/SpotManager.cs: Central manager storing all spot data in contiguous parallel arrays (locations, flags, path edges, chunks) indexed by a cached Spot.ManagerIndex for O(1) access.
NEW PPather/Graph/SearchBuffer.cs: Pooled per-search state buffer (traceBack indices, G/F scores, closed/scoreSet BitArrays) allocated from a ConcurrentStack pool and returned after each search. Replaces search state fields that were previously scattered across individual Spot objects (searchID, traceBack, score, closed, scoreSet). ~180 KB per search, reused across searches via pool.
CHANGED PPather/Graph/Spot.cs: Stripped down to a lightweight identifier. Removed ~170 lines of path management, search state, and scoring methods. All data now lives in SpotManager arrays. Only Loc, flags, chunk, next (for spatial hash chaining), and ManagerIndex remain. Added FLAG_ON_OBJECT (0x0040) to distinguish bridge/ramp surfaces from indoor corridors.
=== Pathfinding: Grid-based connectivity ===
CHANGED PPather/Graph/PathGraph.cs:
All A* scoring methods (ScoreSpot_A_Star, ScoreSpot_A_Star_With_Model_ And_Gradient_Avoidance, ScoreSpot_Pather) updated to use SpotManager for distance calculations, flag checks, and search state management.
CreatePath() now wraps Search() in try/finally to guarantee SpotManager.CompleteSearch() returns the SearchBuffer to the pool.
Search start/end spots are now integrated into the grid by calling CreateSpotsAroundSpot() at path creation time.
=== Wall and edge avoidance ===
NEW: ApplyWallRepulsion() post-processes path waypoints (2 passes):
CHANGED: IsCloseToObjectRange increased from MinStepLength to MaxStepLength for broader object proximity detection.
CHANGED Core/GoalsComponent/Navigation.cs: minAngleToStopBeforeTurn reduced from PI/2 (90 degrees) to PI/3 (60 degrees) for smoother turning behavior.
=== Triangle geometry improvements ===
CHANGED PPather/Triangles/ChunkedTriangleCollection.cs:
NEW PPather/Triangles/MCNKHelper.cs: MCNK-aligned coordinate conversion utilities (ADT->MCNK coord mapping, bounds calculation, AABB intersection tests for Model/WMO instances). Enables MCNK-granularity chunk loading.
CHANGED PPather/Triangles/MPQTriangleSupplier.cs: NEW GetMCNKTriangles() loads only a single MCNK's terrain (145 vertices) plus spatially-culled models/WMOs, ~97% memory reduction vs full ADT loading.
NEW PPather/Triangles/Utils.cs: Added ClosestPointOnSegment() and ClosestPointOnTriangle() for wall repulsion distance calculations.
CHANGED PPather/Triangles/TriangleMatrix.cs: Replaced ArrayPool
rentals with ThreadStatic reusable buffer (t_elementBuffer) to eliminate per-query pool overhead in hot triangle lookup paths.
=== GraphChunk alignment ===
=== Visualization enhancements ===
CHANGED PathingAPI/Pages/Watch.razor:
NEW PPather/Search/MeshFactory.cs: Three sparse mesh creation variants:
CHANGED PPather/Search/PPatherService.cs: Exposed TriangleWorld property and GetSpots() method for visualization access.
=== Benchmarks ===
=== Performance progression (30-route PathingAPI benchmark suite) ===
Before refactor: 297ms avg, 171ms median, 17.86s total
After initial SoA: 357ms avg, 142ms median, 21.45s total (regression)
Manager index opt: 199ms avg, 10ms median, 11.97s total (breakthrough)
Final stabilized: 243ms avg, 74ms median, 14.65s total
Third warm run: 201ms avg, 62ms median, 12.05s total
Median improved 64% (171ms -> 62ms). Key route improvements:
Few issues which had been addressed
Before: Too close to corridor walls often hugging them

After

Before

After

After

Before: Unable to step on ramps during the terran type transition

After

Before: Crossing bridges dangerously close to the edge

After
