# 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`