Commit graph

242 commits

Author SHA1 Message Date
Kelsi
17c16150d6 fix(vulkan): MSAA crash on AMD RADV due to vkCreateRenderPass2 null dispatch
Some checks are pending
Build / Build (arm64) (push) Waiting to run
Build / Build (x86-64) (push) Waiting to run
Build / Build (macOS arm64) (push) Waiting to run
Build / Build (windows-arm64) (push) Waiting to run
Build / Build (windows-x86-64) (push) Waiting to run
Security / CodeQL (C/C++) (push) Waiting to run
Security / Semgrep (push) Waiting to run
Security / Sanitizer Build (ASan/UBSan) (push) Waiting to run
Instance was created with Vulkan 1.1 but depthResolveSupported_ was gated
on the physical device's API version (1.2+ on RADV). This caused
vkCreateRenderPass2 (core 1.2) to dispatch through a null function pointer
when MSAA was enabled. Now requests 1.2 instance with 1.1 minimum fallback
and gates depth resolve on the actual instance API version. Also removes
all diagnostic crash-phase instrumentation from the previous investigation.
2026-04-03 20:58:32 -07:00
Kelsi
9c4e61a227 fix(diagnostics): instrument applyMsaaChange to find NULL deref
AMD crash is caused by msaaChangePending_ flipping true from saved settings.
applyMsaaChange() then crashes with faultAddr=(nil). Add LOG_WARNING markers
between pipeline recreation groups to identify the failing call.
2026-04-03 20:42:08 -07:00
Kelsi
45ac7e4d8e fix(diagnostics): log renderer state on each beginFrame for AMD crash
Add per-frame LOG_WARNING with this/vkCtx/camera/postProcess pointers and
msaaChangePending state. If the log prints before crash, the pointer values
tell us what's corrupt. If it doesn't print, crash is in the log itself
(meaning this or vkCtx is corrupt).
2026-04-03 20:37:21 -07:00
Kelsi
cd07e23485 fix(diagnostics): finer beginFrame sub-phase markers for AMD crash
Previous markers showed crash before bf:ubo. Add markers at bf:msaa,
bf:pp, bf:swap, bf:acquire, bf:jitter to isolate which early beginFrame
call does the NULL deref.
2026-04-03 20:32:18 -07:00
Kelsi
5778ba230d fix(diagnostics): add sub-phase markers inside Renderer::beginFrame
AMD RADV crash is renderPhase=beginFrame with faultAddr=(nil) — a NULL
pointer dereference somewhere in the pre-pass chain. Add granular markers
(bf:ubo, bf:minimap, bf:worldmap, bf:preview, bf:shadow, bf:reflection,
bf:renderpass) to pinpoint the exact call.
2026-04-03 20:28:37 -07:00
Kelsi
82267320b0 fix(diagnostics): add render-phase crash markers and improve signal handling
Add signal-safe render-phase markers throughout GameScreen::render() and
Application::render() so the crash handler can report which render call was
active when a SIGSEGV occurs. The AMD RADV crash backtrace only shows 2
frames due to missing frame pointers, making it impossible to identify the
actual crash site.

Changes:
- Add volatile g_crashRenderPhase marker updated before each major render call
- Upgrade Linux signal handler to sigaction with SA_SIGINFO for faulting address
- Set ImGui CheckVkResultFn to log silent Vulkan errors in ImGui backend
- Enable -fno-omit-frame-pointer in all build configs (not just Debug/RelWithDebInfo)
2026-04-03 20:19:33 -07:00
Paul
2cb47bf126 chore(testing): add unit tests and update core render/network pipelines
- add new tests:
  - test_blp_loader.cpp
  - test_dbc_loader.cpp
  - test_entity.cpp
  - test_frustum.cpp
  - test_m2_structs.cpp
  - test_opcode_table.cpp
  - test_packet.cpp
  - test_srp.cpp
  - CMakeLists.txt
- add docs and progress tracking:
  - TESTING.md
  - perf_baseline.md
- update project config/build:
  - .gitignore
  - CMakeLists.txt
  - test.sh
- core engine updates:
  - application.cpp
  - game_handler.cpp
  - world_socket.cpp
  - adt_loader.cpp
  - asset_manager.cpp
  - m2_renderer.cpp
  - post_process_pipeline.cpp
  - renderer.cpp
  - terrain_manager.cpp
  - game_screen.cpp
