Files
onion-mc/tools/filter-xaero-regions.py
logan 769af6fa2d chore(tools): add FTB Chunks -> Xaero's map reveal migration tooling
The swap does not carry players' explored terrain across. FTB Chunks stores no
rendered map (its region zips hold chunks.dat plus five data PNGs, one of which
packs height into an image channel), and Xaero's region format is closed and now
version 6 with per-pixel NBT blobs, so the community converters no longer work.
Rather than convert pixels, prune a copy of the world save down to exactly the
chunks a player revealed and let Xaero's own generator map it.

Validated against real data: extracting the 167-region / 98,170-chunk mask
cross-checks clean against FTB's own pixel data, and Anvil pruning round-trips
byte-identically (15.7 MB -> 471 KB on a live region file, slot-to-chunk mapping
confirmed against 1024 chunk NBT headers).

Deferred for now: needs the world save and one singleplayer pass per player.
See analysis/FTB-XAERO-MAP-MIGRATION.md.
2026-09-30 22:04:56 -04:00

89 lines
3.1 KiB
Python
Executable File

#!/usr/bin/env python3
"""Drop Xaero's map regions that a player never actually revealed.
After a player opens their pruned singleplayer world, Xaero's maps it - but Minecraft
also *regenerates* the chunks around wherever that player logged in, so a little
terrain they had never seen can sneak onto the map. This compares the harvested map
against the reveal mask and removes the strays.
It works on file names only (`<rx>_<rz>.zip` is one 512x512-block region, exactly the
same geometry as an FTB Chunks region file), so it needs no knowledge of Xaero's
region format.
Usage:
filter-xaero-regions.py <xaero-map-dir> <mask.json> [--dimension <id>] [--delete]
Without --delete it only reports. Run it on one dimension folder at a time, e.g.
~/.minecraft/xaero/world-map/Multiplayer_advilplug.top/null/
"""
from __future__ import annotations
import argparse
import json
import os
import re
import sys
REGION_RE = re.compile(r"^(-?\d+)_(-?\d+)\.zip$")
def main() -> int:
ap = argparse.ArgumentParser(description=__doc__.split("\n")[0])
ap.add_argument("map_dir", help="Xaero's map instance directory holding <rx>_<rz>.zip")
ap.add_argument("mask", help="mask JSON from ftbchunks-reveal.py")
ap.add_argument("--dimension", default=None,
help="dimension to check (default: the mask's only dimension)")
ap.add_argument("--delete", action="store_true", help="actually remove the strays")
args = ap.parse_args()
with open(args.mask) as fh:
mask = json.load(fh)
dims = mask["dimensions"]
dim = args.dimension
if dim is None:
if len(dims) != 1:
print(f"mask has {len(dims)} dimensions; pass --dimension", file=sys.stderr)
return 2
dim = next(iter(dims))
if dim not in dims:
print(f"no dimension '{dim}' in mask; have {list(dims)}", file=sys.stderr)
return 2
revealed = {tuple(int(v) for v in key.split(",")) for key in dims[dim]["regions"]}
if not os.path.isdir(args.map_dir):
print(f"not a directory: {args.map_dir}", file=sys.stderr)
return 2
present, strays = [], []
for name in sorted(os.listdir(args.map_dir)):
m = REGION_RE.match(name)
if not m:
continue
coords = (int(m.group(1)), int(m.group(2)))
(present if coords in revealed else strays).append((name, coords))
print(f"dimension {dim}")
print(f" map regions present : {len(present)} (of {len(revealed)} revealed)")
print(f" not in the mask : {len(strays)}")
for name, _ in strays:
print(f" {name}")
missing = sorted(revealed - {c for _, c in present})
if missing:
print(f" revealed but absent from the map: {len(missing)} "
f"(e.g. {missing[:4]}) - regenerate these, do not delete anything")
if args.delete and strays:
for name, _ in strays:
os.remove(os.path.join(args.map_dir, name))
print(f"\nremoved {len(strays)} stray region file(s)")
elif strays:
print("\nre-run with --delete to remove them")
return 0
if __name__ == "__main__":
sys.exit(main())