Skip to content

fix(gui): Stop game time clock from jittering horizontally#2860

Open
bobtista wants to merge 1 commit into
TheSuperHackers:mainfrom
bobtista:bobtista/fix/game-time-clock-jitter
Open

fix(gui): Stop game time clock from jittering horizontally#2860
bobtista wants to merge 1 commit into
TheSuperHackers:mainfrom
bobtista:bobtista/fix/game-time-clock-jitter

Conversation

@bobtista

@bobtista bobtista commented Jul 7, 2026

Copy link
Copy Markdown

Addresses #2859

drawGameTime() positioned the top-right clock by subtracting live-measured text width every frame, right-anchored to the screen edge. With the proportional Tahoma font, digit widths change as the time ticks (and the .NN frame counter changes every frame), so the whole timer shifted a pixel or two each frame.

Reserve a fixed width per field from the widest digit and anchor the timer's left edge to it so the white clock no longer moves as digits change. The frame counter is butted against the timer's live right edge so the two stay adjacent.

TODO:

  • Test
  • Replicate to Generals

@greptile-apps

greptile-apps Bot commented Jul 7, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes horizontal jitter of the in-game clock (drawGameTime) caused by measuring live proportional-font text widths every frame to compute the timer's screen position. The fix pre-computes fixed reserved widths (widest digit × digit count + separator widths) once in refreshGameTimeResources() and uses those to pin the timer's left edge, while the grey frame counter (.NN) continues to follow the timer's live right edge so the two remain visually adjacent.

  • refreshGameTimeResources(): loops over digits '0''9' to find maxDigitWidth, measures ':' and '.', then stores m_gameTimeReservedWidth (6 digits + 2 colons) and m_gameTimeFrameReservedWidth (1 dot + 2 digits).
  • drawGameTime(): horizontalTimerOffset is now derived from the pre-computed reserved widths (stable every frame); horizontalFrameOffset is horizontalTimerOffset + m_gameTimeString->getWidth() so the frame counter stays adjacent to wherever the timer text actually ends.
  • Two new Int members are added to InGameUI and zero-initialised in the constructor.

Confidence Score: 5/5

Safe to merge; the change is narrowly scoped to the game-time HUD drawing path and does not touch game logic, state, or persistence.

The reserved-width formula correctly mirrors the format strings (6 digits + 2 colons; 1 dot + 2 digits). Pre-computation happens in the resource-refresh path, not per-frame, so there is no runtime cost concern. Startup behaviour when reserved widths are still 0 is identical to the pre-fix code. The intentional frame-counter float is clearly documented in both the PR description and the new inline comment.

No files require special attention; both changed files are straightforward.

Important Files Changed

Filename Overview
GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h Adds two Int member fields (m_gameTimeReservedWidth, m_gameTimeFrameReservedWidth) to InGameUI; straightforward, correctly aligned with surrounding members.
GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp Initialises both reserved-width fields to 0 in the constructor; computes them from per-glyph measurements in refreshGameTimeResources(); uses the fixed values in drawGameTime() to anchor the timer left edge, with the frame counter following the timer's live right edge. Logic is correct.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["refreshGameTimeResources()"] --> B["Iterate digits '0'–'9'\nfind maxDigitWidth"]
    B --> C["Measure ':' → colonWidth\nMeasure '.' → dotWidth"]
    C --> D["m_gameTimeReservedWidth\n= 6×maxDigitWidth + 2×colonWidth\n\nm_gameTimeFrameReservedWidth\n= dotWidth + 2×maxDigitWidth"]
    D --> E["Stored once per resource refresh"]

    F["drawGameTime()  (every frame)"] --> G["Set timer text HH:MM:SS\nSet frame text .NN"]
    G --> H["horizontalTimerOffset\n= screenWidth − pos.x\n  − ReservedWidth − FrameReservedWidth\n(FIXED left edge)"]
    H --> I["horizontalFrameOffset\n= horizontalTimerOffset\n  + gameTimeString.getWidth()\n(live, adjacent to timer text)"]
    I --> J["Draw white timer at horizontalTimerOffset\nDraw grey .NN at horizontalFrameOffset"]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A["refreshGameTimeResources()"] --> B["Iterate digits '0'–'9'\nfind maxDigitWidth"]
    B --> C["Measure ':' → colonWidth\nMeasure '.' → dotWidth"]
    C --> D["m_gameTimeReservedWidth\n= 6×maxDigitWidth + 2×colonWidth\n\nm_gameTimeFrameReservedWidth\n= dotWidth + 2×maxDigitWidth"]
    D --> E["Stored once per resource refresh"]

    F["drawGameTime()  (every frame)"] --> G["Set timer text HH:MM:SS\nSet frame text .NN"]
    G --> H["horizontalTimerOffset\n= screenWidth − pos.x\n  − ReservedWidth − FrameReservedWidth\n(FIXED left edge)"]
    H --> I["horizontalFrameOffset\n= horizontalTimerOffset\n  + gameTimeString.getWidth()\n(live, adjacent to timer text)"]
    I --> J["Draw white timer at horizontalTimerOffset\nDraw grey .NN at horizontalFrameOffset"]
Loading

Reviews (1): Last reviewed commit: "fix(gui): Stop game time clock from jitt..." | Re-trigger Greptile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant