Description
Sable's Create compatibility mixin crashes the server with a NullPointerException when a Create contraption containing no solid blocks is initialized. The crash is in AbstractContraptionEntityMixin#sable$buildProperties (injected into AbstractContraptionEntity.tick), which calls MassTracker.getCenterOfMass().negate(...) without a null check.
Since the entity crashes during ticking, the server crash-loops: after every restart the same contraption entity is loaded and ticked again, and the server goes down immediately (in our case the follow-up watchdog kill happened ~61s after restart). The world is effectively unloadable until the entity is removed manually.
Versions
- Minecraft: 1.21.1
- NeoForge: 21.1.230
- Sable: 2.0.3 (neoforge) — latest release at time of writing
- Sable Companion: 1.6.0
- Create: 6.0.10
Crash
java.lang.NullPointerException: Cannot invoke "org.joml.Vector3dc.negate(org.joml.Vector3d)"
because the return value of "dev.ryanhcode.sable.api.physics.mass.MassTracker.getCenterOfMass()" is null
at create@6.0.10/.../AbstractContraptionEntity.sable$buildProperties(AbstractContraptionEntity.java:1668)
at create@6.0.10/.../AbstractContraptionEntity.handler$bpc000$sable$contraptionInitialize(AbstractContraptionEntity.java:1594)
at create@6.0.10/.../AbstractContraptionEntity.tick$mixinextras$wrapped$115(AbstractContraptionEntity.java:374)
at minecraft@1.21.1/net.minecraft.world.entity.Entity.rideTick(Entity.java:1960)
at minecraft@1.21.1/net.minecraft.server.level.ServerLevel.tickPassenger(ServerLevel.java:793)
...
Entity being ticked: minecraft:furnace_minecart carrying an OrientedContraptionEntity passenger. Full crash report attached.
Root cause
In MassTracker.build(...), if no block inside the bounds passes VoxelNeighborhoodState.isSolid(...) (blockCount == 0), the returned tracker has centerOfMass = null — this is intentional and documented by the @Nullable annotation.
However, in AbstractContraptionEntityMixin#sable$buildProperties (neoforge, mixin/compatibility/create/contraptions/):
this.sable$massTracker = MassTracker.build(this.sable$blockGetter(), this.sable$localBounds);
final Vector3d temp = this.sable$massTracker.getCenterOfMass().negate(new Vector3d()).add(0.5, 0.5, 0.5); // <-- NPE
The null case is handled by the caller — sable$contraptionInitialize checks getCenterOfMass() == null and treats the contraption as empty — but that check runs after sable$buildProperties() has already dereferenced the null.
There is a second latent NPE in the same method: if every block in the contraption is air, sable$localBounds stays null and is only guarded by an assert (disabled at runtime), so MassTracker.build(getter, null) would also throw.
So this reproduces with any minecart-mounted (or otherwise assembled) contraption whose blocks are all non-solid per VoxelNeighborhoodState.isSolid.
This looks like the same class of bug as #563, #866 and #675 — Create compat mixins reading contraption/block-entity state without null guards.
Suggested fix
Early-return from sable$buildProperties() in both cases, so the existing "empty contraption" path in sable$contraptionInitialize handles it:
if (this.sable$localBounds == null) {
// Contraption has no non-air blocks, leave the tracker empty so the
// caller treats it as an empty contraption
this.sable$massTracker = new MassTracker();
return;
}
this.sable$massTracker = MassTracker.build(this.sable$blockGetter(), this.sable$localBounds);
final Vector3dc centerOfMass = this.sable$massTracker.getCenterOfMass();
if (centerOfMass == null) {
// No block contributed mass (e.g. no solid blocks), treated as empty by the caller
return;
}
final Vector3d temp = centerOfMass.negate(new Vector3d()).add(0.5, 0.5, 0.5);
We patched this on top of the mc1.21.1-2.0.3-neoforge tag, rebuilt, and it resolves the crash loop on our server (the previously-crashing contraption now initializes as an empty contraption and the world loads normally). Happy to open a PR if you'd take one.
Description
Sable's Create compatibility mixin crashes the server with a
NullPointerExceptionwhen a Create contraption containing no solid blocks is initialized. The crash is inAbstractContraptionEntityMixin#sable$buildProperties(injected intoAbstractContraptionEntity.tick), which callsMassTracker.getCenterOfMass().negate(...)without a null check.Since the entity crashes during ticking, the server crash-loops: after every restart the same contraption entity is loaded and ticked again, and the server goes down immediately (in our case the follow-up watchdog kill happened ~61s after restart). The world is effectively unloadable until the entity is removed manually.
Versions
Crash
Entity being ticked:
minecraft:furnace_minecartcarrying anOrientedContraptionEntitypassenger. Full crash report attached.Root cause
In
MassTracker.build(...), if no block inside the bounds passesVoxelNeighborhoodState.isSolid(...)(blockCount == 0), the returned tracker hascenterOfMass = null— this is intentional and documented by the@Nullableannotation.However, in
AbstractContraptionEntityMixin#sable$buildProperties(neoforge,mixin/compatibility/create/contraptions/):The null case is handled by the caller —
sable$contraptionInitializechecksgetCenterOfMass() == nulland treats the contraption as empty — but that check runs aftersable$buildProperties()has already dereferenced the null.There is a second latent NPE in the same method: if every block in the contraption is air,
sable$localBoundsstaysnulland is only guarded by anassert(disabled at runtime), soMassTracker.build(getter, null)would also throw.So this reproduces with any minecart-mounted (or otherwise assembled) contraption whose blocks are all non-solid per
VoxelNeighborhoodState.isSolid.This looks like the same class of bug as #563, #866 and #675 — Create compat mixins reading contraption/block-entity state without null guards.
Suggested fix
Early-return from
sable$buildProperties()in both cases, so the existing "empty contraption" path insable$contraptionInitializehandles it:We patched this on top of the
mc1.21.1-2.0.3-neoforgetag, rebuilt, and it resolves the crash loop on our server (the previously-crashing contraption now initializes as an empty contraption and the world loads normally). Happy to open a PR if you'd take one.