Skip to content

Commit 7715a12

Browse files
tastybentoclaude
andcommitted
debug: delay zero scan and log handicap drift for diagnosis
The race fix landed correctly (the latest report's level matches (total - initialCount)/level_cost so initialCount is being read right), but a 3,358-point gap still appears on a fresh island with no Poseidon pregens and no player builds. That points at decoration evolution inside already-scanned chunks (lava+water forming obsidian late, fluid simulation, trial-spawner state, chunk-border ore patches) — value the zero scan didn't see because it ran too early. Two debugging changes to narrow down what's drifting: - Lengthen the IslandCreatedEvent zero-scan delay from 150 ticks to whatever zero-scan-delay-ticks is set to (default 600 = 30s). Reuses the listener's decoration-settle window so both code paths give the world the same chance to finish generating before snapshotting. Floored at 150 so admins lowering the setting don't break the original "wait a few seconds" intent. - Log per-chunk drift on every reconcile that finds any new/growing/ shrinking chunks: counts of each plus the top 5 growing chunks by delta. Once we can see whether the gap is a handful of chunks evolving by a lot or many chunks evolving by a little, we can pick the right fix (longer delay vs absorb-on-first-scan vs something else). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent f254f04 commit 7715a12

2 files changed

Lines changed: 54 additions & 10 deletions

File tree

src/main/java/world/bentobox/level/LevelsManager.java

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -769,22 +769,59 @@ public long reconcileHandicapChunks(@NonNull Island island,
769769
}
770770
}
771771
long delta = 0L;
772+
int newChunks = 0;
773+
int growingChunks = 0;
774+
int shrinkingChunks = 0;
775+
long totalGrowth = 0L;
776+
long totalShrinkage = 0L;
777+
java.util.List<java.util.Map.Entry<String, Long>> topDiffs = new java.util.ArrayList<>();
772778
for (Map.Entry<String, Long> e : scannedChunkValues.entrySet()) {
773-
if (!persisted.containsKey(e.getKey())) {
774-
long value = e.getValue() == null ? 0L : e.getValue();
775-
persisted.put(e.getKey(), value);
776-
delta += value;
779+
long scanned = e.getValue() == null ? 0L : e.getValue();
780+
Long current = persisted.get(e.getKey());
781+
if (current == null) {
782+
// Self-heal: any chunk the scan saw but the persisted map
783+
// didn't know about gets folded into both the map and the
784+
// initialCount in the same write, so the next scan reads a
785+
// level of zero for that previously-missing terrain.
786+
persisted.put(e.getKey(), scanned);
787+
delta += scanned;
788+
newChunks++;
789+
} else {
790+
long diff = scanned - current;
791+
if (diff > 0) {
792+
growingChunks++;
793+
totalGrowth += diff;
794+
topDiffs.add(new java.util.AbstractMap.SimpleEntry<>(e.getKey(), diff));
795+
} else if (diff < 0) {
796+
shrinkingChunks++;
797+
totalShrinkage += -diff;
798+
}
777799
}
778800
}
779801
if (delta != 0L) {
780-
// Self-heal: any chunk the scan saw but the persisted map didn't
781-
// know about gets folded into both the map and the initialCount
782-
// in the same write, so the next scan reads a level of zero for
783-
// that previously-missing terrain.
784802
long existing = data.getInitialCount() == null ? 0L : data.getInitialCount();
785803
data.setInitialCount(existing + delta);
786804
handler.saveObjectAsync(data);
787805
}
806+
// Diagnostic: surface any drift between what the live scan saw and
807+
// what the persisted handicap says. Growing chunks usually mean
808+
// decoration evolution (lava+water → obsidian forming late, fluid
809+
// simulation spreading, trial spawners activating, chunk-border ore
810+
// patches completing); shrinking chunks mean a player broke
811+
// naturally-generated blocks. Only logged when a delta exists so
812+
// routine /level commands don't spam.
813+
if (newChunks + growingChunks + shrinkingChunks > 0) {
814+
addon.log(String.format(
815+
"Handicap diagnostic for island %s: new=%d (+%,d) growing=%d (+%,d) shrinking=%d (-%,d)",
816+
island.getUniqueId(), newChunks, delta, growingChunks, totalGrowth,
817+
shrinkingChunks, totalShrinkage));
818+
topDiffs.sort((a, b) -> Long.compare(b.getValue(), a.getValue()));
819+
int top = Math.min(5, topDiffs.size());
820+
for (int i = 0; i < top; i++) {
821+
addon.log(String.format(" Top growth chunk #%d: %s +%,d",
822+
i + 1, topDiffs.get(i).getKey(), topDiffs.get(i).getValue()));
823+
}
824+
}
788825
return delta;
789826
}
790827

src/main/java/world/bentobox/level/listeners/IslandActivitiesListeners.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,15 @@ public IslandActivitiesListeners(Level addon) {
4141
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
4242
public void onNewIsland(IslandCreatedEvent e) {
4343
if (addon.getSettings().isZeroNewIslandLevels()) {
44-
// Wait a few seconds before performing the zero
45-
Bukkit.getScheduler().runTaskLater(addon.getPlugin(), () -> zeroIsland(e.getIsland()), 150L);
44+
// Delay the zero scan so decoration (fluid simulation,
45+
// late-arriving obsidian/portal frames, trial-spawner setup,
46+
// chunk-border ore patches) has time to settle before we
47+
// snapshot the baseline. Reuses the listener's decoration-settle
48+
// setting (zero-scan-delay-ticks, default 600 = 30s) so both
49+
// paths honour the same "give the world a chance to finish
50+
// generating" window.
51+
long delay = Math.max(150L, addon.getSettings().getZeroScanDelayTicks());
52+
Bukkit.getScheduler().runTaskLater(addon.getPlugin(), () -> zeroIsland(e.getIsland()), delay);
4653
}
4754
}
4855

0 commit comments

Comments
 (0)