Skip to content

refactor(particlesys): Cleanup retail volume particle depth handling - #3188

Open
Mauller wants to merge 1 commit into
TheSuperHackers:mainfrom
Mauller:Mauller/chore-cleanup-vol-particle-depth-handling
Open

refactor(particlesys): Cleanup retail volume particle depth handling#3188
Mauller wants to merge 1 commit into
TheSuperHackers:mainfrom
Mauller:Mauller/chore-cleanup-vol-particle-depth-handling

Conversation

@Mauller

@Mauller Mauller commented Aug 22, 2026

Copy link
Copy Markdown

This PR is a refactor to cleanup the handling of volume depth for volume type and normal particles.

The particle system class originally returned a hard coded value from getVolumeParticleDepth() instead of returning the variable m_volumeParticleDepth.

This value is now retrieved from the particle template and exposed to configuration through the ini field of VolParticleDepth.

To preserve the retail particle behaviour, we identify uninitialised volume particles and set their particle depth to the original hard coded value. Otherwise the configured by ini value will be used.

EDIT - For normal particles we now also initialise their depth to 1.

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

Copy link
Copy Markdown

PR Summary by Qodo

ParticleSys: Make volume particle depth configurable while preserving retail defaults

🐞 Bug fix ⚙️ Configuration changes 🕐 10-20 Minutes

Grey Divider

AI Description

• Read volume particle depth from particle templates instead of using a hard-coded constant.
• Add INI support for configuring volume particle depth via "VolParticleDepth".
• Preserve retail behavior by defaulting legacy volume particles to the original depth.
Diagram

graph TD
  A[/"INI: VolParticleDepth"/] --> B["ParticleSystemTemplate"] --> C["ParticleSystem ctor"] --> D["m_volumeParticleDepth"] --> E["getVolumeParticleDepth()"] --> F["Volume particle render"]
  C --> G{"PRESERVE_RETAIL_PARTICLES && depth==DEFAULT?"} --> H["Force OPTIMUM depth (6)"] --> D
  subgraph Legend
    direction LR
    _in[/"INI field"/] ~~~ _proc["Runtime/template" ] ~~~ _dec{"Decision"}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Move retail defaulting into template initialization/parsing
  • ➕ Keeps runtime constructor logic simpler (no retail-specific override in ParticleSystem ctor).
  • ➕ Ensures any other users of the template value see the same retail default consistently.
  • ➖ May require careful ordering to know particle type/name at parse time.
  • ➖ Could be harder to reason about if parsing and compatibility logic become intertwined.
2. Use OPTIMUM as the default and treat explicit INI as override
  • ➕ Eliminates the special-case check for DEFAULT implying retail.
  • ➕ Simplifies behavior: depth is always sensible unless explicitly changed.
  • ➖ Risky if DEFAULT has meaning beyond volume particles elsewhere.
  • ➖ Could unintentionally change non-retail assets that relied on DEFAULT semantics.

Recommendation: Current approach is reasonable for a low-risk refactor: it makes depth configurable while explicitly guarding retail parity. If retail-compatibility logic grows, consider relocating the defaulting into template initialization/parsing to keep ParticleSystem construction focused on value transfer.

Files changed (2) +10 / -2

Bug fix (1) +1 / -1
ParticleSys.hUse configured volume particle depth in accessor +1/-1

Use configured volume particle depth in accessor

• Updates ParticleSystem::getVolumeParticleDepth() to return the instance member m_volumeParticleDepth for VOLUME_PARTICLE instead of a hard-coded constant. Non-volume particles continue to return the default depth value.

Core/GameEngine/Include/GameClient/ParticleSys.h

Other (1) +9 / -1
ParticleSys.cppWire volume depth from template/INI and preserve retail default +9/-1

Wire volume depth from template/INI and preserve retail default

• Initializes ParticleSystem::m_volumeParticleDepth from the template value rather than forcing the default. Adds a retail-compatibility fallback that restores the legacy OPTIMUM depth when no INI depth is configured, and introduces the new INI field parse entry "VolParticleDepth" to populate ParticleSystemTemplate::m_volumeParticleDepth.

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

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

Copy link
Copy Markdown

Code Review by Qodo

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

Grey Divider


Action required

1. Vol depth 0 ignored 🐞 Bug ≡ Correctness
Description
When PRESERVE_RETAIL_PARTICLES is enabled, any VOLUME_PARTICLE with m_volumeParticleDepth equal to
DEFAULT_VOLUME_PARTICLE_DEPTH (0) is forcibly changed to OPTIMUM_VOLUME_PARTICLE_DEPTH (6), so an
INI value of VolParticleDepth=0 cannot be honored. This makes it impossible to explicitly configure
depth 0 for volume particles and can unintentionally enable expensive volume rendering when authors
expected depth 0 to disable it.
Code

