game
Live data (read-only, writes raise a Lua error). Accessing a field = the latest published snapshot for the current frame.
Update strategy
The field tables carry an Update strategy column — not every field is read from the game each frame. The backend re-paces reads by state, and a skipped frame reuses the previous value. Understanding this column saves the "why does an off-screen enemy's HP update so slowly" / "why are bones zero off-screen" confusion.
- Enemy players / loot / projectiles down-throttle off-screen: off-screen, farther away, and non-alive all read slower. Any field marked "on-screen only" does not refresh while off-screen — you read the last value seen while it was on-screen.
- One exception: the aimbot's currently locked target is exempt from all throttling and is read every frame (so aiming never depends on ESP toggles).
- The local player is never throttled — you're always relevant, so its default is "every frame".
- "Computed each update" = the value is not read from memory directly; it's derived locally each cycle from the freshest other fields, so its freshness follows whatever it depends on.
- "Every frame / every N frames" refers to backend read cycles, not a fixed 60 Hz.
game.localplayer
The local player has no on-screen / distance throttling; default is "every frame".
| Field | Type | Update strategy | Description |
|---|---|---|---|
index | int | Every frame | entity_list index |
base / self_base | uint64 | Every frame | Entity memory base — combine with offsets + mem.read/write to self-service read/write any attribute. The two diverge while dead-spectating: base follows the spectated player, self_base is always the real local machine — use self_base to read "my own" attributes |
health | int | Every frame | Current HP |
max_health | int | Low frequency | Max HP (only changes on armor pickup / gold-knockdown revive) |
shield | int | Every frame | Current shield |
max_shield | int | Low frequency | Max shield (changes on armor swap) |
origin / camera_origin | Vec3 | Every frame | Entity / camera position (origin follows the spectated player while dead) |
view_angle | Vec2 | Every frame | {pitch, yaw} |
sway_angle | Vec2 | Every frame | Bullet firing direction including breath/sway |
view_offset / punch_angle | Vec3 | Every frame, alive & not knocked | View offset / recoil; keeps last value when dead/knocked |
abs_velocity | Vec3 | Every frame | |
flags_raw | uint32 | Every frame | Raw m_fFlags bits (FL_ONGROUND etc.) |
team_num / squad_id | int | Read once on create | |
platform_uid | string | Read once on create | uint64 → string |
weapon_id | int | Every frame, alive & armed | Weapon string-table index; blank when empty-handed |
weapon_enum | int | Resolved on weapon swap | sdk::ItemId value, more reliable than the weapon string |
weapon | string | Resolved on weapon swap | Weapon name shown in ESP |
weapon_speed / weapon_scale | number | Every frame, alive & armed | Fire rate / charge-up |
target_zoom_fov | number | Every frame, alive & armed | ADS fov of the current weapon |
weapon_next_ready_time | number | Every frame, alive & armed | |
weap_state / burst_fire_index / ammo_in_clip | int | Every frame, alive & armed | Internal ammo state |
is_semi_auto | bool | Every frame, alive & armed | |
is_zooming | bool | Every frame, alive & not knocked | |
is_dead / is_down / is_on_ground | bool | Every frame | |
is_grenade / is_hands | bool | Resolved on weapon swap | |
is_skydive | bool | Low frequency (~every 6 frames) | |
time_base | number | Every frame | World time base |
spec_index | int | Every frame | Spectated target (only set while dead-spectating; -1 when alive) |
backpack_tier | int | Low frequency (~1 Hz) | 0=None / 1=White / 2=Blue / 3=Purple / 4=Gold |
consumables | table | Low frequency (~every 15 frames) | 16-slot consumable inventory (each slot {item: uint16, count: uint16}; count==0 = empty) |
game.entities.players (iterable via pairs)
Per-PlayerEntity fields. The update strategy here stacks two layers: the entity itself is read every frame on-screen, throttled off-screen, and a field may add its own gate (bones need on-screen, identity is read once on create). The effective refresh rate is the slower of the two.
| Field | Type | Update strategy | Description |
|---|---|---|---|
index / base / is_npc | int/uint64/bool | Fixed on create | Not read from memory |
name / platform_uid / xp | string/string/int | Read once on create | platform_uid retries a few times in-match if the first read fails |
team_num / squad_id / rank_source | int | Read once on create | |
grade / rank / rank_icon_key / rank_color | int/string | Locked for the match | Computed from score on the first frame, fixed after one cloud-rank resolve |
legend / legend_icon_key | string | Resolved on legend change | Model name sampled ~every 64 frames; resolves ~once per match |
kills / damage | int | Low frequency (~every 64 frames) | Read on- and off-screen, diluted by entity throttling |
weapon_id | int | Short lag on weapon swap | Weapon pointer refreshes ~every 12 frames on-screen; the underlying handle is sampled very infrequently |
weapon | string | Resolved on weapon swap | |
origin | Vec3 | Every frame on-screen, throttled off-screen | Not read after death |
abs_velocity | Vec3 | On-screen & in valid range only | Falls back to position-delta estimation off-screen |
head_pos | Vec3 | On-screen only | Derived from the head bone each update (needs bones resolved) |
yaw | number | <300m ~every 6 frames, slower beyond | Read on- and off-screen |
health / shield / max_shield | int | On-screen only (~every 12 frames) | |
flags | int | On-screen only (~every 3 frames) | m_fFlags state bits |
is_down / cloak_endtime | bool/number | On-screen only (~every 3 frames) | |
is_dead | bool | Low frequency (~every 8 frames) | |
spec_index | int | Dead players only (~every 8 frames) | Whom this player is spectating; -1 for the living |
last_visible_time | number | On-screen & in valid range only | Visibility timestamp |
vec_min / vec_max | Vec3 | On-screen (~every 3 frames), needs dynamic-box/map-ident | Collision AABB; not read if neither is enabled |
is_glow / is_wall | bool | On-screen (~every 12 frames), needs glow enabled | |
roster | uint8 | Roster lookup each update | Roster bitmask (pro/star/cheater/friends); not re-queried unless the roster changes |
distance | number | Computed each update | Meters |
is_visible / is_teammate / is_cloak | bool | Computed each update | Not read from memory |
in_screen / can_draw / is_model_blocked | bool | Computed each update | Rendering-related state; static occlusion like Gibraltar's bubble |
has_bones / has_fresh_bones() | bool | On-screen only; true only when all 17 bones were read this frame | false off-screen; calling it triggers a full-bone read (see below) |
#game.entities.playersdoes not work (userdata-backed tables have no#) — count withfor _ in pairs(...) do n = n + 1 end.
Skeleton
PlayerEntity exposes 17 bone world positions; pair with game.BONE (part constants) and game.BONE_LINKS (connection table) to draw skeleton ESP.
| Member / constant | Description |
|---|---|
player:bone(idx) | World-space Vec3 of body part idx (0-based body part id); returns a zero vector if out of range or bones not ready |
player:has_fresh_bones() | Whether all 17 bones were actually read this frame (when false, bone() is likely zero — skip) |
game.BONE | Named part constants: head=0, upper_chest, chest, waist, hip, l_shoulder, l_elbow, l_hand, r_shoulder, r_elbow, r_hand, l_thigh, l_knee, l_foot, r_thigh, r_knee, r_foot=16 |
game.BONE_LINKS | Array of {a, b} part-id pairs (matches the native skeleton, 16 segments) |
Update strategy: bones are read on-screen only (
bone()returns zero off-screen). By default the backend reads only the head bone; all 17 are read when one of these holds: skeleton ESP is on, the player is the aimbot's locked target, or it's used as a chams far-enemy anchor. Callingbone()orhas_fresh_bones()makes the backend read all 17 bones for on-screen players for the next ~0.5s — so drawing your own skeleton does not need the menu's skeleton-ESP toggle; just call it.
math.WorldToScreen(world)returns 3 values(sx, sy, ok), not a Vec2.
-- Skeleton ESP: iterate links, project each bone to screen, draw a line
local LINKS = game.BONE_LINKS
event.on("frame_update", function()
for _, p in pairs(game.entities.players) do
if p.is_visible and not p.is_dead and not p.is_teammate and p:has_fresh_bones() then
for _, link in ipairs(LINKS) do
local ax, ay, ok1 = math.WorldToScreen(p:bone(link[1]))
local bx, by, ok2 = math.WorldToScreen(p:bone(link[2]))
if ok1 and ok2 then
draw.line(ax, ay, bx, by, draw.u8(255, 255, 255, 200), 1.5)
end
end
end
end
end)game.entities.loots (iterable via pairs)
Per-LootEntity fields. Loot properties are essentially fixed at create; only origin keeps re-pacing by state. A newly dropped item is discovered after ~0.25s (discovery lag, unrelated to field refresh).
| Field | Type | Update strategy | Description |
|---|---|---|---|
index / base | int/uint64 | Fixed on create | Entity memory base — combine with offsets + mem.read/write for self-service reads |
model_name | string | Read once on create | |
model_hash | uint32 | Read once on create | hash of model_name |
quality_level | int | Read once on create | 0=unknown, 1..5=COMMON..HEIRLOOM |
weapon_name_index | int | Read once on create | Weapon string-table index |
custom_script_int / context_id | int | Read once on create | |
classified_id | int | Computed each update | ItemId enum value |
classified_name / item_id_str / base_name | string | Computed each update | Classified names |
origin | Vec3 | Low-frequency polling | High when just moved; static on-screen ~4 Hz; static off-screen ~1 Hz; beyond loot display range ~every 4s. Never fully stops |
distance | number | Computed each update | Meters |
game.entities.projectiles (iterable via pairs)
Per-ProjectileEntity fields. The core job is high-frequency trajectory drawing: origin is read every frame for nearby local/enemy projectiles. Lifetime-constant fields (owner/team/radius/weapon class) are read once when the projectile appears.
| Field | Type | Update strategy | Description |
|---|---|---|---|
index / base | int/uint64 | Fixed on create | Entity memory base — combine with offsets + mem.read/write for self-service reads |
owner_handle_raw | uint32 | Read once on first appearance | Raw EHandle bits of m_hOwnerEntity |
team_num | int | Read once on first appearance | |
dmg_radius | number | Read once on first appearance | m_DmgRadius |
weapon_class_index | uint16 | Read once on first appearance | m_weaponClassIndex; pair with game.world to look up weapon name |
creation_time | number | Stamped once on first appearance | Entity birth timestamp |
kind | string | Typed once when weapon name resolves | "frag" / "thermite" / "arc_star" / "other" / "unknown" |
origin | Vec3 | Every frame (nearby local/enemy) | ~1 Hz beyond trajectory display range; not read for teammate or static map props once confirmed |
is_throwable | bool | Computed each update | Real player grenade (filters static map props) |
is_enemy / is_local | bool | Computed each update |
game.aimbot (read-only)
| Field | Type | Description |
|---|---|---|
target_index | int | Entity index of the current aimbot target (-1 when no target) |
target_distance | number (meters) | Distance to the current target |
predict_pos | Vec3 | nil | The aimbot's internal predicted target position |
trigger_busy | bool | Whether the trigger decision is currently in a busy state (for custom trigger HUDs) |
trigger | table | Detailed trigger state snapshot (see below, for trigger HUDs) |
weapon_next_ready_timeis ongame.localplayer; trigger state is not its own namespace to avoid duplicate paths.
game.aimbot.trigger
All timestamps are engine-clock seconds (QPC, base::get_time), not time.game()/time.now(). The table includes a same-clock now; use it for countdowns (e.g. ready_in = t.weapon_ready_at - t.now).
| Field | Type | Description |
|---|---|---|
now | number | Current engine clock (QPC seconds), same clock as the timestamps below; use it for countdowns |
busy | bool | In a hold/release-window countdown |
target_index | int | Target being evaluated (0 = none) |
pressed | bool | Already mouse_down |
last_hit | bool | Whether the last evaluation hit |
snapshot_time | number | Game time of this snapshot |
release_at / again_at | number | Planned mouse_up / next-allowed-fire time |
visible_at | number | Target visible start (react timing origin) |
weapon_ready_at | number | Weapon ready time (incl. rdyfudge) |
react_window / again_window / release_window | number | React / re-fire / hold-duration windows |
rdyfudge / radscale | number | Ready offset / radius scale |
aim_offset | Vec3 | Offset of the aim part relative to spine_head |
event.on("frame_update", function()
local t = game.aimbot.trigger
if t.target_index ~= 0 then
local ready_in = math.max(0, t.weapon_ready_at - t.now)
draw.text(20, 200, draw.u8(255,255,255,255),
string.format("trigger: tgt=%d hit=%s ready_in=%.2f",
t.target_index, tostring(t.last_hit), ready_in))
end
end)Custom aim algorithms (movement/smoothing + prediction override)
Scripts can register their own movement algorithm (added to the dropdown) and prediction algorithm (global override). Execution model: callbacks run on the render thread (same thread as the Lua VM — no lock). Smoothing cadence = min(aim publish rate, render FPS) (uncapped render → high rate); prediction is slow-changing, so the render thread computes the aim point and the aim thread consumes it at 500Hz with no cadence loss. The native Normal/PID paths are unaffected.
game.aimbot.solve(shooter, target_pos, target_vel, v0, gravity) → {pitch, yaw, time} | nil
Native ballistic solver (stateless, callable anywhere). pitch/yaw are Source angles (degrees, pitch +down), time is flight time (seconds). v0 ≤ 1 is treated as hitscan (time=0); out of range returns nil. Coordinates are world-space Vec3, velocity u/s, gravity u/s².
game.aimbot.register_algorithm(name, fn)
Register a movement algorithm. name is appended to the aim / trigger Movement Algorithm dropdown; when selected, this algorithm drives mouse smoothing. fn(state) returns dx, dy (mickey delta; the engine accumulates sub-pixel residue). Auto-unregistered on script unload.
Must be called from the script's top-level load body — deferred callbacks (
event.on("frame_update", ...),Delay(),gui.Buttoncallbacks, etc.) are rejected with a throttled log warning, because the owner identity is cleared after load completion and cannot be reliably attributed (would silently leak across scripts). For lazy enable, keepregister_algorithmin the body and gate behavior with a Lua-side flag.
state fields:
| field | type | meaning |
|---|---|---|
err_pitch / err_yaw | number (deg) | current aim error (prediction-compensated + dead-zoned) |
dt | number (s) | time since this algorithm was last called |
distance | number (m) | target distance |
zooming | bool | ADS or not |
mouse_sens | number | in-game sensitivity (for screen-consistent scaling: err/0.022/mouse_sens → mickey) |
target_index | int | target entity index |
shooter / sway | Vec3 / Vec2 | shooter origin / current reference view |
target / velocity | Vec3 | world hit point / target velocity (u/s) |
Sign convention matches native:
dx = -err_yaw·k,dy = err_pitch·k.
game.aimbot.set_predictor(fn)
Globally override the prediction algorithm. fn(target) returns a world aim point Vec3; the aim thread consumes it at 500Hz in place of native solve. Pass nil to clear (fall back to native). target fields: index, shooter, aim_point, head, origin, velocity (Vec3), distance, v0, gravity.
aim_pointvshead:aim_pointis the current actual aim point — in upper-body / full-body modes it rides the body part nearest the crosshair and moves with it, so compute the lead against it.headis the stable real head-bone world point, only for head-specific logic (e.g. fixed head-aim compensation). Always lead withaim_point, nothead(otherwise the aim point gets pulled back to the head and cancels the tracking).
Same as
register_algorithm— load-body-only. Also one-predictor-per-process: if another script already owns the predictor, calls are rejected silently to Lua (throttled log only). Convention: one predictor per process, or unload the holding script first.
local tab = gui.tab("AimAlgo")
local g = tab:group("g", "Lua Aim Algo", 0, 0, 300, 0)
local gain = g:slider_float("gain", "Strength (mickey/deg)", 0.55, 0.05, 2.0)
local lead = g:checkbox("lead", "Prediction override", true)
-- Adaptive easing smoother: appears in the dropdown, active when selected
game.aimbot.register_algorithm("Lua Adaptive", function(s)
local mag = math.sqrt(s.err_pitch^2 + s.err_yaw^2)
local f = gain:get() * (1.0 - 0.4 * math.exp(-mag)) -- soft near target, full far
return -s.err_yaw * f, s.err_pitch * f
end)
-- Prediction override: reuse native solve for flight time, extrapolate by velocity
game.aimbot.set_predictor(function(t)
if not lead:get() then return t.aim_point end
local sol = game.aimbot.solve(t.shooter, t.aim_point, t.velocity, t.v0, t.gravity)
if not sol then return t.aim_point end
return Vec3(t.aim_point.x + t.velocity.x * sol.time,
t.aim_point.y + t.velocity.y * sol.time,
t.aim_point.z + t.velocity.z * sol.time)
end)game.world
| Field | Type | Description |
|---|---|---|
ring | table | {origin=Vec3, radius_start=N, radius_end=N, time_start=N, time_end=N, is_active=bool}; radius / origin are in game units (≈ inches), divide by 39.37 for meters |
map_name | string | Current map string |
game top-level methods
| Method | Returns | Description |
|---|---|---|
game:is_in_game() | bool | Whether currently in a match (signon_state + not in lobby/match-making) |
game:signon_state() | int | Raw client_state.signon_state |
Example
local lp = game.localplayer
if lp.is_dead then return end
for _, p in pairs(game.entities.players) do
if (not p.is_teammate) and p.is_visible and p.distance < 50 then
log.info("Enemy " .. (p.name or "?") .. " at " .. p.distance .. "m")
end
end
if game.world.map_name == "mp_rr_arena_skygarden" then
-- ...
end