From d8efdabe66e524c044975fec0d5dfefd5e830b00 Mon Sep 17 00:00:00 2001 From: XxFran10xX <318299142+XxFran10xX@users.noreply.github.com> Date: Thu, 22 Oct 2026 10:20:19 +0200 Subject: [PATCH 1/2] Skip gear estates already settled in the hourly pass Decided estates stay decided for good, and the hourly pass settled every one of them again each hour. Each settle reads gear-stations.yml several times on the main thread, so the cost grows with both the file and the number of estates; on 10-21 that froze Main for 20-30 s every hour. A revision this process already reported DONE is now skipped until the next restart. RETRY and BLOCKED answers, and new revisions, still settle every hour, and the first pass after a restart settles every estate once. Co-Authored-By: Claude Opus 5.5 --- .../gear/ownership/CharacterGearEstate.java | 11 ++++++++- .../ownership/CharacterGearEstateTest.java | 23 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/tfminecraft/magic/gear/ownership/CharacterGearEstate.java b/src/main/java/net/tfminecraft/magic/gear/ownership/CharacterGearEstate.java index 58e8bc2..494e8dc 100644 --- a/src/main/java/net/tfminecraft/magic/gear/ownership/CharacterGearEstate.java +++ b/src/main/java/net/tfminecraft/magic/gear/ownership/CharacterGearEstate.java @@ -58,16 +58,25 @@ public Holdings describe(UUID character) { return ids.isEmpty() ? Holdings.NONE : new Holdings(Map.of("gear", ids.size()), ids); } catch (IOException | RuntimeException ex) { return new Holdings(Map.of("heldGear", 1), List.of("Magic gear needs review")); } } + /** + * Decided estates stay decided for good, and each settle reads the station file several times, + * so a revision this process already reported DONE is not settled again until the next restart. + * RETRY and BLOCKED answers, and new revisions, are still settled every hour. + */ void reconcile() { try { GearStationStore.property().migrate(); GearStationStore.loadViews(); } catch (IOException ex) { GearStationStore.warning(ex); } for (EstateDecision decision : Estates.decided()) { try { - if (!Estates.guardHolds(decision)) continue; + if (settled(decision) || !Estates.guardHolds(decision)) continue; Outcome outcome = settle(decision); var result = Map.entry(decision.revision(), outcome); if (!result.equals(reported.get(decision.deathId())) && Estates.report(decision.deathId(), id(), decision.revision(), outcome)) reported.put(decision.deathId(), result); } catch (RuntimeException ex) { plugin.getLogger().warning("[Magic] Gear estate held: " + decision.deathId() + " " + ex); } } } + private boolean settled(EstateDecision decision) { + var last = reported.get(decision.deathId()); + return last != null && last.getKey() == decision.revision() && last.getValue() instanceof Outcome.Done; + } private static GearIdentity.Owner owner(CharacterRef ref) { return new GearIdentity.Owner(ref.ownerId(), ref.characterId()); } } diff --git a/src/test/java/net/tfminecraft/magic/gear/ownership/CharacterGearEstateTest.java b/src/test/java/net/tfminecraft/magic/gear/ownership/CharacterGearEstateTest.java index eb6a30d..9ad11b0 100644 --- a/src/test/java/net/tfminecraft/magic/gear/ownership/CharacterGearEstateTest.java +++ b/src/test/java/net/tfminecraft/magic/gear/ownership/CharacterGearEstateTest.java @@ -192,6 +192,29 @@ UUID station() throws IOException { verify(estates, times(1)).report(eq(death), eq("magic-gear"), eq(1L), any()); } + @Test void hourlyReconcileSkipsARevisionAlreadyReportedDoneButSettlesRetriesAndNewRevisions() throws Exception { + decision(Recipient.FROZEN, 1); + orbs.when(() -> GearOrbService.touches(any())).thenReturn(true); + participant.reconcile(); participant.reconcile(); + orbs.verify(() -> GearOrbService.touches(any()), times(2)); + verify(estates, times(1)).report(eq(death), eq("magic-gear"), eq(1L), isA(Outcome.Retry.class)); + orbs.when(() -> GearOrbService.touches(any())).thenReturn(false); + participant.reconcile(); participant.reconcile(); + orbs.verify(() -> GearOrbService.touches(any()), times(3)); + verify(estates, times(1)).report(eq(death), eq("magic-gear"), eq(1L), isA(Outcome.Done.class)); + decision(Recipient.FROZEN, 2); + participant.reconcile(); participant.reconcile(); + orbs.verify(() -> GearOrbService.touches(any()), times(4)); + verify(estates, times(1)).report(eq(death), eq("magic-gear"), eq(2L), isA(Outcome.Done.class)); + participant = new CharacterGearEstate(plugin); + when(characters.deathId(from.characterId())).thenReturn(Optional.of(UUID.randomUUID())); + participant.reconcile(); + orbs.verify(() -> GearOrbService.touches(any()), times(4)); + when(characters.deathId(from.characterId())).thenReturn(Optional.of(death)); + participant.reconcile(); + orbs.verify(() -> GearOrbService.touches(any()), times(5)); + } + @Test void registeredHourlyReconcileReportsOnlyChangedSuccessfulOutcomesAndSurvivesFailure() throws Exception { var value = decision(Recipient.FROZEN, 1); Server facade = mock(Server.class); From 9cd61cfce25c7c90223e18ba0d60b7ff179694ff Mon Sep 17 00:00:00 2001 From: XxFran10xX <318299142+XxFran10xX@users.noreply.github.com> Date: Thu, 22 Oct 2026 10:22:34 +0200 Subject: [PATCH 2/2] Note when late gear for a settled estate moves Co-Authored-By: Claude Opus 5.5 --- .../tfminecraft/magic/gear/ownership/CharacterGearEstate.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/tfminecraft/magic/gear/ownership/CharacterGearEstate.java b/src/main/java/net/tfminecraft/magic/gear/ownership/CharacterGearEstate.java index 494e8dc..50e0c73 100644 --- a/src/main/java/net/tfminecraft/magic/gear/ownership/CharacterGearEstate.java +++ b/src/main/java/net/tfminecraft/magic/gear/ownership/CharacterGearEstate.java @@ -61,7 +61,8 @@ public Holdings describe(UUID character) { /** * Decided estates stay decided for good, and each settle reads the station file several times, * so a revision this process already reported DONE is not settled again until the next restart. - * RETRY and BLOCKED answers, and new revisions, are still settled every hour. + * RETRY and BLOCKED answers, and new revisions, are still settled every hour. Gear that reaches a + * settled testator later, such as a staff-recovered station, moves at the first pass after a restart. */ void reconcile() { try { GearStationStore.property().migrate(); GearStationStore.loadViews(); }