- add profiler header:
  - profiler.hpp
2026-04-03 09:41:34 +03:00
Paul
5af9f7aa4b chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.

AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
  emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()

Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
  directly, grouped by access pattern:
  - UIServices: settings_panel, game_screen, toast_manager, chat_panel,
    combat_ui, window_manager
  - GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
    social_handler, combat_handler
  - Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
  MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
Paul
5ef600098a chore(renderer): refactor renderer and add post-process + spell visuals systems
- Updated core render pipeline and renderer integration in CMakeLists.txt, renderer.cpp, renderer.hpp
- Added post-process pipeline module:
  - post_process_pipeline.hpp
  - post_process_pipeline.cpp
- Added spell visual system module:
  - spell_visual_system.hpp
  - spell_visual_system.cpp
- Adjusted application/audio integration:
  - application.cpp
  - audio_coordinator.cpp
2026-04-02 00:21:21 +03:00
Kelsi
7cfaf2c7e9 refactor: complete OpenGL→Vulkan migration (Phase 7)
Some checks are pending
Build / Build (arm64) (push) Waiting to run
Build / Build (x86-64) (push) Waiting to run
Build / Build (macOS arm64) (push) Waiting to run
Build / Build (windows-arm64) (push) Waiting to run
Build / Build (windows-x86-64) (push) Waiting to run
Security / CodeQL (C/C++) (push) Waiting to run
Security / Semgrep (push) Waiting to run
Security / Sanitizer Build (ASan/UBSan) (push) Waiting to run
Remove all OpenGL/GLEW code and dependencies. The Vulkan renderer has
been the sole active backend for months; these files were dead code.

Deleted (8 files, 641 lines):
- rendering/mesh.cpp+hpp: OpenGL VAO/VBO/EBO wrapper (never instantiated)
- rendering/shader.cpp+hpp: OpenGL GLSL compiler (replaced by VkShaderModule)
- rendering/scene.cpp+hpp: Scene graph holding Mesh objects (created but
  never populated — all rendering uses Vulkan sub-renderers directly)
- rendering/video_player.cpp+hpp: FFmpeg+GL texture uploader (never
  included by any other file — login video feature can be re-implemented
  with VkTexture when needed)

Cleaned up:
- renderer.hpp: remove Scene forward-decl, getScene() accessor, scene member
- renderer.cpp: remove scene.hpp/shader.hpp includes, Scene create/destroy
- application.cpp: remove stale "GL/glew.h removed" comment
- CMakeLists.txt: remove find_package(OpenGL/GLEW), source/header entries,
  and target_link_libraries for OpenGL::GL and GLEW::GLEW
- PKGBUILD: remove glew dependency
- BUILD_INSTRUCTIONS.md: remove glew from all platform install commands
2026-03-30 19:22:36 -07:00
Kelsi
6dfac314ee fix: remove dead code, name constants, add why-comments
- renderer: remove no-op assignment (mountAnims_.stand = 0 when already 0)
- renderer: add why-comments on blacksmith WMO ID 96048 (ambient forge
  sounds) with TODO for other smithy buildings
- terrain_renderer: replace 1e30f sentinel with numeric_limits::max(),
  name terrain view distance constant (1200 units ≈ 9 ADT tiles)
- social_handler: add missing LFG case 15, document case 0 nullptr
  return (success = no error message), add enum name comments
