diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8d0bfcc..29f66e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,16 +1,16 @@ -name: baseElectric 1.17 CI +name: baseElectric 1.18 CI on: push: - branches: [master-1.17.1, 'feature/**'] + branches: [master-1.18.2, 'feature/**'] pull_request: - branches: [master-1.17.1] + branches: [master-1.18.2] permissions: contents: read concurrency: - group: baseelectric-1.17-${{ github.workflow }}-${{ github.ref }} + group: baseelectric-1.18-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: @@ -24,7 +24,7 @@ jobs: - name: Use canonical LF dependency inputs # Apply before every dependency checkout, including OreSpawn. run: git config --global core.autocrlf false - - name: Require coordinated 1.17.1 dependency pins + - name: Require coordinated 1.18.2 dependency pins id: pins shell: bash run: | @@ -35,9 +35,9 @@ jobs: bm_ref="$(sed -n 's/^basemetals_source_commit=//p' gradle.properties | tr -d '\r')" bm_sha="$(sed -n 's/^basemetals_deobf_sha256=//p' gradle.properties | tr -d '\r')" bootstrap_sha="$(sed -n 's/^poweradvantage_unit_bootstrap_sha256=//p' gradle.properties | tr -d '\r')" - if [[ "$pin_state" != "LOCAL_CHECKPOINT" || ! "$power_ref" =~ ^[a-f0-9]{40}$ || ! "$bm_ref" =~ ^[a-f0-9]{40}$ || + if [[ "$pin_state" != "PUBLISHED_RELEASE" || ! "$power_ref" =~ ^[a-f0-9]{40}$ || ! "$bm_ref" =~ ^[a-f0-9]{40}$ || ! "$power_sha" =~ ^[A-F0-9]{64}$ || ! "$bm_sha" =~ ^[A-F0-9]{64}$ || ! "$bootstrap_sha" =~ ^[A-F0-9]{64}$ ]]; then - echo "The coordinated basePower and Base Metals 1.17.1 pins are pending" >&2 + echo "The coordinated basePower and Base Metals 1.18.2 pins are pending" >&2 exit 1 fi echo "power_ref=$power_ref" >> "$GITHUB_OUTPUT" @@ -46,20 +46,18 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: repository: MinecraftModDevelopmentMods/OreSpawn - ref: b56e2b62a1a6ea4d1dd96f8646a87801a817dbdf + ref: d31f52f296739f34a0c13f554abf3e1ac071ae25 path: .ci-dependencies/OreSpawn - name: Check out pinned Base Metals uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: repository: MinecraftModDevelopmentMods/BaseMetals - # Local-only startup fix; hosted checkout must wait for authorized source availability. ref: ${{ steps.pins.outputs.basemetals_ref }} path: .ci-dependencies/BaseMetals - - name: Check out basePower 1.17.1 + - name: Check out basePower 1.18.2 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: repository: MinecraftModDevelopmentMods/basePower - # Local-only Power source; publish upstream before hosted builds. ref: ${{ steps.pins.outputs.power_ref }} path: .ci-dependencies/PowerAdvantage - name: Install pinned Java 25 Mavenizer runtime @@ -67,11 +65,6 @@ jobs: with: distribution: temurin java-version: '25.0.3+9.0.LTS' - - name: Install Java 16 production toolchain - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 - with: - distribution: temurin - java-version: '16.0.2+7' - name: Install Java 8 build utilities uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 with: @@ -86,12 +79,22 @@ jobs: # These are Gradle project properties, not JVM system properties. shell: bash run: >- - echo "MMD_GRADLE_TOOLCHAINS=-Porg.gradle.java.installations.paths=$JAVA_HOME,$JAVA_HOME_16_X64,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64 + echo "MMD_GRADLE_TOOLCHAINS=-Porg.gradle.java.installations.paths=$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64 -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: Build 1.17.1 dependency chain + - name: Stage the pinned Forge 40 Mavenizer compatibility fixture + id: mavenizer + shell: bash + run: | + set -euo pipefail + fixture="$RUNNER_TEMP/minecraft-mavenizer-0.5.21-orespawn-1.18.jar" + curl --fail --silent --show-error --location --retry 3 --output "$fixture" \ + 'https://raw.githubusercontent.com/MinecraftModDevelopmentMods/IronAgeFurniture/8283a09d4c75dc01a5b62a912be722654e960de8/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-1.18.jar' + printf '%s %s\n' '28A6697BDF6D9500EC0AF9C1C949B9F3ED7DDFDA649CB5CAC4BEEA2E9567570A' "$fixture" | sha256sum --check + echo "fixture=$fixture" >> "$GITHUB_OUTPUT" + - name: Build 1.18.2 dependency chain id: dependencies shell: bash run: | @@ -104,47 +107,71 @@ jobs: chmod +x "$orespawn/gradlew" "$basemetals/gradlew" "$power/gradlew" (cd "$orespawn" && ./gradlew $MMD_GRADLE_TOOLCHAINS jar --no-daemon --stacktrace) (cd "$basemetals" && ./gradlew $MMD_GRADLE_TOOLCHAINS jar --no-daemon --stacktrace) - osjar="$orespawn/build/libs-dev/OreSpawn-4.1.0.117011-deobf.jar" - bmjar="$basemetals/build/libs-dev/BaseMetals-3.1.1.117011-deobf.jar" + osjar="$orespawn/build/libs-dev/OreSpawn-4.1.0.118021-deobf.jar" + bmjar="$basemetals/build/libs-dev/BaseMetals-3.1.2.118021-deobf.jar" (cd "$power" && ./gradlew $MMD_GRADLE_TOOLCHAINS jar verifyReleaseDependencies \ -PbaseMetalsDeobfJar="$bmjar" -PoreSpawnDeobfJar="$osjar" \ --no-daemon --stacktrace) - powerjar="$power/build/libs-dev/basePower-2.4.9.117011-deobf.jar" + powerjar="$power/build/libs-dev/basePower-2.4.10.118021-deobf.jar" test -f "$osjar" && test -f "$bmjar" && test -f "$powerjar" - release="$RUNNER_TEMP/basePower-2.4.9.117011.jar" + release="$RUNNER_TEMP/basePower-2.4.10.118021.jar" curl --fail --silent --show-error --location --retry 3 --output "$release" \ - 'https://maven.moddev.zone/releases/zone/moddev/mc/basepower/basePower/2.4.9.117011/basePower-2.4.9.117011.jar' + 'https://maven.moddev.zone/releases/zone/moddev/mc/basepower/basePower/2.4.10.118021/basePower-2.4.10.118021.jar' power_release_sha=$(sed -n 's/^poweradvantage_runtime_sha256=//p' gradle.properties | tr -d '\r') printf '%s %s\n' "$power_release_sha" "$release" | sha256sum --check + pin() { sed -n "s/^$1=//p" gradle.properties | tr -d '\r'; } + fetch_runtime() { + local key="$1" url="$2" destination="$3" expected="$4" + [[ "$expected" =~ ^[A-F0-9]{64}$ ]] + curl --fail --silent --show-error --location --retry 3 --output "$destination" "$url" + printf '%s %s\n' "$expected" "$destination" | sha256sum --check + echo "$key=$destination" >> "$GITHUB_OUTPUT" + } + bm_version="$(pin basemetals_version)" + os_version="$(pin orespawn_version)" + forge_target="$(pin minecraft_version)-$(pin forge_version)" + maven='https://maven.moddev.zone/releases/zone/moddev/mc' + fetch_runtime basemetals_runtime "$maven/basemetals/BaseMetals/$bm_version/BaseMetals-$bm_version.jar" \ + "$RUNNER_TEMP/BaseMetals-$bm_version.jar" "$(pin basemetals_runtime_sha256)" + fetch_runtime orespawn_runtime "$maven/orespawn/OreSpawn/$os_version/OreSpawn-$os_version.jar" \ + "$RUNNER_TEMP/OreSpawn-$os_version.jar" "$(pin orespawn_runtime_sha256)" + fetch_runtime forge_installer "https://maven.minecraftforge.net/net/minecraftforge/forge/$forge_target/forge-$forge_target-installer.jar" \ + "$RUNNER_TEMP/forge-$forge_target-installer.jar" "$(pin forge_installer_sha256)" echo "orespawn=$osjar" >> "$GITHUB_OUTPUT" echo "basemetals=$bmjar" >> "$GITHUB_OUTPUT" echo "power=$powerjar" >> "$GITHUB_OUTPUT" + echo "power_runtime=$release" >> "$GITHUB_OUTPUT" - name: Build and audit release artifacts run: >- ./gradlew $MMD_GRADLE_TOOLCHAINS clean check build javadoc verifyReleaseDependencies verifyReleaseArtifacts - writeReleaseChecksums verifyEclipseProductionClasspath + writeReleaseChecksums verifyEclipseProductionClasspath verifyLocalRuntimeInputs -PpowerAdvantageDeobfJar=${{ steps.dependencies.outputs.power }} + -PmavenizerCompatibilityJar=${{ steps.mavenizer.outputs.fixture }} -PpowerAdvantageUnitBootstrapSource=${{ github.workspace }}/.ci-dependencies/PowerAdvantage/test-rig/unit-bootstrap/ForgeCapabilityBootstrap.java -PbaseMetalsDeobfJar=${{ steps.dependencies.outputs.basemetals }} -PoreSpawnDeobfJar=${{ steps.dependencies.outputs.orespawn }} + -PpowerAdvantageRuntimeJar=${{ steps.dependencies.outputs.power_runtime }} + -PbaseMetalsRuntimeJar=${{ steps.dependencies.outputs.basemetals_runtime }} + -PoreSpawnRuntimeJar=${{ steps.dependencies.outputs.orespawn_runtime }} + -PforgeInstallerJar=${{ steps.dependencies.outputs.forge_installer }} --no-daemon --stacktrace - name: Upload audited release candidate uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: baseElectric-1.17.1-${{ github.sha }} + name: baseElectric-1.18.2-${{ github.sha }} if-no-files-found: error retention-days: 30 path: | - build/libs/baseElectric-2.2.10.117011.jar - build/libs/baseElectric-2.2.10.117011-sources.jar - build/libs/baseElectric-2.2.10.117011-javadoc.jar + build/libs/baseElectric-2.2.11.118021.jar + build/libs/baseElectric-2.2.11.118021-sources.jar + build/libs/baseElectric-2.2.11.118021-javadoc.jar build/release/SHA256SUMS CHANGELOG.txt - name: Upload diagnostics on failure if: failure() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: baseElectric-1.17.1-diagnostics-${{ github.sha }} + name: baseElectric-1.18.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 24a0fcb..f5937c4 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -2,9 +2,9 @@ name: CodeQL on: push: - branches: [master-1.17.1, 'feature/**'] + branches: [master-1.18.2, 'feature/**'] pull_request: - branches: [master-1.17.1] + branches: [master-1.18.2] schedule: - cron: '59 7 * * 4' @@ -24,7 +24,7 @@ jobs: - name: Use canonical LF dependency inputs # Apply before every dependency checkout, including OreSpawn. run: git config --global core.autocrlf false - - name: Require coordinated 1.17.1 dependency pins + - name: Require coordinated 1.18.2 dependency pins id: pins shell: bash run: | @@ -35,9 +35,9 @@ jobs: bm_ref="$(sed -n 's/^basemetals_source_commit=//p' gradle.properties | tr -d '\r')" bm_sha="$(sed -n 's/^basemetals_deobf_sha256=//p' gradle.properties | tr -d '\r')" bootstrap_sha="$(sed -n 's/^poweradvantage_unit_bootstrap_sha256=//p' gradle.properties | tr -d '\r')" - if [[ "$pin_state" != "LOCAL_CHECKPOINT" || ! "$power_ref" =~ ^[a-f0-9]{40}$ || ! "$bm_ref" =~ ^[a-f0-9]{40}$ || + if [[ "$pin_state" != "PUBLISHED_RELEASE" || ! "$power_ref" =~ ^[a-f0-9]{40}$ || ! "$bm_ref" =~ ^[a-f0-9]{40}$ || ! "$power_sha" =~ ^[A-F0-9]{64}$ || ! "$bm_sha" =~ ^[A-F0-9]{64}$ || ! "$bootstrap_sha" =~ ^[A-F0-9]{64}$ ]]; then - echo "The coordinated basePower and Base Metals 1.17.1 pins are pending" >&2 + echo "The coordinated basePower and Base Metals 1.18.2 pins are pending" >&2 exit 1 fi echo "power_ref=$power_ref" >> "$GITHUB_OUTPUT" @@ -46,20 +46,18 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: repository: MinecraftModDevelopmentMods/OreSpawn - ref: b56e2b62a1a6ea4d1dd96f8646a87801a817dbdf + ref: d31f52f296739f34a0c13f554abf3e1ac071ae25 path: .ci-dependencies/OreSpawn - name: Check out pinned Base Metals uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: repository: MinecraftModDevelopmentMods/BaseMetals - # Local-only startup fix; hosted checkout must wait for authorized source availability. ref: ${{ steps.pins.outputs.basemetals_ref }} path: .ci-dependencies/BaseMetals - - name: Check out basePower 1.17.1 + - name: Check out basePower 1.18.2 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: repository: MinecraftModDevelopmentMods/basePower - # Local-only Power source; publish upstream before hosted builds. ref: ${{ steps.pins.outputs.power_ref }} path: .ci-dependencies/PowerAdvantage - name: Install pinned Java 25 Mavenizer runtime @@ -67,11 +65,6 @@ jobs: with: distribution: temurin java-version: '25.0.3+9.0.LTS' - - name: Install Java 16 production toolchain - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 - with: - distribution: temurin - java-version: '16.0.2+7' - name: Install Java 8 build utilities uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 with: @@ -86,12 +79,22 @@ jobs: # These are Gradle project properties, not JVM system properties. shell: bash run: >- - echo "MMD_GRADLE_TOOLCHAINS=-Porg.gradle.java.installations.paths=$JAVA_HOME,$JAVA_HOME_16_X64,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64 + echo "MMD_GRADLE_TOOLCHAINS=-Porg.gradle.java.installations.paths=$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64 -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: Build 1.17.1 dependency chain + - name: Stage the pinned Forge 40 Mavenizer compatibility fixture + id: mavenizer + shell: bash + run: | + set -euo pipefail + fixture="$RUNNER_TEMP/minecraft-mavenizer-0.5.21-orespawn-1.18.jar" + curl --fail --silent --show-error --location --retry 3 --output "$fixture" \ + 'https://raw.githubusercontent.com/MinecraftModDevelopmentMods/IronAgeFurniture/8283a09d4c75dc01a5b62a912be722654e960de8/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-1.18.jar' + printf '%s %s\n' '28A6697BDF6D9500EC0AF9C1C949B9F3ED7DDFDA649CB5CAC4BEEA2E9567570A' "$fixture" | sha256sum --check + echo "fixture=$fixture" >> "$GITHUB_OUTPUT" + - name: Build 1.18.2 dependency chain id: dependencies shell: bash run: | @@ -104,12 +107,12 @@ jobs: chmod +x "$orespawn/gradlew" "$basemetals/gradlew" "$power/gradlew" (cd "$orespawn" && ./gradlew $MMD_GRADLE_TOOLCHAINS jar --no-daemon --stacktrace) (cd "$basemetals" && ./gradlew $MMD_GRADLE_TOOLCHAINS jar --no-daemon --stacktrace) - osjar="$orespawn/build/libs-dev/OreSpawn-4.1.0.117011-deobf.jar" - bmjar="$basemetals/build/libs-dev/BaseMetals-3.1.1.117011-deobf.jar" + osjar="$orespawn/build/libs-dev/OreSpawn-4.1.0.118021-deobf.jar" + bmjar="$basemetals/build/libs-dev/BaseMetals-3.1.2.118021-deobf.jar" (cd "$power" && ./gradlew $MMD_GRADLE_TOOLCHAINS jar verifyReleaseDependencies \ -PbaseMetalsDeobfJar="$bmjar" -PoreSpawnDeobfJar="$osjar" \ --no-daemon --stacktrace) - powerjar="$power/build/libs-dev/basePower-2.4.9.117011-deobf.jar" + powerjar="$power/build/libs-dev/basePower-2.4.10.118021-deobf.jar" test -f "$osjar" && test -f "$bmjar" && test -f "$powerjar" echo "orespawn=$osjar" >> "$GITHUB_OUTPUT" echo "basemetals=$bmjar" >> "$GITHUB_OUTPUT" @@ -124,6 +127,7 @@ jobs: chmod +x ./gradlew ./gradlew $MMD_GRADLE_TOOLCHAINS clean classes --rerun-tasks --no-build-cache --no-daemon --stacktrace \ -PpowerAdvantageDeobfJar=${{ steps.dependencies.outputs.power }} \ + -PmavenizerCompatibilityJar=${{ steps.mavenizer.outputs.fixture }} \ -PbaseMetalsDeobfJar=${{ steps.dependencies.outputs.basemetals }} \ -PoreSpawnDeobfJar=${{ steps.dependencies.outputs.orespawn }} - name: Analyze diff --git a/.github/workflows/validate-gradle-build.yml b/.github/workflows/validate-gradle-build.yml index 0bf04b8..afe21ad 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.17.1 + - master-1.18.2 - 'feature/**' pull_request: branches: - - master-1.17.1 + - master-1.18.2 permissions: contents: read diff --git a/CHANGELOG.txt b/CHANGELOG.txt index f960d3a..acd64e6 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,9 @@ +baseElectric 2.2.11.118021 (Minecraft 1.18.2) + +- Bring baseElectric to Minecraft 1.18.2, with the same machines and recipes. +- Keep saved power, inventories, drill facings and hydroturbines when upgrading. +- Use the released basePower 2.4.10.118021 and Base Metals 3.1.2 save hook. + baseElectric 2.2.10.117011 (Minecraft 1.17.1) - Continue Electric Advantage as baseElectric, with its blocks and items in the shared basePower creative tab. diff --git a/README.md b/README.md index d6a44c0..a846bb3 100644 --- a/README.md +++ b/README.md @@ -4,18 +4,18 @@ baseElectric continues DrCyano's Electric Advantage. It adds electric machines, generation, storage, automation and defence to basePower, and its blocks and items appear in the shared basePower creative tab. -This is the local **Minecraft 1.17.1 candidate, 2.2.10.117011**, not a published -release. Back up your world before upgrading. The port retains the mappings for +This is **baseElectric 2.2.11.118021 for Minecraft 1.18.2**. +Back up your world before upgrading. The port retains the mappings for older Electric Advantage saves, as well as saves that already use baseElectric IDs. ## Target -- Forge `37.1.1`, official `1.17.1` mappings, MCP `20210706.113038`. +- Forge `40.3.0`, official `1.18.2` mappings, MCP `20220404.173914`. - ForgeGradle `7.0.34`, Renamer `1.1.5`, Gradle `9.6.1`. - Java 17 runs Gradle; Java 25 runs Mavenizer. Production and tests use - Java `16.0.2+7`, class major `60`. The Eclipse generator also needs Java 8 - for ForgeGradle's Slime Launcher tooling; the game launches use Java 16. -- Production artifact: `build/libs/baseElectric-2.2.10.117011.jar`. + Java 17, class major `61`. The Eclipse generator also needs Java 8 + for ForgeGradle's Slime Launcher tooling; the game launches use Java 17. +- Player artifact: `build/libs/baseElectric-2.2.11.118021.jar`. The separate deobfuscated API artifact stays under `build/libs-dev`. basePower is required. Base Metals remains optional in Forge metadata; @@ -24,54 +24,51 @@ basePower, Base Metals and OreSpawn are separate dependencies, never embedded. ## Current State -The identity port keeps the native 1.17.1 models, shader, GUI, loot and recipe -book behavior. The 96-test suite is retained, with additional legacy ID cases in -the existing migration test. The prior Electric Advantage build and ECJ results -are historical; they do not qualify this candidate. Local builds are recorded -against exact source and artifact hashes. Packaged world migration and manual -gameplay are separate checks. - -See [the native port notes](docs/NATIVE117.md) for the API audit and remaining gates. -The retained [1.16 notes](docs/NATIVE116.md) describe the previous target, not proof -that this one passes. +The port keeps the 96-test suite, registry paths, machine NBT, loot and recipes. +See [the 1.18.2 port notes](docs/NATIVE118.md) for the exact inputs, evidence and +remaining gates. Results in the [1.17.1 notes](docs/NATIVE117.md) and +[1.16.5 notes](docs/NATIVE116.md) are historical. ## Dependencies -The build pins the merged source and the published -[basePower 2.4.9.117011 player jar](https://maven.moddev.zone/releases/zone/moddev/mc/basepower/basePower/2.4.9.117011/basePower-2.4.9.117011.jar). -The development jar, player jar and test bootstrap have separate SHA-256 -pins in `gradle.properties`. Base Metals `3.1.1.117011` includes the published -startup fix; its source and artifacts are pinned there too. -OreSpawn `4.1.0.117011` uses public source -`b56e2b62a1a6ea4d1dd96f8646a87801a817dbdf`. -Record the runtime SHA-256 values separately from the development jar hashes. -Local runtime qualification must use the exact coordinated jars; it does not -establish public availability or release readiness. +The [basePower 2.4.10.118021 release](https://github.com/MinecraftModDevelopmentMods/basePower/releases/tag/2.4.10.118021) +provides the player jar. The build pins its source, development jar and test +bootstrap separately. Base Metals +`3.1.2.118021`, OreSpawn `4.1.0.118021` and the Forge installer also have +explicit hashes in `gradle.properties`. Hosted checks build the development +dependencies and verify the published basePower jar from MMD Maven. -Once the pins are set, run from this repository with the arguments below: +Run from this repository with the explicit input arguments below: ```text -./gradlew clean check build javadoc verifyReleaseDependencies verifyReleaseArtifacts writeReleaseChecksums verifyEclipseProductionClasspath +./gradlew --no-build-cache --rerun-tasks build verifyLocalRuntimeInputs writeReleaseChecksums prepareEclipse verifyEclipseProductionClasspath ``` On Windows, use `gradlew.bat` instead of `./gradlew`. Set `JAVA_HOME` to Java 17 -for Gradle and install the Java 16 production toolchain, Java 25 Mavenizer -runtime and Java 8 build tooling listed above. +for Gradle and production, with Java 25 for Mavenizer and Java 8 for Eclipse +launch generation. The checksum-verified IAF118 Mavenizer fixture fixes a +decompiler error in Forge 40's `Holder.Direct` record. Append these arguments using immutable local inputs. In particular, the build does not discover a basePower jar or bootstrap source in a sibling checkout: ```text --PpowerAdvantageDeobfJar="/path/to/basePower-2.4.9.117011-deobf.jar" --PbaseMetalsDeobfJar="/path/to/BaseMetals-3.1.1.117011-deobf.jar" --PoreSpawnDeobfJar="/path/to/OreSpawn-4.1.0.117011-deobf.jar" +-PpowerAdvantageDeobfJar="/path/to/basePower-2.4.10.118021-deobf.jar" +-PpowerAdvantageRuntimeJar="/path/to/basePower-2.4.10.118021.jar" +-PbaseMetalsDeobfJar="/path/to/BaseMetals-3.1.2.118021-deobf.jar" +-PbaseMetalsRuntimeJar="/path/to/BaseMetals-3.1.2.118021.jar" +-PoreSpawnDeobfJar="/path/to/OreSpawn-4.1.0.118021-deobf.jar" +-PoreSpawnRuntimeJar="/path/to/OreSpawn-4.1.0.118021.jar" -PpowerAdvantageUnitBootstrapSource="/path/to/ForgeCapabilityBootstrap.java" +-PmavenizerCompatibilityJar="/path/to/minecraft-mavenizer-0.5.21-orespawn-1.18.jar" +-PforgeInstallerJar="/path/to/forge-1.18.2-40.3.0-installer.jar" ``` The build checks these inputs against the pinned hashes; do not substitute jars from another Minecraft version. If Gradle cannot find the installed JDKs, pass -`-Porg.gradle.java.installations.paths=,,,`. -No machine-specific helper or shared Gradle cache is required. +`-Porg.gradle.java.installations.paths=,,`. +For a prepared offline cache, also set `ORESPAWN_MAVENIZER_OFFLINE=true` and use +Gradle's `--offline` flag. Tests use basePower's hash-checked, build-only Forge capability bootstrap source from `-PpowerAdvantageUnitBootstrapSource`. Electric's build-only wrapper also @@ -83,17 +80,17 @@ after Gradle has produced a fresh classpath, unit bootstrap jar and complete tes Static checks need no Gradle or game launch: ```text -node tools/verify-native-port.js --report build/baseelectric-117-static-unique.json +node tools/verify-native-port.js --report build/baseelectric-118-static-unique.json node tools/generate-recipe-unlocks.js --check ``` -Use a new report name on each run. The audit checks the merged 1.16.5 and -pre-rename native 1.17.1 baselines. It does not prove compilation, native ticking, +Use a new report name on each run. The audit checks the historical native +baselines and the exact port delta from the PA117 clone point. It does not prove compilation, native ticking, shader compilation or migration safety. ## Compatibility -The mod ID is now `baseelectric` and the implementation package is +The mod ID remains `baseelectric` and the implementation package is `zone.moddev.mc.baseelectric`. The old namespace maps to the new one through basePower's migration API. Registry paths after the namespace, machine NBT keys, and the legacy `cyano.poweradvantage.api` integration are retained. @@ -110,7 +107,7 @@ launches. Generate metadata through `verifyEclipseProductionClasspath`; do not hand-edit generated classpaths or disable restricted-reference tests. Repeated refreshes replace stale runtime entries while keeping the native Forge launcher and excluding compile-only libraries from production launches. -Use Java `16.0.2+7` for the generated client and server launches, not Java 8. +Use Java 17 for the generated client and server launches, not Java 8. Java 17 is for Gradle and Buildship; Java 25 is for Mavenizer. Test only disposable copies of worlds. Preserve originals, backups, failed diff --git a/build.gradle b/build.gradle index cb30e36..13b51e6 100644 --- a/build.gradle +++ b/build.gradle @@ -42,9 +42,41 @@ ext.functional_version = versionParts[0..2].join('.') ext.display_version = project.mod_version ext.release_tag = project.mod_version +def sha256Of = { File input -> + MessageDigest.getInstance('SHA-256').digest(input.bytes).encodeHex().toString().toUpperCase() +} + +def pinnedFile = { String property, String expected -> + if (!(expected ==~ /[A-F0-9]{64}/)) { + throw new GradleException("No approved SHA-256 for -P${property}.") + } + File input = providers.gradleProperty(property).map { file(it) }.orNull + if (input == null || !input.isFile()) { + throw new GradleException("Pass the exact local input with -P${property}= (received ${input}).") + } + String actual = sha256Of(input) + if (actual != expected) { + throw new GradleException("${property} SHA-256 mismatch for ${input}: expected ${expected}, got ${actual}.") + } + input +} + +// The IAF118/OreSpawn fixture fixes the Forge 40 Holder record decompilation. +// Its build-tool JVM is separate from the Java 17 production/test JVM. +fgtools.configure('mavenizer') { + classpath.from(providers.provider { + pinnedFile('mavenizerCompatibilityJar', '28A6697BDF6D9500EC0AF9C1C949B9F3ED7DDFDA649CB5CAC4BEEA2E9567570A') + }) + mainClass.set('net.minecraftforge.mcmaven.cli.Main') + javaLauncher.set(javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(25) + vendor = JvmVendorSpec.ADOPTIUM + }) +} + java { toolchain { - languageVersion = JavaLanguageVersion.of(16) + languageVersion = JavaLanguageVersion.of(17) vendor = JvmVendorSpec.ADOPTIUM } withSourcesJar() @@ -53,31 +85,24 @@ java { tasks.withType(JavaCompile).configureEach { javaCompiler = javaToolchains.compilerFor { - languageVersion = JavaLanguageVersion.of(16) + languageVersion = JavaLanguageVersion.of(17) vendor = JvmVendorSpec.ADOPTIUM } - sourceCompatibility = JavaVersion.VERSION_16 - targetCompatibility = JavaVersion.VERSION_16 + options.release = 17 options.encoding = 'UTF-8' options.compilerArgs.addAll(['-Xmaxerrs', '1000']) } -def unitBootstrapSourcePath = providers.gradleProperty('powerAdvantageUnitBootstrapSource') +def unitBootstrapSource = providers.provider { + pinnedFile('powerAdvantageUnitBootstrapSource', project.poweradvantage_unit_bootstrap_sha256) +} def compileUnitBootstrap = tasks.register('compileUnitBootstrap', JavaCompile) { - if (!unitBootstrapSourcePath.isPresent()) { - throw new GradleException('Pass the pinned basePower source with -PpowerAdvantageUnitBootstrapSource=.') - } - File unitBootstrapSource = file(unitBootstrapSourcePath.get()) source unitBootstrapSource, file('tools/verification/ForgeNativeTestBootstrap.java') classpath = files() destinationDirectory = layout.buildDirectory.dir('unit-bootstrap/classes') inputs.property('approvedSourceSha256', project.poweradvantage_unit_bootstrap_sha256) doFirst { - String hash = MessageDigest.getInstance('SHA-256').digest(unitBootstrapSource.bytes) - .encodeHex().toString().toUpperCase() - if (hash != project.poweradvantage_unit_bootstrap_sha256) { - throw new GradleException('Verified native capability bootstrap source changed') - } + unitBootstrapSource.get() } } def unitBootstrapJar = tasks.register('unitBootstrapJar', Jar) { @@ -92,7 +117,7 @@ tasks.named('test', Test) { dependsOn unitBootstrapJar doFirst { jvmArgs "-javaagent:${unitBootstrapJar.get().archiveFile.get().asFile}" } javaLauncher = javaToolchains.launcherFor { - languageVersion = JavaLanguageVersion.of(16) + languageVersion = JavaLanguageVersion.of(17) vendor = JvmVendorSpec.ADOPTIUM } afterSuite { descriptor, result -> @@ -163,7 +188,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.17 launches.' + description = 'Merges classes and processed resources for Forge 1.18.2 launches.' dependsOn tasks.named('classes') from sourceSets.main.output into forgeRunModClassesDirectory @@ -194,48 +219,26 @@ repositories { } } -// Require the coordinated artifacts before resolving or reading any development jar. -def requireDevelopmentPins = { - if (project.poweradvantage_pin_state != 'LOCAL_CHECKPOINT' - || !(project.poweradvantage_source_ref ==~ /[a-f0-9]{40}/) - || !(project.basemetals_source_commit ==~ /[a-f0-9]{40}/) - || ![project.poweradvantage_deobf_sha256, project.poweradvantage_unit_bootstrap_sha256, - project.basemetals_deobf_sha256, project.orespawn_deobf_sha256].every { it ==~ /[A-F0-9]{64}/ }) { - throw new GradleException('The coordinated basePower and Base Metals 1.17.1 source and API pins are pending') +def requirePowerPin = { + if (project.poweradvantage_pin_state != 'PUBLISHED_RELEASE' + || project.poweradvantage_source_ref != 'bcf5105ef5c10ba36c26c8ff941742fecc3e11a1' + || project.poweradvantage_deobf_sha256 != '767BABC0D6E1536DCB937B9F1DC565AF348B3CAF92D3207671EF1E1C8D8EF5D4' + || project.poweradvantage_runtime_sha256 != '8CA46D5BCBFFDDAEB4F40137B5BF71E5AF7DD36515E5BFEA751FC5C02249823B' + || project.poweradvantage_unit_bootstrap_sha256 != '025F16FCA9FD5CFC585931E7C8BCFAB22FC1DF933BE7DAAD490B8E4FC824B76E') { + throw new GradleException('The published basePower 2.4.10.118021 source and artifact pins do not match.') } } -requireDevelopmentPins() -def powerAdvantageDeobfJarPath = providers.gradleProperty('powerAdvantageDeobfJar') -if (!powerAdvantageDeobfJarPath.isPresent()) { - throw new GradleException('Pass an immutable basePower API jar with -PpowerAdvantageDeobfJar=.') -} -def powerAdvantageDeobfJar = file(powerAdvantageDeobfJarPath.get()) -if (!powerAdvantageDeobfJar.isFile()) { - throw new GradleException("Missing basePower deobf jar at ${powerAdvantageDeobfJar}.") -} -// These defaults follow the sibling MMD checkout layout. They are constants -// here so another developer can override them without editing the script. -def baseMetalsDeobfJar = file(providers.gradleProperty('baseMetalsDeobfJar') - .getOrElse('../../../../BaseMetals/BaseMetals 117/BaseMetals/build/libs-dev/' - + "BaseMetals-${project.basemetals_version}-deobf.jar")) -if (!baseMetalsDeobfJar.isFile()) { - throw new GradleException("Missing Base Metals deobf jar at ${baseMetalsDeobfJar}. " - + "Pass the coordinated 1.17.1 API with -PbaseMetalsDeobfJar=.") + +// Resolve and check only the dependencies required by the selected tasks. +def powerAdvantageDeobfJar = providers.provider { + requirePowerPin() + pinnedFile('powerAdvantageDeobfJar', project.poweradvantage_deobf_sha256) } -def oreSpawnDeobfJar = file(providers.gradleProperty('oreSpawnDeobfJar') - .getOrElse('../../../../MinecraftMineralogy/OS117/OreSpawn/build/libs-dev/' - + 'OreSpawn-4.1.0.117011-deobf.jar')) -if (!oreSpawnDeobfJar.isFile()) { - throw new GradleException("Missing OreSpawn deobf jar at ${oreSpawnDeobfJar}. " - + "Pass the coordinated 1.17.1 API with -PoreSpawnDeobfJar=.") +def baseMetalsDeobfJar = providers.provider { + pinnedFile('baseMetalsDeobfJar', project.basemetals_deobf_sha256) } - -[(powerAdvantageDeobfJar): project.poweradvantage_deobf_sha256, - (baseMetalsDeobfJar): project.basemetals_deobf_sha256, - (oreSpawnDeobfJar): project.orespawn_deobf_sha256].each { artifact, approvedHash -> - String actual = MessageDigest.getInstance('SHA-256').digest(artifact.bytes) - .encodeHex().toString().toUpperCase() - if (actual != approvedHash) throw new GradleException("API input changed: ${artifact}") +def oreSpawnDeobfJar = providers.provider { + pinnedFile('oreSpawnDeobfJar', project.orespawn_deobf_sha256) } dependencies { @@ -282,6 +285,13 @@ def prepareEclipseResources = tasks.register('prepareEclipseResources') { } } +def powerPinManifest = [ + 'API-Pin-State' : project.poweradvantage_pin_state, + 'basePower-Source-Ref' : project.poweradvantage_source_ref, + 'basePower-Deobf-SHA256': project.poweradvantage_deobf_sha256, + 'basePower-Player-SHA256': project.poweradvantage_runtime_sha256 +] + tasks.named('jar', Jar) { archiveClassifier = 'deobf' destinationDirectory = layout.buildDirectory.dir('libs-dev') @@ -295,10 +305,9 @@ tasks.named('jar', Jar) { 'Implementation-Title' : base.archivesName.get(), 'Implementation-Version': project.version, 'Implementation-Vendor' : project.mod_authors, - 'API-Pin-State' : project.poweradvantage_pin_state, 'Built-On-Java' : project.java_version, 'Built-On' : "${project.minecraft_version}-${project.forge_version}" - ]) + ] + powerPinManifest) } } @@ -328,10 +337,9 @@ def releaseJar = tasks.register('releaseJar', Jar) { 'Implementation-Title' : base.archivesName.get(), 'Implementation-Version': project.version, 'Implementation-Vendor' : project.mod_authors, - 'API-Pin-State' : project.poweradvantage_pin_state, 'Built-On-Java' : project.java_version, 'Built-On' : "${project.minecraft_version}-${project.forge_version}" - ]) + ] + powerPinManifest) } } @@ -389,23 +397,27 @@ tasks.register('verifyReleaseConfiguration') { group = 'verification' description = 'Validates release identity, target metadata, and dependency policy.' doLast { - requireDevelopmentPins() - if (new JsonSlurper().parse(file('src/main/resources/pack.mcmeta')).pack.pack_format != 7) { - throw new GradleException('Minecraft 1.17.1 resources must use pack format 7') + def pack = new JsonSlurper().parse(file('src/main/resources/pack.mcmeta')).pack + if (pack.pack_format != 9 || pack['forge:resource_pack_format'] != 8 || pack['forge:data_pack_format'] != 9) { + throw new GradleException('Minecraft 1.18.2 requires Forge resource pack format 8 and data pack format 9') } - if (project.mod_version != '2.2.10.117011') { + if (project.mod_version != '2.2.11.118021') { throw new GradleException("Unexpected baseElectric release version: ${project.mod_version}") } - if (project.minecraft_version != '1.17.1' - || project.forge_version != '37.1.1' + if (project.minecraft_version != '1.18.2' + || project.minecraft_version_range != '[1.18.2]' + || project.forge_version != '40.3.0' + || project.forge_version_range != '[40.3.0,41)' + || project.loader_version_range != '[40,)' || project.mapping_channel != 'official' - || project.mapping_version != '1.17.1' - || project.mcp_version != '20210706.113038') { + || project.mapping_version != '1.18.2' + || project.mcp_version != '20220404.173914') { throw new GradleException('Unexpected Minecraft, Forge, or mappings target') } if (project.loader_name != 'forge' || project.loader_code != '1' - || project.java_version != '16' - || project.java_toolchain_version != '16.0.2+7' + || project.java_version != '17' + || project.java_toolchain_version != '17.0.1+12' + || project.java_setup_version != '17.0.1+12' || project.gradle_java_version != '17') { throw new GradleException('Unexpected release dispatcher target metadata') } @@ -419,35 +431,34 @@ tasks.register('verifyReleaseConfiguration') { } if (project.cf_requirements != 'basepower,base-metals' || project.cf_optionals != '' - || project.cf_versions != '1.17.1') { + || project.cf_versions != '1.18.2') { throw new GradleException('Unexpected CurseForge dependency or game-version metadata') } - if (project.poweradvantage_version != '2.4.9.117011' - || project.poweradvantage_source_repository != 'MinecraftModDevelopmentMods/basePower' - || project.poweradvantage_source_ref != 'bb7023835fd41aefe89c92275caa74da9e126cc7' - || project.poweradvantage_deobf_sha256 != 'AFC3FBDF84FF7E8FF8929F8192FDBCEC170C9AE1CF1B248E1C4B9C6ED17AA357' - || project.poweradvantage_unit_bootstrap_sha256 != '28080BD563D6D2F378AD2735BBEFEBB079DBFC87A9457E066DDB99F43C3D4095' - || project.poweradvantage_runtime_sha256 != 'A788C08163CECE1C6D33FBDC295BE6EC272DE9AF7911BE74E707D0EE9121DF73') { + if (project.poweradvantage_version != '2.4.10.118021' + || project.poweradvantage_source_repository != 'MinecraftModDevelopmentMods/basePower') { throw new GradleException('Unexpected basePower compile dependency identity') } - if (project.basemetals_version != '3.1.1.117011' - || project.orespawn_version != '4.1.0.117011' - || project.basemetals_source_commit != '65f9956c07d5d0c0b6ec0ac2108ce69c3ce52c6e' - || project.basemetals_deobf_sha256 != '83A8C32C633B56BB7E4405E8B3B24A4DEA32B9119216E5097B884056D679CAD1' - || project.orespawn_source_commit != 'b56e2b62a1a6ea4d1dd96f8646a87801a817dbdf' - || project.orespawn_deobf_sha256 != 'CB7A736314842B110B2E4788FBD5E21D4AE8803C5ED34A49199B034BE572DA39' - || project.forge_installer_sha256 != '31F2319B1B9CA491AACCC020887240F8524A4FF2A8E0F8B795E17E889CBC043B') { + requirePowerPin() + if (project.basemetals_version != '3.1.2.118021' + || project.orespawn_version != '4.1.0.118021' + || project.basemetals_source_commit != '3488ed58403722ac3ff203cff7680dfc16e0fd3f' + || project.basemetals_deobf_sha256 != 'E4F9E2CDE355275218C79954D3792D000615790C7CBAE5FCEC59636A8C4CA57D' + || project.basemetals_runtime_sha256 != '5595C2CE7ADCDF1599E4327F1130121148A5CACD928CBC2190312D61F7CA37A9' + || project.orespawn_source_commit != 'd31f52f296739f34a0c13f554abf3e1ac071ae25' + || project.orespawn_deobf_sha256 != '34B25B2116FD91B0050C80393F8356A5CA256E707BF443E4882BE5CFAD1335B6' + || project.orespawn_runtime_sha256 != 'BD8D84A96DAFB7668E95D61B9D72C62AEF3440675473E41244C182C64653356F' + || project.forge_installer_sha256 != '9434C29790504DCD11CE97CB30DD8891B4B18848982357BC1B5ABFA79FB95103') { 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.10.117011"')) { + || !mainSource.contains('VERSION = "2.2.11.118021"')) { 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.9.117011,3.0.0)"', 'versionRange="[3.1.1.117011,4.0.0)"'].each { marker -> + 'versionRange="[2.4.10.118021,3.0.0)"', 'versionRange="[3.1.2.118021,4.0.0)"'].each { marker -> if (!metadata.contains(marker)) { throw new GradleException("Source mods.toml is missing ${marker}") } @@ -458,60 +469,79 @@ tasks.register('verifyReleaseConfiguration') { } } +def auditModInput = { File jarFile, String modId, String version, List required -> + new ZipFile(jarFile).withCloseable { zip -> + (required + ['META-INF/MANIFEST.MF', 'META-INF/mods.toml']).each { entry -> + if (zip.getEntry(entry) == null) throw new GradleException("${jarFile.name} is missing ${entry}") + } + 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') + if (manifest.getValue('Implementation-Version') != version + || manifest.getValue('Built-On') != '1.18.2-40.3.0' + || manifest.getValue('Built-On-Java') != '17' + || !metadata.contains("modId=\"${modId}\"") + || !metadata.contains("version=\"${version}\"")) { + throw new GradleException("${jarFile.name} has unexpected ${modId} version or target metadata") + } + logger.lifecycle("Verified ${modId} ${version}: ${sha256Of(jarFile)} ${jarFile}") + } +} + +tasks.register('verifyLocalNativeInputs') { + group = 'verification' + description = 'Verifies explicit Base Metals and OreSpawn 1.18.2 development and player JARs.' + dependsOn tasks.named('verifyReleaseConfiguration') + doLast { + auditModInput(baseMetalsDeobfJar.get(), 'basemetals', project.basemetals_version, []) + auditModInput(pinnedFile('baseMetalsRuntimeJar', project.basemetals_runtime_sha256), + 'basemetals', project.basemetals_version, []) + auditModInput(oreSpawnDeobfJar.get(), 'orespawn', project.orespawn_version, []) + auditModInput(pinnedFile('oreSpawnRuntimeJar', project.orespawn_runtime_sha256), + 'orespawn', project.orespawn_version, []) + } +} + +tasks.register('verifyLocalRuntimeInputs') { + group = 'verification' + description = 'Audits the exact player dependency stack before a disposable packaged run.' + dependsOn tasks.named('verifyLocalNativeInputs') + doLast { + requirePowerPin() + auditModInput(pinnedFile('powerAdvantageRuntimeJar', project.poweradvantage_runtime_sha256), + 'basepower', project.poweradvantage_version, []) + File installer = pinnedFile('forgeInstallerJar', project.forge_installer_sha256) + logger.lifecycle("Verified Forge installer: ${project.forge_installer_sha256} ${installer}") + } +} + tasks.register('verifyReleaseDependencies') { group = 'verification' description = 'Resolves Forge and audits the exact required basePower development jar.' dependsOn tasks.named('verifyReleaseConfiguration') doLast { - String actualHash = MessageDigest.getInstance('SHA-256') - .digest(powerAdvantageDeobfJar.bytes).encodeHex().toString().toUpperCase() - if (actualHash != project.poweradvantage_deobf_sha256) { - throw new GradleException("basePower SHA-256 is ${actualHash}, expected " - + project.poweradvantage_deobf_sha256) - } - new ZipFile(powerAdvantageDeobfJar).withCloseable { zip -> + File powerJar = powerAdvantageDeobfJar.get() + File metalsJar = baseMetalsDeobfJar.get() + File oreJar = oreSpawnDeobfJar.get() + auditModInput(powerJar, 'basepower', project.poweradvantage_version, ['cyano/poweradvantage/api/PoweredEntity.class', 'cyano/poweradvantage/api/GUIBlock.class', 'cyano/poweradvantage/api/FluidCategoryRegistry.class', 'cyano/poweradvantage/api/migration/LegacyMigrationApi.class', 'zone/moddev/mc/basepower/conduitnetwork/ConduitRegistry.class', - 'zone/moddev/mc/basepower/init/Fluids.class'].each { required -> - if (zip.getEntry(required) == null) { - throw new GradleException("basePower dependency is missing ${required}") - } - } - def manifest = new Manifest(zip.getInputStream(zip.getEntry('META-INF/MANIFEST.MF'))).mainAttributes - if (manifest.getValue('Implementation-Version') != project.poweradvantage_version - || manifest.getValue('Built-On') != '1.17.1-37.1.1') { - throw new GradleException('basePower dependency manifest has the wrong version') - } - } - String actualBaseMetalsHash = MessageDigest.getInstance('SHA-256') - .digest(baseMetalsDeobfJar.bytes).encodeHex().toString().toUpperCase() - if (actualBaseMetalsHash != project.basemetals_deobf_sha256) { - throw new GradleException("Base Metals SHA-256 is ${actualBaseMetalsHash}, expected " - + project.basemetals_deobf_sha256) - } - String actualOreSpawnHash = MessageDigest.getInstance('SHA-256') - .digest(oreSpawnDeobfJar.bytes).encodeHex().toString().toUpperCase() - if (actualOreSpawnHash != project.orespawn_deobf_sha256) { - throw new GradleException("OreSpawn SHA-256 is ${actualOreSpawnHash}, expected " - + project.orespawn_deobf_sha256) - } + 'zone/moddev/mc/basepower/init/Fluids.class']) + auditModInput(metalsJar, 'basemetals', project.basemetals_version, []) + auditModInput(oreJar, 'orespawn', project.orespawn_version, []) Set compileFiles = configurations.compileClasspath.files.collect { it.canonicalFile } as Set - if (!compileFiles.contains(powerAdvantageDeobfJar.canonicalFile)) { - throw new GradleException('basePower is missing from compileClasspath') + if (!compileFiles.containsAll([powerJar.canonicalFile, metalsJar.canonicalFile, oreJar.canonicalFile])) { + throw new GradleException('A verified development mod is missing from compileClasspath') } Set runtimeFiles = configurations.runtimeClasspath.files.collect { it.canonicalFile } as Set - if (!runtimeFiles.contains(powerAdvantageDeobfJar.canonicalFile)) { - throw new GradleException('basePower is missing from runtimeClasspath') + if (!runtimeFiles.containsAll([powerJar.canonicalFile, metalsJar.canonicalFile, oreJar.canonicalFile])) { + throw new GradleException('A verified development mod is missing from runtimeClasspath') } - if (!runtimeFiles.contains(baseMetalsDeobfJar.canonicalFile)) { - throw new GradleException('Base Metals is missing from runtimeClasspath') - } - if (!runtimeFiles.contains(oreSpawnDeobfJar.canonicalFile)) { - throw new GradleException('OreSpawn 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') } List forbiddenRuntimeMods = runtimeFiles.collect { it.name.toLowerCase(Locale.ROOT) } .findAll { name -> @@ -528,6 +558,7 @@ tasks.register('verifyReleaseArtifacts') { group = 'verification' description = 'Audits the exact three distributable jars and release-critical contents.' dependsOn tasks.named('verifyReleaseDependencies') + dependsOn tasks.named('verifyLocalRuntimeInputs') dependsOn tasks.named('assemble') doLast { File libs = layout.buildDirectory.dir('libs').get().asFile @@ -605,6 +636,9 @@ tasks.register('verifyReleaseArtifacts') { || manifest.getValue('Timestamp') != null) { throw new GradleException('Release manifest identity is incorrect or volatile') } + if (powerPinManifest.any { key, value -> manifest.getValue(key) != value }) { + throw new GradleException('Player JAR does not record the exact basePower pins') + } zip.entries().findAll { it.name.endsWith('.class') }.each { entry -> byte[] header = new byte[8] zip.getInputStream(entry).withCloseable { input -> @@ -613,12 +647,19 @@ tasks.register('verifyReleaseArtifacts') { } } int major = ((header[6] & 0xff) << 8) | (header[7] & 0xff) - if (major != 60) { - throw new GradleException("${entry.name} uses Java class major ${major}, expected 60") + if (major != 61) { + throw new GradleException("${entry.name} uses Java class major ${major}, expected 61") } } } + new ZipFile(tasks.named('jar', Jar).get().archiveFile.get().asFile).withCloseable { zip -> + def manifest = new Manifest(zip.getInputStream(zip.getEntry('META-INF/MANIFEST.MF'))).mainAttributes + if (powerPinManifest.any { key, value -> manifest.getValue(key) != value }) { + throw new GradleException('Development JAR does not record the exact basePower pins') + } + } + new ZipFile(new File(libs, expectedReleaseFiles.get()[1])).withCloseable { sourceZip -> if (sourceZip.getEntry('zone/moddev/mc/baseelectric/ElectricAdvantage.java') == null || sourceZip.getEntry('zone/moddev/mc/baseelectric/machines/ElectricDrillTileEntity.java') == null) { @@ -663,6 +704,7 @@ tasks.register('verifyPreparedReleaseArtifacts') { group = 'verification' description = 'Audits an immutable release bundle before Maven publication.' doLast { + requirePowerPin() if (!preparedReleaseDir.isPresent()) { throw new GradleException('preparedReleaseDir is required for prepared artifact publication') } @@ -750,16 +792,9 @@ publishing { tasks.register('validateMavenReleaseCredentials') { group = 'publishing' - description = 'Prevents Maven publication from using a local or incomplete destination.' + description = 'Rejects remote publication from this local port.' doLast { - if (!providers.environmentVariable('MAVEN_UPLOAD_URL').isPresent() - || !mavenUploadUsername.isPresent() || !mavenUploadPassword.isPresent()) { - throw new GradleException( - 'MAVEN_UPLOAD_URL, MAVEN_UPLOAD_USERNAME, and MAVEN_UPLOAD_PASSWORD are required') - } - if (providers.environmentVariable('MAVEN_UPLOAD_URL').get().startsWith('file:')) { - throw new GradleException('Maven release publication must use a remote repository') - } + throw new GradleException('Remote Maven publication is disabled on the local 1.18.2 port.') } } @@ -1005,8 +1040,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-16') { - throw new GradleException('Eclipse production JRE must be JavaSE-16') + if (productionJre.size() != 1 || productionJre[0].@path.text().tokenize('/').last() != 'JavaSE-17') { + throw new GradleException('Eclipse production JRE must be JavaSE-17') } def processedResources = eclipseClasspath.classpathentry.findAll { it.@kind.text() == 'src' && it.@path.text() == 'build/resources/main' @@ -1074,11 +1109,11 @@ tasks.register('verifyEclipseProductionClasspath') { "${name} includes forbidden runtime dependency ${forbiddenDependency}") } } - if (!runtimeArchives.contains(powerAdvantageDeobfJar.canonicalFile)) { + if (!runtimeArchives.contains(powerAdvantageDeobfJar.get().canonicalFile)) { throw new GradleException("${name} is missing the required basePower runtime") } - if (!runtimeArchives.contains(baseMetalsDeobfJar.canonicalFile) - || !runtimeArchives.contains(oreSpawnDeobfJar.canonicalFile)) { + if (!runtimeArchives.contains(baseMetalsDeobfJar.get().canonicalFile) + || !runtimeArchives.contains(oreSpawnDeobfJar.get().canonicalFile)) { throw new GradleException("${name} is missing the required Base Metals or OreSpawn runtime") } List bootstrapArtifacts = [ @@ -1106,7 +1141,7 @@ tasks.register('verifyEclipseProductionClasspath') { || !programArguments.contains("--fml.forgeVersion ${project.forge_version}") || !programArguments.contains("--fml.mcpVersion ${project.mcp_version}") || !vmArguments.contains('--add-opens java.base/java.lang.invoke=ALL-UNNAMED')) { - throw new GradleException("${name} does not define the native Forge 37 bootstrap and literal target versions") + throw new GradleException("${name} does not define the native Forge 40 bootstrap and literal target versions") } } } @@ -1132,7 +1167,7 @@ tasks.register('verifyModelAssets') { new JsonSlurper().parse(jsonFile) } - Set powerAdvantageAssets = new ZipFile(powerAdvantageDeobfJar).withCloseable { zip -> + Set powerAdvantageAssets = new ZipFile(powerAdvantageDeobfJar.get()).withCloseable { zip -> zip.entries().collect { it.name } as Set } def verifyReference = { String reference, String assetType, String sourcePath -> diff --git a/docs/NATIVE118.md b/docs/NATIVE118.md new file mode 100644 index 0000000..e70fad4 --- /dev/null +++ b/docs/NATIVE118.md @@ -0,0 +1,126 @@ +# baseElectric 1.18.2 port notes + +The release branch uses the shared Base Metals 3.1.2 migration hook and the +published basePower 2.4.10.118021 build. +See [gradle.properties](../gradle.properties) for the exact Power, Base Metals +and OreSpawn pins. + +The first locally tested Electric player jar had SHA-256 +`C5C1A6DEAE795F8AF741DBE35B0D8941F7FA4BBC708758BDFE056B4AEBADCBC1`. +The notes below record earlier port milestones and their original inputs. + +`feature/baseelectric-1.18.2-port` was cloned from the clean PA117 +`feature/baseelectric-1.17.1-identity-migration` tip +`cf3d4b0e2d1591b2ae8e5d638002ca016e46eac4`. +The candidate is `2.2.11.118021`, for Forge `40.3.0`, official `1.18.2`, +MCP `20220404.173914`, Java 17 and ForgeGradle `7.0.34`. + +## Native changes + +- Blocks, items and block items construct only in their Forge registration + events. The sulfur drop stack resolves after item registration; the + original ore constructor, 1-4 sulfur range and silk touch behavior remain. + GUI definitions register in enqueued common setup after registries. +- Machines use `saveAdditional` and `saveWithoutMetadata` for native saving, + synchronization and drill transfer. Existing keys, energy, inventory and + progress remain. The turret has separate render-only updates and full + chunk data. Hydroturbine spawn uses Forge's moved network hook. +- Native tags replace removed item tag lookups. Recipe mode conditions work + with the Forge context and retain the legacy caller. Electric's unshaded + vertex shader uses the native 1.18 fog function and still omits diffuse light. +- All IDs, recipe JSON, unlocks, model and texture assets, legacy aliases, + creative tab and machine balance values are retained. Pack metadata is + Forge resource format 8 and data format 9. + +## Exact local inputs + +Versions, distinct development/player SHA-256 values, and source checkout +revisions are in [gradle.properties](../gradle.properties). The Base Metals +and OreSpawn source revisions describe the checked local checkouts, while the +verified hashes identify the bytes actually used. The local release inputs are: + +- Base Metals: `D:/BaseMetals/BaseMetals 118/BaseMetals/build/libs-dev/BaseMetals-3.1.1.118021-deobf.jar` and `D:/BaseMetals/BaseMetals 118/BaseMetals/build/libs/BaseMetals-3.1.1.118021.jar`. +- OreSpawn: `D:/MinecraftMineralogy/OS118/OreSpawn/build/libs-dev/OreSpawn-4.1.0.118021-deobf.jar` and `D:/MinecraftMineralogy/OS118/OreSpawn/build/libs/OreSpawn-4.1.0.118021.jar`. +- basePower: `D:/PowerAdvantage/_verification/base-118-port-20261109/power-source-pin-a/basePower-2.4.10.118021-deobf.jar` and `D:/PowerAdvantage/_verification/base-118-port-20261109/power-source-pin-a/basePower-2.4.10.118021.jar`. +- Bootstrap: `D:/PowerAdvantage/PA118/PowerAdvantage/test-rig/unit-bootstrap/ForgeCapabilityBootstrap.java`. Its Git blob matches the pinned source revision. +- Forge installer: `D:/BaseMetals/.runtime-cache/forge-1.18.2-40.3.0-installer.jar`. +- Mavenizer: `D:/IronAgeFurniture/IAF118/IronAgeFurniture/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-1.18.jar`. + +The basePower pin is source commit `8bd3b8213554551fc688a46eb1653a0b572b8d0a`, +deobf SHA-256 `CDDB8E1BB6998F625BEEF677B4E2FC87847A450D77775DA86CF31566A926D28F`, +and player SHA-256 `3C3E9154BC0F7CC88C4AE4C5F0044527D7F39BBF12C71F83AD99CF2E9646E273`. +Both Electric manifests record these pins. The ForgeGradle audit checked the +resolved official MCP mapping ZIP, exact mod files on both classpaths, metadata, +and the exclusion of RF/RebornCore. The separate player-input audit checked all +three player mod JARs and the Forge installer. + +## Source-pinned handoff + +Frozen outputs are in `build/port-evidence/source-pin-a/artifacts`. + +| Output | SHA-256 | +| --- | --- | +| `baseElectric-2.2.11.118021.jar` | `1094AE5A8AF02148C90111E8CAB46380DD111DF5D9387B2A966FB2F939F5AE35` | +| `baseElectric-2.2.11.118021-deobf.jar` | `AE13D69B8A8B06A8838CEFB4E725A5BCACEB79141A2E875080132C179AAC3B7D` | +| `baseElectric-2.2.11.118021-sources.jar` | `0BFE22BF6753CADB17812732EFFCF070403A95921056A12F6EC11A8E44B6AAC6` | +| `baseElectric-2.2.11.118021-javadoc.jar` | `860B84F5B49DD49C865088D1B6A056B7C06C5B504C9AE59943AEB8B9B3E8ECE3` | + +Both `gradle-source-pin-a.log` and `gradle-source-pin-b.log` record all 37 +tasks executed successfully, including 96 tests with no skips, release +audits and Java 17 Eclipse generation. Both runs used the explicit paths +above with the command in the README, `--offline`, and +`ORESPAWN_MAVENIZER_OFFLINE=true`. The second forced build reproduced all +four hashes (`source-pin-b/rebuild-comparison.json`). This proves repeatability +in the same checkout with the prepared dependency cache, not a cold-cache build. +Both runs retain the JUnit XML and Eclipse launch files separately. + +`ecj-source-pin-a/ecj-results.json` records independent Java 17 compilation +and 96 JUnit tests with no failures, ignores or assumptions. Its restricted-JDK +negative control fails as intended. `source-final-pin-a.json` records the +static preservation and pin audit. + +`packaged-source-pin-a/receipt.json` records the dedicated-server smoke of +the exact source-pinned player JARs: Forge readiness, a live drill NBT read, +sulfur loot and entity NBT, a flushed save, and shutdown with exit code 0. +The combined harness, client/GPU behavior and existing-world migration are +separate gates. No hosted workflow or publication was run. + +## Provisional receipts retained + +Local receipts are under `build/port-evidence`. `diagnostic-baseline.log` was a +failure-finding compile against the older Power ABI, not a 1.18.2 result. +`gradle-check-a.log` and its XML retain the first JUnit fixture failures; +`gradle-check-b.log` retains the stale Java 16 Eclipse audit failure. +`gradle-lifecycle-e.log` is the subsequent 36-task forced local build against +the provisional 1.18.2 API, including all 96 tests, package, assets, pins and generated +Java 17 Eclipse launch-file checks. + +`ecj-lifecycle-e/ecj-results.json` independently compiles 85 production and +28 test sources on Temurin 17.0.1 and passes all 96 tests, with zero failures, +ignores or assumptions. Its restricted-JDK negative control fails as intended. +`source-lifecycle-e.json` passes all 14 static contracts; the unlock generator +checks all 50 unlocks. A second forced build reproduced all four provisional archives, +including the deobf JAR, byte-for-byte (`rebuild-lifecycle-comparison.json`). +`gradle-negative-power-pin.log` confirms the same-version Power player JAR +is rejected when supplied as the development JAR. + +The provisional packaged server smoke passed in `packaged-c/receipt.json`: +Electric player SHA-256 +`38A66525DE2F9FA525D9FFEE9AE33BD69F4457B23995A255F2B17B7C9CD9B465` +and the Power player's `smoke-power-c` copy SHA-256 +`FE7F0EDD01F16A7FA33DD748E0395FEDE8DEC32FE77E5B27A6F13EED7D712C55`. +It reached Forge readiness, placed a drill and read native Energy, progress, +inventory and registry NBT, placed sulfur ore, confirmed its live one-item +sulfur stack, saved, and exited 0. `packaged-a` retains the initial harness +timeout: the game produced three powder, but the matcher expected generic +item-count text. `packaged-b` passed the assertions but retained the vanilla +flat-world preset parser's registry-ops diagnostic. The final smoke used the +default world preset and an explicitly loaded test chunk, with no error or +fatal messages and no changes to the four mod JARs. + +Use `tools/verification/Invoke-ElectricPackagedSmoke.ps1` with the explicitly +selected Power player JAR/hash, a prepared Forge 40.3.0 seed and a fresh evidence +path. It runs only on loopback in a disposable world; the development JARs are +not runtime inputs. No client launch, gameplay acceptance, original-world +migration, hosted workflow, or publication is implied by a local module build +or a dedicated-server smoke. diff --git a/gradle.properties b/gradle.properties index c634424..8143b78 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,52 +5,51 @@ org.gradle.caching=true org.gradle.parallel=false net.minecraftforge.gradle.merge-source-sets=false -minecraft_version=1.17.1 -minecraft_version_range=[1.17.1] -forge_version=37.1.1 -forge_installer_sha256=31F2319B1B9CA491AACCC020887240F8524A4FF2A8E0F8B795E17E889CBC043B -forge_version_range=[37.1.1,38) -loader_version_range=[37,) +minecraft_version=1.18.2 +minecraft_version_range=[1.18.2] +forge_version=40.3.0 +forge_installer_sha256=9434C29790504DCD11CE97CB30DD8891B4B18848982357BC1B5ABFA79FB95103 +forge_version_range=[40.3.0,41) +loader_version_range=[40,) mapping_channel=official -mapping_version=1.17.1 -mcp_version=20210706.113038 +mapping_version=1.18.2 +mcp_version=20220404.173914 loader_name=forge loader_code=1 -java_version=16 -java_toolchain_version=16.0.2+7 -java_setup_version=16.0.2+7 +java_version=17 +java_toolchain_version=17.0.1+12 +java_setup_version=17.0.1+12 gradle_java_version=17 curseforge_project_id=1783006 mod_id=baseelectric mod_name=baseElectric mod_license=GPL-3.0-only -mod_version=2.2.10.117011 +mod_version=2.2.11.118021 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.17.1 +cf_versions=1.18.2 -poweradvantage_version=2.4.9.117011 -poweradvantage_pin_state=LOCAL_CHECKPOINT +poweradvantage_version=2.4.10.118021 +poweradvantage_pin_state=PUBLISHED_RELEASE poweradvantage_source_repository=MinecraftModDevelopmentMods/basePower -# Local-only source checkpoint; development and player jars are distinct. -poweradvantage_source_ref=bb7023835fd41aefe89c92275caa74da9e126cc7 -poweradvantage_deobf_sha256=AFC3FBDF84FF7E8FF8929F8192FDBCEC170C9AE1CF1B248E1C4B9C6ED17AA357 -poweradvantage_runtime_sha256=A788C08163CECE1C6D33FBDC295BE6EC272DE9AF7911BE74E707D0EE9121DF73 -poweradvantage_unit_bootstrap_sha256=28080BD563D6D2F378AD2735BBEFEBB079DBFC87A9457E066DDB99F43C3D4095 +# Owner-frozen local source checkpoint; development and player JARs are distinct. +poweradvantage_source_ref=bcf5105ef5c10ba36c26c8ff941742fecc3e11a1 +poweradvantage_deobf_sha256=767BABC0D6E1536DCB937B9F1DC565AF348B3CAF92D3207671EF1E1C8D8EF5D4 +poweradvantage_runtime_sha256=8CA46D5BCBFFDDAEB4F40137B5BF71E5AF7DD36515E5BFEA751FC5C02249823B +poweradvantage_unit_bootstrap_sha256=025F16FCA9FD5CFC585931E7C8BCFAB22FC1DF933BE7DAAD490B8E4FC824B76E -basemetals_version=3.1.1.117011 -# Local startup fix; exact source is not available to hosted builds. -basemetals_source_commit=65f9956c07d5d0c0b6ec0ac2108ce69c3ce52c6e -basemetals_deobf_sha256=83A8C32C633B56BB7E4405E8B3B24A4DEA32B9119216E5097B884056D679CAD1 -basemetals_runtime_sha256=F3053F23D2415476FC52426A24E3B0B828198A678FBD5DD789923DB2063E0FEB +basemetals_version=3.1.2.118021 +basemetals_source_commit=3488ed58403722ac3ff203cff7680dfc16e0fd3f +basemetals_deobf_sha256=E4F9E2CDE355275218C79954D3792D000615790C7CBAE5FCEC59636A8C4CA57D +basemetals_runtime_sha256=5595C2CE7ADCDF1599E4327F1130121148A5CACD928CBC2190312D61F7CA37A9 -orespawn_version=4.1.0.117011 -orespawn_source_commit=b56e2b62a1a6ea4d1dd96f8646a87801a817dbdf -orespawn_deobf_sha256=CB7A736314842B110B2E4788FBD5E21D4AE8803C5ED34A49199B034BE572DA39 -orespawn_runtime_sha256=1BDEDCBB179CB5A3B169E5C8A6B301E3888AE15B94108B0AEC627D705CFAD833 +orespawn_version=4.1.0.118021 +orespawn_source_commit=d31f52f296739f34a0c13f554abf3e1ac071ae25 +orespawn_deobf_sha256=34B25B2116FD91B0050C80393F8356A5CA256E707BF443E4882BE5CFAD1335B6 +orespawn_runtime_sha256=BD8D84A96DAFB7668E95D61B9D72C62AEF3440675473E41244C182C64653356F diff --git a/src/main/java/zone/moddev/mc/baseelectric/ElectricAdvantage.java b/src/main/java/zone/moddev/mc/baseelectric/ElectricAdvantage.java index e2451f9..ef0b287 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/ElectricAdvantage.java +++ b/src/main/java/zone/moddev/mc/baseelectric/ElectricAdvantage.java @@ -21,25 +21,20 @@ import cyano.poweradvantage.api.migration.LegacyConfigMigration.ValueType; import cyano.poweradvantage.api.migration.LegacyOreSpawnConfigBridge; import zone.moddev.mc.baseelectric.config.ElectricAdvantageConfig; -import zone.moddev.mc.baseelectric.init.Blocks; -import zone.moddev.mc.baseelectric.init.Enchantments; -import zone.moddev.mc.baseelectric.init.Entities; import zone.moddev.mc.baseelectric.init.GUI; -import zone.moddev.mc.baseelectric.init.Items; import zone.moddev.mc.baseelectric.init.OreSpawnWorldGen; -import zone.moddev.mc.baseelectric.init.Recipes; 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.17.1 entry point for baseElectric. */ +/** Forge 1.18.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.10.117011"; + public static final String VERSION = "2.2.11.118021"; public static final Logger LOGGER = LogManager.getLogger(MODID); public static ElectricAdvantage INSTANCE; @@ -48,11 +43,6 @@ public final class ElectricAdvantage { public ElectricAdvantage() { INSTANCE = this; - Blocks.init(); - Items.init(); - Entities.init(); - Enchantments.init(); - GUI.init(); ElectricLegacyMigrations.register(); migrateLegacyOreSpawnConfiguration(); @@ -96,10 +86,10 @@ private static void migrateLegacyConfiguration() { } private void commonSetup(FMLCommonSetupEvent event) { + event.enqueueWork(GUI::init); // CraftingHelper's shared condition map is not safe during parallel loading. event.enqueueWork(RecipeConditions::register); ElectricAdvantageConfig.apply(); - Recipes.init(); 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 1e97776..44d82d0 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/blocks/Ore.java +++ b/src/main/java/zone/moddev/mc/baseelectric/blocks/Ore.java @@ -3,6 +3,7 @@ import java.util.Random; import java.util.Collections; import java.util.List; +import java.util.function.Supplier; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.SoundType; @@ -13,32 +14,43 @@ import net.minecraft.world.item.enchantment.Enchantments; import net.minecraft.world.level.storage.loot.LootContext; import net.minecraft.world.level.storage.loot.parameters.LootContextParams; -import net.minecraft.core.NonNullList; -import net.minecraft.core.BlockPos; -import net.minecraft.world.level.Level; /** Legacy ElectricAdvantage ore block retaining its original drops and harvest level. */ public class Ore extends Block { - private final ItemStack dropItem; + private final Supplier drop; private final int dropRange; public Ore(ItemStack oreDrop, int pickLevel, int variation) { + this(fixedDrop(oreDrop), pickLevel, variation); + } + + public static Ore withDeferredDrop(Supplier drop, int pickLevel, int variation) { + return new Ore(drop, pickLevel, variation); + } + + private static Supplier fixedDrop(ItemStack oreDrop) { + ItemStack copy = oreDrop.copy(); + return () -> copy; + } + + private Ore(Supplier drop, int pickLevel, int variation) { super(Properties.of(Material.STONE).requiresCorrectToolForDrops().sound(SoundType.STONE) .strength(1.5F + pickLevel, 5.0F * (1 + pickLevel))); - dropItem = oreDrop.copy(); + this.drop = drop; dropRange = variation + 1; } public Ore(int pickLevel) { super(Properties.of(Material.STONE).requiresCorrectToolForDrops().sound(SoundType.STONE) .strength(1.5F + pickLevel, 5.0F * (1 + pickLevel))); - dropItem = ItemStack.EMPTY; + drop = () -> ItemStack.EMPTY; dropRange = 0; } @Override public List getDrops(BlockState state, LootContext.Builder context) { ItemStack tool = context.getOptionalParameter(LootContextParams.TOOL); + ItemStack dropItem = drop.get(); if (dropItem.isEmpty() || (tool != null && EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) > 0)) { return Collections.singletonList(new ItemStack(this)); 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 97ac43a..48b478c 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/entities/HydroturbineEntity.java +++ b/src/main/java/zone/moddev/mc/baseelectric/entities/HydroturbineEntity.java @@ -8,7 +8,7 @@ import net.minecraft.world.level.material.FluidState; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.protocol.Packet; -import net.minecraftforge.fmllegacy.network.NetworkHooks; +import net.minecraftforge.network.NetworkHooks; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.core.BlockPos; import net.minecraft.world.phys.Vec3; diff --git a/src/main/java/zone/moddev/mc/baseelectric/init/Blocks.java b/src/main/java/zone/moddev/mc/baseelectric/init/Blocks.java index bc3cb86..ca3b6b9 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/init/Blocks.java +++ b/src/main/java/zone/moddev/mc/baseelectric/init/Blocks.java @@ -35,7 +35,7 @@ import zone.moddev.mc.basepower.blocks.BlockPowerSwitch; import zone.moddev.mc.basepower.init.ItemGroups; -/** Stable block registry for ElectricAdvantage 1.13. */ +/** Stable block registry for Electric Advantage. */ @Mod.EventBusSubscriber(modid = ElectricAdvantage.MODID, bus = Mod.EventBusSubscriber.Bus.MOD) public final class Blocks { private static final Map ALL_BLOCKS = new LinkedHashMap<>(); @@ -67,13 +67,15 @@ public final class Blocks { private static boolean initialized; private Blocks() {} - public static synchronized void init() { + /** Kept for callers of the legacy bootstrap; Forge owns block construction. */ + public static void init() { ItemGroups.init(); } + + private static synchronized void constructBlocks() { if (initialized) return; - Items.initWithoutBlocks(); - ItemGroups.init(); + init(); electric_conduit = add(new ElectricConduitBlock(), "electric_conduit"); lithium_ore = add(new Ore(1), "li_ore"); - sulfur_ore = add(new Ore(new ItemStack(Items.sulfur_powder, 1), 0, 3), "sulfur_ore"); + sulfur_ore = add(Ore.withDeferredDrop(() -> new ItemStack(Items.sulfur_powder, 1), 0, 3), "sulfur_ore"); electric_track = add(new ElectricScaffoldBlock(), "electric_track"); laser_turret = (GUIBlock) add(new LaserTurretBlock(false), "laser_turret"); laser_turret_evil = (GUIBlock) add(new LaserTurretBlock(true), "laser_turret_evil"); @@ -104,12 +106,11 @@ private static Block add(Block block, String id) { } @SubscribeEvent public static void registerBlocks(RegistryEvent.Register event) { - init(); + constructBlocks(); event.getRegistry().registerAll(ALL_BLOCKS.values().toArray(new Block[0])); } @SubscribeEvent public static void registerBlockItems(RegistryEvent.Register event) { - init(); for (Map.Entry entry : ALL_BLOCKS.entrySet()) { Item.Properties properties = new Item.Properties(); if (!"pump_pipe_electric".equals(entry.getKey()) && !"laser_turret_evil".equals(entry.getKey())) { diff --git a/src/main/java/zone/moddev/mc/baseelectric/init/GUI.java b/src/main/java/zone/moddev/mc/baseelectric/init/GUI.java index aabc089..0222578 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/init/GUI.java +++ b/src/main/java/zone/moddev/mc/baseelectric/init/GUI.java @@ -10,9 +10,6 @@ public abstract class GUI { public static void init(){ if(initDone) return; - Blocks.init(); - Entities.init(); - Blocks.steam_powered_generator.setGuiID(MachineGUIRegistry.addGUI(new PowerGeneratorGUI()), PowerAdvantage.getInstance()); Blocks.photovoltaic_generator.setGuiID(MachineGUIRegistry.addGUI(new PowerGeneratorGUI()), PowerAdvantage.getInstance()); Blocks.hydroelectric_generator.setGuiID(MachineGUIRegistry.addGUI(new PowerGeneratorGUI()), PowerAdvantage.getInstance()); diff --git a/src/main/java/zone/moddev/mc/baseelectric/init/Items.java b/src/main/java/zone/moddev/mc/baseelectric/init/Items.java index 83a1dcc..6ce1d0d 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/init/Items.java +++ b/src/main/java/zone/moddev/mc/baseelectric/init/Items.java @@ -12,7 +12,7 @@ import zone.moddev.mc.baseelectric.items.BatteryItem; import zone.moddev.mc.basepower.init.ItemGroups; -/** Stable item registry for ElectricAdvantage 1.13. */ +/** Stable item registry for Electric Advantage. */ @Mod.EventBusSubscriber(modid = ElectricAdvantage.MODID, bus = Mod.EventBusSubscriber.Bus.MOD) public final class Items { public static final Map allItems = new LinkedHashMap<>(); @@ -36,11 +36,13 @@ public final class Items { private static boolean initialized; private Items() {} - public static void init() { initWithoutBlocks(); Blocks.init(); } - static synchronized void initWithoutBlocks() { + /** Kept for callers of the legacy bootstrap; Forge owns item construction. */ + public static void init() { ItemGroups.init(); } + + private static synchronized void constructItems() { if (initialized) return; - ItemGroups.init(); + init(); blank_circuit_board = add("blank_circuit_board", new Item(properties())); control_circuit = add("control_circuit", new Item(properties())); integrated_circuit = add("integrated_circuit", new Item(properties())); @@ -69,7 +71,7 @@ private static Item add(String id, Item item) { } @SubscribeEvent public static void register(RegistryEvent.Register event) { - initWithoutBlocks(); + constructItems(); event.getRegistry().registerAll(allItems.values().toArray(new Item[0])); } diff --git a/src/main/java/zone/moddev/mc/baseelectric/init/Recipes.java b/src/main/java/zone/moddev/mc/baseelectric/init/Recipes.java index 6b3be26..8e1caea 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/init/Recipes.java +++ b/src/main/java/zone/moddev/mc/baseelectric/init/Recipes.java @@ -8,8 +8,5 @@ public final class Recipes { private Recipes() {} - public static void init() { - Blocks.init(); - Items.init(); - } + public static void init() {} } diff --git a/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricDrillTileEntity.java b/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricDrillTileEntity.java index 50d273d..3dbeb20 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricDrillTileEntity.java +++ b/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricDrillTileEntity.java @@ -152,8 +152,7 @@ private boolean followTrack(){ BlockPos nextPos = getBlockPos().relative(trackDir); w.setBlock(nextPos, w.getBlockState(getBlockPos()), 2); ElectricDrillTileEntity te = (ElectricDrillTileEntity)w.getBlockEntity(nextPos); - CompoundTag dataTransfer = new CompoundTag(); - this.save(dataTransfer); + CompoundTag dataTransfer = saveWithoutMetadata(); te.load(dataTransfer); te.clearRemoved(); te.subtractEnergy(ENERGY_COST_MOVE, getEnergyType()); diff --git a/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricGeneratorTileEntity.java b/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricGeneratorTileEntity.java index 1c6797c..77b0198 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricGeneratorTileEntity.java +++ b/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricGeneratorTileEntity.java @@ -179,10 +179,9 @@ public int getComparatorOutput(){ @Override - public CompoundTag save(CompoundTag tagRoot){ - super.save(tagRoot); + protected void saveAdditional(CompoundTag tagRoot){ + super.saveAdditional(tagRoot); saveTo(tagRoot); - return tagRoot; } @Override 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 c4c68f9..e1a5177 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricMachineTileEntity.java +++ b/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricMachineTileEntity.java @@ -231,15 +231,14 @@ protected ItemStack getSmeltingResult(ItemStack input) { } @Override - public CompoundTag save(CompoundTag tagRoot){ - super.save(tagRoot); + protected void saveAdditional(CompoundTag tagRoot){ + super.saveAdditional(tagRoot); saveTo(tagRoot); - return tagRoot; } @Override public CompoundTag getUpdateTag() { - return save(new CompoundTag()); + return saveWithoutMetadata(); } @Override diff --git a/src/main/java/zone/moddev/mc/baseelectric/machines/GrowthChamberControllerTileEntity.java b/src/main/java/zone/moddev/mc/baseelectric/machines/GrowthChamberControllerTileEntity.java index 7327e10..7959bc9 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/machines/GrowthChamberControllerTileEntity.java +++ b/src/main/java/zone/moddev/mc/baseelectric/machines/GrowthChamberControllerTileEntity.java @@ -16,10 +16,13 @@ import cyano.poweradvantage.api.fluid.FluidStack; import cyano.poweradvantage.api.fluid.FluidTank; import cyano.poweradvantage.api.fluid.FluidTankInfo; -import net.minecraft.tags.ItemTags; +import net.minecraft.core.Registry; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; import net.minecraft.resources.ResourceLocation; public class GrowthChamberControllerTileEntity extends cyano.poweradvantage.api.simple.TileEntitySimplePowerMachine implements LegacyFluidHandler{ + private static final TagKey DIRT_TAG = TagKey.create(Registry.ITEM_REGISTRY, new ResourceLocation("forge", "dirt")); static final float ELECTRICITY_PER_UNIT = 8f; @@ -82,7 +85,7 @@ public void tickUpdate(boolean isServerWorld) { private static boolean isDirt(ItemStack i){ return i != null && !i.isEmpty() && (i.getItem() == net.minecraft.world.level.block.Blocks.DIRT.asItem() - || ItemTags.getAllTags().getTagOrEmpty(new ResourceLocation("forge", "dirt")).contains(i.getItem())); + || i.is(DIRT_TAG)); } private static float lowest(float... numbers){ @@ -190,13 +193,12 @@ public void onDataFieldUpdate() { * @param tagRoot An NBT tag */ @Override - public CompoundTag save(final CompoundTag tagRoot) { - super.save(tagRoot); + protected void saveAdditional(final CompoundTag tagRoot) { + super.saveAdditional(tagRoot); CompoundTag tankTag = new CompoundTag(); this.getTank().writeToNBT(tankTag); tagRoot.put("Tank", tankTag); tagRoot.putFloat("soil", soil); - return tagRoot; } /** * Handles data saving and loading diff --git a/src/main/java/zone/moddev/mc/baseelectric/machines/LaserTurretTileEntity.java b/src/main/java/zone/moddev/mc/baseelectric/machines/LaserTurretTileEntity.java index 9df170a..c9af390 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/machines/LaserTurretTileEntity.java +++ b/src/main/java/zone/moddev/mc/baseelectric/machines/LaserTurretTileEntity.java @@ -496,14 +496,16 @@ private void storeOld(){ */ @Override public ClientboundBlockEntityDataPacket getUpdatePacket(){ - CompoundTag nbtTag = new CompoundTag(); - writeRenderSync(nbtTag); - return new ClientboundBlockEntityDataPacket(this.getBlockPos(), 0, nbtTag); + return ClientboundBlockEntityDataPacket.create(this, tile -> { + CompoundTag tag = new CompoundTag(); + writeRenderSync(tag); + return tag; + }); } @Override public CompoundTag getUpdateTag() { - CompoundTag nbtTag = save(new CompoundTag()); + CompoundTag nbtTag = super.getUpdateTag(); writeRenderSync(nbtTag); return nbtTag; } diff --git a/src/main/java/zone/moddev/mc/baseelectric/machines/PlasticRefineryTileEntity.java b/src/main/java/zone/moddev/mc/baseelectric/machines/PlasticRefineryTileEntity.java index 98461bc..59a072d 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/machines/PlasticRefineryTileEntity.java +++ b/src/main/java/zone/moddev/mc/baseelectric/machines/PlasticRefineryTileEntity.java @@ -18,13 +18,16 @@ import cyano.poweradvantage.api.fluid.FluidStack; import cyano.poweradvantage.api.fluid.FluidTank; import cyano.poweradvantage.api.fluid.FluidTankInfo; -import net.minecraft.tags.ItemTags; +import net.minecraft.core.Registry; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; import net.minecraft.resources.ResourceLocation; import net.minecraft.core.Registry; import java.util.List; public class PlasticRefineryTileEntity extends ElectricMachineTileEntity implements LegacyFluidHandler{ + private static final TagKey PLASTICS_TAG = TagKey.create(Registry.ITEM_REGISTRY, new ResourceLocation("forge", "plastics")); public static final float ENERGY_PER_TICK = 12f; @@ -127,9 +130,7 @@ protected void loadFrom(CompoundTag tagRoot) { @Override protected boolean isValidInputItem(ItemStack item) { - return item != null && !item.isEmpty() - && ItemTags.getAllTags().getTagOrEmpty(new ResourceLocation("forge", "plastics")) - .contains(item.getItem()); + return item != null && !item.isEmpty() && item.is(PLASTICS_TAG); } @Override diff --git a/src/main/java/zone/moddev/mc/baseelectric/recipe/RecipeConditions.java b/src/main/java/zone/moddev/mc/baseelectric/recipe/RecipeConditions.java index 300b229..a50c854 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/recipe/RecipeConditions.java +++ b/src/main/java/zone/moddev/mc/baseelectric/recipe/RecipeConditions.java @@ -37,7 +37,9 @@ static final class ModeCondition implements ICondition { inverted ? "not_recipe_mode" : "recipe_mode"); } - @Override public boolean test() { + @Override public boolean test() { return test(IContext.EMPTY); } + + @Override public boolean test(IContext context) { return (PowerAdvantage.recipeMode == mode) != inverted; } } diff --git a/src/main/java/zone/moddev/mc/baseelectric/util/crafting/ItemMatcher.java b/src/main/java/zone/moddev/mc/baseelectric/util/crafting/ItemMatcher.java index 25b489f..ab86722 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/util/crafting/ItemMatcher.java +++ b/src/main/java/zone/moddev/mc/baseelectric/util/crafting/ItemMatcher.java @@ -9,7 +9,9 @@ import net.minecraft.world.level.block.Block; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; -import net.minecraft.tags.ItemTags; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.tags.TagKey; import net.minecraft.resources.ResourceLocation; /** Matches a crafting ingredient using flattened items or Forge item tags. */ @@ -18,8 +20,8 @@ public final class ItemMatcher { public ItemMatcher(String legacyName) { this.choices = new ArrayList<>(); - for (Item item : ItemTags.getAllTags().getTagOrEmpty(tagId(legacyName)).getValues()) { - choices.add(new ItemStack(item)); + for (Holder item : Registry.ITEM.getTagOrEmpty(TagKey.create(Registry.ITEM_REGISTRY, tagId(legacyName)))) { + choices.add(new ItemStack(item.value())); } } diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 5315889..314a5d4 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -30,13 +30,13 @@ side="BOTH" [[dependencies.baseelectric]] modId="basepower" mandatory=true -versionRange="[2.4.9.117011,3.0.0)" +versionRange="[2.4.10.118021,3.0.0)" ordering="AFTER" side="BOTH" [[dependencies.baseelectric]] modId="basemetals" mandatory=false -versionRange="[3.1.1.117011,4.0.0)" +versionRange="[3.1.2.118021,4.0.0)" ordering="AFTER" side="BOTH" diff --git a/src/main/resources/assets/baseelectric/shaders/core/unshaded.json b/src/main/resources/assets/baseelectric/shaders/core/unshaded.json index 0dfe265..8ef3e67 100644 --- a/src/main/resources/assets/baseelectric/shaders/core/unshaded.json +++ b/src/main/resources/assets/baseelectric/shaders/core/unshaded.json @@ -7,9 +7,11 @@ "uniforms": [ {"name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]}, {"name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]}, + {"name": "IViewRotMat", "type": "matrix3x3", "count": 9, "values": [1,0,0,0,1,0,0,0,1]}, {"name": "ColorModulator", "type": "float", "count": 4, "values": [1,1,1,1]}, {"name": "FogStart", "type": "float", "count": 1, "values": [0]}, {"name": "FogEnd", "type": "float", "count": 1, "values": [1]}, - {"name": "FogColor", "type": "float", "count": 4, "values": [0,0,0,0]} + {"name": "FogColor", "type": "float", "count": 4, "values": [0,0,0,0]}, + {"name": "FogShape", "type": "int", "count": 1, "values": [0]} ] } diff --git a/src/main/resources/assets/baseelectric/shaders/core/unshaded.vsh b/src/main/resources/assets/baseelectric/shaders/core/unshaded.vsh index 2900132..0f53d7a 100644 --- a/src/main/resources/assets/baseelectric/shaders/core/unshaded.vsh +++ b/src/main/resources/assets/baseelectric/shaders/core/unshaded.vsh @@ -1,5 +1,7 @@ #version 150 +#moj_import + in vec3 Position; in vec4 Color; in vec2 UV0; @@ -11,6 +13,8 @@ uniform sampler2D Sampler1; uniform sampler2D Sampler2; uniform mat4 ModelViewMat; uniform mat4 ProjMat; +uniform mat3 IViewRotMat; +uniform int FogShape; out float vertexDistance; out vec4 vertexColor; @@ -22,7 +26,7 @@ out vec4 normal; void main() { vec4 viewPosition = ModelViewMat * vec4(Position, 1.0); gl_Position = ProjMat * viewPosition; - vertexDistance = length(viewPosition.xyz); + vertexDistance = fog_distance(ModelViewMat, IViewRotMat * Position, FogShape); vertexColor = Color; lightMapColor = texelFetch(Sampler2, UV2 / 16, 0); overlayColor = texelFetch(Sampler1, UV1, 0); diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta index a1f06d6..1a55219 100644 --- a/src/main/resources/pack.mcmeta +++ b/src/main/resources/pack.mcmeta @@ -1,6 +1,8 @@ { "pack":{ - "pack_format":7, + "pack_format":9, + "forge:resource_pack_format":8, + "forge:data_pack_format":9, "description":"baseElectric" } } diff --git a/src/test/java/zone/moddev/mc/baseelectric/blocks/NativeBlockContractTest.java b/src/test/java/zone/moddev/mc/baseelectric/blocks/NativeBlockContractTest.java index b91499b..7649712 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/blocks/NativeBlockContractTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/blocks/NativeBlockContractTest.java @@ -10,7 +10,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.Shapes; -import net.minecraft.server.Bootstrap; +import zone.moddev.mc.baseelectric.verification.NativeTestBootstrap; import net.minecraft.world.level.Explosion; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.Level; @@ -23,7 +23,7 @@ import static org.junit.Assert.*; public class NativeBlockContractTest { - @BeforeClass public static void bootstrapNativeRegistries() { net.minecraft.SharedConstants.tryDetectVersion(); net.minecraft.server.Bootstrap.bootStrap(); } + @BeforeClass public static void bootstrapNativeRegistries() { NativeTestBootstrap.initialize(); } @Test public void scaffoldRetainsNonSolidFullCollisionAndSharedStateProperties() { ElectricScaffoldBlock block = new ElectricScaffoldBlock(); diff --git a/src/test/java/zone/moddev/mc/baseelectric/blocks/OreHarvestContractTest.java b/src/test/java/zone/moddev/mc/baseelectric/blocks/OreHarvestContractTest.java index e26d7d0..8625adb 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/blocks/OreHarvestContractTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/blocks/OreHarvestContractTest.java @@ -1,26 +1,18 @@ package zone.moddev.mc.baseelectric.blocks; +import zone.moddev.mc.baseelectric.verification.NativeTestBootstrap; import com.mojang.authlib.GameProfile; import java.lang.reflect.Field; +import java.util.HashSet; +import java.util.Set; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.core.BlockPos; -import net.minecraft.server.Bootstrap; import net.minecraft.world.level.EmptyBlockGetter; -import net.minecraftforge.common.ForgeHooks; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; import net.minecraft.tags.BlockTags; -import net.minecraft.tags.StaticTags; -import net.minecraft.tags.SerializationTags; -import net.minecraft.tags.Tag; -import net.minecraft.tags.TagCollection; -import net.minecraft.tags.TagContainer; -import net.minecraft.core.Registry; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; import net.minecraft.world.level.block.Block; import org.junit.BeforeClass; import org.junit.Test; @@ -28,10 +20,7 @@ public class OreHarvestContractTest { @BeforeClass public static void bootstrapNativeRegistries() throws Exception { - net.minecraft.SharedConstants.tryDetectVersion(); - net.minecraft.server.Bootstrap.bootStrap(); - StaticTags.resetAllToEmpty(); - SerializationTags.bind(TagContainer.EMPTY); + NativeTestBootstrap.initialize(); // A plain JVM does not run Forge's resource reload that orders vanilla tiers. java.lang.reflect.Method sort = net.minecraftforge.common.TierSortingRegistry.class .getDeclaredMethod("recalculateItemTiers"); @@ -40,10 +29,6 @@ public class OreHarvestContractTest { assertTrue(net.minecraftforge.common.TierSortingRegistry.getSortedTiers() .contains(net.minecraft.world.item.Tiers.STONE)); } - private TagContainer previousTags; - @org.junit.Before public void saveTags() { previousTags = SerializationTags.getInstance(); } - @org.junit.After public void restoreTags() { install(previousTags); } - @Test public void lithiumRetainsStonePickaxeMinimumThroughNativeHarvestChecks() throws Exception { BlockState lithium = new Ore(1).defaultBlockState(); bindHarvestTags(lithium.getBlock(), "baseelectric:li_ore"); @@ -59,7 +44,9 @@ public class OreHarvestContractTest { } @Test public void sulfurRetainsWoodenPickaxeMinimumThroughNativeHarvestChecks() throws Exception { - BlockState sulfur = new Ore(new ItemStack(Items.GUNPOWDER), 0, 3).defaultBlockState(); + BlockState sulfur = Ore.withDeferredDrop(() -> { + throw new AssertionError("Harvest eligibility must not require the drop item to be registered"); + }, 0, 3).defaultBlockState(); bindHarvestTags(sulfur.getBlock(), "baseelectric:sulfur_ore"); assertTrue(sulfur.requiresCorrectToolForDrops()); assertTrue(sulfur.is(BlockTags.MINEABLE_WITH_PICKAXE)); @@ -71,31 +58,17 @@ public class OreHarvestContractTest { } private static void bindHarvestTags(Block block, String id) throws Exception { - Map> tags = new HashMap<>(BlockTags.getAllTags().getAllTags()); - for (String name : new String[]{"mineable/pickaxe", "needs_stone_tool"}) { - java.nio.file.Path path = java.nio.file.Paths.get("src/main/resources/data/minecraft/tags/blocks", name + ".json"); - com.google.gson.JsonObject json = new com.google.gson.JsonParser().parse(java.nio.file.Files.readString(path)).getAsJsonObject(); - boolean present = false; - for (com.google.gson.JsonElement value : json.getAsJsonArray("values")) present |= id.equals(value.getAsString()); - tags.put(new ResourceLocation(name), Tag.fromSet(present ? Set.of(block) : Set.of())); - } - tags.putIfAbsent(new ResourceLocation("needs_iron_tool"), Tag.fromSet(Set.of())); - tags.putIfAbsent(new ResourceLocation("needs_diamond_tool"), Tag.fromSet(Set.of())); - for (Tag.Named other : java.util.List.of(BlockTags.MINEABLE_WITH_AXE, - BlockTags.MINEABLE_WITH_SHOVEL, BlockTags.MINEABLE_WITH_HOE)) { - tags.putIfAbsent(other.getName(), Tag.fromSet(Set.of())); - } - for (net.minecraft.world.item.Tier tier : net.minecraftforge.common.TierSortingRegistry.getSortedTiers()) { - if (tier.getTag() instanceof Tag.Named) { - tags.putIfAbsent(((Tag.Named) tier.getTag()).getName(), Tag.fromSet(Set.of())); + Set> tags = new HashSet<>(); + for (TagKey tag : java.util.List.of(BlockTags.MINEABLE_WITH_PICKAXE, BlockTags.NEEDS_STONE_TOOL)) { + java.nio.file.Path path = java.nio.file.Paths.get("src/main/resources/data/minecraft/tags/blocks", + tag.location().getPath() + ".json"); + var json = com.google.gson.JsonParser.parseString(java.nio.file.Files.readString(path)).getAsJsonObject(); + for (var value : json.getAsJsonArray("values")) { + if (id.equals(value.getAsString())) tags.add(tag); } } - install(new TagContainer.Builder().add(Registry.BLOCK_REGISTRY, TagCollection.of(tags)).build()); - } - - private static void install(TagContainer tags) { - SerializationTags.bind(tags); - StaticTags.resetAll(tags); + // Bind only the fresh test block's holder; leave the global registry alone. + block.builtInRegistryHolder().bindTags(tags); } private static boolean canHarvest(BlockState state, ItemStack held) throws Exception { 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 514bf3f..5de01f3 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/entities/ElectricOperationalHydroturbineAnchorTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/entities/ElectricOperationalHydroturbineAnchorTest.java @@ -11,7 +11,7 @@ import net.minecraft.nbt.ListTag; import net.minecraft.network.protocol.Packet; import net.minecraft.world.phys.Vec3; -import net.minecraft.server.Bootstrap; +import zone.moddev.mc.baseelectric.verification.NativeTestBootstrap; import net.minecraft.world.level.Level; import net.minecraft.server.level.ServerLevel; import org.junit.BeforeClass; @@ -20,7 +20,7 @@ import static org.junit.Assert.*; public class ElectricOperationalHydroturbineAnchorTest { - @BeforeClass public static void bootstrap() { net.minecraft.SharedConstants.tryDetectVersion(); net.minecraft.server.Bootstrap.bootStrap(); } + @BeforeClass public static void bootstrap() { NativeTestBootstrap.initialize(); } @Test public void nativeEntityLoadStillClampsOverspeedMotionAndPreservesItsBoundary() throws Exception { ControlEntity control = new ControlEntity(controlType(), world()); diff --git a/src/test/java/zone/moddev/mc/baseelectric/entities/HydroturbinePersistenceTest.java b/src/test/java/zone/moddev/mc/baseelectric/entities/HydroturbinePersistenceTest.java index 0648f82..4435ff4 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/entities/HydroturbinePersistenceTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/entities/HydroturbinePersistenceTest.java @@ -1,5 +1,6 @@ package zone.moddev.mc.baseelectric.entities; +import zone.moddev.mc.baseelectric.verification.NativeTestBootstrap; import java.lang.reflect.Field; import net.minecraft.nbt.CompoundTag; import net.minecraft.core.BlockPos; @@ -9,7 +10,7 @@ import static org.junit.Assert.*; public class HydroturbinePersistenceTest { - @org.junit.BeforeClass public static void bootstrap() { net.minecraft.SharedConstants.tryDetectVersion(); net.minecraft.server.Bootstrap.bootStrap(); } + @org.junit.BeforeClass public static void bootstrap() { NativeTestBootstrap.initialize(); } @Test public void generatorGuiOutputIsNormalizedWithoutChangingGenerationRate() { HydroelectricGeneratorTileEntity active = new HydroelectricGeneratorTileEntity(net.minecraft.core.BlockPos.ZERO, new zone.moddev.mc.baseelectric.machines.HydroelectricGeneratorBlock().defaultBlockState()) { @Override public boolean isActive() { return true; } diff --git a/src/test/java/zone/moddev/mc/baseelectric/graphics/RendererBufferContractTest.java b/src/test/java/zone/moddev/mc/baseelectric/graphics/RendererBufferContractTest.java index 0779fff..36230b8 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/graphics/RendererBufferContractTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/graphics/RendererBufferContractTest.java @@ -10,7 +10,7 @@ import net.minecraft.client.renderer.RenderType; import com.mojang.math.Vector3f; import net.minecraft.core.Direction; -import net.minecraft.server.Bootstrap; +import zone.moddev.mc.baseelectric.verification.NativeTestBootstrap; import org.junit.BeforeClass; import org.junit.Test; import zone.moddev.mc.baseelectric.init.Power; @@ -25,7 +25,7 @@ public class RendererBufferContractTest { private static final int FULL_BRIGHT = 15 << 20 | 15 << 4; @BeforeClass public static void bootstrapNativeRegistries() { - net.minecraft.SharedConstants.tryDetectVersion(); net.minecraft.server.Bootstrap.bootStrap(); + NativeTestBootstrap.initialize(); } @Test public void quadNormalsUseWindingAndTheNativeNormalMatrix() { 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 a789e6c..649c8c7 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/init/VillagesTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/init/VillagesTest.java @@ -3,14 +3,14 @@ import java.util.Random; import net.minecraft.world.item.trading.MerchantOffer; import net.minecraft.nbt.CompoundTag; -import net.minecraft.server.Bootstrap; +import zone.moddev.mc.baseelectric.verification.NativeTestBootstrap; import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert.*; public class VillagesTest { @BeforeClass public static void bootstrapVanillaItems() { - net.minecraft.SharedConstants.tryDetectVersion(); net.minecraft.server.Bootstrap.bootStrap(); + NativeTestBootstrap.initialize(); } @Test public void nativeNegativePricesKeepBulkGoodsForOneEmerald() { diff --git a/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricDrillPersistenceTest.java b/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricDrillPersistenceTest.java index 006a3f6..f64b2b2 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricDrillPersistenceTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricDrillPersistenceTest.java @@ -4,7 +4,7 @@ import net.minecraft.nbt.ListTag; import net.minecraft.core.Direction; import net.minecraft.core.BlockPos; -import net.minecraft.server.Bootstrap; +import zone.moddev.mc.baseelectric.verification.NativeTestBootstrap; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.HashSet; @@ -15,7 +15,7 @@ import static org.junit.Assert.assertEquals; public class ElectricDrillPersistenceTest { - @BeforeClass public static void bootstrapNativeRegistries() { net.minecraft.SharedConstants.tryDetectVersion(); net.minecraft.server.Bootstrap.bootStrap(); } + @BeforeClass public static void bootstrapNativeRegistries() { NativeTestBootstrap.initialize(); } @Test public void retainsLegacyProgressUntilWorldIsAttached() { ElectricDrillTileEntity drill = new ElectricDrillTileEntity(net.minecraft.core.BlockPos.ZERO, new zone.moddev.mc.baseelectric.machines.ElectricDrillBlock().defaultBlockState()); @@ -81,6 +81,13 @@ public class ElectricDrillPersistenceTest { assertEquals(250.0F, ElectricDrillTileEntity.ENERGY_COST_MOVE, 0.0F); assertEquals(24.0F, ElectricDrillTileEntity.ENERGY_COST_PROGRESS_TICK, 0.0F); assertEquals(6, drill.numberOfOutputSlots()); + + CompoundTag transfer = drill.saveWithoutMetadata(); + assertEquals(94, transfer.getShort("progress")); + ElectricDrillTileEntity placed = new ElectricDrillTileEntity(new BlockPos(5, 70, 9), drill.getBlockState()); + placed.load(transfer); + assertEquals(416.0F, placed.getEnergy(placed.getEnergyType()), 0.0F); + assertEquals(new BlockPos(5, 70, 9), placed.getBlockPos()); } private static CompoundTag target(int progress) { 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 80fbcb0..beb7e4e 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalNativeDefectsTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalNativeDefectsTest.java @@ -11,7 +11,7 @@ import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.level.ClipContext; import net.minecraft.world.phys.Vec3; -import net.minecraft.server.Bootstrap; +import zone.moddev.mc.baseelectric.verification.NativeTestBootstrap; import net.minecraft.world.level.Level; import net.minecraft.server.level.ServerLevel; import org.junit.BeforeClass; @@ -22,7 +22,7 @@ /** Focused regressions for retained independent native operating failures. */ public class ElectricOperationalNativeDefectsTest { - @BeforeClass public static void bootstrap() { net.minecraft.SharedConstants.tryDetectVersion(); net.minecraft.server.Bootstrap.bootStrap(); } + @BeforeClass public static void bootstrap() { NativeTestBootstrap.initialize(); } @Test public void controllerCannotRequestMoreThanItsHundredUnitElectricalBuffer() throws Exception { GrowthChamberControllerTileEntity controller = controller(); 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 eaabebc..56d4e5c 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalPumpPickupTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalPumpPickupTest.java @@ -24,7 +24,7 @@ import net.minecraft.core.Direction; import net.minecraft.sounds.SoundEvent; import net.minecraft.core.BlockPos; -import net.minecraft.server.Bootstrap; +import zone.moddev.mc.baseelectric.verification.NativeTestBootstrap; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.Level; import net.minecraft.server.level.ServerLevel; @@ -41,7 +41,7 @@ public class ElectricOperationalPumpPickupTest { private static final float RESERVE = 8000; private static final float PICKUP_COST = 1000 + 32; - @BeforeClass public static void bootstrap() { net.minecraft.SharedConstants.tryDetectVersion(); net.minecraft.server.Bootstrap.bootStrap(); } + @BeforeClass public static void bootstrap() { NativeTestBootstrap.initialize(); } @Test public void waterloggedSlabKeepsItsBlockAndSlabType() throws Exception { BlockState wet = Blocks.OAK_SLAB.defaultBlockState() 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 fe3a937..e1e93aa 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalRedstoneCacheTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalRedstoneCacheTest.java @@ -7,7 +7,7 @@ import java.util.Collections; import java.util.List; import net.minecraft.core.BlockPos; -import net.minecraft.server.Bootstrap; +import zone.moddev.mc.baseelectric.verification.NativeTestBootstrap; import net.minecraft.world.level.Level; import net.minecraft.server.level.ServerLevel; import org.junit.BeforeClass; @@ -20,7 +20,7 @@ public class ElectricOperationalRedstoneCacheTest { private static final BlockPos EVEN = new BlockPos(8, 80, 8); private static final BlockPos ODD = new BlockPos(9, 81, 9); - @BeforeClass public static void bootstrap() { net.minecraft.SharedConstants.tryDetectVersion(); net.minecraft.server.Bootstrap.bootStrap(); } + @BeforeClass public static void bootstrap() { NativeTestBootstrap.initialize(); } @Test public void signalledFirstUseDoesNotWaitForPowerUpdate() throws Exception { ProbeMachine machine = fixture(EVEN, true); 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 f94d4f5..eacea42 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalSoundBroadcastTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalSoundBroadcastTest.java @@ -12,7 +12,7 @@ import net.minecraft.network.protocol.game.ClientboundCustomSoundPacket; import net.minecraft.sounds.SoundEvents; import net.minecraft.world.phys.AABB; -import net.minecraft.server.Bootstrap; +import zone.moddev.mc.baseelectric.verification.NativeTestBootstrap; import net.minecraft.world.level.Level; import net.minecraft.server.level.ServerLevel; import org.junit.BeforeClass; @@ -22,7 +22,7 @@ /** Real broadcast methods with an explicit player list and forbidden chunk-backed entity queries. */ public class ElectricOperationalSoundBroadcastTest { - @BeforeClass public static void bootstrap() { net.minecraft.SharedConstants.tryDetectVersion(); net.minecraft.server.Bootstrap.bootStrap(); } + @BeforeClass public static void bootstrap() { NativeTestBootstrap.initialize(); } @Test public void machineBroadcastUsesOnlyExistingIntersectingConnectedPlayers() throws Exception { QueryRejectingWorld world = fixture(); diff --git a/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalStillCapacityTest.java b/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalStillCapacityTest.java index 645785b..dd86839 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalStillCapacityTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalStillCapacityTest.java @@ -3,7 +3,7 @@ import cyano.poweradvantage.api.fluid.FluidStack; import java.lang.reflect.Method; import net.minecraft.world.level.material.Fluids; -import net.minecraft.server.Bootstrap; +import zone.moddev.mc.baseelectric.verification.NativeTestBootstrap; import org.junit.BeforeClass; import org.junit.Test; import zone.moddev.mc.basepower.registry.still.recipe.DistillationRecipe; @@ -13,7 +13,7 @@ /** Capacity preflight tests; the shared rig separately tests natural machine ticks. */ public class ElectricOperationalStillCapacityTest { - @BeforeClass public static void bootstrap() { net.minecraft.SharedConstants.tryDetectVersion(); net.minecraft.server.Bootstrap.bootStrap(); } + @BeforeClass public static void bootstrap() { NativeTestBootstrap.initialize(); } @Test public void aFullTankCannotConsumeInput() throws Exception { ElectricStillTileEntity still = fixture(1000); diff --git a/src/test/java/zone/moddev/mc/baseelectric/machines/GrowthChamberPersistenceTest.java b/src/test/java/zone/moddev/mc/baseelectric/machines/GrowthChamberPersistenceTest.java index 28d518e..fdbd12d 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/machines/GrowthChamberPersistenceTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/machines/GrowthChamberPersistenceTest.java @@ -1,20 +1,20 @@ package zone.moddev.mc.baseelectric.machines; +import zone.moddev.mc.baseelectric.verification.NativeTestBootstrap; import net.minecraft.nbt.CompoundTag; import org.junit.Test; import static org.junit.Assert.*; public class GrowthChamberPersistenceTest { - @org.junit.BeforeClass public static void bootstrap() { net.minecraft.SharedConstants.tryDetectVersion(); net.minecraft.server.Bootstrap.bootStrap(); } + @org.junit.BeforeClass public static void bootstrap() { NativeTestBootstrap.initialize(); } @Test public void legacyGrowthAndProgressSurviveReadSaveBeforeWorldAttachment() { CompoundTag source = new CompoundTag(); source.putByteArray("growth", new byte[] {2, 4, 6}); source.putIntArray("prog", new int[] {245, 468, 627}); for (int pass = 0; pass < 3; pass++) { GrowthChamberTileEntity chamber = new GrowthChamberTileEntity(net.minecraft.core.BlockPos.ZERO, new zone.moddev.mc.baseelectric.machines.GrowthChamberBlock().defaultBlockState()); - chamber.loadFrom(source); - CompoundTag saved = new CompoundTag(); - chamber.saveTo(saved); + chamber.load(source); + CompoundTag saved = chamber.saveWithoutMetadata(); assertArrayEquals(new byte[] {2, 4, 6}, saved.getByteArray("growth")); assertArrayEquals(new int[] {245, 468, 627}, saved.getIntArray("prog")); source = saved; diff --git a/src/test/java/zone/moddev/mc/baseelectric/machines/Native117ConstructionTest.java b/src/test/java/zone/moddev/mc/baseelectric/machines/Native118ConstructionTest.java similarity index 69% rename from src/test/java/zone/moddev/mc/baseelectric/machines/Native117ConstructionTest.java rename to src/test/java/zone/moddev/mc/baseelectric/machines/Native118ConstructionTest.java index 3a62658..f3a6d2e 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/machines/Native117ConstructionTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/machines/Native118ConstructionTest.java @@ -2,7 +2,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; -import net.minecraft.server.Bootstrap; +import zone.moddev.mc.baseelectric.verification.NativeTestBootstrap; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.EntityBlock; import net.minecraft.world.level.block.entity.BlockEntity; @@ -12,8 +12,8 @@ import org.junit.Test; import static org.junit.Assert.*; -public class Native117ConstructionTest { - @BeforeClass public static void bootstrap() { net.minecraft.SharedConstants.tryDetectVersion(); net.minecraft.server.Bootstrap.bootStrap(); } +public class Native118ConstructionTest { + @BeforeClass public static void bootstrap() { NativeTestBootstrap.initialize(); } @Test public void everyFactoryPreservesItsNativePositionAndState() { BlockPos pos = new BlockPos(-117, 83, 4917); @@ -33,10 +33,22 @@ public class Native117ConstructionTest { @Test public void turretPacketUsesConstructorCoordinatesBeforeWorldAttachment() { BlockPos pos = new BlockPos(1531, 91, -475); - LaserTurretTileEntity turret = new LaserTurretTileEntity(pos, new LaserTurretBlock(false).defaultBlockState()); - assertEquals(pos, turret.getUpdatePacket().getPos()); - turret.setOpticPosition(); - assertEquals(pos, turret.getBlockPos()); + LaserTurretBlock block = new LaserTurretBlock(false); + BlockEntityType type = BlockEntityType.Builder.of(LaserTurretTileEntity::new, block).build(null); + BlockEntityType previous = zone.moddev.mc.baseelectric.init.Entities.LASER_TURRET; + try { + zone.moddev.mc.baseelectric.init.Entities.LASER_TURRET = type; + LaserTurretTileEntity turret = new LaserTurretTileEntity(pos, block.defaultBlockState()); + var packet = turret.getUpdatePacket(); + assertEquals(pos, packet.getPos()); + assertSame(type, packet.getType()); + assertEquals(java.util.Set.of("energy", "lock"), packet.getTag().getAllKeys()); + assertTrue(turret.getUpdateTag().contains("player")); + turret.setOpticPosition(); + assertEquals(pos, turret.getBlockPos()); + } finally { + zone.moddev.mc.baseelectric.init.Entities.LASER_TURRET = previous; + } } @Test public void ledOnlyOffersItsOwnNativeTicker() { diff --git a/src/test/java/zone/moddev/mc/baseelectric/recipe/RecipeConditionsTest.java b/src/test/java/zone/moddev/mc/baseelectric/recipe/RecipeConditionsTest.java index 13db56f..8c5ccc6 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/recipe/RecipeConditionsTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/recipe/RecipeConditionsTest.java @@ -3,6 +3,7 @@ import java.util.Locale; import com.google.gson.JsonObject; import org.junit.Test; +import net.minecraftforge.common.crafting.conditions.ICondition; import zone.moddev.mc.basepower.PowerAdvantage; import zone.moddev.mc.basepower.RecipeMode; import static org.junit.Assert.*; @@ -24,6 +25,7 @@ public class RecipeConditionsTest { for (RecipeMode selected : RecipeMode.values()) { PowerAdvantage.recipeMode = selected; assertEquals((selected == expected) != inverted, condition.test()); + assertEquals(condition.test(), condition.test(ICondition.IContext.EMPTY)); } } } diff --git a/src/test/java/zone/moddev/mc/baseelectric/resources/BaseMetalsRawRecipeContractTest.java b/src/test/java/zone/moddev/mc/baseelectric/resources/BaseMetalsRawRecipeContractTest.java index 59361c4..9cf55f2 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/resources/BaseMetalsRawRecipeContractTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/resources/BaseMetalsRawRecipeContractTest.java @@ -5,12 +5,13 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; +import net.minecraft.util.GsonHelper; import org.junit.Test; import static org.junit.Assert.*; /** Audits real dependency recipe bytes, not a replacement recipe or a runtime job. */ public class BaseMetalsRawRecipeContractTest { - @Test public void publishedOneSeventeenRecipesKeepRawAndOreOutputsDistinct() throws Exception { + @Test public void nativeCrushingRecipesKeepRawAndOreOutputsDistinct() throws Exception { for (String metal : new String[]{"copper", "iron", "gold"}) { for (String input : new String[]{"ore", "raw"}) { String path = "/data/basemetals/recipes/" + metal + "_" + input + "_crushing.json"; @@ -21,7 +22,8 @@ public class BaseMetalsRawRecipeContractTest { assertEquals("forge:" + (input.equals("ore") ? "ores/" : "raw_materials/") + metal, recipe.getAsJsonObject("ingredient").get("tag").getAsString()); assertEquals("basemetals:" + metal + "_powder", recipe.getAsJsonObject("result").get("item").getAsString()); - assertEquals(input.equals("ore") ? 2 : 1, recipe.getAsJsonObject("result").get("count").getAsInt()); + // Base Metals delegates to CraftingHelper, whose omitted count defaults to one. + assertEquals(input.equals("ore") ? 2 : 1, GsonHelper.getAsInt(recipe.getAsJsonObject("result"), "count", 1)); } } } 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 9ab7f52..86e9d32 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/resources/BlockLootResourceTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/resources/BlockLootResourceTest.java @@ -33,9 +33,11 @@ public class BlockLootResourceTest { assertFalse(Files.exists(root.resolve("pump_pipe_electric.json"))); } - @Test public void resourcePackDeclaresTheNativeOneSeventeenFormat() throws Exception { - assertEquals(7, read(Paths.get("src/main/resources/pack.mcmeta")) - .getAsJsonObject("pack").get("pack_format").getAsInt()); + @Test public void resourcePackDeclaresTheNativeOneEighteenFormats() throws Exception { + JsonObject pack = read(Paths.get("src/main/resources/pack.mcmeta")).getAsJsonObject("pack"); + assertEquals(9, pack.get("pack_format").getAsInt()); + assertEquals(8, pack.get("forge:resource_pack_format").getAsInt()); + assertEquals(9, pack.get("forge:data_pack_format").getAsInt()); } private static JsonObject read(Path path) 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 cc3fe9b..f0aaead 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/resources/BuildWorkflowContractTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/resources/BuildWorkflowContractTest.java @@ -11,11 +11,12 @@ public class BuildWorkflowContractTest { 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", "16.0.2+7", "8.0.502+7", "17.0.1+12"}) { + for (String version : new String[] {"25.0.3+9.0.LTS", "8.0.502+7", "17.0.1+12"}) { 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_16_X64,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64")); + assertTrue(source.contains("-Porg.gradle.java.installations.paths=$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64")); + assertFalse(source.contains("JAVA_HOME_16_X64")); 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.")); @@ -33,18 +34,18 @@ public class BuildWorkflowContractTest { 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 policy = source.indexOf("git config --global core.autocrlf false"); - int pins = source.indexOf("- name: Require coordinated 1.17.1 dependency pins"); + int pins = source.indexOf("- name: Require coordinated 1.18.2 dependency pins"); assertTrue(policy > 0); assertTrue(policy < pins); for (String repository : new String[] {"OreSpawn", "BaseMetals", "basePower"}) { assertTrue(pins < source.indexOf("repository: MinecraftModDevelopmentMods/" + repository)); } - assertTrue(source.contains("ref: b56e2b62a1a6ea4d1dd96f8646a87801a817dbdf")); - assertTrue(source.contains("BaseMetals-3.1.1.117011-deobf.jar")); - assertTrue(source.contains("basePower-2.4.9.117011-deobf.jar")); + assertTrue(source.contains("ref: d31f52f296739f34a0c13f554abf3e1ac071ae25")); + assertTrue(source.contains("BaseMetals-3.1.2.118021-deobf.jar")); + assertTrue(source.contains("basePower-2.4.10.118021-deobf.jar")); assertTrue(source.contains("ref: ${{ steps.pins.outputs.basemetals_ref }}")); assertTrue(source.contains("ref: ${{ steps.pins.outputs.power_ref }}")); - assertTrue(source.contains("\"$pin_state\" != \"LOCAL_CHECKPOINT\"")); + assertTrue(source.contains("\"$pin_state\" != \"PUBLISHED_RELEASE\"")); assertTrue(source.contains("! \"$power_ref\" =~ ^[a-f0-9]{40}$")); assertTrue(source.contains("! \"$bm_ref\" =~ ^[a-f0-9]{40}$")); for (String hash : new String[] {"power_sha", "bm_sha", "bootstrap_sha"}) { @@ -59,7 +60,8 @@ public class BuildWorkflowContractTest { assertFalse(source.contains("78E58DD2AB04E2B9F3267E5735C64AAE7E93EAC97571CB04485038862C853ED3")); assertFalse(source.contains("ce90c24c302d0ff3565db2db0c6c94a3b5f21f83")); assertFalse(source.contains("8BBB03DD7E4AF08A7390A161ED5AA94E292BEE56EF60E550B5512E967E0E9174")); - assertTrue(source.contains("branches: [master-1.17.1")); + assertTrue(source.contains("branches: [master-1.18.2")); + assertTrue(source.contains("-PmavenizerCompatibilityJar=${{ steps.mavenizer.outputs.fixture }}")); assertFalse(source.contains("master-1.14.4")); } } diff --git a/src/test/java/zone/moddev/mc/baseelectric/resources/RecipeUnlockContractTest.java b/src/test/java/zone/moddev/mc/baseelectric/resources/RecipeUnlockContractTest.java index 1accdd3..e364c65 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/resources/RecipeUnlockContractTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/resources/RecipeUnlockContractTest.java @@ -1,5 +1,6 @@ package zone.moddev.mc.baseelectric.resources; +import zone.moddev.mc.baseelectric.verification.NativeTestBootstrap; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -77,8 +78,7 @@ public class RecipeUnlockContractTest { } @Test public void nativeParserRestrictsItemArraysButSilentlyIgnoresTheLegacySingularKey() { - net.minecraft.SharedConstants.tryDetectVersion(); - net.minecraft.server.Bootstrap.bootStrap(); + NativeTestBootstrap.initialize(); JsonObject nativeShape = new JsonParser().parse("{\"items\":[\"minecraft:apple\"]}").getAsJsonObject(); ItemPredicate nativePredicate = ItemPredicate.fromJson(nativeShape); assertTrue(nativePredicate.matches(new ItemStack(Items.APPLE))); @@ -143,7 +143,7 @@ private static String ingredientKey(JsonObject predicate) { private static String unlockPredicateKey(JsonObject predicate) { assertEquals("Unlock predicates must constrain exactly one item or tag", 1, predicate.size()); - assertFalse("Native 1.17 ignores the singular item key in advancement predicates", predicate.has("item")); + assertFalse("Native 1.18 ignores the singular item key in advancement predicates", predicate.has("item")); if (predicate.has("tag")) { assertTrue("A tag predicate must be a string", predicate.get("tag").isJsonPrimitive() && predicate.getAsJsonPrimitive("tag").isString()); 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 1a97af9..7f1b305 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/resources/ShaderResourceContractTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/resources/ShaderResourceContractTest.java @@ -21,6 +21,9 @@ public class ShaderResourceContractTest { java.util.stream.StreamSupport.stream(json.getAsJsonArray("attributes").spliterator(), false) .map(value -> value.getAsString()).collect(Collectors.toList())); assertNotNull(getClass().getResource("/assets/minecraft/shaders/core/rendertype_entity_translucent.fsh")); + var uniformNames = java.util.stream.StreamSupport.stream(json.getAsJsonArray("uniforms").spliterator(), false) + .map(value -> value.getAsJsonObject().get("name").getAsString()).collect(Collectors.toSet()); + assertTrue(uniformNames.containsAll(java.util.Set.of("IViewRotMat", "FogShape"))); } @Test public void noDiffuseLightingDoesNotRemoveLightmapOrOverlaySampling() throws Exception { @@ -29,6 +32,12 @@ public class ShaderResourceContractTest { assertTrue(vertex.contains("lightMapColor = texelFetch(Sampler2, UV2 / 16, 0);")); assertTrue(vertex.contains("overlayColor = texelFetch(Sampler1, UV1, 0);")); assertFalse(vertex.contains("minecraft_mix_light")); - assertTrue(vertex.contains("vertexDistance = length(viewPosition.xyz);")); + assertTrue(vertex.contains("#moj_import ")); + String fog = "vertexDistance = fog_distance(ModelViewMat, IViewRotMat * Position, FogShape);"; + assertTrue(vertex.contains(fog)); + try (var vanilla = getClass().getResourceAsStream("/assets/minecraft/shaders/core/rendertype_entity_translucent.vsh")) { + assertNotNull(vanilla); + assertTrue(new String(vanilla.readAllBytes(), java.nio.charset.StandardCharsets.UTF_8).contains(fog)); + } } } diff --git a/src/test/java/zone/moddev/mc/baseelectric/verification/NativeTestBootstrap.java b/src/test/java/zone/moddev/mc/baseelectric/verification/NativeTestBootstrap.java new file mode 100644 index 0000000..ab41388 --- /dev/null +++ b/src/test/java/zone/moddev/mc/baseelectric/verification/NativeTestBootstrap.java @@ -0,0 +1,21 @@ +package zone.moddev.mc.baseelectric.verification; + +import net.minecraft.SharedConstants; +import net.minecraft.server.Bootstrap; +import net.minecraftforge.registries.GameData; + +/** Opens the native Forge registration phase for worldless test fixtures. */ +public final class NativeTestBootstrap { + private static boolean ready; + + private NativeTestBootstrap() {} + + public static synchronized void initialize() { + if (ready) return; + SharedConstants.tryDetectVersion(); + Bootstrap.bootStrap(); + // The plain test JVM has no FML registration phase after vanilla bootstrap. + GameData.unfreezeData(); + ready = true; + } +} diff --git a/tools/verification/AuditedJUnit.java b/tools/verification/AuditedJUnit.java index 2e6907e..4b279a7 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.17.1-ECJ"); + xml.writeAttribute("name", "baseElectric-1.18.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/ForgeNativeTestBootstrap.java b/tools/verification/ForgeNativeTestBootstrap.java index 958aea0..e4bd9c3 100644 --- a/tools/verification/ForgeNativeTestBootstrap.java +++ b/tools/verification/ForgeNativeTestBootstrap.java @@ -13,8 +13,8 @@ public static void premain(String options, Instrumentation instrumentation) { instrumentation.addTransformer(new ClassFileTransformer() { @Override public byte[] transform(ClassLoader loader, String name, Class type, ProtectionDomain domain, byte[] bytes) { - if (loader == null || name == null || !(name.equals("net/minecraftforge/fmllegacy/network/NetworkEvent") - || name.startsWith("net/minecraftforge/fmllegacy/network/NetworkEvent$"))) return null; + if (loader == null || name == null || !(name.equals("net/minecraftforge/network/NetworkEvent") + || name.startsWith("net/minecraftforge/network/NetworkEvent$"))) return null; try { Class readerType = Class.forName("org.objectweb.asm.ClassReader", true, loader); Object reader = readerType.getConstructor(byte[].class).newInstance((Object) bytes); diff --git a/tools/verification/Invoke-ElectricPackagedSmoke.ps1 b/tools/verification/Invoke-ElectricPackagedSmoke.ps1 new file mode 100644 index 0000000..447bd82 --- /dev/null +++ b/tools/verification/Invoke-ElectricPackagedSmoke.ps1 @@ -0,0 +1,170 @@ +param( + [Parameter(Mandatory)][string]$Evidence, + [Parameter(Mandatory)][string]$ServerSeed, + [Parameter(Mandatory)][string]$EulaFile, + [Parameter(Mandatory)][string]$Java, + [Parameter(Mandatory)][string]$ElectricJar, + [Parameter(Mandatory)][ValidatePattern('^[A-F0-9]{64}$')][string]$ElectricSha256, + [Parameter(Mandatory)][string]$PowerJar, + [Parameter(Mandatory)][ValidatePattern('^[A-F0-9]{64}$')][string]$PowerSha256, + [Parameter(Mandatory)][string]$BaseMetalsJar, + [Parameter(Mandatory)][string]$OreSpawnJar +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest +$root = (Resolve-Path -LiteralPath (Join-Path $PSScriptRoot '../..')).Path +$output = [IO.Path]::GetFullPath($Evidence, $root) +$evidenceRoot = Join-Path $root 'build/port-evidence' +if (!$output.StartsWith($evidenceRoot + [IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase) -or + (Test-Path -LiteralPath $output)) { + throw 'Use a new disposable directory inside build/port-evidence.' +} +$null = New-Item -ItemType Directory -Path $output +$pins = ConvertFrom-StringData (Get-Content -LiteralPath (Join-Path $root 'gradle.properties') -Raw) +$seed = (Resolve-Path -LiteralPath $ServerSeed).Path +$javaPath = (Resolve-Path -LiteralPath $Java).Path +$winArgs = 'libraries/net/minecraftforge/forge/1.18.2-40.3.0/win_args.txt' +$receipt = [ordered]@{ + status = 'FAIL'; scope = 'Packaged, loopback-only Forge 1.18.2 dedicated server smoke; no client or migration proof' + seed = $seed; java = $javaPath; inputs = @(); checks = [ordered]@{}; exitCode = $null; error = $null +} +$process = $null +$stdout = $null +$stderr = $null + +function Wait-ForLine([string]$Pattern, [int]$Seconds) { + $timer = [Diagnostics.Stopwatch]::StartNew() + while ($timer.Elapsed.TotalSeconds -lt $Seconds) { + $read = $process.StandardOutput.ReadLineAsync() + while (!$read.Wait(250)) { + if ($timer.Elapsed.TotalSeconds -ge $Seconds) { throw "Timed out waiting for $Pattern" } + } + $line = $read.Result + if ($null -eq $line) { throw "Server output ended before $Pattern" } + $stdout.WriteLine($line) + if ($line -match $Pattern) { return $line } + if ($line -match 'Failed to start the minecraft server|Failed to complete lifecycle event|Failed to load datapacks') { + throw "Server reported a startup failure: $line" + } + } + throw "Timed out waiting for $Pattern" +} + +function Send-Command([string]$Command) { + $stdout.WriteLine(">> $Command") + $process.StandardInput.WriteLine($Command) + $process.StandardInput.Flush() +} + +try { + if ($pins.poweradvantage_pin_state -ne 'PUBLISHED_RELEASE' -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="17\.') { + throw 'The dedicated-server smoke requires Java 17.' + } + $receipt.javaRelease = (Get-FileHash -LiteralPath $javaRelease).Hash + $receipt.javaSha256 = (Get-FileHash -LiteralPath $javaPath).Hash + $argsText = Get-Content -LiteralPath (Join-Path $seed $winArgs) -Raw + foreach ($marker in @('--launchTarget forgeserver', '--fml.forgeVersion 40.3.0', + '--fml.mcVersion 1.18.2', '--fml.mcpVersion 20220404.173914')) { + if (!$argsText.Contains($marker)) { throw "Unexpected Forge seed arguments: missing $marker" } + } + $receipt.seedArgumentsSha256 = (Get-FileHash -LiteralPath (Join-Path $seed $winArgs)).Hash + $forgeUniversal = Join-Path $seed 'libraries/net/minecraftforge/forge/1.18.2-40.3.0/forge-1.18.2-40.3.0-universal.jar' + $receipt.forgeUniversalSha256 = (Get-FileHash -LiteralPath $forgeUniversal).Hash + if ((Get-Content -LiteralPath $EulaFile -Raw) -notmatch '(?m)^eula=true\s*$') { + throw 'Pass the existing accepted EULA from the local smoke fixture.' + } + Copy-Item -LiteralPath (Join-Path $seed 'libraries') -Destination $output -Recurse + Copy-Item -LiteralPath $EulaFile -Destination (Join-Path $output 'eula.txt') + $modDir = (New-Item -ItemType Directory -Path (Join-Path $output 'mods')).FullName + foreach ($input in @( + @{ path = $ElectricJar; hash = $ElectricSha256; name = 'baseElectric-2.2.11.118021.jar' }, + @{ path = $PowerJar; hash = $PowerSha256; name = 'basePower-2.4.10.118021.jar' }, + @{ path = $BaseMetalsJar; hash = $pins.basemetals_runtime_sha256; name = 'BaseMetals-3.1.2.118021.jar' }, + @{ path = $OreSpawnJar; hash = $pins.orespawn_runtime_sha256; name = 'OreSpawn-4.1.0.118021.jar' })) { + $actual = Get-FileHash -LiteralPath $input.path + if ($actual.Hash -ne $input.hash -or [IO.Path]::GetFileName($actual.Path) -cne $input.name) { + throw "Wrong packaged input: $($actual.Path) SHA-256 $($actual.Hash)" + } + $copy = Join-Path $modDir $input.name + Copy-Item -LiteralPath $actual.Path -Destination $copy + if ((Get-FileHash -LiteralPath $copy).Hash -ne $input.hash) { throw "Copy changed: $copy" } + $receipt.inputs += [ordered]@{ path = $actual.Path; sha256 = $actual.Hash; copy = $copy } + } + [IO.File]::WriteAllLines((Join-Path $output 'server.properties'), @( + 'server-ip=127.0.0.1', 'server-port=0', 'enable-rcon=false', 'enable-query=false', + 'level-name=electric-smoke', 'level-seed=118021', 'generate-structures=false', + 'view-distance=3', 'simulation-distance=3', 'spawn-protection=0' + )) + $start = [Diagnostics.ProcessStartInfo]::new($javaPath) + $start.WorkingDirectory = $output + $start.UseShellExecute = $false + $start.CreateNoWindow = $true + $start.RedirectStandardInput = $true + $start.RedirectStandardOutput = $true + $start.RedirectStandardError = $true + foreach ($arg in @('-Xmx3G', '-Dfml.earlyprogresswindow=false', "@$winArgs", 'nogui')) { + $start.ArgumentList.Add($arg) + } + $stdout = [IO.StreamWriter]::new((Join-Path $output 'console.log')) + $stdout.AutoFlush = $true + $process = [Diagnostics.Process]::Start($start) + $stderr = $process.StandardError.ReadToEndAsync() + $receipt.checks.commonSetup = Wait-ForLine 'baseElectric 2\.2\.11\.118021 common setup complete' 120 + $receipt.checks.ready = Wait-ForLine 'Done \([^)]+\)! For help' 180 + + Send-Command 'forceload add 0 0' + for ($attempt = 0; $attempt -lt 20; $attempt++) { + Send-Command 'execute if block 0 300 0 minecraft:air' + $probe = Wait-ForLine 'Test passed|Test failed|That position is not loaded' 10 + if ($probe -match 'Test passed') { $receipt.checks.testChunkReady = $probe; break } + if ($probe -match 'Test failed') { throw 'The smoke test coordinates are not clear.' } + Start-Sleep -Milliseconds 500 + } + if (!$receipt.checks.Contains('testChunkReady')) { throw 'The smoke chunk never loaded.' } + Send-Command 'setblock 0 300 0 baseelectric:electric_drill' + $receipt.checks.placeDrill = Wait-ForLine 'Changed the block at 0, 300, 0' 20 + Send-Command 'data get block 0 300 0' + $receipt.checks.drillNbt = Wait-ForLine 'block data:.*baseelectric:electric_drill' 20 + Send-Command 'setblock 1 300 0 baseelectric:sulfur_ore' + $receipt.checks.placeSulfur = Wait-ForLine 'Changed the block at 1, 300, 0' 20 + Send-Command 'loot spawn 1 300 0 mine 1 300 0 minecraft:wooden_pickaxe' + $receipt.checks.sulfurLoot = Wait-ForLine 'Dropped [1-4] \[Powdered Sulfur\] from loot table baseelectric:blocks/sulfur_ore' 20 + Send-Command 'data get entity @e[type=minecraft:item,sort=nearest,limit=1,x=1,y=300,z=0,distance=..5] Item' + $receipt.checks.sulfurItem = Wait-ForLine 'entity data:.*baseelectric:sulfur_powder' 20 + $expectedCount = [regex]::Match($receipt.checks.sulfurLoot, 'Dropped ([1-4]) ').Groups[1].Value + if ($receipt.checks.sulfurItem -notmatch "Count: ${expectedCount}b") { + throw 'The live item stack count does not match the sulfur loot response.' + } + Send-Command 'save-all flush' + $receipt.checks.save = Wait-ForLine 'Saved the game' 30 + Send-Command 'stop' + $receipt.checks.shutdown = Wait-ForLine 'All dimensions are saved' 45 + if (!$process.WaitForExit(30000) -or $process.ExitCode -ne 0) { throw 'Server did not stop cleanly.' } + $receipt.status = 'PASS' +} catch { + $receipt.error = $_.ToString() + throw +} finally { + if ($process) { + if (!$process.HasExited) { + try { $process.StandardInput.WriteLine('stop'); $process.StandardInput.Flush() } catch {} + if (!$process.WaitForExit(10000)) { + $process.Kill($true) + $process.WaitForExit() + } + } + $receipt.exitCode = $process.ExitCode + if ($stderr) { [IO.File]::WriteAllText((Join-Path $output 'stderr.log'), $stderr.GetAwaiter().GetResult()) } + $process.Dispose() + } + if ($stdout) { $stdout.Dispose() } + [IO.File]::WriteAllText((Join-Path $output 'receipt.json'), ($receipt | ConvertTo-Json -Depth 8) + "`n") +} + +$receipt | ConvertTo-Json -Depth 8 diff --git a/tools/verification/Verify-ElectricEcj.ps1 b/tools/verification/Verify-ElectricEcj.ps1 index 626fdaf..030848c 100644 --- a/tools/verification/Verify-ElectricEcj.ps1 +++ b/tools/verification/Verify-ElectricEcj.ps1 @@ -2,8 +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]$Java17Home = 'C:/Users/johnb/.jdks/temurin-17.0.1+12/jdk-17.0.1+12', - [string]$Java16Home = 'C:/Users/johnb/.gradle/jdks/eclipse_foundation-16-amd64-windows.2' + [string]$Java17Home = 'C:/Users/johnb/.jdks/temurin-17.0.1+12/jdk-17.0.1+12' ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest @@ -31,7 +30,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 60) { throw "Non-Java-16 ECJ output: $($file.FullName)" } + if (($bytes[6] * 256 + $bytes[7]) -ne 61) { throw "Non-Java-17 ECJ output: $($file.FullName)" } } return $files.Count } @@ -39,9 +38,9 @@ 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" - # Java 16 uses the JDK module image; keep forbidden/discouraged accesses fatal. - $arguments = @('-16', '-proc:none', '-encoding', 'UTF-8', '-err:+forbidden,discouraged', '--system', - ('"' + $Java16Home.Replace('\', '/') + '"'), '-classpath', + # Use the real Java 17 module image and keep forbidden/discouraged accesses fatal. + $arguments = @('-17', '-proc:none', '-encoding', 'UTF-8', '-err:+forbidden,discouraged', '--system', + ('"' + $Java17Home.Replace('\', '/') + '"'), '-classpath', ('"' + (($Libraries -replace '\\', '/') -join ';') + '"'), '-d', ('"' + $output.Replace('\', '/') + '"'), '-log', ('"' + $xmlLog.Replace('\', '/') + '"')) $arguments += @($Sources | ForEach-Object { '"' + $_.Replace('\', '/') + '"' }) @@ -62,37 +61,39 @@ function Invoke-Ecj([string]$Name, [string[]]$Sources, [string[]]$Libraries) { } $report = [ordered]@{ status = 'RUNNING'; project = $ProjectRoot; evidence = $Evidence - scope = 'Pinned baseElectric 1.17.1 candidate; fresh ECJ main/test/helper outputs and worldless Java 16 JUnit; no Gradle or Minecraft launch' + scope = 'Pinned local baseElectric 1.18.2 candidate; fresh ECJ main/test/helper outputs and worldless Java 17 JUnit; no Gradle or Minecraft launch' compiler = $null; toolchains = $null; libraries = @(); sources = @(); gradleInventory = @() compilations = [ordered]@{}; junit = $null; negativeControl = $null; apiPinState = $null; capabilityBootstrap = $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.10.117011' -or - $properties.poweradvantage_version -ne '2.4.9.117011' -or $properties.basemetals_version -ne '3.1.1.117011' -or - $properties.poweradvantage_pin_state -ne 'LOCAL_CHECKPOINT' -or + if ($properties.mod_id -ne 'baseelectric' -or $properties.mod_version -ne '2.2.11.118021' -or + $properties.poweradvantage_version -ne '2.4.10.118021' -or $properties.basemetals_version -ne '3.1.2.118021' -or + $properties.orespawn_version -ne '4.1.0.118021' -or + $properties.poweradvantage_pin_state -ne 'PUBLISHED_RELEASE' -or $properties.poweradvantage_source_ref -cnotmatch '^[a-f0-9]{40}$' -or + $properties.poweradvantage_runtime_sha256 -cnotmatch '^[A-F0-9]{64}$' -or $properties.basemetals_source_commit -cnotmatch '^[a-f0-9]{40}$' -or $properties.poweradvantage_deobf_sha256 -cnotmatch '^[A-F0-9]{64}$' -or $properties.poweradvantage_unit_bootstrap_sha256 -cnotmatch '^[A-F0-9]{64}$' -or - $properties.basemetals_deobf_sha256 -cnotmatch '^[A-F0-9]{64}$') { - throw 'This verifier requires the coordinated baseElectric 1.17.1 inputs' + $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.18.2 inputs' } $report.compiler = Get-Pin $Compiler if ($report.compiler.sha256 -ne 'D0D43F8E2D7003E5EFED612E2CBB5F01870043397D8F1BBE536FD9128F4FCBF7') { throw 'ECJ compiler pin mismatch' } $java17 = Join-Path $Java17Home 'bin/java.exe' - $java16 = Join-Path $Java16Home 'bin/java.exe' $capabilityBootstrap = Join-Path $ProjectRoot 'build/unit-bootstrap/forge-native-unit-bootstrap.jar' $report.capabilityBootstrap = Get-Pin $capabilityBootstrap - $report.toolchains = [ordered]@{ compilerJava = (Get-Pin $java17); runtimeJava = (Get-Pin $java16) - java17Release = (Get-Pin (Join-Path $Java17Home 'release')); java16Release = (Get-Pin (Join-Path $Java16Home 'release')) - systemModules = (Get-Pin (Join-Path $Java16Home 'lib/modules')) } - if ((Get-Content -LiteralPath (Join-Path $Java16Home 'release') -Raw) -notmatch 'JAVA_VERSION="16.0.2"') { - throw 'ECJ production system image must be Java 16.0.2' + $report.toolchains = [ordered]@{ java = (Get-Pin $java17) + java17Release = (Get-Pin (Join-Path $Java17Home 'release')) + systemModules = (Get-Pin (Join-Path $Java17Home 'lib/modules')) } + if ((Get-Content -LiteralPath (Join-Path $Java17Home 'release') -Raw) -notmatch 'JAVA_VERSION="17\.0\.1"') { + throw 'ECJ production system image must be Java 17.0.1' } $negativeSource = Join-Path $PSScriptRoot 'negative/RestrictedJdkReference.java' $negativeLog = Join-Path $Evidence 'restricted-jdk-negative.log' - & $java17 -jar $Compiler -16 -proc:none '-err:+forbidden,discouraged' --system $Java16Home ` + & $java17 -jar $Compiler -17 -proc:none '-err:+forbidden,discouraged' --system $Java17Home ` -d (Join-Path $Evidence 'negative') $negativeSource *> $negativeLog $negativeExit = $LASTEXITCODE $report.negativeControl = [ordered]@{ source = (Get-Pin $negativeSource); exitCode = $negativeExit @@ -138,7 +139,7 @@ try { $expected += [int]$inventory.testsuite.tests $report.gradleInventory += Get-Pin $xml.FullName } - if ($classes.Count -ne 27 -or $expected -ne 96 -or $sources.test.Count -ne 27 -or $sources.main.Count -ne 85) { throw 'Unexpected Electric 1.17.1 source/test inventory' } + if ($classes.Count -ne 27 -or $expected -ne 96 -or $sources.test.Count -ne 28 -or $sources.main.Count -ne 85) { throw 'Unexpected Electric 1.18.2 source/test inventory' } $main = Invoke-Ecj 'main' $sources.main $mainLibraries $test = Invoke-Ecj 'test' $sources.test (@($main) + $testLibraries) $helperSource = Join-Path $PSScriptRoot 'AuditedJUnit.java' @@ -149,7 +150,7 @@ try { $report.runtime = $runtime Push-Location $ProjectRoot try { - & $java16 "-javaagent:$capabilityBootstrap" -Xmx2G -cp ($runtime -join ';') zone.moddev.mc.baseelectric.verification.AuditedJUnit $junitXml $expected @classes *> (Join-Path $Evidence 'junit.log') + & $java17 "-javaagent:$capabilityBootstrap" -Xmx2G -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' } @@ -167,4 +168,4 @@ try { throw } finally { Save-Report } 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 16 class files" +Write-Output "ECJ PASS: $($sources.main.Count) main, $($sources.test.Count) test sources; $expected tests; Java 17 class files" diff --git a/tools/verify-native-port.js b/tools/verify-native-port.js index 2b3e1b7..7cfce78 100644 --- a/tools/verify-native-port.js +++ b/tools/verify-native-port.js @@ -7,6 +7,7 @@ const { createHash } = require('node:crypto'); const root = path.resolve(__dirname, '..'); const baseline = 'c5c8c9ea1d6f2010b8cff61e4bfae86f4c96c978'; const nativeBaseline = 'ce6b075f1d0e417e46e1742b9a2ca856b24dc67f'; +const portBase = 'cf3d4b0e2d1591b2ae8e5d638002ca016e46eac4'; const oldJavaRoot = 'src/main/java/zone/moddev/mc/electricadvantage/'; const javaRoot = 'src/main/java/zone/moddev/mc/baseelectric/'; const migratePath = name => name.startsWith('src/test/resources/') @@ -18,7 +19,8 @@ const migrateText = source => source.replaceAll('\r\n', '\n').replaceAll('electr const read = name => fs.readFileSync(path.join(root, name), 'utf8'); const git = (...args) => execFileSync('git', args, { cwd: root }); const tracked = git('ls-tree', '-r', '--name-only', baseline).toString().trim().split('\n'); -const nativeFiles = git('ls-tree', '-r', '--name-only', nativeBaseline, 'src').toString().trim().split('\n'); +const portBaseFiles = git('ls-tree', '-r', '--name-only', portBase, 'src').toString().trim().split('\n'); +const sha256 = value => createHash('sha256').update(value).digest('hex'); const results = []; const properties = Object.fromEntries(read('gradle.properties').split(/\r?\n/) .filter(line => line && !line.startsWith('#')).map(line => { @@ -32,7 +34,7 @@ function tokens(source) { return source.match(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\/\/[^\n]*|\/\*[\s\S]*?\*\/|[A-Za-z_$][A-Za-z0-9_$]*|(?:0[xX][\da-fA-F_]+|(?:\d[\d_]*\.?[\d_]*|\.\d[\d_]*)(?:[eE][+-]?[\d_]+)?)[dDfFlL]?/g) || []; } const strings = source => tokens(source).filter(t => t.startsWith('"') - && !['"2.2.8.116051"', '"2.2.9.117011"', '"2.2.10.117011"'].includes(t)).sort(); + && !['"2.2.8.116051"', '"2.2.9.117011"', '"2.2.10.117011"', '"2.2.11.118021"'].includes(t)).sort(); const numbers = source => tokens(source).filter(t => /^\d|^\.\d/.test(t)); const production = tracked.filter(n => n.startsWith(oldJavaRoot) && n.endsWith('.java')); const entry = javaRoot + 'ElectricAdvantage.java'; @@ -62,48 +64,65 @@ function assertNativeUnlock(source, actual) { return expected.converted; } -check('Exact native 1.17.1 build target', () => { - for (const [key, value] of Object.entries({minecraft_version:'1.17.1', forge_version:'37.1.1', - mapping_channel:'official', mapping_version:'1.17.1', mcp_version:'20210706.113038', - mod_id:'baseelectric', mod_name:'baseElectric', mod_version:'2.2.10.117011', +check('Exact native 1.18.2 build target', () => { + for (const [key, value] of Object.entries({minecraft_version:'1.18.2', forge_version:'40.3.0', + mapping_channel:'official', mapping_version:'1.18.2', mcp_version:'20220404.173914', + mod_id:'baseelectric', mod_name:'baseElectric', mod_version:'2.2.11.118021', mod_group:'zone.moddev.mc.baseelectric', curseforge_project_id:'1783006', cf_requirements:'basepower,base-metals', - java_version:'16', java_toolchain_version:'16.0.2+7', gradle_java_version:'17', - poweradvantage_version:'2.4.9.117011', basemetals_version:'3.1.1.117011', orespawn_version:'4.1.0.117011'})) { + java_version:'17', java_toolchain_version:'17.0.1+12', gradle_java_version:'17', + poweradvantage_version:'2.4.10.118021', basemetals_version:'3.1.2.118021', orespawn_version:'4.1.0.118021'})) { assert.equal(properties[key], value, key); } - assert.deepEqual(JSON.parse(read('src/main/resources/pack.mcmeta')).pack, {pack_format:7, description:'baseElectric'}); - assert(read('build.gradle').includes('major != 60')); + assert.deepEqual(JSON.parse(read('src/main/resources/pack.mcmeta')).pack, + {pack_format:9, 'forge:resource_pack_format':8, 'forge:data_pack_format':9, description:'baseElectric'}); + assert(read('build.gradle').includes('major != 61')); const metadata = read('src/main/resources/META-INF/mods.toml'); - assert.match(metadata, /modId="basepower"\s+mandatory=true\s+versionRange="\[2\.4\.9\.117011,3\.0\.0\)"/); - assert.match(metadata, /modId="basemetals"\s+mandatory=false\s+versionRange="\[3\.1\.1\.117011,4\.0\.0\)"/); + assert.match(metadata, /modId="basepower"\s+mandatory=true\s+versionRange="\[2\.4\.10\.118021,3\.0\.0\)"/); + assert.match(metadata, /modId="basemetals"\s+mandatory=false\s+versionRange="\[3\.1\.2\.118021,4\.0\.0\)"/); assert(metadata.includes('modId="baseelectric"') && metadata.includes('displayName="baseElectric"')); }); -check('Exact local basePower, Base Metals and OreSpawn pins are retained', () => { +check('The exact published basePower, Base Metals and OreSpawn pins are retained', () => { assert.equal(properties.poweradvantage_source_repository, 'MinecraftModDevelopmentMods/basePower'); - assert.equal(properties.poweradvantage_pin_state, 'LOCAL_CHECKPOINT'); + assert.equal(properties.poweradvantage_pin_state, 'PUBLISHED_RELEASE'); const build = read('build.gradle'); - assert.match(build, /poweradvantage_pin_state != 'LOCAL_CHECKPOINT'/); - assert.match(build, /requireDevelopmentPins\(\)\s+def powerAdvantageDeobfJarPath/); - assert(!build.includes('defaultPowerAdvantageDeobfJar')); + assert.match(build, /poweradvantage_pin_state != 'PUBLISHED_RELEASE'/); + assert.match(build, /requirePowerPin\(\)\s+pinnedFile\('powerAdvantageDeobfJar', project.poweradvantage_deobf_sha256\)/); + assert(!build.includes('PA117') && !build.includes('117011-deobf.jar')); for (const [key, value] of Object.entries({ - poweradvantage_source_ref:'bb7023835fd41aefe89c92275caa74da9e126cc7', - poweradvantage_deobf_sha256:'AFC3FBDF84FF7E8FF8929F8192FDBCEC170C9AE1CF1B248E1C4B9C6ED17AA357', - poweradvantage_runtime_sha256:'A788C08163CECE1C6D33FBDC295BE6EC272DE9AF7911BE74E707D0EE9121DF73', - poweradvantage_unit_bootstrap_sha256:'28080BD563D6D2F378AD2735BBEFEBB079DBFC87A9457E066DDB99F43C3D4095', - basemetals_source_commit:'65f9956c07d5d0c0b6ec0ac2108ce69c3ce52c6e', - basemetals_deobf_sha256:'83A8C32C633B56BB7E4405E8B3B24A4DEA32B9119216E5097B884056D679CAD1', - orespawn_source_commit:'b56e2b62a1a6ea4d1dd96f8646a87801a817dbdf', - orespawn_deobf_sha256:'CB7A736314842B110B2E4788FBD5E21D4AE8803C5ED34A49199B034BE572DA39', - orespawn_runtime_sha256:'1BDEDCBB179CB5A3B169E5C8A6B301E3888AE15B94108B0AEC627D705CFAD833'})) { + poweradvantage_source_ref:'bcf5105ef5c10ba36c26c8ff941742fecc3e11a1', + poweradvantage_deobf_sha256:'767BABC0D6E1536DCB937B9F1DC565AF348B3CAF92D3207671EF1E1C8D8EF5D4', + poweradvantage_runtime_sha256:'8CA46D5BCBFFDDAEB4F40137B5BF71E5AF7DD36515E5BFEA751FC5C02249823B', + poweradvantage_unit_bootstrap_sha256:'025F16FCA9FD5CFC585931E7C8BCFAB22FC1DF933BE7DAAD490B8E4FC824B76E', + basemetals_source_commit:'3488ed58403722ac3ff203cff7680dfc16e0fd3f', + basemetals_deobf_sha256:'E4F9E2CDE355275218C79954D3792D000615790C7CBAE5FCEC59636A8C4CA57D', + basemetals_runtime_sha256:'5595C2CE7ADCDF1599E4327F1130121148A5CACD928CBC2190312D61F7CA37A9', + orespawn_source_commit:'d31f52f296739f34a0c13f554abf3e1ac071ae25', + orespawn_deobf_sha256:'34B25B2116FD91B0050C80393F8356A5CA256E707BF443E4882BE5CFAD1335B6', + orespawn_runtime_sha256:'BD8D84A96DAFB7668E95D61B9D72C62AEF3440675473E41244C182C64653356F', + forge_installer_sha256:'9434C29790504DCD11CE97CB30DD8891B4B18848982357BC1B5ABFA79FB95103'})) { assert.equal(properties[key], value, key); } }); -check('Resources retained except namespace moves, canonical LF and exact native advancement conversion', () => { +check('Hosted release checks receive the verified player jars and Forge installer', () => { + const workflow = read('.github/workflows/ci.yml'); + assert(workflow.includes('verifyEclipseProductionClasspath verifyLocalRuntimeInputs')); + for (const [property, output] of [ + ['powerAdvantageRuntimeJar', 'power_runtime'], + ['baseMetalsRuntimeJar', 'basemetals_runtime'], + ['oreSpawnRuntimeJar', 'orespawn_runtime'], + ['forgeInstallerJar', 'forge_installer'] + ]) assert(workflow.includes('-P' + property + '=${{ steps.dependencies.outputs.' + output + ' }}')); + for (const key of ['basemetals_runtime_sha256', 'orespawn_runtime_sha256', 'forge_installer_sha256']) { + assert(workflow.includes('$(pin ' + key + ')')); + } +}); +check('Resources retained except namespace moves, exact advancement conversion and the native fog shader', () => { let changedFiles = 0, changedPredicates = 0; for (const name of tracked.filter(n => n.startsWith('src/main/resources/') && !['src/main/resources/pack.mcmeta', 'src/main/resources/META-INF/mods.toml'].includes(n))) { const before = git('show', `${baseline}:${name}`); const target = migratePath(name); + if (/\/shaders\/core\/unshaded\.(vsh|json)$/.test(target)) continue; // Exact 1.18 delta audited below. const expected = /\.(json|fsh|vsh|mcmeta)$/.test(name) ? Buffer.from(migrateText(before.toString())) : before; if (name.startsWith('src/main/resources/data/electricadvantage/advancements/recipes/')) { @@ -151,7 +170,12 @@ check('Registry, asset, provider and NBT string identities retained', () => { check('Machine balance and renderer test tolerances retained', () => { const renderer = 'src/test/java/zone/moddev/mc/electricadvantage/graphics/RendererBufferContractTest.java'; for (const name of [...production, renderer]) { - const expected = numbers(git('show', `${baseline}:${name}`).toString()); + let before = git('show', `${baseline}:${name}`).toString(); + if (name.endsWith('/LaserTurretTileEntity.java')) { + assert(before.includes('this.getBlockPos(), 0, nbtTag')); + before = before.replace('this.getBlockPos(), 0, nbtTag', 'this.getBlockPos(), nbtTag'); + } + const expected = numbers(before); if (name.endsWith('/ElectricRenderTypes.java')) { assert.equal(expected.splice(expected.indexOf('7'), 1)[0], '7'); // Native enum replaces GL_QUADS. } @@ -165,7 +189,8 @@ check('Inherited regression tests remain present with no skip or assume bypass', const after = read(migratePath(name)); const methods = s => [...s.matchAll(/@Test\s+public void (\w+)\(/g)].map(m => m[1]); const expected = methods(before).map(n => n.replace('resourcePackDeclaresTheNativeOneSixteenFormat', - 'resourcePackDeclaresTheNativeOneSeventeenFormat')); + 'resourcePackDeclaresTheNativeOneEighteenFormats') + .replace('publishedOneSeventeenRecipesKeepRawAndOreOutputsDistinct', 'nativeCrushingRecipesKeepRawAndOreOutputsDistinct')); count += expected.length; if (name.endsWith('/RecipeUnlockContractTest.java')) { expected.splice(2, 0, 'nativeParserRestrictsItemArraysButSilentlyIgnoresTheLegacySingularKey'); @@ -226,20 +251,33 @@ check('Shared capabilities, legacy cyano API and first-tick interlocks retained' assert(read(javaRoot + 'ElectricAdvantage.java').includes('event.enqueueWork(RecipeConditions::register)')); assert(read(javaRoot + 'machines/PhotovoltaicGeneratorTileEntity.java').includes('BlockPos lightPos = pos.above()')); }); -check('Native 1.17 source, tests and binary assets are preserved beyond the identity changes', () => { +check('Only the reviewed 1.18.2 source, shader, metadata and fixture deltas are present', () => { const actualFiles = fs.readdirSync(path.join(root, 'src'), {recursive:true, withFileTypes:true}) .filter(file => file.isFile()).map(file => path.relative(root, path.join(file.parentPath, file.name)) .split(path.sep).join('/')).sort(); - assert.deepEqual(actualFiles, nativeFiles.map(migratePath).sort(), 'Source path inventory'); - for (const name of nativeFiles) { - const target = migratePath(name); - if ([entry, migration, 'src/main/resources/META-INF/mods.toml', 'src/main/resources/pack.mcmeta'].includes(target) - || /\/(ElectricLegacyMigrationsTest|BuildWorkflowContractTest)\.java$/.test(target)) continue; - const before = git('show', `${nativeBaseline}:${name}`); - const expected = !name.startsWith('src/test/resources/') && /\.(java|json|fsh|vsh|mcmeta)$/.test(name) - ? Buffer.from(migrateText(before.toString())) : before; - assert.deepEqual(fs.readFileSync(path.join(root, target)), expected, target); - } + const expectedFiles = portBaseFiles.map(name => name.replace('/Native117ConstructionTest.java', '/Native118ConstructionTest.java')); + expectedFiles.push('src/test/java/zone/moddev/mc/baseelectric/verification/NativeTestBootstrap.java'); + assert.deepEqual(actualFiles, expectedFiles.sort(), 'Source path inventory'); + const sourceChanges = [...new Set([ + ...git('diff', '--no-renames', '--name-only', portBase, '--', 'src').toString().trim().split('\n'), + ...git('ls-files', '--others', '--exclude-standard', '--', 'src').toString().trim().split('\n') + ])].filter(Boolean).sort(); + const workflowTest = 'src/test/java/zone/moddev/mc/baseelectric/resources/BuildWorkflowContractTest.java'; + assert(sourceChanges.includes(workflowTest)); + assert.equal(sha256(read(workflowTest)), 'fc2ac98237925da76ab6ccd3b1b6c5f0f8839f8eeb2f77b9a30cdc5f554543eb', + 'Reviewed 1.18 hosted workflow test'); + const fingerprints = sourceChanges.filter(file => file !== workflowTest).map(file => { + const source = path.join(root, file); + if (!fs.existsSync(source)) return [file, null]; + let bytes = fs.readFileSync(source); + if (file === 'src/main/resources/META-INF/mods.toml') { + // The live minimum is checked above. Compare the rest to the original review. + bytes = Buffer.from(bytes.toString().replace('3.1.2.118021', '3.1.1.118021')); + } + return [file, sha256(bytes)]; + }); + assert.equal(sha256(JSON.stringify(fingerprints)), 'c684a77312129f3b90b367b6f0bf9b6a81402992e8bb2440b5795e99f9c60898', + 'Exact reviewed source delta from the PA117 clone point'); }); check('Original and baseElectric aliases retain the native registry paths and state decoding', () => { const old = git('show', `${nativeBaseline}:${oldJavaRoot}migration/ElectricLegacyMigrations.java`).toString(); @@ -272,14 +310,16 @@ const changedFiles = [...new Set([ ...git('diff', '--name-only', baseline).toString().trim().split('\n'), ...git('ls-files', '--others', '--exclude-standard').toString().trim().split('\n') ])].filter(Boolean).sort(); -const testFiles = nativeFiles.map(migratePath).filter(n => n.startsWith('src/test/java/') - && n.endsWith('.java') && fs.existsSync(path.join(root, n))); +const testFiles = fs.readdirSync(path.join(root, 'src/test/java'), {recursive:true, withFileTypes:true}) + .filter(file => file.isFile() && file.name.endsWith('.java')) + .map(file => path.relative(root, path.join(file.parentPath, file.name))); const tests = testFiles.reduce((n, file) => n + (read(file).match(/@Test\b/g) || []).length, 0); -check('The complete native 1.17.1 test inventory is retained', () => { - assert.equal(testFiles.length, 27); +check('All 27 inherited suites and 96 tests remain present', () => { + assert.equal(testFiles.length, 28); + assert.equal(testFiles.filter(file => /@Test\b/.test(read(file))).length, 27); assert.equal(tests, 96); }); -const report = {baseline, nativeBaseline, scope:'Static source/resource contracts only; not compilation, runtime or manual acceptance', +const report = {baseline, nativeBaseline, portBase, scope:'Static source/resource contracts only; not compilation, runtime or manual acceptance', inventory: {productionFiles:production.length, testFiles:testFiles.length, tests}, changedFiles:changedFiles.map(file => ({file, sha256:fs.existsSync(path.join(root, file)) ? createHash('sha256').update(fs.readFileSync(path.join(root, file))).digest('hex') : null})),