Skip to content

GridTracer and Coverage

DESKTOP-5KM6RA3\david edited this page Apr 7, 2026 · 5 revisions

GridTracer and Coverage

GridTracer is the utility layer that translates world-space shapes into grid-space coverage. It is one of the most reusable parts of the architecture because several higher-level systems depend on the same answer:

"Which cells does this world-space region touch?"

That question shows up in:

  • line tracing
  • region coverage
  • blocker application
  • scan-region enumeration

The Core Outputs

GridTracer currently exposes four main entry points:

API Output Best Used For
TraceLine(Vector3d, Vector3d, ...) IEnumerable<GridVoxelSet> Voxelized line traversal in 3D
TraceLine(Vector2d, Vector2d, ...) IEnumerable<GridVoxelSet> Voxelized line traversal in 2D projected onto Y=0
GetCoveredVoxels(...) IEnumerable<GridVoxelSet> Bounds-based voxel coverage across one or more grids
GetCoveredScanCells(...) IEnumerable<ScanCell> Bounds-based scan-cell coverage for occupant queries

Three of those return GridVoxelSet, which groups results by grid instead of flattening them immediately.

Why Coverage Is Grouped By Grid

Coverage often crosses more than one grid. Returning grouped results preserves that architectural reality.

GridVoxelSet keeps:

  • the VoxelGrid that owns the covered cells
  • the list of covered voxels for that grid

That makes it easy for callers to:

  • apply per-grid mutations
  • reason about cross-grid boundaries
  • avoid immediately losing the owning-grid context

The Common Coverage Pipeline

Even though the APIs look different, they follow a similar structure:

world-space input
  -> snap to voxel-aligned bounds or points
  -> find candidate grids through GlobalGridManager
  -> enumerate covered voxels or scan cells
  -> group or yield results

The manager provides candidate grid discovery. The tracer provides coverage enumeration inside those candidates.

Line Tracing

The line tracer is used when a continuous path in world space needs to become a discrete voxel path.

At a high level it:

  1. snaps the start and end
  2. computes the dominant-axis step count
  3. iterates intermediate trace positions
  4. resolves those positions to voxels
  5. groups results by grid
  6. optionally appends the end voxel explicitly

This makes it useful for:

  • ray-like traversal
  • line-of-sight style checks
  • path visualization
  • editor and debug tooling

Bounds Coverage

GetCoveredVoxels(...) answers a slightly different question:

"Which voxels fall inside this snapped bounding region?"

The method:

  • snaps min and max to voxel alignment
  • finds candidate grids through the spatial hash
  • iterates the voxel-aligned positions inside the covered range
  • resolves valid voxels in each grid
  • deduplicates results

This is the main coverage primitive behind blockers and other region-based bulk operations.

Scan-Cell Coverage

GetCoveredScanCells(...) is the scan-oriented sibling of voxel coverage.

Instead of yielding every touched voxel, it:

  • snaps the bounds
  • finds candidate grids
  • converts covered bounds into scan-cell coordinate ranges per grid
  • resolves scan-cell keys
  • yields unique scan cells across the relevant grids

This is the architectural bridge between world-space regions and GridScanManager's occupant queries.

How Blockers Use Coverage

Blocker and BoundsBlocker do not perform their own region-to-voxel logic. They delegate to the tracer.

The high-level blocker flow is:

blocker bounds
  -> GridTracer.GetCoveredVoxels(...)
  -> per-grid covered voxel sets
  -> TryAddObstacle / TryRemoveObstacle on each covered voxel

That reuse is important because it keeps coverage rules in one place instead of scattering region math across multiple mutation systems.

Multi-Grid Behavior

Coverage APIs are built with multi-grid worlds in mind.

That means:

  • one traced region can touch multiple registered grids
  • grouped results preserve which grid each voxel came from
  • cross-grid blockers and diagnostics work without special-case code in the caller

This is one of the main reasons the tracer belongs in the architecture as a shared utility rather than a helper local to blockers.

Snapping And Padding

Tracing APIs operate on voxel-aligned space, not arbitrary floating positions.

Important ideas:

  • incoming bounds or endpoints are snapped to voxel alignment
  • coverage is ultimately evaluated in discrete cell steps
  • optional padding can expand the region before snapping

If a result seems larger or smaller than expected, the first debugging step is usually to inspect the snapped bounds rather than the original inputs.

Result Lifetime And Pooling

This is one of the most important practical details on the page:

  • GridVoxelSet.Voxels is backed by pooled storage
  • the tracer releases those pooled lists after yielding each grouped result

That means callers should treat grouped voxel lists as transient and consume them immediately inside the enumeration.

Good pattern:

foreach (var covered in GridTracer.GetCoveredVoxels(min, max))
{
    foreach (Voxel voxel in covered.Voxels)
    {
        // use immediately
    }
}

Risky pattern:

  • storing covered.Voxels for later use after enumeration completes

Choosing The Right Coverage API

Use:

  • TraceLine(...) when the shape is fundamentally a line
  • GetCoveredVoxels(...) when you need direct per-voxel processing
  • GetCoveredScanCells(...) when the goal is occupant scanning instead of cell mutation

If your logic begins to look like "iterate every voxel in this region and then look up scan cells," you are often starting from the wrong tracer API.

Common Mistakes

  • Forgetting that traced results may span multiple grids
  • Holding onto pooled voxel lists beyond immediate consumption
  • Expecting unsnapped floating inputs to map one-to-one to final covered voxels
  • Using voxel coverage when scan-cell coverage would be cheaper for occupant queries

Read This Next

Clone this wiki locally