A core requirement of any aimbot is determining whether an enemy is visible or obstructed by world geometry. To answer that question reliably, the game’s ray tracing system must be used.

What is ray tracing?

Ray tracing is a technique used by game engines to determine whether a line intersects with geometry or entities in the world. A ray is cast from a starting position in a specified direction, and the engine reports the first object it intersects.

Although commonly associated with graphics rendering, Source 2 also uses ray tracing extensively for gameplay systems, including:

  • Bullet collision
  • Visibility checks
  • Collision detection
  • Object interaction

For example, whenever a weapon is fired, the engine performs a trace to determine whether the projectile hits world geometry or another player.

Locating trace_shape in Source 2

Finding the function responsible for ray tracing is straightforward.

Trace Shape Function

Searching for the string "Physics/TraceShape (Client)" immediately leads to the function responsible for client-side tracing.

By placing a breakpoint on this function, the unknown arguments (a2, a5, and a6) can be inspected using tools such as ReClass or Cheat Engine, making it possible to reconstruct the underlying data structures.

Reconstructing the arguments

class c_ray
{
public:
    vec3_t start;
    vec3_t end;
    vec3_t mins;
    vec3_t maxs;
    std::uint32_t N000002C7;
    std::uint8_t N000002BE;
};
static_assert(sizeof(c_ray) == 0x38);

The second argument is a c_ray structure.

It describes the ray being traced, including its start and end positions, along with optional minimum and maximum bounds used for swept traces.

The final two members return values that could not be associated with any meaningful behavior at the time of writing.

Returning to the caller reveals that trace_shape receives an instance of a virtual class named CTraceFilter.

Trace Filter

Further inspection shows that numerous classes derive from CTraceFilter, suggesting that the class controls how a trace should behave.

The base class contains only a destructor and a virtual function that returns a boolean based on the filter’s configuration.

None of the existing implementations matched the desired behavior: ignore the local player, continue tracing until an enemy is encountered, and perform normal collision testing. As a result, a custom implementation was required.

std::uint32_t get_handle_from_entity(entity_t* entity)
{
    using function_t = std::uint32_t(__fastcall*)(entity_t*);
    static function_t fn = reinterpret_cast<function_t>(utilities::pattern_scan(L"client.dll", "48 85 C9 74 ? 48 8B 41 ? 48 85 C0 74 ? 44 8B 40 ? BA ? ? ? ? 8B 48 ? 41 8B C0 83 E1"));

    return fn(entity);
}

class c_trace_filter
{
public:
    std::uint64_t trace_mask;
    std::uint64_t null_it1;
    std::uint64_t null_it2;
    std::uint32_t skip_handle1;
    std::uint32_t skip_handle2;
    std::uint32_t skip_handle3;
    std::uint32_t skip_handle4;
    std::uint16_t collision1;
    std::uint16_t collision2;
    std::uint16_t N0000011C;
    std::uint8_t layer;
    std::uint8_t N00000104;
    std::uint8_t null_it3;

    virtual ~c_trace_filter()
    {
    }

    virtual bool function()
    {
        return true;
    }

    c_trace_filter(std::uint64_t trace_mask, player_t* local_player, std::uint8_t layer)
    {
        this->trace_mask = trace_mask;

        this->null_it1 = 0;
        this->null_it2 = 0;

        this->skip_handle1 = get_handle_from_entity(local_player);
        this->skip_handle2 = 0;
        this->skip_handle3 = 0;
        this->skip_handle4 = 0;

        this->collision1 = local_player->collision_property()->get_collision_mask();
        this->collision2 = 0;

        this->N0000011C = 7;
        this->layer = layer;
        this->N00000104 = 0x49;
        this->null_it3 = 0;
    }
};
static_assert(sizeof(c_trace_filter) == 0x40);

c_trace_filter primarily consists of:

  • a trace mask defining what types of objects should be considered,
  • a tracing layer,
  • four entity handles that can be ignored,
  • two collision masks that can also be skipped.

For visibility checks, both the local player and its collision object must be excluded from the trace.

The remaining fields, N0000011C and N00000104, consistently appear as 7 and 0x49 respectively, although their exact purpose remains unknown.

The collision mask can be obtained from CCollisionProperty at offset 0x38, accessible through C_BaseEntity::m_pCollision.

class c_game_trace
{
public:
    void* surface;
    entity_t* entity;
    void* hitbox;
    char pad_0028[148];
    float fraction;
    char pad_0176[9];
};
static_assert(sizeof(c_game_trace) == 0xC0);

The final argument receives the trace result.

Among other information, it contains:

  • the surface that was hit,
  • the entity that was intersected,
  • the hitbox,
  • the trace fraction.

If entity is nullptr, or fraction is less than or equal to 0.97f, the trace was blocked by world geometry, indicating that the target is not directly visible.

The trace interface

class i_trace
{
public:
    bool trace_shape(c_ray* ray, vec3_t& start, vec3_t& end, c_trace_filter* filter, c_game_trace* trace)
    {
        using function_t = bool(__fastcall*)(i_trace*, c_ray*, vec3_t*, vec3_t*, c_trace_filter* c_game_trace*);
		
		static function_t fn = reinterpret_cast<function_t>(utilities::pattern_scan(L"client.dll", "48 89 54 24 ? 48 89 4C 24 ? 55 53 56 57 41 56 41 57"));

        return fn(this, ray, &start, &end, filter, trace);
    }
};

i_trace* trace = *reinterpret_cast<i_trace**>(utilities::resolve_rip(utilities::pattern_scan(L"client.dll", "48 8B 1D ? ? ? ? 24 ? 0F 11 45"), 3, 7));

The function follows the typical Source engine interface pattern.

trace_shape is exposed through an interface whose instance is initialized from the global GameTraceManager pointer.

Putting everything together

bool can_see_player_position(player_t* player, const vec3_t& position)
{
    c_trace_filter filter(0x1C300B, local_player, 3);
    c_ray ray = {};
    c_game_trace trace = {};

    vec3_t eye_position = local_player->get_eye_position();

    trace->trace_shape(&ray, eye_position, position, &filter, &trace);

    return trace.entity == player || trace.fraction > 0.97f;
}

The trace mask and layer values can be recovered by inspecting the various CTraceFilter implementations used throughout the engine.

player should reference the target entity, while position should correspond to one of the target’s bone positions.

If the trace reaches the target entity, or the trace fraction exceeds 0.97f it’s position can be considered visible.