diff --git a/.github/release120.json b/.github/release120.json
new file mode 100644
index 0000000..5505110
--- /dev/null
+++ b/.github/release120.json
@@ -0,0 +1,16 @@
+{
+ "version": "2.4.11.120011",
+ "minecraft": "1.20.1",
+ "forge": "47.4.28",
+ "local_qualification_complete": true,
+ "qualification_integrity_status": "verified",
+ "manual_accepted": true,
+ "files": {
+ "CHANGELOG.txt": "30D5C91D14146FC202B02CAF3786D017EEF39E19F2785E1CC4C2553D1F9576CC",
+ "LootableBodies-2.4.11.120011-javadoc.jar": "E7B2342A3BC0C698E923E60693FCD6EEDD93077167663306908E327A47B69DA0",
+ "LootableBodies-2.4.11.120011-sources.jar": "E40712C149FD9485EB5FCBA959BA13ACC2B87DC535A6D2593A266CEBE9E26218",
+ "LootableBodies-2.4.11.120011.jar": "FD9D040217D1949C04C5A6C123BEF8C7671B3A2ABE1170083C6769772942DD9E",
+ "LootableBodies-2.4.11.120011.pom": "A9177C59AF6D7FE1B4B86C40D8053FD7A3C7A81B639F6759079B82D27CA5152C",
+ "SHA256SUMS": "BFF65899B03C55A5D70E08FAA6B981250C9D787B6FE13865A058D5A94AAFDEDE"
+ }
+}
diff --git a/.github/scripts/test_release_workflow.py b/.github/scripts/test_release_workflow.py
index 794f71b..a2f642d 100644
--- a/.github/scripts/test_release_workflow.py
+++ b/.github/scripts/test_release_workflow.py
@@ -70,13 +70,14 @@ def validate(self, minecraft, names=None, mutation=None, release_exists=False, w
'1.16.5': ('116051', 'master-1.16.5', '36.2.42'),
'1.17.1': ('117011', 'master-1.17.1', '37.1.1'),
'1.18.2': ('118021', 'master-1.18.2', '40.3.12'),
- '1.19.4': ('119041', 'master-1.19.4', '45.4.5')}[minecraft]
+ '1.19.4': ('119041', 'master-1.19.4', '45.4.5'),
+ '1.20.1': ('120011', 'master-1.20.1', '47.4.28')}[minecraft]
version = '2.4.2.' + suffix
files={name:'C'*64 for name in ['main.jar','sources.jar','javadoc.jar','release.pom','CHANGELOG.txt','SHA256SUMS']}
receipt={'schema':1,'source':SHA,'version':version,'files':files,'evidence_sha256':'F'*64,'qualified_locally':True,'manual_accepted':True}
if local_receipt is not None:receipt=local_receipt
values = dict(minecraft_version=minecraft, forge_version=forge, loader_name='forge', loader_code='1',
- java_version='17' if minecraft in ('1.18.2','1.19.4') else '16' if minecraft=='1.17.1' else '8', java_toolchain_version='17.0.20.1+1' if minecraft in ('1.18.2','1.19.4') else '16.0.2+7' if minecraft=='1.17.1' else '8.0.502+7', gradle_java_version='17', mod_id='lootablebodies',
+ java_version='17' if minecraft in ('1.18.2','1.19.4','1.20.1') else '16' if minecraft=='1.17.1' else '8', java_toolchain_version='17.0.20.1+1' if minecraft in ('1.18.2','1.19.4','1.20.1') else '16.0.2+7' if minecraft=='1.17.1' else '8.0.502+7', gradle_java_version='17', mod_id='lootablebodies',
mod_artifact='LootableBodies', mod_group='zone.moddev.mc.lootablebodies', mod_license='LGPL-2.1-only',
curseforge_project_id='240968', mod_version=version)
if mutation:
@@ -86,13 +87,13 @@ def validate(self, minecraft, names=None, mutation=None, release_exists=False, w
'1.14.4': MATRIX114 + ['Validate release workflow'],
'1.15.2': MATRIX115, '1.16.5': MATRIX116}.get(minecraft, []) if names is None else names
if minecraft=='1.17.1' and names is None:checks=CORE117+MATRIX117
- if minecraft in ('1.18.2','1.19.4') and names is None:checks=CORE118
+ if minecraft in ('1.18.2','1.19.4','1.20.1') and names is None:checks=CORE118
def command(args, **options):
if args[0] == 'git':
return SHA + '\n'
- if any('/actions/workflows/native'+code+'.yml/' in args[-1] for code in ['117','118','119']):
- code={'1.17.1':'117','1.18.2':'118','1.19.4':'119'}[minecraft]
+ if any('/actions/workflows/native'+code+'.yml/' in args[-1] for code in ['117','118','119','120']):
+ code={'1.17.1':'117','1.18.2':'118','1.19.4':'119','1.20.1':'120'}[minecraft]
row=dict(id=int(code),head_sha=SHA,head_branch=branch,event='push',path='.github/workflows/native'+code+'.yml',conclusion='success')
if qualification:row.update(qualification)
return json.dumps({'workflow_runs':[row]})
@@ -116,8 +117,8 @@ def existing(args, **options):
patch('subprocess.check_output', side_effect=command), patch('subprocess.run', side_effect=existing):
exec(compile(code, 'release-preflight', 'exec'), {})
- def test_future_120_candidate_cannot_dispatch_publication(self):
- code, values = self.select('2.4.11.120011')
+ def test_future_1206_candidate_cannot_dispatch_publication(self):
+ code, values = self.select('2.4.12.120061')
self.assertNotEqual(0, code)
self.assertEqual({}, values)
@@ -336,6 +337,24 @@ def test_119_requires_manual_acceptance_and_exact_artifacts(self):
self.assertNotIn('gradlew', step)
self.assertIn("minecraft_version == '1.19.4' && 'Java 17'", WORKFLOW.read_text())
+ def test_120_requires_manual_acceptance_and_exact_artifacts(self):
+ code, values = self.select('2.4.11.120011')
+ self.assertEqual(0, code)
+ self.assertEqual('master-1.20.1', values['source_branch'])
+ self.validate('1.20.1')
+ for missing in CORE118:
+ with self.subTest(missing=missing), self.assertRaises(SystemExit):
+ self.validate('1.20.1', names=[name for name in CORE118 if name != missing])
+ for kwargs in [{'audit_success':False}, {'local_receipt':{}},
+ {'qualification':{'path':'.github/workflows/native118.yml'}},
+ {'mutation':{'java_version':'8'}}]:
+ with self.subTest(kwargs=kwargs), self.assertRaises(SystemExit):
+ self.validate('1.20.1', **kwargs)
+ step = WORKFLOW.read_text().split('- name: Verify all six native120 bytes before publication', 1)[1].split(' - name:', 1)[0]
+ self.assertIn('python3 -B tools/native120_build.py --bundle build/release/bundle', step)
+ self.assertNotIn('gradlew', step)
+ self.assertIn("minecraft_version == '1.20.1' && 'Java 17'", WORKFLOW.read_text())
+
def test_identity_changes_are_rejected(self):
for mutation in [{'mod_id': 'other'}, {'minecraft_version': '1.10.2'}, {'mod_license': 'CC0-1.0'}]:
with self.subTest(mutation=mutation), self.assertRaises(SystemExit):
diff --git a/.github/workflows/artifact-audit-1.20.yml b/.github/workflows/artifact-audit-1.20.yml
new file mode 100644
index 0000000..1f6bb58
--- /dev/null
+++ b/.github/workflows/artifact-audit-1.20.yml
@@ -0,0 +1,48 @@
+name: Audit LootableBodies 1.20 artifacts
+run-name: Audit 1.20.1 artifacts from ${{ github.event.workflow_run.head_sha }}
+on:
+ workflow_run:
+ workflows: [LootableBodies 1.20 CI]
+ types: [completed]
+permissions:
+ contents: read
+ actions: read
+jobs:
+ audit:
+ if: >-
+ github.repository == 'MinecraftModDevelopmentMods/LootableBodies' &&
+ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push' &&
+ github.event.workflow_run.head_branch == 'master-1.20.1'
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
+ with:
+ ref: ${{ github.event.workflow_run.head_sha }}
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
+ with:
+ name: LootableBodies-1.20.1-${{ github.event.workflow_run.head_sha }}
+ run-id: ${{ github.event.workflow_run.id }}
+ github-token: ${{ github.token }}
+ path: audited
+ - name: Independently verify every locally qualified byte
+ env:
+ SOURCE_SHA: ${{ github.event.workflow_run.head_sha }}
+ run: |
+ python3 -B tools/native120_build.py --bundle audited
+ python3 - <<'PY'
+ import json,os
+ from pathlib import Path
+ accepted=json.loads(Path('.github/release120.json').read_text())
+ Path('independent-audit.json').write_text(json.dumps({'source':os.environ['SOURCE_SHA'],'files':accepted['files'],'all_six_exact':True,'identical_to_locally_audited_bundle':True},indent=2)+'\n')
+ PY
+ # Exact hashes reuse the local class61/licence/identity/mapping audit.
+ # This independent receipt is a byte match, not a fresh gameplay run or
+ # another Gradle/Forge preparation. The build job audits new archives.
+ - name: Retain independent audit evidence
+ if: always()
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
+ with:
+ name: native120-independent-audit-${{ github.event.workflow_run.head_sha }}
+ if-no-files-found: error
+ path: independent-audit.json
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 28f6b84..4f6158c 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -3,11 +3,11 @@ name: CodeQL
on:
push:
branches:
- - master-1.19.4
+ - master-1.20.1
- 'feature/**'
pull_request:
branches:
- - master-1.19.4
+ - master-1.20.1
schedule:
- cron: '43 7 * * 4'
@@ -17,7 +17,7 @@ permissions:
security-events: write
concurrency:
- group: lootablebodies-1.19-codeql-${{ github.ref }}
+ group: lootablebodies-1.20-codeql-${{ github.ref }}
cancel-in-progress: true
jobs:
diff --git a/.github/workflows/deploy-release.yml b/.github/workflows/deploy-release.yml
index e8f824f..964caa2 100644
--- a/.github/workflows/deploy-release.yml
+++ b/.github/workflows/deploy-release.yml
@@ -5,7 +5,7 @@ on:
workflow_dispatch:
inputs:
release_version:
- description: Complete version from gradle.properties, ending in 110021, 111021, 112021, 113021, 114041, 115021, 116051, 117011, 118021 or 119041.
+ description: Complete version from gradle.properties, ending in 110021, 111021, 112021, 113021, 114041, 115021, 116051, 117011, 118021, 119041 or 120011.
required: true
type: string
curseforge_release_level:
@@ -20,7 +20,7 @@ on:
default: false
type: boolean
local_qualification:
- description: Hash-bound local qualification receipt, required for the 1.17.1, 1.18.2 and 1.19.4 releases.
+ description: Hash-bound local qualification receipt, required for the 1.17.1, 1.18.2, 1.19.4 and 1.20.1 releases.
required: false
default: ''
type: string
@@ -54,7 +54,7 @@ jobs:
set -euo pipefail
[[ "$GITHUB_REPOSITORY" == 'MinecraftModDevelopmentMods/LootableBodies' ]]
[[ "$GITHUB_REF" == 'refs/heads/master-1.12' ]]
- [[ "$RELEASE_VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(110021|111021|112021|113021|114041|115021|116051|117011|118021|119041)$ ]]
+ [[ "$RELEASE_VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(110021|111021|112021|113021|114041|115021|116051|117011|118021|119041|120011)$ ]]
case "$RELEASE_VERSION" in
*.110021) minecraft_version=1.10.2; source_branch=1.10.2 ;;
*.111021) minecraft_version=1.11.2; source_branch=1.11.2-dev ;;
@@ -66,6 +66,7 @@ jobs:
*.117011) minecraft_version=1.17.1; source_branch=master-1.17.1 ;;
*.118021) minecraft_version=1.18.2; source_branch=master-1.18.2 ;;
*.119041) minecraft_version=1.19.4; source_branch=master-1.19.4 ;;
+ *.120011) minecraft_version=1.20.1; source_branch=master-1.20.1 ;;
*) exit 1 ;;
esac
echo "minecraft_version=$minecraft_version" >> "$GITHUB_OUTPUT"
@@ -100,12 +101,13 @@ jobs:
'1.16.5': ('master-1.16.5', '36.2.42', '116051'),
'1.17.1': ('master-1.17.1', '37.1.1', '117011'),
'1.18.2': ('master-1.18.2', '40.3.12', '118021'),
- '1.19.4': ('master-1.19.4', '45.4.5', '119041')}
+ '1.19.4': ('master-1.19.4', '45.4.5', '119041'),
+ '1.20.1': ('master-1.20.1', '47.4.28', '120011')}
branch, forge, suffix = targets[minecraft]
if os.environ['SOURCE_BRANCH'] != branch:
raise SystemExit('The selected branch does not match its Minecraft version')
native17 = minecraft == '1.17.1'
- archive_only = minecraft in ('1.18.2', '1.19.4')
+ archive_only = minecraft in ('1.18.2', '1.19.4', '1.20.1')
locally_qualified = native17 or archive_only
expected = {'minecraft_version': minecraft, 'forge_version': forge,
'loader_name': 'forge', 'loader_code': '1', 'java_version': '17' if archive_only else '16' if native17 else '8',
@@ -149,13 +151,13 @@ jobs:
except (ValueError,TypeError):
raise SystemExit('A local qualification receipt is required')
keys={'schema','source','version','files','evidence_sha256','qualified_locally','manual_accepted'}
- target_code={'1.17.1':'117','1.18.2':'118','1.19.4':'119'}[minecraft]
+ target_code={'1.17.1':'117','1.18.2':'118','1.19.4':'119','1.20.1':'120'}[minecraft]
metadata=json.loads(Path(f'.github/release{target_code}.json').read_text())
accepted=metadata['files']
if archive_only and (metadata.get('local_qualification_complete') is not True or metadata.get('qualification_integrity_status')!='verified'):
raise SystemExit(f'The local {minecraft} qualification declaration is incomplete')
- if minecraft == '1.19.4' and metadata.get('manual_accepted') is not True:
- raise SystemExit('The local 1.19.4 candidate has not been accepted')
+ if minecraft in ('1.19.4', '1.20.1') and metadata.get('manual_accepted') is not True:
+ raise SystemExit(f'The local {minecraft} candidate has not been accepted')
if (not isinstance(local,dict) or set(local)!=keys or type(local['schema']) is not int or local['schema']!=1
or local['source']!=sha or local['version']!=version or local['files']!=accepted
or local['qualified_locally'] is not True or local['manual_accepted'] is not True
@@ -173,7 +175,7 @@ jobs:
matching = [check for check in checks if check['name'] == name]
if not matching or max(matching, key=lambda c: c['id'])['conclusion'] != 'success':
raise SystemExit(f'The selected commit has no successful {name} check')
- if minecraft in ('1.11.2', '1.12.2', '1.13.2', '1.14.4', '1.15.2', '1.16.5', '1.17.1', '1.18.2', '1.19.4'):
+ if minecraft in ('1.11.2', '1.12.2', '1.13.2', '1.14.4', '1.15.2', '1.16.5', '1.17.1', '1.18.2', '1.19.4', '1.20.1'):
target = minecraft.rsplit('.', 1)[0]
audits = json.loads(subprocess.check_output(['gh', 'api',
f'repos/{repo}/actions/workflows/artifact-audit-{target}.yml/runs?event=workflow_run&per_page=100'], text=True))['workflow_runs']
@@ -207,27 +209,27 @@ jobs:
with:
ref: ${{ needs.preflight.outputs.release_sha }}
- name: Install Mavenizer Java
- if: needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4'
+ if: needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4' && needs.preflight.outputs.minecraft_version != '1.20.1'
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5
with:
distribution: temurin
java-version: '25.0.3+9.0.LTS'
- name: Install production Java
- if: needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4'
+ if: needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4' && needs.preflight.outputs.minecraft_version != '1.20.1'
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5
with:
distribution: temurin
- java-version: ${{ needs.preflight.outputs.minecraft_version == '1.18.2' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.19.4' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.17.1' && '16.0.2+7' || '8.0.502+7' }}
+ java-version: ${{ needs.preflight.outputs.minecraft_version == '1.18.2' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.19.4' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.20.1' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.17.1' && '16.0.2+7' || '8.0.502+7' }}
- name: Install Gradle Java
- if: needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4'
+ if: needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4' && needs.preflight.outputs.minecraft_version != '1.20.1'
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5
with:
distribution: temurin
- java-version: ${{ needs.preflight.outputs.minecraft_version == '1.18.2' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.19.4' && '17.0.20+101' || '17.0.1+12' }}
+ java-version: ${{ needs.preflight.outputs.minecraft_version == '1.18.2' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.19.4' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.20.1' && '17.0.20+101' || '17.0.1+12' }}
- name: Pin the production toolchain
- if: needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4'
+ if: needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4' && needs.preflight.outputs.minecraft_version != '1.20.1'
run: |
- if [[ '${{ needs.preflight.outputs.minecraft_version }}' == '1.18.2' || '${{ needs.preflight.outputs.minecraft_version }}' == '1.19.4' ]]; then
+ if [[ '${{ needs.preflight.outputs.minecraft_version }}' == '1.18.2' || '${{ needs.preflight.outputs.minecraft_version }}' == '1.19.4' || '${{ needs.preflight.outputs.minecraft_version }}' == '1.20.1' ]]; then
echo "LOOTABLEBODIES_JAVA17_HOME=$JAVA_HOME_17_X64" >> "$GITHUB_ENV"
elif [ '${{ needs.preflight.outputs.minecraft_version }}' = '1.17.1' ]; then
echo "LOOTABLEBODIES_JAVA16_HOME=$JAVA_HOME_16_X64" >> "$GITHUB_ENV"
@@ -236,7 +238,7 @@ jobs:
fi
echo "LOOTABLEBODIES_MAVENIZER_JAVA_HOME=$JAVA_HOME_25_X64" >> "$GITHUB_ENV"
- name: Set up Gradle
- if: needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4'
+ if: needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4' && needs.preflight.outputs.minecraft_version != '1.20.1'
uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6
- name: Download the qualified native117 six-file bundle
if: needs.preflight.outputs.minecraft_version == '1.17.1'
@@ -280,8 +282,19 @@ jobs:
- name: Verify all six native119 bytes before publication
if: needs.preflight.outputs.minecraft_version == '1.19.4'
run: python3 -B tools/native119_build.py --bundle build/release/bundle
+ - name: Download the qualified native120 six-file bundle
+ if: needs.preflight.outputs.minecraft_version == '1.20.1'
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
+ with:
+ name: LootableBodies-1.20.1-${{ needs.preflight.outputs.release_sha }}
+ run-id: ${{ needs.preflight.outputs.qualification_run }}
+ github-token: ${{ github.token }}
+ path: build/release/bundle
+ - name: Verify all six native120 bytes before publication
+ if: needs.preflight.outputs.minecraft_version == '1.20.1'
+ run: python3 -B tools/native120_build.py --bundle build/release/bundle
- name: Build, test and audit the release
- if: needs.preflight.outputs.minecraft_version != '1.17.1' && needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4'
+ if: needs.preflight.outputs.minecraft_version != '1.17.1' && needs.preflight.outputs.minecraft_version != '1.18.2' && needs.preflight.outputs.minecraft_version != '1.19.4' && needs.preflight.outputs.minecraft_version != '1.20.1'
run: |
chmod +x ./gradlew
./gradlew clean build javadoc verifyEclipseProductionClasspath prepareReleaseBundle \
@@ -386,15 +399,15 @@ jobs:
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5
with:
distribution: temurin
- java-version: ${{ needs.preflight.outputs.minecraft_version == '1.18.2' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.19.4' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.17.1' && '16.0.2+7' || '8.0.502+7' }}
+ java-version: ${{ needs.preflight.outputs.minecraft_version == '1.18.2' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.19.4' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.20.1' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.17.1' && '16.0.2+7' || '8.0.502+7' }}
- name: Install Gradle Java
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5
with:
distribution: temurin
- java-version: ${{ needs.preflight.outputs.minecraft_version == '1.18.2' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.19.4' && '17.0.20+101' || '17.0.1+12' }}
+ java-version: ${{ needs.preflight.outputs.minecraft_version == '1.18.2' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.19.4' && '17.0.20+101' || needs.preflight.outputs.minecraft_version == '1.20.1' && '17.0.20+101' || '17.0.1+12' }}
- name: Pin the production toolchain
run: |
- if [[ '${{ needs.preflight.outputs.minecraft_version }}' == '1.18.2' || '${{ needs.preflight.outputs.minecraft_version }}' == '1.19.4' ]]; then
+ if [[ '${{ needs.preflight.outputs.minecraft_version }}' == '1.18.2' || '${{ needs.preflight.outputs.minecraft_version }}' == '1.19.4' || '${{ needs.preflight.outputs.minecraft_version }}' == '1.20.1' ]]; then
echo "LOOTABLEBODIES_JAVA17_HOME=$JAVA_HOME_17_X64" >> "$GITHUB_ENV"
elif [ '${{ needs.preflight.outputs.minecraft_version }}' = '1.17.1' ]; then
echo "LOOTABLEBODIES_JAVA16_HOME=$JAVA_HOME_16_X64" >> "$GITHUB_ENV"
@@ -452,7 +465,7 @@ jobs:
game-versions: ${{ needs.preflight.outputs.minecraft_version }}
game-version-filter: none
environment: client | server
- java: ${{ needs.preflight.outputs.minecraft_version == '1.19.4' && 'Java 17' || needs.preflight.outputs.minecraft_version == '1.17.1' && 'Java 16' || 'Java 8' }}
+ java: ${{ needs.preflight.outputs.minecraft_version == '1.19.4' && 'Java 17' || needs.preflight.outputs.minecraft_version == '1.20.1' && 'Java 17' || needs.preflight.outputs.minecraft_version == '1.17.1' && 'Java 16' || 'Java 8' }}
fail-mode: fail
files: |
${{ runner.temp }}/release-input/LootableBodies-${{ needs.preflight.outputs.release_version }}.jar
diff --git a/.github/workflows/native120.yml b/.github/workflows/native120.yml
new file mode 100644
index 0000000..641885a
--- /dev/null
+++ b/.github/workflows/native120.yml
@@ -0,0 +1,78 @@
+name: LootableBodies 1.20 CI
+
+on:
+ push:
+ branches: [master-1.20.1]
+ pull_request:
+ branches: [master-1.20.1]
+
+permissions:
+ contents: read
+
+concurrency:
+ group: native120-${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+env:
+ PYTHONDONTWRITEBYTECODE: '1'
+
+defaults:
+ run:
+ shell: bash
+
+jobs:
+ policy:
+ name: Check hosted work policy
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
+ - name: Reject hosted test and runtime commands
+ run: python3 -B tools/hosted_policy.py
+ build:
+ name: Build and audit locally qualified archives
+ needs: policy
+ if: github.repository == 'MinecraftModDevelopmentMods/LootableBodies'
+ runs-on: ubuntu-latest
+ timeout-minutes: 40
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
+ - name: Require local qualification before build preparation
+ run: |
+ set -euo pipefail
+ python3 -B tools/hosted_policy.py --require-qualified-manifest 2>&1 | tee "$RUNNER_TEMP/native120-build.log"
+ - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5
+ with:
+ distribution: temurin
+ java-version: '25.0.3+9.0.LTS'
+ - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5
+ with:
+ distribution: temurin
+ java-version: '17.0.20+101'
+ # Full Java/Python suites, games, IDE consumers and SDK installs stay local.
+ # Fork pushes run policy checks only. MMD builds independently check bytes.
+ - name: Compile and audit release archives
+ env:
+ LOOTABLEBODIES_JAVA17_HOME: ${{ env.JAVA_HOME_17_X64 }}
+ LOOTABLEBODIES_MAVENIZER_JAVA_HOME: ${{ env.JAVA_HOME_25_X64 }}
+ run: |
+ set -euo pipefail
+ chmod +x gradlew
+ ./gradlew assemble verifyProductionRuntimeClasspath prepareReleaseBundle --no-daemon --no-build-cache --stacktrace 2>&1 | tee -a "$RUNNER_TEMP/native120-build.log"
+ python3 -B tools/native120_build.py 2>&1 | tee -a "$RUNNER_TEMP/native120-build.log"
+ - name: Retain the exact six release files
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
+ with:
+ name: LootableBodies-1.20.1-${{ github.sha }}
+ if-no-files-found: error
+ path: build/release/bundle/*
+ - name: Retain build evidence
+ if: always()
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
+ with:
+ name: native120-fast-${{ github.sha }}
+ if-no-files-found: error
+ path: |
+ build/reports/**
+ build/release/bundle/*
+ ${{ runner.temp }}/native120-build.log
diff --git a/.github/workflows/release-workflow-checks.yml b/.github/workflows/release-workflow-checks.yml
index 79b185f..d769d83 100644
--- a/.github/workflows/release-workflow-checks.yml
+++ b/.github/workflows/release-workflow-checks.yml
@@ -1,9 +1,9 @@
name: Validate release workflow
on:
push:
- branches: ['feature/**', 'master-1.19.4']
+ branches: ['feature/**', 'master-1.20.1']
pull_request:
- branches: ['master-1.19.4']
+ branches: ['master-1.20.1']
permissions:
contents: read
jobs:
@@ -25,7 +25,7 @@ jobs:
run: >-
"$RUNNER_TEMP/actionlint/actionlint" -shellcheck= -pyflakes=
.github/workflows/deploy-release.yml .github/workflows/release-workflow-checks.yml .github/workflows/artifact-audit-1.13.yml .github/workflows/artifact-audit-1.14.yml .github/workflows/artifact-audit-1.15.yml .github/workflows/artifact-audit-1.16.yml .github/workflows/artifact-audit-1.17.yml
- .github/workflows/native119.yml .github/workflows/artifact-audit-1.19.yml .github/workflows/codeql-analysis.yml
+ .github/workflows/native120.yml .github/workflows/artifact-audit-1.20.yml .github/workflows/codeql-analysis.yml
- name: Test version selection and publication checks
run: python3 .github/scripts/test_release_workflow.py -v
diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 905f4c3..0b5ef46 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -1,6 +1,6 @@
-2.4.10.119041 - Minecraft 1.19.4, Forge
+2.4.11.120011 - Minecraft 1.20.1, Forge
-Updated for Minecraft 1.19.4.
+Updated for Minecraft 1.20.1.
Preserves bodies and their contents when upgrading older worlds.
Back up the whole world before upgrading.
diff --git a/README.md b/README.md
index 1f1d8f9..34a5689 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
-# Lootable Bodies for Minecraft 1.19.4
+# Lootable Bodies for Minecraft 1.20.1
Your items stay on a corpse when you die. Right-click the body to recover them.
-Use Forge 45.4.5 and Java 17. Install `LootableBodies-2.4.10.119041.jar`
+Use Forge 47.4.28 and Java 17. Install `LootableBodies-2.4.11.120011.jar`
in the client and server mods folders. Back up your world before upgrading.
Created by Cyanobacterium (DrCyano), maintained by SkyBlade1978.
@@ -12,7 +12,7 @@ Open Mods, select Lootable Bodies and click Config to change its settings.
Appearance changes apply straight away on your client. World and server rules
take effect after restarting Minecraft or the server.
-Existing 1.10.2, 1.11.2, 1.12.2, 1.13.2, 1.14.4, 1.15.2, 1.16.5, 1.17.1 and 1.18.2 corpses are preserved when upgrading,
+Existing 1.10.2, 1.11.2, 1.12.2, 1.13.2, 1.14.4, 1.15.2, 1.16.5, 1.17.1, 1.18.2 and 1.19.4 corpses are preserved when upgrading,
including bodies in chunks you visit later. Keep the whole world backup,
including its `data` folder. If preservation cannot finish safely, world loading
stops with an explanation in the log. Items from missing mods are retained for
diff --git a/RELEASING.md b/RELEASING.md
index 6ab4769..65cf4e9 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -1,5 +1,7 @@
# Releasing
+The Forge 1.20.1 port stays local until qualification and manual testing are complete.
+
Gameplay, graphical clients and world migration checks run locally before
publishing. Full Java/Python suites run locally. GitHub Actions runs production builds,
archive checks, security scans and small workflow/wrapper contracts. It does not install a separate game runtime or run the gameplay suite.
@@ -17,7 +19,7 @@ archive checks, security scans and small workflow/wrapper contracts. It does not
and security checks pass. Recheck the exact merged source and all six file hashes. The independent
artifact audit checks the uploaded files without rebuilding them.
4. Rehash the retained local evidence and supply its receipt to the guarded
- release workflow. For 1.17.1 and 1.18.2 the receipt binds the exact source commit, version,
+ release workflow. For 1.17.1, 1.18.2, 1.19.4 and 1.20.1 the receipt binds the exact source commit, version,
six file hashes, evidence hash and manual acceptance. Publish the files already
checked by CI, then independently download and compare the public GitHub files
and CurseForge jar. An accepted upload alone does not complete the release.
diff --git a/TESTING.md b/TESTING.md
index eea0383..caf8c62 100644
--- a/TESTING.md
+++ b/TESTING.md
@@ -1,11 +1,7 @@
# Testing
-Run functional qualification locally before publishing. Hosted jobs run small
-workflow/wrapper contracts, production builds, archive checks and CodeQL security
-analysis. They do not run the full Java/Python suites, Minecraft, graphical
-clients, migration worlds, runtime SDK installers or IDE consumers. Fork pushes
-get the cheap policy check; the MMD build independently checks the six release
-files. Compiler dependencies needed by ForgeGradle are still build inputs.
+Run qualification for Forge 1.20.1 locally. Hosted checks cover workflow and wrapper contracts, compilation, archive checks
+and security analysis.
During development, reproduce the changed behaviour first, then batch the cheap
Java and Python checks. Reserve the full runtime suite for a frozen candidate.
@@ -24,7 +20,7 @@ For a frozen candidate:
3. Prepare and seal genuine historical writers/worlds once. Run the known
recovery/terrain/entity-region failure origins first and count these runs in
the full matrix. Stop later work after a genuine failure; do not hunt green.
-4. Complete the current 47-origin, 94-case, ten-phase history contract, all
+4. Complete the current 68-origin, 136-case, ten-phase history contract, all
refusals, packaged/development launches, five graphical modes, configuration
restart, rejected viewer, actual two-client inventory packets, 13 visibility
checks and 64 respawns. Keep both 6,200 ticks and 310 seconds and the connected
diff --git a/build.gradle b/build.gradle
index d4e4a67..b54a5e2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -94,14 +94,14 @@ tasks.register('nativeBoundaryTest', Test) {
description = 'Runs native conversion, storage and early-read regressions without launching Minecraft.'
testClassesDirs = sourceSets.test.output.classesDirs
classpath = sourceSets.test.runtimeClasspath
- include '**/Native117*Test.class', '**/Native118*Test.class', '**/Native119*Test.class', '**/EarlyWorldPreservationTest.class', '**/HookRegistrationOrderTest.class'
+ include '**/Native117*Test.class', '**/Native118*Test.class', '**/Native119*Test.class', '**/Native120*Test.class', '**/EarlyWorldPreservationTest.class', '**/HookRegistrationOrderTest.class'
}
-tasks.register('generateNative119Fixtures', JavaExec) {
+tasks.register('generateNative120Fixtures', JavaExec) {
group = 'verification'
dependsOn 'testClasses'
classpath = sourceSets.test.runtimeClasspath
- mainClass = 'cyano.lootable.buildtest.Native119FixtureGenerator'
- args providers.gradleProperty('nativeFixtureOutput').getOrElse('build/native119-fixture-preparation-01')
+ mainClass = 'cyano.lootable.buildtest.Native120FixtureGenerator'
+ args providers.gradleProperty('nativeFixtureOutput').getOrElse('build/native120-fixture-preparation-01')
}
tasks.withType(JavaExec).configureEach { javaLauncher = productionLauncher }
tasks.withType(Javadoc).configureEach {
diff --git a/gradle.properties b/gradle.properties
index a6815c1..9b3283e 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -5,10 +5,10 @@ org.gradle.caching=true
org.gradle.parallel=false
org.gradle.workers.max=2
net.minecraftforge.gradle.merge-source-sets=false
-minecraft_version=1.19.4
-forge_version=45.4.5
+minecraft_version=1.20.1
+forge_version=47.4.28
mapping_channel=official
-mapping_version=1.19.4
+mapping_version=1.20.1
# Release identity and dispatcher target metadata.
loader_name=forge
@@ -25,7 +25,7 @@ mod_vendor=Cyanobacterium
mod_license=LGPL-2.1-only
mod_license_name=GNU Lesser General Public License, Version 2.1
mod_license_url=https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
-mod_version=2.4.10.119041
+mod_version=2.4.11.120011
mod_group=zone.moddev.mc.lootablebodies
mod_authors=Cyanobacterium,SkyBlade1978
mod_description=Saves your items on a lootable corpse when you die.
diff --git a/gradle/assets.gradle b/gradle/assets.gradle
index b9398b9..1beb87d 100644
--- a/gradle/assets.gradle
+++ b/gradle/assets.gradle
@@ -1,6 +1,6 @@
// Keep development launches independent of any installed Minecraft instance.
ext.minecraftGameHome = layout.projectDirectory.dir('.gradle/minecraft-home')
-ext.minecraftAssetIndex = '3'
+ext.minecraftAssetIndex = '5'
// Buildship refresh has no command-line -P arguments. Retain a prepared local
// client location rather than requiring its assets/natives to be copied.
Properties localMinecraftSetup = new Properties()
@@ -10,7 +10,7 @@ String assetFolder=org.gradle.internal.os.OperatingSystem.current().isMacOsX()?'
ext.minecraftAssets = layout.projectDirectory.dir(providers.gradleProperty('minecraftAssetsDirectory')
.getOrElse(localMinecraftSetup.getProperty('minecraftAssetsDirectory',minecraftGameHome.dir(assetFolder).asFile.absolutePath)))
ext.minecraftNatives = layout.projectDirectory.dir(providers.gradleProperty('minecraftNativesDirectory')
- .getOrElse(localMinecraftSetup.getProperty('minecraftNativesDirectory','.gradle/native119-client-inputs/natives')))
+ .getOrElse(localMinecraftSetup.getProperty('minecraftNativesDirectory','.gradle/native120-client-inputs/natives')))
tasks.register('prepareMinecraftAssets') {
group = 'ide'
doLast {
@@ -25,10 +25,10 @@ tasks.register('prepareMinecraftAssets') {
java.nio.file.Files.move(pending.toPath(),output.toPath(),java.nio.file.StandardCopyOption.REPLACE_EXISTING)
}
}
- File descriptor=new File(root,'1.19.4.json')
- download('https://piston-meta.mojang.com/v1/packages/4fc30c189872faebae2617aba033cc17fec5899f/1.19.4.json','4fc30c189872faebae2617aba033cc17fec5899f',descriptor)
+ File descriptor=new File(root,'1.20.1.json')
+ download('https://piston-meta.mojang.com/v1/packages/72ae93b0934df5541860e3e6b72cf883c74da491/1.20.1.json','72ae93b0934df5541860e3e6b72cf883c74da491',descriptor)
def version=new groovy.json.JsonSlurper().parse(descriptor)
- if(version.id!='1.19.4' || version.assetIndex.id!=minecraftAssetIndex)
+ if(version.id!='1.20.1' || version.assetIndex.id!=minecraftAssetIndex)
throw new GradleException('Prepared Minecraft client metadata has the wrong target identity')
File index=new File(root,"indexes/${minecraftAssetIndex}.json")
download(version.assetIndex.url,version.assetIndex.sha1,index)
diff --git a/gradle/audit.gradle b/gradle/audit.gradle
index f382fa8..802b17b 100644
--- a/gradle/audit.gradle
+++ b/gradle/audit.gradle
@@ -62,13 +62,13 @@ ext.auditReleaseArchives = { File directory ->
if (!names.contains(it)) throw new GradleException("Missing production entry ${it}")
}
String info=zip.getInputStream(zip.getEntry('META-INF/mods.toml')).getText('UTF-8')
- ['modId="lootablebodies"',"version=\"${project.version}\"",'authors="Cyanobacterium, SkyBlade1978"','modLoader="javafml"','versionRange="[1.19.4]"'].each {
+ ['modId="lootablebodies"',"version=\"${project.version}\"",'authors="Cyanobacterium, SkyBlade1978"','modLoader="javafml"','versionRange="[1.20.1]"'].each {
if(!info.contains(it))throw new GradleException("Incorrect production metadata: ${it}")
}
def pack=new JsonSlurper().parseText(zip.getInputStream(zip.getEntry('pack.mcmeta')).getText('UTF-8')).pack
- if(pack.pack_format!=13 || pack['forge:server_data_pack_format']!=12 ||
+ if(pack.pack_format!=15 || pack.containsKey('forge:server_data_pack_format') ||
pack.containsKey('forge:resource_pack_format') || pack.containsKey('forge:data_pack_format'))
- throw new GradleException('Native pack formats must be resource13/server-data12')
+ throw new GradleException('Native pack formats must be resource15/server-data15')
def coremods=new JsonSlurper().parseText(zip.getInputStream(zip.getEntry('META-INF/coremods.json')).getText('UTF-8'))
if(coremods!=[lootablebodies_preservation:'coremods/early-world-preservation.js'])
throw new GradleException('Missing or changed early native save attachment')
diff --git a/gradle/ide.gradle b/gradle/ide.gradle
index 8077f6d..e12c0bc 100644
--- a/gradle/ide.gradle
+++ b/gradle/ide.gradle
@@ -67,7 +67,7 @@ tasks.register('isolateEclipseProductionRuns') {
if (!launch.isFile()) throw new GradleException("Generate ${name} with genEclipseRuns first")
String contents = launch.getText('UTF-8')
File nativeRoot=minecraftNatives.asFile
- if(!nativeRoot.isDirectory())throw new GradleException('Prepare the official native1.19.4 client inputs before generating Eclipse launches')
+ if(!nativeRoot.isDirectory())throw new GradleException('Prepare the official native1.20.1 client inputs before generating Eclipse launches')
contents=contents.replace('{asset_index}',minecraftAssetIndex)
.replace('{assets_root}',minecraftAssets.asFile.absolutePath)
.replace('{natives}',nativeRoot.absolutePath)
diff --git a/gradle/log4j2.xml b/gradle/log4j2.xml
index a724e30..0bc9046 100644
--- a/gradle/log4j2.xml
+++ b/gradle/log4j2.xml
@@ -2,8 +2,8 @@
-
-
+
+
-
+
diff --git a/gradle/native119-writer.init.gradle b/gradle/native119-writer.init.gradle
new file mode 100644
index 0000000..0ebcadc
--- /dev/null
+++ b/gradle/native119-writer.init.gradle
@@ -0,0 +1,26 @@
+// Isolated writer compiles against public19's API and exact Java17 toolchain.
+gradle.projectsEvaluated {
+ def p=gradle.rootProject
+ File writerRoot=p.file(p.property('historicalWriterSource'))
+ def classes=p.layout.buildDirectory.dir('native119-seed/classes')
+ def compile=p.tasks.register('compileNative119Seed',JavaCompile) {
+ dependsOn 'classes'
+ source p.fileTree(new File(writerRoot,'src/nativeSeed119/java'))
+ classpath=p.files(p.sourceSets.main.output,p.sourceSets.main.compileClasspath)
+ destinationDirectory=classes
+ options.compilerArgs.add('-proc:none')
+ }
+ def jar=p.tasks.register('native119SeedJar',Jar) {
+ dependsOn compile
+ archiveFileName='lootablebodies-native119-world-fixtures.jar'
+ destinationDirectory=p.layout.buildDirectory.dir('native119-seed')
+ from classes
+ from new File(writerRoot,'src/nativeSeed119/resources')
+ }
+ def packaged=p.renamer.classes(jar) {
+ map.from p.minecraft.dependency.toSrgFile
+ libraries.from(p.tasks.named('jar'),p.sourceSets.main.compileClasspath)
+ output=p.layout.buildDirectory.file('native119-seed/lootablebodies-native119-world-fixtures-reobf.jar')
+ }
+ p.tasks.register('prepareNative119WorldFixtures') {dependsOn packaged}
+}
diff --git a/gradle/release.gradle b/gradle/release.gradle
index 8fcdc5d..6da03c7 100644
--- a/gradle/release.gradle
+++ b/gradle/release.gradle
@@ -5,8 +5,8 @@ def expectedIdentity = [
mod_id: 'lootablebodies', mod_artifact: 'LootableBodies',
mod_group: 'zone.moddev.mc.lootablebodies', mod_vendor: 'Cyanobacterium',
mod_name: "DrCyano's Lootable Bodies", mod_authors: 'Cyanobacterium,SkyBlade1978',
- minecraft_version: '1.19.4', forge_version: '45.4.5',
- mapping_channel: 'official', mapping_version: '1.19.4',
+ minecraft_version: '1.20.1', forge_version: '47.4.28',
+ mapping_channel: 'official', mapping_version: '1.20.1',
loader_name: 'forge', loader_code: '1', curseforge_project_id: '240968',
java_version: '17', java_toolchain_version: '17.0.20.1+1', gradle_java_version: '17',
mod_license: 'LGPL-2.1-only', mod_license_name: 'GNU Lesser General Public License, Version 2.1',
diff --git a/gradle/runtime.gradle b/gradle/runtime.gradle
index 942162d..bdb3494 100644
--- a/gradle/runtime.gradle
+++ b/gradle/runtime.gradle
@@ -75,10 +75,10 @@ def prepareOwnedLoaderConfig = { File root ->
}
}
-// Use an actual native45 codec header in each newly owned flat fixture world.
+// Use an actual native47 codec header in each newly owned flat fixture world.
def prepareOwnedFlatWorld = { File root ->
- File header = file('tools/fixtures/native119-flat-level.dat')
- if (!header.isFile() || releaseDigest(header) != '9792ED9B0BF8A00ACB6BFFAEB50DC8B9C462CD9B6C96A9E6ED5CA65CFD534A55')
+ File header = file('tools/fixtures/native120-flat-level.dat')
+ if (!header.isFile() || releaseDigest(header) != '81E5056FCE49638B40F532B819FC82B0EBBFC954BCF4A7E20E3D6538E8FC0645')
throw new GradleException('Missing or changed native flat fixture header')
File world = new File(root,'world')
if (world.exists()) throw new GradleException('Refusing to replace an existing fixture world')
@@ -138,7 +138,6 @@ def phaseTask = { String phase, Object prerequisite ->
options.workingDir.set(runDirectory)
run.workingDir(runDirectory)
run.systemProperty('lootablebodies.phase', phase)
- run.systemProperty('log4j.configurationFile', file('src/integrationTest/resources/log4j2.xml').toURI())
run.jvmArgs('-Xlog:gc*,safepoint:file=integration-'+phase+'.gc.txt:time,uptime,pid,level,tags')
run.executable = productionLauncher.get().executablePath.asFile.path
run.setClasspath(files(developmentOutput, run.classpath.filter { !sourceSets.main.output.files.contains(it) }))
@@ -161,6 +160,9 @@ def phaseTask = { String phase, Object prerequisite ->
File debug = new File(runDirectory.get().asFile,'logs/debug.log')
if (!debug.isFile() || !debug.length()) throw new GradleException('Missing complete native debug log')
copy { from debug; into runDirectory; rename { "${phase}.debug.log" } }
+ File bootstrap = new File(runDirectory.get().asFile,'logs/bootstrap-debug.log')
+ if (!bootstrap.isFile() || !bootstrap.length()) throw new GradleException('Missing complete Forge bootstrap log')
+ copy { from bootstrap; into runDirectory; rename { "${phase}.bootstrap-debug.log" } }
if (!fileTree(new File(runDirectory.get().asFile,'crash-reports')).isEmpty())
throw new GradleException('Runtime crash reports found')
}
@@ -242,7 +244,6 @@ tasks.register('clientIntegrationTest') {
options.workingDir.set(root)
run.workingDir(root)
run.systemProperty('lootablebodies.clientCheck','true')
- run.systemProperty('log4j.configurationFile',file('src/integrationTest/resources/log4j2.xml').toURI())
run.executable = productionLauncher.get().executablePath.asFile.path
run.setClasspath(files(developmentOutput,run.classpath.filter { !sourceSets.main.output.files.contains(it) }))
run.exec()
@@ -351,7 +352,6 @@ tasks.register('forgeControlServer') {
try {
options.workingDir.set(root)
run.workingDir(root)
- run.systemProperty('log4j.configurationFile',file('src/integrationTest/resources/log4j2.xml').toURI())
run.setClasspath(files(controlOutput,run.classpath.filter { !sourceSets.main.output.files.contains(it) && it != developmentOutput.get().asFile }))
run.exec()
} finally {
diff --git a/settings.gradle b/settings.gradle
index a165db8..d9494d9 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -13,7 +13,7 @@ gradle.taskGraph.whenReady { graph ->
task instanceof org.gradle.api.tasks.JavaExec ||
task.name in ['integrationTest', 'clientIntegrationTest', 'forgeControlServer',
'localRuntimeVerification', 'prepareLegacyWorldFixtures',
- 'generateNative118Fixtures'] ||
+ 'generateNative120Fixtures'] ||
task.name.startsWith('integration')
}
if (!localOnly.empty) throw new GradleException(
diff --git a/src/controlTest/java/buildcontrol/ForgeControl.java b/src/controlTest/java/buildcontrol/ForgeControl.java
index 672390e..3a9c100 100644
--- a/src/controlTest/java/buildcontrol/ForgeControl.java
+++ b/src/controlTest/java/buildcontrol/ForgeControl.java
@@ -31,16 +31,16 @@ private void loadComplete(net.minecraftforge.fml.event.lifecycle.FMLLoadComplete
if(++checkTicks==1) {
p.setGameMode(net.minecraft.world.level.GameType.SURVIVAL);
double x=checkDeaths%2==0?96.5:-.4,z=checkDeaths%2==0?96.5:-.4;
- for(int a=-6;a<=6;a++)for(int b=-6;b<=6;b++)p.level.setBlockAndUpdate(net.minecraft.core.BlockPos.containing(x+a,80,z+b),net.minecraft.world.level.block.Blocks.OBSIDIAN.defaultBlockState());
- p.setRespawnPosition(p.level.dimension(),new net.minecraft.core.BlockPos((int)Math.floor(x),80,(int)Math.floor(z)-3),0F,true,false);
+ for(int a=-6;a<=6;a++)for(int b=-6;b<=6;b++)p.level().setBlockAndUpdate(net.minecraft.core.BlockPos.containing(x+a,80,z+b),net.minecraft.world.level.block.Blocks.OBSIDIAN.defaultBlockState());
+ p.setRespawnPosition(p.level().dimension(),new net.minecraft.core.BlockPos((int)Math.floor(x),80,(int)Math.floor(z)-3),0F,true,false);
p.connection.teleport(x,81,z,0,0);
}
if(checkTicks==50){
int px=(int)Math.floor(p.getX()),pz=(int)Math.floor(p.getZ());
for(int ax=-6;ax<=6;ax++)for(int az=-6;az<=6;az++){
- p.level.setBlockAndUpdate(new net.minecraft.core.BlockPos(px+ax,77,pz+az),net.minecraft.world.level.block.Blocks.OBSIDIAN.defaultBlockState());
- p.level.removeBlock(new net.minecraft.core.BlockPos(px+ax,79,pz+az),false);
- p.level.removeBlock(new net.minecraft.core.BlockPos(px+ax,80,pz+az),false);
+ p.level().setBlockAndUpdate(new net.minecraft.core.BlockPos(px+ax,77,pz+az),net.minecraft.world.level.block.Blocks.OBSIDIAN.defaultBlockState());
+ p.level().removeBlock(new net.minecraft.core.BlockPos(px+ax,79,pz+az),false);
+ p.level().removeBlock(new net.minecraft.core.BlockPos(px+ax,80,pz+az),false);
}
p.kill();checkTicks=0;System.out.println("NATIVE_DEATH "+(++checkDeaths));}
}
@@ -112,11 +112,11 @@ private void tcp(net.minecraft.client.Minecraft mc) {
pinger=new net.minecraft.client.multiplayer.ServerStatusPinger();
try{pinger.pingServer(destination,()->{});}catch(java.net.UnknownHostException failure){throw new IllegalStateException(failure);}
stage=5;
- }else {net.minecraft.client.gui.screens.ConnectScreen.startConnecting(new net.minecraft.client.gui.screens.TitleScreen(),mc,net.minecraft.client.multiplayer.resolver.ServerAddress.parseString(destination.ip),destination);stage=1;}
+ }else {net.minecraft.client.gui.screens.ConnectScreen.startConnecting(new net.minecraft.client.gui.screens.TitleScreen(),mc,net.minecraft.client.multiplayer.resolver.ServerAddress.parseString(destination.ip),destination,false);stage=1;}
wait=0;
}else if(stage==5) {
pinger.tick();
- if(destination.ping>=0){pinger.removeAll();net.minecraft.client.gui.screens.ConnectScreen.startConnecting(new net.minecraft.client.gui.screens.TitleScreen(),mc,net.minecraft.client.multiplayer.resolver.ServerAddress.parseString(destination.ip),destination);stage=1;wait=0;}
+ if(destination.ping>=0){pinger.removeAll();net.minecraft.client.gui.screens.ConnectScreen.startConnecting(new net.minecraft.client.gui.screens.TitleScreen(),mc,net.minecraft.client.multiplayer.resolver.ServerAddress.parseString(destination.ip),destination,false);stage=1;wait=0;}
}else if(stage==1 && mc.level!=null && mc.player!=null) {
if(Boolean.getBoolean("lootablebodies.control.deaths")) {
delayNativePackets(mc);
diff --git a/src/controlTest/resources/META-INF/mods.toml b/src/controlTest/resources/META-INF/mods.toml
index ccaaaa4..3d5ee9c 100644
--- a/src/controlTest/resources/META-INF/mods.toml
+++ b/src/controlTest/resources/META-INF/mods.toml
@@ -1,5 +1,5 @@
modLoader="javafml"
-loaderVersion="[45,)"
+loaderVersion="[47,)"
license="LGPL-2.1-only"
[[mods]]
modId="forgeplatformcontrol"
diff --git a/src/controlTest/resources/pack.mcmeta b/src/controlTest/resources/pack.mcmeta
index ae97e7a..a2d742e 100644
--- a/src/controlTest/resources/pack.mcmeta
+++ b/src/controlTest/resources/pack.mcmeta
@@ -1 +1,6 @@
-{"pack":{"pack_format":13,"forge:server_data_pack_format":12,"description":"Disposable Forge control"}}
+{
+ "pack": {
+ "pack_format": 15,
+ "description": "Disposable Forge control"
+ }
+}
diff --git a/src/integrationTest/java/cyano/lootable/buildtest/ClientChecks.java b/src/integrationTest/java/cyano/lootable/buildtest/ClientChecks.java
index 8f5f7f5..f7bb1e1 100644
--- a/src/integrationTest/java/cyano/lootable/buildtest/ClientChecks.java
+++ b/src/integrationTest/java/cyano/lootable/buildtest/ClientChecks.java
@@ -147,7 +147,7 @@ public void register() {
} catch(Throwable error) { fail(error); }
}
private static void positionViewer(net.minecraft.server.level.ServerPlayer player) {
- ServerLevel world=player.getLevel();
+ ServerLevel world=player.serverLevel();
RuntimeChecks.awaitChunk(world,0,0);
for(int x=7;x<=9;x++)for(int z=6;z<=8;z++)
world.setBlockAndUpdate(new net.minecraft.core.BlockPos(x,79,z),net.minecraft.world.level.block.Blocks.STONE.defaultBlockState());
diff --git a/src/integrationTest/java/cyano/lootable/buildtest/ConfigClientChecks.java b/src/integrationTest/java/cyano/lootable/buildtest/ConfigClientChecks.java
index 43901c7..b972cac 100644
--- a/src/integrationTest/java/cyano/lootable/buildtest/ConfigClientChecks.java
+++ b/src/integrationTest/java/cyano/lootable/buildtest/ConfigClientChecks.java
@@ -99,8 +99,11 @@ private void openMods(Minecraft mc)throws Exception {
if(mc.screen instanceof CorpseConfigGui) {
Screen screen=mc.screen;Map rows=(Map)get(screen,"rows");
Collection positions=rows.isEmpty()?java.util.Collections.singletonList(100):rows.values();
- for(int y:positions)for(int x:new int[]{screen.width/2-150,screen.width/2,screen.width/2+150})
- screen.render(new com.mojang.blaze3d.vertex.PoseStack(),x,y+5,0);
+ var graphics=new net.minecraft.client.gui.GuiGraphics(mc,mc.renderBuffers().bufferSource());
+ for(int y:positions)for(int x:new int[]{screen.width/2-150,screen.width/2,screen.width/2+150}) {
+ screen.render(graphics,x,y+5,0);
+ graphics.flush();
+ }
}
Screenshot.grab(mc.gameDirectory,name,mc.getMainRenderTarget(),message->System.out.println(message.getString()));
}
diff --git a/src/integrationTest/java/cyano/lootable/buildtest/ConnectedClientChecks.java b/src/integrationTest/java/cyano/lootable/buildtest/ConnectedClientChecks.java
index ed578dd..7be92c2 100644
--- a/src/integrationTest/java/cyano/lootable/buildtest/ConnectedClientChecks.java
+++ b/src/integrationTest/java/cyano/lootable/buildtest/ConnectedClientChecks.java
@@ -126,9 +126,9 @@ boolean tick(Minecraft mc) throws Exception {
private void prepareDeath(Minecraft mc) throws Exception {
MinecraftServer server=mc.getSingleplayerServer(); ServerPlayer player=player(mc);
for(int x=21;x<=27;x++) for(int z=5;z<=11;z++)
- player.level.setBlockAndUpdate(new BlockPos(x,79,z),Blocks.STONE.defaultBlockState());
- player.level.getGameRules().getRule(net.minecraft.world.level.GameRules.RULE_KEEPINVENTORY).set(false,null);
- player.level.getGameRules().getRule(net.minecraft.world.level.GameRules.RULE_DOMOBSPAWNING).set(false,null);
+ player.level().setBlockAndUpdate(new BlockPos(x,79,z),Blocks.STONE.defaultBlockState());
+ player.level().getGameRules().getRule(net.minecraft.world.level.GameRules.RULE_KEEPINVENTORY).set(false,null);
+ player.level().getGameRules().getRule(net.minecraft.world.level.GameRules.RULE_DOMOBSPAWNING).set(false,null);
player.setGameMode(GameType.SURVIVAL); player.getInventory().clearContent();player.getInventory().selected=0;
for(int slot=0;slot<6;slot++)player.setItemSlot(EntityLootableBody.EQUIPMENT_SLOTS[slot],RuntimeChecks.stack(slot));
for(int slot=9;slot<=11;slot++)player.getInventory().items.set(slot,RuntimeChecks.stack(slot+10));
@@ -136,7 +136,7 @@ private void prepareDeath(Minecraft mc) throws Exception {
player.containerMenu.broadcastChanges(); player.kill();
RuntimeChecks.require(!player.isAlive(),"Connected player did not die");
EntityLootableBody corpse=null;
- for(Entity entity:FixtureApi.entities(player.level))
+ for(Entity entity:FixtureApi.entities(player.level()))
if(entity instanceof EntityLootableBody && player.getGameProfile().getName().equals(((EntityLootableBody)entity).getCustomNameTag())) corpse=(EntityLootableBody)entity;
RuntimeChecks.require(corpse!=null,"Connected death did not create a corpse");
corpseId=corpse.getUUID();corpse.setNoGravity(true);corpse.setNoAi(true);corpse.setDeltaMovement(0,corpse.getDeltaMovement().y,corpse.getDeltaMovement().z);corpse.setDeltaMovement(corpse.getDeltaMovement().x,0,corpse.getDeltaMovement().z);corpse.setDeltaMovement(corpse.getDeltaMovement().x,corpse.getDeltaMovement().y,0);
@@ -181,7 +181,7 @@ private static void checkPlayer(net.minecraft.world.entity.player.Inventory inve
for(int i=0;i20){schedule(mc,()->{
- RuntimeChecks.awaitChunk(player(mc).getLevel(),2,0);body(mc).baseTick();RuntimeChecks.verifyExpected(folder(mc),"drop-creative-final",body(mc));
+ RuntimeChecks.awaitChunk(player(mc).serverLevel(),2,0);body(mc).baseTick();RuntimeChecks.verifyExpected(folder(mc),"drop-creative-final",body(mc));
RuntimeChecks.require(savedPlayer.equals(player(mc).getInventory().save(new ListTag())),"Creative player inventory changed on disk reload");player(mc).connection.teleport(40.5,80,7.5,0,0);
mark(mc,"creative_cancelled_close_full_inventory_disk_reload");});next(23);
} else if(stage==23 && done()){if(open(mc))next(24);}
@@ -99,17 +99,17 @@ else if(stage==24 && gui(mc) && frames>20){
return false;
}
private void prepare(Minecraft mc){
- for(Entity entity:FixtureApi.entities(player(mc).getLevel()))
+ for(Entity entity:FixtureApi.entities(player(mc).serverLevel()))
if(entity instanceof ItemEntity && !entity.isRemoved())preexistingDrops.add(entity.getUUID());
System.out.println("DROP_PREEXISTING_ITEMS_RETAINED count="+preexistingDrops.size());
- ServerPlayer p=player(mc);for(int x=37;x<=43;x++)for(int z=5;z<=11;z++)p.level.setBlockAndUpdate(new BlockPos(x,79,z),Blocks.STONE.defaultBlockState());
+ ServerPlayer p=player(mc);for(int x=37;x<=43;x++)for(int z=5;z<=11;z++)p.level().setBlockAndUpdate(new BlockPos(x,79,z),Blocks.STONE.defaultBlockState());
p.setGameMode(GameType.SURVIVAL);p.getInventory().clearContent();p.containerMenu.setCarried(ItemStack.EMPTY);p.connection.teleport(40.5,80,7.5,0,0);
- EntityLootableBody b=new EntityLootableBody(p.level);b.setUUID(bodyId);b.setPos(40.5,80,8.5);b.setNoGravity(true);RuntimeChecks.disableVacuum(b);
- b.setItem(6,item(120,17));b.setItem(7,item(120,20));b.setItem(8,item(121,10));RuntimeChecks.require(p.getLevel().addFreshEntity(b),"Graphical drop corpse spawn");refresh(mc);
+ EntityLootableBody b=new EntityLootableBody(p.level());b.setUUID(bodyId);b.setPos(40.5,80,8.5);b.setNoGravity(true);RuntimeChecks.disableVacuum(b);
+ b.setItem(6,item(120,17));b.setItem(7,item(120,20));b.setItem(8,item(121,10));RuntimeChecks.require(p.serverLevel().addFreshEntity(b),"Graphical drop corpse spawn");refresh(mc);
}
private static ItemStack item(int id,int count){ItemStack s=RuntimeChecks.stack(id);s.setCount(count);return s;}
private static void equal(ItemStack a,ItemStack b,String message){RuntimeChecks.require(RuntimeChecks.sameStack(a == null ? ItemStack.EMPTY : a,b == null ? ItemStack.EMPTY : b),message+" expected="+b+" actual="+a);}
- private void drop(Minecraft mc,ItemStack expected){int count=0;for(Entity e:new ArrayList(FixtureApi.entities(player(mc).getLevel())))if(e instanceof ItemEntity && !e.isRemoved() && !preexistingDrops.contains(e.getUUID())){equal(((ItemEntity)e).getItem(),expected,"Accepted new world drop NBT/count");count++;e.discard();}RuntimeChecks.require(count==1,"Expected exactly one new accepted drop, actual="+count);}
+ private void drop(Minecraft mc,ItemStack expected){int count=0;for(Entity e:new ArrayList(FixtureApi.entities(player(mc).serverLevel())))if(e instanceof ItemEntity && !e.isRemoved() && !preexistingDrops.contains(e.getUUID())){equal(((ItemEntity)e).getItem(),expected,"Accepted new world drop NBT/count");count++;e.discard();}RuntimeChecks.require(count==1,"Expected exactly one new accepted drop, actual="+count);}
private static ItemStack slot(Minecraft mc,int index){return mc.player.containerMenu.getSlot(index).getItem();}
private static void click(Minecraft mc,int slot,int button,ClickType type){mc.gameMode.handleInventoryMouseClick(mc.player.containerMenu.containerId,slot,button,type,mc.player);}
private static boolean gui(Minecraft mc){return mc.screen instanceof CorpseGUIContainer;}
diff --git a/src/integrationTest/java/cyano/lootable/buildtest/FixtureApi.java b/src/integrationTest/java/cyano/lootable/buildtest/FixtureApi.java
index c0f3898..87898ef 100644
--- a/src/integrationTest/java/cyano/lootable/buildtest/FixtureApi.java
+++ b/src/integrationTest/java/cyano/lootable/buildtest/FixtureApi.java
@@ -58,12 +58,12 @@ static Entity loadEntity(CompoundTag data, Level world) {
static Entity readChunkEntity(CompoundTag data, ServerLevel world, net.minecraft.world.level.chunk.LevelChunk chunk) {
// Invoke the actual native chunk serializer; do not replace its admission callback.
CompoundTag root=new CompoundTag();net.minecraft.nbt.ListTag list=new net.minecraft.nbt.ListTag();list.add(data.copy());root.put("entities",list);
- root.putInt("xPos",chunk.getPos().x);root.putInt("zPos",chunk.getPos().z);root.put("sections",new net.minecraft.nbt.ListTag());root.putInt("DataVersion",3337);
+ root.putInt("xPos",chunk.getPos().x);root.putInt("zPos",chunk.getPos().z);root.put("sections",new net.minecraft.nbt.ListTag());root.putInt("DataVersion",3465);
// Current fixture NBT follows the real native conversion boundary too.
// The post-load admission callback remains Minecraft's own method.
CompoundTag fixed=cyano.lootable.upgrade.NativeNbt.update(
cyano.lootable.upgrade.UpgradePreservation.wrapper(net.minecraft.util.datafix.DataFixers.getDataFixer()),
- net.minecraft.util.datafix.fixes.References.CHUNK,root,3337,3337);
+ net.minecraft.util.datafix.fixes.References.CHUNK,root,3465,3465);
try {
java.lang.reflect.Method nativeRead=null;
for(java.lang.reflect.Method method:net.minecraft.world.level.chunk.storage.ChunkSerializer.class.getDeclaredMethods())
@@ -135,7 +135,7 @@ static void awaitEntityInbox(ServerLevel world,int x,int z) {
}catch(IllegalAccessException failure){throw new IllegalStateException("Cannot inspect native entity inbox",failure);}
}
static void awaitTicking(Entity entity) {
- ServerLevel world=(ServerLevel)entity.level;int x=net.minecraft.util.Mth.floor(entity.getX())>>4,z=net.minecraft.util.Mth.floor(entity.getZ())>>4;
+ ServerLevel world=(ServerLevel)entity.level();int x=net.minecraft.util.Mth.floor(entity.getX())>>4,z=net.minecraft.util.Mth.floor(entity.getZ())>>4;
world.setChunkForced(x,z,true);
net.minecraft.server.level.ServerChunkCache provider=world.getChunkSource();
long deadline=System.nanoTime()+java.util.concurrent.TimeUnit.SECONDS.toNanos(30);
@@ -160,7 +160,7 @@ static void unload(ServerLevel world,int x,int z) {
@SuppressWarnings("unchecked")
static java.util.Set tracked(Entity entity) {
try {
- Object manager=((ServerLevel)entity.level).getChunkSource().chunkMap;
+ Object manager=((ServerLevel)entity.level()).getChunkSource().chunkMap;
java.lang.reflect.Field map=null;
for(java.lang.reflect.Field field:manager.getClass().getDeclaredFields())
if(it.unimi.dsi.fastutil.ints.Int2ObjectMap.class.isAssignableFrom(field.getType())) {
diff --git a/src/integrationTest/java/cyano/lootable/buildtest/FixtureConnections.java b/src/integrationTest/java/cyano/lootable/buildtest/FixtureConnections.java
index 10bfa28..a06364c 100644
--- a/src/integrationTest/java/cyano/lootable/buildtest/FixtureConnections.java
+++ b/src/integrationTest/java/cyano/lootable/buildtest/FixtureConnections.java
@@ -28,7 +28,7 @@ static boolean ready(String role) {
static void connect(Minecraft mc) {
String address="127.0.0.1:"+Integer.parseInt(System.getProperty("lootablebodies.multiplayer.port"));
ServerData destination=new ServerData("Disposable check",address,false);
- ConnectScreen.startConnecting(new TitleScreen(),mc,net.minecraft.client.multiplayer.resolver.ServerAddress.parseString(address),destination);
+ ConnectScreen.startConnecting(new TitleScreen(),mc,net.minecraft.client.multiplayer.resolver.ServerAddress.parseString(address),destination,false);
}
static boolean retry(Minecraft mc,String role) throws Exception {
if(!(mc.screen instanceof DisconnectedScreen)||mc.level!=null)return false;
diff --git a/src/integrationTest/java/cyano/lootable/buildtest/InventorySafetyChecks.java b/src/integrationTest/java/cyano/lootable/buildtest/InventorySafetyChecks.java
index a733318..236d674 100644
--- a/src/integrationTest/java/cyano/lootable/buildtest/InventorySafetyChecks.java
+++ b/src/integrationTest/java/cyano/lootable/buildtest/InventorySafetyChecks.java
@@ -90,8 +90,8 @@ private static void check(String name,Check check) {
if(!before.contains(entity)) entity.discard();
}
}
- private static ServerPlayer player() {
- ServerPlayer player=new ServerPlayer(server,world,
+ private static InventoryTestPlayer player() {
+ InventoryTestPlayer player=new InventoryTestPlayer(server,world,
new GameProfile(UUID.nameUUIDFromBytes(("inventory-fixture-"+(sequence++)).getBytes(java.nio.charset.StandardCharsets.UTF_8)),"InventoryFixture"));
player.connection=new net.minecraft.server.network.ServerGamePacketListenerImpl(server,
new net.minecraft.network.Connection(net.minecraft.network.protocol.PacketFlow.SERVERBOUND),player);
@@ -113,7 +113,7 @@ private static List drops(ServerPlayer player,ItemStack... stacks) {
return result;
}
private static void handle(PlayerDeathEventHandler handler,ServerPlayer player,List drops) {
- handler.entityDropEvent(new LivingDropsEvent(player,player.damageSources().outOfWorld(),drops,0,false));
+ handler.entityDropEvent(new LivingDropsEvent(player,player.damageSources().fellOutOfWorld(),drops,0,false));
}
private static CompoundTag data(EntityLootableBody body) {
CompoundTag result=new CompoundTag(); body.addAdditionalSaveData(result); return result;
@@ -141,13 +141,13 @@ private static EntityLootableBody newest(Set before) {
throw new AssertionError("No corpse spawned");
}
private static void failedSpawn() {
- ServerPlayer player=player(); RejectingWorld rejected=new RejectingWorld(world);
- player.level=rejected;
+ InventoryTestPlayer player=player(); RejectingWorld rejected=new RejectingWorld(world);
+ player.setFixtureLevel(rejected);
ItemStack bones=new ItemStack(Items.BONE,9); bones.setHoverName(net.minecraft.network.chat.Component.literal("Saved bones"));
List drops=drops(player,bones,item(19,23));
List before=new ArrayList<>(); for(ItemEntity e:drops) before.add(encoded(e.getItem()));
PlayerDeathEventHandler handler=new PlayerDeathEventHandler();
- handler.entityDeathEvent(new LivingDeathEvent(player,player.damageSources().outOfWorld()));
+ handler.entityDeathEvent(new LivingDeathEvent(player,player.damageSources().fellOutOfWorld()));
handle(handler,player,drops);
RuntimeChecks.require(rejected.attempts==1,"Failed spawn branch was not reached");
RuntimeChecks.require(drops.size()==before.size(),"Rejected corpse consumed vanilla death drops");
@@ -164,7 +164,7 @@ private static void cancelledSpawn() {
Map expected=new TreeMap<>(); add(expected,player.getInventory().items.get(0));add(expected,player.getInventory().items.get(9));
Set before=new HashSet<>(FixtureApi.entities(world));
RejectCorpse reject=new RejectCorpse(); MinecraftForge.EVENT_BUS.register(reject);
- try { player.die(player.damageSources().outOfWorld()); } finally { MinecraftForge.EVENT_BUS.unregister(reject); }
+ try { player.die(player.damageSources().fellOutOfWorld()); } finally { MinecraftForge.EVENT_BUS.unregister(reject); }
RuntimeChecks.require(reject.attempts==1,"Corpse cancellation not exercised");
for(Entity entity:FixtureApi.entities(world)) RuntimeChecks.require(before.contains(entity) || !(entity instanceof EntityLootableBody),"Cancelled corpse spawn bypassed Forge cancellation");
RuntimeChecks.require(expected.equals(dropped()),"Cancelled corpse did not fall back to exact vanilla drops");
@@ -172,10 +172,10 @@ private static void cancelledSpawn() {
private static void staleEquipment() {
ServerPlayer player=player(); PlayerDeathEventHandler handler=new PlayerDeathEventHandler();
ItemStack previous=item(0,1); player.setItemSlot(EquipmentSlot.HEAD,previous);
- handler.entityDeathEvent(new LivingDeathEvent(player,player.damageSources().outOfWorld()));
+ handler.entityDeathEvent(new LivingDeathEvent(player,player.damageSources().fellOutOfWorld()));
handle(handler,player,drops(player,previous));
ItemStack current=item(1,1); player.setItemSlot(EquipmentSlot.HEAD,current);
- handler.entityDeathEvent(new LivingDeathEvent(player,player.damageSources().outOfWorld()));
+ handler.entityDeathEvent(new LivingDeathEvent(player,player.damageSources().fellOutOfWorld()));
Map expected=new TreeMap<>(); add(expected,current);add(expected,previous);
Set before=new HashSet<>(FixtureApi.entities(world));
handle(handler,player,drops(player,current,previous));
@@ -192,19 +192,19 @@ private static void staleEquipment() {
private static void cacheLifecycle() throws Exception {
ServerPlayer player=player(); PlayerDeathEventHandler handler=new PlayerDeathEventHandler();
player.setItemSlot(EquipmentSlot.HEAD,item(0,1));
- handler.entityDeathEvent(new LivingDeathEvent(player,player.damageSources().outOfWorld()));
+ handler.entityDeathEvent(new LivingDeathEvent(player,player.damageSources().fellOutOfWorld()));
handle(handler,player,drops(player,player.getItemBySlot(EquipmentSlot.HEAD)));
RuntimeChecks.require(!cache(handler).containsKey(player),"Equipment cache retains completed death");
world.getGameRules().getRule(net.minecraft.world.level.GameRules.RULE_KEEPINVENTORY).set(true,null);
- handler.entityDeathEvent(new LivingDeathEvent(player,player.damageSources().outOfWorld()));
+ handler.entityDeathEvent(new LivingDeathEvent(player,player.damageSources().fellOutOfWorld()));
RuntimeChecks.require(!cache(handler).containsKey(player),"keepInventory death cached equipment");
world.getGameRules().getRule(net.minecraft.world.level.GameRules.RULE_KEEPINVENTORY).set(false,null);
- LivingDeathEvent canceled=new LivingDeathEvent(player,player.damageSources().outOfWorld()); canceled.setCanceled(true);
+ LivingDeathEvent canceled=new LivingDeathEvent(player,player.damageSources().fellOutOfWorld()); canceled.setCanceled(true);
handler.entityDeathEvent(canceled);
RuntimeChecks.require(!cache(handler).containsKey(player),"Cancelled death cached equipment");
- handler.entityDeathEvent(new LivingDeathEvent(player,player.damageSources().outOfWorld()));
+ handler.entityDeathEvent(new LivingDeathEvent(player,player.damageSources().fellOutOfWorld()));
List original=drops(player,item(19,23));
- LivingDropsEvent canceledDrops=new LivingDropsEvent(player,player.damageSources().outOfWorld(),original,0,false);
+ LivingDropsEvent canceledDrops=new LivingDropsEvent(player,player.damageSources().fellOutOfWorld(),original,0,false);
canceledDrops.setCanceled(true);handler.entityDropEvent(canceledDrops);
RuntimeChecks.require(!cache(handler).containsKey(player) && original.size()==1
&& RuntimeChecks.sameStack(original.get(0).getItem(),item(19,23)),"Cancelled drops retained stale cache or changed inventory");
@@ -284,13 +284,13 @@ private static void repeatedRead() {
RuntimeChecks.require(immutable.equals(input),"Reader mutated source NBT");
}
private static void staleAccess() {
- ServerPlayer player=player();EntityLootableBody corpse=body();CorpseContainer container=new CorpseContainer(player.getInventory(),corpse);
+ InventoryTestPlayer player=player();EntityLootableBody corpse=body();CorpseContainer container=new CorpseContainer(player.getInventory(),corpse);
RuntimeChecks.require(container.stillValid(player),"Near living access failed");
player.setPos(100,80,100);RuntimeChecks.require(!container.stillValid(player),"Distant container remained accessible");
player.setPos(64.5,80,64.5);corpse.discard();RuntimeChecks.require(!container.stillValid(player),"Removed corpse remained accessible");
- corpse=body();container=new CorpseContainer(player.getInventory(),corpse);player.level=new RejectingWorld(world);
+ corpse=body();container=new CorpseContainer(player.getInventory(),corpse);player.setFixtureLevel(new RejectingWorld(world));
RuntimeChecks.require(!container.stillValid(player),"Cross-world container remained accessible");
- player.level=world;player.setHealth(0);
+ player.setFixtureLevel(world);player.setHealth(0);
RuntimeChecks.require(!container.stillValid(player),"Dead player retained container access");
player.setHealth(player.getMaxHealth());corpse.kill();corpse.baseTick();
RuntimeChecks.require(!container.stillValid(player),"Dying corpse remained accessible");
@@ -362,6 +362,16 @@ static void seedBlockedRelease(ServerLevel target,File folder) throws Exception
RuntimeChecks.saveExpected(folder,"rejected-release",corpse);
}
/** Controlled false-return seam; no dimension registration, disk writes or chunks of its own. */
+ private static final class InventoryTestPlayer extends ServerPlayer {
+ InventoryTestPlayer(MinecraftServer server, ServerLevel world, GameProfile profile) {
+ super(server,world,profile);
+ }
+
+ void setFixtureLevel(Level world) {
+ setLevel(world);
+ }
+ }
+
private static final class RejectingWorld extends Level {
private final ServerLevel delegate; int attempts;
RejectingWorld(ServerLevel target) {
diff --git a/src/integrationTest/java/cyano/lootable/buildtest/MultiplayerChecks.java b/src/integrationTest/java/cyano/lootable/buildtest/MultiplayerChecks.java
index 7516c2a..b2b0acc 100644
--- a/src/integrationTest/java/cyano/lootable/buildtest/MultiplayerChecks.java
+++ b/src/integrationTest/java/cyano/lootable/buildtest/MultiplayerChecks.java
@@ -99,7 +99,7 @@ private void setPhase(int value) throws Exception {
private void mark(String name) throws Exception{try(FileWriter out=new FileWriter(new File(sync,"checks.txt"),true)){out.write("PASS "+name+"\n");}System.out.println("MULTIPLAYER_CHECK_PASS "+name);}
public static ItemStack stack(int i,int n){ItemStack s=RuntimeChecks.stack(i);s.setCount(n);return s;}
public static void equal(ItemStack a,ItemStack b,String reason){RuntimeChecks.require(RuntimeChecks.sameStack(a == null ? ItemStack.EMPTY : a,b == null ? ItemStack.EMPTY : b),reason);}
- private static int count(ServerPlayer p,int index){int n=0;ItemStack expected=RuntimeChecks.stack(index);for(int i=0;i registry=world.registryAccess().registryOrThrow(net.minecraft.core.registries.Registries.BIOME);
com.mojang.serialization.Codec>> codec=PalettedContainer.codecRW(registry.asHolderIdMap(),registry.holderByNameCodec(),PalettedContainer.Strategy.SECTION_BIOMES,registry.getHolderOrThrow(Biomes.PLAINS));
LevelChunk chunk=world.getChunk(x,z);int samples=0;
diff --git a/src/integrationTest/java/cyano/lootable/buildtest/NativePreflightChecks.java b/src/integrationTest/java/cyano/lootable/buildtest/NativePreflightChecks.java
index 8d807d5..b4dff4b 100644
--- a/src/integrationTest/java/cyano/lootable/buildtest/NativePreflightChecks.java
+++ b/src/integrationTest/java/cyano/lootable/buildtest/NativePreflightChecks.java
@@ -11,7 +11,7 @@
/** Controlled packaged pre-world conversion/refusal; separate from old writers. */
public final class NativePreflightChecks {
- static final String INJECTED="NATIVE119_INJECTED_ENTITY_CHUNK_REFUSAL";
+ static final String INJECTED="NATIVE120_INJECTED_ENTITY_CHUNK_REFUSAL";
private static final AtomicInteger early=new AtomicInteger(),worlds=new AtomicInteger(),starts=new AtomicInteger();
private static String mode;
static void install(net.minecraftforge.eventbus.api.IEventBus bus) {
@@ -32,14 +32,14 @@ public Dynamic update(DSL.TypeReference type,Dynamic input,int from,in
boolean preflight=java.util.Arrays.stream(Thread.currentThread().getStackTrace()).anyMatch(
frame->frame.getClassName().equals("cyano.lootable.upgrade.UpgradeJournal") && frame.getMethodName().equals("validateEntityChunks"));
if(!preflight || worlds.get()!=0 || starts.get()!=0)throw new AssertionError("Entity conversion missed the pre-world boundary");
- early.incrementAndGet();System.out.println("NATIVE119_PREWORLD_ENTITY_CONVERSION from="+from+" to="+to+" mode="+mode);
+ early.incrementAndGet();System.out.println("NATIVE120_PREWORLD_ENTITY_CONVERSION from="+from+" to="+to+" mode="+mode);
if(mode.equals("refuse"))throw new IllegalStateException(INJECTED+": controlled negative fixture, not a native Minecraft error");
}
return nativeFixer.update(type,input,from,to);
}
};
cyano.lootable.upgrade.UpgradePreservation.register(observer);
- System.out.println("NATIVE119_PREFLIGHT_CONTROL_INSTALLED mode="+mode);
+ System.out.println("NATIVE120_PREFLIGHT_CONTROL_INSTALLED mode="+mode);
}));
}
@SubscribeEvent public void loaded(net.minecraftforge.event.level.LevelEvent.Load event){worlds.incrementAndGet();}
@@ -59,7 +59,7 @@ static void verify(MinecraftServer server) throws Exception {
&& item.hasTag() && item.getTag().getString("keep").equals("slot-"+slot),"Preflight inventory changed at slot"+slot);
}
CompoundTag saved=new CompoundTag();body.saveAsPassenger(saved);
- RuntimeChecks.require(saved.getInt("LootableBodiesDataVersion")==9,"Preflight corpse marker was not advanced");
+ RuntimeChecks.require(saved.getInt("LootableBodiesDataVersion")==cyano.lootable.upgrade.UpgradePreservation.DATA_VERSION,"Preflight corpse marker was not advanced");
ListTag aux=saved.getList("Aux",10);RuntimeChecks.require(aux.size()==5,"Preflight Aux record count changed");
for(int index=0;index<5;index++) {
int sourceSlot=index<4?index:47;CompoundTag item=aux.getCompound(index);
@@ -73,6 +73,6 @@ static void verify(MinecraftServer server) throws Exception {
&& ((CompoundTag)value).getCompound("Data").getByte("Slot")==46
&& ((CompoundTag)value).getCompound("Data").getCompound("tag").getString("keep").equals("slot-46")),"Raw Source/Data recovery evidence changed");
Files.writeString(Path.of("preflight-conversion.pass"),"48inputEquipmentRecords/Aux/Source/Data/entityStorage conserved\n",StandardOpenOption.CREATE_NEW);
- System.out.println("NATIVE119_PACKAGED_PREFLIGHT_CONVERSION_PASS");
+ System.out.println("NATIVE120_PACKAGED_PREFLIGHT_CONVERSION_PASS");
}
}
diff --git a/src/integrationTest/java/cyano/lootable/buildtest/NbtClientChecks.java b/src/integrationTest/java/cyano/lootable/buildtest/NbtClientChecks.java
index c7d2556..6330d9d 100644
--- a/src/integrationTest/java/cyano/lootable/buildtest/NbtClientChecks.java
+++ b/src/integrationTest/java/cyano/lootable/buildtest/NbtClientChecks.java
@@ -44,10 +44,10 @@ boolean tick(Minecraft mc) throws Exception {
frames++;RuntimeChecks.require(frames<300,"NBT graphical stage timeout "+stage);
if(stage==0){schedule(mc,()->{
ServerPlayer p=player(mc);p.getInventory().clearContent();p.containerMenu.setCarried(ItemStack.EMPTY);p.setGameMode(GameType.SURVIVAL);
- RuntimeChecks.awaitChunk(p.getLevel(),COORDINATE>>4,COORDINATE>>4);
- for(int x=COORDINATE-3;x<=COORDINATE+11;x++)for(int z=COORDINATE-3;z<=COORDINATE+3;z++)p.level.setBlockAndUpdate(new BlockPos(x,79,z),Blocks.STONE.defaultBlockState());
- RuntimeChecks.require(p.getLevel().getEntities(null,new net.minecraft.world.phys.AABB(COORDINATE,79,COORDINATE-2,COORDINATE+16,83,COORDINATE+4)).isEmpty(),"Graphical malformed fixture chunk is not isolated");
- NbtWorldChecks.seed(p.getLevel(),bodyId,COORDINATE,folder(mc),"graphical");
+ RuntimeChecks.awaitChunk(p.serverLevel(),COORDINATE>>4,COORDINATE>>4);
+ for(int x=COORDINATE-3;x<=COORDINATE+11;x++)for(int z=COORDINATE-3;z<=COORDINATE+3;z++)p.level().setBlockAndUpdate(new BlockPos(x,79,z),Blocks.STONE.defaultBlockState());
+ RuntimeChecks.require(p.serverLevel().getEntities(null,new net.minecraft.world.phys.AABB(COORDINATE,79,COORDINATE-2,COORDINATE+16,83,COORDINATE+4)).isEmpty(),"Graphical malformed fixture chunk is not isolated");
+ NbtWorldChecks.seed(p.serverLevel(),bodyId,COORDINATE,folder(mc),"graphical");
});next(1);}
else if(stage==1 && done() && frames>20){quit(mc);next(2);}
else if(stage==2 && FixtureConnections.stopped(mc)){mc.createWorldOpenFlows().loadLevel(new net.minecraft.client.gui.screens.TitleScreen(),"build-fixture");next(3);}
diff --git a/src/integrationTest/java/cyano/lootable/buildtest/NbtRecoveryChecks.java b/src/integrationTest/java/cyano/lootable/buildtest/NbtRecoveryChecks.java
index e50a6e1..2fa9938 100644
--- a/src/integrationTest/java/cyano/lootable/buildtest/NbtRecoveryChecks.java
+++ b/src/integrationTest/java/cyano/lootable/buildtest/NbtRecoveryChecks.java
@@ -132,13 +132,13 @@ private static void nativeCaps(){
private static void initializedCaps(){
CompoundTag raw=entry(120,17,6),caps=new CompoundTag();caps.putString("absentfixture:inventory","opaque capabilities");raw.put("ForgeCaps",caps);
ItemStack stack=ItemStack.of(raw.copy());
- stack.getCapability(net.minecraftforge.items.CapabilityItemHandler.ITEM_HANDLER_CAPABILITY);
+ stack.getCapability(net.minecraftforge.common.capabilities.ForgeCapabilities.ITEM_HANDLER);
RuntimeChecks.require(!caps.equals(stack.save(new CompoundTag()).get("ForgeCaps")),"Native control no longer exposes unsupported capability loss");
RuntimeChecks.require(raw.getCompound("ForgeCaps").equals(caps),"Native control mutated its source record");
}
private static void knownCaps(){
ItemStack stack=new ItemStack(FixtureItems.keepsake,3);
- stack.getCapability(net.minecraftforge.items.CapabilityItemHandler.ITEM_HANDLER_CAPABILITY);
+ stack.getCapability(net.minecraftforge.common.capabilities.ForgeCapabilities.ITEM_HANDLER);
CompoundTag raw=stack.save(new CompoundTag());raw.putByte("Slot",(byte)6);
RuntimeChecks.require(raw.contains("ForgeCaps",10),"Registered capability control has no serialized payload");
EntityLootableBody body=read(root(raw));contains(body,stack);
diff --git a/src/integrationTest/java/cyano/lootable/buildtest/NbtWorldChecks.java b/src/integrationTest/java/cyano/lootable/buildtest/NbtWorldChecks.java
index 3ebb257..2c74021 100644
--- a/src/integrationTest/java/cyano/lootable/buildtest/NbtWorldChecks.java
+++ b/src/integrationTest/java/cyano/lootable/buildtest/NbtWorldChecks.java
@@ -22,7 +22,7 @@ final class NbtWorldChecks {
static final int GRAPHICAL_COORDINATE=8192;
static int graphicalAdmissions;
static void verifyGraphicalAdmission(Entity entity){
- if(!Boolean.getBoolean("lootablebodies.clientCheck") || entity.level.isClientSide
+ if(!Boolean.getBoolean("lootablebodies.clientCheck") || entity.level().isClientSide
|| entity.getClass()!=EntityLootableBody.class || !entity.getUUID().equals(NEAR))return;
EntityLootableBody body=(EntityLootableBody)entity;
PositionMotionChecks.verifyWorldFields(body);
@@ -120,7 +120,7 @@ static void verifyMetadata(EntityLootableBody b){
RotationFallChecks.verifyWorldFields(b);
CompoundTag raw=rawEquipment(),saved=NameLifetimeChecks.saved(b);
RuntimeChecks.require(b.getCustomNameTag().equals("NbtFixture") && saved.getString("Name").equals("NbtFixture"),"Malformed Name erased vanilla fallback attribution");
- RuntimeChecks.require(!saved.contains("Vac") && saved.getLong("DeathTime")>=0 && saved.getLong("DeathTime")<=b.level.getGameTime() && saved.getLong("DecayTime")>=0 && saved.getLong("DecayTime")<=b.level.getGameTime(),"Malformed disk timers entered active state");
+ RuntimeChecks.require(!saved.contains("Vac") && saved.getLong("DeathTime")>=0 && saved.getLong("DeathTime")<=b.level().getGameTime() && saved.getLong("DecayTime")>=0 && saved.getLong("DecayTime")<=b.level().getGameTime(),"Malformed disk timers entered active state");
for(String key:new String[]{"Name","Vac","DeathTime","DecayTime"})NameLifetimeChecks.retained(b,key,raw.get(key));
RuntimeChecks.require(saved.getList(NameLifetimeChecks.RECOVERY,10).size()==16,"Disk metadata evidence grew or disappeared");
}
diff --git a/src/integrationTest/java/cyano/lootable/buildtest/PortBaselineChecks.java b/src/integrationTest/java/cyano/lootable/buildtest/PortBaselineChecks.java
index dbdf5b2..76821e0 100644
--- a/src/integrationTest/java/cyano/lootable/buildtest/PortBaselineChecks.java
+++ b/src/integrationTest/java/cyano/lootable/buildtest/PortBaselineChecks.java
@@ -48,7 +48,7 @@ public static void run(MinecraftServer server) throws Exception {
require(RespawnPacketTrace.describe(sent).equals(RespawnPacketTrace.describe(received)),"Spawn observer differs before/after native decoding");
require(RespawnPacketTrace.describe(received).contains("mod=lootablebodies "),"Negative control cannot identify corpse spawns");
buffer.clear();
- net.minecraft.network.protocol.game.ClientboundRespawnPacket outgoing=new net.minecraft.network.protocol.game.ClientboundRespawnPacket(world.dimensionTypeRegistration().unwrapKey().orElseThrow(),world.dimension(),0x13572468ABCDEF01L,GameType.SURVIVAL,GameType.CREATIVE,false,true,net.minecraft.network.protocol.game.ClientboundRespawnPacket.KEEP_ALL_DATA,java.util.Optional.empty());
+ net.minecraft.network.protocol.game.ClientboundRespawnPacket outgoing=new net.minecraft.network.protocol.game.ClientboundRespawnPacket(world.dimensionTypeRegistration().unwrapKey().orElseThrow(),world.dimension(),0x13572468ABCDEF01L,GameType.SURVIVAL,GameType.CREATIVE,false,true,net.minecraft.network.protocol.game.ClientboundRespawnPacket.KEEP_ALL_DATA,java.util.Optional.empty(),0);
outgoing.write(buffer);
net.minecraft.network.protocol.game.ClientboundRespawnPacket incoming=new net.minecraft.network.protocol.game.ClientboundRespawnPacket(buffer);
require(RespawnPacketTrace.describe(outgoing).equals(RespawnPacketTrace.describe(incoming)),"Respawn observer differs before/after native decoding");
diff --git a/src/integrationTest/java/cyano/lootable/buildtest/Recovery112Checks.java b/src/integrationTest/java/cyano/lootable/buildtest/Recovery112Checks.java
index c549b60..a9e1d91 100644
--- a/src/integrationTest/java/cyano/lootable/buildtest/Recovery112Checks.java
+++ b/src/integrationTest/java/cyano/lootable/buildtest/Recovery112Checks.java
@@ -47,7 +47,7 @@ private static EntityLootableBody body(ServerLevel world,int coordinate,double y
}
private static void conserved(EntityLootableBody body) {
RuntimeChecks.require(RuntimeChecks.sameStack(body.getItem(6),RuntimeChecks.stack(420)),"Recovery option changed complete item NBT");
- CompoundTag saved=new CompoundTag();body.saveAsPassenger(saved);EntityLootableBody reloaded=new EntityLootableBody(body.level);reloaded.load(saved);
+ CompoundTag saved=new CompoundTag();body.saveAsPassenger(saved);EntityLootableBody reloaded=new EntityLootableBody(body.level());reloaded.load(saved);
RuntimeChecks.require(RuntimeChecks.sameStack(reloaded.getItem(6),RuntimeChecks.stack(420)),"Recovery option inventory changed on reload");
}
}
diff --git a/src/integrationTest/java/cyano/lootable/buildtest/ReportedIssueChecks.java b/src/integrationTest/java/cyano/lootable/buildtest/ReportedIssueChecks.java
index 3fecf5b..e6e32e6 100644
--- a/src/integrationTest/java/cyano/lootable/buildtest/ReportedIssueChecks.java
+++ b/src/integrationTest/java/cyano/lootable/buildtest/ReportedIssueChecks.java
@@ -205,7 +205,7 @@ private static void loadedPlatform() {
EntityLootableBody b=fullBody(c,c,8.5);Map expected=inventory(b);
FixtureApi.awaitTicking(b);
for(int i=0;i<40;i++)world.tickNonPassenger(b);
- RuntimeChecks.require(b.isOnGround() && b.getY()==80 && b.isAlive() && expected.equals(inventory(b)),"Loaded platform/normal gravity lost body or inventory");
+ RuntimeChecks.require(b.onGround() && b.getY()==80 && b.isAlive() && expected.equals(inventory(b)),"Loaded platform/normal gravity lost body or inventory");
}
private static void normalTickGate() {
EntityLootableBody b=fullBody(112,112,8.5);int ticks=b.tickCount;
@@ -228,7 +228,7 @@ private static final class Support implements AutoCloseable {
Map expected;
void unload(){world.setChunkForced(cx,cz,false);net.minecraft.world.level.ChunkPos pos=new net.minecraft.world.level.ChunkPos(cx,cz);world.getChunkSource().addRegionTicket(net.minecraft.server.level.TicketType.FORCED,pos,0,pos);unloadChunk(nx,nz);RuntimeChecks.require(!world.getChunkSource().hasChunk(nx,nz),"Support chunk did not unload");}
void frozen(){for(int i=0;i<100;i++)body.tick();System.out.println("ISSUE17_UNLOADED_SUPPORT y="+body.getY()+" alive="+body.isAlive()+" inventory="+inventory(body).size()+" supportLoaded="+world.getChunkSource().hasChunk(nx,nz));RuntimeChecks.require(body.isAlive() && body.getY()==80 && expected.equals(inventory(body)),"Corpse fell through unloaded neighbour support and lost recoverable inventory");}
- void resume(){RuntimeChecks.awaitChunk(world,nx,nz);FixtureApi.awaitTicking(body);for(int i=0;i<10;i++)world.tickNonPassenger(body);RuntimeChecks.require(body.isOnGround() && body.getY()==80 && FixtureApi.sectionContains(body,cx,cz) && expected.equals(inventory(body)),"Corpse failed to resume safely after support terrain loaded");}
+ void resume(){RuntimeChecks.awaitChunk(world,nx,nz);FixtureApi.awaitTicking(body);for(int i=0;i<10;i++)world.tickNonPassenger(body);RuntimeChecks.require(body.onGround() && body.getY()==80 && FixtureApi.sectionContains(body,cx,cz) && expected.equals(inventory(body)),"Corpse failed to resume safely after support terrain loaded");}
@Override public void close(){if(forced)world.setChunkForced(cx,cz,false);net.minecraft.world.level.ChunkPos pos=new net.minecraft.world.level.ChunkPos(cx,cz);world.getChunkSource().removeRegionTicket(net.minecraft.server.level.TicketType.FORCED,pos,0,pos);}
}
private static Support support(int axis,int direction) {return support(axis,direction,null);}
@@ -249,7 +249,7 @@ private static Support support(int axis,int direction,UUID savedId) {
RuntimeChecks.require(f.forced && world.getChunkSource().hasChunk(f.cx,f.cz),"Native forced chunk unavailable");
FixtureApi.awaitTicking(f.body);
for(int i=0;i<10;i++)world.tickNonPassenger(f.body);
- RuntimeChecks.require(f.body.isOnGround() && f.body.getY()==80 && f.body.isAlive(),"Loaded neighbour platform control failed");
+ RuntimeChecks.require(f.body.onGround() && f.body.getY()==80 && f.body.isAlive(),"Loaded neighbour platform control failed");
return f;
}
private static void forcedSupport(int axis,int direction) {
@@ -297,7 +297,7 @@ static void coldReload(MinecraftServer instance,ServerLevel target,File director
for(int x=cx-2;x<=cx+2;x++)for(int z=cz-2;z<=cz+2;z++)RuntimeChecks.awaitChunk(world,x,z);
FixtureApi.awaitTicking(b);
for(int i=0;i<20;i++)world.tickNonPassenger(b);
- RuntimeChecks.require(b.isOnGround() && b.getY()==80 && b.isAlive(),"Cold-loaded corpse failed to resume on restored platform");
+ RuntimeChecks.require(b.onGround() && b.getY()==80 && b.isAlive(),"Cold-loaded corpse failed to resume on restored platform");
RuntimeChecks.verifyExpected(folder,"issue17-paused",b);
world.setChunkForced(cx,cz,false);
System.out.println("ISSUE17_COLD_RELOAD_DEFERRED_SUPPORT_AND_COMPLETE_NBT_PASS");
diff --git a/src/integrationTest/java/cyano/lootable/buildtest/ReportedIssueClientChecks.java b/src/integrationTest/java/cyano/lootable/buildtest/ReportedIssueClientChecks.java
index b3912fa..d2fdc88 100644
--- a/src/integrationTest/java/cyano/lootable/buildtest/ReportedIssueClientChecks.java
+++ b/src/integrationTest/java/cyano/lootable/buildtest/ReportedIssueClientChecks.java
@@ -55,7 +55,7 @@ void tick(Minecraft mc) throws Exception {
FixtureApi.ClientWorlds.create(mc,"issue-fixture","Issue fixture",net.minecraft.world.level.GameType.SURVIVAL,true);next(1);
} else if(stage==1 && ready(mc)) {
schedule(mc,()->{
- ServerLevel w=player(mc).getLevel();w.getGameRules().getRule(net.minecraft.world.level.GameRules.RULE_KEEPINVENTORY).set(false,null);
+ ServerLevel w=player(mc).serverLevel();w.getGameRules().getRule(net.minecraft.world.level.GameRules.RULE_KEEPINVENTORY).set(false,null);
w.getGameRules().getRule(net.minecraft.world.level.GameRules.RULE_DOMOBSPAWNING).set(false,null);
for(int x=29;x<=36;x++)for(int z=-3;z<=4;z++)w.setBlockAndUpdate(new BlockPos(x,79,z),Blocks.STONE.defaultBlockState());
ServerPlayer p=player(mc);p.setGameMode(GameType.SURVIVAL);p.getInventory().clearContent();p.getInventory().selected=4;
@@ -91,7 +91,7 @@ void tick(Minecraft mc) throws Exception {
} else if(stage==9 && ready(mc) && mc.player.isAlive() && !(mc.screen instanceof DeathScreen)) {
schedule(mc,()->{
ServerPlayer p=player(mc);EntityLootableBody second=null;
- for(Entity e:FixtureApi.entities(p.level))if(e instanceof EntityLootableBody && !e.getUUID().equals(firstId) && p.getGameProfile().getName().equals(((EntityLootableBody)e).getCustomNameTag()))second=(EntityLootableBody)e;
+ for(Entity e:FixtureApi.entities(p.level()))if(e instanceof EntityLootableBody && !e.getUUID().equals(firstId) && p.getGameProfile().getName().equals(((EntityLootableBody)e).getCustomNameTag()))second=(EntityLootableBody)e;
RuntimeChecks.require(second!=null,"Second actual death corpse absent");secondId=second.getUUID();
verifyOriginals(second);equal(second.getItem(4),RuntimeChecks.stack(301),"Second selected main hand");
RuntimeChecks.require(p.getInventory().save(new ListTag()).size()==0,"Second death duplicated player inventory");
@@ -147,8 +147,8 @@ void tick(Minecraft mc) throws Exception {
} else if(stage==22 && FixtureConnections.stopped(mc)) {mc.stop();next(23);}
}
private EntityLootableBody die(ServerPlayer p,UUID skip) {
- Set before=new HashSet<>(FixtureApi.entities(p.level));p.kill();
- for(Entity e:FixtureApi.entities(p.level))if(e instanceof EntityLootableBody && !before.contains(e))return (EntityLootableBody)e;
+ Set before=new HashSet<>(FixtureApi.entities(p.level()));p.kill();
+ for(Entity e:FixtureApi.entities(p.level()))if(e instanceof EntityLootableBody && !before.contains(e))return (EntityLootableBody)e;
throw new AssertionError("Actual connected death absent");
}
private static void addTagged(Map result,ItemStack stack) {
@@ -163,18 +163,18 @@ private static void verifyOriginals(EntityLootableBody b) {
private static void verifyClientOriginals(Minecraft mc){Map n=new TreeMap<>();for(int i=0;i<48;i++)addTagged(n,mc.player.containerMenu.getSlot(i).getItem());RuntimeChecks.require(originals().equals(n),"Connected GUI differs from exact originals");}
private void save(Minecraft mc) throws Exception {
savedBody=new CompoundTag();body(mc).saveAsPassenger(savedBody);savedPlayer=new CompoundTag();savedPlayer.put("Inventory",player(mc).getInventory().save(new ListTag()));
- File folder=player(mc).getLevel().getServer().getWorldPath(net.minecraft.world.level.storage.LevelResource.ROOT).toFile();
+ File folder=player(mc).serverLevel().getServer().getWorldPath(net.minecraft.world.level.storage.LevelResource.ROOT).toFile();
try(FileOutputStream out=new FileOutputStream(new File(folder,"issue5-corpse-expected.nbt"))){NbtIo.writeCompressed(savedBody,out);}
try(FileOutputStream out=new FileOutputStream(new File(folder,"issue5-player-expected.nbt"))){NbtIo.writeCompressed(savedPlayer,out);}
mc.getSingleplayerServer().getPlayerList().saveAll();mc.getSingleplayerServer().saveAllChunks(false,true,false);
}
private void verifyDisk(Minecraft mc) {
- RuntimeChecks.awaitChunk(player(mc).getLevel(),2,0);CompoundTag actual=new CompoundTag();body(mc).saveAsPassenger(actual);
+ RuntimeChecks.awaitChunk(player(mc).serverLevel(),2,0);CompoundTag actual=new CompoundTag();body(mc).saveAsPassenger(actual);
for(String key:Arrays.asList("id","UUID","UUIDMost","UUIDLeast","Equipment","Aux","Name"))RuntimeChecks.require(Objects.equals(savedBody.get(key),actual.get(key)),"Disk corpse differs: "+key);
RuntimeChecks.require(savedPlayer.getList("Inventory",10).equals(player(mc).getInventory().save(new ListTag())),"Complete player inventory differs after disk reload");
}
private ServerPlayer player(Minecraft mc){return mc.getSingleplayerServer().getPlayerList().getPlayer(mc.player.getUUID());}
- private EntityLootableBody body(Minecraft mc){return RuntimeChecks.find(player(mc).getLevel(),secondId);}
+ private EntityLootableBody body(Minecraft mc){return RuntimeChecks.find(player(mc).serverLevel(),secondId);}
private static void equal(ItemStack a,ItemStack b,String reason){RuntimeChecks.require(RuntimeChecks.sameStack(a == null ? ItemStack.EMPTY : a,b == null ? ItemStack.EMPTY : b),reason);}
private static boolean ready(Minecraft mc){return mc.level!=null && mc.player!=null && mc.getSingleplayerServer()!=null;}
private static boolean gui(Minecraft mc){return mc.screen instanceof CorpseGUIContainer;}
@@ -188,7 +188,7 @@ private boolean open(Minecraft mc,UUID id) throws Exception {
// into range, then wait for normal tracking and interaction.
schedule(mc,()->{
ServerPlayer p=player(mc);
- EntityLootableBody corpse=RuntimeChecks.find(p.getLevel(),id);
+ EntityLootableBody corpse=RuntimeChecks.find(p.serverLevel(),id);
if(p.distanceToSqr(corpse)>9) {
double x=corpse.getX(),z=corpse.getZ()-2;
int y=net.minecraft.util.Mth.floor(corpse.getY());
@@ -196,7 +196,7 @@ private boolean open(Minecraft mc,UUID id) throws Exception {
// platform. Give this disposable fixture a stable place
// beside the actual corpse before sending normal clicks.
for(int dx=-1;dx<=1;dx++)for(int dz=-1;dz<=1;dz++)
- p.level.setBlockAndUpdate(BlockPos.containing(x+dx,y-1,z+dz),Blocks.STONE.defaultBlockState());
+ p.level().setBlockAndUpdate(BlockPos.containing(x+dx,y-1,z+dz),Blocks.STONE.defaultBlockState());
p.setDeltaMovement(0,0,0);
p.connection.teleport(x,y,z,0,0);
System.out.println("ISSUE5_VIEWER_APPROACH corpse="+id);
diff --git a/src/integrationTest/java/cyano/lootable/buildtest/RespawnPacketChecks.java b/src/integrationTest/java/cyano/lootable/buildtest/RespawnPacketChecks.java
index 104d027..d6e0caf 100644
--- a/src/integrationTest/java/cyano/lootable/buildtest/RespawnPacketChecks.java
+++ b/src/integrationTest/java/cyano/lootable/buildtest/RespawnPacketChecks.java
@@ -79,34 +79,34 @@ static void verifySlowClientStartup(MinecraftServer server) throws Exception {
RuntimeChecks.require(phaseTicks<500,"Corpse missing after accepted respawn case="+index+" phase="+phase);
if(phase==0 && a!=null&&b!=null&&ack("A",0)&&ack("B",0)) {
int kind=index%8;net.minecraft.resources.ResourceKey dim=kind==7?net.minecraft.world.level.Level.NETHER:net.minecraft.world.level.Level.OVERWORLD;ServerLevel w=server.getLevel(dim);
- if(a.level.dimension()!=dim)transfer(a,dim);
- if(b.level.dimension()!=dim)transfer(b,dim);
+ if(a.level().dimension()!=dim)transfer(a,dim);
+ if(b.level().dimension()!=dim)transfer(b,dim);
double x=kind==6?-.4:96.5,z=kind==6?-.4:96.5;
FixtureArena.prepare(BlockPos.containing(x,79,z),w::setBlockAndUpdate);
FixtureArena.prepare(new BlockPos(160,79,96),w::setBlockAndUpdate);
w.getGameRules().getRule(net.minecraft.world.level.GameRules.RULE_DOMOBSPAWNING).set(false,null);w.getGameRules().getRule(net.minecraft.world.level.GameRules.RULE_KEEPINVENTORY).set(false,null);
a.setGameMode(GameType.SURVIVAL);b.setGameMode(GameType.SURVIVAL);a.getInventory().clearContent();b.getInventory().clearContent();
- FixtureApi.spawn(a,new BlockPos(kind==5?160:(int)Math.floor(x),80,(int)Math.floor(z)-3),true,false,a.level.dimension());
- FixtureApi.spawn(b,new BlockPos((int)Math.floor(x),80,(int)Math.floor(z)-3),true,false,b.level.dimension());
- if(kind==7)FixtureApi.spawn(a,new BlockPos(96,80,93),true,false,a.level.dimension());
+ FixtureApi.spawn(a,new BlockPos(kind==5?160:(int)Math.floor(x),80,(int)Math.floor(z)-3),true,false,a.level().dimension());
+ FixtureApi.spawn(b,new BlockPos((int)Math.floor(x),80,(int)Math.floor(z)-3),true,false,b.level().dimension());
+ if(kind==7)FixtureApi.spawn(a,new BlockPos(96,80,93),true,false,a.level().dimension());
a.getInventory().items.set(9,RuntimeChecks.stack(520+index));a.containerMenu.broadcastChanges();
if(simultaneous){b.getInventory().items.set(9,RuntimeChecks.stack(620+index));b.containerMenu.broadcastChanges();}
a.connection.teleport(x,80,z,0,0);b.connection.teleport(kind==4?160.5:x+2,80,z-2.5,0,25);
state(1);
} else if(phase==1&&ack("A",1)&&ack("B",1)&&phaseTicks>10) {
- dead=a;Set before=new HashSet<>(FixtureApi.entities(a.level));a.kill();body=null;
- for(Entity ent:FixtureApi.entities(a.level))if(ent instanceof EntityLootableBody&&!before.contains(ent))body=(EntityLootableBody)ent;
+ dead=a;Set before=new HashSet<>(FixtureApi.entities(a.level()));a.kill();body=null;
+ for(Entity ent:FixtureApi.entities(a.level()))if(ent instanceof EntityLootableBody&&!before.contains(ent))body=(EntityLootableBody)ent;
RuntimeChecks.require(body!=null,"No server death corpse");state(2);
if(simultaneous) {
- observerDead=b;before=new HashSet<>(FixtureApi.entities(b.level));b.kill();observerBody=null;
- for(Entity ent:FixtureApi.entities(b.level))if(ent instanceof EntityLootableBody&&!before.contains(ent))observerBody=(EntityLootableBody)ent;
+ observerDead=b;before=new HashSet<>(FixtureApi.entities(b.level()));b.kill();observerBody=null;
+ for(Entity ent:FixtureApi.entities(b.level()))if(ent instanceof EntityLootableBody&&!before.contains(ent))observerBody=(EntityLootableBody)ent;
RuntimeChecks.require(observerBody!=null,"No observer death corpse");
}
} else if(phase==2&&a!=null&&a!=dead&&a.isAlive()&&(!simultaneous||(b!=null&&b!=observerDead&&b.isAlive()))) {
RuntimeChecks.require(body.isAlive()&&VisibilityChecks.has(body,520+index),"Server corpse/inventory lost during respawn");
- if(a.level.dimension()!=body.level.dimension())transfer(a,body.level.dimension());
+ if(a.level().dimension()!=body.level().dimension())transfer(a,body.level().dimension());
a.connection.teleport(body.getX(),body.getY(),body.getZ()-2.5,0,25);
- if(b.level.dimension()!=body.level.dimension())transfer(b,body.level.dimension());
+ if(b.level().dimension()!=body.level().dimension())transfer(b,body.level().dimension());
b.connection.teleport(body.getX()+1.5,body.getY(),body.getZ()-2.5,0,25);
// With no players left, Forge may unload/reload the Nether between
// death and return. Saved UUIDs survive; runtime entity IDs do not.
@@ -146,12 +146,12 @@ private void transfer(ServerPlayer player,net.minecraft.resources.ResourceKey expected=new ArrayList<>();ServerLevel w=player.getLevel();
+ List expected=new ArrayList<>();ServerLevel w=player.serverLevel();
for(Entity e:FixtureApi.entities(w))if(e instanceof EntityLootableBody&&e.isAlive()&&FixtureApi.tracked(e).contains(player))expected.add(e.getId()+" "+e.getUUID());
Collections.sort(expected);Files.write(file("expected-"+index+"-"+role+".txt").toPath(),expected,StandardCharsets.UTF_8);
}
private EntityLootableBody live(EntityLootableBody previous) {
- ServerLevel w=server.getLevel(previous.level.dimension());
+ ServerLevel w=server.getLevel(previous.level().dimension());
w.getChunk((int)Math.floor(previous.getX()/16),(int)Math.floor(previous.getZ()/16));
return RuntimeChecks.find(w,previous.getUUID());
}
diff --git a/src/integrationTest/java/cyano/lootable/buildtest/RuntimeChecks.java b/src/integrationTest/java/cyano/lootable/buildtest/RuntimeChecks.java
index 9cd8e26..7e88b2d 100644
--- a/src/integrationTest/java/cyano/lootable/buildtest/RuntimeChecks.java
+++ b/src/integrationTest/java/cyano/lootable/buildtest/RuntimeChecks.java
@@ -98,9 +98,9 @@ static boolean sameStack(ItemStack first,ItemStack second) {
public static void run(MinecraftServer server, String phase) throws Exception {
ServerLevel world = server.getLevel(FixtureApi.dimensionKey(0));
File folder = world.getServer().getWorldPath(net.minecraft.world.level.storage.LevelResource.ROOT).toFile();
- require(LootableBodies.MODID.equals("lootablebodies") && LootableBodies.VERSION.equals("2.4.10.119041"), "identity/version");
+ require(LootableBodies.MODID.equals("lootablebodies") && LootableBodies.VERSION.equals("2.4.11.120011"), "identity/version");
require(net.minecraftforge.fml.ModList.get().getModContainerById("lootablebodies").get().getModInfo().getVersion().toString().equals(phase.equals("seed") ? "2.4.0" : phase.equals("issues") ?
- System.getProperty("lootablebodies.expectedIssueVersion","2.4.10.119041") : "2.4.10.119041"), "loaded release version");
+ System.getProperty("lootablebodies.expectedIssueVersion","2.4.11.120011") : "2.4.11.120011"), "loaded release version");
if (!defaultsChecked) require(LootableBodies.addBonesToCorpse && LootableBodies.displayNameTag && !LootableBodies.hurtByEnvironment &&
!LootableBodies.hurtByAttacks && !LootableBodies.hurtByMisc && LootableBodies.corpseHP == 20 &&
LootableBodies.ticksPerItemDecay == 6000 && LootableBodies.corpseDecayTime == 72000 &&
@@ -266,7 +266,7 @@ static void death(MinecraftServer server, ServerLevel world, File folder) throws
// Assign the fixture ID before the native entity-section manager indexes
// the real death-created body, never by mutating an admitted UUID.
pendingDeathId=DEATH;
- try {player.die(player.damageSources().outOfWorld());}finally{pendingDeathId=null;}
+ try {player.die(player.damageSources().fellOutOfWorld());}finally{pendingDeathId=null;}
EntityLootableBody corpse = null;
for (Entity entity : new ArrayList(FixtureApi.entities(world)))
if (entity instanceof EntityLootableBody && "BuildDeath".equals(((EntityLootableBody)entity).getCustomNameTag())) corpse=(EntityLootableBody)entity;
diff --git a/src/integrationTest/java/cyano/lootable/buildtest/VisibilityChecks.java b/src/integrationTest/java/cyano/lootable/buildtest/VisibilityChecks.java
index e48a59c..246ea5d 100644
--- a/src/integrationTest/java/cyano/lootable/buildtest/VisibilityChecks.java
+++ b/src/integrationTest/java/cyano/lootable/buildtest/VisibilityChecks.java
@@ -80,7 +80,7 @@ public VisibilityChecks(MinecraftServer server) throws Exception {
w.setDefaultSpawnPos(new BlockPos(96,PLAYER_Y,93),0F);server.getCommands().performPrefixedCommand(server.createCommandSourceStack(),"gamerule spawnRadius 0");
w.setDayTime(6000);
for(int x=88;x<=170;x++)for(int z=89;z<=103;z++)w.setBlockAndUpdate(new BlockPos(x,GROUND_Y,z),Blocks.OBSIDIAN.defaultBlockState());
- for(ServerPlayer p:new ServerPlayer[]{a,b}) {p.setGameMode(GameType.SURVIVAL);p.getInventory().clearContent();FixtureApi.spawn(p,new BlockPos(96,PLAYER_Y,93),true,false,p.level.dimension());}
+ for(ServerPlayer p:new ServerPlayer[]{a,b}) {p.setGameMode(GameType.SURVIVAL);p.getInventory().clearContent();FixtureApi.spawn(p,new BlockPos(96,PLAYER_Y,93),true,false,p.level().dimension());}
a.connection.teleport(96.5,PLAYER_Y,96.5,0,65);b.connection.teleport(98.5,PLAYER_Y,93.5,0,25);
a.getInventory().items.set(9,RuntimeChecks.stack(410));a.containerMenu.broadcastChanges();phase(1);
} else if(phase==1 && both(1)) {
diff --git a/src/integrationTest/java/cyano/lootable/buildtest/VisibilityClientChecks.java b/src/integrationTest/java/cyano/lootable/buildtest/VisibilityClientChecks.java
index c0733fa..601ecb9 100644
--- a/src/integrationTest/java/cyano/lootable/buildtest/VisibilityClientChecks.java
+++ b/src/integrationTest/java/cyano/lootable/buildtest/VisibilityClientChecks.java
@@ -81,7 +81,7 @@ final class VisibilityClientChecks {
}
@SubscribeEvent public void removed(net.minecraftforge.event.entity.EntityLeaveLevelEvent e) {
Entity entity=e.getEntity();
- if(entity instanceof EntityLootableBody && entity.level.isClientSide) {
+ if(entity instanceof EntityLootableBody && entity.level().isClientSide) {
System.out.println("VISIBILITY_CLIENT_REMOVAL "+role+" phase="+phase+" uuid="+entity.getUUID()+" reason="+entity.getRemovalReason()+" rendered="+rendered.contains(entity.getUUID()));
StackWalker.getInstance().forEach(frame->System.out.println("VISIBILITY_REMOVAL_FRAME "+frame));
}
diff --git a/src/integrationTest/java/cyano/lootable/buildtest/VisibilitySkinClientChecks.java b/src/integrationTest/java/cyano/lootable/buildtest/VisibilitySkinClientChecks.java
index 978b55b..42a2b11 100644
--- a/src/integrationTest/java/cyano/lootable/buildtest/VisibilitySkinClientChecks.java
+++ b/src/integrationTest/java/cyano/lootable/buildtest/VisibilitySkinClientChecks.java
@@ -43,13 +43,13 @@ void tick(Minecraft mc) throws Exception {
private static final class Panel extends Screen {
private final EntityLootableBody[] bodies=new EntityLootableBody[3];
Panel(Minecraft mc){super(net.minecraft.network.chat.Component.literal("Skin visibility fixture"));for(int i=0;i<3;i++){bodies[i]=new EntityLootableBody(mc.level);bodies[i].setGameProfile(new GameProfile(new UUID(0,512+i),"VisibilityModel"));}}
- @Override public void render(com.mojang.blaze3d.vertex.PoseStack matrices,int mouseX,int mouseY,float partial) {
- fill(matrices,0,0,width,height,0xff304050);
+ @Override public void render(net.minecraft.client.gui.GuiGraphics graphics,int mouseX,int mouseY,float partial) {
+ graphics.fill(0,0,width,height,0xff304050);
String[] names={"Failed skin: wide arms","Failed skin: slim arms","Local texture"};boolean local=LootableBodies.useLocalSkin;
try {for(int i=0;i<3;i++) {
LootableBodies.useLocalSkin=i==2;int x=(2*i+1)*width/6;
- drawCenteredString(matrices,font,names[i],x,35,0xffffff);
- InventoryScreen.renderEntityInInventoryFollowsMouse(matrices,x,height*2/3,65,20,-20,bodies[i]);
+ graphics.drawCenteredString(font,names[i],x,35,0xffffff);
+ InventoryScreen.renderEntityInInventoryFollowsMouse(graphics,x,height*2/3,65,20,-20,bodies[i]);
}} finally {LootableBodies.useLocalSkin=local;}
}
@Override public boolean isPauseScreen(){return false;}
diff --git a/src/integrationTest/java/cyano/lootable/buildtest/WorldUpgradeChecks.java b/src/integrationTest/java/cyano/lootable/buildtest/WorldUpgradeChecks.java
index 2687973..164ad5a 100644
--- a/src/integrationTest/java/cyano/lootable/buildtest/WorldUpgradeChecks.java
+++ b/src/integrationTest/java/cyano/lootable/buildtest/WorldUpgradeChecks.java
@@ -55,7 +55,7 @@ static void run(MinecraftServer server,String phase) throws Exception {
UUID uuid=HistoricalUuid.read(source,"UUID");EntityLootableBody body=find(world,uuid);String id=uuid.toString();
if(ledger.getString("Removed").equals(id)) {require(body==null,"Journal resurrected a deliberately removed corpse");continue;}
require(body!=null,"Missing historical corpse "+uuid);
- require(body.getUUID().equals(uuid) && FixtureApi.dimensionId(body.level)==record.getInt("Dimension"),"UUID/dimension changed");
+ require(body.getUUID().equals(uuid) && FixtureApi.dimensionId(body.level())==record.getInt("Dimension"),"UUID/dimension changed");
require(body.getCustomNameTag().equals(source.contains("Name",8)?source.getString("Name"):source.getString("CustomName")),"Owner attribution changed");
require(body.getX()==source.getList("Pos",6).getDouble(0) && body.getY()==source.getList("Pos",6).getDouble(1) && body.getZ()==source.getList("Pos",6).getDouble(2),"Position changed in dimension "+FixtureApi.dimensionId(world)+": "+body.position());
CompoundTag actual=save(body);require(actual.getInt("LootableBodiesDataVersion")==cyano.lootable.upgrade.UpgradePreservation.DATA_VERSION,"Missing corpse migration marker");
@@ -238,7 +238,7 @@ private static Map indexedPlayer(CompoundTag player,String key)
private static Map indexedCurrent(ListTag list){Map map=new TreeMap<>();for(int i=0;i>4,net.minecraft.util.Mth.floor(pos.getDouble(2))>>4);Entity found=null;for(Entity e:FixtureApi.entities(world))if(e.getUUID().equals(HistoricalUuid.read(source,"UUID")))found=e;require(found!=null,"Vanilla entity disappeared");CompoundTag actual=new CompoundTag();found.saveAsPassenger(actual);require(actual.getString("id").equals(record.getString("ExpectedType")),"Vanilla entity-name conversion failed");require(FixtureApi.dimensionId(found.level)==record.getInt("Dimension"),"Vanilla entity dimension changed");if(record.getString("ExpectedType").equals("minecraft:villager"))verifyVillager(source,actual);if(found instanceof Container)require(indexed(source.getList("Items",10)).equals(contents((Container)found,true)),"Vanilla minecart inventory changed");if(record.getString("ExpectedType").equals("minecraft:armor_stand"))require(indexedEquipment(source.getList("ArmorItems",10)).equals(indexedNativeEquipment(actual.getList("ArmorItems",10))),"Vanilla armour stand equipment changed");}
+ for(int i=0;i>4,net.minecraft.util.Mth.floor(pos.getDouble(2))>>4);Entity found=null;for(Entity e:FixtureApi.entities(world))if(e.getUUID().equals(HistoricalUuid.read(source,"UUID")))found=e;require(found!=null,"Vanilla entity disappeared");CompoundTag actual=new CompoundTag();found.saveAsPassenger(actual);require(actual.getString("id").equals(record.getString("ExpectedType")),"Vanilla entity-name conversion failed");require(FixtureApi.dimensionId(found.level())==record.getInt("Dimension"),"Vanilla entity dimension changed");if(record.getString("ExpectedType").equals("minecraft:villager"))verifyVillager(source,actual);if(found instanceof Container)require(indexed(source.getList("Items",10)).equals(contents((Container)found,true)),"Vanilla minecart inventory changed");if(record.getString("ExpectedType").equals("minecraft:armor_stand"))require(indexedEquipment(source.getList("ArmorItems",10)).equals(indexedNativeEquipment(actual.getList("ArmorItems",10))),"Vanilla armour stand equipment changed");}
}
private static void verifyVillager(CompoundTag source,CompoundTag actual) {
CompoundTag expected=cyano.lootable.upgrade.NativeNbt.update(NativeVanillaDataFixer.get(),net.minecraft.util.datafix.fixes.References.ENTITY,source.copy(),sourceVersion,cyano.lootable.upgrade.UpgradePreservation.MINECRAFT_VERSION);
diff --git a/src/integrationTest/resources/META-INF/mods.toml b/src/integrationTest/resources/META-INF/mods.toml
index e85540b..1562560 100644
--- a/src/integrationTest/resources/META-INF/mods.toml
+++ b/src/integrationTest/resources/META-INF/mods.toml
@@ -1,5 +1,5 @@
modLoader="javafml"
-loaderVersion="[45,)"
+loaderVersion="[47,)"
license="LGPL-2.1-only"
[[mods]]
modId="lootablebodiesbuildtest"
@@ -8,6 +8,6 @@ displayName="Disposable build checks"
[[dependencies.lootablebodiesbuildtest]]
modId="lootablebodies"
mandatory=true
-versionRange="[2.4.10.119041]"
+versionRange="[2.4.11.120011]"
ordering="AFTER"
side="BOTH"
diff --git a/src/integrationTest/resources/pack.mcmeta b/src/integrationTest/resources/pack.mcmeta
index 61c5783..723d4c5 100644
--- a/src/integrationTest/resources/pack.mcmeta
+++ b/src/integrationTest/resources/pack.mcmeta
@@ -1 +1,6 @@
-{"pack":{"pack_format":13,"forge:server_data_pack_format":12,"description":"Disposable build checks"}}
+{
+ "pack": {
+ "pack_format": 15,
+ "description": "Disposable build checks"
+ }
+}
diff --git a/src/main/java/cyano/lootable/LootableBodies.java b/src/main/java/cyano/lootable/LootableBodies.java
index 080827c..00eadae 100644
--- a/src/main/java/cyano/lootable/LootableBodies.java
+++ b/src/main/java/cyano/lootable/LootableBodies.java
@@ -27,7 +27,7 @@
public final class LootableBodies {
public static final String MODID = "lootablebodies";
public static final String NAME = "DrCyano's Lootable Bodies";
- public static final String VERSION = "2.4.10.119041";
+ public static final String VERSION = "2.4.11.120011";
public static final ResourceLocation CORPSE_ID = new ResourceLocation("lootablebodies:corpse");
public static EntityType CORPSE;
public static Item CORPSE_EGG;
@@ -64,8 +64,8 @@ public LootableBodies() {
bus.addListener(this::registerItems);
bus.addListener(cyano.lootable.upgrade.LegacyRegistryMappings::retiredMemories);
bus.addListener(this::registerContainers);
- bus.addListener((net.minecraftforge.event.CreativeModeTabEvent.BuildContents event) -> {
- if(event.getTab()==CreativeModeTabs.SPAWN_EGGS)event.accept(CORPSE_EGG);
+ bus.addListener((net.minecraftforge.event.BuildCreativeModeTabContentsEvent event) -> {
+ if(CreativeModeTabs.SPAWN_EGGS.equals(event.getTabKey()))event.accept(CORPSE_EGG);
});
MinecraftForge.EVENT_BUS.register(new PlayerDeathEventHandler());
MinecraftForge.EVENT_BUS.register(this);
@@ -93,7 +93,7 @@ private void registerItems(RegisterEvent event) {
private void registerContainers(RegisterEvent event) {
event.register(Registries.MENU,CORPSE_ID,()-> {
CORPSE_CONTAINER=net.minecraftforge.common.extensions.IForgeMenuType.create((window,inventory,buffer)-> {
- net.minecraft.world.entity.Entity body=inventory.player.level.getEntity(buffer.readVarInt());
+ net.minecraft.world.entity.Entity body=inventory.player.level().getEntity(buffer.readVarInt());
if(!(body instanceof EntityLootableBody))throw new IllegalStateException("Corpse container has no matching entity");
return new cyano.lootable.graphics.CorpseContainer(window,inventory,(EntityLootableBody)body);
});
diff --git a/src/main/java/cyano/lootable/entities/CorpseDamageRules.java b/src/main/java/cyano/lootable/entities/CorpseDamageRules.java
index a106138..f519223 100644
--- a/src/main/java/cyano/lootable/entities/CorpseDamageRules.java
+++ b/src/main/java/cyano/lootable/entities/CorpseDamageRules.java
@@ -9,7 +9,7 @@ enum Kind { VOID, ATTACK, ENVIRONMENT, MISC }
private CorpseDamageRules(){ }
static Kind kind(DamageSource source) {
if(source==null)return Kind.MISC;
- if(source.is(DamageTypes.OUT_OF_WORLD) || "outOfWorld".equals(source.getMsgId()))return Kind.VOID;
+ if(source.is(DamageTypes.FELL_OUT_OF_WORLD) || "outOfWorld".equals(source.getMsgId()))return Kind.VOID;
// These two native sources gained a causing entity in1.19.4. They
// retain the environmental setting used by their former static sources.
if(source.is(DamageTypes.FALLING_ANVIL) || source.is(DamageTypes.FALLING_BLOCK))return Kind.ENVIRONMENT;
diff --git a/src/main/java/cyano/lootable/entities/EntityLootableBody.java b/src/main/java/cyano/lootable/entities/EntityLootableBody.java
index 9ab4eb4..316a72b 100644
--- a/src/main/java/cyano/lootable/entities/EntityLootableBody.java
+++ b/src/main/java/cyano/lootable/entities/EntityLootableBody.java
@@ -6,7 +6,6 @@
import com.mojang.authlib.GameProfile;
import cyano.lootable.LootableBodies;
-import net.minecraft.world.level.material.Material;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.entity.Mob;
@@ -261,7 +260,7 @@ public boolean handleWaterMovement() {
}
private void generateVoidPlatform() {
- Level level = this.level;
+ Level level = level();
BlockPos testPos = new BlockPos((int)this.getX(), 0, (int)this.getZ());
BlockState platformBlockstate = getPlatformBlock(level.dimension()==Level.NETHER?-1:level.dimension()==Level.END?1:0);
@@ -362,8 +361,7 @@ public ItemStack mergeItem(int slot, ItemStack stack) {
if(amount > 0) this.setItem(slot,stack.split(amount));
return stack.getCount() > 0 ? stack : ItemStack.EMPTY;
} else if(current.getCount() < inventorySizeLimit
- && ItemStack.isSame(stack,current)
- && ItemStack.tagMatches(stack,current)){
+ && ItemStack.isSameItemSameTags(stack,current)){
int delta = Math.min(stack.getCount(),inventorySizeLimit - current.getCount());
current.grow(delta);
stack.shrink(delta);
@@ -481,11 +479,11 @@ public boolean removeWhenFarAway(double distanceToClosestPlayer) {
}
public void jumpOutOfWall(){
- if(level.isClientSide) return;
+ if(level().isClientSide) return;
AABB bounds = getBoundingBox();
// Collision queries omit unloaded chunks. They cannot establish an exit
// until the complete local terrain (including tall neighbour shapes) exists.
- if(!escapeTerrainLoaded(bounds) || level.noCollision(this,bounds)) return;
+ if(!escapeTerrainLoaded(bounds) || level().noCollision(this,bounds)) return;
BlockPos currentCoord = blockPosition();
double x = currentCoord.getX()+0.5 - getX();
double z = currentCoord.getZ()+0.5 - getZ();
@@ -504,15 +502,15 @@ public void jumpOutOfWall(){
}
private boolean escapeTerrainLoaded(AABB bounds) {
- return level.hasChunksAt(BlockPos.containing(bounds.minX-1,0,bounds.minZ-1),
+ return level().hasChunksAt(BlockPos.containing(bounds.minX-1,0,bounds.minZ-1),
BlockPos.containing(bounds.maxX+1,0,bounds.maxZ+1));
}
private boolean tryWallExit(double x,double y,double z) {
AABB bounds = getBoundingBox().move(x-getX(),y-getY(),z-getZ());
- if(bounds.minY < 0 || bounds.maxY > level.getHeight() ||
- !level.getWorldBorder().isWithinBounds(bounds) || !escapeTerrainLoaded(bounds) ||
- !level.noCollision(this,bounds)) return false;
+ if(bounds.minY < 0 || bounds.maxY > level().getHeight() ||
+ !level().getWorldBorder().isWithinBounds(bounds) || !escapeTerrainLoaded(bounds) ||
+ !level().noCollision(this,bounds)) return false;
setPos(x,y,z);
return true;
}
@@ -521,7 +519,7 @@ private boolean tryWallExit(double x,double y,double z) {
@Override
public void kill() {
if(terminate < 0) terminate = DEATH_COUNTDOWN;
- this.hurt(damageSources().outOfWorld(), this.getMaxHealth());
+ this.hurt(damageSources().fellOutOfWorld(), this.getMaxHealth());
this.setChanged();
}
@@ -675,6 +673,7 @@ public int getMaxStackSize() {
*/
@Override
public void setChanged() {
+ Level level = level();
if(level != null && !level.isClientSide && isAddedToWorld() && level.hasChunkAt(blockPosition())) {
level.getChunkAt(blockPosition()).setUnsaved(true);
}
@@ -1259,7 +1258,7 @@ private ItemStack decodeInventoryItem(CompoundTag raw) {
// Resolve them before comparing: unsupported payloads must remain raw,
// rather than disappearing later during an inventory transaction.
if(raw.contains("ForgeCaps"))
- stack.getCapability(net.minecraftforge.items.CapabilityItemHandler.ITEM_HANDLER_CAPABILITY);
+ stack.getCapability(net.minecraftforge.common.capabilities.ForgeCapabilities.ITEM_HANDLER);
CompoundTag encoded = stack.save(new CompoundTag());
Tag expectedTag=raw.get("tag");
// Native16 writes an explicit zero for missing tool durability.
diff --git a/src/main/java/cyano/lootable/graphics/CorpseConfigGui.java b/src/main/java/cyano/lootable/graphics/CorpseConfigGui.java
index 22f3d37..8f2ac39 100644
--- a/src/main/java/cyano/lootable/graphics/CorpseConfigGui.java
+++ b/src/main/java/cyano/lootable/graphics/CorpseConfigGui.java
@@ -65,18 +65,18 @@ private boolean validPending(){
}return true;
}
@Override public void tick(){for(EditBox field:fields.values())field.tick();}
- @Override public void render(com.mojang.blaze3d.vertex.PoseStack matrices,int mouseX,int mouseY,float partialTicks){
- renderBackground(matrices);drawCenteredString(matrices,font,LootableBodies.NAME,width/2,12,0xffffff);
- drawCenteredString(matrices,font,I18n.get(group==null?"lootablebodies.config.local_file":"lootablebodies.config."+group),width/2,28,0xcccccc);
+ @Override public void render(GuiGraphics graphics,int mouseX,int mouseY,float partialTicks){
+ renderBackground(graphics);graphics.drawCenteredString(font,LootableBodies.NAME,width/2,12,0xffffff);
+ graphics.drawCenteredString(font,I18n.get(group==null?"lootablebodies.config.local_file":"lootablebodies.config."+group),width/2,28,0xcccccc);
for(Map.Entry row:rows.entrySet()){
- String text=I18n.get(property(row.getKey()).getLanguageKey());drawString(matrices,font,font.plainSubstrByWidth(text,205),width/2-155,row.getValue()+6,0xffffff);
+ String text=I18n.get(property(row.getKey()).getLanguageKey());graphics.drawString(font,font.plainSubstrByWidth(text,205),width/2-155,row.getValue()+6,0xffffff);
}
- super.render(matrices,mouseX,mouseY,partialTicks);for(EditBox field:fields.values())field.render(matrices,mouseX,mouseY,partialTicks);
+ super.render(graphics,mouseX,mouseY,partialTicks);for(EditBox field:fields.values())field.render(graphics,mouseX,mouseY,partialTicks);
for(Map.Entry row:rows.entrySet())if(mouseX>=width/2-155 && mouseX=row.getValue() && mouseY=width/2-155 && mouseX=95 && mouseY<115)
- renderComponentTooltip(matrices,java.util.Collections.singletonList(net.minecraft.network.chat.Component.translatable("lootablebodies.config.rules.tooltip")),mouseX,mouseY);
- if(invalid)drawCenteredString(matrices,font,I18n.get("lootablebodies.config.invalid"),width/2,height-72,0xff5555);
+ graphics.renderComponentTooltip(font,java.util.Collections.singletonList(net.minecraft.network.chat.Component.translatable("lootablebodies.config.rules.tooltip")),mouseX,mouseY);
+ if(invalid)graphics.drawCenteredString(font,I18n.get("lootablebodies.config.invalid"),width/2,height-72,0xff5555);
}
@Override public void onClose(){capture();minecraft.setScreen(parent);}
public static boolean isValidDecayTime(String value) {
diff --git a/src/main/java/cyano/lootable/graphics/CorpseContainer.java b/src/main/java/cyano/lootable/graphics/CorpseContainer.java
index ac6e14c..f51229e 100644
--- a/src/main/java/cyano/lootable/graphics/CorpseContainer.java
+++ b/src/main/java/cyano/lootable/graphics/CorpseContainer.java
@@ -212,14 +212,13 @@ private void storeInPlayerInventory(Inventory inventory, ItemStack remainder) {
private boolean canStore(Inventory inventory, int slot, ItemStack incoming) {
ItemStack current = inventory.getItem(slot);
- return !emptyStack(current) && current.isStackable() && current.getItem() == incoming.getItem()
- && ItemStack.tagMatches(current,incoming)
+ return !emptyStack(current) && current.isStackable() && ItemStack.isSameItemSameTags(current,incoming)
&& current.getCount() < Math.min(current.getMaxStackSize(),inventory.getMaxStackSize());
}
private boolean tryRelease(Player player, ItemStack stack, boolean includeName) {
if(emptyStack(stack) || stack.getCount() <= 0) return false;
- if(player.level.isClientSide) {
+ if(player.level().isClientSide) {
player.drop(stack.copy(),includeName);
return true;
}
@@ -240,7 +239,7 @@ private boolean tryRelease(Player player, ItemStack stack, boolean includeName)
capturing.add(event.getEntity());
return true;
}
- return player.level.addFreshEntity(event.getEntity());
+ return player.level().addFreshEntity(event.getEntity());
}
@Override
@@ -254,7 +253,7 @@ protected boolean moveItemStackTo(ItemStack stack, int start, int end, boolean r
ItemStack current = slot.getItem();
int limit = Math.min(slot.getMaxStackSize(stack),stack.getMaxStackSize());
if(pass == 0 && stack.isStackable() && !emptyStack(current)
- && ItemStack.isSame(stack,current) && ItemStack.tagMatches(stack,current)) {
+ && ItemStack.isSameItemSameTags(stack,current)) {
int amount = Math.min(stack.getCount(),Math.max(0,limit - current.getCount()));
if(amount > 0) {
current.grow(amount);
diff --git a/src/main/java/cyano/lootable/graphics/CorpseGUIContainer.java b/src/main/java/cyano/lootable/graphics/CorpseGUIContainer.java
index 9bbeb94..9837167 100644
--- a/src/main/java/cyano/lootable/graphics/CorpseGUIContainer.java
+++ b/src/main/java/cyano/lootable/graphics/CorpseGUIContainer.java
@@ -6,8 +6,7 @@
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
-import com.mojang.blaze3d.systems.RenderSystem;
-import net.minecraft.client.renderer.GameRenderer;
+import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.network.chat.Component;
/**
@@ -36,17 +35,14 @@ public CorpseGUIContainer(CorpseContainer container, Inventory playerItems, Comp
* @param mouseY Mouse y coordinate
*/
@Override
- protected void renderBg(com.mojang.blaze3d.vertex.PoseStack matrices, float partialTicks, int mouseX, int mouseY) {
- RenderSystem.setShader(GameRenderer::getPositionTexShader);
- RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
+ protected void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
int x = (width - imageWidth) / 2;
int y = (height - imageHeight) / 2;
- RenderSystem.setShaderTexture(0,image);
- this.blit(matrices, x, y, 0, 0, this.imageWidth, this.imageHeight); // x, y, textureOffsetX, textureOffsetY, width, height)
+ graphics.blit(image, x, y, 0, 0, imageWidth, imageHeight);
}
@Override
- protected void renderLabels(com.mojang.blaze3d.vertex.PoseStack matrices,int mouseX,int mouseY) {
+ protected void renderLabels(GuiGraphics graphics,int mouseX,int mouseY) {
// This seven-row layout has no label strip. Keep the original screen's
// foreground; the new vanilla labels would overlap corpse item slots.
}
diff --git a/src/main/java/cyano/lootable/upgrade/LegacyRegistryMappings.java b/src/main/java/cyano/lootable/upgrade/LegacyRegistryMappings.java
index e6cd0fe..dc34566 100644
--- a/src/main/java/cyano/lootable/upgrade/LegacyRegistryMappings.java
+++ b/src/main/java/cyano/lootable/upgrade/LegacyRegistryMappings.java
@@ -1,6 +1,7 @@
package cyano.lootable.upgrade;
import cyano.lootable.LootableBodies;
+import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvent;
import net.minecraftforge.registries.RegisterEvent;
@@ -30,6 +31,10 @@ public static ResourceLocation itemReplacement(ResourceLocation old) {
case "dandelion_yellow": return new ResourceLocation("minecraft:yellow_dye");
case "grass_path": return new ResourceLocation("minecraft:dirt_path");
case "zombie_pigman_spawn_egg":return new ResourceLocation("minecraft:zombified_piglin_spawn_egg");
+ case "pottery_shard_archer": return new ResourceLocation("minecraft:archer_pottery_sherd");
+ case "pottery_shard_arms_up": return new ResourceLocation("minecraft:arms_up_pottery_sherd");
+ case "pottery_shard_prize": return new ResourceLocation("minecraft:prize_pottery_sherd");
+ case "pottery_shard_skull": return new ResourceLocation("minecraft:skull_pottery_sherd");
default: return null;
}
}
@@ -44,6 +49,8 @@ public static ResourceLocation blockReplacement(ResourceLocation old) {
}
private static final ResourceLocation OLD_XP_SOUND=new ResourceLocation("minecraft:entity.experience_orb.touch");
private static final ResourceLocation XP_SOUND=new ResourceLocation("minecraft:entity.experience_orb.pickup");
+ private static final ResourceLocation OLD_JUNGLE_AND_FOREST_MUSIC =
+ new ResourceLocation("minecraft:music.overworld.jungle_and_forest");
private LegacyRegistryMappings() { }
static T requireMappingTarget(java.util.function.Supplier lookup) {
try {T target=lookup.get();if(target!=null)return target;}
@@ -67,6 +74,10 @@ public static ResourceLocation soundReplacement(ResourceLocation old) {
if(!old.getNamespace().equals("minecraft"))return null;
if(old.getPath().equals("item.sweet_berries.pick_from_bush"))return new ResourceLocation("minecraft:block.sweet_berry_bush.pick_berries");
if(old.getPath().equals("music.nether"))return new ResourceLocation("minecraft:music.nether.nether_wastes");
+ if (old.getPath().equals("item.brush.brushing"))
+ return new ResourceLocation("minecraft:item.brush.brushing.generic");
+ if (old.getPath().equals("item.brush.brush_sand_completed"))
+ return new ResourceLocation("minecraft:item.brush.brushing.sand.complete");
for(String suffix:new String[]{"ambient","angry","death","hurt"})
if(old.getPath().equals("entity.zombie_pigman."+suffix))return new ResourceLocation("minecraft:entity.zombified_piglin."+suffix);
return null;
@@ -128,6 +139,15 @@ private static final class VanillaBiomes {
@SubscribeEvent public static void sounds(MissingMappingsEvent event) {
for(MissingMappingsEvent.Mapping mapping:event.getAllMappings(Registries.SOUND_EVENT)) {
+ if (mapping.getKey().equals(OLD_JUNGLE_AND_FOREST_MUSIC)) {
+ // Vanilla 1.20 defines separate music for the two biomes.
+ // Retire only the obsolete Forge numeric binding.
+ if (!BuiltInRegistries.SOUND_EVENT.containsKey(new ResourceLocation("minecraft:music.overworld.jungle"))
+ || !BuiltInRegistries.SOUND_EVENT.containsKey(new ResourceLocation("minecraft:music.overworld.forest")))
+ throw new UnsafeRegistryUpgrade(null);
+ mapping.ignore();
+ continue;
+ }
ResourceLocation replacement=soundReplacement(mapping.getKey());
if(replacement!=null)mapping.remap(requireMappingTarget(net.minecraftforge.registries.ForgeRegistries.SOUND_EVENTS.containsKey(replacement),()->net.minecraftforge.registries.ForgeRegistries.SOUND_EVENTS.getValue(replacement)));
}
diff --git a/src/main/java/cyano/lootable/upgrade/NativeSafeFixer.java b/src/main/java/cyano/lootable/upgrade/NativeSafeFixer.java
index 6062996..21e2b71 100644
--- a/src/main/java/cyano/lootable/upgrade/NativeSafeFixer.java
+++ b/src/main/java/cyano/lootable/upgrade/NativeSafeFixer.java
@@ -252,7 +252,7 @@ private CompoundTag sourceContext(CompoundTag entity,int version,int x,int z,boo
}catch(IOException damaged){throw new IllegalStateException("Corpse preservation could not be verified",damaged);}
}
CompoundTag context=new CompoundTag();int marker=entity.getInt(UpgradePreservation.VERSION);
- context.putInt("DataVersion",entity.getString("id").equals("lootablebodies.Corpse")?512:marker==1?922:marker==2?1343:marker==3?1631:marker==4?1976:marker==5?2230:marker==6?2586:marker==7?2730:marker==8?2975:version);return context;
+ context.putInt("DataVersion",entity.getString("id").equals("lootablebodies.Corpse")?512:marker==1?922:marker==2?1343:marker==3?1631:marker==4?1976:marker==5?2230:marker==6?2586:marker==7?2730:marker==8?2975:marker==9?3337:version);return context;
}
static CompoundTag stageCorpse(CompoundTag raw,CompoundTag context) {
if(raw.contains(UpgradePreservation.VERSION,3) && raw.getInt(UpgradePreservation.VERSION)>UpgradePreservation.DATA_VERSION)throw new IllegalStateException("This corpse was saved by a newer Lootable Bodies format");
diff --git a/src/main/java/cyano/lootable/upgrade/UpgradeJournal.java b/src/main/java/cyano/lootable/upgrade/UpgradeJournal.java
index e59dc17..0af6dc8 100644
--- a/src/main/java/cyano/lootable/upgrade/UpgradeJournal.java
+++ b/src/main/java/cyano/lootable/upgrade/UpgradeJournal.java
@@ -341,6 +341,7 @@ private static CompoundTag sourceVersions(CompoundTag chunk,CompoundTag world,Co
else if(marker==7)version=2730;
else if(marker==8)version=2975;
else if(marker==9)version=3337;
+ else if(marker==10)version=UpgradePreservation.MINECRAFT_VERSION;
if(legacy) {if(version!=512)context.putBoolean("Inferred",true);version=512;}
if(legacy || (marker>=1 && marker<=UpgradePreservation.DATA_VERSION))
context.putInt("CorpseWriterDataVersion",version);
@@ -349,7 +350,8 @@ private static CompoundTag sourceVersions(CompoundTag chunk,CompoundTag world,Co
for(int i=0;ikeepsake);
+ }
+ }
+ private static final class Stored implements ICapabilitySerializable {
+ private CompoundTag data=new CompoundTag();
+ Stored(){data.putString("owner","Disposable fixture");data.putLong("number",9876543210123L);data.putByteArray("opaque",new byte[]{0,1,-1,17});}
+ public boolean hasCapability(Capability> capability,Direction side){return false;}
+ public net.minecraftforge.common.util.LazyOptional getCapability(Capability capability,Direction side){return net.minecraftforge.common.util.LazyOptional.empty();}
+ public CompoundTag serializeNBT(){return data.copy();}
+ public void deserializeNBT(CompoundTag nbt){data=nbt.copy();}
+ }
+}
diff --git a/src/nativeSeed119/java/cyano/lootable/fixture/NativeWorldSeeder119.java b/src/nativeSeed119/java/cyano/lootable/fixture/NativeWorldSeeder119.java
new file mode 100644
index 0000000..5f0fdac
--- /dev/null
+++ b/src/nativeSeed119/java/cyano/lootable/fixture/NativeWorldSeeder119.java
@@ -0,0 +1,134 @@
+package cyano.lootable.fixture;
+
+import com.mojang.authlib.GameProfile;
+import cyano.lootable.LootableBodies;
+import cyano.lootable.entities.EntityLootableBody;
+import cyano.lootable.buildtest.FixtureItems;
+import java.io.*;
+import java.util.*;
+import net.minecraft.core.*;
+import net.minecraft.nbt.*;
+import net.minecraft.network.chat.Component;
+import net.minecraft.core.registries.Registries;
+import net.minecraft.resources.*;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.*;
+import net.minecraft.world.entity.*;
+import net.minecraft.world.entity.animal.Pig;
+import net.minecraft.world.entity.decoration.ArmorStand;
+import net.minecraft.world.entity.npc.*;
+import net.minecraft.world.entity.vehicle.MinecartChest;
+import net.minecraft.world.item.*;
+import net.minecraft.world.item.enchantment.Enchantments;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.biome.*;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.entity.ChestBlockEntity;
+import net.minecraft.world.level.chunk.LevelChunk;
+import net.minecraft.world.level.chunk.LevelChunkSection;
+import net.minecraft.world.level.storage.*;
+import net.minecraftforge.fml.common.Mod;
+import net.minecraftforge.event.server.ServerStartedEvent;
+import net.minecraftforge.eventbus.api.SubscribeEvent;
+
+/** Compiled against released18 source; runtime contains public6341, never target20. */
+@Mod("lootablebodiesbuildtest")
+public final class NativeWorldSeeder119 {
+ private static final UUID PLAYER=UUID.fromString("00000000-2222-4222-8222-000000000001");
+ public NativeWorldSeeder119(){FixtureItems.register(net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext.get().getModEventBus());net.minecraftforge.common.MinecraftForge.EVENT_BUS.register(this);}
+ @SubscribeEvent public void started(ServerStartedEvent event){
+ MinecraftServer server=event.getServer();
+ try{seed(server);}catch(Throwable failure){failure.printStackTrace();}finally{server.halt(false);}
+ }
+ private static void seed(MinecraftServer server)throws Exception {
+ String minecraft=server.getServerVersion(),version=net.minecraftforge.fml.ModList.get().getModContainerById("lootablebodies").orElseThrow().getModInfo().getVersion().toString();
+ require(minecraft.equals("1.19.4") && version.equals("2.4.10.119041") && version.equals(System.getProperty("lootablebodies.fixture.version")),"Wrong public19 native writer identity");
+ LootableBodies.allowCorpseDecay=false;LootableBodies.ticksPerItemDecay=-1;
+ CompoundTag manifest=new CompoundTag();manifest.putString("Minecraft",minecraft);manifest.putString("ModVersion",version);
+ ListTag bodies=new ListTag(),containers=new ListTag(),vanilla=new ListTag(),biomes=new ListTag();
+ for(int dimension:new int[]{0,-1,1}) {
+ ServerLevel world=server.getLevel(dimensionKey(dimension));
+ for(int wave=0;wave<3;wave++) {
+ int coordinate=wave==0?128:wave==1?4096:8192;
+ awaitChunk(world,coordinate>>4,coordinate>>4);
+ LevelChunk chunk=world.getChunk(coordinate>>4,coordinate>>4);
+ Registry registry=world.registryAccess().registryOrThrow(Registries.BIOME);
+ List> choices=Arrays.asList(registry.getHolderOrThrow(Biomes.PLAINS),registry.getHolderOrThrow(Biomes.DESERT),registry.getHolderOrThrow(Biomes.FOREST),registry.getHolderOrThrow(Biomes.TAIGA));
+ ListTag sections=new ListTag();
+ for(int sectionIndex=0;sectionIndex
+ choices.get(Math.floorMod(sectionY+(quartX&3)+(quartY&3)+(quartZ&3),choices.size())),
+ world.getChunkSource().randomState().sampler(),coordinate>>2,coordinate>>2);
+ ListTag names=new ListTag();
+ for(int yy=0;yy<4;yy++)for(int zz=0;zz<4;zz++)for(int xx=0;xx<4;xx++) {
+ Holder biome=section.getNoiseBiome(xx,yy,zz);
+ names.add(StringTag.valueOf(biome.unwrapKey().orElseThrow().location().toString()));
+ }
+ CompoundTag savedSection=new CompoundTag();savedSection.putInt("Y",sectionY);savedSection.put("Names",names);sections.add(savedSection);
+ }
+ chunk.setUnsaved(true);
+ CompoundTag biomeRecord=new CompoundTag();biomeRecord.putInt("Dimension",dimension);biomeRecord.putInt("Wave",wave);biomeRecord.putInt("ChunkX",coordinate>>4);biomeRecord.putInt("ChunkZ",coordinate>>4);biomeRecord.put("Sections",sections);biomes.add(biomeRecord);
+ EntityLootableBody body=new EntityLootableBody(world);
+ body.setUUID(UUID.nameUUIDFromBytes((minecraft+"/"+version+"/"+dimension+"/"+wave).getBytes("UTF-8")));
+ body.setPos(coordinate+.5,80,coordinate+.5);body.setNoGravity(true);body.setUserName("SavedFixtureOwner");body.setRotation(37);body.setHealth(7.5F);
+ for(int slot=0;slot<48;slot++)body.setItem(slot,item(slot));
+ for(int slot=70;slot<73;slot++)body.addItem(item(slot));
+ if(Boolean.getBoolean("lootablebodies.fixture.recovery")) {
+ CompoundTag damaged=saved(body);ListTag equipment=damaged.getList("Equipment",10);
+ equipment.getCompound(20).putString("id","lootablebodiesbuildtest:returning_item");
+ CompoundTag duplicate=equipment.getCompound(21).copy();duplicate.putByte("Count",(byte)9);duplicate.getCompound("tag").putString("extension","duplicate slot");equipment.add(duplicate);
+ CompoundTag invalid=equipment.getCompound(22).copy();invalid.putInt("Slot",999);invalid.putByte("Count",(byte)7);invalid.getCompound("tag").putString("extension","invalid slot");equipment.add(invalid);
+ damaged.putInt("Name",17);damaged.putDouble("DeathTime",Double.NaN);damaged.putString("Health","opaque health");damaged.putString("ActiveEffects","opaque effects");damaged.putString("HurtTime","opaque hurt timer");damaged.putDouble("FallDistance",Double.POSITIVE_INFINITY);
+ body.load(damaged);body.setPos(coordinate+.5,80,coordinate+.5);
+ }
+ java.lang.reflect.Field vacuum=EntityLootableBody.class.getDeclaredField("vacuumTime");vacuum.setAccessible(true);vacuum.setByte(body,(byte)20);
+ require(world.addFreshEntity(body),"Historical19 corpse admission");
+ CompoundTag record=new CompoundTag();record.putInt("Dimension",dimension);record.putInt("Wave",wave);record.put("Body",saved(body));bodies.add(record);
+ BlockPos chestPos=new BlockPos(coordinate+2,80,coordinate+2);world.setBlockAndUpdate(chestPos,Blocks.CHEST.defaultBlockState());
+ ChestBlockEntity chest=(ChestBlockEntity)world.getBlockEntity(chestPos);for(int slot=0;slot<16;slot++)chest.setItem(slot,item(slot+6));
+ CompoundTag chestRecord=new CompoundTag();chestRecord.putInt("Dimension",dimension);chestRecord.putInt("Wave",wave);chestRecord.put("Tile",chest.saveWithFullMetadata());containers.add(chestRecord);
+ if(wave==0)seedVanilla(world,dimension,coordinate,vanilla);
+ }
+ }
+ ServerLevel world=server.getLevel(Level.OVERWORLD);ServerPlayer player=new ServerPlayer(server,world,new GameProfile(PLAYER,"UpgradeFixture"));player.setPos(136.5,82,136.5);
+ for(int slot=0;slot<36;slot++)player.getInventory().setItem(slot,item(slot+6));
+ for(int slot=0;slot<4;slot++)player.setItemSlot(new EquipmentSlot[]{EquipmentSlot.HEAD,EquipmentSlot.CHEST,EquipmentSlot.LEGS,EquipmentSlot.FEET}[slot],item(slot));
+ player.setItemSlot(EquipmentSlot.OFFHAND,item(5));for(int slot=0;slot<27;slot++)player.getEnderChestInventory().setItem(slot,item(slot+6));
+ player.connection=new net.minecraft.server.network.ServerGamePacketListenerImpl(server,new net.minecraft.network.Connection(net.minecraft.network.protocol.PacketFlow.SERVERBOUND),player){@Override public void send(net.minecraft.network.protocol.Packet> packet){}};
+ net.minecraft.world.item.crafting.Recipe> sugar=server.getRecipeManager().byKey(new ResourceLocation("minecraft:sugar_from_sugar_cane")).orElseThrow();player.awardRecipes(Collections.singleton(sugar));
+ net.minecraft.advancements.Advancement advancement=server.getAdvancements().getAdvancement(new ResourceLocation("minecraft:recipes/misc/sugar_from_sugar_cane"));require(advancement!=null,"Missing native19 sugar advancement");
+ for(String criterion:advancement.getCriteria().keySet())player.getAdvancements().award(advancement,criterion);player.getAdvancements().save();playerData(server).save(player);
+ manifest.put("BiomeSamples",biomes);manifest.putString("Recipe","minecraft:sugar_from_sugar_cane");manifest.putString("Advancement","minecraft:recipes/misc/sugar_from_sugar_cane");manifest.putUUID("Player",PLAYER);manifest.put("Bodies",bodies);manifest.put("Containers",containers);manifest.put("Vanilla",vanilla);
+ server.saveAllChunks(true,true,false);File folder=server.getWorldPath(LevelResource.ROOT).toFile();
+ try(FileOutputStream out=new FileOutputStream(new File(folder,"upgrade-fixtures.nbt"))){NbtIo.writeCompressed(manifest,out);}
+ try(FileWriter out=new FileWriter(new File(folder,"legacy-seed.pass"))){out.write(minecraft+" "+version+"\n");}
+ System.out.println("LOOTABLE_BODIES_LEGACY_SEED_PASS minecraft="+minecraft+" mod="+version+" corpses=9 dimensions=3");
+ }
+ private static void seedVanilla(ServerLevel world,int dimension,int c,ListTag manifest){
+ VillagerProfession[] professions={VillagerProfession.FARMER,VillagerProfession.LIBRARIAN,VillagerProfession.CLERIC,VillagerProfession.ARMORER,VillagerProfession.BUTCHER,VillagerProfession.NITWIT};
+ for(int i=0;i dimensionKey(int id){if(id==0)return Level.OVERWORLD;if(id==-1)return Level.NETHER;if(id==1)return Level.END;throw new IllegalArgumentException("dimension "+id);}
+ private static void awaitChunk(ServerLevel world,int x,int z){world.getChunkSource().getChunk(x,z,net.minecraft.world.level.chunk.ChunkStatus.FULL,true);require(world.getChunkSource().hasChunk(x,z),"Native19 chunk admission");}
+ private static PlayerDataStorage playerData(MinecraftServer server)throws Exception {java.lang.reflect.Field found=null;for(Class> owner=server.getClass();owner!=null;owner=owner.getSuperclass())for(java.lang.reflect.Field f:owner.getDeclaredFields())if(f.getType()==PlayerDataStorage.class){require(found==null,"Ambiguous native19 player storage");found=f;}require(found!=null,"Missing native19 player storage");found.setAccessible(true);return (PlayerDataStorage)found.get(server);}
+ private static ItemStack item(int slot){
+ ItemStack stack;
+ if(slot<4)stack=new ItemStack(new Item[]{Items.DIAMOND_HELMET,Items.DIAMOND_CHESTPLATE,Items.DIAMOND_LEGGINGS,Items.DIAMOND_BOOTS}[slot]);
+ else if(slot==4)stack=new ItemStack(Items.DIAMOND_SWORD);else if(slot==5)stack=new ItemStack(Items.SHIELD);else if(slot==6)stack=new ItemStack(Items.PIG_SPAWN_EGG,3);else if(slot==7)stack=new ItemStack(Blocks.CHEST,2);else if(slot==8)stack=new ItemStack(Items.LIME_BANNER,2);else if(slot==9)stack=new ItemStack(FixtureItems.keepsake,11);else if(slot==10)stack=new ItemStack(Items.WHITE_BED);else if(slot==11)stack=new ItemStack(Items.LIME_BED);else if(slot==12)stack=new ItemStack(Items.OAK_SIGN,11);else stack=new ItemStack(Items.DIAMOND,slot%32+1);
+ CompoundTag tag=new CompoundTag();tag.putString("fixture_slot","original-"+slot);tag.putLong("typed_long",1234567890123L);tag.putByteArray("typed_bytes",new byte[]{0,-1,17});tag.putLongArray("typed_longs",new long[]{Long.MIN_VALUE,slot,Long.MAX_VALUE});
+ if(slot==6){CompoundTag pig=new CompoundTag();pig.putString("id","minecraft:pig");pig.putString("CustomName",net.minecraft.network.chat.Component.Serializer.toJson(Component.literal("Saved pig egg")));tag.put("EntityTag",pig);}
+ if(slot==7){CompoundTag tile=new CompoundTag();ListTag items=new ListTag();CompoundTag nested=new ItemStack(Items.EMERALD,17).save(new CompoundTag());nested.putByte("Slot",(byte)3);items.add(nested);tile.put("Items",items);tag.put("BlockEntityTag",tile);}
+ stack.setTag(tag);if(slot<6)stack.setDamageValue(slot<4?slot+2:slot==4?17:9);stack.setHoverName(Component.literal("Original keepsake "+slot));if(slot==4)stack.enchant(Enchantments.UNBREAKING,2);return stack;
+ }
+ private static void require(boolean condition,String reason){if(!condition)throw new AssertionError(reason);}
+}
diff --git a/src/nativeSeed119/resources/META-INF/mods.toml b/src/nativeSeed119/resources/META-INF/mods.toml
new file mode 100644
index 0000000..3c2e618
--- /dev/null
+++ b/src/nativeSeed119/resources/META-INF/mods.toml
@@ -0,0 +1,13 @@
+modLoader="javafml"
+loaderVersion="[45,46)"
+license="LGPL-2.1-only"
+[[mods]]
+modId="lootablebodiesbuildtest"
+version="1"
+displayName="Disposable historical world writer"
+[[dependencies.lootablebodiesbuildtest]]
+modId="lootablebodies"
+mandatory=true
+versionRange="[2.4.10.119041]"
+ordering="AFTER"
+side="BOTH"
diff --git a/src/nativeSeed119/resources/pack.mcmeta b/src/nativeSeed119/resources/pack.mcmeta
new file mode 100644
index 0000000..103bcab
--- /dev/null
+++ b/src/nativeSeed119/resources/pack.mcmeta
@@ -0,0 +1 @@
+{"pack":{"pack_format":13,"forge:server_data_pack_format":12,"description":"Disposable historical world writer"}}
diff --git a/src/test/java/cyano/lootable/buildtest/Native115PacketTest.java b/src/test/java/cyano/lootable/buildtest/Native115PacketTest.java
index e188abd..bd3f924 100644
--- a/src/test/java/cyano/lootable/buildtest/Native115PacketTest.java
+++ b/src/test/java/cyano/lootable/buildtest/Native115PacketTest.java
@@ -19,7 +19,7 @@ class Native115PacketTest {
private ClientboundRespawnPacket packet(ResourceKey dimension,long seed) {
return new ClientboundRespawnPacket(net.minecraft.world.level.dimension.BuiltinDimensionTypes.OVERWORLD,
dimension,seed,GameType.SURVIVAL,GameType.CREATIVE,false,true,
- ClientboundRespawnPacket.KEEP_ALL_DATA,java.util.Optional.empty());
+ ClientboundRespawnPacket.KEEP_ALL_DATA,java.util.Optional.empty(),0);
}
@Test void native115HashedSeedSurvivesDecodedPacketObservation() throws Exception {
FriendlyByteBuf bytes=new FriendlyByteBuf(Unpooled.buffer());
diff --git a/src/test/java/cyano/lootable/buildtest/Native117ProbeIsolationTest.java b/src/test/java/cyano/lootable/buildtest/Native117ProbeIsolationTest.java
index f7e3e50..79d65b7 100644
--- a/src/test/java/cyano/lootable/buildtest/Native117ProbeIsolationTest.java
+++ b/src/test/java/cyano/lootable/buildtest/Native117ProbeIsolationTest.java
@@ -1,12 +1,13 @@
package cyano.lootable.buildtest;
import java.nio.file.*;
+import cyano.lootable.LootableBodies;
import java.util.*;
import java.util.zip.ZipFile;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
-/** Native45 loads mods as distinct Java modules: split packages are fatal. */
+/** Native47 loads mods as distinct Java modules: split packages are fatal. */
class Native117ProbeIsolationTest {
private Set packages(String archive) throws Exception {
Set result=new TreeSet<>();
@@ -22,9 +23,9 @@ private Set packages(String archive) throws Exception {
assertFalse(result.isEmpty(),archive);return result;
}
@Test void integrationModuleHasNoProductionPackageDefinitions() throws Exception {
- Set main=packages("build/libs-dev/LootableBodies-2.4.10.119041-deobf.jar");
+ Set main=packages("build/libs-dev/LootableBodies-"+LootableBodies.VERSION+"-deobf.jar");
Set probe=packages("build/integration-test-mod/lootablebodies-build-tests.jar");
- probe.retainAll(main);assertTrue(probe.isEmpty(),"Native45 module split packages: "+probe);
+ probe.retainAll(main);assertTrue(probe.isEmpty(),"Native47 module split packages: "+probe);
}
@Test void independentControlHasNoModOrIntegrationPackages() throws Exception {
Set control=packages("build/control-test-mod/forge-platform-control.jar");
diff --git a/src/test/java/cyano/lootable/buildtest/Native119FixtureGenerator.java b/src/test/java/cyano/lootable/buildtest/Native120FixtureGenerator.java
similarity index 86%
rename from src/test/java/cyano/lootable/buildtest/Native119FixtureGenerator.java
rename to src/test/java/cyano/lootable/buildtest/Native120FixtureGenerator.java
index 5a954ec..0f1bd46 100644
--- a/src/test/java/cyano/lootable/buildtest/Native119FixtureGenerator.java
+++ b/src/test/java/cyano/lootable/buildtest/Native120FixtureGenerator.java
@@ -17,8 +17,8 @@
import net.minecraft.world.level.WorldDataConfiguration;
import cyano.lootable.upgrade.Native117RegionFixtures;
-/** Native19 codec fixture preparation, never authentic historical-writer proof. */
-public final class Native119FixtureGenerator {
+/** Native20 codec fixture preparation, never authentic historical-writer proof. */
+public final class Native120FixtureGenerator {
public static void main(String[] args)throws Exception {
if(args.length!=1)throw new IllegalArgumentException("One new owned output root required");
Path root=Path.of(args[0]);Files.createDirectory(root);
@@ -27,19 +27,19 @@ public static void main(String[] args)throws Exception {
.getOrThrow(WorldPresets.FLAT).value().createWorldDimensions();
RegistryAccess registries=Native119Registries.forDimensions(dimensions);
WorldOptions options=new WorldOptions(4267321L,false,false);
- LevelSettings settings=new LevelSettings("Native19 disposable fixture",GameType.CREATIVE,false,Difficulty.NORMAL,true,new GameRules(),WorldDataConfiguration.DEFAULT);
+ LevelSettings settings=new LevelSettings("Native20 disposable fixture",GameType.CREATIVE,false,Difficulty.NORMAL,true,new GameRules(),WorldDataConfiguration.DEFAULT);
PrimaryLevelData nativeData=new PrimaryLevelData(settings,options,PrimaryLevelData.SpecialWorldProperty.FLAT,Lifecycle.stable());
nativeData.setSpawn(new BlockPos(0,80,0),0);nativeData.setGameTime(0);nativeData.setDayTime(6000);
CompoundTag data=nativeData.createTag(registries,null);data.putLong("LastPlayed",0);
int target=net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion();
- if(data.getInt("DataVersion")!=target || !data.contains("WorldGenSettings",10))throw new AssertionError("Native19 header was not encoded");
+ if(data.getInt("DataVersion")!=target || !data.contains("WorldGenSettings",10))throw new AssertionError("Native20 header was not encoded");
CompoundTag header=new CompoundTag();header.put("Data",data);
- NbtIo.writeCompressed(header,root.resolve("native119-flat-level.dat").toFile());
+ NbtIo.writeCompressed(header,root.resolve("native120-flat-level.dat").toFile());
Path world=root.resolve("controlled-entity-source");Files.createDirectory(world);
- CompoundTag originalOwned=new CompoundTag();originalOwned.putString("unknownField","retained-native19-header");originalOwned.putLongArray("unknownData",new long[]{Long.MIN_VALUE,1234567890123L,Long.MAX_VALUE});
+ CompoundTag originalOwned=new CompoundTag();originalOwned.putString("unknownField","retained-native20-header");originalOwned.putLongArray("unknownData",new long[]{Long.MIN_VALUE,1234567890123L,Long.MAX_VALUE});
header.put("lootablebodies",originalOwned);NbtIo.writeCompressed(header,world.resolve("level.dat").toFile());
CompoundTag corpse=Native117RegionFixtures.body(UUID.fromString("00000000-1171-4171-8171-000000000099"),7);
Native117RegionFixtures.write(world,Native117RegionFixtures.chunk(2730,corpse));
- System.out.println("NATIVE119_CODEC_FIXTURE_PREPARED version="+target+" markerSource=7/2730 synthetic=true root="+root);
+ System.out.println("NATIVE120_CODEC_FIXTURE_PREPARED version="+target+" markerSource=7/2730 synthetic=true root="+root);
}
}
diff --git a/src/test/java/cyano/lootable/contracts/BuildContractTest.java b/src/test/java/cyano/lootable/contracts/BuildContractTest.java
index 2c2e64c..0ac073a 100644
--- a/src/test/java/cyano/lootable/contracts/BuildContractTest.java
+++ b/src/test/java/cyano/lootable/contracts/BuildContractTest.java
@@ -50,10 +50,10 @@ private String text(String path) throws Exception {
assertTrue(info.contains("modId=\"lootablebodies\""));
Properties release=new Properties();try(java.io.InputStream input=Files.newInputStream(Paths.get("gradle.properties"))){release.load(input);}
assertTrue(info.contains("version=\""+release.getProperty("mod_version")+"\""));
- assertTrue(info.contains("versionRange=\"[1.19.4]\""));assertTrue(info.contains("Cyanobacterium"));assertFalse(info.contains("${"));
+ assertTrue(info.contains("versionRange=\"[1.20.1]\""));assertTrue(info.contains("Cyanobacterium"));assertFalse(info.contains("${"));
assertFalse(Files.exists(Paths.get("build/resources/main/mcmod.info")));
com.google.gson.JsonObject pack=new com.google.gson.JsonParser().parse(text("build/resources/main/pack.mcmeta")).getAsJsonObject().getAsJsonObject("pack");
- assertEquals(13,pack.get("pack_format").getAsInt());assertEquals(12,pack.get("forge:server_data_pack_format").getAsInt());
+ assertEquals(15,pack.get("pack_format").getAsInt());assertFalse(pack.has("forge:server_data_pack_format"));
}
@Test void everyOriginalTranslationSurvivesTheNativeJsonFormat() throws Exception {
try(DirectoryStream locales=Files.newDirectoryStream(Paths.get("src/test/resources/legacy-translations"),"*.lang")){
diff --git a/src/test/java/cyano/lootable/contracts/ConfigScreenTest.java b/src/test/java/cyano/lootable/contracts/ConfigScreenTest.java
index c5fcb40..6fdd273 100644
--- a/src/test/java/cyano/lootable/contracts/ConfigScreenTest.java
+++ b/src/test/java/cyano/lootable/contracts/ConfigScreenTest.java
@@ -128,9 +128,9 @@ private void save() throws Exception {
cyano.lootable.graphics.CorpseGUIContainer screen=new cyano.lootable.graphics.CorpseGUIContainer(
(cyano.lootable.graphics.CorpseContainer)null,new net.minecraft.world.entity.player.Inventory(null),net.minecraft.network.chat.Component.literal("Saved owner"));
java.lang.reflect.Method foreground=cyano.lootable.graphics.CorpseGUIContainer.class.getDeclaredMethod(
- "renderLabels",com.mojang.blaze3d.vertex.PoseStack.class,int.class,int.class);foreground.setAccessible(true);
+ "renderLabels",net.minecraft.client.gui.GuiGraphics.class,int.class,int.class);foreground.setAccessible(true);
// The original layout uses all seven rows. Native labels would draw
// over those slots; its original foreground needs no font or item state.
- foreground.invoke(screen,new com.mojang.blaze3d.vertex.PoseStack(),0,0);
+ foreground.invoke(screen,null,0,0);
}
}
diff --git a/src/test/java/cyano/lootable/contracts/NativeUpgradeBoundaryTest.java b/src/test/java/cyano/lootable/contracts/NativeUpgradeBoundaryTest.java
index f60c9e3..7a31f2d 100644
--- a/src/test/java/cyano/lootable/contracts/NativeUpgradeBoundaryTest.java
+++ b/src/test/java/cyano/lootable/contracts/NativeUpgradeBoundaryTest.java
@@ -57,7 +57,7 @@ private CompoundTag find(Tag value) {
}
@Test void legacyIdentityAndEveryRawInventoryEntrySurviveBothAnvilPasses()throws Exception {
CompoundTag body=corpse(),source=chunk(body,true);byte[] before=encoded(source);CompoundTag result=update(source),saved=findCorpse(result);
- assertEquals("lootablebodies:corpse",saved.getString("id"));assertEquals(9,saved.getInt("LootableBodiesDataVersion"));assertEquals(ID,saved.getUUID("UUID"));
+ assertEquals("lootablebodies:corpse",saved.getString("id"));assertEquals(10,saved.getInt("LootableBodiesDataVersion"));assertEquals(ID,saved.getUUID("UUID"));
ListTag records=saved.getList("LootableBodiesUnresolved",10);assertEquals(49,records.size());
for(int i=0;i<48;i++){CompoundTag record=records.getCompound(i);assertEquals("Equipment",record.getString("Source"));assertArrayEquals(encoded(body.getList("Equipment",10).getCompound(i)),encoded(record.getCompound("Data")));assertEquals(512,record.getCompound("SourceVersions").getInt("DataVersion"));}
assertEquals("Aux",records.getCompound(48).getString("Source"));assertArrayEquals(before,encoded(source));assertFalse(saved.contains("LootableBodiesUpgradePayload"));assertFalse(saved.contains("LootableBodiesInventorySource"));
diff --git a/src/test/java/cyano/lootable/contracts/ReleaseContractTest.java b/src/test/java/cyano/lootable/contracts/ReleaseContractTest.java
index edd4f8b..a369670 100644
--- a/src/test/java/cyano/lootable/contracts/ReleaseContractTest.java
+++ b/src/test/java/cyano/lootable/contracts/ReleaseContractTest.java
@@ -28,7 +28,7 @@ private String text(String path) throws Exception {
Properties values = properties();
String version = values.getProperty("mod_version");
assertTrue(version.matches("[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+"), version);
- assertEquals("2.4.10.119041", version);
+ assertEquals("2.4.11.120011", version);
assertEquals("forge", values.getProperty("loader_name"));
assertEquals("1", values.getProperty("loader_code"));
assertTrue(text("src/main/java/cyano/lootable/LootableBodies.java")
@@ -44,7 +44,7 @@ private String text(String path) throws Exception {
String source = text("src/main/java/cyano/lootable/LootableBodies.java");
assertTrue(source.startsWith("package cyano.lootable;"));
assertTrue(source.contains("new ResourceLocation(\"lootablebodies:corpse\")"));
- assertTrue(text("build/resources/main/META-INF/mods.toml").contains("versionRange=\"[1.19.4]\""));
+ assertTrue(text("build/resources/main/META-INF/mods.toml").contains("versionRange=\"[1.20.1]\""));
}
@Test void maintainedReleaseHasLgplAndRetainsTheOriginalNotice() throws Exception {
diff --git a/src/test/java/cyano/lootable/entities/Native119DamageTest.java b/src/test/java/cyano/lootable/entities/Native119DamageTest.java
index b6bf4ab..10ec700 100644
--- a/src/test/java/cyano/lootable/entities/Native119DamageTest.java
+++ b/src/test/java/cyano/lootable/entities/Native119DamageTest.java
@@ -91,8 +91,8 @@ public HolderGetter lookup(ResourceKey extends Registry extends S>> k
assertFalse(CorpseDamageRules.isWall(null));
}
@Test void voidBypassesEveryDamageToggleAndCompleteInvulnerability() {
- assertEquals(CorpseDamageRules.Kind.VOID,CorpseDamageRules.kind(sources.outOfWorld()));
- assertTrue(CorpseDamageRules.allows(sources.outOfWorld(),true,false,false,false));
+ assertEquals(CorpseDamageRules.Kind.VOID,CorpseDamageRules.kind(sources.fellOutOfWorld()));
+ assertTrue(CorpseDamageRules.allows(sources.fellOutOfWorld(),true,false,false,false));
}
@Test void allConfigurationCombinationsSelectOnlyTheirCategory() {
for(int bits=0;bits<8;bits++) {
diff --git a/src/test/java/cyano/lootable/upgrade/EarlyWorldPreservationTest.java b/src/test/java/cyano/lootable/upgrade/EarlyWorldPreservationTest.java
index 7a0106a..2047606 100644
--- a/src/test/java/cyano/lootable/upgrade/EarlyWorldPreservationTest.java
+++ b/src/test/java/cyano/lootable/upgrade/EarlyWorldPreservationTest.java
@@ -6,6 +6,8 @@
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.world.level.storage.LevelStorageSource;
+import net.minecraft.world.level.validation.DirectoryValidator;
+import net.minecraft.world.level.validation.PathAllowList;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.io.TempDir;
import static org.junit.jupiter.api.Assertions.*;
@@ -24,7 +26,7 @@ private void write(Path world,CompoundTag root)throws Exception {
Path world=directory.resolve("fixture");CompoundTag root=level(2230);write(world,root);
Path journal=world.resolve("data/lootablebodies-upgrade");Files.createDirectories(journal);Files.write(journal.resolve("index.nbt"),new byte[]{1,2,3});
byte[] before=Files.readAllBytes(world.resolve("level.dat"));Native118UnitLifecycle.activate();
- LevelStorageSource format=new LevelStorageSource(directory,directory.resolve("backups"),NativeVanillaDataFixer.get());
+ LevelStorageSource format=new LevelStorageSource(directory,directory.resolve("backups"),new DirectoryValidator(new PathAllowList(java.util.List.of())),NativeVanillaDataFixer.get());
try(LevelStorageSource.LevelStorageAccess save=format.createAccess("fixture")) {
Error failure=assertThrows(Error.class,()->EarlyWorldPreservation.beforeRead(save));
assertTrue(failure.getMessage().contains("Lootable Bodies could not preserve this world's corpses"));
@@ -34,7 +36,7 @@ private void write(Path world,CompoundTag root)throws Exception {
}
@Test void startupKeepsItsEarlySourceContextAndUnknownOwnedMetadata()throws Exception {
Path world=directory.resolve("fixture");CompoundTag root=level(2230);write(world,root);Native118UnitLifecycle.activate();
- LevelStorageSource format=new LevelStorageSource(directory,directory.resolve("backups"),NativeVanillaDataFixer.get());
+ LevelStorageSource format=new LevelStorageSource(directory,directory.resolve("backups"),new DirectoryValidator(new PathAllowList(java.util.List.of())),NativeVanillaDataFixer.get());
try(LevelStorageSource.LevelStorageAccess save=format.createAccess("fixture")) {
EarlyWorldPreservation.beforeRead(save);
UpgradeJournal early=UpgradePreservation.journal();assertEquals(2230,early.worldVersions().getInt("WorldDataVersion"));
diff --git a/src/test/java/cyano/lootable/upgrade/ItemNbtConversionTest.java b/src/test/java/cyano/lootable/upgrade/ItemNbtConversionTest.java
index be729a0..058a501 100644
--- a/src/test/java/cyano/lootable/upgrade/ItemNbtConversionTest.java
+++ b/src/test/java/cyano/lootable/upgrade/ItemNbtConversionTest.java
@@ -36,7 +36,7 @@ class ItemNbtConversionTest {
CompoundTag versions=context(922);versions.putBoolean("Ambiguous",true);versions.putIntArray("CandidateDataVersions",new int[]{922,1343});assertNull(ItemMigration.convertNbt(item("minecraft:bed",0),versions));assertNotNull(ItemMigration.convertNbt(item("minecraft:stone",1),versions));
}
@Test void malformedCountsAndUnknownFutureVersionsAreNotGuessed() {
- CompoundTag raw=item("minecraft:stone",1);raw.putString("Count","bad");assertNull(ItemMigration.convertNbt(raw,context(512)));assertNull(ItemMigration.convertNbt(item("minecraft:stone",1),context(3338)));
+ CompoundTag raw=item("minecraft:stone",1);raw.putString("Count","bad");assertNull(ItemMigration.convertNbt(raw,context(512)));assertNull(ItemMigration.convertNbt(item("minecraft:stone",1),context(3466)));
}
@Test void unrecognisedMetadataAndEnchantmentsRemainRecoverable() {
assertNull(ItemMigration.convertNbt(item("minecraft:stone",99),context(1343)));
diff --git a/src/test/java/cyano/lootable/upgrade/Native114UpgradeTest.java b/src/test/java/cyano/lootable/upgrade/Native114UpgradeTest.java
index bfd545f..854afc7 100644
--- a/src/test/java/cyano/lootable/upgrade/Native114UpgradeTest.java
+++ b/src/test/java/cyano/lootable/upgrade/Native114UpgradeTest.java
@@ -29,13 +29,13 @@ private CompoundTag body(int marker) {
return body;
}
@Test void currentFormatNamesThe119VanillaSchema() {
- assertEquals(3337, UpgradePreservation.MINECRAFT_VERSION);
- assertEquals(9, UpgradePreservation.DATA_VERSION);
+ assertEquals(3465, UpgradePreservation.MINECRAFT_VERSION);
+ assertEquals(10,UpgradePreservation.DATA_VERSION);
}
@Test void released113EquipmentAndOverflowAreRetainedBeforeNativeConversion() {
CompoundTag original = body(3), before = original.copy();
CompoundTag staged = NativeSafeFixer.stageCorpse(original, context(1631));
- assertEquals(9, staged.getInt(UpgradePreservation.VERSION));
+ assertEquals(10,staged.getInt(UpgradePreservation.VERSION));
assertEquals(original.getUUID("UUID"), staged.getUUID("UUID"));
assertTrue(staged.getList("Equipment", 10).isEmpty());
ListTag records = staged.getList("LootableBodiesUnresolved", 10); assertEquals(49, records.size());
@@ -53,12 +53,12 @@ private CompoundTag body(int marker) {
record.put("SourceVersions", context(922)); ListTag recovery = new ListTag(); recovery.add(record.copy());
original.put("LootableBodiesUnresolved", recovery);
CompoundTag staged = NativeSafeFixer.stageCorpse(original, context(1631));
- assertEquals(9, staged.getInt(UpgradePreservation.VERSION));
+ assertEquals(10,staged.getInt(UpgradePreservation.VERSION));
assertEquals(record, staged.getList("LootableBodiesUnresolved", 10).getCompound(49));
assertEquals(record, original.getList("LootableBodiesUnresolved", 10).getCompound(0));
}
@Test void currentRecordsAreNotWrappedOrDuplicatedAgain() {
- CompoundTag original = body(9);
+ CompoundTag original = body(UpgradePreservation.DATA_VERSION);
assertEquals(original, NativeSafeFixer.stageCorpse(original, context(1976)));
}
@Test void vanilla114ItemRenamesRunForReleased113InventoryCopies() {
diff --git a/src/test/java/cyano/lootable/upgrade/Native115UpgradeTest.java b/src/test/java/cyano/lootable/upgrade/Native115UpgradeTest.java
index 9af9ed3..dad236b 100644
--- a/src/test/java/cyano/lootable/upgrade/Native115UpgradeTest.java
+++ b/src/test/java/cyano/lootable/upgrade/Native115UpgradeTest.java
@@ -33,8 +33,8 @@ private int[] currentBiomes() {
return values;
}
@Test void currentFormatUsesThe119SchemaAndItsOwnMarker() {
- assertEquals(3337, UpgradePreservation.MINECRAFT_VERSION);
- assertEquals(9, UpgradePreservation.DATA_VERSION);
+ assertEquals(3465, UpgradePreservation.MINECRAFT_VERSION);
+ assertEquals(10,UpgradePreservation.DATA_VERSION);
}
@Test void currentBiomeValuesSurvivePlausibleOlderStampsAndRepeatedConversion() {
int[] values = currentBiomes(); CompoundTag original = chunk(values), before = original.copy();
@@ -78,7 +78,7 @@ private int[] currentBiomes() {
ListTag items = new ListTag(); items.add(item); raw.put("Equipment", items);
context.putInt("DataVersion", 1976); CompoundTag before = raw.copy();
CompoundTag staged = NativeSafeFixer.stageCorpse(raw, context);
- assertEquals(9, staged.getInt(UpgradePreservation.VERSION));
+ assertEquals(10,staged.getInt(UpgradePreservation.VERSION));
CompoundTag record = staged.getList("LootableBodiesUnresolved", 10).getCompound(0);
assertEquals(item, record.get("Data")); assertEquals("Equipment", record.getString("Source"));
assertEquals(1976, record.getCompound("SourceVersions").getInt("DataVersion"));
diff --git a/src/test/java/cyano/lootable/upgrade/Native116UpgradeTest.java b/src/test/java/cyano/lootable/upgrade/Native116UpgradeTest.java
index 2ffcb6f..4ada114 100644
--- a/src/test/java/cyano/lootable/upgrade/Native116UpgradeTest.java
+++ b/src/test/java/cyano/lootable/upgrade/Native116UpgradeTest.java
@@ -35,7 +35,7 @@ private CompoundTag legacy() {
body.put("Equipment",inventory);return body;
}
@Test void currentFormatUsesThe119SchemaAndMarker() {
- assertEquals(3337,UpgradePreservation.MINECRAFT_VERSION);assertEquals(9,UpgradePreservation.DATA_VERSION);
+ assertEquals(3465,UpgradePreservation.MINECRAFT_VERSION);assertEquals(10,UpgradePreservation.DATA_VERSION);
}
@Test void legacyCorpseUuidSurvivesShieldingWithoutTruncatingCustomInventory() {
CompoundTag body=legacy(),before=body.copy(),context=new CompoundTag();context.putInt("DataVersion",2230);
diff --git a/src/test/java/cyano/lootable/upgrade/Native117EntityChunkTest.java b/src/test/java/cyano/lootable/upgrade/Native117EntityChunkTest.java
index fa3c9c0..b3d033e 100644
--- a/src/test/java/cyano/lootable/upgrade/Native117EntityChunkTest.java
+++ b/src/test/java/cyano/lootable/upgrade/Native117EntityChunkTest.java
@@ -20,7 +20,7 @@ class Native117EntityChunkTest {
private static final UUID ID=UUID.fromString("00000000-1171-4171-8171-000000000011");
@AfterEach void close(){UpgradePreservation.close();System.out.println("ENTITY_CHUNK_FIXTURE="+world);}
private CompoundTag convert(CompoundTag root,int source) {
- return NativeNbt.update(UpgradePreservation.wrapper(NativeVanillaDataFixer.get()),References.ENTITY_CHUNK,root,source,3337);
+ return NativeNbt.update(UpgradePreservation.wrapper(NativeVanillaDataFixer.get()),References.ENTITY_CHUNK,root,source,3465);
}
@Test void nativeEntityChunkShieldsAllSlotsAuxRecoveryAndPassengers() throws Exception {
CompoundTag corpse=body(ID,6),cart=new CompoundTag();cart.putString("id","minecraft:chest_minecart");cart.putUUID("UUID",new UUID(12,13));
@@ -28,7 +28,7 @@ private CompoundTag convert(CompoundTag root,int source) {
CompoundTag raw=chunk(2586,cart),before=raw.copy();Path region=write(world,raw);byte[] bytes=Files.readAllBytes(region);
UpgradePreservation.prepare(world);
CompoundTag fixed=convert(raw,2586).getList("Entities",10).getCompound(0).getList("Passengers",10).getCompound(0);
- assertEquals(9,fixed.getInt(UpgradePreservation.VERSION));assertEquals(2586,fixed.getCompound(UpgradePreservation.SOURCES).getInt("DataVersion"));
+ assertEquals(10,fixed.getInt(UpgradePreservation.VERSION));assertEquals(2586,fixed.getCompound(UpgradePreservation.SOURCES).getInt("DataVersion"));
ListTag records=fixed.getList("LootableBodiesUnresolved",10);assertEquals(50,records.size());
for(int slot=0;slot<48;slot++) {assertEquals(corpse.getList("Equipment",10).getCompound(slot),records.getCompound(slot).getCompound("Data"));assertEquals("Equipment",records.getCompound(slot).getString("Source"));}
assertEquals(corpse.getList("Aux",10).getCompound(0),records.getCompound(48).getCompound("Data"));
@@ -36,8 +36,8 @@ private CompoundTag convert(CompoundTag root,int source) {
assertEquals(before,raw);assertArrayEquals(bytes,Files.readAllBytes(region));
}
@Test void currentMarkerProtectsNativeRootAgainstDamagedStamps() {
- CompoundTag corpse=body(ID,9);
- for(int version:new int[]{-1,0,512,2230,2586,2730,2975,3337}) {
+ CompoundTag corpse=body(ID,UpgradePreservation.DATA_VERSION);
+ for(int version:new int[]{-1,0,512,2230,2586,2730,2975,3337,3465}) {
CompoundTag raw=chunk(version,corpse),before=raw.copy();
CompoundTag fixed=convert(raw,version);
assertEquals(corpse,fixed.getList("Entities",10).getCompound(0));assertEquals(before,raw);
diff --git a/src/test/java/cyano/lootable/upgrade/Native118EntityBoundaryTest.java b/src/test/java/cyano/lootable/upgrade/Native118EntityBoundaryTest.java
index e8aa6cc..75eac9e 100644
--- a/src/test/java/cyano/lootable/upgrade/Native118EntityBoundaryTest.java
+++ b/src/test/java/cyano/lootable/upgrade/Native118EntityBoundaryTest.java
@@ -22,9 +22,9 @@ class Native118EntityBoundaryTest {
Path file=write(world,raw);byte[] original=Files.readAllBytes(file);
UpgradeJournal journal=UpgradePreservation.prepareEarly(world);
assertEquals(1,journal.size());
- CompoundTag converted=NativeNbt.update(UpgradePreservation.wrapper(NativeVanillaDataFixer.get()),References.ENTITY_CHUNK,raw,-1,3337);
+ CompoundTag converted=NativeNbt.update(UpgradePreservation.wrapper(NativeVanillaDataFixer.get()),References.ENTITY_CHUNK,raw,-1,3465);
CompoundTag actual=converted.getList("Entities",10).getCompound(0);
- assertEquals(9,actual.getInt(UpgradePreservation.VERSION));
+ assertEquals(10,actual.getInt(UpgradePreservation.VERSION));
assertEquals(2730,actual.getCompound(UpgradePreservation.SOURCES).getInt("DataVersion"));
assertEquals(50,actual.getList("LootableBodiesUnresolved",10).size());
for(int slot=0;slot<48;slot++)assertEquals(corpse.getList("Equipment",10).getCompound(slot),actual.getList("LootableBodiesUnresolved",10).getCompound(slot).getCompound("Data"));
@@ -40,7 +40,7 @@ class Native118EntityBoundaryTest {
assertNull(UpgradePreservation.journal());assertArrayEquals(original,Files.readAllBytes(file));
}
@Test void futureSeparateEntityVersionRefusesEvenWhenItsCorpseHasAnOlderKnownMarker() throws Exception {
- CompoundTag raw=chunk(3338,body(new UUID(118,172),7)),before=raw.copy();
+ CompoundTag raw=chunk(3466,body(new UUID(118,172),7)),before=raw.copy();
Path file=write(world,raw);byte[] original=Files.readAllBytes(file);
assertThrows(IOException.class,()->UpgradePreservation.prepareEarly(world));
assertNull(UpgradePreservation.journal());assertEquals(before,raw);assertArrayEquals(original,Files.readAllBytes(file));
@@ -51,8 +51,8 @@ class Native118EntityBoundaryTest {
UpgradePreservation.close();Path folder=world.resolve("typed-"+i);
CompoundTag raw=chunk(2730,body(new UUID(118,173+i),7));raw.put("DataVersion",stamps[i]);CompoundTag before=raw.copy();
Path file=write(folder,raw);byte[] original=Files.readAllBytes(file);UpgradePreservation.prepareEarly(folder);
- CompoundTag converted=NativeNbt.update(UpgradePreservation.wrapper(NativeVanillaDataFixer.get()),References.ENTITY_CHUNK,raw,-1,3337).getList("Entities",10).getCompound(0);
- assertEquals(9,converted.getInt(UpgradePreservation.VERSION));assertEquals(2730,converted.getCompound(UpgradePreservation.SOURCES).getInt("DataVersion"));
+ CompoundTag converted=NativeNbt.update(UpgradePreservation.wrapper(NativeVanillaDataFixer.get()),References.ENTITY_CHUNK,raw,-1,3465).getList("Entities",10).getCompound(0);
+ assertEquals(10,converted.getInt(UpgradePreservation.VERSION));assertEquals(2730,converted.getCompound(UpgradePreservation.SOURCES).getInt("DataVersion"));
assertEquals(50,converted.getList("LootableBodiesUnresolved",10).size());assertEquals(before,raw);assertArrayEquals(original,Files.readAllBytes(file));
}
}
diff --git a/src/test/java/cyano/lootable/upgrade/Native118SavedPositionTest.java b/src/test/java/cyano/lootable/upgrade/Native118SavedPositionTest.java
index f7f4672..76738a5 100644
--- a/src/test/java/cyano/lootable/upgrade/Native118SavedPositionTest.java
+++ b/src/test/java/cyano/lootable/upgrade/Native118SavedPositionTest.java
@@ -17,10 +17,10 @@ private ListTag pos(double x,double y,double z) {
}
private CompoundTag fixed(CompoundTag raw,boolean separate) {
return NativeNbt.update(UpgradePreservation.wrapper(NativeVanillaDataFixer.get()),
- separate?References.ENTITY_CHUNK:References.CHUNK,raw,3337,3337);
+ separate?References.ENTITY_CHUNK:References.CHUNK,raw,3465,3465);
}
private CompoundTag root(CompoundTag body,boolean separate,int x,int z) {
- CompoundTag root=chunk(3337,body);
+ CompoundTag root=chunk(3465,body);
if(separate)root.putIntArray("Position",new int[]{x,z});
else {
root.putInt("xPos",x);root.putInt("zPos",z);root.put("sections",new ListTag());root.put("entities",root.get("Entities"));
@@ -31,7 +31,7 @@ private CompoundTag root(CompoundTag body,boolean separate,int x,int z) {
private CompoundTag first(CompoundTag root,boolean separate){return root.getList(separate?"Entities":"entities",10).getCompound(0);}
@Test void malformedAxisUsesContainingRegionBeforeEitherNativeLoaderConstructsAnEntity() {
for(boolean separate:new boolean[]{false,true}) {
- CompoundTag body=body(ID,9);body.put("Pos",pos(Double.NaN,80,160.5));
+ CompoundTag body=body(ID,10);body.put("Pos",pos(Double.NaN,80,160.5));
CompoundTag raw=root(body,separate,-11,10),before=raw.copy();CompoundTag converted=fixed(raw,separate),actual=first(converted,separate);
assertEquals(pos(-167.5,80,160.5),actual.get("Pos"));assertEquals(before,raw);
CompoundTag record=actual.getList(RECOVERY,10).getCompound(0);assertEquals("Pos",record.getString("Source"));assertEquals(body.get("Pos"),record.get("Data"));
@@ -40,18 +40,18 @@ private CompoundTag root(CompoundTag body,boolean separate,int x,int z) {
}
}
@Test void finiteMismatchedRootAndMissingPositionCannotBeIndexedInTheWrongSection() {
- CompoundTag body=body(ID,9);body.put("Pos",pos(160.5,80,160.5));
+ CompoundTag body=body(ID,10);body.put("Pos",pos(160.5,80,160.5));
assertEquals(pos(-167.5,80,160.5),first(fixed(root(body,true,-11,10),true),true).get("Pos"));
body.remove("Pos");CompoundTag actual=first(fixed(root(body,true,-11,10),true),true);
assertEquals(pos(-167.5,64,168.5),actual.get("Pos"));assertFalse(actual.contains(RECOVERY),"Missing data invented a raw record");
}
@Test void passengersKeepValidCrossChunkPositionsAndUnknownRecoveryRecords() {
- CompoundTag rider=body(new UUID(117,172),9);rider.put("Pos",pos(17,80,8));
- CompoundTag body=body(ID,9);body.put("Pos",pos(8,80,8));ListTag riders=new ListTag();riders.add(rider);body.put("Passengers",riders);
+ CompoundTag rider=body(new UUID(117,172),10);rider.put("Pos",pos(17,80,8));
+ CompoundTag body=body(ID,10);body.put("Pos",pos(8,80,8));ListTag riders=new ListTag();riders.add(rider);body.put("Passengers",riders);
assertEquals(body,first(fixed(root(body,true,0,0),true),true));
}
@Test void rawRecoveryTagSurvivesPositionRepairExactlyOnce() {
- CompoundTag body=body(ID,9);body.putString("Pos","opaque position");body.putString(RECOVERY,"future raw recovery");
+ CompoundTag body=body(ID,10);body.putString("Pos","opaque position");body.putString(RECOVERY,"future raw recovery");
CompoundTag result=fixed(root(body,true,0,0),true),actual=first(result,true);ListTag records=actual.getList(RECOVERY,10);
assertEquals(2,records.size());assertEquals("Recovery",records.getCompound(0).getString("Source"));
assertEquals(StringTag.valueOf("future raw recovery"),records.getCompound(0).get("Data"));
@@ -59,12 +59,12 @@ private CompoundTag root(CompoundTag body,boolean separate,int x,int z) {
assertEquals(result,fixed(result,true));
}
@Test void impossibleContainingCoordinatesRefuseBeforeNativeConstruction() {
- CompoundTag raw=root(body(ID,9),true,Integer.MAX_VALUE,0),before=raw.copy();
+ CompoundTag raw=root(body(ID,10),true,Integer.MAX_VALUE,0),before=raw.copy();
assertThrows(IllegalStateException.class,()->fixed(raw,true));assertEquals(before,raw);
}
@Test void verticalValuesOutsideTheNativeLoadLimitRetainRawDataBeforeClamping() {
for(boolean separate:new boolean[]{false,true})for(double y:new double[]{-20000000.01,20000000.01,-3e7,3e7}) {
- CompoundTag body=body(ID,9);body.put("Pos",pos(8.5,y,8.5));CompoundTag raw=root(body,separate,0,0),before=raw.copy();
+ CompoundTag body=body(ID,10);body.put("Pos",pos(8.5,y,8.5));CompoundTag raw=root(body,separate,0,0),before=raw.copy();
CompoundTag actual=first(fixed(raw,separate),separate);
assertEquals(pos(8.5,64,8.5),actual.get("Pos"));assertEquals(before,raw);
assertEquals(body.get("Pos"),actual.getList(RECOVERY,10).getCompound(0).get("Data"));
@@ -73,7 +73,7 @@ private CompoundTag root(CompoundTag body,boolean separate,int x,int z) {
}
@Test void nativeHorizontalAndVerticalBoundaryValuesRemainExact() {
for(boolean separate:new boolean[]{false,true})for(double y:new double[]{-2e7,2e7}) {
- CompoundTag body=body(ID,9);body.put("Pos",pos(3e7,y,3e7));
+ CompoundTag body=body(ID,10);body.put("Pos",pos(3e7,y,3e7));
assertEquals(body,first(fixed(root(body,separate,1875000,1875000),separate),separate));
}
}
diff --git a/src/test/java/cyano/lootable/upgrade/Native118TerrainBoundaryTest.java b/src/test/java/cyano/lootable/upgrade/Native118TerrainBoundaryTest.java
index 60dd64c..93dda7a 100644
--- a/src/test/java/cyano/lootable/upgrade/Native118TerrainBoundaryTest.java
+++ b/src/test/java/cyano/lootable/upgrade/Native118TerrainBoundaryTest.java
@@ -21,7 +21,7 @@ class Native118TerrainBoundaryTest {
@TempDir(cleanup=CleanupMode.ON_SUCCESS) Path world;
@AfterEach void close(){UpgradePreservation.close();System.out.println("NATIVE118_TERRAIN_FIXTURE="+world);}
private CompoundTag root(CompoundTag... bodies) {
- CompoundTag root=new CompoundTag();root.putInt("DataVersion",3337);root.putInt("xPos",0);root.putInt("zPos",0);root.putInt("yPos",-4);
+ CompoundTag root=new CompoundTag();root.putInt("DataVersion",3465);root.putInt("xPos",0);root.putInt("zPos",0);root.putInt("yPos",-4);
root.putString("Status","full");root.put("sections",new ListTag());
ListTag list=new ListTag();for(CompoundTag body:bodies)list.add(body.copy());root.put("entities",list);return root;
}
@@ -47,9 +47,9 @@ private CompoundTag legacy() {
@Test void missingStampOnModernThreeDimensionalBiomesStillRunsNativeHeightFix()throws Exception {
CompoundTag old=legacy();int[] biomes=new int[1024];for(int i=0;i=16);
- CompoundTag staged=fixed.getList("entities",10).getCompound(0);assertEquals(9,staged.getInt(UpgradePreservation.VERSION));
+ CompoundTag staged=fixed.getList("entities",10).getCompound(0);assertEquals(10,staged.getInt(UpgradePreservation.VERSION));
assertEquals(50,staged.getList("LootableBodiesUnresolved",10).size());assertEquals(2730,staged.getCompound(UpgradePreservation.SOURCES).getInt("DataVersion"));
old.remove("__context");assertEquals(before,old);assertArrayEquals(original,Files.readAllBytes(file));
}
@@ -91,19 +91,19 @@ private CompoundTag legacy() {
// The native schema conversion leaves DataVersion to ChunkStorage's
// caller. This stale stamp does not describe the newly converted root.
CompoundTag nativeCopy=old.copy();nativeCopy.getCompound("Level").put("Entities",new ListTag());
- CompoundTag nativeResult=NativeNbt.update(NativeVanillaDataFixer.get(),References.CHUNK,nativeCopy,2730,3337);
+ CompoundTag nativeResult=NativeNbt.update(NativeVanillaDataFixer.get(),References.CHUNK,nativeCopy,2730,3465);
assertFalse(nativeResult.contains("Level"));assertEquals(Integer.MAX_VALUE,nativeResult.getInt("DataVersion"));
- CompoundTag fixed=NativeNbt.update(UpgradePreservation.wrapper(NativeVanillaDataFixer.get()),References.CHUNK,old,2730,3337);
- assertFalse(fixed.contains("Level"));assertEquals(3337,fixed.getInt("DataVersion"));
+ CompoundTag fixed=NativeNbt.update(UpgradePreservation.wrapper(NativeVanillaDataFixer.get()),References.CHUNK,old,2730,3465);
+ assertFalse(fixed.contains("Level"));assertEquals(3465,fixed.getInt("DataVersion"));
assertEquals(nativeResult.get("sections"),fixed.get("sections"));
assertEquals(body.getUUID("UUID"),fixed.getList("entities",10).getCompound(0).getUUID("UUID"));
assertEquals(before,old);assertArrayEquals(original,Files.readAllBytes(file));
}
@Test void genuinelyFutureFlattenedTerrainStillRefusesBeforeConversion()throws Exception {
- CompoundTag future=root();future.putInt("DataVersion",3338);CompoundTag before=future.copy();
+ CompoundTag future=root();future.putInt("DataVersion",3466);CompoundTag before=future.copy();
Path file=write(future);byte[] original=Files.readAllBytes(file);
assertThrows(IOException.class,()->UpgradeJournal.scan(world));
- assertThrows(Error.class,()->NativeNbt.update(UpgradePreservation.wrapper(NativeVanillaDataFixer.get()),References.CHUNK,future,3338,3337));
+ assertThrows(Error.class,()->NativeNbt.update(UpgradePreservation.wrapper(NativeVanillaDataFixer.get()),References.CHUNK,future,3466,3465));
assertEquals(before,future);assertArrayEquals(original,Files.readAllBytes(file));
}
}
diff --git a/src/test/java/cyano/lootable/upgrade/Native119RegistrySnapshotTest.java b/src/test/java/cyano/lootable/upgrade/Native119RegistrySnapshotTest.java
index f3783e5..c222a61 100644
--- a/src/test/java/cyano/lootable/upgrade/Native119RegistrySnapshotTest.java
+++ b/src/test/java/cyano/lootable/upgrade/Native119RegistrySnapshotTest.java
@@ -48,7 +48,7 @@ private MissingMappingsEvent.Action action(ResourceKey extends Registry>> re
CompoundTag record=new CompoundTag();record.putString("type",type);record.putIntArray("pos",new int[]{0,80,0});record.putInt("free_tickets",1);records.add(record);
}
section.put("Records",records);sections.put("0",section);root.put("Sections",sections);CompoundTag before=root.copy();
- Tag fixed=net.minecraft.util.datafix.DataFixers.getDataFixer().update(References.POI_CHUNK,new Dynamic<>(NbtOps.INSTANCE,root),2975,3337).getValue();
+ Tag fixed=net.minecraft.util.datafix.DataFixers.getDataFixer().update(References.POI_CHUNK,new Dynamic<>(NbtOps.INSTANCE,root),2975,3465).getValue();
assertEquals(before,root,"Native conversion must operate on copies");
ListTag result=((CompoundTag)fixed).getCompound("Sections").getCompound("0").getList("Records",10);
assertEquals(1,result.size());assertEquals("minecraft:farmer",result.getCompound(0).getString("type"));
diff --git a/src/test/java/cyano/lootable/upgrade/Native120RegistryRenameTest.java b/src/test/java/cyano/lootable/upgrade/Native120RegistryRenameTest.java
new file mode 100644
index 0000000..b47a5fc
--- /dev/null
+++ b/src/test/java/cyano/lootable/upgrade/Native120RegistryRenameTest.java
@@ -0,0 +1,67 @@
+package cyano.lootable.upgrade;
+
+import java.lang.reflect.Field;
+import java.util.List;
+import net.minecraft.core.registries.BuiltInRegistries;
+import net.minecraft.core.registries.Registries;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.sounds.SoundEvents;
+import net.minecraft.util.datafix.DataFixers;
+import net.minecraft.util.datafix.fixes.References;
+import net.minecraftforge.registries.MissingMappingsEvent;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+class Native120RegistryRenameTest {
+ @Test void released119PotteryItemsMatchTheVanillaItemStackConversion() {
+ for (String design : List.of("archer", "arms_up", "prize", "skull")) {
+ ResourceLocation old = new ResourceLocation("minecraft:pottery_shard_" + design);
+ ResourceLocation replacement = new ResourceLocation("minecraft:" + design + "_pottery_sherd");
+ CompoundTag item = new CompoundTag();
+ item.putString("id", old.toString());
+ item.putByte("Count", (byte) 3);
+ item.put("tag", new CompoundTag());
+ item.getCompound("tag").putString("LootableBodiesFixture", design);
+ CompoundTag before = item.copy();
+ CompoundTag fixed = NativeNbt.update(DataFixers.getDataFixer(), References.ITEM_STACK, item, 3337, 3465);
+ assertEquals(replacement.toString(), fixed.getString("id"));
+ assertEquals(3, fixed.getByte("Count"));
+ assertEquals(before.getCompound("tag"), fixed.getCompound("tag"));
+ assertEquals(before, item);
+ assertTrue(BuiltInRegistries.ITEM.containsKey(replacement));
+ assertEquals(replacement, LegacyRegistryMappings.itemReplacement(old));
+ }
+ assertNull(LegacyRegistryMappings.itemReplacement(new ResourceLocation("anothermod:pottery_shard_archer")));
+ assertNull(LegacyRegistryMappings.itemReplacement(new ResourceLocation("minecraft:pottery_shard_unknown")));
+ }
+
+ @Test void released119BrushSoundsUseTheNative120Registrations() {
+ ResourceLocation brushing = new ResourceLocation("minecraft:item.brush.brushing.generic");
+ ResourceLocation complete = new ResourceLocation("minecraft:item.brush.brushing.sand.complete");
+ assertSame(SoundEvents.BRUSH_GENERIC, BuiltInRegistries.SOUND_EVENT.get(brushing));
+ assertSame(SoundEvents.BRUSH_SAND_COMPLETED, BuiltInRegistries.SOUND_EVENT.get(complete));
+ assertEquals(brushing, LegacyRegistryMappings.soundReplacement(new ResourceLocation("minecraft:item.brush.brushing")));
+ assertEquals(complete, LegacyRegistryMappings.soundReplacement(new ResourceLocation("minecraft:item.brush.brush_sand_completed")));
+ assertNull(LegacyRegistryMappings.soundReplacement(new ResourceLocation("anothermod:item.brush.brushing")));
+ }
+
+ @Test void splitBiomeMusicOnlyResolvesItsRetiredNumericSoundBinding() throws Exception {
+ ResourceLocation retired = new ResourceLocation("minecraft:music.overworld.jungle_and_forest");
+ assertFalse(BuiltInRegistries.SOUND_EVENT.containsKey(retired));
+ assertTrue(BuiltInRegistries.SOUND_EVENT.containsKey(new ResourceLocation("minecraft:music.overworld.jungle")));
+ assertTrue(BuiltInRegistries.SOUND_EVENT.containsKey(new ResourceLocation("minecraft:music.overworld.forest")));
+ assertEquals(MissingMappingsEvent.Action.IGNORE, soundAction(retired));
+ assertEquals(MissingMappingsEvent.Action.DEFAULT, soundAction(new ResourceLocation("anothermod:music.overworld.jungle_and_forest")));
+ assertEquals(MissingMappingsEvent.Action.DEFAULT, soundAction(new ResourceLocation("minecraft:music.overworld.unknown")));
+ }
+
+ private static MissingMappingsEvent.Action soundAction(ResourceLocation key) throws Exception {
+ MissingMappingsEvent.Mapping mapping = new MissingMappingsEvent.Mapping<>(null, null, key, 17);
+ LegacyRegistryMappings.sounds(new MissingMappingsEvent(Registries.SOUND_EVENT, null, List.of(mapping)));
+ Field action = mapping.getClass().getDeclaredField("action");
+ action.setAccessible(true);
+ return (MissingMappingsEvent.Action) action.get(mapping);
+ }
+}
diff --git a/src/test/java/cyano/lootable/upgrade/Native119TargetTest.java b/src/test/java/cyano/lootable/upgrade/Native120TargetTest.java
similarity index 71%
rename from src/test/java/cyano/lootable/upgrade/Native119TargetTest.java
rename to src/test/java/cyano/lootable/upgrade/Native120TargetTest.java
index 5750561..46af26b 100644
--- a/src/test/java/cyano/lootable/upgrade/Native119TargetTest.java
+++ b/src/test/java/cyano/lootable/upgrade/Native120TargetTest.java
@@ -1,24 +1,31 @@
package cyano.lootable.upgrade;
import java.io.DataInputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.api.AfterEach;
+import cyano.lootable.buildtest.NativeVanillaDataFixer;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
-class Native119TargetTest {
+class Native120TargetTest {
+ @TempDir Path world;
+ @AfterEach void closeJournal() { UpgradePreservation.close(); }
private static int targetDataVersion() {
net.minecraft.SharedConstants.tryDetectVersion();
return net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion();
}
@Test void targetMatchesTheActualNativeWorldVersion() {
net.minecraft.SharedConstants.tryDetectVersion();
- assertEquals("1.19.4",net.minecraft.SharedConstants.getCurrentVersion().getName());
- assertEquals(13,net.minecraft.SharedConstants.getCurrentVersion().getPackVersion(net.minecraft.server.packs.PackType.CLIENT_RESOURCES));
- assertEquals(12,net.minecraft.SharedConstants.getCurrentVersion().getPackVersion(net.minecraft.server.packs.PackType.SERVER_DATA));
- assertTrue(targetDataVersion()>2975);
+ assertEquals("1.20.1",net.minecraft.SharedConstants.getCurrentVersion().getName());
+ assertEquals(15,net.minecraft.SharedConstants.getCurrentVersion().getPackVersion(net.minecraft.server.packs.PackType.CLIENT_RESOURCES));
+ assertEquals(15,net.minecraft.SharedConstants.getCurrentVersion().getPackVersion(net.minecraft.server.packs.PackType.SERVER_DATA));
+ assertEquals(3465,targetDataVersion());
assertEquals(targetDataVersion(),UpgradePreservation.MINECRAFT_VERSION);
- assertEquals(9,UpgradePreservation.DATA_VERSION);
+ assertEquals(10,UpgradePreservation.DATA_VERSION);
}
@Test void released16MarkerIsMigratedWithAllSlotsAndOriginalsRetained() {
assertEquals(2586,cyano.lootable.buildtest.HistoricalVersions.dataVersion("1.16.5"));
@@ -32,7 +39,7 @@ private static int targetDataVersion() {
body.put("Equipment",items);CompoundTag before=body.copy();
CompoundTag source=new CompoundTag();source.putInt("DataVersion",2586);
CompoundTag staged=NativeSafeFixer.stageCorpse(body,source);
- assertEquals(9,staged.getInt(UpgradePreservation.VERSION));
+ assertEquals(10,staged.getInt(UpgradePreservation.VERSION));
assertEquals(48,staged.getList("LootableBodiesUnresolved",10).size());
for(int slot=0;slot<48;slot++) {
CompoundTag record=staged.getList("LootableBodiesUnresolved",10).getCompound(slot);
@@ -63,7 +70,7 @@ private static int targetDataVersion() {
CompoundTag body=Native117RegionFixtures.body(java.util.UUID.randomUUID(),7),before=body.copy();
CompoundTag source=new CompoundTag();source.putInt("DataVersion",2730);
CompoundTag staged=NativeSafeFixer.stageCorpse(body,source);
- assertEquals(9,staged.getInt(UpgradePreservation.VERSION));
+ assertEquals(10,staged.getInt(UpgradePreservation.VERSION));
assertEquals(2730,staged.getCompound(UpgradePreservation.SOURCES).getInt("DataVersion"));
ListTag records=staged.getList("LootableBodiesUnresolved",10);assertEquals(50,records.size());
for(int slot=0;slot<48;slot++) {
@@ -72,7 +79,7 @@ private static int targetDataVersion() {
}
assertEquals(before,body);
}
- @Test void marker7OverridesDamagedOuterStampWhenTheNative19TargetRuns() {
+ @Test void marker7OverridesDamagedOuterStampWhenTheNative20TargetRuns() {
CompoundTag body=Native117RegionFixtures.body(java.util.UUID.randomUUID(),7),before=body.copy();
com.mojang.datafixers.DataFixer identity=new com.mojang.datafixers.DataFixer() {
public com.mojang.datafixers.schemas.Schema getSchema(int version){throw new AssertionError("No schema needed");}
@@ -87,7 +94,7 @@ private static int targetDataVersion() {
CompoundTag body=Native117RegionFixtures.body(java.util.UUID.randomUUID(),8),before=body.copy();
CompoundTag source=new CompoundTag();source.putInt("DataVersion",2975);
CompoundTag staged=NativeSafeFixer.stageCorpse(body,source);
- assertEquals(9,staged.getInt(UpgradePreservation.VERSION));
+ assertEquals(10,staged.getInt(UpgradePreservation.VERSION));
assertEquals(2975,staged.getCompound(UpgradePreservation.SOURCES).getInt("DataVersion"));
ListTag records=staged.getList("LootableBodiesUnresolved",10);assertEquals(50,records.size());
for(int slot=0;slot<48;slot++) {
@@ -96,7 +103,7 @@ private static int targetDataVersion() {
}
assertEquals(before,body);
}
- @Test void marker8OverridesDamagedOuterStampWhenTheNative19TargetRuns() {
+ @Test void marker8OverridesDamagedOuterStampWhenTheNative20TargetRuns() {
CompoundTag body=Native117RegionFixtures.body(java.util.UUID.randomUUID(),8),before=body.copy();
com.mojang.datafixers.DataFixer identity=new com.mojang.datafixers.DataFixer() {
public com.mojang.datafixers.schemas.Schema getSchema(int version){throw new AssertionError("No schema needed");}
@@ -106,4 +113,30 @@ private static int targetDataVersion() {
assertEquals(2975,staged.getCompound(UpgradePreservation.SOURCES).getInt("DataVersion"));
assertEquals(before,body);
}
+
+ @Test void released19MarkerOverridesTheDamagedChunkStampAndKeepsOriginals() throws Exception {
+ assertEquals(3337,cyano.lootable.buildtest.HistoricalVersions.dataVersion("1.19.4"));
+ CompoundTag body=Native117RegionFixtures.body(java.util.UUID.randomUUID(),9),before=body.copy();
+ CompoundTag root=Native117RegionFixtures.chunk(512,body);
+ Path region=Native117RegionFixtures.write(world,root);
+ byte[] original=Files.readAllBytes(region);
+ UpgradePreservation.prepare(world);
+ CompoundTag fixed=NativeNbt.update(UpgradePreservation.wrapper(NativeVanillaDataFixer.get()),
+ net.minecraft.util.datafix.fixes.References.ENTITY_CHUNK,root,512,targetDataVersion());
+ CompoundTag staged=fixed.getList("Entities",10).getCompound(0);
+ assertEquals(10,staged.getInt(UpgradePreservation.VERSION));
+ assertEquals(3337,staged.getCompound(UpgradePreservation.SOURCES).getInt("DataVersion"));
+ CompoundTag detached=NativeNbt.update(UpgradePreservation.wrapper(NativeVanillaDataFixer.get()),
+ net.minecraft.util.datafix.fixes.References.ENTITY_TREE,body,512,targetDataVersion());
+ assertEquals(3337,detached.getCompound(UpgradePreservation.SOURCES).getInt("DataVersion"));
+ assertArrayEquals(original,Files.readAllBytes(region));
+ ListTag records=staged.getList("LootableBodiesUnresolved",10);
+ assertEquals(50,records.size());
+ for(int slot=0;slot<48;slot++) {
+ CompoundTag record=records.getCompound(slot);
+ assertEquals("Equipment",record.getString("Source"));
+ assertEquals(before.getList("Equipment",10).getCompound(slot),record.getCompound("Data"));
+ }
+ assertEquals(before,body);
+ }
}
diff --git a/src/testSupport/java/cyano/lootable/buildtest/HistoricalVersions.java b/src/testSupport/java/cyano/lootable/buildtest/HistoricalVersions.java
index 201a624..df05616 100644
--- a/src/testSupport/java/cyano/lootable/buildtest/HistoricalVersions.java
+++ b/src/testSupport/java/cyano/lootable/buildtest/HistoricalVersions.java
@@ -13,6 +13,7 @@ public static int dataVersion(String minecraft) {
if ("1.16.5".equals(minecraft)) return 2586;
if ("1.17.1".equals(minecraft)) return 2730;
if ("1.18.2".equals(minecraft)) return 2975;
+ if ("1.19.4".equals(minecraft)) return 3337;
throw new IllegalArgumentException("Unsupported historical Minecraft " + minecraft);
}
}
diff --git a/tools/fixtures/forge47-server-default.toml b/tools/fixtures/forge47-server-default.toml
new file mode 100644
index 0000000..f34e254
--- /dev/null
+++ b/tools/fixtures/forge47-server-default.toml
@@ -0,0 +1,10 @@
+# Native Forge47.4.28 server spec baseline; packaged output is checked separately.
+[server]
+zombieBaseSummonChance = 0.1
+zombieBabyChance = 0.05
+fullBoundingBoxLadders = false
+removeErroringBlockEntities = false
+removeErroringEntities = false
+permissionHandler = "forge:default_handler"
+useItemWithDurationZero = false
+advertiseDedicatedServerToLan = true
diff --git a/tools/fixtures/native120-flat-level.dat b/tools/fixtures/native120-flat-level.dat
new file mode 100644
index 0000000..ce874c3
Binary files /dev/null and b/tools/fixtures/native120-flat-level.dat differ
diff --git a/tools/fixtures/native120-flat-level.provenance.json b/tools/fixtures/native120-flat-level.provenance.json
new file mode 100644
index 0000000..7c28582
--- /dev/null
+++ b/tools/fixtures/native120-flat-level.provenance.json
@@ -0,0 +1,11 @@
+{
+ "schema": 1,
+ "minecraft": "1.20.1",
+ "forge": "47.4.28",
+ "data_version": 3465,
+ "generator": "minecraft:flat",
+ "sha256": "81E5056FCE49638B40F532B819FC82B0EBBFC954BCF4A7E20E3D6538E8FC0645",
+ "source": "Native120FixtureGenerator via the target native codec",
+ "synthetic": true,
+ "historical_writer_proof": false
+}
diff --git a/tools/hosted_policy.py b/tools/hosted_policy.py
index a501130..b0e6ec2 100644
--- a/tools/hosted_policy.py
+++ b/tools/hosted_policy.py
@@ -42,13 +42,13 @@ def validate_workflow(text):
words=shlex.split(line)
if '|' in words:
split=words.index('|');tail=words[split+1:]
- if tail not in (['tee','$RUNNER_TEMP/native119-build.log'],['tee','-a','$RUNNER_TEMP/native119-build.log']):raise ValueError('Unknown hosted pipe')
+ if tail not in (['tee','$RUNNER_TEMP/native120-build.log'],['tee','-a','$RUNNER_TEMP/native120-build.log']):raise ValueError('Unknown hosted pipe')
words=words[:split]
if words and words[-1]=='2>&1':words=words[:-1]
allowed=[['set','-euo','pipefail'],['chmod','+x','gradlew'],
['python3','-B','tools/hosted_policy.py'],
['python3','-B','tools/hosted_policy.py','--require-qualified-manifest'],
- ['python3','-B','tools/native119_build.py']]
+ ['python3','-B','tools/native120_build.py']]
if words and words[0]=='./gradlew':gradle_roots(shlex.join(words))
elif words not in allowed:raise ValueError('Unknown hosted command: '+line)
launches=[line.strip() for line in text.splitlines() if line.strip().startswith('./gradlew ')]
@@ -64,7 +64,7 @@ def require_qualified_manifest(record):
if record.get('local_qualification_complete') is not True or record.get('qualification_integrity_status')!='verified':
raise ValueError('Local qualification evidence is incomplete or on an integrity hold')
version=record.get('version','')
- if not re.fullmatch(r'\d+\.\d+\.\d+\.119041',version) or record.get('minecraft')!='1.19.4' or record.get('forge')!='45.4.5':raise ValueError('Wrong target qualification')
+ if not re.fullmatch(r'\d+\.\d+\.\d+\.120011',version) or record.get('minecraft')!='1.20.1' or record.get('forge')!='47.4.28':raise ValueError('Wrong target qualification')
base='LootableBodies-'+version
names={'CHANGELOG.txt','SHA256SUMS',base+'.jar',base+'-sources.jar',base+'-javadoc.jar',base+'.pom'}
if set(record.get('files',{}))!=names or any(not re.fullmatch('[A-F0-9]{64}',value or '') for value in record['files'].values()):raise ValueError('Incomplete six-file declaration')
@@ -72,15 +72,17 @@ def require_qualified_manifest(record):
# Publication still needs independently approved/rehashed local evidence.
def check_source(root):
- validate_workflow((root/'.github/workflows/native119.yml').read_text(encoding='utf-8'))
+ validate_workflow((root/'.github/workflows/native120.yml').read_text(encoding='utf-8'))
for workflow in ('codeql-analysis.yml','deploy-release.yml'):
if any('java-version:' in line and '17.0.20.1+1' in line for line in (root/'.github/workflows'/workflow).read_text(encoding='utf-8').splitlines()):
raise ValueError('Non-SemVer JDK input in '+workflow)
if 'feature/**' in(root/'.github/workflows/build.yml').read_text():raise ValueError('Copied legacy CI still catches modern feature branches')
if 'workflow_dispatch:' in(root/'.github/workflows/migration-matrix.yml').read_text():raise ValueError('Copied legacy migration workflow can still dispatch on a modern ref')
guard=(root/'settings.gradle').read_text()
- for required in ("System.getenv('GITHUB_ACTIONS') == 'true'",'org.gradle.api.tasks.testing.Test','org.gradle.api.tasks.JavaExec','Run expensive verification locally before publishing'):
- if required not in guard:raise ValueError('Missing Gradle hosted-execution guard')
+ if any(rule not in guard for rule in ("System.getenv('GITHUB_ACTIONS') == 'true'",
+ 'org.gradle.api.tasks.testing.Test', 'org.gradle.api.tasks.JavaExec',
+ 'Run expensive verification locally before publishing')):
+ raise ValueError('Missing hosted test and game task guard')
runtime=(root/'gradle/runtime.gradle').read_text()
if "tasks.named('check') { dependsOn 'integrationTest' }" in runtime or "tasks.register('localRuntimeVerification')" not in runtime:raise ValueError('Game checks must remain explicit local gates')
policy=json.loads((root/'tools/qualification-policy.json').read_text())
@@ -89,5 +91,5 @@ def check_source(root):
if __name__=='__main__':
parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('--require-qualified-manifest',action='store_true');args=parser.parse_args()
check_source(ROOT)
- if args.require_qualified_manifest:require_qualified_manifest(json.loads((ROOT/'.github/release119.json').read_text()))
+ if args.require_qualified_manifest:require_qualified_manifest(json.loads((ROOT/'.github/release120.json').read_text()))
print('HOSTED_COMPILE_ARCHIVE_POLICY_OK')
diff --git a/tools/local_case_reuse.py b/tools/local_case_reuse.py
index 746c130..21e2d9a 100644
--- a/tools/local_case_reuse.py
+++ b/tools/local_case_reuse.py
@@ -12,9 +12,9 @@
DEPENDENCIES=('gradle.properties','src/integrationTest/resources/log4j2.xml',
'tools/native_runtime.py','tools/runtime_diagnostics.py','tools/command_logs.py',
'tools/prepared_worlds.py','tools/historical_workers.py','tools/release_identity.py',
- 'tools/nbt_regions.py','tools/native119_persistence.py','tools/audit_runtime_logs.py',
- 'tools/native_evidence.py','tools/native119_server_inputs.json',
- 'tools/native119_worker_budget.json','tools/native119_logs.py')
+ 'tools/nbt_regions.py','tools/native120_persistence.py','tools/audit_runtime_logs.py',
+ 'tools/native_evidence.py','tools/native120_server_inputs.json',
+ 'tools/native120_worker_budget.json','tools/native120_logs.py')
def normal_program(text):
tree=ast.parse(text)
@@ -35,7 +35,7 @@ def compare_inputs(old,new):
def audit_normal(home,details,identity):
from verify_world_upgrades import PHASES
- from native119_logs import audit
+ from native120_logs import audit
if details['phases']!=PHASES or details['stamp_mutations'] or not details['source_unchanged']:
raise ValueError('Only one complete unchanged normal case is reusable')
mods=audit_case_mods(home,identity);receipts=[];logs=[]
diff --git a/tools/native120_build.py b/tools/native120_build.py
new file mode 100644
index 0000000..4fb4634
--- /dev/null
+++ b/tools/native120_build.py
@@ -0,0 +1,24 @@
+"""Check local-qualified release identities; no SDK installation or game launches."""
+import argparse,hashlib,json,re
+from pathlib import Path
+from release_identity import MOD_VERSION,MAIN_JAR
+
+def digest(path):return hashlib.sha256(path.read_bytes()).hexdigest().upper()
+
+def check_bundle(manifest,bundle):
+ record=json.loads(Path(manifest).read_text());bundle=Path(bundle)
+ if (record.get('version'),record.get('minecraft'),record.get('forge'))!=(MOD_VERSION,'1.20.1','47.4.28'):
+ raise ValueError('Wrong target release identity')
+ if record.get('local_qualification_complete') is not True:raise ValueError('Local gameplay qualification is incomplete')
+ if record.get('qualification_integrity_status')!='verified':raise ValueError('Local qualification evidence is on an integrity hold or unverified')
+ expected=record.get('files',{})
+ names={'CHANGELOG.txt','SHA256SUMS',MAIN_JAR,MAIN_JAR[:-4]+'-sources.jar',MAIN_JAR[:-4]+'-javadoc.jar',MAIN_JAR[:-4]+'.pom'}
+ if set(expected)!=names or any(not re.fullmatch('[A-F0-9]{64}',v) for v in expected.values()):raise ValueError('Missing accepted six-file identities')
+ if not bundle.is_dir() or any(p.is_symlink() or not p.is_file() for p in bundle.iterdir()):raise ValueError('Invalid or aliased bundle')
+ actual={p.name:digest(p) for p in bundle.iterdir()}
+ if actual!=expected:raise ValueError('Built six-file bundle differs from local qualification')
+ return actual
+
+if __name__=='__main__':
+ parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('--manifest',type=Path,default=Path('.github/release120.json'));parser.add_argument('--bundle',type=Path,default=Path('build/release/bundle'));args=parser.parse_args()
+ check_bundle(args.manifest,args.bundle);print('LOCAL_QUALIFIED_BUNDLE_MATCH')
diff --git a/tools/native120_client.py b/tools/native120_client.py
new file mode 100644
index 0000000..aaf9f36
--- /dev/null
+++ b/tools/native120_client.py
@@ -0,0 +1,198 @@
+"""Pinned native47 client preparation and descriptor-driven Java17 launch."""
+import hashlib
+import json
+import os
+from pathlib import Path
+import platform
+import re
+import struct
+import zipfile
+
+BASE_VERSION = '1.20.1'
+VERSION = '1.20.1-forge-47.4.28'
+BASE_SHA1 = '72ae93b0934df5541860e3e6b72cf883c74da491'
+FORGE_SHA256 = 'C8E1B6EB02F0D72E5BE8C55ACE50BC10AA2C20F082E41B0D32E7D16CC28B7B56'
+
+
+def native_for_platform(library,system,architecture):
+ parts=library['name'].split(':')
+ if len(parts)<4 or not parts[3].startswith('natives-'):
+ return True
+ variants={('windows','amd64'):'natives-windows',('windows','x86'):'natives-windows-x86',
+ ('windows','arm64'):'natives-windows-arm64',('linux','amd64'):'natives-linux',
+ ('osx','amd64'):'natives-macos',('osx','arm64'):'natives-macos-arm64'}
+ if (system,architecture) not in variants:
+ raise RuntimeError('Unsupported native client platform: '+system+'/'+architecture)
+ return parts[3]==variants[system,architecture]
+
+
+def validate_native_image(content,system,architecture):
+ if system!='windows':return
+ machines={'amd64':0x8664,'x86':0x014c,'arm64':0xAA64}
+ if architecture not in machines or len(content)<64 or content[:2]!=b'MZ':
+ raise RuntimeError('Missing or malformed Windows native image')
+ offset=struct.unpack_from('len(content) or content[offset:offset+4]!=b'PE\0\0':
+ raise RuntimeError('Malformed Windows native PE header')
+ if struct.unpack_from('3 and parts[3].startswith('natives-'):
+ extract(path)
+ classifier = library.get('natives', {}).get(system)
+ if classifier:
+ classifier = classifier.replace('${arch}', '32' if architecture == 'x86' else '64')
+ archive = artifact(downloads['classifiers'][classifier])
+ extract(archive)
+ client = runtime/'versions'/BASE_VERSION/(BASE_VERSION+'.jar')
+ expected = base['downloads']['client']['sha1']
+ if not client.is_file() or hashlib.sha1(client.read_bytes()).hexdigest() != expected:
+ raise RuntimeError('Missing or changed official client jar')
+ paths = list(modules.values())+[client]
+ validate_client_paths(paths)
+ for path in [client, *natives.iterdir(), runtime/'versions'/BASE_VERSION/(BASE_VERSION+'.json'), runtime/'versions'/VERSION/(VERSION+'.json')]:
+ inputs[str(path)] = digest(path)
+ record = {'version':VERSION, 'system':system, 'architecture':architecture,
+ 'sdk':str(runtime), 'library_directory':str(runtime/'libraries'),
+ 'classpath':[str(p) for p in paths], 'natives':str(natives), 'files':inputs}
+ (output/'inputs.json').write_text(json.dumps(record,indent=2)+'\n')
+ return record
+
+
+def load(runtime, prepared):
+ from native_runtime import digest, validate_client_paths
+ base, forge = descriptors(runtime)
+ record = json.loads((Path(prepared)/'inputs.json').read_text())
+ if record['version'] != VERSION or Path(record['sdk']) != Path(runtime).resolve():
+ raise RuntimeError('Prepared client belongs to another SDK')
+ system = {'Windows':'windows', 'Darwin':'osx', 'Linux':'linux'}[platform.system()]
+ architecture = 'x86' if platform.architecture()[0] == '32bit' else 'amd64'
+ if (record['system'], record['architecture']) != (system, architecture):
+ raise RuntimeError('Prepared client belongs to another platform')
+ for name, expected in record['files'].items():
+ if not Path(name).is_file() or digest(Path(name)) != expected:
+ raise RuntimeError('Changed prepared native client input: '+name)
+ paths = [Path(p) for p in record['classpath']]
+ if any(str(path) not in record['files'] for path in paths):
+ raise RuntimeError('Unsealed client classpath entry')
+ validate_client_paths(paths)
+ return base, forge, record
+
+
+def expand(arguments, values, *, system, architecture, os_version):
+ result = []
+ for argument in arguments:
+ if isinstance(argument, dict):
+ permitted = not argument.get('rules')
+ for rule in argument.get('rules', []):
+ target = rule.get('os', {})
+ matches = target.get('name',system) == system and target.get('arch',architecture) == architecture
+ if 'version' in target:
+ matches = matches and re.search(target['version'], os_version) is not None
+ for feature, expected in rule.get('features', {}).items():
+ matches = matches and {'is_demo_user':False,'has_custom_resolution':True}.get(feature,False) == expected
+ if matches:
+ permitted = rule['action'] == 'allow'
+ if not permitted:
+ continue
+ argument = argument['value']
+ for part in argument if isinstance(argument,list) else [argument]:
+ rendered = re.sub(r'\$\{([^}]+)\}',lambda match:values[match[1]],part)
+ if '${' in rendered:
+ raise RuntimeError('Unexpanded launcher argument')
+ result.append(rendered)
+ return result
+
+
+def command(java, home, runtime, assets, prepared, *, username, extra=()):
+ java, home, assets = Path(java).resolve(), Path(home).resolve(), Path(assets).resolve()
+ release = dict(line.split('=',1) for line in (java.parent.parent/'release').read_text().splitlines() if '=' in line)
+ if release.get('SEMANTIC_VERSION') != '"17.0.20.1+1"' or release.get('IMPLEMENTOR') != '"Eclipse Adoptium"':
+ raise RuntimeError('Native client requires exact Temurin17.0.20.1+1')
+ base, forge, record = load(runtime,prepared)
+ index = assets/'indexes'/(base['assetIndex']['id']+'.json')
+ if not index.is_file() or hashlib.sha1(index.read_bytes()).hexdigest() != base['assetIndex']['sha1']:
+ raise RuntimeError('Incorrect official native1.20.1 asset index')
+ values = {'version_name':VERSION,'library_directory':record['library_directory'],
+ 'classpath_separator':os.pathsep,'classpath':os.pathsep.join(record['classpath']),
+ 'natives_directory':record['natives'],'launcher_name':'LootableBodiesTests','launcher_version':'1',
+ 'auth_player_name':username,'game_directory':str(home),'assets_root':str(assets),
+ 'assets_index_name':base['assetIndex']['id'],'auth_uuid':'00000000-0000-4000-8000-000000000001',
+ # Native45 explicitly selects OfflineSocialInteractions for0.
+ 'auth_access_token':'0','user_type':'legacy','version_type':'release',
+ # Match native Main's explicit empty defaults for offline users.
+ 'clientid':'','auth_xuid':'',
+ 'resolution_width':'854','resolution_height':'480'}
+ options = dict(system=record['system'],architecture=record['architecture'],os_version=platform.version())
+ # The inherited profile's game version differs from the actual vanilla jar
+ # filename. Forge's JVM ignoreList must exclude that precise base jar from
+ # the transforming module layer, which already contains the native SRG jar.
+ jvm_values = dict(values,version_name=BASE_VERSION)
+ jvm = expand(base['arguments']['jvm']+forge['arguments']['jvm'],jvm_values,**options)
+ game = expand(base['arguments']['game']+forge['arguments']['game'],values,**options)
+ return [str(java),'-Dfile.encoding=UTF-8','-Xmx2G',*jvm,*extra,forge['mainClass'],*game]
diff --git a/tools/native120_client_diagnostics.json b/tools/native120_client_diagnostics.json
new file mode 100644
index 0000000..9b2c030
--- /dev/null
+++ b/tools/native120_client_diagnostics.json
@@ -0,0 +1,14 @@
+{
+ "minecraft": "1.20.1",
+ "forge": "47.4.28",
+ "platform": "Windows amd64",
+ "qualified": true,
+ "events": [
+ "minecraft:item.goat_horn.play",
+ "minecraft:entity.goat.screaming.horn_break"
+ ],
+ "control_sha256": "AA34653717824CFA843F1202A1603A4F26205C472C1776D7D56600E961D6A333",
+ "proof_sha256": "3E0D5C747E39D895D6BD4C6A09C28C7234D6D7B02BE0E0D518043EE56A287855",
+ "sound_definition_sha256": "52131D187D88128BDE6DCD808768213543F01503FAB3ED6A51B2002D5A4C279C",
+ "scope": "Two native unbound sound events reproduced on the same Forge 47 client without the mod or a world. Every other warning and error is audited."
+}
diff --git a/tools/native120_client_logs.py b/tools/native120_client_logs.py
new file mode 100644
index 0000000..dba6bd4
--- /dev/null
+++ b/tools/native120_client_logs.py
@@ -0,0 +1,58 @@
+"""Explicit native47 client diagnostics, separate from strict server/error audits."""
+from pathlib import Path
+import gzip,hashlib,json,re,tempfile
+from native120_logs import audit as strict_audit
+
+def validated_baseline(policy_path=None,proof_path=None):
+ project=Path(__file__).resolve().parents[1]
+ policy_path=Path(policy_path) if policy_path else Path(__file__).with_name('native120_client_diagnostics.json')
+ proof_path=Path(proof_path) if proof_path else project/'.gradle/native120-client-sound-control-01/proof.json'
+ if not policy_path.is_file() or not proof_path.is_file():raise RuntimeError('Missing native client diagnostic qualification')
+ policy=json.loads(policy_path.read_text(encoding='utf-8'))
+ raw=proof_path.read_bytes()
+ if hashlib.sha256(raw).hexdigest().upper()!=policy.get('proof_sha256'):raise RuntimeError('Changed native client diagnostic qualification')
+ proof=json.loads(raw)
+ if tuple(policy.get(k) for k in ('minecraft','forge','platform'))!=('1.20.1','47.4.28','Windows amd64') or policy.get('qualified') is not True:raise RuntimeError('Wrong native client diagnostic target')
+ if any(proof.get(k)!=policy.get(k) for k in ('minecraft','forge','platform','events','control_sha256')):raise RuntimeError('Native client proof identity mismatch')
+ if proof.get('qualified') is not True or proof.get('no_candidate') is not True or proof.get('no_world') is not True or type(proof.get('native_client_exit')) is not int or proof.get('native_client_exit')!=0 or proof.get('original_strict_audit_passed') is not False:raise RuntimeError('Incomplete independent native client control')
+ for group in ('critical_files','raw_control_logs'):
+ if not proof.get(group):raise RuntimeError('Missing native client diagnostic evidence')
+ for name,expected in proof[group].items():
+ path=Path(name)
+ if not path.is_absolute() or not path.is_file() or path.is_symlink() or hashlib.sha256(path.read_bytes()).hexdigest().upper()!=expected:raise RuntimeError('Changed native client diagnostic input: '+name)
+ definition=Path(proof['sound_definition']);data=definition.read_bytes()
+ if hashlib.sha256(data).hexdigest().upper()!=policy.get('sound_definition_sha256'):raise RuntimeError('Changed official sound definitions')
+ definitions=json.loads(data)
+ if policy['events']!=['minecraft:item.goat_horn.play','minecraft:entity.goat.screaming.horn_break'] or any(not name.startswith('minecraft:') or name.removeprefix('minecraft:') in definitions for name in policy['events']):raise RuntimeError('Native diagnostic is no longer an unbound vanilla event')
+ return policy
+
+def audit_client(path,**options):
+ path=Path(path);raw=path.read_bytes()
+ text=(gzip.decompress(raw) if path.name.endswith('.gz') else raw).decode('utf-8')
+ lines=text.splitlines(keepends=True)
+ missing=[line for line in text.splitlines() if re.search(r'(?i)\bMissing (?:texture|model|sound file)\s*:|\bFile .+ does not exist, cannot add it to event',line)]
+ if missing:raise RuntimeError(str(path)+'\n'+'\n'.join(missing))
+ selected=[i for i,line in enumerate(lines) if 'Missing sound for event:' in line]
+ if not selected:
+ result=strict_audit(path,**options)
+ result.update(classified_native_sound_warnings=0,raw_strict_audit_passed=True,residual_strict_audit_passed=True)
+ return result
+ policy=validated_baseline();events=policy['events'];observed=[]
+ pattern=r'^\[\d{2}:\d{2}:\d{2}(?:\.\d{3})?\] \[Render thread/WARN\] \[net\.minecraft\.client\.sounds\.SoundEngine\]: Missing sound for event: (minecraft:[a-z0-9_./]+)$'
+ for index in selected:
+ match=re.fullmatch(pattern,lines[index].rstrip('\r\n'))
+ if match is None:raise RuntimeError('Unclassified native client sound diagnostic: '+lines[index].rstrip())
+ observed.append(match[1])
+ if len(observed)%len(events) or observed!=events*(len(observed)//len(events)):
+ raise RuntimeError('Incomplete, changed or unknown native client sound diagnostic batch')
+ chosen=set(selected);residual=''.join(line for i,line in enumerate(lines) if i not in chosen)
+ # Preserve original bytes and their strict failure. Only a derived audit
+ # view excludes these proved native WARN records; every generic error and
+ # referenced missing-resource check still runs unchanged on that view.
+ with tempfile.TemporaryDirectory(prefix='native120-diagnostic-audit-') as directory:
+ view=Path(directory)/'client.log';view.write_text(residual,encoding='utf-8',newline='')
+ try:result=strict_audit(view,**options)
+ except RuntimeError as error:raise RuntimeError(str(path)+'\n'+str(error)) from error
+ result.update(path=str(path),lines=len(lines),classified_native_sound_warnings=len(observed),classified_native_sound_events=observed,
+ raw_strict_audit_passed=False,residual_strict_audit_passed=True,native_sound_control_proof_sha256=policy['proof_sha256'],raw_sha256=hashlib.sha256(raw).hexdigest().upper())
+ return result
diff --git a/tools/native120_logs.py b/tools/native120_logs.py
new file mode 100644
index 0000000..9e9d93a
--- /dev/null
+++ b/tools/native120_logs.py
@@ -0,0 +1,20 @@
+"""Current-target log contract, separate from retained legacy controls."""
+import gzip,re
+from audit_runtime_logs import audit as _legacy_audit
+
+def audit(path,development=False,upgrade=False,fixture_logins=False,native=True):
+ if fixture_logins:
+ raise RuntimeError('Legacy login-retry classification is not native47 qualification')
+ if path.name.endswith('.log.gz'):
+ with gzip.open(path,'rt',encoding='utf-8') as stream:text=stream.read()
+ else:text=path.read_text(encoding='utf-8')
+ lines=re.sub(r'\x1b\[[0-9;]*m','',text).splitlines()
+ failures=[line for line in lines if re.search(
+ r'/ERROR\]|/FATAL\]|\bERROR (?:Error processing|Unable to locate)|^AL lib: \(EE\)|Caused by:',line)]
+ if failures:raise RuntimeError(str(path)+'\n'+'\n'.join(failures))
+ # Retain all generic exception/assertion/missing-resource checks, without
+ # importing older platform/development/upgrade waivers into this target.
+ result=_legacy_audit(path)
+ result['target_minecraft']='1.20.1'
+ result['legacy_native_controls_used']=False
+ return result
diff --git a/tools/native120_persistence.py b/tools/native120_persistence.py
new file mode 100644
index 0000000..02befb6
--- /dev/null
+++ b/tools/native120_persistence.py
@@ -0,0 +1,40 @@
+"""Read-only native20 saved entity inspection, distinct from old-writer proof."""
+import re,struct
+from nbt_regions import Tag,read_region
+
+def count_persisted(world,wave):
+ if wave not in (-1,0,1,2):raise ValueError('Invalid loaded wave')
+ def selected(region):return wave<0 or region.name==['r.0.0.mca','r.8.8.mca','r.16.16.mca'][wave]
+ names=('lootablebodies.Corpse','lootablebodies:corpse')
+ # A region can still contain unopened old terrain chunks. Inspect both
+ # actual layouts without relabelling untouched terrain as converted20.
+ for region in world.rglob('region/r.*.mca'):
+ if not selected(region):continue
+ for chunk in read_region(region).values():
+ root=chunk['root'].value
+ level=root.get('Level')
+ entries=level.value.get('Entities') if level is not None and level.kind==10 else root.get('entities')
+ if entries is None:continue
+ if entries.kind!=9 or entries.value[0] not in (0,10):raise ValueError('Malformed terrain entity list')
+ if any(e.value.get('id',Tag(8,'')).value in names for e in entries.value[1]):
+ raise ValueError('Loaded corpse remained in terrain storage')
+ count=0;seen=set()
+ for region in world.rglob('entities/r.*.mca'):
+ if not selected(region):continue
+ match=re.fullmatch(r'r\.(-?[0-9]+)\.(-?[0-9]+)\.mca',region.name)
+ if match is None:raise ValueError('Unknown entity region name')
+ rx,rz=map(int,match.groups())
+ for index,chunk in read_region(region).items():
+ root=chunk['root'].value;position=root.get('Position');entities=root.get('Entities')
+ if root.get('DataVersion')!=Tag(3,3465):raise ValueError('Wrong native20 entity-chunk data version')
+ if position is None or position.kind!=11 or len(position.value)!=8 or struct.unpack('>ii',position.value)!=(rx*32+(index&31),rz*32+(index>>5)):
+ raise ValueError('Wrong native20 entity-chunk Position/index')
+ if entities is None or entities.kind!=9 or entities.value[0] not in (0,10):raise ValueError('Wrong native20 root Entities list')
+ for entity in entities.value[1]:
+ if entity.value.get('id',Tag(8,'')).value not in names:continue
+ if entity.value['id']!=Tag(8,'lootablebodies:corpse') or entity.value.get('LootableBodiesDataVersion')!=Tag(3,10):raise ValueError('Wrong native20 corpse identity/marker')
+ identity=entity.value.get('UUID')
+ if identity is None or identity.kind!=11 or len(identity.value)!=16:raise ValueError('Wrong native20 corpse UUID type')
+ if identity.value in seen:raise ValueError('Duplicate persisted corpse UUID')
+ seen.add(identity.value);count+=1
+ return count
diff --git a/tools/native120_server_inputs.json b/tools/native120_server_inputs.json
new file mode 100644
index 0000000..c684b6a
--- /dev/null
+++ b/tools/native120_server_inputs.json
@@ -0,0 +1,109 @@
+{
+ "libraries/com/electronwill/night-config/core/3.6.4/core-3.6.4.jar": "1BC71C67B54BA9C8E5030B3DDAEF7DEE16AE43FEE6850368F76B0CFFEF819700",
+ "libraries/com/electronwill/night-config/toml/3.6.4/toml-3.6.4.jar": "3CB3D0EF3352E204CDA68215FBE41AFDC7F56425EF4FD8E84006964A656117D4",
+ "libraries/com/github/jponge/lzma-java/1.3/lzma-java-1.3.jar": "75CCC0E24D37ED5B8916E1BCFFAD1E067C2B1CFA21B635287F968DB5C3C1963A",
+ "libraries/com/github/oshi/oshi-core/6.2.2/oshi-core-6.2.2.jar": "EFBCA93DF18F3EB16964A92D8342F0674DD5D90DA575EF8E04EF2E4EABEC6910",
+ "libraries/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar": "766AD2A0783F2687962C8AD74CEECC38A28B9F72A2D085EE438B7813E928D0C7",
+ "libraries/com/google/code/gson/gson/2.10/gson-2.10.jar": "0CDD163CE3598A20FC04EEE71B140B24F6F2A3B35F0A499DBBDD9852E83FBFAF",
+ "libraries/com/google/code/gson/gson/2.10.1/gson-2.10.1.jar": "4241C14A7727C34FEEA6507EC801318A3D4A90F070E4525681079FB94EE4C593",
+ "libraries/com/google/errorprone/error_prone_annotations/2.1.3/error_prone_annotations-2.1.3.jar": "03D0329547C13DA9E17C634D1049EA2EAD093925E290567E1A364FD6B1FC7FF8",
+ "libraries/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar": "A171EE4C734DD2DA837E4B16BE9DF4661AFAB72A41ADAF31EB84DFDAF936CA26",
+ "libraries/com/google/guava/guava/25.1-jre/guava-25.1-jre.jar": "6DB0C3A244C397429C2E362EA2837C3622D5B68BB95105D37C21C36E5BC70ABF",
+ "libraries/com/google/guava/guava/31.1-jre/guava-31.1-jre.jar": "A42EDC9CAB792E39FE39BB94F3FCA655ED157FF87A8AF78E1D6BA5B07C4A00AB",
+ "libraries/com/google/j2objc/j2objc-annotations/1.1/j2objc-annotations-1.1.jar": "2994A7EB78F2710BD3D3BFB639B2C94E219CEDAC0D4D084D516E78C16DDDECF6",
+ "libraries/com/mojang/authlib/4.0.43/authlib-4.0.43.jar": "697043D19E0B84B04F011DBEC1BEC3D80C04D8C468E7E4C6B221C0183BC1C0EF",
+ "libraries/com/mojang/brigadier/1.1.8/brigadier-1.1.8.jar": "CF65AFA612FFFBE4DC976115DAC0312F48B9C84B3DDAF58BEC8018A9454440D7",
+ "libraries/com/mojang/datafixerupper/6.0.8/datafixerupper-6.0.8.jar": "6E38E9FC5404BE7E45189AA14CA6058BC85DF20D6760A212B6D48E666ABF0FEF",
+ "libraries/com/mojang/logging/1.1.1/logging-1.1.1.jar": "C1756EAF0685DA94142BACC84309BB7CCDDF65E004638C3299AB645710B5938C",
+ "libraries/com/nothome/javaxdelta/2.0.1/javaxdelta-2.0.1.jar": "A661DFCB749053FC26EADA2A0A8E619A8EAD1CA9EDA29E1698687AA2CDD80152",
+ "libraries/commons-io/commons-io/2.11.0/commons-io-2.11.0.jar": "961B2F6D87DBACC5D54ABF45AB7A6E2495F89B75598962D8C723CEA9BC210908",
+ "libraries/commons-io/commons-io/2.4/commons-io-2.4.jar": "CC6A41DC3EAACC9E440A6BD0D2890B20D36B4EE408FE2D67122F328BB6E01581",
+ "libraries/cpw/mods/bootstraplauncher/1.1.2/bootstraplauncher-1.1.2.jar": "853B61FD165CAF4103B5B969AFBC5B2B037ABC310ED644518CD967B50D5189A4",
+ "libraries/cpw/mods/modlauncher/10.0.9/modlauncher-10.0.9.jar": "A9B3DCDE1434D02702A251EF1D0671DBC90250B349778E1C27151D86C2A4AA81",
+ "libraries/cpw/mods/securejarhandler/2.1.10/securejarhandler-2.1.10.jar": "7EC9207F47BC8847A16566B9A3B4CED8D073C49592897EB30CE2AAF643FDFCAB",
+ "libraries/de/oceanlabs/mcp/mcp_config/1.20.1-20230612.114412/mcp_config-1.20.1-20230612.114412-mappings-merged.txt": "43660881D0A5FFF41CBDE7A818726A8C950C39B1380B65CBC3B2D3290963BFF1",
+ "libraries/de/oceanlabs/mcp/mcp_config/1.20.1-20230612.114412/mcp_config-1.20.1-20230612.114412-mappings.txt": "19AE000175BFB58E5B26406C31A0E501BFB40000B078FEE2F00EA60A8C7ED453",
+ "libraries/de/oceanlabs/mcp/mcp_config/1.20.1-20230612.114412/mcp_config-1.20.1-20230612.114412.zip": "F46D1050D8BED9046886B90A0D2FB80B4C6B5120FC35D32E5D37498DBE0A6D2A",
+ "libraries/de/siegmar/fastcsv/2.2.2/fastcsv-2.2.2.jar": "BFDA71ABCD7C2B62DB498DEBA24C14AEC0E9059A9C183477840AC1BAE9C98AD2",
+ "libraries/io/netty/netty-buffer/4.1.82.Final/netty-buffer-4.1.82.Final.jar": "A7CE7F6D8C1E82B044CD56765DAB4338251596416DE03B1F9A05D7402429C29D",
+ "libraries/io/netty/netty-codec/4.1.82.Final/netty-codec-4.1.82.Final.jar": "170C0CE091C2E1CC1D952D87A6A30DEB773922507BBC609B962B539F03F53407",
+ "libraries/io/netty/netty-common/4.1.82.Final/netty-common-4.1.82.Final.jar": "D5923DB1CC68B26BAF5368F1F76EC2C6100D12DB8406C6DE00EB6DBC08052550",
+ "libraries/io/netty/netty-handler/4.1.82.Final/netty-handler-4.1.82.Final.jar": "31015505983DC57EEC491AD4873411BA51F0F3BEB24D22D509336C72511019CB",
+ "libraries/io/netty/netty-resolver/4.1.82.Final/netty-resolver-4.1.82.Final.jar": "4CFEBA7EC535964A35722178AEECC87EC186DBA0203D04389A850E8E62575383",
+ "libraries/io/netty/netty-transport/4.1.82.Final/netty-transport-4.1.82.Final.jar": "C210069BE4AB4120F5AC7CB5E4A126D3A007E78A64FD44C9971220E013DBAEF1",
+ "libraries/io/netty/netty-transport-classes-epoll/4.1.82.Final/netty-transport-classes-epoll-4.1.82.Final.jar": "73F827FCA18870959B9581C6875130576E5DE684CFB4E6F346F8C1C266840BA3",
+ "libraries/io/netty/netty-transport-native-epoll/4.1.82.Final/netty-transport-native-epoll-4.1.82.Final-linux-aarch_64.jar": "C9551F4622B4C44C9E7DC8C6DC17874B4DDB953A943F402740E64126180EE85E",
+ "libraries/io/netty/netty-transport-native-epoll/4.1.82.Final/netty-transport-native-epoll-4.1.82.Final-linux-x86_64.jar": "D4651F6082FB60F4218F100322DE33E350184F5287900D163698298CF72DDBE7",
+ "libraries/io/netty/netty-transport-native-unix-common/4.1.82.Final/netty-transport-native-unix-common-4.1.82.Final.jar": "1096F16527A51DD5F44CED90636110A429CC7359A9A8A91DB25000B747793429",
+ "libraries/it/unimi/dsi/fastutil/8.5.9/fastutil-8.5.9.jar": "9578BF2A1700CF20D21746A2EE89E57BA1ABBD37FA9FEDA68FF5E9A28473A7F9",
+ "libraries/net/java/dev/jna/jna/5.12.1/jna-5.12.1.jar": "91A814AC4F40D60DEE91D842E1A8AD874C62197984403D0E3C30D39E55CF53B3",
+ "libraries/net/java/dev/jna/jna-platform/5.12.1/jna-platform-5.12.1.jar": "8CE969116CAC95BD61B07A8D5E07174B352E63301473CAAC72C395E3C08488D2",
+ "libraries/net/jodah/typetools/0.6.3/typetools-0.6.3.jar": "408300867804BE05D1083CC0287DD3A21295A9E76090E288013BA5A4182DC96D",
+ "libraries/net/minecraft/server/1.20.1/server-1.20.1.jar": "3AF73A9DC5A102E38147946360DD27D4D70BAE7055BF91CF2151CD5D121B79E0",
+ "libraries/net/minecraft/server/1.20.1-20230612.114412/server-1.20.1-20230612.114412-extra.jar": "16C690F52A92EF272F3E3A5A841CBB24DADFF19DA5A895885B9520C5A108BE38",
+ "libraries/net/minecraft/server/1.20.1-20230612.114412/server-1.20.1-20230612.114412-extra.jar.cache": "F578F0A5FEEB38FBEED2A0A9E9A47148ED7E59DA3CFB5A7CC4CDADF9BEA86820",
+ "libraries/net/minecraft/server/1.20.1-20230612.114412/server-1.20.1-20230612.114412-mappings.txt": "DCA153D20DEFB32CFAC3F069C3BF77B3E13C30AE63847637479D3137E099BB72",
+ "libraries/net/minecraft/server/1.20.1-20230612.114412/server-1.20.1-20230612.114412-slim.jar": "E1C8265F36EEA0587C0BA08706249A17B648C6374D89794A2729526E65419AE7",
+ "libraries/net/minecraft/server/1.20.1-20230612.114412/server-1.20.1-20230612.114412-slim.jar.cache": "8E5F09D8D3BEFF8A1C59883BA4A333B336475815845F20EA1CE2522920A67F8F",
+ "libraries/net/minecraft/server/1.20.1-20230612.114412/server-1.20.1-20230612.114412-srg.jar": "CBA463381DB0C98D6DA71AB879C8EEE4E66146CB1AF5449CF1525F159F56BBA2",
+ "libraries/net/minecraft/server/1.20.1-20230612.114412/server-1.20.1-20230612.114412-unpacked.jar": "80DB52B203AC5DE6E5FC1C5082259DF440FB2B5390B4C61D474E8FBC63CC41F5",
+ "libraries/net/minecraftforge/accesstransformers/8.0.4/accesstransformers-8.0.4.jar": "3225E2A513F658DF686FE8B2AE5B4808525851A2C238CE65E38847CC4FFC6441",
+ "libraries/net/minecraftforge/binarypatcher/1.1.1/binarypatcher-1.1.1.jar": "E6E85E0B78179D0683751F671E7DC8AEE68668883C1B50A2036813A377868177",
+ "libraries/net/minecraftforge/coremods/5.2.4/coremods-5.2.4.jar": "DB32DC040AF2EEA1216A145A3F5FC6BC45D006740FE696DA5A9A445006167C9F",
+ "libraries/net/minecraftforge/eventbus/6.2.33/eventbus-6.2.33.jar": "C9C9AAB0979E08A0489FC497BA433276648D5192608E53535BAD881DFE14D712",
+ "libraries/net/minecraftforge/fmlcore/1.20.1-47.4.28/fmlcore-1.20.1-47.4.28.jar": "2C947EE5F7A9263ACF0080A940D1F333F16B0AD2F8A8F63BC1AC0FC123A9836E",
+ "libraries/net/minecraftforge/fmlearlydisplay/1.20.1-47.4.28/fmlearlydisplay-1.20.1-47.4.28.jar": "F0F5DB77C9F1F82D9C7FCC4A21EEEC814CEE8EF9C821981A7CA0F5E563A0A4BE",
+ "libraries/net/minecraftforge/fmlloader/1.20.1-47.4.28/fmlloader-1.20.1-47.4.28.jar": "9C26DB7EE5AE50E0D3D493FE2F07DD4A10D61C1E678203AF97986E45C269F414",
+ "libraries/net/minecraftforge/forge/1.20.1-47.4.28/forge-1.20.1-47.4.28-server.jar": "993C0EE3F6C3A4E6CF47119A929F490C5B59A8E8CA0A4F3624E1FDB80508F26C",
+ "libraries/net/minecraftforge/forge/1.20.1-47.4.28/forge-1.20.1-47.4.28-universal.jar": "9A6E4341CB5FC92AC6EFDEA4A9BC7E83CDEC75C0F996F9AF376C58AF7EA7BF97",
+ "libraries/net/minecraftforge/forge/1.20.1-47.4.28/unix_args.txt": "543FF32FDB11B4AE5C86EEACD22849F2EF8AC36E687023AAEE88FBD122E30A50",
+ "libraries/net/minecraftforge/forge/1.20.1-47.4.28/win_args.txt": "292607A1120C035382974FE03D00126942E5B2C8DB2762553FAB9137751B83E1",
+ "libraries/net/minecraftforge/ForgeAutoRenamingTool/0.1.22/ForgeAutoRenamingTool-0.1.22-all.jar": "C459A06A0FF995C31335100C84FB018F12B392BB0574B5CF9E448B10F7CB3679",
+ "libraries/net/minecraftforge/forgespi/7.0.1/forgespi-7.0.1.jar": "2298EABA25D066AFD9B329DE7F75E7BF78EF47F3045E6ECB0E40428D875E4B84",
+ "libraries/net/minecraftforge/installertools/1.4.1/installertools-1.4.1.jar": "C475B48E225524F3358F569CA196FC1FE459575DBA45A9317618661E30C4ED1C",
+ "libraries/net/minecraftforge/JarJarFileSystems/0.3.19/JarJarFileSystems-0.3.19.jar": "BEAA1875DD433F2DD152395C3E3C52EF2BB99F20B74860A07D70F7C4626E56D4",
+ "libraries/net/minecraftforge/JarJarMetadata/0.3.19/JarJarMetadata-0.3.19.jar": "6141C710B5B7B673BD544740A31339F0754D7A794CC46B952B7EF7897B7CE265",
+ "libraries/net/minecraftforge/JarJarSelector/0.3.19/JarJarSelector-0.3.19.jar": "F7ACFBAFC9A715EE7D7CE2D539815B3EBCCEBC5064E0A7252C4A6A1C71D1E740",
+ "libraries/net/minecraftforge/jarsplitter/1.1.4/jarsplitter-1.1.4.jar": "7B7B2882921900B74CAD59008761CB18F7DA1A5BCA17CD7648251E0A65070067",
+ "libraries/net/minecraftforge/javafmllanguage/1.20.1-47.4.28/javafmllanguage-1.20.1-47.4.28.jar": "35E4C955621995A930674496B2B5960D264E5F0519CD7A8C736AFA7BC86358F1",
+ "libraries/net/minecraftforge/lowcodelanguage/1.20.1-47.4.28/lowcodelanguage-1.20.1-47.4.28.jar": "34D627E8D4D1F0A3B17D030FD79CC9E333F86D193F0527D6DCD8B080605CBB92",
+ "libraries/net/minecraftforge/mclanguage/1.20.1-47.4.28/mclanguage-1.20.1-47.4.28.jar": "0607648063449D48D89FE2C7703588FF03BCE22849188D33AF424684F726BD80",
+ "libraries/net/minecraftforge/mergetool/1.1.5/mergetool-1.1.5-api.jar": "39D9A3D9277C1402B8E7B97F917CE278241CF1796312AEC85B87F19A376DD99A",
+ "libraries/net/minecraftforge/srgutils/0.4.3/srgutils-0.4.3.jar": "94027D592A75D9437845C5C5A9B21708697E48EC2143B991BD264B4C7D2C0C51",
+ "libraries/net/minecraftforge/srgutils/0.4.9/srgutils-0.4.9.jar": "362DAEAD41B74BAA84B51A98E6BE9896C77CE15B37413CA1B367B01052F39ADE",
+ "libraries/net/minecraftforge/srgutils/0.5.6/srgutils-0.5.6.jar": "1AAF0178677961242C52E101C1BA8E1EE96DAD054295E20EB15B98F4E6FE2E3D",
+ "libraries/net/minecraftforge/unsafe/0.2.0/unsafe-0.2.0.jar": "946937E182AB3B518B5B88264F367E1F35AC12BC83ABED220490C60C717BE239",
+ "libraries/net/minecrell/terminalconsoleappender/1.2.0/terminalconsoleappender-1.2.0.jar": "912E432C9355C4B2025D788ED87FD72BB95542E2FC967AD6875FBBEB6E1C7A1C",
+ "libraries/net/sf/jopt-simple/jopt-simple/5.0.4/jopt-simple-5.0.4.jar": "DF26CC58F235F477DB07F753BA5A3AB243EBE5789D9F89ECF68DD62EA9A66C28",
+ "libraries/net/sf/jopt-simple/jopt-simple/6.0-alpha-3/jopt-simple-6.0-alpha-3.jar": "D440CEB839D6ADB5CFA81AA321813D60255E68A3E9CA564C91126599310ABDE5",
+ "libraries/org/antlr/antlr4-runtime/4.9.1/antlr4-runtime-4.9.1.jar": "A80502C3140AE7ACFBB1F57847C8EB1C101461A969215EC38C3F1EBDFF61AC29",
+ "libraries/org/apache/commons/commons-lang3/3.12.0/commons-lang3-3.12.0.jar": "D919D904486C037F8D193412DA0C92E22A9FA24230B9D67A57855C5C31C7E94E",
+ "libraries/org/apache/logging/log4j/log4j-api/2.19.0/log4j-api-2.19.0.jar": "5CCB24AD9F92E768D0BC456D3061A737951262DF803E004D2CAD096B75A88D60",
+ "libraries/org/apache/logging/log4j/log4j-core/2.19.0/log4j-core-2.19.0.jar": "B4A1796FAB7BFC36DF015C1B4052459147997E8D215A7199D71D05F9E747E4F4",
+ "libraries/org/apache/logging/log4j/log4j-slf4j2-impl/2.19.0/log4j-slf4j2-impl-2.19.0.jar": "825605EACB2D5605B105C53D4108C18125E0F82F62960D0BE583278B9C524F3C",
+ "libraries/org/apache/maven/maven-artifact/3.8.5/maven-artifact-3.8.5.jar": "91172BC294D6EAB02FC9F45F4EA01FD0E418962D128CF489ABEA7B6957D988EE",
+ "libraries/org/checkerframework/checker-qual/2.0.0/checker-qual-2.0.0.jar": "FC8441632F5FA5537492C9F026D1C8B1ADB6A7796F46031B04B4CC0622427995",
+ "libraries/org/codehaus/mojo/animal-sniffer-annotations/1.14/animal-sniffer-annotations-1.14.jar": "2068320BD6BAD744C3673AB048F67E30BEF8F518996FA380033556600669905D",
+ "libraries/org/jline/jline-reader/3.12.1/jline-reader-3.12.1.jar": "4D1037C7110027ED64816C510FAD2AFA0A5AA9BBC38FB9F0E772870823FCAD2A",
+ "libraries/org/jline/jline-terminal/3.12.1/jline-terminal-3.12.1.jar": "EB6115680F732562F0E8E6777F127DECE81E23B25015925F3C4A2A900D48CC9E",
+ "libraries/org/joml/joml/1.10.5/joml-1.10.5.jar": "CAC9F22F83A7AA33EEBDA73C16FF5261E3CB4911B6BAFCF4C79EA486099D0C9A",
+ "libraries/org/openjdk/nashorn/nashorn-core/15.4/nashorn-core-15.4.jar": "6F816E84DFD63A81D4EAA7829C08337BBAFF3EC683FF3BF6BBD90D017A00DC6F",
+ "libraries/org/ow2/asm/asm/9.2/asm-9.2.jar": "B9D4FE4D71938DF38839F0ECA42AAAA64CF8B313D678DA036F0CB3CA199B47F5",
+ "libraries/org/ow2/asm/asm/9.6/asm-9.6.jar": "3C6FAC2424DB3D4A853B669F4E3D1D9C3C552235E19A319673F887083C2303A1",
+ "libraries/org/ow2/asm/asm/9.9.1/asm-9.9.1.jar": "6F3828A215C920059A5EFA2FB55C233D6C54EC5CADCA99CE1B1BDD10077C7DDD",
+ "libraries/org/ow2/asm/asm-analysis/9.2/asm-analysis-9.2.jar": "878FBE521731C072D14D2D65B983B1BEAE6AD06FDA0007B6A8BAE81F73F433C4",
+ "libraries/org/ow2/asm/asm-analysis/9.9.1/asm-analysis-9.9.1.jar": "6260BFFC8EC008DD1B713702C7994E2C94D188A3DA5BEF9E87278A16DF6A7522",
+ "libraries/org/ow2/asm/asm-commons/9.2/asm-commons-9.2.jar": "BE4CE53138A238BB522CD781CF91F3BA5CE2F6CA93EC62D46A162A127225E0A6",
+ "libraries/org/ow2/asm/asm-commons/9.6/asm-commons-9.6.jar": "7AEFD0D5C0901701C69F7513FEDA765FB6BE33AF2CE7AA17C5781FC87657C511",
+ "libraries/org/ow2/asm/asm-commons/9.9.1/asm-commons-9.9.1.jar": "C2319E014CE7199F2B7F7D56D6BB991863168C3F4B6CD6C9F542A4937EF7EF88",
+ "libraries/org/ow2/asm/asm-tree/9.2/asm-tree-9.2.jar": "AABF9BD23091A4EBFC109C1F3EE7CF3E4B89F6BA2D3F51C5243F16B3CFFAE011",
+ "libraries/org/ow2/asm/asm-tree/9.6/asm-tree-9.6.jar": "C43ECF17B539C777E15DA7B5B86553B377E2D39A683DE6285567D5283888E7EF",
+ "libraries/org/ow2/asm/asm-tree/9.9.1/asm-tree-9.9.1.jar": "0F3555096B720B820BBACAB0B515589BEE0200BEE099BDA14C561738AE837BA1",
+ "libraries/org/ow2/asm/asm-util/9.9.1/asm-util-9.9.1.jar": "C5EBBBEAF68126AF094B42FA4800F59BC4413ABD02D95B9AEFAD722CD257E207",
+ "libraries/org/slf4j/slf4j-api/2.0.1/slf4j-api-2.0.1.jar": "B36B99B8D99EA7857554D9B25DFDBD1FB25378C926F7B9E0249983C99335B2C4",
+ "libraries/org/spongepowered/mixin/0.8.5/mixin-0.8.5.jar": "CA15A907E4D1F6B38CEFED51D7DF96A83347DCFB0BC8CED53111519E991D887D",
+ "libraries/trove/trove/1.0.2/trove-1.0.2.jar": "AD68A86CFD0255A2BC0525CE11EC49640CAEA7E34FF91EB89C3193DCCCD77F05",
+ "run.sh": "F63EA57D9143BE3D6AFFB28A83C2F335F1B1CD9C2EA4601284793DFE05C9A9BB",
+ "run.bat": "6A27492A1A3985449F8F4018DA320498DC47EC3B8222498813EDDD5F0F159F2F",
+ "user_jvm_args.txt": "60908CCC470BC913341006FB076BAF75B1CE2D9C132B837AEC133E8D98474278"
+}
diff --git a/tools/native120_worker_budget.json b/tools/native120_worker_budget.json
new file mode 100644
index 0000000..6538025
--- /dev/null
+++ b/tools/native120_worker_budget.json
@@ -0,0 +1,22 @@
+{
+ "minecraft": "1.20.1",
+ "forge": "47.4.28",
+ "qualified": true,
+ "jvm_args": [],
+ "reloads": 200,
+ "jvms": 4,
+ "max_concurrent_workers": 2,
+ "no_candidate": true,
+ "no_worlds": true,
+ "source_and_sdk_seals_unchanged": true,
+ "controller_sha256": "A6A78C3762981344E8BF70E22EFFF19C72769B4FEEB06E18ECA8CC4940037C58",
+ "server_input_manifest_sha256": "6BE4C7C38DF5341F16EEC19FBADC42867468E948D2D6B61E0F861B2D7713A9F1",
+ "qualification_evidence_sha256": "0A9730A7A7F362FF4E8B0DACEABD4365871D3E37CDFBB899FDCDFA57EFCD9275",
+ "preparation_workers_observed": [
+ 23
+ ],
+ "visible_cpus_observed": [
+ 24
+ ],
+ "scope": "Actual native47 default preparation workers plus Main owner-thread apply executor,200reloads/four independent JVMs before any world/candidate. No native40 override or baseline reused."
+}
diff --git a/tools/native_evidence.py b/tools/native_evidence.py
index 91659c7..b50fa01 100644
--- a/tools/native_evidence.py
+++ b/tools/native_evidence.py
@@ -95,7 +95,7 @@ def audit_case_mods(home,report):
def audit_shard(folder,report,*,relocated=False):
positive=folder/'migration-results';aborts=folder/'migration-abort'
records=[];logs=[]
- from native119_logs import audit
+ from native120_logs import audit
for case,details in report['cases'].items():
home=positive/case
mods=audit_case_mods(home,report)
diff --git a/tools/native_runtime.py b/tools/native_runtime.py
index 6b0db45..c44d9e1 100644
--- a/tools/native_runtime.py
+++ b/tools/native_runtime.py
@@ -1,4 +1,4 @@
-"""Disposable official Forge45 launches, separate from the development bootstrap."""
+"""Disposable official Forge47 launches, separate from the development bootstrap."""
import hashlib
import json
import os
@@ -12,10 +12,10 @@
from command_logs import run_logged
from runtime_diagnostics import run_monitored,validate_logging_inputs
-FORGE = 'forge-1.19.4-45.4.5'
-INSTALLER_SHA256 = '1B4475D6ECE01E7242855504FBE0FE37AC79EFD7B3281BF42B8DCF390D421F35'
-SERVER_INPUTS = json.loads(Path(__file__).with_name('native119_server_inputs.json').read_text(encoding='utf-8'))
-WORKER_BUDGET_PATH = Path(__file__).with_name('native119_worker_budget.json')
+FORGE = 'forge-1.20.1-47.4.28'
+INSTALLER_SHA256 = 'D615397A6B2F1E99D4B454E2DB2B4CA2E94BE91D5E0243C8C7E023D965DB39E7'
+SERVER_INPUTS = json.loads(Path(__file__).with_name('native120_server_inputs.json').read_text(encoding='utf-8'))
+WORKER_BUDGET_PATH = Path(__file__).with_name('native120_worker_budget.json')
WORKER_BUDGET_PROOF = json.loads(WORKER_BUDGET_PATH.read_text(encoding='utf-8'))
# Native45 uses its actual default CPU/worker/GC configuration. A new target's
# history cannot borrow an older engine's resource qualification.
@@ -24,19 +24,19 @@
def require_historical_budget():
proof=json.loads(WORKER_BUDGET_PATH.read_text(encoding='utf-8'))
- if tuple(proof.get(key) for key in ('minecraft','forge','reloads','jvms','qualified'))!=('1.19.4','45.4.5',200,4,True):
+ if tuple(proof.get(key) for key in ('minecraft','forge','reloads','jvms','qualified'))!=('1.20.1','47.4.28',200,4,True):
raise RuntimeError('Native45 historical runtime policy has not been qualified')
if proof.get('jvm_args')!=[]:
- raise RuntimeError('Unqualified native45 worker or GC override')
+ raise RuntimeError('Unqualified native47 worker or GC override')
source=Path(__file__).resolve().parents[1]/'src/controlTest/java/buildcontrol/ResourceControl.java'
- manifest=Path(__file__).with_name('native119_server_inputs.json')
+ manifest=Path(__file__).with_name('native120_server_inputs.json')
if proof.get('controller_sha256')!=digest(source) or proof.get('server_input_manifest_sha256')!=digest(manifest):
raise RuntimeError('Native45 resource qualification source or SDK binding changed')
if not __import__('re').fullmatch('[A-F0-9]{64}',proof.get('qualification_evidence_sha256','')):
- raise RuntimeError('Missing native45 resource qualification identity')
- evidence=Path(__file__).resolve().parents[1]/'.gradle/native119-resource-qualification/proof.json'
+ raise RuntimeError('Missing native47 resource qualification identity')
+ evidence=Path(__file__).resolve().parents[1]/'.gradle/native120-resource-qualification/proof.json'
if not evidence.is_file() or digest(evidence)!=proof['qualification_evidence_sha256']:
- raise RuntimeError('Missing or changed actual local native45 resource qualification evidence')
+ raise RuntimeError('Missing or changed actual local native47 resource qualification evidence')
return HISTORICAL_JVM_ARGS
@@ -63,9 +63,9 @@ def verify_server_inputs(distribution):
actual={}
for name,expected in SERVER_INPUTS.items():
artifact=Path(distribution)/name
- if artifact.is_symlink() or not artifact.is_file():raise FileNotFoundError('Missing native45 SDK input: '+name)
+ if artifact.is_symlink() or not artifact.is_file():raise FileNotFoundError('Missing native47 SDK input: '+name)
value=digest(artifact)
- if value!=expected:raise RuntimeError('Incorrect official Forge45 server input: '+name)
+ if value!=expected:raise RuntimeError('Incorrect official Forge47 server input: '+name)
actual[name]=value
return actual
@@ -87,7 +87,7 @@ def prepare_server(home, distribution, candidate=None, probe=None):
distribution = distribution.resolve()
libraries = distribution / 'libraries'
if not libraries.is_dir():
- raise FileNotFoundError('Incomplete official Forge45 server installation')
+ raise FileNotFoundError('Incomplete official Forge47 server installation')
verify_server_inputs(distribution)
home.mkdir(parents=True, exist_ok=False)
(home / 'mods').mkdir()
@@ -148,7 +148,7 @@ def run_java(java, home, phase, *, timeout=240, jvm_args=()):
gc = diagnostics.relative_to(home) / 'gc.txt'
arguments = 'win_args.txt' if os.name == 'nt' else 'unix_args.txt'
command = [str(java), '-Dfile.encoding=UTF-8', '-Xms256m', '-Xmx2G', '-Xlog:gc*,safepoint:file=' + gc.as_posix() + ':time,uptime,level,tags',
- *jvm_args, '@libraries/net/minecraftforge/forge/1.19.4-45.4.5/' + arguments, '--nogui']
+ *jvm_args, '@libraries/net/minecraftforge/forge/1.20.1-47.4.28/' + arguments, '--nogui']
validate_logging_inputs(command)
def initial_diagnostics(process):
# Short refusals and successful phases must retain real thread/heap
@@ -190,14 +190,14 @@ def seed_flat_fixture(home, header, expected_sha256):
version = data.value['DataVersion']
generator = data.value['WorldGenSettings'].value['dimensions'].value[
'minecraft:overworld'].value['generator'].value['type']
- if version.kind != 3 or version.value != 3337 or generator.kind != 8 or generator.value != 'minecraft:flat':
+ if version.kind != 3 or version.value != 3465 or generator.kind != 8 or generator.value != 'minecraft:flat':
raise ValueError('Wrong native version or generator')
except (AttributeError, KeyError, ValueError) as error:
- raise RuntimeError('Expected an explicit native3337 flat codec header') from error
+ raise RuntimeError('Expected an explicit native3465 flat codec header') from error
world.mkdir()
shutil.copy2(header, world/'level.dat')
(home/'native-flat-header-input.json').write_text(json.dumps({
- 'sha256':expected_sha256, 'native_data_version':3337, 'generator':'minecraft:flat',
+ 'sha256':expected_sha256, 'native_data_version':3465, 'generator':'minecraft:flat',
'only_header_copied':True}, indent=2)+'\n', encoding='utf-8')
@@ -214,7 +214,7 @@ def run_server(java, home, phase, *, timeout=240, extra=(), marker=None, message
raise RuntimeError('Native packaged ' + phase + ' failed: ' + str(log))
if list((home / 'crash-reports').glob('*')):
raise RuntimeError('Native runtime crash reports: ' + str(home))
- from native119_logs import audit
+ from native120_logs import audit
audit(log)
for name in ('latest.log', 'debug.log'):
runtime_log = home / (phase+'.'+name)
@@ -265,18 +265,24 @@ def validate_client_paths(paths):
import re
for path in paths:
value=str(path).replace('\\','/').lower();name=path.name.lower()
+ # Forge 47's pinned descriptor includes the three dist-marker classes.
+ # Reject the merge tool itself and any changed or aliased API archive.
+ if (value.endswith('/net/minecraftforge/mergetool/1.1.5/mergetool-1.1.5-api.jar')
+ and not path.is_symlink() and path.is_file()
+ and digest(path) == '39D9A3D9277C1402B8E7B97F917CE278241CF1796312AEC85B87F19A376DD99A'):
+ continue
if any(word in name for word in ('junit','mockito','opentest4j','slime-launcher','legacydev','mergetool','launchwrapper')) or '/mavenizer/' in value or re.match(r'asm(?:-debug-all|-commons|-tree|-util|-analysis)?-[0-5][.-]',name) or re.match(r'lwjgl(?:_util|-platform)?-2[.-]',name):
raise RuntimeError('Development, testing or incompatible legacy library in packaged client: '+str(path))
def client_inputs(runtime, prepared):
- from native119_client import load
+ from native120_client import load
base, forge, record = load(runtime, prepared)
return base, forge, [Path(p) for p in record['classpath']], Path(record['natives'])
def prepare_assets(runtime, cache=None):
- raise RuntimeError('Use pinned native1.19.4 assets; inherited cache-copy hydration is disabled')
+ raise RuntimeError('Use pinned native1.20.1 assets; inherited cache-copy hydration is disabled')
def prepare_client(home, candidate=None, probe=None):
@@ -293,29 +299,29 @@ def prepare_client(home, candidate=None, probe=None):
def client_command(java, home, runtime, assets, *, username='LootableTest', extra=(), prepared=None):
- from native119_client import command
+ from native120_client import command
if prepared is None:
- prepared=Path(__file__).resolve().parents[1]/'.gradle/native119-client-inputs'
+ prepared=Path(__file__).resolve().parents[1]/'.gradle/native120-client-inputs'
return command(java,home,runtime,assets,prepared,username=username,extra=extra)
def install(java, home, client=False):
- """Install native45 locally; its installer owns the server downloads."""
+ """Install native47 locally; its installer owns the server downloads."""
from prepare_migration_inputs import fetch
home=home.resolve();home.mkdir(parents=True,exist_ok=False)
installer=home/(FORGE+'-installer.jar')
- fetch('https://maven.minecraftforge.net/net/minecraftforge/forge/1.19.4-45.4.5/'+installer.name,installer,INSTALLER_SHA256)
+ fetch('https://maven.minecraftforge.net/net/minecraftforge/forge/1.20.1-47.4.28/'+installer.name,installer,INSTALLER_SHA256)
if client:
- from native119_client import BASE_VERSION,BASE_SHA1
+ from native120_client import BASE_VERSION,BASE_SHA1
base=home/'versions'/BASE_VERSION/(BASE_VERSION+'.json')
- _download({'sha1':BASE_SHA1,'url':'https://piston-meta.mojang.com/v1/packages/'+BASE_SHA1+'/1.19.4.json'},base)
+ _download({'sha1':BASE_SHA1,'url':'https://piston-meta.mojang.com/v1/packages/'+BASE_SHA1+'/1.20.1.json'},base)
metadata=json.loads(base.read_text());_download(metadata['downloads']['client'],base.with_suffix('.jar'))
(home/'launcher_profiles.json').write_text('{"profiles":{}}\n')
run_logged([str(java),'-Djava.awt.headless=true','-jar',installer.name,
'--installClient' if client else '--installServer',str(home)],
cwd=home,path=home/'install.log',timeout=600)
if client:
- from native119_client import prepare
+ from native120_client import prepare
inputs=prepare(home,home/'prepared-inputs')['files']
else:
inputs=verify_server_inputs(home)
diff --git a/tools/prepare_native119_tools.py b/tools/prepare_native119_tools.py
new file mode 100644
index 0000000..f1ffdd9
--- /dev/null
+++ b/tools/prepare_native119_tools.py
@@ -0,0 +1,36 @@
+"""Compile an isolated writer against exact public19, never the target20 API."""
+import argparse,json,os,shutil,subprocess
+from pathlib import Path
+from prepare_legacy_tools import prepare,digest
+from command_logs import run_build_input_logged
+
+SOURCE='1a57bd172f5830c2aaf0d91bd7865dfb853c8feb'
+PUBLIC_MAIN='6341FD1976BC195269F61C58885530CCDA0557873FB300E01EEAD657B5975014'
+OUTPUT='native119-seed/lootablebodies-native119-world-fixtures-reobf.jar'
+
+def main():
+ p=argparse.ArgumentParser(description=__doc__)
+ p.add_argument('--output',type=Path,required=True)
+ p.add_argument('--java17-home',type=Path,required=True)
+ p.add_argument('--java25-home',type=Path,required=True)
+ p.add_argument('--public-main',type=Path,required=True)
+ a=p.parse_args();project=Path(__file__).resolve().parents[1];output=a.output.resolve()
+ if digest(a.public_main)!=PUBLIC_MAIN:raise ValueError('Native19 writer requires genuine public6341 main bytes')
+ source=prepare(project,output,SOURCE)
+ env=dict(os.environ,JAVA_HOME=str(a.java17_home.resolve()),GRADLE_USER_HOME='D:/GradleCaches/minecraft-mods-shared',LOOTABLEBODIES_JAVA17_HOME=str(a.java17_home.resolve()),LOOTABLEBODIES_MAVENIZER_JAVA_HOME=str(a.java25_home.resolve()))
+ env.pop('GITHUB_ACTIONS',None)
+ writer_files={f.relative_to(project).as_posix():digest(f) for f in (project/'src/nativeSeed119').rglob('*') if f.is_file()}
+ init=project/'gradle/native119-writer.init.gradle';init_hash=digest(init)
+ (output/'writer-inputs.json').write_text(json.dumps({'source':SOURCE,'public_main_sha256':PUBLIC_MAIN,'files':writer_files,'init_sha256':init_hash},indent=2)+'\n',encoding='utf-8')
+ command=[str(a.java17_home.resolve()/'bin/java.exe'),'-Xmx64m','-Xms64m','-Dfile.encoding=UTF-8','-Dorg.gradle.appname=gradlew','-classpath',str(source/'gradle/wrapper/gradle-wrapper.jar'),'org.gradle.wrapper.GradleWrapperMain','prepareNative119WorldFixtures','--init-script='+str(init),'-PhistoricalWriterSource='+str(project),'--offline','--no-daemon','--no-build-cache','--console=plain','--stacktrace']
+ run_build_input_logged(command,cwd=source,env=env,path=output/'build.log',timeout=1800)
+ artifact=source/'build'/OUTPUT
+ if not artifact.is_file() or not artifact.stat().st_size:raise FileNotFoundError(artifact)
+ if digest(init)!=init_hash or any(digest(project/name)!=value for name,value in writer_files.items()):raise ValueError('Historical writer sources changed during compilation')
+ main=output/'public-main'/a.public_main.name;main.parent.mkdir();shutil.copy2(a.public_main,main)
+ if digest(main)!=PUBLIC_MAIN:raise ValueError('Copied public19 main bytes changed')
+ record={'source':SOURCE,'public_main':str(main),'public_main_sha256':PUBLIC_MAIN,'artifact':OUTPUT,'sha256':digest(artifact),'writer_sources':writer_files,'production_java':'17.0.20.1+1','compiled_against':'Exact released19 native45; target20 classes not used','runtime_qualified':False}
+ (output/'tools.json').write_text(json.dumps(record,indent=2)+'\n',encoding='utf-8')
+ print('PUBLIC19_NATIVE_WRITER_COMPILE_PASS_RUNTIME_PENDING',flush=True)
+
+if __name__=='__main__':main()
diff --git a/tools/prepared_worlds.py b/tools/prepared_worlds.py
index 5317c1d..b39abac 100644
--- a/tools/prepared_worlds.py
+++ b/tools/prepared_worlds.py
@@ -13,8 +13,8 @@ def unique(pairs):
result[name]=value
return result
record=json.loads(path.read_text(encoding='utf-8'),object_pairs_hook=unique)
- if record.get('schema')!=1 or record.get('target_data_version')!=3337:
- raise ValueError('Wrong native19 prepared manifest schema/target')
+ if record.get('schema')!=1 or record.get('target_data_version')!=3465:
+ raise ValueError('Wrong native20 prepared manifest schema/target')
origins=record.get('origins',{})
if set(origins)!=set(expected):raise ValueError('Prepared manifest must contain every exact origin')
paths=[Path(details['world']).resolve() for details in origins.values()]
diff --git a/tools/qualification-policy.json b/tools/qualification-policy.json
index 04d4e98..9452814 100644
--- a/tools/qualification-policy.json
+++ b/tools/qualification-policy.json
@@ -1,18 +1,14 @@
{
"schema": 1,
- "target": "1.18.2",
- "status": "Full functional qualification local; hosted compilation, archive/security audits and small workflow contracts only. Unvalidated phase consolidation remains disabled.",
+ "target": "1.20.1",
+ "status": "Gameplay and migration qualify locally. Hosted checks validate policy, production archives and security.",
"hosted": {
"allowed": [
- "workflow/wrapper contracts",
- "production compilation",
- "archive/metadata/byte audits",
- "CodeQL security analysis"
- ],
- "native118_gradle_roots": [
- "assemble",
- "verifyProductionRuntimeClasspath",
- "prepareReleaseBundle"
+ "policy",
+ "production_archive",
+ "security",
+ "release_contracts",
+ "wrapper_validation"
],
"fork_gradle_build": false,
"full_java_suite": false,
@@ -34,16 +30,17 @@
"original prepared-world manifest"
],
"historical_priority": [
+ "recovery-119",
"recovery-116",
"recovery-115",
"recovery-117",
"original-110",
"released-117"
],
- "historical_origins": 47,
- "historical_cases": 94,
+ "historical_origins": 68,
+ "historical_cases": 136,
"historical_phases_per_case": 10,
- "historical_receipts_including_future_refusals": 1130,
+ "historical_receipts_including_future_refusals": 1634,
"max_historical_workers": 2,
"shared_cache_gradle_writers": 1,
"visibility": "Keep both6200ticks and310seconds and connected restart; no shorter acceptance substitute.",
@@ -59,11 +56,11 @@
],
"requires": "Native disk-equivalence and deliberate loss/duplication/resurrection/deferred-chunk negative controls on next authorised run, then explicit new coverage/receipt schema",
"origins_and_cases_unchanged_if_validated": [
- 47,
- 94
+ 68,
+ 136
],
- "positive_jvms_if_validated": 752,
- "total_receipts_if_validated": 942,
+ "positive_jvms_if_validated": 1088,
+ "total_receipts_if_validated": 1362,
"current_10_phase_contract_remains_authoritative": true
}
}
diff --git a/tools/test_historical_runtime_budget.py b/tools/test_historical_runtime_budget.py
index f62959e..e0facf0 100644
--- a/tools/test_historical_runtime_budget.py
+++ b/tools/test_historical_runtime_budget.py
@@ -10,7 +10,7 @@
class HistoricalRuntimeBudgetTests(unittest.TestCase):
- def test_every_historical_phase_keeps_native45_defaults_without_changing_its_gate(self):
+ def test_every_historical_phase_keeps_native47_defaults_without_changing_its_gate(self):
launches = []
def launch(java, home, phase, **options):
@@ -71,7 +71,7 @@ def test_forged_flag_or_stale_source_binding_cannot_authorize_history(self):
import json
with tempfile.TemporaryDirectory() as directory:
policy=Path(directory)/'policy.json'
- base={'minecraft':'1.19.4','forge':'45.4.5','reloads':200,'jvms':4,'qualified':True,'jvm_args':[]}
+ base={'minecraft':'1.20.1','forge':'47.4.28','reloads':200,'jvms':4,'qualified':True,'jvm_args':[]}
for fields in ({'jvm_args':['-Dmax.bg.threads=1']},{'controller_sha256':'0'*64}):
policy.write_text(json.dumps(dict(base,**fields)))
with patch.object(native_runtime,'WORKER_BUDGET_PATH',policy),patch.object(native_runtime,'run_monitored') as launch:
diff --git a/tools/test_hosted_policy.py b/tools/test_hosted_policy.py
index 563642a..6d1a1d6 100644
--- a/tools/test_hosted_policy.py
+++ b/tools/test_hosted_policy.py
@@ -5,7 +5,7 @@
class HostedPolicyTests(unittest.TestCase):
def test_raw_jdk_version_is_rejected_before_java_preparation(self):
- text=(Path(__file__).resolve().parents[1]/'.github/workflows/native119.yml').read_text()
+ text=(Path(__file__).resolve().parents[1]/'.github/workflows/native120.yml').read_text()
with self.assertRaises(ValueError):validate_workflow(text.replace('17.0.20+101','17.0.20.1+1'))
def test_hosted_roots_allow_archive_builds_and_reject_every_test_or_game_entry(self):
@@ -18,28 +18,28 @@ def test_current_workflow_is_compile_archive_only(self):
root=Path(__file__).resolve().parents[1];check_source(root)
def test_injected_python_games_full_suites_and_sdk_installers_are_rejected(self):
- text=(Path(__file__).resolve().parents[1]/'.github/workflows/native119.yml').read_text()
+ text=(Path(__file__).resolve().parents[1]/'.github/workflows/native120.yml').read_text()
for command in ('python3 -m unittest discover -s tools','python3 tools/verify_world_upgrades.py','python3 tools/native_runtime.py','python3 tools/prepare_native117_tools.py','xvfb-run ./gradlew runClient','python3 tools/new_game_runner.py','curl https://example.invalid/server.jar','java -jar server.jar'):
changed=text.replace(' chmod +x gradlew',' '+command+'\n chmod +x gradlew')
with self.subTest(command=command),self.assertRaises(ValueError):validate_workflow(changed)
def test_missing_fork_guard_or_pipe_failure_guard_is_rejected(self):
- text=(Path(__file__).resolve().parents[1]/'.github/workflows/native119.yml').read_text()
+ text=(Path(__file__).resolve().parents[1]/'.github/workflows/native120.yml').read_text()
for removed in ("if: github.repository == 'MinecraftModDevelopmentMods/LootableBodies'",'set -euo pipefail','shell: bash'):
with self.subTest(removed=removed),self.assertRaises(ValueError):validate_workflow(text.replace(removed,''))
def test_manifest_boolean_cannot_override_an_integrity_hold(self):
- version='2.4.10.119041';base='LootableBodies-'+version
+ version='2.4.11.120011';base='LootableBodies-'+version
names=['CHANGELOG.txt','SHA256SUMS',base+'.jar',base+'-sources.jar',base+'-javadoc.jar',base+'.pom']
- good={'version':version,'minecraft':'1.19.4','forge':'45.4.5','local_qualification_complete':True,'qualification_integrity_status':'verified','files':{name:'A'*64 for name in names}}
+ good={'version':version,'minecraft':'1.20.1','forge':'47.4.28','local_qualification_complete':True,'qualification_integrity_status':'verified','files':{name:'A'*64 for name in names}}
require_qualified_manifest(good)
for field,value in [('qualification_integrity_status','blocked_receipt_mismatch'),('qualification_integrity_status',None),('local_qualification_complete',False),('minecraft','1.17.1')]:
changed=dict(good);changed[field]=value
with self.subTest(field=field,value=value),self.assertRaises(ValueError):require_qualified_manifest(changed)
def test_missing_or_extra_file_declaration_is_fatal(self):
- version='2.4.10.119041';base='LootableBodies-'+version
- good={'version':version,'minecraft':'1.19.4','forge':'45.4.5','local_qualification_complete':True,'qualification_integrity_status':'verified','files':{name:'A'*64 for name in ['CHANGELOG.txt','SHA256SUMS',base+'.jar',base+'-sources.jar',base+'-javadoc.jar',base+'.pom']}}
+ version='2.4.11.120011';base='LootableBodies-'+version
+ good={'version':version,'minecraft':'1.20.1','forge':'47.4.28','local_qualification_complete':True,'qualification_integrity_status':'verified','files':{name:'A'*64 for name in ['CHANGELOG.txt','SHA256SUMS',base+'.jar',base+'-sources.jar',base+'-javadoc.jar',base+'.pom']}}
for extra in (False,True):
changed=json.loads(json.dumps(good))
if extra:changed['files']['extra.jar']='B'*64
diff --git a/tools/test_migration_ci.py b/tools/test_migration_ci.py
index 9d01eb0..8ae41e3 100644
--- a/tools/test_migration_ci.py
+++ b/tools/test_migration_ci.py
@@ -36,10 +36,14 @@ def test_six_shards_cover_every_origin_exactly_once(self):
for value in [-1,6]:
with self.assertRaises(ValueError):ci.partition(list(ORIGINS),value)
- def test_all_1140_phases_and_228_refusals_retain_every_earlier_case(self):
+ def test_all_1360_phases_and_272_refusals_retain_every_earlier_case(self):
result = ci.aggregate(self.reports(), list(ORIGINS), 'a'*40)
- self.assertEqual((114,1140,228),(len(result['cases']),result['jvm_phases'],result['abort_cases']))
- native18=[name for name in result['cases'] if not name.startswith(('released-118-','recovery-118-','via118-'))]
+ self.assertEqual((136,1360,272),(len(result['cases']),result['jvm_phases'],result['abort_cases']))
+ native19=[name for name in result['cases'] if not name.startswith(('released-119-','recovery-119-','via119-'))]
+ self.assertEqual(114,len(native19))
+ self.assertEqual(1140,sum(len(result['cases'][name]['phases']) for name in native19))
+ self.assertEqual(228,sum(len(result['aborts'][name]['conditions']) for name in native19))
+ native18=[name for name in native19 if not name.startswith(('released-118-','recovery-118-','via118-'))]
self.assertEqual(94,len(native18));self.assertEqual(940,sum(len(result['cases'][name]['phases']) for name in native18));self.assertEqual(188,sum(len(result['aborts'][name]['conditions']) for name in native18))
native17=[name for name in native18 if not name.startswith(('released-117-','recovery-117-','via117-'))]
self.assertEqual(76,len(native17));self.assertEqual(760,sum(len(result['cases'][name]['phases']) for name in native17))
diff --git a/tools/test_native119_build.py b/tools/test_native120_build.py
similarity index 87%
rename from tools/test_native119_build.py
rename to tools/test_native120_build.py
index c82786a..1c9803d 100644
--- a/tools/test_native119_build.py
+++ b/tools/test_native120_build.py
@@ -1,16 +1,16 @@
import json,tempfile,unittest
from pathlib import Path
-from native119_build import check_bundle
+from native120_build import check_bundle
from native_runtime import digest
from release_identity import MOD_VERSION,MAIN_JAR
-class Native119BuildContractTests(unittest.TestCase):
+class Native120BuildContractTests(unittest.TestCase):
def test_missing_changed_extra_and_unqualified_inputs_are_fatal(self):
with tempfile.TemporaryDirectory() as directory:
root=Path(directory);bundle=root/'bundle';bundle.mkdir();manifest=root/'manifest.json'
names=['CHANGELOG.txt','SHA256SUMS',MAIN_JAR,MAIN_JAR[:-4]+'-sources.jar',MAIN_JAR[:-4]+'-javadoc.jar',MAIN_JAR[:-4]+'.pom']
for name in names:(bundle/name).write_bytes(name.encode())
- record={'version':MOD_VERSION,'minecraft':'1.19.4','forge':'45.4.5','local_qualification_complete':True,'qualification_integrity_status':'verified','files':{p.name:digest(p) for p in bundle.iterdir()}}
+ record={'version':MOD_VERSION,'minecraft':'1.20.1','forge':'47.4.28','local_qualification_complete':True,'qualification_integrity_status':'verified','files':{p.name:digest(p) for p in bundle.iterdir()}}
manifest.write_text(json.dumps(record));self.assertEqual(record['files'],check_bundle(manifest,bundle))
for mutation in ['unqualified','integrity-hold','wrong-target','missing-file','changed-file','extra-file','missing-identity']:
with self.subTest(mutation=mutation):
@@ -29,15 +29,15 @@ def test_missing_changed_extra_and_unqualified_inputs_are_fatal(self):
def test_hosted_target_contains_no_game_or_sdk_command(self):
from pathlib import Path
import re
- text=(Path(__file__).resolve().parents[1]/'.github/workflows/native119.yml').read_text()
+ text=(Path(__file__).resolve().parents[1]/'.github/workflows/native120.yml').read_text()
commands='\n'.join(re.findall(r'(?m)^ run: \|\n((?: .*\n)+)',text))
self.assertIn('./gradlew assemble verifyProductionRuntimeClasspath prepareReleaseBundle ',commands)
for forbidden in ('./gradlew test','./gradlew build','./gradlew check','unittest discover','verify_world_upgrades.py','native_runtime.py','verify_packaged','native117_ci.py','integrationTest','--seed-only'):
self.assertNotIn(forbidden,commands)
uploads=re.findall(r'(?m)^ uses: actions/upload-artifact@[^\n]+\n(.*?)(?=^ -|\Z)',text,re.S)
self.assertEqual(2,len(uploads));self.assertTrue(all('if-no-files-found: error' in s for s in uploads))
- independent=(Path(__file__).resolve().parents[1]/'.github/workflows/artifact-audit-1.19.yml').read_text()
- self.assertIn('python3 -B tools/native119_build.py --bundle audited',independent)
+ independent=(Path(__file__).resolve().parents[1]/'.github/workflows/artifact-audit-1.20.yml').read_text()
+ self.assertIn('python3 -B tools/native120_build.py --bundle audited',independent)
self.assertIn('run-id: ${{ github.event.workflow_run.id }}',independent)
self.assertIn('if-no-files-found: error',independent)
for forbidden in ('./gradlew','actions/setup-java@','native_runtime.py','verify_packaged'):
diff --git a/tools/test_native119_client.py b/tools/test_native120_client.py
similarity index 85%
rename from tools/test_native119_client.py
rename to tools/test_native120_client.py
index 9bd41c3..d902d58 100644
--- a/tools/test_native119_client.py
+++ b/tools/test_native120_client.py
@@ -1,10 +1,10 @@
import hashlib,json,shutil,struct,tempfile,unittest,zipfile
from pathlib import Path
from unittest.mock import patch
-import native119_client as client
+import native120_client as client
-class Native119ArchitectureTests(unittest.TestCase):
+class Native120ArchitectureTests(unittest.TestCase):
def test_os_only_rules_cannot_select_other_windows_architectures(self):
libraries=json.loads((Path(__file__).parent/'fixtures/native119-native-library-excerpt.json').read_text())['libraries']
for architecture,expected in [('amd64','natives-windows'),('x86','natives-windows-x86'),('arm64','natives-windows-arm64')]:
@@ -37,9 +37,9 @@ def test_current_native_artifacts_select_and_extract_only_the_actual_platform(se
record['sha1']=hashlib.sha1(path.read_bytes()).hexdigest()
base={'libraries':libraries,'downloads':{'client':{'sha1':hashlib.sha1(b'unit-client').hexdigest()}}}
forge={'libraries':[]}
- version=root/'sdk/versions/1.19.4';version.mkdir(parents=True);(version/'1.19.4.jar').write_bytes(b'unit-client');(version/'1.19.4.json').write_text('{}')
- profile=root/'sdk/versions/1.19.4-forge-45.4.5';profile.mkdir();(profile/'1.19.4-forge-45.4.5.json').write_text('{}')
- with patch.object(client,'descriptors',return_value=(base,forge)),patch.object(client,'BASE_VERSION','1.19.4'),patch.object(client,'VERSION','1.19.4-forge-45.4.5'),patch.object(client.platform,'system',return_value='Windows'),patch.object(client.platform,'architecture',return_value=('64bit','')):
+ version=root/'sdk/versions/1.20.1';version.mkdir(parents=True);(version/'1.20.1.jar').write_bytes(b'unit-client');(version/'1.20.1.json').write_text('{}')
+ profile=root/'sdk/versions/1.20.1-forge-47.4.28';profile.mkdir();(profile/'1.20.1-forge-47.4.28.json').write_text('{}')
+ with patch.object(client,'descriptors',return_value=(base,forge)),patch.object(client,'BASE_VERSION','1.20.1'),patch.object(client,'VERSION','1.20.1-forge-47.4.28'),patch.object(client.platform,'system',return_value='Windows'),patch.object(client.platform,'architecture',return_value=('64bit','')):
prepared=client.prepare(root/'sdk',root/'prepared')
print('NATIVE_CLIENT_UNIT_FIXTURE='+str(root))
native=Path(prepared['natives'])/'glfw.dll'
diff --git a/tools/test_native119_client_contracts.py b/tools/test_native120_client_contracts.py
similarity index 73%
rename from tools/test_native119_client_contracts.py
rename to tools/test_native120_client_contracts.py
index e782fde..10e4efe 100644
--- a/tools/test_native119_client_contracts.py
+++ b/tools/test_native120_client_contracts.py
@@ -4,15 +4,15 @@
import tempfile
import unittest
from unittest.mock import patch
-import native119_client
+import native120_client
import native_runtime
-class Native119ClientContractTests(unittest.TestCase):
+class Native120ClientContractTests(unittest.TestCase):
def test_native_module_metadata_is_used_instead_of_legacy_classpath_only_launch(self):
- with patch('native119_client.command',return_value=['native45-command']) as launch:
+ with patch('native120_client.command',return_value=['native47-command']) as launch:
result=native_runtime.client_command(Path('java'),Path('home'),Path('sdk'),Path('assets'),prepared=Path('prepared'),username='Player',extra=['-Dprobe=true'])
- self.assertEqual(['native45-command'],result)
+ self.assertEqual(['native47-command'],result)
self.assertEqual(Path('prepared'),launch.call_args.args[4])
self.assertEqual('Player',launch.call_args.kwargs['username'])
@@ -21,39 +21,39 @@ def test_feature_os_and_version_rules_do_not_enable_demo_or_wrong_platform_flags
{'rules':[{'action':'allow','features':{'has_custom_resolution':True}}],'value':['--width','${width}']},
{'rules':[{'action':'allow','os':{'name':'windows','version':'^10\\.'}}],'value':'-Dos.version=10.0'},
{'rules':[{'action':'allow','os':{'name':'osx'}}],'value':'-XstartOnFirstThread'}]
- self.assertEqual(['--width','854','-Dos.version=10.0'],native119_client.expand(arguments,{'width':'854'},system='windows',architecture='amd64',os_version='10.0.26200'))
+ self.assertEqual(['--width','854','-Dos.version=10.0'],native120_client.expand(arguments,{'width':'854'},system='windows',architecture='amd64',os_version='10.0.26200'))
def test_unknown_placeholder_fails_before_a_process_can_be_started(self):
with self.assertRaises(KeyError):
- native119_client.expand(['${unrecognised}'],{},system='windows',architecture='amd64',os_version='10.0')
+ native120_client.expand(['${unrecognised}'],{},system='windows',architecture='amd64',os_version='10.0')
def test_jvm_filters_the_actual_base_jar_while_game_keeps_its_forge_profile_version(self):
with tempfile.TemporaryDirectory() as directory:
root=Path(directory);java=root/'jdk/bin/java.exe';java.parent.mkdir(parents=True)
(java.parent.parent/'release').write_text('SEMANTIC_VERSION="17.0.20.1+1"\nIMPLEMENTOR="Eclipse Adoptium"\n')
- index=root/'assets/indexes/3.json';index.parent.mkdir(parents=True);index.write_bytes(b'{}')
- base={'assetIndex':{'id':'3','sha1':hashlib.sha1(b'{}').hexdigest()},'arguments':{'jvm':['-cp','${classpath}'],'game':['--version','${version_name}','--accessToken','${auth_access_token}','--clientId','${clientid}','--xuid','${auth_xuid}']}}
+ index=root/'assets/indexes/5.json';index.parent.mkdir(parents=True);index.write_bytes(b'{}')
+ base={'assetIndex':{'id':'5','sha1':hashlib.sha1(b'{}').hexdigest()},'arguments':{'jvm':['-cp','${classpath}'],'game':['--version','${version_name}','--accessToken','${auth_access_token}','--clientId','${clientid}','--xuid','${auth_xuid}']}}
forge={'arguments':{'jvm':['-DignoreList=forge-,${version_name}.jar'],'game':['--launchTarget','forgeclient']},'mainClass':'cpw.mods.bootstraplauncher.BootstrapLauncher'}
- record={'library_directory':str(root/'sdk/libraries'),'classpath':[str(root/'1.19.4.jar')],'natives':str(root/'natives'),'system':'windows','architecture':'amd64'}
- with patch('native119_client.load',return_value=(base,forge,record)):
- result=native119_client.command(java,root/'home',root/'sdk',root/'assets',root/'prepared',username='Fixture')
- self.assertIn('-DignoreList=forge-,1.19.4.jar',result)
- self.assertEqual('1.19.4-forge-45.4.5',result[result.index('--version')+1])
+ record={'library_directory':str(root/'sdk/libraries'),'classpath':[str(root/'1.20.1.jar')],'natives':str(root/'natives'),'system':'windows','architecture':'amd64'}
+ with patch('native120_client.load',return_value=(base,forge,record)):
+ result=native120_client.command(java,root/'home',root/'sdk',root/'assets',root/'prepared',username='Fixture')
+ self.assertIn('-DignoreList=forge-,1.20.1.jar',result)
+ self.assertEqual('1.20.1-forge-47.4.28',result[result.index('--version')+1])
self.assertEqual('0',result[result.index('--accessToken')+1])
self.assertEqual('',result[result.index('--clientId')+1]);self.assertEqual('',result[result.index('--xuid')+1])
def test_unsealed_classpath_and_changed_native_bytes_are_rejected(self):
with tempfile.TemporaryDirectory() as directory:
root=Path(directory);library=root/'native.dll';library.write_bytes(b'original')
- record={'version':native119_client.VERSION,'sdk':str(root.resolve()),'system':'windows','architecture':'amd64',
+ record={'version':native120_client.VERSION,'sdk':str(root.resolve()),'system':'windows','architecture':'amd64',
'files':{str(library):native_runtime.digest(library)},'classpath':[str(root/'unsealed.jar')]}
(root/'inputs.json').write_text(json.dumps(record))
- with patch('native119_client.descriptors',return_value=({},{})),patch('native119_client.platform.system',return_value='Windows'),patch('native119_client.platform.architecture',return_value=('64bit','')):
+ with patch('native120_client.descriptors',return_value=({},{})),patch('native120_client.platform.system',return_value='Windows'),patch('native120_client.platform.architecture',return_value=('64bit','')):
with self.assertRaisesRegex(RuntimeError,'Unsealed'):
- native119_client.load(root,root)
+ native120_client.load(root,root)
library.write_bytes(b'changed')
with self.assertRaisesRegex(RuntimeError,'Changed prepared'):
- native119_client.load(root,root)
+ native120_client.load(root,root)
if __name__=='__main__':unittest.main()
diff --git a/tools/test_native119_client_logs.py b/tools/test_native120_client_logs.py
similarity index 78%
rename from tools/test_native119_client_logs.py
rename to tools/test_native120_client_logs.py
index 74320d6..7241bf3 100644
--- a/tools/test_native119_client_logs.py
+++ b/tools/test_native120_client_logs.py
@@ -1,14 +1,14 @@
-"""Synthetic diagnostic counterexamples; independent native60 is separate proof."""
+"""Synthetic diagnostic counterexamples; independent Forge 47 is separate proof."""
from pathlib import Path
import hashlib,json,tempfile,unittest
from unittest.mock import patch
-import native119_client_logs as logs
-from native119_logs import audit as strict_audit
+import native120_client_logs as logs
+from native120_logs import audit as strict_audit
-EVENTS=['minecraft:item.brush.brushing','minecraft:item.brush.brush_sand_completed','minecraft:item.goat_horn.play','minecraft:entity.goat.screaming.horn_break','minecraft:music.overworld.cherry_grove']
+EVENTS=['minecraft:item.goat_horn.play','minecraft:entity.goat.screaming.horn_break']
def lines(events=EVENTS):return ''.join('[12:00:00] [Render thread/WARN] [net.minecraft.client.sounds.SoundEngine]: Missing sound for event: '+name+'\n' for name in events)
-class Native119ClientDiagnosticTests(unittest.TestCase):
+class Native120ClientDiagnosticTests(unittest.TestCase):
def check(self,text):
with tempfile.TemporaryDirectory() as directory:
p=Path(directory)/'client.log';p.write_text(text,encoding='utf-8')
@@ -16,7 +16,7 @@ def check(self,text):
def test_complete_proved_native_batch_is_explicitly_classified(self):
for text in [lines(),lines().replace('[12:00:00]','[12:00:00.123]')]:
report=self.check(text+'[12:00:01] [Render thread/INFO] Completed synthetic fixture\n')
- self.assertEqual(5,report['classified_native_sound_warnings']);self.assertFalse(report['raw_strict_audit_passed']);self.assertTrue(report['residual_strict_audit_passed'])
+ self.assertEqual(2,report['classified_native_sound_warnings']);self.assertFalse(report['raw_strict_audit_passed']);self.assertTrue(report['residual_strict_audit_passed'])
def test_missing_or_changed_control_proof_cannot_qualify(self):
with tempfile.TemporaryDirectory() as directory:
p=Path(directory)/'client.log';p.write_text(lines(),encoding='utf-8')
@@ -38,10 +38,10 @@ def test_actual_proof_reader_rejects_changed_resource_proof_target_and_control(s
with self.subTest(mutation=mutation),tempfile.TemporaryDirectory() as directory:
root=Path(directory);definition=root/'sounds.json';definition.write_text('{}');critical=root/'control.fixture';critical.write_text('synthetic independent control input');rawlog=root/'native.log';rawlog.write_text('synthetic native fixture')
sha=lambda p:hashlib.sha256(p.read_bytes()).hexdigest().upper()
- proof={'minecraft':'1.19.4','forge':'45.4.5','platform':'Windows amd64','qualified':True,'no_candidate':True,'no_world':True,'native_client_exit':0,'events':EVENTS,'control_sha256':'C'*64,'sound_definition':str(definition),'critical_files':{str(definition):sha(definition),str(critical):sha(critical)},'raw_control_logs':{str(rawlog):sha(rawlog)},'original_strict_audit_passed':False}
+ proof={'minecraft':'1.20.1','forge':'47.4.28','platform':'Windows amd64','qualified':True,'no_candidate':True,'no_world':True,'native_client_exit':0,'events':EVENTS,'control_sha256':'C'*64,'sound_definition':str(definition),'critical_files':{str(definition):sha(definition),str(critical):sha(critical)},'raw_control_logs':{str(rawlog):sha(rawlog)},'original_strict_audit_passed':False}
saved=root/'proof.json';saved.write_text(json.dumps(proof))
- policy={'minecraft':'1.19.4','forge':'45.4.5','platform':'Windows amd64','qualified':True,'proof_sha256':sha(saved),'events':EVENTS,'control_sha256':'C'*64,'sound_definition_sha256':sha(definition)}
- if mutation=='resource':definition.write_text('{"item.brush.brushing":{}}')
+ policy={'minecraft':'1.20.1','forge':'47.4.28','platform':'Windows amd64','qualified':True,'proof_sha256':sha(saved),'events':EVENTS,'control_sha256':'C'*64,'sound_definition_sha256':sha(definition)}
+ if mutation=='resource':definition.write_text('{"item.goat_horn.play":{}}')
elif mutation=='proof':saved.write_text('{}')
elif mutation=='missing':critical.unlink()
elif mutation=='target':policy['forge']='40.3.12'
diff --git a/tools/test_native119_flat_header.py b/tools/test_native120_flat_header.py
similarity index 85%
rename from tools/test_native119_flat_header.py
rename to tools/test_native120_flat_header.py
index 25534a7..9e93ffd 100644
--- a/tools/test_native119_flat_header.py
+++ b/tools/test_native120_flat_header.py
@@ -7,8 +7,8 @@
from nbt_regions import Tag,encode
-class Native119FlatHeaderTests(unittest.TestCase):
- def header(self,root,version=3337,generator='minecraft:flat'):
+class Native120FlatHeaderTests(unittest.TestCase):
+ def header(self,root,version=3465,generator='minecraft:flat'):
settings=Tag(10,{'dimensions':Tag(10,{'minecraft:overworld':Tag(10,{
'generator':Tag(10,{'type':Tag(8,generator)})})})})
data=encode('',Tag(10,{'Data':Tag(10,{'DataVersion':Tag(3,version),'WorldGenSettings':settings})}))
@@ -22,7 +22,7 @@ def test_only_the_explicit_sealed_header_is_copied(self):
self.assertEqual(before,(home/'world/level.dat').read_bytes());self.assertEqual(before,header.read_bytes())
self.assertEqual(['level.dat'],[p.name for p in (home/'world').iterdir()])
def test_wrong_hash_target_or_generator_refuses_before_creating_world(self):
- for version,generator,bad_hash in [(3337,'minecraft:flat',True),(2586,'minecraft:flat',False),(2730,'minecraft:flat',False),(2975,'minecraft:flat',False),(3337,'minecraft:noise',False)]:
+ for version,generator,bad_hash in [(3465,'minecraft:flat',True),(2586,'minecraft:flat',False),(2730,'minecraft:flat',False),(2975,'minecraft:flat',False),(3465,'minecraft:noise',False)]:
with self.subTest(version=version,generator=generator,bad_hash=bad_hash),tempfile.TemporaryDirectory() as directory:
root=Path(directory);header,digest=self.header(root,version,generator);home=self.home(root)
with self.assertRaises(RuntimeError):seed_flat_fixture(home,header,'0'*64 if bad_hash else digest)
@@ -34,9 +34,9 @@ def test_existing_world_is_never_overwritten(self):
with self.assertRaises(FileExistsError):seed_flat_fixture(home,header,digest)
self.assertEqual(b'existing world bytes',protected.read_bytes())
- def test_actual_native19_codec_header_is_a_sealed_current_target_input(self):
- header=Path(__file__).parent/'fixtures/native119-flat-level.dat'
- expected='9792ED9B0BF8A00ACB6BFFAEB50DC8B9C462CD9B6C96A9E6ED5CA65CFD534A55'
+ def test_actual_native20_codec_header_is_a_sealed_current_target_input(self):
+ header=Path(__file__).parent/'fixtures/native120-flat-level.dat'
+ expected='81E5056FCE49638B40F532B819FC82B0EBBFC954BCF4A7E20E3D6538E8FC0645'
self.assertEqual(expected,hashlib.sha256(header.read_bytes()).hexdigest().upper())
with tempfile.TemporaryDirectory() as directory:
home=self.home(Path(directory));seed_flat_fixture(home,header,expected)
diff --git a/tools/test_native119_infrastructure.py b/tools/test_native120_infrastructure.py
similarity index 61%
rename from tools/test_native119_infrastructure.py
rename to tools/test_native120_infrastructure.py
index 0f067c4..0bc602e 100644
--- a/tools/test_native119_infrastructure.py
+++ b/tools/test_native120_infrastructure.py
@@ -3,45 +3,53 @@
import unittest
import native_runtime as runtime
-class Native119InfrastructureTest(unittest.TestCase):
+class Native120InfrastructureTest(unittest.TestCase):
def test_native_distribution_and_every_server_input_use_the_target(self):
- self.assertEqual('forge-1.19.4-45.4.5',runtime.FORGE)
- self.assertEqual('1B4475D6ECE01E7242855504FBE0FE37AC79EFD7B3281BF42B8DCF390D421F35',runtime.INSTALLER_SHA256)
- self.assertEqual(105,len(runtime.SERVER_INPUTS))
- self.assertEqual('FFA7F65BBCB88AEA51AC5B2D37DCCE89DFCDEED0E54EDCCD6CB915E084E288D3',
- runtime.SERVER_INPUTS['libraries/net/minecraftforge/forge/1.19.4-45.4.5/forge-1.19.4-45.4.5-server.jar'])
- self.assertEqual('5CFAE247E85ECBB7A0E98250635C6DC94C711B8DF74BEF2ACBF18A39792563B6',
- runtime.SERVER_INPUTS['libraries/net/minecraftforge/forge/1.19.4-45.4.5/win_args.txt'])
+ self.assertEqual('forge-1.20.1-47.4.28',runtime.FORGE)
+ self.assertEqual('D615397A6B2F1E99D4B454E2DB2B4CA2E94BE91D5E0243C8C7E023D965DB39E7',runtime.INSTALLER_SHA256)
+ self.assertEqual(107,len(runtime.SERVER_INPUTS))
+ self.assertEqual('993C0EE3F6C3A4E6CF47119A929F490C5B59A8E8CA0A4F3624E1FDB80508F26C',
+ runtime.SERVER_INPUTS['libraries/net/minecraftforge/forge/1.20.1-47.4.28/forge-1.20.1-47.4.28-server.jar'])
+ self.assertEqual('292607A1120C035382974FE03D00126942E5B2C8DB2762553FAB9137751B83E1',
+ runtime.SERVER_INPUTS['libraries/net/minecraftforge/forge/1.20.1-47.4.28/win_args.txt'])
self.assertFalse(any('1.16.5' in path for path in runtime.SERVER_INPUTS))
def test_development_assets_use_the_native_index(self):
text=(Path(__file__).resolve().parents[1]/'gradle/assets.gradle').read_text()
- self.assertIn("minecraftAssetIndex = '3'",text)
- self.assertIn("1.19.4.json",text)
+ self.assertIn("minecraftAssetIndex = '5'",text)
+ self.assertIn("1.20.1.json",text)
self.assertNotIn('e9d0adb8f642abe422909ede50f651b2b58a3573',text)
def test_every_released115_history_extends_the_complete_earlier_matrix(self):
from verify_world_upgrades import ORIGINS,PHASES
names={'released-115','recovery-115','via115-110','via115-111','via115-112','via115-113','via115-114'}
- earlier={name for name in ORIGINS if not name.startswith(('released-116','recovery-116','via116-','released-117','recovery-117','via117-','released-118','recovery-118','via118-'))}
+ earlier={name for name in ORIGINS if not name.startswith(('released-119','recovery-119','via119-','released-116','recovery-116','via116-','released-117','recovery-117','via117-','released-118','recovery-118','via118-'))}
self.assertTrue(names.issubset(earlier));self.assertEqual(30,len(earlier))
self.assertEqual(23,len(earlier-names));self.assertEqual(10,len(PHASES))
self.assertEqual('DC7798F3E165EC369D8092324C9F85F51D7E633824D1BD04D946A60ADA3DDDA7',ORIGINS['released-115'][2])
def test_public116_sources_extend_every_prior_origin_without_replacing_it(self):
from verify_world_upgrades import ORIGINS
names={'released-116','recovery-116',*(f'via116-{version}' for version in range(110,116))}
- earlier={name for name in ORIGINS if not name.startswith(('released-117','recovery-117','via117-','released-118','recovery-118','via118-'))}
+ earlier={name for name in ORIGINS if not name.startswith(('released-119','recovery-119','via119-','released-117','recovery-117','via117-','released-118','recovery-118','via118-'))}
self.assertEqual(38,len(earlier));self.assertTrue(names.issubset(earlier))
self.assertEqual('E545CF725650E8A01C5863A4C92BCB2D23437D61C9441C2F5D27A5272EDDA229',ORIGINS['released-116'][2])
def test_public117_adds_nine_origins_without_replacing_the_original38(self):
from verify_world_upgrades import ORIGINS
names={'released-117','recovery-117',*(f'via117-{version}' for version in range(110,117))}
- earlier={name for name in ORIGINS if not name.startswith(('released-118','recovery-118','via118-'))}
+ earlier={name for name in ORIGINS if not name.startswith(('released-119','recovery-119','via119-','released-118','recovery-118','via118-'))}
self.assertEqual(47,len(earlier));self.assertEqual(9,len(names));self.assertTrue(names.issubset(earlier));self.assertEqual(38,len(earlier-names))
self.assertEqual('854A46D80B8599966E43B3EAED407CD1A99FE4FB019AEE9138FE2F98E8DDA9FA',ORIGINS['released-117'][2])
def test_public118_adds_ten_origins_without_replacing_the_actual47(self):
from verify_world_upgrades import ORIGINS
names={'released-118','recovery-118',*(f'via118-{version}' for version in range(110,118))}
- self.assertEqual(57,len(ORIGINS));self.assertEqual(10,len(names));self.assertTrue(names.issubset(ORIGINS));self.assertEqual(47,len(set(ORIGINS)-names))
+ earlier={name for name in ORIGINS if not name.startswith(('released-119','recovery-119','via119-'))}
+ self.assertEqual(57,len(earlier));self.assertEqual(10,len(names));self.assertTrue(names.issubset(earlier));self.assertEqual(47,len(earlier-names))
self.assertEqual('34EF41EFCE4666A684EA202DAF8712162A1C403A90120BA11EF0260533FB690F',ORIGINS['released-118'][2])
+ def test_public119_adds_eleven_origins_without_replacing_the_actual57(self):
+ from verify_world_upgrades import ORIGINS
+ names={'released-119','recovery-119',*(f'via119-{version}' for version in range(110,119))}
+ self.assertEqual(68,len(ORIGINS));self.assertEqual(11,len(names));self.assertTrue(names.issubset(ORIGINS))
+ self.assertEqual(57,len(set(ORIGINS)-names))
+ self.assertEqual('6341FD1976BC195269F61C58885530CCDA0557873FB300E01EEAD657B5975014',ORIGINS['released-119'][2])
+
if __name__=='__main__':unittest.main()
diff --git a/tools/test_native119_launch.py b/tools/test_native120_launch.py
similarity index 86%
rename from tools/test_native119_launch.py
rename to tools/test_native120_launch.py
index e19cab7..11fc83c 100644
--- a/tools/test_native119_launch.py
+++ b/tools/test_native120_launch.py
@@ -8,7 +8,7 @@
import native_runtime
-class Native119LaunchTests(unittest.TestCase):
+class Native120LaunchTests(unittest.TestCase):
def test_posix_native_diagnostics_fixture_uses_the_posix_tools(self):
with patch.object(native_runtime,'os',SimpleNamespace(name='posix')):
self.test_packaged_server_uses_installer_module_arguments_and_java17_gc()
@@ -31,7 +31,7 @@ def test_packaged_server_uses_installer_module_arguments_and_java17_gc(self):
command=run.call_args.args[0]
self.assertNotIn('-jar',command)
self.assertIn('-Xmx2G',command)
- self.assertTrue(any(arg.startswith('@libraries/net/minecraftforge/forge/1.19.4-45.4.5/') for arg in command))
+ self.assertTrue(any(arg.startswith('@libraries/net/minecraftforge/forge/1.20.1-47.4.28/') for arg in command))
gc=next((arg for arg in command if arg.startswith('-Xlog:')),None)
self.assertIsNotNone(gc)
self.assertIn('gc*,safepoint',gc)
@@ -49,17 +49,17 @@ def test_runtime_rejects_a_different_jdk_before_launch(self):
native_runtime.run_java(java,home,'fixture')
run.assert_not_called()
- def test_server_inputs_pin_native45_instead_of_the_old_launcher(self):
- self.assertEqual('forge-1.19.4-45.4.5',native_runtime.FORGE)
- for path in ('libraries/net/minecraftforge/forge/1.19.4-45.4.5/win_args.txt',
- 'libraries/net/minecraftforge/forge/1.19.4-45.4.5/unix_args.txt',
- 'libraries/net/minecraftforge/forge/1.19.4-45.4.5/forge-1.19.4-45.4.5-server.jar'):
+ def test_server_inputs_pin_native47_instead_of_the_old_launcher(self):
+ self.assertEqual('forge-1.20.1-47.4.28',native_runtime.FORGE)
+ for path in ('libraries/net/minecraftforge/forge/1.20.1-47.4.28/win_args.txt',
+ 'libraries/net/minecraftforge/forge/1.20.1-47.4.28/unix_args.txt',
+ 'libraries/net/minecraftforge/forge/1.20.1-47.4.28/forge-1.20.1-47.4.28-server.jar'):
self.assertIn(path,native_runtime.SERVER_INPUTS)
self.assertFalse(any('1.16.5' in path for path in native_runtime.SERVER_INPUTS))
- def test_native45_historical_workers_have_their_own_fresh_qualification_binding(self):
+ def test_native47_historical_workers_have_their_own_fresh_qualification_binding(self):
proof=native_runtime.WORKER_BUDGET_PROOF
- self.assertEqual(('1.19.4','45.4.5',200,4,True),tuple(proof[key] for key in
+ self.assertEqual(('1.20.1','47.4.28',200,4,True),tuple(proof[key] for key in
('minecraft','forge','reloads','jvms','qualified')))
self.assertTrue(proof['no_candidate'] and proof['no_worlds'] and proof['source_and_sdk_seals_unchanged'])
self.assertRegex(proof['qualification_evidence_sha256'],r'^[A-F0-9]{64}$')
diff --git a/tools/test_native119_persistence.py b/tools/test_native120_persistence.py
similarity index 88%
rename from tools/test_native119_persistence.py
rename to tools/test_native120_persistence.py
index 699d658..60a5d68 100644
--- a/tools/test_native119_persistence.py
+++ b/tools/test_native120_persistence.py
@@ -1,22 +1,22 @@
-"""Synthetic native19 codec shapes; no historical JVM qualification is claimed."""
+"""Synthetic native20 codec shapes; no historical JVM qualification is claimed."""
from pathlib import Path
import gzip,struct,tempfile,unittest
from nbt_regions import Tag,write_disposable_region,encode,read_region
-from native119_persistence import count_persisted
+from native120_persistence import count_persisted
from verify_world_upgrades import patch_stamps
-class Native119PersistenceTest(unittest.TestCase):
- def body(self,number=1,marker=9):return Tag(10,{'id':Tag(8,'lootablebodies:corpse'),'LootableBodiesDataVersion':Tag(3,marker),'UUID':Tag(11,struct.pack('>iiii',0,0,0,number))})
- def chunk(self,bodies,version=3337,position=(8,8)):
+class Native120PersistenceTest(unittest.TestCase):
+ def body(self,number=1,marker=10):return Tag(10,{'id':Tag(8,'lootablebodies:corpse'),'LootableBodiesDataVersion':Tag(3,marker),'UUID':Tag(11,struct.pack('>iiii',0,0,0,number))})
+ def chunk(self,bodies,version=3465,position=(8,8)):
return {'name':'','root':Tag(10,{'DataVersion':Tag(3,version),'Position':Tag(11,struct.pack('>ii',*position)),'Entities':Tag(9,(10,bodies))}),'compression':2,'timestamp':bytes(4)}
def write(self,world,chunk,dimension=''):
path=world/dimension/'entities/r.0.0.mca';path.parent.mkdir(parents=True,exist_ok=True);write_disposable_region(path,{264:chunk});return path
- def test_native19_saved_entities_count_all_dimensions_without_mutation(self):
+ def test_native20_saved_entities_count_all_dimensions_without_mutation(self):
with tempfile.TemporaryDirectory() as directory:
world=Path(directory);paths=[self.write(world,self.chunk([self.body(i+1)]),dim) for i,dim in enumerate(['','DIM-1','DIM1'])]
before=[p.read_bytes() for p in paths];self.assertEqual(3,count_persisted(world,0));self.assertEqual(before,[p.read_bytes() for p in paths])
def test_previous_marker_native_version_or_wrong_entity_chunk_position_is_fatal(self):
- for marker,version,position in [(8,3337,(8,8)),(9,2975,(8,8)),(9,3337,(9,8))]:
+ for marker,version,position in [(9,3465,(8,8)),(10,3337,(8,8)),(10,3465,(9,8))]:
with self.subTest(marker=marker,version=version,position=position),tempfile.TemporaryDirectory() as directory:
world=Path(directory);self.write(world,self.chunk([self.body(marker=marker)],version,position))
with self.assertRaises(ValueError):count_persisted(world,0)
@@ -27,7 +27,7 @@ def test_duplicate_active_id_and_malformed_list_cannot_pass(self):
if not duplicate:chunk['root'].value['Entities']=Tag(8,'malformed')
self.write(world,chunk)
with self.assertRaises(ValueError):count_persisted(world,0)
- def test_corpses_in_old_or_flattened_terrain_cannot_count_as_saved_native19(self):
+ def test_corpses_in_old_or_flattened_terrain_cannot_count_as_saved_native20(self):
for flattened in [False,True]:
with self.subTest(flattened=flattened),tempfile.TemporaryDirectory() as directory:
world=Path(directory);root={'entities':Tag(9,(10,[self.body()]))} if flattened else {'Level':Tag(10,{'Entities':Tag(9,(10,[self.body(1,7)]))})}
@@ -60,7 +60,7 @@ def test_valid_future_entity_fixture_preserves_original_and_rejects_wrong_source
else:
mutation=inject_future_entity_version(world)
self.assertEqual(original,(home/'future-entity-original.mca').read_bytes())
- self.assertEqual(Tag(3,3338),read_region(path)[264]['root'].value['DataVersion'])
+ self.assertEqual(Tag(3,3466),read_region(path)[264]['root'].value['DataVersion'])
self.assertEqual(8,mutation['corpse_marker'])
if __name__=='__main__':unittest.main()
diff --git a/tools/test_native45_platform_config.py b/tools/test_native47_platform_config.py
similarity index 80%
rename from tools/test_native45_platform_config.py
rename to tools/test_native47_platform_config.py
index d189fe7..8682222 100644
--- a/tools/test_native45_platform_config.py
+++ b/tools/test_native47_platform_config.py
@@ -1,31 +1,31 @@
import tempfile,unittest
from pathlib import Path
from native_runtime import fingerprint
-from verify_upgrade_abort import allowed_native45_platform_additions
+from verify_upgrade_abort import allowed_native47_platform_additions
-class Native45ConfigTest(unittest.TestCase):
+class Native47ConfigTest(unittest.TestCase):
def target(self,root,text=None):
path=root/'serverconfig/forge-server.toml';path.parent.mkdir()
- path.write_bytes(text or (Path(__file__).parent/'fixtures/forge45-server-default.toml').read_bytes());return path
+ path.write_bytes(text or (Path(__file__).parent/'fixtures/forge47-server-default.toml').read_bytes());return path
def old(self):return (Path(__file__).parent/'fixtures/forge36-server-default.toml').read_bytes()
def test_exact_actual_native_default_addition(self):
with tempfile.TemporaryDirectory() as directory:
world=Path(directory);self.target(world);after=fingerprint(world)
- self.assertEqual(['serverconfig/forge-server.toml'],allowed_native45_platform_additions({},after,world))
+ self.assertEqual(['serverconfig/forge-server.toml'],allowed_native47_platform_additions({},after,world))
def test_exact_actual_native_spec_retirement_preserves_supported_choices(self):
with tempfile.TemporaryDirectory() as directory:
world=Path(directory);path=self.target(world,self.old().replace(b'fullBoundingBoxLadders = false',b'fullBoundingBoxLadders = true'));original=path.read_bytes();before=fingerprint(world)
- path.write_bytes((Path(__file__).parent/'fixtures/forge45-server-default.toml').read_bytes().replace(b'fullBoundingBoxLadders = false',b'fullBoundingBoxLadders = true'))
- self.assertEqual(['serverconfig/forge-server.toml'],allowed_native45_platform_additions(before,fingerprint(world),world,original))
+ path.write_bytes((Path(__file__).parent/'fixtures/forge47-server-default.toml').read_bytes().replace(b'fullBoundingBoxLadders = false',b'fullBoundingBoxLadders = true'))
+ self.assertEqual(['serverconfig/forge-server.toml'],allowed_native47_platform_additions(before,fingerprint(world),world,original))
def test_supported_mutation_unknown_retirement_or_wrong_type_cannot_pass(self):
for change in ['value','unknown','type']:
with self.subTest(change=change),tempfile.TemporaryDirectory() as directory:
world=Path(directory);original=self.old()
if change=='unknown':original+=b'unknownFlag = true\n'
if change=='type':original=original.replace(b'removeErroringEntities = false',b'removeErroringEntities = 0')
- path=self.target(world,original);before=fingerprint(world);target=(Path(__file__).parent/'fixtures/forge45-server-default.toml').read_bytes()
+ path=self.target(world,original);before=fingerprint(world);target=(Path(__file__).parent/'fixtures/forge47-server-default.toml').read_bytes()
if change=='value':target=target.replace(b'fullBoundingBoxLadders = false',b'fullBoundingBoxLadders = true')
path.write_bytes(target)
- self.assertEqual([],allowed_native45_platform_additions(before,fingerprint(world),world,original))
+ self.assertEqual([],allowed_native47_platform_additions(before,fingerprint(world),world,original))
if __name__=='__main__':unittest.main()
diff --git a/tools/test_native_evidence.py b/tools/test_native_evidence.py
index def937c..53576e4 100644
--- a/tools/test_native_evidence.py
+++ b/tools/test_native_evidence.py
@@ -14,7 +14,7 @@ def fixture(self,home):
record={'timed_out':False,'returncode':0,'pid':123,'timeout':240,'cwd':str(home),
'command':['fixture-java','-Dlootablebodies.phase='+phase,
'-Xlog:gc*,safepoint:file=upgrade-near.diagnostics/gc.txt:time,uptime,level,tags',
- '@libraries/net/minecraftforge/forge/1.19.4-45.4.5/'+('win_args.txt' if os.name=='nt' else 'unix_args.txt'),'--nogui'],
+ '@libraries/net/minecraftforge/forge/1.20.1-47.4.28/'+('win_args.txt' if os.name=='nt' else 'unix_args.txt'),'--nogui'],
'evidence':{name:digest(directory/name) for name in ['gc.txt','initial-threads.txt','initial-heap-gc.txt']},'captures':[],'metrics':[]}
(directory/'result.json').write_text(json.dumps(record));return directory,record
def test_complete_receipt_retains_all_logs_and_rehashes_every_native_file(self):
@@ -64,7 +64,7 @@ def test_downloaded_linux_receipt_is_audited_independently_of_auditor_os(self):
record['cwd']='/original/runner/build/migration-results/case'
record['command']=[part.replace('/win_args.txt','/unix_args.txt') for part in record['command']]
(folder/'result.json').write_text(json.dumps(record));audit_phase(home,'upgrade-near',relocated=True)
- record['command'].append('@libraries/net/minecraftforge/forge/1.19.4-45.4.5/win_args.txt')
+ record['command'].append('@libraries/net/minecraftforge/forge/1.20.1-47.4.28/win_args.txt')
(folder/'result.json').write_text(json.dumps(record))
with self.assertRaises(ValueError):audit_phase(home,'upgrade-near',relocated=True)
diff --git a/tools/test_native_server_preparation.py b/tools/test_native_server_preparation.py
index 1d0a40a..343f738 100644
--- a/tools/test_native_server_preparation.py
+++ b/tools/test_native_server_preparation.py
@@ -6,7 +6,7 @@
class VanillaServerPreparationTests(unittest.TestCase):
def fixture(self,home,payload=b'exact installed native input'):
- relative='libraries/net/minecraft/server/1.19.4/server-1.19.4.jar'
+ relative='libraries/net/minecraft/server/1.20.1/server-1.20.1.jar'
expected=hashlib.sha256(b'exact installed native input').hexdigest().upper()
def installed(command,**options):
self.assertIn('--installServer',command)
@@ -25,7 +25,7 @@ def test_wrong_installed_vanilla_bytes_refuse_before_gameplay_without_installer_
with tempfile.TemporaryDirectory() as directory:
home=Path(directory)/'new-sdk';pins,installed=self.fixture(home,b'changed bytes')
with patch('prepare_migration_inputs.fetch'),patch.object(runtime,'_download') as prefetch,patch.object(runtime,'run_logged',side_effect=installed) as installer,patch.object(runtime,'SERVER_INPUTS',pins):
- with self.assertRaisesRegex(RuntimeError,'Incorrect official Forge45 server input'):runtime.install(Path('fixture-java'),home)
+ with self.assertRaisesRegex(RuntimeError,'Incorrect official Forge47 server input'):runtime.install(Path('fixture-java'),home)
prefetch.assert_not_called();self.assertEqual(1,installer.call_count)
self.assertFalse((home/'runtime-inputs.json').exists())
def test_missing_installed_input_is_fatal(self):
diff --git a/tools/test_prepared_worlds.py b/tools/test_prepared_worlds.py
index c57cbfd..7406daf 100644
--- a/tools/test_prepared_worlds.py
+++ b/tools/test_prepared_worlds.py
@@ -11,7 +11,7 @@ def fixture(self,root):
records={name:{'world':str(world.resolve()),'files':fingerprint(world),'minecraft':mc,'mod_version':version,'jar_sha256':sha} for name,(mc,version,sha) in expected.items()}
second=root/'second';second.mkdir();(second/'level.dat').write_bytes(b'separate synthetic source')
records['two'].update(world=str(second.resolve()),files=fingerprint(second))
- path=root/'inputs.json';record={'schema':1,'target_data_version':3337,'origins':records};path.write_text(json.dumps(record))
+ path=root/'inputs.json';record={'schema':1,'target_data_version':3465,'origins':records};path.write_text(json.dumps(record))
return path,record,expected,world
def test_exact_subset_reuses_original_paths_and_seals(self):
with tempfile.TemporaryDirectory() as directory:
diff --git a/tools/verify_multiplayer.py b/tools/verify_multiplayer.py
index 3a94184..e02d12d 100644
--- a/tools/verify_multiplayer.py
+++ b/tools/verify_multiplayer.py
@@ -8,8 +8,8 @@
import socket
import subprocess
import time
-from native119_logs import audit
-from native119_client_logs import audit_client
+from native120_logs import audit
+from native120_client_logs import audit_client
from fixture_login import LoginMonitor, start_clients, write_receipts, archive_live_logs
from native_runtime import project_assets,FORGE,digest,prepare_server as prepare,run_server as run,client_command as native_client_command,prepare_client,seed_flat_fixture
from owned_process import native_process
diff --git a/tools/verify_native_respawn_control.py b/tools/verify_native_respawn_control.py
index bc50dc6..665b1a6 100644
--- a/tools/verify_native_respawn_control.py
+++ b/tools/verify_native_respawn_control.py
@@ -1,8 +1,8 @@
"""Exercise native45 delayed respawns/FIFO packets without Lootable Bodies."""
import argparse,json,socket,subprocess,time,os,re
from pathlib import Path
-from native119_logs import audit
-from native119_client_logs import audit_client
+from native120_logs import audit
+from native120_client_logs import audit_client
from audit_runtime_logs import audit as legacy_audit, forge36_dead_player_movement_blocks
from native_runtime import prepare_server,prepare_client,client_command,project_assets,FORGE,digest,seed_flat_fixture
from owned_process import native_process
diff --git a/tools/verify_normal_server.py b/tools/verify_normal_server.py
index db2357a..d51b6e2 100644
--- a/tools/verify_normal_server.py
+++ b/tools/verify_normal_server.py
@@ -7,7 +7,7 @@
import time
import re
import shutil
-from native119_logs import audit
+from native120_logs import audit
from verify_packaged import prepare, digest, FORGE, MAIN
from native_runtime import seed_loader_defaults,seed_flat_fixture
from owned_process import native_process
@@ -35,8 +35,7 @@ def main():
prepare_development_home(home)
init=root/'ordinary.init.gradle'
path=home.as_posix().replace("'","\\'")
- logging=(project/'src/integrationTest/resources/log4j2.xml').as_uri()
- init.write_text("gradle.projectsEvaluated { def p=gradle.rootProject; p.minecraft.runs.getByName('server').workingDir.set(new File('"+path+"')); p.tasks.named('runServer') { workingDir new File('"+path+"'); systemProperty 'log4j.configurationFile','"+logging+"'; jvmArgs '-Xlog:gc*,safepoint:file=ordinary-game.gc.txt:time,uptime,pid,level,tags' } }\n")
+ init.write_text("gradle.projectsEvaluated { def p=gradle.rootProject; p.minecraft.runs.getByName('server').workingDir.set(new File('"+path+"')); p.tasks.named('runServer') { workingDir new File('"+path+"'); jvmArgs '-Xlog:gc*,safepoint:file=ordinary-game.gc.txt:time,uptime,pid,level,tags' } }\n")
command=[str(project/('gradlew.bat' if os.name=='nt' else 'gradlew')),'runServer','--init-script',str(init),'-PgameRunDirectory='+str(home),'--no-daemon','--console=plain']
cwd=project
else:
@@ -94,9 +93,12 @@ def main():
raise RuntimeError('Ordinary server failed clean startup/stop: '+str(log))
if any(name in text for name in ['lootablebodiesbuildtest','forgeplatformcontrol']) or list((home/'crash-reports').glob('*')): raise RuntimeError('Probe leak or crash')
audit(log,development=args.development,native=True)
- for name in ('latest.log','debug.log'):
+ log_names = ['latest.log', 'debug.log']
+ if args.development:
+ log_names.append('bootstrap-debug.log')
+ for name in log_names:
complete=home/'logs'/name
- if not complete.is_file():raise FileNotFoundError(complete)
+ if not complete.is_file() or not complete.stat().st_size:raise FileNotFoundError(complete)
audit(complete,development=args.development,native=True)
if any(name in complete.read_text() for name in ['lootablebodiesbuildtest','forgeplatformcontrol']):raise RuntimeError('Probe in ordinary runtime')
shutil.copy2(complete,root/(phase+'.'+name))
diff --git a/tools/verify_packaged_client.py b/tools/verify_packaged_client.py
index 44d77e7..e55d5ea 100644
--- a/tools/verify_packaged_client.py
+++ b/tools/verify_packaged_client.py
@@ -5,7 +5,7 @@
from pathlib import Path
import shutil
import subprocess
-from native119_client_logs import audit_client as audit
+from native120_client_logs import audit_client as audit
from native_runtime import project_assets,prepare_client,client_command,fingerprint
from release_identity import MAIN_JAR
from runtime_diagnostics import run_monitored,validate_logging_inputs
diff --git a/tools/verify_upgrade_abort.py b/tools/verify_upgrade_abort.py
index 1593da2..f7239fa 100644
--- a/tools/verify_upgrade_abort.py
+++ b/tools/verify_upgrade_abort.py
@@ -101,14 +101,14 @@ def allowed_native40_platform_additions(before,after,world,original_config=None)
-def allowed_native45_platform_additions(before,after,world,original_config=None):
- """Exact Forge45 corrections; no unknown keys or supported choices dropped."""
+def allowed_native47_platform_additions(before,after,world,original_config=None):
+ """Exact Forge47 corrections; no unknown keys or supported choices dropped."""
name='serverconfig/forge-server.toml'
if name not in after:return []
actual=(world/name).read_bytes()
if digest(world/name)!=after[name]:return []
try:
- defaults=tomllib.loads((Path(__file__).parent/'fixtures/forge45-server-default.toml').read_text())
+ defaults=tomllib.loads((Path(__file__).parent/'fixtures/forge47-server-default.toml').read_text())
upgraded=tomllib.loads(actual.decode('utf-8'))
if name not in before:return [name] if same_typed_values(defaults,upgraded) else []
if original_config is None or before[name]!=__import__('hashlib').sha256(original_config).hexdigest().upper():return []
@@ -169,9 +169,9 @@ def inject_future_entity_version(world):
before=digest(path);original=world.parent/'future-entity-original.mca'
if original.exists():raise ValueError('Future fixture original already exists')
shutil.copy2(path,original)
- root['DataVersion']=Tag(3,3338);write_disposable_region(path,chunks)
+ root['DataVersion']=Tag(3,3466);write_disposable_region(path,chunks)
return {'region':path.relative_to(world).as_posix(),'chunk_index':index,'source_version':2975,
- 'injected_version':3338,'nbt_type':'Int','original_sha256':before,'mutated_sha256':digest(path),
+ 'injected_version':3466,'nbt_type':'Int','original_sha256':before,'mutated_sha256':digest(path),
'original_copy':str(original),'corpse_marker':8}
@@ -224,7 +224,7 @@ def main():
raise RuntimeError('World loading continued after unsafe preservation')
after = source_files(world)
raw_after = dict(after)
- platform_additions = allowed_native45_platform_additions(before, after, world, original_config)
+ platform_additions = allowed_native47_platform_additions(before, after, world, original_config)
lock_updates=allowed_native_session_lock(before,after,world,original_lock)
for name in platform_additions+lock_updates:
if name in before: after[name] = before[name]
diff --git a/tools/verify_visibility.py b/tools/verify_visibility.py
index 2c17837..87cf96f 100644
--- a/tools/verify_visibility.py
+++ b/tools/verify_visibility.py
@@ -9,8 +9,8 @@
import os
from native_runtime import seed_flat_fixture
from owned_process import native_process
-from native119_logs import audit
-from native119_client_logs import audit_client
+from native120_logs import audit
+from native120_client_logs import audit_client
from fixture_login import LoginMonitor, start_clients, write_receipts, archive_live_logs
from verify_multiplayer import client_command
from verify_packaged import prepare, FORGE, MAIN, digest, run
diff --git a/tools/verify_world_upgrades.py b/tools/verify_world_upgrades.py
index 1e1e818..da64ccf 100644
--- a/tools/verify_world_upgrades.py
+++ b/tools/verify_world_upgrades.py
@@ -78,7 +78,13 @@
'via118-116': ORIGINS['released-116'],
'via118-117': ORIGINS['released-117'],
})
-FORGE = {'1.12.2': 'forge-1.12.2-14.23.5.2859','1.10.2': 'forge-1.10.2-12.18.3.2511', '1.11.2': 'forge-1.11.2-13.20.1.2588'}
+ORIGINS.update({
+ 'released-119': ('1.19.4','2.4.10.119041','6341FD1976BC195269F61C58885530CCDA0557873FB300E01EEAD657B5975014'),
+ 'recovery-119': ('1.19.4','2.4.10.119041','6341FD1976BC195269F61C58885530CCDA0557873FB300E01EEAD657B5975014'),
+ 'via119-110': ORIGINS['original-110'],
+ **{f'via119-{version}': ORIGINS[f'released-{version}'] for version in range(111,119)},
+})
+FORGE = {'1.12.2' : 'forge-1.12.2-14.23.5.2859','1.10.2': 'forge-1.10.2-12.18.3.2511', '1.11.2': 'forge-1.11.2-13.20.1.2588'}
FORGE_SHA = {'1.12.2': 'BD8314729D787E35164476D75AFB337AFB9783917E4F6FA1F77F049ADBA22F2D','1.10.2': '809C2EA5B4D4F3E5A294D3A326744F20FEC412A1263E708297D00F512E377542',
'1.11.2': 'B6DFF35B5CCB0E6353FB89B5330FE930DB8FBE8B63787C9FC2F70196BD108998'}
@@ -626,7 +632,7 @@ def run_origin(origin):
for phase in PHASES:
run_upgrade(args.java.resolve(), home, phase)
wave = 0 if phase.startswith('upgrade-near') else 1 if phase.startswith('upgrade-far') else 2 if phase == 'upgrade-late' else -1
- from native119_persistence import count_persisted
+ from native120_persistence import count_persisted
persisted = count_persisted(world,wave)
if persisted < (3 if wave >= 0 else 8):
raise RuntimeError('Missing persisted corpses after world save')