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 screen | when Gui records it | under F1 |
|---|---|---|
| the saving indicator | only on a frame that renders a level | drawn — the one element that ignores the flag |
| the toasts | whenever resources are loaded, level or not | hidden |
| the debug overlay | whenever resources are loaded and some debug entry is on, unless the debug-options screen is the one open | hidden, unless some screen is open |
| the deferred subtitles | always | whatever 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
| class | what it decides | thread |
|---|---|---|
Hud | the order, the two hidden-gated blocks, and the health animation state | Render thread |
Gui | the four elements recorded after the screen, and their three different gates | Render thread |
ContextualBar | what can occupy the one slot above the hotbar — Hud picks which | Render thread |
BossHealthOverlay | the bars, and three questions the world’s side asks it | Render thread |
ChatComponent | the message list, its wrapped lines, and what is faded out | Render thread |
DebugScreenEntries | the F3 registry: what an entry is, and the presets | Render thread |
DebugScreenEntryList | the per-entry status, the preset applied, and its own save file | Render thread |
GuiGraphicsExtractor | everything the HUD records into | Render 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.
Guiis not the HUD any more. The class that drew the hotbar and hearts isHud; the nameGuiwas reused for the screen and overlay manager that used to be fields onMinecraft. The canonical path isGui.hud. Every render* method onGuiis now an extract* onHud, 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.