2026-03-30 14:10:32 -07:00
Kelsi
3dd1128ecf fix: unguarded future::get() crashed on render/floor-query worker exceptions
std::future::get() re-throws any exception from the async task. The 6
call sites in the render pipeline (terrain/WMO/M2 workers + animation
worker) and 2 floor-query sites in camera_controller were unguarded,
so a single bad_alloc in any worker would terminate the process with
no recovery. Now wrapped in try-catch with error logging.
2026-03-29 21:26:01 -07:00
Kelsi
849542d01d fix: doodad/mount animations synchronized due to unseeded rand()
All 8 rand() calls for animation time offsets and variation timers in
m2_renderer.cpp used C rand() which defaults to seed 1 without srand(),
producing identical sequences every launch. Trees, torches, and grass
all swayed in sync. Replaced with std::mt19937 seeded from
random_device. Same fix for 4 mount idle fidget/sound timer sites in
renderer.cpp which mixed rand() with the mt19937 already present.
2026-03-29 20:42:10 -07:00
Kelsi
34e384e1b2 fix: tavern music always played first track — index never incremented
tavernTrackIndex was initialized to 0 but never modified, so the player
always heard TavernAlliance01.mp3. Added post-increment to rotate
through the 3 available tracks on each tavern entry.
2026-03-29 20:27:08 -07:00
Kelsi
d26eed1e7c perf: constexpr reciprocals, cache redundant lookups, consolidate texture maps
- Hoist DBC field index lookups before loops in game_handler (7 DBC iteration loops)
- Cache getSkybox()/getPosition() calls instead of redundant per-frame queries
- Merge textureHasAlphaByPtr_ + textureColorKeyBlackByPtr_ into single map
- Add constexpr for DEG_TO_RAD, reciprocal constants, physics delta
- Add reserve() for WMO/M2 collision grid queries and portal BFS
- Frustum plane normalize: inversesqrt instead of length+divide
- M2 particle emission: inversesqrt for direction normalization
- Parse creature display IDs from query response
- UI: show spell names/IDs as fallback instead of "Unknown"
2026-03-27 16:47:30 -07:00
Kelsi
b0466e9029 perf: eliminate ~70 unnecessary sqrt ops per frame, optimize caches and threading
Squared distance optimizations across 30 files:
- Convert glm::length() comparisons to glm::dot() (no sqrt)
- Use glm::inversesqrt() for check-then-normalize patterns (1 rsqrt vs 2 sqrt)
- Defer sqrt to after early-out checks in collision/movement code
- Hottest paths: camera_controller (21), weather particles, WMO collision,
  transport movement, creature interpolation, nameplate culling

Container and algorithm improvements:
- std::map<string> → std::unordered_map for asset/DBC/MPQ/warden caches
- std::mutex → std::shared_mutex for asset_manager and mpq_manager caches
- std::sort → std::partial_sort in lighting_manager (top-2 of N volumes)
- Double-lookup find()+operator[] → insert_or_assign in game_handler
- Add reserve() for per-frame vectors: weather, swim_effects, WMO/M2 collision

Threading and synchronization:
- Replace 1ms busy-wait polling with condition_variable in character_renderer
- Move timestamp capture before mutex in logger
- Use memory_order_acquire/release for normal map completion signaling

API additions:
- DBC getStringView()/getStringViewByOffset() for zero-copy string access
- Parse creature display IDs from SMSG_CREATURE_QUERY_SINGLE_RESPONSE
2026-03-27 16:33:16 -07:00
Kelsi
5b91ef398e fix: return UINT32_MAX from findMemType on failure, add [[nodiscard]]
The findMemType/findMemoryType helper in auth_screen, loading_screen,
and vk_context returned 0 on failure — a valid memory type index.
Changed to return UINT32_MAX and log an error, so vkAllocateMemory
receives an invalid index and fails cleanly rather than silently
using the wrong memory type.

Add [[nodiscard]] to VkBuffer::uploadToGPU/createMapped and
VkContext::initialize/recreateSwapchain so callers that ignore
failure are flagged at compile time. Suppress with (void) cast at
3 call sites where failure is non-actionable (resize best-effort).
2026-03-27 14:53:29 -07:00
Kelsi
ff77febb36 fix: guard std::stoi/stof calls at input boundaries against exceptions
Wrap string-to-number conversions in try-catch where input comes from
external sources (realm address port, last_world.cfg, keybinding config,
ADT tile filenames) to prevent crashes on malformed data.
2026-03-27 10:14:49 -07:00
Kelsi
05f2bedf88 refactor: replace C-style casts with static_cast and extract toLowerInPlace
Replace ~300 C-style casts ((int), (float), (uint32_t), etc.) with
static_cast across 15 source files. Extract toLowerInPlace() helper in
lua_engine.cpp to replace 72 identical tolower loop patterns.
2026-03-25 11:40:49 -07:00
Kelsi
98b9e502c5 refactor: extract guidToUnitId/getQuestTitle helpers and misc cleanup
- Extract guidToUnitId(), getQuestTitle(), findQuestLogEntry() helpers
  to replace 14 duplicated GUID-to-unitId patterns and 7 quest log
  search patterns in game_handler.cpp
