Files
horizon-airdef/tools/test_resource_pack_parity.py
T
Ludovic Marie 0d0378916b Resource packs: reload everything in game and merge stacked files like vanilla (#72)
Choosing packs in the Resource Packs screen only reloaded the GUI atlas
(and only when it kept its size), item models and languages; block
textures and models changed after a restart, and sounds, clouds and the
world border never did. Block textures were also squeezed to 16x16, and
the pack overlay took every file from the top pack, so a pack's own
en_us.json, sounds.json, font or atlas definition replaced vanilla's
instead of adding to it (all other text, or every vanilla sound, gone).

- Minecraft.reloadResourcePacks: rebuild block sprites and models, mesh
  tables, the shared atlas, item models, sounds, languages, GUI
  metadata, clouds.png and forcefield.png from the new overlay; swap the
  mesher's context (workers read it per job), mesh every loaded section
  again (LevelRenderer.allChanged) and build a renderer with the new
  textures. Nothing switches if loading fails.
- The block sprite array takes the largest texture's resolution (power
  of two, at most 256 MiB), so 32x/64x packs keep their detail;
  animations upload at that size.
- Files vanilla reads as resource stacks are merged in the overlay:
  ClientLanguage (keys override), SoundManager (sounds add up unless an
  event replaces; a sounds.json Gson rejects is skipped whole),
  FontManager (top pack's providers first), SpriteSourceList (sources
  bottom up). Languages are read from every namespace, not only
  minecraft.
- Missing or broken block textures use vanilla's missing texture; packs
  that still break startup fall back to the default resources
  (Minecraft.rollbackResourcePacks).
- Open Pack Folder opens the folder on macOS and Windows too.

tools/test_resource_pack_parity.py checks pack discovery, overlays,
filters, which pack provides a file and the merged languages, sounds,
fonts and atlases against vanilla's FolderRepositorySource,
MultiPackResourceManager and reload listeners.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 15:33:34 +02:00

