From 00b73dfdd686292fdd95da23e715874c0f0062e3 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Thu, 5 Nov 2026 12:00:44 +0100 Subject: [PATCH 001/141] chore: continue compatibility maintenance From bd9c7aef55a8e2484a0e7e51db379352f363849d Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Thu, 5 Nov 2026 12:15:19 +0100 Subject: [PATCH 002/141] docs(camera): record dynamic native FOV controls Record native and translated underwater projection, posture, and packet-to-frame boost expiry controls. Exclude the direct trial that died during fixture preparation. Keep death-controller behavior and exact native expiry timing open. --- .../0023-render-core-resolved-camera-movement.pr.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/patches/viafabricplus-bedrock/upstreamable/0023-render-core-resolved-camera-movement.pr.md b/patches/viafabricplus-bedrock/upstreamable/0023-render-core-resolved-camera-movement.pr.md index 50e95a99..7edf0abb 100644 --- a/patches/viafabricplus-bedrock/upstreamable/0023-render-core-resolved-camera-movement.pr.md +++ b/patches/viafabricplus-bedrock/upstreamable/0023-render-core-resolved-camera-movement.pr.md @@ -28,8 +28,14 @@ Original Bedrock 1.26.60.5 instructions supply 25 player FOV cases covering move Matching stationary world markers on official 1.26.60.5 and rebuilt direct/ViaProxy clients cover slider values 60, 30, 110, and return to 60. The corrected projection matches native within pixel-spacing quantization. An add-on control with gameplay FOV disabled matches the native disabled control. A direct-route server command retains its specified 40-degree projection, and clear restores the corrected local projection. Complete protocol-2225 journals, installed JAR hashes, stationary position, eye height, and facing are verified. The extra proxy server-FOV command check did not run before its timed recorder ended. +Native high-Speed controls reach the 130-degree projection limit and return to baseline after expiry. Direct and ViaProxy observations retain the 2.42 modifier before the final projection clamp. The native expiry video records intermediate projections above the former Java modifier limit. + +A stationary, submerged fixture with a low ceiling compares native, direct, and ViaProxy swimming without movement input. All three reach the swimming pose and matching underwater projection. Dolphins widen that projection, which persists briefly after their removal and returns to baseline. Unobstructed marker pairs match within pixel-spacing quantization. Frames with obscured markers are excluded. + +Both translated journals contain six Dolphin Boost refreshes with 60-tick durations. Client-thread observations retain the multiplier through the final packet tick plus 60, then clear it on the next tick. The first direct water trial died during fixture preparation and remained on the death screen. It does not establish a swimming defect. The repeated direct trial completes alive with the expected pose, eye height, boost, and expiry. + The four-project build passes 3,515 tests with 329 skipped and no failures or errors, including the executable FOV comparison. Bundle and patch-health checks pass. -Timeline and selected native/replay controls cover supported commands. Dynamic swim expiry, fluid/death composition, portrait scaling, complete transition timing, aim assistance, collision, and visual composition remain partial. Static marker controls do not establish complete camera or pixel parity. +Timeline and selected native/replay controls cover supported commands. Exact native input-frame expiry, other fluid effects, death projection, portrait scaling, complete transition timing, aim assistance, collision, and visual composition remain partial. Original 1.26.60.5 world-FOV instructions confirm an ordinary death curve without Java's 20-tick cap. The default native death controller and its counter lifetime still need live comparison before changing that path. These controls do not establish complete camera or pixel parity. The [owning patch](0023-render-core-resolved-camera-movement.patch) retains implementation rationale and versioned evidence. From d5b786797876a7b7fb033e1457b6aa42665b711d Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Thu, 5 Nov 2026 15:50:23 +0100 Subject: [PATCH 003/141] fix(camera): reproduce the native default death view Use the Bedrock death orbit and projection while preserving explicit server camera ownership. Continue the native short counter after Java stops ticking the dead actor, and keep auxiliary cameras from consuming shared timelines. Compare original executable projection cases and native marker captures. Verify final direct and proxy builds, FOV preferences, camera clear, and respawn. Native death UI and broader timing and collision parity remain open. --- ...w-confirmed-blacklisted-server-joins.patch | 4 +- ...render-core-resolved-camera-movement.patch | 407 ++++++++++++++++-- ...render-core-resolved-camera-movement.pr.md | 8 +- ...ompleted-local-physics-to-viabedrock.patch | 6 +- ...e-fence-geometry-in-bedrock-sessions.patch | 4 +- ...thoritative-projectile-owner-changes.patch | 6 +- ...9-present-authored-native-containers.patch | 4 +- ...0029-share-native-ui-scene-rendering.patch | 8 +- ...orted-native-world-actor-definitions.patch | 4 +- ...1-edit-native-command-block-settings.patch | 6 +- ...2-reconcile-native-block-break-audio.patch | 6 +- ...-animate-native-moving-piston-blocks.patch | 6 +- .../0035-forward-bedrock-fluid-pickup.patch | 4 +- ...0038-predict-held-trapdoor-placement.patch | 4 +- ...d-use-on-bedrock-peaceful-difficulty.patch | 4 +- ...ply-native-command-stacks-to-dry-air.patch | 4 +- ...ve-native-occlusion-with-moreculling.patch | 4 +- ...polate-native-remote-player-movement.patch | 6 +- 18 files changed, 413 insertions(+), 82 deletions(-) diff --git a/patches/viafabricplus-bedrock/deferred/0001-allow-confirmed-blacklisted-server-joins.patch b/patches/viafabricplus-bedrock/deferred/0001-allow-confirmed-blacklisted-server-joins.patch index a95c9ad0..900ce1d3 100644 --- a/patches/viafabricplus-bedrock/deferred/0001-allow-confirmed-blacklisted-server-joins.patch +++ b/patches/viafabricplus-bedrock/deferred/0001-allow-confirmed-blacklisted-server-joins.patch @@ -1,4 +1,4 @@ -From 0a76d82100ed213fb903e3776f371b3e141d3216 Mon Sep 17 00:00:00 2001 +From 188d8659f6672a55c086f54dabe7b5a5d6a13bff Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 1 Nov 2026 14:06:07 +0100 Subject: [PATCH] feat(connection): allow confirmed blacklisted server joins @@ -247,7 +247,7 @@ index f345d7c..40e7ae9 100644 "command_block.viafabricplus.first_tick": "Execute on first tick", "base.viafabricplus.accept": "Accept", diff --git a/src/main/resources/viafabricplus-bedrock.mixins.json b/src/main/resources/viafabricplus-bedrock.mixins.json -index ddd097f..0f32d42 100644 +index 4e383fc..0f484fa 100644 --- a/src/main/resources/viafabricplus-bedrock.mixins.json +++ b/src/main/resources/viafabricplus-bedrock.mixins.json @@ -49,6 +49,8 @@ diff --git a/patches/viafabricplus-bedrock/upstreamable/0023-render-core-resolved-camera-movement.patch b/patches/viafabricplus-bedrock/upstreamable/0023-render-core-resolved-camera-movement.patch index 6eb767b5..3dd8539f 100644 --- a/patches/viafabricplus-bedrock/upstreamable/0023-render-core-resolved-camera-movement.patch +++ b/patches/viafabricplus-bedrock/upstreamable/0023-render-core-resolved-camera-movement.patch @@ -1,4 +1,4 @@ -From 4aa8acab6512334b2387eb3eef8f2f8136f2430c Mon Sep 17 00:00:00 2001 +From 626aa644277b87a80fe8c03671aa4a089eb88c50 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:35 +0200 Subject: [PATCH] feat(camera): render core-resolved server camera movement @@ -157,45 +157,73 @@ cases cover modifier math and six DolphinBoostSystem kernel cases confirm that swim boost uses marker presence, including zero duration. Consume the existing retained movement-effect timeline rather than a second timer. Keep Java's fractional FOV accessibility setting between the native boolean -endpoints. Fluid/death composition, portrait scaling and complete camera +endpoints. Other fluid effects, portrait scaling and complete camera scheduling still need native world comparisons. + +Apply the default Bedrock 1.26.60.5 death camera while the local view owns +camera activation. Its native profile uses an eight-block orbit with a +fixed 60-degree polar angle. Use the existing orbit and avoidance kernels, +render the detached player view, and omit Java's additional death roll. +Explicit server camera poses retain ownership, and clear resolves blends +through the current local death pose. + +Original world-FOV function 0x145309b40 replaces the slider-derived death +projection with a sine-table ease from 60 to 84 degrees over 120 ticks. +The initialized native lookup and original instructions establish its +float order. Disabled gameplay FOV retains the ordinary modifier history. +Keep Java's fractional accessibility setting between those endpoints. + +Advance shared camera timelines only from the main view. Auxiliary camera +instances must not consume transitions or advance the death counter. + +Native DeathTickingComponent updates a signed short. Seed the counter at +activation and continue on camera ticks after Java stops ticking its dead +local actor. Respawn, actor replacement and disconnect retire that state. +Two native death recordings at sliders 60 and 30 show the same settled +view. The profile-derived pose predicts both marker rectangles within one +pixel in nine native stills without fitting camera coordinates. Broader +activation timing, collision and non-default death contexts remain open. --- - .../bedrock/ViaFabricPlusBedrock.java | 44 +++++ - .../camera/BedrockCameraAimAssist.java | 70 +++++++ + .../bedrock/ViaFabricPlusBedrock.java | 44 ++++ + .../camera/BedrockCameraAimAssist.java | 70 ++++++ .../bedrock/camera/BedrockCameraFade.java | 40 ++++ - .../bedrock/camera/BedrockCameraFov.java | 68 +++++++ - .../bedrock/camera/BedrockCameraPosition.java | 183 ++++++++++++++++++ - .../bedrock/camera/BedrockCameraShake.java | 49 +++++ - .../bedrock/camera/BedrockCameraWorld.java | 95 +++++++++ - .../bedrock/camera/BedrockPlayerFov.java | 34 ++++ - .../camera/CameraAimAssistControlPayload.java | 22 +++ - .../camera/CameraAimAssistPayload.java | 22 +++ - .../bedrock/camera/CameraFadePayload.java | 22 +++ - .../bedrock/camera/CameraFollowPayload.java | 22 +++ - .../bedrock/camera/CameraFovPayload.java | 22 +++ - .../bedrock/camera/CameraPositionPayload.java | 22 +++ - .../bedrock/camera/CameraShakePayload.java | 26 +++ - .../bedrock/camera/CameraSplinePayload.java | 22 +++ - .../bedrock/camera/CameraTrackingPayload.java | 22 +++ - .../render/MixinBedrockCameraFade.java | 23 +++ - .../render/MixinBedrockCameraFov.java | 64 ++++++ + .../bedrock/camera/BedrockCameraFov.java | 68 ++++++ + .../bedrock/camera/BedrockCameraPosition.java | 224 ++++++++++++++++++ + .../bedrock/camera/BedrockCameraShake.java | 49 ++++ + .../bedrock/camera/BedrockCameraWorld.java | 95 ++++++++ + .../bedrock/camera/BedrockDeathCamera.java | 79 ++++++ + .../bedrock/camera/BedrockPlayerFov.java | 34 +++ + .../camera/CameraAimAssistControlPayload.java | 22 ++ + .../camera/CameraAimAssistPayload.java | 22 ++ + .../bedrock/camera/CameraFadePayload.java | 22 ++ + .../bedrock/camera/CameraFollowPayload.java | 22 ++ + .../bedrock/camera/CameraFovPayload.java | 22 ++ + .../bedrock/camera/CameraPositionPayload.java | 22 ++ + .../bedrock/camera/CameraShakePayload.java | 26 ++ + .../bedrock/camera/CameraSplinePayload.java | 22 ++ + .../bedrock/camera/CameraTrackingPayload.java | 22 ++ + .../render/MixinBedrockCameraFade.java | 23 ++ + .../render/MixinBedrockCameraFov.java | 85 +++++++ .../render/MixinBedrockCameraPerspective.java | 21 ++ - .../render/MixinBedrockCameraShake.java | 73 +++++++ - .../render/MixinBedrockFollowInput.java | 23 +++ + .../render/MixinBedrockCameraShake.java | 76 ++++++ + .../MixinBedrockDeathCameraEffects.java | 23 ++ + .../render/MixinBedrockFollowInput.java | 23 ++ .../render/MixinMinecraftHudVisibility.java | 6 + .../sound/MixinBedrockCameraListener.java | 30 +++ .../settings/impl/BedrockSettings.java | 4 + .../viafabricplus-bedrock/lang/en_us.json | 1 + - .../viafabricplus-bedrock.mixins.json | 8 +- - .../camera/BedrockCameraPositionTest.java | 68 +++++++ - .../bedrock/camera/BedrockPlayerFovTest.java | 85 ++++++++ - 29 files changed, 1190 insertions(+), 1 deletion(-) + .../viafabricplus-bedrock.mixins.json | 9 +- + .../camera/BedrockCameraPositionTest.java | 68 ++++++ + .../camera/BedrockDeathCameraTest.java | 113 +++++++++ + .../bedrock/camera/BedrockPlayerFovTest.java | 85 +++++++ + 32 files changed, 1471 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/viaversion/viafabricplus/bedrock/camera/BedrockCameraAimAssist.java create mode 100644 src/main/java/com/viaversion/viafabricplus/bedrock/camera/BedrockCameraFade.java create mode 100644 src/main/java/com/viaversion/viafabricplus/bedrock/camera/BedrockCameraFov.java create mode 100644 src/main/java/com/viaversion/viafabricplus/bedrock/camera/BedrockCameraPosition.java create mode 100644 src/main/java/com/viaversion/viafabricplus/bedrock/camera/BedrockCameraShake.java create mode 100644 src/main/java/com/viaversion/viafabricplus/bedrock/camera/BedrockCameraWorld.java + create mode 100644 src/main/java/com/viaversion/viafabricplus/bedrock/camera/BedrockDeathCamera.java create mode 100644 src/main/java/com/viaversion/viafabricplus/bedrock/camera/BedrockPlayerFov.java create mode 100644 src/main/java/com/viaversion/viafabricplus/bedrock/camera/CameraAimAssistControlPayload.java create mode 100644 src/main/java/com/viaversion/viafabricplus/bedrock/camera/CameraAimAssistPayload.java @@ -210,9 +238,11 @@ scheduling still need native world comparisons. create mode 100644 src/main/java/com/viaversion/viafabricplus/bedrock/injection/mixin/features/render/MixinBedrockCameraFov.java create mode 100644 src/main/java/com/viaversion/viafabricplus/bedrock/injection/mixin/features/render/MixinBedrockCameraPerspective.java create mode 100644 src/main/java/com/viaversion/viafabricplus/bedrock/injection/mixin/features/render/MixinBedrockCameraShake.java + create mode 100644 src/main/java/com/viaversion/viafabricplus/bedrock/injection/mixin/features/render/MixinBedrockDeathCameraEffects.java create mode 100644 src/main/java/com/viaversion/viafabricplus/bedrock/injection/mixin/features/render/MixinBedrockFollowInput.java create mode 100644 src/main/java/com/viaversion/viafabricplus/bedrock/injection/mixin/features/sound/MixinBedrockCameraListener.java create mode 100644 src/test/java/com/viaversion/viafabricplus/bedrock/camera/BedrockCameraPositionTest.java + create mode 100644 src/test/java/com/viaversion/viafabricplus/bedrock/camera/BedrockDeathCameraTest.java create mode 100644 src/test/java/com/viaversion/viafabricplus/bedrock/camera/BedrockPlayerFovTest.java diff --git a/src/main/java/com/viaversion/viafabricplus/bedrock/ViaFabricPlusBedrock.java b/src/main/java/com/viaversion/viafabricplus/bedrock/ViaFabricPlusBedrock.java @@ -475,10 +505,10 @@ index 0000000..49fab8a +} diff --git a/src/main/java/com/viaversion/viafabricplus/bedrock/camera/BedrockCameraPosition.java b/src/main/java/com/viaversion/viafabricplus/bedrock/camera/BedrockCameraPosition.java new file mode 100644 -index 0000000..c76154a +index 0000000..cec3cf2 --- /dev/null +++ b/src/main/java/com/viaversion/viafabricplus/bedrock/camera/BedrockCameraPosition.java -@@ -0,0 +1,183 @@ +@@ -0,0 +1,224 @@ +/* + * This file is part of ViaFabricPlus Bedrock - https://github.com/ViaVersionAddons/viafabricplus-bedrock + * Copyright (C) 2026 ViaVersion and contributors @@ -487,6 +517,8 @@ index 0000000..c76154a +package com.viaversion.viafabricplus.bedrock.camera; + +import net.minecraft.client.CameraType; ++import net.minecraft.client.player.LocalPlayer; ++import com.viaversion.viafabricplus.bedrock.input.BedrockInputPermissions; +import com.viaversion.viafabricplus.bedrock.render.BedrockActorState; +import net.raphimc.viabedrock.api.model.CameraTrackingState; +import net.raphimc.viabedrock.api.model.CameraSplineState; @@ -510,6 +542,8 @@ index 0000000..c76154a + private static final CameraFollowRuntime FOLLOW = new CameraFollowRuntime(); + private static final CameraTrackingRuntime TRACKING = new CameraTrackingRuntime(); + private static final CameraPoseTimeline TIMELINE = new CameraPoseTimeline(); ++ private static final BedrockDeathCamera DEATH = new BedrockDeathCamera(); ++ private static LocalPlayer deathSubject; + private static final Deque PENDING = new ArrayDeque<>(); + private static CameraPositionState state = CameraPositionState.EMPTY; + private static CameraPose rendered; @@ -547,6 +581,8 @@ index 0000000..c76154a + } + + public static void clear() { ++ DEATH.clear(); ++ deathSubject = null; + FOLLOW.clear(); + TRACKING.clear(); + PENDING.clear(); @@ -563,6 +599,31 @@ index 0000000..c76154a + preparingTarget = true; + } + ++ public static void tickDeath() { ++ final var client = Minecraft.getInstance(); ++ final var player = client.player; ++ if (!BedrockInputPermissions.isBedrockConnection() || player == null || client.getCameraEntity() != player) { ++ DEATH.clear(); ++ deathSubject = null; ++ return; ++ } ++ if (deathSubject != player) { ++ DEATH.clear(); ++ deathSubject = player; ++ } ++ // Java stops ticking its dead local actor. The native death counter continues on camera ticks. ++ DEATH.tick(player.getHealth() <= 0, player.deathTime); ++ } ++ ++ public static boolean defaultDeathActive() { ++ final var requested = PENDING.isEmpty() ? state : PENDING.getLast(); ++ return DEATH.active() && requested.mode() == Mode.LOCAL; ++ } ++ ++ public static float deathFieldOfView(final float partialTick, final float normalFov, final float effectScale) { ++ return DEATH.projection(normalFov, effectScale, partialTick); ++ } ++ + public static CameraAudioListener playerListener() { + final var player = Minecraft.getInstance().player; + if (state.mode() == Mode.LOCAL || !state.playerAudio() || player == null) return null; @@ -572,6 +633,7 @@ index 0000000..c76154a + } + + public static CameraType perspective(final CameraType local) { ++ if (!preparingTarget && defaultDeathActive()) return CameraType.THIRD_PERSON_BACK; + final Mode mode; + if (preparingTarget) { + final CameraPositionState requested = PENDING.isEmpty() ? state : PENDING.getLast(); @@ -593,18 +655,27 @@ index 0000000..c76154a + final long now = System.nanoTime(); + final float delta = previousFrame == 0 ? 0 : Math.max(0, (now - previousFrame) / 1_000_000_000F); + previousFrame = now; ++ final var player = Minecraft.getInstance().player; ++ final CameraPose local; ++ if (DEATH.active() && player != null) { ++ final var eye = player.getEyePosition(partialTick); ++ local = DEATH.pose(new CameraPose.Vector((float) eye.x, (float) eye.y, (float) eye.z), ++ player.getYRot(partialTick), normal.fov(), delta, new BedrockCameraWorld(Minecraft.getInstance().level)); ++ } else { ++ local = normal; ++ } + boolean changed = false; + while (!PENDING.isEmpty()) { + final CameraPositionState snapshot = PENDING.removeFirst(); + state = snapshot; + if (snapshot.mode() == Mode.LOCAL) { + // Clear resets the next blend even when both commands arrive before this frame. -+ rendered = normal; ++ rendered = local; + } else { + // A snapshot lacks the old client's interrupted frame. Restore its settled target, + // rather than replaying an expired or newly negotiated cutscene from a guessed pose. + final boolean continuous = snapshot.live() && snapshot.sequence() == appliedSequence + 1; -+ TIMELINE.begin(rendered != null ? rendered : normal, continuous ? snapshot.duration() : 0, snapshot.ease()); ++ TIMELINE.begin(rendered != null ? rendered : local, continuous ? snapshot.duration() : 0, snapshot.ease()); + } + appliedSequence = snapshot.sequence(); + changed = true; @@ -612,8 +683,8 @@ index 0000000..c76154a + TRACKING.advance(delta, BedrockCameraPosition::targetActor); + FOLLOW.advance(delta, localFollowActor(), BedrockCameraPosition::followActor, new BedrockCameraWorld(Minecraft.getInstance().level)); + if (state.mode() == Mode.LOCAL) { -+ rendered = normal; -+ return null; ++ rendered = local; ++ return local != normal ? local : null; + } + final CameraPose tracked = TRACKING.active(state.sequence(), normal.fov(), Mode.FREE.ordinal()); + final CameraPose follow = FOLLOW.active(state.sequence(), normal.fov(), Mode.BOOM.ordinal()); @@ -818,6 +889,91 @@ index 0000000..3bbb458 + return new CameraRaycast.Box(minX, minY, minZ, maxX, maxY, maxZ); + } +} +diff --git a/src/main/java/com/viaversion/viafabricplus/bedrock/camera/BedrockDeathCamera.java b/src/main/java/com/viaversion/viafabricplus/bedrock/camera/BedrockDeathCamera.java +new file mode 100644 +index 0000000..63d6f68 +--- /dev/null ++++ b/src/main/java/com/viaversion/viafabricplus/bedrock/camera/BedrockDeathCamera.java +@@ -0,0 +1,79 @@ ++/* ++ * This file is part of ViaFabricPlus Bedrock - https://github.com/ViaVersionAddons/viafabricplus-bedrock ++ * Copyright (C) 2026 ViaVersion and contributors ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++package com.viaversion.viafabricplus.bedrock.camera; ++ ++import net.raphimc.viabedrock.api.model.CameraAvoidance; ++import net.raphimc.viabedrock.api.model.CameraFollowRuntime; ++import net.raphimc.viabedrock.api.model.CameraOrbit; ++import net.raphimc.viabedrock.api.model.CameraPose; ++import net.raphimc.viabedrock.api.model.CameraPositionState; ++import net.raphimc.viabedrock.api.util.BedrockTrigonometry; ++ ++/** The default death profile and counter in Bedrock 1.26.60.5. */ ++final class BedrockDeathCamera { ++ private static final float RADIUS = 8F; ++ private static final float POLAR = 60F * CameraPose.DEGREES; ++ private boolean active; ++ private short ticks; ++ private CameraOrbit orbit; ++ private CameraAvoidance avoidance; ++ ++ void tick(final boolean dead, final int initialDeathTicks) { ++ if (!dead) { ++ this.clear(); ++ } else if (this.active) { ++ this.ticks++; ++ } else { ++ this.active = true; ++ this.ticks = (short) initialDeathTicks; ++ } ++ } ++ ++ boolean active() { ++ return this.active; ++ } ++ ++ float fieldOfView(final float partialTick) { ++ return fieldOfView(this.ticks, partialTick); ++ } ++ ++ float projection(final float normalFov, final float effectScale, final float partialTick) { ++ final float nativeFov = this.fieldOfView(partialTick); ++ // Disabled gameplay effects preserve the existing modifier history before it converges to one. ++ return effectScale == 1F ? nativeFov : normalFov + effectScale * (nativeFov - normalFov); ++ } ++ ++ static float fieldOfView(final int deathTicks, final float partialTick) { ++ final float progress = Math.clamp(((float) (short) deathTicks + partialTick) / 120F, 0F, 1F); ++ // The original renderer uses the native sine lookup, then replaces the slider-derived FOV. ++ final float eased = .4F * BedrockTrigonometry.sine(progress * (CameraPose.PI * .5F)); ++ return (eased + 1F) * 60F; ++ } ++ ++ CameraPose pose(final CameraPose.Vector eye, final float yaw, final float fov, final float delta, ++ final CameraFollowRuntime.World world) { ++ final var target = new CameraOrbit.Target(RADIUS, (yaw - 90F) * CameraPose.DEGREES, POLAR); ++ if (this.orbit == null) { ++ this.orbit = new CameraOrbit(new CameraOrbit.Settings(0, 0, 0, POLAR, POLAR, ++ -CameraPose.PI, CameraPose.PI, false, false), ++ new CameraOrbit.State(RADIUS, target.yaw(), POLAR, 0, 0, 0), target); ++ this.avoidance = new CameraAvoidance(CameraAvoidance.Settings.boom(), new CameraAvoidance.State(RADIUS, 0)); ++ } ++ this.orbit.target(target); ++ this.orbit.input(0, 0); ++ final var requested = this.orbit.advance(delta, eye, eye, Float.MAX_VALUE); ++ final var position = this.avoidance.advance(delta, eye, requested, RADIUS, .025F, ++ world.insideSolidBlock(eye), null, world); ++ return CameraPose.fromDegrees(position, 30F, yaw, fov, CameraPositionState.Mode.THIRD_PERSON.ordinal()); ++ } ++ ++ void clear() { ++ this.active = false; ++ this.ticks = 0; ++ this.orbit = null; ++ this.avoidance = null; ++ } ++} diff --git a/src/main/java/com/viaversion/viafabricplus/bedrock/camera/BedrockPlayerFov.java b/src/main/java/com/viaversion/viafabricplus/bedrock/camera/BedrockPlayerFov.java new file mode 100644 index 0000000..92640be @@ -1145,10 +1301,10 @@ index 0000000..d122b86 +} diff --git a/src/main/java/com/viaversion/viafabricplus/bedrock/injection/mixin/features/render/MixinBedrockCameraFov.java b/src/main/java/com/viaversion/viafabricplus/bedrock/injection/mixin/features/render/MixinBedrockCameraFov.java new file mode 100644 -index 0000000..742ac02 +index 0000000..e09bea0 --- /dev/null +++ b/src/main/java/com/viaversion/viafabricplus/bedrock/injection/mixin/features/render/MixinBedrockCameraFov.java -@@ -0,0 +1,64 @@ +@@ -0,0 +1,85 @@ +/* + * This file is part of ViaFabricPlus Bedrock - https://github.com/ViaVersionAddons/viafabricplus-bedrock + * Copyright (C) 2026 ViaVersion and contributors @@ -1161,10 +1317,12 @@ index 0000000..742ac02 +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.viaversion.viafabricplus.bedrock.appearance.BedrockItemMetadata; +import com.viaversion.viafabricplus.bedrock.camera.BedrockCameraFov; ++import com.viaversion.viafabricplus.bedrock.camera.BedrockCameraPosition; +import com.viaversion.viafabricplus.bedrock.camera.BedrockPlayerFov; +import com.viaversion.viafabricplus.bedrock.input.BedrockInputPermissions; +import com.viaversion.viafabricplus.bedrock.input.BedrockMovementEffects; +import net.minecraft.client.Camera; ++import net.minecraft.client.Minecraft; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.core.component.DataComponents; +import net.minecraft.world.effect.MobEffects; @@ -1173,12 +1331,30 @@ index 0000000..742ac02 +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; ++import org.spongepowered.asm.mixin.injection.Inject; ++import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Camera.class) +public abstract class MixinBedrockCameraFov { + @Shadow private float fov; + @Shadow private boolean isPanoramicMode; + ++ @Inject(method = "tick", at = @At("HEAD")) ++ private void advanceNativeDeathCounter(final CallbackInfo ci) { ++ if ((Object) this == Minecraft.getInstance().gameRenderer.mainCamera()) BedrockCameraPosition.tickDeath(); ++ } ++ ++ @WrapOperation(method = "calculateFov", at = @At(value = "INVOKE", ++ target = "Lnet/minecraft/client/Camera;modifyFovBasedOnDeathOrFluid(FF)F")) ++ private float nativeDeathProjection(final Camera camera, final float partialTick, final float fov, ++ final Operation original) { ++ if (camera != Minecraft.getInstance().gameRenderer.mainCamera() || !BedrockCameraPosition.defaultDeathActive()) ++ return original.call(camera, partialTick, fov); ++ final var options = Minecraft.getInstance().options; ++ return BedrockCameraPosition.deathFieldOfView(partialTick, fov, ++ options.fovEffectScale().get().floatValue()); ++ } ++ + @WrapOperation(method = "tickFov", at = @At(value = "INVOKE", + target = "Lnet/minecraft/client/player/AbstractClientPlayer;getFieldOfViewModifier(ZF)F")) + private float nativePlayerModifier(final AbstractClientPlayer player, final boolean firstPerson, @@ -1208,6 +1384,7 @@ index 0000000..742ac02 + + @ModifyReturnValue(method = "calculateFov", at = @At("RETURN")) + private float serverProjection(final float normal) { ++ if ((Object) this != Minecraft.getInstance().gameRenderer.mainCamera()) return normal; + final float local = BedrockInputPermissions.isBedrockConnection() && !this.isPanoramicMode + ? Math.clamp(normal, 5F, 130F) : normal; + return BedrockCameraFov.project(local, this.fov); @@ -1242,10 +1419,10 @@ index 0000000..c36c380 +} diff --git a/src/main/java/com/viaversion/viafabricplus/bedrock/injection/mixin/features/render/MixinBedrockCameraShake.java b/src/main/java/com/viaversion/viafabricplus/bedrock/injection/mixin/features/render/MixinBedrockCameraShake.java new file mode 100644 -index 0000000..bce161d +index 0000000..7ba1ce3 --- /dev/null +++ b/src/main/java/com/viaversion/viafabricplus/bedrock/injection/mixin/features/render/MixinBedrockCameraShake.java -@@ -0,0 +1,73 @@ +@@ -0,0 +1,76 @@ +/* + * This file is part of ViaFabricPlus Bedrock - https://github.com/ViaVersionAddons/viafabricplus-bedrock + * Copyright (C) 2026 ViaVersion and contributors @@ -1259,6 +1436,7 @@ index 0000000..bce161d +import net.raphimc.viabedrock.api.model.CameraPositionState; + +import net.minecraft.client.Camera; ++import net.minecraft.client.Minecraft; +import net.minecraft.world.phys.Vec3; + +import org.spongepowered.asm.mixin.Mixin; @@ -1283,11 +1461,13 @@ index 0000000..bce161d + + @Inject(method = "alignWithEntity", at = @At("HEAD")) + private void prepareServerPose(final float partialTick, final CallbackInfo ci) { ++ if ((Object) this != Minecraft.getInstance().gameRenderer.mainCamera()) return; + BedrockCameraPosition.beginFrame(partialTick); + } + + @Inject(method = "alignWithEntity", at = @At("TAIL")) + private void applyServerShake(final float partialTick, final CallbackInfo ci) { ++ if ((Object) this != Minecraft.getInstance().gameRenderer.mainCamera()) return; + final CameraPose normal = CameraPose.fromDegrees(new CameraPose.Vector((float) this.position.x, + (float) this.position.y, (float) this.position.z), this.xRot, this.yRot, + this.fov, net.minecraft.client.Minecraft.getInstance().options.getCameraType() == net.minecraft.client.CameraType.FIRST_PERSON @@ -1319,6 +1499,35 @@ index 0000000..bce161d + } + } +} +diff --git a/src/main/java/com/viaversion/viafabricplus/bedrock/injection/mixin/features/render/MixinBedrockDeathCameraEffects.java b/src/main/java/com/viaversion/viafabricplus/bedrock/injection/mixin/features/render/MixinBedrockDeathCameraEffects.java +new file mode 100644 +index 0000000..7cef2ef +--- /dev/null ++++ b/src/main/java/com/viaversion/viafabricplus/bedrock/injection/mixin/features/render/MixinBedrockDeathCameraEffects.java +@@ -0,0 +1,23 @@ ++/* ++ * This file is part of ViaFabricPlus Bedrock - https://github.com/ViaVersionAddons/viafabricplus-bedrock ++ * Copyright (C) 2026 ViaVersion and contributors ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++package com.viaversion.viafabricplus.bedrock.injection.mixin.features.render; ++ ++import com.mojang.blaze3d.vertex.PoseStack; ++import com.viaversion.viafabricplus.bedrock.camera.BedrockCameraPosition; ++import net.minecraft.client.renderer.GameRenderer; ++import net.minecraft.client.renderer.state.level.CameraRenderState; ++import org.spongepowered.asm.mixin.Mixin; ++import org.spongepowered.asm.mixin.injection.At; ++import org.spongepowered.asm.mixin.injection.Inject; ++import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; ++ ++@Mixin(GameRenderer.class) ++public abstract class MixinBedrockDeathCameraEffects { ++ @Inject(method = "bobHurt", at = @At("HEAD"), cancellable = true) ++ private void retainNativeDeathOrientation(final CameraRenderState camera, final PoseStack poses, final CallbackInfo ci) { ++ if (BedrockCameraPosition.defaultDeathActive()) ci.cancel(); ++ } ++} diff --git a/src/main/java/com/viaversion/viafabricplus/bedrock/injection/mixin/features/render/MixinBedrockFollowInput.java b/src/main/java/com/viaversion/viafabricplus/bedrock/injection/mixin/features/render/MixinBedrockFollowInput.java new file mode 100644 index 0000000..371ff8d @@ -1450,21 +1659,22 @@ index eadb404..b83cf42 100644 "bedrock_settings.viafabricplus.hide_own_captions": "Hide captions for your sounds", "bedrock_settings.viafabricplus.hide_ambient_captions": "Hide ambient and weather captions", diff --git a/src/main/resources/viafabricplus-bedrock.mixins.json b/src/main/resources/viafabricplus-bedrock.mixins.json -index 549dc2b..605b96f 100644 +index 549dc2b..0bf601f 100644 --- a/src/main/resources/viafabricplus-bedrock.mixins.json +++ b/src/main/resources/viafabricplus-bedrock.mixins.json -@@ -141,6 +141,10 @@ +@@ -141,6 +141,11 @@ "features.render.MixinBedrockFirstPersonVisibility", "features.render.MixinBedrockFirstPersonBobbing", "features.render.MixinBedrockFirstPersonProjection", + "features.render.MixinBedrockCameraFov", ++ "features.render.MixinBedrockDeathCameraEffects", + "features.render.MixinBedrockCameraShake", + "features.render.MixinBedrockCameraPerspective", + "features.render.MixinBedrockFollowInput", "features.render.MixinBedrockHandItemLifecycle", "features.render.MixinBedrockModelPartTransforms", "features.render.MixinPlayerSkinWidget", -@@ -165,11 +169,13 @@ +@@ -165,11 +170,13 @@ "features.sound.MixinBedrockSoundManager", "features.sound.MixinBedrockSoundCaptions", "features.sound.MixinBedrockSoundEngine", @@ -1553,6 +1763,125 @@ index 0000000..0836de0 + return new CameraPositionState(sequence, 0, true, mode, position, 0, 0, duration, CameraEase.LINEAR, true, false); + } +} +diff --git a/src/test/java/com/viaversion/viafabricplus/bedrock/camera/BedrockDeathCameraTest.java b/src/test/java/com/viaversion/viafabricplus/bedrock/camera/BedrockDeathCameraTest.java +new file mode 100644 +index 0000000..7751a91 +--- /dev/null ++++ b/src/test/java/com/viaversion/viafabricplus/bedrock/camera/BedrockDeathCameraTest.java +@@ -0,0 +1,113 @@ ++/* ++ * This file is part of ViaFabricPlus Bedrock - https://github.com/ViaVersionAddons/viafabricplus-bedrock ++ * Copyright (C) 2026 ViaVersion and contributors ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++package com.viaversion.viafabricplus.bedrock.camera; ++ ++import com.google.gson.JsonObject; ++import com.google.gson.JsonParser; ++import net.raphimc.viabedrock.api.model.CameraFollowRuntime; ++import net.raphimc.viabedrock.api.model.CameraPose; ++import org.junit.jupiter.api.Test; ++import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; ++ ++import java.nio.file.Files; ++import java.nio.file.Path; ++ ++import static org.junit.jupiter.api.Assertions.*; ++ ++final class BedrockDeathCameraTest { ++ @Test ++ void continuesAfterTheHostDeathCounterStopsAndRestartsAfterRespawn() { ++ final var camera = new BedrockDeathCamera(); ++ camera.tick(true, 20); ++ final float early = camera.fieldOfView(.5F); ++ for (int i = 0; i < 100; i++) camera.tick(true, 20); ++ assertEquals(84F, camera.fieldOfView(.5F)); ++ assertTrue(early < camera.fieldOfView(.5F)); ++ camera.tick(false, 20); ++ assertFalse(camera.active()); ++ camera.tick(true, 0); ++ assertEquals(60F, camera.fieldOfView(0)); ++ } ++ ++ @Test ++ void retainsTheSignedShortCounterWhenTheDeathScreenStaysOpen() { ++ final var camera = new BedrockDeathCamera(); ++ camera.tick(true, Short.MAX_VALUE); ++ assertEquals(84F, camera.fieldOfView(0)); ++ camera.tick(true, 20); ++ assertEquals(60F, camera.fieldOfView(0)); ++ } ++ ++ @Test ++ @EnabledIfEnvironmentVariable(named = "STACKANVIL_DEATH_CAMERA_REFERENCE", matches = ".+") ++ void matchesTheExecutedDefaultDeathProjectionAcrossSliderAndFluidInputs() throws Exception { ++ final var fixture = reference("native-world-default-death-cases.json"); ++ int checked = 0; ++ for (final var value : fixture.getAsJsonArray("cases")) { ++ final var row = value.getAsJsonObject(); ++ if (row.get("alive").getAsBoolean()) continue; ++ final var camera = new BedrockDeathCamera(); ++ camera.tick(true, row.get("ticks").getAsInt()); ++ final float normal = row.get("fov").getAsFloat() * row.get("modifier").getAsFloat(); ++ assertEquals(row.get("bits").getAsInt(), Float.floatToRawIntBits(camera.projection(normal, ++ row.get("effects").getAsBoolean() ? 1F : 0F, row.get("alpha").getAsFloat())), row::toString); ++ checked++; ++ } ++ assertTrue(checked > 0, "The native fixture contains no default death projections"); ++ } ++ ++ @Test ++ @EnabledIfEnvironmentVariable(named = "STACKANVIL_DEATH_CAMERA_REFERENCE", matches = ".+") ++ void projectsTheSettledDeathViewOntoBothNativeMarkerCaptures() throws Exception { ++ final var fixture = reference("native-death-geometry-review.json"); ++ final var eye = fixture.getAsJsonArray("eye"); ++ final var camera = new BedrockDeathCamera(); ++ camera.tick(true, 120); ++ final var pose = camera.pose(new CameraPose.Vector(eye.get(0).getAsFloat(), eye.get(1).getAsFloat(), ++ eye.get(2).getAsFloat()), 0, camera.fieldOfView(0), 0, new CameraFollowRuntime.World() { ++ @Override ++ public boolean insideSolidBlock(final CameraPose.Vector origin) { return false; } ++ @Override ++ public CameraPose.Vector cast(final CameraPose.Vector start, final CameraPose.Vector end) { return null; } ++ }); ++ final var angles = pose.orientation().angles(); ++ final double pitch = -angles.x(), yaw = CameraPose.PI - angles.y(); ++ final double sx = Math.sin(yaw), cx = Math.cos(yaw), sp = Math.sin(pitch), cp = Math.cos(pitch); ++ final double focal = 360 / Math.tan(pose.fov() * Math.PI / 360); ++ final double[][] bounds = new double[2][4]; ++ for (int marker = 0; marker < 2; marker++) { ++ bounds[marker] = new double[]{Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, ++ Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY}; ++ for (final int x : new int[]{-marker * 2, 1 - marker * 2}) { ++ for (final int y : new int[]{102, 103}) { ++ final double dx = x - pose.position().x(), dy = y - pose.position().y(), dz = 8 - pose.position().z(); ++ final double depth = -sx * cp * dx - sp * dy + cx * cp * dz; ++ final double px = 640 + (-cx * dx - sx * dz) * focal / depth; ++ final double py = 360 - (-sx * sp * dx + cp * dy + cx * sp * dz) * focal / depth; ++ bounds[marker][0] = Math.min(bounds[marker][0], px); ++ bounds[marker][1] = Math.min(bounds[marker][1], py); ++ bounds[marker][2] = Math.max(bounds[marker][2], px); ++ bounds[marker][3] = Math.max(bounds[marker][3], py); ++ } ++ } ++ } ++ for (final var value : fixture.getAsJsonArray("rows")) { ++ final var row = value.getAsJsonObject(); ++ final var boxes = row.getAsJsonArray("boxes"); ++ for (int marker = 0; marker < 2; marker++) { ++ for (int edge = 0; edge < 4; edge++) { ++ assertEquals(boxes.get(marker).getAsJsonArray().get(edge).getAsDouble(), bounds[marker][edge], ++ 1.1, row::toString); ++ } ++ } ++ } ++ } ++ ++ private static JsonObject reference(final String name) throws Exception { ++ return JsonParser.parseString(Files.readString(Path.of(System.getenv("STACKANVIL_DEATH_CAMERA_REFERENCE"), name))) ++ .getAsJsonObject(); ++ } ++} diff --git a/src/test/java/com/viaversion/viafabricplus/bedrock/camera/BedrockPlayerFovTest.java b/src/test/java/com/viaversion/viafabricplus/bedrock/camera/BedrockPlayerFovTest.java new file mode 100644 index 0000000..4871b06 diff --git a/patches/viafabricplus-bedrock/upstreamable/0023-render-core-resolved-camera-movement.pr.md b/patches/viafabricplus-bedrock/upstreamable/0023-render-core-resolved-camera-movement.pr.md index 7edf0abb..22a211c2 100644 --- a/patches/viafabricplus-bedrock/upstreamable/0023-render-core-resolved-camera-movement.pr.md +++ b/patches/viafabricplus-bedrock/upstreamable/0023-render-core-resolved-camera-movement.pr.md @@ -26,7 +26,7 @@ Rebuilt direct and ViaProxy clients each pass clear-only, ordinary blend, and tw Original Bedrock 1.26.60.5 instructions supply 25 player FOV cases covering movement speed, flight, Slowness, swim boost, bow charge, scoping, disabled gameplay effects, and zero walking speed. Finite outputs match native float bits; the zero-over-zero result remains NaN. Six native Dolphin Boost kernel controls establish the swimming gate and marker-presence rule. The effect timeline regression covers the final marker frame and restored values. -Matching stationary world markers on official 1.26.60.5 and rebuilt direct/ViaProxy clients cover slider values 60, 30, 110, and return to 60. The corrected projection matches native within pixel-spacing quantization. An add-on control with gameplay FOV disabled matches the native disabled control. A direct-route server command retains its specified 40-degree projection, and clear restores the corrected local projection. Complete protocol-2225 journals, installed JAR hashes, stationary position, eye height, and facing are verified. The extra proxy server-FOV command check did not run before its timed recorder ended. +Matching stationary world markers on official 1.26.60.5 and rebuilt direct/ViaProxy clients cover slider values 60, 30, 110, and return to 60. The corrected projection matches native within pixel-spacing quantization. An add-on control with gameplay FOV disabled matches the native disabled control. A direct-route server command retains its specified 40-degree projection, and clear restores the corrected local projection. Complete protocol-2225 journals, installed JAR hashes, stationary position, eye height, and facing are verified. The final death-camera controls also exercise server FOV set and clear on both routes. Native high-Speed controls reach the 130-degree projection limit and return to baseline after expiry. Direct and ViaProxy observations retain the 2.42 modifier before the final projection clamp. The native expiry video records intermediate projections above the former Java modifier limit. @@ -34,8 +34,10 @@ A stationary, submerged fixture with a low ceiling compares native, direct, and Both translated journals contain six Dolphin Boost refreshes with 60-tick durations. Client-thread observations retain the multiplier through the final packet tick plus 60, then clear it on the next tick. The first direct water trial died during fixture preparation and remained on the death screen. It does not establish a swimming defect. The repeated direct trial completes alive with the expected pose, eye height, boost, and expiry. -The four-project build passes 3,515 tests with 329 skipped and no failures or errors, including the executable FOV comparison. Bundle and patch-health checks pass. +The four-project build passes 3,519 tests with 329 skipped and no failures or errors, including the executable FOV and death-camera comparisons. Bundle and patch-health checks pass. -Timeline and selected native/replay controls cover supported commands. Exact native input-frame expiry, other fluid effects, death projection, portrait scaling, complete transition timing, aim assistance, collision, and visual composition remain partial. Original 1.26.60.5 world-FOV instructions confirm an ordinary death curve without Java's 20-tick cap. The default native death controller and its counter lifetime still need live comparison before changing that path. These controls do not establish complete camera or pixel parity. +The default death camera uses the native eight-block orbit and 60-to-84-degree projection. Original 1.26.60.5 instructions supply 54 death-projection cases, including disabled gameplay effects with retained modifier history. Nine native stills at slider values 60 and 30 match the profile-derived marker bounds within 1.1 pixels. Rebuilt direct and ViaProxy clients pass settled pose, FOV disable/restore, explicit server FOV, server-camera ownership, clear, and respawn controls. Auxiliary camera ticks leave the shared death counter unchanged. Complete protocol-2225 journals and installed artifact hashes identify both final builds. + +Exact death activation timing, varied angles, collision and postures, non-default death contexts, and native death-screen UI remain open. Other fluid effects, portrait scaling, input-frame expiry, complete transition timing, aim assistance, and visual composition remain partial. These controls do not establish complete camera or pixel parity. The [owning patch](0023-render-core-resolved-camera-movement.patch) retains implementation rationale and versioned evidence. diff --git a/patches/viafabricplus-bedrock/upstreamable/0025-send-completed-local-physics-to-viabedrock.patch b/patches/viafabricplus-bedrock/upstreamable/0025-send-completed-local-physics-to-viabedrock.patch index eec10cc0..67e0b273 100644 --- a/patches/viafabricplus-bedrock/upstreamable/0025-send-completed-local-physics-to-viabedrock.patch +++ b/patches/viafabricplus-bedrock/upstreamable/0025-send-completed-local-physics-to-viabedrock.patch @@ -1,4 +1,4 @@ -From e518b0163fff16a67057f77e809bf45af7b9849a Mon Sep 17 00:00:00 2001 +From be58d49855c8ab6addaf7134fae0e932b2260b12 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:35 +0200 Subject: [PATCH] feat(movement): send completed local physics to ViaBedrock @@ -6918,10 +6918,10 @@ index f42de8f..8e8672c 100644 accessible field net/minecraft/client/renderer/RenderPipelines ENTITY_SNIPPET Lcom/mojang/renderpearl/api/pipeline/RenderPipeline$Snippet; diff --git a/src/main/resources/viafabricplus-bedrock.mixins.json b/src/main/resources/viafabricplus-bedrock.mixins.json -index 605b96f..fab28a7 100644 +index 0bf601f..f3878fb 100644 --- a/src/main/resources/viafabricplus-bedrock.mixins.json +++ b/src/main/resources/viafabricplus-bedrock.mixins.json -@@ -155,12 +155,38 @@ +@@ -156,12 +156,38 @@ "features.input.MixinMouseHandler", "features.input.MixinInputPermissionConnection", "features.input.MixinMinecraftInputPermissions", diff --git a/patches/viafabricplus-bedrock/upstreamable/0026-apply-native-fence-geometry-in-bedrock-sessions.patch b/patches/viafabricplus-bedrock/upstreamable/0026-apply-native-fence-geometry-in-bedrock-sessions.patch index e0c4069f..26eb8c20 100644 --- a/patches/viafabricplus-bedrock/upstreamable/0026-apply-native-fence-geometry-in-bedrock-sessions.patch +++ b/patches/viafabricplus-bedrock/upstreamable/0026-apply-native-fence-geometry-in-bedrock-sessions.patch @@ -1,4 +1,4 @@ -From b8755e58a98e02e146df551afef70b2f269773d4 Mon Sep 17 00:00:00 2001 +From eebc7026348d3056ac1b6df0c38f1e01150d938b Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:35 +0200 Subject: [PATCH] feat(blocks): apply native fence geometry in Bedrock sessions @@ -151,7 +151,7 @@ index 0000000..4e14218 + } +} diff --git a/src/main/resources/viafabricplus-bedrock.mixins.json b/src/main/resources/viafabricplus-bedrock.mixins.json -index fab28a7..ad52238 100644 +index f3878fb..703e368 100644 --- a/src/main/resources/viafabricplus-bedrock.mixins.json +++ b/src/main/resources/viafabricplus-bedrock.mixins.json @@ -79,6 +79,7 @@ diff --git a/patches/viafabricplus-bedrock/upstreamable/0027-apply-authoritative-projectile-owner-changes.patch b/patches/viafabricplus-bedrock/upstreamable/0027-apply-authoritative-projectile-owner-changes.patch index 43f3c1d8..959e7efb 100644 --- a/patches/viafabricplus-bedrock/upstreamable/0027-apply-authoritative-projectile-owner-changes.patch +++ b/patches/viafabricplus-bedrock/upstreamable/0027-apply-authoritative-projectile-owner-changes.patch @@ -1,4 +1,4 @@ -From c4e8d77aa6aca46a5524850584854a5cd2a91a24 Mon Sep 17 00:00:00 2001 +From 2420c2360400d101656b439695823016f132ef41 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:35 +0200 Subject: [PATCH] feat(entities): apply authoritative projectile owner changes @@ -255,10 +255,10 @@ index 0000000..0a3e377 + } +} diff --git a/src/main/resources/viafabricplus-bedrock.mixins.json b/src/main/resources/viafabricplus-bedrock.mixins.json -index ad52238..91cd497 100644 +index 703e368..c12e42c 100644 --- a/src/main/resources/viafabricplus-bedrock.mixins.json +++ b/src/main/resources/viafabricplus-bedrock.mixins.json -@@ -202,7 +202,10 @@ +@@ -203,7 +203,10 @@ "features.particle.MixinBedrockParticleRenderType", "features.render.MixinBedrockHudVisibility", "features.render.MixinMinecraftHudVisibility", diff --git a/patches/viafabricplus-bedrock/upstreamable/0029-present-authored-native-containers.patch b/patches/viafabricplus-bedrock/upstreamable/0029-present-authored-native-containers.patch index 4b93ca87..b35d1664 100644 --- a/patches/viafabricplus-bedrock/upstreamable/0029-present-authored-native-containers.patch +++ b/patches/viafabricplus-bedrock/upstreamable/0029-present-authored-native-containers.patch @@ -1,4 +1,4 @@ -From 07ee1a99037901644f562dd0103087f5fbc2b407 Mon Sep 17 00:00:00 2001 +From f18ac12fddced28cd41f2c8a67d384c49ffd8b68 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:36 +0200 Subject: [PATCH] feat(inventory): present authored native containers @@ -7640,7 +7640,7 @@ index d0fac3c..da0aa13 100644 "bedrock_settings.viafabricplus.allow_camera_shake": "Allow camera shake", "bedrock_settings.viafabricplus.particle_view_distance": "Particle render distance", diff --git a/src/main/resources/viafabricplus-bedrock.mixins.json b/src/main/resources/viafabricplus-bedrock.mixins.json -index 77e339e..e1f51e2 100644 +index 4f3bb1a..2fc3627 100644 --- a/src/main/resources/viafabricplus-bedrock.mixins.json +++ b/src/main/resources/viafabricplus-bedrock.mixins.json @@ -14,6 +14,12 @@ diff --git a/patches/viafabricplus-bedrock/upstreamable/0029-share-native-ui-scene-rendering.patch b/patches/viafabricplus-bedrock/upstreamable/0029-share-native-ui-scene-rendering.patch index d6bf354f..66272ce2 100644 --- a/patches/viafabricplus-bedrock/upstreamable/0029-share-native-ui-scene-rendering.patch +++ b/patches/viafabricplus-bedrock/upstreamable/0029-share-native-ui-scene-rendering.patch @@ -1,4 +1,4 @@ -From 00f166135e78a6a7debd8aa462e4091147242d2d Mon Sep 17 00:00:00 2001 +From 663208d7e78f67febcb349bcd4aa451807e137c2 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:36 +0200 Subject: [PATCH] feat(ui): share native scene rendering and GUI actors @@ -10451,10 +10451,10 @@ index 8e8672c..288cc7a 100644 +accessible field net/minecraft/client/gui/GuiGraphicsExtractor scissorStack Lnet/minecraft/client/gui/GuiGraphicsExtractor$ScissorStack; +accessible field net/minecraft/client/gui/GuiGraphicsExtractor$ScissorStack stack Ljava/util/Deque; diff --git a/src/main/resources/viafabricplus-bedrock.mixins.json b/src/main/resources/viafabricplus-bedrock.mixins.json -index 91cd497..77e339e 100644 +index c12e42c..4f3bb1a 100644 --- a/src/main/resources/viafabricplus-bedrock.mixins.json +++ b/src/main/resources/viafabricplus-bedrock.mixins.json -@@ -150,6 +150,13 @@ +@@ -151,6 +151,13 @@ "features.render.MixinBedrockModelPartTransforms", "features.render.MixinPlayerSkinWidget", "features.render.MixinGuiSkinRenderer", @@ -10468,7 +10468,7 @@ index 91cd497..77e339e 100644 "features.input.MixinKeyboardInput", "features.input.MixinLocalPlayerInput", "features.input.MixinClientInput", -@@ -205,7 +212,13 @@ +@@ -206,7 +213,13 @@ "features.render.MixinBedrockCameraFade", "features.entities.MixinClientEntityTracking", "features.entities.MixinMinecraftProjectileOwners", diff --git a/patches/viafabricplus-bedrock/upstreamable/0030-use-supported-native-world-actor-definitions.patch b/patches/viafabricplus-bedrock/upstreamable/0030-use-supported-native-world-actor-definitions.patch index 406a2f19..df03cee6 100644 --- a/patches/viafabricplus-bedrock/upstreamable/0030-use-supported-native-world-actor-definitions.patch +++ b/patches/viafabricplus-bedrock/upstreamable/0030-use-supported-native-world-actor-definitions.patch @@ -1,4 +1,4 @@ -From 318776b45c6ef11153a3f4775f00b4fb1df5c53f Mon Sep 17 00:00:00 2001 +From 6f4e9299d6aa843debf94d00b0e49d374b9c9a41 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:36 +0200 Subject: [PATCH] feat(render): use supported native world actor definitions @@ -1031,7 +1031,7 @@ index 0000000..a7d9469 + } +} diff --git a/src/main/resources/viafabricplus-bedrock.mixins.json b/src/main/resources/viafabricplus-bedrock.mixins.json -index e1f51e2..45c0917 100644 +index 2fc3627..0e532ca 100644 --- a/src/main/resources/viafabricplus-bedrock.mixins.json +++ b/src/main/resources/viafabricplus-bedrock.mixins.json @@ -117,6 +117,7 @@ diff --git a/patches/viafabricplus-bedrock/upstreamable/0031-edit-native-command-block-settings.patch b/patches/viafabricplus-bedrock/upstreamable/0031-edit-native-command-block-settings.patch index 8c0cccba..e5518127 100644 --- a/patches/viafabricplus-bedrock/upstreamable/0031-edit-native-command-block-settings.patch +++ b/patches/viafabricplus-bedrock/upstreamable/0031-edit-native-command-block-settings.patch @@ -1,4 +1,4 @@ -From 3596380b12b4fcfcf0d70ee0084a3b01ea4a54bb Mon Sep 17 00:00:00 2001 +From 50457908f99492c8cf4660aa21fb501940501eea Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:36 +0200 Subject: [PATCH] feat(command): edit native command block settings @@ -341,10 +341,10 @@ index da0aa13..f345d7c 100644 "base.viafabricplus.code": "Code", "base.viafabricplus.copy_link": "Copy link", diff --git a/src/main/resources/viafabricplus-bedrock.mixins.json b/src/main/resources/viafabricplus-bedrock.mixins.json -index 45c0917..19db653 100644 +index 0e532ca..4fb68ce 100644 --- a/src/main/resources/viafabricplus-bedrock.mixins.json +++ b/src/main/resources/viafabricplus-bedrock.mixins.json -@@ -204,6 +204,8 @@ +@@ -205,6 +205,8 @@ "features.movement.MixinLocalPlayer", "features.movement.MixinPlayerCorrectionPacketListener", "features.movement.MixinPlayer", diff --git a/patches/viafabricplus-bedrock/upstreamable/0032-reconcile-native-block-break-audio.patch b/patches/viafabricplus-bedrock/upstreamable/0032-reconcile-native-block-break-audio.patch index 412ec7a0..ad71e87b 100644 --- a/patches/viafabricplus-bedrock/upstreamable/0032-reconcile-native-block-break-audio.patch +++ b/patches/viafabricplus-bedrock/upstreamable/0032-reconcile-native-block-break-audio.patch @@ -1,4 +1,4 @@ -From 0c2b81a4c1d4a642015a97382178c395d88a7411 Mon Sep 17 00:00:00 2001 +From b3f88d53e7e7189987500f9bdbe6e8483e0d54e7 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:36 +0200 Subject: [PATCH] fix(sound): reconcile native block break audio @@ -71,10 +71,10 @@ index 0000000..9f24afb + } +} diff --git a/src/main/resources/viafabricplus-bedrock.mixins.json b/src/main/resources/viafabricplus-bedrock.mixins.json -index 19db653..df0bc1f 100644 +index 4fb68ce..ad37fd6 100644 --- a/src/main/resources/viafabricplus-bedrock.mixins.json +++ b/src/main/resources/viafabricplus-bedrock.mixins.json -@@ -204,6 +204,7 @@ +@@ -205,6 +205,7 @@ "features.movement.MixinLocalPlayer", "features.movement.MixinPlayerCorrectionPacketListener", "features.movement.MixinPlayer", diff --git a/patches/viafabricplus-bedrock/upstreamable/0034-animate-native-moving-piston-blocks.patch b/patches/viafabricplus-bedrock/upstreamable/0034-animate-native-moving-piston-blocks.patch index 4974b82d..df4082e0 100644 --- a/patches/viafabricplus-bedrock/upstreamable/0034-animate-native-moving-piston-blocks.patch +++ b/patches/viafabricplus-bedrock/upstreamable/0034-animate-native-moving-piston-blocks.patch @@ -1,4 +1,4 @@ -From c385de39f5f0b0fe3c16b14e2add9cc166cef73b Mon Sep 17 00:00:00 2001 +From dfad56cd9010722bd420b23ffa0390fa356626df Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:36 +0200 Subject: [PATCH] fix(render): animate native moving piston blocks @@ -155,10 +155,10 @@ index 0000000..f23da98 + } +} diff --git a/src/main/resources/viafabricplus-bedrock.mixins.json b/src/main/resources/viafabricplus-bedrock.mixins.json -index df0bc1f..e837c43 100644 +index ad37fd6..8ac7aa4 100644 --- a/src/main/resources/viafabricplus-bedrock.mixins.json +++ b/src/main/resources/viafabricplus-bedrock.mixins.json -@@ -207,6 +207,8 @@ +@@ -208,6 +208,8 @@ "features.sound.MixinBedrockMiningAudio", "features.block.MixinBedrockCommandBlockSettings", "features.inventory.MixinBedrockCommandBlockScreen", diff --git a/patches/viafabricplus-bedrock/upstreamable/0035-forward-bedrock-fluid-pickup.patch b/patches/viafabricplus-bedrock/upstreamable/0035-forward-bedrock-fluid-pickup.patch index 5e4f0bf0..b0aa0324 100644 --- a/patches/viafabricplus-bedrock/upstreamable/0035-forward-bedrock-fluid-pickup.patch +++ b/patches/viafabricplus-bedrock/upstreamable/0035-forward-bedrock-fluid-pickup.patch @@ -1,4 +1,4 @@ -From 4ce9e70e4b298aab8085040b5d1de7ed3976bdff Mon Sep 17 00:00:00 2001 +From 97159e2656a204d0807a4112a18db9de2d3e4b95 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:36 +0200 Subject: [PATCH] fix(interaction): forward Bedrock fluid pickup @@ -113,7 +113,7 @@ index 0000000..d3c7c2d + } +} diff --git a/src/main/resources/viafabricplus-bedrock.mixins.json b/src/main/resources/viafabricplus-bedrock.mixins.json -index e837c43..2529ebe 100644 +index 8ac7aa4..2df6d5a 100644 --- a/src/main/resources/viafabricplus-bedrock.mixins.json +++ b/src/main/resources/viafabricplus-bedrock.mixins.json @@ -97,6 +97,7 @@ diff --git a/patches/viafabricplus-bedrock/upstreamable/0038-predict-held-trapdoor-placement.patch b/patches/viafabricplus-bedrock/upstreamable/0038-predict-held-trapdoor-placement.patch index 89245f71..ea2beb10 100644 --- a/patches/viafabricplus-bedrock/upstreamable/0038-predict-held-trapdoor-placement.patch +++ b/patches/viafabricplus-bedrock/upstreamable/0038-predict-held-trapdoor-placement.patch @@ -1,4 +1,4 @@ -From b9b035c2314ede27c25a10333889c6466bb550cf Mon Sep 17 00:00:00 2001 +From 48b3cfa41eddac801e358b7e80ea44d946f66831 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Wed, 28 Oct 2026 03:36:18 +0100 Subject: [PATCH] feat(interaction): preserve native held item use @@ -1054,7 +1054,7 @@ index 0000000..7a2e793 + } +} diff --git a/src/main/resources/viafabricplus-bedrock.mixins.json b/src/main/resources/viafabricplus-bedrock.mixins.json -index 2529ebe..192d194 100644 +index 2df6d5a..1a5057b 100644 --- a/src/main/resources/viafabricplus-bedrock.mixins.json +++ b/src/main/resources/viafabricplus-bedrock.mixins.json @@ -92,6 +92,9 @@ diff --git a/patches/viafabricplus-bedrock/upstreamable/0039-allow-food-use-on-bedrock-peaceful-difficulty.patch b/patches/viafabricplus-bedrock/upstreamable/0039-allow-food-use-on-bedrock-peaceful-difficulty.patch index a4ca87cb..1caef611 100644 --- a/patches/viafabricplus-bedrock/upstreamable/0039-allow-food-use-on-bedrock-peaceful-difficulty.patch +++ b/patches/viafabricplus-bedrock/upstreamable/0039-allow-food-use-on-bedrock-peaceful-difficulty.patch @@ -1,4 +1,4 @@ -From 7ba54cb5f0ffdb9cc9bf2bee9a28114fd840ab0d Mon Sep 17 00:00:00 2001 +From b5fd0a8a781f664f8aed0c22e59b4fad0f339b5f Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 1 Nov 2026 19:54:04 +0100 Subject: [PATCH] fix(items): allow food use on Bedrock Peaceful difficulty @@ -57,7 +57,7 @@ index 0000000..e8a7d8c + +} diff --git a/src/main/resources/viafabricplus-bedrock.mixins.json b/src/main/resources/viafabricplus-bedrock.mixins.json -index 9091a1f..401ea35 100644 +index 6be77ba..422d093 100644 --- a/src/main/resources/viafabricplus-bedrock.mixins.json +++ b/src/main/resources/viafabricplus-bedrock.mixins.json @@ -64,6 +64,7 @@ diff --git a/patches/viafabricplus-bedrock/upstreamable/0039-apply-native-command-stacks-to-dry-air.patch b/patches/viafabricplus-bedrock/upstreamable/0039-apply-native-command-stacks-to-dry-air.patch index 6095e4f6..886c7c45 100644 --- a/patches/viafabricplus-bedrock/upstreamable/0039-apply-native-command-stacks-to-dry-air.patch +++ b/patches/viafabricplus-bedrock/upstreamable/0039-apply-native-command-stacks-to-dry-air.patch @@ -1,4 +1,4 @@ -From 7e06b3414a57324f466a2472e2616806960ce7d1 Mon Sep 17 00:00:00 2001 +From cade1d01f61030c9a151c4461f740f33075335b3 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Thu, 29 Oct 2026 01:28:02 +0100 Subject: [PATCH] feat(fog): apply native biome and command settings @@ -591,7 +591,7 @@ index 0000000..afd7b44 + texCoord0 = UV0; +} diff --git a/src/main/resources/viafabricplus-bedrock.mixins.json b/src/main/resources/viafabricplus-bedrock.mixins.json -index 192d194..9091a1f 100644 +index 1a5057b..6be77ba 100644 --- a/src/main/resources/viafabricplus-bedrock.mixins.json +++ b/src/main/resources/viafabricplus-bedrock.mixins.json @@ -140,6 +140,9 @@ diff --git a/patches/viafabricplus-bedrock/upstreamable/0040-preserve-native-occlusion-with-moreculling.patch b/patches/viafabricplus-bedrock/upstreamable/0040-preserve-native-occlusion-with-moreculling.patch index b7b22b62..46c4ffc4 100644 --- a/patches/viafabricplus-bedrock/upstreamable/0040-preserve-native-occlusion-with-moreculling.patch +++ b/patches/viafabricplus-bedrock/upstreamable/0040-preserve-native-occlusion-with-moreculling.patch @@ -1,4 +1,4 @@ -From 5ce93d858e3b90aa435997f1e213d19fac505f48 Mon Sep 17 00:00:00 2001 +From 6f68b658782d5b31093f7799ad0514165e3f6c22 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Mon, 2 Nov 2026 06:35:10 +0100 Subject: [PATCH] fix(blocks): preserve native occlusion with MoreCulling @@ -103,7 +103,7 @@ index 0000000..d65280d + } +} diff --git a/src/main/resources/viafabricplus-bedrock.mixins.json b/src/main/resources/viafabricplus-bedrock.mixins.json -index 401ea35..a105991 100644 +index 422d093..ba6059a 100644 --- a/src/main/resources/viafabricplus-bedrock.mixins.json +++ b/src/main/resources/viafabricplus-bedrock.mixins.json @@ -6,6 +6,7 @@ diff --git a/patches/viafabricplus-bedrock/upstreamable/0041-interpolate-native-remote-player-movement.patch b/patches/viafabricplus-bedrock/upstreamable/0041-interpolate-native-remote-player-movement.patch index ab5e6c4e..148f28a4 100644 --- a/patches/viafabricplus-bedrock/upstreamable/0041-interpolate-native-remote-player-movement.patch +++ b/patches/viafabricplus-bedrock/upstreamable/0041-interpolate-native-remote-player-movement.patch @@ -1,4 +1,4 @@ -From cdf89c1075278330f3fc1eb9a40579f93644976b Mon Sep 17 00:00:00 2001 +From 62f74ac08b35fbabbbb15d386c36319549c940e7 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Mon, 2 Nov 2026 15:51:01 +0100 Subject: [PATCH] fix(players): interpolate negotiated remote movement as @@ -779,10 +779,10 @@ index 0000000..a958a6d + } +} diff --git a/src/main/resources/viafabricplus-bedrock.mixins.json b/src/main/resources/viafabricplus-bedrock.mixins.json -index a105991..ddd097f 100644 +index ba6059a..4e383fc 100644 --- a/src/main/resources/viafabricplus-bedrock.mixins.json +++ b/src/main/resources/viafabricplus-bedrock.mixins.json -@@ -212,6 +212,9 @@ +@@ -213,6 +213,9 @@ "features.movement.MixinFireworkRocketEntity", "features.movement.MixinLocalPlayer", "features.movement.MixinPlayerCorrectionPacketListener", From 491466c7f85d5a949c0656b1ce6bfdce8ef41dd6 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Thu, 5 Nov 2026 16:01:15 +0100 Subject: [PATCH 004/141] docs(hud): describe locator patch validation and limits --- ...13-translate-remote-player-locations.pr.md | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 patches/viabedrock/upstreamable/0013-translate-remote-player-locations.pr.md diff --git a/patches/viabedrock/upstreamable/0013-translate-remote-player-locations.pr.md b/patches/viabedrock/upstreamable/0013-translate-remote-player-locations.pr.md new file mode 100644 index 00000000..3fbcce08 --- /dev/null +++ b/patches/viabedrock/upstreamable/0013-translate-remote-player-locations.pr.md @@ -0,0 +1,29 @@ +# Preserve native player locations and locator groups + +## Purpose + +Decode legacy player locations and native locator groups for client integrations. +Retain visibility, position, texture, size, color, actor binding, and position authority. +Publish complete snapshots after join and when a listener registers late. + +## Design constraints + +Keep locator groups across dimension changes. +Ignore duplicate additions and updates for unknown groups. +Preserve the native rules for omitted fields instead of clearing every absent value. +Publish removal before replacement of the same group ID so clients can reset smoothing. +Coalesce ordinary updates and preserve unrelated groups. +Keep published snapshots immutable and bound decoding to the payload size. + +## Validation and remaining limits + +Native Bedrock 1.26.51.1, build 51061372 and protocol 2193, supplies packet and lifecycle evidence. +Captured controls establish the field masks and distinguish smoothed updates from immediate group replacement. +The owning patch records those versioned observations. + +Local regressions cover packet discriminators, signed actor IDs, optional fields, group merging, removal and replacement ordering, late listeners, and snapshot round trips. +The latest full core build passes these tests. +Tests remain in the local stack and are excluded from the upstream PR. + +These checks establish decoding and state transport. +Native rendering, current-version live route comparisons, and complete locator HUD parity require separate validation. From 605962dedb5711640ca13c48ff45812c3eaf398a Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Thu, 5 Nov 2026 16:11:10 +0100 Subject: [PATCH 005/141] fix(inventory): skip empty recipes when counting output Reuse preview selection admission for recipe output quantities. Zero-slot and all-empty placeholders contribute no output and cannot interrupt later valid candidates. Fold the fix and regression into the owning inventory patch. --- ...ain-bedrock-creative-catalog-entries.patch | 52 +++++++++++++------ ...ain-bedrock-creative-catalog-entries.pr.md | 2 + 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/patches/viabedrock/deferred/0012-retain-bedrock-creative-catalog-entries.patch b/patches/viabedrock/deferred/0012-retain-bedrock-creative-catalog-entries.patch index dfac6441..68d0d284 100644 --- a/patches/viabedrock/deferred/0012-retain-bedrock-creative-catalog-entries.patch +++ b/patches/viabedrock/deferred/0012-retain-bedrock-creative-catalog-entries.patch @@ -1,4 +1,4 @@ -From addf49fbfe303874cf52002221c1bb1ef8e8ea28 Mon Sep 17 00:00:00 2001 +From e21e0c424e03eb727263b5cd4c765f439f819373 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:37 +0200 Subject: [PATCH] feat(viabedrock): retain Bedrock creative catalog entries @@ -536,6 +536,11 @@ at its float AABB center with data-1 and an empty selector. Protocol2225 LevelSoundEvent explicit x-enum-binary-value maps insertion446, removal447 and failed insertion533. Operation admission/mutation and final level dispatch remain supplied boundaries; native bank selection and installed audio are separate. + +Apply the same nonempty-ingredient admission to output quantity counting +as preview selection. Zero-slot and all-empty placeholders contribute no +output and cannot abort later valid candidates. A regression reproduces +the inventory count failure before this guard and retains later outputs. --- .../api/model/NativeBundleAction.java | 76 ++ .../api/model/NativeBundleResult.java | 72 ++ @@ -545,7 +550,7 @@ remain supplied boundaries; native bank selection and installed audio are separa .../api/model/container/Container.java | 4 +- .../container/NativeBundleActionSession.java | 285 +++++++ .../container/NativeBundleInputSession.java | 185 +++++ - .../model/container/NativeRecipeCounter.java | 119 +++ + .../model/container/NativeRecipeCounter.java | 124 +++ .../model/container/NativeRecipeCrafting.java | 286 +++++++ .../container/NativeRecipeDescriptor.java | 44 ++ .../model/container/NativeStoragePolicy.java | 103 +++ @@ -572,7 +577,7 @@ remain supplied boundaries; native bank selection and installed audio are separa .../NativePlayerInventoryMessageTest.java | 197 +++++ .../container/NativeRecipeCounterTest.java | 84 +++ .../container/NativeRecipeDescriptorTest.java | 24 + - .../container/NativeRecipeQuantityTest.java | 61 ++ + .../container/NativeRecipeQuantityTest.java | 69 ++ .../ui/PlayerInventoryUiTest.java | 153 ++++ .../protocol/model/CreativeCatalogTest.java | 382 ++++++++++ .../model/NativeStoragePolicyTest.java | 113 +++ @@ -588,7 +593,7 @@ remain supplied boundaries; native bank selection and installed audio are separa .../NativePlayerCreativeAuxiliaryTest.java | 333 +++++++++ .../storage/NativePlayerCreativePickTest.java | 138 ++++ .../storage/NativeRecipeCraftingTest.java | 500 +++++++++++++ - 51 files changed, 8154 insertions(+), 172 deletions(-) + 51 files changed, 8167 insertions(+), 172 deletions(-) create mode 100644 src/main/java/net/raphimc/viabedrock/api/model/NativeBundleAction.java create mode 100644 src/main/java/net/raphimc/viabedrock/api/model/NativeBundleResult.java create mode 100644 src/main/java/net/raphimc/viabedrock/api/model/NativePlayerInventoryAction.java @@ -1777,10 +1782,10 @@ index 00000000..d92a02a3 + +} diff --git a/src/main/java/net/raphimc/viabedrock/api/model/container/NativeRecipeCounter.java b/src/main/java/net/raphimc/viabedrock/api/model/container/NativeRecipeCounter.java -index fe874172..96547dc6 100644 +index fe874172..73e5e7a8 100644 --- a/src/main/java/net/raphimc/viabedrock/api/model/container/NativeRecipeCounter.java +++ b/src/main/java/net/raphimc/viabedrock/api/model/container/NativeRecipeCounter.java -@@ -150,6 +150,121 @@ public final class NativeRecipeCounter { +@@ -150,6 +150,126 @@ public final class NativeRecipeCounter { return partial || selectedOutput.getAsBoolean() ? 2 : 3; } @@ -1796,14 +1801,7 @@ index fe874172..96547dc6 100644 + if (candidate.requiresCraftingTable && !workbench) { + continue; + } -+ boolean hasIngredient = false; -+ for (int position = 0; position < candidate.recipe.ingredientCount(); position++) { -+ if (!candidate.recipe.emptyIngredient(position)) { -+ hasIngredient = true; -+ break; -+ } -+ } -+ if (!hasIngredient) { ++ if (!hasIngredient(candidate.recipe)) { + continue; + } + final var round = count(candidate.recipe, sources, descriptorHash).rounds.getFirst(); @@ -1829,6 +1827,15 @@ index fe874172..96547dc6 100644 + return bestCount > 0 ? counted : partial; + } + ++ private static boolean hasIngredient(final Recipe recipe) { ++ for (int position = 0; position < recipe.ingredientCount(); position++) { ++ if (!recipe.emptyIngredient(position)) { ++ return true; ++ } ++ } ++ return false; ++ } ++ + /** Count outputs across candidates without spending the same source descriptor twice. */ + public static Quantity quantity(final List candidates, + final List sources, final ToIntFunction descriptorHash) { @@ -1849,6 +1856,9 @@ index fe874172..96547dc6 100644 + for (final Production production : NativeSort.sorted(candidates, + Comparator.comparingInt(value -> value.candidate.priority))) { + final Candidate candidate = production.candidate; ++ if (!hasIngredient(candidate.recipe)) { ++ continue; ++ } + for (final List round : count(candidate.recipe, sources, descriptorHash).rounds) { + int crafts = Integer.MAX_VALUE; + final Map positions = new HashMap<>(); @@ -1902,7 +1912,7 @@ index fe874172..96547dc6 100644 /** Output lookup owns candidate admission, concrete grid requirements and priority. */ public record Candidate(int priority, boolean requiresCraftingTable, Recipe recipe) { public Candidate { -@@ -164,6 +279,10 @@ public final class NativeRecipeCounter { +@@ -164,6 +284,10 @@ public final class NativeRecipeCounter { boolean emptyIngredient(int index); boolean matchesIngredient(int index, BedrockItem item); @@ -5868,10 +5878,10 @@ index 14d72266..ee6954fc 100644 final var backing = new Registry(new JsonObject()); diff --git a/src/test/java/net/raphimc/viabedrock/api/model/container/NativeRecipeQuantityTest.java b/src/test/java/net/raphimc/viabedrock/api/model/container/NativeRecipeQuantityTest.java new file mode 100644 -index 00000000..f6d3290d +index 00000000..2b43dfcc --- /dev/null +++ b/src/test/java/net/raphimc/viabedrock/api/model/container/NativeRecipeQuantityTest.java -@@ -0,0 +1,61 @@ +@@ -0,0 +1,69 @@ +/* + * This file is part of ViaBedrock - https://github.com/RaphiMC/ViaBedrock + * Copyright (C) 2026 RK_01/RaphiMC and contributors @@ -5891,6 +5901,14 @@ index 00000000..f6d3290d +class NativeRecipeQuantityTest { + + @Test ++ void outputCountingSkipsEmptyPlaceholdersAndRetainsLaterValidRecipes() { ++ final var placeholders = List.of(production(0, -1, Set.of(), 1), production(1, 0, Set.of(), 1)); ++ assertEquals(new NativeRecipeCounter.Quantity(0, 0), quantity(placeholders, 8, 3)); ++ assertEquals(new NativeRecipeCounter.Quantity(32, 8), quantity( ++ List.of(placeholders.get(0), placeholders.get(1), production(1, -1, Set.of(1), 4)), 8, 3)); ++ } ++ ++ @Test + void alternativeLogRecipesMatchNativeDisplayedPlankQuantity() { + // Official 1.26.60.5 classic player inventory: eight logs and three stripped logs show 44 planks. + final var candidates = List.of( diff --git a/patches/viabedrock/deferred/0012-retain-bedrock-creative-catalog-entries.pr.md b/patches/viabedrock/deferred/0012-retain-bedrock-creative-catalog-entries.pr.md index dff1db06..eace645d 100644 --- a/patches/viabedrock/deferred/0012-retain-bedrock-creative-catalog-entries.pr.md +++ b/patches/viabedrock/deferred/0012-retain-bedrock-creative-catalog-entries.pr.md @@ -153,6 +153,8 @@ Native 1.26.60.5 and BDS 1.26.60.6 controls establish capacity admission before With one free inventory item-space, native drops an incompatible or full held stack after partial placement and takes the remainder. Request/state regressions cover descriptors, changed-slot references, grid sources, capacity, cursor preservation, and rejection rollback. A placeholder-recipe regression retains the following valid plan and leaves the source inventory unchanged. +Output quantity counting also skips zero-slot and all-empty placeholders, preserving later valid candidates instead of interrupting recipe-cell construction. +The quantity regression fails with the previous unguarded counter call. Ordinary named ingredients retain resolved recipe metadata. Tag and Molang ingredients use the selected source descriptor. A real registry regression covers named wildcard logs and sticks alongside concrete tag-selected planks. Installed direct and ViaProxy controls each complete 11 server-accepted requests with matching normalized structures across routes. From 594dd584bce45bcd79d13d599a9740f2bf42e82a Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Thu, 5 Nov 2026 19:46:10 +0100 Subject: [PATCH 006/141] fix(crafting): reject empty availability candidates Require a nonempty ingredient before capturing inventory sources. Preserve selected-output fallback and later valid recipes, and share this admission with preview selection and output counting. Retain the admitted ingredient count through the counter call. A registered-recipe regression reproduces the prior exception. Existing native availability fixtures and the full four-project build pass. Update camera coverage to reflect the completed selected controls while retaining broader parity limits. --- docs/bedrock-coverage.md | 2 +- ...edrock-recipes-and-place-ingredients.patch | 66 ++++++++++++++++--- ...edrock-recipes-and-place-ingredients.pr.md | 5 ++ ...ain-bedrock-creative-catalog-entries.patch | 44 ++++++------- 4 files changed, 84 insertions(+), 33 deletions(-) diff --git a/docs/bedrock-coverage.md b/docs/bedrock-coverage.md index 4039a9ed..e5bfd870 100644 --- a/docs/bedrock-coverage.md +++ b/docs/bedrock-coverage.md @@ -30,7 +30,7 @@ No complete area has verified Bedrock parity. | Food use | Native components govern admission and Java food/consumable properties, with Creative and Peaceful exceptions. Local use retains native item identity across Java carriers. | Selected admission, input-deadline, timed-food, held-mode, release, and pause controls pass. Short input edges during active Java use, broader use cycles, and the original intermittent report remain open. See the [food-use evidence](../patches/viabedrock/deferred/0008-signal-sustained-bedrock-item-use.pr.md). | | Cooking | Snapshot-bound recipe actions, transfer batches, preferences, search, and progress. | Default cooking screens retain ordinary controls until authored bindings are complete. See the [cooking reference](native-cooking-actions.md). | | Skins and appearance | Complete skin transport, classic changes, persona assembly, animation graphs, costumes, and attachables. | Broader wardrobe masking, profile/LOD changes, shield rendering, remote appearances, and account/platform flows need further native comparisons. See [skin support](skin-flow.md). | -| Camera | Core-resolved poses, follow targets, blends, FOV commands, shake, fades, and native ordinary player FOV. See [camera validation](../patches/viafabricplus-bedrock/upstreamable/0023-render-core-resolved-camera-movement.pr.md). | Selected stationary projections match native. Dynamic swim expiry, fluid/death composition, portrait scaling, transition timing, collision, aim assistance, and complete visual parity remain open. | +| Camera | Core-resolved poses, follow targets, blends, FOV commands, shake, fades, native ordinary player FOV, and the default death view. See [camera validation](../patches/viafabricplus-bedrock/upstreamable/0023-render-core-resolved-camera-movement.pr.md). | Selected stationary projections, swim-boost expiry, and settled death views have native comparisons. Death activation timing, varied angles and postures, native death-screen UI, other fluid effects, portrait scaling, transition timing, collision, aim assistance, and complete visual parity remain open. | | Movement and effects | Completed physics frames, retained prediction history, correction replay, terrain queries, and authoritative impulses. [Local modes survive profile refreshes and physics replay](../patches/viafabricplus-bedrock/upstreamable/0025-send-completed-local-physics-to-viabedrock.pr.md). [Remote-player live and sparse movement origins remain separate](../patches/viabedrock/upstreamable/0106-align-retained-remote-spawn-origins.pr.md). [Negotiated remote movement retains native float steps, pitch history, and head targets](../patches/viafabricplus-bedrock/upstreamable/0041-interpolate-native-remote-player-movement.pr.md). Overworld air blindness attenuates night-vision strength and fog color together. Both Java routes match native water-boundary fall controls. | Selected reset, head, body, and queued-movement controls match native after packet/tick state alignment. Full trajectories and pose parity remain open. Readiness, ground state, exact timing, and mounted behavior remain unverified. Native Fancy controls establish the tick-based expiry blend and distance curve at noon and midnight. Other fluid movement, vision combinations and media, mount variants, network conditions, and broader replay behavior remain open. | | Fog and picking | Fog commands, inherited legacy biome bindings, pack-defined defaults, Overworld clock modulation, End dragon distance rules, native actor picking transport, and native float camera rays. Disabling the daylight rule freezes explicit Overworld clocks. | Both routes match selected native fog colors and distances, three non-cardinal rays, and paired armor-stand and block reach controls. Eye precision, other actor shapes, multipart target ordering, spatial blending, timed command transitions, directional sunrise tint, sky sampling, and other render modes remain open. | | Custom content | Resource-defined actors, materials, particles, sound, block geometry, lighting, and collision. | Unsupported material methods, missing assets, carrier capacity, and visual differences remain. Live-server admission does not establish parity. | diff --git a/patches/viabedrock/deferred/0004-expose-bedrock-recipes-and-place-ingredients.patch b/patches/viabedrock/deferred/0004-expose-bedrock-recipes-and-place-ingredients.patch index f4962e9c..48a5725e 100644 --- a/patches/viabedrock/deferred/0004-expose-bedrock-recipes-and-place-ingredients.patch +++ b/patches/viabedrock/deferred/0004-expose-bedrock-recipes-and-place-ingredients.patch @@ -1,4 +1,4 @@ -From 86c464e5216432fe19b04b51177f5c8d2778aa52 Mon Sep 17 00:00:00 2001 +From 8f76eb1a8a382f8ac874716f4f17ff8892230d92 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:37 +0200 Subject: [PATCH] feat(crafting): expose Bedrock recipes and place ingredients @@ -888,6 +888,18 @@ once after the complete batch instead of once per request. Keep rollback refreshing the restored model. Player and workbench regressions retain linked request identities and require one Java refresh for a two-request Shift-click. The Bedrock request payload and admission remain unchanged. + +Admit only recipes with a nonempty ingredient before availability captures +source collections. Registered placeholders otherwise throw and interrupt +inventory population, or read sources from a retired owner unnecessarily. +Preserve selected-output fallback and later valid candidates. Retain the +admitted ingredient count for the raw counter so source capture does not +re-read recipe shape. The raw counter continues to reject empty recipes. + +A registered-recipe regression fails before this admission guard. Existing +original 1.26.51.1 availability fixtures retain their valid-recipe results. +This is defensive Java handling of retained placeholders, not a claim of +new native 1.26.60 availability or live screen parity. --- .../api/model/NativeFurnaceOptions.java | 44 + .../container/CookingRecipeAvailability.java | 30 + @@ -898,7 +910,7 @@ Shift-click. The Bedrock request payload and admission remain unchanged. .../container/FurnaceRecipePlacement.java | 221 +++++ .../NativeCookingRecipeSelection.java | 205 +++++ .../model/container/NativeCraftingOutput.java | 272 ++++++ - .../model/container/NativeRecipeCounter.java | 202 ++++ + .../model/container/NativeRecipeCounter.java | 219 +++++ .../container/NativeRecipeDescriptor.java | 60 +- .../block/CraftingTableContainer.java | 116 +-- .../container/block/FurnaceContainer.java | 209 +++++ @@ -959,7 +971,7 @@ Shift-click. The Bedrock request payload and admission remain unchanged. .../NativeRecipeConcreteCounterTest.java | 165 ++++ .../rewriter/NativeRecipePaletteTest.java | 203 +++++ .../PlayerRecipeAvailabilityTest.java | 296 ++++++ - ...egisteredPlayerRecipeAvailabilityTest.java | 372 ++++++++ + ...egisteredPlayerRecipeAvailabilityTest.java | 389 ++++++++ .../RegisteredPlayerRecipeSearchTest.java | 268 ++++++ .../ResolvedRecipeIngredientTest.java | 283 ++++++ .../storage/CookingRecipeBookTest.java | 194 ++++ @@ -979,7 +991,7 @@ Shift-click. The Bedrock request payload and admission remain unchanged. .../types/model/FurnaceOptionsTypeTest.java | 75 ++ .../types/model/InventoryOptionsTypeTest.java | 46 + .../types/recipe/CraftingDataReaderTest.java | 84 +- - 90 files changed, 13031 insertions(+), 234 deletions(-) + 90 files changed, 13065 insertions(+), 234 deletions(-) create mode 100644 src/main/java/net/raphimc/viabedrock/api/model/NativeFurnaceOptions.java create mode 100644 src/main/java/net/raphimc/viabedrock/api/model/container/CookingRecipeAvailability.java create mode 100644 src/main/java/net/raphimc/viabedrock/api/model/container/CookingRecipeCatalog.java @@ -2327,10 +2339,10 @@ index 00000000..1e69ee4c +} diff --git a/src/main/java/net/raphimc/viabedrock/api/model/container/NativeRecipeCounter.java b/src/main/java/net/raphimc/viabedrock/api/model/container/NativeRecipeCounter.java new file mode 100644 -index 00000000..fe874172 +index 00000000..06746721 --- /dev/null +++ b/src/main/java/net/raphimc/viabedrock/api/model/container/NativeRecipeCounter.java -@@ -0,0 +1,202 @@ +@@ -0,0 +1,219 @@ +/* + * This file is part of ViaBedrock - https://github.com/RaphiMC/ViaBedrock + * Copyright (C) 2026 RK_01/RaphiMC and contributors @@ -2363,7 +2375,11 @@ index 00000000..fe874172 + */ + public static Result count(final Recipe recipe, final List sources, + final ToIntFunction descriptorHash) { -+ final int ingredientCount = recipe.ingredientCount(); ++ return count(recipe, recipe.ingredientCount(), sources, descriptorHash); ++ } ++ ++ private static Result count(final Recipe recipe, final int ingredientCount, final List sources, ++ final ToIntFunction descriptorHash) { + final Map byHash = new HashMap<>(); + final List groups = new ArrayList<>(); + for (final Source source : sources) { @@ -2468,7 +2484,11 @@ index 00000000..fe874172 + if (candidate.requiresCraftingTable && !workbench) { + continue; + } -+ final Result result = count(candidate.recipe, sources.get(), descriptorHash); ++ final int ingredientCount = candidate.recipe.ingredientCount(); ++ if (!hasIngredient(candidate.recipe, ingredientCount)) { ++ continue; ++ } ++ final Result result = count(candidate.recipe, ingredientCount, sources.get(), descriptorHash); + if (result.count > 0) { + return filtering ? 0 : 1; + } @@ -2483,6 +2503,15 @@ index 00000000..fe874172 + return partial || selectedOutput.getAsBoolean() ? 2 : 3; + } + ++ private static boolean hasIngredient(final Recipe recipe, final int ingredientCount) { ++ for (int position = 0; position < ingredientCount; position++) { ++ if (!recipe.emptyIngredient(position)) { ++ return true; ++ } ++ } ++ return false; ++ } ++ + /** Output lookup owns candidate admission, concrete grid requirements and priority. */ + public record Candidate(int priority, boolean requiresCraftingTable, Recipe recipe) { + public Candidate { @@ -10686,10 +10715,10 @@ index 00000000..ed9829fd +} diff --git a/src/test/java/net/raphimc/viabedrock/protocol/rewriter/RegisteredPlayerRecipeAvailabilityTest.java b/src/test/java/net/raphimc/viabedrock/protocol/rewriter/RegisteredPlayerRecipeAvailabilityTest.java new file mode 100644 -index 00000000..de622a23 +index 00000000..7b1f3805 --- /dev/null +++ b/src/test/java/net/raphimc/viabedrock/protocol/rewriter/RegisteredPlayerRecipeAvailabilityTest.java -@@ -0,0 +1,372 @@ +@@ -0,0 +1,389 @@ +/* + * This file is part of ViaBedrock - https://github.com/RaphiMC/ViaBedrock + * Copyright (C) 2026 RK_01/RaphiMC and contributors @@ -10746,6 +10775,23 @@ index 00000000..de622a23 + private static final int IDENTIFIER_BASE = 30000; + + @Test ++ void emptyRegisteredTemplatesDoNotAbortAvailabilityOrReadRetiredSources() throws Exception { ++ final var context = PlayerRecipeAvailabilityTest.context(false, IDENTIFIER_BASE); ++ final var tracker = new CraftingDataTracker(context.items().user()); ++ final var output = item(1); ++ final var placeholders = List.of(recipe(RecipeType.SHAPELESS, 1, 0, 0, List.of()), ++ recipe(RecipeType.SHAPED, 2, 1, 1, List.of(new ItemDescriptor.InvalidDescriptor()))); ++ tracker.updateCraftingDataList(placeholders, true); ++ assertEquals(3, tracker.playerRecipeAvailability(output, BedrockItem.empty(), context.owner(), false, true)); ++ assertEquals(3, tracker.playerRecipeAvailability(output, BedrockItem.empty(), context.owner().copy(), false, true)); ++ assertEquals(2, tracker.playerRecipeAvailability(output, output, context.owner().copy(), false, true)); ++ tracker.updateCraftingDataList(List.of(recipe(RecipeType.SHAPED, 3, 1, 1, List.of(name(1, 1)))), false); ++ context.inventory().getInventoryContainer().setItem(9, output.copy()); ++ assertEquals(1, tracker.playerRecipeAvailability(output, BedrockItem.empty(), context.owner(), false, true)); ++ assertEquals(0, tracker.playerRecipeAvailability(output, BedrockItem.empty(), context.owner(), true, true)); ++ } ++ ++ @Test + void retainedOwnersKeepFirstNameResolutionAndTagAdmission() throws Exception { + for (final ItemDescriptor late : List.of(new ItemDescriptor.ItemNameDescriptor("test:late", 0), + new ItemDescriptor.ItemTagDescriptor("test:late"))) { diff --git a/patches/viabedrock/deferred/0004-expose-bedrock-recipes-and-place-ingredients.pr.md b/patches/viabedrock/deferred/0004-expose-bedrock-recipes-and-place-ingredients.pr.md index 70eff3e0..252edc9b 100644 --- a/patches/viabedrock/deferred/0004-expose-bedrock-recipes-and-place-ingredients.pr.md +++ b/patches/viabedrock/deferred/0004-expose-bedrock-recipes-and-place-ingredients.pr.md @@ -12,6 +12,11 @@ Resolve native ingredient descriptors and connection-owned item definitions befo Regression coverage includes `NativeFurnaceOptionsTest`, `CookingRecipeAvailabilityTest`, `CookingRecipeInputsTest`, `CookingRecipeSourcesTest`, `CraftingCursorTest`, and `InventoryTransferBatchTest`. +Registered zero-slot and all-empty recipe templates are excluded before availability reads source collections. +They preserve selected-output fallback and later valid candidates without accessing retired inventory sources. +The regression fails before the guard. Existing 1.26.51.1 executable availability fixtures retain their valid-recipe results. +This defensive Java admission does not establish new 1.26.60 live availability or screen parity. + Official Bedrock 1.26.60.5/BDS 1.26.60.6 captures establish manual output pickup and Shift-click behavior for planks, cake, and honey blocks. Manual requests keep CraftResults times one, select primary output before returned containers, and use Take for cursor output. Right-click crafts once. Shift-click fills inventory through linked stack-sized requests in one packet. diff --git a/patches/viabedrock/deferred/0012-retain-bedrock-creative-catalog-entries.patch b/patches/viabedrock/deferred/0012-retain-bedrock-creative-catalog-entries.patch index 68d0d284..7d090498 100644 --- a/patches/viabedrock/deferred/0012-retain-bedrock-creative-catalog-entries.patch +++ b/patches/viabedrock/deferred/0012-retain-bedrock-creative-catalog-entries.patch @@ -1,4 +1,4 @@ -From e21e0c424e03eb727263b5cd4c765f439f819373 Mon Sep 17 00:00:00 2001 +From 05428ad254118439039699c96d13fe62acf710a4 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:37 +0200 Subject: [PATCH] feat(viabedrock): retain Bedrock creative catalog entries @@ -550,7 +550,7 @@ the inventory count failure before this guard and retains later outputs. .../api/model/container/Container.java | 4 +- .../container/NativeBundleActionSession.java | 285 +++++++ .../container/NativeBundleInputSession.java | 185 +++++ - .../model/container/NativeRecipeCounter.java | 124 +++ + .../model/container/NativeRecipeCounter.java | 117 +++ .../model/container/NativeRecipeCrafting.java | 286 +++++++ .../container/NativeRecipeDescriptor.java | 44 ++ .../model/container/NativeStoragePolicy.java | 103 +++ @@ -593,7 +593,7 @@ the inventory count failure before this guard and retains later outputs. .../NativePlayerCreativeAuxiliaryTest.java | 333 +++++++++ .../storage/NativePlayerCreativePickTest.java | 138 ++++ .../storage/NativeRecipeCraftingTest.java | 500 +++++++++++++ - 51 files changed, 8167 insertions(+), 172 deletions(-) + 51 files changed, 8160 insertions(+), 172 deletions(-) create mode 100644 src/main/java/net/raphimc/viabedrock/api/model/NativeBundleAction.java create mode 100644 src/main/java/net/raphimc/viabedrock/api/model/NativeBundleResult.java create mode 100644 src/main/java/net/raphimc/viabedrock/api/model/NativePlayerInventoryAction.java @@ -1782,10 +1782,10 @@ index 00000000..d92a02a3 + +} diff --git a/src/main/java/net/raphimc/viabedrock/api/model/container/NativeRecipeCounter.java b/src/main/java/net/raphimc/viabedrock/api/model/container/NativeRecipeCounter.java -index fe874172..73e5e7a8 100644 +index 06746721..63c3102c 100644 --- a/src/main/java/net/raphimc/viabedrock/api/model/container/NativeRecipeCounter.java +++ b/src/main/java/net/raphimc/viabedrock/api/model/container/NativeRecipeCounter.java -@@ -150,6 +150,126 @@ public final class NativeRecipeCounter { +@@ -158,6 +158,45 @@ public final class NativeRecipeCounter { return partial || selectedOutput.getAsBoolean() ? 2 : 3; } @@ -1801,10 +1801,11 @@ index fe874172..73e5e7a8 100644 + if (candidate.requiresCraftingTable && !workbench) { + continue; + } -+ if (!hasIngredient(candidate.recipe)) { ++ final int ingredientCount = candidate.recipe.ingredientCount(); ++ if (!hasIngredient(candidate.recipe, ingredientCount)) { + continue; + } -+ final var round = count(candidate.recipe, sources, descriptorHash).rounds.getFirst(); ++ final var round = count(candidate.recipe, ingredientCount, sources, descriptorHash).rounds.getFirst(); + int minimum = Integer.MAX_VALUE; + int matched = 0; + for (int position = 0; position < round.size(); position++) { @@ -1827,15 +1828,13 @@ index fe874172..73e5e7a8 100644 + return bestCount > 0 ? counted : partial; + } + -+ private static boolean hasIngredient(final Recipe recipe) { -+ for (int position = 0; position < recipe.ingredientCount(); position++) { -+ if (!recipe.emptyIngredient(position)) { -+ return true; -+ } -+ } -+ return false; -+ } -+ + private static boolean hasIngredient(final Recipe recipe, final int ingredientCount) { + for (int position = 0; position < ingredientCount; position++) { + if (!recipe.emptyIngredient(position)) { +@@ -167,6 +206,80 @@ public final class NativeRecipeCounter { + return false; + } + + /** Count outputs across candidates without spending the same source descriptor twice. */ + public static Quantity quantity(final List candidates, + final List sources, final ToIntFunction descriptorHash) { @@ -1856,10 +1855,11 @@ index fe874172..73e5e7a8 100644 + for (final Production production : NativeSort.sorted(candidates, + Comparator.comparingInt(value -> value.candidate.priority))) { + final Candidate candidate = production.candidate; -+ if (!hasIngredient(candidate.recipe)) { ++ final int ingredientCount = candidate.recipe.ingredientCount(); ++ if (!hasIngredient(candidate.recipe, ingredientCount)) { + continue; + } -+ for (final List round : count(candidate.recipe, sources, descriptorHash).rounds) { ++ for (final List round : count(candidate.recipe, ingredientCount, sources, descriptorHash).rounds) { + int crafts = Integer.MAX_VALUE; + final Map positions = new HashMap<>(); + final Map consumption = new HashMap<>(); @@ -1912,7 +1912,7 @@ index fe874172..73e5e7a8 100644 /** Output lookup owns candidate admission, concrete grid requirements and priority. */ public record Candidate(int priority, boolean requiresCraftingTable, Recipe recipe) { public Candidate { -@@ -164,6 +284,10 @@ public final class NativeRecipeCounter { +@@ -181,6 +294,10 @@ public final class NativeRecipeCounter { boolean emptyIngredient(int index); boolean matchesIngredient(int index, BedrockItem item); @@ -7738,7 +7738,7 @@ index 00000000..af54a6a1 + +} diff --git a/src/test/java/net/raphimc/viabedrock/protocol/rewriter/RegisteredPlayerRecipeAvailabilityTest.java b/src/test/java/net/raphimc/viabedrock/protocol/rewriter/RegisteredPlayerRecipeAvailabilityTest.java -index de622a23..dc9f36f4 100644 +index 7b1f3805..b8633b1a 100644 --- a/src/test/java/net/raphimc/viabedrock/protocol/rewriter/RegisteredPlayerRecipeAvailabilityTest.java +++ b/src/test/java/net/raphimc/viabedrock/protocol/rewriter/RegisteredPlayerRecipeAvailabilityTest.java @@ -19,6 +19,7 @@ import net.raphimc.viabedrock.protocol.model.PlayerAbilities; @@ -7749,7 +7749,7 @@ index de622a23..dc9f36f4 100644 import net.raphimc.viabedrock.protocol.model.BedrockItem; import net.raphimc.viabedrock.protocol.model.ItemEntry; import net.raphimc.viabedrock.protocol.model.ItemRegistry; -@@ -113,7 +114,7 @@ class RegisteredPlayerRecipeAvailabilityTest { +@@ -130,7 +131,7 @@ class RegisteredPlayerRecipeAvailabilityTest { final var context = PlayerRecipeAvailabilityTest.context(false, IDENTIFIER_BASE); final var tracker = new CraftingDataTracker(context.items().user()); final var output = item(1); @@ -7758,7 +7758,7 @@ index de622a23..dc9f36f4 100644 assertEquals(3, tracker.playerRecipeAvailability(output, BedrockItem.empty(), context.owner().copy(), false, true)); tracker.updateCraftingDataList(List.of(recipe(RecipeType.SHAPED, 1, 3, 1, List.of(name(1, 1), new ItemDescriptor.InvalidDescriptor(), new ItemDescriptor.InvalidDescriptor()))), true); -@@ -261,7 +262,7 @@ class RegisteredPlayerRecipeAvailabilityTest { +@@ -278,7 +279,7 @@ class RegisteredPlayerRecipeAvailabilityTest { abilities.getOrCreateLayer(SerializedAbilitiesData_SerializedAbilitiesLayer.Base).setAbility(AbilitiesIndex.Instabuild, false); when(rules.getGameRule("recipesUnlock")).thenReturn(false); // The disabled rule skips context production and the invalid output skips source capture. From 9c79f7910eb7c169251d23df752437474a01222b Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Thu, 5 Nov 2026 20:30:41 +0100 Subject: [PATCH 007/141] fix(hud): honor death message visibility Apply showDeathMessages to cached and late death reasons while preserving death admission and retained text. Refresh dead players when the rule changes. Native 1.26.60.5 query execution establishes the visibility policy. Keep the independent fix applicable to pinned upstream and replay later patch contexts without changing their resulting source. Native OreUI layout and lifecycle remain unfinished. --- docs/bedrock-coverage.md | 2 +- ...edrock-actor-events-to-java-statuses.patch | 18 +- ...erver-authoritative-bedrock-requests.patch | 29 +- ...edrock-recipes-and-place-ingredients.patch | 8 +- patches/viabedrock/series.json | 24 ++ ...rse-properties-and-composed-metadata.patch | 20 +- ...suppress-redundant-pick-item-overlay.patch | 10 +- ...0007-forward-local-game-mode-updates.patch | 4 +- .../0008-discard-stale-passenger-links.patch | 10 +- ...13-translate-remote-player-locations.patch | 4 +- ...lize-and-preserve-bedrock-clock-time.patch | 8 +- ...reserve-sparse-actor-spatial-updates.patch | 18 +- ...rd-predicted-boat-and-horse-movement.patch | 18 +- ...6-remove-score-ids-across-objectives.patch | 6 +- ...43-load-and-apply-editable-gamerules.patch | 6 +- ...-encode-current-bedrock-skin-packets.patch | 8 +- ...debar-title-images-and-styled-scores.patch | 4 +- ...port-native-actor-animation-commands.patch | 8 +- ...n-completed-client-prediction-frames.patch | 18 +- ...nize-authoritative-entity-references.patch | 10 +- ...rate-authored-native-ui-presentation.patch | 4 +- ...are-native-ui-programs-and-resources.patch | 6 +- ...removals-for-absent-unmapped-effects.patch | 6 +- ...ansport-remote-player-movement-modes.patch | 8 +- .../0111-honor-death-message-visibility.patch | 334 ++++++++++++++++++ .../0111-honor-death-message-visibility.pr.md | 9 + 26 files changed, 487 insertions(+), 113 deletions(-) create mode 100644 patches/viabedrock/upstreamable/0111-honor-death-message-visibility.patch create mode 100644 patches/viabedrock/upstreamable/0111-honor-death-message-visibility.pr.md diff --git a/docs/bedrock-coverage.md b/docs/bedrock-coverage.md index e5bfd870..8db0c72d 100644 --- a/docs/bedrock-coverage.md +++ b/docs/bedrock-coverage.md @@ -25,7 +25,7 @@ No complete area has verified Bedrock parity. | Protocol 2225 | Updated packet layouts, explicit enum values, stonecutter notifications, cursor drags, skin identity, and sound unions. | Block seating, signed content playback, matchmaking UI, and environment layers remain partial or unsupported. | | World resources | World base-game version selects vanilla metadata, textures, actor graphs, and audio before server overrides. The context survives ViaProxy. Server legacy biome bindings retain unqualified names across versions. | Native current/older/current cat, sound, and remote biome-name controls cover selected paths. Broader pose and audio behavior need comparisons. | | Held block placement | Retained stationary build paths, next-cell ray selection, independent fresh block/air admission, frame retries after rejection, use timing, trapdoor orientation, cancellation, and prediction. | Native, direct, and proxy controls cover selected stationary paths, first rejection, last-stack placement, activation, re-aiming, and release. Moving placement, air/entity dispatch, build-height limits, and broader input scheduling remain open. | -| Fonts and UI | Native bitmap metrics and italic geometry, authored scenes, TrueType cache and atlas sampling, and primary font aliases. | Selected bitmap and TrueType controls match at 1280 × 720. [Validation and limits](../patches/viafabricplus-bedrock/upstreamable/0029-share-native-ui-scene-rendering.pr.md). Complete screens, bold and shadowed text, other font families, mixed or scaled aliases, and broader viewports remain open. | +| Fonts and UI | Native bitmap metrics and italic geometry, authored scenes, TrueType cache and atlas sampling, and primary font aliases. [Death reasons follow the visibility game rule](../patches/viabedrock/upstreamable/0111-honor-death-message-visibility.pr.md). | Selected bitmap and TrueType controls match at 1280 × 720. [Validation and limits](../patches/viafabricplus-bedrock/upstreamable/0029-share-native-ui-scene-rendering.pr.md). Complete screens, bold and shadowed text, other font families, mixed or scaled aliases, and broader viewports remain open. | | Player inventory and crafting | Opening ownership, recipe catalogs, Creative catalog actions, Survival bulk and manual crafting actions, retained recipe queries, compressed snapshots, local Help, inactive item lighting, native storage weights, desktop bundle insertion/default extraction, and native stack identities. | Complete E-key/workbench presentation, bundle controller and tooltip lifetimes, query lifetime across connections, broader Survival recipes, and original live bug reports remain unresolved. See the [inventory reference](native-player-inventory.md). | | Food use | Native components govern admission and Java food/consumable properties, with Creative and Peaceful exceptions. Local use retains native item identity across Java carriers. | Selected admission, input-deadline, timed-food, held-mode, release, and pause controls pass. Short input edges during active Java use, broader use cycles, and the original intermittent report remain open. See the [food-use evidence](../patches/viabedrock/deferred/0008-signal-sustained-bedrock-item-use.pr.md). | | Cooking | Snapshot-bound recipe actions, transfer batches, preferences, search, and progress. | Default cooking screens retain ordinary controls until authored bindings are complete. See the [cooking reference](native-cooking-actions.md). | diff --git a/patches/viabedrock/deferred/0001-translate-bedrock-actor-events-to-java-statuses.patch b/patches/viabedrock/deferred/0001-translate-bedrock-actor-events-to-java-statuses.patch index b3edc79e..cdea4b67 100644 --- a/patches/viabedrock/deferred/0001-translate-bedrock-actor-events-to-java-statuses.patch +++ b/patches/viabedrock/deferred/0001-translate-bedrock-actor-events-to-java-statuses.patch @@ -1,4 +1,4 @@ -From ceae6155574f1147d94a6e814a2bf37c40dfe82e Mon Sep 17 00:00:00 2001 +From 43b813832a3a2dc09cccb7f23f0af08aca64d20b Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:37 +0200 Subject: [PATCH] feat(entity-events): translate Bedrock actor events to Java @@ -327,7 +327,7 @@ index a4b3ec09..ae173e3f 100644 levelSoundEvent.send(BedrockProtocol.class, false); } diff --git a/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java b/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java -index 6f65acbc..b91f27d8 100644 +index dde3d4b1..39d02453 100644 --- a/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java +++ b/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java @@ -41,6 +41,7 @@ import net.raphimc.viabedrock.api.model.ActorMotionCommand; @@ -338,8 +338,8 @@ index 6f65acbc..b91f27d8 100644 import net.raphimc.viabedrock.api.model.entity.ClientPlayerEntity; import net.raphimc.viabedrock.api.model.entity.CustomEntity; import net.raphimc.viabedrock.api.model.entity.Entity; -@@ -52,10 +53,12 @@ import net.raphimc.viabedrock.api.util.RegistryUtil; - import net.raphimc.viabedrock.api.util.TextUtil; +@@ -51,10 +52,12 @@ import net.raphimc.viabedrock.api.util.PacketFactory; + import net.raphimc.viabedrock.api.util.RegistryUtil; import net.raphimc.viabedrock.protocol.BedrockProtocol; import net.raphimc.viabedrock.protocol.ClientboundBedrockPackets; +import net.raphimc.viabedrock.protocol.data.BedrockMappingData; @@ -351,7 +351,7 @@ index 6f65acbc..b91f27d8 100644 import net.raphimc.viabedrock.protocol.data.enums.java.Relative; import net.raphimc.viabedrock.protocol.data.enums.java.generated.EquipmentSlot; import net.raphimc.viabedrock.protocol.data.generated.java.EntityDataFields; -@@ -64,6 +67,9 @@ import net.raphimc.viabedrock.protocol.model.*; +@@ -63,6 +66,9 @@ import net.raphimc.viabedrock.protocol.model.*; import net.raphimc.viabedrock.protocol.rewriter.EntityMetadataRewriter; import net.raphimc.viabedrock.protocol.rewriter.ItemRewriter; import net.raphimc.viabedrock.protocol.storage.EntityPropertyTracker; @@ -361,7 +361,7 @@ index 6f65acbc..b91f27d8 100644 import net.raphimc.viabedrock.protocol.storage.ChannelStorage; import net.raphimc.viabedrock.protocol.storage.EntityTracker; import net.raphimc.viabedrock.protocol.storage.PlayerPredictionStorage; -@@ -74,6 +80,7 @@ import net.raphimc.viabedrock.protocol.types.BedrockTypes; +@@ -73,6 +79,7 @@ import net.raphimc.viabedrock.protocol.types.BedrockTypes; import java.util.ArrayList; import java.util.List; @@ -369,7 +369,7 @@ index 6f65acbc..b91f27d8 100644 import java.util.logging.Level; public final class EntityPackets { -@@ -512,6 +519,43 @@ public final class EntityPackets { +@@ -511,6 +518,43 @@ public final class EntityPackets { hurt.send(BedrockProtocol.class); } switch (event) { @@ -413,7 +413,7 @@ index 6f65acbc..b91f27d8 100644 case HURT -> { final CompoundTag damageTypeRegistry = gameSession.getJavaRegistries().getCompoundTag(RegistryKeys.DAMAGE_TYPE); final SharedTypes_Legacy_ActorDamageCause damageCause = SharedTypes_Legacy_ActorDamageCause.getByValue(data, SharedTypes_Legacy_ActorDamageCause.Override); -@@ -541,12 +585,17 @@ public final class EntityPackets { +@@ -537,12 +581,17 @@ public final class EntityPackets { } if (entity != entityTracker.getClientPlayer()) { entity.playSound("death"); @@ -434,7 +434,7 @@ index 6f65acbc..b91f27d8 100644 } } }); -@@ -794,6 +843,34 @@ public final class EntityPackets { +@@ -790,6 +839,34 @@ public final class EntityPackets { return entity; } diff --git a/patches/viabedrock/deferred/0003-translate-server-authoritative-bedrock-requests.patch b/patches/viabedrock/deferred/0003-translate-server-authoritative-bedrock-requests.patch index a45b7518..027986a1 100644 --- a/patches/viabedrock/deferred/0003-translate-server-authoritative-bedrock-requests.patch +++ b/patches/viabedrock/deferred/0003-translate-server-authoritative-bedrock-requests.patch @@ -1,4 +1,4 @@ -From 3d4d74b5f6e933e1a0f4f0525bfec56b8f199bd3 Mon Sep 17 00:00:00 2001 +From 2d95d953670b96d2deac15949896e79f442f8b34 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:37 +0200 Subject: [PATCH] feat(inventory): translate server-authoritative Bedrock @@ -6352,7 +6352,7 @@ index f8282f25..c0b6e452 100644 public ItemUseModifiers useModifiers() { diff --git a/src/main/java/net/raphimc/viabedrock/api/util/PacketFactory.java b/src/main/java/net/raphimc/viabedrock/api/util/PacketFactory.java -index c51ffd40..642d5f28 100644 +index f9fec82b..b7579f0f 100644 --- a/src/main/java/net/raphimc/viabedrock/api/util/PacketFactory.java +++ b/src/main/java/net/raphimc/viabedrock/api/util/PacketFactory.java @@ -21,6 +21,7 @@ import com.viaversion.nbt.tag.Tag; @@ -6373,7 +6373,7 @@ index c51ffd40..642d5f28 100644 import net.raphimc.viabedrock.api.model.entity.Entity; import net.raphimc.viabedrock.protocol.BedrockProtocol; import net.raphimc.viabedrock.protocol.ServerboundBedrockPackets; -@@ -39,13 +42,25 @@ import net.raphimc.viabedrock.protocol.data.enums.java.EntityEvent; +@@ -39,11 +42,18 @@ import net.raphimc.viabedrock.protocol.data.enums.java.EntityEvent; import net.raphimc.viabedrock.protocol.data.enums.java.GameEventType; import net.raphimc.viabedrock.protocol.data.enums.java.generated.CustomChatCompletionsAction; import net.raphimc.viabedrock.protocol.model.Position3f; @@ -6381,6 +6381,9 @@ index c51ffd40..642d5f28 100644 +import net.raphimc.viabedrock.protocol.rewriter.ItemRewriter; +import net.raphimc.viabedrock.protocol.storage.InventoryRequestTracker; import net.raphimc.viabedrock.protocol.storage.InventoryTracker; + import net.raphimc.viabedrock.protocol.storage.EntityTracker; + import net.raphimc.viabedrock.protocol.storage.GameRulesStorage; + import net.raphimc.viabedrock.protocol.storage.GameSessionStorage; import net.raphimc.viabedrock.protocol.types.BedrockTypes; +import net.raphimc.viabedrock.protocol.types.array.ArrayType; +import net.raphimc.viabedrock.protocol.types.inventory.ItemStackRequestType; @@ -6389,6 +6392,10 @@ index c51ffd40..642d5f28 100644 public final class PacketFactory { +@@ -60,8 +70,13 @@ public final class PacketFactory { + death.send(BedrockProtocol.class); + } + - public static void sendJavaBlockDestroyProgress(final UserConnection user, final int id, final BlockPosition position, final int stage) { - final PacketWrapper blockDestruction = PacketWrapper.create(ClientboundPackets26_3.BLOCK_DESTRUCTION, user); + public static void sendJavaBlockDestroyProgress( @@ -6401,7 +6408,7 @@ index c51ffd40..642d5f28 100644 blockDestruction.write(Types.VAR_INT, id); // id blockDestruction.write(Types.BLOCK_POSITION1_14, position); // position blockDestruction.write(Types.BYTE, (byte) stage); // destroy stage -@@ -53,89 +68,175 @@ public final class PacketFactory { +@@ -69,89 +84,175 @@ public final class PacketFactory { } public static void sendJavaSystemChat(final UserConnection user, final Tag message) { @@ -6465,10 +6472,8 @@ index c51ffd40..642d5f28 100644 + containerSetContent.send(BedrockProtocol.class); + sendJavaCursorForHudUpdate(user, container); + } - } - -- public static void sendJavaGameEvent(final UserConnection user, final GameEventType event, final float value) { -- final PacketWrapper gameEvent = PacketWrapper.create(ClientboundPackets26_3.GAME_EVENT, user); ++ } ++ + public static void sendJavaCursorForHudUpdate(final UserConnection user, final Container container) { + final InventoryTracker tracker = user.get(InventoryTracker.class); + if (container != tracker.getHudContainer() || tracker.getCurrentContainer() == null @@ -6494,8 +6499,10 @@ index c51ffd40..642d5f28 100644 + final PacketWrapper packet = PacketWrapper.create(ServerboundBedrockPackets.ITEM_STACK_REQUEST, user); + packet.write(new ArrayType<>(new ItemStackRequestType(user.get(ItemRewriter.class).stackRequestResultType()), BedrockTypes.UNSIGNED_VAR_INT), requests); + packet.sendToServer(BedrockProtocol.class); -+ } -+ + } + +- public static void sendJavaGameEvent(final UserConnection user, final GameEventType event, final float value) { +- final PacketWrapper gameEvent = PacketWrapper.create(ClientboundPackets26_3.GAME_EVENT, user); + public static void sendJavaGameEvent( + final UserConnection user, final GameEventType event, final float value) { + final PacketWrapper gameEvent = @@ -6600,7 +6607,7 @@ index c51ffd40..642d5f28 100644 loadingScreen.write(BedrockTypes.VAR_INT, type.getValue()); // type loadingScreen.write(Types.BOOLEAN, loadingScreenId != null); // has loading screen id if (loadingScreenId != null) { -@@ -146,20 +247,53 @@ public final class PacketFactory { +@@ -162,20 +263,53 @@ public final class PacketFactory { public static void writeJavaDisconnect(final PacketWrapper wrapper, final String reason) { switch (wrapper.getPacketType().state()) { diff --git a/patches/viabedrock/deferred/0004-expose-bedrock-recipes-and-place-ingredients.patch b/patches/viabedrock/deferred/0004-expose-bedrock-recipes-and-place-ingredients.patch index 48a5725e..957258ca 100644 --- a/patches/viabedrock/deferred/0004-expose-bedrock-recipes-and-place-ingredients.patch +++ b/patches/viabedrock/deferred/0004-expose-bedrock-recipes-and-place-ingredients.patch @@ -1,4 +1,4 @@ -From 8f76eb1a8a382f8ac874716f4f17ff8892230d92 Mon Sep 17 00:00:00 2001 +From d0390f10093244e80a78227bee5d7b24d65cd199 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:37 +0200 Subject: [PATCH] feat(crafting): expose Bedrock recipes and place ingredients @@ -3286,7 +3286,7 @@ index 84d17e3b..0e8f147a 100644 } diff --git a/src/main/java/net/raphimc/viabedrock/api/util/PacketFactory.java b/src/main/java/net/raphimc/viabedrock/api/util/PacketFactory.java -index 642d5f28..b1f82a80 100644 +index b7579f0f..b2ddd19c 100644 --- a/src/main/java/net/raphimc/viabedrock/api/util/PacketFactory.java +++ b/src/main/java/net/raphimc/viabedrock/api/util/PacketFactory.java @@ -44,6 +44,7 @@ import net.raphimc.viabedrock.protocol.data.enums.java.generated.CustomChatCompl @@ -3296,8 +3296,8 @@ index 642d5f28..b1f82a80 100644 +import net.raphimc.viabedrock.protocol.storage.NativeCookingUiState; import net.raphimc.viabedrock.protocol.storage.InventoryRequestTracker; import net.raphimc.viabedrock.protocol.storage.InventoryTracker; - import net.raphimc.viabedrock.protocol.types.BedrockTypes; -@@ -119,6 +120,7 @@ public final class PacketFactory { + import net.raphimc.viabedrock.protocol.storage.EntityTracker; +@@ -135,6 +136,7 @@ public final class PacketFactory { if (!containerSetContent.isCancelled()) { containerSetContent.send(BedrockProtocol.class); sendJavaCursorForHudUpdate(user, container); diff --git a/patches/viabedrock/series.json b/patches/viabedrock/series.json index 30e8982b..2ebef7f3 100644 --- a/patches/viabedrock/series.json +++ b/patches/viabedrock/series.json @@ -7,6 +7,30 @@ "file": "0001-update-bedrock-to-1-26-60.patch", "title": "fix(protocol): complete Bedrock 1.26.60 compatibility" }, + { + "file": "0111-honor-death-message-visibility.patch", + "title": "fix(hud): honor death message visibility", + "prerequisites": [ + { + "project": "viabedrock", + "patch": "0001-update-bedrock-to-1-26-60.patch", + "reason": "Provide the local JVM test configuration.", + "testOnly": true + }, + { + "project": "viabedrock", + "patch": "0015-transport-native-resources-and-sound-commands.patch", + "reason": "Provide Mockito for the local packet regression.", + "testOnly": true + }, + { + "project": "viabedrock", + "patch": "0092-retain-completed-client-prediction-frames.patch", + "reason": "Install the local Mockito test agent.", + "testOnly": true + } + ] + }, { "file": "0109-keep-resource-image-codecs-in-memory.patch", "title": "fix(resources): keep resource image codecs in memory" diff --git a/patches/viabedrock/upstreamable/0002-preserve-sparse-properties-and-composed-metadata.patch b/patches/viabedrock/upstreamable/0002-preserve-sparse-properties-and-composed-metadata.patch index 8ffae213..85da375c 100644 --- a/patches/viabedrock/upstreamable/0002-preserve-sparse-properties-and-composed-metadata.patch +++ b/patches/viabedrock/upstreamable/0002-preserve-sparse-properties-and-composed-metadata.patch @@ -1,4 +1,4 @@ -From 647da9bd992fdb8fccca1c9380a95539847c78c9 Mon Sep 17 00:00:00 2001 +From f68dcefa530e4f0ceffd7908cc6467266a15f5c3 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:37 +0200 Subject: [PATCH] fix(metadata): preserve sparse properties and composed actor @@ -560,7 +560,7 @@ index b98af526..3514995f 100644 CRAWLING(114), TIMER_FLAG_1(115), diff --git a/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java b/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java -index 9c4b8cec..1c23b759 100644 +index 8a8c6f74..d6ea7d51 100644 --- a/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java +++ b/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java @@ -20,9 +20,6 @@ package net.raphimc.viabedrock.protocol.packet; @@ -591,7 +591,7 @@ index 9c4b8cec..1c23b759 100644 import net.raphimc.viabedrock.api.resourcepack.definition.EntityDefinitions; import net.raphimc.viabedrock.api.util.MathUtil; import net.raphimc.viabedrock.api.util.PacketFactory; -@@ -55,6 +52,7 @@ import net.raphimc.viabedrock.api.util.TextUtil; +@@ -54,6 +51,7 @@ import net.raphimc.viabedrock.api.util.RegistryUtil; import net.raphimc.viabedrock.protocol.BedrockProtocol; import net.raphimc.viabedrock.protocol.ClientboundBedrockPackets; import net.raphimc.viabedrock.protocol.data.enums.Direction; @@ -599,7 +599,7 @@ index 9c4b8cec..1c23b759 100644 import net.raphimc.viabedrock.protocol.data.enums.bedrock.generated.*; import net.raphimc.viabedrock.protocol.data.enums.java.AnimateAction; import net.raphimc.viabedrock.protocol.data.enums.java.Relative; -@@ -62,6 +60,7 @@ import net.raphimc.viabedrock.protocol.data.enums.java.generated.EquipmentSlot; +@@ -61,6 +59,7 @@ import net.raphimc.viabedrock.protocol.data.enums.java.generated.EquipmentSlot; import net.raphimc.viabedrock.protocol.data.generated.java.EntityDataFields; import net.raphimc.viabedrock.protocol.data.generated.java.RegistryKeys; import net.raphimc.viabedrock.protocol.model.*; @@ -607,7 +607,7 @@ index 9c4b8cec..1c23b759 100644 import net.raphimc.viabedrock.protocol.rewriter.ItemRewriter; import net.raphimc.viabedrock.protocol.storage.EntityPropertyTracker; import net.raphimc.viabedrock.protocol.storage.ChannelStorage; -@@ -81,6 +80,12 @@ public final class EntityPackets { +@@ -80,6 +79,12 @@ public final class EntityPackets { private static final int INTERPOLATION_STEP_TICKS = 3; private static final float PAINTING_POS_OFFSET = -0.46875F; private static final SwingAnimation DEFAULT_SWING_ANIMATION = new SwingAnimation(EnumTypes.SWING_ANIMATION1_21_11.idFromName("whack"), 6); @@ -620,7 +620,7 @@ index 9c4b8cec..1c23b759 100644 static Position3f mergePosition(final Position3f current, final boolean hasX, final float x, final boolean hasY, final float y, final boolean hasZ, final float z) { -@@ -144,7 +149,7 @@ public final class EntityPackets { +@@ -143,7 +148,7 @@ public final class EntityPackets { attributes[i] = new EntityAttribute(name, currentValue, minValue, maxValue); } final EntityData[] entityData = wrapper.read(BedrockTypes.ENTITY_DATA_ARRAY); // entity data @@ -629,7 +629,7 @@ index 9c4b8cec..1c23b759 100644 final EntityLink[] links = wrapper.read(BedrockTypes.ENTITY_LINK_ARRAY); // entity links wrapper.read(BedrockTypes.OPTIONAL_PASSENGER_OF_BLOCKS); // passenger of blocks -@@ -193,7 +198,7 @@ public final class EntityPackets { +@@ -192,7 +197,7 @@ public final class EntityPackets { if (entity instanceof LivingEntity livingEntity) { livingEntity.updateAttributes(attributes); } @@ -638,7 +638,7 @@ index 9c4b8cec..1c23b759 100644 for (EntityLink link : links) { entityTracker.applyEntityLink(link); } -@@ -596,53 +601,6 @@ public final class EntityPackets { +@@ -592,53 +597,6 @@ public final class EntityPackets { wrapper.write(Types.VAR_INT, entity.javaId()); // entity id wrapper.write(VersionedTypes.V26_3.entityDataList, javaEntityData); // entity data }); @@ -692,7 +692,7 @@ index 9c4b8cec..1c23b759 100644 protocol.registerClientbound(ClientboundBedrockPackets.MOB_EFFECT, ClientboundPackets26_3.UPDATE_MOB_EFFECT, wrapper -> { final long entityRuntimeId = wrapper.read(BedrockTypes.UNSIGNED_VAR_LONG); // entity runtime id final MobEffectPacketPayload_Event event = MobEffectPacketPayload_Event.getByValue(wrapper.read(Types.BYTE), MobEffectPacketPayload_Event.Invalid); // event id -@@ -747,8 +705,18 @@ public final class EntityPackets { +@@ -743,8 +701,18 @@ public final class EntityPackets { wrapper.write(Types.BYTE, (byte) EquipmentSlot.MAINHAND.ordinal()); // slot wrapper.write(VersionedTypes.V26_3.item, itemRewriter.javaItem(item)); // item } else if (containerId == ContainerID.CONTAINER_ID_OFFHAND.getValue()) { @@ -711,7 +711,7 @@ index 9c4b8cec..1c23b759 100644 } else { wrapper.cancel(); } -@@ -768,6 +736,10 @@ public final class EntityPackets { +@@ -764,6 +732,10 @@ public final class EntityPackets { wrapper.write(Types.VAR_INT, collectorEntity.javaId()); // collector entity id wrapper.write(Types.VAR_INT, 0); // amount }); diff --git a/patches/viabedrock/upstreamable/0005-suppress-redundant-pick-item-overlay.patch b/patches/viabedrock/upstreamable/0005-suppress-redundant-pick-item-overlay.patch index 18a7927b..aec299d9 100644 --- a/patches/viabedrock/upstreamable/0005-suppress-redundant-pick-item-overlay.patch +++ b/patches/viabedrock/upstreamable/0005-suppress-redundant-pick-item-overlay.patch @@ -1,4 +1,4 @@ -From 0d6e4cab95eeada276ea3aea8ea52e8dfe40df6a Mon Sep 17 00:00:00 2001 +From e94ec8c5b1f8eea12f2e8081e05084e2f20f01ad Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:33 +0200 Subject: [PATCH] fix(input): suppress redundant pick-item overlay @@ -16,12 +16,12 @@ server-selected hotbar slot translation. 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java b/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java -index 1e094252..82795b85 100644 +index 4b40f209..a8721512 100644 --- a/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java +++ b/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java -@@ -451,18 +451,6 @@ public final class HudPackets { - playerCombatKill.send(BedrockProtocol.class); - } +@@ -445,18 +445,6 @@ public final class HudPackets { + gameSession.setDeathMessage(TextUtil.stringToTextComponent(BedrockTranslator.translate(message, translator, parameters))); + PacketFactory.sendJavaDeathMessage(wrapper.user()); }); - protocol.registerClientbound(ClientboundBedrockPackets.GUI_DATA_PICK_ITEM, ClientboundPackets26_3.SYSTEM_CHAT, wrapper -> { - final String itemName = wrapper.read(BedrockTypes.STRING); // item name diff --git a/patches/viabedrock/upstreamable/0007-forward-local-game-mode-updates.patch b/patches/viabedrock/upstreamable/0007-forward-local-game-mode-updates.patch index 47799601..6fa544c9 100644 --- a/patches/viabedrock/upstreamable/0007-forward-local-game-mode-updates.patch +++ b/patches/viabedrock/upstreamable/0007-forward-local-game-mode-updates.patch @@ -1,4 +1,4 @@ -From 436fec0895e3d98387414cbbde8d21d3d0ca690c Mon Sep 17 00:00:00 2001 +From d85896f802b7cd9c8029442872ff2d8c39e0b7a5 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:33 +0200 Subject: [PATCH] fix(players): refresh local and inherited game modes @@ -95,7 +95,7 @@ index 6197d5f0..65919a9d 100644 } }); diff --git a/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java b/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java -index 82795b85..eea1b264 100644 +index a8721512..7635a534 100644 --- a/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java +++ b/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java @@ -143,12 +143,15 @@ public final class HudPackets { diff --git a/patches/viabedrock/upstreamable/0008-discard-stale-passenger-links.patch b/patches/viabedrock/upstreamable/0008-discard-stale-passenger-links.patch index 4b842d69..c44f99c1 100644 --- a/patches/viabedrock/upstreamable/0008-discard-stale-passenger-links.patch +++ b/patches/viabedrock/upstreamable/0008-discard-stale-passenger-links.patch @@ -1,4 +1,4 @@ -From e91a6b486f4aac421ec8c79679707f2e7bbce523 Mon Sep 17 00:00:00 2001 +From e81e529e1a38a0038623fd3b34f8f88a0ac5936a Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:33 +0200 Subject: [PATCH] fix(entities): resolve and order Bedrock passenger links @@ -148,10 +148,10 @@ index 0ae576c6..9909bd21 100644 setPassengersPacket.send(BedrockProtocol.class); } diff --git a/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java b/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java -index 39c8bf5f..c45e07d7 100644 +index 63b06dce..11e82603 100644 --- a/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java +++ b/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java -@@ -95,7 +95,7 @@ public final class EntityPackets { +@@ -94,7 +94,7 @@ public final class EntityPackets { } final EntityData[] entityData = wrapper.read(BedrockTypes.ENTITY_DATA_ARRAY); // entity data wrapper.read(BedrockTypes.ENTITY_PROPERTIES); // entity properties @@ -160,7 +160,7 @@ index 39c8bf5f..c45e07d7 100644 wrapper.read(BedrockTypes.OPTIONAL_PASSENGER_OF_BLOCKS); // passenger of blocks final Entity entity; -@@ -143,6 +143,10 @@ public final class EntityPackets { +@@ -142,6 +142,10 @@ public final class EntityPackets { livingEntity.updateAttributes(attributes); } entity.updateEntityData(entityData); @@ -171,7 +171,7 @@ index 39c8bf5f..c45e07d7 100644 }); protocol.registerClientbound(ClientboundBedrockPackets.ADD_ITEM_ENTITY, ClientboundPackets26_3.ADD_ENTITY, wrapper -> { final EntityTracker entityTracker = wrapper.user().get(EntityTracker.class); -@@ -353,9 +357,15 @@ public final class EntityPackets { +@@ -352,9 +356,15 @@ public final class EntityPackets { wrapper.cancel(); return; } diff --git a/patches/viabedrock/upstreamable/0013-translate-remote-player-locations.patch b/patches/viabedrock/upstreamable/0013-translate-remote-player-locations.patch index a03e740c..bcfad147 100644 --- a/patches/viabedrock/upstreamable/0013-translate-remote-player-locations.patch +++ b/patches/viabedrock/upstreamable/0013-translate-remote-player-locations.patch @@ -1,4 +1,4 @@ -From a282442dec513ccd726c3fa76d86e0f24165877e Mon Sep 17 00:00:00 2001 +From 6a8ee278d4bab9fb507904ed0d873e3648b78d72 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:34 +0200 Subject: [PATCH] feat(hud): preserve native Bedrock locator updates @@ -341,7 +341,7 @@ index 00000000..52b621c8 + +} diff --git a/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java b/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java -index eea1b264..80c97cff 100644 +index 7635a534..bb514a85 100644 --- a/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java +++ b/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java @@ -129,6 +129,7 @@ public final class HudPackets { diff --git a/patches/viabedrock/upstreamable/0014-initialize-and-preserve-bedrock-clock-time.patch b/patches/viabedrock/upstreamable/0014-initialize-and-preserve-bedrock-clock-time.patch index ef2e1d4f..6662972c 100644 --- a/patches/viabedrock/upstreamable/0014-initialize-and-preserve-bedrock-clock-time.patch +++ b/patches/viabedrock/upstreamable/0014-initialize-and-preserve-bedrock-clock-time.patch @@ -1,4 +1,4 @@ -From 562ea4fb7abc4e46ccfeb28f8fec7bac7be0a6aa Mon Sep 17 00:00:00 2001 +From 7b2afbc5ae0f74e9c4e6810dac4c3c4328373c52 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:34 +0200 Subject: [PATCH] fix(world): preserve native Bedrock world clock semantics @@ -292,7 +292,7 @@ index 5f991ba9..5f2db346 100644 + } diff --git a/src/main/java/net/raphimc/viabedrock/protocol/storage/GameRulesStorage.java b/src/main/java/net/raphimc/viabedrock/protocol/storage/GameRulesStorage.java -index 9dcfed5b..e1c58934 100644 +index 9d4df851..8285bf9c 100644 --- a/src/main/java/net/raphimc/viabedrock/protocol/storage/GameRulesStorage.java +++ b/src/main/java/net/raphimc/viabedrock/protocol/storage/GameRulesStorage.java @@ -27,6 +27,7 @@ import net.raphimc.viabedrock.protocol.BedrockProtocol; @@ -303,8 +303,8 @@ index 9dcfed5b..e1c58934 100644 import java.util.HashMap; import java.util.Locale; -@@ -63,6 +64,8 @@ public class GameRulesStorage extends StoredObject { - PacketFactory.sendJavaGameEvent(this.user(), GameEventType.IMMEDIATE_RESPAWN, (boolean) gameRule.value() ? 1F : 0F); +@@ -65,6 +66,8 @@ public class GameRulesStorage extends StoredObject { + PacketFactory.sendJavaDeathMessage(this.user()); } else if (gameRule.name().equalsIgnoreCase("doLimitedCrafting")) { PacketFactory.sendJavaGameEvent(this.user(), GameEventType.LIMITED_CRAFTING, (boolean) gameRule.value() ? 1F : 0F); + } else if (gameRule.name().equalsIgnoreCase("doDayLightCycle")) { diff --git a/patches/viabedrock/upstreamable/0018-preserve-sparse-actor-spatial-updates.patch b/patches/viabedrock/upstreamable/0018-preserve-sparse-actor-spatial-updates.patch index 53e0471f..aa4429db 100644 --- a/patches/viabedrock/upstreamable/0018-preserve-sparse-actor-spatial-updates.patch +++ b/patches/viabedrock/upstreamable/0018-preserve-sparse-actor-spatial-updates.patch @@ -1,4 +1,4 @@ -From e77122ebb64527652a49f91e7bf97707b50360d7 Mon Sep 17 00:00:00 2001 +From 4a82a8af711a6a19cf750d1fc089979e9b97104e Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:34 +0200 Subject: [PATCH] fix(entities): preserve Bedrock spatial updates @@ -157,10 +157,10 @@ index 5addfdbd..163c5267 100644 return this.onGround; } diff --git a/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java b/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java -index c45e07d7..34e1466f 100644 +index 11e82603..74d910d8 100644 --- a/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java +++ b/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java -@@ -73,6 +73,11 @@ public final class EntityPackets { +@@ -72,6 +72,11 @@ public final class EntityPackets { private static final float PAINTING_POS_OFFSET = -0.46875F; private static final SwingAnimation DEFAULT_SWING_ANIMATION = new SwingAnimation(EnumTypes.SWING_ANIMATION1_21_11.idFromName("whack"), 6); @@ -172,7 +172,7 @@ index c45e07d7..34e1466f 100644 public static void register(final BedrockProtocol protocol) { protocol.registerClientbound(ClientboundBedrockPackets.ADD_ENTITY, ClientboundPackets26_3.ADD_ENTITY, wrapper -> { final EntityTracker entityTracker = wrapper.user().get(EntityTracker.class); -@@ -204,6 +209,8 @@ public final class EntityPackets { +@@ -203,6 +208,8 @@ public final class EntityPackets { return; } @@ -181,7 +181,7 @@ index c45e07d7..34e1466f 100644 if (entity == entityTracker.getClientPlayer()) { if (!teleported && !forceMoveLocalEntity) { wrapper.cancel(); -@@ -265,8 +272,8 @@ public final class EntityPackets { +@@ -264,8 +271,8 @@ public final class EntityPackets { final boolean onGround = wrapper.read(Types.BOOLEAN); final boolean teleported = wrapper.read(Types.BOOLEAN); // If the position shouldn't be interpolated final boolean forceMoveLocalEntity = wrapper.read(Types.BOOLEAN); @@ -192,7 +192,7 @@ index c45e07d7..34e1466f 100644 final Entity entity = entityTracker.getEntityByRid(entityRuntimeId); if (entity == null) { -@@ -274,14 +281,23 @@ public final class EntityPackets { +@@ -273,14 +280,23 @@ public final class EntityPackets { return; } @@ -219,7 +219,7 @@ index c45e07d7..34e1466f 100644 if (teleported) { wrapper.setPacketType(ClientboundPackets26_3.PLAYER_POSITION); entityTracker.getClientPlayer().writePlayerPositionPacketToClient(wrapper, Relative.union(Relative.ROTATION, Relative.VELOCITY), true); -@@ -300,23 +316,9 @@ public final class EntityPackets { +@@ -299,23 +315,9 @@ public final class EntityPackets { return; } @@ -245,7 +245,7 @@ index c45e07d7..34e1466f 100644 PacketFactory.sendJavaRotateHead(wrapper.user(), entity); } entity.setOnGround(onGround); -@@ -333,20 +335,19 @@ public final class EntityPackets { +@@ -332,20 +334,19 @@ public final class EntityPackets { wrapper.write(Types.BOOLEAN, entity.isOnGround()); // on ground }); protocol.registerClientbound(ClientboundBedrockPackets.SET_ENTITY_MOTION, ClientboundPackets26_3.SET_ENTITY_MOTION, wrapper -> { @@ -275,7 +275,7 @@ index c45e07d7..34e1466f 100644 }); protocol.registerClientbound(ClientboundBedrockPackets.REMOVE_ENTITY, ClientboundPackets26_3.REMOVE_ENTITIES, wrapper -> { final EntityTracker entityTracker = wrapper.user().get(EntityTracker.class); -@@ -707,6 +708,17 @@ public final class EntityPackets { +@@ -703,6 +704,17 @@ public final class EntityPackets { }); } diff --git a/patches/viabedrock/upstreamable/0020-forward-predicted-boat-and-horse-movement.patch b/patches/viabedrock/upstreamable/0020-forward-predicted-boat-and-horse-movement.patch index bf9a152f..91aff306 100644 --- a/patches/viabedrock/upstreamable/0020-forward-predicted-boat-and-horse-movement.patch +++ b/patches/viabedrock/upstreamable/0020-forward-predicted-boat-and-horse-movement.patch @@ -1,4 +1,4 @@ -From 10a53b2544c110749b6ad50130e7be1d127aeafd Mon Sep 17 00:00:00 2001 +From 0708a80106dba9b4105bfeaca3be3e4c33b55405 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:34 +0200 Subject: [PATCH] fix(vehicles): forward predicted boat and horse movement @@ -187,10 +187,10 @@ index 163c5267..767c62d6 100644 if (this.lastReceivedRotation == null) { this.lastReceivedRotation = new Position3f(receivedAngle(rotation.x()), receivedAngle(rotation.y()), receivedAngle(rotation.z())); diff --git a/src/main/java/net/raphimc/viabedrock/api/util/PacketFactory.java b/src/main/java/net/raphimc/viabedrock/api/util/PacketFactory.java -index f67b7f7d..4bdf42be 100644 +index 68fa4fac..38463948 100644 --- a/src/main/java/net/raphimc/viabedrock/api/util/PacketFactory.java +++ b/src/main/java/net/raphimc/viabedrock/api/util/PacketFactory.java -@@ -101,7 +101,7 @@ public final class PacketFactory { +@@ -117,7 +117,7 @@ public final class PacketFactory { public static void sendJavaRotateHead(final UserConnection user, final Entity entity) { final PacketWrapper rotateHead = PacketWrapper.create(ClientboundPackets26_3.ROTATE_HEAD, user); rotateHead.write(Types.VAR_INT, entity.javaId()); // entity id @@ -378,10 +378,10 @@ index 50758f19..dd053c7a 100644 wrapper.write(BedrockTypes.POSITION_2F, MathUtil.calculateMovementDirections(clientPlayer.authInputData(), false)); // raw move vector diff --git a/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java b/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java -index 34e1466f..dcd49bf6 100644 +index 74d910d8..4103bd32 100644 --- a/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java +++ b/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java -@@ -134,12 +134,12 @@ public final class EntityPackets { +@@ -133,12 +133,12 @@ public final class EntityPackets { wrapper.write(Types.UUID, entity.javaUuid()); // uuid wrapper.write(Types.VAR_INT, entity.javaType().getId()); // type id wrapper.write(Types.DOUBLE, (double) position.x()); // x @@ -398,7 +398,7 @@ index 34e1466f..dcd49bf6 100644 wrapper.write(Types.VAR_INT, 0); // data wrapper.send(BedrockProtocol.class); wrapper.cancel(); -@@ -226,11 +226,11 @@ public final class EntityPackets { +@@ -225,11 +225,11 @@ public final class EntityPackets { wrapper.write(Types.VAR_INT, 1); // Stepped wrapper.write(Types.VAR_INT, 1); // 1 step wrapper.write(Types.DOUBLE, (double) entity.position().x()); // x @@ -413,7 +413,7 @@ index 34e1466f..dcd49bf6 100644 wrapper.write(Types.BOOLEAN, entity.isOnGround()); // on ground } return; -@@ -244,10 +244,10 @@ public final class EntityPackets { +@@ -243,10 +243,10 @@ public final class EntityPackets { wrapper.write(Types.VAR_INT, 1); // Stepped wrapper.write(Types.VAR_INT, 1); // 1 step wrapper.write(Types.DOUBLE, (double) position.x()); // x @@ -426,7 +426,7 @@ index 34e1466f..dcd49bf6 100644 wrapper.write(Types.FLOAT, pitch); // pitch wrapper.write(Types.BOOLEAN, entity.isOnGround()); // on ground -@@ -306,11 +306,11 @@ public final class EntityPackets { +@@ -305,11 +305,11 @@ public final class EntityPackets { wrapper.write(Types.VAR_INT, 1); // Stepped wrapper.write(Types.VAR_INT, 1); // 1 step wrapper.write(Types.DOUBLE, (double) entity.position().x()); // x @@ -441,7 +441,7 @@ index 34e1466f..dcd49bf6 100644 wrapper.write(Types.BOOLEAN, entity.isOnGround()); // on ground } return; -@@ -327,11 +327,11 @@ public final class EntityPackets { +@@ -326,11 +326,11 @@ public final class EntityPackets { wrapper.write(Types.VAR_INT, 1); // Stepped wrapper.write(Types.VAR_INT, 1); // 1 step wrapper.write(Types.DOUBLE, (double) entity.position().x()); // x diff --git a/patches/viabedrock/upstreamable/0026-remove-score-ids-across-objectives.patch b/patches/viabedrock/upstreamable/0026-remove-score-ids-across-objectives.patch index ceab3b4b..d89093a4 100644 --- a/patches/viabedrock/upstreamable/0026-remove-score-ids-across-objectives.patch +++ b/patches/viabedrock/upstreamable/0026-remove-score-ids-across-objectives.patch @@ -1,4 +1,4 @@ -From 4af13c3a2eb0aaec43caba8a36a6aec611b7a647 Mon Sep 17 00:00:00 2001 +From 49935d0b8b8587a33e595e177c066b71f1744f19 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:34 +0200 Subject: [PATCH] fix(scoreboard): remove score IDs across objectives @@ -11,7 +11,7 @@ Bedrock can remove a score ID without naming an objective. Clear every tracked o create mode 100644 src/test/java/net/raphimc/viabedrock/protocol/packet/HudPacketsTest.java diff --git a/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java b/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java -index 80c97cff..48cadcff 100644 +index bb514a85..0b9b34b1 100644 --- a/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java +++ b/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java @@ -18,6 +18,7 @@ @@ -51,7 +51,7 @@ index 80c97cff..48cadcff 100644 } final ScoreboardObjective objective = scoreboardTracker.getObjective(objectiveName); -@@ -457,6 +456,21 @@ public final class HudPackets { +@@ -451,6 +450,21 @@ public final class HudPackets { }); } diff --git a/patches/viabedrock/upstreamable/0043-load-and-apply-editable-gamerules.patch b/patches/viabedrock/upstreamable/0043-load-and-apply-editable-gamerules.patch index 11e29bbc..924a9185 100644 --- a/patches/viabedrock/upstreamable/0043-load-and-apply-editable-gamerules.patch +++ b/patches/viabedrock/upstreamable/0043-load-and-apply-editable-gamerules.patch @@ -1,4 +1,4 @@ -From 62ec1bf62e24e82ac42b3e55483eef2c2899262b Mon Sep 17 00:00:00 2001 +From 0027c4f7e5015c91cc40bdcdfdd202002a9b7372 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:35 +0200 Subject: [PATCH] fix(world): load and apply editable gamerules in Java options @@ -211,7 +211,7 @@ index 00000000..f4df37f9 + +} diff --git a/src/main/java/net/raphimc/viabedrock/protocol/storage/GameRulesStorage.java b/src/main/java/net/raphimc/viabedrock/protocol/storage/GameRulesStorage.java -index e1c58934..5940a168 100644 +index 8285bf9c..634a5704 100644 --- a/src/main/java/net/raphimc/viabedrock/protocol/storage/GameRulesStorage.java +++ b/src/main/java/net/raphimc/viabedrock/protocol/storage/GameRulesStorage.java @@ -39,6 +39,7 @@ public class GameRulesStorage extends StoredObject { @@ -230,7 +230,7 @@ index e1c58934..5940a168 100644 final Object previousValue = this.gameRules.put(gameRule.name().toLowerCase(Locale.ROOT), gameRule.value()); if (previousValue == null) { ViaBedrock.getPlatform().getLogger().log(Level.WARNING, "Received unknown game rule: " + gameRule.name()); -@@ -76,4 +78,12 @@ public class GameRulesStorage extends StoredObject { +@@ -78,4 +80,12 @@ public class GameRulesStorage extends StoredObject { return (T) this.gameRules.get(name.toLowerCase(Locale.ROOT)); } diff --git a/patches/viabedrock/upstreamable/0047-encode-current-bedrock-skin-packets.patch b/patches/viabedrock/upstreamable/0047-encode-current-bedrock-skin-packets.patch index 245cfa99..0fe72120 100644 --- a/patches/viabedrock/upstreamable/0047-encode-current-bedrock-skin-packets.patch +++ b/patches/viabedrock/upstreamable/0047-encode-current-bedrock-skin-packets.patch @@ -1,4 +1,4 @@ -From 88243458440d7fc5e28a0738eb56b725b3e63e02 Mon Sep 17 00:00:00 2001 +From 0039c9bff45942aeb85d9e546e538e9c1f0ec5f7 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:35 +0200 Subject: [PATCH] fix(skins): encode current Bedrock skin packets @@ -380,7 +380,7 @@ index 00000000..be644704 + +} diff --git a/src/main/java/net/raphimc/viabedrock/api/util/PacketFactory.java b/src/main/java/net/raphimc/viabedrock/api/util/PacketFactory.java -index 4bdf42be..c51ffd40 100644 +index 38463948..f9fec82b 100644 --- a/src/main/java/net/raphimc/viabedrock/api/util/PacketFactory.java +++ b/src/main/java/net/raphimc/viabedrock/api/util/PacketFactory.java @@ -22,6 +22,8 @@ import com.viaversion.viaversion.api.connection.UserConnection; @@ -392,7 +392,7 @@ index 4bdf42be..c51ffd40 100644 import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.version.VersionedTypes; import com.viaversion.viaversion.libs.gson.JsonNull; -@@ -174,6 +176,21 @@ public final class PacketFactory { +@@ -190,6 +192,21 @@ public final class PacketFactory { wrapper.write(Types.VAR_INT, 0); // Default randomization type } @@ -451,7 +451,7 @@ index 070d83f4..3a5f8a94 100644 public record AnimationData(BufferedImage image, int type, float frames, int expression) { } diff --git a/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java b/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java -index 48cadcff..c717576d 100644 +index 0b9b34b1..ae020452 100644 --- a/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java +++ b/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java @@ -99,9 +99,8 @@ public final class HudPackets { diff --git a/patches/viabedrock/upstreamable/0072-convert-native-sidebar-title-images-and-styled-scores.patch b/patches/viabedrock/upstreamable/0072-convert-native-sidebar-title-images-and-styled-scores.patch index 68631c1a..b55a2152 100644 --- a/patches/viabedrock/upstreamable/0072-convert-native-sidebar-title-images-and-styled-scores.patch +++ b/patches/viabedrock/upstreamable/0072-convert-native-sidebar-title-images-and-styled-scores.patch @@ -1,4 +1,4 @@ -From a177eea5706536c4bc0360875a52accbce7fe663 Mon Sep 17 00:00:00 2001 +From 84d1537af848b9d4b4e0b063e332d4cd890acd41 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:36 +0200 Subject: [PATCH] feat(scoreboard): convert native sidebar title images and @@ -862,7 +862,7 @@ index 129790fd..8f4de419 100644 out.append(style.asString()); } diff --git a/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java b/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java -index c717576d..910f6420 100644 +index ae020452..35ab3921 100644 --- a/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java +++ b/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java @@ -17,6 +17,7 @@ diff --git a/patches/viabedrock/upstreamable/0086-transport-native-actor-animation-commands.patch b/patches/viabedrock/upstreamable/0086-transport-native-actor-animation-commands.patch index 8c81fb9d..99e11b4d 100644 --- a/patches/viabedrock/upstreamable/0086-transport-native-actor-animation-commands.patch +++ b/patches/viabedrock/upstreamable/0086-transport-native-actor-animation-commands.patch @@ -1,4 +1,4 @@ -From 07d384e0f4afec9484626980ec1007eeb1d616ec Mon Sep 17 00:00:00 2001 +From 0c393241641102c8bf9579501cf0eba4bf96b213 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:36 +0200 Subject: [PATCH] feat(animations): decode and transport native actor commands @@ -353,7 +353,7 @@ index 00000000..e47da430 + +} diff --git a/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java b/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java -index dcd49bf6..c6ea78df 100644 +index 4103bd32..a88292f9 100644 --- a/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java +++ b/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java @@ -18,6 +18,7 @@ @@ -373,7 +373,7 @@ index dcd49bf6..c6ea78df 100644 import net.raphimc.viabedrock.api.model.entity.ClientPlayerEntity; import net.raphimc.viabedrock.api.model.entity.CustomEntity; import net.raphimc.viabedrock.api.model.entity.Entity; -@@ -58,6 +61,7 @@ import net.raphimc.viabedrock.protocol.data.generated.java.RegistryKeys; +@@ -57,6 +60,7 @@ import net.raphimc.viabedrock.protocol.data.generated.java.RegistryKeys; import net.raphimc.viabedrock.protocol.model.*; import net.raphimc.viabedrock.protocol.rewriter.ItemRewriter; import net.raphimc.viabedrock.protocol.storage.EntityPropertyTracker; @@ -381,7 +381,7 @@ index dcd49bf6..c6ea78df 100644 import net.raphimc.viabedrock.protocol.storage.EntityTracker; import net.raphimc.viabedrock.protocol.storage.GameSessionStorage; import net.raphimc.viabedrock.protocol.storage.ResourcePackStorage; -@@ -78,7 +82,43 @@ public final class EntityPackets { +@@ -77,7 +81,43 @@ public final class EntityPackets { return new Position3f(hasX ? x : current.x(), hasY ? y : current.y(), hasZ ? z : current.z()); } diff --git a/patches/viabedrock/upstreamable/0092-retain-completed-client-prediction-frames.patch b/patches/viabedrock/upstreamable/0092-retain-completed-client-prediction-frames.patch index ca9e14da..c1f90337 100644 --- a/patches/viabedrock/upstreamable/0092-retain-completed-client-prediction-frames.patch +++ b/patches/viabedrock/upstreamable/0092-retain-completed-client-prediction-frames.patch @@ -1,4 +1,4 @@ -From 307da33ed56db6ef7c55d5cd6952fcceaf3ace5f Mon Sep 17 00:00:00 2001 +From 980edf31c39bfbedb840c65187cec0ededa87bfa Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:36 +0200 Subject: [PATCH] feat(movement): retain completed client prediction frames @@ -4690,7 +4690,7 @@ index 21d20d88..ceabfddc 100644 + } diff --git a/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java b/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java -index c6ea78df..423c0c09 100644 +index a88292f9..1d7c6c07 100644 --- a/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java +++ b/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java @@ -37,6 +37,8 @@ import com.viaversion.viaversion.protocols.v26_2to26_3.packet.ClientboundPackets @@ -4702,7 +4702,7 @@ index c6ea78df..423c0c09 100644 import net.raphimc.viabedrock.api.model.NativeAnimationMessage; import net.raphimc.viabedrock.api.model.entity.ClientPlayerEntity; import net.raphimc.viabedrock.api.model.entity.CustomEntity; -@@ -63,6 +65,8 @@ import net.raphimc.viabedrock.protocol.rewriter.ItemRewriter; +@@ -62,6 +64,8 @@ import net.raphimc.viabedrock.protocol.rewriter.ItemRewriter; import net.raphimc.viabedrock.protocol.storage.EntityPropertyTracker; import net.raphimc.viabedrock.protocol.storage.ChannelStorage; import net.raphimc.viabedrock.protocol.storage.EntityTracker; @@ -4711,7 +4711,7 @@ index c6ea78df..423c0c09 100644 import net.raphimc.viabedrock.protocol.storage.GameSessionStorage; import net.raphimc.viabedrock.protocol.storage.ResourcePackStorage; import net.raphimc.viabedrock.protocol.types.BedrockTypes; -@@ -251,11 +255,13 @@ public final class EntityPackets { +@@ -250,11 +254,13 @@ public final class EntityPackets { entity.retainSpatialUpdate(position, new Position3f(pitch, yaw, headYaw), flags, 0); @@ -4729,7 +4729,7 @@ index c6ea78df..423c0c09 100644 entity.setPosition(position); if (teleported) { -@@ -276,6 +282,7 @@ public final class EntityPackets { +@@ -275,6 +281,7 @@ public final class EntityPackets { return; } @@ -4737,7 +4737,7 @@ index c6ea78df..423c0c09 100644 entity.setPosition(position); entity.setRotation(new Position3f(pitch, yaw, headYaw)); entity.setOnGround(onGround); -@@ -332,11 +339,13 @@ public final class EntityPackets { +@@ -331,11 +338,13 @@ public final class EntityPackets { | (forceMoveLocalEntity ? 4 : 0) | (forceCompletion ? 8 : 0)); entity.retainSpatialUpdate(position, rotation, flags, interpolationTicks); @@ -4755,7 +4755,7 @@ index c6ea78df..423c0c09 100644 entity.setPosition(position); if (teleported) { wrapper.setPacketType(ClientboundPackets26_3.PLAYER_POSITION); -@@ -356,6 +365,7 @@ public final class EntityPackets { +@@ -355,6 +364,7 @@ public final class EntityPackets { return; } @@ -4763,7 +4763,7 @@ index c6ea78df..423c0c09 100644 entity.setPosition(position); entity.setRotation(rotation); if (hasHeadYaw) { -@@ -377,8 +387,8 @@ public final class EntityPackets { +@@ -376,8 +386,8 @@ public final class EntityPackets { protocol.registerClientbound(ClientboundBedrockPackets.SET_ENTITY_MOTION, ClientboundPackets26_3.SET_ENTITY_MOTION, wrapper -> { final long entityRuntimeId = wrapper.read(BedrockTypes.UNSIGNED_VAR_LONG); // entity runtime id final Position3f motion = wrapper.read(BedrockTypes.POSITION_3F); // motion @@ -4774,7 +4774,7 @@ index c6ea78df..423c0c09 100644 }); protocol.registerClientbound(ClientboundBedrockPackets.MOTION_PREDICTION_HINTS, ClientboundPackets26_3.SET_ENTITY_MOTION, wrapper -> { final long entityRuntimeId = wrapper.read(BedrockTypes.UNSIGNED_VAR_LONG); // entity runtime id -@@ -748,6 +758,39 @@ public final class EntityPackets { +@@ -744,6 +754,39 @@ public final class EntityPackets { }); } diff --git a/patches/viabedrock/upstreamable/0094-synchronize-authoritative-entity-references.patch b/patches/viabedrock/upstreamable/0094-synchronize-authoritative-entity-references.patch index 43447dae..7d43774e 100644 --- a/patches/viabedrock/upstreamable/0094-synchronize-authoritative-entity-references.patch +++ b/patches/viabedrock/upstreamable/0094-synchronize-authoritative-entity-references.patch @@ -1,4 +1,4 @@ -From 5b974bce408e1ffd3218ffdbd4a509e955dc9017 Mon Sep 17 00:00:00 2001 +From 3f20da1644d7c3f8e486ef15c00ecf8afbd917b2 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:37 +0200 Subject: [PATCH] fix(entities): synchronize authoritative entity references @@ -304,10 +304,10 @@ index 4ebe5ed5..28991e6a 100644 public final void updateEntityData(final EntityData[] entityData) { diff --git a/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java b/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java -index 423c0c09..b8ba20f5 100644 +index 1d7c6c07..efe4871a 100644 --- a/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java +++ b/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java -@@ -184,9 +184,10 @@ public final class EntityPackets { +@@ -183,9 +183,10 @@ public final class EntityPackets { wrapper.write(Types.BYTE, MathUtil.float2Byte(entity.javaRotation().x())); // pitch wrapper.write(Types.BYTE, MathUtil.float2Byte(entity.javaRotation().y())); // yaw wrapper.write(Types.BYTE, MathUtil.float2Byte(entity.javaRotation().z())); // head yaw @@ -319,7 +319,7 @@ index 423c0c09..b8ba20f5 100644 if (entity instanceof LivingEntity livingEntity) { livingEntity.updateAttributes(attributes); -@@ -225,6 +226,7 @@ public final class EntityPackets { +@@ -224,6 +225,7 @@ public final class EntityPackets { wrapper.write(Types.VAR_INT, 0); // data wrapper.send(BedrockProtocol.class); wrapper.cancel(); @@ -327,7 +327,7 @@ index 423c0c09..b8ba20f5 100644 final List javaEntityData = new ArrayList<>(); entity.updateEntityData(entityData, javaEntityData); -@@ -465,6 +467,7 @@ public final class EntityPackets { +@@ -464,6 +466,7 @@ public final class EntityPackets { wrapper.write(Types.VAR_INT, direction.verticalId()); // data wrapper.send(BedrockProtocol.class); wrapper.cancel(); diff --git a/patches/viabedrock/upstreamable/0095-integrate-authored-native-ui-presentation.patch b/patches/viabedrock/upstreamable/0095-integrate-authored-native-ui-presentation.patch index 303b2c73..3013209a 100644 --- a/patches/viabedrock/upstreamable/0095-integrate-authored-native-ui-presentation.patch +++ b/patches/viabedrock/upstreamable/0095-integrate-authored-native-ui-presentation.patch @@ -1,4 +1,4 @@ -From 89dbbf8d733db412c0f6ef85a14ccb7870fc75a8 Mon Sep 17 00:00:00 2001 +From e5d0da65314ee1bccfedb3bbd385af5bb371087b Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:37 +0200 Subject: [PATCH] feat(ui): resolve authored Bedrock server UI scenes @@ -5158,7 +5158,7 @@ index 00000000..5ceed231 + +} diff --git a/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java b/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java -index 910f6420..ea1d8d7e 100644 +index 35ab3921..4cb81aa6 100644 --- a/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java +++ b/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java @@ -26,14 +26,22 @@ import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; diff --git a/patches/viabedrock/upstreamable/0095-share-native-ui-programs-and-resources.patch b/patches/viabedrock/upstreamable/0095-share-native-ui-programs-and-resources.patch index 409bd836..784e2ab3 100644 --- a/patches/viabedrock/upstreamable/0095-share-native-ui-programs-and-resources.patch +++ b/patches/viabedrock/upstreamable/0095-share-native-ui-programs-and-resources.patch @@ -1,4 +1,4 @@ -From 61029b133c68d4b916990d2b58cdd74489be35d8 Mon Sep 17 00:00:00 2001 +From 942cc788139de223f5e0260418f84959b409b7d7 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 18 Oct 2026 16:55:37 +0200 Subject: [PATCH] feat(ui): share bounded native UI programs and resource @@ -9673,7 +9673,7 @@ index 00000000..20278d84 + +} diff --git a/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java b/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java -index b8ba20f5..9c4b8cec 100644 +index efe4871a..8a8c6f74 100644 --- a/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java +++ b/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java @@ -36,6 +36,7 @@ import com.viaversion.viaversion.api.type.types.version.VersionedTypes; @@ -9684,7 +9684,7 @@ index b8ba20f5..9c4b8cec 100644 import net.raphimc.viabedrock.api.model.NativeActorMessage; import net.raphimc.viabedrock.api.model.ActorMotionCommand; import net.raphimc.viabedrock.api.model.MovementCorrectionTiming; -@@ -494,6 +495,14 @@ public final class EntityPackets { +@@ -493,6 +494,14 @@ public final class EntityPackets { wrapper.cancel(); return; } diff --git a/patches/viabedrock/upstreamable/0105-ignore-removals-for-absent-unmapped-effects.patch b/patches/viabedrock/upstreamable/0105-ignore-removals-for-absent-unmapped-effects.patch index 2f81aaeb..ffca5d34 100644 --- a/patches/viabedrock/upstreamable/0105-ignore-removals-for-absent-unmapped-effects.patch +++ b/patches/viabedrock/upstreamable/0105-ignore-removals-for-absent-unmapped-effects.patch @@ -1,4 +1,4 @@ -From 86b7b04308a33aade7cb9884538cea97e197c9b4 Mon Sep 17 00:00:00 2001 +From 6de9511cb4301fe6974fc5be7687284e2d863405 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sat, 31 Oct 2026 21:25:36 +0100 Subject: [PATCH] fix(effects): ignore removals for absent unmapped effects @@ -32,10 +32,10 @@ probes can crash and do not establish behavior for arbitrary integers. create mode 100644 src/test/java/net/raphimc/viabedrock/protocol/packet/MobEffectPacketTest.java diff --git a/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java b/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java -index 1c23b759..4578a73f 100644 +index d6ea7d51..f66c7f90 100644 --- a/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java +++ b/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java -@@ -618,7 +618,12 @@ public final class EntityPackets { +@@ -614,7 +614,12 @@ public final class EntityPackets { } final String bedrockIdentifier = BedrockProtocol.MAPPINGS.getBedrockEffects().inverse().get(effectId); diff --git a/patches/viabedrock/upstreamable/0107-transport-remote-player-movement-modes.patch b/patches/viabedrock/upstreamable/0107-transport-remote-player-movement-modes.patch index 006f4343..0e077690 100644 --- a/patches/viabedrock/upstreamable/0107-transport-remote-player-movement-modes.patch +++ b/patches/viabedrock/upstreamable/0107-transport-remote-player-movement-modes.patch @@ -1,4 +1,4 @@ -From da3c1093042008fab5ed5df2366aed3c39f9d23b Mon Sep 17 00:00:00 2001 +From eaf597e82caa97a2dd73d1b75cd83ef5ef690e4a Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Mon, 2 Nov 2026 15:43:18 +0100 Subject: [PATCH] feat(players): transport remote movement modes and targets @@ -237,7 +237,7 @@ index b47f5471..a4b3ec09 100644 final var tracker = this.user == null ? null : this.user.get(EntityTracker.class); // The horse writer sets VehicleInputIntent.mIsClientPredicted only for a saddled, tamed local controller. diff --git a/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java b/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java -index 4578a73f..6f65acbc 100644 +index f66c7f90..dde3d4b1 100644 --- a/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java +++ b/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java @@ -38,6 +38,7 @@ import net.raphimc.viabedrock.ViaBedrock; @@ -248,7 +248,7 @@ index 4578a73f..6f65acbc 100644 import net.raphimc.viabedrock.api.model.MovementCorrectionTiming; import net.raphimc.viabedrock.api.model.NativeAnimationMessage; import net.raphimc.viabedrock.api.model.entity.ClientPlayerEntity; -@@ -294,6 +295,7 @@ public final class EntityPackets { +@@ -293,6 +294,7 @@ public final class EntityPackets { entity.setPosition(position); entity.setRotation(new Position3f(pitch, yaw, headYaw)); entity.setOnGround(onGround); @@ -256,7 +256,7 @@ index 4578a73f..6f65acbc 100644 wrapper.write(Types.VAR_INT, entity.javaId()); // entity id wrapper.write(Types.VAR_INT, 1); // Stepped -@@ -380,6 +382,7 @@ public final class EntityPackets { +@@ -379,6 +381,7 @@ public final class EntityPackets { PacketFactory.sendJavaRotateHead(wrapper.user(), entity); } entity.setOnGround(onGround); diff --git a/patches/viabedrock/upstreamable/0111-honor-death-message-visibility.patch b/patches/viabedrock/upstreamable/0111-honor-death-message-visibility.patch new file mode 100644 index 00000000..97ce6327 --- /dev/null +++ b/patches/viabedrock/upstreamable/0111-honor-death-message-visibility.patch @@ -0,0 +1,334 @@ +From 57a093dc5f31941bcec7ad0dfedffccc738cd578 Mon Sep 17 00:00:00 2001 +From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> +Date: Thu, 5 Nov 2026 20:20:15 +0100 +Subject: [PATCH] fix(hud): honor death message visibility + +Keep the translated death reason in session storage and apply +showDeathMessages when sending the death screen. Share this policy between +DeathInfo and actor-death handling so either packet order hides only the +reason. Refresh a dead player when the rule changes, including restoring +the retained reason when it is enabled again. + +Bedrock 1.26.60.5, protocol 2225, evaluates this rule in its active OreUI +PlayerDeathInfoQuery as well as its classic death controller. Eight +original-executable query controls select an empty string unless the world, +player and enabled rule are all present. Executable SHA-256: +988f6a61403b4381aa41e6c66d64cce441b14e561a9b10f647c2758ef3f2672a. + +Packet regressions cover both arrival orders, alive-player admission, +retained text, live rule changes and repeated unchanged rules. This fixes +reason projection; the OreUI screen layout and lifecycle remain separate +work. +--- + .../viabedrock/api/util/PacketFactory.java | 16 ++ + .../protocol/packet/EntityPackets.java | 8 +- + .../protocol/packet/HudPackets.java | 8 +- + .../protocol/storage/GameRulesStorage.java | 2 + + .../packet/DeathMessagePacketsTest.java | 201 ++++++++++++++++++ + 5 files changed, 222 insertions(+), 13 deletions(-) + create mode 100644 src/test/java/net/raphimc/viabedrock/protocol/packet/DeathMessagePacketsTest.java + +diff --git a/src/main/java/net/raphimc/viabedrock/api/util/PacketFactory.java b/src/main/java/net/raphimc/viabedrock/api/util/PacketFactory.java +index f67b7f7d..68fa4fac 100644 +--- a/src/main/java/net/raphimc/viabedrock/api/util/PacketFactory.java ++++ b/src/main/java/net/raphimc/viabedrock/api/util/PacketFactory.java +@@ -38,10 +38,26 @@ import net.raphimc.viabedrock.protocol.data.enums.java.GameEventType; + import net.raphimc.viabedrock.protocol.data.enums.java.generated.CustomChatCompletionsAction; + import net.raphimc.viabedrock.protocol.model.Position3f; + import net.raphimc.viabedrock.protocol.storage.InventoryTracker; ++import net.raphimc.viabedrock.protocol.storage.EntityTracker; ++import net.raphimc.viabedrock.protocol.storage.GameRulesStorage; ++import net.raphimc.viabedrock.protocol.storage.GameSessionStorage; + import net.raphimc.viabedrock.protocol.types.BedrockTypes; + + public final class PacketFactory { + ++ public static void sendJavaDeathMessage(final UserConnection user) { ++ final var player = user.get(EntityTracker.class).getClientPlayer(); ++ final var message = user.get(GameSessionStorage.class).getDeathMessage(); ++ if (!player.isDead() || message == null) { ++ return; ++ } ++ final boolean showReason = Boolean.TRUE.equals(user.get(GameRulesStorage.class).getGameRule("showDeathMessages")); ++ final PacketWrapper death = PacketWrapper.create(ClientboundPackets26_3.PLAYER_COMBAT_KILL, user); ++ death.write(Types.VAR_INT, player.javaId()); // entity id ++ death.write(Types.TAG, showReason ? TextUtil.textComponentToNbt(message) : TextUtil.stringToNbt("")); // message ++ death.send(BedrockProtocol.class); ++ } ++ + public static void sendJavaBlockDestroyProgress(final UserConnection user, final int id, final BlockPosition position, final int stage) { + final PacketWrapper blockDestruction = PacketWrapper.create(ClientboundPackets26_3.BLOCK_DESTRUCTION, user); + blockDestruction.write(Types.VAR_INT, id); // id +diff --git a/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java b/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java +index 39c8bf5f..63b06dce 100644 +--- a/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java ++++ b/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java +@@ -45,7 +45,6 @@ import net.raphimc.viabedrock.api.resourcepack.definition.EntityDefinitions; + import net.raphimc.viabedrock.api.util.MathUtil; + import net.raphimc.viabedrock.api.util.PacketFactory; + import net.raphimc.viabedrock.api.util.RegistryUtil; +-import net.raphimc.viabedrock.api.util.TextUtil; + import net.raphimc.viabedrock.protocol.BedrockProtocol; + import net.raphimc.viabedrock.protocol.ClientboundBedrockPackets; + import net.raphimc.viabedrock.protocol.data.enums.Direction; +@@ -452,11 +451,8 @@ public final class EntityPackets { + livingEntity.setHealth(0F); + livingEntity.sendAttribute("minecraft:health"); + } +- if (entity == entityTracker.getClientPlayer() && entityTracker.getClientPlayer().isDead() && gameSession.getDeathMessage() != null) { +- final PacketWrapper playerCombatKill = PacketWrapper.create(ClientboundPackets26_3.PLAYER_COMBAT_KILL, wrapper.user()); +- playerCombatKill.write(Types.VAR_INT, entityTracker.getClientPlayer().javaId()); // entity id +- playerCombatKill.write(Types.TAG, TextUtil.textComponentToNbt(gameSession.getDeathMessage())); // message +- playerCombatKill.send(BedrockProtocol.class); ++ if (entity == entityTracker.getClientPlayer()) { ++ PacketFactory.sendJavaDeathMessage(wrapper.user()); + } + if (entity != entityTracker.getClientPlayer()) { + entity.playSound("death"); +diff --git a/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java b/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java +index 1e094252..4b40f209 100644 +--- a/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java ++++ b/src/main/java/net/raphimc/viabedrock/protocol/packet/HudPackets.java +@@ -438,18 +438,12 @@ public final class HudPackets { + protocol.registerClientbound(ClientboundBedrockPackets.DEATH_INFO, null, wrapper -> { + wrapper.cancel(); + final GameSessionStorage gameSession = wrapper.user().get(GameSessionStorage.class); +- final EntityTracker entityTracker = wrapper.user().get(EntityTracker.class); + final String message = wrapper.read(BedrockTypes.STRING); // death cause message + final String[] parameters = wrapper.read(BedrockTypes.STRING_ARRAY); // parameters + + final Function translator = wrapper.user().get(ResourcePackStorage.class).getTexts().lookup(); + gameSession.setDeathMessage(TextUtil.stringToTextComponent(BedrockTranslator.translate(message, translator, parameters))); +- if (entityTracker.getClientPlayer().isDead()) { +- final PacketWrapper playerCombatKill = PacketWrapper.create(ClientboundPackets26_3.PLAYER_COMBAT_KILL, wrapper.user()); +- playerCombatKill.write(Types.VAR_INT, entityTracker.getClientPlayer().javaId()); // entity id +- playerCombatKill.write(Types.TAG, TextUtil.textComponentToNbt(gameSession.getDeathMessage())); // message +- playerCombatKill.send(BedrockProtocol.class); +- } ++ PacketFactory.sendJavaDeathMessage(wrapper.user()); + }); + protocol.registerClientbound(ClientboundBedrockPackets.GUI_DATA_PICK_ITEM, ClientboundPackets26_3.SYSTEM_CHAT, wrapper -> { + final String itemName = wrapper.read(BedrockTypes.STRING); // item name +diff --git a/src/main/java/net/raphimc/viabedrock/protocol/storage/GameRulesStorage.java b/src/main/java/net/raphimc/viabedrock/protocol/storage/GameRulesStorage.java +index 9dcfed5b..9d4df851 100644 +--- a/src/main/java/net/raphimc/viabedrock/protocol/storage/GameRulesStorage.java ++++ b/src/main/java/net/raphimc/viabedrock/protocol/storage/GameRulesStorage.java +@@ -61,6 +61,8 @@ public class GameRulesStorage extends StoredObject { + PacketFactory.sendJavaEntityEvent(this.user(), clientPlayer, (boolean) gameRule.value() ? EntityEvent.FULL_DEBUG_INFO : EntityEvent.REDUCED_DEBUG_INFO); + } else if (gameRule.name().equalsIgnoreCase("doImmediateRespawn")) { + PacketFactory.sendJavaGameEvent(this.user(), GameEventType.IMMEDIATE_RESPAWN, (boolean) gameRule.value() ? 1F : 0F); ++ } else if (gameRule.name().equalsIgnoreCase("showDeathMessages")) { ++ PacketFactory.sendJavaDeathMessage(this.user()); + } else if (gameRule.name().equalsIgnoreCase("doLimitedCrafting")) { + PacketFactory.sendJavaGameEvent(this.user(), GameEventType.LIMITED_CRAFTING, (boolean) gameRule.value() ? 1F : 0F); + } +diff --git a/src/test/java/net/raphimc/viabedrock/protocol/packet/DeathMessagePacketsTest.java b/src/test/java/net/raphimc/viabedrock/protocol/packet/DeathMessagePacketsTest.java +new file mode 100644 +index 00000000..20c41ece +--- /dev/null ++++ b/src/test/java/net/raphimc/viabedrock/protocol/packet/DeathMessagePacketsTest.java +@@ -0,0 +1,201 @@ ++/* ++ * This file is part of ViaBedrock - https://github.com/ViaVersionAddons/ViaBedrock ++ * Copyright (C) 2026 ViaVersion and contributors ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++package net.raphimc.viabedrock.protocol.packet; ++ ++import com.viaversion.nbt.tag.Tag; ++import com.viaversion.viaversion.api.connection.ProtocolInfo; ++import com.viaversion.viaversion.api.connection.UserConnection; ++import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; ++import com.viaversion.viaversion.api.protocol.packet.State; ++import com.viaversion.viaversion.api.protocol.packet.mapping.PacketMapping; ++import com.viaversion.viaversion.api.type.Types; ++import com.viaversion.viaversion.protocol.packet.PacketWrapperImpl; ++import com.viaversion.viaversion.protocols.v26_2to26_3.packet.ClientboundPackets26_3; ++import io.netty.buffer.ByteBuf; ++import io.netty.buffer.Unpooled; ++import java.lang.reflect.Field; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.Map; ++import java.util.concurrent.atomic.AtomicBoolean; ++import java.util.function.Consumer; ++import java.util.function.Function; ++import net.raphimc.viabedrock.api.model.entity.ClientPlayerEntity; ++import net.raphimc.viabedrock.api.resourcepack.definition.TextDefinitions; ++import net.raphimc.viabedrock.protocol.BedrockProtocol; ++import net.raphimc.viabedrock.protocol.ClientboundBedrockPackets; ++import net.raphimc.viabedrock.protocol.data.ProtocolConstants; ++import net.raphimc.viabedrock.protocol.data.enums.bedrock.generated.ActorEvent; ++import net.raphimc.viabedrock.protocol.model.GameRule; ++import net.raphimc.viabedrock.protocol.storage.ChannelStorage; ++import net.raphimc.viabedrock.protocol.storage.EntityTracker; ++import net.raphimc.viabedrock.protocol.storage.GameRulesStorage; ++import net.raphimc.viabedrock.protocol.storage.GameSessionStorage; ++import net.raphimc.viabedrock.protocol.storage.ResourcePackStorage; ++import net.raphimc.viabedrock.protocol.types.BedrockTypes; ++import org.junit.jupiter.api.Test; ++import org.mockito.MockedStatic; ++ ++import static org.junit.jupiter.api.Assertions.*; ++import static org.mockito.Mockito.*; ++ ++final class DeathMessagePacketsTest { ++ ++ @Test ++ void hidesTheReasonWithoutSuppressingTheDeathScreenInEitherPacketOrder() throws Exception { ++ for (final boolean infoFirst : List.of(false, true)) { ++ try (var fixture = new Fixture()) { ++ fixture.showReason(false); ++ if (infoFirst) { ++ fixture.info(); ++ } ++ fixture.death(); ++ if (!infoFirst) { ++ fixture.info(); ++ } ++ assertEquals(List.of(""), fixture.messages); ++ assertEquals("Controlled death reason", fixture.session.getDeathMessage().asUnformattedString()); ++ } ++ } ++ } ++ ++ @Test ++ void usesTheCurrentRuleWhenCachedOrLateDeathInformationIsPresented() throws Exception { ++ try (var fixture = new Fixture()) { ++ fixture.info(); ++ assertTrue(fixture.messages.isEmpty(), "An alive player must not receive a death screen"); ++ fixture.showReason(false); ++ fixture.death(); ++ assertEquals(List.of(""), fixture.messages); ++ fixture.showReason(true); ++ assertEquals(List.of("", "Controlled death reason"), fixture.messages); ++ fixture.showReason(false); ++ assertEquals(List.of("", "Controlled death reason", ""), fixture.messages); ++ fixture.showReason(false); ++ assertEquals(3, fixture.messages.size(), "An unchanged rule must not replace the screen"); ++ } ++ } ++ ++ private static GameRulesStorage rules(final UserConnection user) throws Exception { ++ final Field defaults = BedrockProtocol.MAPPINGS.getClass().getDeclaredField("bedrockGameRules"); ++ defaults.setAccessible(true); ++ final Object previous = defaults.get(BedrockProtocol.MAPPINGS); ++ try { ++ defaults.set(BedrockProtocol.MAPPINGS, Map.of("showdeathmessages", true)); ++ return new GameRulesStorage(user, new GameRule[0]); ++ } finally { ++ defaults.set(BedrockProtocol.MAPPINGS, previous); ++ } ++ } ++ ++ private static final class Fixture implements AutoCloseable { ++ private final UserConnection user = mock(UserConnection.class); ++ private final ClientPlayerEntity player = mock(ClientPlayerEntity.class); ++ private final GameSessionStorage session = mock(GameSessionStorage.class, CALLS_REAL_METHODS); ++ private final AtomicBoolean dead = new AtomicBoolean(); ++ private GameRulesStorage rules; ++ private final List messages = new ArrayList<>(); ++ private final MockedStatic packets = mockStatic(PacketWrapper.class); ++ private final TestProtocol protocol = new TestProtocol(); ++ ++ private Fixture() throws Exception { ++ final var entities = mock(EntityTracker.class); ++ final var info = mock(ProtocolInfo.class); ++ when(this.user.getProtocolInfo()).thenReturn(info); ++ when(info.getServerState()).thenReturn(State.PLAY); ++ this.rules = rules(this.user); ++ final var resources = mock(ResourcePackStorage.class); ++ final var texts = mock(TextDefinitions.class); ++ final Map, Object> storage = Map.of(EntityTracker.class, entities, ++ GameSessionStorage.class, this.session, GameRulesStorage.class, this.rules, ++ ResourcePackStorage.class, resources, ChannelStorage.class, new ChannelStorage()); ++ when(this.user.get(any())).thenAnswer(call -> storage.get(call.getArgument(0))); ++ when(entities.getClientPlayer()).thenReturn(this.player); ++ when(entities.getEntityByRid(42L)).thenReturn(this.player); ++ when(this.player.javaId()).thenReturn(7); ++ when(this.player.isDead()).thenAnswer(call -> this.dead.get()); ++ doAnswer(call -> { ++ this.dead.set((float) call.getArgument(0) <= 0); ++ return null; ++ }) ++ .when(this.player).setHealth(anyFloat()); ++ when(resources.getTexts()).thenReturn(texts); ++ when(texts.lookup()).thenReturn(Function.identity()); ++ this.packets.when(() -> PacketWrapper.create(ClientboundPackets26_3.PLAYER_COMBAT_KILL, this.user)) ++ .thenAnswer(call -> { ++ final var output = spy(new PacketWrapperImpl(ClientboundPackets26_3.PLAYER_COMBAT_KILL, null, this.user)); ++ doAnswer(send -> { ++ final var buffer = Unpooled.buffer(); ++ try { ++ output.writeToBuffer(buffer); ++ assertEquals(ClientboundPackets26_3.PLAYER_COMBAT_KILL.getId(), Types.VAR_INT.readPrimitive(buffer)); ++ assertEquals(7, Types.VAR_INT.readPrimitive(buffer)); ++ final Tag reason = Types.TAG.read(buffer); ++ this.messages.add(ProtocolConstants.JAVA_TEXT_COMPONENT_SERIALIZER ++ .deserializeNbtTree(reason).asUnformattedString()); ++ assertFalse(buffer.isReadable()); ++ } finally { ++ buffer.release(); ++ } ++ return null; ++ }).when(output).send(BedrockProtocol.class); ++ return output; ++ }); ++ } ++ ++ private void showReason(final boolean show) { ++ this.rules.updateGameRules(new GameRule[]{new GameRule("showDeathMessages", false, show)}); ++ } ++ ++ private void info() throws Exception { ++ this.receive(ClientboundBedrockPackets.DEATH_INFO, buffer -> { ++ BedrockTypes.STRING.write(buffer, "Controlled death reason"); ++ BedrockTypes.STRING_ARRAY.write(buffer, new String[0]); ++ }); ++ } ++ ++ private void death() throws Exception { ++ this.receive(ClientboundBedrockPackets.ENTITY_EVENT, buffer -> { ++ BedrockTypes.UNSIGNED_VAR_LONG.write(buffer, 42L); ++ Types.UNSIGNED_BYTE.write(buffer, (short) ActorEvent.DEATH.getValue()); ++ BedrockTypes.VAR_INT.write(buffer, 0); ++ BedrockTypes.OPTIONAL_POSITION_3F.write(buffer, null); ++ }); ++ } ++ ++ private void receive(final ClientboundBedrockPackets type, final Consumer encode) throws Exception { ++ final var input = Unpooled.buffer(); ++ try { ++ encode.accept(input); ++ final var packet = new PacketWrapperImpl(type, input, this.user); ++ final var mapping = this.protocol.mapping(packet); ++ mapping.applyType(packet); ++ mapping.handler().handle(packet); ++ assertTrue(packet.isCancelled()); ++ assertFalse(input.isReadable()); ++ } finally { ++ input.release(); ++ } ++ } ++ ++ @Override ++ public void close() { ++ this.packets.close(); ++ } ++ } ++ ++ private static final class TestProtocol extends BedrockProtocol { ++ private TestProtocol() { ++ HudPackets.register(this); ++ EntityPackets.register(this); ++ } ++ ++ private PacketMapping mapping(final PacketWrapper packet) { ++ return this.clientboundMappings.mappedPacket(State.PLAY, packet.getId()); ++ } ++ } ++ ++} +-- +2.56.0 + diff --git a/patches/viabedrock/upstreamable/0111-honor-death-message-visibility.pr.md b/patches/viabedrock/upstreamable/0111-honor-death-message-visibility.pr.md new file mode 100644 index 00000000..9853d2fb --- /dev/null +++ b/patches/viabedrock/upstreamable/0111-honor-death-message-visibility.pr.md @@ -0,0 +1,9 @@ +# fix(hud): honor death message visibility + +Hide the death reason when `showDeathMessages` is disabled while preserving the death screen and cached text. Apply the rule to either DeathInfo/actor-death arrival order and refresh a dead player when the rule changes. + +Bedrock 1.26.60.5 on protocol 2225 applies this rule in its active OreUI `PlayerDeathInfoQuery`. Eight original-executable controls select an empty reason unless the world, player, and enabled rule are present. The executable digest and rationale are in the patch body. + +`DeathMessagePacketsTest` covers both packet orders, alive-player admission, retained text, live visibility changes, and unchanged rules. Both tests fail on the previous projection and pass with this change. Main and test Checkstyle pass. The standalone production patch applies and builds on pinned upstream without setup or local tests. The full four-project build passes 3,524 tests with 328 skipped and no failures. + +This changes reason projection. The native OreUI layout, animation stages, menu routing, and respawn lifecycle remain unfinished. From 57f8ec3be1f2997d987cd5967b5cda56fa03cb11 Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Thu, 5 Nov 2026 21:26:28 +0100 Subject: [PATCH 008/141] fix(ui): retain Bedrock death screen lifetimes Handle reason updates before Java looks up the dead player in its world index. Update the retained screen without restarting its button deadline or replacing an active confirmation, and retire it across respawn or connection changes. Cover the real Java timer and replacement lifetimes with focused tests. Validate the installed handler on direct and ViaProxy routes while keeping the remaining native OreUI presentation and respawn work explicit. --- docs/bedrock-coverage.md | 2 +- .../0111-honor-death-message-visibility.pr.md | 2 +- ...w-confirmed-blacklisted-server-joins.patch | 4 +- patches/viafabricplus-bedrock/series.json | 11 + ...etain-bedrock-death-screen-lifetimes.patch | 384 ++++++++++++++++++ ...etain-bedrock-death-screen-lifetimes.pr.md | 11 + 6 files changed, 410 insertions(+), 4 deletions(-) create mode 100644 patches/viafabricplus-bedrock/upstreamable/0042-retain-bedrock-death-screen-lifetimes.patch create mode 100644 patches/viafabricplus-bedrock/upstreamable/0042-retain-bedrock-death-screen-lifetimes.pr.md diff --git a/docs/bedrock-coverage.md b/docs/bedrock-coverage.md index 8db0c72d..b74226d9 100644 --- a/docs/bedrock-coverage.md +++ b/docs/bedrock-coverage.md @@ -25,7 +25,7 @@ No complete area has verified Bedrock parity. | Protocol 2225 | Updated packet layouts, explicit enum values, stonecutter notifications, cursor drags, skin identity, and sound unions. | Block seating, signed content playback, matchmaking UI, and environment layers remain partial or unsupported. | | World resources | World base-game version selects vanilla metadata, textures, actor graphs, and audio before server overrides. The context survives ViaProxy. Server legacy biome bindings retain unqualified names across versions. | Native current/older/current cat, sound, and remote biome-name controls cover selected paths. Broader pose and audio behavior need comparisons. | | Held block placement | Retained stationary build paths, next-cell ray selection, independent fresh block/air admission, frame retries after rejection, use timing, trapdoor orientation, cancellation, and prediction. | Native, direct, and proxy controls cover selected stationary paths, first rejection, last-stack placement, activation, re-aiming, and release. Moving placement, air/entity dispatch, build-height limits, and broader input scheduling remain open. | -| Fonts and UI | Native bitmap metrics and italic geometry, authored scenes, TrueType cache and atlas sampling, and primary font aliases. [Death reasons follow the visibility game rule](../patches/viabedrock/upstreamable/0111-honor-death-message-visibility.pr.md). | Selected bitmap and TrueType controls match at 1280 × 720. [Validation and limits](../patches/viafabricplus-bedrock/upstreamable/0029-share-native-ui-scene-rendering.pr.md). Complete screens, bold and shadowed text, other font families, mixed or scaled aliases, and broader viewports remain open. | +| Fonts and UI | Native bitmap metrics and italic geometry, authored scenes, TrueType cache and atlas sampling, and primary font aliases. [Death reasons follow the visibility game rule](../patches/viabedrock/upstreamable/0111-honor-death-message-visibility.pr.md) and [retain the current screen](../patches/viafabricplus-bedrock/upstreamable/0042-retain-bedrock-death-screen-lifetimes.pr.md). | Selected bitmap and TrueType controls match at 1280 × 720. [Validation and limits](../patches/viafabricplus-bedrock/upstreamable/0029-share-native-ui-scene-rendering.pr.md). Complete screens, bold and shadowed text, other font families, mixed or scaled aliases, and broader viewports remain open. | | Player inventory and crafting | Opening ownership, recipe catalogs, Creative catalog actions, Survival bulk and manual crafting actions, retained recipe queries, compressed snapshots, local Help, inactive item lighting, native storage weights, desktop bundle insertion/default extraction, and native stack identities. | Complete E-key/workbench presentation, bundle controller and tooltip lifetimes, query lifetime across connections, broader Survival recipes, and original live bug reports remain unresolved. See the [inventory reference](native-player-inventory.md). | | Food use | Native components govern admission and Java food/consumable properties, with Creative and Peaceful exceptions. Local use retains native item identity across Java carriers. | Selected admission, input-deadline, timed-food, held-mode, release, and pause controls pass. Short input edges during active Java use, broader use cycles, and the original intermittent report remain open. See the [food-use evidence](../patches/viabedrock/deferred/0008-signal-sustained-bedrock-item-use.pr.md). | | Cooking | Snapshot-bound recipe actions, transfer batches, preferences, search, and progress. | Default cooking screens retain ordinary controls until authored bindings are complete. See the [cooking reference](native-cooking-actions.md). | diff --git a/patches/viabedrock/upstreamable/0111-honor-death-message-visibility.pr.md b/patches/viabedrock/upstreamable/0111-honor-death-message-visibility.pr.md index 9853d2fb..43e41cf8 100644 --- a/patches/viabedrock/upstreamable/0111-honor-death-message-visibility.pr.md +++ b/patches/viabedrock/upstreamable/0111-honor-death-message-visibility.pr.md @@ -6,4 +6,4 @@ Bedrock 1.26.60.5 on protocol 2225 applies this rule in its active OreUI `Player `DeathMessagePacketsTest` covers both packet orders, alive-player admission, retained text, live visibility changes, and unchanged rules. Both tests fail on the previous projection and pass with this change. Main and test Checkstyle pass. The standalone production patch applies and builds on pinned upstream without setup or local tests. The full four-project build passes 3,524 tests with 328 skipped and no failures. -This changes reason projection. The native OreUI layout, animation stages, menu routing, and respawn lifecycle remain unfinished. +This changes reason projection. The [add-on](../../viafabricplus-bedrock/upstreamable/0042-retain-bedrock-death-screen-lifetimes.pr.md) owns on-screen update admission and lifetime. The native OreUI layout, animation stages, menu routing, and respawn lifecycle remain unfinished. diff --git a/patches/viafabricplus-bedrock/deferred/0001-allow-confirmed-blacklisted-server-joins.patch b/patches/viafabricplus-bedrock/deferred/0001-allow-confirmed-blacklisted-server-joins.patch index 900ce1d3..5a91361e 100644 --- a/patches/viafabricplus-bedrock/deferred/0001-allow-confirmed-blacklisted-server-joins.patch +++ b/patches/viafabricplus-bedrock/deferred/0001-allow-confirmed-blacklisted-server-joins.patch @@ -1,4 +1,4 @@ -From 188d8659f6672a55c086f54dabe7b5a5d6a13bff Mon Sep 17 00:00:00 2001 +From 7ffd920e1849d1519a56a76d90ce566fd8f6ff4d Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 1 Nov 2026 14:06:07 +0100 Subject: [PATCH] feat(connection): allow confirmed blacklisted server joins @@ -247,7 +247,7 @@ index f345d7c..40e7ae9 100644 "command_block.viafabricplus.first_tick": "Execute on first tick", "base.viafabricplus.accept": "Accept", diff --git a/src/main/resources/viafabricplus-bedrock.mixins.json b/src/main/resources/viafabricplus-bedrock.mixins.json -index 4e383fc..0f484fa 100644 +index 67091e0..abcd8b9 100644 --- a/src/main/resources/viafabricplus-bedrock.mixins.json +++ b/src/main/resources/viafabricplus-bedrock.mixins.json @@ -49,6 +49,8 @@ diff --git a/patches/viafabricplus-bedrock/series.json b/patches/viafabricplus-bedrock/series.json index 5aca044b..23c943c7 100644 --- a/patches/viafabricplus-bedrock/series.json +++ b/patches/viafabricplus-bedrock/series.json @@ -402,6 +402,17 @@ "reason": "Use the existing physical replay scope to prevent live remote interpolation during historical simulation." } ] + }, + { + "file": "0042-retain-bedrock-death-screen-lifetimes.patch", + "title": "fix(ui): retain Bedrock death screen lifetimes", + "prerequisites": [ + { + "project": "viafabricplus-bedrock", + "patch": "0020-apply-server-movement-and-camera-permissions.patch", + "reason": "Recognize Bedrock sessions across direct and ViaProxy connections." + } + ] } ], "deferred": [ diff --git a/patches/viafabricplus-bedrock/upstreamable/0042-retain-bedrock-death-screen-lifetimes.patch b/patches/viafabricplus-bedrock/upstreamable/0042-retain-bedrock-death-screen-lifetimes.patch new file mode 100644 index 00000000..95561252 --- /dev/null +++ b/patches/viafabricplus-bedrock/upstreamable/0042-retain-bedrock-death-screen-lifetimes.patch @@ -0,0 +1,384 @@ +From a05f4207d8a16e55417424cf3bc21fd177447e68 Mon Sep 17 00:00:00 2001 +From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> +Date: Thu, 5 Nov 2026 20:48:28 +0100 +Subject: [PATCH] fix(ui): retain Bedrock death screen lifetimes + +Update a retained death reason instead of re-entering the screen for each +DeathInfo or visibility-rule update. Keep its existing button deadline and +controls, and do not replace a confirmation or reopen the screen while a +respawn is pending. Adopt the local death timer's already-open screen. +Retire the retained screen when the player becomes alive, its object +changes, or its connection ends. Ignore stale connection updates. + +The active OreUI death controller in Bedrock 1.26.60.5/protocol 2225 reads +deathInfo as an observable view property. Its stage effects do not depend +on that text. Executing the original shipped controller confirms that +reason changes retain the button stage and pending respawn timers. Source +gameplay-6de8e340eddced55490b.js SHA-256: +9e7efe423b6cc8a1c1fa92756bd181675cffa51a4728bce386b0081cad1bb979. + +Headless regressions exercise the real Java death timer through text +changes, modal and pending-respawn ownership, first-reason adoption, and +connection/player lifetime replacement. The current presentation remains +Java; native OreUI typography, layout, rendering and complete lifecycle +porting are still unfinished. +--- + .../features/screen/DeathScreenAccess.java | 21 ++ + .../MixinBedrockDeathScreenUpdates.java | 46 +++++ + .../death/BedrockDeathScreenUpdates.java | 55 ++++++ + .../viafabricplus-bedrock.mixins.json | 2 + + .../death/BedrockDeathScreenUpdatesTest.java | 187 ++++++++++++++++++ + 5 files changed, 311 insertions(+) + create mode 100644 src/main/java/com/viaversion/viafabricplus/bedrock/injection/mixin/features/screen/DeathScreenAccess.java + create mode 100644 src/main/java/com/viaversion/viafabricplus/bedrock/injection/mixin/features/screen/MixinBedrockDeathScreenUpdates.java + create mode 100644 src/main/java/com/viaversion/viafabricplus/bedrock/screen/death/BedrockDeathScreenUpdates.java + create mode 100644 src/test/java/com/viaversion/viafabricplus/bedrock/screen/death/BedrockDeathScreenUpdatesTest.java + +diff --git a/src/main/java/com/viaversion/viafabricplus/bedrock/injection/mixin/features/screen/DeathScreenAccess.java b/src/main/java/com/viaversion/viafabricplus/bedrock/injection/mixin/features/screen/DeathScreenAccess.java +new file mode 100644 +index 0000000..016b30f +--- /dev/null ++++ b/src/main/java/com/viaversion/viafabricplus/bedrock/injection/mixin/features/screen/DeathScreenAccess.java +@@ -0,0 +1,21 @@ ++/* ++ * This file is part of ViaFabricPlus Bedrock - https://github.com/ViaVersionAddons/viafabricplus-bedrock ++ * Copyright (C) 2026 ViaVersion and contributors ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++package com.viaversion.viafabricplus.bedrock.injection.mixin.features.screen; ++ ++import net.minecraft.client.gui.screens.DeathScreen; ++import net.minecraft.client.player.LocalPlayer; ++import net.minecraft.network.chat.Component; ++import org.spongepowered.asm.mixin.Mixin; ++import org.spongepowered.asm.mixin.Mutable; ++import org.spongepowered.asm.mixin.gen.Accessor; ++ ++@Mixin(DeathScreen.class) ++public interface DeathScreenAccess { ++ @Accessor("player") LocalPlayer bedrockDeathPlayer(); ++ @Accessor("hardcore") boolean bedrockDeathHardcore(); ++ @Accessor("causeOfDeath") Component bedrockDeathReason(); ++ @Mutable @Accessor("causeOfDeath") void bedrockDeathReason(Component reason); ++} +diff --git a/src/main/java/com/viaversion/viafabricplus/bedrock/injection/mixin/features/screen/MixinBedrockDeathScreenUpdates.java b/src/main/java/com/viaversion/viafabricplus/bedrock/injection/mixin/features/screen/MixinBedrockDeathScreenUpdates.java +new file mode 100644 +index 0000000..be81aff +--- /dev/null ++++ b/src/main/java/com/viaversion/viafabricplus/bedrock/injection/mixin/features/screen/MixinBedrockDeathScreenUpdates.java +@@ -0,0 +1,46 @@ ++/* ++ * This file is part of ViaFabricPlus Bedrock - https://github.com/ViaVersionAddons/viafabricplus-bedrock ++ * Copyright (C) 2026 ViaVersion and contributors ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++package com.viaversion.viafabricplus.bedrock.injection.mixin.features.screen; ++ ++import com.viaversion.viafabricplus.bedrock.input.BedrockInputPermissions; ++import com.viaversion.viafabricplus.bedrock.screen.death.BedrockDeathScreenUpdates; ++import net.minecraft.client.Minecraft; ++import net.minecraft.client.gui.screens.DeathScreen; ++import net.minecraft.client.multiplayer.ClientPacketListener; ++import net.minecraft.network.protocol.game.ClientboundPlayerCombatKillPacket; ++import org.spongepowered.asm.mixin.Mixin; ++import org.spongepowered.asm.mixin.Unique; ++import org.spongepowered.asm.mixin.injection.At; ++import org.spongepowered.asm.mixin.injection.Inject; ++import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; ++ ++@Mixin(ClientPacketListener.class) ++public abstract class MixinBedrockDeathScreenUpdates { ++ @Unique private final BedrockDeathScreenUpdates bedrockDeathScreens = new BedrockDeathScreenUpdates(); ++ ++ @Inject(method = "handlePlayerCombatKill", at = @At(value = "INVOKE", target = ++ "Lnet/minecraft/network/protocol/PacketUtils;ensureRunningOnSameThread(Lnet/minecraft/network/protocol/Packet;Lnet/minecraft/network/PacketListener;Lnet/minecraft/network/PacketProcessor;)V", ++ shift = At.Shift.AFTER), cancellable = true) ++ private void updateReason(final ClientboundPlayerCombatKillPacket packet, final CallbackInfo ci) { ++ final var client = Minecraft.getInstance(); ++ final var player = client.player; ++ if (!BedrockInputPermissions.isBedrockConnection() || player == null || !player.isDeadOrDying() ++ || player.getId() != packet.playerId() || !player.shouldShowDeathScreen()) return; ++ // A dead local player need not remain registered in the world's entity lookup. ++ final var incoming = new DeathScreen(packet.message(), client.level.getLevelData().isHardcore(), player); ++ if (!this.bedrockDeathScreens.receive((ClientPacketListener) (Object) this, ++ client.getConnection(), player, client.gui.screen(), incoming)) { ++ client.gui.setScreen(incoming); ++ } ++ ci.cancel(); ++ } ++ ++ @Inject(method = "tick", at = @At("HEAD")) ++ private void retireDeathScreen(final CallbackInfo ci) { ++ final var client = Minecraft.getInstance(); ++ this.bedrockDeathScreens.tick(client.getConnection(), client.player); ++ } ++} +diff --git a/src/main/java/com/viaversion/viafabricplus/bedrock/screen/death/BedrockDeathScreenUpdates.java b/src/main/java/com/viaversion/viafabricplus/bedrock/screen/death/BedrockDeathScreenUpdates.java +new file mode 100644 +index 0000000..227f3d8 +--- /dev/null ++++ b/src/main/java/com/viaversion/viafabricplus/bedrock/screen/death/BedrockDeathScreenUpdates.java +@@ -0,0 +1,55 @@ ++/* ++ * This file is part of ViaFabricPlus Bedrock - https://github.com/ViaVersionAddons/viafabricplus-bedrock ++ * Copyright (C) 2026 ViaVersion and contributors ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++package com.viaversion.viafabricplus.bedrock.screen.death; ++ ++import com.viaversion.viafabricplus.bedrock.injection.mixin.features.screen.DeathScreenAccess; ++import net.minecraft.client.gui.screens.DeathScreen; ++import net.minecraft.client.gui.screens.Screen; ++import net.minecraft.client.multiplayer.ClientPacketListener; ++import net.minecraft.client.player.LocalPlayer; ++ ++/** One connection's death reason updates do not re-enter its screen or undo a pending respawn. */ ++public final class BedrockDeathScreenUpdates { ++ private ClientPacketListener owner; ++ private LocalPlayer player; ++ private DeathScreen screen; ++ ++ /** True consumes an update; false admits the supplied screen through the ordinary screen path. */ ++ public boolean receive(final ClientPacketListener source, final ClientPacketListener current, ++ final LocalPlayer player, final Screen foreground, final DeathScreen incoming) { ++ this.tick(current, player); ++ if (source != current || source == null || !source.getConnection().isConnected()) return true; ++ if (player == null || !player.isDeadOrDying() || !(incoming instanceof DeathScreenAccess next) ++ || next.bedrockDeathPlayer() != player) return false; ++ ++ // Prefer the actual foreground if Java or another screen path replaced the retained instance. ++ if (foreground instanceof DeathScreen existing && existing instanceof DeathScreenAccess previous ++ && previous.bedrockDeathPlayer() == player ++ && previous.bedrockDeathHardcore() == next.bedrockDeathHardcore()) { ++ this.owner = source; ++ this.player = player; ++ this.screen = existing; ++ } ++ if (this.screen == null || this.owner != source || this.player != player ++ || ((DeathScreenAccess) this.screen).bedrockDeathHardcore() != next.bedrockDeathHardcore()) { ++ this.owner = source; ++ this.player = player; ++ this.screen = incoming; ++ return false; ++ } ++ ((DeathScreenAccess) this.screen).bedrockDeathReason(next.bedrockDeathReason()); ++ return true; ++ } ++ ++ public void tick(final ClientPacketListener current, final LocalPlayer player) { ++ if (this.screen != null && (this.owner != current || this.player != player ++ || player == null || !player.isDeadOrDying() || !this.owner.getConnection().isConnected())) { ++ this.owner = null; ++ this.player = null; ++ this.screen = null; ++ } ++ } ++} +diff --git a/src/main/resources/viafabricplus-bedrock.mixins.json b/src/main/resources/viafabricplus-bedrock.mixins.json +index 4e383fc..67091e0 100644 +--- a/src/main/resources/viafabricplus-bedrock.mixins.json ++++ b/src/main/resources/viafabricplus-bedrock.mixins.json +@@ -161,6 +161,8 @@ + "features.render.MixinBedrockFirstPersonProjection", + "features.render.MixinBedrockCameraFov", + "features.render.MixinBedrockDeathCameraEffects", ++ "features.screen.DeathScreenAccess", ++ "features.screen.MixinBedrockDeathScreenUpdates", + "features.render.MixinBedrockCameraShake", + "features.render.MixinBedrockCameraPerspective", + "features.render.MixinBedrockFollowInput", +diff --git a/src/test/java/com/viaversion/viafabricplus/bedrock/screen/death/BedrockDeathScreenUpdatesTest.java b/src/test/java/com/viaversion/viafabricplus/bedrock/screen/death/BedrockDeathScreenUpdatesTest.java +new file mode 100644 +index 0000000..83adbca +--- /dev/null ++++ b/src/test/java/com/viaversion/viafabricplus/bedrock/screen/death/BedrockDeathScreenUpdatesTest.java +@@ -0,0 +1,187 @@ ++/* ++ * This file is part of ViaFabricPlus Bedrock - https://github.com/ViaVersionAddons/viafabricplus-bedrock ++ * Copyright (C) 2026 ViaVersion and contributors ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++package com.viaversion.viafabricplus.bedrock.screen.death; ++ ++import com.viaversion.viafabricplus.bedrock.injection.mixin.features.screen.DeathScreenAccess; ++import java.lang.reflect.Field; ++import java.util.List; ++import java.util.concurrent.atomic.AtomicBoolean; ++import net.minecraft.SharedConstants; ++import net.minecraft.client.Minecraft; ++import net.minecraft.client.gui.components.Button; ++import net.minecraft.client.gui.screens.DeathScreen; ++import net.minecraft.client.gui.screens.Screen; ++import net.minecraft.client.multiplayer.ClientPacketListener; ++import net.minecraft.client.player.LocalPlayer; ++import net.minecraft.network.Connection; ++import net.minecraft.network.chat.Component; ++import net.minecraft.server.Bootstrap; ++import org.junit.jupiter.api.AfterEach; ++import org.junit.jupiter.api.BeforeAll; ++import org.junit.jupiter.api.BeforeEach; ++import org.mockito.MockedStatic; ++import org.junit.jupiter.api.Test; ++ ++import static org.junit.jupiter.api.Assertions.*; ++import static org.mockito.Mockito.*; ++ ++class BedrockDeathScreenUpdatesTest { ++ private MockedStatic singleton; ++ ++ @BeforeEach ++ void client() { ++ final var client = mock(Minecraft.class); ++ this.singleton = mockStatic(Minecraft.class); ++ this.singleton.when(Minecraft::getInstance).thenReturn(client); ++ } ++ ++ @AfterEach ++ void closeClient() { this.singleton.close(); } ++ ++ @BeforeAll ++ static void bootstrap() { ++ SharedConstants.tryDetectVersion(); ++ Bootstrap.bootStrap(); ++ } ++ ++ @Test ++ void reasonChangesKeepTheExistingButtonDeadlineAndEnabledControls() throws Exception { ++ final var fixture = new Fixture(); ++ final var screen = fixture.screen("Initial reason"); ++ final var button = mock(Button.class); ++ button.active = false; ++ screen.exitButtons().add(button); ++ assertFalse(fixture.receive(null, screen), "The first death must still open a screen"); ++ for (int tick = 0; tick < 19; tick++) screen.tick(); ++ assertTrue(fixture.receive(screen, fixture.screen("Late reason"))); ++ assertEquals("Late reason", screen.bedrockDeathReason().getString()); ++ assertFalse(button.active); ++ screen.tick(); ++ assertTrue(button.active, "A text update must not restart the existing button deadline"); ++ assertTrue(fixture.receive(screen, fixture.screen(""))); ++ assertEquals("", screen.bedrockDeathReason().getString()); ++ assertTrue(button.active, "Hiding the reason must retain enabled controls"); ++ } ++ ++ @Test ++ void lateTextUpdatesTheRetainedScreenWithoutReplacingAModalOrPendingRespawn() { ++ final var fixture = new Fixture(); ++ final var screen = fixture.screen("Initial reason"); ++ assertFalse(fixture.receive(null, screen)); ++ final var confirmation = mock(Screen.class); ++ assertTrue(fixture.receive(confirmation, fixture.screen("While confirming"))); ++ assertEquals("While confirming", screen.bedrockDeathReason().getString()); ++ assertTrue(fixture.receive(null, fixture.screen("While respawning"))); ++ assertEquals("While respawning", screen.bedrockDeathReason().getString()); ++ } ++ ++ @Test ++ void adoptsThePlayersAlreadyOpenDeathScreenBeforeTheFirstReasonPacket() throws Exception { ++ final var fixture = new Fixture(); ++ final var automatic = fixture.screen(""); ++ for (int tick = 0; tick < 9; tick++) automatic.tick(); ++ assertTrue(fixture.receive(automatic, fixture.screen("First reason"))); ++ assertEquals("First reason", automatic.bedrockDeathReason().getString()); ++ assertEquals(9, automatic.field("delayTicker")); ++ } ++ ++ @Test ++ void updatesTheVisibleReplacementInsteadOfAnOlderRetainedScreen() { ++ final var fixture = new Fixture(); ++ final var old = fixture.screen("Original"); ++ assertFalse(fixture.receive(null, old)); ++ final var replacement = fixture.screen("Replacement"); ++ assertTrue(fixture.receive(replacement, fixture.screen("Current reason"))); ++ assertEquals("Current reason", replacement.bedrockDeathReason().getString()); ++ assertEquals("Original", old.bedrockDeathReason().getString()); ++ } ++ ++ @Test ++ void respawnAndConnectionReplacementRetireOldReasonsEvenWhenPlayerIdsRepeat() { ++ final var fixture = new Fixture(); ++ final var old = fixture.screen("Old death"); ++ assertFalse(fixture.receive(null, old)); ++ fixture.dead.set(false); ++ fixture.updates.tick(fixture.owner, fixture.player); ++ fixture.dead.set(true); ++ final var next = fixture.screen("Next death"); ++ assertFalse(fixture.receive(null, next)); ++ assertEquals("Old death", old.bedrockDeathReason().getString()); ++ ++ final var replacementOwner = fixture.listener(); ++ final var replacementPlayer = mock(LocalPlayer.class); ++ when(replacementPlayer.isDeadOrDying()).thenReturn(true); ++ when(replacementPlayer.getId()).thenReturn(7); ++ fixture.updates.tick(replacementOwner, replacementPlayer); ++ assertTrue(fixture.updates.receive(fixture.owner, replacementOwner, replacementPlayer, null, ++ new TestDeathScreen("Stale packet", replacementPlayer))); ++ final var current = new TestDeathScreen("Current death", replacementPlayer); ++ assertFalse(fixture.updates.receive(replacementOwner, replacementOwner, replacementPlayer, null, current)); ++ assertEquals("Next death", next.bedrockDeathReason().getString()); ++ assertTrue(fixture.updates.receive(replacementOwner, replacementOwner, replacementPlayer, current, ++ new TestDeathScreen("Current update", replacementPlayer))); ++ assertEquals("Current update", current.bedrockDeathReason().getString()); ++ } ++ ++ private static final class Fixture { ++ private final BedrockDeathScreenUpdates updates = new BedrockDeathScreenUpdates(); ++ private final ClientPacketListener owner = this.listener(); ++ private final LocalPlayer player = mock(LocalPlayer.class); ++ private final AtomicBoolean dead = new AtomicBoolean(true); ++ ++ private Fixture() { ++ when(this.player.isDeadOrDying()).thenAnswer(call -> this.dead.get()); ++ when(this.player.getId()).thenReturn(7); ++ } ++ ++ private ClientPacketListener listener() { ++ final var listener = mock(ClientPacketListener.class); ++ final var connection = mock(Connection.class); ++ when(listener.getConnection()).thenReturn(connection); ++ when(connection.isConnected()).thenReturn(true); ++ return listener; ++ } ++ ++ private TestDeathScreen screen(final String reason) { return new TestDeathScreen(reason, this.player); } ++ ++ private boolean receive(final Screen foreground, final TestDeathScreen incoming) { ++ return this.updates.receive(this.owner, this.owner, this.player, foreground, incoming); ++ } ++ } ++ ++ /** Headless bridge for the fields supplied by DeathScreenAccess in the installed client. */ ++ private static final class TestDeathScreen extends DeathScreen implements DeathScreenAccess { ++ private TestDeathScreen(final String reason, final LocalPlayer player) { ++ super(Component.literal(reason), false, player); ++ } ++ ++ private Object field(final String name) { ++ try { ++ final Field field = DeathScreen.class.getDeclaredField(name); ++ field.setAccessible(true); ++ return field.get(this); ++ } catch (ReflectiveOperationException error) { ++ throw new AssertionError(error); ++ } ++ } ++ ++ @SuppressWarnings("unchecked") ++ private List