- Remove duplicate #include in renderer.cpp
- Remove commented-out model cleanup code in terrain_manager.cpp
- Replace C-style casts with static_cast in auth and transport code
2026-03-25 11:25:44 -07:00
Kelsi
9a6a430768 fix: track render pass subpass mode to prevent ImGui secondary violation
When parallel recording is active, the scene pass uses
VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS. Post-processing paths
(FSR/FXAA) end the scene pass and begin a new INLINE render pass for
the swapchain output. ImGui rendering must use the correct mode —
secondary buffers for SECONDARY passes, direct calls for INLINE.

Previously the check used a static condition based on enabled features
(!fsr && !fsr2 && !fxaa && parallel), which could mismatch if a
feature was enabled but initialization failed. Replace with
endFrameInlineMode_ flag that tracks the actual current render pass
mode at runtime, eliminating the validation error
VUID-vkCmdDrawIndexed-commandBuffer-recording that caused intermittent
NVIDIA driver crashes.
2026-03-24 13:05:27 -07:00
Kelsi
a152023e5e fix: add VkSampler cache to prevent sampler exhaustion crash
Validation layers revealed 9965 VkSamplers allocated against a device
limit of 4000 — every VkTexture created its own sampler even when
configurations were identical. This exhausted NVIDIA's sampler pool
and caused intermittent SIGSEGV in vkCmdBeginRenderPass.

Add a thread-safe sampler cache in VkContext that deduplicates samplers
by FNV-1a hash of all 14 VkSamplerCreateInfo fields. All texture,
render target, renderer, water, and loading screen sampler creation
now goes through getOrCreateSampler(). Textures set ownsSampler_=false
so shared samplers aren't double-freed.

Also auto-disable anisotropy in the cache when the physical device
doesn't support the samplerAnisotropy feature, fixing the validation
error VUID-VkSamplerCreateInfo-anisotropyEnable-01070.
2026-03-24 11:44:54 -07:00
Kelsi
4fcb92dfdc fix: skip FSR3 frame gen on non-AMD GPUs to prevent NVIDIA driver crash
The AMD FidelityFX FSR3 runtime corrupts Vulkan driver state when
context creation fails on NVIDIA GPUs, causing vkCmdBeginRenderPass
to SIGSEGV inside libnvidia-glcore. Gate FSR3 frame gen initialization
behind isAmdGpu() check — FSR2 upscaling still works on all GPUs.
2026-03-24 10:11:21 -07:00
Kelsi
c8c01f8ac0 perf: add Vulkan pipeline cache persistence for faster startup
Create a VkPipelineCache at device init, loaded from disk if available.
All 65 pipeline creation calls across 19 renderer files now use the
shared cache. On shutdown, the cache is serialized to disk so subsequent
launches skip redundant shader compilation.

Cache path: ~/.local/share/wowee/pipeline_cache.bin (Linux),
~/Library/Caches/wowee/ (macOS), %APPDATA%\wowee\ (Windows).
Stale/corrupt caches are handled gracefully (fallback to empty cache).
2026-03-24 09:47:03 -07:00
Kelsi
df7feed648 feat: add distinct STORM weather type with wind-driven particles
Add Weather::Type::STORM enum value and wire it from SMSG_WEATHER type 3.
Storm particles are faster (70 units/s vs rain's 50), wind-angled at 15+
units lateral velocity with gusty turbulence, darker blue-grey tint, and
shorter lifetime. Previously storms rendered identically to rain.
2026-03-20 15:56:58 -07:00
Kelsi
b89aa36483 fix: clear spell visual negative cache on world entry
The spell visual failed-model cache was never cleared across world
changes, so models that failed to load during initial asset loading
(before MPQ/CASC data was fully indexed) would never retry. Now clears
spellVisualFailedModels_ in resetCombatVisualState() alongside the
active spell visual cleanup, giving failed models a fresh attempt on
each world entry.
2026-03-20 09:14:53 -07:00
Kelsi
f101ed7c83 fix: clear spell visual instances on map change/world entry
Active spell visual M2 instances were never cleaned up when the player
teleported to a different map or re-entered the world. Orphaned effects
could linger visually from the previous combat session. Now properly
removes all active spell visual instances in resetCombatVisualState(),
which is called on every world entry.
2026-03-20 07:23:38 -07:00
Kelsi
fa82d32a9f feat: add [indoors]/[outdoors] macro conditionals via WMO detection
Add indoor/outdoor state macro conditionals using the renderer's WMO
interior detection. Essential for mount macros that need to select
ground mounts indoors vs flying mounts outdoors. The Renderer now
caches the insideWmo state in playerIndoors_ and exposes it via
isPlayerIndoors(). Updated /macrohelp to list the new conditionals.
2026-03-20 06:29:33 -07:00
Kelsi
bda5bb0a2b fix: add negative cache for failed spell visual model loads
Spell visual M2 models that fail to load (missing file, empty model,
or GPU upload failure) were re-attempted on every subsequent spell cast,
causing repeated file I/O during combat. Now caches failed model IDs in
spellVisualFailedModels_ so they are skipped on subsequent attempts.
2026-03-20 05:56:33 -07:00
Kelsi
90edb3bc07 feat: use M2 animation duration for spell visual lifetime
Spell visual effects previously used a fixed 3.5s duration for all
effects, causing some to linger too long and overlap during combat.
Now queries the M2 model's default animation duration via the new
getInstanceAnimDuration() method and clamps it to 0.5-5s. Effects
without animations fall back to a 2s default. This makes spell impacts
feel more responsive and reduces visual clutter.
2026-03-20 05:52:47 -07:00
Kelsi
2dc5b21341 feat: add screenshot capture (PrintScreen key and /screenshot command)
Captures the Vulkan swapchain image to PNG via stb_image_write.
Screenshots saved to ~/.wowee/screenshots/ with timestamped filenames.
Cross-platform: BGRA→RGBA swizzle, localtime_r/localtime_s.
2026-03-18 10:47:34 -07:00
Kelsi
36fed15d43 feat: separate cast/impact kit paths in spell visual DBC lookup
loadSpellVisualDbc() now builds two distinct maps:
  spellVisualCastPath_  — visualId → M2 via SpellVisual.CastKit chain
  spellVisualImpactPath_ — visualId → M2 via SpellVisual.ImpactKit chain

playSpellVisual() accepts useImpactKit=false (default, cast) / true (impact).
SMSG_PLAY_SPELL_IMPACT passes useImpactKit=true so impact effects (explosions,
debuff indicators) use the ImpactKit model instead of the CastKit model.
Added ImpactKit field to all four dbc_layouts.json files.
2026-03-17 18:30:11 -07:00
Kelsi
315adfbe93 feat: implement SMSG_PLAY_SPELL_VISUAL with SpellVisual DBC chain lookup
Parse SMSG_PLAY_SPELL_VISUAL (casterGuid + visualId) and spawn a
transient M2 spell effect at the caster's world position.

DBC chain: SpellVisual.dbc → SpellVisualKit.dbc → SpellVisualEffectName.dbc
Lookup priority: CastKit.SpecialEffect0, fallback to MissileModel.
Models are lazy-loaded and cached by path; instances auto-expire after 3.5s.
DBC layouts added to all four expansion layout files (Classic/TBC/WotLK/Turtle).
2026-03-17 18:23:05 -07:00
Kelsi
01685cc0bb feat: add ghost mode visual overlay when player is dead
Apply a cold blue-grey fullscreen overlay when the player is in ghost
form, creating a desaturated, muted appearance that clearly signals the
death state. Uses the existing overlay pipeline infrastructure. Applied
in both parallel and non-parallel rendering paths, after underwater tint
but before brightness adjustment so UI elements remain unaffected.
2026-03-17 10:58:07 -07:00
Kelsi
192c6175b8 feat: add brightness slider to Video settings
Black overlay dims below 50%, white overlay brightens above 50%.
Persisted in settings.cfg, with restore-defaults support.
2026-03-17 09:04:53 -07:00
Kelsi
a3279ea1ad fix: async Warden PAGE_A/PAGE_B checks to prevent main-loop stalls
Move 5-second brute-force HMAC-SHA1 code pattern searches to a
background thread via std::async. The main loop now detects PAGE_A/B
checks, launches the response builder async, and drains the result
in update() — encrypting and sending on the main thread to keep
wardenCrypto_ RC4 state thread-safe.

Also adds Turtle WoW PE binary support (isTurtle flag, dedicated exe
search, runtime patches), searchCodePattern with result caching,
writeLE32 public API, and Warden scan entry verification.
2026-03-16 16:46:29 -07:00
Kelsi
075b4c1772 fix(gameplay): tighten TB elevator bounds and reset stale combat visuals
Some checks are pending
Build / Build (arm64) (push) Waiting to run
Build / Build (x86-64) (push) Waiting to run
Build / Build (macOS arm64) (push) Waiting to run
Build / Build (windows-arm64) (push) Waiting to run
Build / Build (windows-x86-64) (push) Waiting to run
Security / CodeQL (C/C++) (push) Waiting to run
Security / Semgrep (push) Waiting to run
Security / Sanitizer Build (ASan/UBSan) (push) Waiting to run
2026-03-14 09:19:16 -07:00
Kelsi
cebca9a882 fix(gameplay): stabilize run animation and clean ghost/death visuals 2026-03-14 08:27:32 -07:00
Kelsi
5b195781ad fix(death): restore corpse reclaim and enforce ghost grayscale 2026-03-14 06:43:49 -07:00
Kelsi
44ff2dd4ee feat: show rain particles and audio during thunderstorm weather
Storm weather (wType==3 from SMSG_WEATHER) previously rendered no
visual particles and no audio. Map it to RAIN in the weather system so
thunderstorms produce rain particles at the server-sent intensity level,
and the ambient sound manager picks up rain_heavy/medium/light audio
from the same intensity logic already used for plain rain.

This pairs with the lightning commit — storms now have both rain
particles and lightning flashes for a complete thunderstorm experience.
2026-03-13 09:59:58 -07:00
Kelsi
727dfa5c6c feat: integrate lightning system for storm weather and heavy rain
The lightning system (lightning.hpp/cpp) was fully implemented but never
wired into the renderer. Connect it now:
- Enable lightning during server storm weather (wType==3, intensity>0.1)
  and heavy rain (wType==1, intensity>0.7) as a bonus visual
- Scale lightning intensity proportionally to weather intensity
- Render in both parallel (SEC_POST) and fallback rendering paths
- Update and shutdown alongside the weather system
- Show active lightning info in the performance HUD weather section
2026-03-13 09:52:23 -07:00
Kelsi
862d743f87 fix: WMO culling dead-end group fallback and minimap arrow direction
wmo_renderer: when portal BFS starts from a group with no portal refs
(utility/transition group), the rest of the WMO becomes invisible because
BFS only adds the starting group. Fix: if cameraGroup has portalCount==0,
fall back to marking all groups visible (same as camera-outside behavior).

renderer: minimap player-orientation arrow was pointing the wrong direction.
The shader convention is arrowRotation=0→North, positive→clockwise (West),
negative→East. The correct mapping from canonical yaw is arrowRotation =
-canonical_yaw. Fixed both render paths (cameraController and gameHandler)
in both the FXAA and non-FXAA minimap render calls.

World-map W-key: already corrected in prior commit (13c096f); users with
stale ~/.wowee/settings.cfg should rebind via Settings > Controls.
2026-03-13 02:25:06 -07:00
Kelsi
1108aa9ae6 feat: implement M2 ribbon emitter rendering for spell trail effects
Parse M2RibbonEmitter data (WotLK format) from M2 files — bone index,
position, color/alpha/height tracks, edgesPerSecond, edgeLifetime,
gravity. Add CPU-side trail simulation per instance (edge birth at bone
world position, lifetime expiry, gravity droop). New m2_ribbon.vert/frag
shaders render a triangle-strip quad per emitter using the existing
particleTexLayout_ descriptor set. Supports both alpha-blend and additive
pipeline variants based on material blend mode. Fixes invisible spell
trail effects (~5-10%% of spell visuals) that were silently skipped.
2026-03-13 01:17:30 -07:00
Kelsi
acf99354b3 feat: add ghost mode grayscale screen effect
- FXAA path: repurpose _pad field as 'desaturate' push constant; when
  ghostMode_ is true, convert final pixel to grayscale with slight cool
  blue tint using luma(0.299,0.587,0.114) mix
- Non-FXAA path: apply a high-opacity gray overlay (rgba 0.5,0.5,0.55,0.82)
  over the scene for a washed-out look
- Both parallel (SEC_POST) and single-threaded render paths covered
- ghostMode_ flag set each frame from gameHandler->isPlayerGhost()
2026-03-13 00:59:36 -07:00
Kelsi
d52c49c9fa fix: FXAA sharpening and MSAA exclusion
- Post-FXAA unsharp mask: when FSR2 is active alongside FXAA, forward
  the FSR2 sharpness value (0–2) to the FXAA fragment shader via a new
  vec4 push constant. A contrast-adaptive sharpening step (unsharp mask
  scaled to 0–0.3) is applied after FXAA blending, recovering the
  crispness that FXAA's sub-pixel blend removes.  At sharpness=2.0 the
  output matches RCAS quality; at sharpness=0 the step is a no-op.

- MSAA guard: setFXAAEnabled() refuses to activate FXAA when hardware
  MSAA is in use. FXAA's role is to supplement FSR temporal AA, not to
  stack on top of MSAA which already resolves jaggies during the scene
  render pass.
2026-03-12 22:38:37 -07:00
Kelsi
ebaf95cc42 fix: remove Y-flip counter-hacks in FSR shaders; invert mouse by default; FSR1 disables MSAA
FSR EASU and FSR2 sharpen fragment shaders had a manual Y-flip to undo
the now-removed postprocess.vert flip.  Strip those since the vertex
shader no longer flips, making all postprocess paths consistent.

Also flip the default mouse Y-axis to match user expectation (mouse
down = look up / flight-sim style) and make FSR1 disable MSAA on
enable, matching FSR2 behaviour (FSR provides its own spatial AA).
2026-03-12 21:59:41 -07:00
Kelsi
eafd09aca0 feat: allow FXAA alongside FSR1 and FSR3 simultaneously
- Remove !fsr_.enabled / !fsr2_.enabled guards that blocked FXAA init
- FXAA can now coexist with FSR1 and FSR3 simultaneously
- Priority: FSR3 > FXAA > FSR1
  - FSR3 + FXAA: scene renders at FSR3 internal res, temporal AA runs,
    then FXAA reads FSR3 history and applies spatial AA to swapchain
    (replaces RCAS sharpening for ultra-quality native mode)
  - FXAA + FSR1: scene renders at native res, FXAA post-processes;
    FSR1 resources exist but are idle (FXAA wins for better quality)
  - FSR3 only / FSR1 only: unchanged paths
- Fix missing fxaa.frag.spv: shader was present but uncompiled; the
  CMake compile_shaders() function will now pick it up on next build
2026-03-12 18:58:30 -07:00
Kelsi
6e95709b68 feat: add FXAA post-process anti-aliasing, combinable with MSAA 2026-03-12 16:43:48 -07:00
Kelsi
68a379610e Fix animation timing precision loss by replacing fmod with iterative subtraction
Floating-point fmod() loses precision with large accumulated time values, causing
subtle jumps/hitches in animation loops. Replace with iterative duration subtraction
to keep animationTime bounded and maintain precision, consistent with the fix
applied to character_renderer.cpp.

Applies to:
- M2 creature/object animation loops (main update)
- M2 particle-only instance wrapping (3333ms limit)
- M2 global sequence timing resolution
- M2 animated particle tile indexing
- Mount bobbing motion (sinusoidal rider motion)
- Character footstep trigger timing
- Mount footstep trigger timing

All timing computations now use the same precision-preserving approach.
2026-03-11 18:14:25 -07:00
Kelsi
2b9f216dae fix: rest state detection and minimap north-up orientation
- WotLK opcode 0x21E is aliased to both SMSG_SET_REST_START and
  SMSG_QUEST_FORCE_REMOVE. In WotLK, treat as SET_REST_START (non-zero
  = entering rest area, zero = leaving); Classic/TBC treat as quest removal.
- PLAYER_BYTES_2 rest state byte: change from `& 0x01` to `!= 0` to also
  detect REST_TYPE_IN_CITY (value 2), not just REST_TYPE_IN_TAVERN (1).
- Minimap arrow: server orientation (π/2=North) needed conversion to
  minimap arrow space (0=North). Subtract π/2 in both render paths so
  arrow points North when player faces North.
2026-03-10 20:29:55 -07:00