Building Halo PC Nametags: Understanding Chimera's Widescreen Renderer
I wanted to draw a nametag above a player's head. That led me into Chimera's widescreen renderer.
The initial plan was to capture the camera, project the player's world position into screen space, and pass the result to draw_text(). Halo PC and Chimera already exposed most of the data I needed.
The text appeared, but its position drifted at some resolutions and lagged behind quick camera movement. I had to work out which coordinate system each part of the renderer was actually using.
The original plan
The goal was straightforward enough.
For each player, I would:
Obtain the player's world position.
Capture the current camera position and orientation.
Convert the world position into screen coordinates.
Draw the player's name above their head.
The projection code itself wasn't especially complicated. Using the camera's forward, right, and up vectors, I transformed a world-space position into normalised device coordinates before converting them into screen coordinates.
Everything appeared to work correctly.
Then things started drifting.
One community reference also described the draw_text() parameters differently from the way Chimera interpreted them. I did not find that discrepancy until later.
The first clue
My initial assumption was that the projection mathematics were wrong.
The obvious calculation looked something like this:
local screen_center = screen_width / 2 That seems perfectly reasonable. After all, the center of a 1920-pixel-wide display should be 960 pixels.
Except it wasn't.
The more I tested, the stranger things became. Some resolutions behaved correctly. Others didn't. Sometimes the text appeared to drift horizontally. Sometimes it aligned perfectly at one resolution before becoming increasingly inaccurate at another.
I blamed my trigonometry.
The trigonometry was not the problem.
Capturing the camera
One of the first issues I encountered was a subtle one-frame delay.
Initially, the nametags were rendered during the preframe event. Camera data, however, was being captured during precamera.
This meant that each frame was being projected using the previous frame's camera position, producing a noticeable delay whenever the camera moved quickly. Eventually I moved the rendering code directly into OnPreCamera(), immediately after capturing the camera state.
The lag disappeared instantly.
The camera lag was gone, but the horizontal alignment still changed between resolutions.
The mystery of the missing pixels
While investigating the issue, I discovered something extremely surprising.
Chimera wasn't drawing directly into the monitor's native coordinate space at all.
Instead, it was maintaining an internal coordinate system derived from Halo's original 640×480 interface. Even on a modern widescreen display, Chimera continued to operate within this virtual coordinate space.
That changed the coordinate system I needed to feed into the renderer.
The centre of the screen wasn't 960 or even 640 pixels. It was still 320.
Even more surprisingly, Chimera wasn't simply stretching Halo's interface to fit a modern display. Instead, it was translating between Halo's original 4:3 coordinate system and the monitor's actual resolution.
That meant I wasn't dealing with a native 1920×1080 coordinate system at all. I was dealing with an abstraction layer built on top of one.
Internally, Chimera adjusted its calculations according to the selected widescreen mode while preserving Halo's original coordinate system [1]. The renderer supports three modes:
Full-screen widescreen rendering.
A centered 4:3 region.
A centered 16:9 region.
Once I understood the virtual 640×480 space, the resolution-dependent drift made sense.
Understanding the projection system
Eventually I settled on the following approach:
local screen_x = halfw + ndc_x * halfw
local screen_y = 240 - ndc_y * 240
The important detail here is that halfw isn't derived directly from the monitor resolution. Instead, it depends upon Chimera's internal projection state.
When widescreen rendering is enabled, the center remains fixed at 320.
The vertical field of view must also be calculated separately from the horizontal field of view, since Chimera scales the horizontal and vertical axes differently.
The head isn't where you think it is
Another challenge involved determining exactly where the nametag should appear.
My first attempt simply added a constant offset above the player's feet. This worked well enough while players were standing, but it quickly fell apart when vehicles entered the picture.
The reason turned out to be surprisingly simple.
When a player is seated inside a vehicle, the position stored at +0x5C is not the player's world position at all. Instead, it is an offset relative to the seat itself.
Eventually I discovered that the player's actual world position could be found elsewhere, while the head itself corresponded to node 12 within the biped node array.
That allowed the nametag to follow crouching, animation changes, and seated players naturally.
draw_text() wasn't doing what I thought
Another rabbit hole emerged when I began digging through the available community resources.
Chalwk, a long-time member of the Halo modding community, had written an excellent reference for Chimera [2], and I naturally assumed the following function signature was correct:
draw_text(text, left, top, right, bottom, font, align, a, r, g, b) The documentation itself was perfectly understandable, but my implementation refused to behave as expected.
Eventually I discovered that the function was actually treating those values as:
draw_text(text, left, top, width, height, font, align, a, r, g, b) In other words, the final two position values represented the dimensions of the drawing region rather than the coordinates of its opposite corner.
Once alignment entered the picture, things became even more interesting. Centering text wasn't a matter of supplying the middle of the screen as an x coordinate; instead, Chimera calculated the final position relative to the supplied region.
After tracking down the behaviour and confirming it against the source code, I sent my findings back so the reference could be updated.
I sent the corrected parameter behaviour back so the reference could be updated.
The final result
After several days of experimentation, calibration overlays, memory inspection, logging, and far more trigonometry than I expected, the system finally behaved correctly.
Standing and crouching players both worked correctly.
Vehicle occupants worked correctly — more importantly, I finally understood why the original implementation had failed.
The problem wasn't my projection mathematics, the field-of-view calculation, or the aspect ratio.
The real problem was simple: I was solving a 2026 problem with a 2003 coordinate system, without noticing Chimera's translation between them.
Where the renderer ended up
The final nametag renderer uses the camera from precamera, Chimera's virtual 640×480 coordinate space, and the biped's head node rather than a fixed offset from the player's feet.
With those pieces in the same coordinate system, the labels stay aligned while players move, crouch, enter vehicles, and use different widescreen resolutions.
References
- [1] Chimera Source Code — Snowy Mouse / Chimera contributors
Used to verify the widescreen scaling, text anchoring, and internal 640×480 coordinate behaviour described in this article.
- [2] Scripting with Chimera - Client-Side Lua — Chalwk
An excellent community reference for Chimera’s Lua API. It was the starting point for much of this work, and the draw_text() parameter description was later updated from left, top, right, bottom to left, top, width, height based on the findings from this project.