Skip to main content
CS2’s HUD, main menu, popups and scoreboard are panorama. This module reads them: which panels exist, what was painted in the last frame and in what order, where it sits on screen, its opacity, text and classes. Stick a box to the money counter, or keep your ESP from drawing over the HUD.
No permission needed. panorama.ready is false while the UI engine isn’t found; lookups return null and lists come back empty until it is.
Every value is panorama’s as of its last frame, at most a frame behind. Screen positions are overlay pixels, the same ones render draws in.

Overview

Finding panels

ref, find, path, findAll, walk, windows, the inspector.

Panels

Panel and UIWindow: tree, visibility, opacity, text, classes, screen position.

What's painted

drawList, panelsAt, paintOps, DrawItem, PaintOp.

Coverage

coverageShapes, coverage, isCovered: keep your drawing off the HUD.

Finding panels

find, path, findAll and ref take { in, visibleOnly }: in limits the search to a panel’s subtree or one window, visibleOnly skips hidden subtrees (a lot cheaper on the menu, where most pages are hidden). walk takes { maxDepth, visibleOnly, paintOrder }. Layer names the windows: hud ("CSGOHud"), mainMenu, loadingScreen, popups, customHuds, console.

ref


Panels die on a map change or a HUD reload, and a new one with the same id takes their place. A PanelRef finds it again: get() returns the panel, or null while there isn’t one. A miss is retried at most every retryMs (default 500), so calling get() every frame is fine.

Finding an id

Hover the thing you want:
F8 toggles it, PageUp / PageDown walk through the panels stacked under the cursor. It shows the path, class, bounds, opacity, classes and text. Options: toggleKey, enabled (default true), intervalMs (hit-test interval, default 100). It returns { enabled, hovered, dispose() }. Or print the tree:
dump positions are in window pixels. panorama.draw(panel, options?) outlines one panel where it’s drawn, inside render only: color (magenta), fill, label (true for id [Type] WxH, a string, or false), thickness.

Panels

A Panel is one panorama panel. You get the same object for the same panel while you hold it. Bounds are { x, y, width, height, min, max }, so min/max go straight into render.rect. Quads are top-left, top-right, bottom-right, bottom-left. The position methods return null for a panel that’s gone. A UIWindow is one top-level layer: HUD, main menu, loading screen, popups, console. It has name (its root’s id), root, roots (in paint order), shown (the main menu isn’t, in a match), width, height, size, scale (height / 1080), find(id), toScreenScale(), address and alive.

What’s painted

drawList takes { in, contentOnly, minOpacity }, panelsAt / panelAt the same plus skip(panel). contentOnly drops layout containers, minOpacity defaults to 0.01. A PaintOp is one thing panorama drew: a fill, gradient, image, text run, blur, border or shadow. paintOps takes { in, minAlpha, kinds, skip(op) }, minAlpha defaulting to 0.02. Read an op’s geometry in the frame you got it. It’s kept for a few frames, then it’s gone.

Coverage

Where panorama actually paints, built from every primitive above. This is the mask to keep your drawing off the HUD, the menu and the scoreboard. Both shape calls take the paintOps options. isCovered takes { in, minAlpha, skip(panel) }. Icons and opacity-mask images are cut to their real pixels, so a weapon silhouette or a round avatar covers just its shape. The images load in the background; until one has, it counts as a full rectangle. panorama.imageAlpha = false turns this off and every image is a rectangle.

Draw order

The same order panorama paints in:
  1. Windows in windows() order: HUD, then main menu, loading screen, popups, console.
  2. The window’s roots, in order.
  3. Depth-first: a parent, then its children sorted by z-index (ties keep children order). z-index only sorts siblings.
HUD popups, context menus and tooltips live inside CSGOHud; the main menu’s popups are their own window. The scoreboard is Scoreboard in the HUD and stays empty (0×0) until it’s opened. All of it lands in drawList and the coverage as soon as it’s painted.

Styles that count

Limitations

  • Text covers its line box, not the glyphs.
  • Only what panorama draws. The engine’s own overlays and console text (the radio and chat lines in the corner) aren’t in it.
  • During a transition or animation, opacity and transforms read as their end values. Only position is interpolated. animating tells you when it’s happening.
  • perspective is applied without the perspective divide.
  • Color filters (wash-color, brightness, saturation, hue-rotation, mix-blend-mode) are ignored. Borders follow the panel’s edge.
  • background-image on non-image panels and Steam avatars stay rectangles.

Tools

Nothing is read until you ask. After the first frame only what panorama changed gets read again, so calling coverageShapes every frame is fine.

Example

Enemy boxes that duck under the HUD, plus a frame around the money counter.