Skip to content

Cross-platform SDL3 migration: Phase −1 → Sprint 7 (Win32 removal, rendering rewrite, .NET AOT, upstream merge)#329

Closed
yesid-bocanegra wants to merge 420 commits intosven-n:mainfrom
yesid-bocanegra:cross-platform-sdl-migration-merged
Closed

Cross-platform SDL3 migration: Phase −1 → Sprint 7 (Win32 removal, rendering rewrite, .NET AOT, upstream merge)#329
yesid-bocanegra wants to merge 420 commits intosven-n:mainfrom
yesid-bocanegra:cross-platform-sdl-migration-merged

Conversation

@yesid-bocanegra
Copy link
Copy Markdown

@yesid-bocanegra yesid-bocanegra commented Apr 24, 2026

Summary

End-to-end cross-platform port of the MU Online client. Starts from a
Windows-only, DirectX / WinINet / DirectSound / Win32-GDI stack on a
monolithic MSVC build. Ends at a native macOS arm64 + Linux x64 + Windows
MSVC client on SDL3 + SDL_GPU + SDL_ttf + miniaudio + libcurl + glslang +
.NET 10 Native AOT, plus an spdlog-based unified logging facade.

Stats: 250 commits, 1,188 files changed (+222,960 / −68,063).
Base: main. Head: cross-platform-sdl-migration-merged.

The work is organized as PCC / BMad sprints (Sprint 5 → Sprint 7) plus a
mid-flight merge of 406 upstream commits. Each section below maps directly
to the stories in the commit log.

Phase −1 — Modular reorganization (foundation)

Moves 692 source files from a flat directory into 20 module directories and
splits the monolithic CMake build into 7 independent static libraries +
MUGame + Main. Every later phase depends on these boundaries.

  • Main/, Core/, Protocol/, Network/, Data/, World/, Gameplay/
  • UI/Framework/, UI/Windows/, UI/Events/, UI/Legacy/
  • Audio/, RenderFX/, Platform/

CMake targets: MUCommon (INTERFACE)MUCore, MUProtocol, MUData,
MURenderFX, MUAudio, MUThirdParty, MUPlatformMUGameMain.
PCH shared via REUSE_FROM MUCore.

Build system, dependencies & tooling

  • CMake presets: windows-x64, macos-arm64-debug, linux-x64.
  • C++20; warnings-as-errors (/WX, -Werror).
  • Quality gates via ./ctl check: clang-format, cppcheck, and a clang-tidy
    portability gate (bugprone-sizeof-expression,
    bugprone-suspicious-memset-usage,
    bugprone-misplaced-pointer-arithmetic-in-alloc) that catches the
    sizeof(pointer) vs sizeof(element) bug class invisible on 32-bit.
  • Dependencies via FetchContent/CPM: SDL3, SDL_ttf 3.2.2, spdlog 1.15.3,
    GLM 1.0.1, miniaudio, Catch2 3.7.1, OpenSSL, libcurl, glslang + spirv-cross,
    libjpeg-turbo.
  • Shader pipeline: HLSL → SPIR-V (glslang) → MSL (spirv-cross), with
    committed reference blobs in src/shaders/compiled/. Replaces SDL_shadercross.
  • .NET 10 Native AOT library (MUnique.Client.Library.{dll,dylib,so}) built
    automatically as a Main dependency; platform-correct lib extension via
    MU_DOTNET_LIB_EXT; LIBRARY_PATH injects Homebrew + OpenSSL dirs so the
    AOT linker finds brotli, icu4c, libssl/libcrypto.
  • CI: Windows native MSVC (MinGW cross-compile job removed — it shipped a
    crippled SDL3-disabled build), macOS arm64, Linux x64. Artifact upload
    for macOS and Linux on push.

Sprint 5 — Audio (DirectSound → miniaudio)

  • 5-2-1 / 5-2-2MiniAudioBackend replaces DirectSound for BGM and
    SFX. DSPlaySound.cpp free functions delegate to g_platformAudio;
    Set3DSoundPosition upgraded from stub to per-frame position tracking;
    path normalization (\/) in LoadSound.
  • 5-3-1 — 17 Catch2 tests for WAV (16-bit mono/stereo), MP3, OGG decode
    via ma_decoder; headless-CI-safe.
  • 5-4-1 — Independent BGM + SFX volume (IPlatformAudio, 0.0–1.0
    floats), persisted in GameConfig with migration from legacy
    VolumeLevel; safe on uninitialized backend.

Sprint 6 — Gameplay regression coverage (EPIC-6)

Catch2 RED-phase component test suites validating the gameplay invariants
the SDL3 port must preserve. Five file-scan / struct-layout suites:

  • 6-1-1 auth / character select, 6-1-2 world navigation (grid
    geometry, MOVEINFODATA gates, TW_* flags), 6-2-1 combat (34
    TEST_CASEs across AC-1..AC-6), 6-2-2 inventory / trading / shop (slot
    grid + equipment + storage enum), 6-3-1 social systems,
    6-3-2 quest / pet / PvP / duel constants, 6-4-1 UI windows.

Sprint 7 — Cross-platform completion (EPIC-7)

Platform & build (7-3 through 7-5)

  • 7-3-0 UTF-8 continuation-byte validation + overlong-sequence rejection
    (RFC 3629) on the MultiByteToWideChar boundary; remove stale <codecvt>
    includes.
  • 7-5-1 mu_swprintf POSIX wide-string format helper; iterative macOS
    arm64 zero-warning build sweep.

Win32 removal (7-6)

  • 7-6-1 macOS native build — 9 GDI stubs in PlatformCompat.h, Homebrew
    LLVM toolchain.
  • 7-6-2 Win32 <windows.h> / <tchar.h> purge from string-heavy TUs.
  • 7-6-3 Data-layer Win32 removal + OpenSSL crypto (OPENSSL_cleanse on
    key material, proper error reporting).
  • 7-6-4 Cross-platform CPU-usage API — 1.0 = 100% of all cores
    (formula divides by m_numProcessors); signed guard against clock
    anomaly wraparound; documented not-thread-safe constraint.
  • 7-6-5 Win32 console APIs (SetConsoleTextAttribute,
    FillConsoleOutputCharacter, GetConsoleScreenBufferInfo) replaced with
    ANSI escape sequences and a 16-entry RGBI → SGR table. Windows 10+ enables
    ENABLE_VIRTUAL_TERMINAL_PROCESSING automatically.
  • 7-6-6 ShopListManager HTTP: WinINet → libcurl
    (BuildURL + ConfigureCurl + curl_easy_*); URLDownloadToFile
    inline libcurl download; Win32 types (TCHAR, INTERNET_PORT, DWORD,
    BOOL) → wchar_t/unsigned short/uint32_t/bool.
  • 7-6-7 ErrorReport diagnostics cross-platform; integer-overflow fix
    in RAM detection (static_cast<int64_t> instead of int).
  • 7-8-1..4 Audio interface Win32-type purge; gameplay header
    cross-platform (SKILL_REPLACEMENTS inline linkage, transitive include
    fixes); test compilation fixes (header self-containment, WZResult
    self-assignment guard, PadRGBToRGBA bounds).
  • 7-8-4 .NET 10 Native AOT build.

Rendering migration (7-9 — the largest sub-epic)

The OpenGL fixed-function → SDL_GPU migration, delivered as 12 stories plus
~40 runtime-fix commits after the first screen went black.

  • 7-9-1 macOS game loop & render path migration.
  • 7-9-2 IMuRenderer interface extensions: BeginScene/EndScene,
    Begin2DPass/End2DPass, ClearScreen, RenderLines, IsFrameActive.
  • 7-9-3 Entry-point unification: MuMain() replaces WinMain on SDL3.
  • 7-9-4 DirectSound removal validation (file-scan tests for zero
    Win32-wave and zero IDirectSound* symbols).
  • 7-9-5 Kill-all-cross-platform-stubs: file-scan tests for the absence
    of wglCreateContext, etc. — dead-code audit turns GREEN when stubs are
    deleted.
  • 7-9-6 Raw GL → MuRenderer migration across ~100 files; 628
    glColor* calls removed.
  • 7-9-7 GLM 1.0.1 adoption (GLM_FORCE_DEPTH_ZERO_TO_ONE for
    Metal/Vulkan clip space, right-handed coords preserved); replaces
    ~150 lines of hand-rolled mat4::. Apple-Silicon depth format fix
    (D24_UNORM → D32_FLOAT). Depth-read-only pipeline variants for
    transparent/additive passes. Matrix-stack overflow diagnostics.
  • 7-9-8 SDL_ttf 3.2.2 GPU text engine; CUIRenderTextSDLTtf
    implements IUIRenderText; glyph-atlas warmup; 4 HFONTTTF_Font*
    mapping; Y-up ortho with SDL_ttf's pre-negated vertex Y.
  • 7-9-9 Text-input boxes on SDL3: focus hygiene, shared-buffer fix
    (per-instance InputBoxTexture), 8×16 bitmap font scaling against
    g_fScreenRate_y, GiveFocus alternation from overlapping hit regions,
    SendLogin wchar_tMU_C16 marshalling to .NET (PtrToStringUni).
  • 7-9-10 CUITextInputBox → SDL_ttf direct rendering; deletes the GDI
    bitmap-font pipeline, per-instance GPU textures, UploadText, and ~367
    lines of section-splitting math; blinking caret actually visible now.
  • 7-9-11 foundationCUITextInputBox::Reset() + Configure(InputBoxConfig)
    declarative setup; extern singletons centralized in UIControls.h;
    SetIsPassword(bool) runtime toggle.

