> ## Documentation Index
> Fetch the complete documentation index at: https://docs.spurdoverse.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Trace

> Rays, sweeps, overlaps and line of sight against the map's physics world.

Collision queries against the map's physics world. Positions take any `Vec3Like`.

```ts theme={null}
import trace from "@native/trace";
```

<Note>
  Without a map loaded every query returns `null`, the `boolean` ones too. Check for `null` before `.hit`.
</Note>

## Overview

<CardGroup cols={2}>
  <Card title="Filters" icon="filter" href="#filters">
    Presets, `filter`, `ignore`, `clearIgnore`.
  </Card>

  <Card title="Rays and sweeps" icon="arrow-right" href="#rays-and-sweeps">
    `ray`, `rayAll`, `box`, `sphere`, `capsule`.
  </Card>

  <Card title="Point queries" icon="location-dot" href="#point-queries">
    `isVisible`, `isSolid`, `overlapSphere`, `closestPoint`.
  </Card>

  <Card title="Results" icon="bullseye" href="#results">
    `TraceResult`, `OverlapResult`, `ClosestPointResult`.
  </Card>
</CardGroup>

***

## Filters

Every query takes a filter last: a preset name, a `trace.filter()` object, or inline options. Omitted means `"bullet"`.

| Preset | Collides with |
| :- | :- |
| `"bullet"` | what bullets hit, back faces included |
| `"visibility"` | skips conditionally-solid geometry, no material or entity info |
| `"everything"` | every shape, back faces included |
| `"grenade"` | what grenades hit |
| `"player_movement"` | what player movement hits |
| `"occlusion"` | bullet collision without normal, material or entity info (cheapest) |

### filter

<br />

```ts theme={null}
trace.filter(presetOrOptions?: TracePreset | TraceFilterOptions): TraceFilter
```

A reusable filter. Build it once at the top level and pass it every tick.

```ts theme={null}
const shots = trace.filter({ type: "bullet", players: true });
const hit = trace.ray(eye, end, shots);
```

Options you leave out keep the preset's value.

| Option | Default | |
| :- | :- | :- |
| `type` | `"bullet"` | base preset |
| `static`, `dynamic` | `true` | world geometry, dynamic bodies (doors, props) |
| `players` | `false` | player hitboxes; honoured by `ray` and `rayAll` only |
| `resolveNormals` | `true` | `false` in `"occlusion"` |
| `resolveMaterials`, `resolveEntity` | `true` | `false` in `"visibility"` and `"occlusion"` |
| `backFaces` | preset | `true` in `"bullet"` and `"everything"` |
| `includeTags` | | only default-group shapes and shapes with one of these tags |
| `excludeTags` | | never shapes with one of these tags |
| `ignore` | | `EntityRef` or `EntityRef[]`, at most 8 |

Tags: `pass_bullets`, `player_clip`, `npc_clip`, `grenade_clip`, `ladder`, `window`, `sky`, `player`, `thrown_grenade`, `solid`, `block_los`, `block_light`, `block_sound`.

### ignore

<br />

```ts theme={null}
filter.ignore(entities: EntityRef | EntityRef[]): TraceFilter
filter.clearIgnore(): TraceFilter
```

Queries pass through these entities, at most 8 per filter. Both return the filter, so they chain.

```ts theme={null}
const shots = trace.filter({ type: "bullet", players: true });

on("tick", () => {
    const me = entities.getLocalPlayer();
    if (me) shots.clearIgnore().ignore(me);
});
```

***

## Rays and sweeps

### ray

<br />

```ts theme={null}
trace.ray(start: Vec3Like, end: Vec3Like, filter?: TraceFilterArg): TraceResult | null
```

