HOOZiDocs
Skip to content

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".

FieldTypeUpdate strategyDescription
indexintEvery frameentity_list index
base / self_baseuint64Every frameEntity 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
healthintEvery frameCurrent HP
max_healthintLow frequencyMax HP (only changes on armor pickup / gold-knockdown revive)
shieldintEvery frameCurrent shield
max_shieldintLow frequencyMax shield (changes on armor swap)
origin / camera_originVec3Every frameEntity / camera position (origin follows the spectated player while dead)
view_angleVec2Every frame{pitch, yaw}
sway_angleVec2Every frameBullet firing direction including breath/sway
view_offset / punch_angleVec3Every frame, alive & not knockedView offset / recoil; keeps last value when dead/knocked
abs_velocityVec3Every frame
flags_rawuint32Every frameRaw m_fFlags bits (FL_ONGROUND etc.)
team_num / squad_idintRead once on create
platform_uidstringRead once on createuint64 → string
weapon_idintEvery frame, alive & armedWeapon string-table index; blank when empty-handed
weapon_enumintResolved on weapon swapsdk::ItemId value, more reliable than the weapon string
weaponstringResolved on weapon swapWeapon name shown in ESP
weapon_speed / weapon_scalenumberEvery frame, alive & armedFire rate / charge-up
target_zoom_fovnumberEvery frame, alive & armedADS fov of the current weapon
weapon_next_ready_timenumberEvery frame, alive & armed
weap_state / burst_fire_index / ammo_in_clipintEvery frame, alive & armedInternal ammo state
is_semi_autoboolEvery frame, alive & armed
is_zoomingboolEvery frame, alive & not knocked
is_dead / is_down / is_on_groundboolEvery frame
is_grenade / is_handsboolResolved on weapon swap
is_skydiveboolLow frequency (~every 6 frames)
time_basenumberEvery frameWorld time base
spec_indexintEvery frameSpectated target (only set while dead-spectating; -1 when alive)
backpack_tierintLow frequency (~1 Hz)0=None / 1=White / 2=Blue / 3=Purple / 4=Gold
consumablestableLow 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.

FieldTypeUpdate strategyDescription
index / base / is_npcint/uint64/boolFixed on createNot read from memory
name / platform_uid / xpstring/string/intRead once on createplatform_uid retries a few times in-match if the first read fails
team_num / squad_id / rank_sourceintRead once on create
grade / rank / rank_icon_key / rank_colorint/stringLocked for the matchComputed from score on the first frame, fixed after one cloud-rank resolve
legend / legend_icon_keystringResolved on legend changeModel name sampled ~every 64 frames; resolves ~once per match
kills / damageintLow frequency (~every 64 frames)Read on- and off-screen, diluted by entity throttling
weapon_idintShort lag on weapon swapWeapon pointer refreshes ~every 12 frames on-screen; the underlying handle is sampled very infrequently
weaponstringResolved on weapon swap
originVec3Every frame on-screen, throttled off-screenNot read after death
abs_velocityVec3On-screen & in valid range onlyFalls back to position-delta estimation off-screen
head_posVec3On-screen onlyDerived from the head bone each update (needs bones resolved)
yawnumber<300m ~every 6 frames, slower beyondRead on- and off-screen
health / shield / max_shieldintOn-screen only (~every 12 frames)
flagsintOn-screen only (~every 3 frames)m_fFlags state bits
is_down / cloak_endtimebool/numberOn-screen only (~every 3 frames)
is_deadboolLow frequency (~every 8 frames)
spec_indexintDead players only (~every 8 frames)Whom this player is spectating; -1 for the living
last_visible_timenumberOn-screen & in valid range onlyVisibility timestamp
vec_min / vec_maxVec3On-screen (~every 3 frames), needs dynamic-box/map-identCollision AABB; not read if neither is enabled
is_glow / is_wallboolOn-screen (~every 12 frames), needs glow enabled
rosteruint8Roster lookup each updateRoster bitmask (pro/star/cheater/friends); not re-queried unless the roster changes
distancenumberComputed each updateMeters
is_visible / is_teammate / is_cloakboolComputed each updateNot read from memory
in_screen / can_draw / is_model_blockedboolComputed each updateRendering-related state; static occlusion like Gibraltar's bubble
has_bones / has_fresh_bones()boolOn-screen only; true only when all 17 bones were read this framefalse off-screen; calling it triggers a full-bone read (see below)

