Replace the door-specific static-animation seed with one retail description-then-enter-world initializer shared by normal and reactive spawns. This preserves authoritative Dead and On/Off states, prevents replacement corpses from returning to Ready, and pins the lifecycle with Core and App tests. Co-Authored-By: Codex <codex@openai.com>
144 lines
5.6 KiB
Markdown
144 lines
5.6 KiB
Markdown
# Persistent death and post-death Auto Target pseudocode
|
|
|
|
Sources: Sept 2013 EoR named retail client.
|
|
|
|
- `ClientCombatSystem::AutoTarget` (`0x0056BC80`)
|
|
- `ClientCombatSystem::RecvNotice_SelectionChanged` (`0x0056BD80`)
|
|
- `CPlayerSystem::SelectNext` (`0x0055F9A0`)
|
|
- `MovementManager::unpack_movement` (`0x00524440`)
|
|
- `CPhysicsObj::MoveOrTeleport` (`0x00516330`)
|
|
- `SmartBox::HandleCreateObject` (`0x00454C80`)
|
|
- `ACCObjectMaint::CreateObject` (`0x00558870`)
|
|
- `CPhysicsObj::set_description` (`0x00514F40`)
|
|
- `CPhysicsObj::enter_world` (`0x00516170`)
|
|
- `CPartArray::HandleEnterWorld` (`0x00517D70`)
|
|
- `MotionTableManager::HandleEnterWorld` (`0x0051BDD0`)
|
|
|
|
## Animation authority
|
|
|
|
```text
|
|
on UpdateMotion(interpretedState):
|
|
pass the state through MovementManager and CMotionInterp
|
|
Dead is a persistent substate selected by the motion table
|
|
|
|
on UpdatePosition(position, velocity):
|
|
update the physics pose and velocity
|
|
do not select Ready, Walk, Run, or any other animation
|
|
```
|
|
|
|
acdream retains a documented AP-80 adaptation for ACE actors that move only
|
|
through position snapshots: velocity may choose a visible locomotion cycle.
|
|
That adaptation is now restricted to replacing only Ready/Walk/Run-family
|
|
states. An authoritative action or substate such as attack, hit reaction, or
|
|
Dead always wins. This preserves the adaptation without allowing a late
|
|
position delta to make a corpse stand again.
|
|
|
|
## CreateObject motion lifecycle
|
|
|
|
ACE creates a new corpse object after the victim's death animation finishes.
|
|
Its CreateObject packet correctly carries `NonCombat + Dead`; this was verified
|
|
in the 2026-07-12 live trace for six corpse GUIDs. The remaining stand-up was
|
|
not a parser or server-authority bug. The decisive follow-up trace showed the
|
|
replacement corpse completing `Ready` immediately after spawn. Dead is a
|
|
static persistent rest pose for this setup, so the corpse entered acdream's
|
|
"reactive" static-animation branch rather than the normal multi-frame branch.
|
|
That branch's gate had been generalized from doors to every nonzero motion
|
|
table, but its initializer was still door-specific: it inferred On/Off from
|
|
`PhysicsState`, discarded the wire's Dead command, and left the corpse on the
|
|
table's Ready default.
|
|
|
|
Retail performs these operations in this order:
|
|
|
|
```text
|
|
SmartBox::HandleCreateObject(packet):
|
|
object = ACCObjectMaint::CreateObject(...)
|
|
|
|
ACCObjectMaint::CreateObject(...):
|
|
object.set_description(physicsDesc, applyMovement=true)
|
|
|
|
CPhysicsObj::set_description(...):
|
|
SetMotionTableID(physicsDesc.motionTable)
|
|
-> initialize the motion table's default Ready state
|
|
unpack_movement(physicsDesc.movement)
|
|
-> apply the wire's NonCombat + Dead state
|
|
-> temporarily build [Ready-to-Dead transition link, Dead cycle]
|
|
|
|
SmartBox::HandleCreateObject(...), after CreateObject returns:
|
|
object.enter_world(position)
|
|
|
|
CPhysicsObj::enter_world(...):
|
|
partArray.HandleEnterWorld()
|
|
|
|
CPartArray::HandleEnterWorld():
|
|
motionTableManager.HandleEnterWorld()
|
|
|
|
MotionTableManager::HandleEnterWorld():
|
|
sequence.remove_all_link_animations()
|
|
while pendingAnimations is not empty:
|
|
AnimationDone(success=false)
|
|
```
|
|
|
|
Therefore the first in-world corpse pose comes from the persistent Dead cycle,
|
|
not the Ready-to-Dead transition that the already-deleted victim just played.
|
|
This is a generic CreateObject lifecycle rule for every motion-table object;
|
|
there is no corpse-name or corpse-type special case in the animation path.
|
|
|
|
acdream must apply this sequence identically in both of its render-registration
|
|
paths:
|
|
|
|
```text
|
|
initialize_spawn_motion(motionTable, wireMovementData):
|
|
sequencer.initialize_state() // table default
|
|
sequencer.set_cycle(wire.stance, wire.motion)
|
|
sequencer.handle_enter_world() // strip transient links
|
|
|
|
normal multi-frame spawn:
|
|
sequencer = initialize_spawn_motion(...)
|
|
|
|
static/reactive spawn (doors, corpses, gates, ...):
|
|
sequencer = initialize_spawn_motion(...)
|
|
```
|
|
|
|
Cross-checks:
|
|
|
|
- ACE `WorldObject_Networking.SerializePhysicsData` writes the corpse's
|
|
`CurrentMotionState` into CreateObject MovementData.
|
|
- ACE `Corpse.SetEphemeralValues` sets that state to `NonCombat + Dead`.
|
|
- ACE `Door.SetEphemeralValues` likewise supplies `NonCombat + On/Off`; the
|
|
client does not need to infer door motion from `PhysicsState`.
|
|
- The existing acdream `MotionTableManager.HandleEnterWorld` port and its
|
|
#174 conformance tests already implement the retail strip-and-drain
|
|
semantics. The defect was that the static/reactive spawn path neither used
|
|
the wire motion generically nor called that lifecycle step.
|
|
|
|
## Auto Target after the selected creature dies
|
|
|
|
```text
|
|
when authoritative Dead motion is applied to the selected object:
|
|
clear selected object
|
|
publish SelectionChanged
|
|
|
|
RecvNotice_SelectionChanged():
|
|
update target tracking
|
|
if selected object is zero
|
|
and combat mode is Melee or Missile
|
|
and Auto Target is enabled:
|
|
AutoTarget()
|
|
|
|
AutoTarget():
|
|
if a recent valid last-attacked object exists:
|
|
select it
|
|
else:
|
|
SelectNext(forward=true, includeCurrent=true, COMPASS_ITEM)
|
|
|
|
SelectNext(... COMPASS_ITEM) while in Melee/Missile:
|
|
reject non-attackable, fellowship, hidden, and out-of-radar candidates
|
|
rank eligible candidates by the retail weighted spatial distance
|
|
select the best candidate
|
|
```
|
|
|
|
The current M2 world-state projection has no retail `lastAttacked` instance-id
|
|
quality, so its existing closest eligible creature scan implements the
|
|
fallback branch. The important lifecycle behavior is exact: authoritative
|
|
death clears selection, and the SelectionChanged consumer reacquires only
|
|
when Auto Target is enabled in Melee/Missile combat.
|