minerender 2.1.0-alpha.127 → 2.1.0-alpha.129

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.
@@ -251,6 +251,13 @@ var Caching;
251
251
  var init_Caching = __esm({
252
252
  "src/cache/Caching.ts"() {
253
253
  Caching = class _Caching {
254
+ static {
255
+ this._generation = 0;
256
+ }
257
+ /** Changes when shared assets are cleared, so derived caches can discard their old values. */
258
+ static get generation() {
259
+ return this._generation;
260
+ }
254
261
  static {
255
262
  this.rawImageCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(5)).buildAsync();
256
263
  }
@@ -361,6 +368,7 @@ var init_Caching = __esm({
361
368
  }
362
369
  /** Invalidates in-memory entries so later lookups reload them. Persisted entries are retained. */
363
370
  static clear() {
371
+ this._generation++;
364
372
  for (const cache of this.all) {
365
373
  cache.invalidateAll();
366
374
  }
@@ -372,6 +380,7 @@ var init_Caching = __esm({
372
380
  * Use {@link shutdown} to also stop requests and the shared Ticker.
373
381
  */
374
382
  static end() {
383
+ this._generation++;
375
384
  for (const cache of this.all) {
376
385
  cache.invalidateAll();
377
386
  cache.end();
@@ -1264,118 +1273,6 @@ var init_Materials = __esm({
1264
1273
  }
1265
1274
  });
1266
1275
 
1267
- // src/model/Model.ts
1268
- function isDoubleArray(obj) {
1269
- return Array.isArray(obj) && obj.length === 2;
1270
- }
1271
- function isTripleArray(obj) {
1272
- return Array.isArray(obj) && obj.length === 3;
1273
- }
1274
- function isQuadArray(obj) {
1275
- return Array.isArray(obj) && obj.length === 4;
1276
- }
1277
- var ITEM_GENERATED, BUILTIN_GENERATED, BUILTIN_ENTITY, DEFAULT_ELEMENTS;
1278
- var init_Model = __esm({
1279
- "src/model/Model.ts"() {
1280
- ITEM_GENERATED = "item/generated";
1281
- BUILTIN_GENERATED = "builtin/generated";
1282
- BUILTIN_ENTITY = "builtin/entity";
1283
- DEFAULT_ELEMENTS = [];
1284
- }
1285
- });
1286
-
1287
- // ../../node_modules/ts-deepmerge/dist/index.js
1288
- var require_dist = __commonJS({
1289
- "../../node_modules/ts-deepmerge/dist/index.js"(exports) {
1290
- "use strict";
1291
- var __assign = exports && exports.__assign || function() {
1292
- __assign = Object.assign || function(t) {
1293
- for (var s, i = 1, n = arguments.length; i < n; i++) {
1294
- s = arguments[i];
1295
- for (var p10 in s) if (Object.prototype.hasOwnProperty.call(s, p10))
1296
- t[p10] = s[p10];
1297
- }
1298
- return t;
1299
- };
1300
- return __assign.apply(this, arguments);
1301
- };
1302
- var __read = exports && exports.__read || function(o, n) {
1303
- var m = typeof Symbol === "function" && o[Symbol.iterator];
1304
- if (!m) return o;
1305
- var i = m.call(o), r, ar = [], e;
1306
- try {
1307
- while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
1308
- } catch (error) {
1309
- e = { error };
1310
- } finally {
1311
- try {
1312
- if (r && !r.done && (m = i["return"])) m.call(i);
1313
- } finally {
1314
- if (e) throw e.error;
1315
- }
1316
- }
1317
- return ar;
1318
- };
1319
- var __spreadArray = exports && exports.__spreadArray || function(to, from, pack) {
1320
- if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
1321
- if (ar || !(i in from)) {
1322
- if (!ar) ar = Array.prototype.slice.call(from, 0, i);
1323
- ar[i] = from[i];
1324
- }
1325
- }
1326
- return to.concat(ar || Array.prototype.slice.call(from));
1327
- };
1328
- Object.defineProperty(exports, "__esModule", { value: true });
1329
- var isObject = function(obj) {
1330
- if (typeof obj === "object" && obj !== null) {
1331
- if (typeof Object.getPrototypeOf === "function") {
1332
- var prototype = Object.getPrototypeOf(obj);
1333
- return prototype === Object.prototype || prototype === null;
1334
- }
1335
- return Object.prototype.toString.call(obj) === "[object Object]";
1336
- }
1337
- return false;
1338
- };
1339
- var PROTECTED_KEYS = ["__proto__"];
1340
- var merge10 = function() {
1341
- var objects = [];
1342
- for (var _i = 0; _i < arguments.length; _i++) {
1343
- objects[_i] = arguments[_i];
1344
- }
1345
- return objects.reduce(function(result, current) {
1346
- Object.keys(current).forEach(function(key) {
1347
- if (PROTECTED_KEYS.includes(key)) {
1348
- return;
1349
- }
1350
- if (Array.isArray(result[key]) && Array.isArray(current[key])) {
1351
- result[key] = merge10.options.mergeArrays ? Array.from(new Set(result[key].concat(current[key]))) : current[key];
1352
- } else if (isObject(result[key]) && isObject(current[key])) {
1353
- result[key] = merge10(result[key], current[key]);
1354
- } else {
1355
- result[key] = current[key];
1356
- }
1357
- });
1358
- return result;
1359
- }, {});
1360
- };
1361
- var defaultOptions = {
1362
- mergeArrays: true
1363
- };
1364
- merge10.options = defaultOptions;
1365
- merge10.withOptions = function(options) {
1366
- var objects = [];
1367
- for (var _i = 1; _i < arguments.length; _i++) {
1368
- objects[_i - 1] = arguments[_i];
1369
- }
1370
- merge10.options = __assign({ mergeArrays: true }, options);
1371
- var result = merge10.apply(void 0, __spreadArray([], __read(objects), false));
1372
- merge10.options = defaultOptions;
1373
- return result;
1374
- };
1375
- exports.default = merge10;
1376
- }
1377
- });
1378
-
1379
1276
  // src/assets/AssetKey.ts
1380
1277
  function isResourceLocation(value) {
1381
1278
  return typeof value === "string" && /^(?:[a-z0-9_.-]+:)?[a-z0-9_./-]+$/.test(value);
@@ -1478,17 +1375,306 @@ var init_AssetKey = __esm({
1478
1375
  if (this.type) {
1479
1376
  p10.push(this.type);
1480
1377
  }
1481
- p10.push(this.path);
1482
- return p10.join("/");
1378
+ p10.push(this.path);
1379
+ return p10.join("/");
1380
+ }
1381
+ toNamespacedString() {
1382
+ return this.namespace + ":" + this.getFullPath();
1383
+ }
1384
+ /** Returns a cache identifier that includes the explicit root or {@link AssetLoader.ROOT}. */
1385
+ serialize() {
1386
+ return this.toString();
1387
+ }
1388
+ };
1389
+ }
1390
+ });
1391
+
1392
+ // src/assets/source/parser/AssetParsers.ts
1393
+ var AssetParser;
1394
+ var init_AssetParsers = __esm({
1395
+ "src/assets/source/parser/AssetParsers.ts"() {
1396
+ AssetParser = /* @__PURE__ */ ((AssetParser2) => {
1397
+ AssetParser2["MODEL"] = "model";
1398
+ AssetParser2["BLOCKSTATE"] = "blockstate";
1399
+ AssetParser2["META"] = "meta";
1400
+ AssetParser2["NBT"] = "nbt";
1401
+ AssetParser2["IMAGE"] = "image";
1402
+ AssetParser2["LIST"] = "list";
1403
+ AssetParser2["JSON"] = "json";
1404
+ return AssetParser2;
1405
+ })(AssetParser || {});
1406
+ }
1407
+ });
1408
+
1409
+ // src/assets/MineRenderData.ts
1410
+ var object, release, compare, MineRenderData;
1411
+ var init_MineRenderData = __esm({
1412
+ "src/assets/MineRenderData.ts"() {
1413
+ init_AssetKey();
1414
+ init_AssetLoader();
1415
+ init_AssetParsers();
1416
+ init_Caching();
1417
+ object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
1418
+ release = (value) => /^\d+\.\d+(?:\.\d+)?$/.test(value) ? value.split(".").map(Number) : void 0;
1419
+ compare = (left, right) => {
1420
+ for (let i = 0; i < 3; i++) {
1421
+ const difference = (left[i] ?? 0) - (right[i] ?? 0);
1422
+ if (difference) return difference;
1423
+ }
1424
+ return 0;
1425
+ };
1426
+ MineRenderData = class {
1427
+ /** Loads one registry. Missing files and invalid data reject the request. */
1428
+ static async get(dataset, root) {
1429
+ root = root?.replace(/\/+$/, "");
1430
+ const scope = AssetLoader.persistentScope;
1431
+ const defaultRoot = AssetLoader.ROOT;
1432
+ const resolved = await this.resolve(root);
1433
+ const entry = resolved.manifest.datasets[dataset];
1434
+ if (!entry) throw new Error(`MineRender ${dataset} data is unavailable for ${resolved.version}: ${resolved.manifest.unavailable[dataset] ?? "not listed in the manifest"}`);
1435
+ const file = entry.file;
1436
+ const requestRoot = resolved.root === (root ?? defaultRoot).replace(/\/+$/, "") ? root : resolved.root;
1437
+ return await this.cached(`${requestRoot === void 0 ? "sources" : "explicit"}:${resolved.root}/${file}:${entry.sha256}`, scope, defaultRoot, async () => {
1438
+ const data = await this.load(file.slice(0, -5), requestRoot);
1439
+ if (!this.validDataset(dataset, data)) throw new Error(`Invalid MineRender ${dataset} data at ${resolved.root}/minerender-data/${file}`);
1440
+ return data;
1441
+ });
1442
+ }
1443
+ /**
1444
+ * Uses the exact version when published. Otherwise, selects the nearest patch in the same
1445
+ * release line (older on ties), then the latest earlier release or the oldest published release.
1446
+ * Unpublished snapshots reject.
1447
+ */
1448
+ static async resolve(root) {
1449
+ const requestRoot = root?.replace(/\/+$/, "");
1450
+ const defaultRoot = AssetLoader.ROOT;
1451
+ root = (root ?? defaultRoot).replace(/\/+$/, "");
1452
+ const scope = AssetLoader.persistentScope;
1453
+ return this.cached(`resolve:${requestRoot === void 0 ? "sources" : "explicit"}:${root}`, scope, defaultRoot, async () => {
1454
+ const requestedVersion = root.slice(root.lastIndexOf("/") + 1);
1455
+ let manifest = await this.manifest(root, requestRoot !== void 0 && release(requestedVersion) ? requestedVersion : void 0, requestRoot);
1456
+ if (manifest) return { requestedVersion, version: manifest.minecraftVersion, root, manifest };
1457
+ const requested = release(requestedVersion);
1458
+ if (!requested) {
1459
+ throw new Error(`No MineRender data is published for ${requestedVersion}; automatic fallback requires a release`);
1460
+ }
1461
+ const base = root.slice(0, root.lastIndexOf("/"));
1462
+ const index = await this.cached(`versions:${base}`, scope, defaultRoot, async () => {
1463
+ const value = await this.load("versions", base);
1464
+ if (!object(value) || !Array.isArray(value.versions) || !value.versions.every((entry) => object(entry) && typeof entry.name === "string")) {
1465
+ throw new Error(`Invalid MineRender version index at ${base}/minerender-data/versions.json`);
1466
+ }
1467
+ return value.versions;
1468
+ });
1469
+ const versions = index.map((entry) => ({ name: entry.name, number: release(entry.name) })).filter((entry) => !!entry.number).sort((left, right) => compare(left.number, right.number));
1470
+ const sameLine = versions.filter((entry) => entry.number[0] === requested[0] && entry.number[1] === requested[1]);
1471
+ const selected = sameLine.sort((left, right) => Math.abs((left.number[2] ?? 0) - (requested[2] ?? 0)) - Math.abs((right.number[2] ?? 0) - (requested[2] ?? 0)) || compare(left.number, right.number))[0] ?? versions.filter((entry) => compare(entry.number, requested) <= 0).pop() ?? versions[0];
1472
+ if (!selected) throw new Error(`No compatible MineRender data is published for ${requestedVersion}`);
1473
+ const resolvedRoot = `${base}/${selected.name}`;
1474
+ manifest = await this.manifest(resolvedRoot, selected.name, resolvedRoot);
1475
+ if (!manifest) throw new Error(`Missing MineRender manifest for published version ${selected.name}`);
1476
+ return { requestedVersion, version: selected.name, root: resolvedRoot, manifest };
1477
+ });
1478
+ }
1479
+ static async manifest(root, expectedVersion, requestRoot) {
1480
+ const data = await this.load("manifest", requestRoot);
1481
+ if (data === void 0) return void 0;
1482
+ if (!object(data) || data.schemaVersion !== 1 || typeof data.minecraftVersion !== "string" || !Number.isInteger(data.dataVersion) || !object(data.unavailable) || !Object.values(data.unavailable).every((value) => typeof value === "string") || !object(data.datasets) || !Object.values(data.datasets).every((entry) => {
1483
+ return object(entry) && typeof entry.file === "string" && /^[a-z0-9][a-z0-9._-]*\.json$/.test(entry.file) && typeof entry.sha256 === "string" && /^[0-9a-f]{64}$/.test(entry.sha256);
1484
+ }) || expectedVersion !== void 0 && data.minecraftVersion !== expectedVersion) {
1485
+ throw new Error(`Invalid MineRender manifest at ${root}/minerender-data/manifest.json`);
1486
+ }
1487
+ return data;
1488
+ }
1489
+ static load(file, root) {
1490
+ const key = new AssetKey("minerender-data", file, void 0, void 0, void 0, ".json", root);
1491
+ key.rootType = void 0;
1492
+ return AssetLoader.get(key, "json" /* JSON */);
1493
+ }
1494
+ static async cached(key, scope, defaultRoot, loader) {
1495
+ const checkSources = () => {
1496
+ if (AssetLoader.persistentScope !== scope || AssetLoader.ROOT !== defaultRoot) throw new Error("Asset sources changed while loading MineRender data; retry the request");
1497
+ };
1498
+ checkSources();
1499
+ return await Caching.mineRenderDataCache.get(scope ? `${scope}
1500
+ ${key}` : key, async () => {
1501
+ const value = await loader();
1502
+ checkSources();
1503
+ return value;
1504
+ });
1505
+ }
1506
+ static validDataset(dataset, data) {
1507
+ if (!object(data)) return false;
1508
+ if (dataset === "potionColors") return object(data.effects) && Object.values(data.effects).every(Number.isInteger) && object(data.potions) && Object.values(data.potions).every((effects) => Array.isArray(effects) && effects.every((effect) => object(effect) && typeof effect.id === "string" && Number.isInteger(effect.amplifier)));
1509
+ if (dataset === "renames") return data.format === "data-fixer-renames" && Number.isInteger(data.baseDataVersion) && ["blocks", "items", "entities"].every((kind) => object(data[kind]) && Object.values(data[kind]).every((entry) => object(entry) && typeof entry.registered === "boolean" && Array.isArray(entry.ranges) && entry.ranges.length > 0 && entry.ranges.every((range) => object(range) && Number.isInteger(range.before) && typeof range.to === "string")));
1510
+ if (dataset === "legacyBlocks") return object(data.blocks) && Object.values(data.blocks).every((value) => typeof value === "string");
1511
+ if (dataset === "blockCulling") return Array.isArray(data.profiles) && data.profiles.every((profile) => object(profile) && typeof profile.type === "string") && object(data.blocks) && Object.values(data.blocks).every((index) => Number.isInteger(index) && index >= 0 && index < data.profiles.length) && (data.groups === void 0 || object(data.groups) && Object.values(data.groups).every((group) => Array.isArray(group) && group.every((type) => typeof type === "string")));
1512
+ return Object.values(data).every((value) => {
1513
+ if (!object(value)) return false;
1514
+ switch (dataset) {
1515
+ case "itemDefaults":
1516
+ return true;
1517
+ case "blockStates":
1518
+ return Object.values(value).every((property) => object(property) && "default" in property && Array.isArray(property.values));
1519
+ case "blockTints":
1520
+ return ["constant", "grass", "stem", "redstone"].includes(value.source) && (value.source !== "constant" || Number.isInteger(value.color)) && (value.biome === void 0 || ["grass", "foliage", "dry_foliage", "water"].includes(value.biome));
1521
+ case "fluids":
1522
+ return ["water", "lava"].includes(value.kind) && typeof value.renderModel === "boolean" && (value.levelProperty === void 0 || typeof value.levelProperty === "string");
1523
+ case "dyes":
1524
+ return [value.id, value.textureDiffuseColor, value.fireworkColor, value.textColor].every(Number.isInteger);
1525
+ default:
1526
+ return false;
1527
+ }
1528
+ });
1529
+ }
1530
+ };
1531
+ }
1532
+ });
1533
+
1534
+ // src/assets/Renames.ts
1535
+ var Renames;
1536
+ var init_Renames = __esm({
1537
+ "src/assets/Renames.ts"() {
1538
+ init_AssetKey();
1539
+ init_MineRenderData();
1540
+ Renames = class {
1541
+ /** Loads the selected version's rename table, or `undefined` when its published data has none. */
1542
+ static async get(root) {
1543
+ const resolved = await MineRenderData.resolve(root);
1544
+ return resolved.manifest.datasets.renames ? MineRenderData.get("renames", root) : void 0;
1545
+ }
1546
+ /**
1547
+ * Returns the ID that replaced `id`, or `undefined` when `id` keeps its meaning.
1548
+ * Without a data version, an ID that the selected version still registers keeps its meaning.
1549
+ */
1550
+ static async resolve(kind, id, dataVersion, root) {
1551
+ const data = await this.get(root);
1552
+ if (!data || !Object.prototype.hasOwnProperty.call(data[kind], id)) return void 0;
1553
+ const entry = data[kind][id];
1554
+ if (dataVersion === void 0) return entry.registered ? void 0 : entry.ranges[entry.ranges.length - 1].to;
1555
+ if (dataVersion < data.baseDataVersion) return void 0;
1556
+ return entry.ranges.find((range) => dataVersion < range.before)?.to;
1557
+ }
1558
+ /** Returns a copy of `key` for the replacement ID, or `undefined` when the ID keeps its meaning. */
1559
+ static async key(kind, key) {
1560
+ const renamed = await this.resolve(kind, `${key.namespace}:${key.path}`, void 0, key.root);
1561
+ if (!renamed) return void 0;
1562
+ const [namespace, path] = renamed.split(":");
1563
+ return new AssetKey(namespace, path, key.assetType, key.type, key.rootType, key.extension, key.root);
1564
+ }
1565
+ };
1566
+ }
1567
+ });
1568
+
1569
+ // src/model/Model.ts
1570
+ function isDoubleArray(obj) {
1571
+ return Array.isArray(obj) && obj.length === 2;
1572
+ }
1573
+ function isTripleArray(obj) {
1574
+ return Array.isArray(obj) && obj.length === 3;
1575
+ }
1576
+ function isQuadArray(obj) {
1577
+ return Array.isArray(obj) && obj.length === 4;
1578
+ }
1579
+ var ITEM_GENERATED, BUILTIN_GENERATED, BUILTIN_ENTITY, DEFAULT_ELEMENTS;
1580
+ var init_Model = __esm({
1581
+ "src/model/Model.ts"() {
1582
+ ITEM_GENERATED = "item/generated";
1583
+ BUILTIN_GENERATED = "builtin/generated";
1584
+ BUILTIN_ENTITY = "builtin/entity";
1585
+ DEFAULT_ELEMENTS = [];
1586
+ }
1587
+ });
1588
+
1589
+ // ../../node_modules/ts-deepmerge/dist/index.js
1590
+ var require_dist = __commonJS({
1591
+ "../../node_modules/ts-deepmerge/dist/index.js"(exports) {
1592
+ "use strict";
1593
+ var __assign = exports && exports.__assign || function() {
1594
+ __assign = Object.assign || function(t) {
1595
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
1596
+ s = arguments[i];
1597
+ for (var p10 in s) if (Object.prototype.hasOwnProperty.call(s, p10))
1598
+ t[p10] = s[p10];
1599
+ }
1600
+ return t;
1601
+ };
1602
+ return __assign.apply(this, arguments);
1603
+ };
1604
+ var __read = exports && exports.__read || function(o, n) {
1605
+ var m = typeof Symbol === "function" && o[Symbol.iterator];
1606
+ if (!m) return o;
1607
+ var i = m.call(o), r, ar = [], e;
1608
+ try {
1609
+ while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
1610
+ } catch (error) {
1611
+ e = { error };
1612
+ } finally {
1613
+ try {
1614
+ if (r && !r.done && (m = i["return"])) m.call(i);
1615
+ } finally {
1616
+ if (e) throw e.error;
1617
+ }
1618
+ }
1619
+ return ar;
1620
+ };
1621
+ var __spreadArray = exports && exports.__spreadArray || function(to, from, pack) {
1622
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
1623
+ if (ar || !(i in from)) {
1624
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
1625
+ ar[i] = from[i];
1626
+ }
1627
+ }
1628
+ return to.concat(ar || Array.prototype.slice.call(from));
1629
+ };
1630
+ Object.defineProperty(exports, "__esModule", { value: true });
1631
+ var isObject = function(obj) {
1632
+ if (typeof obj === "object" && obj !== null) {
1633
+ if (typeof Object.getPrototypeOf === "function") {
1634
+ var prototype = Object.getPrototypeOf(obj);
1635
+ return prototype === Object.prototype || prototype === null;
1636
+ }
1637
+ return Object.prototype.toString.call(obj) === "[object Object]";
1483
1638
  }
1484
- toNamespacedString() {
1485
- return this.namespace + ":" + this.getFullPath();
1639
+ return false;
1640
+ };
1641
+ var PROTECTED_KEYS = ["__proto__"];
1642
+ var merge10 = function() {
1643
+ var objects = [];
1644
+ for (var _i = 0; _i < arguments.length; _i++) {
1645
+ objects[_i] = arguments[_i];
1486
1646
  }
1487
- /** Returns a cache identifier that includes the explicit root or {@link AssetLoader.ROOT}. */
1488
- serialize() {
1489
- return this.toString();
1647
+ return objects.reduce(function(result, current) {
1648
+ Object.keys(current).forEach(function(key) {
1649
+ if (PROTECTED_KEYS.includes(key)) {
1650
+ return;
1651
+ }
1652
+ if (Array.isArray(result[key]) && Array.isArray(current[key])) {
1653
+ result[key] = merge10.options.mergeArrays ? Array.from(new Set(result[key].concat(current[key]))) : current[key];
1654
+ } else if (isObject(result[key]) && isObject(current[key])) {
1655
+ result[key] = merge10(result[key], current[key]);
1656
+ } else {
1657
+ result[key] = current[key];
1658
+ }
1659
+ });
1660
+ return result;
1661
+ }, {});
1662
+ };
1663
+ var defaultOptions = {
1664
+ mergeArrays: true
1665
+ };
1666
+ merge10.options = defaultOptions;
1667
+ merge10.withOptions = function(options) {
1668
+ var objects = [];
1669
+ for (var _i = 1; _i < arguments.length; _i++) {
1670
+ objects[_i - 1] = arguments[_i];
1490
1671
  }
1672
+ merge10.options = __assign({ mergeArrays: true }, options);
1673
+ var result = merge10.apply(void 0, __spreadArray([], __read(objects), false));
1674
+ merge10.options = defaultOptions;
1675
+ return result;
1491
1676
  };
1677
+ exports.default = merge10;
1492
1678
  }
1493
1679
  });
1494
1680
 
@@ -1614,147 +1800,6 @@ var init_ModelMerger = __esm({
1614
1800
  }
1615
1801
  });
1616
1802
 
1617
- // src/assets/source/parser/AssetParsers.ts
1618
- var AssetParser;
1619
- var init_AssetParsers = __esm({
1620
- "src/assets/source/parser/AssetParsers.ts"() {
1621
- AssetParser = /* @__PURE__ */ ((AssetParser2) => {
1622
- AssetParser2["MODEL"] = "model";
1623
- AssetParser2["BLOCKSTATE"] = "blockstate";
1624
- AssetParser2["META"] = "meta";
1625
- AssetParser2["NBT"] = "nbt";
1626
- AssetParser2["IMAGE"] = "image";
1627
- AssetParser2["LIST"] = "list";
1628
- AssetParser2["JSON"] = "json";
1629
- return AssetParser2;
1630
- })(AssetParser || {});
1631
- }
1632
- });
1633
-
1634
- // src/assets/MineRenderData.ts
1635
- var object, release, compare, MineRenderData;
1636
- var init_MineRenderData = __esm({
1637
- "src/assets/MineRenderData.ts"() {
1638
- init_AssetKey();
1639
- init_AssetLoader();
1640
- init_AssetParsers();
1641
- init_Caching();
1642
- object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
1643
- release = (value) => /^\d+\.\d+(?:\.\d+)?$/.test(value) ? value.split(".").map(Number) : void 0;
1644
- compare = (left, right) => {
1645
- for (let i = 0; i < 3; i++) {
1646
- const difference = (left[i] ?? 0) - (right[i] ?? 0);
1647
- if (difference) return difference;
1648
- }
1649
- return 0;
1650
- };
1651
- MineRenderData = class {
1652
- /** Loads one registry. Missing files and invalid data reject the request. */
1653
- static async get(dataset, root) {
1654
- root = root?.replace(/\/+$/, "");
1655
- const scope = AssetLoader.persistentScope;
1656
- const defaultRoot = AssetLoader.ROOT;
1657
- const resolved = await this.resolve(root);
1658
- const entry = resolved.manifest.datasets[dataset];
1659
- if (!entry) throw new Error(`MineRender ${dataset} data is unavailable for ${resolved.version}: ${resolved.manifest.unavailable[dataset] ?? "not listed in the manifest"}`);
1660
- const file = entry.file;
1661
- const requestRoot = resolved.root === (root ?? defaultRoot).replace(/\/+$/, "") ? root : resolved.root;
1662
- return await this.cached(`${requestRoot === void 0 ? "sources" : "explicit"}:${resolved.root}/${file}:${entry.sha256}`, scope, defaultRoot, async () => {
1663
- const data = await this.load(file.slice(0, -5), requestRoot);
1664
- if (!this.validDataset(dataset, data)) throw new Error(`Invalid MineRender ${dataset} data at ${resolved.root}/minerender-data/${file}`);
1665
- return data;
1666
- });
1667
- }
1668
- /**
1669
- * Uses the exact version when published. Otherwise, selects the nearest patch in the same
1670
- * release line (older on ties), then the latest earlier release or the oldest published release.
1671
- * Unpublished snapshots reject.
1672
- */
1673
- static async resolve(root) {
1674
- const requestRoot = root?.replace(/\/+$/, "");
1675
- const defaultRoot = AssetLoader.ROOT;
1676
- root = (root ?? defaultRoot).replace(/\/+$/, "");
1677
- const scope = AssetLoader.persistentScope;
1678
- return this.cached(`resolve:${requestRoot === void 0 ? "sources" : "explicit"}:${root}`, scope, defaultRoot, async () => {
1679
- const requestedVersion = root.slice(root.lastIndexOf("/") + 1);
1680
- let manifest = await this.manifest(root, requestRoot !== void 0 && release(requestedVersion) ? requestedVersion : void 0, requestRoot);
1681
- if (manifest) return { requestedVersion, version: manifest.minecraftVersion, root, manifest };
1682
- const requested = release(requestedVersion);
1683
- if (!requested) {
1684
- throw new Error(`No MineRender data is published for ${requestedVersion}; automatic fallback requires a release`);
1685
- }
1686
- const base = root.slice(0, root.lastIndexOf("/"));
1687
- const index = await this.cached(`versions:${base}`, scope, defaultRoot, async () => {
1688
- const value = await this.load("versions", base);
1689
- if (!object(value) || !Array.isArray(value.versions) || !value.versions.every((entry) => object(entry) && typeof entry.name === "string")) {
1690
- throw new Error(`Invalid MineRender version index at ${base}/minerender-data/versions.json`);
1691
- }
1692
- return value.versions;
1693
- });
1694
- const versions = index.map((entry) => ({ name: entry.name, number: release(entry.name) })).filter((entry) => !!entry.number).sort((left, right) => compare(left.number, right.number));
1695
- const sameLine = versions.filter((entry) => entry.number[0] === requested[0] && entry.number[1] === requested[1]);
1696
- const selected = sameLine.sort((left, right) => Math.abs((left.number[2] ?? 0) - (requested[2] ?? 0)) - Math.abs((right.number[2] ?? 0) - (requested[2] ?? 0)) || compare(left.number, right.number))[0] ?? versions.filter((entry) => compare(entry.number, requested) <= 0).pop() ?? versions[0];
1697
- if (!selected) throw new Error(`No compatible MineRender data is published for ${requestedVersion}`);
1698
- const resolvedRoot = `${base}/${selected.name}`;
1699
- manifest = await this.manifest(resolvedRoot, selected.name, resolvedRoot);
1700
- if (!manifest) throw new Error(`Missing MineRender manifest for published version ${selected.name}`);
1701
- return { requestedVersion, version: selected.name, root: resolvedRoot, manifest };
1702
- });
1703
- }
1704
- static async manifest(root, expectedVersion, requestRoot) {
1705
- const data = await this.load("manifest", requestRoot);
1706
- if (data === void 0) return void 0;
1707
- if (!object(data) || data.schemaVersion !== 1 || typeof data.minecraftVersion !== "string" || !Number.isInteger(data.dataVersion) || !object(data.unavailable) || !Object.values(data.unavailable).every((value) => typeof value === "string") || !object(data.datasets) || !Object.values(data.datasets).every((entry) => {
1708
- return object(entry) && typeof entry.file === "string" && /^[a-z0-9][a-z0-9._-]*\.json$/.test(entry.file) && typeof entry.sha256 === "string" && /^[0-9a-f]{64}$/.test(entry.sha256);
1709
- }) || expectedVersion !== void 0 && data.minecraftVersion !== expectedVersion) {
1710
- throw new Error(`Invalid MineRender manifest at ${root}/minerender-data/manifest.json`);
1711
- }
1712
- return data;
1713
- }
1714
- static load(file, root) {
1715
- const key = new AssetKey("minerender-data", file, void 0, void 0, void 0, ".json", root);
1716
- key.rootType = void 0;
1717
- return AssetLoader.get(key, "json" /* JSON */);
1718
- }
1719
- static async cached(key, scope, defaultRoot, loader) {
1720
- const checkSources = () => {
1721
- if (AssetLoader.persistentScope !== scope || AssetLoader.ROOT !== defaultRoot) throw new Error("Asset sources changed while loading MineRender data; retry the request");
1722
- };
1723
- checkSources();
1724
- return await Caching.mineRenderDataCache.get(scope ? `${scope}
1725
- ${key}` : key, async () => {
1726
- const value = await loader();
1727
- checkSources();
1728
- return value;
1729
- });
1730
- }
1731
- static validDataset(dataset, data) {
1732
- if (!object(data)) return false;
1733
- if (dataset === "potionColors") return object(data.effects) && Object.values(data.effects).every(Number.isInteger) && object(data.potions) && Object.values(data.potions).every((effects) => Array.isArray(effects) && effects.every((effect) => object(effect) && typeof effect.id === "string" && Number.isInteger(effect.amplifier)));
1734
- if (dataset === "legacyBlocks") return object(data.blocks) && Object.values(data.blocks).every((value) => typeof value === "string");
1735
- if (dataset === "blockCulling") return Array.isArray(data.profiles) && data.profiles.every((profile) => object(profile) && typeof profile.type === "string") && object(data.blocks) && Object.values(data.blocks).every((index) => Number.isInteger(index) && index >= 0 && index < data.profiles.length) && (data.groups === void 0 || object(data.groups) && Object.values(data.groups).every((group) => Array.isArray(group) && group.every((type) => typeof type === "string")));
1736
- return Object.values(data).every((value) => {
1737
- if (!object(value)) return false;
1738
- switch (dataset) {
1739
- case "itemDefaults":
1740
- return true;
1741
- case "blockStates":
1742
- return Object.values(value).every((property) => object(property) && "default" in property && Array.isArray(property.values));
1743
- case "blockTints":
1744
- return ["constant", "grass", "stem", "redstone"].includes(value.source) && (value.source !== "constant" || Number.isInteger(value.color)) && (value.biome === void 0 || ["grass", "foliage", "dry_foliage", "water"].includes(value.biome));
1745
- case "fluids":
1746
- return ["water", "lava"].includes(value.kind) && typeof value.renderModel === "boolean" && (value.levelProperty === void 0 || typeof value.levelProperty === "string");
1747
- case "dyes":
1748
- return [value.id, value.textureDiffuseColor, value.fireworkColor, value.textColor].every(Number.isInteger);
1749
- default:
1750
- return false;
1751
- }
1752
- });
1753
- }
1754
- };
1755
- }
1756
- });
1757
-
1758
1803
  // src/assets/BannerPatterns.ts
1759
1804
  var DYE_COLORS, BannerPatterns;
1760
1805
  var init_BannerPatterns = __esm({
@@ -2077,6 +2122,7 @@ var init_ItemTints = __esm({
2077
2122
  var Models;
2078
2123
  var init_Models = __esm({
2079
2124
  "src/assets/Models.ts"() {
2125
+ init_Renames();
2080
2126
  init_Caching();
2081
2127
  init_ModelMerger();
2082
2128
  init_AssetLoader();
@@ -2410,6 +2456,11 @@ var init_Models = __esm({
2410
2456
  key.extension = ".json";
2411
2457
  }
2412
2458
  const preview = this.snapshotContext(key, context);
2459
+ if (key.type === "item") {
2460
+ const renamed = await Renames.key("items", key);
2461
+ const replacement = renamed && await this.getMerged(renamed, preview);
2462
+ if (replacement) return replacement;
2463
+ }
2413
2464
  const dataKey = await this.applyDefaults(key, preview);
2414
2465
  const keyStr = AssetLoader.persistentKey(key.serialize() + (key.type === "item" ? this.contextKey(preview) + dataKey : ""));
2415
2466
  const model = await Caching.mergedModelCache.get(keyStr, async () => {
@@ -2441,6 +2492,7 @@ var init_BlockStates = __esm({
2441
2492
  init_AssetKey();
2442
2493
  init_PersistentCache();
2443
2494
  init_AssetParsers();
2495
+ init_Renames();
2444
2496
  init_MineRenderData();
2445
2497
  BlockStates = class {
2446
2498
  static get PERSISTENT_CACHE() {
@@ -2481,6 +2533,9 @@ var init_BlockStates = __esm({
2481
2533
  if (!key.extension) {
2482
2534
  key.extension = ".json";
2483
2535
  }
2536
+ const renamed = await Renames.key("blocks", key);
2537
+ const replacement = renamed && await this.get(renamed);
2538
+ if (replacement) return replacement;
2484
2539
  const keyStr = key.serialize();
2485
2540
  return Caching.blockStateCache.get(keyStr, (k) => {
2486
2541
  return this.PERSISTENT_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
@@ -8017,6 +8072,7 @@ var Fonts = class {
8017
8072
  init_MineRenderData();
8018
8073
  init_ModelTextures();
8019
8074
  init_Models();
8075
+ init_Renames();
8020
8076
  init_source();
8021
8077
 
8022
8078
  // src/cache/index.ts
@@ -17334,18 +17390,23 @@ var SectionModels = class {
17334
17390
  return this.template(variant, choice, tints);
17335
17391
  }));
17336
17392
  }
17337
- /** Resolves section templates for one tint palette, with a synchronous weighted choice for each cell. */
17338
- async prepareState(blockState3, properties = {}, biomeColor) {
17393
+ /** Resolves shared templates; dynamic biome tints are supplied on section placements instead of cached geometry. */
17394
+ async prepareState(blockState3, properties = {}, biomeColor, dynamicBiomeTint = false) {
17339
17395
  const prepared = await this.state(blockState3, properties);
17340
17396
  if (!prepared) return void 0;
17341
17397
  let color2;
17342
17398
  const resolveColor = typeof biomeColor === "function" ? () => color2 ??= biomeColor() : biomeColor;
17343
17399
  const templates = /* @__PURE__ */ new Map();
17400
+ const source = dynamicBiomeTint && await BlockTints.biomeSource(blockState3.key);
17344
17401
  await Promise.all([...prepared.choices].map(async ([variant, choice]) => {
17345
17402
  const tints = await BlockTints.get(blockState3.key, prepared.state, choice.model, void 0, resolveColor);
17346
- templates.set(variant, await this.template(variant, choice, tints));
17403
+ const biomeTints = source ? await BlockTints.get(blockState3.key, prepared.state, choice.model, void 0, 16777215) : void 0;
17404
+ templates.set(variant, await this.template(variant, choice, tints, biomeTints));
17347
17405
  }));
17348
- return { pick: (position) => prepared.groups.map((group) => templates.get(BlockStateResolver.choose(group, position))) };
17406
+ return {
17407
+ pick: (position) => prepared.groups.map((group) => templates.get(BlockStateResolver.choose(group, position))),
17408
+ biomeTint: source ? [...templates.values()].some((template) => template.biomeTint?.some((value) => value === 1)) || void 0 : void 0
17409
+ };
17349
17410
  }
17350
17411
  async state(blockState3, properties) {
17351
17412
  let states = this.states.get(blockState3);
@@ -17388,10 +17449,10 @@ var SectionModels = class {
17388
17449
  }
17389
17450
  return { state, groups, choices: prepared };
17390
17451
  }
17391
- async template(variant, { model, atlas, rotation }, tints) {
17452
+ async template(variant, { model, atlas, rotation }, tints, biomeTints) {
17392
17453
  let models = this.models.get(model);
17393
17454
  if (!models) this.models.set(model, models = /* @__PURE__ */ new Map());
17394
- const key = JSON.stringify([rotation.x, rotation.y, rotation.z, variant.uvlock, tints]);
17455
+ const key = JSON.stringify([rotation.x, rotation.y, rotation.z, variant.uvlock, tints, biomeTints]);
17395
17456
  return cached(models, key, async () => {
17396
17457
  const object4 = new SectionModel(model, {
17397
17458
  instanceMeshes: false,
@@ -17405,11 +17466,13 @@ var SectionModels = class {
17405
17466
  object4.clear();
17406
17467
  const keep = [];
17407
17468
  const cullFaces = [];
17469
+ const biomeTint = biomeTints ? [] : void 0;
17408
17470
  for (const element of atlas.model.elements) {
17409
17471
  for (const face of CUBE_FACES) {
17410
17472
  const data = element.faces[face];
17411
17473
  keep.push(data !== void 0);
17412
17474
  if (!data) continue;
17475
+ biomeTint?.push(Number(data.tintindex !== void 0 && biomeTints?.[data.tintindex] !== void 0));
17413
17476
  const local = CUBE_FACES.findIndex((name) => name === data.cullface);
17414
17477
  if (local < 0) {
17415
17478
  cullFaces.push(0);
@@ -17425,6 +17488,7 @@ var SectionModels = class {
17425
17488
  geometry,
17426
17489
  atlas,
17427
17490
  cullFaces: new Uint8Array(cullFaces),
17491
+ biomeTint: biomeTint?.some(Boolean) ? new Uint8Array(biomeTint) : void 0,
17428
17492
  occludes: ModelCulling.isOpaqueFullCube(atlas) && ModelCulling.toLocalMask(63, rotation) === 63,
17429
17493
  layer: atlas.hasTranslucency ? 1 : 0,
17430
17494
  ambientOcclusion: model.ambientocclusion !== false
@@ -17444,6 +17508,7 @@ var SectionModels = class {
17444
17508
  };
17445
17509
 
17446
17510
  // src/world/_entities/WorldEntities.ts
17511
+ init_Renames();
17447
17512
  init_Entities();
17448
17513
  init_EntityObject();
17449
17514
 
@@ -17603,8 +17668,10 @@ var WorldEntities = class {
17603
17668
  try {
17604
17669
  if (!current()) return;
17605
17670
  const models = await Entities.getEntityList();
17606
- if (!models.includes(placement.key.path) || !current()) return;
17607
- const model = await Entities.getEntity(placement.key, placement.texture, { when: placement.when });
17671
+ const renamed = models.includes(placement.key.path) ? void 0 : await Renames.key("entities", placement.key);
17672
+ const key = renamed && models.includes(renamed.path) ? renamed : placement.key;
17673
+ if (!models.includes(key.path) || !current()) return;
17674
+ const model = await Entities.getEntity(key, placement.texture, { when: placement.when });
17608
17675
  if (!model || !current()) return;
17609
17676
  let tints = placement.tints;
17610
17677
  if (placement.woolDye) {
@@ -17619,7 +17686,7 @@ var WorldEntities = class {
17619
17686
  object4.rotation.y = placement.yaw;
17620
17687
  await object4.init();
17621
17688
  if (!current()) return;
17622
- placement.owner.set(object4, { type: placement.key.toNamespacedString(), position: placement.position });
17689
+ placement.owner.set(object4, { type: key.toNamespacedString(), position: placement.position });
17623
17690
  this.scene.add(object4);
17624
17691
  } catch (error) {
17625
17692
  console.warn(`Could not draw saved entity ${placement.key.toNamespacedString()}, skipping it`, error);
@@ -17651,6 +17718,132 @@ var WorldEntities = class {
17651
17718
  // src/world/MineRenderWorld.ts
17652
17719
  init_FluidQuads();
17653
17720
 
17721
+ // src/world/_biomes/BiomeBlend.ts
17722
+ init_AssetKey();
17723
+ init_AssetLoader();
17724
+ init_Caching();
17725
+ var MAX_TILES = 256;
17726
+ function validateBiomeBlendRadius(radius) {
17727
+ if (!Number.isInteger(radius) || radius < 0 || radius > 7) {
17728
+ throw new RangeError("biomeBlendRadius must be an integer from 0 to 7");
17729
+ }
17730
+ }
17731
+ var BiomeBlend = class {
17732
+ constructor(biomeAt, radius) {
17733
+ this.biomeAt = biomeAt;
17734
+ this.tiles = /* @__PURE__ */ new Map();
17735
+ this.generation = Caching.generation;
17736
+ validateBiomeBlendRadius(radius);
17737
+ this._radius = radius;
17738
+ }
17739
+ get radius() {
17740
+ return this._radius;
17741
+ }
17742
+ clear() {
17743
+ this.tiles.clear();
17744
+ }
17745
+ invalidateColumn(x, z) {
17746
+ for (const [key, tile] of this.tiles) {
17747
+ if (Math.abs(tile.x - x) <= 1 && Math.abs(tile.z - z) <= 1) this.tiles.delete(key);
17748
+ }
17749
+ }
17750
+ async getColor(kind, origin, position) {
17751
+ const [x, y, z] = position;
17752
+ const biome = this.biomeAt(x, y, z);
17753
+ if (biome === void 0) return void 0;
17754
+ if (this.generation !== Caching.generation) {
17755
+ this.clear();
17756
+ this.generation = Caching.generation;
17757
+ }
17758
+ const tileX = Math.floor(x / 16), tileZ = Math.floor(z / 16), quartY = Math.floor(y / 4);
17759
+ const root = origin?.root ?? AssetLoader.ROOT;
17760
+ const key = JSON.stringify([root, AssetLoader.persistentScope, kind, this._radius, tileX, quartY, tileZ]);
17761
+ let tile = this.tiles.get(key);
17762
+ if (tile) {
17763
+ this.tiles.delete(key);
17764
+ this.tiles.set(key, tile);
17765
+ } else {
17766
+ const snapshot = new AssetKey("minecraft", "", void 0, void 0, "assets", ".json", root);
17767
+ const colors = this.buildTile(kind, snapshot, tileX, quartY, tileZ, this._radius).then((result2) => {
17768
+ if (!result2.complete && this.tiles.get(key)?.colors === colors) this.tiles.delete(key);
17769
+ return result2;
17770
+ }, (error) => {
17771
+ if (this.tiles.get(key)?.colors === colors) this.tiles.delete(key);
17772
+ throw error;
17773
+ });
17774
+ tile = { x: tileX, z: tileZ, colors };
17775
+ this.tiles.set(key, tile);
17776
+ if (this.tiles.size > MAX_TILES) this.tiles.delete(this.tiles.keys().next().value);
17777
+ }
17778
+ const result = await tile.colors;
17779
+ if (result.failures.has(biome)) throw result.failures.get(biome);
17780
+ const color2 = result.colors[(z - tileZ * 16) * 16 + x - tileX * 16];
17781
+ return color2 === -1 ? void 0 : color2;
17782
+ }
17783
+ async buildTile(kind, origin, tileX, quartY, tileZ, radius) {
17784
+ const size = 16 + radius * 2, stride = size + 1;
17785
+ const startX = tileX * 16 - radius, startZ = tileZ * 16 - radius;
17786
+ const quartX = Math.floor(startX / 4), quartZ = Math.floor(startZ / 4);
17787
+ const quartSize = Math.floor((startX + size - 1) / 4) - quartX + 1;
17788
+ const ids = new Array(quartSize * quartSize);
17789
+ const biomes = /* @__PURE__ */ new Map();
17790
+ for (let z = 0; z < quartSize; z++) {
17791
+ for (let x = 0; x < quartSize; x++) {
17792
+ const id = this.biomeAt((quartX + x) * 4, quartY * 4, (quartZ + z) * 4);
17793
+ ids[z * quartSize + x] = id;
17794
+ if (id !== void 0 && !biomes.has(id)) {
17795
+ biomes.set(id, (async () => {
17796
+ const swamp = kind === "grass" && (await Biomes.get(id, origin))?.effects.grass_color_modifier === "swamp";
17797
+ return { color: swamp ? void 0 : await Biomes.getColor(id, kind, origin), swamp };
17798
+ })());
17799
+ }
17800
+ }
17801
+ }
17802
+ const failures = /* @__PURE__ */ new Map();
17803
+ const loaded = new Map(await Promise.all([...biomes].map(async ([id, color2]) => {
17804
+ try {
17805
+ return [id, await color2];
17806
+ } catch (error) {
17807
+ failures.set(id, error);
17808
+ return [id, { color: void 0, swamp: false }];
17809
+ }
17810
+ })));
17811
+ const complete = [...loaded.values()].every((value) => value.swamp || value.color !== void 0);
17812
+ const samples = new Int32Array(size * size).fill(-1);
17813
+ const sums = Array.from({ length: 4 }, () => new Uint32Array(stride * stride));
17814
+ for (let z = 0; z < size; z++) {
17815
+ const row = [0, 0, 0, 0];
17816
+ for (let x = 0; x < size; x++) {
17817
+ const id = ids[(Math.floor((startZ + z) / 4) - quartZ) * quartSize + Math.floor((startX + x) / 4) - quartX];
17818
+ const value = id === void 0 ? void 0 : loaded.get(id);
17819
+ const color2 = value?.swamp ? swampGrassColor(startX + x, startZ + z) : value?.color;
17820
+ if (color2 !== void 0) {
17821
+ samples[z * size + x] = color2;
17822
+ row[0] += color2 >> 16 & 255;
17823
+ row[1] += color2 >> 8 & 255;
17824
+ row[2] += color2 & 255;
17825
+ row[3]++;
17826
+ }
17827
+ for (let channel = 0; channel < 4; channel++) {
17828
+ sums[channel][(z + 1) * stride + x + 1] = sums[channel][z * stride + x + 1] + row[channel];
17829
+ }
17830
+ }
17831
+ }
17832
+ const colors = new Int32Array(256).fill(-1), width = radius * 2 + 1;
17833
+ for (let z = 0; z < 16; z++) {
17834
+ for (let x = 0; x < 16; x++) {
17835
+ if (samples[(z + radius) * size + x + radius] === -1) continue;
17836
+ const topLeft = z * stride + x, topRight = topLeft + width;
17837
+ const bottomLeft = (z + width) * stride + x, bottomRight = bottomLeft + width;
17838
+ const sum = (values) => values[bottomRight] - values[bottomLeft] - values[topRight] + values[topLeft];
17839
+ const count = sum(sums[3]);
17840
+ if (count) colors[z * 16 + x] = Math.floor(sum(sums[0]) / count) << 16 | Math.floor(sum(sums[1]) / count) << 8 | Math.floor(sum(sums[2]) / count);
17841
+ }
17842
+ }
17843
+ return { colors, complete, failures };
17844
+ }
17845
+ };
17846
+
17654
17847
  // src/world/WorldLight.ts
17655
17848
  function copySectionLight(light) {
17656
17849
  const copy = {};
@@ -17714,12 +17907,17 @@ var MineRenderWorld = class {
17714
17907
  this.lightValidity = /* @__PURE__ */ new Map();
17715
17908
  this.highestSkySection = /* @__PURE__ */ new Map();
17716
17909
  this.ambientOcclusionPending = false;
17910
+ this.pendingBiomes = /* @__PURE__ */ new Map();
17717
17911
  this.pendingCulling = /* @__PURE__ */ new Map();
17718
17912
  if (options.savedLighting && !options.sectionMeshing) throw new TypeError("savedLighting requires sectionMeshing");
17719
17913
  if (options.ambientOcclusion && !options.sectionMeshing) throw new TypeError("ambientOcclusion requires sectionMeshing");
17720
17914
  if (options.ambientLight !== void 0 && (!Number.isFinite(options.ambientLight) || options.ambientLight < 0 || options.ambientLight > 1)) {
17721
17915
  throw new RangeError("ambientLight must be a number from 0 to 1");
17722
17916
  }
17917
+ this._biomeBlendRadius = options.biomeBlendRadius ?? 0;
17918
+ validateBiomeBlendRadius(this._biomeBlendRadius);
17919
+ if (this._biomeBlendRadius > 0 && !options.sectionMeshing) throw new TypeError("biomeBlendRadius requires sectionMeshing");
17920
+ if (this._biomeBlendRadius) this.biomeBlend = new BiomeBlend((x, y, z) => this.getBiomeAt(x, y, z), this._biomeBlendRadius);
17723
17921
  this.scene = scene;
17724
17922
  this.savedLighting = options.savedLighting ?? false;
17725
17923
  this.ambientLight = options.ambientLight ?? 0;
@@ -17760,6 +17958,65 @@ var MineRenderWorld = class {
17760
17958
  await this.updateCulling([]);
17761
17959
  this.ambientOcclusionPending = false;
17762
17960
  }
17961
+ /** Horizontal biome-color sampling radius, from 0 to 7 blocks. Zero disables blending. */
17962
+ get biomeBlendRadius() {
17963
+ return this._biomeBlendRadius;
17964
+ }
17965
+ /** Refreshes loaded grass, foliage, and water colors without reloading their source chunks. */
17966
+ async setBiomeBlendRadius(radius) {
17967
+ validateBiomeBlendRadius(radius);
17968
+ if (radius > 0 && !this.sectionModels) throw new TypeError("biomeBlendRadius requires sectionMeshing");
17969
+ if (radius !== this._biomeBlendRadius) {
17970
+ this._biomeBlendRadius = radius;
17971
+ this.biomeBlend?.clear();
17972
+ this.biomeBlend = radius ? new BiomeBlend((x, y, z) => this.getBiomeAt(x, y, z), radius) : void 0;
17973
+ for (const chunk of this._chunks.values()) this.pendingBiomes.set(chunk, void 0);
17974
+ }
17975
+ const failures = await this.refreshBiomes();
17976
+ if (failures.size) throw failures.values().next().value;
17977
+ }
17978
+ markBiomesChanged(x, z, y, includeColumn = true) {
17979
+ const queued = [];
17980
+ if (!this.biomeBlend) return queued;
17981
+ this.biomeBlend.invalidateColumn(x, z);
17982
+ for (const chunk of this._chunks.values()) {
17983
+ if (Math.abs(chunk.x - x) > 1 || Math.abs(chunk.z - z) > 1 || !includeColumn && chunk.x === x && chunk.z === z || y !== void 0 && chunk.y !== y && chunk.y !== y + 1) continue;
17984
+ queued.push(chunk);
17985
+ if (!this.pendingBiomes.has(chunk)) this.pendingBiomes.set(chunk, []);
17986
+ const columns = this.pendingBiomes.get(chunk);
17987
+ if (columns && !columns.some((column) => column.x === x && column.z === z)) columns.push({ x, z });
17988
+ }
17989
+ return queued;
17990
+ }
17991
+ async refreshBiomes() {
17992
+ if (this.biomeRefreshing) await this.biomeRefreshing;
17993
+ if (!this.pendingBiomes.size) return /* @__PURE__ */ new Map();
17994
+ return this.biomeRefreshing ??= Promise.resolve().then(async () => {
17995
+ const failures = /* @__PURE__ */ new Map();
17996
+ const batch = [...this.pendingBiomes.keys()];
17997
+ let sliceStart = performance.now();
17998
+ try {
17999
+ for (const chunk of batch) {
18000
+ const columns = this.pendingBiomes.get(chunk);
18001
+ this.pendingBiomes.delete(chunk);
18002
+ if (this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`) !== chunk) continue;
18003
+ try {
18004
+ await chunk.refreshBiomes(columns);
18005
+ } catch (error) {
18006
+ failures.set(chunk, error);
18007
+ if (this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`) === chunk) this.pendingBiomes.set(chunk, void 0);
18008
+ }
18009
+ if (performance.now() - sliceStart > 8) {
18010
+ await yieldToEventLoop();
18011
+ sliceStart = performance.now();
18012
+ }
18013
+ }
18014
+ return failures;
18015
+ } finally {
18016
+ this.biomeRefreshing = void 0;
18017
+ }
18018
+ });
18019
+ }
17763
18020
  getBlockAt(posOrX, y, z) {
17764
18021
  if (typeof posOrX == "number") {
17765
18022
  return this.getBlockAt(new Vector319(posOrX, y, z));
@@ -17995,6 +18252,9 @@ var MineRenderWorld = class {
17995
18252
  }
17996
18253
  await placeEntities?.();
17997
18254
  } finally {
18255
+ this.markBiomesChanged(chunk.x, chunk.z, void 0, false);
18256
+ const failures = await this.refreshBiomes();
18257
+ if (failures.size) console.warn("Failed to refresh biome colors", failures.values().next().value);
17998
18258
  await this.updateCulling([...changes.values()]);
17999
18259
  }
18000
18260
  }
@@ -18011,11 +18271,14 @@ var MineRenderWorld = class {
18011
18271
  try {
18012
18272
  await this.clearChunkColumn(x, z);
18013
18273
  } finally {
18274
+ const failures = await this.refreshBiomes();
18275
+ if (failures.size) console.warn("Failed to refresh biome colors", failures.values().next().value);
18014
18276
  await this.updateCulling([]);
18015
18277
  }
18016
18278
  }
18017
18279
  async clearChunkColumn(x, z) {
18018
18280
  this.biomes.delete(`${x}_${z}`);
18281
+ this.markBiomesChanged(x, z, void 0, false);
18019
18282
  if (this.savedLighting) {
18020
18283
  for (const y of this.light.get(`${x}_${z}`)?.keys() ?? []) this.markSectionChanged({ x, y, z });
18021
18284
  }
@@ -18026,6 +18289,7 @@ var MineRenderWorld = class {
18026
18289
  for (const [key, section] of previous) {
18027
18290
  this._chunks.delete(key);
18028
18291
  this.pendingCulling.delete(section);
18292
+ this.pendingBiomes.delete(section);
18029
18293
  await section.clear(false);
18030
18294
  this.markSectionChanged(section);
18031
18295
  }
@@ -18060,6 +18324,8 @@ var MineRenderWorld = class {
18060
18324
  const chunks = [...this._chunks.values()];
18061
18325
  this._chunks.clear();
18062
18326
  this.biomes.clear();
18327
+ this.biomeBlend?.clear();
18328
+ this.pendingBiomes.clear();
18063
18329
  this.light.clear();
18064
18330
  this.lightValidity.clear();
18065
18331
  this.highestSkySection.clear();
@@ -18178,8 +18444,14 @@ var MineRenderWorld = class {
18178
18444
  column?.delete(y);
18179
18445
  if (!column?.size) this.biomes.delete(columnKey);
18180
18446
  }
18181
- await chunk.refreshBiomes();
18182
- await this._chunks.get(`${x}_${y + 1}_${z}`)?.refreshBiomes();
18447
+ if (this.biomeBlend) {
18448
+ const queued = this.markBiomesChanged(x, z, y);
18449
+ const failures = await this.refreshBiomes();
18450
+ for (const chunk2 of queued) if (failures.has(chunk2)) throw failures.get(chunk2);
18451
+ } else {
18452
+ await chunk.refreshBiomes();
18453
+ await this._chunks.get(`${x}_${y + 1}_${z}`)?.refreshBiomes();
18454
+ }
18183
18455
  },
18184
18456
  this.savedLighting ? (x2, y2, z2) => this.lightLevelAt(x2, y2, z2) : void 0,
18185
18457
  async () => {
@@ -18196,7 +18468,8 @@ var MineRenderWorld = class {
18196
18468
  await this.updateCulling([]);
18197
18469
  }
18198
18470
  },
18199
- this.ambientLight
18471
+ this.ambientLight,
18472
+ () => this.biomeBlend
18200
18473
  );
18201
18474
  this._chunks.set(key, chunk);
18202
18475
  }
@@ -18315,9 +18588,10 @@ var LIGHT_SAMPLE_OFFSETS = [0, 1, -1, 324, -324, 18, -18];
18315
18588
 
18316
18589
  // src/world/SectionGeometry.ts
18317
18590
  function buildSectionGeometry(input) {
18318
- const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize, fluids, light, ambientLight, ambientOcclusion } = input;
18591
+ const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize, fluids, light, ambientLight, ambientOcclusion, biomeTints } = input;
18319
18592
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
18320
18593
  if (count > indices.length || count > templates.length || count > cullMasks.length) throw new RangeError("Section entry count exceeds its arrays");
18594
+ if (biomeTints && biomeTints.length !== count * 3) throw new RangeError("Section biome tints must contain RGB for each placement");
18321
18595
  if (fluids && fluids.cells.length !== 5832) throw new RangeError("Section fluid cells must cover an 18\xD718\xD718 neighborhood");
18322
18596
  if (fluids?.waterTints && fluids.waterTints.length !== 4096 * 3) throw new RangeError("Section water tints must contain RGB for 4096 cells");
18323
18597
  if (light && (light.length !== 5832 || light.some((level) => level > 15 && level !== 255))) {
@@ -18332,7 +18606,7 @@ function buildSectionGeometry(input) {
18332
18606
  const cornerBrightness2 = ambientOcclusion ? new Float32Array(4) : void 0;
18333
18607
  for (const template of templateData) {
18334
18608
  const { quads } = template;
18335
- if (template.positions.length !== quads * 12 || template.normals.length !== quads * 12 || template.uvs.length !== quads * 8 || template.uvBounds.length !== quads * 16 || template.colors.length !== quads * 12 || template.indices.length !== quads * 6 || template.cullFaces.length !== quads) {
18609
+ if (template.positions.length !== quads * 12 || template.normals.length !== quads * 12 || template.uvs.length !== quads * 8 || template.uvBounds.length !== quads * 16 || template.colors.length !== quads * 12 || template.indices.length !== quads * 6 || template.cullFaces.length !== quads || template.biomeTint && template.biomeTint.length !== quads) {
18336
18610
  throw new RangeError("Section template arrays do not match its quad count");
18337
18611
  }
18338
18612
  }
@@ -18442,6 +18716,7 @@ function buildSectionGeometry(input) {
18442
18716
  blockIndices[indexOffset / 6] = indices[entry];
18443
18717
  if (ambientMask) quadAmbientOcclusion(template.positions, template.normals, quad, ambientMask, cornerBrightness2);
18444
18718
  const brightness = light ? lightBrightness(light[lightIndex + LIGHT_SAMPLE_OFFSETS[quadLightIndex(template.positions, template.normals, quad)]], ambientLight) : 1;
18719
+ const tint = biomeTints && template.biomeTint?.[quad] && !Number.isNaN(biomeTints[entry * 3]) ? entry * 3 : void 0;
18445
18720
  const start = vertex;
18446
18721
  for (let corner = 0; corner < 4; corner++, vertex++) {
18447
18722
  const source = quad * 4 + corner;
@@ -18458,9 +18733,9 @@ function buildSectionGeometry(input) {
18458
18733
  uvBounds[vertex * 4 + 2] = mapU(template.uvBounds[source * 4 + 2]);
18459
18734
  uvBounds[vertex * 4 + 3] = mapV(template.uvBounds[source * 4 + 3]);
18460
18735
  const factor = brightness * (ambientMask ? cornerBrightness2[corner] : 1);
18461
- colors[vertex * 3] = template.colors[source * 3] * factor;
18462
- colors[vertex * 3 + 1] = template.colors[source * 3 + 1] * factor;
18463
- colors[vertex * 3 + 2] = template.colors[source * 3 + 2] * factor;
18736
+ colors[vertex * 3] = (tint === void 0 ? template.colors[source * 3] : biomeTints[tint]) * factor;
18737
+ colors[vertex * 3 + 1] = (tint === void 0 ? template.colors[source * 3 + 1] : biomeTints[tint + 1]) * factor;
18738
+ colors[vertex * 3 + 2] = (tint === void 0 ? template.colors[source * 3 + 2] : biomeTints[tint + 2]) * factor;
18464
18739
  }
18465
18740
  for (let corner = 0; corner < 6; corner++) outputIndices[indexOffset++] = start + template.indices[quad * 6 + corner];
18466
18741
  }
@@ -18582,7 +18857,11 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
18582
18857
  ...lighting
18583
18858
  };
18584
18859
  for (let i = 0; i < entries.length; i++) {
18585
- const { index, template, cullMask } = entries[i];
18860
+ const { index, template, cullMask, biomeTint } = entries[i];
18861
+ if (biomeTint) {
18862
+ input.biomeTints ??= new Float32Array(entries.length * 3).fill(NaN);
18863
+ input.biomeTints.set(biomeTint, i * 3);
18864
+ }
18586
18865
  let id = templateIds.get(template);
18587
18866
  if (id === void 0) {
18588
18867
  let atlas = atlasIds.get(template.atlas);
@@ -18609,6 +18888,7 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
18609
18888
  colors: new Float32Array(quads * 12),
18610
18889
  indices: new Uint16Array(quads * 6),
18611
18890
  cullFaces: new Uint8Array(template.cullFaces),
18891
+ biomeTint: template.biomeTint ? new Uint8Array(template.biomeTint) : void 0,
18612
18892
  atlas: 0,
18613
18893
  layer: template.layer,
18614
18894
  ambientOcclusion: template.ambientOcclusion
@@ -18753,7 +19033,7 @@ init_CubeFace();
18753
19033
  init_InstanceReference();
18754
19034
  var WATER_TINT2 = new Color13(4159204).toArray();
18755
19035
  var Chunk = class _Chunk {
18756
- constructor(scene, x, y, z, onBlocksChanged, sectionModels, neighborCell, biomeAt, onBiomesChanged, lightAt, onLightCleared, ambientLight = 0) {
19036
+ constructor(scene, x, y, z, onBlocksChanged, sectionModels, neighborCell, biomeAt, onBiomesChanged, lightAt, onLightCleared, ambientLight = 0, biomeBlend) {
18757
19037
  this.onBlocksChanged = onBlocksChanged;
18758
19038
  this.sectionModels = sectionModels;
18759
19039
  this.neighborCell = neighborCell;
@@ -18762,6 +19042,7 @@ var Chunk = class _Chunk {
18762
19042
  this.lightAt = lightAt;
18763
19043
  this.onLightCleared = onLightCleared;
18764
19044
  this.ambientLight = ambientLight;
19045
+ this.biomeBlend = biomeBlend;
18765
19046
  this.data = new ChunkData();
18766
19047
  this.renderedBlocks = /* @__PURE__ */ new Map();
18767
19048
  this.objectBlocks = /* @__PURE__ */ new Set();
@@ -18771,6 +19052,7 @@ var Chunk = class _Chunk {
18771
19052
  this.blockPosition = new Vector320();
18772
19053
  this.meshDirty = false;
18773
19054
  this.meshGeneration = 0;
19055
+ this.blockChanges = /* @__PURE__ */ new Set();
18774
19056
  this.scene = scene;
18775
19057
  this.x = x;
18776
19058
  this.y = y;
@@ -18869,10 +19151,20 @@ var Chunk = class _Chunk {
18869
19151
  return this.refreshBiomes();
18870
19152
  }
18871
19153
  /** Rebuilds biome-tinted blocks while preserving hidden blocks and unrelated render objects. */
18872
- async refreshBiomes() {
19154
+ refreshBiomes(columns) {
19155
+ return this.changeBlocks(() => this.refreshBiomeBlocks(columns), true);
19156
+ }
19157
+ async refreshBiomeBlocks(columns) {
19158
+ const blend = columns && this.biomeBlend?.();
19159
+ const ownColumn = columns?.some((column) => column.x === this.x && column.z === this.z);
18873
19160
  const affected = /* @__PURE__ */ new Map();
18874
19161
  const blocks = [];
18875
19162
  for (const index of this.renderedBlocks.keys()) {
19163
+ if (blend) {
19164
+ if (this.objectBlocks.has(index) && !ownColumn) continue;
19165
+ const [x, , z] = this.positionAt(index), radius = blend.radius;
19166
+ if (!columns.some((column) => x >= column.x * 16 - radius && x <= column.x * 16 + 15 + radius && z >= column.z * 16 - radius && z <= column.z * 16 + 15 + radius)) continue;
19167
+ }
18876
19168
  const block = this.data.get(index);
18877
19169
  const stateKey = ChunkData.paletteKey(block);
18878
19170
  let matches = affected.get(stateKey);
@@ -18887,10 +19179,32 @@ var Chunk = class _Chunk {
18887
19179
  }
18888
19180
  if (await matches) blocks.push({ index, block });
18889
19181
  }
19182
+ if (blend) {
19183
+ const groups = /* @__PURE__ */ new Map();
19184
+ for (const entry of blocks) {
19185
+ const key = ChunkData.paletteKey(entry.block);
19186
+ let group = groups.get(key);
19187
+ if (!group) groups.set(key, group = []);
19188
+ group.push(entry);
19189
+ }
19190
+ blocks.length = 0;
19191
+ const biomes = /* @__PURE__ */ new Map();
19192
+ for (const group of groups.values()) {
19193
+ const { contexts, fluid } = await this.resolveState(group[0].block, group.map((entry) => entry.index), biomes);
19194
+ for (const entry of group) {
19195
+ const context = contexts.get(entry.index);
19196
+ const current = this.sectionBlocks.get(entry.index)?.[0]?.biomeTint;
19197
+ const tint = context?.tint;
19198
+ const tintChanged = !!current !== !!tint || tint?.some((value, channel) => value !== current?.[channel]);
19199
+ const waterChanged = fluid?.kind === "water" && (context?.waterTint ?? WATER_TINT2).some((value, channel) => Math.fround(value) !== (this.waterTints?.[entry.index * 3 + channel] ?? Math.fround(WATER_TINT2[channel])));
19200
+ if (this.objectBlocks.has(entry.index) || tintChanged || waterChanged) blocks.push(entry);
19201
+ }
19202
+ }
19203
+ }
18890
19204
  if (!blocks.length) return;
18891
19205
  const hidden = new Set(this.hiddenBlocks);
18892
19206
  try {
18893
- await this.placeBlocks(blocks);
19207
+ await this.replaceBlocks(blocks, true);
18894
19208
  } finally {
18895
19209
  for (const index of hidden) {
18896
19210
  this.hiddenBlocks.add(index);
@@ -18930,6 +19244,20 @@ var Chunk = class _Chunk {
18930
19244
  }
18931
19245
  /** Places cells in input order and returns changed world positions without refreshing neighbors. */
18932
19246
  async placeBlocks(blocks) {
19247
+ return this.changeBlocks(() => this.replaceBlocks(blocks));
19248
+ }
19249
+ changeBlocks(change, refreshBiomes = false) {
19250
+ const before = refreshBiomes ? Promise.allSettled(this.blockChanges) : this.biomeChange;
19251
+ const result = before ? before.catch(() => void 0).then(change) : change();
19252
+ const pending = result.finally(() => {
19253
+ this.blockChanges.delete(pending);
19254
+ if (this.biomeChange === pending) this.biomeChange = void 0;
19255
+ });
19256
+ this.blockChanges.add(pending);
19257
+ if (refreshBiomes) this.biomeChange = pending;
19258
+ return pending;
19259
+ }
19260
+ async replaceBlocks(blocks, preserveDataOnFailure = false) {
18933
19261
  const placements = blocks.map(({ index, block }) => ({
18934
19262
  index,
18935
19263
  key: block && !ChunkData.isAir(block) ? ChunkData.paletteKey(block) : void 0,
@@ -18949,6 +19277,10 @@ var Chunk = class _Chunk {
18949
19277
  const biomes = /* @__PURE__ */ new Map();
18950
19278
  const settled = await Promise.allSettled([...states.values()].map(({ stored, indices }) => this.resolveState(stored, indices, biomes)));
18951
19279
  const resolutions = new Map([...states.keys()].map((key, i) => [key, settled[i]]));
19280
+ if (preserveDataOnFailure) {
19281
+ const rejected = settled.find((result) => result.status === "rejected");
19282
+ if (rejected?.status === "rejected") throw rejected.reason;
19283
+ }
18952
19284
  const positions = [];
18953
19285
  let failed = false, failure;
18954
19286
  for (const { index, key, block } of placements) {
@@ -18986,7 +19318,7 @@ var Chunk = class _Chunk {
18986
19318
  }
18987
19319
  const templates = perBlock ? void 0 : fluid?.renderModel === false ? [] : (context?.prepared ?? prepared)?.pick(variantPosition);
18988
19320
  if (templates) {
18989
- this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0 })));
19321
+ this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0, biomeTint: context?.tint })));
18990
19322
  this.meshDirty = true;
18991
19323
  } else {
18992
19324
  object4 = await this.scene.addBlock(blockState3, {
@@ -19005,7 +19337,11 @@ var Chunk = class _Chunk {
19005
19337
  }, object: object4 });
19006
19338
  if (object4) this.objectBlocks.add(index);
19007
19339
  } catch (error) {
19008
- this.data.assign(index, 0);
19340
+ if (preserveDataOnFailure && readBlock) {
19341
+ this.renderedBlocks.set(index, { get block() {
19342
+ return readBlock();
19343
+ }, object: void 0 });
19344
+ } else this.data.assign(index, 0);
19009
19345
  this.fluidCells[index] = 0;
19010
19346
  object4?.removeFromScene();
19011
19347
  if (!failed) failure = error;
@@ -19016,7 +19352,10 @@ var Chunk = class _Chunk {
19016
19352
  return positions;
19017
19353
  }
19018
19354
  /** Replaces every cell from parsed section data. The caller refreshes neighbor culling. */
19019
- async placeSection(data) {
19355
+ placeSection(data) {
19356
+ return this.changeBlocks(() => this.replaceSection(data));
19357
+ }
19358
+ async replaceSection(data) {
19020
19359
  for (const info of this.renderedBlocks.values()) info.object?.removeFromScene();
19021
19360
  this.sectionBlocks.clear();
19022
19361
  this.fluidCells.fill(0);
@@ -19063,7 +19402,7 @@ var Chunk = class _Chunk {
19063
19402
  ];
19064
19403
  const templates = perBlock ? void 0 : fluid?.renderModel === false ? [] : (context?.prepared ?? prepared)?.pick(variantPosition);
19065
19404
  if (templates) {
19066
- this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0 })));
19405
+ this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0, biomeTint: context?.tint })));
19067
19406
  this.meshDirty = true;
19068
19407
  } else {
19069
19408
  perBlocks.push({ index, blockState: blockState3, biome: context?.biome });
@@ -19147,12 +19486,35 @@ var Chunk = class _Chunk {
19147
19486
  }
19148
19487
  async resolveState(stored, indices, biomes) {
19149
19488
  const blockState3 = await BlockStates.get(AssetKey.parse("blockstates", stored.type));
19150
- const perBlock = !blockState3 || !this.sectionModels || !!(blockState3.key && BlockEntities.entry(await BlockEntities.getIndex(blockState3.key.root), blockState3.key.toNamespacedString()));
19489
+ let perBlock = !blockState3 || !this.sectionModels || !!(blockState3.key && BlockEntities.entry(await BlockEntities.getIndex(blockState3.key.root), blockState3.key.toNamespacedString()));
19151
19490
  const fluid = await getBlockFluidState(blockState3?.key, stored.properties);
19152
19491
  const hasBiomes = this.getBiomeAt(this.x * 16, this.y * 16, this.z * 16) !== void 0 || this.biomeAt?.(this.x * 16, this.y * 16 - 1, this.z * 16) !== void 0;
19153
19492
  const source = hasBiomes && blockState3 ? await BlockTints.biomeSource(blockState3.key) : void 0;
19154
19493
  const prepare = (color2) => perBlock || fluid?.renderModel === false ? void 0 : this.sectionModels.prepareState(blockState3, stored.properties, color2);
19155
19494
  const contexts = /* @__PURE__ */ new Map();
19495
+ const blend = this.biomeBlend?.();
19496
+ if (blend && !perBlock && blockState3 && (source || fluid?.kind === "water")) {
19497
+ const prepared2 = fluid?.renderModel === false ? void 0 : await this.sectionModels.prepareState(blockState3, stored.properties, void 0, true);
19498
+ if (!prepared2 && fluid?.renderModel !== false) {
19499
+ perBlock = true;
19500
+ } else {
19501
+ await Promise.all(indices.map(async (index) => {
19502
+ const position = this.biomePosition(index, blockState3, stored);
19503
+ const biomeColor = (kind) => blend.getColor(kind, blockState3.key, position);
19504
+ const context = {};
19505
+ if (source && prepared2?.biomeTint) {
19506
+ const color2 = await biomeColor(source);
19507
+ if (color2 !== void 0) context.tint = new Color13(color2).toArray();
19508
+ }
19509
+ if (fluid?.kind === "water") {
19510
+ const color2 = await biomeColor("water");
19511
+ if (color2 !== void 0 && color2 !== 4159204) context.waterTint = new Color13(color2).toArray();
19512
+ }
19513
+ contexts.set(index, context);
19514
+ }));
19515
+ return { blockState: blockState3, perBlock, fluid, prepared: prepared2, contexts, stored };
19516
+ }
19517
+ }
19156
19518
  const groups = /* @__PURE__ */ new Map();
19157
19519
  if (blockState3 && (source || fluid?.kind === "water")) {
19158
19520
  for (const index of indices) {
@@ -19200,7 +19562,11 @@ var Chunk = class _Chunk {
19200
19562
  this.waterTints.set(tint ?? WATER_TINT2, index * 3);
19201
19563
  }
19202
19564
  /** Changes visibility at world block coordinates while preserving the block's data. */
19203
- async setBlockVisibleAt(pos, visible) {
19565
+ setBlockVisibleAt(pos, visible) {
19566
+ const position = pos.clone();
19567
+ return this.changeBlocks(() => this.setBlockVisibility(position, visible));
19568
+ }
19569
+ async setBlockVisibility(pos, visible) {
19204
19570
  const index = _Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos));
19205
19571
  const block = this.renderedBlocks.get(index);
19206
19572
  if (!block || visible === !this.hiddenBlocks.has(index)) {
@@ -19370,6 +19736,7 @@ var Chunk = class _Chunk {
19370
19736
  }
19371
19737
  /** Removes stored blocks and render objects. Set `notify` to false to skip the owning world's update. */
19372
19738
  async clear(notify = true) {
19739
+ if (this.blockChanges.size) await Promise.allSettled(this.blockChanges);
19373
19740
  const positions = notify && this.onBlocksChanged ? [...this.renderedBlocks.keys()].map((index) => this.chunkPosToWorldPos(
19374
19741
  new Vector320(index % 16, Math.floor(index / 256), Math.floor(index / 16) % 16)
19375
19742
  )) : [];
@@ -19848,6 +20215,7 @@ export {
19848
20215
  PackMetadata,
19849
20216
  PersistentCache,
19850
20217
  PlayerHeadTextures,
20218
+ Renames,
19851
20219
  Renderer,
19852
20220
  RequestError,
19853
20221
  Requests,