Skip to content

Commit 4054eba

Browse files
authored
Merge pull request #46 from SkyBlade1978/feature/advantage-1.12.2-test-coverage
Fix Power Advantage 1.12.2 machine state and strengthen tests
2 parents 40811a0 + 7ea484b commit 4054eba

96 files changed

Lines changed: 12049 additions & 52 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.gitignore‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@
1515
/.idea/
1616

1717
# Gradle and Forge run output
18+
**/build/
19+
**/.gradle/
20+
**/logs/
21+
**/.cache/
22+
**/__pycache__/
23+
**/.venv/
1824
/build/
1925
/.gradle/
2026
/eclipse/
@@ -28,6 +34,10 @@
2834
/.ci-dependencies/
2935

3036
# Machine-specific agent context
37+
**/AGENTS.md
38+
**/AGENT.md
39+
**/agent-notes/
40+
**/.codex/
3141
/AGENTS.md
3242
/AGENT.md
3343
/agent-notes/

‎CHANGELOG.txt‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
Unreleased test-coverage repairs for 1.12.2
2+
3+
- Restore the missing carbon-dust recipe name from registered coal and charcoal powders, without replacing existing entries or changing recipe amounts.
4+
- Enable Forge's universal bucket during mod construction so oil buckets work without another mod enabling them first.
5+
- Stop steam, electricity and fluids from loading chunks or crossing an unloaded pipe section. Reconnect the network when that section loads again.
6+
- Keep refused energy available to the rest of the network instead of charging it to the source.
7+
- Check all conveyor destination slots, respect stack limits and preserve different item NBT.
8+
- Route overflow through the same insertion checks and save both inventories after transfers.
9+
- Stop the smeltable filter from accepting items whose furnace result is an empty stack.
10+
- Save changing timers and energy even when a machine has not sent a GUI update.
11+
- Add measured natural-tick jobs, negative controls and exact full-process restart comparisons.
12+
113
Power Advantage 2.4.1.112021
214

315
- Restore conduit networks and machine GUI state after a world reload.

‎README.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
Power Advantage provides shared power, fluid, and item transport systems for its Minecraft 1.12.2 expansion mods.
44

5+
The [test coverage backport notes](TEST-COVERAGE-BACKPORT.md#final-recorded-candidate) record the final machine, restart, control, partial-network, GUI, endurance, connected-world and reproducibility results alongside exact candidate hashes. The bounded legacy checks pass; the recipe progression graph is still partial. Earlier failures, modern follow-up findings and manual-testing limits are kept in the notes. This is not an all-machines or all-worlds guarantee.
6+
57
## Building Minecraft 1.12.2
68

79
The `master-1.12.2` build uses ForgeGradle 7.0.34 and Gradle 9.6.1. Run Gradle with Java 17; Gradle resolves the Java 8 toolchain used for compilation.

‎TEST-COVERAGE-BACKPORT.md‎

Lines changed: 229 additions & 0 deletions
Large diffs are not rendered by default.

‎build.gradle‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,21 @@ plugins {
1414
id 'net.minecraftforge.gradle' version '7.0.34'
1515
}
1616

17+
sourceSets {
18+
guiProbe {
19+
java.srcDir 'test-rig/client-resources/java'
20+
resources.srcDir 'test-rig/client-resources/resources'
21+
compileClasspath += sourceSets.main.compileClasspath + sourceSets.main.output
22+
}
23+
}
24+
25+
tasks.register('clientResourceProbeJar', Jar) {
26+
dependsOn tasks.named('guiProbeClasses')
27+
from sourceSets.guiProbe.output
28+
archiveFileName = 'advantageclientresources-deobf.jar'
29+
destinationDirectory = layout.buildDirectory.dir('client-resource-probe')
30+
}
31+
1732
group = project.mod_group
1833
version = project.mod_version
1934
base.archivesName = 'PowerAdvantage'
@@ -172,6 +187,7 @@ def rebornCoreCoordinate = usingStagedRebornCore
172187
: "RebornCore:RebornCore-1.12.2:${project.reborncore_version}:universal"
173188

174189
dependencies {
190+
testImplementation 'junit:junit:4.13.2'
175191
implementation minecraft.dependency(
176192
"net.minecraftforge:forge:${project.minecraft_version}-${project.forge_version}")
177193
// Mavenizer's legacy metadata omits this compile-time Minecraft library.

‎src/main/java/com/mcmoddev/poweradvantage/PowerAdvantage.java‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import net.minecraft.item.ItemStack;
1212
import net.minecraft.tileentity.TileEntity;
1313
import net.minecraftforge.common.config.Configuration;
14+
import net.minecraftforge.fluids.FluidRegistry;
1415
import net.minecraftforge.fml.common.FMLLog;
1516
import net.minecraftforge.fml.common.Mod;
1617
import net.minecraftforge.fml.common.Mod.EventHandler;
@@ -216,6 +217,11 @@ compile files(
216217
acceptedMinecraftVersions = "[1.12.2]")
217218
public class PowerAdvantage
218219
{
220+
// Forge registers the bucket during its pre-init, before our fluid setup.
221+
static {
222+
FluidRegistry.enableUniversalBucket();
223+
}
224+
219225
/** The identifier for this mod */
220226
public static final String MODID = "poweradvantage";
221227
/** The display name for this mod */

‎src/main/java/com/mcmoddev/poweradvantage/compat/BaseMetalsCompat.java‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,25 @@
55
import net.minecraft.block.Block;
66
import net.minecraft.item.Item;
77
import net.minecraft.item.ItemStack;
8+
import net.minecraft.item.crafting.IRecipe;
89
import net.minecraft.util.ResourceLocation;
10+
import net.minecraftforge.event.RegistryEvent;
911
import net.minecraftforge.fml.common.FMLLog;
1012
import net.minecraftforge.fml.common.Loader;
13+
import net.minecraftforge.fml.common.Mod;
14+
import net.minecraftforge.fml.common.eventhandler.EventPriority;
15+
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
1116
import net.minecraftforge.fml.common.registry.ForgeRegistries;
1217
import net.minecraftforge.oredict.OreDictionary;
1318
import org.apache.logging.log4j.Level;
1419

1520
import java.lang.reflect.Field;
1621
import java.lang.reflect.Method;
1722
import java.util.List;
23+
import java.util.function.BiConsumer;
24+
import java.util.function.Function;
1825

26+
@Mod.EventBusSubscriber(modid = PowerAdvantage.MODID)
1927
public final class BaseMetalsCompat {
2028

2129
private static final String MODID = "basemetals";
@@ -32,6 +40,26 @@ public static boolean isLoaded() {
3240
return Loader.isModLoaded(MODID);
3341
}
3442

43+
@SubscribeEvent(priority = EventPriority.HIGHEST)
44+
public static void registerRecipeAliases(RegistryEvent.Register<IRecipe> event) {
45+
// Item registration has finished; populate aliases before recipe factories inspect them.
46+
registerCarbonDustFallback();
47+
}
48+
49+
public static void registerCarbonDustFallback() {
50+
registerCarbonDustFallback(OreDictionary::getOres, OreDictionary::registerOre);
51+
}
52+
53+
static void registerCarbonDustFallback(Function<String, List<ItemStack>> ores,
54+
BiConsumer<String, ItemStack> register) {
55+
if (!ores.apply("dustCarbon").isEmpty()) return;
56+
for (String source : new String[]{"dustCoal", "dustCharcoal"}) {
57+
for (ItemStack stack : ores.apply(source)) {
58+
if (stack != null && !stack.isEmpty()) register.accept("dustCarbon", stack.copy());
59+
}
60+
}
61+
}
62+
3563
public static void setStrongHammers(boolean enabled) {
3664
if (!isLoaded()) return;
3765
if (setOldStrongHammers(enabled)) return;

‎src/main/java/com/mcmoddev/poweradvantage/conduitnetwork/ConduitNetworkManager.java‎

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import com.mcmoddev.poweradvantage.util.PowerHelper;
1414
import net.minecraft.block.state.IBlockState;
1515
import net.minecraft.util.EnumFacing;
16+
import net.minecraft.util.math.ChunkPos;
1617
import net.minecraft.world.World;
1718

1819
/**
@@ -124,18 +125,40 @@ public void invalidateAll() {
124125
}
125126

126127
/**
127-
* Invalidates the cached network at a block location and then rescans the world to make a new
128-
* cached network
128+
* Invalidates networks crossing a changed chunk or touching its boundary.
129+
* Boundary networks must be rescanned when a pipe-only chunk returns.
129130
*
130-
* @param coord The block position that needs revalidating
131-
* @param w The world instance for the same dimension as the coord
132-
* @param type The energy type that is triggering the revalidation (should be the same type as
133-
* this manager)
131+
* @param dimension dimension containing the changed chunk
132+
* @param chunk changed chunk position
133+
*/
134+
public void invalidateChunk(int dimension, ChunkPos chunk) {
135+
lock.writeLock().lock();
136+
try {
137+
int minX = (chunk.x << 4) - 1;
138+
int minZ = (chunk.z << 4) - 1;
139+
for (BlockPos4D coord : new ArrayList<>(networkCache.keySet())) {
140+
if (coord.dimension == dimension && coord.pos.getX() >= minX
141+
&& coord.pos.getX() <= minX + 17 && coord.pos.getZ() >= minZ
142+
&& coord.pos.getZ() <= minZ + 17) invalidate(coord);
143+
}
144+
} finally {
145+
lock.writeLock().unlock();
146+
}
147+
}
148+
149+
/**
150+
* Rebuilds a network using only blocks already loaded in this world.
151+
*
152+
* @param coord origin of the network
153+
* @param w world containing the network
154+
* @param type conduit type being scanned
134155
*/
135156
public void revalidate(BlockPos4D coord, World w, ConduitType type) {
136157
lock.writeLock().lock();
137158
try {
138159
invalidate(coord);
160+
if (!w.isBlockLoaded(coord.pos)) return;
161+
createNewNetwork(coord);
139162
recursiveScan(w, coord, w.getBlockState(coord.pos), type);
140163
} finally {
141164
lock.writeLock().unlock();
@@ -172,7 +195,7 @@ protected void recursiveScan(World w, BlockPos4D coord, IBlockState src, Conduit
172195
for (int i = 0; i < EnumFacing.values().length; i++) {
173196
EnumFacing face = EnumFacing.values()[i];
174197
BlockPos4D n = coord.offset(face);
175-
if (areInSameNetwork(coord, n)) {
198+
if (!w.isBlockLoaded(n.pos) || areInSameNetwork(coord, n)) {
176199
continue;
177200
}
178201
IBlockState blockstate = w.getBlockState(n.pos);

‎src/main/java/com/mcmoddev/poweradvantage/conduitnetwork/ConduitRegistry.java‎

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@
1414
import net.minecraft.tileentity.TileEntity;
1515
import net.minecraft.util.EnumFacing;
1616
import net.minecraft.util.math.BlockPos;
17+
import net.minecraft.util.math.ChunkPos;
1718
import net.minecraft.world.World;
19+
import net.minecraftforge.common.MinecraftForge;
20+
import net.minecraftforge.event.world.ChunkEvent;
21+
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
1822

1923
import java.util.*;
2024
import java.util.concurrent.locks.Lock;
@@ -45,6 +49,7 @@ public static ConduitRegistry getInstance() {
4549
try {
4650
if (instance == null) {
4751
instance = new ConduitRegistry();
52+
MinecraftForge.EVENT_BUS.register(instance);
4853
}
4954
} finally {
5055
initLock.unlock();
@@ -87,6 +92,7 @@ public List<PowerRequest> getRequestsForPower(World w, BlockPos coord, ConduitTy
8792
* priority.
8893
*/
8994
public List<PowerRequest> getRequestsForPower(World w, BlockPos coord, ConduitType conduitType, ConduitType energyType) {
95+
if (!w.isBlockLoaded(coord)) return Collections.emptyList();
9096
int dimension = w.provider.getDimension();
9197
clearIfWorldChanged(w, dimension);
9298
ConduitNetworkManager manager = getConduitNetworkManager(conduitType);
@@ -107,6 +113,7 @@ public List<PowerRequest> getRequestsForPower(World w, BlockPos coord, ConduitTy
107113
private List<PowerRequest> collectRequests(World w, List<BlockPos4D> net, ConduitType conduitType, ConduitType energyType) {
108114
List<PowerRequest> requests = new ArrayList<>();
109115
for (BlockPos4D pos : net) {
116+
if (!w.isBlockLoaded(pos.pos)) continue;
110117
Block b = w.getBlockState(pos.pos).getBlock();
111118
if (b instanceof ITileEntityProvider) {
112119
TileEntity e = w.getTileEntity(pos.pos);
@@ -184,27 +191,29 @@ public static float transmitPowerToConsumers(
184191
World world, BlockPos srcPos, Object providerInstance
185192
) {
186193
List<PowerRequest> requests = ConduitRegistry.getInstance().getRequestsForPower(world, srcPos, networkType, powerType);
194+
return distributePower(availableEnergy, powerType, minimumPriority, world, providerInstance, requests);
195+
}
196+
197+
static float distributePower(float availableEnergy, ConduitType powerType, byte minimumPriority,
198+
World world, Object providerInstance, List<PowerRequest> requests) {
199+
if (!Float.isFinite(availableEnergy) || availableEnergy <= 0) return 0;
187200
float e = availableEnergy;
188201
for (PowerRequest req : requests) {
189-
if (req.amount <= 0) continue;
202+
if (!Float.isFinite(req.amount) || req.amount <= 0) continue;
190203
if (req.entity == providerInstance) continue;
191204
if (req.priority < minimumPriority) continue;
192205

206+
float offered = Math.min(e, req.amount);
207+
float accepted;
193208
if (req instanceof ExternalPowerRequest) {
194-
e -= LightWeightPowerRegistry.getInstance().addPower(world, ((ExternalPowerRequest) req).pos,
195-
powerType, Math.min(e, req.amount));
196-
if (e <= 0) break;
197-
continue;
198-
}
199-
200-
if (req.entity == null) continue;
201-
if (req.amount < e) {
202-
e -= req.entity.addEnergy(req.amount, powerType);
209+
accepted = LightWeightPowerRegistry.getInstance().addPower(world, ((ExternalPowerRequest) req).pos,
210+
powerType, offered);
203211
} else {
204-
req.entity.addEnergy(e, powerType);
205-
e = 0;
206-
break;
212+
if (req.entity == null) continue;
213+
accepted = req.entity.addEnergy(offered, powerType);
207214
}
215+
if (Float.isFinite(accepted)) e -= Math.max(0, Math.min(offered, accepted));
216+
if (e <= 0) break;
208217
}
209218
return availableEnergy - e;
210219
}
@@ -297,6 +306,29 @@ public void conduitBlockRemovedEvent(World w, int dimension, BlockPos location,
297306
}
298307
}
299308

309+
/** Clears networks crossing a changed chunk, including adjacent pipe-only boundaries. */
310+
@SubscribeEvent
311+
public void onChunkLoad(ChunkEvent.Load event) {
312+
invalidateChunk(event);
313+
}
314+
315+
/** Clears networks before an unloaded section can be used for another transfer. */
316+
@SubscribeEvent
317+
public void onChunkUnload(ChunkEvent.Unload event) {
318+
invalidateChunk(event);
319+
}
320+
321+
private void invalidateChunk(ChunkEvent event) {
322+
World world = event.getWorld();
323+
if (world.isRemote) return;
324+
int dimension = world.provider.getDimension();
325+
clearIfWorldChanged(world, dimension);
326+
for (ConduitNetworkManager manager : networkManagers.values()) {
327+
manager.invalidateChunk(dimension, new ChunkPos(
328+
event.getChunk().x, event.getChunk().z));
329+
}
330+
}
331+
300332
private ConduitNetworkManager getConduitNetworkManager(ConduitType type) {
301333
if (networkManagers.containsKey(type)) {
302334
return networkManagers.get(type);

‎src/main/java/com/mcmoddev/poweradvantage/gui/FilteredFluidTankGUI.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public static void drawFluidBar(
3737
FluidStack fs, float barHeight, int xPos, int yPos,
3838
ResourceLocation displayImage, GUIContainer guiContainer, int x, int y, float zLevel
3939
) {
40+
barHeight = Float.isFinite(barHeight) ? Math.max(0F, Math.min(1F, barHeight)) : 0F;
4041
final int w = 16;
4142
final int barSlotHeight = 60;
4243
final int h = (int) (barSlotHeight * barHeight);

0 commit comments

Comments
 (0)