test(combat): await proved basic attack reentry
Release / release (push) Successful in 14m58s

This commit is contained in:
dmg committed 2026-09-25 15:57:40 -04:00
1 parent bb8d1857b4
commit c13c3093cf
1 file changed
+68 -3
@@ -171,8 +171,11 @@ class BasicAttackEventsNativeTest {
}).when(target.player).damage(anyDouble(), same(source.player));
attack(fixture, source, target);
var sheet = source.core.inventory.getItemInOffHand().clone();
var targetSheet = target.core.inventory.getItemInOffHand().clone();
source.core.inventory.setItemInOffHand(null); fixture.changed(source);
fixture.awaitCharacterAndReceipt(source);
target.core.inventory.setItemInOffHand(null); fixture.changed(target);
awaitCleanExit(fixture, sessions, source);
awaitCleanExit(fixture, sessions, target);
fixture.plugin.setEnabled(false); fixture.awaitWorkers();
fixture.plugin.setEnabled(true);
var resumed = (games.dmg.fantasystick.combat.BasicAttack)
@@ -183,10 +186,14 @@ class BasicAttackEventsNativeTest {
() -> false);
resumed.accept(stale); resumed.accept(stale);
assertEquals(1, requests.get(), "Stale/duplicate notifications cannot replay native damage");
source.core.inventory.setItemInOffHand(sheet); fixture.activate(source);
var recoveredSessions = (games.dmg.fantasystick.session.EquipmentSessions)
FantasyStickLifecycleNativeTest.Fixture.field(fixture.plugin, "sessions");
if (recoveredSessions.active(target.core.id).isEmpty()) fixture.activate(target);
awaitOrdinaryArrival(fixture, recoveredSessions, source);
awaitOrdinaryArrival(fixture, recoveredSessions, target);
enterAfterRestoredExit(fixture, recoveredSessions, source, sheet);
enterAfterRestoredExit(fixture, recoveredSessions, target, targetSheet);
assertTrue(recoveredSessions.active(target.core.id).isPresent(),
"Fresh entry after proved ordinary arrival must own a current target session");
source.core.inventory.getInventory().setSelectedSlot(1);
attack(fixture, source, target);
assertEquals(2, requests.get(), "Fresh session gets a fresh action after restart");
@@ -281,6 +288,64 @@ class BasicAttackEventsNativeTest {
}
}
private static void enterAfterRestoredExit(FantasyStickLifecycleNativeTest.Fixture fixture,
games.dmg.fantasystick.session.EquipmentSessions sessions,
FantasyStickLifecycleNativeTest.Actor actor,
org.bukkit.inventory.ItemStack sheet) throws Exception {
assertTrue(actor.core.inventory.getItemInOffHand() == null
|| actor.core.inventory.getItemInOffHand().isEmpty(),
"Observe the ordinary empty offhand before a fresh sheet move");
fixture.changed(actor); // First RESTORED observation is not a new entry request.
actor.core.inventory.setItemInOffHand(sheet);
fixture.changed(actor);
for (int i = 0; i < 80 && (sessions.active(actor.core.id).isEmpty()
|| actor.core.marker() == null || !actor.core.marker().endsWith("|PACKED")); i++) {
var task = fixture.tasks.poll(5, java.util.concurrent.TimeUnit.SECONDS);
assertNotNull(task, "Fresh entry has no callback after proved restored exit; feedback=" + actor.messages);
task.run();
}
assertTrue(sessions.active(actor.core.id).isPresent(), "Fresh entry must own a current session");
assertTrue(actor.core.marker().endsWith("|PACKED"), "Native equipment must be packed for the new session");
}
private static void awaitCleanExit(FantasyStickLifecycleNativeTest.Fixture fixture,
games.dmg.fantasystick.session.EquipmentSessions sessions,
FantasyStickLifecycleNativeTest.Actor actor) throws Exception {
var transitioning = (java.util.Map<?, ?>) FantasyStickLifecycleNativeTest.Fixture.field(sessions, "transitioning");
for (int i = 0; i < 80; i++) {
if (sessions.active(actor.core.id).isEmpty() && !transitioning.containsKey(actor.core.id)
&& restoredExitProof(fixture, actor)) return;
var task = fixture.tasks.poll(5, java.util.concurrent.TimeUnit.SECONDS);
assertNotNull(task, "No exit callback before acknowledged receipt and native RESTORED marker");
task.run();
}
fail("Session exit did not clear owned native marker and acknowledged receipt before restart");
}
private static void awaitOrdinaryArrival(FantasyStickLifecycleNativeTest.Fixture fixture,
games.dmg.fantasystick.session.EquipmentSessions sessions,
FantasyStickLifecycleNativeTest.Actor actor) throws Exception {
var pending = (java.util.Map<?, ?>) FantasyStickLifecycleNativeTest.Fixture.field(sessions, "pending");
var transitioning = (java.util.Map<?, ?>) FantasyStickLifecycleNativeTest.Fixture.field(sessions, "transitioning");
for (int i = 0; i < 80 && (pending.containsKey(actor.core.id)
|| transitioning.containsKey(actor.core.id)); i++) fixture.tick();
assertFalse(pending.containsKey(actor.core.id), "Startup arrival must settle before a new entry request");
assertFalse(transitioning.containsKey(actor.core.id), "No unresolved native transition may be replaced");
assertTrue(sessions.active(actor.core.id).isEmpty(), "An ordinary holder cannot have a recovered active session");
assertTrue(restoredExitProof(fixture, actor),
"A completed receipt and matching RESTORED native marker are required before re-entry");
}
private static boolean restoredExitProof(FantasyStickLifecycleNativeTest.Fixture fixture,
FantasyStickLifecycleNativeTest.Actor actor) throws Exception {
return fixture.receipts().find(actor.core.id).toCompletableFuture()
.get(5, java.util.concurrent.TimeUnit.SECONDS)
.filter(state -> state.pending() == null && state.lastExit() != null)
.map(state -> ("1|" + state.lastExit().sessionId() + "|" + state.lastExit().characterId()
+ "|RESTORED").equals(actor.core.marker()))
.orElse(false);
}
private static void attack(FantasyStickLifecycleNativeTest.Fixture fixture,
FantasyStickLifecycleNativeTest.Actor source,
FantasyStickLifecycleNativeTest.Actor target) {