Skip to content

refactor(particlesys): Cleanup retail smudge particle type identification - #3162

Merged
xezon merged 2 commits into
TheSuperHackers:mainfrom
Mauller:Mauller/chore-cleanup-smudge-type-handling
Aug 18, 2026
Merged

refactor(particlesys): Cleanup retail smudge particle type identification#3162
xezon merged 2 commits into
TheSuperHackers:mainfrom
Mauller:Mauller/chore-cleanup-smudge-type-handling

Conversation

@Mauller

@Mauller Mauller commented Aug 17, 2026

Copy link
Copy Markdown

Squash Merge

This PR is a simple refactor to cleanup the identification of smudge type particles.

The particle system manager originally had a hack to look for smudges based on the particles name.
Particles already contained code that allowed their type to be set as smudge and to query if they were smudge particles using the isUsingSmudge() helper function.

With this refactor, the retail "hack" check occurs within the particles constructor, allowing the particle type to be set to the smudge type.
This then allows external code to use the isUsingSmudge() helper function as originally intended.

@Mauller Mauller self-assigned this Aug 17, 2026
@Mauller Mauller added Gen Relates to Generals ZH Relates to Zero Hour Refactor Edits the code with insignificant behavior changes, is never user facing labels Aug 17, 2026
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Fix smudge particle type detection by setting type in ParticleSystem ctor

🐞 Bug fix ✨ Enhancement 🕐 10-20 Minutes

Grey Divider

AI Description

• Detect misconfigured retail smudge systems and force ParticleType::SMUDGE in the constructor.
• Replace renderer-side texture-name hack with the existing isUsingSmudge() type check.
• Ensure smudge rendering paths rely on particle type, not string matching.
Diagram

graph TD
  A[("Retail particle templates")] --> B["ParticleSystem ctor"] --> C["Force SMUDGE type"] --> D["W3D particle render"] --> E["SmudgeManager"]
  D --> F["isUsingSmudge()"]
  subgraph Legend
    direction LR
    _data[(Data)] ~~~ _mod["Code module"] ~~~ _svc([Service])
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Fix retail particle templates at the source
  • ➕ Removes runtime heuristics
  • ➕ Keeps engine logic strictly data-driven
  • ➖ May not be feasible if retail assets must remain unchanged
  • ➖ Requires asset validation/distribution across packs/mods
2. Normalize type during template/INI parsing (not per instance)
  • ➕ Centralizes the workaround where types are loaded
  • ➕ Avoids repeating the check on every ParticleSystem instantiation
  • ➖ Still a heuristic, just moved earlier
  • ➖ May complicate template caching/versioning depending on loader design
3. Use a safe prefix compare instead of DWORD cast
  • ➕ Avoids potential strict-aliasing/alignment/endianness pitfalls
  • ➕ More readable intent (e.g., strncmp(name,"SMUD",4))
  • ➖ Marginally slower than an integer compare (likely irrelevant)
  • ➖ Requires careful handling of short/empty names

Recommendation: The chosen approach—set the correct particle type once in the ParticleSystem constructor and then rely on isUsingSmudge()—is the best architectural outcome because it removes duplicated renderer-side hacks. Consider switching the DWORD-based prefix check to a safer string prefix comparison to reduce portability/UB risk while preserving behavior.

Files changed (2) +24 / -19

Bug fix (1) +8 / -0
ParticleSys.cppNormalize smudge particle type during ParticleSystem construction +8/-0

Normalize smudge particle type during ParticleSystem construction

• Adds a constructor-time fallback that checks whether the particle type name starts with "SMUD" and forces m_particleType to ParticleType::SMUDGE when the retail template type is incorrect. This enables downstream code to use type-based helpers reliably.

Core/GameEngine/Source/GameClient/System/ParticleSys.cpp

Refactor (1) +16 / -19
W3DParticleSys.cppUse isUsingSmudge() for smudge rendering path gating +16/-19

Use isUsingSmudge() for smudge rendering path gating

