Skip to content

Crash: ArrayIndexOutOfBoundsException in Sable's VoxelNeighborhoodState.isSolid when wrapped by TreePhysics' leaf_collision mixin #59

Description

@thatsacat2021-cell

The leaf_collision.VoxelNeighborhoodStateMixin in TreePhysics wraps Sable's VoxelNeighborhoodState.isSolid call, and while that wrap is active Sable's internal Int2BooleanOpenHashMap cache becomes corrupted, throwing ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 513 from fastutil's rehash. Crash fires on the server thread during a routine SpreadingSnowyDirtBlock.randomTick (grass spreading), so it is easily hit under normal survival play.

Removing TreePhysics eliminates the crash; keeping TreePhysics reproduces it reliably.

Environment:

Minecraft 1.21.1
Loader NeoForge 21.1.234
Tree Physics 2.2 (treephysics-neoforge-1.21.1.jar)
Sable |2.0.3 (sable-neoforge-1.21.1-2.0.3.jar)
Sable Rapier| 2.0.3 (sable-sable_rapier-1.21.1-2.0.3.jar)
Java | 25.0.1 (Eclipse Adoptium)

What happens:

While walking around a survival world, the server thread crashes with:

Description: Exception ticking world
java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 513

Trigger is a vanilla grass block running its random tick and setting a neighbouring dirt block to grass. Sable hooks the setBlockState to update its physics world, and Sable's Rapier pipeline queries VoxelNeighborhoodState.isSolid on the affected voxels. TreePhysics' wrapOperation around isSolid is on the stack for every failing frame, and the crash lands inside the Int2BooleanOpenHashMap cache that isSolid uses.

Stack trace (trimmed to the relevant portion)

java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 513
    at it.unimi.dsi.fastutil.ints.Int2BooleanOpenHashMap.rehash(Int2BooleanOpenHashMap.java:1335)
    at it.unimi.dsi.fastutil.ints.Int2BooleanOpenHashMap.insert(Int2BooleanOpenHashMap.java:257)
    at it.unimi.dsi.fastutil.ints.Int2BooleanOpenHashMap.computeIfAbsent(Int2BooleanOpenHashMap.java:456)
    at sable@2.0.3/dev.ryanhcode.sable.physics.chunk.VoxelNeighborhoodState$1.apply(VoxelNeighborhoodState.java:31)
    at sable@2.0.3/dev.ryanhcode.sable.physics.chunk.VoxelNeighborhoodState$1.apply(VoxelNeighborhoodState.java:26)
    at sable@2.0.3/dev.ryanhcode.sable.physics.chunk.VoxelNeighborhoodState.isSolid(VoxelNeighborhoodState.java:66)
        {pl:mixin:APP:treephysics.mixins.json:leaf_collision.VoxelNeighborhoodStateMixin from mod treephysics}
    at sable@2.0.3/dev.ryanhcode.sable.physics.chunk.VoxelNeighborhoodState.mixinextras$bridge$isSolid$14(VoxelNeighborhoodState.java)
        {pl:mixin:APP:treephysics.mixins.json:leaf_collision.VoxelNeighborhoodStateMixin from mod treephysics}
    at sable@2.0.3/dev.ryanhcode.sable.physics.chunk.VoxelNeighborhoodState.wrapOperation$cfh000$treephysics$isSolid(VoxelNeighborhoodState.java:524)
        {pl:mixin:APP:treephysics.mixins.json:leaf_collision.VoxelNeighborhoodStateMixin from mod treephysics}
    at sable@2.0.3/dev.ryanhcode.sable.physics.chunk.VoxelNeighborhoodState.getState(VoxelNeighborhoodState.java:104)
        {pl:mixin:APP:treephysics.mixins.json:leaf_collision.VoxelNeighborhoodStateMixin from mod treephysics}
    at sable_rapier@1.21.1-2.0.3/dev.ryanhcode.sable.physics.impl.rapier.RapierPhysicsPipeline.handleBlockChange(RapierPhysicsPipeline.java:394)
        {pl:mixin:APP:treephysics.mixins.json:leaf_collision.RapierPhysicsPipelineMixin from mod treephysics}
    at sable@2.0.3/dev.ryanhcode.sable.sublevel.system.SubLevelPhysicsSystem.handleBlockChange(SubLevelPhysicsSystem.java:472)
    at sable@2.0.3/dev.ryanhcode.sable.SableCommonEvents.handleBlockChange(SableCommonEvents.java:91)
    at LevelChunk.wrapOperation$cnf000$sable$setBlockState(LevelChunk.java:3802)
    at LevelChunk.setBlockState(LevelChunk.java:249)
    at Level.setBlock(Level.java:236)
    at Level.setBlock(Level.java:212)
    at Level.setBlockAndUpdate(Level.java:328)
    at SpreadingSnowyDirtBlock.randomTick(SpreadingSnowyDirtBlock.java:46)
    at BlockBehaviour$BlockStateBase.randomTick(BlockBehaviour.java:736)
    at ServerLevel.tickChunk(ServerLevel.java:529)
    at ServerChunkCache.tickChunks(ServerChunkCache.java:371)
    ...

Notice treephysics.mixins.json:leaf_collision.VoxelNeighborhoodStateMixin on every frame from isSolid down to getState, and treephysics.mixins.json:leaf_collision.RapierPhysicsPipelineMixin on the handleBlockChange frame — TreePhysics' leaf_collision module is the only external code modifying the failing methods.

Reproduction steps:

  1. Install Sable 2.0.3 + Sable Rapier 2.0.3 on Minecraft 1.21.1 / NeoForge 21.1.234.
  2. Install Tree Physics 2.2.
  3. Load a survival world with normal grass/dirt terrain and wait for random ticks (~1–2 minutes of loaded chunks).
  4. Server crashes with the stack above.

crash-2026-07-06_13.25.04-server.txt

latest.log

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