Skip to content

Zero-mass blocks get duplicated as item drops during sub-level disassembly (destroyAllBlocks fires with drops when massTracker hits 0) #1532

Description

@aihaoDIYlove

Description

Environment

Sable 2.0.5+mc1.21.1 (also reproduced on 2.0.3)
Simulated 1.3.0 / 1.3.2 (Physics Assembler)
Create Aeronautics 1.3.2
Loader NeoForge 21.1.235
Minecraft 1.21.1

Summary

Disassembling a sub-level can duplicate any collision-less (zero-mass) block that happens to be cleared after the last mass-bearing block in the plot-sweep order.

The block is destroyed with drops even though a copy of it has already been placed at the destination, so the player receives an extra copy of the item.

This is reproducible with vanilla blocks only (e.g. wall signs and torches) and is still reproducible on Sable 2.0.5.

Reproduction

  1. Build a small structure from solid blocks, then attach vanilla wall signs / torches to it.
  2. Assemble and then disassemble it using the Physics Assembler.
  3. Signs/torches that come after the last solid block in the plot-sweep order are dropped as items while also being correctly placed at the destination.

Which blocks are duplicated depends on their position relative to the last mass-bearing block in the clear order, so the affected blocks vary with the structure's orientation and placement.

A sunflower is a handy east-facing indicator when re-testing. 🙂

Root Cause

  1. PhysicsBlockPropertyHelper.getMass short-circuits to 0.0 for any block whose collision shape is empty (VoxelNeighborhoodState.isSolid).

    The source itself contains a TODO explicitly acknowledging this case:

    // TODO: Doing this means that sub-levels can end up with an existent bounding box but without any mass, invalidating them

    PhysicsBlockPropertyHelper.java:28

  2. During SubLevelAssemblyHelper.moveBlocks, each source-position clear is routed through SubLevelPhysicsSystem.updateMassDataFromBlockChange, which subtracts the block's mass from the tracker.

    When the last mass-bearing block is cleared, the total mass reaches 0, causing MassData.isInvalid() to become true even though zero-mass blocks may still remain in the source plot.

  3. This triggers ServerLevelPlot.destroyAllBlocks(), which destroys the remaining plot blocks with drops.

    However, those blocks have already been placed at their destination by moveBlocks, so the destruction drops create duplicate items.

In short:

moveBlocks places all blocks at the destination
        ↓
source positions are cleared one by one
        ↓
last mass-bearing block is cleared
        ↓
mass reaches 0
        ↓
MassData.isInvalid()
        ↓
destroyAllBlocks()
        ↓
remaining zero-mass blocks are destroyed with drops
        ↓
destination copies + dropped source items = duplication

This also explains why the issue can appear to be random: the result depends on the source-position clear order and the structure's orientation/placement.

Suggested Fixes

Any of the following could prevent the duplication:

  • Do not trigger the isInvalid()destroyAllBlocks() path while moveBlocks is still performing its source-position clearing.
  • Make destroyAllBlocks silent (destroyBlock(pos, false) or removeBlock) when it is being used as part of this cleanup, since the blocks in the remaining plot have already been moved to their destination.
  • More generally, do not use mass == 0 alone to determine that a sub-level contains no remaining blocks; only invalidate/destroy the sub-level once all of its blocks have actually been processed.

Workaround

For our own decorative blocks, we currently work around this by giving them a tiny 0.01³ collision box so Sable treats them as mass-bearing.

Collision-less vanilla blocks such as signs and torches remain affected.

Attached log: reproduction on Sable 2.0.3. The issue was also tested and reproduced on Sable 2.0.5.

Image Image Image

Game Log

https://mclo.gs/miDxgEv

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

    type: bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions