This is a racing simulator HUD plugin for PiBoSo racing games (MX Bikes, GP Bikes, WRS, KRP). It’s a DLL plugin written in C++ using each game’s proprietary API, with a shared core that works across all supported games.
For deep technical details: See ARCHITECTURE.md (comprehensive documentation with mermaid diagrams, component descriptions, dependency graphs, multi-game architecture). This file is a quick-start guide.
Game Engine (MX Bikes / GP Bikes / WRS / KRP)
↓ (callbacks via plugin API)
mxb_api.cpp / gpb_api.cpp (per-game DLL exports)
↓ (converts to unified types via adapters)
PluginManager (receives unified types only)
↓
PluginData (singleton - caches all game state)
↓ (notifies on data changes)
HudManager (singleton - owns all HUD instances)
↓
Individual HUDs (IdealLap, Standings, Map, etc.)
↓ (build render primitives)
Game Engine (renders quads/strings)
PluginData ──(notifies on data changes)──→ HttpServer
↓ (builds JSON snapshot on game thread)
SSE stream → Web Overlay (browser/OBS)
Key Singletons:
PluginData - Central game state cache, change detectionHudManager - HUD lifecycle, owns all HUD instancesSettingsManager - Save/load HUD configurationsInputManager - Mouse and keyboard inputXInputReader - Controller state and rumble effectsRumbleProfileManager - Per-bike rumble profiles stored in JSONStatsManager - Unified stats, personal bests, odometers in a single JSON fileFmxManager - FMX trick detection state machine, scoring, chain systemAssetManager - Dynamic discovery of fonts, textures, icons from subdirectoriesFontConfig - User-configurable font categories (Title, Normal, Strong, Marker, Small)ColorConfig - User-configurable color paletteHttpServer - Embedded HTTP server with SSE streaming for web overlays (OBS)The plugin supports multiple PiBoSo games from a single codebase:
| Game | Config | Output | Status |
|---|---|---|---|
| MX Bikes | MXB-Release |
mxbmrp3.dlo |
✅ Full support |
| GP Bikes | GPB-Release |
mxbmrp3_gpb.dlo |
✅ Core features |
| Kart Racing Pro | KRP-Release |
mxbmrp3_krp.dlo |
✅ Core features (no FMX) |
| WRS | - | wrsmrp3.dlo |
⏳ Stubbed |
Translation Layer:
game/unified_types.h - Game-agnostic data structures (Unified:: namespace)game/game_config.h - Compile-time game selection, feature macrosgame/adapters/*_adapter.h - Convert game structs → unified typesvendor/piboso/*_api.cpp - Per-game DLL exports⚠️ IMPORTANT - Build Environment:
⚠️ IMPORTANT - Shell Commands:
& instead of && for chaining commands (or provide separate commands)\ for paths, or forward slashes / (git accepts both)git fetch origin & git reset --hard origin/branch-nameBuild Instructions (Windows only):
mxbmrp3.sln in Visual Studio 2022 (C++17, v143 toolset)All-Release / All-Debug → builds MXB + GPB + KRP sequentially via the build_all meta-project (default in the dropdown)MXB-Debug / MXB-Release → build/MXB-Release/mxbmrp3.dloGPB-Debug / GPB-Release → build/GPB-Release/mxbmrp3_gpb.dloKRP-Debug / KRP-Release → build/KRP-Release/mxbmrp3_krp.dlo.dlo to game’s plugins/ folderThe plugin must run efficiently at 240fps (4.17ms frame budget). Many competitive players use high refresh rate monitors. Avoid per-frame allocations, unnecessary string operations, and complex calculations in hot paths like Draw() and RunTelemetry().
new/delete)strncpy_s, snprintf)DEBUG_INFO_F() for logging (not printf)API_GUARD_CATCH("ExportName") (see vendor/piboso/api_guard.h); uncaught exceptions across the boundary crash the host gamestd::thread function bodies in a top-level try/catch; uncaught throws in threads call std::terminate()Singletons Everywhere Required by plugin API - we get one global entry point, everything branches from there.
Lambdas in settings_hud.cpp rebuildRenderData() Intentional - they capture local layout state. Alternatives were worse (passing 8+ parameters).
Public member variables on HUDs (e.g., m_enabledRows)
These are configuration data, not encapsulated state. SettingsHud needs direct access.
HUDs don’t cache raw game data
HUDs pull fresh from PluginData on rebuild - they only cache formatted render data (m_displayEntries, m_quads, m_strings).
This enforces PluginData as single source of truth and prevents synchronization issues.
Settings reset reuses save/load serialization (don’t add a third list)
“Reset to defaults” replays a startup snapshot through the same applier loadSettings() uses, never a hand-maintained list of per-setting resets. Two snapshots back this, and they are intentionally separate:
m_globalDefaultsIni — global sections (writeGlobalSettings/applyGlobalLine).m_hudFactoryDefaults — pristine per-HUD constructor defaults, captured before loadSettings() folds user base-section keys into m_hudDefaults.m_hudDefaults (sparse-save baseline, with base-section edits folded in) is not a clean factory snapshot — don’t “simplify” reset by pointing it at m_hudDefaults or by merging the two caches; that reintroduces stale-default-on-reset bugs (e.g. an upgraded HUD default not taking effect). A new setting gets reset coverage for free as long as it’s wired into save/load. See ARCHITECTURE.md “Settings & Persistence”.
Widget vs HUD Distinction Widgets (TimeWidget, PositionWidget, LapWidget, SpeedWidget, GearWidget, ClockWidget, SpeedoWidget, TachoWidget, BarsWidget, FuelWidget, LeanWidget, GForceWidget, TyreTempWidget, EcuWidget, GamepadWidget, VersionWidget, SettingsButtonWidget) are simplified HUD components with:
Full HUDs (StandingsHud, LapLogHud, PitboardHud, TimingHud, NoticesHud, StatsHud, etc.) have:
Helmet Overlay (HelmetOverlayHud) Full-screen immersion overlay — neither a widget nor a typical HUD:
[HelmetOverlay] INI section like [Rumble]Handler-to-API Event Mapping Each handler corresponds to game API callback(s), but receives unified types:
No unit tests Requires game engine to run. Manual testing in-game is current workflow.
Logger has an internal mutex
Logger::log() is called from the game thread and from at least five background threads (HttpServer, UpdateChecker, UpdateDownloader, DiscordManager, RecordsHud). The mutex serializes concurrent writes so log lines don’t interleave. Don’t remove it. The SEH crash filter deliberately doesn’t call Logger to avoid deadlocking on this mutex.
BaseHud (.h and .cpp files in mxbmrp3/hud/)mxbmrp3/mxbmrp3.vcxproj - Add <ClInclude> for .h and <ClCompile> for .cppmxbmrp3/mxbmrp3.vcxproj.filters - Add filter entries to place files in Header Files\hud and Source Files\hudrebuildRenderData() - builds vectors of quads/stringsHudManager constructor (add pointer, getter, initialize in initialize(), null in clear())SettingsHud for configurationSettingsManager (per-HUD capture/apply, or — for a global single-value setting — one line in writeGlobalSettings() and one branch in applyGlobalLine()). Reset is then automatic via the factory snapshots; no separate reset code needed.hud->isVisible()hud->isDataDirty() triggers rebuildrebuildRenderData()When implementing event handlers or debugging timing/lap data:
mxbmrp3/vendor/piboso/mxb_api.h, gpb_api.h, etc.Unified::* types, not raw game structsm_iLapNum=0 for first lap) but UI typically shows 1-based (display as “L1”)The embedded HTTP server (core/http_server.cpp) streams race data to browser-based overlays via Server-Sent Events (SSE):
buildJsonSnapshot() (PluginData access is not thread-safe)session (time, type, palette, fonts), standings[] (per-rider with all chips), events[] (all events, unfiltered)CONFIG block in app.jsplugins/mxbmrp3_data/web/ — users can customize CSS/HTML/JS freely (user overrides synced from Documents folder)Documents\PiBoSo\[Game]\mxbmrp3\web\custom.css (synced into the served folder on game start) instead of forking style.css. index.html already links custom.css; the plugin doesn’t ship a stub, so the link 404s harmlessly when no override exists. It’s loaded after style.css, served with Cache-Control: no-cache, and excluded from the SW precache + fetch handler so edits show up on the next browser reload.buildJsonSnapshot() in the appropriate section, then consume in app.jsGET /api/logos scans web/logos/ for PNGs and returns a sorted filename list. Users drop PNGs into the logos/ folder (or the Documents user-override path mxbmrp3/web/logos/), no config editing needed. Bundled logos should be added to PRECACHE_URLS in sw.js.mxbmrp3_data/web/sw.js precaches the overlay shell so OBS can render it before the plugin’s HTTP server is up. The PRECACHE_URLS list is hand-maintained — when adding new CSS/JS/font/icon assets under mxbmrp3_data/web/, also add them to PRECACHE_URLS in sw.js, otherwise they won’t be available offline until first online load. Cache name is tied to PLUGIN_VERSION (substituted server-side in http_server.cpp), so plugin upgrades auto-invalidate the cache.Unified:: struct in game/unified_types.hgame/adapters/*_adapter.h)game/game_config.h if game-specificWhen an entire feature (HUD, manager, integration) doesn’t apply to one or more games — e.g. FMX freestyle tricks on karts, Discord Rich Presence on non-MXB, the records provider on non-MXB:
GAME_HAS_X flag to game/game_config.h. Examples already in the file: GAME_HAS_DISCORD, GAME_HAS_HTTP_SERVER, GAME_HAS_FMX, GAME_HAS_RECORDS_PROVIDER. Pattern:
#if defined(GAME_MXBIKES) || defined(GAME_GPBIKES)
#define GAME_HAS_FMX 1
#else
#define GAME_HAS_FMX 0
#endif
HudManager::initialize(). Leave the member pointer as nullptr; existing null-checks downstream (if (m_pFmxHud)) will fall through silently.SettingsHud — prefer the runtime null-check pattern used for TAB_RECORDS (if (i == TAB_FMX && !m_fmxHud) continue;) over a #if block. Cleaner and reuses the nullptr you set up in step 2.settings_tab_hotkeys.cpp. The hotkey action itself can stay in the enum (the handler in HudManager::processHotkeys is already null-safe), but the row should be hidden so users don’t see a binding that does nothing.run_telemetry_handler.cpp, race_session_handler.cpp, etc.). Skip the singleton calls entirely so the binary doesn’t pull them in.SettingsManager save/load if the disabled HUD has its own profile section. Crucial when HudManager::getXxxHud() returns a Hud& with assert(m_pXxxHud) — calling it with a null member crashes in debug and null-derefs in release.mxbmrp3.nsi) if the feature has supporting data files (e.g. web/ for HTTP server) so they don’t ship to a build that can’t use them.If a .cpp file’s GAME_HAS_X reference is in a file that doesn’t transitively include game_config.h, add #include "../../game/game_config.h" (path from the file). The handlers’ plugin_data.h already pulls it in; hud_manager.h pulls it in; isolated tab files like settings_tab_hotkeys.cpp may need the explicit include.
Reference implementations to copy from: FMX (commit deba67f), Discord (GAME_HAS_DISCORD), Records provider (GAME_HAS_RECORDS_PROVIDER).
Core:
mxbmrp3/core/plugin_manager.cpp - Plugin coordinator (receives unified types)mxbmrp3/core/plugin_data.h/.cpp - Game state cachemxbmrp3/core/hud_manager.h/.cpp - HUD ownershipmxbmrp3/core/asset_manager.h/.cpp - Dynamic asset discovery (with user override support)mxbmrp3/core/font_config.h/.cpp - Font category configurationmxbmrp3/core/color_config.h/.cpp - Color palette configurationmxbmrp3/core/update_checker.h/.cpp - GitHub update checkermxbmrp3/core/update_downloader.h/.cpp - Update download and installationmxbmrp3/core/tooltip_manager.h - UI tooltip management (header-only)mxbmrp3/core/xinput_reader.h/.cpp - Controller input and rumble effectsmxbmrp3/core/rumble_profile_manager.h/.cpp - Per-bike rumble profilesmxbmrp3/core/stats_manager.h/.cpp - Unified stats, personal bests, odometersmxbmrp3/core/fmx_manager.h/.cpp - FMX trick detection and scoringmxbmrp3/core/fmx_types.h - FMX data structures (TrickType, TrickInstance, RotationTracker, etc.)mxbmrp3/core/http_server.h/.cpp - Embedded HTTP server with SSE for web overlaysmxbmrp3/core/event_log_types.h - Event log entry types and filter flagsmxbmrp3/core/crash_handler.h/.cpp - SEH minidump filter; writes .dmp to <savePath>\mxbmrp3\crashes\ on unhandled hardware faultstools/mdmp_analyze.py - Standalone minidump triage (no Windows debugger needed): prints the exception code/faulting address, maps the faulting RIP to its module, and does a heuristic live-stack scan to attribute a crash to the plugin vs the game vs a GPU/system DLL. Run python3 tools/mdmp_analyze.py <file.dmp>. Symbol-less, so it gives module+offset, not function names — pair with the matching .pdb for deeper analysis.Multi-Game Layer:
mxbmrp3/game/unified_types.h - Game-agnostic data structuresmxbmrp3/game/game_config.h - Compile-time game selectionmxbmrp3/game/adapters/mxbikes_adapter.h - MX Bikes type conversionmxbmrp3/game/adapters/gpbikes_adapter.h - GP Bikes type conversionmxbmrp3/vendor/piboso/mxb_api.cpp - MX Bikes DLL exportsmxbmrp3/vendor/piboso/gpb_api.cpp - GP Bikes DLL exportsmxbmrp3/vendor/piboso/api_guard.h - API_GUARD_CATCH macro that wraps every DLL exportHUD Base:
mxbmrp3/hud/base_hud.h/.cpp - Base class for all HUDsExample HUDs:
mxbmrp3/hud/ideal_lap_hud.cpp - Simple HUD (good starting point)mxbmrp3/hud/standings_hud.cpp - Complex HUD (dynamic table)mxbmrp3/hud/map_hud.cpp - Advanced (2D rendering, rotation)mxbmrp3/hud/event_log_hud.h/.cpp - Event log with configurable event type filtersmxbmrp3/hud/helmet_overlay_hud.h/.cpp - Full-screen overlay with telemetry-driven tilt/vibrationWeb Overlay:
mxbmrp3_data/web/index.html - Overlay HTML structuremxbmrp3_data/web/style.css - Overlay theme (CSS variables for full customization)mxbmrp3_data/web/app.js - SSE client, rendering, focus card logicSettings:
mxbmrp3/hud/settings_hud.cpp - Main settings UI classmxbmrp3/hud/settings/settings_tab_*.cpp - Individual tab implementationsmxbmrp3/hud/settings/settings_layout.cpp - Layout helper contextmxbmrp3/core/settings_manager.cpp - Persistence layerAdd podium colors for P1/P2/P3 in standingsFix position cache not being marked dirty when standings updateRefactor SettingsHud click handlers to reduce complexityclaude/descriptive-name-sessionIDclaude/analyze-comments-correctness-01EqgeCF2tcaLHWDT9xpeK1Wclaude/ and end with matching session ID, otherwise push will fail with 403mxbmrp3/core/plugin_constants.h (the PLUGIN_VERSION string used at runtime)mxbmrp3/resource.h (the VER_MAJOR/MINOR/PATCH/BUILD macros and VER_STRING consumed by the Windows DLL version info via mxbmrp3.rc)main first: Before peer reviewing a branch, fetch and bring local main up to date with origin/main. Diff and review the branch against the current main so feedback reflects the latest base, not a stale one.