feat(vfx): decode retail hooks and typed tables

Replace the incomplete package path with one DatCollection-backed compatibility seam for PhysicsScripts and Animations. Preserve CreateBlockingParticle's inherited payload and following cursor, route every production and audit consumer through the corrected loaders, and apply retail's post-UnPack StartTime ordering.

Add exact stored-order PhysicsScriptTable upper-threshold resolution, high-byte DID and embedded-ID validation, plus live effect profiles with Setup-to-PhysicsDesc precedence across top-level and attached entity lifetimes. Keep blocking execution deferred and narrow TS-11 accordingly.

Pin synthetic malformed/cursor/order fixtures, installed-DAT blocking and recall audits, high-index IDs, IEEE boundaries, profile teardown, and ordinary decoder parity; synchronize architecture, inventory, milestones, roadmap, research, and memory.

Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
Erik 2026-07-14 08:17:44 +02:00
parent 8dd996053d
commit 363e046112
31 changed files with 1102 additions and 73 deletions

View file

@ -3,18 +3,21 @@
using System;
using System.Collections.Generic;
using System.IO;
using AcDream.Content.Vfx;
using DatReaderWriter;
using DatReaderWriter.DBObjs;
using DatReaderWriter.Options;
using DatReaderWriter.Types;
using SysEnv = System.Environment;
using DatPhysicsScript = DatReaderWriter.DBObjs.PhysicsScript;
using RetailBlockingHook = AcDream.Core.Vfx.RetailCreateBlockingParticleHook;
string datDir = SysEnv.GetEnvironmentVariable("ACDREAM_DAT_DIR")
?? Path.Combine(SysEnv.GetFolderPath(SysEnv.SpecialFolder.UserProfile),
"Documents", "Asheron's Call");
Console.WriteLine($"datDir = {datDir}");
using var dats = new DatCollection(datDir, DatAccessType.Read);
var physicsScripts = new RetailPhysicsScriptLoader(dats);
uint[] roots = { 0x33000453u, 0x33000428u, 0x3300042Cu, 0x33000429u, 0x3300042Du };
@ -35,7 +38,8 @@ foreach (var root in roots)
Console.WriteLine($"{pad}PES 0x{pesId:X8} (already visited — cycle skip)");
return;
}
if (!dats.TryGet<DatPhysicsScript>(pesId, out var ps) || ps is null)
DatPhysicsScript? ps = physicsScripts.LoadPhysicsScript(pesId);
if (ps is null)
{
Console.WriteLine($"{pad}PES 0x{pesId:X8} NOT FOUND");
return;
@ -52,13 +56,18 @@ foreach (var root in roots)
Console.WriteLine($"{head} -> PES=0x{call.PES:X8} pause={call.Pause:F3}");
WalkPes(call.PES, depth + 2, rootForReport);
break;
case RetailBlockingHook blocking:
uint blockingEmitterId = (uint)blocking.EmitterInfoId;
Console.WriteLine($"{head} EmitterInfoId=0x{blockingEmitterId:X8} PartIdx={blocking.PartIndex} EmitterId={blocking.EmitterId} (blocking)");
DumpEmitter(blockingEmitterId, pad + " ", rootForReport);
break;
case CreateParticleHook cp:
uint eid = (uint)cp.EmitterInfoId;
Console.WriteLine($"{head} EmitterInfoId=0x{eid:X8} PartIdx={cp.PartIndex} EmitterId={cp.EmitterId}");
DumpEmitter(eid, pad + " ", rootForReport);
break;
case CreateBlockingParticleHook _:
Console.WriteLine($"{head} (package schema omits retail's inherited CreateParticle payload)");
Console.WriteLine($"{head} (unexpected package header-only shape)");
break;
case SoundHook sh:
Console.WriteLine($"{head} sound=0x{(uint)sh.Id:X8}");