research(vfx): pin retail projectile and effect oracle

Establish the executable-backed PhysicsDesc, sequence-gate, PhysicsScript, CreateBlocking, particle-anchor, projectile, and Hidden-state behavior before changing runtime code. Correct stale blocking/threshold claims and synchronize the project instructions with the current UI architecture and matching retail binary.

Add copyright-safe packet and DAT-container fixtures plus a failing installed-DAT conformance audit for projectile shapes, typed tables, recall motion, default scripts, and raw CreateBlocking inventory.

Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
Erik 2026-07-13 22:40:42 +02:00
parent bb5acab9e6
commit d53fe30ffe
14 changed files with 1545 additions and 53 deletions

View file

@ -190,7 +190,7 @@ PhysicsScriptData {
| Hook | Fields | Role |
|------|--------|------|
| `CreateParticleHook` | `EmitterInfoId`, `PartIndex`, `Offset (Frame)`, `EmitterId` | Spawn a named emitter bound to a body part |
| `CreateBlockingParticleHook` | — | Identical to CreateParticle but blocks motion-table advance until the emitter retires (used to gate spell cast sequences) |
| `CreateBlockingParticleHook` | Same fields as CreateParticle | Suppresses creation while the same nonzero logical emitter ID remains live; it does not pause motion-table advance |
| `StopParticleHook` | `EmitterId` | Mark emitter stopped (no new spawns, existing particles finish) |
| `DestroyParticleHook` | `EmitterId` | Instantly despawn all remaining particles |
| `DefaultScriptHook` | — | Used inside animation tables; implicit |
@ -211,7 +211,7 @@ PhysicsScriptTable {
}
PhysicsScriptTableData {
List<ScriptAndModData> Scripts // sorted by Mod descending
List<ScriptAndModData> Scripts // preserve stored DAT order
}
ScriptAndModData {
@ -221,12 +221,11 @@ ScriptAndModData {
```
The server sends `GameMessageScript(objectGuid, PlayScript, mod)` to clients.
The client picks the `Scripts` entry whose `Mod` is the largest value ≤ the
incoming `mod`. That's how a Frost I missile and a Frost VII missile can
share a `PlayScript.Launch` entry but show different-intensity effects: the
spell sets mod=0.3 vs mod=0.95, and the table picks the matching
`PhysicsScript` DID. This is the dispatch lookup done by
`ParticleViewer.GetModIdx()` (`ACViewer/ParticleViewer.cs:46-53`).
Retail scans the stored `Scripts` entries and returns the first entry for which
the incoming intensity is less than or equal to `Mod`. Equality matches; the
client neither sorts the entries nor falls back to the first. ACViewer's
`ParticleViewer.GetModIdx()` is only an interpretation aid where it differs
from `PhysicsScriptTableData::GetScript` (`0x00521A20`).
### PlayScript enum — the well-known effect namespace
@ -460,14 +459,14 @@ mapping is setup-specific; for humanoids the well-known indices are:
### Offset Frame
`CreateParticleHook.Offset` is a `Frame` (position + quaternion) applied
inside the parent-part's local space. For a torch: `PartIndex=0` (torch
mesh), `Offset=(0, 0, 0.45)` spawns the fire emitter at the top of the
torch regardless of how the torch is held. The frame is stored in dat
coordinates; our port multiplies it into the part-local transform with
standard `Matrix4x4.CreateFromQuaternion(Offset.Orientation) *
Matrix4x4.CreateTranslation(Offset.Origin)` and composes with the part's
world matrix each frame.
`CreateParticleHook.Offset` is encoded as a complete `Frame` (position plus
quaternion), but retail `Particle::Init` (`0x0051C930`) consumes only its
origin for particle emission. For a torch: `PartIndex=0` (torch mesh),
`Offset.Origin=(0, 0, 0.45)` spawns the fire emitter at the top of the torch
regardless of how the torch is held. Retail transforms that origin and the
emitter's own vectors through the current parent-part/root frame; it does not
apply `Offset.Orientation`. The quaternion is still decoded and retained for
schema fidelity, not multiplied into the particle anchor.
### Retail `set_parent` call