What you get is a plain 1-based Lua table, so #, ipairs and pairs all work.

Every read materialises a fresh table. Touching one several times in the same block builds several identical tables, which is not free in a match with hundreds of loot items — read it once into a local: local players = game.entities.players. The table is good for the current frame only, do not hold it across frames.

Skeleton ​

PlayerEntity exposes 17 bone world positions; pair with game.BONE (part constants) and game.BONE_LINKS (connection table) to draw skeleton ESP.

Member / constantDescription
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.BONENamed 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_LINKSArray 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. Calling bone() or has_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.

lua
-- 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).

FieldTypeUpdate strategyDescription
index / baseint/uint64Fixed on createEntity memory base — combine with offsets + mem.read/write for self-service reads
model_namestringRead once on create
model_hashuint32Read once on createhash of model_name
quality_levelintRead once on create0=unknown, 1..5=COMMON..HEIRLOOM
weapon_name_indexintRead once on createWeapon string-table index
custom_script_int / context_idintRead once on create
classified_idintComputed each updateItemId enum value
classified_name / item_id_str / base_namestringComputed each updateClassified names
originVec3Low-frequency pollingHigh when just moved; static on-screen ~4 Hz; static off-screen ~1 Hz; beyond loot display range ~every 4s. Never fully stops
distancenumberComputed each updateMeters

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.

FieldTypeUpdate strategyDescription
index / baseint/uint64Fixed on createEntity memory base — combine with offsets + mem.read/write for self-service reads
owner_handle_rawuint32Read once on first appearanceRaw EHandle bits of m_hOwnerEntity
team_numintRead once on first appearance
dmg_radiusnumberRead once on first appearancem_DmgRadius
weapon_class_indexuint16Read once on first appearancem_weaponClassIndex; pair with game.world to look up weapon name
creation_timenumberStamped once on first appearanceEntity birth timestamp
kindstringTyped once when weapon name resolves"frag" / "thermite" / "arc_star" / "other" / "unknown"
originVec3Every frame (nearby local/enemy)~1 Hz beyond trajectory display range; not read for teammate or static map props once confirmed
is_throwableboolComputed each updateReal player grenade (filters static map props)
is_enemy / is_localboolComputed each update

game.aimbot (read-only) ​

FieldTypeDescription
target_indexintEntity index of the current aimbot target (-1 when no target)
target_distancenumber (meters)Distance to the current target
predict_posVec3 | nilThe aimbot's internal predicted target position
trigger_busyboolWhether the trigger decision is currently in a busy state (for custom trigger HUDs)
triggertableDetailed trigger state snapshot (see below, for trigger HUDs)

weapon_next_ready_time is on game.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).

FieldTypeDescription
nownumberCurrent engine clock (QPC seconds), same clock as the timestamps below; use it for countdowns
busyboolIn a hold/release-window countdown
target_indexintTarget being evaluated (0 = none)
pressedboolAlready mouse_down
last_hitboolWhether the last evaluation hit
snapshot_timenumberGame time of this snapshot
release_at / again_atnumberPlanned mouse_up / next-allowed-fire time
visible_atnumberTarget visible start (react timing origin)
weapon_ready_atnumberWeapon ready time
react_window_ms / again_window_ms / release_window_msintReact / re-fire / hold-duration windows, in milliseconds (same unit as the menu)
radscalenumberHit-radius multiplier
aim_offsetVec3Offset of the aim part relative to spine_head
lua
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.Button callbacks, 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, keep register_algorithm in the body and gate behavior with a Lua-side flag.

state fields:

