From cb13f9740f241a360c7929bf13abde9838f30477 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 9 Nov 2026 07:44:50 +0000 Subject: [PATCH 1/2] Fix parts not being reattached to their network after chunk reloads When a chunk with a cable is loaded, the network lazily revalidates its updated parts. The first such part links the cable's network carrier, after which revalidateNetworkElements skips the cable. Other parts on that cable were therefore never passed afterNetworkReAlive, and parts that are not updated, such as Integrated Tunnels interfaces, were not even revalidated. For example, an energy interface sharing a cable with an omni-directional connector stopped accepting energy after a reload, until the network was reinitialized with a wrench. The part that links the network carrier now also revalidates the other parts at its position. Closes #1785 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01UeSuG8QxSiLg19VkMZScf3 --- .../gametest/GameTestsNetwork.java | 38 +++++++++++++++++++ .../core/network/PartNetworkElement.java | 14 +++++++ 2 files changed, 52 insertions(+) diff --git a/src/integrationtest/java/org/cyclops/integrateddynamics/gametest/GameTestsNetwork.java b/src/integrationtest/java/org/cyclops/integrateddynamics/gametest/GameTestsNetwork.java index ea94946859b..5c1e2b93ecd 100644 --- a/src/integrationtest/java/org/cyclops/integrateddynamics/gametest/GameTestsNetwork.java +++ b/src/integrationtest/java/org/cyclops/integrateddynamics/gametest/GameTestsNetwork.java @@ -3,6 +3,7 @@ import com.google.common.collect.Sets; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.core.HolderLookup; import net.minecraft.gametest.framework.GameTest; import net.minecraft.gametest.framework.GameTestHelper; import net.minecraft.world.InteractionHand; @@ -17,18 +18,23 @@ import net.neoforged.neoforge.gametest.PrefixGameTestTemplate; import org.cyclops.cyclopscore.client.particle.ParticleBlurData; import org.cyclops.cyclopscore.datastructure.DimPos; +import org.cyclops.cyclopscore.helper.BlockEntityHelpers; import org.cyclops.integrateddynamics.IntegratedDynamics; import org.cyclops.integrateddynamics.Reference; import org.cyclops.integrateddynamics.RegistryEntries; import org.cyclops.integrateddynamics.api.network.INetwork; +import org.cyclops.integrateddynamics.api.part.PartPos; import org.cyclops.integrateddynamics.api.path.ISidedPathElement; import org.cyclops.integrateddynamics.capability.path.PathElementPosition; import org.cyclops.integrateddynamics.capability.path.SidedPathElement; +import org.cyclops.integrateddynamics.core.blockentity.BlockEntityMultipartTicking; import org.cyclops.integrateddynamics.core.helper.CableHelpers; import org.cyclops.integrateddynamics.core.helper.NetworkHelpers; import org.cyclops.integrateddynamics.core.helper.PartHelpers; +import org.cyclops.integrateddynamics.core.network.PartNetworkElement; import org.cyclops.integrateddynamics.core.part.PartTypes; import org.cyclops.integrateddynamics.core.persist.world.NetworkWorldStorage; +import org.cyclops.integrateddynamics.part.PartTypeConnector; import java.util.Optional; @@ -2056,4 +2062,36 @@ public void testNetworkOmnidirectionalUnconnected(GameTestHelper helper) { }); } + @GameTest(template = TEMPLATE_EMPTY) + public void testNetworkRevalidateAllPartsAtPosition(GameTestHelper helper) { + // Place a cable with two parts + helper.setBlock(POS, RegistryEntries.BLOCK_CABLE.value()); + ItemStack omniDirectionalConnector = new ItemStack(PartTypes.CONNECTOR_OMNI.getItem()); + omniDirectionalConnector.set(RegistryEntries.DATACOMPONENT_OMNIDIRECTIONAL_GROUP, 1); + PartHelpers.addPart(helper.getLevel(), helper.absolutePos(POS), Direction.DOWN, PartTypes.CONNECTOR_OMNI, omniDirectionalConnector.copy()); + PartHelpers.addPart(helper.getLevel(), helper.absolutePos(POS), Direction.UP, PartTypes.CONNECTOR_OMNI, omniDirectionalConnector.copy()); + PartPos posDown = PartPos.of(helper.getLevel(), helper.absolutePos(POS), Direction.DOWN); + PartPos posUp = PartPos.of(helper.getLevel(), helper.absolutePos(POS), Direction.UP); + + helper.runAfterDelay(20, () -> { + INetwork network = NetworkHelpers.getNetworkChecked(helper.getLevel(), helper.absolutePos(POS), null); + + // Simulate loading the cable from disk, which creates new part states that are not attached to the network yet + BlockEntityMultipartTicking blockEntity = BlockEntityHelpers.get(helper.getLevel(), helper.absolutePos(POS), BlockEntityMultipartTicking.class).orElseThrow(); + HolderLookup.Provider provider = helper.getLevel().registryAccess(); + blockEntity.getPartContainer().deserializeNBT(provider, blockEntity.getPartContainer().serializeNBT(provider)); + blockEntity.getNetworkCarrier().setNetwork(null); + helper.assertTrue(((PartTypeConnector.State) PartHelpers.getPart(posUp).getState()).getPosition() == null, "Part state was not recreated"); + + // The network lazily revalidates the first updatable part at this position, which links the cable to the network + new PartNetworkElement<>(PartTypes.CONNECTOR_OMNI, posDown).revalidate(network); + helper.assertTrue(blockEntity.getNetwork() == network, "Network was not revalidated"); + + // The other part at this position must also be attached to the network again + helper.assertTrue(((PartTypeConnector.State) PartHelpers.getPart(posDown).getState()).getPosition() != null, "First part was not attached to the network"); + helper.assertTrue(((PartTypeConnector.State) PartHelpers.getPart(posUp).getState()).getPosition() != null, "Second part was not attached to the network"); + helper.succeed(); + }); + } + } diff --git a/src/main/java/org/cyclops/integrateddynamics/core/network/PartNetworkElement.java b/src/main/java/org/cyclops/integrateddynamics/core/network/PartNetworkElement.java index 6efea9b3eeb..a05666c5842 100644 --- a/src/main/java/org/cyclops/integrateddynamics/core/network/PartNetworkElement.java +++ b/src/main/java/org/cyclops/integrateddynamics/core/network/PartNetworkElement.java @@ -118,6 +118,20 @@ public void revalidate(INetwork network) { super.revalidate(network); if (revalidatePositioned(network, this.center.getPos())) { afterNetworkReAlive(network); + + // The other parts at this position share the network carrier that was just linked, + // so NetworkHelpers#revalidateNetworkElements will skip them, + // and parts that are not updated, such as interfaces, are never revalidated by the network. + DimPos dimPos = this.center.getPos(); + NetworkHelpers.getNetworkElementProvider(dimPos.getLevel(true), dimPos.getBlockPos(), null) + .ifPresent(networkElementProvider -> { + for (INetworkElement element : networkElementProvider.createNetworkElements(dimPos.getLevel(true), dimPos.getBlockPos())) { + if (!element.equals(this)) { + element.revalidate(network); + element.afterNetworkReAlive(network); + } + } + }); } } From b6f5b46897a18069f35e252e769b8f29712a7294 Mon Sep 17 00:00:00 2001 From: Ruben Taelman Date: Mon, 9 Nov 2026 16:36:55 +0100 Subject: [PATCH 2/2] Update src/main/java/org/cyclops/integrateddynamics/core/network/PartNetworkElement.java --- .../integrateddynamics/core/network/PartNetworkElement.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/org/cyclops/integrateddynamics/core/network/PartNetworkElement.java b/src/main/java/org/cyclops/integrateddynamics/core/network/PartNetworkElement.java index a05666c5842..fc332085c9c 100644 --- a/src/main/java/org/cyclops/integrateddynamics/core/network/PartNetworkElement.java +++ b/src/main/java/org/cyclops/integrateddynamics/core/network/PartNetworkElement.java @@ -119,9 +119,7 @@ public void revalidate(INetwork network) { if (revalidatePositioned(network, this.center.getPos())) { afterNetworkReAlive(network); - // The other parts at this position share the network carrier that was just linked, - // so NetworkHelpers#revalidateNetworkElements will skip them, - // and parts that are not updated, such as interfaces, are never revalidated by the network. + // Ensure that non-ticking parts are also revalidated. DimPos dimPos = this.center.getPos(); NetworkHelpers.getNetworkElementProvider(dimPos.getLevel(true), dimPos.getBlockPos(), null) .ifPresent(networkElementProvider -> {