# Raycast API

The raycast API gives scripts access to the cheat's built-in visibility system. A background worker thread maintains a spatial hash of world obstacles and per-player visibility results — the Lua API just reads from that cache with no render-thread cost.

> All examples use `snake_case`. Every function is also accessible in `camelCase` and `PascalCase` — the engine accepts all three.

***

### How it works

The raycast system runs on a dedicated worker thread:

* **Obstacles** are rebuilt from the workspace instance tree every \~2 seconds into a spatial hash grid (50 unit cells).
* **Player visibility** is rechecked every \~16ms against those obstacles, testing head, upper torso, lower torso, and torso for each enemy player.
* Lua reads the already-computed results — there is no per-frame cost beyond a hash lookup.

`raycast.is_ready()` returns false while the first obstacle pass is still running. All functions fail open (return `true`) if the cache is not ready, so your ESP will still draw during startup.

***

### raycast.is\_ready

```lua
local ready = raycast.is_ready()
```

Returns `true` once the obstacle cache has completed its first build. Use this as a guard if you want to suppress ESP until visibility data is available.

```lua
function on_frame()
    if not raycast.is_ready() then
        draw.text(10, 10, "building raycast cache...", {1, 1, 0, 1})
        return
    end
    -- normal ESP below
end
```

***

### raycast.is\_player\_visible

```lua
local visible = raycast.is_player_visible(character_address)
```

Returns `true` if any key body part of the player is visible from the camera position.

Reads directly from the double-buffered visibility cache — no memory reads, no ray tests. The result is updated every \~16ms by the worker thread.

**Parameter:** the character's memory address as a number. Obtain it from `p.character.address` on a player object from `entity.get_players()`.

Returns `true` if the cache is not yet ready (fail open).

```lua
function on_frame()
    local players = entity.get_players()

    for _, p in ipairs(players) do
        if p.is_local or not p.is_alive then goto continue end

        local b = p:get_bounds()
        if not b.valid then goto continue end

        local char = p.character
        if not utility.is_valid(char) then goto continue end

        local visible = raycast.is_player_visible(char.address)

        -- white box when visible, dark grey when occluded
        local color = visible and {1, 1, 1, 1} or {0.4, 0.4, 0.4, 0.6}
        draw.box(b.x, b.y, b.w, b.h, color)

        ::continue::
    end
end
```

***

### raycast.is\_visible

```lua
-- Six numbers
local visible = raycast.is_visible(x1, y1, z1, x2, y2, z2)

-- Two Vector3 objects
local visible = raycast.is_visible(vec3_from, vec3_to)
```

Fires a ray between two world positions and returns `true` if no obstacle intersects it.

Uses the cached spatial hash to limit OBB tests to only the cells along the ray — typically a small fraction of the total obstacle count. More expensive than `is_player_visible` since it runs the test live, but still much cheaper than a full scene traverse.

Returns `true` if the cache is not yet ready.

```lua
function on_frame()
    local cam_pos = camera.get_position()
    local players = entity.get_players()

    for _, p in ipairs(players) do
        if p.is_local or not p.is_alive then goto continue end

        local head_pos = p.head_position
        if not head_pos then goto continue end

        local visible = raycast.is_visible(
            cam_pos.x, cam_pos.y, cam_pos.z,
            head_pos.x, head_pos.y, head_pos.z
        )

        local hx, hy, hv = p:get_bone_screen("Head")
        if hv then
            local color = visible and {0, 1, 0, 1} or {1, 0, 0, 1}
            draw.circle_filled(hx, hy, 5, color)
        end

        ::continue::
    end
end
```

**Vector3 form:**

```lua
local from = camera.get_position()
local to   = p.head_position
if from and to then
    local visible = raycast.is_visible(from, to)
end
```

***

### Examples

**Visible-only ESP — skip drawing occluded players entirely:**

```lua
function on_frame()
    if not raycast.is_ready() then return end

    local players = entity.get_players()

    for _, p in ipairs(players) do
        if p.is_local or not p.is_alive then goto continue end

        local char = p.character
        if not utility.is_valid(char) then goto continue end

        -- skip players behind walls entirely
        if not raycast.is_player_visible(char.address) then goto continue end

        local b = p:get_bounds()
        if not b.valid then goto continue end

        draw.box(b.x, b.y, b.w, b.h, {0, 1, 0.5, 1})
        draw.health_bar(b.x - 5, b.y, b.h, p.health, p.max_health)

        local tw, th = draw.get_text_size(p.name, 14)
        draw.text(b.x + b.w / 2 - tw / 2, b.y - th - 2, p.name, {1, 1, 1, 1})

        ::continue::
    end
end
```

**Visible/occluded color coding with info window:**

```lua
function on_frame()
    local players = entity.get_players()

    for _, p in ipairs(players) do
        if p.is_local or not p.is_alive then goto continue end

        local char = p.character
        if not utility.is_valid(char) then goto continue end

        local visible = raycast.is_player_visible(char.address)

        local b = p:get_bounds()
        if not b.valid then goto continue end

        local box_color = visible and {0, 1, 0.4, 1} or {0.5, 0.5, 0.5, 0.5}
        draw.box(b.x, b.y, b.w, b.h, box_color)

        if visible then
            draw.window(b.x + b.w + 4, b.y, "vis_" .. p.user_id, p.name, {
                "HP: " .. math.floor(p.health),
                "Visible: yes",
            })
        end

        ::continue::
    end
end
```

**Custom raycast from camera to arbitrary world point:**

```lua
function on_frame()
    local cam = camera.get_position()

    -- check if a fixed world coordinate is visible from the camera
    local target_x, target_y, target_z = 100, 5, 200

    if raycast.is_visible(cam.x, cam.y, cam.z, target_x, target_y, target_z) then
        local sx, sy, on_screen = utility.world_to_screen(target_x, target_y, target_z)
        if on_screen then
            draw.circle_filled(sx, sy, 6, {0, 1, 0, 1})
            draw.text(sx + 8, sy, "visible", {0, 1, 0, 1})
        end
    end
end
```

***

### Performance notes

* `raycast.is_player_visible` is essentially free — it's a single table scan of the visibility buffer which contains at most one entry per player.
* `raycast.is_visible` runs a live ray test but uses the spatial hash, so it only tests the OBBs in cells the ray actually passes through. Calling it once per player per frame is fine. Calling it dozens of times per frame for many players may add up — prefer `is_player_visible` for per-player visibility checks.
* The obstacle cache rebuilds every \~2 seconds. Freshly spawned parts (doors opening, destructible walls) will not be reflected until the next rebuild.
* Transparent parts (transparency >= 1.0) are excluded from the obstacle list — they won't block visibility.

***

### API Reference

| Function                                 | Returns | Description                               |
| ---------------------------------------- | ------- | ----------------------------------------- |
| `raycast.is_ready()`                     | boolean | True once the obstacle cache is built     |
| `raycast.is_player_visible(char_addr)`   | boolean | Cached visibility check for a player      |
| `raycast.is_visible(x1,y1,z1, x2,y2,z2)` | boolean | Live ray test between two world positions |
| `raycast.is_visible(vec3_from, vec3_to)` | boolean | Same, accepts Vector3 objects             |


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://project-vector-1.gitbook.io/vector-lua-engine/api/raycast-api.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