fieldtypemeaning
err_pitch / err_yawnumber (deg)current aim error (prediction-compensated + dead-zoned)
dtnumber (s)time since this algorithm was last called
distancenumber (m)target distance
zoomingboolADS or not
mouse_sensnumberin-game sensitivity (for screen-consistent scaling: err/0.022/mouse_sens → mickey)
target_indexinttarget entity index
shooter / swayVec3 / Vec2shooter origin / current reference view
target / velocityVec3world 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_point vs head: aim_point is 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. head is the stable real head-bone world point, only for head-specific logic (e.g. fixed head-aim compensation). Always lead with aim_point, not head (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.

lua
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 ​

FieldTypeDescription
ringtable{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_namestringCurrent map string

game top-level methods ​

MethodReturnsDescription
game:is_in_game()boolWhether currently in a match (signon_state + not in lobby/match-making)
game:signon_state()intRaw client_state.signon_state
game:smart_filter()table | nilInternals of the smart item/attachment filter, see below

game:smart_filter() ​

Diagnostics: exposes the intermediate state of the smart item filter — which attachment slots the game offers for the held weapon, what each one was recognized as, and which items ended up selected. This is what answers "why isn't this attachment showing up".

Built on demand: each call renews a 1-second lease; with no script reading, nothing is built at all. So the very first call always returns nil (the background pass only produces data on its next recompute) — calling it every frame makes that a non-issue. One second after the last call it goes back to zero cost. Also nil when not in a match.

Returned table:

FieldTypeDescription
recomputeintRecompute counter. Not moving = loadout/inventory unchanged, not a stalled feed
weaponstableThe two primary weapon slots, see below (empty slot has weapon == "")
desiredtableArray of the finally selected items, each {name, id, group, checked}

weapons[i]:

FieldTypeDescription
slotint1 / 2
weapon / weapon_idstringDebug short name / item-list key; "" for an empty slot
weapon_icon / ammo_iconstringIcon resource names, feed straight into file.image:get() (see Icons below)
ammostringAmmo key from the item spec table (e.g. ammo_energy). Magazines are filtered by this
mods_readboolWhether the attachment list was read. false = neither attachments nor ammo will be suggested for this weapon
care_packageboolCare-package weapon (fully kitted; nothing is suggested)
dual_ammoboolDual-ammo weapon (C.A.R.)
mag_offeredtableMagazine series the game offers for this weapon ("light"/"heavy"/"energy"/"sniper")
mag_equippedtableOf those, the ones currently equipped
mag_mismatchbooltrue = the spec table's ammo is not among mag_offered, so no magazine will ever be shown for this weapon (spec table needs fixing)
wantedtableAttachments contributed by this weapon, same shape as desired. This is the value before the global optic rules run — don't diff it against desired
modstablePer-slot attachment rows, see below

weapons[i].mods[k]:

FieldTypeDescription
indexintIndex in the game's attachment list (0-based)
namestringRaw attachment name from the game, e.g. stock_sniper_l3
item / item_idstringRecognition result (debug short name / item key); "" = not recognized
icon / tierstring / intIcon resource name / rarity tier (0=single 1 white 2 blue 3 purple 4 gold 5 red)
equippedboolCurrently equipped
markerstring"" / "dual_ammo" / "care_package" — the latter two are markers, not real attachments

desired[k] / wanted[k]:

FieldTypeDescription
name / idstringDebug short name / item-list key
iconstringIcon resource name (see below); "" when the item has no icon
tierintRarity: 0=single tier / 1 white / 2 blue / 3 purple / 4 gold / 5 red. All tiers of one attachment share a single icon — use this to color them apart
groupstringweapon / weapon_mod / optic / ammo / heal / gear …
checkedboolWhether it is checked in the item list. Being checked is a hard prerequisite for drawing: selected but unchecked still draws nothing

Icons ​

icon / weapon_icon / ammo_icon are resource names (e.g. icon_attachments/Extended_Energy_Mag), not file paths. The app preloads the whole resources/icon_* tree into the texture table at startup, so scripts just fetch them — no file lookup, no re-rasterizing the SVG:

lua
local tex = file.image:get(item.icon)
if tex then draw.image(tex, x, y, 28, 28) end

Returns nil when that item has no icon. Don't use file.image:load() for these — it re-reads and rasterizes a second copy, wasting VRAM.

lua
-- Find weapons the spec table got wrong: ammo mismatch, or two stock series offered at once
local sf = game:smart_filter()
if sf then
    for _, w in ipairs(sf.weapons) do
        if w.mag_mismatch then
            log.warn(w.weapon .. ": wrong ammo in the spec table, its magazines will never show")
        end
    end
end

A full example ships with the app: scripts/_test_16_smart_filter.lua (window + console dump + automatic flagging).


Example ​

lua
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