fix(recovery): silence clean equipment arrival warnings (#18)
Release / release (push) Successful in 1m24s
CI / build (push) Successful in 48s

This commit was merged in pull request #18.
This commit is contained in:
dmg committed 2026-10-17 05:51:40 +00:00
commit b3473aff18
2 files changed
+70 -9

No files matched your search

@@ -624,19 +624,22 @@ public final class EquipmentSessions implements AutoCloseable {
if (hasRemnants(attempt.player) || !gameModes.available(attempt.player)
|| vitals != null && !vitals.available(attempt.player)) return false;
var marker = EquipmentSessionMarker.read(attempt.player).orElse(null);
if (EquipmentSessionMarker.present(attempt.player)) {
// A terminal marker is resolved only by its matching completed file acknowledgment.
// Keep it until the next successful entry; worker acknowledgments never touch PDC.
var exit = state == null ? null : state.lastExit();
if (marker == null || marker.phase() != EquipmentSessionMarker.Phase.RESTORED
|| state == null || state.pending() != null || exit == null
|| !marker.matches(exit.sessionId(), exit.characterId())) return false;
}
if (EquipmentSessionMarker.present(attempt.player) && !completedRestoredMarker(marker, state)) return false;
attempt.entryMarker = marker;
attempt.markerValidated = true;
return true;
}
private static boolean completedRestoredMarker(EquipmentSessionMarker.Token marker,
EquipmentSessionStore.State state) {
// A terminal marker is resolved only by its matching completed file acknowledgment.
// Keep it until the next successful entry; worker acknowledgments never touch PDC.
var exit = state == null ? null : state.lastExit();
return marker != null && marker.phase() == EquipmentSessionMarker.Phase.RESTORED
&& state != null && state.pending() == null && exit != null
&& marker.matches(exit.sessionId(), exit.characterId());
}
private static boolean hasRemnants(Player player) {
return Arrays.stream(player.getInventory().getContents()).anyMatch(EquipmentSessions::remnant)
|| remnant(player.getItemOnCursor());
@@ -1940,7 +1943,9 @@ public final class EquipmentSessions implements AutoCloseable {
var arrivingOwner = attempt.owner;
var episode=downedDirectory==null?null:downedDirectory.all().stream().filter(value->value.player().equals(arrivingOwner)&&value.phase()==DownedEpisode.Phase.PENDING).findFirst().orElse(null);
if (sheets.identify(attempt.player.getInventory().getItemInOffHand()).isPresent()) enter(attempt.player,episode);
else if (EquipmentSessionMarker.present(attempt.player) || hasRemnants(attempt.player)) {
else if (hasRemnants(attempt.player)
|| (EquipmentSessionMarker.present(attempt.player)
&& !completedRestoredMarker(EquipmentSessionMarker.read(attempt.player).orElse(null), state.orElse(null)))) {
notice(attempt.owner, "NATIVE_RECOVERY_REQUIRED", "Native equipment remnants have no pending receipt; items were retained for inspection.");
}
return; // Ordinary no-sheet/no-receipt arrivals are not rejected entry attempts.
@@ -1128,6 +1128,62 @@ class EquipmentSessionEventsNativeTest {
}
}
@Test void joinAfterCleanExitIsQuietAndPreservesPlayerAndReceiptState() throws Exception {
try (var f = new Fixture()) {
var a = f.actor; f.core.activate(a);
assertEquals(games.dmg.fantasystick.items.EquipmentStorage.RestoreResult.RESTORED,
f.core.sessions.exit(a.player));
var completed = f.core.state(a);
assertNull(completed.pending()); assertNotNull(completed.lastExit());
a.inventory.setItemInOffHand(new ItemStack(Material.EMERALD));
var before = a.snapshot(); var cursor = a.cursor.clone(); var marker = a.marker();
var record = f.core.characters.find(a.record.id()).join().orElseThrow();
int notices = f.core.notices.size();
f.dispatcher.accept(new org.bukkit.event.player.PlayerJoinEvent(a.player,
(net.kyori.adventure.text.Component) null));
f.core.tick();
assertEquals(notices, f.core.notices.size(),
"A matching completed RESTORED marker is clean arrival proof, not a recovery warning");
assertArrayEquals(before, a.snapshot()); assertEquals(cursor, a.cursor);
assertEquals(marker, a.marker()); assertEquals(completed, f.core.state(a));
assertEquals(record, f.core.characters.find(a.record.id()).join().orElseThrow());
assertTrue(f.core.sessions.active(a.id).isEmpty()); assertTrue(f.ticks.isEmpty());
}
}
@Test void joinWarnsWithoutMutationForUnsafeMarkersAndGenuineRemnants() throws Exception {
for (int scenario = 0; scenario < 4; scenario++) try (var f = new Fixture()) {
var a = f.actor; f.core.activate(a);
var genuineBox = a.inventory.getItem(9).clone();
assertEquals(games.dmg.fantasystick.items.EquipmentStorage.RestoreResult.RESTORED,
f.core.sessions.exit(a.player));
var completed = f.core.state(a);
assertNull(completed.pending()); assertNotNull(completed.lastExit());
a.inventory.setItemInOffHand(new ItemStack(Material.EMERALD));
if (scenario == 0) a.pdc.set(new org.bukkit.NamespacedKey("fantasystick", "equipment_session"),
org.bukkit.persistence.PersistentDataType.STRING, "malformed");
if (scenario == 1) a.marker(java.util.UUID.randomUUID(), "RESTORED");
if (scenario == 2) a.marker(completed.lastExit().sessionId(), "PACKED");
if (scenario == 3) a.inventory.setItem(12, genuineBox);
var before = a.snapshot(); var cursor = a.cursor.clone(); var marker = a.marker();
var record = f.core.characters.find(a.record.id()).join().orElseThrow();
int notices = f.core.notices.size();
f.dispatcher.accept(new org.bukkit.event.player.PlayerJoinEvent(a.player,
(net.kyori.adventure.text.Component) null));
f.core.tick();
assertEquals(notices + 1, f.core.notices.size());
assertEquals("NATIVE_RECOVERY_REQUIRED", f.core.notices.getLast().code());
assertArrayEquals(before, a.snapshot()); assertEquals(cursor, a.cursor);
assertEquals(marker, a.marker()); assertEquals(completed, f.core.state(a));
assertEquals(record, f.core.characters.find(a.record.id()).join().orElseThrow());
assertTrue(f.core.sessions.active(a.id).isEmpty()); assertTrue(f.ticks.isEmpty());
}
}
@Test void reservedWorldAndInventorySwapsRestoreBeforeExchangingOrdinaryGear() throws Exception {
for (int route = 0; route < 3; route++) {
try (var f = new Fixture()) {