139 lines
6.5 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Compare the resources a selection of resource packs gives the client with 26.3.
Builds test packs in a folder (a folder pack and a zip pack, with pack.mcmeta
overlays for this and an older format, a filter, block textures, language
files, sounds.json with added and replaced events, font providers and atlas
sources) and selects them over the prepared vanilla resources. The Java oracle
(VanillaPackResourcesOracle) opens them with vanilla's FolderRepositorySource
and MultiPackResourceManager and asks what each reload listener reads: which
pack a file comes from, translations (ClientLanguage), sound events
(SoundManager.prepare), font providers (FontManager) and atlas sources
(SpriteSourceList). examples/pack_resources_fixture builds Horizon's resource
root for the same selection (packs::build_overlay) and reads it with Horizon's
loaders. Every answer must agree.
"""
import json
import shutil
import zipfile
try:
from . import lab, oracle_harness as harness
except ImportError:
import lab, oracle_harness as harness
FORMAT = {"min_format": [97, 0], "max_format": [97, 1]}
def write(root, files):
for name, data in files.items():
path = root / name
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(data if isinstance(data, bytes) else json.dumps(data).encode())
def packs(folder):
shutil.rmtree(folder, ignore_errors=True)
folder.mkdir(parents=True)
write(folder / "Alpha", {
"pack.mcmeta": {"pack": {"description": "alpha", **FORMAT}, "overlays": {"entries": [
{"directory": "alpha_now", **FORMAT},
{"directory": "alpha_old", "min_format": [80, 0], "max_format": [80, 0]}]}},
"assets/minecraft/textures/block/stone.png": b"alpha stone",
"assets/minecraft/textures/block/granite.png": b"alpha granite",
"alpha_now/assets/minecraft/textures/block/dirt.png": b"alpha overlay dirt",
"alpha_old/assets/minecraft/textures/block/sand.png": b"old overlay sand",
"assets/minecraft/lang/en_us.json": {"block.minecraft.stone": "Alpha Stone", "horizon.k1": "A1",
"horizon.k2": "A2"},
"assets/minecraft/sounds.json": {
"horizon.test": {"subtitle": "subtitles.horizon.a", "sounds": ["horizon/a1"]},
"block.stone.break": {"sounds": [{"name": "horizon/a2", "weight": 2}]},
"horizon.ref": {"sounds": [{"name": "horizon.test", "type": "event"}]}},
"assets/minecraft/sounds/horizon/a1.ogg": b"a1",
"assets/minecraft/sounds/horizon/a2.ogg": b"a2",
"assets/minecraft/font/default.json": {"providers": [{"type": "space", "advances": {" ": 9}}]},
"assets/minecraft/atlases/blocks.json": {"sources": [{"type": "single", "resource": "horizon:block/a"}]},
"assets/horizon/lang/en_us.json": {"horizon.ns": "Alpha namespace"},
})
beta = folder / "beta-src"
write(beta, {
"pack.mcmeta": {"pack": {"description": "beta", **FORMAT},
"filter": {"block": [{"namespace": "minecraft", "path": "textures/block/stone\\.png"}]}},
"assets/minecraft/textures/block/granite.png": b"beta granite",
"assets/minecraft/lang/en_us.json": {"horizon.k1": "B1"},
"assets/minecraft/sounds.json": {
"horizon.test": {"replace": True, "subtitle": "subtitles.horizon.b",
"sounds": ["horizon/b1", {"name": "horizon/a1", "weight": 3}]},
"block.stone.break": {"sounds": ["horizon/missing"]}},
"assets/minecraft/sounds/horizon/b1.ogg": b"b1",
"assets/minecraft/font/default.json": {"providers": [{"type": "space", "advances": {" ": 4}},
{"type": "space", "advances": {" ": 5}}]},
"assets/minecraft/atlases/blocks.json": {"sources": [{"type": "single", "resource": "horizon:block/b"}]},
})
# Gamma: a sounds.json Gson rejects (a bad entry) is skipped whole.
write(folder / "Gamma", {
"pack.mcmeta": {"pack": {"description": "gamma", **FORMAT}},
"assets/minecraft/sounds.json": {"horizon.test": {"sounds": ["horizon/a1"]}, "bad": 3},
"assets/minecraft/lang/en_us.json": {"horizon.k2": "G2"},
})
with zipfile.ZipFile(folder / "Beta.zip", "w") as z:
for path in sorted(beta.rglob("*")):
if path.is_file():
z.write(path, path.relative_to(beta).as_posix())
shutil.rmtree(beta)
QUERIES = [
"file minecraft:textures/block/stone.png",
"file minecraft:textures/block/granite.png",
"file minecraft:textures/block/dirt.png",
"file minecraft:textures/block/sand.png",
"file minecraft:textures/block/oak_planks.png",
"lang block.minecraft.stone",
"lang block.minecraft.dirt",
"lang horizon.k1",
"lang horizon.k2",
"lang horizon.ns",
"lang menu.singleplayer",
"sound horizon.test",
"sound horizon.ref",
"sound block.stone.break",
"sound block.stone.place",
"font minecraft:default",
"font minecraft:uniform",
"atlas minecraft:blocks",
"atlas minecraft:gui",
]
SELECTIONS = [
["file/Alpha", "file/Beta.zip", "file/Gamma"],
["file/Beta.zip", "file/Alpha"],
["file/Alpha"],
[],
]
def main():
total = 0
for index, selection in enumerate(SELECTIONS):
# One run per selection: the input's first line names the packs, bottom first.
def make(build, selection=selection):
folder = build / "resourcepacks"
packs(folder)
vanilla = lab.LOCAL / "horizon-client" / "26.3"
path = build / "inputs.txt"
path.write_text(f"root {vanilla} {folder} {' '.join(selection)}".rstrip() + "\n"
+ "".join(q + "\n" for q in QUERIES))
return path, len(QUERIES)
harness.differential(f"resource-pack-parity-{index}", "VanillaPackResourcesOracle",
"pack_resources_fixture", make,
"Pack discovery, overlays, filters and the resource stacks vanilla merges.",
sources=[lab.ROOT / "crates/horizon-client/src/packs.rs",
lab.ROOT / "crates/horizon-client/src/sound_events.rs",
lab.ROOT / "crates/horizon-client/src/lang.rs"])
total += len(QUERIES)
print(f"{total} resource pack queries match the pinned client over {len(SELECTIONS)} selections")
if __name__ == "__main__":
main()