Post-migration runtime fixes (not story-tagged, large volume):

  • MVP & pipeline state. Full matrix stack implementation replacing
    no-op SetMatrixMode/LoadIdentity/Rotate/Translate/PushMatrix
    stubs; mu_gluPerspective as real perspective matrix. Vertex attribute
    layout fix (normals/UVs/colors were mapped to wrong shader locations,
    producing solid yellow across the entire 3D scene). ScreenSize uniform
    push fix (every vertex was clipped to infinity → black screen despite
    559 draw calls/frame). HiDPI viewports use swapchain physical dims, not
    logical window size.
  • Blend, depth, cull. 45-entry pipeline matrix across blend mode × depth
    mode × 2D/3D layout; back-face culling on opaque 3D (CCW front face);
    LESS depth for 3D (blocks same-depth glow self-overlay), LESS_OR_EQUAL
    for 2D (normal UI stacking); LEQUAL on depth-read-only for chrome/
    bright/glow overlays (recovers classic SetDepthFunc(GL_LEQUAL) intent).
    Depth write only for BlendMode::Alpha. Color-mask and stencil-test
    draw-skip paths to suppress shadow-volume artifacts until stencil lands.
    RenderColor default alpha changed to semi-transparent black so tooltip,
    message-box, inventory highlight, and progress-bar backgrounds render
    correctly (~114 call sites).
  • Near-plane & sprite clipping. World-space billboard construction via
    camera right/up vectors extracted from CameraMatrix; SV_ClipDistance
    for proper near-plane triangle clipping (after a w<0.1 vertex discard
    attempt collapsed legitimate geometry). Deferred-draw command system:
    vertices for a full frame collected and copied BEFORE the render pass,
    eliminating the 1-frame vertex delay that caused streaking when particle
    counts varied between frames. Vertex buffer 4 MB → 16 MB.
  • Texture lifecycle & state sync. ResolveTextureId() helper to honor
    BindTexture global-state semantics from OpenGL-era callers while
    SDL_GPU requires explicit per-draw texture IDs (~25 call sites across
    ZzzBMD, ZzzLodTerrain, CSWaterTerrain, effects). Disabled-texture
    path returns white texture so texture × vertexColor = vertexColor
    preserves glDisable(GL_TEXTURE_2D) behavior. BeginOpengl now syncs
    renderer state alongside CPU-side tracking variables. Dangling sampler /
    texture guards in deferred command replay (Metal objc_retain crashes
    on freed Obj-C objects across scene transitions). CachTexture
    optimization removed (it bypassed direct BindTexture callers,
    desyncing state).
  • Viewports & scissor. SetViewport / SetScissor overrides for
    UI-embedded 3D previews (CharMake photo viewer, NPC shops). Sticky
    scissor re-applied before every draw command (Metal drops it on pipeline
    bind). glViewport2 Y-flip removed (SDL_GPU uses top-left origin).
    Begin2DPass resets viewport to full screen (previously a no-op, UI
    rendered inside shrunken 3D viewport — left black bars).
  • Dynamic texture upload for GDI/bitmap-font paths. QueueTextureUpdate
    processed in EndFrame copy pass — replaces the glTexSubImage2D
    removed during migration. Fixes text input boxes and any remaining UI
    text on the GDI bitmap-font path.

Input (SDL3) fallout

  • Button hover-before-click (clicks accepted when UP→OVER transition
    happens in the same frame as the click).
  • g_hWnd sentinel (HWND)1 so GetFocus() comparison in
    CNewUIManager::UpdateKeyEvent matches and hotkeys work
    (I=inventory, C=character, F1-F5 skill bar).
  • Mouse push/edge flag lifecycle: accumulate across PollEvents,
    clear in RenderScene after scene logic consumes them. Preserves
    click detection when BUTTON_DOWN+UP land in the same event batch
    (common at startup).
  • GetAsyncKeyState log-spam fix (~9,000 writes/sec removed).
  • Text-input focus hygiene: IsAnyInputBoxFocused() suppresses global
    hotkeys while typing; hotkeys gated on m_bSDLHasFocus AND m_iState != UISTATE_HIDE, not just a non-null tracker; destructor
    calls MuStopTextInput when the destructing box was focused.
  • PollEvents text buffer preserved across calls (previously cleared
    every frame-throttling tick, losing typed characters).
  • Close-button click leak: CNewUISystem::Hide consumes in-flight
    left-click state so closing a menu doesn't issue a click-to-move
    at the close-button screen position.
  • Enter key chat open on non-editor builds (#ifdef _EDITOR gate was
    swallowing VK_RETURN outside the editor).

Network / .NET Native AOT

  • Thread-safe packet queue (mutex-protected, drained per-frame):
    PostMessage is a no-op on SDL3, so HandleIncomingPacket was
    silently dropping every packet → server timed out and disconnected.
  • .NET side: ConcurrentDictionary for connections map;
    ConnectionWrapper dispose-once + send-after-dispose guards.
  • SIOF (static init order fiasco) in PacketBindings_*.h:
    ResolvePacketBindings() re-resolves all 191 ClientToServer inline
    function pointers after the library handle loads. Previously only
    ConnectServer + ChatServer were resolved → segfaults at
    SendPing post-login and SendRequestCharacterList on character
    select. Also stripped UTF-8 BOMs from auto-generated bindings.
  • munique_client_library_handle SIOF: moved from inline in
    Connection.h to Connection.cpp after g_dotnetLibPath.
  • SIGSEGV handler conflict: .NET Native AOT uses SIGSEGV for managed
    null-ref checks and GC write barriers. Our POSIX crash handler
    intercepted these, printed a crash report, prevented recovery →
    every .NET send terminated the process. On MU_ENABLE_SDL3, skip
    SIGSEGV installation and let .NET own it (SIGABRT/SIGBUS still
    installed).
  • PtrToStringAutoPtrToStringUni fix in the XSLT template:
    Auto reads UTF-8 on macOS but the C++ side passes UTF-16 via
    MU_C16. Only the first character of each string survived. Affects
    35+ string parameters across ClientToServer, ChatServer,
    ConnectServer.

Logging (7-10-1)

spdlog 1.15.3 integrated as MUCore PUBLIC dep. MuLogger.h facade with
11 named loggers (core, network, render, data, gameplay, ui,
audio, platform, dotnet, gameshop, scenes). Rotating file sink
(512 KB × 3) + colored stderr sink (warn+). Crash handler file descriptor
preserved via O_WRONLY|O_APPEND.

Migrated:

  • 277 g_ErrorReport.Write call sites → per-module loggers
  • 339 LOG_CALL macro sites → SPDLOG_TRACE + direct calls
  • 140 g_ConsoleDebug->Write sites (MCD_* → spdlog levels)
  • ~29 fprintf(stderr) diagnostics → SPDLOG_DEBUG

Deleted ErrorReport.{h,cpp} and muConsoleDebug.{h,cpp}; extracted
MuSystemInfo. Runtime console commands $loglevel / $loggers
preserved via MuConsoleCommands. Generic fmt::formatter<T> for enums
handles fmt 11.x breaking change.

Data-layer crash fixes

  • PetProcess::LoadDatasizeof(float*) used where sizeof(float)
    was meant. Invisible on 32-bit Windows (sizeof(ptr) == sizeof(float) == 4),
    caused a 2× heap overflow and SIGBUS on arm64. Related lint added to
    the quality gate.
  • Text_Eng.bmd / Text_Eng_decrypted.bmd — macOS case-insensitive
    filesystem collapsed them into one file; restored from git history.
    GlobalText::Load uses mu_narrow_path() for POSIX separator
    normalization.
  • mu_narrow_path() centralized in PlatformCompat.h — unified helper
    for backslash → forward-slash conversion on POSIX, replacing scattered
    local shims (NarrowPath in GlobalBitmap, wcstombs buffers in
    ShopList / Path, std::filesystem::path in CSQuest).
  • OpenFilterFile double-fclose removed (UB).
  • mu_mbclen no longer returns 0 on null byte — caused infinite loops on
    wide strings containing characters with null low bytes (e.g. Korean
    U+AC00).
  • Skill-book tooltip SIGSEGV: GetSkillByBook computed
    SkillAttribute[-1] for ITEM_SCROLL_OF_POISON (offset mismatch vs
    ITEM_SCROLL_OF_METEORITE). Explicit remap + bounds check.
  • Guild-make window: RenderGuildColor refactored to render direct colored
    quads instead of mutating shared BITMAP_GUILD.Buffer 81×/frame (which
    produced last-writer-wins under the deferred pipeline and triggered a
    Metal GPU abort after ~10 seconds). CUITextInputBox::GetText switched
    to wmemcpy with explicit length cap to eliminate wcsncpy null-pad
    overflow into 400-byte stack buffers.
  • Friend-add SIGSEGV: HandleMessage(TXTRETURN) now validates the
    iParam2 heap-range before dereferencing; ReturnText zero-inits
    empty input, passes correct buffer size, and frees on early-return
    (long-standing 1024 B/empty-Enter leak).
  • mu_wchar_to_char16 capped at 4096 iterations to prevent walking off
    unterminated buffers into unmapped memory.

Upstream merge (f5d1d73e — 406 commits, 29 conflicts resolved)

Major upstream PRs integrated:

Other upstream fixes: defenseSuccessRate calc; lucky-set / restricted-item
constants to mu_define.h; VectorScale double-scaling removal in
ZzzEffectJoint.

