Skip to content

Fix EitherHolder item data components (damage_type, chicken/variant, zombie_nautilus/variant) for 1.21.11 - #1195

Open
atiweb wants to merge 4 commits into
PrismarineJS:masterfrom
atiweb:fix/1.21.11-eitherholder-components
Open

Fix EitherHolder item data components (damage_type, chicken/variant, zombie_nautilus/variant) for 1.21.11#1195
atiweb wants to merge 4 commits into
PrismarineJS:masterfrom
atiweb:fix/1.21.11-eitherholder-components

Conversation

@atiweb

@atiweb atiweb commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Problem

Three 1.21.11 item data components are encoded on the wire with EitherHolder.streamCodec (net.minecraft.world.item.EitherHolder):

  • minecraft:damage_typeEitherHolder<DamageType>
  • minecraft:chicken/variantEitherHolder<ChickenVariant>
  • minecraft:zombie_nautilus/variantEitherHolder<ZombieNautilusVariant>

EitherHolder.streamCodec is ByteBufCodecs.either(holderCodec, ResourceKey.streamCodec): a boolean, then either the holder or a resource-key string. For all three, the holder codec is ByteBufCodecs.holderRegistry(...), which writes a plain VarInt registry id (reference only — no inline value and no id + 1).

They were defined as registryEntryHolder, which reads the holder as VarInt(n) and returns n - 1, and treats n == 0 as an inline value. That is off by one for every referenced holder, and desyncs on holder id 0.

Evidence (real 1.21.11 server)

A stone_sword[minecraft:damage_type="minecraft:player_attack"] is sent in container_set_content as the component bytes 08 01 22:

  • 08 = SlotComponentType damage_type
  • 01 = hasHolder = true
  • 22 = holder id 34

The server's damage_type registry has minecraft:player_attack at id 34. The old registryEntryHolder decoded it as 34 - 1 = 33 = minecraft:outside_border (wrong). The fixed structure decodes damageType = 34 = minecraft:player_attack.

Fix

Define the holder branch as a plain varint (matching holderRegistry), keeping the leading hasHolder boolean and the false → string (resource key) branch:

hasHolder: bool
<value>: hasHolder ?
   if true: varint
   if false: string

Applied to damage_type, chicken/variant and zombie_nautilus/variant in data/pc/latest/proto.yml (used by 1.21.11) and rebuilt data/pc/1.21.11/protocol.json. The now-unused DamageTypeData type is removed.

The other variant components are intentionally left unchanged: pig/cow/frog/horse/variant use holderRegistry (already a plain varint); wolf/cat/painting/variant use their own variant stream codecs and are not EitherHolder.

atiweb and others added 2 commits June 15, 2026 13:39
damage_type, chicken/variant and zombie_nautilus/variant are encoded on the wire
with EitherHolder.streamCodec (net.minecraft.world.item.EitherHolder): a boolean,
then either a plain holder id (ByteBufCodecs.holderRegistry, a VarInt with no inline
case and no +1) or a resource-key string. They were defined as registryEntryHolder,
which reads the holder as VarInt(n) and returns n-1, treating n==0 as an inline value
-- off by one for every referenced holder, and a desync for holder id 0.

Verified against a real 1.21.11 server: stone_sword[minecraft:damage_type=
"minecraft:player_attack"] is sent as 08 01 22 (component 8, hasHolder=true,
holder 0x22=34). 34 is minecraft:player_attack; the old registryEntryHolder decoded
it as 33 (minecraft:outside_border). The other variant components (pig/cow/frog/
horse use holderRegistry; wolf/cat/painting use their own variant codecs) are not
EitherHolder and are left unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
chicken/variant (item data component) has been an EitherHolder since chicken
variants were added, not only in 1.21.11. Verified against each official server
jar: Chicken references EitherHolder in 1.21.6, 1.21.8 and 1.21.9 (1.21.10 shares
1.21.9 via dataPaths). Apply the same registryEntryHolder -> hasHolder+switch
transform there so all versions match.

painting/variant is a plain Holder (not EitherHolder), so it is correctly left as
registryEntryHolder.
@atiweb

atiweb commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

Extended this to 1.21.6, 1.21.8 and 1.21.9chicken/variant has been an EitherHolder item data component since chicken variants were added, not only in 1.21.11. Verified against each version's official server jar (Chicken references EitherHolder in all of them; 1.21.10 shares 1.21.9 via dataPaths.json), and the chicken/variant definition is now identical across 1.21.6–1.21.11.

While at it I checked the other registryEntryHolder variant/holder components against the jars so nothing is missed:

  • painting/variant and the entity-metadata chicken_variant are plain Holders (the only EitherHolder use in Chicken is the data component) — correctly left as registryEntryHolder.
  • instrument / jukebox_playable / provides_trim_material are already modeled with the hasHolder bool + switch.

So this should cover every EitherHolder case across 1.21.6–1.21.11.

Chicken variants were added in 1.21.5, so chicken/variant is already an EitherHolder
there (verified: Chicken references EitherHolder in the 1.21.5 server jar). The earlier
extension covered 1.21.6/8/9 but missed 1.21.5. Same registryEntryHolder -> hasHolder
transform; chicken/variant is now identical across 1.21.5-1.21.11.
@extremeheat

Copy link
Copy Markdown
Member

Conflicts, please merge in latest master and update PR after npm run build

Master moved 1.21.11 to its own data/pc/1.21.11/proto.yml and repointed pc/latest at
26.1, which conflicted in pc/latest/proto.yml. Resolved by moving the fix to 1.21.11's
own yaml and keeping master's definitions for 26.1, because the three components are
no longer EitherHolder there:

  1.21.11: DataComponentType<EitherHolder<DamageType>> DAMAGE_TYPE = ... EitherHolder.streamCodec(...)
  26.1:    DataComponentType<Holder<DamageType>>       DAMAGE_TYPE = ... DamageType.STREAM_CODEC

net.minecraft.world.item.EitherHolder is gone from the 26.1 jar entirely (0 references,
class removed), and chicken/variant and zombie_nautilus/variant were likewise reverted
to plain holders, so pc/latest correctly keeps registryEntryHolder.
@extremeheat

Copy link
Copy Markdown
Member

This doesn't apply to 26.1 ?

@atiweb

atiweb commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

Merged master and reran npm run build — all green now.

One note on how I resolved the conflict in pc/latest/proto.yml: master gave 1.21.11 its own proto.yml and repointed pc/latest at 26.1, and these components are no longer EitherHolder in 26.1:

// 1.21.11
DataComponentType<EitherHolder<DamageType>> DAMAGE_TYPE = register("damage_type", b -> b.persistent(EitherHolder.codec(...)).networkSynchronized(EitherHolder.streamCodec(...)));
// 26.1
DataComponentType<Holder<DamageType>> DAMAGE_TYPE = register("damage_type", b -> b.persistent(DamageType.CODEC).networkSynchronized(DamageType.STREAM_CODEC));

net.minecraft.world.item.EitherHolder is gone from the 26.1 jar entirely (class removed, 0 references vs 14 in 1.21.11), and chicken/variant and zombie_nautilus/variant were reverted to plain holders too. So I kept master's registryEntryHolder definitions for pc/latest and applied the fix only to data/pc/1.21.11/proto.yml. The PR now only changes 1.21.5–1.21.11.

Unrelated heads-up while I was in there: 26.1's damage_type still carries the hasHolder: bool from the EitherHolder era, which is now a spurious leading byte since it's a plain holder. Happy to send that as a separate PR if useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants