Skip to content

Fix labels not being saved in singleplayer - #1784

Merged
rubensworks merged 2 commits into
master-26-ltsfrom
fix/labels-singleplayer
Nov 9, 2026
Merged

rubensworks merged 2 commits into
master-26-ltsfrom
fix/labels-singleplayer

Conversation

@rubensworks

Copy link
Copy Markdown
Member

Labels set with the Labeller were never saved in singleplayer on 26.1. They disappeared when leaving the world, and #label references in expressions stopped resolving. Found while testing #1783.

Cause

  • Wrong side check: LabelsWorldStorage.Access#get() returned a client-only copy whenever MinecraftHelpers#isClientSide() was true. That checks the physical side (FMLEnvironment.getDist() == Dist.CLIENT), so it is also true for the integrated server. In singleplayer, the server put labels into the client copy, and the saved data stayed empty. labels.dat kept an empty list. Dedicated servers were not affected.
  • Login sync: every LabelsWorldStorage registered itself on the event bus and sent its labels to a player on login. That included the client copy and the storages of every world opened earlier in the session. With only the first part fixed, the server's list still happened to arrive last, but only because of registration order.

Fix

  • Getter: on the physical client, get() returns the client copy unless the current thread is the integrated server's (ServerLifecycleHooks.getCurrentServer().isSameThread()). I didn't use isClientSideThread(), because that is false on the render thread while no level is loaded, and the getter would then fall through to the server storage with a null server.
  • Login sync: the handler moved to the single Access, which sends the labels of the server's storage. Storage instances no longer register on the event bus.

Is this on 1.21?

No. On master-1.21-lts, LabelsWorldStorage is a single WorldStorage instance shared by client and server. It is saved through @NBTPersist on server stop and world save, and its data holder is always dirty. The client-only copy was introduced in the 26.1 port, so this PR targets master-26-lts.

Testing

  • ./gradlew build: all 1057 unit tests pass.
  • ./gradlew runGameTestServer: all 1096 game tests pass. The game test server is a dedicated server, so it never reached the broken branch.
  • Singleplayer, in a 26.1 dev client with clientdevbridge, in a fresh world:
    1. Wrote an expression card holding 5 (id 0) and labelled it five with the Labeller.
    2. Left the world. labels.dat now holds 0 -> five; before the fix, it stayed empty in the same scenario.
    3. Restarted the whole client, so no in-memory copy could survive, and loaded the world. The card is still named five.
    4. #five + 1 resolves in the Logic Programmer's expression panel, typed Integer.

Generated by Claude Code

The integrated server used the client's copy of the labels, as the client
copy was chosen for the physical client side instead of its thread, so labels
never reached the saved data and were gone after leaving the world. Only the
client thread now uses its copy.

Labels are sent on login from the storage access instead of from every
storage instance, as those were registered for each opened world, and the
client copy, so stale labels were sent as well.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MHtucHo4Qy2pTfBMgeE54y
@coveralls

coveralls commented Nov 8, 2026 •

Copy link
Copy Markdown

Coverage Status

coverage: 58.6% (+0.02%) from 58.582% — fix/labels-singleplayer into master-26-lts

@rubensworks
rubensworks merged commit 99e4c56 into master-26-lts Nov 9, 2026
7 checks passed
@rubensworks
rubensworks deleted the fix/labels-singleplayer branch November 9, 2026 15:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants