Skip to content

HUD Media Lifetime

Contract

HUD FDF trees may stay cached for a level. Texture and font configstring indices must not. Names live for the process; indices live for the level.

SV_Map does memset(&sv, 0, sizeof(sv)), which empties CS_IMAGES and CS_FONTS. The game module is not unloaded. All in-game HUD bindings live in one BSS object, hud. UI_ResetHud is memset(&hud, 0, sizeof(hud)) plus UI_ClearTemplates() for the FDF frames[] pool. Scene files bind into hud.console, hud.simple, and so on; a NULL root means "not bound yet".

Quake 2 does not parse UI files every frame. G_SetStats calls gi.imageindex(item->icon) from a name every frame, and DrawPic looks up that name. The one cached pic (level.pic_health) is assigned again in SpawnEntities after memset(&sv). Same rule here.

Data Flow

G_LoadMap
  -> memset(&hud, 0, sizeof(hud))
  -> UI_ClearTemplates()
  -> UI_LoadHud()                   // every panel, once per level
  -> UI_Write* / UI_BuildFrameForWrite
       UI_LiveImage / UI_LiveFont re-ImageIndex from hud.image_key[]

hud.image_key[] is write-once per slot until the next memset. After a wipe, ImageIndex reuses slot 1; overwriting that slot's remembered chrome name with the new command-button occupant is the same collision.

Do not parse ConsoleUI.fdf on every resource-bar write. Isolated scene files stay; they share one accumulator.

Glue UI (games/warcraft-3/menu/) is a separate stb_fdf instance with its own frames[] and ui_textures[]. It is not this contract. Keep the frames[] definition hidden per shared library: on ELF/Linux an exported duplicate symbol can be interposed between libgame and libmenu, causing this server-side UI_ClearTemplates() to erase the client's glue/loading frame registry during SV_Map.

Client

Same-map load never changes CS_WORLD, so the client cannot wait for CL_ClearState. CL_RestartRefresh zeros cl.pics / cl.fonts / cl.models (renderer caches still own the GPU objects). CL_PrepRefresh always binds from the current configstring, and empty slots stay NULL so leftover art cannot draw.

Runtime Registration Order And Capacity

Command/build/status art is still server-authored through gi.ImageIndex() and CS_IMAGES; it is not part of the client's MAX_DYNAMIC_IMAGES text-path cache. Two constraints follow from that ownership:

  • a newly allocated image configstring must be serialized before the svc_layout that first references its numeric slot; PF_Unicast() therefore calls SV_QueuePendingConfigStrings() before appending the authored layout to the client's message;
  • occupied CS_IMAGES slots cannot be evicted/reused during the level because retained layers may still hold their numbers. MAX_IMAGES is 2048 (2,047 usable nonzero indices), replacing the legacy 256-entry table that could make later command icons resolve to index 0 after enough distinct UI art had been visited.

A renderer texture-cache miss and a network image-index exhaustion are different failures. Renderer-local textures remain in the unbounded session hash described in docs/fs-loading-architecture.md; increasing or recycling MAX_DYNAMIC_IMAGES does not repair a full CS_IMAGES table.

Verification is covered by server_net.image_registry_exceeds_legacy_255_slot_limit and server_net.pending_image_configstring_precedes_dependent_payload. A visual run is still useful for final artwork correctness, but is not required to establish these transport/index invariants.

Diagnostic Workflow

After load quick, tabs must show resolved KEY_QUESTS strings and tab art, not the FDF ids themselves. Command-card icons must match the selected unit, not shuffled chrome.

build/bin/openwarcraft3 -data "data/Warcraft III" -roc \
  +map "Maps/Campaign/Human02.w3m" +wait +save quick +wait +load quick \
  +screenshot 20 +com_frame_limit 80

Repeat with -tft. Engine coverage: make test-wc3-engine WC3_PATTERN='wc3_game.hud_image*'

Known Pitfalls

  • Copying FRAMEDEF.Texture.Image onto the wire without UI_LiveImage after a configstring wipe.
  • Leaving hud bindings live across G_LoadMap while frames[] was cleared.
  • Treating GetConfigstring(CS_IMAGES + old_index) as the name of a cached frame after the slot has been reused.
  • Preserving CS_IMAGES across SV_Map as a workaround. The table is per-level.

Widescreen Console Extension Tiles

Retail 1.30+ ConsoleUI.fdf uses symbolic ConsoleTexture05 and ConsoleTexture06 for the left/right widescreen console extensions, anchored 0.256 outside the 4:3 root on each side; classic archives author no such frames. The server resolves symbolic keys through UI_ThemeImagePath and the recipient's war3skins.txt race category before registering concrete CS_IMAGES paths. Some classic/pre-widescreen skin tables contain the extension art in the archives but omit one or both symbolic fields. In that case the skin lookup lacks a value and the renderer otherwise searches for a literal file named ConsoleTexture06, producing the 16x16 placeholder.

When one console extension key remains unresolved, UI_ThemeImagePath() derives its path from the resolved sibling (...05[.blp] ↔ ...06[.blp]). This keeps the active race/custom skin authoritative instead of hard-coding a race path. Runtime confirmation on a 2880x1620 Human campaign showed the left extension resolving to HumanUITile05 while the missing right extension was laid out correctly but retained literal ConsoleTexture06; this is a media-resolution failure, not an anchor failure.

Class-gated, deferred registration. These two keys are the exception to parse-time registration. UI_LoadTexture gives them a handle at or above HUD_DEFERRED_IMAGE_BASE (hud.deferred_key[]) instead of calling gi.ImageIndex, UI_LoadHudConsole collects the frames into hud.console_wide[], and UI_WriteConsoleBackdrop hides them for recipients whose client_s.canvas is not UI_CANVAS_WIDE. UI_LiveImage resolves a deferred handle for the recipient's race and registers it the first time a wide client's console is written, so sessions on 4:3 windows never carry the tiles in CS_IMAGES. UI_ImageKey returns the symbolic key for either handle range. Once registered the slots follow the normal per-level lifetime above. See ui-canvas.md for the client side (window class, settle timer, ui_canvas command).