Casts one ray and returns the first hit, see [TraceResult](#results). `null` without a map.

```ts theme={null}
const result = trace.ray(eye, eye.add(view.forward().scale(8192)), "bullet");
if (result?.hit) console.log("hit", result.surface?.name, "at", result.position);
```

### rayAll

<br />

```ts theme={null}
trace.rayAll(start: Vec3Like, end: Vec3Like, filter?: TraceFilterArg, maxHits?: number): TraceResult[] | null
```

Every hit along the ray, up to `maxHits` (default 32, max 128). `null` without a map.

```ts theme={null}
const hits = trace.rayAll(eye, end, { type: "bullet", players: true }) ?? [];
const walls = hits.filter((h) => !h.entity).length;
```

### Sweeps

<br />

```ts theme={null}
trace.box(start: Vec3Like, end: Vec3Like, mins: Vec3Like, maxs: Vec3Like, filter?: TraceFilterArg): TraceResult | null
trace.sphere(start: Vec3Like, end: Vec3Like, radius: number, filter?: TraceFilterArg): TraceResult | null
trace.capsule(start: Vec3Like, end: Vec3Like, radius: number, halfHeight: number, filter?: TraceFilterArg): TraceResult | null
```

Moves a shape from `start` to `end` and stops at the first hit. Box `mins`/`maxs` are relative to the moving position.

```ts theme={null}
// would a standing player fit 64 units ahead?
const box = trace.box(origin, origin.add([64, 0, 0]), [-16, -16, 0], [16, 16, 72], "player_movement");
const blocked = box?.hit ?? true;
```

***

## Point queries

### isVisible

<br />

```ts theme={null}
trace.isVisible(from: Vec3Like, to: Vec3Like): boolean | null
```

Line of sight through world geometry. Players don't block it. `null` without a map.

```ts theme={null}
if (trace.isVisible(myEye, enemyEye)) console.log("in sight");
```

Checking one entity from another? [entity.isVisible](/api/entities#isvisible) picks the eye positions for you.

### isSolid

<br />

```ts theme={null}
trace.isSolid(point: Vec3Like): boolean | null
```

Whether `point` is inside solid geometry, using the `"everything"` preset. `null` without a map.

```ts theme={null}
const stuck = trace.isSolid(origin.add([0, 0, 36]));
```

### overlapSphere

<br />

```ts theme={null}
trace.overlapSphere(origin: Vec3Like, radius: number, filter?: TraceFilterArg): OverlapResult[] | null
```

Every collider touching the sphere. `null` without a map.

```ts theme={null}
const near = trace.overlapSphere(origin, 128, { type: "everything" }) ?? [];
for (const o of near) console.log(o.entity?.className ?? "world", o.depth);
```

### closestPoint

<br />

```ts theme={null}
trace.closestPoint(origin: Vec3Like, maxDistance: number, filter?: TraceFilterArg): ClosestPointResult | null
```

The nearest surface point within `maxDistance`, or `null` when there's none (or no map).

```ts theme={null}
const wall = trace.closestPoint(origin, 64);
if (wall) console.log(wall.distance, wall.normal);
```

<Warning>
  Of the filter only `static` and `dynamic` apply here. Preset rules, tags and `ignore` are not honoured.
</Warning>

***

## Results

| `TraceResult` | |
| :- | :- |
| `hit` | something was hit |
| `fraction` | 0–1, how far along the path (`1` = no hit) |
| `position`, `normal` | where it stopped, surface normal |
| `startSolid`, `allSolid` | started inside / stayed inside solid |
| `hitSky`, `hitWindow`, `hitLadder`, `hitPassBullets` | surface flags |
| `entity` | the hit entity while it exists; `null` for world, with `resolveEntity` off, or once it's gone |
| `entityIndex` | its index, still set after `entity` is gone; `null` for world |
| `hitGroup` | player hitbox group with `players: true`, else `null` |
| `surface` | `{ name, penetration, damage }`, `null` on no hit or with `resolveMaterials` off |

`OverlapResult` has `position`, `normal`, `depth` (penetration), `entity` and `entityIndex`. `ClosestPointResult` has `position`, `normal`, `distance`, `entity` and `entityIndex`.

***

## Example

Shows what's under your crosshair.

```ts theme={null}
import entities from "@native/entities";
import render from "@native/render";
import trace from "@native/trace";

const shots = trace.filter({ type: "bullet", players: true });
let label = "";

on("tick", () => {
    label = "";
    const me = entities.getLocalPlayer();
    if (me?.className !== "C_CSPlayerPawn") return;
    const eye = me.getEyePosition();
    const view = me.getViewAngles();
    if (!eye || !view) return;

    const hit = trace.ray(eye, eye.add(view.forward().scale(8192)), shots.clearIgnore().ignore(me));
    if (!hit?.hit) return;
    label = hit.entity?.className ?? hit.surface?.name ?? "world";
    if (hit.hitGroup !== null) label += ` (group ${hit.hitGroup})`;
});

on("render", () => {
    if (!label) return;
    const { x, y } = render.screenSize;
    render.text([x / 2 + 12, y / 2 + 12], label, "#ffffff", { outline: true });
});
```
