From 93059c7c0f0836f5cfc45ef409fd14b71a47c54f Mon Sep 17 00:00:00 2001 From: JohnBraham Date: Thu, 12 Nov 2026 04:03:18 +0000 Subject: [PATCH 1/7] Port baseElectric to native Forge 26.1.2 local checkpoint --- .github/verify-published-pins.sh | 10 +- .github/workflows/ci.yml | 39 +- .github/workflows/codeql-analysis.yml | 27 +- .github/workflows/validate-gradle-build.yml | 4 +- CHANGELOG.txt | 7 + README.md | 75 ++-- build.gradle | 359 ++++++++++++++---- docs/NATIVE2612.md | 137 +++++++ gradle.properties | 82 ++-- ...enizer.gradle => forge64-mavenizer.gradle} | 14 +- .../gametest/ElectricGameTests.java | 30 ++ .../mc/baseelectric/ElectricAdvantage.java | 6 +- .../moddev/mc/baseelectric/blocks/Ore.java | 5 +- .../baseelectric/compat/BaseMetalsCompat.java | 4 +- .../entities/HydroturbineEntity.java | 2 +- .../graphics/ElectricRenderTypes.java | 3 +- .../graphics/HydroturbineRenderer.java | 2 +- .../graphics/LaserDrillRenderer.java | 2 +- .../graphics/LaserTurretRenderer.java | 2 +- .../baseelectric/init/OreSpawnWorldGen.java | 4 +- .../moddev/mc/baseelectric/init/Villages.java | 45 --- .../machines/ElectricMachineTileEntity.java | 2 +- .../machines/GrowthChamberTileEntity.java | 6 +- .../PhotovoltaicGeneratorTileEntity.java | 2 +- ...eamPoweredElectricGeneratorTileEntity.java | 2 +- .../util/farming/VirtualCrop.java | 4 +- src/main/resources/META-INF/mods.toml | 4 +- .../librarian/blank_circuit_board.json | 22 ++ .../librarian/electric_conduit.json | 22 ++ .../librarian/integrated_circuit.json | 22 ++ .../librarian/petrolplastic_ingot.json | 22 ++ .../villager_trade/librarian/psu.json | 16 + .../villager_trade/librarian/solder.json | 22 ++ .../villager_trade/librarian/level_1.json | 9 + .../villager_trade/librarian/level_2.json | 7 + src/main/resources/pack.mcmeta | 4 +- ...tricOperationalHydroturbineAnchorTest.java | 4 +- .../mc/baseelectric/init/VillagesTest.java | 116 ++++-- .../items/BatteryComponentTest.java | 14 +- .../ElectricOperationalNativeDefectsTest.java | 4 +- .../ElectricOperationalPumpPickupTest.java | 2 +- .../ElectricOperationalRedstoneCacheTest.java | 2 +- ...ElectricOperationalSoundBroadcastTest.java | 2 +- .../machines/Native118ConstructionTest.java | 2 +- .../resources/BlockLootResourceTest.java | 25 +- .../resources/BuildWorkflowContractTest.java | 15 +- .../NativePackRangeContractTest.java | 44 ++- .../resources/RecipeResourceTest.java | 4 +- .../resources/ShaderResourceContractTest.java | 5 +- .../crafting/RecipeDeconstructorTest.java | 13 +- .../verification/NativePackResourceProof.java | 41 +- .../verification/NativeTestBootstrap.java | 9 + tools/verification/AuditedJUnit.java | 2 +- tools/verification/EclipseTestInventory.java | 64 ++++ .../Invoke-ElectricPackagedSmoke.ps1 | 23 +- tools/verification/Verify-ElectricEcj.ps1 | 102 +++-- tools/verify-native-port.js | 88 +++-- 57 files changed, 1188 insertions(+), 418 deletions(-) create mode 100644 docs/NATIVE2612.md rename gradle/{forge61-mavenizer.gradle => forge64-mavenizer.gradle} (58%) delete mode 100644 src/main/java/zone/moddev/mc/baseelectric/init/Villages.java create mode 100644 src/main/resources/data/baseelectric/villager_trade/librarian/blank_circuit_board.json create mode 100644 src/main/resources/data/baseelectric/villager_trade/librarian/electric_conduit.json create mode 100644 src/main/resources/data/baseelectric/villager_trade/librarian/integrated_circuit.json create mode 100644 src/main/resources/data/baseelectric/villager_trade/librarian/petrolplastic_ingot.json create mode 100644 src/main/resources/data/baseelectric/villager_trade/librarian/psu.json create mode 100644 src/main/resources/data/baseelectric/villager_trade/librarian/solder.json create mode 100644 src/main/resources/data/minecraft/tags/villager_trade/librarian/level_1.json create mode 100644 src/main/resources/data/minecraft/tags/villager_trade/librarian/level_2.json create mode 100644 tools/verification/EclipseTestInventory.java diff --git a/.github/verify-published-pins.sh b/.github/verify-published-pins.sh index 40fb118f..eadb217f 100644 --- a/.github/verify-published-pins.sh +++ b/.github/verify-published-pins.sh @@ -18,10 +18,10 @@ done require_merged() { local repo="$1" ref="$2" status [[ "$ref" =~ ^[a-f0-9]{40}$ ]] - status="$(gh api "repos/MinecraftModDevelopmentMods/$repo/compare/$ref...master-1.21.11" --jq '.status')" + status="$(gh api "repos/MinecraftModDevelopmentMods/$repo/compare/$ref...master-26.1.2" --jq '.status')" case "$status" in identical|ahead) ;; - *) echo "::error::$repo $ref is not on upstream master-1.21.11 ($status)."; exit 1 ;; + *) echo "::error::$repo $ref is not on upstream master-26.1.2 ($status)."; exit 1 ;; esac } @@ -48,7 +48,7 @@ require_merged basePower "$power_ref" require_merged BaseMetals "$(value basemetals_source_commit)" require_merged OreSpawn "$orespawn_ref" -inputs="${RUNNER_TEMP:?}/forge61-release-dependencies" +inputs="${RUNNER_TEMP:?}/forge64-release-dependencies" mkdir -p "$inputs" power="$inputs/basePower-$(value poweradvantage_version).jar" metals="$inputs/BaseMetals-$(value basemetals_version).jar" @@ -57,8 +57,8 @@ public_jar basePower 1780140 "$(value poweradvantage_public_file_id)" "$(value p public_jar BaseMetals 240967 "$(value basemetals_public_file_id)" "$(value basemetals_deobf_sha256)" "$metals" public_jar OreSpawn 245586 "$(value orespawn_public_file_id)" "$(value orespawn_deobf_sha256)" "$orespawn" -installer="$inputs/forge-1.21.11-61.1.0-installer.jar" -download 'https://maven.minecraftforge.net/net/minecraftforge/forge/1.21.11-61.1.0/forge-1.21.11-61.1.0-installer.jar' \ +installer="$inputs/forge-26.1.2-64.1.5-installer.jar" +download 'https://maven.minecraftforge.net/net/minecraftforge/forge/26.1.2-64.1.5/forge-26.1.2-64.1.5-installer.jar' \ "$(value forge_installer_sha256)" "$installer" { printf 'power_ref=%s\n' "$power_ref" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eb7544f7..fc08f8b8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,16 +1,16 @@ -name: baseElectric 1.21.11 CI +name: baseElectric 26.1.2 CI on: push: - branches: [master-1.21.11, 'feature/**'] + branches: [master-26.1.2, 'feature/**'] pull_request: - branches: [master-1.21.11] + branches: [master-26.1.2] permissions: contents: read concurrency: - group: baseelectric-1.21.11-${{ github.workflow }}-${{ github.ref }} + group: baseelectric-26.1.2-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: @@ -40,29 +40,19 @@ jobs: repository: MinecraftModDevelopmentMods/basePower ref: ${{ steps.public-pins.outputs.power_ref }} path: .ci-dependencies/basePower - - name: Install Java 25 for Mavenizer + - name: Install Java 25 for all build and runtime roles uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 with: distribution: temurin java-version: '25.0.3+9.0.LTS' - - name: Install Java 8 for the launcher helper - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 - with: - distribution: temurin - java-version: '8.0.502+7' - - name: Install Java 21 for Gradle and production - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 - with: - distribution: temurin - java-version: '21.0.7+6.0.LTS' - name: Use pinned build toolchains run: >- - echo "MMD_GRADLE_TOOLCHAINS=-Porg.gradle.java.installations.paths=$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64 + echo "MMD_GRADLE_TOOLCHAINS=-Porg.gradle.java.installations.paths=$JAVA_HOME -Porg.gradle.java.installations.auto-detect=false -Porg.gradle.java.installations.auto-download=false" >> "$GITHUB_ENV" - name: Set up Gradle uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6 - - name: Verify the Forge 61 build inputs + - name: Verify the Forge 64 build inputs shell: bash env: ORESPAWN_REF: ${{ steps.public-pins.outputs.orespawn_ref }} @@ -74,33 +64,34 @@ jobs: test "$(git -C "$orespawn" rev-parse HEAD)" = "$ORESPAWN_REF" test "$(git -C "$power" rev-parse HEAD)" = "$POWER_REF" mavenizer="$orespawn/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.jar" - printf '%s %s\n' 'B36CA046DB72E30F27BD38E02B4AE34274ADB51AE1D65BAF9E1D07763755682E' "$mavenizer" | sha256sum -c - + printf '%s %s\n' 'BB0DACB3DEB2A711CA67DD402D5FDAB5A9847C37C7B9B596DEC7CF37378FA085' "$mavenizer" | sha256sum -c - echo "MAVENIZER=$mavenizer" >> "$GITHUB_ENV" - name: Build and audit release artifacts shell: bash run: | ./gradlew $MMD_GRADLE_TOOLCHAINS clean check build javadoc verifyReleaseDependencies verifyReleaseArtifacts \ writeReleaseChecksums verifyEclipseProductionClasspath --no-build-cache --no-daemon --stacktrace \ - "-Pforge61MavenizerJar=$MAVENIZER" "-PpowerAdvantageDeobfJar=$POWER_PLAYER" \ + "-Pforge64MavenizerJar=$MAVENIZER" "-PpowerAdvantageDeobfJar=$POWER_PLAYER" \ + "-PpowerUnitSource=$GITHUB_WORKSPACE/.ci-dependencies/basePower" \ "-PbaseMetalsDeobfJar=$METALS_PLAYER" "-PoreSpawnDeobfJar=$ORESPAWN_PLAYER" \ "-PforgeInstallerJar=$FORGE_INSTALLER" - name: Upload audited release candidate uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: baseElectric-1.21.11-${{ github.sha }} + name: baseElectric-26.1.2-${{ github.sha }} if-no-files-found: error retention-days: 30 path: | - build/libs/baseElectric-2.2.16.121111.jar - build/libs/baseElectric-2.2.16.121111-sources.jar - build/libs/baseElectric-2.2.16.121111-javadoc.jar + build/libs/baseElectric-2.2.17.2601021.jar + build/libs/baseElectric-2.2.17.2601021-sources.jar + build/libs/baseElectric-2.2.17.2601021-javadoc.jar build/release/SHA256SUMS CHANGELOG.txt - name: Upload diagnostics on failure if: failure() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: baseElectric-1.21.11-diagnostics-${{ github.sha }} + name: baseElectric-26.1.2-diagnostics-${{ github.sha }} if-no-files-found: ignore retention-days: 14 path: | diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index d0771d40..22f54310 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -2,9 +2,9 @@ name: CodeQL on: push: - branches: [master-1.21.11, 'feature/**'] + branches: [master-26.1.2, 'feature/**'] pull_request: - branches: [master-1.21.11] + branches: [master-26.1.2] permissions: contents: read @@ -12,7 +12,7 @@ permissions: security-events: write concurrency: - group: baseelectric-1.21.11-${{ github.workflow }}-${{ github.ref }} + group: baseelectric-26.1.2-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: @@ -42,29 +42,19 @@ jobs: repository: MinecraftModDevelopmentMods/basePower ref: ${{ steps.public-pins.outputs.power_ref }} path: .ci-dependencies/basePower - - name: Install Java 25 for Mavenizer + - name: Install Java 25 for all build and runtime roles uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 with: distribution: temurin java-version: '25.0.3+9.0.LTS' - - name: Install Java 8 for the launcher helper - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 - with: - distribution: temurin - java-version: '8.0.502+7' - - name: Install Java 21 for Gradle and production - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 - with: - distribution: temurin - java-version: '21.0.7+6.0.LTS' - name: Use pinned build toolchains run: >- - echo "MMD_GRADLE_TOOLCHAINS=-Porg.gradle.java.installations.paths=$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64 + echo "MMD_GRADLE_TOOLCHAINS=-Porg.gradle.java.installations.paths=$JAVA_HOME -Porg.gradle.java.installations.auto-detect=false -Porg.gradle.java.installations.auto-download=false" >> "$GITHUB_ENV" - name: Set up Gradle uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6 - - name: Verify the Forge 61 build inputs + - name: Verify the Forge 64 build inputs shell: bash env: ORESPAWN_REF: ${{ steps.public-pins.outputs.orespawn_ref }} @@ -76,7 +66,7 @@ jobs: test "$(git -C "$orespawn" rev-parse HEAD)" = "$ORESPAWN_REF" test "$(git -C "$power" rev-parse HEAD)" = "$POWER_REF" mavenizer="$orespawn/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.jar" - printf '%s %s\n' 'B36CA046DB72E30F27BD38E02B4AE34274ADB51AE1D65BAF9E1D07763755682E' "$mavenizer" | sha256sum -c - + printf '%s %s\n' 'BB0DACB3DEB2A711CA67DD402D5FDAB5A9847C37C7B9B596DEC7CF37378FA085' "$mavenizer" | sha256sum -c - echo "MAVENIZER=$mavenizer" >> "$GITHUB_ENV" - name: Initialize CodeQL uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4 @@ -87,7 +77,8 @@ jobs: shell: bash run: | ./gradlew $MMD_GRADLE_TOOLCHAINS classes verifyReleaseDependencies --rerun-tasks --no-build-cache --no-daemon --stacktrace \ - "-Pforge61MavenizerJar=$MAVENIZER" "-PpowerAdvantageDeobfJar=$POWER_PLAYER" \ + "-Pforge64MavenizerJar=$MAVENIZER" "-PpowerAdvantageDeobfJar=$POWER_PLAYER" \ + "-PpowerUnitSource=$GITHUB_WORKSPACE/.ci-dependencies/basePower" \ "-PbaseMetalsDeobfJar=$METALS_PLAYER" "-PoreSpawnDeobfJar=$ORESPAWN_PLAYER" - name: Analyze uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4 diff --git a/.github/workflows/validate-gradle-build.yml b/.github/workflows/validate-gradle-build.yml index b37dfc94..531f53fe 100644 --- a/.github/workflows/validate-gradle-build.yml +++ b/.github/workflows/validate-gradle-build.yml @@ -3,11 +3,11 @@ name: Validate Gradle Wrapper on: push: branches: - - master-1.21.11 + - master-26.1.2 - 'feature/**' pull_request: branches: - - master-1.21.11 + - master-26.1.2 permissions: contents: read diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 7a0fea92..7fc869ed 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,10 @@ +baseElectric 2.2.17.2601021 (Minecraft 26.1.2 / Forge, unreleased) + +- Port to Forge 64.1.5 and Java 25 for every build and runtime role. +- Keep the same machines, saved data, aliases, material ratios and recipe IDs. +- Move all six librarian offers to native trade data without changing their ranges or tiers. +- Keep 1.21.11 release evidence separate from the new port's verification. + baseElectric 2.2.16.121111 (Minecraft 1.21.11 / Forge) - Port to Forge 61.1.0 with basePower 2.4.15.121111 and Java 21. diff --git a/README.md b/README.md index b6689c03..3b485423 100644 --- a/README.md +++ b/README.md @@ -4,55 +4,53 @@ baseElectric continues DrCyano's Electric Advantage. It adds electric machines, generation, storage, automation and defence to basePower. Its equipment appears in the shared basePower creative tab. -baseElectric **2.2.16.121111** runs on Minecraft **1.21.11**, Forge **61.1.0**, -official mappings with MCP **20251223.124241**, ForgeGradle **7.0.34**, Gradle -**9.6.1**, and Java **21**. It requires -[basePower **2.4.15.121111**](https://www.curseforge.com/minecraft/mc-mods/basepower/files/9566998). -[Base Metals **3.1.2.121111**](https://www.curseforge.com/minecraft/mc-mods/base-metals/files/9566738) -is also **REQUIRED** on both client and server; -Forge enforces this dependency. The coordinated OreSpawn version is -**4.1.0.121111**. This version has been accepted for release but is not yet published. -See the [1.21.11 notes](docs/NATIVE12111.md) for compatibility and verification status. - -Combined machine, upgrade, graphical and two-world 30-minute endurance checks -passed before the released dependency retarget. The full record and its manual-test limits are in -basePower's `docs/1.21.11/QUALIFICATION.md`. Those limits still apply. -Publication is waiting for the final combined restart/endurance checks and -baseSteam's public release. The released dependency pins are recorded in -`gradle.properties`. +This checkout is the local **2.2.17.2601021** port for Minecraft **26.1.2** and +Forge **64.1.5**, using official runtime names, MCP **20260409.101008**, +ForgeGradle **7.0.34**, Gradle **9.6.1**, and Temurin **25.0.3+9**. +It is not a released or qualified build yet. + +basePower **2.4.16.2601021** and Base Metals **3.1.2.2601021** are required on +both client and server. Their local source and ordinary-jar checkpoints are +recorded in `gradle.properties`; they are not public releases or a completed +family qualification. The build rejects old 1.21.11 inputs. OreSpawn +**4.1.0.2601021** is pinned to its public release. See the [26.1.2 notes](docs/NATIVE2612.md). + +The completed [1.21.11 release](https://www.curseforge.com/minecraft/mc-mods/baseelectric/files/9567881) +and its [qualification notes](docs/NATIVE12111.md) are historical evidence, +not proof of this port. New gameplay, migration and graphical checks must use +the actual 26.1.2 jars. Back up your world and try the upgrade on a copy. Replace Electric Advantage and Power Advantage with baseElectric and basePower; do not install both sets. ## Building locally -Use Temurin 21.0.7+6 for Gradle and Minecraft. The pinned OreSpawn Mavenizer -fixture uses Java 25, and ForgeGradle's Slime Launcher tooling uses Java 8. Set -`-Porg.gradle.java.installations.paths=,,` -if Gradle cannot discover all three. +Use Temurin 25.0.3+9 for Gradle, Minecraft, compilation, tests, Mavenizer and +Slime Launcher. No Java 8 helper is required. Set +`-Porg.gradle.java.installations.paths=` if needed. +Use the isolated `D:/GradleCaches/baseelectric-2612-port` cache for local work. The independent Forge/resource checks do not require sibling jars: ```text -./gradlew --offline nativeResourceChecks compileNativeApiDiagnostics verifyRecipeTagGuards processResources +./gradlew nativeResourceChecks compileNativeApiDiagnostics verifyRecipeTagGuards processResources --max-workers=2 --no-daemon node tools/verify-native-port.js node tools/generate-recipe-unlocks.js --check ``` -For a full local build, use the exact dependency jars recorded in -`gradle.properties`. The sibling paths are the defaults and these flags are -optional path overrides: +For a full local build, use the exact local dependency pins in +`gradle.properties`. Path overrides never replace source or hash checks: ```text --PpowerAdvantageRuntimeJar=/path/to/basePower-2.4.15.121111.jar --PbaseMetalsRuntimeJar=/path/to/BaseMetals-3.1.2.121111.jar --PforgeInstallerJar=/path/to/forge-1.21.11-61.1.0-installer.jar +-PpowerAdvantageRuntimeJar=/path/to/basePower-2.4.16.2601021.jar +-PbaseMetalsRuntimeJar=/path/to/BaseMetals-3.1.2.2601021.jar +-PforgeInstallerJar=/path/to/forge-26.1.2-64.1.5-installer.jar ``` -Forge 61 uses the ordinary native jar for both play and development. The former +Forge 64 uses the ordinary native jar for both play and development. The former `*DeobfJar` path flags remain aliases for the same bytes. OreSpawn and the -Mavenizer fixture default to the sibling `MinecraftMineralogy/OS12111/OreSpawn` -checkout. Use `-PoreSpawnRuntimeJar` and `-Pforge61MavenizerJar` to override paths. +Mavenizer fixture default to the sibling `MinecraftMineralogy/OS2612/OreSpawn` +checkout. Use `-PoreSpawnRuntimeJar` and `-Pforge64MavenizerJar` to override paths. For the prepared cache, use `--offline` with `ORESPAWN_MAVENIZER_OFFLINE=true`. ```text @@ -60,14 +58,25 @@ For the prepared cache, use `--offline` with `ORESPAWN_MAVENIZER_OFFLINE=true`. ``` Use `gradlew.bat` on Windows. Output jars go to `build/libs`. Dependencies -remain separate mods. The 1.21.11 CI and CodeQL jobs require merged source pins +remain separate mods. The 26.1.2 CI and CodeQL jobs require merged source pins and checksum-matching public dependencies before checkout or compilation. Local jars do not satisfy that gate. ## Verification -The current local checks pass 114 unit tests, 25 native resource tests and -114 tests from fresh Eclipse compiler output, with no failures or skips. +The current local source passes 115 unit tests, 28 independent native resource +tests and 115 tests from fresh ECJ output, with no failures or skips. The ECJ +gate compares every compiled test method with the Gradle XML inventory. +All four named source GameTests also pass on the current Power checkpoint. +Independent warm-cache and new cold-cache builds in paths containing spaces +reproduce all three jars byte-for-byte. +Their first-boot GameTest log contains +native configuration and union-URL warnings, so it is not a clean-log receipt. +The packaged server smoke passes its 19 narrow checks; combined gameplay, +restart, migration and actual client checks remain separate parent-owned gates. +The Power pin includes the parent's fuel-container repair. Each qualification +receipt retains its exact input hashes. In particular, +no old screenshot or earlier version's result qualifies this port. Run `runGameTestServer` with the same pinned mod inputs, an existing accepted EULA passed as `-PelectricGameTestEula=`, and a new directory passed as diff --git a/build.gradle b/build.gradle index 9483094a..f457bfbd 100644 --- a/build.gradle +++ b/build.gradle @@ -61,36 +61,50 @@ def pinnedFile = { String property, String expected, String fallback = null -> input } -apply from: 'gradle/forge61-mavenizer.gradle' +apply from: 'gradle/forge64-mavenizer.gradle' java { toolchain { - languageVersion = JavaLanguageVersion.of(21) + languageVersion = JavaLanguageVersion.of(25) vendor = JvmVendorSpec.ADOPTIUM } withSourcesJar() withJavadocJar() } +fgtools.configure('slimelauncher') { + javaLauncher.set(javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(25) + vendor = JvmVendorSpec.ADOPTIUM + }) +} + tasks.withType(JavaCompile).configureEach { javaCompiler = javaToolchains.compilerFor { - languageVersion = JavaLanguageVersion.of(21) + languageVersion = JavaLanguageVersion.of(25) vendor = JvmVendorSpec.ADOPTIUM } - options.release = 21 + options.release = 25 options.encoding = 'UTF-8' options.compilerArgs.addAll(['-Xmaxerrs', '1000']) } +tasks.withType(Test).configureEach { + jvmArgs '-XX:ActiveProcessorCount=4' + environment = environment.findAll { key, value -> + !(key in ['JAVA_TOOL_OPTIONS', 'JDK_JAVA_OPTIONS', '_JAVA_OPTIONS']) + } +} + tasks.named('test', Test) { javaLauncher = javaToolchains.launcherFor { - languageVersion = JavaLanguageVersion.of(21) + languageVersion = JavaLanguageVersion.of(25) vendor = JvmVendorSpec.ADOPTIUM } afterSuite { descriptor, result -> if (!descriptor.parent && filter.includePatterns.empty && filter.commandLineIncludePatterns.empty - && (result.testCount != 114 || result.skippedTestCount != 0)) { - throw new GradleException("Expected 114 tests with no skips, got ${result.testCount} tests and ${result.skippedTestCount} skips") + && (result.testCount != 115 || result.skippedTestCount != 0)) { + throw new GradleException("Expected 115 tests with no skips, got ${result.testCount} tests and ${result.skippedTestCount} skips") } } doFirst { @@ -154,6 +168,7 @@ minecraft { configureEach { workingDir.convention layout.projectDirectory.dir(configuredRunDirectory) jvmArgs '--add-opens', 'java.base/java.lang.invoke=ALL-UNNAMED' + jvmArgs '-XX:ActiveProcessorCount=4' systemProperty 'forge.logging.markers', 'REGISTRIES' systemProperty 'forge.logging.console.level', 'info' mods { baseelectric { source sourceSets.main } } @@ -166,6 +181,8 @@ minecraft { register('gameTestServer') { workingDir.set(electricGameTestRunDirectory) systemProperty 'forge.enabledGameTestNamespaces', project.mod_id + systemProperty 'baseelectric.gameTestRuntimeReceipt', + electricGameTestRunDirectory.file('runtime-receipt.json').asFile.absolutePath args '--tests', 'baseelectric:*', '--report', 'game-test-results.xml' mods { baseelectric { source sourceSets.gameTestValidation } } environment 'MOD_CLASSES', "${mod_id}%%${gameTestModOutput.get().asFile}" @@ -186,7 +203,7 @@ sourceSets.main.resources { def forgeRunModClassesDirectory = layout.buildDirectory.dir('forge-run-mod-classes/main') def prepareForgeRunModClasses = tasks.register('prepareForgeRunModClasses', Sync) { group = 'ide' - description = 'Merges classes and processed resources for Forge 1.21.11 launches.' + description = 'Merges classes and processed resources for Forge 26.1.2 launches.' dependsOn tasks.named('classes') from sourceSets.main.output into forgeRunModClassesDirectory @@ -228,6 +245,10 @@ def prepareElectricGameTestFixture = tasks.register('prepareElectricGameTestFixt } } tasks.withType(JavaExec).configureEach { + jvmArgs '-XX:ActiveProcessorCount=4' + environment = environment.findAll { key, value -> + !(key in ['JAVA_TOOL_OPTIONS', 'JDK_JAVA_OPTIONS', '_JAVA_OPTIONS']) + } if (name in ['runClient', 'runServer', 'runData']) { dependsOn prepareForgeRunModClasses } @@ -286,7 +307,8 @@ file('gradle.properties').withInputStream { committedPins.load(it) } def requireCommittedPins = { String prefix -> committedPins.stringPropertyNames().findAll { key -> key.startsWith(prefix) && (key.endsWith('_sha256') || key.endsWith('_ref') - || key.endsWith('_commit') || key.endsWith('_pin_state') || key.endsWith('_version')) + || key.endsWith('_commit') || key.endsWith('_pin_state') || key.endsWith('_version') + || key.endsWith('_built_on')) }.each { key -> if (project.property(key) != committedPins.getProperty(key)) { throw new GradleException("Cannot override the recorded release pin ${key}.") @@ -299,12 +321,12 @@ def requirePowerPin = { || !(project.poweradvantage_source_ref ==~ /[a-f0-9]{40}/) || ![project.poweradvantage_deobf_sha256, project.poweradvantage_runtime_sha256] .every { it ==~ /[A-F0-9]{64}/ }) { - throw new GradleException('The owner-approved basePower 1.21.11 source and artifact pins are required.') + throw new GradleException('The owner-approved basePower 26.1.2 source and artifact pins are required.') } } // Resolve and check only the dependencies required by the selected tasks. def powerDefaultJar = "../../PowerAdvantage/build/libs/basePower-${project.poweradvantage_version}.jar" -def metalsDefaultJar = "../../../../BaseMetals/.verification-basemetals12111/Advantage shared hook/build/libs/BaseMetals-${project.basemetals_version}.jar" +def metalsDefaultJar = "../../../../BaseMetals/BaseMetals 2612/BaseMetals/build/libs/BaseMetals-${project.basemetals_version}.jar" def powerAdvantageDeobfJar = providers.provider { requirePowerPin() pinnedFile('powerAdvantageDeobfJar', project.poweradvantage_deobf_sha256, @@ -315,7 +337,7 @@ def baseMetalsDeobfJar = providers.provider { pinnedFile('baseMetalsDeobfJar', project.basemetals_deobf_sha256, providers.gradleProperty('baseMetalsRuntimeJar').getOrElse(metalsDefaultJar)) } -def oreSpawnProject = '../../../../MinecraftMineralogy/OS12111/OreSpawn' +def oreSpawnProject = '../../../../MinecraftMineralogy/OS2612/OreSpawn' def oreSpawnDeobfJar = providers.provider { requireCommittedPins('orespawn_') pinnedFile('oreSpawnDeobfJar', project.orespawn_deobf_sha256, @@ -323,6 +345,52 @@ def oreSpawnDeobfJar = providers.provider { "${oreSpawnProject}/build/libs/OreSpawn-${project.orespawn_version}.jar")) } +def powerUnitSource = providers.gradleProperty('powerUnitSource').getOrElse('../../PowerAdvantage') +def unitBootstrapJava = providers.provider { + requireCommittedPins('unit_') + pinnedFile('unitBootstrapJava', project.unit_bootstrap_java_sha256, + "${powerUnitSource}/test-rig/unit-bootstrap/ForgeCapabilityBootstrap.java") +} +def unitRegistriesJava = providers.provider { + requireCommittedPins('unit_') + pinnedFile('unitRegistriesJava', project.unit_registries_java_sha256, + "${powerUnitSource}/test-rig/unit-support/zone/moddev/mc/advantageworks/unit/ForgeUnitRegistries.java") +} +def unitLoaderJava = providers.provider { + requireCommittedPins('unit_') + pinnedFile('unitLoaderJava', project.unit_loader_java_sha256, + "${powerUnitSource}/test-rig/unit-support/zone/moddev/mc/advantageworks/unit/Forge64UnitLoader.java") +} +def pinnedUnitSupport = layout.buildDirectory.dir('generated-unit-support') +def preparePinnedUnitSupport = tasks.register('preparePinnedUnitSupport', Sync) { + from unitRegistriesJava, unitLoaderJava + into pinnedUnitSupport.map { it.dir('zone/moddev/mc/advantageworks/unit') } +} +sourceSets.test.java.srcDir pinnedUnitSupport +tasks.named('compileTestJava') { dependsOn preparePinnedUnitSupport } +def compileUnitBootstrapJava = tasks.register('compileUnitBootstrapJava', JavaCompile) { + source unitBootstrapJava + classpath = files() + destinationDirectory = layout.buildDirectory.dir('unit-bootstrap/classes') +} +def electricUnitBootstrapJar = tasks.register('electricUnitBootstrapJar', Jar) { + dependsOn compileUnitBootstrapJava + archiveFileName = 'forge-native-unit-bootstrap.jar' + destinationDirectory = layout.buildDirectory.dir('unit-bootstrap') + from compileUnitBootstrapJava.flatMap { it.destinationDirectory } + manifest.attributes('Premain-Class': 'zone.moddev.mc.advantageworks.unit.ForgeCapabilityBootstrap') + doLast { + requireCommittedPins('unit_') + if (sha256Of(archiveFile.get().asFile) != project.unit_bootstrap_sha256) { + throw new GradleException('Rebuilt unit agent differs from the frozen helper bytes') + } + } +} +tasks.named('test', Test) { + dependsOn electricUnitBootstrapJar + doFirst { jvmArgs "-javaagent:${electricUnitBootstrapJar.get().archiveFile.get().asFile}" } +} + def nativeForge = minecraft.dependency( "net.minecraftforge:forge:${project.minecraft_version}-${project.forge_version}") def forgeInstallerConfiguration = configurations.create('forgeInstallerArtifact') { @@ -382,21 +450,67 @@ def compileNativeResourceChecks = tasks.register('compileNativeResourceChecks', '**/RecipeRegistrationThreadTest.java', '**/NativeTestBootstrap.java', '**/LegacyBatteryDataTest.java', '**/NativePackRangeContractTest.java', '**/NativePackResourceProof.java', '**/BuildWorkflowContractTest.java', '**/ShaderResourceContractTest.java', '**/DatapackLayoutTest.java', - '**/RecipeDeconstructorTest.java' + '**/RecipeDeconstructorTest.java', '**/VillagesTest.java' } classpath = nativeResourceCheckClasspath.get() + files(layout.buildDirectory.dir('classes/native-api-diagnostics')) destinationDirectory = nativeResourceCheckClasses } + +tasks.register('compileNativeProductionDiagnostics', JavaCompile) { + group = 'verification' + description = 'Compiles all production sources against explicitly hashed provisional native inputs; never produces a release jar.' + source sourceSets.main.java + classpath = nativeResourceCheckClasspath.get() + files(providers.provider { + ['powerAdvantage', 'baseMetals'].collect { name -> + pinnedFile("${name}DiagnosticJar", providers.gradleProperty("${name}DiagnosticSha256").getOrElse('')) + } + }, oreSpawnDeobfJar) + destinationDirectory = layout.buildDirectory.dir('classes/native-production-diagnostics') +} + +def diagnosticMain = tasks.named('compileNativeProductionDiagnostics', JavaCompile) +def diagnosticTests = tasks.register('compileNativeTestDiagnostics', JavaCompile) { + group = 'verification' + description = 'Compiles retained unit tests against provisional inputs, isolated from release outputs.' + dependsOn diagnosticMain, preparePinnedUnitSupport + source sourceSets.test.java + classpath = files(diagnosticMain.flatMap { it.destinationDirectory }) + diagnosticMain.get().classpath + destinationDirectory = layout.buildDirectory.dir('classes/native-test-diagnostics') +} + +tasks.register('nativeUnitDiagnostics', Test) { + group = 'verification' + description = 'Runs worldless unit diagnostics with provisional native inputs; not a release qualification.' + dependsOn diagnosticTests, tasks.named('processResources'), tasks.named('processTestResources'), electricUnitBootstrapJar + useJUnit() + testClassesDirs = files(diagnosticTests.flatMap { it.destinationDirectory }) + classpath = testClassesDirs + files(diagnosticMain.flatMap { it.destinationDirectory }, + layout.buildDirectory.dir('resources/main'), layout.buildDirectory.dir('resources/test')) + diagnosticMain.get().classpath + javaLauncher = javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(25) + vendor = JvmVendorSpec.ADOPTIUM + } + afterSuite { descriptor, result -> + if (!descriptor.parent && filter.includePatterns.empty && filter.commandLineIncludePatterns.empty + && (result.testCount != 115 || result.skippedTestCount != 0)) { + throw new GradleException("Expected 115 provisional unit diagnostics without skips, got ${result.testCount}/${result.skippedTestCount}") + } + } + doFirst { + systemProperty 'baseelectric.nativePackClasspath', classpath.asPath + jvmArgs "-javaagent:${electricUnitBootstrapJar.get().archiveFile.get().asFile}" + } +} tasks.register('nativeResourceChecks', Test) { group = 'verification' - description = 'Checks Electric resources against native Forge 61 without sibling mod jars.' + description = 'Checks Electric resources against native Forge 64 without sibling mod jars.' dependsOn compileNativeResourceChecks, tasks.named('processResources') useJUnit() testClassesDirs = files(nativeResourceCheckClasses) classpath = testClassesDirs + nativeResourceCheckClasspath.get() + files(layout.buildDirectory.dir('classes/native-api-diagnostics')) afterSuite { descriptor, result -> - if (!descriptor.parent && (result.testCount != 25 || result.skippedTestCount != 0)) { - throw new GradleException("Expected 25 native resource tests without skips, got ${result.testCount}/${result.skippedTestCount}") + if (!descriptor.parent && (result.testCount != 28 || result.skippedTestCount != 0)) { + throw new GradleException("Expected 28 native resource tests without skips, got ${result.testCount}/${result.skippedTestCount}") } } doFirst { @@ -476,13 +590,13 @@ def releaseJar = tasks.named('jar', Jar) tasks.register('releaseJar') { group = 'build' - description = 'Builds the native Forge 61 player jar.' + description = 'Builds the native Forge 64 player jar.' dependsOn releaseJar } tasks.register('deobfJar') { group = 'build' - description = 'Builds the same native Forge 61 jar used for development and play.' + description = 'Builds the same native Forge 64 jar used for development and play.' dependsOn tasks.named('jar') } @@ -557,29 +671,29 @@ tasks.register('verifyReleaseConfiguration') { requireCommittedPins('orespawn_') requireCommittedPins('forge_') def pack = new JsonSlurper().parse(file('src/main/resources/pack.mcmeta')).pack - if (pack.min_format != [94, 1] || pack.max_format != [94, 1] + if (pack.min_format != [101, 1] || pack.max_format != [101, 1] || pack.containsKey('pack_format') || pack.containsKey('supported_formats') || !(file('src/main/resources/META-INF/mods.toml').text =~ /(?m)^showAsResourcePack=false$/)) { - throw new GradleException('Root metadata must declare data 94.1 with the native hidden Forge client pack') + throw new GradleException('Root metadata must declare data 101.1 with the native hidden Forge client pack') } - if (project.mod_version != '2.2.16.121111') { + if (project.mod_version != '2.2.17.2601021') { throw new GradleException("Unexpected baseElectric release version: ${project.mod_version}") } - if (project.minecraft_version != '1.21.11' - || project.minecraft_version_range != '[1.21.11]' - || project.forge_version != '61.1.0' - || project.forge_version_range != '[61.1.0,62)' - || project.loader_version_range != '[61,)' + if (project.minecraft_version != '26.1.2' + || project.minecraft_version_range != '[26.1.2]' + || project.forge_version != '64.1.5' + || project.forge_version_range != '[64.1.5,65)' + || project.loader_version_range != '[64,65)' || project.mapping_channel != 'official' - || project.mapping_version != '1.21.11' - || project.mcp_version != '20251223.124241') { + || project.mapping_version != '26.1.2' + || project.mcp_version != '20260409.101008') { throw new GradleException('Unexpected Minecraft, Forge, or mappings target') } if (project.loader_name != 'forge' || project.loader_code != '1' - || project.java_version != '21' - || project.java_toolchain_version != '21.0.7+6' - || project.java_setup_version != '21.0.7+6.0.LTS' - || project.gradle_java_version != '21') { + || project.java_version != '25' + || project.java_toolchain_version != '25.0.3+9' + || project.java_setup_version != '25.0.3+9.0.LTS' + || project.gradle_java_version != '25') { throw new GradleException('Unexpected release dispatcher target metadata') } if (project.mod_id != 'baseelectric' @@ -592,15 +706,17 @@ tasks.register('verifyReleaseConfiguration') { } if (project.cf_requirements != 'basepower,base-metals' || project.cf_optionals != '' - || project.cf_versions != '1.21.11') { + || project.cf_versions != '26.1.2') { throw new GradleException('Unexpected CurseForge dependency or game-version metadata') } - if (project.poweradvantage_version != '2.4.15.121111' + if (project.poweradvantage_version != '2.4.16.2601021' || project.poweradvantage_source_repository != 'MinecraftModDevelopmentMods/basePower') { throw new GradleException('Unexpected basePower compile dependency identity') } - if (!(project.basemetals_version ==~ /3\.\d+\.\d+\.121111/) - || project.orespawn_version != '4.1.0.121111' + if (!(project.basemetals_version ==~ /3\.\d+\.\d+\.2601021/) + || project.orespawn_version != '4.1.0.2601021' + || project.basemetals_built_on != '26.1.2-64.0.9' + || project.orespawn_built_on != '26.1.2-64.0.9' || !(project.basemetals_source_commit ==~ /[a-f0-9]{40}/) || !(project.basemetals_deobf_sha256 ==~ /[A-F0-9]{64}/) || !(project.basemetals_runtime_sha256 ==~ /[A-F0-9]{64}/) @@ -608,18 +724,18 @@ tasks.register('verifyReleaseConfiguration') { || project.orespawn_deobf_sha256 != project.orespawn_runtime_sha256 || project.basemetals_deobf_sha256 != project.basemetals_runtime_sha256 || project.poweradvantage_deobf_sha256 != project.poweradvantage_runtime_sha256 - || project.forge_installer_sha256 != '8BD1724EAEAF26F51DACFB7FDC46D79E9B640705B70D5F0019ED38907145EB48') { + || project.forge_installer_sha256 != '6E01323837051176762BD466340A601DA122F179FE6A00AC616E05429AE07648') { throw new GradleException('Unexpected native dependency pins') } String mainSource = file( 'src/main/java/zone/moddev/mc/baseelectric/ElectricAdvantage.java').getText('UTF-8') if (!mainSource.contains('MODID = "baseelectric"') - || !mainSource.contains('VERSION = "2.2.16.121111"')) { + || !mainSource.contains('VERSION = "2.2.17.2601021"')) { throw new GradleException('baseElectric source identity changed') } String metadata = file('src/main/resources/META-INF/mods.toml').getText('UTF-8').replace('\r\n', '\n') ['modId="baseelectric"', 'displayName="baseElectric"', 'modId="basepower"', 'modId="basemetals"', - 'versionRange="[2.4.15.121111,3.0.0)"', 'versionRange="[3.1.2.121111,4.0.0)"'].each { marker -> + 'versionRange="[2.4.16.2601021,3.0.0)"', 'versionRange="[3.1.2.2601021,4.0.0)"'].each { marker -> if (!metadata.contains(marker)) { throw new GradleException("Source mods.toml is missing ${marker}") } @@ -637,9 +753,11 @@ def auditModInput = { File jarFile, String modId, String version, List r } def manifest = new Manifest(zip.getInputStream(zip.getEntry('META-INF/MANIFEST.MF'))).mainAttributes String metadata = zip.getInputStream(zip.getEntry('META-INF/mods.toml')).getText('UTF-8') + String builtOn = [orespawn: project.orespawn_built_on, basemetals: project.basemetals_built_on] + .getOrDefault(modId, "${project.minecraft_version}-${project.forge_version}".toString()) if (manifest.getValue('Implementation-Version') != version - || manifest.getValue('Built-On') != "${project.minecraft_version}-${project.forge_version}" - || manifest.getValue('Built-On-Java') != '21' + || manifest.getValue('Built-On') != builtOn + || manifest.getValue('Built-On-Java') != '25' || !metadata.contains("modId=\"${modId}\"") || !metadata.contains("version=\"${version}\"")) { throw new GradleException("${jarFile.name} has unexpected ${modId} version or target metadata") @@ -648,10 +766,24 @@ def auditModInput = { File jarFile, String modId, String version, List r } } +def verifyOreSpawnNativeInputs = tasks.register('verifyOreSpawnNativeInputs') { + group = 'verification' + description = 'Verifies the unchanged public OreSpawn jar and its own exact native build provenance.' + doLast { + requireCommittedPins('orespawn_') + auditModInput(oreSpawnDeobfJar.get(), 'orespawn', project.orespawn_version, []) + auditModInput(pinnedFile('oreSpawnRuntimeJar', project.orespawn_runtime_sha256, + providers.gradleProperty('oreSpawnDeobfJar').getOrElse( + "${oreSpawnProject}/build/libs/OreSpawn-${project.orespawn_version}.jar")), + 'orespawn', project.orespawn_version, []) + } +} + tasks.register('verifyLocalNativeInputs') { group = 'verification' - description = 'Verifies explicit Base Metals and OreSpawn 1.21.11 development and player JARs.' + description = 'Verifies explicit Base Metals and OreSpawn 26.1.2 development and player JARs.' dependsOn tasks.named('verifyReleaseConfiguration') + dependsOn verifyOreSpawnNativeInputs doLast { auditModInput(baseMetalsDeobfJar.get(), 'basemetals', project.basemetals_version, ['zone/moddev/mc/basemetals/api/migration/Legacy112MigrationExtension.class', @@ -659,11 +791,6 @@ tasks.register('verifyLocalNativeInputs') { auditModInput(pinnedFile('baseMetalsRuntimeJar', project.basemetals_runtime_sha256, providers.gradleProperty('baseMetalsDeobfJar').getOrElse(metalsDefaultJar)), 'basemetals', project.basemetals_version, []) - auditModInput(oreSpawnDeobfJar.get(), 'orespawn', project.orespawn_version, []) - auditModInput(pinnedFile('oreSpawnRuntimeJar', project.orespawn_runtime_sha256, - providers.gradleProperty('oreSpawnDeobfJar').getOrElse( - "${oreSpawnProject}/build/libs/OreSpawn-${project.orespawn_version}.jar")), - 'orespawn', project.orespawn_version, []) } } @@ -710,8 +837,30 @@ tasks.register('verifyReleaseDependencies') { if (!runtimeFiles.containsAll([powerJar.canonicalFile, metalsJar.canonicalFile, oreJar.canonicalFile])) { throw new GradleException('A verified development mod is missing from runtimeClasspath') } - if (!compileFiles.any { it.name == "mappings_official-${project.minecraft_version}-${project.mcp_version}.zip" }) { - throw new GradleException('The resolved Forge official mappings do not match the MCP pin') + def nativeMappings = new JsonSlurper().parse(file('.gradle/mavenizer/dependencies/default.json')) + String mcpCoordinate = "${project.minecraft_version}-${project.mcp_version}" + if (nativeMappings['mc.version'] != project.minecraft_version + || nativeMappings['mappings.channel'] != 'official' + || nativeMappings['mappings.version'] != project.mapping_version + || nativeMappings['mcp.version'] != mcpCoordinate + || nativeMappings['mcp.artifact'] != "de.oceanlabs.mcp:mcp_config:${mcpCoordinate}@zip" + || nativeMappings['mappings.csv.artifact'] != "net.minecraft:mappings_official:${mcpCoordinate}@zip" + || !compileFiles.any { it.name == "forge-${project.minecraft_version}-${project.forge_version}.jar" } + || compileFiles.any { it.name.startsWith('mappings_') } + || project.reobf != 'false') { + throw new GradleException('The native official-name engine or MCP metadata does not match the target pin') + } + File emptyMappings = file(nativeMappings['mappings.csv.file']) + new ZipFile(emptyMappings).withCloseable { zip -> + if (zip.entries().collect { it.name }.sort() != ['fields.csv', 'methods.csv']) { + throw new GradleException('Unexpected official-name mapping entries') + } + ['fields.csv', 'methods.csv'].each { name -> + String csv = zip.getInputStream(zip.getEntry(name)).getText('UTF-8').replace('\r\n', '\n') + if (csv != 'searge,name,side,desc') { + throw new GradleException('Forge 64 official runtime mappings must be empty header-only CSVs') + } + } } List forbiddenRuntimeMods = runtimeFiles.collect { it.name.toLowerCase(Locale.ROOT) } .findAll { name -> @@ -787,7 +936,14 @@ tasks.register('verifyReleaseArtifacts') { 'zone/moddev/mc/baseelectric/machines/HydroelectricGeneratorTileEntity.class', 'zone/moddev/mc/baseelectric/migration/ElectricLegacyMigrations.class', 'zone/moddev/mc/baseelectric/init/OreSpawnWorldGen.class', - 'zone/moddev/mc/baseelectric/init/Villages.class', + 'data/baseelectric/villager_trade/librarian/electric_conduit.json', + 'data/baseelectric/villager_trade/librarian/solder.json', + 'data/baseelectric/villager_trade/librarian/integrated_circuit.json', + 'data/baseelectric/villager_trade/librarian/blank_circuit_board.json', + 'data/baseelectric/villager_trade/librarian/petrolplastic_ingot.json', + 'data/baseelectric/villager_trade/librarian/psu.json', + 'data/minecraft/tags/villager_trade/librarian/level_1.json', + 'data/minecraft/tags/villager_trade/librarian/level_2.json', 'zone/moddev/mc/baseelectric/recipe/RecipeConditions$ModeCondition.class', 'assets/baseelectric/lang/en_us.json', 'data/baseelectric/loot_table/blocks/electric_drill.json', @@ -802,6 +958,7 @@ tasks.register('verifyReleaseArtifacts') { } } ['agent-notes/', '.codex/', 'src/test/', 'zone/moddev/mc/baseelectric/gametest/', + 'zone/moddev/mc/advantageworks/unit/', 'data/baseelectric/structure/empty.nbt', 'fernflower_abstract_parameter_names.txt', 'cyano/poweradvantage/', 'zone/moddev/mc/basepower/', 'zone/moddev/mc/poweradvantage/', 'zone/moddev/mc/electricadvantage/', @@ -839,8 +996,8 @@ tasks.register('verifyReleaseArtifacts') { } } int major = ((header[6] & 0xff) << 8) | (header[7] & 0xff) - if (major != 65) { - throw new GradleException("${entry.name} uses Java class major ${major}, expected 65") + if (major != 69) { + throw new GradleException("${entry.name} uses Java class major ${major}, expected 69") } } } @@ -856,7 +1013,7 @@ tasks.register('verifyReleaseArtifacts') { throw new GradleException('Javadoc jar is missing index.html') } ['copy.svg', 'link.svg'].each { name -> - def entry = javadocZip.getEntry(name) + def entry = javadocZip.getEntry("resource-files/${name}") if (entry == null || javadocZip.getInputStream(entry).getText('UTF-8').contains('\r')) { throw new GradleException("Javadoc resource ${name} must use canonical LF line endings") } @@ -994,7 +1151,7 @@ tasks.register('validateMavenReleaseCredentials') { group = 'publishing' description = 'Rejects remote publication from this local port.' doLast { - throw new GradleException('Remote Maven publication is disabled on the local 1.21.11 port.') + throw new GradleException('Remote Maven publication is disabled on the local 26.1.2 port.') } } @@ -1042,7 +1199,7 @@ eclipse { synchronizationTasks 'isolateEclipseProductionRuns' } -tasks.named('eclipseClasspath') { dependsOn tasks.named('processResources') } +tasks.named('eclipseClasspath') { dependsOn tasks.named('processResources'), preparePinnedUnitSupport } tasks.register('configureEclipseBuildship') { group = 'ide' @@ -1053,6 +1210,8 @@ tasks.register('configureEclipseBuildship') { [ 'eclipse.preferences.version' : '1', 'connection.gradle.distribution': 'GRADLE_DISTRIBUTION(WRAPPER)', + 'connection.java.home' : javaToolchains.compilerFor(java.toolchain).get() + .metadata.installationPath.asFile.canonicalPath, 'connection.gradle.user.home' : gradle.gradleUserHomeDir.canonicalPath, 'connection.project.dir' : '', 'gradle.user.home' : gradle.gradleUserHomeDir.canonicalPath, @@ -1067,10 +1226,12 @@ tasks.register('configureEclipseBuildship') { } def quoteEclipseLaunchPaths = { String name, String arguments -> + if (arguments.contains('--to-srg') || arguments.contains('--to-obf')) { + throw new GradleException("${name} must not remap the official Forge 64 runtime") + } // ForgeGradle flattens path arguments without quoting them for Eclipse. Map nextOptions = [ - '--cache': '--metadata', '--metadata': '--to-srg', - '--to-srg': '--to-obf', '--to-obf': '--' + '--cache': '--metadata', '--metadata': '--' ] if (name == 'runData.launch') { nextOptions.put('--output', '--existing') @@ -1095,6 +1256,58 @@ def quoteEclipseLaunchPaths = { String name, String arguments -> arguments } +def nativeEclipseAssets = { + File nativeJar = nativeResourceCheckClasspath.get().files.find { + it.name == "forge-${project.minecraft_version}-${project.forge_version}.jar" + } + if (nativeJar == null) throw new GradleException('Missing native client metadata input') + File metadata = new File(nativeJar.parentFile, + "forge-${project.minecraft_version}-${project.forge_version}-metadata.zip") + def version + new ZipFile(metadata).withCloseable { zip -> + def entry = zip.getEntry('minecraft/version.json') + if (entry == null) throw new GradleException('Native metadata has no Minecraft version descriptor') + version = new JsonSlurper().parse(zip.getInputStream(entry)) + } + if (version.id != project.minecraft_version || !(version.assetIndex.sha1 ==~ /[a-f0-9]{40}/)) { + throw new GradleException('Unexpected native Minecraft asset metadata') + } + File assets = file(providers.gradleProperty('minecraftAssetsDir').getOrElse( + new File(System.getenv('APPDATA') ?: System.getProperty('user.home'), '.minecraft/assets').path)) + [id: version.assetIndex.id.toString(), sha1: version.assetIndex.sha1.toString(), root: assets.canonicalPath] +} + +def resolveEclipseClientAssets = { String arguments -> + def assets = nativeEclipseAssets() + arguments.replace('{asset_index}', assets.id).replace('{assets_root}', '"' + assets.root + '"') +} + +tasks.register('verifyEclipseLaunchArguments') { + group = 'verification' + description = 'Checks official-name launch paths and actual native asset metadata without starting Minecraft.' + doLast { + String sample = '--main native --cache D:/cache with spaces --metadata D:/metadata with spaces -- --launchTarget forge_userdev_client' + String expected = '--main native --cache "D:/cache with spaces" --metadata "D:/metadata with spaces" -- --launchTarget forge_userdev_client' + if (quoteEclipseLaunchPaths('runClient.launch', sample) != expected + || quoteEclipseLaunchPaths('runClient.launch', expected) != expected) { + throw new GradleException('Native Eclipse paths must be quoted once') + } + ['--to-srg', '--to-obf'].each { flag -> + boolean rejected = false + try { quoteEclipseLaunchPaths('runClient.launch', sample + " ${flag} obsolete") } + catch (GradleException expectedFailure) { rejected = true } + if (!rejected) throw new GradleException("Accepted obsolete remapping flag ${flag}") + } + def assets = nativeEclipseAssets() + String resolved = resolveEclipseClientAssets('--assetsDir {assets_root} --assetIndex {asset_index}') + if (resolved != "--assetsDir \"${assets.root}\" --assetIndex ${assets.id}" + || resolved.contains('{asset_index}') || resolved.contains('{assets_root}')) { + throw new GradleException('Native client assets must resolve to literal arguments') + } + logger.lifecycle("Verified native Eclipse assets ${assets.id} SHA-1 ${assets.sha1}; metadata generation downloaded no assets") + } +} + tasks.register('isolateEclipseProductionRuns') { group = 'ide' description = 'Normalizes ForgeGradle Eclipse launches for production-only execution.' @@ -1166,6 +1379,9 @@ tasks.register('isolateEclipseProductionRuns') { throw new GradleException("${launch.name} is missing its program arguments") } programArguments.@value = quoteEclipseLaunchPaths(launch.name, programArguments.@value) + if (launch.name == 'runClient.launch') { + programArguments.@value = resolveEclipseClientAssets(programArguments.@value) + } } launch.setText(groovy.xml.XmlUtil.serialize(launchXml).replace('\r\n', '\n'), 'UTF-8') } @@ -1260,6 +1476,10 @@ tasks.register('verifyEclipseProductionClasspath') { != gradle.gradleUserHomeDir.canonicalFile) { throw new GradleException('Eclipse Buildship does not use this wrapper and Gradle home') } + File javaHome = javaToolchains.compilerFor(java.toolchain).get().metadata.installationPath.asFile + if (file(preferences.getProperty('connection.java.home', '')).canonicalFile != javaHome.canonicalFile) { + throw new GradleException('Eclipse Buildship must use the pinned Java 25 installation') + } def eclipseProject = new groovy.xml.XmlSlurper().parse(file('.project')) if (project.name != 'ElectricAdvantage' || eclipseProject.name.text() != project.name) { throw new GradleException('The Eclipse and Gradle project names must both be ElectricAdvantage') @@ -1302,8 +1522,8 @@ tasks.register('verifyEclipseProductionClasspath') { def productionJre = eclipseClasspath.classpathentry.findAll { it.@kind.text() == 'con' && it.@path.text().startsWith('org.eclipse.jdt.launching.JRE_CONTAINER/') } - if (productionJre.size() != 1 || productionJre[0].@path.text().tokenize('/').last() != 'JavaSE-21') { - throw new GradleException('Eclipse production JRE must be JavaSE-21') + if (productionJre.size() != 1 || productionJre[0].@path.text().tokenize('/').last() != 'JavaSE-25') { + throw new GradleException('Eclipse production JRE must be JavaSE-25') } def processedResources = eclipseClasspath.classpathentry.findAll { it.@kind.text() == 'src' && it.@path.text() == 'build/resources/main' @@ -1344,7 +1564,8 @@ tasks.register('verifyEclipseProductionClasspath') { if (launch.booleanAttribute.find { it.@key.text() == 'org.eclipse.jdt.launching.ATTR_EXCLUDE_TEST_CODE' }.@value.text() != 'true' - || contents.contains('src/test') || contents.contains('bin/test')) { + || contents.contains('src/test') || contents.contains('bin/test') + || contents.contains('generated-unit-support') || contents.contains('forge-native-unit-bootstrap')) { throw new GradleException("${name} does not isolate production code") } def runtimeEntries = launch.listAttribute.find { @@ -1371,13 +1592,13 @@ tasks.register('verifyEclipseProductionClasspath') { } List bootstrapArtifacts = [ "forge-${project.minecraft_version}-${project.forge_version}", - "client-extra-${project.minecraft_version}", - "mappings_${project.mapping_channel}-${project.minecraft_version}" + "client-extra-${project.minecraft_version}" ] List bootstrapPositions = bootstrapArtifacts.collect { artifact -> runtimeArchives.findIndexOf { it.name.contains(artifact) } } - if (bootstrapPositions.any { it < 0 } + if (runtimeArchives.any { it.name.startsWith('mappings_') } + || bootstrapPositions.any { it < 0 } || bootstrapPositions != bootstrapPositions.sort(false)) { throw new GradleException( "${name} does not preserve the Forge bootstrap classpath order") @@ -1392,14 +1613,19 @@ tasks.register('verifyEclipseProductionClasspath') { it.@key.text() == 'org.eclipse.jdt.launching.MAIN_TYPE' }.@value.text() String target = name == 'runClient.launch' ? 'forge_userdev_client' : 'forge_userdev_server' + if (programArguments.contains('--to-srg') || programArguments.contains('--to-obf') + || programArguments.contains('{asset_index}') || programArguments.contains('{assets_root}') + || !contents.contains('StandardVMType/JavaSE-25')) { + throw new GradleException("${name} has obsolete mappings, unresolved assets or the wrong Java runtime") + } if (contents.contains('${MC_VERSION}') || mainType != 'net.minecraftforge.launcher.Main' || !programArguments.startsWith('--main net.minecraftforge.bootstrap.ForgeBootstrap ') - || !programArguments.contains(" -- --launchTarget ${target} ") + || !(programArguments =~ /(?:^|\s)--launchTarget\s+${target}(?:\s|$)/).find() || !vmArguments.contains('--add-opens java.base/java.lang.invoke=ALL-UNNAMED')) { - throw new GradleException("${name} does not define the Forge 61 bootstrap and native launch target") + throw new GradleException("${name} does not define the Forge 64 bootstrap and native launch target") } - ['--cache', '--metadata', '--to-srg', '--to-obf'].each { option -> + ['--cache', '--metadata'].each { option -> def argument = programArguments =~ /(?:^|\s)${option}\s+(?:"([^"]+)"|([^"\s]+))(?=\s+--|$)/ if (!argument.find()) { throw new GradleException("${name} must keep ${option} as one Slime Launcher argument") @@ -1617,6 +1843,7 @@ tasks.register('verifyRecipeTagGuards') { } tasks.named('check') { + dependsOn tasks.named('verifyEclipseLaunchArguments') dependsOn tasks.named('compileGameTestValidationJava'), tasks.named('compileClientValidationJava') dependsOn tasks.named('verifyJavaSourceLayout') dependsOn tasks.named('verifyReleaseConfiguration') diff --git a/docs/NATIVE2612.md b/docs/NATIVE2612.md new file mode 100644 index 00000000..90b9a090 --- /dev/null +++ b/docs/NATIVE2612.md @@ -0,0 +1,137 @@ +# Minecraft 26.1.2 + +This is a local port, not a release. The starting source is the released +1.21.11 merge `951fc3921f27e963e96f29c78a38ee7093a65fde`. + +Candidate01 was stopped after the parent found an actual Power inventory-lock +reflection failure during finite operations. The parent repaired that code. +Historical candidate01 results remain bound to their original inputs, including Electric +`D836D4AC22D1C46BFCD98B77A10A9475429D95141192B9C632F59DC87C273D73`. +They must not be relabeled as candidate02 or full family acceptance. + +## Current Checkpoint + +Electric's local candidate05 player jar is +`1936D3E2272B72CFD387ED59FFC389018D0FC841BFB9F1E0713334501CD2E54A`. +The ZIP comparison against the previous `1515772F...` jar has 643 entries on +each side and changes only `META-INF/MANIFEST.MF`, for the repaired Power +dependency pin. Every class and resource is byte-identical. This comparison +is not gameplay qualification. + +The forced fresh 51-task suite passes 115 unit and 28 native resource tests, +with zero failures or skips. Fresh ECJ output passes all 115 tests with the +complete compiled-method inventory and negative controls. All four named +source GameTests and both new-pin reproduction builds pass. Receipts are +recorded separately in `build/port-evidence/candidate05-2612-01`. +This local checkpoint is not final family qualification. + +The parent repaired Power's fuel-container remainder handling and handed over +the exact source and ordinary jar below. The previous `F39FF5EC...` Power and +`1515772F...` Electric results remain under their original receipts in +`candidate02-2612-01`; they are not relabeled for this checkpoint. + +## Target + +- Minecraft 26.1.2, Forge 64.1.5, MCP 20260409.101008, official runtime names. +- Temurin 25.0.3+9 for all roles, ForgeGradle 7.0.34, Gradle 9.6.1, `reobf=false`. +- baseElectric 2.2.17.2601021; basePower 2.4.16.2601021 and Base Metals 3.1.2.2601021 required. +- OreSpawn 4.1.0.2601021, source `c45ea2a111520ef904e994dd8dcd90babfcef29b`, + ordinary jar SHA-256 `6D5CE2017C3314968083D140309E2F1A54BC239AC65C48944C995E9ECF937948`. + +The local Power checkpoint is `e49a2e947e066a0f0e497982c2d5db596dfe11d9`; +the Base Metals shared-hook checkpoint is `13d4c4e38bf74d76f2f69f307dc55d13a81e8674`. +Their exact ordinary jar hashes are recorded in `gradle.properties`. +These are local integration inputs, not public releases or completed family +qualification. Earlier provisional diagnostics remain under their original +hashes and scope labels. + +## Compatibility + +Registry IDs, legacy aliases, flat NBT keys, recipes, unlock IDs and machine +balance remain unchanged. Native recipe assembly takes only the input; +results use native stack templates. Client renderers use native extraction +and the target camera-state package. Rendered scenes remain a required gate. + +The old villager trade event no longer exists. The six Electric librarian +offers are now `villager_trade` resources, added to the existing novice and +apprentice tags without replacing vanilla offers. The same quantity and +emerald ranges, seven uses, one XP and zero price multiplier are retained. + +Root pack metadata declares data format 101.1. Forge's hidden client pack +must synthesize native resource format 84.0. Both paths are tested through +actual Forge discovery, with strict warning and error rejection unchanged. + +## Verification Status + +The independent native suite passes 28 tests without skips. All 58 recipe +resources and 50 unlocks are retained. Native pack discovery accepts resource +84.0 and data 101.1, with zero warning/error diagnostics. + +Compilation of all 87 production sources and all 36 test sources +passes against Power `3D9ACF8B86DD1B443B138C61E5E1C5BAAC3059D83EED83B8E6E927BC501CA446` +and Base Metals `4911669B93A5C64C0F7AE0AAAEBB50BA90EC82A42436B9E6DEF6C1524B1CFF9C`. +All 115 unit tests pass without skips. Fresh ECJ output also passes 115 tests; +main, test, GameTest and client-contract scopes compile as Java 25. Both pinned +unit-support sources are compiled with the tests. The separately rebuilt agent +must match `088FC137BEBB9B2E75C596DE868499E6099CF228BECFCF07EAE36791245FD1B6`. +It never enters production jars or game launches. The ECJ gate scans every +fresh compiled `@Test` method, rejects filtered/stale XML and unreviewed runners, +and retains forbidden-JDK-access and namespace-exclusion negative controls. + +The full 51-task local release/dependency/artifact/Eclipse suite passes against +these local pins. Native resource children use an explicit four-CPU argument, +clear/reject all three inherited JVM-option variables and record runtime +arguments and classpaths. No warning or error filtering was added. + +All four named source GameTests passed again against the current Power pin on +actual Forge 64.1.5, followed by normal save and shutdown: + +- `baseelectric:drillupgradesandtransferslegacybattery` +- `baseelectric:recipeavailabilitydoesnotremovediscovery` +- `baseelectric:siliconandsolderresolverealmaterialtags` +- `baseelectric:turretusesnativeupdatepacket` + +The XML and runtime receipt are in `candidate05-2612-01/gametest-01`. +This is source-output GameTest evidence, not frozen-player-jar proof. The +first-boot log retains configuration-default corrections and native union-URL +warnings; its clean-log gate is not green. The runtime receipt records Java +25.0.3+9, four CPUs, explicit JVM arguments and absent inherited JVM options. + +The previous Power pin's two independent source snapshots in paths containing spaces pass the same +51-task suite, each with 115 unit and 28 native checks and no skips. One uses +the established isolated cache; the other starts with a new empty cache, +downloads Gradle and native inputs, and regenerates the Forge engine. That +engine and all three Electric jars match the primary bytes exactly. The +completed warm/cold receipts remain under `candidate02-2612-01`. These runs +started before the fuel-repair hold; no later pin is covered by them. The new +checkpoint has separate passing reproduction receipts, using the established +cache and the previously cold-generated engine cache rather than repeating +downloads. Both use new source snapshots and output directories containing +spaces, force all 51 tasks without a build cache, and reproduce the ordinary, +sources and Javadoc jars exactly. Both again pass 115 unit and 28 native checks. + +The current frozen ordinary Electric and Power jars also pass the packaged +server smoke's 19 narrow checks, followed by normal save/stop. The run uses +Java 25 with explicit CPU arguments and no inherited JVM-option variables. +It is not a restart, whole-world, fuel-container or clean-log qualification. + +GUI/assets/scenes and combined development and packaged classpaths remain +parent-owned gates. Current logs, including failures, are retained under +`build/port-evidence/initial-2612-01` for candidate01 and the separate candidate02 +folder and candidate05 folder above; none is a release acceptance claim. + +A candidate01 packaged smoke already in flight at the hold completed 19 narrow +checks, including drill inventory conversion, battery components, sulfur drops, +damage types and a normal save/stop. It did not exercise the failed inventory +lock path, a restart, or a whole-world upgrade. It remains historical evidence. + +The approved Base Metals and public OreSpawn jars both retain their exact +`26.1.2-64.0.9` build stamps. Electric targets runtime 64.1.5 and audits those +dependency stamps separately without rebuilding or changing their jars. +Actual coordinated client/server checks still determine runtime compatibility. + +No protected or manual world may be started by these checks. The parent owns +combined migrations, gameplay campaigns and endurance. The historical turbine +manual-world limit is not a pass for this version. + +No 26.1.2 push, PR, tag or publication is authorized. diff --git a/gradle.properties b/gradle.properties index 79bd9311..0cfd2f2c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,58 +5,66 @@ org.gradle.caching=true org.gradle.parallel=false org.gradle.workers.max=2 net.minecraftforge.gradle.merge-source-sets=false +reobf=false -minecraft_version=1.21.11 -minecraft_version_range=[1.21.11] -forge_version=61.1.0 -forge_installer_sha256=8BD1724EAEAF26F51DACFB7FDC46D79E9B640705B70D5F0019ED38907145EB48 -forge_shim_sha256=09008DB9881E35E62669865F3B7420741EDB1A52BC59C67A5FA03D24C6C8E6E3 -forge_server_sha256=8D997159003977A22DB8E4EA6DAAB6CBB0E76F752A196A1821C74734A1CF71D9 -forge_universal_sha256=101041931319D508D94EF9ED6643CA4374EA3950237443301F7AA5A3B2631CC4 -forge_version_range=[61.1.0,62) -loader_version_range=[61,) +minecraft_version=26.1.2 +minecraft_version_range=[26.1.2] +forge_version=64.1.5 +forge_installer_sha256=6E01323837051176762BD466340A601DA122F179FE6A00AC616E05429AE07648 +forge_shim_sha256=0C74CB2A8E7A71AEB7055396C1E25864584A2D837D7DD42136A06D0B41ADD545 +forge_server_sha256=C6B85C178B037519464D436A75B7C68408CBE591576270BA0E826542B50BF41B +forge_universal_sha256=687C2698ECC1906CA2AD398A445C2498CF8BC09F1CBE6C2A6E24BFC9F90736C7 +forge_version_range=[64.1.5,65) +loader_version_range=[64,65) mapping_channel=official -mapping_version=1.21.11 -mcp_version=20251223.124241 +mapping_version=26.1.2 +mcp_version=20260409.101008 loader_name=forge loader_code=1 -java_version=21 -java_toolchain_version=21.0.7+6 -java_setup_version=21.0.7+6.0.LTS -gradle_java_version=21 +java_version=25 +java_toolchain_version=25.0.3+9 +java_setup_version=25.0.3+9.0.LTS +gradle_java_version=25 curseforge_project_id=1783006 mod_id=baseelectric mod_name=baseElectric mod_license=GPL-3.0-only -mod_version=2.2.16.121111 +mod_version=2.2.17.2601021 mod_group=zone.moddev.mc.baseelectric mod_authors=DrCyano mod_description=Electric-powered machines and infrastructure for basePower. cf_requirements=basepower,base-metals cf_optionals= -cf_versions=1.21.11 +cf_versions=26.1.2 -poweradvantage_version=2.4.15.121111 -poweradvantage_pin_state=MERGED_SOURCE +poweradvantage_version=2.4.16.2601021 +poweradvantage_pin_state=LOCAL_SOURCE poweradvantage_source_repository=MinecraftModDevelopmentMods/basePower -# Verified public basePower release; the ordinary native jar is also the dev jar. -poweradvantage_source_ref=9b0043483d79d3e575895f12aeb0dde7ef6c4890 -poweradvantage_public_file_id=9566998 -poweradvantage_deobf_sha256=C98D2CF163BE2480D9AFB0023BBD2D92D401AE0D50BEF73191235BF3F83774A7 -poweradvantage_runtime_sha256=C98D2CF163BE2480D9AFB0023BBD2D92D401AE0D50BEF73191235BF3F83774A7 - -basemetals_version=3.1.2.121111 -basemetals_source_commit=6f4c44e4d0a68bca3838fb025c09cddfaaa08fe8 -# Verified public release includes the shared non-cancelling migration hook. -basemetals_public_file_id=9566738 -basemetals_deobf_sha256=598D3FB0FF956EBCC67FE477A01CE393C75D0013AF0CE00A79E2072B63E9F2BA -basemetals_runtime_sha256=598D3FB0FF956EBCC67FE477A01CE393C75D0013AF0CE00A79E2072B63E9F2BA - -orespawn_version=4.1.0.121111 -orespawn_source_commit=ecad75b24309a344f712d898d717c6a8823fc9f2 -orespawn_public_file_id=9268464 -orespawn_deobf_sha256=DE39929057288CEA110196C9E9D708E129B80BC360A2E53976337FFA6A84A67B -orespawn_runtime_sha256=DE39929057288CEA110196C9E9D708E129B80BC360A2E53976337FFA6A84A67B +# Owner-approved local integration checkpoint, not a public or family-qualified release. +poweradvantage_source_ref=e49a2e947e066a0f0e497982c2d5db596dfe11d9 +poweradvantage_public_file_id= +poweradvantage_deobf_sha256=3D9ACF8B86DD1B443B138C61E5E1C5BAAC3059D83EED83B8E6E927BC501CA446 +poweradvantage_runtime_sha256=3D9ACF8B86DD1B443B138C61E5E1C5BAAC3059D83EED83B8E6E927BC501CA446 + +basemetals_version=3.1.2.2601021 +basemetals_built_on=26.1.2-64.0.9 +# Local native shared-hook checkpoint; family qualification is separate. +basemetals_source_commit=13d4c4e38bf74d76f2f69f307dc55d13a81e8674 +basemetals_public_file_id= +basemetals_deobf_sha256=4911669B93A5C64C0F7AE0AAAEBB50BA90EC82A42436B9E6DEF6C1524B1CFF9C +basemetals_runtime_sha256=4911669B93A5C64C0F7AE0AAAEBB50BA90EC82A42436B9E6DEF6C1524B1CFF9C + +unit_bootstrap_sha256=088FC137BEBB9B2E75C596DE868499E6099CF228BECFCF07EAE36791245FD1B6 +unit_bootstrap_java_sha256=D4B72FD65D9F8549B142507C6446E3B1E047B637784AF9FA74E8F28413E14A51 +unit_registries_java_sha256=01DDE50C9874C2DC501BE2F699CD8D16D6974FACFAAC1B803434CA8C64227ABF +unit_loader_java_sha256=455CA9403D9CBEA72E7D055966D6CDC822E634418C14B3CAD0500588DEE35E5B + +orespawn_version=4.1.0.2601021 +orespawn_built_on=26.1.2-64.0.9 +orespawn_source_commit=c45ea2a111520ef904e994dd8dcd90babfcef29b +orespawn_public_file_id=9299079 +orespawn_deobf_sha256=6D5CE2017C3314968083D140309E2F1A54BC239AC65C48944C995E9ECF937948 +orespawn_runtime_sha256=6D5CE2017C3314968083D140309E2F1A54BC239AC65C48944C995E9ECF937948 diff --git a/gradle/forge61-mavenizer.gradle b/gradle/forge64-mavenizer.gradle similarity index 58% rename from gradle/forge61-mavenizer.gradle rename to gradle/forge64-mavenizer.gradle index f587816d..a84b873c 100644 --- a/gradle/forge61-mavenizer.gradle +++ b/gradle/forge64-mavenizer.gradle @@ -1,22 +1,22 @@ import java.security.MessageDigest import org.gradle.jvm.toolchain.JvmVendorSpec -def fixture = file(providers.gradleProperty('forge61MavenizerJar').getOrElse( - '../../../../MinecraftMineralogy/OS12111/OreSpawn/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.jar')) -def expected = 'B36CA046DB72E30F27BD38E02B4AE34274ADB51AE1D65BAF9E1D07763755682E' +def fixture = file(providers.gradleProperty('forge64MavenizerJar').getOrElse( + '../../../../MinecraftMineralogy/OS2612/OreSpawn/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.jar')) +def expected = 'BB0DACB3DEB2A711CA67DD402D5FDAB5A9847C37C7B9B596DEC7CF37378FA085' if (!fixture.isFile()) { - throw new GradleException("Missing Forge 61 Mavenizer fixture: ${fixture}. Set -Pforge61MavenizerJar=.") + throw new GradleException("Missing Forge 64 Mavenizer fixture: ${fixture}. Set -Pforge64MavenizerJar=.") } def actual = MessageDigest.getInstance('SHA-256').digest(fixture.bytes).encodeHex().toString().toUpperCase(Locale.ROOT) if (actual != expected) { - throw new GradleException("Forge 61 Mavenizer fixture SHA-256 mismatch: expected ${expected}, got ${actual}") + throw new GradleException("Forge 64 Mavenizer fixture SHA-256 mismatch: expected ${expected}, got ${actual}") } def rules = new File(fixture.parentFile, 'minecraft-source-compatibility.json') -def expectedRules = '88F2EE4C6B7C903EB1C7D693C0BEEFC0EFC601C1023FB197F7CBA187B1142DB7' +def expectedRules = '21D1F5984E735BCEA1B1EFB9F965655796B39538FC91AAED1C3474004710423D' if (!rules.isFile() || MessageDigest.getInstance('SHA-256').digest(rules.bytes) .encodeHex().toString().toUpperCase(Locale.ROOT) != expectedRules) { - throw new GradleException("Missing or mismatched Forge 61 Mavenizer rules: ${rules}") + throw new GradleException("Missing or mismatched Forge 64 Mavenizer rules: ${rules}") } fgtools.configure('mavenizer') { diff --git a/src/gametest/java/zone/moddev/mc/baseelectric/gametest/ElectricGameTests.java b/src/gametest/java/zone/moddev/mc/baseelectric/gametest/ElectricGameTests.java index 86bce497..ba4c983b 100644 --- a/src/gametest/java/zone/moddev/mc/baseelectric/gametest/ElectricGameTests.java +++ b/src/gametest/java/zone/moddev/mc/baseelectric/gametest/ElectricGameTests.java @@ -36,6 +36,9 @@ private ElectricGameTests() {} @net.minecraftforge.eventbus.api.listener.SubscribeEvent public static void registerFunctions(net.minecraftforge.registries.RegisterEvent event) { + if (event.getRegistryKey().equals(net.minecraft.core.registries.Registries.TEST_FUNCTION)) { + recordRuntimeBoundary(); + } event.register(net.minecraft.core.registries.Registries.TEST_FUNCTION, registry -> { registry.register(Identifier.parse("baseelectric:drillupgradesandtransferslegacybattery"), ElectricGameTests::drillUpgradesAndTransfersLegacyBattery); @@ -54,6 +57,33 @@ public static void main(String[] args) throws Exception { NbtIo.writeCompressed(NbtUtils.snbtToStructure(Files.readString(Path.of(args[0]))), output); } + private static void recordRuntimeBoundary() { + var arguments = java.lang.management.ManagementFactory.getRuntimeMXBean().getInputArguments(); + for (String name : java.util.List.of("JAVA_TOOL_OPTIONS", "JDK_JAVA_OPTIONS", "_JAVA_OPTIONS")) { + if (System.getenv(name) != null) throw new AssertionError("Inherited native GameTest JVM options: " + name); + } + if (!arguments.contains("-XX:ActiveProcessorCount=4") || Runtime.getRuntime().availableProcessors() != 4) { + throw new AssertionError("Native GameTests require an explicit four-CPU JVM argument"); + } + String destination = System.getProperty("baseelectric.gameTestRuntimeReceipt"); + if (destination == null) throw new AssertionError("Native GameTest runtime receipt path is missing"); + var result = new com.google.gson.JsonObject(); + result.addProperty("scope", "SOURCE_GAMETEST_NOT_FROZEN_PLAYER_JAR"); + result.addProperty("javaRuntimeVersion", System.getProperty("java.runtime.version")); + result.addProperty("availableProcessors", Runtime.getRuntime().availableProcessors()); + result.addProperty("environmentJvmOptionsAbsent", true); + result.addProperty("classpath", System.getProperty("java.class.path")); + result.addProperty("electricCodeSource", ElectricAdvantage.class.getProtectionDomain().getCodeSource().getLocation().toString()); + var inputs = new com.google.gson.JsonArray(); + arguments.forEach(inputs::add); + result.add("jvmArguments", inputs); + try { + Files.writeString(Path.of(destination), result.toString(), java.nio.file.StandardOpenOption.CREATE_NEW); + } catch (java.io.IOException failure) { + throw new AssertionError("Cannot preserve native GameTest runtime receipt", failure); + } + } + @GameTest(name = "drillupgradesandtransferslegacybattery", structure = "empty") public static void drillUpgradesAndTransfersLegacyBattery(GameTestHelper helper) { BlockPos source = new BlockPos(1, 1, 1); diff --git a/src/main/java/zone/moddev/mc/baseelectric/ElectricAdvantage.java b/src/main/java/zone/moddev/mc/baseelectric/ElectricAdvantage.java index 479774ce..2e9008cf 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/ElectricAdvantage.java +++ b/src/main/java/zone/moddev/mc/baseelectric/ElectricAdvantage.java @@ -21,17 +21,16 @@ import zone.moddev.mc.baseelectric.init.GUI; import zone.moddev.mc.baseelectric.init.OreSpawnWorldGen; import zone.moddev.mc.baseelectric.init.TreasureChests; -import zone.moddev.mc.baseelectric.init.Villages; import zone.moddev.mc.baseelectric.recipe.RecipeConditions; import zone.moddev.mc.baseelectric.migration.ElectricLegacyMigrations; -/** Forge 1.21.11 entry point for baseElectric. */ +/** Forge 26.1.2 entry point for baseElectric. */ @Mod(ElectricAdvantage.MODID) public final class ElectricAdvantage { public static final String MODID = "baseelectric"; public static final String LEGACY_MODID = "electricadvantage"; public static final String NAME = "baseElectric"; - public static final String VERSION = "2.2.16.121111"; + public static final String VERSION = "2.2.17.2601021"; public static final Logger LOGGER = LogManager.getLogger(MODID); public static ElectricAdvantage INSTANCE; @@ -87,7 +86,6 @@ private void commonSetup(FMLCommonSetupEvent event) { event.enqueueWork(GUI::init); ElectricAdvantageConfig.apply(); TreasureChests.init(FMLPaths.CONFIGDIR.get()); - Villages.init(); LOGGER.info("{} {} common setup complete", NAME, VERSION); } diff --git a/src/main/java/zone/moddev/mc/baseelectric/blocks/Ore.java b/src/main/java/zone/moddev/mc/baseelectric/blocks/Ore.java index cf774559..db1726ff 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/blocks/Ore.java +++ b/src/main/java/zone/moddev/mc/baseelectric/blocks/Ore.java @@ -11,6 +11,7 @@ import net.minecraft.world.level.material.MapColor; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.ItemInstance; import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.item.enchantment.Enchantments; import net.minecraft.world.level.storage.loot.LootParams; @@ -52,14 +53,14 @@ public Ore(int pickLevel) { @Override public List getDrops(BlockState state, LootParams.Builder context) { - ItemStack tool = context.getOptionalParameter(LootContextParams.TOOL); + ItemInstance tool = context.getOptionalParameter(LootContextParams.TOOL); ItemStack dropItem = drop.get(); if (dropItem.isEmpty() || (tool != null && EnchantmentHelper.getItemEnchantmentLevel(context.getLevel().registryAccess() .lookupOrThrow(Registries.ENCHANTMENT).getOrThrow(Enchantments.SILK_TOUCH), tool) > 0)) { return Collections.singletonList(new ItemStack(this)); } - RandomSource random = context.getLevel().random; + RandomSource random = context.getLevel().getRandom(); ItemStack result = dropItem.copy(); result.setCount(dropItem.getCount() + (dropRange > 1 ? random.nextInt(dropRange) : 0)); return Collections.singletonList(result); diff --git a/src/main/java/zone/moddev/mc/baseelectric/compat/BaseMetalsCompat.java b/src/main/java/zone/moddev/mc/baseelectric/compat/BaseMetalsCompat.java index cabdac37..3eecc94a 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/compat/BaseMetalsCompat.java +++ b/src/main/java/zone/moddev/mc/baseelectric/compat/BaseMetalsCompat.java @@ -17,7 +17,7 @@ public final class BaseMetalsCompat { private BaseMetalsCompat() {} public static boolean isLoaded() { - return ModList.get().isLoaded("basemetals"); + return ModList.isLoaded("basemetals"); } /** 1.13 additions are supplied as JSON crushing recipes and item tags. */ @@ -36,7 +36,7 @@ public static ItemStack getCrusherRecipeOutput(Level world, ItemStack input) { SingleRecipeInput inventory = new SingleRecipeInput(input.copy()); for (RecipeHolder holder : server.recipeAccess().getRecipes()) { if (holder.value() instanceof CrushingRecipe candidate && candidate.matches(inventory, world)) { - return candidate.assemble(inventory, world.registryAccess()); + return candidate.assemble(inventory); } } } diff --git a/src/main/java/zone/moddev/mc/baseelectric/entities/HydroturbineEntity.java b/src/main/java/zone/moddev/mc/baseelectric/entities/HydroturbineEntity.java index e2c3f527..95453f59 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/entities/HydroturbineEntity.java +++ b/src/main/java/zone/moddev/mc/baseelectric/entities/HydroturbineEntity.java @@ -30,7 +30,7 @@ public class HydroturbineEntity extends net.minecraft.world.entity.Entity{ public HydroturbineEntity(EntityType type, Level w) { super(type, w); this.blocksBuilding = true; - roffset = w.random.nextInt(8); + roffset = w.getRandom().nextInt(8); } public HydroturbineEntity(Level w, BlockPos parentTileEntity) { this(w, w.getBlockEntity(parentTileEntity)); diff --git a/src/main/java/zone/moddev/mc/baseelectric/graphics/ElectricRenderTypes.java b/src/main/java/zone/moddev/mc/baseelectric/graphics/ElectricRenderTypes.java index 1a84168b..9fae3841 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/graphics/ElectricRenderTypes.java +++ b/src/main/java/zone/moddev/mc/baseelectric/graphics/ElectricRenderTypes.java @@ -1,6 +1,7 @@ package zone.moddev.mc.baseelectric.graphics; import com.mojang.blaze3d.pipeline.BlendFunction; +import com.mojang.blaze3d.pipeline.ColorTargetState; import com.mojang.blaze3d.pipeline.RenderPipeline; import net.minecraft.client.renderer.RenderPipelines; import net.minecraft.client.renderer.rendertype.RenderSetup; @@ -19,7 +20,7 @@ private static RenderPipeline pipeline(boolean translucent) { .withLocation(Identifier.fromNamespaceAndPath("baseelectric", translucent ? "pipeline/unshaded_translucent" : "pipeline/unshaded")) .withShaderDefine("NO_CARDINAL_LIGHTING").withSampler("Sampler1").withCull(false); - if (translucent) builder.withBlend(BlendFunction.TRANSLUCENT); + if (translucent) builder.withColorTargetState(new ColorTargetState(BlendFunction.TRANSLUCENT)); RenderPipeline pipeline = builder.build(); RenderPipelines.PIPELINES_BY_LOCATION.put(pipeline.getLocation(), pipeline); return pipeline; diff --git a/src/main/java/zone/moddev/mc/baseelectric/graphics/HydroturbineRenderer.java b/src/main/java/zone/moddev/mc/baseelectric/graphics/HydroturbineRenderer.java index f6268709..81472d9d 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/graphics/HydroturbineRenderer.java +++ b/src/main/java/zone/moddev/mc/baseelectric/graphics/HydroturbineRenderer.java @@ -7,7 +7,7 @@ import net.minecraft.client.renderer.rendertype.RenderTypes; import net.minecraft.client.renderer.SubmitNodeCollector; import net.minecraft.client.renderer.entity.state.EntityRenderState; -import net.minecraft.client.renderer.state.CameraRenderState; +import net.minecraft.client.renderer.state.level.CameraRenderState; import com.mojang.math.Axis; import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.client.renderer.entity.EntityRenderer; diff --git a/src/main/java/zone/moddev/mc/baseelectric/graphics/LaserDrillRenderer.java b/src/main/java/zone/moddev/mc/baseelectric/graphics/LaserDrillRenderer.java index e0e6973d..1bbfeb3f 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/graphics/LaserDrillRenderer.java +++ b/src/main/java/zone/moddev/mc/baseelectric/graphics/LaserDrillRenderer.java @@ -8,7 +8,7 @@ import net.minecraft.client.renderer.SubmitNodeCollector; import net.minecraft.client.renderer.blockentity.state.BlockEntityRenderState; import net.minecraft.client.renderer.feature.ModelFeatureRenderer; -import net.minecraft.client.renderer.state.CameraRenderState; +import net.minecraft.client.renderer.state.level.CameraRenderState; import net.minecraft.world.phys.Vec3; import net.minecraft.client.renderer.texture.OverlayTexture; import com.mojang.math.Axis; diff --git a/src/main/java/zone/moddev/mc/baseelectric/graphics/LaserTurretRenderer.java b/src/main/java/zone/moddev/mc/baseelectric/graphics/LaserTurretRenderer.java index 237682c9..40e46bc3 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/graphics/LaserTurretRenderer.java +++ b/src/main/java/zone/moddev/mc/baseelectric/graphics/LaserTurretRenderer.java @@ -8,7 +8,7 @@ import net.minecraft.client.renderer.SubmitNodeCollector; import net.minecraft.client.renderer.blockentity.state.BlockEntityRenderState; import net.minecraft.client.renderer.feature.ModelFeatureRenderer; -import net.minecraft.client.renderer.state.CameraRenderState; +import net.minecraft.client.renderer.state.level.CameraRenderState; import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.world.phys.Vec3; import com.mojang.math.Axis; diff --git a/src/main/java/zone/moddev/mc/baseelectric/init/OreSpawnWorldGen.java b/src/main/java/zone/moddev/mc/baseelectric/init/OreSpawnWorldGen.java index 1e6a0045..21aed57d 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/init/OreSpawnWorldGen.java +++ b/src/main/java/zone/moddev/mc/baseelectric/init/OreSpawnWorldGen.java @@ -25,8 +25,8 @@ private OreSpawnWorldGen() { } public static void register(Path configDirectory) { - boolean mineralogyLoaded = ModList.get().isLoaded("mineralogy"); - boolean baseMineralsLoaded = ModList.get().isLoaded("baseminerals"); + boolean mineralogyLoaded = ModList.isLoaded("mineralogy"); + boolean baseMineralsLoaded = ModList.isLoaded("baseminerals"); boolean accepted = OreSpawnApi.enqueue(createProvider(mineralogyLoaded, baseMineralsLoaded)); String sulfurOwner = mineralogyLoaded ? "mineralogy" : baseMineralsLoaded ? "baseminerals" : ElectricAdvantage.MODID; diff --git a/src/main/java/zone/moddev/mc/baseelectric/init/Villages.java b/src/main/java/zone/moddev/mc/baseelectric/init/Villages.java deleted file mode 100644 index 59add056..00000000 --- a/src/main/java/zone/moddev/mc/baseelectric/init/Villages.java +++ /dev/null @@ -1,45 +0,0 @@ -package zone.moddev.mc.baseelectric.init; - -import java.util.List; -import net.minecraft.world.entity.npc.villager.VillagerProfession; -import net.minecraft.world.entity.npc.villager.VillagerTrades; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.trading.MerchantOffer; -import net.minecraft.world.item.trading.ItemCost; -import net.minecraftforge.event.village.VillagerTradesEvent; - -/** Adds the historical librarian offers through the native profession trade event. */ -public final class Villages { - private static boolean initialized; - - private Villages() {} - - public static synchronized void init() { - if (initialized) return; - VillagerTradesEvent.BUS.addListener(Villages::addTrades); - initialized = true; - } - - public static void addTrades(VillagerTradesEvent event) { - if (event.getType() != VillagerProfession.LIBRARIAN) return; - List novice = event.getTrades().get(1); - List apprentice = event.getTrades().get(2); - novice.add(sell(Blocks.electric_conduit.asItem(), -6, -3)); - novice.add(sell(Items.solder, -10, -5)); - novice.add(sell(Items.integrated_circuit, -6, -3)); - novice.add(sell(Items.blank_circuit_board, -6, -3)); - apprentice.add(sell(Items.petrolplastic_ingot, -8, -4)); - apprentice.add(sell(Items.power_supply_unit, 1, 3)); - } - - static VillagerTrades.ItemListing sell(Item item, int minimumPrice, int maximumPrice) { - return (level, villager, random) -> { - int price = minimumPrice + random.nextInt(maximumPrice - minimumPrice + 1); - int emeralds = price < 0 ? 1 : price; - int count = price < 0 ? -price : 1; - return new MerchantOffer(new ItemCost(net.minecraft.world.item.Items.EMERALD, emeralds), - new ItemStack(item, count), 7, 1, 0.0F); - }; - } -} diff --git a/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricMachineTileEntity.java b/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricMachineTileEntity.java index a29d4017..abcbfea5 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricMachineTileEntity.java +++ b/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricMachineTileEntity.java @@ -221,7 +221,7 @@ protected ItemStack getSmeltingResult(ItemStack input) { SingleRecipeInput inventory = new SingleRecipeInput(input); if (!(getLevel() instanceof net.minecraft.server.level.ServerLevel server)) return ItemStack.EMPTY; return server.recipeAccess().getRecipeFor(RecipeType.SMELTING, inventory, getLevel()) - .map(recipe -> recipe.value().assemble(inventory, getLevel().registryAccess())).orElse(ItemStack.EMPTY); + .map(recipe -> recipe.value().assemble(inventory)).orElse(ItemStack.EMPTY); } @Override diff --git a/src/main/java/zone/moddev/mc/baseelectric/machines/GrowthChamberTileEntity.java b/src/main/java/zone/moddev/mc/baseelectric/machines/GrowthChamberTileEntity.java index 26fbf923..f581344e 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/machines/GrowthChamberTileEntity.java +++ b/src/main/java/zone/moddev/mc/baseelectric/machines/GrowthChamberTileEntity.java @@ -75,9 +75,9 @@ public void tickUpdate(boolean isServer) { getLevel().addFreshEntity( new ItemEntity( getLevel(), - 2*(getLevel().random.nextDouble()-0.5), - 2*(getLevel().random.nextDouble()-0.5), - 2*(getLevel().random.nextDouble()-0.5), + 2*(getLevel().getRandom().nextDouble()-0.5), + 2*(getLevel().getRandom().nextDouble()-0.5), + 2*(getLevel().getRandom().nextDouble()-0.5), remainder ) ); diff --git a/src/main/java/zone/moddev/mc/baseelectric/machines/PhotovoltaicGeneratorTileEntity.java b/src/main/java/zone/moddev/mc/baseelectric/machines/PhotovoltaicGeneratorTileEntity.java index 179b5b99..69ebf454 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/machines/PhotovoltaicGeneratorTileEntity.java +++ b/src/main/java/zone/moddev/mc/baseelectric/machines/PhotovoltaicGeneratorTileEntity.java @@ -97,7 +97,7 @@ public static float getLightIntensityAt(BlockPos pos, Level w){ float light; if(w.canSeeSky(lightPos) && w.dimensionType().hasSkyLight()){ // outdoor light imposes no penalty - light = Mth.sin((float)w.getDayTime() * 2.61799E-4f); // (worldTime * pi / 12000); + light = Mth.sin((float)w.getOverworldClockTime() * 2.61799E-4f); // (worldTime * pi / 12000); // handle weather if(w.isThundering()){ light *= 0.2f; diff --git a/src/main/java/zone/moddev/mc/baseelectric/machines/SteamPoweredElectricGeneratorTileEntity.java b/src/main/java/zone/moddev/mc/baseelectric/machines/SteamPoweredElectricGeneratorTileEntity.java index 49c9a9dd..8751a8f5 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/machines/SteamPoweredElectricGeneratorTileEntity.java +++ b/src/main/java/zone/moddev/mc/baseelectric/machines/SteamPoweredElectricGeneratorTileEntity.java @@ -46,7 +46,7 @@ public SteamPoweredElectricGeneratorTileEntity(net.minecraft.core.BlockPos pos, } super.powerUpdate(); super.setActive(flag); - if(flag && getLevel().random.nextInt(200) == 0){ + if(flag && getLevel().getRandom().nextInt(200) == 0){ playSoundEffect(getBlockPos().getX()+0.5, getBlockPos().getY()+0.5, getBlockPos().getZ()+0.5, SoundEvents.FIRE_EXTINGUISH, 0.25f, 1f); } } diff --git a/src/main/java/zone/moddev/mc/baseelectric/util/farming/VirtualCrop.java b/src/main/java/zone/moddev/mc/baseelectric/util/farming/VirtualCrop.java index aabea912..e4f9da77 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/util/farming/VirtualCrop.java +++ b/src/main/java/zone/moddev/mc/baseelectric/util/farming/VirtualCrop.java @@ -56,7 +56,7 @@ public static VirtualCrop createVirtualCrop(ItemStack stack, Level w, BlockPos p && !(b instanceof net.minecraft.world.level.block.CropBlock) && !(b instanceof StemBlock)) || b instanceof net.minecraft.world.level.block.GrassBlock || b instanceof net.minecraft.world.level.block.VineBlock - || b instanceof net.minecraft.world.level.block.WaterlilyBlock){ + || b instanceof net.minecraft.world.level.block.LilyPadBlock){ // duplicate ItemStack product = seed.copy(); product.setCount(2); @@ -84,7 +84,7 @@ public static VirtualCrop createVirtualCrop(ItemStack stack, Level w, BlockPos p private static Block resolveStemFruit(StemBlock stem, Level world) { - // Forge 61 exposes the registered fruit key through the native stem codec. + // The native stem codec exposes the registered fruit key. var ops = world.registryAccess().createSerializationContext(JsonOps.INSTANCE); return StemBlock.CODEC.codec().encodeStart(ops, stem).result() .flatMap(data -> ResourceKey.codec(Registries.BLOCK) diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 427e2221..4020e365 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -31,13 +31,13 @@ side="BOTH" [[dependencies.baseelectric]] modId="basepower" mandatory=true -versionRange="[2.4.15.121111,3.0.0)" +versionRange="[2.4.16.2601021,3.0.0)" ordering="AFTER" side="BOTH" [[dependencies.baseelectric]] modId="basemetals" mandatory=true -versionRange="[3.1.2.121111,4.0.0)" +versionRange="[3.1.2.2601021,4.0.0)" ordering="AFTER" side="BOTH" diff --git a/src/main/resources/data/baseelectric/villager_trade/librarian/blank_circuit_board.json b/src/main/resources/data/baseelectric/villager_trade/librarian/blank_circuit_board.json new file mode 100644 index 00000000..4de72dd2 --- /dev/null +++ b/src/main/resources/data/baseelectric/villager_trade/librarian/blank_circuit_board.json @@ -0,0 +1,22 @@ +{ + "wants": { + "id": "minecraft:emerald", + "count": 1 + }, + "gives": { + "id": "baseelectric:blank_circuit_board" + }, + "max_uses": 7, + "xp": 1, + "reputation_discount": 0, + "given_item_modifiers": [ + { + "function": "minecraft:set_count", + "count": { + "type": "minecraft:uniform", + "min": 3, + "max": 6 + } + } + ] +} diff --git a/src/main/resources/data/baseelectric/villager_trade/librarian/electric_conduit.json b/src/main/resources/data/baseelectric/villager_trade/librarian/electric_conduit.json new file mode 100644 index 00000000..16177c80 --- /dev/null +++ b/src/main/resources/data/baseelectric/villager_trade/librarian/electric_conduit.json @@ -0,0 +1,22 @@ +{ + "wants": { + "id": "minecraft:emerald", + "count": 1 + }, + "gives": { + "id": "baseelectric:electric_conduit" + }, + "max_uses": 7, + "xp": 1, + "reputation_discount": 0, + "given_item_modifiers": [ + { + "function": "minecraft:set_count", + "count": { + "type": "minecraft:uniform", + "min": 3, + "max": 6 + } + } + ] +} diff --git a/src/main/resources/data/baseelectric/villager_trade/librarian/integrated_circuit.json b/src/main/resources/data/baseelectric/villager_trade/librarian/integrated_circuit.json new file mode 100644 index 00000000..60be326f --- /dev/null +++ b/src/main/resources/data/baseelectric/villager_trade/librarian/integrated_circuit.json @@ -0,0 +1,22 @@ +{ + "wants": { + "id": "minecraft:emerald", + "count": 1 + }, + "gives": { + "id": "baseelectric:integrated_circuit" + }, + "max_uses": 7, + "xp": 1, + "reputation_discount": 0, + "given_item_modifiers": [ + { + "function": "minecraft:set_count", + "count": { + "type": "minecraft:uniform", + "min": 3, + "max": 6 + } + } + ] +} diff --git a/src/main/resources/data/baseelectric/villager_trade/librarian/petrolplastic_ingot.json b/src/main/resources/data/baseelectric/villager_trade/librarian/petrolplastic_ingot.json new file mode 100644 index 00000000..0e4f4b60 --- /dev/null +++ b/src/main/resources/data/baseelectric/villager_trade/librarian/petrolplastic_ingot.json @@ -0,0 +1,22 @@ +{ + "wants": { + "id": "minecraft:emerald", + "count": 1 + }, + "gives": { + "id": "baseelectric:petrolplastic_ingot" + }, + "max_uses": 7, + "xp": 1, + "reputation_discount": 0, + "given_item_modifiers": [ + { + "function": "minecraft:set_count", + "count": { + "type": "minecraft:uniform", + "min": 4, + "max": 8 + } + } + ] +} diff --git a/src/main/resources/data/baseelectric/villager_trade/librarian/psu.json b/src/main/resources/data/baseelectric/villager_trade/librarian/psu.json new file mode 100644 index 00000000..640910f8 --- /dev/null +++ b/src/main/resources/data/baseelectric/villager_trade/librarian/psu.json @@ -0,0 +1,16 @@ +{ + "wants": { + "id": "minecraft:emerald", + "count": { + "type": "minecraft:uniform", + "min": 1, + "max": 3 + } + }, + "gives": { + "id": "baseelectric:psu" + }, + "max_uses": 7, + "xp": 1, + "reputation_discount": 0 +} diff --git a/src/main/resources/data/baseelectric/villager_trade/librarian/solder.json b/src/main/resources/data/baseelectric/villager_trade/librarian/solder.json new file mode 100644 index 00000000..b0b9d9bb --- /dev/null +++ b/src/main/resources/data/baseelectric/villager_trade/librarian/solder.json @@ -0,0 +1,22 @@ +{ + "wants": { + "id": "minecraft:emerald", + "count": 1 + }, + "gives": { + "id": "baseelectric:solder" + }, + "max_uses": 7, + "xp": 1, + "reputation_discount": 0, + "given_item_modifiers": [ + { + "function": "minecraft:set_count", + "count": { + "type": "minecraft:uniform", + "min": 5, + "max": 10 + } + } + ] +} diff --git a/src/main/resources/data/minecraft/tags/villager_trade/librarian/level_1.json b/src/main/resources/data/minecraft/tags/villager_trade/librarian/level_1.json new file mode 100644 index 00000000..aa7c4f62 --- /dev/null +++ b/src/main/resources/data/minecraft/tags/villager_trade/librarian/level_1.json @@ -0,0 +1,9 @@ +{ + "replace": false, + "values": [ + "baseelectric:librarian/electric_conduit", + "baseelectric:librarian/solder", + "baseelectric:librarian/integrated_circuit", + "baseelectric:librarian/blank_circuit_board" + ] +} diff --git a/src/main/resources/data/minecraft/tags/villager_trade/librarian/level_2.json b/src/main/resources/data/minecraft/tags/villager_trade/librarian/level_2.json new file mode 100644 index 00000000..76aa7893 --- /dev/null +++ b/src/main/resources/data/minecraft/tags/villager_trade/librarian/level_2.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": [ + "baseelectric:librarian/petrolplastic_ingot", + "baseelectric:librarian/psu" + ] +} diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta index e1986b3d..01402369 100644 --- a/src/main/resources/pack.mcmeta +++ b/src/main/resources/pack.mcmeta @@ -1,7 +1,7 @@ { "pack":{ - "min_format":[94,1], - "max_format":[94,1], + "min_format":[101,1], + "max_format":[101,1], "description":"baseElectric" } } diff --git a/src/test/java/zone/moddev/mc/baseelectric/entities/ElectricOperationalHydroturbineAnchorTest.java b/src/test/java/zone/moddev/mc/baseelectric/entities/ElectricOperationalHydroturbineAnchorTest.java index da1adedc..a3f75a98 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/entities/ElectricOperationalHydroturbineAnchorTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/entities/ElectricOperationalHydroturbineAnchorTest.java @@ -103,7 +103,7 @@ private static Level world() throws Exception { field.setAccessible(true); Level world = (Level)allocator.getMethod("allocateInstance", Class.class) .invoke(field.get(null), NoTickWorld.class); - Field random = Level.class.getField("random"); + Field random = Level.class.getDeclaredField("random"); random.setAccessible(true); random.set(world, RandomSource.create(0)); return world; @@ -111,7 +111,7 @@ private static Level world() throws Exception { private static final class NoTickWorld extends ServerLevel { private NoTickWorld() { - super(null, null, null, null, null, null, false, 0L, Collections.emptyList(), false, null); + super(null, null, null, null, null, null, false, 0L, Collections.emptyList(), false); } } diff --git a/src/test/java/zone/moddev/mc/baseelectric/init/VillagesTest.java b/src/test/java/zone/moddev/mc/baseelectric/init/VillagesTest.java index f0ed429f..90baf88b 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/init/VillagesTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/init/VillagesTest.java @@ -1,51 +1,109 @@ package zone.moddev.mc.baseelectric.init; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.mojang.serialization.JsonOps; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; +import net.minecraft.core.HolderGetter; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.Identifier; import net.minecraft.util.RandomSource; -import net.minecraft.world.level.levelgen.LegacyRandomSource; +import net.minecraft.util.context.ContextMap; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Items; import net.minecraft.world.item.trading.MerchantOffer; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.NbtOps; -import zone.moddev.mc.baseelectric.verification.NativeTestBootstrap; +import net.minecraft.world.item.trading.VillagerTrade; +import net.minecraft.world.level.levelgen.LegacyRandomSource; +import net.minecraft.world.level.storage.loot.LootContext; +import net.minecraft.world.level.storage.loot.LootParams; import org.junit.BeforeClass; import org.junit.Test; +import zone.moddev.mc.baseelectric.verification.NativeTestBootstrap; import static org.junit.Assert.*; public class VillagesTest { + private static final Path TRADES = Path.of("src/main/resources/data/baseelectric/villager_trade/librarian"); + private static Map GOODS; + private static HolderLookup.Provider registries; + @BeforeClass public static void bootstrapVanillaItems() { NativeTestBootstrap.initialize(); + // Native Item.CODEC resolves the built-in registry, not a private RegistryOps substitute. + GOODS = Stream.of("electric_conduit", "solder", "integrated_circuit", "blank_circuit_board", "petrolplastic_ingot", "psu") + .collect(java.util.stream.Collectors.toMap(id -> id, id -> { + var key = ResourceKey.create(Registries.ITEM, Identifier.parse("baseelectric:" + id)); + Item item = new Item(new Item.Properties().setId(key)); + net.minecraftforge.registries.ForgeRegistries.ITEMS.register(key.identifier(), item); + return item; + })); + NativeTestBootstrap.bindNativeComponents(); + registries = NativeTestBootstrap.registries(); + } + + @Test public void nativeNegativePricesKeepBulkGoodsForOneEmerald() throws Exception { + for (var bounds : List.of(new Object[] {"electric_conduit", 3, 6}, new Object[] {"solder", 5, 10}, + new Object[] {"integrated_circuit", 3, 6}, new Object[] {"blank_circuit_board", 3, 6}, + new Object[] {"petrolplastic_ingot", 4, 8})) { + assertOffer((String)bounds[0], false, 1, (int)bounds[1]); + assertOffer((String)bounds[0], true, 1, (int)bounds[2]); + } } - @Test public void nativeNegativePricesKeepBulkGoodsForOneEmerald() { - assertOffer(-8, -4, false, 1, 8); - assertOffer(-8, -4, true, 1, 4); - assertOffer(-6, -3, false, 1, 6); - assertOffer(-6, -3, true, 1, 3); - assertOffer(-10, -5, false, 1, 10); - assertOffer(-10, -5, true, 1, 5); + @Test public void nativePositivePricesKeepOneGoodForOneToThreeEmeralds() throws Exception { + assertOffer("psu", false, 1, 1); + assertOffer("psu", true, 3, 1); } - @Test public void nativePositivePricesKeepOneGoodForOneToThreeEmeralds() { - assertOffer(1, 3, false, 1, 1); - assertOffer(1, 3, true, 3, 1); + @Test public void allSixOffersExtendOnlyTheirHistoricalLibrarianTiers() throws Exception { + try (var files = Files.list(TRADES)) { + assertEquals(GOODS.keySet(), files.map(file -> file.getFileName().toString().replace(".json", "")) + .collect(java.util.stream.Collectors.toSet())); + } + for (int level : List.of(1, 2)) { + JsonObject tag = JsonParser.parseString(Files.readString(Path.of( + "src/main/resources/data/minecraft/tags/villager_trade/librarian/level_" + level + ".json"))) + .getAsJsonObject(); + assertFalse("Vanilla offers must remain available", tag.get("replace").getAsBoolean()); + Set expected = level == 1 ? Set.of("electric_conduit", "solder", "integrated_circuit", "blank_circuit_board") + : Set.of("petrolplastic_ingot", "psu"); + assertEquals(expected.stream().map(id -> "baseelectric:librarian/" + id).collect(java.util.stream.Collectors.toSet()), + tag.getAsJsonArray("values").asList().stream().map(value -> value.getAsString()) + .collect(java.util.stream.Collectors.toSet())); + assertEquals(expected.size(), tag.getAsJsonArray("values").size()); + } } - private static void assertOffer(int minimum, int maximum, boolean upper, int emeralds, int goods) { + private static void assertOffer(String id, boolean upper, int emeralds, int goods) throws Exception { + var json = JsonParser.parseString(Files.readString(TRADES.resolve(id + ".json"))); + var ops = registries.createSerializationContext(JsonOps.INSTANCE); + VillagerTrade trade = VillagerTrade.CODEC.parse(ops, json).getOrThrow(); + var roundTrip = VillagerTrade.CODEC.parse(ops, VillagerTrade.CODEC.encodeStart(ops, trade).getOrThrow()).getOrThrow(); RandomSource random = new LegacyRandomSource(0L) { @Override public int nextInt(int bound) { return upper ? bound - 1 : 0; } }; - MerchantOffer offer = Villages.sell(net.minecraft.world.item.Items.APPLE, minimum, maximum) - .getOffer(null, null, random); - CompoundTag nbt = (CompoundTag) MerchantOffer.CODEC.encodeStart( - NativeTestBootstrap.registries().createSerializationContext(NbtOps.INSTANCE), offer).getOrThrow(); - assertEquals("minecraft:emerald", nbt.getCompound("buy").orElseThrow().getString("id").orElseThrow()); - CompoundTag payment = nbt.getCompound("buy").orElseThrow(); - assertEquals(emeralds, payment.contains("count") ? payment.getInt("count").orElseThrow() : 1); - assertEquals("minecraft:apple", nbt.getCompound("sell").orElseThrow().getString("id").orElseThrow()); - CompoundTag sale = nbt.getCompound("sell").orElseThrow(); - assertEquals(goods, sale.contains("count") ? sale.getInt("count").orElseThrow() : 1); - assertEquals(7, nbt.getInt("maxUses").orElseThrow().intValue()); - assertEquals(0.0F, offer.getPriceMultiplier(), 0.0F); - // Native MerchantOffer.CODEC omits its zero price-multiplier default. - assertEquals(0.0F, nbt.getFloatOr("priceMultiplier", 0.0F), 0.0F); + // Constant/uniform providers need no world; use the native context constructor directly. + var constructor = LootContext.class.getDeclaredConstructor(LootParams.class, RandomSource.class, HolderGetter.Provider.class); + constructor.setAccessible(true); + LootContext context = constructor.newInstance(new LootParams(null, new ContextMap.Builder().create(), Map.of(), 0), + random, registries); + for (VillagerTrade definition : List.of(trade, roundTrip)) { + MerchantOffer offer = definition.getOffer(context); + assertNotNull(id, offer); + assertTrue(offer.getBaseCostA().is(Items.EMERALD)); + assertEquals(id, emeralds, offer.getBaseCostA().getCount()); + assertTrue(id, offer.getResult().is(GOODS.get(id))); + assertEquals(id, goods, offer.getResult().getCount()); + assertTrue(offer.getCostB().isEmpty()); + assertEquals(7, offer.getMaxUses()); + assertEquals(1, offer.getXp()); + assertEquals(0.0F, offer.getPriceMultiplier(), 0.0F); + } } } diff --git a/src/test/java/zone/moddev/mc/baseelectric/items/BatteryComponentTest.java b/src/test/java/zone/moddev/mc/baseelectric/items/BatteryComponentTest.java index e6dcf1bd..ecc7dc2b 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/items/BatteryComponentTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/items/BatteryComponentTest.java @@ -12,13 +12,21 @@ import static org.junit.Assert.*; public class BatteryComponentTest { - @BeforeClass public static void bootstrap() { NativeTestBootstrap.initialize(); } + private static BatteryItem testBattery; + + @BeforeClass public static void bootstrap() { + NativeTestBootstrap.initialize(); + var key = zone.moddev.mc.baseelectric.util.RegistryIds.item("lead_acid_battery"); + testBattery = new BatteryItem(100, new Item.Properties().setId(key)); + net.minecraftforge.registries.ForgeRegistries.ITEMS.register(key.identifier(), testBattery); + NativeTestBootstrap.bindNativeComponents(); + } @Test public void vanillaItemUpgradePreservesDepletedChargeAndUnrelatedData() { CompoundTag custom = LegacyBatteryDataTest.upgradeBattery() .getCompound("components").orElseThrow().getCompound("minecraft:custom_data").orElseThrow(); - BatteryItem battery = new BatteryItem(100, new Item.Properties().setId(zone.moddev.mc.baseelectric.util.RegistryIds.item("lead_acid_battery"))); + BatteryItem battery = testBattery; ItemStack stack = new ItemStack(battery); assertEquals(100, battery.getEnergy(stack), 0); stack.set(DataComponents.CUSTOM_DATA, CustomData.of(custom)); @@ -30,7 +38,7 @@ public class BatteryComponentTest { } @Test public void chargeMutationDoesNotLeakIntoAnItemCopy() { - BatteryItem battery = new BatteryItem(100, new Item.Properties().setId(zone.moddev.mc.baseelectric.util.RegistryIds.item("lead_acid_battery"))); + BatteryItem battery = testBattery; ItemStack original = new ItemStack(battery); battery.setEnergy(original, 25); ItemStack copy = original.copy(); diff --git a/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalNativeDefectsTest.java b/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalNativeDefectsTest.java index c6d114cd..b2fdce31 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalNativeDefectsTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalNativeDefectsTest.java @@ -113,7 +113,7 @@ private static final class LightWorld extends ServerLevel { BlockPos surface; int brightness, skyReads, lightReads; private LightWorld() { - super(null, null, null, null, null, null, false, 0L, Collections.emptyList(), false, null); + super(null, null, null, null, null, null, false, 0L, Collections.emptyList(), false); } @Override public boolean canSeeSky(BlockPos pos) { assertEquals("Sky access must be sampled above the solid panel", surface, pos); @@ -131,7 +131,7 @@ private static final class BeamWorld extends ServerLevel { BlockPos turret; int traces; private BeamWorld() { - super(null, null, null, null, null, null, false, 0L, Collections.emptyList(), false, null); + super(null, null, null, null, null, null, false, 0L, Collections.emptyList(), false); } @Override public BlockHitResult clip(ClipContext context) { traces++; diff --git a/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalPumpPickupTest.java b/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalPumpPickupTest.java index 0694b35a..d1a0e2dc 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalPumpPickupTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalPumpPickupTest.java @@ -184,7 +184,7 @@ private static final class PumpWorld extends ServerLevel { Map blocks; int writes; private PumpWorld() { - super(null, null, null, null, null, null, false, 0L, Collections.emptyList(), false, null); + super(null, null, null, null, null, null, false, 0L, Collections.emptyList(), false); } @Override public boolean hasChunkAt(BlockPos pos) { return true; } @Override public BlockState getBlockState(BlockPos pos) { diff --git a/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalRedstoneCacheTest.java b/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalRedstoneCacheTest.java index 35ab6e6e..61ddf1b3 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalRedstoneCacheTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalRedstoneCacheTest.java @@ -208,7 +208,7 @@ private static final class SignalWorld extends ServerLevel { long time; List queryTimes; private SignalWorld() { - super(null, null, null, null, null, null, false, 0L, Collections.emptyList(), false, null); + super(null, null, null, null, null, null, false, 0L, Collections.emptyList(), false); } @Override public boolean hasNeighborSignal(BlockPos position) { assertEquals(this.position, position); diff --git a/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalSoundBroadcastTest.java b/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalSoundBroadcastTest.java index 9c5bc623..cc857881 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalSoundBroadcastTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalSoundBroadcastTest.java @@ -112,7 +112,7 @@ private static final class QueryRejectingWorld extends ServerLevel { int playerListReads; private QueryRejectingWorld() { - super(null, null, null, null, null, null, false, 0L, Collections.emptyList(), false, null); + super(null, null, null, null, null, null, false, 0L, Collections.emptyList(), false); } @Override public List players() { diff --git a/src/test/java/zone/moddev/mc/baseelectric/machines/Native118ConstructionTest.java b/src/test/java/zone/moddev/mc/baseelectric/machines/Native118ConstructionTest.java index ccd543bb..32712ecd 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/machines/Native118ConstructionTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/machines/Native118ConstructionTest.java @@ -69,7 +69,7 @@ public class Native118ConstructionTest { private static final class PacketWorld extends ServerLevel { private PacketWorld() { - super(null, null, null, null, null, null, false, 0L, java.util.List.of(), false, null); + super(null, null, null, null, null, null, false, 0L, java.util.List.of(), false); } @Override public RegistryAccess registryAccess() { diff --git a/src/test/java/zone/moddev/mc/baseelectric/resources/BlockLootResourceTest.java b/src/test/java/zone/moddev/mc/baseelectric/resources/BlockLootResourceTest.java index 0b0c5371..58e4fb2a 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/resources/BlockLootResourceTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/resources/BlockLootResourceTest.java @@ -33,10 +33,10 @@ public class BlockLootResourceTest { assertFalse(Files.exists(root.resolve("pump_pipe_electric.json"))); } - @Test public void resourcePackDeclaresTheNativeOneTwentyOneFormat() throws Exception { + @Test public void resourcePackDeclaresTheNativeTwentySixFormat() throws Exception { JsonObject pack = read(Paths.get("src/main/resources/pack.mcmeta")).getAsJsonObject("pack"); - assertEquals(JsonParser.parseString("[94,1]"), pack.get("min_format")); - assertEquals(JsonParser.parseString("[94,1]"), pack.get("max_format")); + assertEquals(JsonParser.parseString("[101,1]"), pack.get("min_format")); + assertEquals(JsonParser.parseString("[101,1]"), pack.get("max_format")); assertFalse(pack.has("pack_format")); assertFalse(pack.has("supported_formats")); assertFalse(pack.has("forge:server_data_pack_format")); @@ -44,9 +44,9 @@ public class BlockLootResourceTest { .matcher(Files.readString(Path.of("src/main/resources/META-INF/mods.toml"))).find()); zone.moddev.mc.baseelectric.verification.NativeTestBootstrap.initialize(); var version = net.minecraft.SharedConstants.getCurrentVersion(); - assertEquals(new net.minecraft.server.packs.metadata.pack.PackFormat(75, 0), + assertEquals(new net.minecraft.server.packs.metadata.pack.PackFormat(84, 0), version.packVersion(net.minecraft.server.packs.PackType.CLIENT_RESOURCES)); - assertEquals(new net.minecraft.server.packs.metadata.pack.PackFormat(94, 1), + assertEquals(new net.minecraft.server.packs.metadata.pack.PackFormat(101, 1), version.packVersion(net.minecraft.server.packs.PackType.SERVER_DATA)); var proof = zone.moddev.mc.baseelectric.verification.NativePackResourceProof.read(); assertEquals(0, proof.getAsJsonArray("warningsAndErrors").size()); @@ -58,8 +58,8 @@ public class BlockLootResourceTest { assertTrue(type.toString(), range.isValueInRange(version.packVersion(type))); assertEquals("COMPATIBLE", side.get("currentCompatibility").getAsString()); assertTrue(side.get("resourcesVerified").getAsInt() > 0); - assertFalse(range.isValueInRange(new net.minecraft.server.packs.metadata.pack.PackFormat(74, 0))); - assertFalse(range.isValueInRange(new net.minecraft.server.packs.metadata.pack.PackFormat(95, 0))); + assertFalse(range.isValueInRange(new net.minecraft.server.packs.metadata.pack.PackFormat(83, 0))); + assertFalse(range.isValueInRange(new net.minecraft.server.packs.metadata.pack.PackFormat(102, 0))); var codec = net.minecraft.server.packs.metadata.pack.PackMetadataSection.forPackType(type).codec(); var decoded = codec.parse(com.mojang.serialization.JsonOps.INSTANCE, metadata).getOrThrow(); assertEquals(metadata, codec.encodeStart(com.mojang.serialization.JsonOps.INSTANCE, decoded).getOrThrow()); @@ -71,14 +71,17 @@ public class BlockLootResourceTest { assertEquals("mod:baseelectric", server.get("id").getAsString()); assertEquals("net.minecraft.server.packs.PathPackResources", server.get("resourcesClass").getAsString()); assertEquals("COMPATIBLE", client.get("loaderCompatibility").getAsString()); - assertEquals("TOO_NEW", server.get("loaderCompatibility").getAsString()); + assertEquals("COMPATIBLE", server.get("loaderCompatibility").getAsString()); JsonObject insufficient = pack.deepCopy(); - insufficient.add("min_format", JsonParser.parseString("[75,0]")); + insufficient.add("min_format", JsonParser.parseString("[84,0]")); insufficient.remove("pack_format"); insufficient.remove("supported_formats"); - assertTrue(net.minecraft.server.packs.metadata.pack.PackFormat.packCodec( + var broad = net.minecraft.server.packs.metadata.pack.PackFormat.packCodec( net.minecraft.server.packs.PackType.SERVER_DATA).codec() - .parse(com.mojang.serialization.JsonOps.INSTANCE, insufficient).error().isPresent()); + .parse(com.mojang.serialization.JsonOps.INSTANCE, insufficient).getOrThrow(); + assertTrue("Native 26 accepts the broad range, so reject it in the exact root contract above", + broad.isValueInRange(version.packVersion(net.minecraft.server.packs.PackType.CLIENT_RESOURCES))); + assertTrue(broad.isValueInRange(version.packVersion(net.minecraft.server.packs.PackType.SERVER_DATA))); } @Test public void eachTrackStateUsesTheNativeCutoutRenderLayer() throws Exception { diff --git a/src/test/java/zone/moddev/mc/baseelectric/resources/BuildWorkflowContractTest.java b/src/test/java/zone/moddev/mc/baseelectric/resources/BuildWorkflowContractTest.java index 19da6562..488db441 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/resources/BuildWorkflowContractTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/resources/BuildWorkflowContractTest.java @@ -7,15 +7,20 @@ import static org.junit.Assert.*; public class BuildWorkflowContractTest { - @Test public void hostedBuildsProvisionAllThreePinnedToolchainsBeforeGradle() throws Exception { + @Test public void hostedBuildsPinJava25ForEveryRoleBeforeGradle() throws Exception { for (String workflow : new String[] {"ci.yml", "codeql-analysis.yml"}) { String source = new String(Files.readAllBytes(Paths.get(".github/workflows", workflow)), StandardCharsets.UTF_8); int setup = source.indexOf("- name: Set up Gradle"); - for (String version : new String[] {"25.0.3+9.0.LTS", "8.0.502+7", "21.0.7+6.0.LTS"}) { + for (String version : new String[] {"25.0.3+9.0.LTS"}) { int pin = source.indexOf("java-version: '" + version + "'"); assertTrue(workflow + " missing pinned JDK " + version, pin >= 0 && pin < setup); } - assertTrue(source.contains("-Porg.gradle.java.installations.paths=$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64")); + assertTrue(source.contains("-Porg.gradle.java.installations.paths=$JAVA_HOME\n")); + assertEquals("One pinned JDK must serve all roles", 1, source.split("java-version:", -1).length - 1); + assertFalse(source.contains("JAVA_HOME_8_X64")); + assertFalse(source.contains("JAVA_HOME_21_X64")); + assertFalse(source.contains("8.0.502+7")); + assertFalse(source.contains("21.0.7+6")); assertTrue(source.contains("-Porg.gradle.java.installations.auto-detect=false")); assertTrue(source.contains("-Porg.gradle.java.installations.auto-download=false")); assertFalse(source.contains("-Dorg.gradle.java.installations.")); @@ -42,8 +47,8 @@ public class BuildWorkflowContractTest { } assertTrue(source.contains("ref: ${{ steps.public-pins.outputs.orespawn_ref }}")); assertTrue(source.contains("ref: ${{ steps.public-pins.outputs.power_ref }}")); - assertTrue(source.contains("branches: [master-1.21.11")); - assertTrue(source.contains("-Pforge61MavenizerJar=$MAVENIZER")); + assertTrue(source.contains("branches: [master-26.1.2")); + assertTrue(source.contains("-Pforge64MavenizerJar=$MAVENIZER")); assertTrue(source.contains("-PpowerAdvantageDeobfJar=$POWER_PLAYER")); assertFalse(source.contains("forge47")); if (workflow.equals("ci.yml")) { diff --git a/src/test/java/zone/moddev/mc/baseelectric/resources/NativePackRangeContractTest.java b/src/test/java/zone/moddev/mc/baseelectric/resources/NativePackRangeContractTest.java index 339ae89d..414aeab7 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/resources/NativePackRangeContractTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/resources/NativePackRangeContractTest.java @@ -18,6 +18,10 @@ public class NativePackRangeContractTest { Path evidence = Files.createTempDirectory(root, "piped-dumb-"); var builder = NativePackResourceProof.childProcess(evidence); assertEquals("dumb", builder.environment().get("TERM")); + assertTrue(builder.command().contains("-XX:ActiveProcessorCount=4")); + for (String name : java.util.List.of("JAVA_TOOL_OPTIONS", "JDK_JAVA_OPTIONS", "_JAVA_OPTIONS")) { + assertFalse(builder.environment().containsKey(name)); + } assertEquals("terminal.jline", net.minecrell.terminalconsole.TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY); assertEquals(ProcessBuilder.Redirect.PIPE, builder.redirectInput()); assertEquals(ProcessBuilder.Redirect.PIPE, builder.redirectOutput()); @@ -44,6 +48,10 @@ public class NativePackRangeContractTest { property.isJsonNull() ? null : property.getAsString()); assertFalse(proof.get("terminalPresent").getAsBoolean()); assertTrue(proof.get("interactiveTerminalDisabled").getAsBoolean()); + assertTrue(proof.get("environmentJvmOptionsAbsent").getAsBoolean()); + assertEquals(4, proof.get("availableProcessors").getAsInt()); + assertTrue(proof.getAsJsonArray("jvmArguments").asList().stream() + .anyMatch(value -> value.getAsString().equals("-XX:ActiveProcessorCount=4"))); assertEquals(0, proof.getAsJsonArray("warningsAndErrors").size()); assertTrue(proof.getAsJsonObject("CLIENT_RESOURCES").get("resourcesVerified").getAsInt() > 0); assertTrue(proof.getAsJsonObject("SERVER_DATA").get("resourcesVerified").getAsInt() > 0); @@ -52,7 +60,7 @@ public class NativePackRangeContractTest { } } - @Test public void nonInteractiveModeDoesNotWhitelistAnyDiagnostic() { + @Test public void nonInteractiveModeDoesNotWhitelistAnyDiagnostic() throws Exception { Path log = Path.of("negative-control.log"); for (String diagnostic : new String[] { "[main/WARN] real resource warning", "[main/ERROR] real resource error", "[main/FATAL] real fatal", @@ -63,6 +71,24 @@ public class NativePackRangeContractTest { () -> NativePackResourceProof.requireNoDiagnostics(log, diagnostic)); } NativePackResourceProof.requireNoDiagnostics(log, "[main/INFO] valid resource receipt"); + Path root = Path.of("build/native-pack-resources").toAbsolutePath(); + Files.createDirectories(root); + for (String name : java.util.List.of("JAVA_TOOL_OPTIONS", "JDK_JAVA_OPTIONS", "_JAVA_OPTIONS")) { + Path evidence = Files.createTempDirectory(root, "inherited-options-negative-"); + Path output = evidence.resolve("rejected.log"); + var command = NativePackResourceProof.childProcess(evidence); + command.environment().put(name, "-XX:ActiveProcessorCount=3"); + Process child = command.redirectOutput(output.toFile()).start(); + try { + assertTrue(child.waitFor(30, java.util.concurrent.TimeUnit.SECONDS)); + assertNotEquals(0, child.exitValue()); + assertTrue(Files.readString(output), Files.readString(output).contains( + "Inherited JVM environment options are forbidden in native proof: " + name)); + assertFalse("Rejected environment must not emit a success receipt", Files.exists(evidence.resolve("receipt.json"))); + } finally { + if (child.isAlive()) child.destroyForcibly().waitFor(); + } + } } @Test public void baseMetalsIsRequiredByNativeModInfoWithOptionalNegativeControl() throws Exception { @@ -72,7 +98,7 @@ public class NativePackRangeContractTest { assertEquals(java.util.Set.of("forge", "minecraft", "basepower", "basemetals"), dependencies.keySet()); var metals = dependencies.getAsJsonObject("basemetals"); assertTrue("Forge must require Base Metals on both sides", metals.get("mandatory").getAsBoolean()); - assertEquals("[3.1.2.121111,4.0.0)", metals.get("versionRange").getAsString()); + assertEquals("[3.1.2.2601021,4.0.0)", metals.get("versionRange").getAsString()); assertEquals("AFTER", metals.get("ordering").getAsString()); assertEquals("BOTH", metals.get("side").getAsString()); var control = proof.getAsJsonObject("optionalBaseMetalsControl"); @@ -91,11 +117,11 @@ public class NativePackRangeContractTest { JsonObject pack = side.getAsJsonObject("nativeMetadata"); var range = PackFormat.packCodec(type).codec().parse(JsonOps.INSTANCE, pack).getOrThrow(); assertEquals(type.toString(), type == PackType.CLIENT_RESOURCES, - range.isValueInRange(PackFormat.of(75, 0))); + range.isValueInRange(PackFormat.of(84, 0))); assertEquals(type.toString(), type == PackType.SERVER_DATA, - range.isValueInRange(PackFormat.of(94, 1))); - assertFalse(range.isValueInRange(PackFormat.of(74))); - assertFalse(range.isValueInRange(PackFormat.of(95))); + range.isValueInRange(PackFormat.of(101, 1))); + assertFalse(range.isValueInRange(PackFormat.of(83))); + assertFalse(range.isValueInRange(PackFormat.of(102))); } } @@ -117,11 +143,11 @@ public class NativePackRangeContractTest { } @Test public void dataOnlyRangeDoesNotQualifyClientResources() { - JsonObject dataOnly = JsonParser.parseString("{\"min_format\":[94,1],\"max_format\":[94,1]}") + JsonObject dataOnly = JsonParser.parseString("{\"min_format\":[101,1],\"max_format\":[101,1]}") .getAsJsonObject(); var range = PackFormat.packCodec(PackType.CLIENT_RESOURCES).codec() .parse(JsonOps.INSTANCE, dataOnly).getOrThrow(); - assertFalse(range.isValueInRange(PackFormat.of(75, 0))); - assertTrue(range.isValueInRange(PackFormat.of(94, 1))); + assertFalse(range.isValueInRange(PackFormat.of(84, 0))); + assertTrue(range.isValueInRange(PackFormat.of(101, 1))); } } diff --git a/src/test/java/zone/moddev/mc/baseelectric/resources/RecipeResourceTest.java b/src/test/java/zone/moddev/mc/baseelectric/resources/RecipeResourceTest.java index 985b1a9b..99e5965f 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/resources/RecipeResourceTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/resources/RecipeResourceTest.java @@ -32,7 +32,7 @@ public class RecipeResourceTest { String text = new String(Files.readAllBytes(file), StandardCharsets.UTF_8); JsonObject json = new JsonParser().parse(text).getAsJsonObject(); String type = json.get("type").getAsString(); - assertTrue(file + " must use the 1.21.11 result stack codec", json.getAsJsonObject("result").has("id")); + assertTrue(file + " must use the 26.1.2 result template codec", json.getAsJsonObject("result").has("id")); assertFalse(file + " still has pre-1.21.11 Forge conditions", json.has("conditions")); assertTrue(file + " has unsupported serializer " + type, "minecraft:crafting_shaped".equals(type) @@ -96,7 +96,7 @@ private static void assertNativeCodecShapes() throws ReflectiveOperationExceptio } for (String obsolete : List.of("{\"item\":\"minecraft:apple\"}", "{\"tag\":\"baseelectric:codec_test_choices\"}")) { - assertTrue("Forge 61 must reject old ingredient objects: " + obsolete, + assertTrue("Forge 64 must reject old ingredient objects: " + obsolete, net.minecraft.world.item.crafting.Ingredient.CODEC.parse(ops, JsonParser.parseString(obsolete)).error().isPresent()); } diff --git a/src/test/java/zone/moddev/mc/baseelectric/resources/ShaderResourceContractTest.java b/src/test/java/zone/moddev/mc/baseelectric/resources/ShaderResourceContractTest.java index bf619229..436fc832 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/resources/ShaderResourceContractTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/resources/ShaderResourceContractTest.java @@ -29,7 +29,7 @@ public class ShaderResourceContractTest { assertFalse(vertex.contains("IViewRotMat")); for (String renderer : Arrays.asList("HydroturbineRenderer", "LaserDrillRenderer", "LaserTurretRenderer")) { String source = Files.readString(PIPELINES.resolveSibling(renderer + ".java")); - assertFalse("Forge 61 warns about mod-owned class annotations: " + renderer, source.contains("@OnlyIn")); + assertFalse("Forge 64 warns about mod-owned class annotations: " + renderer, source.contains("@OnlyIn")); } String registration = Files.readString(PIPELINES.resolveSibling("ClientRegistration.java")); assertTrue(registration.contains("value = Dist.CLIENT")); @@ -38,7 +38,8 @@ public class ShaderResourceContractTest { @Test public void noDiffuseLightingDoesNotRemoveLightmapOrOverlaySampling() throws Exception { String vertex = vanilla("entity.vsh"); assertTrue(vertex.contains("vertexColor = Color;")); - assertTrue(vertex.contains("lightMapColor = texelFetch(Sampler2, UV2 / 16, 0);")); + assertTrue(vertex.contains("#moj_import ")); + assertTrue(vertex.contains("lightMapColor = sample_lightmap(Sampler2, UV2);")); assertTrue(vertex.contains("overlayColor = texelFetch(Sampler1, UV1, 0);")); assertTrue(vertex.contains("#elif defined(NO_CARDINAL_LIGHTING)\n vertexColor = Color;")); assertTrue(vertex.contains("#moj_import ")); diff --git a/src/test/java/zone/moddev/mc/baseelectric/util/crafting/RecipeDeconstructorTest.java b/src/test/java/zone/moddev/mc/baseelectric/util/crafting/RecipeDeconstructorTest.java index b08b0498..391bfb2b 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/util/crafting/RecipeDeconstructorTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/util/crafting/RecipeDeconstructorTest.java @@ -3,6 +3,9 @@ import java.util.List; import java.util.Map; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.ItemStackTemplate; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.CraftingRecipe; import net.minecraft.world.item.Items; import net.minecraft.world.item.crafting.CraftingBookCategory; import net.minecraft.world.item.crafting.Ingredient; @@ -18,9 +21,10 @@ public class RecipeDeconstructorTest { @BeforeClass public static void bootstrap() { NativeTestBootstrap.initialize(); } @Test public void shapedPlansKeepRepeatedIngredientsAndResultCountWithoutEmptySlots() { - var recipe = new ShapedRecipe("native-plan", CraftingBookCategory.MISC, + var recipe = new ShapedRecipe(new Recipe.CommonInfo(true), + new CraftingRecipe.CraftingBookInfo(CraftingBookCategory.MISC, "native-plan"), ShapedRecipePattern.of(Map.of('A', Ingredient.of(Items.APPLE)), " A ", "AA "), - new ItemStack(Items.GOLDEN_APPLE, 3)); + new ItemStackTemplate(Items.GOLDEN_APPLE, 3)); var plans = RecipeDeconstructor.describe(recipe, NativeTestBootstrap.registries()); assertEquals(1, plans.size()); var plan = plans.getFirst(); @@ -34,8 +38,9 @@ public class RecipeDeconstructorTest { } @Test public void shapelessPlansKeepAlternativesAndDoNotInventFreeIngredients() { - var recipe = new ShapelessRecipe("native-plan", CraftingBookCategory.MISC, - new ItemStack(Items.BREAD, 2), List.of(Ingredient.of(Items.APPLE, Items.CARROT), Ingredient.of(Items.WHEAT))); + var recipe = new ShapelessRecipe(new Recipe.CommonInfo(true), + new CraftingRecipe.CraftingBookInfo(CraftingBookCategory.MISC, "native-plan"), + new ItemStackTemplate(Items.BREAD, 2), List.of(Ingredient.of(Items.APPLE, Items.CARROT), Ingredient.of(Items.WHEAT))); var plan = RecipeDeconstructor.describe(recipe, NativeTestBootstrap.registries()).getFirst(); assertEquals(2, plan.ingredients().size()); assertEquals(2, plan.result().getCount()); diff --git a/src/test/java/zone/moddev/mc/baseelectric/verification/NativePackResourceProof.java b/src/test/java/zone/moddev/mc/baseelectric/verification/NativePackResourceProof.java index 2b20da4a..928bddfb 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/verification/NativePackResourceProof.java +++ b/src/test/java/zone/moddev/mc/baseelectric/verification/NativePackResourceProof.java @@ -47,7 +47,7 @@ public static synchronized JsonObject read() throws Exception { if (receipt != null) return receipt.deepCopy(); Path evidenceRoot = Path.of("build/native-pack-resources").toAbsolutePath(); Files.createDirectories(evidenceRoot); - Path evidence = Files.createTempDirectory(evidenceRoot, "forge61-"); + Path evidence = Files.createTempDirectory(evidenceRoot, "forge64-"); Path log = evidence.resolve("forge-resources.log"); Process process = childProcess(evidence).redirectOutput(log.toFile()).start(); if (!process.waitFor(90, TimeUnit.SECONDS)) { @@ -75,6 +75,9 @@ public static ProcessBuilder childProcess(Path evidence) { evidence.toString()).directory(evidence.toFile()) .redirectErrorStream(true); process.environment().put("TERM", "dumb"); + for (String name : List.of("JAVA_TOOL_OPTIONS", "JDK_JAVA_OPTIONS", "_JAVA_OPTIONS")) { + process.environment().remove(name); + } return process; } @@ -86,6 +89,16 @@ public static void requireNoDiagnostics(Path log, String output) { } public static void main(String[] args) throws Exception { + for (String name : List.of("JAVA_TOOL_OPTIONS", "JDK_JAVA_OPTIONS", "_JAVA_OPTIONS")) { + if (System.getenv(name) != null) { + throw new IllegalStateException("Inherited JVM environment options are forbidden in native proof: " + name); + } + } + if (Runtime.getRuntime().availableProcessors() != 4 + || !java.lang.management.ManagementFactory.getRuntimeMXBean().getInputArguments() + .contains("-XX:ActiveProcessorCount=4")) { + throw new IllegalStateException("Native proof requires the explicit four-CPU JVM argument"); + } SharedConstants.tryDetectVersion(); Bootstrap.bootStrap(); Path resources = Path.of(args[0]); @@ -115,7 +128,7 @@ public static void main(String[] args) throws Exception { output.closeEntry(); } ModFile optionalMod = new ModFile(SecureJar.from(optionalJar), new ForgeDevLocator(), ModFileParser::modsTomlParser); - ModList.of(List.of(mod), mod.getModInfos().stream().map(ModInfo.class::cast).toList()); + initializeNativeModList(mod); List diagnostics = new ArrayList<>(); AbstractAppender appender = new AbstractAppender("ElectricPackContract", null, PatternLayout.createDefaultLayout(), false, Property.EMPTY_ARRAY) { @@ -127,6 +140,13 @@ public static void main(String[] args) throws Exception { var logger = (org.apache.logging.log4j.core.Logger)LogManager.getRootLogger(); logger.addAppender(appender); JsonObject result = new JsonObject(); + result.addProperty("environmentJvmOptionsAbsent", true); + result.addProperty("availableProcessors", Runtime.getRuntime().availableProcessors()); + result.addProperty("javaRuntimeVersion", System.getProperty("java.runtime.version")); + JsonArray arguments = new JsonArray(); + java.lang.management.ManagementFactory.getRuntimeMXBean().getInputArguments().forEach(arguments::add); + result.add("jvmArguments", arguments); + result.addProperty("classpath", System.getProperty("java.class.path")); result.addProperty("terminalJline", System.getProperty(net.minecrell.terminalconsole.TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY)); result.addProperty("terminalPresent", net.minecrell.terminalconsole.TerminalConsoleAppender.getTerminal() != null); result.addProperty("interactiveTerminalDisabled", "false".equals(System.getProperty( @@ -164,8 +184,8 @@ public static void main(String[] args) throws Exception { side.addProperty("resourcesClass", opened.getClass().getName()); side.addProperty("acceptsClient", formats.isValueInRange(SharedConstants.getCurrentVersion().packVersion(PackType.CLIENT_RESOURCES))); side.addProperty("acceptsServer", formats.isValueInRange(SharedConstants.getCurrentVersion().packVersion(PackType.SERVER_DATA))); - side.addProperty("accepts74", formats.isValueInRange(net.minecraft.server.packs.metadata.pack.PackFormat.of(74))); - side.addProperty("accepts95", formats.isValueInRange(net.minecraft.server.packs.metadata.pack.PackFormat.of(95))); + side.addProperty("accepts83", formats.isValueInRange(net.minecraft.server.packs.metadata.pack.PackFormat.of(83))); + side.addProperty("accepts102", formats.isValueInRange(net.minecraft.server.packs.metadata.pack.PackFormat.of(102))); side.add("nativeMetadata", encoded); if (type == PackType.CLIENT_RESOURCES) { if (opened.getChildren() == null || opened.getChildren().size() != 1) { @@ -213,6 +233,19 @@ public static void main(String[] args) throws Exception { System.out.println("BASEELECTRIC_NATIVE_PACK_RESOURCES_PASS " + result); } + private static void initializeNativeModList(ModFile mod) throws ReflectiveOperationException { + // Forge 64 freezes discovery before ModList's static initialization. No mod code runs in this child. + Class stateType = Class.forName("net.minecraftforge.fml.loading.ModSorter$State"); + var constructor = stateType.getDeclaredConstructor(List.class, List.class); + constructor.setAccessible(true); + Object state = constructor.newInstance(List.of(mod), mod.getModInfos().stream().map(ModInfo.class::cast).toList()); + Class loader = Class.forName("net.minecraftforge.fml.loading.LoadingModListImpl"); + var initialize = loader.getDeclaredMethod("init", stateType, net.minecraftforge.fml.loading.EarlyLoadingException.class); + initialize.setAccessible(true); + initialize.invoke(null, state, null); + ModList.init(); + } + private static JsonObject dependencies(ModFile mod) { ModInfo info = ModInfo.class.cast(mod.getModInfos().stream() .filter(entry -> entry.getModId().equals("baseelectric")).findFirst().orElseThrow()); diff --git a/src/test/java/zone/moddev/mc/baseelectric/verification/NativeTestBootstrap.java b/src/test/java/zone/moddev/mc/baseelectric/verification/NativeTestBootstrap.java index c3d41071..601b120a 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/verification/NativeTestBootstrap.java +++ b/src/test/java/zone/moddev/mc/baseelectric/verification/NativeTestBootstrap.java @@ -28,6 +28,7 @@ public static synchronized void initialize() { if (ready) return; SharedConstants.tryDetectVersion(); Bootstrap.bootStrap(); + bindNativeComponents(); // The plain test JVM has no FML registration phase after vanilla bootstrap. GameData.unfreezeData(); ready = true; @@ -37,16 +38,24 @@ public static RegistryAccess.Frozen registries() { return RegistryAccess.fromRegistryOfRegistries(BuiltInRegistries.REGISTRY); } + public static void bindNativeComponents() { + var lookup = net.minecraft.data.registries.VanillaRegistries.createLookup(); + BuiltInRegistries.DATA_COMPONENT_INITIALIZERS.build(lookup).forEach( + net.minecraft.core.component.DataComponentInitializers.PendingComponents::apply); + } + public static net.minecraft.world.level.storage.ValueInput input(net.minecraft.nbt.CompoundTag data) { return net.minecraft.world.level.storage.TagValueInput.create(net.minecraft.util.ProblemReporter.DISCARDING, registries(), data); } public static synchronized void registerVanillaFluidTypes() throws ReflectiveOperationException { + initialize(); registerNativeForgeRegistry(ForgeRegistries.FLUID_TYPES, "VANILLA_FLUID_TYPES"); } public static synchronized void registerVanillaIngredientSerializers() throws ReflectiveOperationException { + initialize(); registerNativeForgeRegistry(ForgeRegistries.INGREDIENT_SERIALIZERS, "INGREDIENT_SERIALIZERS"); } diff --git a/tools/verification/AuditedJUnit.java b/tools/verification/AuditedJUnit.java index fa80d4e1..42511edd 100644 --- a/tools/verification/AuditedJUnit.java +++ b/tools/verification/AuditedJUnit.java @@ -30,7 +30,7 @@ public static void main(String[] args) throws Exception { XMLStreamWriter xml = XMLOutputFactory.newFactory().createXMLStreamWriter(output, "UTF-8"); xml.writeStartDocument("UTF-8", "1.0"); xml.writeStartElement("testsuite"); - xml.writeAttribute("name", "baseElectric-1.21.11-ECJ"); + xml.writeAttribute("name", "baseElectric-26.1.2-ECJ"); xml.writeAttribute("tests", Integer.toString(result.getRunCount())); xml.writeAttribute("failures", Integer.toString(result.getFailureCount())); xml.writeAttribute("ignored", Integer.toString(result.getIgnoreCount())); diff --git a/tools/verification/EclipseTestInventory.java b/tools/verification/EclipseTestInventory.java new file mode 100644 index 00000000..9f29a137 --- /dev/null +++ b/tools/verification/EclipseTestInventory.java @@ -0,0 +1,64 @@ +package zone.moddev.mc.baseelectric.verification; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; + +/** Compares fresh compiled JUnit methods with exact Gradle XML inventory, without loading test classes. */ +public final class EclipseTestInventory { + public static void main(String[] args) throws Exception { + if (args.length != 3) throw new IllegalArgumentException("classes, inventory file, expected test count"); + List lines = Files.readAllLines(Path.of(args[1]), StandardCharsets.UTF_8); + Set declared = new TreeSet<>(lines); + if (declared.isEmpty() || declared.contains("") || declared.size() != lines.size()) { + throw new AssertionError("Empty or duplicate Gradle test inventory"); + } + Set compiled = new TreeSet<>(); + Set suites = new TreeSet<>(); + try (var paths = Files.walk(Path.of(args[0]))) { + for (Path file : paths.filter(path -> path.toString().endsWith(".class")).sorted().toList()) { + ClassNode type = new ClassNode(); + new ClassReader(Files.readAllBytes(file)).accept(type, + ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + if (has(type.visibleAnnotations, "Lorg/junit/runner/RunWith;") + || "junit/framework/TestCase".equals(type.superName)) { + throw new AssertionError("Review custom JUnit discovery before qualification: " + type.name); + } + for (var method : type.methods) { + if (!has(method.visibleAnnotations, "Lorg/junit/Test;")) continue; + if (type.name.contains("$") || !"java/lang/Object".equals(type.superName) + || (type.access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_INTERFACE)) != 0 + || (method.access & Opcodes.ACC_PUBLIC) == 0 + || (method.access & Opcodes.ACC_STATIC) != 0 || !"()V".equals(method.desc) + || has(type.visibleAnnotations, "Lorg/junit/Ignore;") + || has(method.visibleAnnotations, "Lorg/junit/Ignore;")) { + throw new AssertionError("Review excluded, nested, inherited or nonstandard JUnit test: " + type.name + "#" + method.name); + } + String suite = type.name.replace('/', '.'); + suites.add(suite); + if (!compiled.add(suite + "#" + method.name)) throw new AssertionError("Duplicate compiled test"); + } + } + } + if (!compiled.equals(declared) || compiled.size() != Integer.parseInt(args[2])) { + Set omitted = new TreeSet<>(compiled); + omitted.removeAll(declared); + Set stale = new TreeSet<>(declared); + stale.removeAll(compiled); + throw new AssertionError("Incomplete or stale Gradle method inventory: omitted=" + omitted + + ", stale=" + stale + ", compiled tests=" + compiled.size() + ", declared tests=" + args[2]); + } + System.out.println("PASS: exact compiled JUnit inventory; " + suites.size() + " suites, " + compiled.size() + " methods"); + } + + private static boolean has(List annotations, String descriptor) { + return annotations != null && annotations.stream().anyMatch(annotation -> descriptor.equals(annotation.desc)); + } +} diff --git a/tools/verification/Invoke-ElectricPackagedSmoke.ps1 b/tools/verification/Invoke-ElectricPackagedSmoke.ps1 index 11ec3cf7..f15f3d7e 100644 --- a/tools/verification/Invoke-ElectricPackagedSmoke.ps1 +++ b/tools/verification/Invoke-ElectricPackagedSmoke.ps1 @@ -30,7 +30,7 @@ $target = "$($pins.minecraft_version)-$($pins.forge_version)" $forgeDir = "libraries/net/minecraftforge/forge/$target" $winArgs = "$forgeDir/win_args.txt" $receipt = [ordered]@{ - status = 'FAIL'; scope = 'Packaged Forge 1.21.11 dedicated server and legacy inventory probe; no client or whole-world upgrade proof' + status = 'FAIL'; scope = 'Packaged Forge 26.1.2 dedicated server and legacy inventory probe; no client or whole-world upgrade proof' baseMetalsIncluded = !$WithoutBaseMetals seed = $seed; java = $javaPath; inputs = @(); checks = [ordered]@{}; exitCode = $null; error = $null } @@ -63,31 +63,31 @@ function Send-Command([string]$Command) { } try { - if ($target -cne '1.21.11-61.1.0' -or $pins.mod_version -cne '2.2.16.121111') { - throw 'This smoke runner is bound to baseElectric 2.2.16.121111 on Forge 61.1.0.' + if ($target -cne '26.1.2-64.1.5' -or $pins.mod_version -cne '2.2.17.2601021') { + throw 'This smoke runner is bound to baseElectric 2.2.17.2601021 on Forge 64.1.5.' } if ($pins.poweradvantage_pin_state -notin @('LOCAL_SOURCE', 'MERGED_SOURCE') -or $PowerSha256 -ne $pins.poweradvantage_runtime_sha256) { throw 'Packaged smoke requires the exact source-pinned basePower player JAR.' } $receipt.powerSourceRef = $pins.poweradvantage_source_ref $javaRelease = Join-Path (Split-Path (Split-Path $javaPath)) 'release' - if ((Get-Content -LiteralPath $javaRelease -Raw) -notmatch 'JAVA_VERSION="21\.') { - throw 'The dedicated-server smoke requires Java 21.' + if ((Get-Content -LiteralPath $javaRelease -Raw) -notmatch 'JAVA_VERSION="25\.') { + throw 'The dedicated-server smoke requires Java 25.' } $receipt.javaRelease = (Get-FileHash -LiteralPath $javaRelease).Hash $receipt.javaSha256 = (Get-FileHash -LiteralPath $javaPath).Hash $argsText = Get-Content -LiteralPath (Join-Path $seed $winArgs) -Raw $shim = "forge-$target-shim.jar" - if (!$argsText.Contains("-jar $shim")) { throw 'Unexpected Forge 61 server argument file.' } + if (!$argsText.Contains("-jar $shim")) { throw 'Unexpected Forge 64 server argument file.' } $receipt.shimSha256 = (Get-FileHash -LiteralPath (Join-Path $seed $shim)).Hash - if ($receipt.shimSha256 -cne $pins.forge_shim_sha256) { throw 'Forge shim differs from the recorded 61.1.0 runtime pin.' } + if ($receipt.shimSha256 -cne $pins.forge_shim_sha256) { throw 'Forge shim differs from the recorded 64.1.5 runtime pin.' } $receipt.seedArgumentsSha256 = (Get-FileHash -LiteralPath (Join-Path $seed $winArgs)).Hash $forgeUniversal = Join-Path $seed "$forgeDir/forge-$target-universal.jar" $receipt.forgeUniversalSha256 = (Get-FileHash -LiteralPath $forgeUniversal).Hash - if ($receipt.forgeUniversalSha256 -cne $pins.forge_universal_sha256) { throw 'Forge universal differs from the recorded 61.1.0 runtime pin.' } + if ($receipt.forgeUniversalSha256 -cne $pins.forge_universal_sha256) { throw 'Forge universal differs from the recorded 64.1.5 runtime pin.' } $forgeServer = Join-Path $seed "$forgeDir/forge-$target-server.jar" $receipt.forgeServerSha256 = (Get-FileHash -LiteralPath $forgeServer).Hash - if ($receipt.forgeServerSha256 -cne $pins.forge_server_sha256) { throw 'Forge server differs from the recorded 61.1.0 runtime pin.' } + if ($receipt.forgeServerSha256 -cne $pins.forge_server_sha256) { throw 'Forge server differs from the recorded 64.1.5 runtime pin.' } if ((Get-Content -LiteralPath $EulaFile -Raw) -notmatch '(?m)^eula=true\s*$') { throw 'Pass the existing accepted EULA from the local smoke fixture.' } @@ -127,9 +127,12 @@ try { $start.RedirectStandardInput = $true $start.RedirectStandardOutput = $true $start.RedirectStandardError = $true - foreach ($arg in @('-Xmx3G', '-Dfml.earlyprogresswindow=false', "@$winArgs", 'nogui')) { + foreach ($arg in @('-Xmx3G', '-XX:ActiveProcessorCount=4', '-Dterminal.jline=false', '-Dfml.earlyprogresswindow=false', "@$winArgs", 'nogui')) { $start.ArgumentList.Add($arg) } + foreach ($name in @('JAVA_TOOL_OPTIONS', 'JDK_JAVA_OPTIONS', '_JAVA_OPTIONS')) { $null = $start.Environment.Remove($name) } + $receipt.jvmArguments = @($start.ArgumentList) + $receipt.environmentJvmOptionsAbsent = @('JAVA_TOOL_OPTIONS', 'JDK_JAVA_OPTIONS', '_JAVA_OPTIONS').Where({ $start.Environment.ContainsKey($_) }).Count -eq 0 $stdout = [IO.StreamWriter]::new((Join-Path $output 'console.log')) $stdout.AutoFlush = $true $process = [Diagnostics.Process]::Start($start) diff --git a/tools/verification/Verify-ElectricEcj.ps1 b/tools/verification/Verify-ElectricEcj.ps1 index ac7be307..dd4b1af5 100644 --- a/tools/verification/Verify-ElectricEcj.ps1 +++ b/tools/verification/Verify-ElectricEcj.ps1 @@ -2,7 +2,7 @@ param( [string]$ProjectRoot = (Join-Path $PSScriptRoot '../..'), [Parameter(Mandatory = $true)][string]$Evidence, [string]$Compiler = 'C:/Users/johnb/.p2/pool/plugins/org.eclipse.jdt.core.compiler.batch_3.46.0.v20260528-0407.jar', - [string]$Java21Home = 'C:/Users/johnb/.jdks/temurin-21.0.7+6' + [string]$Java25Home = 'C:/Users/johnb/.jdks/jdk-25.0.3+9' ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest @@ -13,6 +13,11 @@ if (!$Evidence.StartsWith($ProjectRoot + [IO.Path]::DirectorySeparatorChar, [Str } if (Test-Path -LiteralPath $Evidence) { throw 'Refusing to overwrite ECJ evidence' } $null = New-Item -ItemType Directory -Path $Evidence +$inheritedJvmOptions = @{} +foreach ($name in @('JAVA_TOOL_OPTIONS', 'JDK_JAVA_OPTIONS', '_JAVA_OPTIONS')) { + $inheritedJvmOptions[$name] = [Environment]::GetEnvironmentVariable($name, 'Process') + [Environment]::SetEnvironmentVariable($name, $null, 'Process') +} function Get-Pin([string]$Path) { $file = Get-Item -LiteralPath $Path @@ -30,7 +35,7 @@ function Test-ClassFiles([string]$Directory) { if ($bytes.Length -lt 8 -or $bytes[0] -ne 0xCA -or $bytes[1] -ne 0xFE -or $bytes[2] -ne 0xBA -or $bytes[3] -ne 0xBE) { throw "Invalid class file: $($file.FullName)" } - if (($bytes[6] * 256 + $bytes[7]) -ne 65) { throw "Non-Java-21 ECJ output: $($file.FullName)" } + if (($bytes[6] * 256 + $bytes[7]) -ne 69) { throw "Non-Java-25 ECJ output: $($file.FullName)" } } return $files.Count } @@ -38,17 +43,18 @@ function Invoke-Ecj([string]$Name, [string[]]$Sources, [string[]]$Libraries) { $output = Join-Path $Evidence $Name $null = New-Item -ItemType Directory -Path $output $xmlLog = Join-Path $Evidence "$Name.xml" - # Use the real Java 21 module image and keep forbidden/discouraged accesses fatal. - $arguments = @('-21', '-proc:none', '-encoding', 'UTF-8', '-err:+forbidden,discouraged', '--system', - ('"' + $Java21Home.Replace('\', '/') + '"'), '-classpath', + # Use the real Java 25 module image and keep forbidden/discouraged accesses fatal. + $arguments = @('-25', '-proc:none', '-encoding', 'UTF-8', '-err:+forbidden,discouraged', '--system', + ('"' + $Java25Home.Replace('\', '/') + '"'), '-classpath', ('"' + (($Libraries -replace '\\', '/') -join ';') + '"'), '-d', ('"' + $output.Replace('\', '/') + '"'), '-log', ('"' + $xmlLog.Replace('\', '/') + '"')) $arguments += @($Sources | ForEach-Object { '"' + $_.Replace('\', '/') + '"' }) $argFile = Join-Path $Evidence "$Name.args" [IO.File]::WriteAllLines($argFile, $arguments, [Text.UTF8Encoding]::new($false)) - & $java21 -Xmx2G -jar $Compiler "@$argFile" *> (Join-Path $Evidence "$Name-compile.log") + & $java25 -XX:ActiveProcessorCount=4 -Xmx2G -jar $Compiler "@$argFile" *> (Join-Path $Evidence "$Name-compile.log") $exitCode = $LASTEXITCODE $entry = [ordered]@{ exitCode = $exitCode; sourceCount = $Sources.Count; output = $output; log = "$Name-compile.log" } + $entry.jvmArguments = @('-XX:ActiveProcessorCount=4', '-Xmx2G', '-jar', $Compiler, "@$argFile") $report.compilations[$Name] = $entry if ($exitCode -ne 0) { throw "$Name ECJ compilation failed; retain its log and XML diagnostics" } [xml]$diagnostics = Get-Content -LiteralPath $xmlLog -Raw @@ -61,16 +67,16 @@ function Invoke-Ecj([string]$Name, [string[]]$Sources, [string[]]$Libraries) { } $report = [ordered]@{ status = 'RUNNING'; project = $ProjectRoot; evidence = $Evidence - scope = 'Pinned local baseElectric 1.21.11 candidate; fresh ECJ main/test/helper outputs and worldless Java 21 JUnit; no Gradle or Minecraft launch' + scope = 'Pinned local baseElectric 26.1.2 candidate; fresh ECJ main/test/helper outputs and worldless Java 25 JUnit; no Gradle or Minecraft launch' compiler = $null; toolchains = $null; libraries = @(); sources = @(); gradleInventory = @() compilations = [ordered]@{}; junit = $null; negativeControl = $null; apiPinState = $null } try { $properties = ConvertFrom-StringData -StringData (Get-Content -LiteralPath (Join-Path $ProjectRoot 'gradle.properties') -Raw) $report.apiPinState = $properties.poweradvantage_pin_state - if ($properties.mod_id -ne 'baseelectric' -or $properties.mod_version -ne '2.2.16.121111' -or - $properties.minecraft_version -ne '1.21.11' -or $properties.forge_version -ne '61.1.0' -or - $properties.poweradvantage_version -ne '2.4.15.121111' -or $properties.basemetals_version -ne '3.1.2.121111' -or - $properties.orespawn_version -ne '4.1.0.121111' -or + if ($properties.mod_id -ne 'baseelectric' -or $properties.mod_version -ne '2.2.17.2601021' -or + $properties.minecraft_version -ne '26.1.2' -or $properties.forge_version -ne '64.1.5' -or + $properties.poweradvantage_version -ne '2.4.16.2601021' -or $properties.basemetals_version -ne '3.1.2.2601021' -or + $properties.orespawn_version -ne '4.1.0.2601021' -or $properties.poweradvantage_pin_state -notin @('LOCAL_SOURCE', 'MERGED_SOURCE') -or $properties.poweradvantage_source_ref -cnotmatch '^[a-f0-9]{40}$' -or $properties.poweradvantage_runtime_sha256 -cnotmatch '^[A-F0-9]{64}$' -or @@ -78,20 +84,23 @@ try { $properties.poweradvantage_deobf_sha256 -cnotmatch '^[A-F0-9]{64}$' -or $properties.basemetals_deobf_sha256 -cnotmatch '^[A-F0-9]{64}$' -or $properties.orespawn_deobf_sha256 -cnotmatch '^[A-F0-9]{64}$') { - throw 'This verifier requires the source-pinned baseElectric 1.21.11 inputs' + throw 'This verifier requires the source-pinned baseElectric 26.1.2 inputs' } $report.compiler = Get-Pin $Compiler if ($report.compiler.sha256 -ne 'D0D43F8E2D7003E5EFED612E2CBB5F01870043397D8F1BBE536FD9128F4FCBF7') { throw 'ECJ compiler pin mismatch' } - $java21 = Join-Path $Java21Home 'bin/java.exe' - $report.toolchains = [ordered]@{ java = (Get-Pin $java21) - java21Release = (Get-Pin (Join-Path $Java21Home 'release')) - systemModules = (Get-Pin (Join-Path $Java21Home 'lib/modules')) } - if ((Get-Content -LiteralPath (Join-Path $Java21Home 'release') -Raw) -notmatch 'JAVA_VERSION="21\.0\.7"') { - throw 'ECJ production system image must be Java 21.0.7' + $java25 = Join-Path $Java25Home 'bin/java.exe' + $report.toolchains = [ordered]@{ java = (Get-Pin $java25) + java25Release = (Get-Pin (Join-Path $Java25Home 'release')) + systemModules = (Get-Pin (Join-Path $Java25Home 'lib/modules')) } + if ($report.toolchains.java.sha256 -cne 'D5F1B9E03E298EBFB04D2326ECC1E2AF3E6817F9B4D7F9F151FE856D56975650') { + throw 'ECJ must use the pinned Temurin 25.0.3+9 executable' + } + if ((Get-Content -LiteralPath (Join-Path $Java25Home 'release') -Raw) -notmatch 'JAVA_VERSION="25\.0\.3"') { + throw 'ECJ production system image must be Java 25.0.3' } $negativeSource = Join-Path $PSScriptRoot 'negative/RestrictedJdkReference.java' $negativeLog = Join-Path $Evidence 'restricted-jdk-negative.log' - & $java21 -jar $Compiler -21 -proc:none '-err:+forbidden,discouraged' --system $Java21Home ` + & $java25 -XX:ActiveProcessorCount=4 -jar $Compiler -25 -proc:none '-err:+forbidden,discouraged' --system $Java25Home ` -d (Join-Path $Evidence 'negative') $negativeSource *> $negativeLog $negativeExit = $LASTEXITCODE $report.negativeControl = [ordered]@{ source = (Get-Pin $negativeSource); exitCode = $negativeExit @@ -129,17 +138,33 @@ try { $report.sources += @($sources[$set] | ForEach-Object { Get-Pin $_ }) } $classes = @() + $methodInventory = @() $expected = 0 foreach ($xml in @(Get-ChildItem -LiteralPath (Join-Path $ProjectRoot 'build/test-results/test') -File -Filter 'TEST-*.xml' | Sort-Object Name)) { [xml]$inventory = Get-Content -LiteralPath $xml.FullName -Raw if ([int]$inventory.testsuite.failures -or [int]$inventory.testsuite.errors -or [int]$inventory.testsuite.skipped) { throw 'Gradle suite inventory contains failed/skipped tests' } $classes += [string]$inventory.testsuite.name $expected += [int]$inventory.testsuite.tests + foreach ($case in @($inventory.testsuite.testcase)) { + if ([string]$case.classname -cne [string]$inventory.testsuite.name) { throw 'Unexpected JUnit testcase owner' } + $methodInventory += ([string]$case.classname + '#' + [string]$case.name) + } $report.gradleInventory += Get-Pin $xml.FullName } - if ($classes.Count -ne 33 -or $expected -ne 114 -or $sources.test.Count -ne 36 -or $sources.main.Count -ne 88) { throw 'Unexpected Electric 1.21.11 source/test inventory' } + if ($classes.Count -ne 33 -or $expected -ne 115 -or $sources.test.Count -ne 36 -or $sources.main.Count -ne 87) { throw 'Unexpected Electric 26.1.2 source/test inventory' } + $support = @(Get-ChildItem -LiteralPath (Join-Path $ProjectRoot 'build/generated-unit-support') -Recurse -File -Filter '*.java' | Sort-Object Name) + if ($support.Count -ne 2) { throw 'Expected exactly the two pinned native unit support sources' } + $supportPins = @{ 'ForgeUnitRegistries.java' = $properties.unit_registries_java_sha256; 'Forge64UnitLoader.java' = $properties.unit_loader_java_sha256 } + foreach ($source in $support) { + $pin = Get-Pin $source.FullName + if (!$supportPins.ContainsKey($source.Name) -or $pin.sha256 -cne $supportPins[$source.Name]) { throw 'Pinned unit support source mismatch' } + $report.sources += $pin + } + $bootstrap = Join-Path $ProjectRoot 'build/unit-bootstrap/forge-native-unit-bootstrap.jar' + $report.unitBootstrap = Get-Pin $bootstrap + if ($report.unitBootstrap.sha256 -cne $properties.unit_bootstrap_sha256) { throw 'Frozen unit bootstrap jar mismatch' } $main = Invoke-Ecj 'main' $sources.main $mainLibraries - $test = Invoke-Ecj 'test' $sources.test (@($main) + $testLibraries) + $test = Invoke-Ecj 'test' (@($sources.test) + @($support.FullName)) (@($main) + $testLibraries) $gameTests = @(Get-ChildItem -LiteralPath (Join-Path $ProjectRoot 'src/gametest/java') -Recurse -File -Filter '*.java' | ForEach-Object { $_.FullName }) if ($gameTests.Count -ne 1) { throw 'Unexpected GameTest source inventory' } $report.sources += @($gameTests | ForEach-Object { Get-Pin $_ }) @@ -149,21 +174,41 @@ try { $null = Invoke-Ecj 'client' $clientTests (@($main, $test) + $testLibraries) $helperSource = Join-Path $PSScriptRoot 'AuditedJUnit.java' $report.helper = Get-Pin $helperSource - $helper = Invoke-Ecj 'helper' @($helperSource) $testLibraries + $inventorySource = Join-Path $PSScriptRoot 'EclipseTestInventory.java' + $report.inventoryHelper = Get-Pin $inventorySource + $helper = Invoke-Ecj 'helper' @($helperSource, $inventorySource) $testLibraries + $inventoryPath = Join-Path $Evidence 'gradle-method-inventory.txt' + [IO.File]::WriteAllLines($inventoryPath, $methodInventory, [Text.UTF8Encoding]::new($false)) + $inventoryRuntime = @($helper) + $testLibraries + & $java25 -XX:ActiveProcessorCount=4 -cp ($inventoryRuntime -join ';') zone.moddev.mc.baseelectric.verification.EclipseTestInventory $test $inventoryPath $expected *> (Join-Path $Evidence 'inventory.log') + if ($LASTEXITCODE -ne 0) { throw 'Fresh compiled JUnit method inventory does not match Gradle XML' } + $report.inventory = [ordered]@{ status = 'PASS'; methods = $methodInventory.Count; suites = $classes.Count; negatives = @() } + foreach ($prefix in @('zone.moddev.mc.baseelectric.entities.', 'zone.moddev.mc.baseelectric.items.')) { + $filtered = @($methodInventory | Where-Object { !$_.StartsWith($prefix, [StringComparison]::Ordinal) }) + if (!$filtered.Count -or $filtered.Count -eq $methodInventory.Count) { throw 'Inventory negative control excluded no tests' } + $negativePath = Join-Path $Evidence ('inventory-exclusion-' + $report.inventory.negatives.Count + '.txt') + [IO.File]::WriteAllLines($negativePath, $filtered, [Text.UTF8Encoding]::new($false)) + $log = $negativePath + '.log' + & $java25 -XX:ActiveProcessorCount=4 -cp ($inventoryRuntime -join ';') zone.moddev.mc.baseelectric.verification.EclipseTestInventory $test $negativePath $filtered.Count *> $log + if ($LASTEXITCODE -eq 0 -or (Get-Content -LiteralPath $log -Raw) -notmatch 'Incomplete or stale Gradle method inventory') { throw 'JUnit prefix-exclusion negative control was not rejected' } + $report.inventory.negatives += [ordered]@{ prefix = $prefix; tests = $filtered.Count; expectedRejection = $true; log = $log } + } $junitXml = Join-Path $Evidence 'junit.xml' $runtime = @($main, $test, $helper, (Join-Path $ProjectRoot 'build/resources/main'), (Join-Path $ProjectRoot 'build/resources/test')) + $testLibraries $report.runtime = $runtime Push-Location $ProjectRoot try { - & $java21 -Xmx2G -cp ($runtime -join ';') zone.moddev.mc.baseelectric.verification.AuditedJUnit $junitXml $expected @classes *> (Join-Path $Evidence 'junit.log') + & $java25 -XX:ActiveProcessorCount=4 -Xmx2G "-javaagent:$bootstrap" -cp ($runtime -join ';') zone.moddev.mc.baseelectric.verification.AuditedJUnit $junitXml $expected @classes *> (Join-Path $Evidence 'junit.log') $junitExit = $LASTEXITCODE } finally { Pop-Location } $report.junit = [ordered]@{ exitCode = $junitExit; expected = $expected; suites = $classes; log = 'junit.log'; xml = 'junit.xml' } + $report.junit.jvmArguments = @('-XX:ActiveProcessorCount=4', '-Xmx2G', "-javaagent:$bootstrap", '-cp', ($runtime -join ';')) + $report.junit.inheritedJvmEnvironment = 'CLEARED' if ($junitExit -ne 0) { throw 'Fresh ECJ JUnit execution failed' } [xml]$junit = Get-Content -LiteralPath $junitXml -Raw if ($junit.testsuite.passed -ne 'true' -or [int]$junit.testsuite.tests -ne $expected -or [int]$junit.testsuite.failures -or [int]$junit.testsuite.ignored -or [int]$junit.testsuite.assumptions) { throw 'Strict ECJ test counts failed' } - foreach ($pin in @($report.sources) + @($report.libraries) + @($report.helper) + @($report.negativeControl.source)) { + foreach ($pin in @($report.sources) + @($report.libraries) + @($report.helper, $report.inventoryHelper, $report.unitBootstrap) + @($report.negativeControl.source)) { if ((Get-FileHash -LiteralPath $pin.path -Algorithm SHA256).Hash -ne $pin.sha256) { throw "Input changed during ECJ qualification: $($pin.path)" } } $report.status = 'PASS' @@ -171,6 +216,11 @@ try { $report.status = 'FAIL' $report.error = $_.ToString() throw -} finally { Save-Report } +} finally { + Save-Report + foreach ($name in $inheritedJvmOptions.Keys) { + [Environment]::SetEnvironmentVariable($name, $inheritedJvmOptions[$name], 'Process') + } +} Get-Content -LiteralPath (Join-Path $Evidence 'junit.log') -Tail 8 -Write-Output "ECJ PASS: $($sources.main.Count) main, $($sources.test.Count) test sources; $expected tests; Java 21 class files" +Write-Output "ECJ PASS: $($sources.main.Count) main, $($sources.test.Count) test sources; $expected tests; Java 25 class files" diff --git a/tools/verify-native-port.js b/tools/verify-native-port.js index fb49ac0a..acca5387 100644 --- a/tools/verify-native-port.js +++ b/tools/verify-native-port.js @@ -5,7 +5,7 @@ const { execFileSync } = require('node:child_process'); const { createHash } = require('node:crypto'); const root = path.resolve(__dirname, '..'); -const baseline = 'b81645579c3330476b84149402c3ce78183df85f'; +const baseline = '951fc3921f27e963e96f29c78a38ee7093a65fde'; const legacyTrackBaseline = '013737d4cc41947e8b6fa42f4bf926a1fa5ab998'; const git = (...args) => execFileSync('git', args, { cwd: root }); const old = file => git('show', baseline + ':' + file); @@ -35,34 +35,26 @@ function check(name, run) { catch (error) { checks.push({name, pass: false, error: error.message}); } } -check('Exact Forge 61 and local port identity', () => { +check('Exact Forge 64 and local port identity', () => { const props = Object.fromEntries(read('gradle.properties').toString().split(/\r?\n/) .filter(line => /^[a-z][a-z_\d]*=/.test(line)).map(line => line.split(/=(.*)/s).slice(0, 2))); for (const [key, value] of Object.entries({ - minecraft_version: '1.21.11', forge_version: '61.1.0', mapping_channel: 'official', - mapping_version: '1.21.11', mcp_version: '20251223.124241', java_version: '21', - mod_id: 'baseelectric', mod_name: 'baseElectric', mod_version: '2.2.16.121111', - poweradvantage_version: '2.4.15.121111', basemetals_version: '3.1.2.121111', - poweradvantage_pin_state: 'MERGED_SOURCE', - poweradvantage_source_ref: '9b0043483d79d3e575895f12aeb0dde7ef6c4890', - poweradvantage_public_file_id: '9566998', - poweradvantage_deobf_sha256: 'C98D2CF163BE2480D9AFB0023BBD2D92D401AE0D50BEF73191235BF3F83774A7', - poweradvantage_runtime_sha256: 'C98D2CF163BE2480D9AFB0023BBD2D92D401AE0D50BEF73191235BF3F83774A7', - basemetals_source_commit: '6f4c44e4d0a68bca3838fb025c09cddfaaa08fe8', - basemetals_public_file_id: '9566738', - basemetals_deobf_sha256: '598D3FB0FF956EBCC67FE477A01CE393C75D0013AF0CE00A79E2072B63E9F2BA', - basemetals_runtime_sha256: '598D3FB0FF956EBCC67FE477A01CE393C75D0013AF0CE00A79E2072B63E9F2BA', - orespawn_source_commit: 'ecad75b24309a344f712d898d717c6a8823fc9f2', - orespawn_deobf_sha256: 'DE39929057288CEA110196C9E9D708E129B80BC360A2E53976337FFA6A84A67B', - orespawn_version: '4.1.0.121111', curseforge_project_id: '1783006', + minecraft_version: '26.1.2', forge_version: '64.1.5', mapping_channel: 'official', + mapping_version: '26.1.2', mcp_version: '20260409.101008', java_version: '25', + mod_id: 'baseelectric', mod_name: 'baseElectric', mod_version: '2.2.17.2601021', + poweradvantage_version: '2.4.16.2601021', basemetals_version: '3.1.2.2601021', + orespawn_source_commit: 'c45ea2a111520ef904e994dd8dcd90babfcef29b', + orespawn_deobf_sha256: '6D5CE2017C3314968083D140309E2F1A54BC239AC65C48944C995E9ECF937948', + orespawn_version: '4.1.0.2601021', curseforge_project_id: '1783006', cf_requirements: 'basepower,base-metals' })) assert.equal(props[key], value, key); + assert.equal(props.reobf, 'false'); assert.deepEqual(json('src/main/resources/pack.mcmeta').pack, - {min_format:[94,1], max_format:[94,1], description:'baseElectric'}); + {min_format:[101,1], max_format:[101,1], description:'baseElectric'}); const mods = read('src/main/resources/META-INF/mods.toml').toString(); assert.match(mods, /^showAsResourcePack=false$/m); - assert.match(mods, /modId="basepower"\s+mandatory=true\s+versionRange="\[2\.4\.15\.121111,3\.0\.0\)"/); - assert.match(mods, /modId="basemetals"\s+mandatory=true\s+versionRange="\[3\.1\.2\.121111,4\.0\.0\)"/); + assert.match(mods, /modId="basepower"\s+mandatory=true\s+versionRange="\[2\.4\.16\.2601021,3\.0\.0\)"/); + assert.match(mods, /modId="basemetals"\s+mandatory=true\s+versionRange="\[3\.1\.2\.2601021,4\.0\.0\)"/); const build = read('build.gradle').toString(); assert.match(build, /inputs\.files\(javadocTool\.map/); assert.match(build, /legal\/java\.base/); @@ -70,6 +62,18 @@ check('Exact Forge 61 and local port identity', () => { assert.match(build, /must contain the selected JDK's full canonical legal text/); }); +check('Native source and artifact pins are complete before qualification', () => { + const props = Object.fromEntries(read('gradle.properties').toString().split(/\r?\n/) + .filter(line => /^[a-z][a-z_\d]*=/.test(line)).map(line => line.split(/=(.*)/s).slice(0, 2))); + assert(['LOCAL_SOURCE', 'MERGED_SOURCE'].includes(props.poweradvantage_pin_state)); + for (const prefix of ['poweradvantage', 'basemetals', 'orespawn']) { + const source = props[prefix + (prefix === 'poweradvantage' ? '_source_ref' : '_source_commit')]; + assert.match(source, /^[a-f0-9]{40}$/, prefix + ' source pin is pending'); + assert.match(props[prefix + '_deobf_sha256'], /^[A-F0-9]{64}$/, prefix + ' artifact pin is pending'); + assert.equal(props[prefix + '_deobf_sha256'], props[prefix + '_runtime_sha256']); + } +}); + function checkTrackModel(file, model) { assert.equal(model.render_type, 'minecraft:cutout', file + ' lacks its native cutout layer'); const { render_type, ...unchanged } = model; @@ -96,7 +100,7 @@ check('Track audit rejects the old asset and a geometry mutation', () => { assert.throws(() => checkTrackModel(file, mutated), /changed beyond the render layer/); }); -check('Accepted 1.21.1 resources retain bytes except native recipe schema and pack metadata', () => { +check('Released 1.21.11 resources retain bytes except native pack metadata', () => { const resources = tracked.filter(file => /^src\/(main|test)\/resources\//.test(file)); const exceptions = new Set(['src/main/resources/pack.mcmeta', 'src/main/resources/META-INF/mods.toml']); for (const file of resources.filter(file => !exceptions.has(file))) { @@ -117,14 +121,10 @@ check('Accepted 1.21.1 resources retain bytes except native recipe schema and pa const id = path.basename(file, '.json'); assert.deepEqual(json(file), {model:{type:'minecraft:model', model:'baseelectric:item/' + id}}, file); } - assert.deepEqual(actual, [...tracked.map(relocated), ...definitions, - javaRoot + 'util/RegistryIds.java', javaRoot + 'util/LegacyNbt.java', - javaRoot.replace('main', 'test') + 'util/crafting/RecipeDeconstructorTest.java', - javaRoot.replace('main', 'test') + 'resources/NativePackRangeContractTest.java', - javaRoot.replace('main', 'test') + 'verification/NativePackResourceProof.java', - ...['drillupgradesandtransferslegacybattery', 'recipeavailabilitydoesnotremovediscovery', - 'siliconandsolderresolverealmaterialtags', 'turretusesnativeupdatepacket'] - .map(name => 'src/gametest/resources/data/baseelectric/test_instance/' + name + '.json')].sort(), + const trades = ['electric_conduit', 'solder', 'integrated_circuit', 'blank_circuit_board', 'petrolplastic_ingot', 'psu'] + .map(id => 'src/main/resources/data/baseelectric/villager_trade/librarian/' + id + '.json'); + const tiers = [1,2].map(level => 'src/main/resources/data/minecraft/tags/villager_trade/librarian/level_' + level + '.json'); + assert.deepEqual(actual, [...tracked.filter(file => file !== javaRoot + 'init/Villages.java'), ...trades, ...tiers].sort(), 'Source and resource inventory'); }); @@ -143,36 +143,44 @@ check('Migration and registration code keep their accepted IDs and compatibility .replaceAll('ResourceLocation', 'Identifier').replace(/\s/g,'')); }); -check('All 106 accepted tests and their assertions are retained without skips', () => { +check('All 114 released tests retain their coverage without skips', () => { const methods = value => [...value.toString().matchAll(/@Test\s+public void (\w+)\(/g)].map(m => m[1]); const files = tracked.filter(file => file.startsWith('src/test/java/') && file.endsWith('.java')); + const renamed = { + hostedBuildsProvisionAllThreePinnedToolchainsBeforeGradle: 'hostedBuildsPinJava25ForEveryRoleBeforeGradle', + resourcePackDeclaresTheNativeOneTwentyOneFormat: 'resourcePackDeclaresTheNativeTwentySixFormat' + }; let inherited = 0; let actual = 0; for (const file of files) { const current = read(file); const currentMethods = methods(current); const priorMethods = methods(old(file)); - for (const name of priorMethods) assert(currentMethods.includes(name), file + ' lost ' + name); + for (const name of priorMethods) assert(currentMethods.includes(renamed[name] || name), file + ' lost ' + name); const assertions = value => [...value.toString().matchAll(/\bassert\w*\s*\(/g)].length; assert(assertions(current) >= assertions(old(file)), file + ' lost assertions'); assert.doesNotMatch(current.toString(), /@Ignore|Assume\.|assumeTrue\(|assumeFalse\(/, file); inherited += priorMethods.length; actual += currentMethods.length; } - assert.equal(inherited, 106); - assert.equal(actual, 106); - const added = read(javaRoot.replace('main','test') + 'util/crafting/RecipeDeconstructorTest.java'); - assert.equal(methods(added).length, 2); - assert.doesNotMatch(added.toString(), /@Ignore|Assume\.|assumeTrue\(|assumeFalse\(/); - const pack = read(javaRoot.replace('main', 'test') + 'resources/NativePackRangeContractTest.java'); - assert.equal(methods(pack).length, 6); - assert.doesNotMatch(pack.toString(), /@Ignore|Assume\.|assumeTrue\(|assumeFalse\(/); + assert.equal(inherited, 114); + assert.equal(actual, 115); const gameTests = javaRoot.replace('main', 'gametest') + 'gametest/ElectricGameTests.java'; const names = value => [...value.toString().matchAll(/public static void (\w+)\(GameTestHelper/g)].map(m => m[1]); for (const name of names(old(gameTests))) assert(names(read(gameTests)).includes(name), 'Lost GameTest: ' + name); }); +check('Official-name production code contains no obsolete SRG field or method names', () => { + const files = fs.readdirSync(path.join(root, javaRoot), {recursive:true, withFileTypes:true}) + .filter(entry => entry.isFile() && entry.name.endsWith('.java')); + for (const entry of files) { + const file = path.join(entry.parentPath, entry.name); + assert.doesNotMatch(fs.readFileSync(file, 'utf8'), /\b(?:f_\d+_|m_\d+_|field_\d+_[A-Za-z_]+|func_\d+_[A-Za-z_]+)\b/, + path.relative(root, file) + ' still uses obsolete mapped field/method names'); + } +}); + check('All 58 recipe IDs, results, guards and ingredients are unchanged', () => { const files = tracked.filter(file => /\/data\/baseelectric\/recipe\/.*\.json$/.test(file)); assert.equal(files.length, 58); From 7d35dee11cd95d0a88c20255c8a013e9750fb107 Mon Sep 17 00:00:00 2001 From: JohnBraham Date: Thu, 12 Nov 2026 05:38:52 +0000 Subject: [PATCH 2/7] Verify native atlas repairs and checkpoint candidate06 inputs --- .gitattributes | 3 + CHANGELOG.txt | 1 + README.md | 7 +- build.gradle | 100 ++++++--- docs/NATIVE2612.md | 62 ++++-- gradle.properties | 12 +- .../gametest/ElectricGameTests.java | 20 ++ .../baseelectric/items/battery_base.json | 6 - .../assets/minecraft/atlases/blocks.json | 205 +++++++++++++++++- .../assets/minecraft/atlases/items.json | 72 ++++++ .../resources/ClientResourceContractTest.java | 32 +++ tools/verify-native-port.js | 30 ++- 12 files changed, 481 insertions(+), 69 deletions(-) delete mode 100644 src/main/resources/assets/baseelectric/items/battery_base.json create mode 100644 src/main/resources/assets/minecraft/atlases/items.json diff --git a/.gitattributes b/.gitattributes index 0fc4431d..14ae4072 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,3 +4,6 @@ *.ogg binary *.jar binary *.zip binary +# Preserve the exact atlas bytes used by the frozen native client candidate. +src/main/resources/assets/minecraft/atlases/blocks.json -text whitespace=blank-at-eol,blank-at-eof,space-before-tab,cr-at-eol +src/main/resources/assets/minecraft/atlases/items.json -text whitespace=blank-at-eol,blank-at-eof,space-before-tab,cr-at-eol diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 7fc869ed..9edcab59 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -3,6 +3,7 @@ baseElectric 2.2.17.2601021 (Minecraft 26.1.2 / Forge, unreleased) - Port to Forge 64.1.5 and Java 25 for every build and runtime role. - Keep the same machines, saved data, aliases, material ratios and recipe IDs. - Move all six librarian offers to native trade data without changing their ranges or tiers. +- Fix client texture loading for the laser beam and inherited battery models. - Keep 1.21.11 release evidence separate from the new port's verification. baseElectric 2.2.16.121111 (Minecraft 1.21.11 / Forge) diff --git a/README.md b/README.md index 3b485423..d687c17e 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Local jars do not satisfy that gate. ## Verification -The current local source passes 115 unit tests, 28 independent native resource +The current local candidate passes 115 unit tests, 28 independent native resource tests and 115 tests from fresh ECJ output, with no failures or skips. The ECJ gate compares every compiled test method with the Gradle XML inventory. All four named source GameTests also pass on the current Power checkpoint. @@ -78,6 +78,11 @@ The Power pin includes the parent's fuel-container repair. Each qualification receipt retains its exact input hashes. In particular, no old screenshot or earlier version's result qualifies this port. +The atlas and abstract-battery definition repairs have native codec checks and +a real registered-item census. Their sources and packaged archives reject +global directories, foreign namespaces, missing sprites and duplicates across +atlases. The earlier diagnostic asset jar is not a release or final proof. + Run `runGameTestServer` with the same pinned mod inputs, an existing accepted EULA passed as `-PelectricGameTestEula=`, and a new directory passed as `-PelectricGameTestRunDirectory=build/port-evidence/`. The test code and diff --git a/build.gradle b/build.gradle index f457bfbd..c74f5489 100644 --- a/build.gradle +++ b/build.gradle @@ -874,17 +874,42 @@ tasks.register('verifyReleaseDependencies') { } def blocksAtlasPath = 'assets/minecraft/atlases/blocks.json' -def electricBlocksAtlas = [sources: [[type: 'single', resource: 'baseelectric:entity/laser_turret']]] +def itemsAtlasPath = 'assets/minecraft/atlases/items.json' +def validateOwnedAtlas = { Object atlas, String owner, Set sprites, Closure exists, String context -> + if (!(atlas instanceof Map) || atlas.keySet() != (['sources'] as Set) + || !(atlas.sources instanceof List) || atlas.sources.isEmpty()) { + throw new GradleException("${context} must declare nonempty explicit sprite sources") + } + atlas.sources.each { source -> + if (!(source instanceof Map) || source.keySet() != (['type', 'resource'] as Set) + || source.type != 'minecraft:single' || !(source.resource instanceof String) + || !source.resource.startsWith(owner + ':')) { + throw new GradleException("${context} must only register ${owner}'s exact native single sprites") + } + String sprite = source.resource + String path = sprite.substring(sprite.indexOf(':') + 1) + if (!(path ==~ '[a-z0-9_./-]+') || path.tokenize('/').any { it == '..' } + || !exists("assets/${owner}/textures/${path}.png".toString())) { + throw new GradleException("${context} registers a missing or unsafe sprite ${sprite}") + } + if (!sprites.add(sprite)) throw new GradleException("Duplicate sprite across native atlases: ${sprite}") + } +} def verifyPackagedBlocksAtlas = { ZipFile zip -> def overrides = zip.entries().findAll { !it.isDirectory() && it.name.startsWith('assets/minecraft/') }.collect { it.name } - if (overrides != [blocksAtlasPath]) { - throw new GradleException("Expected only ${blocksAtlasPath} in the Minecraft asset namespace, found ${overrides}") - } - zip.getInputStream(zip.getEntry(blocksAtlasPath)).withCloseable { input -> - if (new JsonSlurper().parse(input, 'UTF-8') != electricBlocksAtlas) { - throw new GradleException('Packaged blocks atlas must add only the turret item sprite') + if (overrides.sort() != [blocksAtlasPath, itemsAtlasPath].sort()) { + throw new GradleException("Expected exactly the two native atlases in the Minecraft asset namespace, found ${overrides}") + } + Set sprites = [] as Set + [blocksAtlasPath, itemsAtlasPath].each { path -> + zip.getInputStream(zip.getEntry(path)).withCloseable { input -> + def atlas = new JsonSlurper().parse(input, 'UTF-8') + validateOwnedAtlas(atlas, project.mod_id, sprites, { resource -> zip.getEntry(resource) != null }, path) + if (atlas != new JsonSlurper().parse(file('src/main/resources/' + path))) { + throw new GradleException("Packaged ${path} differs from the validated source atlas") + } } } } @@ -946,6 +971,10 @@ tasks.register('verifyReleaseArtifacts') { 'data/minecraft/tags/villager_trade/librarian/level_2.json', 'zone/moddev/mc/baseelectric/recipe/RecipeConditions$ModeCondition.class', 'assets/baseelectric/lang/en_us.json', + 'assets/minecraft/atlases/blocks.json', 'assets/minecraft/atlases/items.json', + 'assets/baseelectric/models/item/battery_base.json', + 'assets/baseelectric/items/alkaline_battery.json', 'assets/baseelectric/items/lead_acid_battery.json', + 'assets/baseelectric/items/lithium_battery.json', 'assets/baseelectric/items/nickel_hydride_battery.json', 'data/baseelectric/loot_table/blocks/electric_drill.json', 'data/baseelectric/loot_table/blocks/electric_conduit.json', 'data/baseelectric/damage_type/laser.json', @@ -958,6 +987,7 @@ tasks.register('verifyReleaseArtifacts') { } } ['agent-notes/', '.codex/', 'src/test/', 'zone/moddev/mc/baseelectric/gametest/', + 'assets/baseelectric/items/battery_base.json', 'zone/moddev/mc/advantageworks/unit/', 'data/baseelectric/structure/empty.nbt', 'fernflower_abstract_parameter_names.txt', 'cyano/poweradvantage/', @@ -1651,6 +1681,7 @@ tasks.register('verifyModelAssets') { inputs.dir(assetRoot) inputs.dir(new File(resourceRoot, 'data')) inputs.files(atlasFile) + inputs.file(new File(resourceRoot, 'assets/minecraft/atlases/items.json')) inputs.file(powerAdvantageDeobfJar) doLast { @@ -1661,24 +1692,48 @@ tasks.register('verifyModelAssets') { new JsonSlurper().parse(jsonFile) } - def localAtlas = atlasFile.isFile() ? new JsonSlurper().parse(atlasFile) : [sources: []] - // Vanilla's generic directories also cover mod namespaces. Power adds the legacy directories. - List atlasSources = [[type: 'directory', source: 'block', prefix: 'block/'], - [type: 'directory', source: 'item', prefix: 'item/']] + Set registeredSprites = [] as Set + [blocksAtlasPath, itemsAtlasPath].each { path -> + validateOwnedAtlas(new JsonSlurper().parse(new File(resourceRoot, path)), project.mod_id, + registeredSprites, { resource -> new File(resourceRoot, resource).isFile() }, path) + } + if (registeredSprites.contains('baseelectric:materials/laser_beam') + || !registeredSprites.contains('baseelectric:entity/laser_turret') + || !registeredSprites.contains('baseelectric:materials/machine_case')) { + throw new GradleException('Electric atlas is missing a required sprite or includes the direct-render beam') + } Set powerAdvantageAssets = new ZipFile(powerAdvantageDeobfJar.get()).withCloseable { zip -> - def entry = zip.getEntry(blocksAtlasPath) - if (entry == null) throw new GradleException('Pinned basePower is missing its blocks atlas') - zip.getInputStream(entry).withCloseable { input -> - atlasSources.addAll(new JsonSlurper().parse(input, 'UTF-8').sources) + [blocksAtlasPath, itemsAtlasPath].each { path -> + def entry = zip.getEntry(path) + if (entry == null) throw new GradleException("Pinned basePower is missing ${path}") + zip.getInputStream(entry).withCloseable { input -> + validateOwnedAtlas(new JsonSlurper().parse(input, 'UTF-8'), 'basepower', registeredSprites, + { resource -> zip.getEntry(resource) != null }, 'Pinned Power ' + path) + } } zip.entries().collect { it.name } as Set } - atlasSources.addAll(localAtlas.sources) + Set vanillaAssets = [] as Set + sourceSets.main.compileClasspath.files.findAll { it.isFile() && it.name.endsWith('.jar') }.each { archive -> + new ZipFile(archive).withCloseable { zip -> + zip.entries().each { entry -> + if (entry.name.startsWith('assets/minecraft/')) vanillaAssets.add(entry.name) + } + } + } def verifyReference = { String reference, String assetType, String sourcePath -> if (reference.startsWith('#')) return int separator = reference.indexOf(':') String namespace = separator >= 0 ? reference.substring(0, separator) : 'minecraft' String path = separator >= 0 ? reference.substring(separator + 1) : reference + if (namespace == 'minecraft') { + if (assetType == 'models' && path.startsWith('builtin/')) return + String suffix = assetType == 'textures' ? '.png' : '.json' + if (!vanillaAssets.contains("assets/minecraft/${assetType}/${path}${suffix}".toString())) { + throw new GradleException("${sourcePath} references missing vanilla ${assetType} asset ${reference}") + } + return + } if (namespace != project.mod_id && namespace != 'basepower') return String suffix = assetType == 'textures' ? '.png' : '.json' @@ -1690,13 +1745,8 @@ tasks.register('verifyModelAssets') { throw new GradleException( "${sourcePath} references missing ${assetType} asset ${reference}") } - if (assetType == 'textures' && !atlasSources.any { source -> - if (source.type == 'single') return (source.sprite ?: source.resource) == reference - if (source.type != 'directory') return false - String folder = source.source + '/' - path.startsWith(folder) && (source.prefix + path.substring(folder.length())) == path - }) { - throw new GradleException("${sourcePath} references unstitched blocks-atlas sprite ${reference}") + if (assetType == 'textures' && !registeredSprites.contains(reference)) { + throw new GradleException("${sourcePath} references an unregistered native sprite ${reference}") } } @@ -1720,10 +1770,6 @@ tasks.register('verifyModelAssets') { } } } - if (localAtlas != electricBlocksAtlas) { - throw new GradleException('Electric blocks atlas must add only the turret item sprite') - } - Closure verifyBlockstateModels verifyBlockstateModels = { Object node, String sourcePath -> if (node instanceof Map) { diff --git a/docs/NATIVE2612.md b/docs/NATIVE2612.md index 90b9a090..7561e3d3 100644 --- a/docs/NATIVE2612.md +++ b/docs/NATIVE2612.md @@ -11,24 +11,48 @@ They must not be relabeled as candidate02 or full family acceptance. ## Current Checkpoint -Electric's local candidate05 player jar is -`1936D3E2272B72CFD387ED59FFC389018D0FC841BFB9F1E0713334501CD2E54A`. -The ZIP comparison against the previous `1515772F...` jar has 643 entries on -each side and changes only `META-INF/MANIFEST.MF`, for the repaired Power -dependency pin. Every class and resource is byte-identical. This comparison -is not gameplay qualification. - -The forced fresh 51-task suite passes 115 unit and 28 native resource tests, +The frozen candidate06 player jar is +`DFC63C4E619A74BEE2A22146D9720ADB2B32801865EAE1B716726424015775FA`. +It uses Power `0ac874de66f792c6efca5bccd0536b1b113fb786`, ordinary jar +`618C3F96736392CE2853575F5A6A076EAC2A9B9F76F9303D86DEF58E418857D0`, +and Base Metals `331fa46914f854db71a8c33500cc559a8878b7d3` / `67862388...`. +Java classes and PNGs remain byte-identical to the previous `1936D3E2...` +candidate. The intended resource differences are the two atlas definitions and +removal of the abstract battery's standalone item definition, plus dependency +provenance in the manifest. This is not final family or manual acceptance. + +Actual native client loading exposed an 8-pixel direct-render laser beam being +included by Power's global materials directory, and a standalone item definition +for the unregistered abstract battery parent. Hegel replaced the atlas imports +with explicit namespace-owned sprites. Electric's common native registry census +confirms `battery_base` is absent and all four real batteries are registered. +Only `items/battery_base.json` is removed; its inherited parent model and every +PNG remain unchanged. A focused resource regression fails before that deletion +and passes after it, including native atlas codec and required-sprite checks. + +The earlier diagnostic asset jar is +`982D94A4B469216643D1B6609B3C16990447F82DD4A47BD155EE00CA91BE60C4`. +Against the intermediate jar it changes only the blocks atlas, adds the items +atlas and removes the abstract item definition. All class and PNG bytes match. +It uses the sealed `000A...` compile input and is not final qualification. +Its original receipt is retained. Candidate06 uses the later coherent atlas +pins and needs its own graphical, migration and endurance receipts. + +The current candidate's forced fresh 51-task suite passes 115 unit and 28 native resource tests, with zero failures or skips. Fresh ECJ output passes all 115 tests with the complete compiled-method inventory and negative controls. All four named source GameTests and both new-pin reproduction builds pass. Receipts are -recorded separately in `build/port-evidence/candidate05-2612-01`. +recorded separately in `build/port-evidence/candidate06-final-assets-01`. This local checkpoint is not final family qualification. -The parent repaired Power's fuel-container remainder handling and handed over -the exact source and ordinary jar below. The previous `F39FF5EC...` Power and -`1515772F...` Electric results remain under their original receipts in -`candidate02-2612-01`; they are not relabeled for this checkpoint. +The parent repaired the shared migration context after candidate05's actual +original110 actor run lost four Steam enchantments from cart and dropped-item +stacks. The raw failure, source lineage and independently assessed dimension +results remain bound to candidate05. New qualification must use the repaired +Power and Base Metals inputs below; no old result is relabeled for candidate06. +The intermediate `fb5fb0e` source-pin jar is retained separately and is not the +final candidate. The later `540bc666` context jar and `4D2C5563...` child also +remain historical, not relabeled as the atlas-repaired candidate06. ## Target @@ -38,8 +62,8 @@ the exact source and ordinary jar below. The previous `F39FF5EC...` Power and - OreSpawn 4.1.0.2601021, source `c45ea2a111520ef904e994dd8dcd90babfcef29b`, ordinary jar SHA-256 `6D5CE2017C3314968083D140309E2F1A54BC239AC65C48944C995E9ECF937948`. -The local Power checkpoint is `e49a2e947e066a0f0e497982c2d5db596dfe11d9`; -the Base Metals shared-hook checkpoint is `13d4c4e38bf74d76f2f69f307dc55d13a81e8674`. +The local Power checkpoint is `0ac874de66f792c6efca5bccd0536b1b113fb786`; +the Base Metals shared-hook checkpoint is `331fa46914f854db71a8c33500cc559a8878b7d3`. Their exact ordinary jar hashes are recorded in `gradle.properties`. These are local integration inputs, not public releases or completed family qualification. Earlier provisional diagnostics remain under their original @@ -68,8 +92,8 @@ resources and 50 unlocks are retained. Native pack discovery accepts resource 84.0 and data 101.1, with zero warning/error diagnostics. Compilation of all 87 production sources and all 36 test sources -passes against Power `3D9ACF8B86DD1B443B138C61E5E1C5BAAC3059D83EED83B8E6E927BC501CA446` -and Base Metals `4911669B93A5C64C0F7AE0AAAEBB50BA90EC82A42436B9E6DEF6C1524B1CFF9C`. +passes against Power `618C3F96736392CE2853575F5A6A076EAC2A9B9F76F9303D86DEF58E418857D0` +and Base Metals `6786238835D4D8A653C769F3B0E61D226959146300E17523799CFDF17540FD19`. All 115 unit tests pass without skips. Fresh ECJ output also passes 115 tests; main, test, GameTest and client-contract scopes compile as Java 25. Both pinned unit-support sources are compiled with the tests. The separately rebuilt agent @@ -91,7 +115,7 @@ actual Forge 64.1.5, followed by normal save and shutdown: - `baseelectric:siliconandsolderresolverealmaterialtags` - `baseelectric:turretusesnativeupdatepacket` -The XML and runtime receipt are in `candidate05-2612-01/gametest-01`. +The XML and runtime receipt are in `candidate06-final-assets-01/gametest-01`. This is source-output GameTest evidence, not frozen-player-jar proof. The first-boot log retains configuration-default corrections and native union-URL warnings; its clean-log gate is not green. The runtime receipt records Java @@ -118,7 +142,7 @@ It is not a restart, whole-world, fuel-container or clean-log qualification. GUI/assets/scenes and combined development and packaged classpaths remain parent-owned gates. Current logs, including failures, are retained under `build/port-evidence/initial-2612-01` for candidate01 and the separate candidate02 -folder and candidate05 folder above; none is a release acceptance claim. +folder and separate candidate05/candidate06 folders; none is a release acceptance claim. A candidate01 packaged smoke already in flight at the hold completed 19 narrow checks, including drill inventory conversion, battery components, sulfur drops, diff --git a/gradle.properties b/gradle.properties index 0cfd2f2c..85e009ab 100644 --- a/gradle.properties +++ b/gradle.properties @@ -44,18 +44,18 @@ poweradvantage_version=2.4.16.2601021 poweradvantage_pin_state=LOCAL_SOURCE poweradvantage_source_repository=MinecraftModDevelopmentMods/basePower # Owner-approved local integration checkpoint, not a public or family-qualified release. -poweradvantage_source_ref=e49a2e947e066a0f0e497982c2d5db596dfe11d9 +poweradvantage_source_ref=0ac874de66f792c6efca5bccd0536b1b113fb786 poweradvantage_public_file_id= -poweradvantage_deobf_sha256=3D9ACF8B86DD1B443B138C61E5E1C5BAAC3059D83EED83B8E6E927BC501CA446 -poweradvantage_runtime_sha256=3D9ACF8B86DD1B443B138C61E5E1C5BAAC3059D83EED83B8E6E927BC501CA446 +poweradvantage_deobf_sha256=618C3F96736392CE2853575F5A6A076EAC2A9B9F76F9303D86DEF58E418857D0 +poweradvantage_runtime_sha256=618C3F96736392CE2853575F5A6A076EAC2A9B9F76F9303D86DEF58E418857D0 basemetals_version=3.1.2.2601021 basemetals_built_on=26.1.2-64.0.9 # Local native shared-hook checkpoint; family qualification is separate. -basemetals_source_commit=13d4c4e38bf74d76f2f69f307dc55d13a81e8674 +basemetals_source_commit=331fa46914f854db71a8c33500cc559a8878b7d3 basemetals_public_file_id= -basemetals_deobf_sha256=4911669B93A5C64C0F7AE0AAAEBB50BA90EC82A42436B9E6DEF6C1524B1CFF9C -basemetals_runtime_sha256=4911669B93A5C64C0F7AE0AAAEBB50BA90EC82A42436B9E6DEF6C1524B1CFF9C +basemetals_deobf_sha256=6786238835D4D8A653C769F3B0E61D226959146300E17523799CFDF17540FD19 +basemetals_runtime_sha256=6786238835D4D8A653C769F3B0E61D226959146300E17523799CFDF17540FD19 unit_bootstrap_sha256=088FC137BEBB9B2E75C596DE868499E6099CF228BECFCF07EAE36791245FD1B6 unit_bootstrap_java_sha256=D4B72FD65D9F8549B142507C6446E3B1E047B637784AF9FA74E8F28413E14A51 diff --git a/src/gametest/java/zone/moddev/mc/baseelectric/gametest/ElectricGameTests.java b/src/gametest/java/zone/moddev/mc/baseelectric/gametest/ElectricGameTests.java index ba4c983b..39db043f 100644 --- a/src/gametest/java/zone/moddev/mc/baseelectric/gametest/ElectricGameTests.java +++ b/src/gametest/java/zone/moddev/mc/baseelectric/gametest/ElectricGameTests.java @@ -126,6 +126,26 @@ public static void drillUpgradesAndTransfersLegacyBattery(GameTestHelper helper) @GameTest(name = "recipeavailabilitydoesnotremovediscovery", structure = "empty") public static void recipeAvailabilityDoesNotRemoveDiscovery(GameTestHelper helper) { var server = helper.getLevel().getServer(); + var itemIds = net.minecraftforge.registries.ForgeRegistries.ITEMS.getKeys().stream() + .filter(id -> id.getNamespace().equals(ElectricAdvantage.MODID)) + .map(Object::toString).sorted().toList(); + helper.assertTrue(!itemIds.contains("baseelectric:battery_base"), "Abstract battery parent became a registered item"); + for (String battery : java.util.List.of("alkaline_battery", "lead_acid_battery", "lithium_battery", "nickel_hydride_battery")) { + helper.assertTrue(itemIds.contains("baseelectric:" + battery), "Concrete battery is not registered: " + battery); + } + var census = new com.google.gson.JsonObject(); + census.addProperty("scope", "ACTUAL_NATIVE_COMMON_ITEM_REGISTRY_SOURCE_GAMETEST_NOT_CLIENT_PIXELS"); + census.addProperty("abstractBatteryRegistered", false); + var registryEntries = new com.google.gson.JsonArray(); + itemIds.forEach(registryEntries::add); + census.add("registeredElectricItems", registryEntries); + try { + Files.writeString(Path.of(System.getProperty("baseelectric.gameTestRuntimeReceipt")) + .resolveSibling("electric-item-registry-census.json"), census.toString(), + java.nio.file.StandardOpenOption.CREATE_NEW); + } catch (java.io.IOException failure) { + throw new AssertionError("Cannot retain actual item registry census", failure); + } var advancement = Identifier.fromNamespaceAndPath(ElectricAdvantage.MODID, "recipes/alkaline_battery"); helper.assertTrue(server.getAdvancements().get(advancement) != null, "A material-gated recipe lost its discovery advancement"); diff --git a/src/main/resources/assets/baseelectric/items/battery_base.json b/src/main/resources/assets/baseelectric/items/battery_base.json deleted file mode 100644 index 01738b1e..00000000 --- a/src/main/resources/assets/baseelectric/items/battery_base.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "model": { - "type": "minecraft:model", - "model": "baseelectric:item/battery_base" - } -} diff --git a/src/main/resources/assets/minecraft/atlases/blocks.json b/src/main/resources/assets/minecraft/atlases/blocks.json index d413b816..70bd027d 100644 --- a/src/main/resources/assets/minecraft/atlases/blocks.json +++ b/src/main/resources/assets/minecraft/atlases/blocks.json @@ -1,5 +1,200 @@ -{ - "sources": [ - {"type":"single","resource":"baseelectric:entity/laser_turret"} - ] -} +{ + "sources": [ + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/electric_battery_array" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/electric_conduit" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/electric_crusher_off" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/electric_crusher_on" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/electric_crusher_powered" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/electric_drill_off" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/electric_drill_on" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/electric_fabricator_off" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/electric_fabricator_on" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/electric_fabricator_powered" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/electric_furnace_off" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/electric_furnace_on" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/electric_furnace_powered" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/electric_oven_off" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/electric_oven_on" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/electric_oven_powered" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/electric_pcb_printer_off" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/electric_pcb_printer_on" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/electric_pcb_printer_powered" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/electric_pump_off" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/electric_pump_on" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/electric_still_off" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/electric_still_on" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/electric_still_powered" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/electric_switch_off" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/electric_switch_on" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/growth_chamber_controller" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/growth_chamber_off" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/growth_chamber_on" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/growth_chamber_powered" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/hydroelectric_generator_off" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/hydroelectric_generator_on" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/laser_drill_optic" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/laser_drill_optic_on" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/laser_turret_base" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/led_bar_off" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/led_bar_on" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/lithium_ore" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/machine_back" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/machine_side" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/machine_top" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/photovoltaic_cell" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/plastic_refinery_off" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/plastic_refinery_on" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/steam_powered_generator_off" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/steam_powered_generator_on" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:blocks/sulfur_ore" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:entity/laser_turret" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:materials/machine_case" + } + ] +} diff --git a/src/main/resources/assets/minecraft/atlases/items.json b/src/main/resources/assets/minecraft/atlases/items.json new file mode 100644 index 00000000..c1703fce --- /dev/null +++ b/src/main/resources/assets/minecraft/atlases/items.json @@ -0,0 +1,72 @@ +{ + "sources": [ + { + "type": "minecraft:single", + "resource": "baseelectric:items/alkaline_battery" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:items/blank_circuit_board" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:items/control_circuit" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:items/integrated_circuit" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:items/lead_acid_battery" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:items/li_ingot" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:items/li_nugget" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:items/li_powder" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:items/lithium_battery" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:items/nickel_hydride_battery" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:items/petrolplastic_ingot" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:items/psu" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:items/silicon_ingot" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:items/silicon_mix" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:items/solder" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:items/solder_mix" + }, + { + "type": "minecraft:single", + "resource": "baseelectric:items/sulfur_powder" + } + ] +} diff --git a/src/test/java/zone/moddev/mc/baseelectric/resources/ClientResourceContractTest.java b/src/test/java/zone/moddev/mc/baseelectric/resources/ClientResourceContractTest.java index 053fdfa6..80c77115 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/resources/ClientResourceContractTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/resources/ClientResourceContractTest.java @@ -3,6 +3,9 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.mojang.serialization.JsonOps; +import net.minecraft.client.renderer.texture.atlas.SpriteSources; +import net.minecraft.client.renderer.texture.atlas.sources.SingleFile; import org.junit.Test; import java.io.Reader; import java.nio.charset.StandardCharsets; @@ -39,6 +42,35 @@ public class ClientResourceContractTest { } @Test public void parentsAndTexturesExistInActualPacks() throws Exception { + SpriteSources.bootstrap(); + var sprites = new java.util.HashSet(); + for (String atlas : new String[]{"blocks", "items"}) { + var decoded = SpriteSources.FILE_CODEC.parse(JsonOps.INSTANCE, + read(ROOT.resolve("assets/minecraft/atlases/" + atlas + ".json"))).getOrThrow(); + assertEquals(atlas, "blocks".equals(atlas) ? 49 : 17, decoded.size()); + for (var source : decoded) { + assertTrue("No global directories or order-dependent filters", source instanceof SingleFile); + var single = (SingleFile) source; + assertEquals("baseelectric", single.resourceId().getNamespace()); + assertTrue(single.spriteId().isEmpty()); + assertTrue("Duplicate sprite across native atlases", sprites.add(single.resourceId().toString())); + } + } + assertTrue(sprites.contains("baseelectric:entity/laser_turret")); + assertTrue(sprites.contains("baseelectric:materials/machine_case")); + assertFalse(sprites.contains("baseelectric:materials/laser_beam")); + assertTrue(SpriteSources.FILE_CODEC.parse(JsonOps.INSTANCE, JsonParser.parseString( + "{\"sources\":[{\"type\":\"minecraft:single\",\"resource\":5}]}" )).error().isPresent()); + assertFalse("Abstract parent is not a standalone registered item", + Files.exists(ASSETS.resolve("items/battery_base.json"))); + assertTrue(Files.isRegularFile(ASSETS.resolve("models/item/battery_base.json"))); + for (String battery : new String[]{"alkaline_battery", "lead_acid_battery", "lithium_battery", "nickel_hydride_battery"}) { + JsonObject child = read(ASSETS.resolve("models/item/" + battery + ".json")); + assertEquals("baseelectric:item/battery_base", child.get("parent").getAsString()); + assertEquals("baseelectric:items/" + battery, child.getAsJsonObject("textures").get("item_texture").getAsString()); + assertEquals("baseelectric:item/" + battery, read(ASSETS.resolve("items/" + battery + ".json")) + .getAsJsonObject("model").get("model").getAsString()); + } try (Stream files = Files.walk(ASSETS.resolve("models"))) { for (Path file : (Iterable) files.filter(p -> p.toString().endsWith(".json"))::iterator) { JsonObject model = read(file); diff --git a/tools/verify-native-port.js b/tools/verify-native-port.js index acca5387..7b623778 100644 --- a/tools/verify-native-port.js +++ b/tools/verify-native-port.js @@ -100,9 +100,29 @@ check('Track audit rejects the old asset and a geometry mutation', () => { assert.throws(() => checkTrackModel(file, mutated), /changed beyond the render layer/); }); -check('Released 1.21.11 resources retain bytes except native pack metadata', () => { +check('Released resources retain bytes except exact native metadata and atlas repairs', () => { const resources = tracked.filter(file => /^src\/(main|test)\/resources\//.test(file)); - const exceptions = new Set(['src/main/resources/pack.mcmeta', 'src/main/resources/META-INF/mods.toml']); + const removedAbstract = assets + 'items/battery_base.json'; + const blocksAtlas = 'src/main/resources/assets/minecraft/atlases/blocks.json'; + const itemsAtlas = 'src/main/resources/assets/minecraft/atlases/items.json'; + const exceptions = new Set(['src/main/resources/pack.mcmeta', 'src/main/resources/META-INF/mods.toml', blocksAtlas, removedAbstract]); + assert(!fs.existsSync(path.join(root, removedAbstract)), 'Abstract battery parent must not be a standalone item definition'); + const sprites = new Set(); + for (const [atlas, count] of [[blocksAtlas, 49], [itemsAtlas, 17]]) { + const entries = json(atlas).sources; + assert.equal(entries.length, count, atlas); + for (const source of entries) { + assert.equal(source.type, 'minecraft:single', atlas + ' has a non-owned directory or filter'); + assert.match(source.resource, /^baseelectric:[a-z0-9_./-]+$/); + assert(!('sprite' in source), 'Unexpected sprite alias'); + assert(!sprites.has(source.resource), 'Duplicate block/item atlas sprite: ' + source.resource); + sprites.add(source.resource); + assert(fs.existsSync(path.join(root, assets + 'textures/' + source.resource.split(':')[1] + '.png'))); + } + } + assert(sprites.has('baseelectric:entity/laser_turret')); + assert(sprites.has('baseelectric:materials/machine_case')); + assert(!sprites.has('baseelectric:materials/laser_beam'), 'Direct-render beam must not enter a mipmapped atlas'); for (const file of resources.filter(file => !exceptions.has(file))) { if (file.startsWith('src/main/resources/data/baseelectric/recipe/')) { assert.deepEqual(json(file), nativeIngredients(JSON.parse(old(file))), file); @@ -115,8 +135,8 @@ check('Released 1.21.11 resources retain bytes except native pack metadata', () .filter(entry => entry.isFile()).map(entry => path.relative(root, path.join(entry.parentPath, entry.name)) .split(path.sep).join('/')).sort(); const definitions = tracked.filter(file => file.startsWith(assets + 'models/item/') && file.endsWith('.json')) - .map(file => file.replace('/models/item/', '/items/')); - assert.equal(definitions.length, 44); + .map(file => file.replace('/models/item/', '/items/')).filter(file => file !== removedAbstract); + assert.equal(definitions.length, 43); for (const file of definitions) { const id = path.basename(file, '.json'); assert.deepEqual(json(file), {model:{type:'minecraft:model', model:'baseelectric:item/' + id}}, file); @@ -124,7 +144,7 @@ check('Released 1.21.11 resources retain bytes except native pack metadata', () const trades = ['electric_conduit', 'solder', 'integrated_circuit', 'blank_circuit_board', 'petrolplastic_ingot', 'psu'] .map(id => 'src/main/resources/data/baseelectric/villager_trade/librarian/' + id + '.json'); const tiers = [1,2].map(level => 'src/main/resources/data/minecraft/tags/villager_trade/librarian/level_' + level + '.json'); - assert.deepEqual(actual, [...tracked.filter(file => file !== javaRoot + 'init/Villages.java'), ...trades, ...tiers].sort(), + assert.deepEqual(actual, [...tracked.filter(file => file !== javaRoot + 'init/Villages.java' && file !== removedAbstract), itemsAtlas, ...trades, ...tiers].sort(), 'Source and resource inventory'); }); From 303c0046805b61d130abd050e787ce986822cc16 Mon Sep 17 00:00:00 2001 From: JohnBraham Date: Thu, 12 Nov 2026 06:55:28 +0000 Subject: [PATCH 3/7] Preserve retained OreSpawn settings before native generation --- CHANGELOG.txt | 1 + README.md | 26 ++++--- build.gradle | 8 +- docs/NATIVE2612.md | 46 +++++++++++- gradle.properties | 6 +- .../mc/baseelectric/ElectricAdvantage.java | 3 +- .../baseelectric/init/OreSpawnWorldGen.java | 7 ++ .../init/OreSpawnWorldGenTest.java | 74 +++++++++++++++++++ tools/verification/Verify-ElectricEcj.ps1 | 2 +- tools/verify-native-port.js | 2 +- 10 files changed, 152 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 9edcab59..715c8e7d 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -4,6 +4,7 @@ baseElectric 2.2.17.2601021 (Minecraft 26.1.2 / Forge, unreleased) - Keep the same machines, saved data, aliases, material ratios and recipe IDs. - Move all six librarian offers to native trade data without changing their ranges or tiers. - Fix client texture loading for the laser beam and inherited battery models. +- Preserve retained OreSpawn profiles before generation and stop on ambiguous migration choices. - Keep 1.21.11 release evidence separate from the new port's verification. baseElectric 2.2.16.121111 (Minecraft 1.21.11 / Forge) diff --git a/README.md b/README.md index d687c17e..b1553fcb 100644 --- a/README.md +++ b/README.md @@ -64,17 +64,23 @@ Local jars do not satisfy that gate. ## Verification -The current local candidate passes 115 unit tests, 28 independent native resource -tests and 115 tests from fresh ECJ output, with no failures or skips. The ECJ +The current local candidate passes 117 unit tests, 28 independent native resource +tests and 117 tests from fresh ECJ output, with no failures or skips. The ECJ gate compares every compiled test method with the Gradle XML inventory. -All four named source GameTests also pass on the current Power checkpoint. -Independent warm-cache and new cold-cache builds in paths containing spaces -reproduce all three jars byte-for-byte. -Their first-boot GameTest log contains -native configuration and union-URL warnings, so it is not a clean-log receipt. -The packaged server smoke passes its 19 narrow checks; combined gameplay, -restart, migration and actual client checks remain separate parent-owned gates. -The Power pin includes the parent's fuel-container repair. Each qualification +Electric registers its pure JSON migration defaults during construction, after +the old OS1 importer and before lifecycle setup. Migration refusal or an I/O +failure stops startup; old provider identities and saved choices are not +silently replaced by fresh defaults. +Both independent reproduction builds match all three jars exactly; each passes +117 unit and 28 native checks. One reuses the previously cold-generated engine +cache, without claiming a new cold download. All four named source GameTests +also pass on these pins. Their first-boot warnings remain in the receipts; +this is not warning-free or manual gameplay proof. The current receipts are in +`build/port-evidence/candidate07-retained-profile-01`. Candidate06's four +GameTests, reproductions and narrow packaged smoke remain historical proof, +not qualification of the new player jar. Combined gameplay, restart, migration +and actual client checks remain separate parent-owned gates. +The Power pin includes the parent's retained-profile repair. Each qualification receipt retains its exact input hashes. In particular, no old screenshot or earlier version's result qualifies this port. diff --git a/build.gradle b/build.gradle index c74f5489..84b494d9 100644 --- a/build.gradle +++ b/build.gradle @@ -103,8 +103,8 @@ tasks.named('test', Test) { } afterSuite { descriptor, result -> if (!descriptor.parent && filter.includePatterns.empty && filter.commandLineIncludePatterns.empty - && (result.testCount != 115 || result.skippedTestCount != 0)) { - throw new GradleException("Expected 115 tests with no skips, got ${result.testCount} tests and ${result.skippedTestCount} skips") + && (result.testCount != 117 || result.skippedTestCount != 0)) { + throw new GradleException("Expected 117 tests with no skips, got ${result.testCount} tests and ${result.skippedTestCount} skips") } } doFirst { @@ -492,8 +492,8 @@ tasks.register('nativeUnitDiagnostics', Test) { } afterSuite { descriptor, result -> if (!descriptor.parent && filter.includePatterns.empty && filter.commandLineIncludePatterns.empty - && (result.testCount != 115 || result.skippedTestCount != 0)) { - throw new GradleException("Expected 115 provisional unit diagnostics without skips, got ${result.testCount}/${result.skippedTestCount}") + && (result.testCount != 117 || result.skippedTestCount != 0)) { + throw new GradleException("Expected 117 provisional unit diagnostics without skips, got ${result.testCount}/${result.skippedTestCount}") } } doFirst { diff --git a/docs/NATIVE2612.md b/docs/NATIVE2612.md index 7561e3d3..5ac85397 100644 --- a/docs/NATIVE2612.md +++ b/docs/NATIVE2612.md @@ -11,6 +11,46 @@ They must not be relabeled as candidate02 or full family acceptance. ## Current Checkpoint +The candidate07 ordinary jar is +`F2E71F0693033FB4BBA0DE775A542C185C9163AEFA8BF819D0E8F423121A94BC`. +It uses local Power `9f6ffb30a14efe4360fee0cbdfa52dc8090a9819` / +`05031F1A3DCF25A405D9222E65619F7173C37CCF57B76308B72F741CA0E5198C`. +The full 51-task suite passes 117 unit and 28 independent native checks; +fresh ECJ output passes all 117 tests with the full compiled inventory. +The two added tests check the actual compiled constructor path, fail-closed +exception handling, single registration and the native JSON defaults. + +Only the manifest and the two migration integration classes differ from the +candidate06 ordinary jar. All resources, PNGs and other classes are exact. +Both new independent source snapshots pass all 51 tasks, 117 unit tests and +28 native checks, reproducing all three jars exactly. One uses the earlier +cold-generated engine cache; no fresh cold-download claim is made. All four +named source GameTests also pass against these pins, with their first-boot +warnings retained. Current receipts are kept separately in +`build/port-evidence/candidate07-retained-profile-01`; old results do not +qualify this jar. Family migration, generation, graphics and endurance remain +separate candidate-bound gates, not a release or manual acceptance claim. + +An actual frozen06 retained-profile regression started a fresh normal world +with the approved released110 saved profile, generated 126 sampled chunks, +then restarted in a second process. Oil/lithium/sulfur counts were positive, +but both boots correctly failed the profile contract: old output IDs remained +unresolved and new active defaults were added. The source profile, warnings, +raw failure and frozen06 inputs are retained, not rewritten as candidate07. + +Read-only collision inventory covers all eight supported machine source routes +and nine actor/dimension source routes. All 22 existing global/world profile +hashes match their genuine pre-setup seals, with no original-source active +canonical collisions. Candidate06 destinations did acquire duplicate defaults. +The released110 machine source has real world overrides: lithium frequency +0.25 versus global 0.125, and sulfur disabled versus globally enabled. +The released112 machine and actor fixtures are distinct and keep their own +profile/import oracles. Unsupported chained113 findings are labelled separately. +Dormant quartz custom rules are not proof of lost active vanilla generation: +these profiles keep vanilla management and global ore suppression disabled. + +## Candidate06 History + The frozen candidate06 player jar is `DFC63C4E619A74BEE2A22146D9720ADB2B32801865EAE1B716726424015775FA`. It uses Power `0ac874de66f792c6efca5bccd0536b1b113fb786`, ordinary jar @@ -38,7 +78,7 @@ It uses the sealed `000A...` compile input and is not final qualification. Its original receipt is retained. Candidate06 uses the later coherent atlas pins and needs its own graphical, migration and endurance receipts. -The current candidate's forced fresh 51-task suite passes 115 unit and 28 native resource tests, +Candidate06's forced fresh 51-task suite passes 115 unit and 28 native resource tests, with zero failures or skips. Fresh ECJ output passes all 115 tests with the complete compiled-method inventory and negative controls. All four named source GameTests and both new-pin reproduction builds pass. Receipts are @@ -62,7 +102,7 @@ remain historical, not relabeled as the atlas-repaired candidate06. - OreSpawn 4.1.0.2601021, source `c45ea2a111520ef904e994dd8dcd90babfcef29b`, ordinary jar SHA-256 `6D5CE2017C3314968083D140309E2F1A54BC239AC65C48944C995E9ECF937948`. -The local Power checkpoint is `0ac874de66f792c6efca5bccd0536b1b113fb786`; +The local Power checkpoint is `9f6ffb30a14efe4360fee0cbdfa52dc8090a9819`; the Base Metals shared-hook checkpoint is `331fa46914f854db71a8c33500cc559a8878b7d3`. Their exact ordinary jar hashes are recorded in `gradle.properties`. These are local integration inputs, not public releases or completed family @@ -85,7 +125,7 @@ Root pack metadata declares data format 101.1. Forge's hidden client pack must synthesize native resource format 84.0. Both paths are tested through actual Forge discovery, with strict warning and error rejection unchanged. -## Verification Status +## Candidate06 Verification The independent native suite passes 28 tests without skips. All 58 recipe resources and 50 unlocks are retained. Native pack discovery accepts resource diff --git a/gradle.properties b/gradle.properties index 85e009ab..088d562e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -44,10 +44,10 @@ poweradvantage_version=2.4.16.2601021 poweradvantage_pin_state=LOCAL_SOURCE poweradvantage_source_repository=MinecraftModDevelopmentMods/basePower # Owner-approved local integration checkpoint, not a public or family-qualified release. -poweradvantage_source_ref=0ac874de66f792c6efca5bccd0536b1b113fb786 +poweradvantage_source_ref=9f6ffb30a14efe4360fee0cbdfa52dc8090a9819 poweradvantage_public_file_id= -poweradvantage_deobf_sha256=618C3F96736392CE2853575F5A6A076EAC2A9B9F76F9303D86DEF58E418857D0 -poweradvantage_runtime_sha256=618C3F96736392CE2853575F5A6A076EAC2A9B9F76F9303D86DEF58E418857D0 +poweradvantage_deobf_sha256=05031F1A3DCF25A405D9222E65619F7173C37CCF57B76308B72F741CA0E5198C +poweradvantage_runtime_sha256=05031F1A3DCF25A405D9222E65619F7173C37CCF57B76308B72F741CA0E5198C basemetals_version=3.1.2.2601021 basemetals_built_on=26.1.2-64.0.9 diff --git a/src/main/java/zone/moddev/mc/baseelectric/ElectricAdvantage.java b/src/main/java/zone/moddev/mc/baseelectric/ElectricAdvantage.java index 2e9008cf..bcedea43 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/ElectricAdvantage.java +++ b/src/main/java/zone/moddev/mc/baseelectric/ElectricAdvantage.java @@ -58,12 +58,13 @@ private static void migrateLegacyOreSpawnConfiguration() { try { LegacyOreSpawnConfigBridge.Result result = LegacyOreSpawnConfigBridge.migrate(FMLPaths.CONFIGDIR.get(), MODID, LEGACY_MODID); + OreSpawnWorldGen.registerMigrationDefaults(FMLPaths.CONFIGDIR.get()); if (result.wasMigrated()) { LOGGER.info("Imported {} legacy Electric Advantage OreSpawn rule(s) from '{}' into '{}'", result.getRuleCount(), result.getSource(), result.getTarget()); } } catch (IOException exception) { - LOGGER.error("Could not migrate legacy Electric Advantage OreSpawn configuration", exception); + throw new IllegalStateException("Could not preserve Electric Advantage OreSpawn configuration", exception); } } diff --git a/src/main/java/zone/moddev/mc/baseelectric/init/OreSpawnWorldGen.java b/src/main/java/zone/moddev/mc/baseelectric/init/OreSpawnWorldGen.java index 21aed57d..10ec6cc8 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/init/OreSpawnWorldGen.java +++ b/src/main/java/zone/moddev/mc/baseelectric/init/OreSpawnWorldGen.java @@ -1,11 +1,13 @@ package zone.moddev.mc.baseelectric.init; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import net.minecraft.resources.Identifier; import net.minecraftforge.fml.ModList; import zone.moddev.mc.baseelectric.ElectricAdvantage; +import zone.moddev.mc.basepower.migration.OreSpawnProfileMigration; import zone.moddev.mc.orespawn.api.GeologyFamily; import zone.moddev.mc.orespawn.api.OrePattern; import zone.moddev.mc.orespawn.api.OreSpawnApi; @@ -24,6 +26,11 @@ public final class OreSpawnWorldGen { private OreSpawnWorldGen() { } + public static void registerMigrationDefaults(Path configDirectory) throws IOException { + OreSpawnProfileMigration.registerProvider(configDirectory, + createProvider(ModList.isLoaded("mineralogy"), ModList.isLoaded("baseminerals")).toJson()); + } + public static void register(Path configDirectory) { boolean mineralogyLoaded = ModList.isLoaded("mineralogy"); boolean baseMineralsLoaded = ModList.isLoaded("baseminerals"); diff --git a/src/test/java/zone/moddev/mc/baseelectric/init/OreSpawnWorldGenTest.java b/src/test/java/zone/moddev/mc/baseelectric/init/OreSpawnWorldGenTest.java index ab029c87..341914bb 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/init/OreSpawnWorldGenTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/init/OreSpawnWorldGenTest.java @@ -1,9 +1,18 @@ package zone.moddev.mc.baseelectric.init; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import com.google.gson.JsonObject; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; import org.junit.Test; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; public class OreSpawnWorldGenTest { @Test public void electricOwnsBothMaterialsWhenNoPreferredProviderIsLoaded() { @@ -22,6 +31,71 @@ public class OreSpawnWorldGenTest { assertDefaults(true, true, false, false); } + @Test public void constructorRegistersMigrationBeforeLifecycleAndFailsClosed() throws Exception { + ClassNode entry = bytecode("zone/moddev/mc/baseelectric/ElectricAdvantage"); + List constructor = calls(method(entry, "")); + assertTrue(constructor.contains("migrateLegacyOreSpawnConfiguration") && constructor.contains("registerConfig")); + assertTrue(constructor.indexOf("migrateLegacyOreSpawnConfiguration") + < constructor.indexOf("registerConfig")); + MethodNode migration = method(entry, "migrateLegacyOreSpawnConfiguration"); + List migrationCalls = calls(migration); + assertTrue(migrationCalls.contains("migrate")); + assertEquals(1, migrationCalls.stream().filter("registerMigrationDefaults"::equals).count()); + assertTrue(migrationCalls.indexOf("migrate") < migrationCalls.indexOf("registerMigrationDefaults")); + assertTrue(migration.tryCatchBlocks.stream().anyMatch(block -> "java/io/IOException".equals(block.type))); + assertTrue(java.util.stream.StreamSupport.stream(migration.instructions.spliterator(), false) + .anyMatch(instruction -> instruction.getOpcode() == Opcodes.ATHROW)); + assertTrue(!migrationCalls.contains("error")); + + MethodNode registration = method(bytecode("zone/moddev/mc/baseelectric/init/OreSpawnWorldGen"), + "registerMigrationDefaults"); + assertTrue((registration.access & (Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC)) + == (Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC)); + assertTrue(registration.exceptions.contains("java/io/IOException")); + List registrationCalls = calls(registration); + assertEquals(1, registrationCalls.stream().filter("registerProvider"::equals).count()); + assertEquals(2, registrationCalls.stream().filter("isLoaded"::equals).count()); + assertTrue(registrationCalls.contains("createProvider") && registrationCalls.contains("toJson")); + assertTrue(!registrationCalls.contains("enqueue")); + } + + @Test public void migrationDefaultsKeepOwnedNativeRangesAndFallbackHierarchy() { + JsonObject root = OreSpawnWorldGen.createProvider(false, false).toJson(); + assertEquals("baseelectric", root.get("provider_modid").getAsString()); + for (String material : new String[] {"lithium", "sulfur"}) { + JsonObject rule = root.getAsJsonObject("ores").getAsJsonObject("baseelectric:ore/" + material); + assertEquals("baseelectric:" + (material.equals("lithium") ? "li_ore" : "sulfur_ore"), + rule.get("block").getAsString()); + JsonObject dimension = rule.getAsJsonObject("dimensions").getAsJsonObject("minecraft:overworld"); + assertEquals(1, dimension.get("min_y").getAsInt()); + assertEquals(31, dimension.get("max_y").getAsInt()); + assertEquals(material.equals("lithium") ? 0.125 : 1.0, dimension.get("frequency").getAsDouble(), 0.0); + assertEquals(material.equals("lithium") ? 6 : 8, dimension.get("min_quantity").getAsInt()); + assertEquals(material.equals("lithium") ? 9 : 23, dimension.get("max_quantity").getAsInt()); + } + } + + private static ClassNode bytecode(String name) throws Exception { + try (InputStream input = OreSpawnWorldGenTest.class.getResourceAsStream("/" + name + ".class")) { + assertTrue("Missing actual compiled production class", input != null); + ClassNode result = new ClassNode(); + new ClassReader(input).accept(result, 0); + return result; + } + } + + private static MethodNode method(ClassNode type, String name) { + return type.methods.stream().filter(method -> method.name.equals(name)).findFirst().orElseThrow(); + } + + private static List calls(MethodNode method) { + List names = new ArrayList<>(); + for (var instruction : method.instructions) { + if (instruction instanceof MethodInsnNode call) names.add(call.name); + } + return names; + } + private static void assertDefaults(boolean mineralogy, boolean baseMinerals, boolean sulfurEnabled, boolean lithiumEnabled) { JsonObject root = OreSpawnWorldGen.createProvider(mineralogy, baseMinerals).toJson(); diff --git a/tools/verification/Verify-ElectricEcj.ps1 b/tools/verification/Verify-ElectricEcj.ps1 index dd4b1af5..314d0c7a 100644 --- a/tools/verification/Verify-ElectricEcj.ps1 +++ b/tools/verification/Verify-ElectricEcj.ps1 @@ -151,7 +151,7 @@ try { } $report.gradleInventory += Get-Pin $xml.FullName } - if ($classes.Count -ne 33 -or $expected -ne 115 -or $sources.test.Count -ne 36 -or $sources.main.Count -ne 87) { throw 'Unexpected Electric 26.1.2 source/test inventory' } + if ($classes.Count -ne 33 -or $expected -ne 117 -or $sources.test.Count -ne 36 -or $sources.main.Count -ne 87) { throw 'Unexpected Electric 26.1.2 source/test inventory' } $support = @(Get-ChildItem -LiteralPath (Join-Path $ProjectRoot 'build/generated-unit-support') -Recurse -File -Filter '*.java' | Sort-Object Name) if ($support.Count -ne 2) { throw 'Expected exactly the two pinned native unit support sources' } $supportPins = @{ 'ForgeUnitRegistries.java' = $properties.unit_registries_java_sha256; 'Forge64UnitLoader.java' = $properties.unit_loader_java_sha256 } diff --git a/tools/verify-native-port.js b/tools/verify-native-port.js index 7b623778..d3357256 100644 --- a/tools/verify-native-port.js +++ b/tools/verify-native-port.js @@ -184,7 +184,7 @@ check('All 114 released tests retain their coverage without skips', () => { actual += currentMethods.length; } assert.equal(inherited, 114); - assert.equal(actual, 115); + assert.equal(actual, 117); const gameTests = javaRoot.replace('main', 'gametest') + 'gametest/ElectricGameTests.java'; const names = value => [...value.toString().matchAll(/public static void (\w+)\(GameTestHelper/g)].map(m => m[1]); for (const name of names(old(gameTests))) assert(names(read(gameTests)).includes(name), 'Lost GameTest: ' + name); From 878a6aee180508801f01f8e9c30bcecef67927bb Mon Sep 17 00:00:00 2001 From: JohnBraham Date: Thu, 12 Nov 2026 07:37:23 +0000 Subject: [PATCH 4/7] Pin verified local Power schema repair and record retained-profile hold --- README.md | 11 ++++++++--- docs/NATIVE2612.md | 35 ++++++++++++++++++++++++++++++++++- gradle.properties | 6 +++--- 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index b1553fcb..2232a267 100644 --- a/README.md +++ b/README.md @@ -76,12 +76,17 @@ Both independent reproduction builds match all three jars exactly; each passes cache, without claiming a new cold download. All four named source GameTests also pass on these pins. Their first-boot warnings remain in the receipts; this is not warning-free or manual gameplay proof. The current receipts are in -`build/port-evidence/candidate07-retained-profile-01`. Candidate06's four +`build/port-evidence/candidate08-schema-pin-01`. Candidate06's four GameTests, reproductions and narrow packaged smoke remain historical proof, not qualification of the new player jar. Combined gameplay, restart, migration and actual client checks remain separate parent-owned gates. -The Power pin includes the parent's retained-profile repair. Each qualification -receipt retains its exact input hashes. In particular, +The Power pin includes the parent's retained-profile and provider-schema repairs. +Candidate07's actual OS1 startup failure remains recorded separately, despite +its passing local build checks. Candidate08 retained-profile runs passed the +original OS1 route but failed both released-source first boots on the old quartz +output; passing restarts do not waive those failures. Further family runs are +held for the parent's lifecycle repair. Each qualification receipt retains its +exact input hashes. In particular, no old screenshot or earlier version's result qualifies this port. The atlas and abstract-battery definition repairs have native codec checks and diff --git a/docs/NATIVE2612.md b/docs/NATIVE2612.md index 5ac85397..b9d84120 100644 --- a/docs/NATIVE2612.md +++ b/docs/NATIVE2612.md @@ -11,6 +11,39 @@ They must not be relabeled as candidate02 or full family acceptance. ## Current Checkpoint +The candidate08 ordinary jar is +`E873CE65BA94AF5D37AB4FD059368251427C81DB88F4D5AD2CCADC97ECAA9913`. +It uses local Power `e565f99284de5e6c7860d378abc43b96d9faa5ee` / +`CC94656A75B3E6311E5F9F7CB4E4E60014B94A87665743D5A5D3C84CEA849720`. +Only dependency provenance in the ordinary jar's manifest changes from +candidate07; all classes and resources are identical. Sources and Javadoc +jars are also identical. Keeping the old player hash would retain a false +Power source/hash manifest, so the new ordinary bytes have their own receipt. + +The full 51-task suite passes 117 unit and 28 native checks. Fresh ECJ output +passes all 117 tests with the complete compiled inventory and negative controls. +Current evidence belongs to `build/port-evidence/candidate08-schema-pin-01`. +It is not family gameplay, generation, graphics or manual acceptance. + +The parent's frozen family08 profile instead contains the previous `F2E71...` +ordinary Electric jar. Its retained-profile receipts are evidence for those +exact frozen bytes, not for this repinned `E873...` archive. Original110 passed +both boots. Released110 and released112 failed first boot on unresolved +`minecraft:quartz_ore`; their passing restarts do not waive that real failure. +All other retained output payloads and policy selections were preserved, and +released110's disabled sulfur produced zero blocks. New family campaigns are +held for the parent's lifecycle repair. Failed logs and raw verdicts remain +unchanged. + +Candidate07 remains blocked by its actual first OS1 startup failure: seeding +schema5 typed defaults into a schema4 provider override caused OreSpawn to +reject `placement_channel`. Power's candidate08 repair upgrades only the owned +provider schema at that boundary; global8 and world7 markers remain unchanged. +The successful local07 checks and failed live07 receipts remain historical. +No old result is relabelled as08 or used to waive the early legacy startup gate. + +## Candidate07 History + The candidate07 ordinary jar is `F2E71F0693033FB4BBA0DE775A542C185C9163AEFA8BF819D0E8F423121A94BC`. It uses local Power `9f6ffb30a14efe4360fee0cbdfa52dc8090a9819` / @@ -102,7 +135,7 @@ remain historical, not relabeled as the atlas-repaired candidate06. - OreSpawn 4.1.0.2601021, source `c45ea2a111520ef904e994dd8dcd90babfcef29b`, ordinary jar SHA-256 `6D5CE2017C3314968083D140309E2F1A54BC239AC65C48944C995E9ECF937948`. -The local Power checkpoint is `9f6ffb30a14efe4360fee0cbdfa52dc8090a9819`; +The local Power checkpoint is `e565f99284de5e6c7860d378abc43b96d9faa5ee`; the Base Metals shared-hook checkpoint is `331fa46914f854db71a8c33500cc559a8878b7d3`. Their exact ordinary jar hashes are recorded in `gradle.properties`. These are local integration inputs, not public releases or completed family diff --git a/gradle.properties b/gradle.properties index 088d562e..598e5bc1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -44,10 +44,10 @@ poweradvantage_version=2.4.16.2601021 poweradvantage_pin_state=LOCAL_SOURCE poweradvantage_source_repository=MinecraftModDevelopmentMods/basePower # Owner-approved local integration checkpoint, not a public or family-qualified release. -poweradvantage_source_ref=9f6ffb30a14efe4360fee0cbdfa52dc8090a9819 +poweradvantage_source_ref=e565f99284de5e6c7860d378abc43b96d9faa5ee poweradvantage_public_file_id= -poweradvantage_deobf_sha256=05031F1A3DCF25A405D9222E65619F7173C37CCF57B76308B72F741CA0E5198C -poweradvantage_runtime_sha256=05031F1A3DCF25A405D9222E65619F7173C37CCF57B76308B72F741CA0E5198C +poweradvantage_deobf_sha256=CC94656A75B3E6311E5F9F7CB4E4E60014B94A87665743D5A5D3C84CEA849720 +poweradvantage_runtime_sha256=CC94656A75B3E6311E5F9F7CB4E4E60014B94A87665743D5A5D3C84CEA849720 basemetals_version=3.1.2.2601021 basemetals_built_on=26.1.2-64.0.9 From 943dfaa9ca986703db2750fe33edf58285f253fb Mon Sep 17 00:00:00 2001 From: JohnBraham Date: Thu, 12 Nov 2026 08:07:10 +0000 Subject: [PATCH 5/7] Pin local Power lifecycle repair and preserve family qualification holds --- README.md | 11 +++++++---- docs/NATIVE2612.md | 31 ++++++++++++++++++++++++++++++- gradle.properties | 6 +++--- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 2232a267..51211766 100644 --- a/README.md +++ b/README.md @@ -76,17 +76,20 @@ Both independent reproduction builds match all three jars exactly; each passes cache, without claiming a new cold download. All four named source GameTests also pass on these pins. Their first-boot warnings remain in the receipts; this is not warning-free or manual gameplay proof. The current receipts are in -`build/port-evidence/candidate08-schema-pin-01`. Candidate06's four +`build/port-evidence/candidate09-lifecycle-pin-01`. Candidate06's four GameTests, reproductions and narrow packaged smoke remain historical proof, not qualification of the new player jar. Combined gameplay, restart, migration and actual client checks remain separate parent-owned gates. -The Power pin includes the parent's retained-profile and provider-schema repairs. +The Power pin includes the parent's retained-profile, provider-schema and +profile-loading lifecycle repairs, without an OreSpawn patch or log waiver. Candidate07's actual OS1 startup failure remains recorded separately, despite its passing local build checks. Candidate08 retained-profile runs passed the original OS1 route but failed both released-source first boots on the old quartz output; passing restarts do not waive those failures. Further family runs are -held for the parent's lifecycle repair. Each qualification receipt retains its -exact input hashes. In particular, +held until the current retained-profile gates are resolved. Candidate09's saved +Forge alias is valid, but the old canonical-ID-only audit still records quartz +failures; a separate Base Metals duplicate-placement review is also unresolved. +Each qualification receipt retains its exact input hashes. In particular, no old screenshot or earlier version's result qualifies this port. The atlas and abstract-battery definition repairs have native codec checks and diff --git a/docs/NATIVE2612.md b/docs/NATIVE2612.md index b9d84120..4df50415 100644 --- a/docs/NATIVE2612.md +++ b/docs/NATIVE2612.md @@ -11,6 +11,35 @@ They must not be relabeled as candidate02 or full family acceptance. ## Current Checkpoint +The candidate09 ordinary jar is +`0911C5D68C81E3B0E4EE3A596F20E25E59A4EDE1BFDC96A36398A3799E754FC1`. +It uses local Power `b947b4b81f3508fa220a212b41157ed2f801909f` / +`5309E7C433933F1426543310611292BF80388A476836139F0117E6A7E5C9AFC1`. +The full 51-task suite passes 117 unit and 28 native checks. Fresh ECJ passes +all 117 tests with the complete compiled inventory and negative controls. +Evidence is in `build/port-evidence/candidate09-lifecycle-pin-01`. + +Only the three Power provenance attributes in the ordinary manifest change +from the previous `E873...` and frozen `F2E71...` archives. Every class and +resource is identical; sources and Javadoc jars are also identical. The new +Power hash must not be recorded against either old Electric player hash. + +Power adds a canonical quartz lookup alias and a load-complete profile migration +and cache refresh. This is not an OreSpawn patch or a log waiver. Actual first +boot, restart and new-chunk checks must use a newly sealed candidate09 family. +The candidate08 failures below remain unchanged and do not qualify this pin. + +The sealed family09 profile retains the old `F2E71...` Electric archive, not the +repinned `0911...` jar above. Its raw retained-profile checks pass original110 +but record first-boot quartz failures for released110 and released112. The +stopped Forge registry actually contains the valid old-to-canonical quartz +alias, which the old canonical-ID-only check does not read; there are no ignored +quartz warnings. Raw failures remain unchanged while that oracle boundary is +reviewed. A separate Base Metals legacy/default duplicate-placement review is +also unresolved. Neither issue is waived by these passing child build checks. + +## Candidate08 History + The candidate08 ordinary jar is `E873CE65BA94AF5D37AB4FD059368251427C81DB88F4D5AD2CCADC97ECAA9913`. It uses local Power `e565f99284de5e6c7860d378abc43b96d9faa5ee` / @@ -135,7 +164,7 @@ remain historical, not relabeled as the atlas-repaired candidate06. - OreSpawn 4.1.0.2601021, source `c45ea2a111520ef904e994dd8dcd90babfcef29b`, ordinary jar SHA-256 `6D5CE2017C3314968083D140309E2F1A54BC239AC65C48944C995E9ECF937948`. -The local Power checkpoint is `e565f99284de5e6c7860d378abc43b96d9faa5ee`; +The local Power checkpoint is `b947b4b81f3508fa220a212b41157ed2f801909f`; the Base Metals shared-hook checkpoint is `331fa46914f854db71a8c33500cc559a8878b7d3`. Their exact ordinary jar hashes are recorded in `gradle.properties`. These are local integration inputs, not public releases or completed family diff --git a/gradle.properties b/gradle.properties index 598e5bc1..7e043231 100644 --- a/gradle.properties +++ b/gradle.properties @@ -44,10 +44,10 @@ poweradvantage_version=2.4.16.2601021 poweradvantage_pin_state=LOCAL_SOURCE poweradvantage_source_repository=MinecraftModDevelopmentMods/basePower # Owner-approved local integration checkpoint, not a public or family-qualified release. -poweradvantage_source_ref=e565f99284de5e6c7860d378abc43b96d9faa5ee +poweradvantage_source_ref=b947b4b81f3508fa220a212b41157ed2f801909f poweradvantage_public_file_id= -poweradvantage_deobf_sha256=CC94656A75B3E6311E5F9F7CB4E4E60014B94A87665743D5A5D3C84CEA849720 -poweradvantage_runtime_sha256=CC94656A75B3E6311E5F9F7CB4E4E60014B94A87665743D5A5D3C84CEA849720 +poweradvantage_deobf_sha256=5309E7C433933F1426543310611292BF80388A476836139F0117E6A7E5C9AFC1 +poweradvantage_runtime_sha256=5309E7C433933F1426543310611292BF80388A476836139F0117E6A7E5C9AFC1 basemetals_version=3.1.2.2601021 basemetals_built_on=26.1.2-64.0.9 From 54625c4826a4bc930c065e8aec06d29b3e83a8ec Mon Sep 17 00:00:00 2001 From: JohnBraham Date: Thu, 12 Nov 2026 15:23:34 +0000 Subject: [PATCH 6/7] Prepare Electric 2.2.17 for release --- CHANGELOG.txt | 17 ++--- README.md | 32 +++++++--- build.gradle | 2 +- docs/NATIVE2612.md | 75 +++++++++++++++++++++-- gradle.properties | 16 ++--- tools/verification/Verify-ElectricEcj.ps1 | 2 +- tools/verify-native-port.js | 6 +- 7 files changed, 118 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 715c8e7d..06e85884 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,11 +1,12 @@ -baseElectric 2.2.17.2601021 (Minecraft 26.1.2 / Forge, unreleased) - -- Port to Forge 64.1.5 and Java 25 for every build and runtime role. -- Keep the same machines, saved data, aliases, material ratios and recipe IDs. -- Move all six librarian offers to native trade data without changing their ranges or tiers. -- Fix client texture loading for the laser beam and inherited battery models. -- Preserve retained OreSpawn profiles before generation and stop on ambiguous migration choices. -- Keep 1.21.11 release evidence separate from the new port's verification. +baseElectric 2.2.17.2601021 (Minecraft 26.1.2 / Forge) + +- Bring baseElectric to Minecraft 26.1.2 with Forge 64.1.5 and Java 25. +- Preserve machine state, battery charge, old-world aliases, material ratios and recipe progression. +- Keep all six librarian offers and their original ranges and tiers in native trade data. +- Fix laser-beam texture loading and inherited battery models. +- Preserve saved OreSpawn choices before generation and stop on ambiguous migrations. +- Use OreSpawn 4.1.1.2601021 with the coordinated basePower and Base Metals builds. +- Complete the local machine, upgrade, graphical, endurance and manual-acceptance checks. baseElectric 2.2.16.121111 (Minecraft 1.21.11 / Forge) diff --git a/README.md b/README.md index 51211766..b624d735 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,37 @@ # baseElectric +**Version 2.2.17.2601021 is accepted for release.** + baseElectric continues DrCyano's Electric Advantage. It adds electric machines, generation, storage, automation and defence to basePower. Its equipment appears in the shared basePower creative tab. -This checkout is the local **2.2.17.2601021** port for Minecraft **26.1.2** and +This checkout targets Minecraft **26.1.2** and Forge **64.1.5**, using official runtime names, MCP **20260409.101008**, ForgeGradle **7.0.34**, Gradle **9.6.1**, and Temurin **25.0.3+9**. -It is not a released or qualified build yet. +The frozen profile12 family has completed automated qualification and manual +testing. See the +[family qualification record](../../PowerAdvantage/docs/26.1.2/QUALIFICATION.md) +for the exact tested jars, completed checks and scope limits. Manual acceptance +is complete. Publication is being prepared; this version is not public yet. basePower **2.4.16.2601021** and Base Metals **3.1.2.2601021** are required on both client and server. Their local source and ordinary-jar checkpoints are -recorded in `gradle.properties`; they are not public releases or a completed -family qualification. The build rejects old 1.21.11 inputs. OreSpawn -**4.1.0.2601021** is pinned to its public release. See the [26.1.2 notes](docs/NATIVE2612.md). +recorded in `gradle.properties`; they remain local inputs, not public releases. +The build rejects old 1.21.11 inputs. OreSpawn +**4.1.1.2601021** is pinned to its public release (CurseForge 9572926). +See the [26.1.2 notes](docs/NATIVE2612.md). + +The accepted qualification uses Power source `d0bd0bdb` / jar `23ABCC1F` and the +exact local Base Metals hook. Verified public dependency pins are still needed +before release CI and publication. Power is published first, then Steam, then +Electric. Electric's manifest records its Power source and artifact provenance; +the public-pin rebuild will have a separate byte comparison and receipt. +The new clean release suite passes 117 unit tests and 28 native checks, and its +warm and fresh empty-cache path-with-spaces reproductions match all three +archives. Fresh ECJ passes all 117 tests with the complete compiled inventory, +and the standalone source audit passes all nine checks after its exact OreSpawn +constants were updated. The original stale-pin failures remain preserved. The completed [1.21.11 release](https://www.curseforge.com/minecraft/mc-mods/baseelectric/files/9567881) and its [qualification notes](docs/NATIVE12111.md) are historical evidence, @@ -62,9 +80,9 @@ remain separate mods. The 26.1.2 CI and CodeQL jobs require merged source pins and checksum-matching public dependencies before checkout or compilation. Local jars do not satisfy that gate. -## Verification +## Candidate09 Verification History -The current local candidate passes 117 unit tests, 28 independent native resource +The previous candidate09 passed 117 unit tests, 28 independent native resource tests and 117 tests from fresh ECJ output, with no failures or skips. The ECJ gate compares every compiled test method with the Gradle XML inventory. Electric registers its pure JSON migration defaults during construction, after diff --git a/build.gradle b/build.gradle index 84b494d9..9282a68e 100644 --- a/build.gradle +++ b/build.gradle @@ -714,7 +714,7 @@ tasks.register('verifyReleaseConfiguration') { throw new GradleException('Unexpected basePower compile dependency identity') } if (!(project.basemetals_version ==~ /3\.\d+\.\d+\.2601021/) - || project.orespawn_version != '4.1.0.2601021' + || project.orespawn_version != '4.1.1.2601021' || project.basemetals_built_on != '26.1.2-64.0.9' || project.orespawn_built_on != '26.1.2-64.0.9' || !(project.basemetals_source_commit ==~ /[a-f0-9]{40}/) diff --git a/docs/NATIVE2612.md b/docs/NATIVE2612.md index 4df50415..a18c9b32 100644 --- a/docs/NATIVE2612.md +++ b/docs/NATIVE2612.md @@ -1,5 +1,21 @@ # Minecraft 26.1.2 +Version 2.2.17.2601021 is accepted for release after automated qualification and +manual testing. See the +[family qualification record](../../../PowerAdvantage/docs/26.1.2/QUALIFICATION.md) +for the exact tested jars, completed checks and scope limits. The usual fork, +MMD merge and CurseForge/GitHub/MMD Maven release workflow is authorized. +Publication has not happened. Source pushes, merges, tags and publisher runs +remain held until the verified public Power and Base Metals dependency handoff. +Power is released first, then Steam, then Electric. + +The existing source branch remains feature/baseelectric-26.1.2-port. Public file +IDs and merged-source pins are not filled from local candidates. Electric's +manifest records Power provenance, so the public-pin rebuild needs its own hash +and entry comparison. Earlier checkpoint notes, old holds and qualification +receipts below remain historical and bound to their original inputs, not proof +of a later public-pin jar. + This is a local port, not a release. The starting source is the released 1.21.11 merge `951fc3921f27e963e96f29c78a38ee7093a65fde`. @@ -9,7 +25,58 @@ Historical candidate01 results remain bound to their original inputs, including `D836D4AC22D1C46BFCD98B77A10A9475429D95141192B9C632F59DC87C273D73`. They must not be relabeled as candidate02 or full family acceptance. -## Current Checkpoint +## OreSpawn 4.1.1 Resume + +The resumed local qualification pins published OreSpawn 4.1.1.2601021, source +`101fd12b598934c353e417c57edc5c2bbefd5f53`, CurseForge file `9572926`. +Player and development SHA-256 are both +`810284ECB32D5249381F246498CEFA57E70FFF7CCC40110F799FC276CBC42BF7`. +The supplied public jar and the parent's frozen copy match that hash and retain +the actual `26.1.2-64.0.9` / Java 25 manifest. Provenance checks stay strict. + +The exact parent Power handoff is source +`d0bd0bdbde6c499e800e5a5b4ecc7f9ac63d2055`, player/development SHA-256 +`23ABCC1FEFDAA6988BF709437EB165C8948C757E773ADAB738092EB93CA8D2B0`. +The jar and all four unit-helper inputs were verified and frozen locally. +The clean full Gradle suite executes 52 tasks, with 117 unit tests and 28 +independent native resource checks passing without failures, errors or skips. +Javadoc, dependency, exact archive, checksum and Eclipse audits pass. +The independent warm-cache source export in a path with spaces passes the same +suite and reproduces all three release archives exactly. A genuinely empty-cache +export also passes all 52 tasks in 6m55s and reproduces all three archives. +The Gradle download and regenerated native Forge SDK use a new unseeded cache; +both the source and cache paths contain spaces and stay in this checkout. +The regenerated Forge binary and source jars also match the primary SDK exactly. + +The four old exact OreSpawn constants in the standalone audit and ECJ guard +were updated with user approval. The standalone source audit now passes all +nine checks. Fresh ECJ compiles 87 main and 36 test sources, the GameTest and +client-contract scopes, and passes all 117 tests with no ignored tests or +assumptions. The complete compiled inventory and strict negative controls remain +enabled. The original stale-pin failures remain separately preserved; the new +receipts are static-contracts-green-02.log and ecj-02/ecj-results.json under the +new Electric evidence directory. No assertion was relaxed or skipped. + +Final player SHA-256 is +`E2929ED56B4CEE1AD50B6B0D2BF1AF000AA15F2C9D66E5DFB3679B53FBDED9FC`. +Only the manifest differs from candidate09; all 576 other entries match. +No new gameplay or combined-family result is claimed here. The parent owns all +harnesses, scripts and combined profiles. Assigned actor/order evidence is +separate: all nine routes and both actual-order campaigns completed against +profile12. The three actor routes pass strictly; six dimension routes retain +PASS_WITH_NATIVE_DIFFERENCE for the independently controlled native wolf health +and max-health scaling. Original strict differences are preserved. Both actual +orders, six strict profile-boot audits, source/input custody and all 39 game +process closures pass. Instrumented order qualificationAllowed remains false. +The assigned handoff is actors-orders/final-owned-handoff-01.json under the +resume directory, SHA-256 +53E74959A4B3DF78F92652FB4C79C77BC185E326F23F525CB12E1CDBB2B48020. +This is not human gameplay acceptance or a global family verdict. New child evidence is +kept under +`D:/PowerAdvantage/_verification/base-2612-port-20261112/resume-orespawn411-20261112/electric`. +All historical results below retain their original candidate bindings. + +## Previous Candidate09 Checkpoint The candidate09 ordinary jar is `0911C5D68C81E3B0E4EE3A596F20E25E59A4EDE1BFDC96A36398A3799E754FC1`. @@ -161,10 +228,10 @@ remain historical, not relabeled as the atlas-repaired candidate06. - Minecraft 26.1.2, Forge 64.1.5, MCP 20260409.101008, official runtime names. - Temurin 25.0.3+9 for all roles, ForgeGradle 7.0.34, Gradle 9.6.1, `reobf=false`. - baseElectric 2.2.17.2601021; basePower 2.4.16.2601021 and Base Metals 3.1.2.2601021 required. -- OreSpawn 4.1.0.2601021, source `c45ea2a111520ef904e994dd8dcd90babfcef29b`, - ordinary jar SHA-256 `6D5CE2017C3314968083D140309E2F1A54BC239AC65C48944C995E9ECF937948`. +- OreSpawn 4.1.1.2601021, source `101fd12b598934c353e417c57edc5c2bbefd5f53`, + ordinary jar SHA-256 `810284ECB32D5249381F246498CEFA57E70FFF7CCC40110F799FC276CBC42BF7`. -The local Power checkpoint is `b947b4b81f3508fa220a212b41157ed2f801909f`; +The local Power checkpoint is `d0bd0bdbde6c499e800e5a5b4ecc7f9ac63d2055`; the Base Metals shared-hook checkpoint is `331fa46914f854db71a8c33500cc559a8878b7d3`. Their exact ordinary jar hashes are recorded in `gradle.properties`. These are local integration inputs, not public releases or completed family diff --git a/gradle.properties b/gradle.properties index 7e043231..c62a0670 100644 --- a/gradle.properties +++ b/gradle.properties @@ -44,10 +44,10 @@ poweradvantage_version=2.4.16.2601021 poweradvantage_pin_state=LOCAL_SOURCE poweradvantage_source_repository=MinecraftModDevelopmentMods/basePower # Owner-approved local integration checkpoint, not a public or family-qualified release. -poweradvantage_source_ref=b947b4b81f3508fa220a212b41157ed2f801909f +poweradvantage_source_ref=d0bd0bdbde6c499e800e5a5b4ecc7f9ac63d2055 poweradvantage_public_file_id= -poweradvantage_deobf_sha256=5309E7C433933F1426543310611292BF80388A476836139F0117E6A7E5C9AFC1 -poweradvantage_runtime_sha256=5309E7C433933F1426543310611292BF80388A476836139F0117E6A7E5C9AFC1 +poweradvantage_deobf_sha256=23ABCC1FEFDAA6988BF709437EB165C8948C757E773ADAB738092EB93CA8D2B0 +poweradvantage_runtime_sha256=23ABCC1FEFDAA6988BF709437EB165C8948C757E773ADAB738092EB93CA8D2B0 basemetals_version=3.1.2.2601021 basemetals_built_on=26.1.2-64.0.9 @@ -62,9 +62,9 @@ unit_bootstrap_java_sha256=D4B72FD65D9F8549B142507C6446E3B1E047B637784AF9FA74E8F unit_registries_java_sha256=01DDE50C9874C2DC501BE2F699CD8D16D6974FACFAAC1B803434CA8C64227ABF unit_loader_java_sha256=455CA9403D9CBEA72E7D055966D6CDC822E634418C14B3CAD0500588DEE35E5B -orespawn_version=4.1.0.2601021 +orespawn_version=4.1.1.2601021 orespawn_built_on=26.1.2-64.0.9 -orespawn_source_commit=c45ea2a111520ef904e994dd8dcd90babfcef29b -orespawn_public_file_id=9299079 -orespawn_deobf_sha256=6D5CE2017C3314968083D140309E2F1A54BC239AC65C48944C995E9ECF937948 -orespawn_runtime_sha256=6D5CE2017C3314968083D140309E2F1A54BC239AC65C48944C995E9ECF937948 +orespawn_source_commit=101fd12b598934c353e417c57edc5c2bbefd5f53 +orespawn_public_file_id=9572926 +orespawn_deobf_sha256=810284ECB32D5249381F246498CEFA57E70FFF7CCC40110F799FC276CBC42BF7 +orespawn_runtime_sha256=810284ECB32D5249381F246498CEFA57E70FFF7CCC40110F799FC276CBC42BF7 diff --git a/tools/verification/Verify-ElectricEcj.ps1 b/tools/verification/Verify-ElectricEcj.ps1 index 314d0c7a..e6e5a21c 100644 --- a/tools/verification/Verify-ElectricEcj.ps1 +++ b/tools/verification/Verify-ElectricEcj.ps1 @@ -76,7 +76,7 @@ try { if ($properties.mod_id -ne 'baseelectric' -or $properties.mod_version -ne '2.2.17.2601021' -or $properties.minecraft_version -ne '26.1.2' -or $properties.forge_version -ne '64.1.5' -or $properties.poweradvantage_version -ne '2.4.16.2601021' -or $properties.basemetals_version -ne '3.1.2.2601021' -or - $properties.orespawn_version -ne '4.1.0.2601021' -or + $properties.orespawn_version -ne '4.1.1.2601021' -or $properties.poweradvantage_pin_state -notin @('LOCAL_SOURCE', 'MERGED_SOURCE') -or $properties.poweradvantage_source_ref -cnotmatch '^[a-f0-9]{40}$' -or $properties.poweradvantage_runtime_sha256 -cnotmatch '^[A-F0-9]{64}$' -or diff --git a/tools/verify-native-port.js b/tools/verify-native-port.js index d3357256..c5b407a6 100644 --- a/tools/verify-native-port.js +++ b/tools/verify-native-port.js @@ -43,9 +43,9 @@ check('Exact Forge 64 and local port identity', () => { mapping_version: '26.1.2', mcp_version: '20260409.101008', java_version: '25', mod_id: 'baseelectric', mod_name: 'baseElectric', mod_version: '2.2.17.2601021', poweradvantage_version: '2.4.16.2601021', basemetals_version: '3.1.2.2601021', - orespawn_source_commit: 'c45ea2a111520ef904e994dd8dcd90babfcef29b', - orespawn_deobf_sha256: '6D5CE2017C3314968083D140309E2F1A54BC239AC65C48944C995E9ECF937948', - orespawn_version: '4.1.0.2601021', curseforge_project_id: '1783006', + orespawn_source_commit: '101fd12b598934c353e417c57edc5c2bbefd5f53', + orespawn_deobf_sha256: '810284ECB32D5249381F246498CEFA57E70FFF7CCC40110F799FC276CBC42BF7', + orespawn_version: '4.1.1.2601021', curseforge_project_id: '1783006', cf_requirements: 'basepower,base-metals' })) assert.equal(props[key], value, key); assert.equal(props.reobf, 'false'); From 798ad77fddcca27abf219f426d042786c2fafd82 Mon Sep 17 00:00:00 2001 From: JohnBraham Date: Thu, 12 Nov 2026 16:36:52 +0000 Subject: [PATCH 7/7] Pin the released 26.1.2 basePower and Base Metals jars --- CHANGELOG.txt | 2 +- README.md | 16 +++++++++------- gradle.properties | 14 +++++++------- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 06e85884..68912b29 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -5,7 +5,7 @@ baseElectric 2.2.17.2601021 (Minecraft 26.1.2 / Forge) - Keep all six librarian offers and their original ranges and tiers in native trade data. - Fix laser-beam texture loading and inherited battery models. - Preserve saved OreSpawn choices before generation and stop on ambiguous migrations. -- Use OreSpawn 4.1.1.2601021 with the coordinated basePower and Base Metals builds. +- Pin released basePower 2.4.16.2601021, Base Metals 3.1.2.2601021 and OreSpawn 4.1.1.2601021. - Complete the local machine, upgrade, graphical, endurance and manual-acceptance checks. baseElectric 2.2.16.121111 (Minecraft 1.21.11 / Forge) diff --git a/README.md b/README.md index b624d735..bdfea9a5 100644 --- a/README.md +++ b/README.md @@ -16,17 +16,19 @@ for the exact tested jars, completed checks and scope limits. Manual acceptance is complete. Publication is being prepared; this version is not public yet. basePower **2.4.16.2601021** and Base Metals **3.1.2.2601021** are required on -both client and server. Their local source and ordinary-jar checkpoints are -recorded in `gradle.properties`; they remain local inputs, not public releases. +both client and server. Their public source and ordinary-jar checkpoints are +recorded in `gradle.properties`. The build rejects old 1.21.11 inputs. OreSpawn **4.1.1.2601021** is pinned to its public release (CurseForge 9572926). See the [26.1.2 notes](docs/NATIVE2612.md). -The accepted qualification uses Power source `d0bd0bdb` / jar `23ABCC1F` and the -exact local Base Metals hook. Verified public dependency pins are still needed -before release CI and publication. Power is published first, then Steam, then -Electric. Electric's manifest records its Power source and artifact provenance; -the public-pin rebuild will have a separate byte comparison and receipt. +The public pins use Power source `a684e71e` / jar `23ABCC1F` (CurseForge 9576332) +and Base Metals source `5e708468` / jar `67862388` (9576007). Their public +downloads match the accepted dependency jars. Power is published first, then +Steam, then Electric. Electric's promoted jar changes only its manifest to +record the merged Power source; every other archive entry is byte-identical +to the accepted candidate. Restart and endurance qualification for those final +jar bytes is recorded separately from the earlier candidate's results. The new clean release suite passes 117 unit tests and 28 native checks, and its warm and fresh empty-cache path-with-spaces reproductions match all three archives. Fresh ECJ passes all 117 tests with the complete compiled inventory, diff --git a/gradle.properties b/gradle.properties index c62a0670..3a82cf50 100644 --- a/gradle.properties +++ b/gradle.properties @@ -41,19 +41,19 @@ cf_optionals= cf_versions=26.1.2 poweradvantage_version=2.4.16.2601021 -poweradvantage_pin_state=LOCAL_SOURCE +poweradvantage_pin_state=MERGED_SOURCE poweradvantage_source_repository=MinecraftModDevelopmentMods/basePower -# Owner-approved local integration checkpoint, not a public or family-qualified release. -poweradvantage_source_ref=d0bd0bdbde6c499e800e5a5b4ecc7f9ac63d2055 -poweradvantage_public_file_id= +# Exact merged parent source; the hosted gate also checks its public player jar. +poweradvantage_source_ref=a684e71e5397cb5a1a4879dcf0b6a2be0815e85d +poweradvantage_public_file_id=9576332 poweradvantage_deobf_sha256=23ABCC1FEFDAA6988BF709437EB165C8948C757E773ADAB738092EB93CA8D2B0 poweradvantage_runtime_sha256=23ABCC1FEFDAA6988BF709437EB165C8948C757E773ADAB738092EB93CA8D2B0 basemetals_version=3.1.2.2601021 basemetals_built_on=26.1.2-64.0.9 -# Local native shared-hook checkpoint; family qualification is separate. -basemetals_source_commit=331fa46914f854db71a8c33500cc559a8878b7d3 -basemetals_public_file_id= +# Published shared-hook patch; family qualification is recorded separately. +basemetals_source_commit=5e708468302a7fb3b35aca09d5f832f8882f88f3 +basemetals_public_file_id=9576007 basemetals_deobf_sha256=6786238835D4D8A653C769F3B0E61D226959146300E17523799CFDF17540FD19 basemetals_runtime_sha256=6786238835D4D8A653C769F3B0E61D226959146300E17523799CFDF17540FD19