Retail Font Stack Audit
May 28, 2026 ยท View on GitHub
This note records what the committed retail Quake Live evidence shows about the Windows font stack and how that stack is now reconstructed and validated in the current writable source tree.
Evidence summary
Observed facts from the committed retail launcher HLIL:
quakelive_steam.execontains a second text system beyond the classic rendererRE_RegisterFontpath.- That host text system creates a texture named
*fontstash, installs the error callback stringR_fonsErrorCallback: error %d val %d\n, and exposes a debug atlas path guarded byr_debugFontAtlas. - The host initializes five named faces in a contiguous handle table:
normal->fonts/handelgothic.ttfsans->fonts/notosans-regular.ttfmono->fonts/droidsansmono.ttfsans-fallback->fonts/droidsansfallbackfull.ttfsans-windows-fallback->%WINDIR%\fonts\ARIALUNI.TTF,%WINDIR%\fonts\segoeui.ttf, or%WINDIR%\fonts\l_10646.ttf
- Retail host
DrawScaledText/MeasureTextwrappers index that handle table directly, instead of collapsing every call to a single bakedfontInfo_t. - Retail host
DrawScaledText/MeasureTextconsume UTF-8 text through the retained host lane instead of indexing raw bytes. HLIL0x00443BE0and0x00444360walk the same DFA-style decode tables before probing glyphs. - Retail glyph lookup caches entries by decoded codepoint plus the rounded
tenths-sized request and probes the requested face before the retained
fallback-face slots. HLIL
0x00443720shows the current face search, the three retained fallback slots, and the size-tenths key in the same helper. - Retail
R_InitFontStashcreates the retained512 x 512atlas, installs the callback, loads the face table, and leaves glyph population lazy; no initialization-time loop prebuilds every byte glyph for every face. - Retail atlas expansion preserves the existing alpha atlas contents while growing the retained image, and it only clears cached glyph state on the maximum-size flush path.
- Retail
*fontstashtexture refreshes rebind the renderer image asGL_ALPHAstorage and upload the retained one-byte alpha buffer directly. - Retail host draw only treats
^0through^7as color escapes. Other caret pairs stay visible in the glyph stream, whileforceColorstill consumes the recognized color escapes without recoloring. - UI HLIL
sub_10003d90/sub_10003ec0computes host text scale as(vidHeight / 768) * 96. Against the source UI/cgameyscale = vidHeight / 480, that matches aQL_FONT_HOST_POINT_SIZEbaseline of60.0f.
Observed facts from the writable source tree:
- The classic renderer FreeType path still lives in
src/code/renderer/tr_font.c. - The renderer no longer depends on a missing in-tree
src/code/ft2/vendor drop. The build now uses a repo-managed FreeType replacement lane on Windows andpkg-config freetype2on Unix whenBUILD_FREETYPEis enabled. The Win32 build now defaults that lane on, bootstraps FreeType intosrc/libs/freetypefrom an official upstream cache when needed, and no longer silently compiles the tiny bitmap fallback path on clean checkouts. src/code/renderer/tr_font.cnow contains a renderer-owned retained host text core that creates*fontstash, installsR_fonsErrorCallback, and retains the five recovered host-text faces plus the preferred sans/fallback face slots.src/code/renderer/tr_font.cnow also decodes UTF-8 host text into codepoints, caches retained glyphs by codepoint plus rounded size tenths, and probes the recovered retained fallback-face chain before dropping to the classic baked-font compatibility lane.src/code/renderer/tr_font.cnow leaves retained host glyphs lazy: glyphs are cached lazily as text is measured or drawn, matching the retailR_InitFontStashownership path and avoiding startup atlas saturation.src/code/renderer/tr_font.cnow also preserves the previous atlas pixels and cached glyph coordinates whenR_fonsErrorCallbackexpands the retained atlas, keeping cache invalidation reserved for the retail max-size flush.src/code/renderer/tr_font.cnow flushes queued renderer text quads before a retained atlas resize or max-size reset, matching the retail FontStash draw-queue boundary so loading-screen text cannot sample a newer atlas generation than the one used for its UVs.src/code/renderer/tr_font.cnow refreshes the retained*fontstashtexture as aGL_ALPHAatlas instead of expanding the alpha buffer into RGBA on every upload.- native
DrawScaledText/MeasureTextnow route through the shared renderer host-text helpers instead of through duplicated client-side glyph loops. r_debugFontAtlasnow has an in-source draw path intr_backend.c.- The retail
GetRefAPItable still carries a legacy font slot afterModelBounds, but the current source keeps that slot as a no-op and routes native UI/cgame font registration through the client compatibility wrappers that call the classicRE_RegisterFontlane directly.
Source alignment landed in this pass
src/code/renderer/tr_font.cnow recognizes the retail Quake Live font aliases, builds face-specific cache and page names, and can read absolute Windows font paths when the caller resolves a host fallback face.src/code/renderer/tr_font.cnow also owns the renderer-side retained host text core: a renderer-owned retained host text core that keeps the*fontstashatlas alive, installs the retail expansion or flush callback, and retainsnormal,sans,mono,sans-fallback, andsans-windows-fallbackin one face table.- The retained host text core now drains pending renderer commands before the
expansion/reset callback mutates
*fontstash, preserving already-queued UI and loading-screen text against the old atlas contents. src/code/renderer/tr_font.cnow also owns the shared host draw/measure helpers used by the nativeuiandcgameimport wrappers.- Those shared helpers now resolve glyphs from the retained
*fontstashface table first and only fall back to the classicRE_RegisterFontcache lane when the retained atlas path is unavailable for the requested face. - Those same helpers now consume UTF-8 codepoints, treat only
^0..^7as host color escapes, and keep the retailforceColorbehavior where valid color escapes are consumed but do not recolor the active draw state. - The renderer host helpers are now gate-checked for their core draw/measure
contract: face handles fall back through the retained normal/sans faces,
requested sizes are rounded and clamped to tenths, max-X clipping happens
before drawing the overflowing glyph,
outMaxXreturns0on clipping, and measurement preserves bounds/outLeftwhile still reporting the retained font ascent for height. src/code/client/cl_ui.candsrc/code/client/cl_cgame.cnow route nativeDrawScaledText/MeasureTextthrough the shared renderer host-text helpers instead of resolvingfontInfo_tlocally.- The native UI/cgame font import slots are now parity-gated as a trio:
RegisterFontremains on the compatibility registration lane, whileDrawScaledTextandMeasureTextfail closed through the recovered host text import wrappers. - The native draw wrappers also preserve the module current-color sidecar and
normalize
forceColorbefore enteringRE_DrawScaledText; the measure wrappers return retail-style packed width/height floats after callingRE_MeasureScaledText. - UI and cgame clipped text helpers now have explicit max-X projection gates:
Text_Paint_LimitandCG_Text_Paint_Limitproject both draw coordinates and clip bounds into screen space, draw through host text, then convert the returnedoutMaxXback into 640-space coordinates. - UI and cgame host text metrics/span helpers are gated as retained-text
consumers too: measurement uses
trap_QL_MeasureTextatscale * QL_FONT_HOST_POINT_SIZE, span drawing usestrap_QL_DrawScaledText, and UI cursor painting measures the prefix through the same host lane before drawing the cursor glyph. src/code/client/cl_console.cnow keeps the console prompt, chat prompt, scrollback, and editable field cursor on the retained mono host-text lane. The strict audit checks the UTF-8 cursor windowing, host metrics lookup, and packed console color-cell conversion into host text color escapes.src/code/client/cl_scrn.cnow routes client screen-overlay helpers such asSCR_DrawStringExt,SCR_DrawBigString,SCR_DrawBigStringColor, and the demo-recording overlay through the same retained mono host-text lane used by retail, instead of the legacy charset glyph loop.src/code/renderer/tr_backend.cnow exposes the retained atlas through the debug draw path guarded byr_debugFontAtlas.- Renderer advertisement debug labels in
src/code/renderer/tr_world.calso draw through retained host text at the recovered16 / 48debug scale.
Interface font helpers and registered sizes
Observed facts from the committed retail UI and cgame references:
- UI
AssetCacheis an art-only cache; the committed retail reconstruction foruix86.dllregisters gradient, FX, scrollbar, slider, and crosshair art there, but not fonts. - UI
MenuParse_fontnormalizes legacy tokens onto the Quake Live baked font buckets, stores the resolved token on the menu, and seeds the shared display context fonts on first use. - cgame
CG_AssetCacheis described in the committed symbol map as the shared HUD art cache, whileCG_RegisterHudFontsremains the explicit HUD font bootstrap helper.
Observed facts from the writable source tree:
- The shared baked font buckets are now defined as:
fonts/fontat24fonts/smallfontat16fonts/bigfontat48fonts/monofontat16
- UI menu asset globals use two retail size tiers:
- gameplay and HUD menus such as
src/ui/hud.menuregister24 / 16 / 48 src/ui/main.menuregisters16 / 12 / 20
- gameplay and HUD menus such as
- UI font registration now stays in
Asset_ParseandMenuParse_font, whileAssetCacheremains art-only. - UI
Asset_Parsenow normalizes the globalfont,smallFont, andbigFontdeclarations before registering the retained text, small, and big buckets, whileMenuParse_fontbootstraps that trio once from the resolved menu token. - UI item-level
fontstays a retail integer bucket:ItemParse_fontwritesitem->fontIndex, and the shared item width, height, paint, and cursor wrappers forward that bucket into the extended host-text callbacks. - cgame HUD font registration stays in
CG_RegisterHudFonts, whileCG_AssetCacheremains art-only. - cgame
CG_Asset_Parseis now parity-gated for the retail token spelling:font,smallFont, and lowercasebigfontroute throughcgDC.registerFont, while the one-time HUD bootstrap keeps the default text/small/big trio available when scripts omit a bucket. - cgame browser/HUD menu parsing is now gated through the shared menu parser:
CG_SetupBrowserMenuKeywordHashrebuilds the keyword hash containingfont -> MenuParse_font,CG_ParseBrowserMenuentersMenu_Parse, and bothCG_Initandloadhudrebuild that parser state before HUD menu font declarations are replayed.
Inference:
- The source tree now matches the retail face selection and per-face cache behavior much more closely for the retained host-text lane itself.
- The hidden exactness tail inside that retained lane is now also closed: host text is no longer byte-oriented, and Unicode fallback probing no longer depends on the caller choosing the fallback face manually.
- The UI and cgame font helpers now also match the retail ownership split more closely: asset caches own shaders, while dedicated font parse/bootstrap helpers own the registered Quake Live font buckets and their point sizes.
- The renderer font stack now has a coherent source, build, and runtime parity story instead of a missing-vendor plus warning-only tail.
Closure status
RG-P10is now complete:- the renderer build metadata no longer points at
..\ft2\* BUILD_FREETYPEis now tied to a repo-managed FreeType lane, while the non-retail codec stack is bootstrapped from repo-managed sources instead of from system SDK or Vcpkg discovery
- the renderer build metadata no longer points at
RG-P11is now complete:- the tracked runtime artifact is
artifacts/renderer_validation/logs/renderer_runtime_evidence_latest.json - the stable alias was refreshed on 2026-04-21 from the clean
renderer_runtime_evidence_20260421.jsonbundle - that artifact now proves a windowed UI-bootstrap pass, retained-atlas
debug rendering, and live
bloodrunruntime with distinct engine/window capture hashes, while rejectingRE_RegisterFontfallback-lane logs
- the tracked runtime artifact is
- The 2026-04-17 full font audit reopened one hidden renderer-host gap
inside the earlier
RG-P8closure:- raw-byte host glyph lookup instead of UTF-8 decode
- point-size-only retained glyph caching instead of codepoint-plus-size cache
- retained fallback faces that were stored but not automatically probed
- over-permissive caret color parsing in the host draw helper
- That reopened gap is now reclosed in source, tests, and the strict audit script.
- No confirmed renderer or client screen-overlay font-stack gap remains after
RG-P11, the 2026-04-17 audit refresh, and the retained mono overlay wiring follow-up. - The strict audit also now covers the native UI/cgame font import slot values,
import-table assignments, fail-closed trap wrappers, the VM-side
RegisterFontcompatibility wrappers, cgameQ3_VMhost-text stubs, cgame snapshot/FPS upper-right host text, module current-color/packed-measure bridge semantics, renderer face/scale/color/max-X/out-left core semantics, UI/cgame metrics, cursor, span, and clipped max-X text, renderer advertisement debug labels, console/chat host-text consumers, UI asset/menu parser bootstrap ownership, UI item integer font buckets, UI legacy font alias normalization, cgame lowercase-bigfontHUD asset parsing, and cgame browser-menu font parser routing plus HUD reload ordering.
Verification
Run:
pwsh tools\ci\audit-retail-font-stack.ps1
Use -Strict to turn any reopened font-stack or runtime-evidence regression
into a hard failure.