Skip to content

Mutually-dependent sub-levels can never load again once the whole group is unloaded (jointed assembly permanently lost) #1387

Description

@Sacfiritan

Summary

A jointed multi-part sub-level assembly on our server became permanently unloadable: its block data is intact in storage, /sable storage find_all_sub_levels lists it, but it never instantiates in the world and cannot be targeted by any command. The server logs Sub-level dependency does not exist in chunk. Something has gone terribly wrong.

The cause appears to be that mutually-dependent sub-levels cannot bootstrap: each part lists the others in loading_dependencies, dependency resolution only looks at sub-levels already present in loaded chunks, so if the whole group is unloaded at once, none of them can ever load again.

Environment

  • Sable 2.0.3 (sable-neoforge-1.21.1-2.0.3.jar), Sable Companion
  • Create 6.0.10, Create: Aeronautics 1.3.0 (bundled)
  • Minecraft 1.21.1, NeoForge 21.1.233, dedicated server, Java 21

Symptom

A named airship (Hellicarrier, ~3200 blocks, 68 blocks across, 32 chunk sections) is invisible in the world. Flying a player to its stored coordinates shows empty air. Neighbouring sub-levels in the same region load normally at the same moment, so it is not a chunk-loading problem.

[Server thread/ERROR] [dev.ryanhcode.sable.Sable/]: Sub-level dependency does not exist in chunk. Something has gone terribly wrong.

(logged once per part of the assembly)

What the data shows

The assembly is 4 sub-levels stored in r.-1.27.0.slvls, slots 2/3/4/5. Each one lists the other three in loading_dependencies:

slot uuid depends on
2 9aa0dd6c 4c59497b, dba13ad9, 46b9180a
3 (Hellicarrier) 46b9180a 4c59497b, dba13ad9, 9aa0dd6c
4 dba13ad9 4c59497b, 46b9180a, 9aa0dd6c
5 4c59497b dba13ad9, 46b9180a, 9aa0dd6c

All four dependency targets exist in storage — verified by decoding every sub-level UUID on the server (248 of them). Nothing is missing; the block data of all four parts is intact and parses cleanly.

Additionally, none of the four had a record in the chunk map (.slvlr) pointing at their storage slot, while healthy sub-levels in the same region did. The four parts sit in four different chunks ([-20,888], [-21,889] ×2, [-21,887]).

What we tried

  1. Rebuilt the chunk-map records — wrote pointers entries in .slvlr for the chunk containing each part's world position (matching the convention used by working sub-levels: chunk = floor(pos/16), table slot = (cx & 31) + (cz & 31) * 32). Verified the records survived a restart and that zero sub-levels were left unreferenced. The assembly still did not load.
  2. Player presence at the coordinates — other sub-levels ~70 blocks away loaded fine in the same tick; these four did not.
  3. /sable teleport, /sable info with @e[distance=…], @n — all return No sub-levels found, since the selector only sees instantiated sub-levels.

Likely cause

Dependency resolution looks for the dependencies among sub-levels already loaded in chunks, rather than resolving them from storage. For a group whose members only depend on each other, there is no entry point: loading part A requires B, C, D to already be present, and vice-versa. As long as the whole group is unloaded simultaneously, it can never come back.

Possible contributing factor: runaway velocities

While investigating, we found sub-levels persisted with extreme velocities in their NBT (linear_velocity), the worst at ~46 000 blocks/s, several in the 1 000–9 000 range, and one with angular_velocity ≈ 2 085 rad/s. These travel kilometres between autosaves, crossing region files as they go (we watched one migrate r.8.29r.10.27 within minutes). That churn seems a plausible way for a group's chunk-map registration to end up lost or desynchronised — which is exactly the state we found this assembly in.

Zeroing those velocity fields offline stopped all of them dead (verified: zero movement across repeated samples), with no other side effects.

Impact

A player's largest build is intact on disk but permanently unreachable, with no in-game way to recover it — remove, teleport and info all require the sub-level to be loaded first.

Suggested fixes

  • Resolve loading_dependencies from storage rather than only from loaded chunks, or load a dependency group atomically.
  • Clamp or sanity-check persisted velocities on save/load — a 46 000 blocks/s sub-level is never legitimate and causes the region churn above.
  • A recovery path for orphaned sub-levels (e.g. /sable storage repair, or letting teleport/remove target entries by UUID from storage, since find_all_sub_levels can already list them).

Notes

Happy to supply the region files, the full dependency dump, or test a patch — this reproduces reliably here.
Related: #1386 (empty chunk-map records never pruned), found on the same server while debugging autosave stalls.

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