diff --git a/Dalamud/Hooking/Internal/CallHook.cs b/Dalamud/Hooking/Internal/CallHook.cs
index 0f8c681c2..2bef59c86 100644
--- a/Dalamud/Hooking/Internal/CallHook.cs
+++ b/Dalamud/Hooking/Internal/CallHook.cs
@@ -6,8 +6,14 @@ using Reloaded.Hooks.Definitions;
namespace Dalamud.Hooking.Internal;
///
-/// Hooking class for callsite hooking. This hook does not have capabilities of calling the original function.
-/// The intended use is replacing virtual function calls where you are able to manually invoke the original call using the delegate arguments.
+/// This class represents a callsite hook. Only the specific address's instructions are replaced with this hook.
+/// This is a destructive operation, no other callsite hooks can coexist at the same address.
+///
+/// There's no .Original for this hook type.
+/// This is only intended for be for functions where the parameters provided allow you to invoke the original call.
+///
+/// This class was specifically added for hooking virtual function callsites.
+/// Only the specific callsite hooked is modified, if the game calls the virtual function from other locations this hook will not be triggered.
///
/// Delegate signature for this hook.
internal class CallHook : IDisposable where T : Delegate
diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/AddonLifecycleAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/AddonLifecycleAgingStep.cs
new file mode 100644
index 000000000..9dcaec558
--- /dev/null
+++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/AddonLifecycleAgingStep.cs
@@ -0,0 +1,133 @@
+using System.Collections.Generic;
+
+using Dalamud.Game.AddonLifecycle;
+using Dalamud.Plugin.Services;
+using ImGuiNET;
+
+namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps;
+
+///
+/// Test setup AddonLifecycle Service.
+///
+internal class AddonLifecycleAgingStep : IAgingStep
+{
+ private readonly List listeners;
+
+ private AddonLifecycle? service;
+ private TestStep currentStep = TestStep.CharacterRefresh;
+ private bool listenersRegistered;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public AddonLifecycleAgingStep()
+ {
+ this.listeners = new List
+ {
+ new(AddonEvent.PostSetup, "Character", this.PostSetup),
+ new(AddonEvent.PostUpdate, "Character", this.PostUpdate),
+ new(AddonEvent.PostDraw, "Character", this.PostDraw),
+ new(AddonEvent.PostRefresh, "Character", this.PostRefresh),
+ new(AddonEvent.PostRequestedUpdate, "Character", this.PostRequestedUpdate),
+ new(AddonEvent.PreFinalize, "Character", this.PreFinalize),
+ };
+ }
+
+ private enum TestStep
+ {
+ CharacterRefresh,
+ CharacterSetup,
+ CharacterRequestedUpdate,
+ CharacterUpdate,
+ CharacterDraw,
+ CharacterFinalize,
+ Complete,
+ }
+
+ ///
+ public string Name => "Test AddonLifecycle";
+
+ ///
+ public SelfTestStepResult RunStep()
+ {
+ this.service ??= Service.Get();
+ if (this.service is null) return SelfTestStepResult.Fail;
+
+ if (!this.listenersRegistered)
+ {
+ foreach (var listener in this.listeners)
+ {
+ this.service.RegisterListener(listener);
+ }
+
+ this.listenersRegistered = true;
+ }
+
+ switch (this.currentStep)
+ {
+ case TestStep.CharacterRefresh:
+ ImGui.Text("Open Character Window.");
+ break;
+
+ case TestStep.CharacterSetup:
+ ImGui.Text("Open Character Window.");
+ break;
+
+ case TestStep.CharacterRequestedUpdate:
+ ImGui.Text("Change tabs, or un-equip/equip gear.");
+ break;
+
+ case TestStep.CharacterFinalize:
+ ImGui.Text("Close Character Window.");
+ break;
+
+ case TestStep.CharacterUpdate:
+ case TestStep.CharacterDraw:
+ case TestStep.Complete:
+ default:
+ // Nothing to report to tester.
+ break;
+ }
+
+ return this.currentStep is TestStep.Complete ? SelfTestStepResult.Pass : SelfTestStepResult.Waiting;
+ }
+
+ ///
+ public void CleanUp()
+ {
+ foreach (var listener in this.listeners)
+ {
+ this.service?.UnregisterListener(listener);
+ }
+ }
+
+ private void PostSetup(AddonEvent eventType, IAddonLifecycle.AddonArgs addonInfo)
+ {
+ if (this.currentStep is TestStep.CharacterSetup) this.currentStep++;
+ }
+
+ private void PostUpdate(AddonEvent eventType, IAddonLifecycle.AddonArgs addonInfo)
+ {
+ if (this.currentStep is TestStep.CharacterUpdate) this.currentStep++;
+ }
+
+ private void PostDraw(AddonEvent eventType, IAddonLifecycle.AddonArgs addonInfo)
+ {
+ if (this.currentStep is TestStep.CharacterDraw) this.currentStep++;
+ }
+
+ private void PostRefresh(AddonEvent eventType, IAddonLifecycle.AddonArgs addonInfo)
+ {
+ if (this.currentStep is TestStep.CharacterRefresh) this.currentStep++;
+ }
+
+ private void PostRequestedUpdate(AddonEvent eventType, IAddonLifecycle.AddonArgs addonInfo)
+ {
+ if (this.currentStep is TestStep.CharacterRequestedUpdate) this.currentStep++;
+ }
+
+ private void PreFinalize(AddonEvent eventType, IAddonLifecycle.AddonArgs addonInfo)
+ {
+ if (this.currentStep is TestStep.CharacterFinalize) this.currentStep++;
+ }
+}
diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs b/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs
index 3e25b6f5a..68d197208 100644
--- a/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs
+++ b/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs
@@ -39,6 +39,7 @@ internal class SelfTestWindow : Window
new ChatAgingStep(),
new HoverAgingStep(),
new LuminaAgingStep(),
+ new AddonLifecycleAgingStep(),
new PartyFinderAgingStep(),
new HandledExceptionAgingStep(),
new DutyStateAgingStep(),