# 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. ### Selection-notice reentrancy Retail's selection notice has no selected-object payload: ```text ACCWeenieObject::SetSelectedObject(newId): selectedID = newId CM_UI::SendNotice_SelectionChanged() CM_UI::SendNotice_SelectionChanged(): for each registered handler: handler.RecvNotice_SelectionChanged() gmToolbarUI::HandleSelectionChanged(): id = ACCWeenieObject::selectedID // read current global state clear and repopulate selected-object strip from id ``` If `ClientCombatSystem::RecvNotice_SelectionChanged` runs first for a clear, its `AutoTarget()` may select a replacement reentrantly. A later toolbar handler reads the replacement from global state; it does not receive and apply the stale outer clear. acdream's retained toolbar must likewise read the canonical `SelectionState.SelectedObjectId` when notified rather than use the captured transition payload. ### acdream automatic monster narrowing Retail's combat-mode `SelectNext(COMPASS_ITEM)` rejects candidates that fail `ClientCombatSystem::ObjectIsAttackable @ 0x0056A600`; that predicate rejects friendly NPCs, pets, corpses, and non-creatures but can accept enemy players in compatible PK states. By explicit product direction, acdream narrows automatic acquisition further to non-player hostile monsters. Manual player selection remains separate. This intentional PK edge divergence is register IA-19.