Core/GameEngine/Source/GameClient/System/ParticleSys.cpp[R1201-1204]

+	// In retail, volume particle depth was not setup through ini and was hard coded to a particle depth of 6
+	if (m_particleType == ParticleType::VOLUME_PARTICLE && m_volumeParticleDepth == DEFAULT_VOLUME_PARTICLE_DEPTH)
+	{
+		m_volumeParticleDepth = OPTIMUM_VOLUME_PARTICLE_DEPTH;
Evidence
The PR’s new retail-compatibility block forces depth 0 to 6 for all volume particles, while the
codebase documents 0 as the default “disable volume” depth and the renderer only uses volume
rendering when depth > 1. Because INI parsing cannot distinguish “unset” from an explicit 0, the
override prevents intentionally configuring depth 0.

Core/GameEngine/Source/GameClient/System/ParticleSys.cpp[1193-1205]
Core/GameEngine/Include/GameClient/ParticleSys.h[60-62]
Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp[328-334]

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

### Issue description
`VolParticleDepth` is parsed into `m_volumeParticleDepth` with a default of `DEFAULT_VOLUME_PARTICLE_DEPTH` (0). Under `PRESERVE_RETAIL_PARTICLES`, the constructor treats `m_volumeParticleDepth == 0` as “retail/unset” and overwrites it to 6, which also overwrites an explicit INI configuration of `VolParticleDepth=0`.

### Issue Context
- `DEFAULT_VOLUME_PARTICLE_DEPTH` is documented as “not to do the volume thing” (0).
- Volume rendering is only used when depth > 1.
- Current logic cannot tell whether depth==0 came from “field missing” or “field explicitly set to 0”.

### Fix Focus Areas
- Implement an explicit "was specified" signal (or sentinel value) for `VolParticleDepth`, and only apply the retail override when the field was not specified.
- file: Core/GameEngine/Source/GameClient/System/ParticleSys.cpp[2756-2760]
- file: Core/GameEngine/Source/GameClient/System/ParticleSys.cpp[1193-1205]
- file: Core/GameEngine/Include/GameClient/ParticleSys.h[60-63]

### Suggested approach
- Add a `bool m_volumeParticleDepthSpecified` (or similar) to `ParticleSystemTemplate` (or `ParticleSystemInfo` if appropriate).
- Replace the parse-table entry for `VolParticleDepth` with a custom parser that sets both `m_volumeParticleDepth` and `m_volumeParticleDepthSpecified=true`.
- Change the retail-compat block to:
 - if volume particle AND `!m_volumeParticleDepthSpecified` then set to `OPTIMUM_VOLUME_PARTICLE_DEPTH`.
 - otherwise honor the configured value, including 0.

ⓘ 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 commit Qodo's fix in one click with committable suggestions (GitHub & GitLab)

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread Core/GameEngine/Source/GameClient/System/ParticleSys.cpp Outdated
Comment thread Core/GameEngine/Include/GameClient/ParticleSys.h Outdated
Comment thread Core/GameEngine/Source/GameClient/System/ParticleSys.cpp Outdated
Comment thread Core/GameEngine/Source/GameClient/System/ParticleSys.cpp Outdated
Comment thread Core/GameEngine/Source/GameClient/System/ParticleSys.cpp Outdated
@Mauller
Mauller force-pushed the Mauller/chore-cleanup-vol-particle-depth-handling branch from bbbc607 to a596261 Compare August 22, 2026 14:53
Comment thread Core/GameEngine/Source/Common/INI/INIParticleSys.cpp Outdated
@OmarAglan

Copy link
Copy Markdown

The overall direction looks good, but I think two issues should be addressed before merging:

  1. The retail fallback cannot distinguish an omitted VolParticleDepth from an explicitly configured value of 0. Depths 0 and 1 are handled safely by the rendering path by falling back to standard particle rendering, so 0 appears to be a meaningful way to disable volume rendering. It may be cleaner to default volume templates to OPTIMUM_VOLUME_PARTICLE_DEPTH and allow the INI value to override it, including with 0.

  2. VolParticleDepth is parsed, but the Generals and Zero Hour _writeSingleParticleSystem() implementations do not write it. Because the particle editor regenerates ParticleSystem.ini, saving the file would silently discard a configured depth.

Other than these configuration and round-trip concerns, the change looks clean and the CI results are good.

@Mauller
Mauller force-pushed the Mauller/chore-cleanup-vol-particle-depth-handling branch from a596261 to 291a2e3 Compare August 22, 2026 21:48
Comment thread GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp Outdated
Comment thread Core/GameEngine/Source/Common/INI/INIParticleSys.cpp Outdated
@Mauller
Mauller force-pushed the Mauller/chore-cleanup-vol-particle-depth-handling branch from 291a2e3 to 53eef90 Compare August 23, 2026 08:38
@Mauller

Mauller commented Aug 23, 2026

Copy link
Copy Markdown
Author

Here's a test showing different levels of volume particle depth.

One appears to not work very well in this instance if you let the volume particle code work. but this could also be down to placement of the particle emitter on the microwave tank. It looks like the particle is there but it pretty much renders billboard and flat, you can partly see the line of it across the top of the flat portion of the microwave emitter / turret.

Screenshots

This is as volume particle set to 1
image

This is letting it render as a normal particle in this instance using the escape code.
image

Two you can minimally start to see the particle effect
image

Three is significantly more noticeable
image

depth four
image

depth five
image

Retail depth of "Optimal" at 6
image

I am going to jump a bit for the next ones to every second level to 16 which is considered the max.

Depth 8
image

Depth 10
image

Depth 12
image

Going to jump to 16 here as i think people get the point
image

image

Brighter than the SUN! if only we had HDR for this we could blind people.

@Mauller
Mauller force-pushed the Mauller/chore-cleanup-vol-particle-depth-handling branch from 53eef90 to c32d92c Compare August 23, 2026 13:37
@Mauller

Mauller commented Aug 23, 2026

Copy link
Copy Markdown
Author

Fixed based on feedback and did a little more cleanup around it as well.

Comment thread Core/GameEngine/Include/GameClient/ParticleSys.h Outdated
Comment thread Core/GameEngine/Source/Common/INI/INIParticleSys.cpp Outdated
@Mauller
Mauller force-pushed the Mauller/chore-cleanup-vol-particle-depth-handling branch from c32d92c to ffa3dc6 Compare August 23, 2026 21:23
@Mauller

Mauller commented Aug 23, 2026

Copy link
Copy Markdown
Author

Updated with recent suggestions along with a little extra cleanup around the same area.

Comment thread Core/GameEngine/Include/GameClient/ParticleSys.h Outdated
//RENDER IT!
if( sys->getVolumeParticleDepth() > 1 )
const UnsignedInt volumeParticleDepth = sys->getVolumeParticleDepth();
if( sys->isUsingVolumeParticles() && volumeParticleDepth > DEFAULT_VOLUME_PARTICLE_DEPTH )

@Mauller Mauller Aug 24, 2026

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.

I might clean this up in the particle batching PR, it becomes more significant for ignoring misconfigured volume particles as it skips all of their setup.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Why is testing for volumeParticleDepth > DEFAULT_VOLUME_PARTICLE_DEPTH not sufficient?

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.

Because that information is not meant to tell you the particle type.

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.

I didn't want to drop the early depth test since it helps escape earlier in the batching code.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

So non-volume particles cannot be drawn with depth >= 2 ?

@Mauller Mauller Aug 24, 2026

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.

They don't get draw by RenderVolumeParticle() they use Render. Non volume particles also don't make use of the volume depth variable.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

What happens if a non volume particle with depth 2 is passed to RenderVolumeParticle? Does it do anything?

Comment thread Core/GameEngine/Include/GameClient/ParticleSys.h Outdated
Comment thread Core/GameEngine/Include/GameClient/ParticleSys.h Outdated
//RENDER IT!
if( sys->getVolumeParticleDepth() > 1 )
const UnsignedInt volumeParticleDepth = sys->getVolumeParticleDepth();
if( sys->isUsingVolumeParticles() && volumeParticleDepth > DEFAULT_VOLUME_PARTICLE_DEPTH )

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Why is testing for volumeParticleDepth > DEFAULT_VOLUME_PARTICLE_DEPTH not sufficient?

@Mauller
Mauller force-pushed the Mauller/chore-cleanup-vol-particle-depth-handling branch from ffa3dc6 to 1034a5b Compare August 24, 2026 19:39
Bool isUsingDrawables() { return m_particleType == DRAWABLE; }
Bool isUsingStreak() { return m_particleType == STREAK; }
Bool isUsingSmudge() { return m_particleType == SMUDGE; }
Bool isUsingVolumeParticles() { return m_particleType == VOLUME_PARTICLE; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

const

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

Labels

Gen Relates to Generals Minor Severity: Minor < Major < Critical < Blocker 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