Ambiguity & Preferences
The uncommon-but-real case where the same block type is a valid core or activation symbol for more than one registered definition. Priority order (see Core Concepts § Registration and lookup) resolves this globally — but it has no notion of which structure a player is building at a specific position. This system lets a per-position override break the tie.
Packages: net.astronomy.multilib.core.registry (resolver, tracker), net.astronomy.multilib.core.preference (dev wrench), net.astronomy.multilib.api (public API).
When does ambiguity happen?
Every feature that acts on an unformed block — the ghost overlay, auto-place, wrench formation — must resolve one definition out of the possibly-many that share the clicked block. Most of the time there's only one candidate and there's nothing to decide. Ambiguity arises only when 2+ eligible definitions list that exact block as their core (or activation) symbol — e.g. two reactor tiers whose controller is the same block.
Resolution order
MultiblockAmbiguityResolver.resolve(level, pos, matcher) is the single shared answer to "which definition should this block be treated as":
- Take
MultiblockRegistry.getCandidatesFor(block)(already priority-sorted). - Filter to whatever the caller's predicate accepts (e.g. "core symbol only" for auto-place, "core or activation" for the ghost overlay).
- 0 or 1 candidate → nothing to disambiguate; priority order decides, exactly as before.
- 2+ candidates → consult the preference tracker. If a stored preference is still among the eligible candidates, it wins. Otherwise (unset, or stale because the block/definition changed) fall through to priority order — the first, highest-priority candidate.
A preference is only ever an override on top of priority order, never the sole source of truth. A stale binding is silently ignored, so this is never worse than plain priority resolution — only ever more precise.
MultiblockAmbiguityResolver.candidatesAt(level, pos, matcher) returns the full priority-sorted candidate list without touching the tracker — for tools (like the preference wrench) that need to show a player everything ambiguous there, not just the winner. It takes a plain Level, so client-side picker UI and server-side validation run the identical check and can't drift apart.
Public API
Three thin, validated passthroughs on MultiLib — the intended integration surface. MultiLib ships no forced UI; a mod can drive these from its own tool, GUI, or command.
setPreferredDefinition(ServerLevel level, BlockPos pos, ResourceLocation definitionId)
public static boolean setPreferredDefinition(ServerLevel level, BlockPos pos, ResourceLocation definitionId)
Binds pos to definitionId: when the block there is a valid core/activation symbol for more than one definition, definitionId wins at that position instead of whatever priority order would pick.
Validates immediately — definitionId must presently be among the candidates the resolver would consider for the block currently at pos (either core or activation). If not, it's a no-op returning false. Returns true when the binding is accepted and stored.
A binding that goes stale later (block changes, definition removed) is not cleaned up eagerly; resolve(...) falls back to priority order on its own next time, as if nothing were set.
getPreferredDefinition(ServerLevel level, BlockPos pos)
public static Optional<ResourceLocation> getPreferredDefinition(ServerLevel level, BlockPos pos)
The definition id currently bound to pos, or empty if never set / already cleared.
clearPreferredDefinition(ServerLevel level, BlockPos pos)
public static void clearPreferredDefinition(ServerLevel level, BlockPos pos)
Removes any binding for pos. No-op if none was set.
Preference persistence
MultiblockPreferenceTracker is a per-ServerLevel SavedData named multilib_preferences, keyed by BlockPos. It's real per-world gameplay state — persisted across restarts, just like WorldMultiblockTracker. Corrupted/foreign entries are skipped on load rather than failing the whole file. You normally never touch it directly; go through the MultiLib methods above.
Preference wrench (dev mode)
MultiLib's own optional, dev-facing tool built on the exact same API — the Multiblock Preference Wrench. Like the rest of the Dev Tools, it's registered only when devMode is true, appearing in the Tools & Utilities creative tab; a production build carries no trace of it.
Right-clicking a core/activation block with it:
- opens a picker listing every definition that block is ambiguously a candidate for (one button per definition, using the same display-name convention the recipe viewers use, plus Cancel);
- picking one calls
setPreferredDefinition(...)for that exact position and reports the outcome as a chat line; - clicking a block that's a core/activation symbol for exactly one definition (no variants to choose between) reports that as a chat line instead of opening a picker;
- clicking a block that isn't a core/activation symbol for any registered definition reports that as a chat line too.
It's an authoring convenience for testing multi-candidate setups; ship your own tool/command/GUI (built on the public API) for anything player-facing.
See also
- MultiLib — the three preference methods in the full API listing
- Core Concepts § Registration and lookup — how priority order works
- Dev Tools — the other
devMode-gated authoring aids - MultiblockBuilder § priority — the tie-break priority set per definition