docs(B.4c): correct handoff fabrications surfaced by final review

Opus final review of B.4c flagged that Task 4's handoff doc invented
implementation details that don't exist in the code:

1. IsDoorSpawn claimed to check "spawn.WeenieObj.WeenieType == 8 OR
   IsDoorName(spawn.Name)" — the actual code is just IsDoorName(spawn.Name)
   delegating to "name == "Door"". No WeenieType lookup exists.

2. A "_doorSequencers" per-door dict was referenced in three places — that
   dict doesn't exist. The actual code reuses the existing
   _animatedEntities[entity.Id] dict (same one that holds creatures + the
   player), with Animation = null! per the existing pattern at line 7885.

3. The UM dispatch path was described as a new B.4c-added branch with
   pseudocode — that's wrong. B.4c does NOT add a new dispatch path;
   OnLiveMotionUpdated's existing TryGetValue against _animatedEntities
   handles doors automatically once Task 1's spawn-time branch registers
   them. The only UM-dispatch B.4c contribution is the [door-cycle]
   diagnostic line, gated on IsDoorName.

Corrects sections "At world load (spawn time)", "When the door opens",
"Per-frame mesh rebuild", and "Door types covered" to reflect the actual
shipped code. cmd→motion mapping (cmd=0x000C → open, cmd=0x000B → close)
left as-is — it was correct.

No code change. Verified by re-reading GameWindow.cs IsDoorSpawn /
IsDoorName helpers, the Task 1 spawn-time branch body, and the
TickAnimations sequencer dispatch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-14 08:03:45 +02:00
parent ebdbf821dc
commit 8bb81db659

View file

@ -66,9 +66,11 @@ happens inside `GameWindow.OnLiveEntitySpawnedLocked`, which branches on
### At world load (spawn time) ### At world load (spawn time)
1. `IsDoorSpawn(spawn)` — checks `spawn.WeenieObj.WeenieType == 8` (the 1. `IsDoorSpawn(spawn)` — delegates to `IsDoorName(spawn.Name)`, which
`Door` weenie type) OR `IsDoorName(spawn.Name)` (fallback for servers that returns `name == "Door"`. Detection by server-sent name string only.
tag door-weenies with non-8 types). If true, the entity is a door. Cheap, exact, no WeenieType lookup. If a future ACE localizes "Door"
or sends a different name, those entities silently won't animate —
acceptable per B.4c's "doors only at English Holtburg" scope.
2. **Initial state seed** — the door's `PhysicsState` from `spawn` carries the 2. **Initial state seed** — the door's `PhysicsState` from `spawn` carries the
open/closed bit. The code reads `spawn.PhysicsState` (or open/closed bit. The code reads `spawn.PhysicsState` (or
@ -83,8 +85,13 @@ happens inside `GameWindow.OnLiveEntitySpawnedLocked`, which branches on
var cycleCmd = isOpen ? MotionCommand.On : MotionCommand.Off; var cycleCmd = isOpen ? MotionCommand.On : MotionCommand.Off;
sequencer.SetCycle(style, (uint)cycleCmd, speed: 0f); sequencer.SetCycle(style, (uint)cycleCmd, speed: 0f);
``` ```
The sequencer is registered in a new per-door side-dict on `GameWindow` The fully-initialized `AnimatedEntity` (with the seeded `Sequencer`) is
keyed by `entity.Id`. At first `Advance(dt)` call, it produces the correct registered into the existing `_animatedEntities` dict keyed by `entity.Id`
— same dict that holds creatures and the player. `Animation = null!`
(the null-forgiving suppression matches an existing pattern at
`GameWindow.cs:7885` for sequencer-driven entities where the legacy
`Animation` field is unused). At the first per-frame `Advance(dt)`
call from `TickAnimations`, the sequencer produces the correct
rest-pose frames for the door's current state. rest-pose frames for the door's current state.
4. **Log evidence at spawn:** 4. **Log evidence at spawn:**
@ -97,17 +104,17 @@ happens inside `GameWindow.OnLiveEntitySpawnedLocked`, which branches on
### When the door opens (UpdateMotion arrives) ### When the door opens (UpdateMotion arrives)
ACE broadcasts `UpdateMotion (0xF74D)` with `stance=0x003D` (NonCombat) and ACE broadcasts `UpdateMotion (0xF74D)` with `stance=0x003D` (NonCombat) and
`cmd=0x000C` (On = open). The existing `OnLiveMotionUpdated` handler previously wire `cmd=0x000C` (which `MotionCommandResolver.ReconstructFullCommand`
dropped this silently for non-creature entities. B.4c adds a `IsDoorName`-gated maps to full motion `0x4000000B` = `MotionCommand.On` = door open).
branch:
```csharp B.4c does NOT add a new dispatch path here — the existing
if (_doorSequencers.TryGetValue(entity.Id, out var seq)) `OnLiveMotionUpdated` handler already routes via the `_animatedEntities`
{ dict + per-entity `Sequencer`, the same code path creatures use. The
var style = 0x80000000u | (uint)um.Stance; only B.4c contribution at UM dispatch is the new `[door-cycle]`
seq.SetCycle(style, (uint)um.ForwardCommand, um.ForwardSpeed); diagnostic gated on `IsDoorName(doorInfo.Name)`. Before B.4c, doors
} silently dropped at the `_animatedEntities.TryGetValue` check at
``` `GameWindow.cs:3036` because doors weren't registered; B.4c's Task 1
spawn-time branch fixed that.
The sequencer transitions from the `Off` cycle (static closed pose) through The sequencer transitions from the `Off` cycle (static closed pose) through
the door-swing link animation to the `On` cycle (static open pose). the door-swing link animation to the `On` cycle (static open pose).
@ -148,13 +155,14 @@ UM guid=0x7A9B403A mt=... cmd=0x000B ... motion=0x4000000C
### Per-frame mesh rebuild ### Per-frame mesh rebuild
The door sequencer integrates into `GameWindow.TickAnimations` via the same The door sequencer integrates into `GameWindow.TickAnimations` via the same
`_doorSequencers` dict. Each frame, `Advance(dt)` is called on the sequencer `_animatedEntities` dict that holds creatures. Each frame, `ae.Sequencer.Advance(dt)`
and the resulting `PartFrames` drive the same `MeshRefs` rebuild that creature is called and the resulting per-part transforms drive the same `MeshRefs` rebuild
entities use. This is the reason the stance-value bug produced underground doors: that creature entities use (sequencer branch at `GameWindow.cs:7497`; doors
with the wrong style key (`0x80000001`) `HasCycle` returned false, the sequencer never enter the legacy slerp `else` branch). This is the reason the stance-value
was empty, `Advance` returned no frames, and the per-frame part-matrix rebuild bug produced underground doors: with the wrong style key (`0x80000001`)
at `GameWindow.cs:7691` received zero frames — collapsing every part to the `HasCycle` returned false, the sequencer was empty at spawn, `Advance` returned
entity origin. identity frames, and the per-frame part-matrix rebuild received `Vector3.Zero /
Quaternion.Identity` for every part — collapsing them all to the entity origin.
--- ---
@ -244,8 +252,9 @@ Other interactable non-creature entities (chests, levers, traps) will still
silently drop their `UpdateMotion` commands — they are not covered by B.4c and silently drop their `UpdateMotion` commands — they are not covered by B.4c and
no issue has been filed for them yet. When those animations become relevant no issue has been filed for them yet. When those animations become relevant
(M2/M3 inventory + dungeon content), the same spawn-time registration pattern (M2/M3 inventory + dungeon content), the same spawn-time registration pattern
can be extended by widening `IsDoorSpawn` and reusing the `_doorSequencers` can be extended: broaden the detection predicate beyond `name == "Door"` and
infrastructure. register additional entity types in the existing `_animatedEntities` dict via
the same sibling branch.
### Door toggle behavior ### Door toggle behavior