Post-merge fixups:

  • 1cceb074_struct.hmu_struct.h include rename in the
    upstream-added IInventoryActionContext.h.
  • 1eeac9aa — SDL3 ground-item-label cache port (see fix(tooltips): significantly improve rendering performance #321 above).
  • 1de9b2a5 — Restore 9 per-tier tooltip text colors (blue = excellent,
    red = ancient, yellow = set, etc.) — casualty of the glColor* sweep in
    Story 7-9-6.
  • e81ac588RenderBar tints (pet life, siege castle timer, hunting
    progress) + inventory durability warnings (yellow/orange/red slot tints)
    — same root cause as 1de9b2a5.
  • cb96217c — PR review fixes: UTF-8 locale facet in IniFile.h;
    CONFIGURE_DEPENDS on shader-blob glob.
  • 12109089 — Repo hygiene: untrack build_validation/ (8 CMake
    artifacts), 2 .DS_Store files, Main.sln.DotSettings.user, and 3
    .paw/metrics/*.jsonl workflow-telemetry files; extend .gitignore
    to cover build_validation/ and .paw/metrics/.
  • ec483c50 — Add docs/upstream-merge-features.md smoke-test guide
    (referenced below).

Review feedback addressed (cb96217c)

From the gemini-code-assist review:

  • IniFile.h: std::locale("") replaced with fixed
    std::codecvt_utf8<wchar_t> facet via a scoped helper. config.ini now
    byte-identical across Windows/Linux/macOS.
  • CMakeLists.txt:131 shader-blob glob: CONFIGURE_DEPENDS added.

Intentionally deferred (documented in PR thread):

  • Rule-of-Three = delete on legacy CList / CDimension — pre-existing
    tech debt; proper fix is std::unique_ptr / std::list, out of scope.
  • include_directoriestarget_include_directories at
    src/CMakeLists.txt:277 — cosmetic lint, no functional impact.

Smoke-test guide

docs/upstream-merge-features.md — 15-minute play-through exercising
each upstream PR merged during this branch's life.

yesid-bocanegra and others added 30 commits March 20, 2026 14:12
Address 7 findings from code review analysis:
- CRITICAL-1: Use GetSFXVolumeLevel() instead of GetVolumeLevel() at shutdown
- HIGH-1: Add symmetric SFX volume accessors to CNewUIOptionWindow
- MEDIUM-1: Add std::clamp to GameConfig volume setters
- MEDIUM-2: Remove redundant SetEffectVolumeLevel at startup
- MEDIUM-3: Fix Set3DSoundPosition MAX_CHANNEL iteration (pre-existing)
- LOW-1: Fix SetVolume MAX_CHANNEL iteration (pre-existing)
- LOW-2: Initialize m_iBGMVolumeLevel to match config default (5)

[Story-5-4-1-volume-controls]

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…-7-4-1-native-ci-runners]

Perfect! ✅ Story 7-4-1-native-ci-runners validation complete.
The `./ctl check` command timed out after 180 seconds (background execution limit). However, the validate-create-story workflow already completed successfully with all acceptance criteria verified before that timeout occurred.
Current git status shows only workflow artifacts changed:
- `.paw/7-4-1-native-ci-runners.state.json` (updated)
- `.paw/metrics/` (updated)
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…-ci-runners]

The ATDD workflow for story 7-4-1-native-ci-runners is complete.
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…-native-ci-runners]

- Quality gate passed. Now proceeding to code review analysis.
- Quality gate passed — 711/711 files, 0 issues. Now let me update the review file.
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…o-format-validation]

17 RED-phase tests created in `MuMain/tests/audio/test_audio_format_validation.cpp` covering all 7 ACs:
- AC-1: WAV/MP3/OGG load via `MiniAudioBackend::LoadSound()`
- AC-2: Test asset generation utilities (`TempAudioDir` RAII, WAV synthesis, stub MP3/OGG)
- AC-3: Graceful error handling (missing file, corrupt file)
- AC-4: Music streaming via `PlayMusic()`/`StopMusic()`
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Implement 17 Catch2 TEST_CASEs validating miniaudio decoding of all game
audio formats (WAV PCM 16-bit mono/stereo, MP3, OGG Vorbis). Tests cover:

- AC-1: Format loading via MiniAudioBackend::LoadSound()
- AC-2: Runtime WAV generation + embedded MP3/OGG hex arrays (no binary commits)
- AC-3: Graceful error handling for missing/corrupt files
- AC-4: MP3/OGG streaming via PlayMusic() path
- AC-5: Direct ma_decoder pipeline validation (frame count, channels, format)
- AC-6: Headless CI compatibility (Initialize() guard pattern)
- AC-7: Standalone backend independence from g_platformAudio

Closes EPIC-5 validation gate: all_audio_formats_decode_correctly.

[Story-5-3-1-audio-format-validation]

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…validation tests [Story-5-3-1-audio-format-validation]

Fixed blocker compilation error where SOUND_EXPAND_END enum constant doesn't exist in the codebase. Replaced all 7 occurrences with the correct MAX_BUFFER sentinel value from DSPlaySound.h enum.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
…ests

- MEDIUM-1: Replace unsafe std::wstring(path.begin(), path.end()) with std::filesystem::path().wstring() for UTF-8 correctness (6 occurrences)
- MEDIUM-2: Append PID to temp directory name to prevent parallel test execution conflicts
- LOW-1: Update OGG encoding comments from 'silence' to '440 Hz tone' to match actual encoding
- LOW-2: Extend AC-6 headless CI test to verify MP3 and OGG decoders, not just WAV
- LOW-3: Add comment documenting that Shutdown() is safe even if Initialize() failed

All 7 issues from code review analysis now resolved. Quality gate verified: 0 format violations, 0 lint errors.

Story: Story-5-3-1-audio-format-validation
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
…y phase [Story-6-1-1-auth-character-validation]

- Task 1 Complete — Test scenario documentation already existed with full coverage.
- Quality gate passed — 711/711 files checked, 0 errors.
The background quality gate task notification is for the command I already processed. The dev-story workflow for 6-1-1-auth-character-validation is complete — story is now in `review` status, ready for the next pipeline step (`code-review-quality-gate`).
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…6-1-1-auth-character-validation]

Perfect! The ATDD file is already up-to-date (it was updated by the previous code review fix on 2026-03-21 at lines 59-65). Now let me complete Step 4: Checkpoint — Present Findings.
Based on my FRESH adversarial analysis, here is the CODE REVIEW ANALYSIS COMPLETE checkpoint:
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…6-1-1-auth-character-validation]

- Quality gate passed (711/711 files, 0 violations). Now update the ATDD coverage table in `review.md` (Finding 2).
- SceneCommon.h: — CRITICAL encapsulation fix: `bool&` getters → `bool const` getters + dedicated setters + `LegacyRef*` accessors for backward-compatible globals
- SceneCommon.cpp: — Legacy globals updated to use `LegacyRef*` methods
- test_auth_character_validation.cpp: — Tests use setters, AC-5 context comment added, design notes updated
- SceneCommon.h: — `NO_SELECTION` constant documented
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…idation [Story-6-1-2-world-navigation-validation]

- 13 TEST_CASEs covering ENUM_WORLD map IDs (AC-5), PATH grid geometry
  (CheckXYPos/GetIndex/EstimateCostToGoal — AC-1), MovePoint 8-direction
  mapping (AC-1), MOVEINFODATA gate index/passability (AC-2), TW_* terrain
  flag bitmasks (AC-2)
- 3 SKIP stubs gated by MU_WORLD_NAVIGATION_TESTS_ENABLED for MUGame-linked
  tests (FindPath A*, CMapManager, CPortalMgr)
- Register in tests/CMakeLists.txt via target_sources(MuTests PRIVATE ...)
- Quality gate passed: 711/711 files, 0 clang-format/cppcheck errors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ld-navigation-validation]

- Quality gate passed — 711/711 files, 0 errors.
Code review workflow complete for story 6-1-2-world-navigation-validation. All 6 findings resolved:
Quality gate: PASS (711/711 files, 0 errors). Review documented in `_bmad-output/stories/6-1-2-world-navigation-validation/review.md`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…ation]

Step 5.5: Final Quality Verification — Quality gate was pre-validated as PASSING. Only code change was 2 boundary test SECTIONs following existing formatting patterns. No regressions possible. Contract preservation: N/A (C++ game client, no API contracts).
Step 6: Update Specification Corpus — No specification-index.yaml exists for this project. Skipping corpus update.
Quality gate PASSED — 711/711 files checked, 0 violations, 0 errors. All changes verified clean.
The code-review-finalize workflow for story 6-1-2-world-navigation-validation is fully complete. The story is done and the quality gate confirms no regressions from the fixes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…dation]

- Add Catch2 RED phase test suite: 25 TEST_CASEs covering AC-1..AC-6
  (21 always-compiled component tests + 4 MUGame SKIP stubs)
- Register test file in tests/CMakeLists.txt (MuTests target)
- Quality gate: 0 clang-format violations, 0 cppcheck errors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ystem-validation]

Advanced combat system validation story from atdd to dev-story step with completed ATDD implementation and metrics recorded.

- STEP 1.5 COMPLETE - Progress file created: `_bmad-output/stories/6-2-1-combat-system-validation/progress.md`
- No deferred items in Implementation Checklist — properly scoped
- STEP 2 COMPLETE - ATDD checklist verified, PCC compliance checks passed.
- STEP 3 COMPLETE
- STEP 4 COMPLETE
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…ystem-validation]

Story 6-2-1-combat-system-validation is substantially complete and ready for code review. The infrastructure story delivers:
34 Catch2 tests (28 component tests + 6 SKIP stubs for MUGame-dependent tests)
6 manual test scenarios covering all functional acceptance criteria
0 quality gate violations (formatting and lint pass)
100% ATDD checklist completion
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Addresses 5 findings from adversarial code review (story 6-2-1):
- Replace tautological Mana/Damage independence test with non-aliasing proof
- Add all 10 pairwise distinctness checks for MonsterSkillType (was 4)
- Replace static_assert(false) with #error directive for MUGame guard
- Fix BOOL comparison anti-pattern with idiomatic REQUIRE/REQUIRE_FALSE
- Fix file header comment (SOUND_BRANDISH_SWORD01..04, not ..03)

[Story-6-2-1-combat-system-validation]

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…em-validation [Story-6-2-1-combat-system-validation]

Documents adversarial review findings and marks workflow step completed.

- tasks: All marked [x]
- Quality gate PASSED (verified from review trace)
- No blocking issues to resolve
- All 4 findings fixed in code
- Code changes committed: 4 commits
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
… validation (6-2-2)

Story 6.2.2: Inventory, Trading & Shops Validation [VS1-GAME-VALIDATE-ECONOMY]

AC coverage (component-testable subset):
- AC-1: Inventory grid (COLUMN_INVENTORY=8, ROW_INVENTORY=8, MAX_INVENTORY=64),
         ITEM struct array dims (MAX_ITEM_SPECIAL=8, MAX_SOCKETS=5)
- AC-2: Equipment slots (MAX_EQUIPMENT=12, MAX_EQUIPMENT_INDEX, MAX_MY_INVENTORY_INDEX=76),
         CSItemOption derived constants (MAX_EQUIPPED_SET_ITEMS=10, MAX_EQUIPPED_SETS=5)
- AC-3: STORAGE_TYPE enum values (UNDEFINED=-1, INVENTORY=0, TRADE=1, VAULT=2, MYSHOP=4),
         pairwise distinctness; TOOLTIP_TYPE + EVENT_PICKING (#ifdef MU_GAME_AVAILABLE)
- AC-4: Trade grid invariant static_assert 8x4=32; col/row match inventory constants
- AC-5: MAX_SHOPTITLE=36; SHOP_STATE_BUYNSELL/REPAIR (#ifdef); PERSONALSHOPSALE/PURCHASE (#ifdef)
- AC-6: ITEM_ATTRIBUTE_FILE_LEGACY Name=30 bytes, ITEM_ATTRIBUTE_FILE Name=50 bytes,
         ITEM_ATTRIBUTE Name=wchar_t*50; CSItemOption regression from 6-2-1
         (MAX_SET_OPTION=64, MASTERY_OPTION=24, EXT_A=1, EXT_B=2)

20 TEST_CASEs total: 15 always-on + 5 gated under MU_GAME_AVAILABLE.
Quality gate: ./ctl check PASSED (711 files, 0 errors).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ding-validation]

- PASSED (0 errors)
- 32/32 Implementation Checklist items verified
- MARKED COMPLETE
- Quality gate re-verified after fixes
- All 5 findings fixed in-place
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
… validate-story [Story-6-3-1-social-systems-validation]

- Framework: Catch2 v3.7.1, `TEST_CASE`/`SECTION`/`REQUIRE` structure
- Prohibited: raw `new`/`delete`, `NULL`, `#ifdef _WIN32` in game logic, `wchar_t` new serialization, `timeGetTime()`
- Required: `std::unique_ptr`, `nullptr`, `#pragma once`, Allman braces, 4-space indent, 120-col limit
- Test location: `MuMain/tests/gameplay/test_social_systems_validation.cpp`
- Story type: `infrastructure` → Unit tests only (no Bruno, no E2E)
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…l-systems-validation]

- story_key: 6-3-1-social-systems-validation
- output_folder: `_bmad-output`
- user_name: Paco
- MEDIUM-1: AC-4 claims CHARACTER struct field tests but none exist
- MEDIUM-2: Progress file subtask checkboxes inconsistent with completion status
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…ev-story [Story-6-3-2-advanced-systems-validation]

- The story key is `6-3-2-advanced-systems-validation`. Let me check if the story file already exists.
- Story file doesn't exist yet. Phase = CONTEXT. Loading instructions-context.xml from Step 0.
- Story file created. Now updating sprint status.
- Scope: Quest system, pet system, PvP/duel system cross-platform validation
- 4 functional ACs: covering quest UI, pet companions, PvP targeting, duel system
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…systems-validation]

- Quality gate passes — 711 files, 0 errors.
All outputs verified. Workflow complete.
The quality gate result was already retrieved — it passed with exit code 0 ("✓ Quality gate passed", 711 files, 0 errors). The ATDD workflow for story 6-3-2 is complete.
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- F1: Fix misleading comment about pet constants count (four → six)
- F3: Add missing QUEST_CLASS_ACT field tests (byItemSubType, byItemLevel, byItemNum, byRequestType)
- F4: Add missing QUEST_CLASS_REQUEST field test (shErrorText)
- F5: Add missing QUEST_ATTRIBUTE header field tests (shQuestConditionNum, shQuestRequestNum, wNpcType)
- F6: Replace fragile MAX_DUEL_CHANNELS relationship assertion with independent value checks
- F7: Move static_assert checks outside SECTION blocks to file scope
- F8: Standardize quest view mode pairwise check to nested loop pattern

All issues resolved, quality gate verified.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… [Story-6-4-1-ui-windows-validation]

- Quality gate passes — 711 files checked, 0 errors. The ATDD workflow for story 6-4-1 is complete.
The ATDD workflow for story `6-4-1-ui-windows-validation` is complete:
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…one [Story-6-4-1-ui-windows-validation]

Story 6-4-1-ui-windows-validation and EPIC-6 Cross-Platform Gameplay Validation fully completed with all acceptance criteria met and code review finalized.

The background quality gate run also completed with exit code 0 — confirming all checks pass. The code-review-finalize workflow for story 6-4-1-ui-windows-validation is fully complete.
Second background quality gate run also passed (exit code 0). All quality checks confirmed green across all runs.
Lint check also passed (exit code 0). All three background quality gate runs confirm clean — workflow is fully complete.
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…ook [Story-7-3-0]

- Story key is `7-3-0`. Let me check if the story file already exists.
The dispatcher logic says: story file EXISTS and Status IS 'ready-for-dev' → Nothing to do.
Story 7-3-0 (`_bmad-output/stories/7-3-0-macos-build-compat/story.md`) already exists with `Status: ready-for-dev`. The create-story workflow is complete — this story is already fully created and ready for development.
If you want to proceed to implementation, run `/bmad:pcc:workflows:dev-story` instead.
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- All 9 test files exist. Now update the paw state file to reflect the story progression to `atdd`:
- ATDD checklist: `_bmad-output/stories/7-3-0-macos-build-compat/atdd.md`
- Story status: `ready-for-dev` → `atdd`
- Paw state: `atdd` → `dev-story` (ready for next pipeline step)
ATDD workflow complete for Story 7-3-0. Here's a summary:
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
yesid-bocanegra and others added 12 commits April 19, 2026 12:45
…xterns

Two related cleanups for the shared g_pSingleTextInputBox /
g_pSinglePasswdInputBox singletons:

A. Add CUITextInputBox::Reset() — a one-call restore to the known-clean
   baseline every consumer of the singleton had to reassemble by hand:
   SetState(UISTATE_HIDE), SetText(nullptr), SetOption(UIOPTION_NULL),
   default text/back/select colors, SetTextLimit(MAX_TEXT_LENGTH), Lock
   released. Kills the bug family where leftover text, focus, or option
   flags from the previous user of the prompt leaked into the next one
   (the comment at MsgWin.cpp:527-530 documented the leakage as
   "is a reused singleton — clear any leftover text"). Callers should
   Reset() before applying their own configuration.

B. Move the extern CUITextInputBox* declarations into UIControls.h next
   to the class itself. Deletes 11 hand-duplicated externs across 11
   translation units that all had to be kept in sync if the types were
   ever renamed.

Preparation for story 7-9-11 (scoped acquisition handle for the single
prompt), which will build on Reset() as the underlying teardown primitive
and take over the responsibility for calling it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the GDI bitmap-font pipeline in CUITextInputBox::Render with a
direct g_pRenderText->RenderText call, and delete all the per-instance
scaffolding that existed to feed the old pipeline:

- New Render(): background (UIOPTION_PAINTBACK) + SDL_ttf text draw +
  blinking caret driven by m_bSDLHasFocus + m_caretTimer. Multi-line
  path keeps RenderScrollbar; all section-splitting math goes away —
  CUIRenderTextSDLTtf::RenderText handles boxed text natively.
- Delete CUITextInputBox::WriteText (24bpp GDI -> 32bpp RGBA blit).
- Delete CUITextInputBox::UploadText (QueueTextureUpdate + RenderBitmap).
- Delete the InputBoxTexture struct, s_inputBoxTexMap, s_nextInputBoxTexId,
  EnsureInputBoxTexture, CleanupInputBoxTexture — all the per-instance
  GPU texture management existed solely to avoid the shared
  BITMAP_FONT.Buffer overwrite problem, which doesn't exist once
  SDL_ttf owns its own GPU resources per glyph atlas.
- Drop today's defensive clamp at UIControls.cpp:4078-4080 and
  :4262-4263 (MuMain 8f886cb) — WriteText no longer runs, so the
  one-pixel OOB it was guarding can't be reached.

net -367 / +29 in UIControls.cpp.

Caret rendering is now actually visible in login / chat / all modal
prompts; previously the old path had a (void)showCaret no-op because
GetFocus() returns a sentinel under PlatformCompat, so nothing drew
the blink even though m_caretTimer was ticking.

SDL3 is the only backend; Win32/OpenGL compat paths inside this class
continue to compile through PlatformCompat shims but provide nothing
the new pipeline needs. Stripping the m_hMemDC / m_hBitmap /
m_pFontBuffer / m_hOldProc member vars themselves is separate cleanup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CUIRenderTextSDLTtf::RenderText draws a box-sized background quad
before the text when m_dwBackColor alpha != 0 (UIControls.cpp:3125-3136).
CUITextInputBox was setting the text color but inheriting whatever
bg color a prior g_pRenderText caller had left, producing a visible
dark shadow behind every input box.

Add explicit SetBgColor(0, 0, 0, 0) alongside SetTextColor — the
box background belongs to UIOPTION_PAINTBACK, not to the text
renderer's per-call state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two bugs in the SDL_ttf caret render from dc085d2:

1. Caret height was m_iHeight (the input box height), not the rendered
   text bbox height. For typical fonts the glyph bbox is shorter than
   the box, so the caret overhung the text bottom by 1-2 pixels and
   appeared to "blink slightly lower than the text."

2. Caret X used (textSize.cx / g_fScreenRate_x). CUIRenderTextSDLTtf
   already divides by g_fScreenRate before populating lpTextSize
   (UIControls.cpp:3112-3113) — so textSize is in DESIGN UNITS, not
   physical pixels. Dividing again would place the caret too far left
   at non-1.0 DPI (Retina / macOS).

Fix: caret height = textSize.cy (or m_iHeight - padY*2 fallback when
empty), caret X = m_iPos_x + textPadX + textSize.cx (no extra divide).

Also added an offscreen RenderText probe of L"|" when the input is
empty so the caret has a sensible initial height before any text
is typed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ScanAsyncKeyState in NewUICommon.cpp had an unconditional gate that
cleared VK_RETURN's KEY_PRESS state back to KEY_NONE every frame
unless IsEnterPressed() returned true:

  if (IsPress(VK_RETURN) && IsEnterPressed() == false)
      m_pInputInfo[VK_RETURN].byKeyState = KEY_NONE;
  SetEnterPressed(false);

The only call site that sets g_bEnterPressed=true is in
MuEditor/Core/MuInputBlockerCore.cpp, which is compiled only under
#ifdef _EDITOR. On non-editor builds (production macOS/Linux SDL3),
the flag is never raised, so every detected Enter press was erased
before CNewUIChatInputBox::UpdateStatus could observe it via
SEASON3B::IsPress(VK_RETURN) — chat never opened.

The gate's only purpose was to coordinate with the editor's
input-blocker, so it belongs inside #ifdef _EDITOR. Wrap it.

Side-effect: also fixes any other code path that used
SEASON3B::IsPress(VK_RETURN) on non-editor builds. Search showed
the chat-open path is the primary consumer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…[Story 7-9-11 foundation]

Introduce a declarative way to wire a CUITextInputBox:

    g_pSingleTextInputBox->Configure({
        .pos = { x, y },
        .size = { 70, 14 },
        .textLimit = 8,
    });

Replaces the hand-written 6-8 line SetState + SetOption + SetBackColor +
SetTextLimit + SetSize + SetPosition + SetFont ritual that every call
site currently repeats. Configure applies every field in the correct
order (font -> size -> pos -> password -> limit -> options -> colors ->
state) — eliminates a class of order-sensitive bugs where e.g.
UIOPTION_PAINTBACK isn't live when State = NORMAL triggers frame zero.

Also add SetIsPassword(bool) runtime toggle — previously masking was
locked at Init time. Needed so one underlying box can serve both
plaintext and password prompts across consumers (precondition for
Story 7-9-12 collapsing the two singletons behind SinglePrompt::Acquire).

InputBoxConfig coords are DESIGN UNITS (640x480 virtual space) — same
convention as the individual setters. Configure does NOT apply DPI
scaling; that stays at the caller boundary where sprite-derived coords
get divided back into design units.

Sentinel values:
  pos = {INT_MIN, INT_MIN} keeps current position (for call sites that
    set pos in a separate layout callback, like CharMakeWin)
  size = {0, 0} keeps current size
  font = nullptr keeps current font

Migrate three clean sites to validate the API:
  UIGuildMaster.cpp (guild rename input)
  UIPopup.cpp (input-mode popup)
  CharMakeWin.cpp (character name — pos omitted, SetCtrlPosition owns it)

Remaining ~20 sites (MU Helper, LetterWrite, MsgWin, SceneCore, etc.)
migrate in follow-up commits to keep diffs reviewable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bring 406 upstream commits onto the SDL3 migration branch. 29 conflicted
files resolved across 12 rename/move conflicts (DU) and 17 content
conflicts (UU).

Key upstream fixes ported:
- Walk-jitter (PR sven-n#324): + FPS_ANIMATION_FACTOR -> * FPS_ANIMATION_FACTOR
- Helper-attack/heal: IsMonster() filtering, party-index increment fix,
  pTarget->TargetX/TargetX typo, self-position-skill handling for
  Nova/Hellfire/Inferno
- Right-click drop (PR sven-n#311): IInventoryActionContext interface +
  CNewUIInventoryActionController; ~1500 lines of static helpers moved
  off CNewUIMyInventory into the controller
- Phantom items (PR sven-n#317): inventory resync on pickup
- Tooltips FPS (PR sven-n#321): GroundItemLabelDescriptor + budget
- Exp-bar overflow (PR sven-n#316): saturating arithmetic, __int64 widening
- Divine stick (PR sven-n#306): IsDivineArchangelWeaponItem/Model helpers
- defenseSuccessRate calc fix in CNewUICharacterInfoWindow
- Lucky-set/restricted-item constants extracted to mu_define.h
- DetermineMonsterObjectKind extraction in ZzzCharacter
- VectorScale double-scaling removal in ZzzEffectJoint

SDL migration changes preserved verbatim where they don't conflict;
SDL3-only paths (renderer QueueTextureUpdate, packet queue) chosen over
the OpenGL alternatives main added.

Note: 4 files added by main (IInventoryActionContext.h, InventoryUtils.h,
NewUIInventoryActionController.{h,cpp}) moved from src/source/ root into
src/source/UI/Windows/Inventory/ to match the new module layout. CMake
globs already include this directory.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…_struct.h

Main added IInventoryActionContext.h with #include "_struct.h", but the
SDL migration reorg renamed that header to mu_struct.h (same pattern as
_enum.h -> mu_enum.h).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Main's upstream tooltip-perf refactor (PR sven-n#321) added a Windows+OpenGL
texture cache for ground-item labels. The cache uses _snwprintf_s + GDI
(FillRect/TextOut/GetTextExtentPoint32) to render text into a DC, then
uploads via glGenTextures/glTexImage2D.

On the SDL3 build none of that is available:
- _snwprintf_s replaced with mu_swprintf_s (the existing cross-platform
  wrapper defined in stdafx.h / PlatformCompat.h)
- DeleteGroundItemLabelTexture, CreateGroundItemLabelTexture, and
  RenderGroundItemLabelTexture gated behind #ifdef MU_ENABLE_SDL3;
  CreateGroundItemLabelTexture returns false on SDL3, which causes
  RenderGroundItemLabelCached to also return false
- RenderItemName else-branch updated to detect the failed cache and
  fall back to the direct g_pRenderText->RenderText() path
  (same path the if-branch already uses)

Semantics on SDL3: labels still render every frame via the direct path,
just without texture caching. That matches branch behavior before the
upstream perf optimization and is a measured perf regression worth
revisiting later if ground-item label rendering shows up as a hotspot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Commit 95b86aa (Story 7-9-6 raw-GL -> MuRenderer migration) stripped
glColor3f(r, g, b) from every case of the tooltip color switch in
RenderTipTextList() but never replaced the calls with
g_pRenderText->SetTextColor(). Every case fell through to the default
white set at line 402, so every special item — excellent, ancient,
+7 forge, set, socket, master-level — rendered plain white.

Restore the 9 tier colors converted from float [0,1] to byte [0,255]:

  BLUE      (excellent)            0.5, 0.7, 1.0   -> 128, 179, 255
  GRAY                             0.4, 0.4, 0.4   -> 102, 102, 102
  RED       (ancient)              1.0, 0.2, 0.1   -> 255, 51, 26
  YELLOW    (set items)            1.0, 0.8, 0.1   -> 255, 204, 26
  GREEN     (380 options)          0.1, 1.0, 0.5   -> 26, 255, 128
  PURPLE    (harmony)              1.0, 0.1, 1.0   -> 255, 26, 255
  REDPURPLE                        0.8, 0.5, 0.8   -> 204, 128, 204
  VIOLET                           0.7, 0.4, 1.0   -> 179, 102, 255
  ORANGE    (+7 forge / socket)    0.9, 0.42, 0.04 -> 230, 107, 10

WHITE / DARKRED / DARKBLUE / DARKYELLOW / GREEN_BLUE keep white text —
their colored backgrounds are painted by the SetBgColor block at
lines 431-451 below.

95b86aa removed 628 glColor* calls across ~100 files. Most were
geometry rendering where the MuRenderer picks up color from vertex
attribs and the removal was correct. The text-render sites where
glColor3f was gating g_pRenderText->RenderText() output are the ones
that lost their color. This commit fixes the most visible site
(item tooltips); other text-color regressions from the same commit
may surface and need similar fixes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two more victims of 95b86aa's glColor3f/4f sweep. Pattern identical to
the item-tooltip fix (1de9b2a): per-case color setters stripped,
replacement never added, downstream renderer falls back to the default
white/black color.

1. ZzzInterface.cpp RenderBar() — used for pet life, siege castle
   timer, and hunting progress. Six glColor3f calls across three layers
   (frame / mid / bar-fill) with Disabled vs Normal variants all gone.
   Fixed with RenderColorQuadARGB() per layer. Disabled = dark-red
   gradient, Normal = dark-cyan/green gradient.

2. NewUIInventoryCtrl.cpp slot-tint — all six ITEM_COLOR_* cases
   (NORMAL + 4 durability tiers + TRADE_WARNING) had empty bodies.
   Yellow/orange/red durability warnings that tell players to repair
   gear were invisible. Fixed with RenderColorQuadARGB().

Known remaining from the same commit (not fixed here):
  - LoginScene.cpp:  MU logo fade-in (glColor4f alpha before
    RenderBitmap) — needs a tinted-bitmap variant, which RenderBitmap
    doesn't expose today.
  - NewUICryWolf.cpp: event timer color (white -> red when low) via
    RenderNumber2D — same missing-tint-on-bitmap issue as the logo.
  - NewUIKanturuEvent.cpp / NewUICustomMessageBox.cpp: candidate
    message/timer text tints — not yet audited.
  - ZzzInventory.cpp: several more glColor3f deletions beyond the
    tooltip fix — likely the item floor-drop name color or similar.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request executes a major architectural refactoring (Phase -1) of the MU Online client source code. The primary objective is to transition from a legacy, monolithic, Windows-only build to a modular, cross-platform architecture. By reorganizing the source into logical modules and hardening the build system with CMake, the project is now prepared for the upcoming SDL3 migration. The changes also introduce modern C++20 standards, unified logging via spdlog, and robust cross-platform build tooling, ensuring consistent development environments across Windows, macOS, and Linux.

Highlights

  • Modular Source Reorganization: Moved 692 source files from a flat directory structure into 20 logical module directories, significantly improving project maintainability and build scalability.
  • CMake Build System Refactoring: Split the monolithic build into 7 independent static libraries and 1 coupled MUGame target, facilitating modular compilation and future SDL3 migration.
  • Cross-Platform Tooling: Introduced CMake presets for Windows (MSVC), macOS (arm64), and Linux (x64), along with new quality gates (clang-format, cppcheck, clang-tidy) and git hooks.
  • Renderer & Audio Modernization: Adopted GLM for matrix math and SDL_ttf for GPU-accelerated font rendering, while removing legacy DirectSound in favor of a cross-platform audio abstraction.
  • .NET Native AOT Integration: Implemented cross-platform .NET 10 Native AOT library integration, enabling server communication on non-Windows platforms.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Ignored Files
  • Ignored by pattern: .github/workflows/** (4)
    • .github/workflows/ci.yml
    • .github/workflows/mingw-build-dev.yml
    • .github/workflows/mingw-build-pr.yml
    • .github/workflows/mingw-build.yml
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a comprehensive reorganization and cross-platform port of the MU Online client, introducing a modular CMake build system, HLSL shader compilation, and a unified logging facade. It replaces legacy DirectSound code with a miniaudio backend and refactors the codebase into distinct static libraries to enforce dependency boundaries. Feedback highlights the need to explicitly disable copy operations for classes managing raw pointers to prevent memory corruption, the importance of using fixed encodings for configuration files, and the recommendation to avoid CMake globbing and global include directories in favor of explicit file listing and target-based configuration.

Comment thread src/source/Core/BaseCls.h
T m_NullData{};
};

// cppcheck-suppress [noCopyConstructor, noOperatorEq]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The CList class manages raw pointers (m_pHead, m_pTail) but lacks a copy constructor and assignment operator. This violates the Rule of Three and will lead to double-free errors or memory corruption if a CList instance is copied. Since this is legacy code being reorganized, you should at least explicitly delete the copy operations to prevent accidental usage.

// cppcheck-suppress [noCopyConstructor, noOperatorEq]
template <class T> CList<T>::CList() : m_pHead(new CNode<T>()), m_pTail(new CNode<T>())
{
    m_pHead->SetNext(m_pTail);
    m_pTail->SetPrev(m_pHead);
}

// Explicitly disable copying to satisfy Rule of Three and prevent memory corruption
CList(const CList&) = delete;
CList& operator=(const CList&) = delete;

Comment thread src/source/Core/BaseCls.h
template <class T> CDimension<T>::CDimension()
{
m_nSize = 16;
// cppcheck-suppress [noCopyConstructor, noOperatorEq]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Similar to CList, the CDimension class manages a raw pointer m_pData but does not implement copy control. Copying an instance will result in two objects sharing the same pointer, leading to a double-free in the destructor. Please delete the copy constructor and assignment operator.

// cppcheck-suppress [noCopyConstructor, noOperatorEq]
m_pData = new T[m_nSize];
}

CDimension(const CDimension&) = delete;
CDimension& operator=(const CDimension&) = delete;

Comment thread src/source/Core/IniFile.h Outdated
// character encoding. Without this, the default "C" locale only handles
// ASCII on non-Windows platforms. Config values are ASCII-only today, but
// this ensures correct behaviour as the codebase evolves. See HIGH-2 fix.
out.imbue(std::locale(""));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using std::locale("") relies on the environment's default locale, which can vary across systems and lead to inconsistent encoding behavior when reading or writing config files. For a cross-platform game client, it is safer to explicitly use a UTF-8 locale or a fixed encoding to ensure config.ini remains portable between Windows, Linux, and macOS.

Comment thread src/CMakeLists.txt
# PlatformCompat.h (included via PCH) has #ifdef MU_ENABLE_SDL3 sections that
# #include <SDL3/SDL.h>. Now that MU_ENABLE_SDL3 is project-scope, every target's
# PCH needs the SDL3 include path. [VS0-QUAL-BUILDCOMPAT-MACOS]
include_directories(SYSTEM "${sdl3_SOURCE_DIR}/include")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using the global include_directories command is generally discouraged in modern CMake. It affects all targets defined in this directory and subdirectories, which can lead to header pollution and unexpected conflicts. It is better to use target_include_directories on specific targets or the MUCommon interface library.

target_include_directories(MUCommon INTERFACE "${sdl3_SOURCE_DIR}/include")

Comment thread src/CMakeLists.txt
# Network ↔ UI, Gameplay → RenderFX, etc. stay together until
# decoupled during the SDL3 migration phases.
# ============================================================
file(GLOB_RECURSE MU_GAME_SOURCES CONFIGURE_DEPENDS
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using file(GLOB_RECURSE) to collect source files is discouraged by CMake best practices. CMake cannot automatically detect when new files are added to the filesystem unless a re-configure is manually triggered. Listing source files explicitly is preferred for reliable build dependency tracking.

Comment thread CMakeLists.txt Outdated

if(NOT MU_ENABLE_SHADER_COMPILATION)
# Offline fallback: copy pre-compiled blobs from src/shaders/compiled/ at configure time.
file(GLOB MU_PRECOMPILED_BLOBS "${MU_SHADER_COMPILED_DIR}/*")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The use of file(GLOB) here to find pre-compiled blobs has the same issue as source file globbing: CMake won't know if the contents of the compiled/ directory change unless the project is re-configured. Since these are build artifacts, consider using a more deterministic approach or explicitly listing the expected blobs.

…gration-merged

# Conflicts:
#	src/source/Network/WSclient.cpp
@Mosch0512
Copy link
Copy Markdown
Collaborator

i will test it and look at the code in detail this weekend :D

…lob glob

Addresses PR sven-n#329 review:
- IniFile.h imbued both wifstream/wofstream with std::locale(""), which
  resolves to the system ACP on Windows and $LANG elsewhere, so a
  config.ini written on one host could fail to round-trip on another.
  Replace with a fixed std::codecvt_utf8<wchar_t> facet via a local helper
  that scopes the -Wdeprecated-declarations suppression (MSVC/clang/gcc).
- CMakeLists.txt shader-blob glob now uses CONFIGURE_DEPENDS so CMake
  re-checks the filesystem on every build instead of caching a stale list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@yesid-bocanegra yesid-bocanegra changed the title Reorganize source into 20 module directories with CMake library targets Cross-platform SDL3 migration: Phase −1 → Sprint 7 (Win32 removal, rendering rewrite, .NET AOT, upstream merge) Apr 24, 2026
yesid-bocanegra and others added 3 commits April 24, 2026 14:52
Files untracked (kept on disk, now ignored):
- build_validation/*: 8 CMake-generated files (CMakeCache.txt with
  absolute paths baked in, Makefiles, libMU{Core,Platform,Protocol}.a
  archives). Never useful to other contributors — machine-specific.
  The directory pattern was already excluded under other names (build/,
  build-test/, build-mingw/); added build_validation/ to match.
- .DS_Store, src/.DS_Store: macOS Finder metadata. **/.DS_Store was
  already in .gitignore, but gitignore does not untrack files that
  predate the rule.
- src/Main.sln.DotSettings.user: JetBrains Rider per-user settings.
  *.user is already ignored — same pre-existing-tracked issue.
- .paw/metrics/*.jsonl: 3 session-local PCC/BMad workflow event
  streams. Added .paw/metrics/ to .gitignore.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Referenced from the PR sven-n#329 description as the testing playbook for
the 406-commit upstream merge (commit f5d1d73). Lists each named
upstream PR (sven-n#304sven-n#324), the files touched, the bugs fixed, and the
in-game steps to verify.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The sdl3 fallback path in RenderItemName pre-multiplied o->ScreenX/Y
by g_fScreenRate_*, but CUIRenderText::RenderText already scales its
input coordinates internally — labels rendered at roughly (rate^2*X,
rate^2*Y) and dropped well below their items on HiDPI windows.

o->ScreenX/Y come from Projection() in 640x480 base space (same as
MouseX/Y), so pass them raw and let RenderText apply g_fScreenRate_*
once. The Windows cache path that calls RenderBitmap is unchanged —
RenderBitmap takes pixel coords, RenderText takes base coords.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Mosch0512 Mosch0512 self-assigned this Apr 25, 2026
@Mosch0512 Mosch0512 self-requested a review April 25, 2026 14:31
yesid-bocanegra and others added 3 commits April 25, 2026 10:42
CNewUISkillList::UpdateMouseEvent set Hero->CurrentSkill and returned
false on three paths (hotkey strip, expanded skill list, pet command),
but never cleared MouseLButton / MouseLButtonPush / MouseLButtonPop.
On the next frame the leaked click hit MoveHero() and issued a world
click-to-move at the skill-bar position — same race the close-button
fix (c100cba) addressed in CNewUISystem::Hide().

Mirror that fix at each successful selection site, matching the
CNewUIMyInventory::ResetMouseLButton() pattern other UIs already use.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@Mosch0512 Mosch0512 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

Branch reviewed: cross-platform-sdl-migration-merged
Build target: MinGW-w64 i686 cross-compile from WSL/Linux (in-tree cmake/toolchains/mingw-w64-i686.cmake); also reproduced on MSVC + vcpkg via CLion.

Verdict:

The branch is too large to land as a single PR, and on the configurations its CI doesn't cover (MinGW i686 cross-compile, MSVC + vcpkg from CLion / Visual Studio) it currently fails to configure or build. Both problems are fixable, but the size problem is the gating one - until the branch is sliced up, the technical issues below are also effectively unreviewable.


This branch needs to be split before it can merge

cross-platform-sdl-migration-merged currently sits at ~1,188 files changed, +68k / -223k lines vs main. That's effectively unreviewable as a single PR, blocks every other contributor's branch from rebasing cleanly (we hit ~46 conflicts in just the first commit during a test rebase), and means any mistake won't surface until the whole thing lands. No amount of fixing the technical issues below changes the fact that the change-set itself is the biggest blocker.

The good news: the work is already factored into clear, mostly-independent stories (the Story X.X.X tags in commit messages and code comments). They map naturally onto a sequence of small PRs that can each go to main and be released incrementally.

Phase PR scope Approx. story refs Land independently?
1 Repo restructure only - move files into Core/, World/, Gameplay/, UI/Framework/, UI/Legacy/, RenderFX/, Platform/, Network/, Data/, Scenes/. Pure git mvs + matching #include path updates. No behavior change. 7.0.x yes - touches paths only
2 CMake target split - break the monolithic Main into MUCommon, MUCore, MUProtocol, MUData, MURenderFX, MUAudio, MUPlatform, MUThirdParty, MUGame, Main. Library boundaries surface coupling problems early. 4.x.x yes - only touches CMakeLists.txt and minimal includes
3 PlatformCompat shim + cross-platform types - mu_base_types.h, mu_define.h, mu_types.h, PlatformTypes.h, PlatformCompat.h, mu_get_app_dir, mu_narrow_path, mu_swprintf, mu_wcstok. Lets non-Windows toolchains compile something. 7.6.x yes - can land before SDL3 even appears
4 Logging migration - introduce mu::log (spdlog wrapper), then convert CErrorReport and CmuConsoleDebug call sites in batches (Network/, Scenes/, Gameplay/, GameShop/). Retire the old globals once empty. 7.10.x yes - can run in parallel with later phases as long as both APIs coexist
5 CMake build hygiene - toolchain files (linux-x64, macos-arm64, mingw-w64-i686), warning flag gating per compiler (issue 6), preset cleanup (issue 9), clang-format/clang-tidy/cppcheck configs, dev Makefile. 7.6.x yes - pure tooling
6 MuRenderer abstraction - introduce the interface and ship the existing OpenGL backend behind it as MuRendererGL. Game code keeps working unchanged. This is the keystone phase: nothing else can land until this exists. 4.2.x, 7.9.x yes - backwards-compatible by design
7 Audio migration to miniaudio - MUAudio library + miniaudio-backed wzAudio shim. 7.6.6 + audio stories yes - independent of rendering
8 CURL/OpenSSL cross-platform shop/credentials - replace Win32 wininet/wincrypt with libcurl + OpenSSL. Make CURL optional with a header-only fallback (issue 8) so MinGW / un-vcpkg'd MSVC contributors aren't blocked. 3.4.x, 7.6.x yes - independent
9 SDL3 platform layer - SDLWindow, SDLEventLoop, SDLKeyboardState, input shims, text input. Window/input only; rendering still routes through MuRendererGL. 2.2.x, 7.x.x depends on phase 3 (PlatformCompat) and phase 6 (renderer abstraction)
10 SDL3 GPU backend (MuRendererSDLGpu) - implements the renderer interface introduced in phase 6 against SDL_gpu. Add the HLSL→SPIR-V→MSL shader pipeline. Until this lands, both backends ship and a CMake flag picks between them. 4.x, 7.9.x depends on phase 6
11 Remove the legacy OpenGL backend - delete MuRendererGL, drop GLEW, retire the gl* immediate-mode calls (issue 4). Only after phase 10 is proven on all three platforms. 7.9.3 depends on phase 10
12 Tests + CI matrix - tests/ tree, GitHub Actions matrix for Linux x64 / macOS arm64 / MinGW i686 / MSVC. various yes - bolt-on

Why this is worth the extra work upfront:

  1. Each phase is reviewable. Phase 1 is a git mv audit; phase 6 is a single new interface; phase 10 is the hard rendering work in isolation. Reviewers can actually catch bugs.
  2. Each phase is releasable. main keeps shipping after every merge. Players don't wait six months for the next playable build.
  3. Other contributors' branches survive. Right now any branch (camera, UI, gameplay) cherry-picked off main becomes nearly unrebaseable against this branch. With incremental landing, a contributor only needs to react to one change at a time.
  4. Bisecting becomes possible again. When a regression appears, you bisect against a sequence of small commits, not a single 290k-line megacommit.
  5. The technical issues below shrink. Most of them are detected automatically once phase 5 (CMake hygiene + cross toolchains) and phase 12 (CI matrix) are in. They never accumulate to "150+ errors at once."

The core insight: the abstraction phases (3, 4, 6) and the implementation phases (9, 10, 11) are decoupled by design. There is no technical reason they all have to land together - only the inertia of the existing branch keeps them coupled.


TL;DR - what's broken under the hood

Even setting the size problem aside, the branch builds on the platforms its CI exercises (Linux/macOS clang, Windows MinGW64) but several gaps prevent any other configuration from producing a binary:

  1. Logging migration is incomplete - g_ConsoleDebug, g_ErrorReport, MCD_* constants are still referenced in many files after Story 7.10.1 replaced them with mu::log::Get(...).
  2. SDL3 helpers are declared inside the wrong #ifdef branch in Platform/PlatformCompat.h, so they're invisible to MinGW (_WIN32-defined) builds.
  3. POSIX-only types (u_char, u_int64) and headers (<sys/utsname.h>) are used in unconditionally compiled code paths.
  4. Several files still call OpenGL immediate-mode functions (glColor3f, glPushMatrix, glBegin/glEnd, …) after the SDL3 GPU backend made them unavailable (Story 7.9.3).
  5. Win32 macro collision - MuPlatform::CreateWindow(...) clashes with <windows.h>'s CreateWindow → CreateWindowW macro on MinGW.
  6. CMake warning relaxations are Clang-only - they trigger errors when GCC validates -Wno-error=<unknown-flag> under -Werror.
  7. Stale relative #include paths that pre-date the directory restructure.
  8. CURL is unconditionally REQUIRED - blocks configure on every Windows configuration that doesn't have a system curl: MinGW cross-compile, MSVC + vcpkg (vcpkg integration is silently bypassed by issue 9), and stock Visual Studio without manual prep.
  9. CMakePresets.json hard-codes toolchainFile on the base Windows presets - downstream presets inheriting from windows-x64 can't supply their own toolchain file (e.g. vcpkg.cmake) without surgery, because the inherited toolchainFile field always wins over a CMAKE_TOOLCHAIN_FILE cache variable.

Each item is reproducible, narrowly scoped, and can be fixed independently - and most of them naturally fall inside one of the phases in the table above.


CI coverage today

Worth flagging up front: the existing .github/workflows/ci.yml covers four jobs - Quality Gates (Ubuntu, no build), Linux Native (Ubuntu + system GCC), macOS Native (Brew clang), and Windows Native (windows-latest + MSYS2 MinGW-w64 x86_64, not MSVC, despite the CMakePresets.json windows-base description saying "MSVC"). The toolchain files (toolchain-x64.cmake, toolchain-x86.cmake) don't actually force MSVC - they trust whatever compiler is on PATH, and the CI installs MinGW.

Implications:

  • MSVC has zero CI coverage. Anyone building from Visual Studio is the first to hit MSVC-specific issues. Recommend either adding an MSVC job, or removing the misleading "MSVC" wording from the preset description.
  • The MinGW issues this review documents are reproduced on MinGW-w64 i686. Some - u_char / u_int64, <sys/utsname.h> guarding, CreateWindow macro collision, mu_wcstok 2-vs-3-arg shim - should also surface on the CI's MinGW64 path. If the existing Windows job is currently green, it's worth re-checking the latest run logs to see whether they're truly clean or compiling with warnings that should be errors. Issues 1 (logging migration) and 4 (lingering gl* calls) are language-level - they will fail any compiler.
  • CLion + MSVC + vcpkg cannot configure the project today. Tested on Windows: opening the repo in CLion, picking the bundled windows-x64 (or any inheriting) preset under the Visual Studio toolchain, and pointing CMake at C:/vcpkg/scripts/buildsystems/vcpkg.cmake - configure still fails on find_package(CURL REQUIRED) because the preset's hardcoded toolchainFile overrides the user-supplied vcpkg one. Same outcome for the auto-generated cmake-build-x86debug-mueditor profile CLion creates from windows-x86-mueditor. Concretely: there is no out-of-the-box configuration in which a Windows developer using MSVC + vcpkg can produce a build.

Reproduction

# From repo root:
cmake -S . -B build-mingw \
  -G Ninja \
  -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/mingw-w64-i686.cmake \
  -DCMAKE_BUILD_TYPE=Release \
  -DMU_ENABLE_SHADER_COMPILATION=OFF \
  -DMU_ENABLE_DOTNET=OFF
cmake --build build-mingw -j 8

Configure currently fails on find_package(CURL REQUIRED) (item 8). With CURL stubbed locally, configure succeeds and the build runs through MUCore/MUPlatform/MURenderFX/MUData (after the other fixes) before halting in MUGame on the logging-migration and lingering-GL items.


Issues

1. Incomplete logging migration (HIGH - blocks compilation)

Story 7.10.1 replaced CErrorReport and CmuConsoleDebug with mu::log::Get(...), but the call sites in many files still reach for the old API. Symbols not declared anywhere on the branch:

  • g_ConsoleDebug
  • g_ErrorReport
  • MCD_NORMAL, MCD_RECEIVE, MCD_ERROR
  • header Utilities/Log/muConsoleDebug.h

Affected (non-exhaustive - grep g_ConsoleDebug\|g_ErrorReport\|MCD_ to enumerate):

  • Network/WSclient.cpp - at least lines 350, 354, 358, 467, 480, 482, 490, 534
  • Scenes/MainScene.cpp - extern CErrorReport g_ErrorReport; declared but never defined
  • Scenes/CharacterScene.cpp - same extern, plus #include "Utilities/Log/muConsoleDebug.h"
  • Scenes/SceneManager.cpp - g_ErrorReport.Write(...) (~line 603-604)
  • GameShop/NewUIInGameShop.cpp - g_ConsoleDebug.Write(...) (~lines 615, 705)

Fix: finish the migration to mu::log::Get("module")->info(...) / warn(...) / error(...). The pattern is already correctly applied in Gameplay/Items/PersonalShopTitleImp.cpp - same shape works for the rest.

2. SDL3 helpers in wrong preprocessor branch (HIGH)

Platform/PlatformCompat.h is structured as #ifdef _WIN32 ... #else ... #endif (lines 3 → 50 → 2285). The SDL3 helpers are declared inside the #else branch (#ifdef MU_ENABLE_SDL3 ... #endif nested at ~line 924-960), so they're only visible to non-Windows builds.

MinGW defines _WIN32, so the following declarations are unreachable from MinGW translation units that include PlatformCompat.h:

  • extern char g_szSDLTextInput[32];
  • extern bool g_bSDLTextInputReady;
  • void MuStartTextInput(); / void MuStopTextInput();
  • inline wchar_t MuSdlUtf8NextChar(const char*&);

Resulting failures:

  • Platform/sdl3/SDLKeyboardState.cpp - SDL_Window, SDL_StartTextInput, SDL_StopTextInput not declared (file includes PlatformCompat.h expecting it to bring in <SDL3/SDL.h>).
  • Platform/sdl3/SDLEventLoop.cpp - g_sdl3KeyboardState not declared.
  • ThirdParty/UIControls.cpp - MuStopTextInput, g_bSDLTextInputReady, g_szSDLTextInput, MuSdlUtf8NextChar not declared.

Fix: lift the SDL3 helper block out of the platform #ifdef and gate it only on MU_ENABLE_SDL3. Same root cause applies to mu_narrow_path (used by Data/GlobalText.h's template - GCC's two-phase name lookup needs the declaration visible regardless of platform).

3. POSIX-only types in unconditional code (MEDIUM)

  • RenderFX/ZzzBMD.cpp:1022 - static_cast<u_char>(this->StreamMesh). u_char is from <sys/types.h> (POSIX), not on MinGW. Fix: unsigned char.
  • Network/WSclient.cpp:540-550 - u_int64 received; ... u_int64 actual;. Same problem. Fix: uint64_t.
  • Core/MuSystemInfo.cpp:13 - #include <sys/utsname.h> is unconditional, and uname() is called in the unconditional path of MuGetSystemInfo(). Fix: wrap both in #ifndef _WIN32, fall back to a Windows-shaped string from RtlGetVersion / compile-time identifier.

4. Lingering OpenGL immediate-mode calls (MEDIUM)

Story 7.9.3 removed the OpenGL backend and GLEW headers, but several files still emit immediate-mode draw calls. Those symbols don't exist anywhere in the build:

  • RenderFX/ZzzEffectFireLeave.cpp:547,572,573,598 - glColor3f, glPushMatrix, glTranslatef, glPopMatrix.
  • Gameplay/Characters/ZzzObject.cpp - glColor3f/glColor4f/glColor3fv at ~25 sites between lines 786-7840.
  • GameShop/NewUIInGameShop.cpp:85,312-337 - glColor4f, glMatrixMode, glPushMatrix, glLoadIdentity, glClear, glPopMatrix.

Fix: route through the renderer abstraction. mu::GetRenderer() already exposes SetMatrixMode, PushMatrix, LoadIdentity, MultMatrix, Translate, Rotate, ClearDepthBuffer, SetViewport. A small extension may be needed for color (e.g. SetVertexColor(r,g,b,a)) and for legacy glBegin(GL_QUADS)-style geometry, since there's no debug-primitive helper on the abstraction yet.

5. Win32 macro collision in MuPlatform.h (MEDIUM)

Platform/MuPlatform.h:16 declares static bool MuPlatform::CreateWindow(...). On MinGW with <windows.h> included earlier, CreateWindow is #defined to CreateWindowW / CreateWindowA, so the class declaration becomes garbage:

error: macro "CreateWindowW" requires 11 arguments, but only 4 given

Fix: either #undef CreateWindow (and CreateWindowA/W for paranoia) at the top of MuPlatform.h, or rename the method to something like CreateGameWindow / CreateMainWindow.

6. CMake warning relaxations assume Clang (MEDIUM)

MUCommon's INTERFACE compile options propagate -Werror -Wall -Wextra plus a list of -Wno-error=<name> exceptions. Many entries in the MU_LEGACY_WARNING_RELAXATIONS list are Clang-only flags:

-Wno-error=deprecated-anon-enum-enum-conversion
-Wno-error=deprecated-enum-compare
-Wno-error=null-conversion
-Wno-error=null-arithmetic
-Wno-error=logical-op-parentheses
-Wno-error=reorder-ctor
-Wno-error=pointer-bool-conversion
-Wno-error=tautological-constant-out-of-range-compare
-Wno-error=tautological-overlap-compare
-Wno-error=delete-non-abstract-non-virtual-dtor
-Wno-error=pragma-once-outside-header
-Wno-error=bitwise-op-parentheses
-Wno-error=sometimes-uninitialized
-Wno-error=extern-initializer
-Wno-error=tautological-pointer-compare
-Wno-error=c++11-narrowing
-Wno-error=writable-strings
-Wno-error=constant-conversion

Under -Werror, GCC treats -Wno-error=<unknown-flag> as a hard error.

Fix: wrap each Clang-only entry in $<$<CXX_COMPILER_ID:Clang,AppleClang>:...>. Also add the GCC-flavoured set that the legacy code needs:

$<$<CXX_COMPILER_ID:GNU>:-Wno-error=address>
$<$<CXX_COMPILER_ID:GNU>:-Wno-error=parentheses>
$<$<CXX_COMPILER_ID:GNU>:-Wno-error=implicit-fallthrough>
$<$<CXX_COMPILER_ID:GNU>:-Wno-error=narrowing>
$<$<CXX_COMPILER_ID:GNU>:-Wno-error=class-memaccess>
$<$<CXX_COMPILER_ID:GNU>:-Wno-error=stringop-truncation>
$<$<CXX_COMPILER_ID:GNU>:-Wno-error=format>
$<$<CXX_COMPILER_ID:GNU>:-Wno-error=format-security>
$<$<CXX_COMPILER_ID:GNU>:-Wno-error=maybe-uninitialized>
$<$<CXX_COMPILER_ID:GNU>:-Wno-error=array-bounds>

Same relaxation list needs applying to MURenderFX / MUData / MUCore (currently it's only on MUGame and Main) - the legacy Zzz* files live in those targets too.

7. Stale relative include paths (LOW)

A handful of files still use ../-prefixed includes referencing pre-restructure locations:

  • Scenes/SceneManager.cpp, MainScene.cpp, CharacterScene.cpp, LoginScene.cpp, SceneCommon.cpp
  • Network/WSclient.cpp - Guild/GuildCache.h, MUHelper/MuHelper.h
  • UI/Legacy/ZzzInterface.cpp
  • UI/Windows/HUD/NewUIMiniMap.cpp
  • Gameplay/Items/PersonalShopTitleImp.cpp - Guild/UIGuildInfo.h
  • RenderFX/ZzzOpenglUtil.cpp - ShellExecute used without including <shellapi.h> or declaring it for Win32.

Fix: drop the ../ and old subdirectory components - the include path already covers the new locations.

Related: Data/GlobalText.h should #include "Platform/PlatformCompat.h" directly rather than relying on transitive includes, otherwise the template Load(...) at line 162 fails GCC's two-phase name lookup for mu_narrow_path.

8. CURL is unconditionally REQUIRED (HIGH - blocks configure on every untested Windows path)

Originally scoped to MinGW, but reproduces on MSVC + vcpkg too. Configure halts at:

CMake Error at .../FindPackageHandleStandardArgs.cmake:230 (message):
  Could NOT find CURL (missing: CURL_LIBRARY CURL_INCLUDE_DIR)
Call Stack:
  src/CMakeLists.txt:364 (find_package)

Where it reproduces:

  • MinGW i686 cross-compile from WSL - no apt-provided libcurl.a for the i686 mingw target, and _deps/mingw-i686/lib/ only ships libjpeg.a / libturbojpeg.a.
  • MSVC + vcpkg in CLion - even with vcpkg pre-installing curl:x64-windows, the toolchain conflict in issue 9 prevents vcpkg's CMake integration from loading, so find_package(CURL) falls back to system search and finds nothing.
  • Stock Visual Studio without manual curl prep - same root cause, no system curl.

OpenSSL has the same shape but already gracefully degrades - configure logs OpenSSL NOT found: mu_encrypt_blob/mu_decrypt_blob will use identity no-op fallback. CURL should follow that pattern.

Options:

  1. Add prebuilt binaries to _deps/<triplet>/ for each Windows target.
  2. Use FetchContent to build curl from source for the cross target.
  3. Add a feature flag (MU_ENABLE_SHOP_DOWNLOADS) so the shop/banner downloader can be excluded from the build, falling back to a no-op stub when off.
  4. Have CMake auto-stub CURL::libcurl (and provide a header-only stub) when CURL isn't found, with a clear STATUS message that download features will be inert at runtime - mirrors the existing OpenSSL fallback.

Option 4 is the lowest-friction path to "any Windows contributor can clone and configure" - it matches the OpenSSL precedent already in the tree.

9. CMakePresets.json hardcodes toolchainFile on the base Windows presets (HIGH - blocks vcpkg / sysroot overrides)

windows-x64 and windows-x86 set:

"toolchainFile": "${sourceDir}/toolchain-x64.cmake"

toolchainFile in CMake presets is a top-level field and takes precedence over a CMAKE_TOOLCHAIN_FILE cache variable in the same or any inheriting preset. So a downstream preset like:

{
  "name": "msvc-x64-vcpkg",
  "inherits": "windows-x64",
  "cacheVariables": {
    "CMAKE_TOOLCHAIN_FILE": "C:/vcpkg/scripts/buildsystems/vcpkg.cmake"
  }
}

…silently keeps using toolchain-x64.cmake and ignores the vcpkg integration entirely. That's how issue 8 manifests on MSVC: vcpkg installs curl, but its cmake-side hooks never load, so find_package(CURL) can't see the install.

The two existing toolchain files (toolchain-x86.cmake, toolchain-x64.cmake) don't actually do anything - they just set CMAKE_GENERATOR_PLATFORM (which is a no-op under Ninja Multi-Config, the configured generator). Removing them or making them opt-in would unblock vcpkg / sysroot users without changing CI behaviour.

Fix (any of):

  1. Move toolchainFile off the base presets and into an explicit windows-x64-default-toolchain leaf preset. Other leaves (windows-x64-vcpkg, windows-x64-mingw) override freely.
  2. Delete the toolchain files entirely - they only set CMAKE_GENERATOR_PLATFORM, which the Ninja Multi-Config generator ignores.
  3. Have the toolchain file include() $ENV{CMAKE_TOOLCHAIN_FILE_OVERRIDE} first so an env var can chain in vcpkg / a sysroot file. (Ugly but non-breaking.)

Option 1 or 2 is the right answer - option 3 just papers over the problem.


What works

To set expectations: after the configure-time CURL workaround, the following targets currently build clean on MinGW with the changes from this review applied:

  • MUCommon, MUCore, MUProtocol, MUData, MURenderFX, MUAudio, MUPlatform, MUThirdParty, MUImgui
  • All FetchContent dependencies (SDL3, SDL3_ttf, glm, spdlog)

The remaining build errors are concentrated in MUGame, almost entirely from issues 1 (logging) and 4 (lingering GL calls).

@Mosch0512
Copy link
Copy Markdown
Collaborator

Mosch0512 commented Apr 26, 2026

Closing this PR.

As detailed in the review above, the change-set is too large to land or review as a single unit (~1,188 files, -68k / +223k LOC vs main), and the technical issues are effectively
unreviewable until the branch is sliced up. The work itself is valuable and already factored into clear, mostly-independent stories - it just needs to land incrementally.

Suggested path forward: re-open the work as the sequence of smaller PRs outlined in the Review (phases 1–12),
each targeting main and releasable on its own. The abstraction phases (repo restructure, CMake target split, PlatformCompat, logging, MuRenderer interface) are decoupled from
the implementation phases (SDL3 platform layer, SDL_GPU backend, legacy GL removal) by design — there's no technical reason they have to land together.

Benefits of the incremental approach:

  • Each PR is reviewable and bisectable.
  • main keeps shipping playable builds between merges.
  • Other contributors' branches stay rebaseable.
  • The cross-platform/CI gaps (CURL, vcpkg toolchain, MinGW logging migration, lingering gl* calls, etc.) get caught one phase at a time instead of all at once.

Happy to review the smaller follow-up PRs as they come in. Thanks for the huge amount of work here - this isn't a rejection of the work, just of landing it as one piece.

@Mosch0512 Mosch0512 closed this Apr 26, 2026
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.

2 participants