Skip to content

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

Open

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.29 → r.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.

Activity

  1. Sacfiritan commented on Jul 23, 2026

    @Sacfiritan
    Author

    Follow-up: restoring the chunk-map registration is not enough

    I rebuilt the chunk-map records for the whole assembly and confirmed they persist. Current state of r.-1.27.slvlr:

    chunk [-21,887] → [4]
    chunk [-20,888] → [2]
    chunk [-21,889] → [3, 5]      # 3 = Hellicarrier
    

    Every part is registered in the chunk matching its stored world position, and the records survive restarts.

    I then force-loaded all the relevant chunks to rule out chunk loading entirely:

    /forceload add -336 14192 -320 14232
    → Marked 6 chunks in Overworld from [-21, 887] to [-20, 889] to be force loaded
    → 6 force loaded chunks were found in Overworld at: [-21, 887], [-20, 887], [-21, 888],
      [-20, 888], [-21, 889], [-20, 889]
    

    Result: the assembly still does not instantiate, and — notably — Sable logs nothing at all. No Sub-level dependency does not exist, no warning, no exception. An unrelated sub-level ~70 blocks away (-403, 63, 14252) loads normally in the same area at the same time.

    So with:

    • block data intact and parsing cleanly,
    • all four loading_dependencies targets present in storage,
    • chunk-map registration correct and persistent,
    • the owning chunks force-loaded and confirmed loaded,

    …the group is still never brought in, silently. That seems to point at the dependency-resolution step bailing out before it ever attempts (or logs) anything, rather than at chunk/storage state.

    Happy to run any instrumented build or dump additional state — this is reproducible on demand here.

  2. Yelf42 commented on Aug 15, 2026

    @Yelf42

    I think I've also run into this bug.

    I was trying to help a player get their ship back, so I teleported it to their base (which was unloaded at time of teleporting)
    However, after teleporting, the sub-level is no longer loaded, and only appears in /sable storage.
    The listed coordinates are the teleport destination

    The ship consisted of at least 3 connected sub-levels

  3. Hygolon commented on Aug 26, 2026

    @Hygolon

    I think i also had this issue, after removing an attached sublevel of my airship.

    The moment the sublevel connection (swivel bearing) got destroyed my (single-player) world crashed and when i loaded back in the ship was gone.
    "/sable storage find_all_sublevels" shows that its still at the coordinates where it was, interestingly the sublevel i removed is also still displayed there.
    Reading out the slvls file also shows the ship to be complete.
    I couldn't find any method to recover this stored sublevel since the commands all rely on it being loaded.

    Environment is:
    sable-neoforge-1.21.1-2.0.5.jar
    create-aeronautics-bundled-1.21.1-1.3.1.jar
    Minecraft 1.21.1

    The Log shows this error message:
    [26Aug2026 21:55:34.581] [Server thread/ERROR][dev.ryanhcode.sable.Sable/]: Recovering zeroed out bounds for sub-level HalfLoadedSublevel[uuid=13789f27-65a4-49e0-9eb3-f3033cd78cb0, bounds=dev.ryanhcode.sable.companion.math.BoundingBox3d@2cbcc2d8, pose=Pose3d{position=(2.672,04 135,979 -2.352,226), orientation=(-0,027 -0,678 -0,018 -0,734), rotationPoint=(20.481.032,5 128,628 20.485.128,5), scale=(1 1 1)}, relations=[d9632ea5-47fa-4767-9540-3ad060ee7794, a5baaf01-d6fc-4db4-9093-96c60c8cbd0d]] loaded at local->[storageIndex=0, subLevelIndex=1] in chunk [165, -152]
    and:
    [26Aug2026 21:55:34.581] [Server thread/ERROR][dev.ryanhcode.sable.Sable/]: Sub-level dependency does not exist in chunk. Something has gone terribly wrong.

    The suggested fixes would help greatly or maybe just a way to remove dependencies of sublevels so they can at least be loaded seperatly and assembled again after.

  4. JamesXNelson commented on Aug 30, 2026

    @JamesXNelson

    Additional Sable 2.0.5 case: exact storage failure after split/remove crashes; offline repair recovered the intact castle

    I have a damaged save that matches this issue very closely and also appears to connect it to #1376 (Sub-level assembly attempted inside plot of already removed sub-level).

    Environment

    • Minecraft 1.21.1
    • NeoForge 21.1.249
    • Sable 2.0.5
    • Sable Companion 1.6.0
    • Create 6.0.10
    • Create Aeronautics 1.3.1
    • Create Gadgets & Gizmos 1.1.3
    • Create Propulsion: Simulated 1.1.5
    • Waystones 21.1.41
    • WaystonesSable 1.0.7
    • Single-player integrated server

    Structure setup

    The important surviving object is a large flying castle, UUID:

    845625ba-98c8-461a-93b5-da56312c9109

    Separately, my kid assembled a random vanilla village hut into its own Sable sublevel at the village, then later moved/placed that already-assembled hut sublevel inside the castle.

    The hut was not assembled from blocks while inside the castle.

    Important chronology / pre-existing-state caveat

    I cannot honestly claim the later Waystone/hut event created all of the bad state from scratch.

    The earliest retained Aug 30 log already contains:

    [02:49:56.464] [Sable/ERROR]:
    Received a sub-level movement packet for a non-existent sub-level
    

    And by 03:16, Sable repeatedly cannot restore the eventual castle UUID from its recorded pointer:

    [03:16:12.713] [Sable/ERROR]:
    Attempted to snatch sub-level with ID
    global->[chunkPos=[-41, 11], storageIndex=0, subLevelIndex=0]
    stored at 845625ba-98c8-461a-93b5-da56312c9109,
    but the requested sub-level wasn't present in the holding chunk
    

    I had a crash the night before from a separate Immersive-Aeronautics/portal experiment the previous night which crashed the world. That mod was removed afterward and is not present in the retained 02:48+ mod lists.

    That earlier portal crash is a plausible source of pre-existing Sable damage, but I do not currently have the earlier log needed to prove it. I am including this specifically so the later Waystone interaction is not presented as a clean-room reproduction.

    Later user-visible trigger

    We remember breaking/deleting a Waystone immediately before the crash which caused the flying castle to disappear.

    Again, I would treat that as a trigger candidate, not proven root cause. Minecraft does not log the ordinary block break, and the later crash stack contains no Waystones frame.

    Runtime crash corresponding to #1376

    The damaged world subsequently crashes twice with:

    java.lang.RuntimeException: Sub-level assembly attempted inside plot of already removed sub-level
        at dev.ryanhcode.sable.api.SubLevelAssemblyHelper.assembleBlocks(SubLevelAssemblyHelper.java:85)
        at dev.ryanhcode.sable.sublevel.plot.heat.SubLevelHeatMapManager.split(SubLevelHeatMapManager.java:242)
        at dev.ryanhcode.sable.sublevel.plot.heat.SubLevelHeatMapManager.step(SubLevelHeatMapManager.java:118)
        at dev.ryanhcode.sable.sublevel.plot.heat.SubLevelHeatMapManager.tick(SubLevelHeatMapManager.java:85)
    

    Those crashes occurred at 09:34:21 and 10:03:05.

    Restart produces the exact storage-side failure

    On the following restart Sable logs:

    [10:04:17.189] [Sable/ERROR]:
    Couldn't find sub-level at index 0 in storage file for chunk [9, 65]
    
    [10:04:17.189] [Sable/ERROR]:
    Due to a failed storage sub-level data load, we can't add a holding sub-level
    for pointer local->[storageIndex=0, subLevelIndex=0].
    This will cause issues later down the line.
    
    [10:04:17.191] [Sable/ERROR]:
    Sub-level dependency does not exist in chunk. Something has gone terribly wrong.
    

    In-game:

    • /sable storage find_all_sub_levels could still enumerate the castle and its bounds/position;
    • /sable teleport, /sable info, @n, etc. could not target it because it was not instantiated;
    • force-loading/visiting its stored world position did not restore it.

    Offline inspection matches those errors exactly

    I investigated the Sable storage collaboratively with ChatGPT (GPT-5.6 Sol), which decoded the .slvlr / .slvls data; I then verified the repair in-game.

    The damaged state contained:

    • a clean, parseable castle .slvls payload with the castle blocks intact;
    • holding-chunk pointer list [0, 1];
    • pointer/slot 1 = intact castle;
    • pointer/slot 0 = empty;
    • castle loading_dependencies referencing 89ff0293-8292-4e32-8b0c-dbcd19584fad;
    • that dependency UUID still existed elsewhere and decoded as the small village-hut-like sublevel;
    • mutual loading-dependency state between those two objects.

    A minimal repair that:

    1. removed the dead pointer,
    2. removed the stale/missing dependency from the castle, and
    3. zeroed the castle's saved linear velocity

    made the castle instantiate normally again without changing its block payload.

    This is why a storage-side recovery facility would have been extremely valuable: the player's build was not destroyed; Sable simply had no normal command path that could reach the intact stored object.

    Suggested recovery capability

    Something like:

    /sable storage repair

    would be ideal, preferably dry-run/report-only by default.

    Useful checks could include:

    • .slvlr pointers whose referenced .slvls slot is empty;
    • stored sublevel UUIDs whose loading_dependencies are missing;
    • circular dependency groups that cannot bootstrap;
    • stored sublevels that are not currently live;
    • missing/incorrect holding-chunk registrations when the correct target is unambiguous;
    • suspicious/extreme saved velocities.

    Possible explicit repair operations could then:

    • remove only provably dead pointers;
    • rebuild unambiguous holding-chunk registrations;
    • remove a dependency only when its referenced payload is definitely missing/invalid, or expose a manual recovery command for that operation;
    • zero/clamp velocity as an explicit recovery option;
    • allow storage-addressable info / teleport by UUID without requiring the sublevel to instantiate first.

    I would strongly favor an idempotent dry-run + explicit apply workflow because these files can contain a player's largest build.

    Relation to #1376

    This save provides a concrete bridge:

    split/remove inconsistency → server crash → dead pointer + stale dependency persisted → surviving sublevel becomes storage-only

    So I suspect #1376 and #1387 may share a lifecycle/invariant problem even if they ultimately need separate fixes.

    Evidence

    I have attached one ZIP containing:

    • retained logs 2026-08-30-1.log.gz through 2026-08-30-6.log.gz;
    • the 09:34 and 10:03 server crash reports;
    • a concise timeline and offline-storage summary.

    sable-issue-evidence-bundle.zip

    I can also provide the damaged and successfully repaired Sable sublevels/ directories if useful.

    I have a regression-test plan prepared for a future development session, including:

    • a pure-Sable split/remove race test;
    • unload/reload after child removal;
    • pointer/dependency consistency checks;
    • and storage-repair dry-run/idempotence coverage.
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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions