Skip to content

Biome writes at y = world max (320) → ArrayIndexOutOfBoundsException on Paper 26.2 in /plot setbiome and plot clear #4937

Description

@minoneer

Server Implementation

Paper

Server Version

Paper 26.2 (build 121, 26.2-121-main@a2a42c5; also reproduced on build 112). WorldEdit 7.4.5 (7.4.5+7590-b8dc4c1).

Describe the bug

Since moving the server from Paper 1.21.11 to Paper 26.2, every /plot setbiome <biome> and every plot clear (/plot delete/plot confirm) prints one ArrayIndexOutOfBoundsException: Index 24 out of bounds for length 24 per chunk of the plot. The world is a standard -64..319 world (24 sections).

The throw site is WorldEdit's adapter-26.2 PaperweightAdapter.setBiome, but the cause is PlotSquared asking it to set a biome at y = 320, one block above the world. Two independent code paths do this:

1. /plot setbiomePlot.getRegions() uses the exclusive max build height as an inclusive region bound.

if (!this.isMerged()) {
Location pos1 = this.getBottomAbs().withY(getArea().getMinBuildHeight());
Location pos2 = this.getTopAbs().withY(getArea().getMaxBuildHeight());
CuboidRegion rg = new CuboidRegion(pos1.getBlockVector3(), pos2.getBlockVector3());

if (!this.isMerged()) {
    Location pos1 = this.getBottomAbs().withY(getArea().getMinBuildHeight());
    Location pos2 = this.getTopAbs().withY(getArea().getMaxBuildHeight());   // exclusive (default versionMaxHeight() + 1 == 320)
    CuboidRegion rg = new CuboidRegion(pos1.getBlockVector3(), pos2.getBlockVector3());

PlotArea.getMaxBuildHeight() is documented "Exclusive" and defaults to versionMaxHeight() + 1 (320). The merged-plot branch of the same method already subtracts 1 (int maxHeight = getArea().getMaxBuildHeight() - 1;, line 2563); the unmerged branch does not. RegionManager.setBiomeWorldUtil.setBiomes then does region.forEach(bv -> world.setBiome(bv, biome)), and CuboidRegion iteration is inclusive, so the last layer visited is y = 320.

2. Plot clear — BukkitQueueCoordinator.enqueue rebuilds biome Y from the layer index without the negative-section offset.

int y = ChunkUtil.getY(layer + localChunk.getMinSection(), j);

// blocks (line 180) — correct
int y = ChunkUtil.getY(layer + localChunk.getMinSection(), j);
...
// biomes (line 201) — missing the offset
int y = ChunkUtil.getY(layer, j);

LocalChunk.setBiome stores biomes with getLayerIndex(y) = (y >> 4) - minSection, exactly like blocks. Reading them back without adding minSection back shifts every biome up by 64 blocks in a -64..319 world: a biome queued for y = -64 is written at y = 0, and a biome queued for y ≥ 256 is written at y ≥ 320. HybridPlotManager.clearPlot queues setBiomeCuboid(minGenHeight .. maxGenHeight), so layer 20 (queued y = 256) is the first to land at y = 320 → index 24.

Why it only shows up on 26.2. WorldEdit's adapter-1.21.11 implemented setBiome via vanilla ChunkAccess.setBiome, which clamps Y into the world. The adapter-26.1/adapter-26.2 implementation does chunk.getSection(chunk.getSectionIndex(y)).getBiomes() directly, with no clamp. So both PlotSquared bugs are older than 26.2 and were previously silently swallowed by the clamp (which also means path 2 has been writing biomes 64 blocks too high, and never writing y < 0, in every extended-height world).

Since BukkitChunkCoordinator catches the throwable per chunk and continues, the visible effect is: /plot setbiome applies the biome to every valid layer but skips the refreshChunk for the chunk that threw (players see the old biome until the chunk reloads); a plot clear sets the biome for y ≥ 0 only and skips the tile-entity / entity restoration for that chunk.

To Reproduce

  1. Paper 26.2 + WorldEdit 7.4.5 + PlotSquared, plot world with default heights (world.max_height: 320, world.max_gen_height: 319).
  2. Stand in an unmerged plot, run /plot setbiome flower_forest.
  3. Console prints one ArrayIndexOutOfBoundsException: Index 24 out of bounds for length 24 per chunk.
  4. /plot delete + /plot confirm on the same plot prints the same trace per chunk (via BukkitQueueCoordinator.lambda$enqueue$2).

Expected behaviour

No biome writes outside the world's Y range; the region top should be getMaxBuildHeight() - 1 like the merged branch, and the queue readback should be ChunkUtil.getY(layer + localChunk.getMinSection(), j) like the block loop directly above it.

Screenshots / Videos

No response

Error log (if applicable)

[WARN]: java.lang.ArrayIndexOutOfBoundsException: Index 24 out of bounds for length 24
[WARN]: 	at net.minecraft.world.level.chunk.ChunkAccess.getSection(ChunkAccess.java:231)
[WARN]: 	at WorldEdit.jar//com.sk89q.worldedit.bukkit.adapter.impl.v26_2.PaperweightAdapter.setBiome(PaperweightAdapter.java:445)
[WARN]: 	at WorldEdit.jar//com.sk89q.worldedit.bukkit.BukkitWorld.setBiome(BukkitWorld.java:589)
[WARN]: 	at PlotSquared.jar//com.plotsquared.core.util.WorldUtil.lambda$setBiomes$0(WorldUtil.java:230)
[WARN]: 	at java.base/java.lang.Iterable.forEach(Iterable.java:75)
[WARN]: 	at PlotSquared.jar//com.plotsquared.core.util.WorldUtil.setBiomes(WorldUtil.java:230)
[WARN]: 	at PlotSquared.jar//com.plotsquared.core.util.WorldUtil.setBiome(WorldUtil.java:83)
[WARN]: 	at PlotSquared.jar//com.plotsquared.core.util.RegionManager.lambda$setBiome$2(RegionManager.java:409)
[WARN]: 	at PlotSquared.jar//com.plotsquared.bukkit.queue.BukkitChunkCoordinator.run(BukkitChunkCoordinator.java:187)
[WARN]: 	at PlotSquared.jar//com.plotsquared.bukkit.util.task.BukkitPlotSquaredTask.runTask(BukkitPlotSquaredTask.java:39)
[WARN]: 	at PlotSquared.jar//com.plotsquared.core.util.task.PlotSquaredTask.run(PlotSquaredTask.java:44)
[WARN]: 	at org.bukkit.craftbukkit.scheduler.CraftTask.run(CraftTask.java:78)
[WARN]: 	at org.bukkit.craftbukkit.scheduler.CraftScheduler.mainThreadHeartbeat(CraftScheduler.java:474)
[WARN]: 	at net.minecraft.server.MinecraftServer.tickChildren(MinecraftServer.java:1767)

Plot clear variant:

[WARN]: java.lang.ArrayIndexOutOfBoundsException: Index 24 out of bounds for length 24
[WARN]: 	at net.minecraft.world.level.chunk.ChunkAccess.getSection(ChunkAccess.java:231)
[WARN]: 	at WorldEdit.jar//com.sk89q.worldedit.bukkit.adapter.impl.v26_2.PaperweightAdapter.setBiome(PaperweightAdapter.java:445)
[WARN]: 	at WorldEdit.jar//com.sk89q.worldedit.bukkit.BukkitWorld.setBiome(BukkitWorld.java:589)
[WARN]: 	at PlotSquared.jar//com.plotsquared.bukkit.queue.BukkitQueueCoordinator.lambda$enqueue$2(BukkitQueueCoordinator.java:203)
[WARN]: 	at PlotSquared.jar//com.plotsquared.bukkit.queue.BukkitChunkCoordinator.run(BukkitChunkCoordinator.java:187)

Plot Debugpaste

Not attached — this is a code-level report; both lines are unchanged on main (b49bd002) and nothing in the paste would add to it. Happy to provide one if you still want it.

PlotSquared Version

Observed on a 7.5.11-SNAPSHOT build; the offending lines are identical in 7.5.11 (where the line numbers in the trace match exactly), 7.6.0 and current main @ b49bd00.

Checklist

Anything else?

Proposed fix (two one-line changes):

--- a/Core/src/main/java/com/plotsquared/core/plot/Plot.java
+++ b/Core/src/main/java/com/plotsquared/core/plot/Plot.java
@@ public @NonNull Set<CuboidRegion> getRegions() {
         if (!this.isMerged()) {
             Location pos1 = this.getBottomAbs().withY(getArea().getMinBuildHeight());
-            Location pos2 = this.getTopAbs().withY(getArea().getMaxBuildHeight());
+            Location pos2 = this.getTopAbs().withY(getArea().getMaxBuildHeight() - 1);
--- a/Bukkit/src/main/java/com/plotsquared/bukkit/queue/BukkitQueueCoordinator.java
+++ b/Bukkit/src/main/java/com/plotsquared/bukkit/queue/BukkitQueueCoordinator.java
@@ for (int layer = 0; layer < localChunk.getBiomes().length; layer++) {
                             int x = sx + ChunkUtil.getX(j);
-                            int y = ChunkUtil.getY(layer, j);
+                            int y = ChunkUtil.getY(layer + localChunk.getMinSection(), j);
                             int z = sz + ChunkUtil.getZ(j);

WorldEdit could additionally guard PaperweightAdapter.setBiome against out-of-range Y (as the 1.21.11 adapter effectively did), but the out-of-range coordinates originate here.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions