acdream/docs/research/2026-07-26-retail-hardwareless-particle-emitter-diagnostics.md

82 lines
2.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Retail hardwareless particle-emitter resolution
Date: 2026-07-26
Oracle: matching Sept-2013 `acclient.exe` + `acclient.pdb`
## Symptom
Dense scenes repeatedly logged that ParticleEmitterInfo records such as
`0x320002D6` and `0x320003B6``0x320003B9` were “not found” for many different
owners. The records were present in the installed DAT, so the message was
misleading and each owner repeated the same failed resolution work.
## Named-retail behavior
`ParticleEmitter::SetInfo @ 0x0051CE90` begins with:
```text
Destroy()
info = suppliedInfo
if info.hw_gfxobj_id == INVALID_DID:
Destroy()
return false
particleObject = makeParticleObject(info.max_particles, info.sorting_sphere)
...
```
`ParticleEmitter::SetInfo(DataID) @ 0x0051D3C0` first loads the
ParticleEmitterInfo and delegates to the overload above.
`ParticleManager::CreateParticleEmitter @ 0x0051B6C0` destroys the attempted
emitter when either `SetInfo` or parenting fails. Retail does not substitute
the software `gfxobj_id` for an absent hardware GfxObj.
## Installed-DAT audit
The production `DatCollection` and `EmitterDescRegistry` were exercised
through `ProjectileVfxAudit --emitters`:
| Emitter | Record | Software GfxObj | Hardware GfxObj | Retail outcome |
|---|---|---:|---:|---|
| `0x320002D6` | present/parsed | present | `0` | skip |
| `0x320003B6` | present/parsed | present | `0` | skip |
| `0x320003B7` | present/parsed | present | `0` | skip |
| `0x320003B8` | present/parsed | present | `0` | skip |
| `0x320003B9` | present/parsed | present | `0` | skip |
These are authored hardwareless records, not missing DAT files.
## Cross-reference
- ACViewer `ParticleEmitter.SetInfo` returns false when `HWGfxObjID == 0`.
Its render initialization explicitly comments that this case is expected and
skips the absent emitter.
- WorldBuilder contains no contradictory live-client hardware-emitter
fallback. Its particle code is an editor/rendering reference, while the
retail client remains the behavior oracle.
## Runtime contract
`EmitterDescRegistry` now stores either one resolved descriptor or one
classified negative result per DAT ID:
```text
MissingEmitterInfo
InvalidHardwareGfxObjId
MissingHardwareGfxObj
```
The particle hook sink reports the first failure for each emitter DAT ID, not
one line per owner. Hardwareless records say that retail skipped them rather
than claiming the ParticleEmitterInfo was absent. No fallback asset or
particle is fabricated.
Registration of an explicit runtime descriptor clears a cached negative
result, preserving test/tool injection and future hot-reload behavior.
## Conformance coverage
- `EmitterDescRegistry_NegativeCachesMissingDatAndRegisterClearsFailure`
- `EmitterDescRegistry_ClassifiesAndCachesAuthoredHardwarelessEmitter`
- `MissingEmitterAsset_ReportsOnlyOnceAcrossManyOwners`