minerender 2.1.0-alpha.91 → 2.1.0-alpha.93

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18963,6 +18963,7 @@ __export(index_node_exports, {
18963
18963
  parseSceneDocument: () => parseSceneDocument,
18964
18964
  prefix: () => prefix,
18965
18965
  registerNodeEnv: () => registerNodeEnv,
18966
+ resolveLegacyBlock: () => resolveLegacyBlock,
18966
18967
  sampleEntityAnimation: () => sampleEntityAnimation,
18967
18968
  sampleEntityKeyframes: () => sampleEntityKeyframes,
18968
18969
  sampleFluid: () => sampleFluid,
@@ -23945,158 +23946,6 @@ function isBlockObject(obj) {
23945
23946
  init_FluidGeometry();
23946
23947
  init_FluidModelObject();
23947
23948
 
23948
- // src/model/multiblock/LitematicaParser.ts
23949
- init_MineRenderError();
23950
- var LitematicaParser = class {
23951
- /**
23952
- * Parses decoded NBT, preserving subregion placement and omitting air.
23953
- * Later entries in regions replace earlier cells, including with air.
23954
- * Size encloses all region bounds; positions retain the schematic origin.
23955
- * Metadata and scheduled ticks are not imported. Named regions can be placed individually.
23956
- */
23957
- static async parse(nbt) {
23958
- const tags = nbt.value;
23959
- const version2 = integer2(tags.Version, "Version");
23960
- if (version2 < 5 || version2 > 7) throw new MineRenderError(`Unsupported Litematica version ${version2}; expected 5, 6, or 7`);
23961
- const dataVersion = tags.MinecraftDataVersion === void 0 ? void 0 : integer2(tags.MinecraftDataVersion, "MinecraftDataVersion");
23962
- const blocks = /* @__PURE__ */ new Map();
23963
- const regions = [];
23964
- const min = [Infinity, Infinity, Infinity];
23965
- const max = [-Infinity, -Infinity, -Infinity];
23966
- for (const [name, tag] of Object.entries(compound(tags.Regions, "Regions"))) {
23967
- const region = readRegion(compound(tag, `region ${name}`), name, dataVersion, blocks);
23968
- regions.push(region);
23969
- for (let axis = 0; axis < 3; axis++) {
23970
- const start = minimum(region.position[axis], region.signedSize[axis]);
23971
- min[axis] = Math.min(min[axis], start);
23972
- max[axis] = Math.max(max[axis], start + region.size[axis]);
23973
- }
23974
- }
23975
- return {
23976
- size: regions.length ? min.map((value, axis) => max[axis] - value) : [0, 0, 0],
23977
- blocks: [...blocks.values()],
23978
- entities: regions.flatMap((region) => region.entities ?? []),
23979
- dataVersion,
23980
- regions
23981
- };
23982
- }
23983
- };
23984
- function readRegion(tags, name, dataVersion, combined) {
23985
- const position = blockPosition(compound(tags.Position, `region ${name} Position`));
23986
- const signedSize = blockPosition(compound(tags.Size, `region ${name} Size`));
23987
- if (signedSize.some((value) => value === 0)) throw new MineRenderError(`Litematica region ${name} dimensions must be nonzero`);
23988
- const size = signedSize.map(Math.abs);
23989
- const min = position.map((value, axis) => minimum(value, signedSize[axis]));
23990
- const [width, height, length] = size;
23991
- const volume = width * height * length;
23992
- const palette = compounds(tags.BlockStatePalette, "BlockStatePalette").map(blockState);
23993
- if (!palette.length) throw new MineRenderError(`Litematica region ${name} has no block palette`);
23994
- const bits = Math.max(2, Math.ceil(Math.log2(palette.length)));
23995
- if (!Number.isSafeInteger(volume * bits)) throw new MineRenderError(`Litematica region ${name} volume is too large`);
23996
- const packed = tags.BlockStates;
23997
- const expected = Math.ceil(volume * bits / 64);
23998
- if (packed?.type !== "longArray" || !Array.isArray(packed.value) || packed.value.length !== expected) {
23999
- throw new MineRenderError(`Litematica region ${name} BlockStates must contain ${expected} longs`);
24000
- }
24001
- const words = packed.value.map((pair) => {
24002
- if (!Array.isArray(pair) || pair.length !== 2 || !pair.every((value) => Number.isInteger(value) && value >= -2147483648 && value <= 4294967295)) {
24003
- throw new MineRenderError(`Litematica region ${name} has an invalid BlockStates long`);
24004
- }
24005
- return [pair[0] >>> 0, pair[1] >>> 0];
24006
- });
24007
- const blockEntities = /* @__PURE__ */ new Map();
24008
- for (const tile of compounds(tags.TileEntities, "TileEntities")) {
24009
- const pos = blockPosition(tile);
24010
- if (pos.some((value2, axis) => value2 < 0 || value2 >= size[axis])) {
24011
- throw new MineRenderError(`Litematica region ${name} block entity is outside its dimensions`);
24012
- }
24013
- const index = pos[0] + pos[2] * width + pos[1] * width * length;
24014
- if (blockEntities.has(index)) throw new MineRenderError(`Duplicate Litematica block entity at index ${index}`);
24015
- const value = { ...tile };
24016
- for (const [axis, key] of ["x", "y", "z"].entries()) value[key] = { type: "int", value: pos[axis] + min[axis] };
24017
- blockEntities.set(index, { type: "compound", value });
24018
- }
24019
- const blocks = [];
24020
- const mask = 2 ** bits - 1;
24021
- for (let index = 0; index < volume; index++) {
24022
- const word = Math.floor(index * bits / 64);
24023
- const shift = index * bits % 64;
24024
- const [high, low] = words[word];
24025
- let value = shift < 32 ? low >>> shift : high >>> shift - 32;
24026
- if (shift < 32 && shift + bits > 32) value |= high << 32 - shift;
24027
- if (shift + bits > 64) value |= words[word + 1][1] << 64 - shift;
24028
- const id = (value & mask) >>> 0;
24029
- const state = palette[id];
24030
- if (!state) throw new MineRenderError(`Litematica region ${name} palette index ${id} does not exist`);
24031
- const pos = [
24032
- index % width + min[0],
24033
- Math.floor(index / (width * length)) + min[1],
24034
- Math.floor(index / width) % length + min[2]
24035
- ];
24036
- const key = pos.join(",");
24037
- if (["minecraft:air", "minecraft:cave_air", "minecraft:void_air"].includes(state.type)) {
24038
- combined.delete(key);
24039
- continue;
24040
- }
24041
- const block = { type: state.type, properties: { ...state.properties }, position: pos, nbt: blockEntities.get(index) };
24042
- blocks.push(block);
24043
- combined.set(key, block);
24044
- }
24045
- const entities = compounds(tags.Entities, "Entities").map((entity) => {
24046
- const pos = entity.Pos;
24047
- if (pos?.type !== "list" || pos.value.type !== "double" || pos.value.value.length !== 3 || !pos.value.value.every(Number.isFinite)) {
24048
- throw new MineRenderError(`Litematica region ${name} entity Pos must contain three doubles`);
24049
- }
24050
- const absolute = pos.value.value.map((value2, axis) => value2 + position[axis]);
24051
- const value = { ...entity, Pos: { type: "list", value: { type: "double", value: [...absolute] } } };
24052
- return { position: absolute, nbt: { type: "compound", value } };
24053
- });
24054
- return { name, position, signedSize, size, blocks, entities, dataVersion };
24055
- }
24056
- function minimum(position, size) {
24057
- return position + Math.min(0, size + 1);
24058
- }
24059
- function integer2(tag, name) {
24060
- if (tag?.type !== "int" || !Number.isInteger(tag.value) || tag.value < -2147483648 || tag.value > 2147483647) {
24061
- throw new MineRenderError(`Litematica ${name} must be an int`);
24062
- }
24063
- return tag.value;
24064
- }
24065
- function compound(tag, name) {
24066
- if (tag?.type !== "compound" || !tag.value || typeof tag.value !== "object" || Array.isArray(tag.value)) {
24067
- throw new MineRenderError(`Litematica ${name} must be a compound`);
24068
- }
24069
- return tag.value;
24070
- }
24071
- function blockPosition(tags) {
24072
- return ["x", "y", "z"].map((axis) => integer2(tags[axis], axis));
24073
- }
24074
- function compounds(tag, name) {
24075
- if (tag === void 0) return [];
24076
- if (tag.type === "list" && Array.isArray(tag.value.value)) {
24077
- if (tag.value.value.length === 0) return [];
24078
- if (tag.value.type === "compound") return tag.value.value;
24079
- }
24080
- throw new MineRenderError(`Litematica ${name} must be a compound list`);
24081
- }
24082
- function blockState(tags) {
24083
- const name = tags.Name;
24084
- if (name?.type !== "string" || !/^(?:[a-z0-9_.-]+:)?[a-z0-9_./-]+$/.test(name.value)) {
24085
- throw new MineRenderError("Litematica palette entry must have a block Name");
24086
- }
24087
- const properties = {};
24088
- if (tags.Properties !== void 0) {
24089
- for (const [key, tag] of Object.entries(compound(tags.Properties, "block Properties"))) {
24090
- if (tag?.type !== "string") throw new MineRenderError("Litematica block properties must be strings");
24091
- Object.defineProperty(properties, key, { value: tag.value, enumerable: true, writable: true, configurable: true });
24092
- }
24093
- }
24094
- return { type: name.value.includes(":") ? name.value : `minecraft:${name.value}`, properties };
24095
- }
24096
-
24097
- // src/model/multiblock/SchematicParser.ts
24098
- init_MineRenderError();
24099
-
24100
23949
  // src/model/multiblock/legacyBlocks.json
24101
23950
  var legacyBlocks_default = {
24102
23951
  __comment: "Block mappings from res/idsToNames.json (WorldEdit legacy.json); block names and cauldrons updated for Minecraft 1.21.11. Source: https://github.com/sk89q/WorldEdit/blob/master/worldedit-core/src/main/resources/com/sk89q/worldedit/world/registry/legacy.json",
@@ -24940,12 +24789,20 @@ var legacyBlocks_default = {
24940
24789
  "131:15": "minecraft:tripwire_hook[powered=true,attached=true,facing=east]",
24941
24790
  "132:0": "minecraft:tripwire[disarmed=false,east=false,powered=false,south=false,north=false,west=false,attached=false]",
24942
24791
  "132:1": "minecraft:tripwire[disarmed=false,east=false,powered=true,south=false,north=false,west=false,attached=false]",
24792
+ "132:2": "minecraft:tripwire[disarmed=false,east=false,powered=false,south=false,north=false,west=false,attached=false]",
24793
+ "132:3": "minecraft:tripwire[disarmed=false,east=false,powered=true,south=false,north=false,west=false,attached=false]",
24943
24794
  "132:4": "minecraft:tripwire[disarmed=false,east=false,powered=false,south=false,north=false,west=false,attached=true]",
24944
24795
  "132:5": "minecraft:tripwire[disarmed=false,east=false,powered=true,south=false,north=false,west=false,attached=true]",
24796
+ "132:6": "minecraft:tripwire[disarmed=false,east=false,powered=false,south=false,north=false,west=false,attached=true]",
24797
+ "132:7": "minecraft:tripwire[disarmed=false,east=false,powered=true,south=false,north=false,west=false,attached=true]",
24945
24798
  "132:8": "minecraft:tripwire[disarmed=true,east=false,powered=false,south=false,north=false,west=false,attached=false]",
24946
24799
  "132:9": "minecraft:tripwire[disarmed=true,east=false,powered=true,south=false,north=false,west=false,attached=false]",
24800
+ "132:10": "minecraft:tripwire[disarmed=true,east=false,powered=false,south=false,north=false,west=false,attached=false]",
24801
+ "132:11": "minecraft:tripwire[disarmed=true,east=false,powered=true,south=false,north=false,west=false,attached=false]",
24947
24802
  "132:12": "minecraft:tripwire[disarmed=true,east=false,powered=false,south=false,north=false,west=false,attached=true]",
24948
24803
  "132:13": "minecraft:tripwire[disarmed=true,east=false,powered=true,south=false,north=false,west=false,attached=true]",
24804
+ "132:14": "minecraft:tripwire[disarmed=true,east=false,powered=false,south=false,north=false,west=false,attached=true]",
24805
+ "132:15": "minecraft:tripwire[disarmed=true,east=false,powered=true,south=false,north=false,west=false,attached=true]",
24949
24806
  "133:0": "minecraft:emerald_block",
24950
24807
  "134:0": "minecraft:spruce_stairs[half=bottom,shape=outer_right,facing=east]",
24951
24808
  "134:1": "minecraft:spruce_stairs[half=bottom,shape=outer_right,facing=west]",
@@ -25786,7 +25643,175 @@ var legacyBlocks_default = {
25786
25643
  }
25787
25644
  };
25788
25645
 
25646
+ // src/model/multiblock/LegacyBlocks.ts
25647
+ function resolveLegacyBlock(id, metadata, customMappings = {}, lenient = false) {
25648
+ const mapping = legacyBlocks_default.blocks;
25649
+ const key = `${id}:${metadata}`;
25650
+ let mapped = customMappings[key] ?? mapping[key];
25651
+ if (!mapped && lenient) mapped = customMappings[`${id}:0`] ?? mapping[`${id}:0`];
25652
+ if (!mapped) return void 0;
25653
+ const [type, state] = mapped.split("[");
25654
+ const properties = {};
25655
+ if (state) {
25656
+ for (const property of state.slice(0, -1).split(",")) {
25657
+ const [key2, value] = property.split("=");
25658
+ properties[key2] = value;
25659
+ }
25660
+ }
25661
+ return { type, properties };
25662
+ }
25663
+
25664
+ // src/model/multiblock/LitematicaParser.ts
25665
+ init_MineRenderError();
25666
+ var LitematicaParser = class {
25667
+ /**
25668
+ * Parses decoded NBT, preserving subregion placement and omitting air.
25669
+ * Later entries in regions replace earlier cells, including with air.
25670
+ * Size encloses all region bounds; positions retain the schematic origin.
25671
+ * Metadata and scheduled ticks are not imported. Named regions can be placed individually.
25672
+ */
25673
+ static async parse(nbt) {
25674
+ const tags = nbt.value;
25675
+ const version2 = integer2(tags.Version, "Version");
25676
+ if (version2 < 5 || version2 > 7) throw new MineRenderError(`Unsupported Litematica version ${version2}; expected 5, 6, or 7`);
25677
+ const dataVersion = tags.MinecraftDataVersion === void 0 ? void 0 : integer2(tags.MinecraftDataVersion, "MinecraftDataVersion");
25678
+ const blocks = /* @__PURE__ */ new Map();
25679
+ const regions = [];
25680
+ const min = [Infinity, Infinity, Infinity];
25681
+ const max = [-Infinity, -Infinity, -Infinity];
25682
+ for (const [name, tag] of Object.entries(compound(tags.Regions, "Regions"))) {
25683
+ const region = readRegion(compound(tag, `region ${name}`), name, dataVersion, blocks);
25684
+ regions.push(region);
25685
+ for (let axis = 0; axis < 3; axis++) {
25686
+ const start = minimum(region.position[axis], region.signedSize[axis]);
25687
+ min[axis] = Math.min(min[axis], start);
25688
+ max[axis] = Math.max(max[axis], start + region.size[axis]);
25689
+ }
25690
+ }
25691
+ return {
25692
+ size: regions.length ? min.map((value, axis) => max[axis] - value) : [0, 0, 0],
25693
+ blocks: [...blocks.values()],
25694
+ entities: regions.flatMap((region) => region.entities ?? []),
25695
+ dataVersion,
25696
+ regions
25697
+ };
25698
+ }
25699
+ };
25700
+ function readRegion(tags, name, dataVersion, combined) {
25701
+ const position = blockPosition(compound(tags.Position, `region ${name} Position`));
25702
+ const signedSize = blockPosition(compound(tags.Size, `region ${name} Size`));
25703
+ if (signedSize.some((value) => value === 0)) throw new MineRenderError(`Litematica region ${name} dimensions must be nonzero`);
25704
+ const size = signedSize.map(Math.abs);
25705
+ const min = position.map((value, axis) => minimum(value, signedSize[axis]));
25706
+ const [width, height, length] = size;
25707
+ const volume = width * height * length;
25708
+ const palette = compounds(tags.BlockStatePalette, "BlockStatePalette").map(blockState);
25709
+ if (!palette.length) throw new MineRenderError(`Litematica region ${name} has no block palette`);
25710
+ const bits = Math.max(2, Math.ceil(Math.log2(palette.length)));
25711
+ if (!Number.isSafeInteger(volume * bits)) throw new MineRenderError(`Litematica region ${name} volume is too large`);
25712
+ const packed = tags.BlockStates;
25713
+ const expected = Math.ceil(volume * bits / 64);
25714
+ if (packed?.type !== "longArray" || !Array.isArray(packed.value) || packed.value.length !== expected) {
25715
+ throw new MineRenderError(`Litematica region ${name} BlockStates must contain ${expected} longs`);
25716
+ }
25717
+ const words = packed.value.map((pair) => {
25718
+ if (!Array.isArray(pair) || pair.length !== 2 || !pair.every((value) => Number.isInteger(value) && value >= -2147483648 && value <= 4294967295)) {
25719
+ throw new MineRenderError(`Litematica region ${name} has an invalid BlockStates long`);
25720
+ }
25721
+ return [pair[0] >>> 0, pair[1] >>> 0];
25722
+ });
25723
+ const blockEntities = /* @__PURE__ */ new Map();
25724
+ for (const tile of compounds(tags.TileEntities, "TileEntities")) {
25725
+ const pos = blockPosition(tile);
25726
+ if (pos.some((value2, axis) => value2 < 0 || value2 >= size[axis])) {
25727
+ throw new MineRenderError(`Litematica region ${name} block entity is outside its dimensions`);
25728
+ }
25729
+ const index = pos[0] + pos[2] * width + pos[1] * width * length;
25730
+ if (blockEntities.has(index)) throw new MineRenderError(`Duplicate Litematica block entity at index ${index}`);
25731
+ const value = { ...tile };
25732
+ for (const [axis, key] of ["x", "y", "z"].entries()) value[key] = { type: "int", value: pos[axis] + min[axis] };
25733
+ blockEntities.set(index, { type: "compound", value });
25734
+ }
25735
+ const blocks = [];
25736
+ const mask = 2 ** bits - 1;
25737
+ for (let index = 0; index < volume; index++) {
25738
+ const word = Math.floor(index * bits / 64);
25739
+ const shift = index * bits % 64;
25740
+ const [high, low] = words[word];
25741
+ let value = shift < 32 ? low >>> shift : high >>> shift - 32;
25742
+ if (shift < 32 && shift + bits > 32) value |= high << 32 - shift;
25743
+ if (shift + bits > 64) value |= words[word + 1][1] << 64 - shift;
25744
+ const id = (value & mask) >>> 0;
25745
+ const state = palette[id];
25746
+ if (!state) throw new MineRenderError(`Litematica region ${name} palette index ${id} does not exist`);
25747
+ const pos = [
25748
+ index % width + min[0],
25749
+ Math.floor(index / (width * length)) + min[1],
25750
+ Math.floor(index / width) % length + min[2]
25751
+ ];
25752
+ const key = pos.join(",");
25753
+ if (["minecraft:air", "minecraft:cave_air", "minecraft:void_air"].includes(state.type)) {
25754
+ combined.delete(key);
25755
+ continue;
25756
+ }
25757
+ const block = { type: state.type, properties: { ...state.properties }, position: pos, nbt: blockEntities.get(index) };
25758
+ blocks.push(block);
25759
+ combined.set(key, block);
25760
+ }
25761
+ const entities = compounds(tags.Entities, "Entities").map((entity) => {
25762
+ const pos = entity.Pos;
25763
+ if (pos?.type !== "list" || pos.value.type !== "double" || pos.value.value.length !== 3 || !pos.value.value.every(Number.isFinite)) {
25764
+ throw new MineRenderError(`Litematica region ${name} entity Pos must contain three doubles`);
25765
+ }
25766
+ const absolute = pos.value.value.map((value2, axis) => value2 + position[axis]);
25767
+ const value = { ...entity, Pos: { type: "list", value: { type: "double", value: [...absolute] } } };
25768
+ return { position: absolute, nbt: { type: "compound", value } };
25769
+ });
25770
+ return { name, position, signedSize, size, blocks, entities, dataVersion };
25771
+ }
25772
+ function minimum(position, size) {
25773
+ return position + Math.min(0, size + 1);
25774
+ }
25775
+ function integer2(tag, name) {
25776
+ if (tag?.type !== "int" || !Number.isInteger(tag.value) || tag.value < -2147483648 || tag.value > 2147483647) {
25777
+ throw new MineRenderError(`Litematica ${name} must be an int`);
25778
+ }
25779
+ return tag.value;
25780
+ }
25781
+ function compound(tag, name) {
25782
+ if (tag?.type !== "compound" || !tag.value || typeof tag.value !== "object" || Array.isArray(tag.value)) {
25783
+ throw new MineRenderError(`Litematica ${name} must be a compound`);
25784
+ }
25785
+ return tag.value;
25786
+ }
25787
+ function blockPosition(tags) {
25788
+ return ["x", "y", "z"].map((axis) => integer2(tags[axis], axis));
25789
+ }
25790
+ function compounds(tag, name) {
25791
+ if (tag === void 0) return [];
25792
+ if (tag.type === "list" && Array.isArray(tag.value.value)) {
25793
+ if (tag.value.value.length === 0) return [];
25794
+ if (tag.value.type === "compound") return tag.value.value;
25795
+ }
25796
+ throw new MineRenderError(`Litematica ${name} must be a compound list`);
25797
+ }
25798
+ function blockState(tags) {
25799
+ const name = tags.Name;
25800
+ if (name?.type !== "string" || !/^(?:[a-z0-9_.-]+:)?[a-z0-9_./-]+$/.test(name.value)) {
25801
+ throw new MineRenderError("Litematica palette entry must have a block Name");
25802
+ }
25803
+ const properties = {};
25804
+ if (tags.Properties !== void 0) {
25805
+ for (const [key, tag] of Object.entries(compound(tags.Properties, "block Properties"))) {
25806
+ if (tag?.type !== "string") throw new MineRenderError("Litematica block properties must be strings");
25807
+ Object.defineProperty(properties, key, { value: tag.value, enumerable: true, writable: true, configurable: true });
25808
+ }
25809
+ }
25810
+ return { type: name.value.includes(":") ? name.value : `minecraft:${name.value}`, properties };
25811
+ }
25812
+
25789
25813
  // src/model/multiblock/SchematicParser.ts
25814
+ init_MineRenderError();
25790
25815
  var SchematicParser = class {
25791
25816
  /**
25792
25817
  * Parses a legacy schematic using custom mappings before the bundled defaults.
@@ -25828,35 +25853,21 @@ var SchematicParser = class {
25828
25853
  tileEntities.set((y * length + z) * width + x, { type: "compound", value: tile });
25829
25854
  }
25830
25855
  const blocks = [];
25831
- const mapping = legacyBlocks_default.blocks;
25832
25856
  for (let index = 0; index < volume; index++) {
25833
25857
  const packed = addBlocks?.value[index >> 1] ?? 0;
25834
25858
  const high = packed >> (index & 1) * 4 & 15;
25835
25859
  const id = high << 8 | ids.value[index] & 255;
25836
25860
  const metadata = data.value[index] & 255;
25837
- const key = `${id}:${metadata}`;
25838
- let mapped = customMappings[key] ?? mapping[key];
25839
- if (!mapped && options.lenient) {
25840
- mapped = customMappings[`${id}:0`] ?? mapping[`${id}:0`];
25841
- }
25842
- if (!mapped) {
25861
+ const block = resolveLegacyBlock(id, metadata, customMappings, options.lenient);
25862
+ if (!block) {
25843
25863
  if (options.lenient) {
25844
25864
  continue;
25845
25865
  }
25846
25866
  throw new MineRenderError(`Unsupported legacy block ${id}:${metadata} at schematic index ${index}`);
25847
25867
  }
25848
- const [type, state] = mapped.split("[");
25849
- if (type === "minecraft:air") continue;
25850
- const properties = {};
25851
- if (state) {
25852
- for (const property of state.slice(0, -1).split(",")) {
25853
- const [key2, value] = property.split("=");
25854
- properties[key2] = value;
25855
- }
25856
- }
25868
+ if (block.type === "minecraft:air") continue;
25857
25869
  blocks.push({
25858
- type,
25859
- properties,
25870
+ ...block,
25860
25871
  position: [index % width, Math.floor(index / (width * length)), Math.floor(index / width) % length],
25861
25872
  nbt: tileEntities.get(index)
25862
25873
  });
@@ -29238,10 +29249,13 @@ var AnvilParser = class {
29238
29249
  return this.locations(this.bytes(data)).map(({ x, z }) => ({ x, z }));
29239
29250
  }
29240
29251
  /** Decodes all stored chunks. Gzip, zlib, LZ4, and uncompressed payloads are supported. */
29241
- static async parse(data) {
29252
+ static async parse(data, options = {}) {
29253
+ options.signal?.throwIfAborted();
29242
29254
  const bytes = this.bytes(data);
29243
29255
  const chunks = [];
29244
- for (const location of this.locations(bytes)) chunks.push(await this.readChunk(bytes, location));
29256
+ for (const location of this.locations(bytes)) {
29257
+ chunks.push(await this.abortable(this.readChunk(bytes, location, options), options.signal));
29258
+ }
29245
29259
  return { chunks };
29246
29260
  }
29247
29261
  /**
@@ -29250,13 +29264,28 @@ var AnvilParser = class {
29250
29264
  * @param localZ - Chunk z within the region, from 0 to 31.
29251
29265
  * @returns The chunk with absolute coordinates, or `undefined` if the region has no chunk there.
29252
29266
  */
29253
- static async parseChunk(data, localX, localZ) {
29267
+ static async parseChunk(data, localX, localZ, options = {}) {
29268
+ options.signal?.throwIfAborted();
29254
29269
  if (![localX, localZ].every((value) => Number.isInteger(value) && value >= 0 && value < 32)) {
29255
29270
  throw new RangeError("Region-local chunk coordinates must be integers from 0 to 31");
29256
29271
  }
29257
29272
  const bytes = this.bytes(data);
29258
29273
  const location = this.locations(bytes).find(({ x, z }) => x === localX && z === localZ);
29259
- return location ? this.readChunk(bytes, location) : void 0;
29274
+ return location ? this.abortable(this.readChunk(bytes, location, options), options.signal) : void 0;
29275
+ }
29276
+ static async abortable(promise, signal) {
29277
+ if (!signal) return promise;
29278
+ let abort;
29279
+ const cancelled = new Promise((_, reject) => {
29280
+ abort = () => reject(signal.reason);
29281
+ signal.addEventListener("abort", abort, { once: true });
29282
+ if (signal.aborted) abort();
29283
+ });
29284
+ try {
29285
+ return await Promise.race([promise, cancelled]);
29286
+ } finally {
29287
+ signal.removeEventListener("abort", abort);
29288
+ }
29260
29289
  }
29261
29290
  static bytes(data) {
29262
29291
  return data instanceof Uint8Array ? data : new Uint8Array(data);
@@ -29282,20 +29311,41 @@ var AnvilParser = class {
29282
29311
  }
29283
29312
  return locations;
29284
29313
  }
29285
- static async readChunk(data, location) {
29286
- const compression = data[location.offset + 4];
29287
- if (compression & 128) throw new MineRenderError("External Anvil .mcc chunks are not supported");
29314
+ static async readChunk(data, location, options) {
29315
+ options.signal?.throwIfAborted();
29316
+ const format2 = data[location.offset + 4];
29317
+ const external = (format2 & 128) !== 0;
29318
+ const compression = format2 & 127;
29288
29319
  if (compression !== 1 && compression !== 2 && compression !== 3 && compression !== 4) {
29289
29320
  throw new MineRenderError(`Unsupported Anvil compression ${compression}`);
29290
29321
  }
29291
29322
  let payload = data.subarray(location.offset + 5, location.offset + 4 + location.length);
29323
+ let expected;
29324
+ if (external) {
29325
+ if (!options.region || !options.readExternalChunk) {
29326
+ throw new MineRenderError("External Anvil .mcc chunks require region coordinates and readExternalChunk");
29327
+ }
29328
+ if (![options.region.x, options.region.z].every(Number.isSafeInteger)) {
29329
+ throw new RangeError("Anvil region coordinates must be safe integers");
29330
+ }
29331
+ expected = { x: options.region.x * 32 + location.x, z: options.region.z * 32 + location.z };
29332
+ if (![expected.x, expected.z].every(Number.isSafeInteger)) {
29333
+ throw new RangeError("External Anvil chunk coordinates must be safe integers");
29334
+ }
29335
+ const externalData = await options.readExternalChunk(expected.x, expected.z, options.signal);
29336
+ options.signal?.throwIfAborted();
29337
+ if (externalData === void 0) throw new MineRenderError(`External Anvil chunk c.${expected.x}.${expected.z}.mcc is missing`);
29338
+ payload = this.bytes(externalData);
29339
+ }
29292
29340
  if (compression === 4) {
29293
29341
  payload = decodeAnvilLz4(payload);
29294
29342
  } else if (compression !== 3) {
29295
29343
  const stream = new Blob([payload]).stream().pipeThrough(new DecompressionStream(compression === 1 ? "gzip" : "deflate"));
29296
29344
  payload = new Uint8Array(await new Response(stream).arrayBuffer());
29297
29345
  }
29346
+ options.signal?.throwIfAborted();
29298
29347
  const nbt = await NBTHelper.fromBuffer(payload, "big");
29348
+ options.signal?.throwIfAborted();
29299
29349
  const root = nbt.value;
29300
29350
  const level = root.Level?.type === "compound" ? root.Level.value : root;
29301
29351
  const dataVersion = root.DataVersion?.type === "int" ? root.DataVersion.value : void 0;
@@ -29308,28 +29358,37 @@ var AnvilParser = class {
29308
29358
  if ((chunk.x % 32 + 32) % 32 !== location.x || (chunk.z % 32 + 32) % 32 !== location.z) {
29309
29359
  throw new MineRenderError("Anvil chunk coordinates do not match its region location");
29310
29360
  }
29361
+ if (expected && (chunk.x !== expected.x || chunk.z !== expected.z)) {
29362
+ throw new MineRenderError(`Anvil chunk coordinates do not match external file c.${expected.x}.${expected.z}.mcc`);
29363
+ }
29364
+ const numericStates = /* @__PURE__ */ new Map();
29311
29365
  for (const section of this.compounds(level.sections ?? level.Sections, "sections")) {
29312
29366
  const y = this.integer(section.Y, "section Y");
29367
+ let data2;
29313
29368
  if (section.Blocks || section.Data || section.Add) {
29314
- throw new MineRenderError("Numeric Anvil block IDs before Minecraft 1.13 are not supported");
29315
- }
29316
- if (section.block_states && section.block_states.type !== "compound") {
29317
- throw new MineRenderError("Anvil block_states must be a compound");
29318
- }
29319
- const modern = section.block_states?.type === "compound" ? section.block_states.value : void 0;
29320
- const paletteTag = modern ? modern.palette : section.Palette;
29321
- const palette = paletteTag?.type === "list" && paletteTag.value.type === "string" ? paletteTag.value.value : this.compounds(paletteTag, "block palette");
29322
- const data2 = new ChunkData();
29323
- if (palette.length) {
29324
- const states = palette.map((entry) => this.block(entry));
29325
- const indices = this.unpack(modern ? modern.data : section.BlockStates, states.length, !!modern, dataVersion);
29326
- for (let index = 0; index < 4096; index++) {
29327
- const state = states[indices[index]];
29328
- if (!state) throw new MineRenderError(`Anvil palette index ${indices[index]} is out of range`);
29329
- if (!ChunkData.isAir(state)) data2.set(index, state);
29369
+ if (section.Palette || section.BlockStates || section.block_states) {
29370
+ throw new MineRenderError(`Anvil section ${y} mixes numeric and paletted block states`);
29371
+ }
29372
+ data2 = this.numericSection(section, y, options, numericStates);
29373
+ } else {
29374
+ if (section.block_states && section.block_states.type !== "compound") {
29375
+ throw new MineRenderError("Anvil block_states must be a compound");
29376
+ }
29377
+ const modern = section.block_states?.type === "compound" ? section.block_states.value : void 0;
29378
+ const paletteTag = modern ? modern.palette : section.Palette;
29379
+ const palette = paletteTag?.type === "list" && paletteTag.value.type === "string" ? paletteTag.value.value : this.compounds(paletteTag, "block palette");
29380
+ data2 = new ChunkData();
29381
+ if (palette.length) {
29382
+ const states = palette.map((entry) => this.block(entry));
29383
+ const indices = this.unpack(modern ? modern.data : section.BlockStates, states.length, !!modern, dataVersion);
29384
+ for (let index = 0; index < 4096; index++) {
29385
+ const state = states[indices[index]];
29386
+ if (!state) throw new MineRenderError(`Anvil palette index ${indices[index]} is out of range`);
29387
+ if (!ChunkData.isAir(state)) data2.set(index, state);
29388
+ }
29389
+ } else if (modern || section.Palette || section.BlockStates) {
29390
+ throw new MineRenderError("Anvil section has block states without a palette");
29330
29391
  }
29331
- } else if (modern || section.Palette || section.BlockStates) {
29332
- throw new MineRenderError("Anvil section has block states without a palette");
29333
29392
  }
29334
29393
  if (chunk.sections.some((section2) => section2.y === y)) throw new MineRenderError(`Duplicate Anvil section Y ${y}`);
29335
29394
  chunk.sections.push({ y, data: data2 });
@@ -29357,6 +29416,33 @@ var AnvilParser = class {
29357
29416
  }
29358
29417
  return chunk;
29359
29418
  }
29419
+ static numericSection(section, y, options, states) {
29420
+ const bytes = (name, length, optional = false) => {
29421
+ const tag = section[name];
29422
+ if (!tag && optional) return void 0;
29423
+ if (tag?.type !== "byteArray" || !Array.isArray(tag.value) || tag.value.length !== length) {
29424
+ throw new MineRenderError(`Anvil section ${y} ${name} must contain ${length} bytes`);
29425
+ }
29426
+ return tag.value;
29427
+ };
29428
+ const ids = bytes("Blocks", 4096);
29429
+ const metadata = bytes("Data", 2048, true);
29430
+ const extra = bytes("Add", 2048, true);
29431
+ const data = new ChunkData();
29432
+ for (let index = 0; index < 4096; index++) {
29433
+ const shift = (index & 1) * 4;
29434
+ const id = ids[index] & 255 | ((extra?.[index >> 1] ?? 0) >> shift & 15) << 8;
29435
+ const value = (metadata?.[index >> 1] ?? 0) >> shift & 15;
29436
+ const key = id * 16 + value;
29437
+ if (!states.has(key)) states.set(key, resolveLegacyBlock(id, value, options.legacyMappings, options.lenient));
29438
+ const block = states.get(key);
29439
+ if (!block && !options.lenient) {
29440
+ throw new MineRenderError(`Unsupported legacy block ${id}:${value} at Anvil section ${y} index ${index}`);
29441
+ }
29442
+ if (block && !ChunkData.isAir(block)) data.set(index, block);
29443
+ }
29444
+ return data;
29445
+ }
29360
29446
  static compounds(tag, name) {
29361
29447
  if (!tag) return [];
29362
29448
  if (tag.type !== "list" || tag.value.type !== "compound" && tag.value.value.length !== 0) {
@@ -29439,6 +29525,7 @@ var AnvilWorldSource = class {
29439
29525
  this.cachedBytes = 0;
29440
29526
  this.maxCachedRegions = options.maxCachedRegions ?? 4;
29441
29527
  this.maxCachedBytes = options.maxCachedBytes ?? 64 * 1024 * 1024;
29528
+ this.parseOptions = { readExternalChunk: options.readExternalChunk, legacyMappings: options.legacyMappings, lenient: options.lenient };
29442
29529
  for (const [name, value] of Object.entries({ maxCachedRegions: this.maxCachedRegions, maxCachedBytes: this.maxCachedBytes })) {
29443
29530
  if (!Number.isSafeInteger(value) || value < 0) throw new RangeError(`${name} must be a nonnegative safe integer`);
29444
29531
  }
@@ -29452,7 +29539,11 @@ var AnvilWorldSource = class {
29452
29539
  const data = await this.getRegion(regionX, regionZ, signal);
29453
29540
  signal?.throwIfAborted();
29454
29541
  if (!data) return void 0;
29455
- const chunk = await abortable2(AnvilParser.parseChunk(data, x - regionX * 32, z - regionZ * 32), signal);
29542
+ const chunk = await AnvilParser.parseChunk(data, x - regionX * 32, z - regionZ * 32, {
29543
+ ...this.parseOptions,
29544
+ region: { x: regionX, z: regionZ },
29545
+ signal
29546
+ });
29456
29547
  signal?.throwIfAborted();
29457
29548
  if (chunk && (chunk.x !== x || chunk.z !== z)) {
29458
29549
  throw new MineRenderError(`Anvil chunk coordinates ${chunk.x},${chunk.z} do not match requested column ${x},${z}`);
@@ -29483,7 +29574,7 @@ var AnvilWorldSource = class {
29483
29574
  settled: false,
29484
29575
  promise: Promise.resolve().then(() => {
29485
29576
  controller.signal.throwIfAborted();
29486
- return this.readRegion(x, z, controller.signal);
29577
+ return abortable2(this.readRegion(x, z, controller.signal), controller.signal);
29487
29578
  }).then((data) => {
29488
29579
  controller.signal.throwIfAborted();
29489
29580
  let bytes = data instanceof Uint8Array ? data : data === void 0 ? void 0 : new Uint8Array(data);
@@ -30832,6 +30923,7 @@ var WorldStreamer = class {
30832
30923
  parseSceneDocument,
30833
30924
  prefix,
30834
30925
  registerNodeEnv,
30926
+ resolveLegacyBlock,
30835
30927
  sampleEntityAnimation,
30836
30928
  sampleEntityKeyframes,
30837
30929
  sampleFluid,