Stops entity collision against Sable sub-levels from freezing the server tick. For Create: Aeronautics.
When an entity moves near a sub-level, Sable works out which blocks to test like this:
- take the entity's bounding box swept from where it was to where it is going
- expand it by 0.35
- inverse transform it into the sub-level's local frame at
lastPose - inverse transform it again at the sub-level's current
logicalPose() - union those two boxes
- walk every block position inside the union with
BlockPos.betweenClosed(min, max)
Step 5 is where it goes wrong. The two boxes are the same entity in the same tick, but seen from a frame that has moved in between. If the sub-level rotated or travelled a long way since its last pose, the two land far apart and the union spans everything between them. That union has nothing to do with the size of the ship or the size of the entity.
Step 6 then walks it, four times over, for one entity taking one step.
Numbers from a real world:
region 155 x 985 x 379 = 57,863,825 positions
one server tick stuck for 207 seconds
six entities involved
Flying mobs make it much worse, because they call Entity.move() every tick. In the case
above it was six blazes near a parked airship.
Two changes to the same method, one that fixes it and one that catches what the first one misses.
Narrowing happens at step 5. Only the union is enormous. box(lastPose) and
box(currentPose) are each about the size of the entity, because that is what they are.
So when the union would exceed the cap, Plumbline hands back the current-pose box on its
own instead. Collision against where the sub-level actually is keeps working. What is
given up is the sweep back to where it used to be, which is the part that stops an entity
tunnelling through a fast rotation.
The guard is the fallback at step 6. If a region still comes through oversized, it returns an empty iterable and the pass finds nothing. That does cost you collision for that pass, which is why it is second and not first.
Sable already has the equivalent of the guard. It compares the same volume against
125,000,000 and logs Enormous local sub-level collision bounds, quitting. The problem is
the number: the 57.9 million case above went straight through that ceiling and still froze
the server for three and a half minutes.
The narrowing is the part that is actually new. Skipping a pass is a decision Sable already knows how to make; keeping half of it is not.
Worth being straight about what is and is not known.
In 35,502 observed collision passes with sub-levels at rest, nothing came within an order of magnitude of 262,144. In a session where something was wrong, 120 out of 120 passes exceeded it, at 1.1 to 2.3 million positions each.
What has not been measured is how large a legitimate pass gets while a sub-level is
rotating fast, because the union grows with rotation by design. There is no sample of that.
If you find entities passing through a spinning sub-level, raise guardMaxVolume and open
an issue with the numbers, because that sample is the missing one.
Sable's 125,000,000 is the only figure anyone has published a justification for, and it is demonstrably too high.
Most reports of this amount to "airships lag my server", which nobody can act on.
/plumbline report writes markdown to the log: mod versions, the cap in force, how many
collision passes were seen, and every oversized region with a hit count.
The seen count matters as much as the skip count. Zero skips out of zero passes means the mixin never ran. Zero skips out of thirty thousand means it is working and your world is fine, and those deserve different replies.
/plumbline status prints one line. Both need permission level 2.
config/plumbline-common.toml
| option | default | meaning |
|---|---|---|
enabled |
true |
master switch, still counts passes when off |
guardMaxVolume |
262144 |
largest region a collision pass may walk |
narrowSweep |
true |
keep the current pose instead of dropping the pass |
logRegions |
true |
log each distinct oversized region once |
Turning narrowSweep off gives the 1.0.0 behaviour, where an oversized sweep meant no
sub-level collision at all for that pass.
The narrowing injection is optional. If a future Sable reshapes that method it will simply
not apply, and the mod runs on the guard alone rather than refusing to load. /plumbline report prints how many sweeps it saw, so nought means it never attached.
Minecraft 1.21.1, NeoForge 21.1+, Sable 2.0+, Java 21. Singleplayer and dedicated servers.
build.sh compiles with plain javac against the jars in a normal Minecraft install. There
is no Gradle setup because an untested NeoGradle config would be worse than none. If you
want to add one, that is a very welcome pull request.
MC_LIBS=/path/to/launcher/libraries SABLE_JAR=/path/to/sable.jar ./build.sh
Earlier versions also shipped a "healer" that periodically checked each sub-level's bounding box and asked Sable to recompute any that reached outside the world height limits. It was removed in 1.1.0 because it could not work.
sub.boundingBox() is the sub-level's box in overworld coordinates, which is small and
correct. The region that explodes is built from the entity's box in sub-level local
coordinates, and the two never meet. The healer ran for a full session against a world
where the guard was skipping 120 out of 120 passes, and correctly reported nothing wrong,
because nothing was wrong with the thing it was looking at.
Sable is by ryanhcode and does all the actual work. Plumbline is unaffiliated and not endorsed by anyone involved with it.
Related upstream issues: #857, #338 and #1098.
If you want to actually manage sub-levels, listing and freezing and archiving and deleting them, use Shtreimel or Sable CleanUp. Plumbline only does the one thing.
MIT.