- Status
- Offline
- Joined
- Mar 3, 2026
- Messages
- 720
- Reaction score
- 457
Anyone digging into the CS2 internals has likely hit this wall: one match your ESP is crisp and covers the whole lobby, the next it’s flickering or ignoring half the players. If you're running a standard internal/external and your offsets are supposedly green, the issue usually boils down to how you're traversing the entity list or handling the player pawn relationship.
Got my hands on a common loop implementation that's been causing headaches. The logic seems fine at a glance, but the way Source 2 handles the controller-to-pawn link is prone to desync if your masks or list entry offsets are even slightly off.
The Entity Loop Logic
Technical Troubleshooting
— Handle Masking: In CS2, the entity list is fragmented. The shift >> 9 and the bitwise & 0x1FF are critical. If your listEntry2 calculation uses a different multiplier (like 0x70 instead of 0x8 for the first stage), you'll pull garbage memory half the time.
— Dormancy Check: Valve's networking in Premiere and subtick matches often marks players as dormant (m_bDormant) much more aggressively than in CS:GO. If your ESP disappears when a player is behind a wall, your dormancy logic might be too strict or the server is simply not sending the data (PVS/Far-ESP issues).
— Pawn vs Controller: Always ensure you are pulling the health and origin from the Pawn, not the Controller. The code above correctly pulls the m_hPlayerPawn handle, but if that handle becomes stale (e.g., during a round transition), your loop will break.
Anyone else noticed the entity list shifting during long sessions or after the latest subtick updates?
Got my hands on a common loop implementation that's been causing headaches. The logic seems fine at a glance, but the way Source 2 handles the controller-to-pawn link is prone to desync if your masks or list entry offsets are even slightly off.
The Entity Loop Logic
Code:
void EntitySystem::UpdateEntities()
{
uintptr_t entityListOffset = offsets.getEntityList();
if (!entityListOffset) return;
uintptr_t localPawn = offsets.getLocalPawn();
if (!localPawn) return;
myTeamID = *reinterpret_cast<int*>(localPawn + C_BaseEntity::m_iTeamNum);
{
std::lock_guard<std::mutex> lock(entityMutex);
Vec3 myOrigin = offsets.getOrigin();
for (int playerIndex = 0; playerIndex < 64; ++playerIndex)
{
Entity& entity = playerEntities[playerIndex];
uintptr_t listEntry = *reinterpret_cast<uintptr_t*>(entityListOffset + (0x8 * (playerIndex & 0x7FFF) >> 9) + 16);
if (!listEntry) { entity.isAlive = false; continue; }
uintptr_t currentController = *reinterpret_cast<uintptr_t*>(listEntry + 0x70 * (playerIndex & 0x1FF));
if (!currentController) { entity.isAlive = false; continue; }
uint32_t pawnHandle = *reinterpret_cast<uint32_t*>(currentController + CCSPlayerController::m_hPlayerPawn);
if (!pawnHandle || pawnHandle == 0xFFFFFFFF) { entity.isAlive = false; continue; }
uintptr_t listEntry2 = *reinterpret_cast<uintptr_t*>(entityListOffset + 0x8 * ((pawnHandle & 0x7FFF) >> 9) + 16);
if (!listEntry2) { entity.isAlive = false; continue; }
uintptr_t currentPawn = *reinterpret_cast<uintptr_t*>(listEntry2 + 0x70 * (pawnHandle & 0x1FF));
if (!currentPawn) { entity.isAlive = false; continue; }
const bool isDormant = *reinterpret_cast<bool*>(currentPawn + CGameSceneNode::m_bDormant);
if (isDormant) { entity.isAlive = false; continue; }
const int health = *reinterpret_cast<int*>(currentPawn + C_BaseEntity::m_iHealth);
if (health < 1) { entity.isAlive = false; continue; }
entity.health = health;
if (currentPawn == localPawn) { entity.isAlive = false; continue; }
entity.isAlive = true;
Vec3 origin = *reinterpret_cast<Vec3*>(currentPawn + C_BasePlayerPawn::m_vOldOrigin);
entity.origin = origin;
entity.headPosition = offsets.BonePos(currentPawn, BoneIndices::HEAD);
const int pawnTeam = *reinterpret_cast<int*>(currentPawn + C_BaseEntity::m_iTeamNum);
entity.team = pawnTeam;
entity.isFriendly = (pawnTeam == myTeamID);
entity.distanceFromMe = origin.Dist(myOrigin);
}
}
}
Technical Troubleshooting
— Handle Masking: In CS2, the entity list is fragmented. The shift >> 9 and the bitwise & 0x1FF are critical. If your listEntry2 calculation uses a different multiplier (like 0x70 instead of 0x8 for the first stage), you'll pull garbage memory half the time.
— Dormancy Check: Valve's networking in Premiere and subtick matches often marks players as dormant (m_bDormant) much more aggressively than in CS:GO. If your ESP disappears when a player is behind a wall, your dormancy logic might be too strict or the server is simply not sending the data (PVS/Far-ESP issues).
— Pawn vs Controller: Always ensure you are pulling the health and origin from the Pawn, not the Controller. The code above correctly pulls the m_hPlayerPawn handle, but if that handle becomes stale (e.g., during a round transition), your loop will break.
Check m_iHealth and m_vOldOrigin offsets first. If those are outdated, your vectors will be zeroed out. Also, ensure you're using the correct CGameSceneNode for bone positions; getting bone data directly from the pawn without traversing the scene node can cause jitter.
Anyone else noticed the entity list shifting during long sessions or after the latest subtick updates?