Skip to content

[Bug] Server crash: NPE in Create compat — MassTracker.getCenterOfMass() is null in AbstractContraptionEntityMixin#sable$buildProperties #1315

Description

@sapogi

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions