diff --git a/.claude/skills/bulbapedia-tests/SKILL.md b/.claude/skills/bulbapedia-tests/SKILL.md
index 57d34db..4c5aa79 100644
--- a/.claude/skills/bulbapedia-tests/SKILL.md
+++ b/.claude/skills/bulbapedia-tests/SKILL.md
@@ -107,6 +107,12 @@ Follow the existing conventions — `Scripts/Moves/DrainTests.cs` and
division, overflow guards).
- Cover the negative cases the description implies (non-contact move → no trigger, holding an
item → no boost, attacker already fainted, null `BattleData`, ...).
+- **Write the test even when the behavior is not implemented at all.** A described behavior the
+ script simply lacks (a missing check, a missing effect, a missing interface) still gets a test
+ encoding the correct Gen VII behavior — it will fail and be marked `[TestFailing]` in step 5,
+ which is the point: the gap stays tracked in the suite instead of in a report nobody rereads.
+ "Not implemented" is never a reason to skip writing the test, and reporting a known deviation
+ as a prose note instead of a `[TestFailing]` test is not acceptable.
- `LibraryHelpers.LoadLibrary()` is available when a test genuinely needs the full Gen7 library
(e.g. looking up real move data); prefer pure mocks otherwise.
diff --git a/PkmnLib.Dynamic/AI/AIHelpers.cs b/PkmnLib.Dynamic/AI/AIHelpers.cs
index 2d76774..147b2dd 100644
--- a/PkmnLib.Dynamic/AI/AIHelpers.cs
+++ b/PkmnLib.Dynamic/AI/AIHelpers.cs
@@ -40,6 +40,9 @@ public static class AIHelpers
///
public uint Damage => 0;
+ ///
+ public bool HasExecuted => false;
+
///
public TypeIdentifier? Type { get; init; }
diff --git a/PkmnLib.Dynamic/BattleFlow/MoveTurnExecutor.cs b/PkmnLib.Dynamic/BattleFlow/MoveTurnExecutor.cs
index a3b1c83..0a8be67 100644
--- a/PkmnLib.Dynamic/BattleFlow/MoveTurnExecutor.cs
+++ b/PkmnLib.Dynamic/BattleFlow/MoveTurnExecutor.cs
@@ -142,6 +142,7 @@ public static class MoveTurnExecutor
if (isInvulnerable)
{
battle.EventHook.Invoke(new MoveInvulnerableEvent(executingMove, target));
+ executingMove.RunScriptHook(x => x.OnAfterHits(executingMove, target));
return;
}
@@ -311,6 +312,7 @@ public static class MoveTurnExecutor
}
}
}
+ hitData.HasExecuted = true;
}
if (numberOfHits == 0)
diff --git a/PkmnLib.Dynamic/Models/ExecutingMove.cs b/PkmnLib.Dynamic/Models/ExecutingMove.cs
index dd71c42..2a632e5 100644
--- a/PkmnLib.Dynamic/Models/ExecutingMove.cs
+++ b/PkmnLib.Dynamic/Models/ExecutingMove.cs
@@ -32,6 +32,11 @@ public interface IHitData
///
uint Damage { get; }
+ ///
+ /// Gets set to true after the hit has fully executed, and has not been interrupted by anything.
+ ///
+ bool HasExecuted { get; }
+
///
/// The type of the hit. Null if the move is typeless.
///
@@ -78,6 +83,9 @@ public record HitData : IHitData
///
public uint Damage { get; internal set; }
+ ///
+ public bool HasExecuted { get; internal set; }
+
///
public TypeIdentifier? Type { get; internal set; }
diff --git a/PkmnLib.Dynamic/ScriptHandling/Script.cs b/PkmnLib.Dynamic/ScriptHandling/Script.cs
index 2fa8cf2..9b82830 100644
--- a/PkmnLib.Dynamic/ScriptHandling/Script.cs
+++ b/PkmnLib.Dynamic/ScriptHandling/Script.cs
@@ -14,6 +14,13 @@ public abstract class Script : IDeepCloneable, IScriptOnRemove
{
internal event Action