Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The HUD

Verified against Minecraft 26.3 · Part X · press F1 and the interface goes away — but not the thing that can black out your whole screen.

Press F1 and the hearts go, the hotbar goes, the crosshair, chat and the tab list go. Get into bed with the interface hidden and the screen still fades to black. That exception is not a special case in the code: it is a consequence of where a single element sits. Hud.extractRenderState is one ordered method wrapped in two blocks gated on the hidden flag, and the sleep fade is recorded in the gap between them.

That is what this page is: not a tour of hearts and hotbars but a policy. What is drawn over the world, in what order, under exactly which conditions, and which of those conditions is a surprise. The full per-element table is what the HUD draws, and when in Reference; the machinery underneath every one of them is the GUI render tree, and what a screen is — the other thing recorded into the same tree — is GUI and screens.

The order, and the two blocks

flowchart TD
    PUB["GuiRenderState.isHudHidden published, before any check"]
    LLS{"is a LevelLoadingScreen up?"}
    STOP["record nothing"]
    H1{"hidden?"}
    A["camera overlays, crosshair, a new stratum, the hotbar block, effects, boss bars"]
    SLEEP["the sleep fade — ungated"]
    H2{"hidden?"}
    B["demo text, scoreboard sidebar, action bar, title, chat, tab list, subtitles"]
    B2["the subtitles only, and only with an in-game-UI screen up"]
    GUI["Gui continues: the screen if any, then saving indicator, toasts, debug overlay, the deferred subtitles"]
    PUB --> LLS
    LLS -- "yes" --> STOP --> GUI
    LLS -- "no" --> H1
    H1 -- "no" --> A --> SLEEP
    H1 -- "yes" --> SLEEP
    SLEEP --> H2
    H2 -- "no" --> B --> GUI
    H2 -- "yes" --> B2 --> GUI

Two identical hidden? gates with the sleep fade between them, which is the whole shape: the fade is the only thing Hud.extractRenderState records whatever you press. Both branches reach the subtitles: the visible one always, deferring them when there is no screen or an in-game-UI one, and the hidden one only with an in-game-UI screen up.

Above all of that sit gates that are not Hud’s at all, and a page about conditions has to name them: GameRenderer.extract combines whether resources are loaded, whether this frame advances game time (which it is handed) and whether there is a level, and Gui.extractRenderState applies them, calling Hud.extractRenderState only when they hold. That is why a HUD-less frame is the normal state of a loading screen shown before a level exists; the level-loading screen, which is up while one does, is the one Hud tests for by name.

Four more elements are recorded by Gui itself, after the screen rather than with the rest of the HUD, and they are gated at three different depths — which is the one thing about them worth memorising, because it decides what you see on a title screen, on a loading screen and under F1.

recorded after the screenwhen Gui records itunder F1
the saving indicatoronly on a frame that renders a leveldrawn — the one element that ignores the flag
the toastswhenever resources are loaded, level or nothidden
the debug overlaywhenever resources are loaded and some debug entry is on, unless the debug-options screen is the one openhidden, unless some screen is open
the deferred subtitlesalwayswhatever Hud’s second block decided

Two structural facts follow from the shape. The hidden flag is published before the loading-screen short-circuit, so the renderer’s copy is correct even on a frame where the HUD records nothing. And toasts and the debug overlay are always above a screen, because Gui records them after it — while the deferred subtitles are called from a screen’s background pass, so the deferral that fires after the screen finds nothing left to draw and the subtitles a screen drew for itself sit under that screen’s widgets.

Hud.tick runs from Gui.tick once per client tick and takes a pause flag: the autosave indicator animates either way, everything else only when the game is not paused.

The cast

classwhat it decidesthread
Hudthe order, the two hidden-gated blocks, and the health animation stateRender thread
Guithe four elements recorded after the screen, and their three different gatesRender thread
ContextualBarwhat can occupy the one slot above the hotbar — Hud picks whichRender thread
BossHealthOverlaythe bars, and three questions the world’s side asks itRender thread
ChatComponentthe message list, its wrapped lines, and what is faded outRender thread
DebugScreenEntriesthe F3 registry: what an entry is, and the presetsRender thread
DebugScreenEntryListthe per-entry status, the preset applied, and its own save fileRender thread
GuiGraphicsExtractoreverything the HUD records intoRender thread

The toast shelf

The toasts are the one element in that list with arbitration of its own, and it is a shelf rather than a queue. ToastManager holds five slots and a waiting deque; a toast declares how many consecutive slots it wants through Toast.occcupiedSlotCount — Mojang’s spelling — and is admitted only when that many free slots sit next to each other, so a wide toast can wait behind narrow ones that arrived after it. Toast.Visibility is the two-state animation, each state carrying its own sound, and a toast is asked its wanted visibility every frame rather than given a lifetime. Toast.getToken is what lets SystemToast.addOrUpdate reset a system toast with the same id instead of stacking a second, and ToastManager.getToast, which matches class and token, is how a newly unlocked recipe joins the recipe toast already up; advancement toasts always stack. The implementations are AdvancementToast, RecipeToast, TutorialToast, SystemToast, FriendToast and NowPlayingToast — the last of which is not on the shelf at all: it is a field of its own, drawn after the five and suppressed by the pause screen and by MusicToastDisplayState.

The hidden flag travels two ways

The interesting one is the smaller. GuiRenderState.isHudHidden is read by GameRenderer in three places, to suppress the held item, the three-dimensional crosshair and — the smallest of the three — the totem-pop animation. The block-in-eyes, water and fire overlays are drawn whatever F1 says. So a 2D flag does change how the world is drawn, in three narrow ways. But Hud.isHidden itself is read directly by six other places across the client, including two entity renderers that suppress name tags.

It is not the only field on the tree the frame reads back: a clear-colour override lives there too, and every site that reads either of them is GameRenderer’s rather than LevelRenderer’s — the 2D side never reaches into the world renderer, only into the thing that drives it.

This page’s own boss bar is the loudest case of the same traffic. Five places on the world’s side — the game renderer’s tick and its level pass, the lightmap, the fog environment and the music picker on Minecraft — ask BossHealthOverlay three questions: should the screen darken, should world fog be created, should the End music play. So the dragon’s fog and the Wither’s darkening come from the world asking a HUD element, which holds the flags the boss-bar packets set. The bar itself interpolates against wall-clock time inside LerpingBossEvent, which is what turns the discrete progress the server sends into a smooth bar; what is on the other end of those packets — the saved model, its members, and the execute store that writes it — is scores, teams and stored data.

Four states, one slot, and an asymmetric rule

Hud.contextualInfoBar holds one of four states — nothing, experience, the locator, a jumpable vehicle — and Hud.nextContextualInfoState re-decides which every frame. It is a rule, not a state machine, and the rule is not symmetric. With waypoints present, a jumping vehicle or a recently changed experience total beats the locator; with no waypoints, a jumpable vehicle beats experience unconditionally — so mounting a horse silently takes your XP bar away. The level number is recorded separately from the bar, so it survives whichever bar wins.

The three implementations are ExperienceBar, LocatorBar — backed by ClientWaypointManager and styled by Hud.waypointStyles, a WaypointStyleManager that is the HUD’s own reload listener — and JumpableVehicleBar. Hud.ContextualInfo is the enum of the four states.

The hearts, which are three numbers at once

sequenceDiagram
    participant CPL as ClientPacketListener
    participant LP as LocalPlayer
    participant Hud as Hud
    participant GGE as GuiGraphicsExtractor

    CPL->>LP: hurtTo — the health, on a change the cooldown, on damage hurtTime
    Note over CPL,GGE: the next frame
    Hud->>Hud: extractPlayerHealth, gated on the game mode
    Hud->>Hud: healthBlinkTime — 20 ticks, or 10 for a heal
    Hud->>Hud: displayHealth catches up after the second
    Hud->>Hud: Hud.random takes tickCount, so the jitter is per tick
    Hud->>Hud: extractHearts — one descending pass: container, absorption, ghost, truth
    Hud->>GGE: blitSprite a heart, chosen by Hud.HeartType.getSprite

One packet, one frame later: the packet’s handler, ClientPacketListener.handleSetHealth, does everything it does to the hearts by calling LocalPlayer.hurtTo, and the six steps after the note belong to Hud. None of them reacts to the packet — it is what the HUD does every frame, reading numbers the packet left behind.

The figure’s steps are Hud.extractPlayerHealth, which sets Hud.healthBlinkTime from Hud.tickCount when the health fell or rose while LivingEntity.damageCooldownTime runs; Hud.displayHealth, which lags; Hud.random, reseeded from that same tick counter; Hud.extractHearts, the one descending pass; and one GuiGraphicsExtractor.blitSprite per layer of each heart, the sprite chosen by Hud.HeartType.getSprite. LocalPlayer.hurtTo is what the packet touched: it sets the health, LivingEntity.damageCooldownTime when the health changed, LivingEntity.hurtTime too when it fell, and nothing on Hud at all.

The shake is seeded from the tick counter, so it jitters once per client tick and is identical across every frame of one tick — and the same seeded stream drives the hunger jitter and the air-bubble wobble, which is why each of them holds still within a tick. The blink is a square wave with a three-tick half-period, running for twenty ticks after damage and ten after a heal.

Three numbers and four layers is the arithmetic of a heart row, and they are not the same list. The three numbers are Hud.lastHealth, which is what the previous frame saw; Hud.displayHealth, a lagging value that catches up about once a second; and the truth, read off the player. The four layers the descending pass draws are the container, absorption, the ghost and the truth — and the ghost is Hud.displayHealth, the number that is out of date on purpose. That is what the blink shows you: not damage, but the gap between two of the three numbers.

Hud.HeartType is six constants, one of which Mojang spells Hud.HeartType.POISIONED, each with eight sprites.

One gate silences four elements at once: armour, hearts, food and air are all recorded inside Hud.extractPlayerHealth, which is gated on the game mode being able to hurt you — which is why creative has no armour bar either. Food and mount health share a slot, and the air bubbles sit a row above it, rising further for each extra row of mount hearts. And the air bubbles make a sound: Hud.playAirBubblePoppedSound ramps volume and pitch with how many bubbles are gone, and hands it to the sound engine like any other client-side sound. It is the only element inside Hud.extractRenderState that is also an audio event — the toasts have sounds too, but they arrive with the toast rather than out of a drawing pass.

Chat is one thing in two modes, and five collections

The chat you read and the chat you type into are the same code in different modes: the HUD bails out entirely when the chat screen is focused. Message age is measured in HUD ticks rather than timestamps, and a message the server asked to delete keeps its original timestamp when it is replaced by a marker — so the marker fades on the original message’s schedule.

ChatComponent holds four collections, not two: every message and every wrapped line are separate lists with separate caps, and the deletion queue and the recent-input history are the other two. A fifth, the delay-option queue, lives on ChatListener, and is the reason a chat-delay setting can hold a message that has already arrived.

What the message list holds is a GuiMessage — the time it arrived in HUD ticks, the Component, the signature if it had one, a GuiMessageSource saying whether a player, the server or this client produced it, and a nullable GuiMessageTag. The tag is the client’s own verdict, drawn as a two-pixel bar to the left of the line, outside the text entirely, with the reason as a tooltip when you hover it: system, system-in-singleplayer, not-secure, modified, error. Only modified also carries an icon, and that one goes after the text rather than beside the bar; each tag additionally carries a short GuiMessageTag.logTag string, which goes to the game’s log beside the message. No GuiMessageTag reaches ChatLog, the separate ring of LoggedChatEvents kept for the reporting screens; what it keeps of the verdict is a player message’s ChatTrustLevel, which the modified and not-secure tags are made from. Which verdict a message earned belongs to chat and signing; this page owns the bar.

What a debug line is, and who turns one on

The F3 overlay is a registry rather than a method, and it is the second of the two debug systems this book describes. DebugScreenEntries holds every entry by Identifier, each a DebugScreenEntry writing lines through a DebugScreenDisplayer. DebugScreenEntryList, reachable as Minecraft.debugEntries, stores a DebugScreenEntryStatus per entry, applies the DebugScreenProfile presets DebugScreenEntries defines and persists to its own file with its own data-fixer type — so an entry set to always-on renders with F3 never pressed, and the game remembers that you set it. The screen that edits it is suppressed by Gui rather than by the overlay, and the overlay’s own test asks about screens too: under F1 it still records, given an entry to show, whenever a screen is open.

The charts hanging off it are FpsDebugChart, TpsDebugChart, PingDebugChart and BandwidthDebugChart over AbstractDebugChart, plus ProfilerPieChart, which is not one of them.

Two seams run out of here. Two of the rebindable debug mappings toggle entries that print no line at all and exist only to carry a flag the world renderer reads — input and keybinds says which family a given shortcut belongs to. And the other debug system, the one that asks the server for a villager’s brain, is debugging the running game: the two meet because an F3 entry decides whether eleven of that page’s twenty-five renderers exist at all, and the FPS charts are what turn its tick-time subscription on.

Questions players ask

Why do subtitles appear under an open chest? They are deferred to the screen’s background pass, beneath its widgets, whereas the tooltip and the pre-edit overlay the extractor holds are deferred past the widgets. The deferral happens when there is no screen at all or the screen declares itself in-game UI — the common case, not the rare one — and with no screen Gui makes the deferred call itself — or, while the loading overlay fades in or out, LoadingOverlay does.

Is the pumpkin blur hardcoded? No. The camera overlay list is data-driven: every equipment slot is asked whether its item declares a camera overlay.

Why does everything on my HUD vanish at once when I disconnect? Because one method clears it as a whole. Hud.onDisconnected resets the tab list, the boss bars, the toasts, the debug overlay, chat and titles together — which is a better definition of “HUD state” than any list of fields.

For a 1.21-era reader. Gui is not the HUD any more. The class that drew the hotbar and hearts is Hud; the name Gui was reused for the screen and overlay manager that used to be fields on Minecraft. The canonical path is Gui.hud. Every render* method on Gui is now an extract* on Hud, and gone with them: LayeredDraw, Minecraft.screen, Minecraft.getToastManager, Minecraft.fpsString, Options.hideGui, and DebugScreenOverlay.render along with its two information-gathering methods — the line content moved out into the entry registry.

Where to look

Hud.extractRenderState — all but the last four of the HUD’s elements in one ordered method, and the two hidden-gated blocks are visible at a glance. Then Hud.extractPlayerHealth for the most-loved method in the client, Hud.nextContextualInfoState for the bar arbitration, Gui.extractRenderState for the four recorded after the screen, DebugScreenEntries for the F3 registry, and ChatComponent for the message list.


Rules: names, never code · how the system works, not how the code reads · newest version only · every backticked name passes tools/verify_names.py.