• Removes the texture-name DWORD hack used to detect smudge systems in the W3D particle render loop and instead gates smudge processing on drawSmudge && sys->isUsingSmudge(). Keeps the same per-particle culling and SmudgeManager draw-flag behavior.

Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Unsafe SMUD prefix read 🐞 Bug ☼ Reliability
Description
ParticleSystem now detects smudges by dereferencing a DWORD cast of m_particleTypeName.str(), which
is undefined behavior (unaligned access + reads beyond the empty-string sentinel) and can crash or
misclassify. This runs for every ParticleSystem construction, so the UB is no longer limited to the
smudge-rendering path.
Code

Core/GameEngine/Source/GameClient/System/ParticleSys.cpp[R1196-1199]

+	if (m_particleType != ParticleType::SMUDGE && *((DWORD*)m_particleTypeName.str()) == 0x44554D53) // "SMUD"
+	{
+		m_particleType = ParticleType::SMUDGE;
+	}
Evidence
The new code dereferences a 32-bit word from AsciiString::str(). For empty strings,
AsciiString::str() returns a pointer to a single static NUL byte, so reading 4 bytes is
out-of-bounds; additionally, dereferencing a DWORD* from a char* can be
unaligned/aliasing-unsafe. The engine already treats empty particle names as valid during asset
preload, so this path is plausible.

Core/GameEngine/Source/GameClient/System/ParticleSys.cpp[1193-1199]
Core/GameEngine/Include/Common/AsciiString.h[448-453]
Core/GameEngine/Source/GameClient/System/ParticleSys.cpp[3321-3326]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`ParticleSystem::ParticleSystem(...)` currently checks for the "SMUD" prefix by casting `const char*` to `DWORD*` and dereferencing it. This is undefined behavior due to (1) reading 4 bytes even when the string is empty and `AsciiString::str()` returns a pointer to a single NUL byte, and (2) potential unaligned access/strict-aliasing violations.

## Issue Context
`AsciiString::str()` returns a pointer to a static `char` when empty, so any 4-byte read is out-of-bounds. The codebase already treats empty `m_particleTypeName` as valid, so this can be hit in real data.

## Fix Focus Areas
- Core/GameEngine/Source/GameClient/System/ParticleSys.cpp[1193-1200]

Suggested implementation shape:
- Guard with `if (m_particleType != SMUDGE)` and `if (m_particleTypeName.getLength() >= 4)`
- Compare via `memcmp(m_particleTypeName.str(), "SMUD", 4) == 0` (or `strncmp(..., 4) == 0`)
- Avoid any `DWORD*` casts/dereferences

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Smudge classification mismatch 🐞 Bug ≡ Correctness
Description
W3D rendering now routes systems through the smudge path based on sys->isUsingSmudge() (i.e.,
m_particleType == SMUDGE), but smudge-object creation in ParticleSystemManager::update still
uses the legacy "SMUD" name-prefix hack. If a template is configured with Type=SMUDGE but its
ParticleName doesn’t start with "SMUD", the renderer will skip normal particle rendering yet
findSmudge(p) will fail, so particles won’t render as expected.
Code

Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp[R159-162]

