Skip to content

Fix DeferredHolderCommon delegates not being references on Forge - #245

Merged
rubensworks merged 1 commit into
master-26-ltsfrom
claude/epic-noether-3ue5cn
Sep 21, 2026
Merged

rubensworks merged 1 commit into
master-26-ltsfrom
claude/epic-noether-3ue5cn

Conversation

@rubensworks

Copy link
Copy Markdown
Member

Background

Two earlier fixes made DeferredHolderCommon behave for other mods and for serialization:

  • 9dacc1a "Fix DeferredHolderCommon not handling delegates correctly": added IHolderCommon#getDelegate plus the MixinRegistry/MixinHolder mixins, so that Registry#safeCastToReference can unwrap a DeferredHolderCommon into the Holder.Reference it wraps. NeoForge does this natively through IHolderExtension#getDelegate; Fabric and Forge get it from the mixins.
  • e199d51 "Fix Mixin breaking other DeferredHolders": removed the error branch from that mixin, so holders of other mods fall through to the loader's own implementation.

Both were applied to all three loaders at the time, so there is nothing left to port as such. The delegate fix does not take effect on Forge though, because of an older Forge-only addition:

  • f3a4fe8 "Make DeferredHolderCommon work with IForgeRegistry" added DeferredHolderCommon.BIND_OVERRIDE, which Forge installs to resolve entries from IForgeRegistrys. It is consulted before the regular registry and binds to Holder.direct(value).

Since a Holder.Direct is not a Holder.Reference, the delegate never satisfies safeCastToReference, and Forge keeps hitting exactly the failure the delegate fix was meant to remove. Forge wraps many vanilla registries (particle_type, menu, block, item, block_entity_type, ...), so the override is hit for most entries. Reproduced with the new game tests on Forge before this change:

Expected a Holder.Reference delegate, but got Direct{org.cyclops.cyclopscore.client.particle.ParticleBlurConfig$1@629059a9}
Expected a successful encoding, but got DataResult.Error['Unregistered holder in ResourceKey[minecraft:root / minecraft:particle_type]: DeferredHolderCommon{ResourceKey[minecraft:particle_type / cyclopscore:blur]}']

Changes

  • DeferredHolderCommon#bind consults the regular registry first and only falls back to BIND_OVERRIDE for entries that are not in a regular registry. This matches NeoForge's upstream DeferredHolder, which binds through the registry only, and yields the canonical Holder.Reference.
  • The Forge BIND_OVERRIDE prefers ForgeRegistry#getDelegate, which returns a Holder.Reference, and only falls back to Holder.direct for registries without a wrapper (those are not in the root registry, so they cannot be serialized through a registry codec anyway).
  • Added DeferredHolderCommonTest game tests, registered on all three loaders, asserting that the delegate is a reference and that the holder encodes through a registry holder codec.

Testing

  • ./gradlew build passes.
  • ./gradlew runGameTestServer passes on Forge, NeoForge and Fabric. Before the change, the two new tests failed on Forge and passed on NeoForge and Fabric.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WyxzX1DGvXj5nV4zew3V8u


Generated by Claude Code

Registry#safeCastToReference only accepts Holder.Reference, which is why
DeferredHolderCommon exposes its bound holder as delegate. On Forge, the
IForgeRegistry bind override ran before the regular registry lookup and
bound to Holder.direct, so the delegate was never a reference and
serialization kept failing there.

The regular registry is now consulted first, and the Forge override
prefers the registry's own delegate. Covered by game tests on all
loaders.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WyxzX1DGvXj5nV4zew3V8u
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
3.3% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@coveralls

Copy link
Copy Markdown

Coverage Status

coverage: 31.415% (+0.2%) from 31.187% — claude/epic-noether-3ue5cn into master-26-lts

@rubensworks
rubensworks merged commit 92d973a into master-26-lts Sep 21, 2026
6 of 7 checks passed
@rubensworks
rubensworks deleted the claude/epic-noether-3ue5cn branch September 21, 2026 14:44
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.

2 participants