fix(#187): drop the "Name == Door" special-case — register any entity with a resolvable MotionTableId

The door-swing animation rescue (GameWindow.cs:3897, for entities whose rest pose
is a static single frame but which still carry a reactive MotionTable) was gated on
an exact display-name string match. Sliding doors, gates, portcullises, and disguised
secret-passage props ("Magic Wall") all fail that check because their in-game Name
isn't literally "Door" -- so ACE's UpdateMotion for them was silently dropped forever
by the _animatedEntities.TryGetValue bail-out in OnLiveUpdateMotion.

Retail's own dispatch chain (ACCObjectMaint::CreateObject -> CPhysicsObj::
set_description -> SetMotionTableID -> CPartArray::SetMotionTableID 0x005186e0 ->
MotionTableManager::PerformMovement) is unconditionally data-driven: the only gate
for creating a motion dispatcher anywhere in that chain is "motion table id != 0" --
no CDoor class, no WeenieType switch, no name check. Production weenie data confirms
Sliding Door / Portcullis / Gate / Magic Wall all carry the identical WeenieType=Door
+ non-zero-MotionTableId shape as a plain "Door", differing only in display name.

Fix: the branch's existing `mtableId != 0` check (already computed one line later)
is now the entire gate, matching retail exactly. IsDoorSpawn deleted (dead code);
IsDoorName kept only for an unrelated diagnostic log-label filter.

Live-verified: sliding doors now animate open/closed correctly. Full regression
green (App 741 / Core 2631).

docs(#188): file the fading-wall render gap surfaced during #187's live gate

A "Pedestal Weak Spot" secret-passage door dispatches correctly (proving #187's fix
reaches it) but never visibly changes. Decoded its actual dat MotionTable directly
(0x090000F9): its open cycle carries EtherealHook + TransparentPartHook +
SoundTableHook -- a translucency-fade effect, not part-transform motion. acdream's
IAnimationHookSink documents these hook types as intended for "GfxObjMesh / renderer
state mutations" but no sink anywhere consumes them (only Particle/Lighting/Audio are
wired) -- confirmed via full-repo grep. Collision already works correctly via a
separate server-authoritative SetState wire message, independent of the animation
hook. This is feature-shaped rendering work (a per-part runtime alpha under the
mandatory N.5 bindless pipeline), not a quick fix -- filed for its own design pass.

Kept Issue188FadingDoorMotionTableInspectionTests.cs as a reusable MotionTable/hook
decoder for future "why doesn't this animate" questions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-08 20:10:45 +02:00
parent 0efa7ed2a1
commit e2e285b855
3 changed files with 271 additions and 47 deletions

View file

@ -46,42 +46,93 @@ Copy this block when adding a new issue:
---
## #187 — Non-hinged doors (sliding doors, "fading wall" gates) don't play their open animation
## #187 [DONE 2026-07-08] Non-hinged doors (sliding doors, gates) don't play their open animation
**Status:** OPEN — under investigation
**Status:** CLOSED 2026-07-08 — user-confirmed live gate: "sliding doors now work."
**Severity:** MEDIUM (visual correctness; collision + interaction already work — #137)
**Filed:** 2026-07-08
**Component:** render — entity animation registration (`GameWindow.cs` live-spawn dispatch)
**Description (user):** door COLLISION is confirmed fixed (#137) — all doors can be clicked
and passed through correctly. But not all door TYPES play their open animation: normal
hinged doors swing open, but sliding doors and "fading wall" style dungeon gates do not
animate — the player passes through as if the door/wall just isn't there.
**Root cause (CONFIRMED via retail decomp + a live weenie-data survey):**
`GameWindow.cs:3897` registered the reactive-motion-table rescue sequencer only when
`spawn.Name == "Door"` (an exact display-name string match). Retail's own client-side
motion dispatch chain (`ACCObjectMaint::CreateObject``CPhysicsObj::set_description`
`SetMotionTableID``CPartArray::SetMotionTableID` 0x005186e0 → `MotionTableManager::
PerformMovement`) is unconditionally data-driven — the only gate anywhere in that chain
is "does this object have a non-zero MotionTableId" (`if (ebx != 0)`); there is no
`CDoor` class and no name/type check. Production weenie data confirms Sliding Door /
Portcullis / Gate / "Magic Wall" all carry the identical WeenieType=Door +
non-zero-MotionTableId shape as a plain "Door", differing only in display name — so the
name-string gate silently dropped every door-like object not literally named "Door".
**Suspected root cause (pre-investigation read, NOT yet decomp-verified):**
`GameWindow.cs:3897` registers the door-swing sequencer only when
`IsDoorSpawn(spawn)` (`GameWindow.cs:3128`: `spawn.Name == "Door"`, an exact
display-name string match). The branch above it (`:3818`) already handles ANY entity
generically via its resolved idle cycle (multi-frame, framerate != 0) — no name check.
Doors need the special branch only because their REST pose is a static single frame
(fails the generic gate) while their MotionTable still carries reactive On/Off cycles
ACE drives via `UpdateMotion`. That "static rest + reactive motion-table cycle" shape
is not unique to hinged doors — any sliding/lever/gate prop with the same shape would
need the identical rescue, but only literal name `"Door"` gets it. Retail's own decomp
has ZERO `CDoor`-class or door-specific dispatch (only `ObjCollisionProfile::SetDoor`,
a collision-only concept) — suggesting retail's client-side animation dispatch is
entity-type-agnostic (MotionTable-driven), and the name-string gate is an acdream-only
divergence, not a port of anything retail does.
**Fix:** `GameWindow.cs:3897` — dropped the name check; the branch's existing
`mtableId != 0` test (already computed one line later) is now the entire gate, matching
retail exactly. `IsDoorSpawn` deleted (dead code); `IsDoorName` kept only for an
unrelated diagnostic log-label filter. Full regression green (App 741 / Core 2631).
Live-verified: sliding doors now animate open/closed correctly.
**Open question for the investigation:** are "fading wall" gates even MotionTable/
skeletal objects, or a structurally different mechanism (an ObjectDescription /
translucency-toggle triggered by a script, no motion involved)? Needs dat/weenie-data
survey before designing the fix — must not just add more name strings (same class of
bug, just deferred).
**Scope note:** the investigation also surfaced a SEPARATE, deeper gap — some
door-family objects (confirmed: "Pedestal Weak Spot", a fading-wall secret passage)
don't use ordinary part-transform motion at all; their open cycle is a translucency-fade
+ ethereal-toggle effect that acdream has no rendering sink for. That is NOT a
registration problem (this fix's dispatch reaches it correctly) — filed separately as
**#188**.
**Acceptance:** sliding doors + fading-wall gates play their open/close transition
matching the door's actual open/closed wire state, same as hinged doors today. Hinged
doors keep working (no regression). Suites green.
---
## #188 — "Fading wall" secret passages don't visually fade (missing TransparentHook/EtherealHook render sink)
**Status:** OPEN
**Severity:** MEDIUM (a real but narrow class of dungeon secret-door objects; collision
already correct via a separate wire channel)
**Filed:** 2026-07-08 (surfaced during #187's live gate)
**Component:** render — animation hook dispatch (`IAnimationHookSink` / render-state sinks)
**Description (user):** a "fading wall" style secret-passage door ("Pedestal Weak Spot")
can be used and passed through, but never visibly changes — no fade, no motion, nothing.
Confirmed distinct from #187 (registration/dispatch already reaches this entity
correctly).
**Root cause (CONFIRMED via a live dat decode of the actual entity, not inference):**
loaded the real MotionTable (`0x090000F9`) for the live-identified "Pedestal Weak Spot"
(guid `0x7C95B03B`) and dumped its open cycle's animation hooks directly — the cycle
(`anim 0x03000919`, 10 frames) carries **`EtherealHook`, `TransparentPartHook`,
`SoundTableHook`** — i.e. this door type's "open" isn't skeletal motion at all, it's a
per-part translucency fade + a collision-passthrough toggle + a sound cue, fired as
keyframe hooks during the animation.
`src/AcDream.Core/Physics/IAnimationHookSink.cs`'s own doc comment documents
`TransparentHook`/`NoDrawHook`/`ScaleHook`/`ReplaceObjectHook`/etc. as intended to route
to "GfxObjMesh / renderer state mutations on the target entity" — but only three sinks
are ever registered (`_particleSink`, `_lightingSink`, `_audioSink`; `GameWindow.cs`
~1414-1441). No sink anywhere in the codebase pattern-matches `TransparentHook` /
`TransparentPartHook` / `EtherealHook` / `NoDrawHook` / `ScaleHook` /
`ReplaceObjectHook` (verified by a full-repo grep) — the sequencer correctly parses and
fires these hooks every tick, the router fans them out, and all three registered sinks
silently ignore them. Nothing crashes (the router swallows exceptions per-sink); nothing
renders differently either.
**Confirmed NOT a collision bug:** the object's ethereal/collision-passthrough state is
applied correctly via a SEPARATE server-authoritative wire message (`SetState`
`OnLiveStateUpdated`, `GameWindow.cs:5517`) — independent of the animation-hook
mechanism. `EtherealHook` firing client-side during the animation is very likely a
redundant/cosmetic signal in retail's own design; the real remaining gap is purely
**`TransparentPartHook` → no visible fade**.
**Scope (not yet designed):** implementing this touches the render pipeline (a per-part
runtime alpha under the mandatory N.5 bindless/MDI pipeline — see
`memory/reference_modern_rendering_pipeline.md` for the existing SSBO layout
constraints) — this is feature-shaped work, not a one-line fix. Needs its own design
pass (grep retail's `TransparentHook::Execute`/`SetTranslucency2`/`SetPartTranslucency`
decomp for the exact interpolation semantics) before implementation.
**Apparatus (kept):** `tests/AcDream.Core.Tests/Physics/Issue187FadingDoorMotionTableInspectionTests.cs`
— reflects the real `DatReaderWriter` hook-type shapes + decodes a live MotionTable's
hook contents directly (no guessing). Reusable for any future "why doesn't this animate"
question — just swap the MotionTableId.
**Acceptance:** the Pedestal Weak Spot (and similar fading-wall objects) visibly fades
out/in when triggered, matching retail. No regression to #187's fix or any other door type.
---