+		// Handle smudge type particles
+		if (drawSmudge && sys->isUsingSmudge())
		{
-			if (drawSmudge)
+			for (Particle* p = sys->getFirstParticle(); p; p = p->m_systemNext)
Evidence
The PR changes W3D smudge detection from a name-prefix hack to isUsingSmudge(), but the smudge
rebuild path still uses the name-prefix hack. Since Type is parsed independently of ParticleName
and isUsingSmudge() depends only on type, these two paths can disagree and produce missing smudge
objects for systems the renderer considers smudges.

Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp[159-184]
Core/GameEngine/Source/GameClient/System/ParticleSys.cpp[3014-3047]
Core/GameEngine/Source/GameClient/System/ParticleSys.cpp[2669-2674]
Core/GameEngine/Include/GameClient/ParticleSys.h[605-608]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Smudge rendering in `W3DParticleSystemManager::doParticles` now relies on `sys->isUsingSmudge()`, but smudge creation/reset in `ParticleSystemManager::update` still decides which systems are smudges using a hard-coded name-prefix check. This creates a split-brain where the renderer and smudge builder can disagree, leading to smudge systems being diverted away from normal rendering without any corresponding `Smudge` objects.

## Issue Context
Templates can set `Type` independently from `ParticleName` (INI parsing), and `isUsingSmudge()` checks only the type.

## Fix Focus Areas
- Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp[159-184]
- Core/GameEngine/Source/GameClient/System/ParticleSys.cpp[3014-3047]
- Core/GameEngine/Source/GameClient/System/ParticleSys.cpp[2669-2674]

Recommended fix:
- Change smudge creation in `ParticleSystemManager::update` to use `sys->isUsingSmudge()` (or a shared helper) instead of the "SMUD" prefix check.
- If you still need a retail-data fallback, keep the constructor normalization but ensure *all* downstream smudge logic uses the normalized type (not a separate prefix heuristic).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can route each action level your way: inline, summary, both, or drop

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread Core/GameEngine/Source/GameClient/System/ParticleSys.cpp Outdated
Comment thread Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp Outdated
@Mauller
Mauller force-pushed the Mauller/chore-cleanup-smudge-type-handling branch from 14032b1 to 29621f3 Compare August 17, 2026 21:01
@Mauller

Mauller commented Aug 17, 2026

Copy link
Copy Markdown
Author

Refactored now, this shouldn't be too much slower than the original retail hack, but it should be significantly safer.

@Mauller
Mauller force-pushed the Mauller/chore-cleanup-smudge-type-handling branch 2 times, most recently from 24df39e to 01d934c Compare August 17, 2026 21:32
@Mauller

Mauller commented Aug 17, 2026

Copy link
Copy Markdown
Author

Fixed another instance where the smudge hack was being used in particle system manager

@Mauller
Mauller force-pushed the Mauller/chore-cleanup-smudge-type-handling branch from 01d934c to f133dc4 Compare August 17, 2026 22:09

@Caball009 Caball009 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Looks ok to me. Did not test.

Comment thread Core/GameEngine/Source/GameClient/System/ParticleSys.cpp Outdated
@Mauller
Mauller force-pushed the Mauller/chore-cleanup-smudge-type-handling branch from f133dc4 to 9917403 Compare August 18, 2026 16:21
@Mauller

Mauller commented Aug 18, 2026

Copy link
Copy Markdown
Author

Updated based on feedback, should be good now.

m_particleType = sysTemplate->m_particleType;
m_particleTypeName = sysTemplate->m_particleTypeName;

// TheSuperHackers @info Hack to allow isUsingSmudge() functionality with retail smudge particles

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This would be good to put behind PRESERVE_RETAIL_PARTICLES after #2709. Or an adjacent define.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Is that going to get merged soon? If not can i grab the define section from it and add it to this PR.

Looking to get this merged so i can rebase the particle optimisation PR off it as it helps clean up some of the code there.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Yes you can grab the PRESERVE_RETAIL_PARTICLES for this change.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done, this can be squash merged, the seperate PR's are just to make it easier to review.

Comment thread Core/GameEngine/Source/GameClient/System/ParticleSys.cpp Outdated
@Mauller
Mauller force-pushed the Mauller/chore-cleanup-smudge-type-handling branch 2 times, most recently from d488219 to 971e4ce Compare August 18, 2026 19:51
@Mauller

Mauller commented Aug 18, 2026

Copy link
Copy Markdown
Author

Updated and ready

Comment thread Core/GameEngine/Source/GameClient/System/ParticleSys.cpp Outdated
@Mauller
Mauller force-pushed the Mauller/chore-cleanup-smudge-type-handling branch from 971e4ce to 81a3187 Compare August 18, 2026 20:00

@xezon xezon left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Very big

@xezon xezon changed the title chore(particlesys): Cleanup retail smudge particle type identification refactor(particlesys): Cleanup retail smudge particle type identification Aug 18, 2026
@xezon
xezon merged commit 242f5a4 into TheSuperHackers:main Aug 18, 2026
16 checks passed
@xezon
xezon deleted the Mauller/chore-cleanup-smudge-type-handling branch August 18, 2026 20:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Gen Relates to Generals Refactor Edits the code with insignificant behavior changes, is never user facing ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants