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.
@@ -252,6 +252,13 @@ var init_Caching = __esm({
252
252
  import_loading_cache = require("@inventivetalent/loading-cache");
253
253
  import_time = require("@inventivetalent/time");
254
254
  Caching = class _Caching {
255
+ static {
256
+ this._generation = 0;
257
+ }
258
+ /** Changes when shared assets are cleared, so derived caches can discard their old values. */
259
+ static get generation() {
260
+ return this._generation;
261
+ }
255
262
  static {
256
263
  this.rawImageCache = import_loading_cache.Caches.builder().expireAfterWrite(import_time.Time.minutes(10)).expireAfterAccess(import_time.Time.minutes(5)).expirationInterval(import_time.Time.seconds(5)).buildAsync();
257
264
  }
@@ -362,6 +369,7 @@ var init_Caching = __esm({
362
369
  }
363
370
  /** Invalidates in-memory entries so later lookups reload them. Persisted entries are retained. */
364
371
  static clear() {
372
+ this._generation++;
365
373
  for (const cache of this.all) {
366
374
  cache.invalidateAll();
367
375
  }
@@ -373,6 +381,7 @@ var init_Caching = __esm({
373
381
  * Use {@link shutdown} to also stop requests and the shared Ticker.
374
382
  */
375
383
  static end() {
384
+ this._generation++;
376
385
  for (const cache of this.all) {
377
386
  cache.invalidateAll();
378
387
  cache.end();
@@ -1265,118 +1274,6 @@ var init_Materials = __esm({
1265
1274
  }
1266
1275
  });
1267
1276
 
1268
- // src/model/Model.ts
1269
- function isDoubleArray(obj) {
1270
- return Array.isArray(obj) && obj.length === 2;
1271
- }
1272
- function isTripleArray(obj) {
1273
- return Array.isArray(obj) && obj.length === 3;
1274
- }
1275
- function isQuadArray(obj) {
1276
- return Array.isArray(obj) && obj.length === 4;
1277
- }
1278
- var ITEM_GENERATED, BUILTIN_GENERATED, BUILTIN_ENTITY, DEFAULT_ELEMENTS;
1279
- var init_Model = __esm({
1280
- "src/model/Model.ts"() {
1281
- ITEM_GENERATED = "item/generated";
1282
- BUILTIN_GENERATED = "builtin/generated";
1283
- BUILTIN_ENTITY = "builtin/entity";
1284
- DEFAULT_ELEMENTS = [];
1285
- }
1286
- });
1287
-
1288
- // ../../node_modules/ts-deepmerge/dist/index.js
1289
- var require_dist = __commonJS({
1290
- "../../node_modules/ts-deepmerge/dist/index.js"(exports2) {
1291
- "use strict";
1292
- var __assign = exports2 && exports2.__assign || function() {
1293
- __assign = Object.assign || function(t) {
1294
- for (var s, i = 1, n = arguments.length; i < n; i++) {
1295
- s = arguments[i];
1296
- for (var p10 in s) if (Object.prototype.hasOwnProperty.call(s, p10))
1297
- t[p10] = s[p10];
1298
- }
1299
- return t;
1300
- };
1301
- return __assign.apply(this, arguments);
1302
- };
1303
- var __read = exports2 && exports2.__read || function(o, n) {
1304
- var m = typeof Symbol === "function" && o[Symbol.iterator];
1305
- if (!m) return o;
1306
- var i = m.call(o), r, ar = [], e;
1307
- try {
1308
- while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
1309
- } catch (error) {
1310
- e = { error };
1311
- } finally {
1312
- try {
1313
- if (r && !r.done && (m = i["return"])) m.call(i);
1314
- } finally {
1315
- if (e) throw e.error;
1316
- }
1317
- }
1318
- return ar;
1319
- };
1320
- var __spreadArray = exports2 && exports2.__spreadArray || function(to, from, pack) {
1321
- if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
1322
- if (ar || !(i in from)) {
1323
- if (!ar) ar = Array.prototype.slice.call(from, 0, i);
1324
- ar[i] = from[i];
1325
- }
1326
- }
1327
- return to.concat(ar || Array.prototype.slice.call(from));
1328
- };
1329
- Object.defineProperty(exports2, "__esModule", { value: true });
1330
- var isObject = function(obj) {
1331
- if (typeof obj === "object" && obj !== null) {
1332
- if (typeof Object.getPrototypeOf === "function") {
1333
- var prototype = Object.getPrototypeOf(obj);
1334
- return prototype === Object.prototype || prototype === null;
1335
- }
1336
- return Object.prototype.toString.call(obj) === "[object Object]";
1337
- }
1338
- return false;
1339
- };
1340
- var PROTECTED_KEYS = ["__proto__"];
1341
- var merge10 = function() {
1342
- var objects = [];
1343
- for (var _i = 0; _i < arguments.length; _i++) {
1344
- objects[_i] = arguments[_i];
1345
- }
1346
- return objects.reduce(function(result, current) {
1347
- Object.keys(current).forEach(function(key) {
1348
- if (PROTECTED_KEYS.includes(key)) {
1349
- return;
1350
- }
1351
- if (Array.isArray(result[key]) && Array.isArray(current[key])) {
1352
- result[key] = merge10.options.mergeArrays ? Array.from(new Set(result[key].concat(current[key]))) : current[key];
1353
- } else if (isObject(result[key]) && isObject(current[key])) {
1354
- result[key] = merge10(result[key], current[key]);
1355
- } else {
1356
- result[key] = current[key];
1357
- }
1358
- });
1359
- return result;
1360
- }, {});
1361
- };
1362
- var defaultOptions = {
1363
- mergeArrays: true
1364
- };
1365
- merge10.options = defaultOptions;
1366
- merge10.withOptions = function(options) {
1367
- var objects = [];
1368
- for (var _i = 1; _i < arguments.length; _i++) {
1369
- objects[_i - 1] = arguments[_i];
1370
- }
1371
- merge10.options = __assign({ mergeArrays: true }, options);
1372
- var result = merge10.apply(void 0, __spreadArray([], __read(objects), false));
1373
- merge10.options = defaultOptions;
1374
- return result;
1375
- };
1376
- exports2.default = merge10;
1377
- }
1378
- });
1379
-
1380
1277
  // src/assets/AssetKey.ts
1381
1278
  function isResourceLocation(value) {
1382
1279
  return typeof value === "string" && /^(?:[a-z0-9_.-]+:)?[a-z0-9_./-]+$/.test(value);
@@ -1479,17 +1376,306 @@ var init_AssetKey = __esm({
1479
1376
  if (this.type) {
1480
1377
  p10.push(this.type);
1481
1378
  }
1482
- p10.push(this.path);
1483
- return p10.join("/");
1379
+ p10.push(this.path);
1380
+ return p10.join("/");
1381
+ }
1382
+ toNamespacedString() {
1383
+ return this.namespace + ":" + this.getFullPath();
1384
+ }
1385
+ /** Returns a cache identifier that includes the explicit root or {@link AssetLoader.ROOT}. */
1386
+ serialize() {
1387
+ return this.toString();
1388
+ }
1389
+ };
1390
+ }
1391
+ });
1392
+
1393
+ // src/assets/source/parser/AssetParsers.ts
1394
+ var AssetParser;
1395
+ var init_AssetParsers = __esm({
1396
+ "src/assets/source/parser/AssetParsers.ts"() {
1397
+ AssetParser = /* @__PURE__ */ ((AssetParser2) => {
1398
+ AssetParser2["MODEL"] = "model";
1399
+ AssetParser2["BLOCKSTATE"] = "blockstate";
1400
+ AssetParser2["META"] = "meta";
1401
+ AssetParser2["NBT"] = "nbt";
1402
+ AssetParser2["IMAGE"] = "image";
1403
+ AssetParser2["LIST"] = "list";
1404
+ AssetParser2["JSON"] = "json";
1405
+ return AssetParser2;
1406
+ })(AssetParser || {});
1407
+ }
1408
+ });
1409
+
1410
+ // src/assets/MineRenderData.ts
1411
+ var object, release, compare, MineRenderData;
1412
+ var init_MineRenderData = __esm({
1413
+ "src/assets/MineRenderData.ts"() {
1414
+ init_AssetKey();
1415
+ init_AssetLoader();
1416
+ init_AssetParsers();
1417
+ init_Caching();
1418
+ object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
1419
+ release = (value) => /^\d+\.\d+(?:\.\d+)?$/.test(value) ? value.split(".").map(Number) : void 0;
1420
+ compare = (left, right) => {
1421
+ for (let i = 0; i < 3; i++) {
1422
+ const difference = (left[i] ?? 0) - (right[i] ?? 0);
1423
+ if (difference) return difference;
1424
+ }
1425
+ return 0;
1426
+ };
1427
+ MineRenderData = class {
1428
+ /** Loads one registry. Missing files and invalid data reject the request. */
1429
+ static async get(dataset, root) {
1430
+ root = root?.replace(/\/+$/, "");
1431
+ const scope = AssetLoader.persistentScope;
1432
+ const defaultRoot = AssetLoader.ROOT;
1433
+ const resolved = await this.resolve(root);
1434
+ const entry = resolved.manifest.datasets[dataset];
1435
+ if (!entry) throw new Error(`MineRender ${dataset} data is unavailable for ${resolved.version}: ${resolved.manifest.unavailable[dataset] ?? "not listed in the manifest"}`);
1436
+ const file = entry.file;
1437
+ const requestRoot = resolved.root === (root ?? defaultRoot).replace(/\/+$/, "") ? root : resolved.root;
1438
+ return await this.cached(`${requestRoot === void 0 ? "sources" : "explicit"}:${resolved.root}/${file}:${entry.sha256}`, scope, defaultRoot, async () => {
1439
+ const data = await this.load(file.slice(0, -5), requestRoot);
1440
+ if (!this.validDataset(dataset, data)) throw new Error(`Invalid MineRender ${dataset} data at ${resolved.root}/minerender-data/${file}`);
1441
+ return data;
1442
+ });
1443
+ }
1444
+ /**
1445
+ * Uses the exact version when published. Otherwise, selects the nearest patch in the same
1446
+ * release line (older on ties), then the latest earlier release or the oldest published release.
1447
+ * Unpublished snapshots reject.
1448
+ */
1449
+ static async resolve(root) {
1450
+ const requestRoot = root?.replace(/\/+$/, "");
1451
+ const defaultRoot = AssetLoader.ROOT;
1452
+ root = (root ?? defaultRoot).replace(/\/+$/, "");
1453
+ const scope = AssetLoader.persistentScope;
1454
+ return this.cached(`resolve:${requestRoot === void 0 ? "sources" : "explicit"}:${root}`, scope, defaultRoot, async () => {
1455
+ const requestedVersion = root.slice(root.lastIndexOf("/") + 1);
1456
+ let manifest = await this.manifest(root, requestRoot !== void 0 && release(requestedVersion) ? requestedVersion : void 0, requestRoot);
1457
+ if (manifest) return { requestedVersion, version: manifest.minecraftVersion, root, manifest };
1458
+ const requested = release(requestedVersion);
1459
+ if (!requested) {
1460
+ throw new Error(`No MineRender data is published for ${requestedVersion}; automatic fallback requires a release`);
1461
+ }
1462
+ const base = root.slice(0, root.lastIndexOf("/"));
1463
+ const index = await this.cached(`versions:${base}`, scope, defaultRoot, async () => {
1464
+ const value = await this.load("versions", base);
1465
+ if (!object(value) || !Array.isArray(value.versions) || !value.versions.every((entry) => object(entry) && typeof entry.name === "string")) {
1466
+ throw new Error(`Invalid MineRender version index at ${base}/minerender-data/versions.json`);
1467
+ }
1468
+ return value.versions;
1469
+ });
1470
+ const versions = index.map((entry) => ({ name: entry.name, number: release(entry.name) })).filter((entry) => !!entry.number).sort((left, right) => compare(left.number, right.number));
1471
+ const sameLine = versions.filter((entry) => entry.number[0] === requested[0] && entry.number[1] === requested[1]);
1472
+ 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];
1473
+ if (!selected) throw new Error(`No compatible MineRender data is published for ${requestedVersion}`);
1474
+ const resolvedRoot = `${base}/${selected.name}`;
1475
+ manifest = await this.manifest(resolvedRoot, selected.name, resolvedRoot);
1476
+ if (!manifest) throw new Error(`Missing MineRender manifest for published version ${selected.name}`);
1477
+ return { requestedVersion, version: selected.name, root: resolvedRoot, manifest };
1478
+ });
1479
+ }
1480
+ static async manifest(root, expectedVersion, requestRoot) {
1481
+ const data = await this.load("manifest", requestRoot);
1482
+ if (data === void 0) return void 0;
1483
+ 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) => {
1484
+ 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);
1485
+ }) || expectedVersion !== void 0 && data.minecraftVersion !== expectedVersion) {
1486
+ throw new Error(`Invalid MineRender manifest at ${root}/minerender-data/manifest.json`);
1487
+ }
1488
+ return data;
1489
+ }
1490
+ static load(file, root) {
1491
+ const key = new AssetKey("minerender-data", file, void 0, void 0, void 0, ".json", root);
1492
+ key.rootType = void 0;
1493
+ return AssetLoader.get(key, "json" /* JSON */);
1494
+ }
1495
+ static async cached(key, scope, defaultRoot, loader) {
1496
+ const checkSources = () => {
1497
+ if (AssetLoader.persistentScope !== scope || AssetLoader.ROOT !== defaultRoot) throw new Error("Asset sources changed while loading MineRender data; retry the request");
1498
+ };
1499
+ checkSources();
1500
+ return await Caching.mineRenderDataCache.get(scope ? `${scope}
1501
+ ${key}` : key, async () => {
1502
+ const value = await loader();
1503
+ checkSources();
1504
+ return value;
1505
+ });
1506
+ }
1507
+ static validDataset(dataset, data) {
1508
+ if (!object(data)) return false;
1509
+ 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)));
1510
+ 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")));
1511
+ if (dataset === "legacyBlocks") return object(data.blocks) && Object.values(data.blocks).every((value) => typeof value === "string");
1512
+ 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")));
1513
+ return Object.values(data).every((value) => {
1514
+ if (!object(value)) return false;
1515
+ switch (dataset) {
1516
+ case "itemDefaults":
1517
+ return true;
1518
+ case "blockStates":
1519
+ return Object.values(value).every((property) => object(property) && "default" in property && Array.isArray(property.values));
1520
+ case "blockTints":
1521
+ 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));
1522
+ case "fluids":
1523
+ return ["water", "lava"].includes(value.kind) && typeof value.renderModel === "boolean" && (value.levelProperty === void 0 || typeof value.levelProperty === "string");
1524
+ case "dyes":
1525
+ return [value.id, value.textureDiffuseColor, value.fireworkColor, value.textColor].every(Number.isInteger);
1526
+ default:
1527
+ return false;
1528
+ }
1529
+ });
1530
+ }
1531
+ };
1532
+ }
1533
+ });
1534
+
1535
+ // src/assets/Renames.ts
1536
+ var Renames;
1537
+ var init_Renames = __esm({
1538
+ "src/assets/Renames.ts"() {
1539
+ init_AssetKey();
1540
+ init_MineRenderData();
1541
+ Renames = class {
1542
+ /** Loads the selected version's rename table, or `undefined` when its published data has none. */
1543
+ static async get(root) {
1544
+ const resolved = await MineRenderData.resolve(root);
1545
+ return resolved.manifest.datasets.renames ? MineRenderData.get("renames", root) : void 0;
1546
+ }
1547
+ /**
1548
+ * Returns the ID that replaced `id`, or `undefined` when `id` keeps its meaning.
1549
+ * Without a data version, an ID that the selected version still registers keeps its meaning.
1550
+ */
1551
+ static async resolve(kind, id, dataVersion, root) {
1552
+ const data = await this.get(root);
1553
+ if (!data || !Object.prototype.hasOwnProperty.call(data[kind], id)) return void 0;
1554
+ const entry = data[kind][id];
1555
+ if (dataVersion === void 0) return entry.registered ? void 0 : entry.ranges[entry.ranges.length - 1].to;
1556
+ if (dataVersion < data.baseDataVersion) return void 0;
1557
+ return entry.ranges.find((range) => dataVersion < range.before)?.to;
1558
+ }
1559
+ /** Returns a copy of `key` for the replacement ID, or `undefined` when the ID keeps its meaning. */
1560
+ static async key(kind, key) {
1561
+ const renamed = await this.resolve(kind, `${key.namespace}:${key.path}`, void 0, key.root);
1562
+ if (!renamed) return void 0;
1563
+ const [namespace, path] = renamed.split(":");
1564
+ return new AssetKey(namespace, path, key.assetType, key.type, key.rootType, key.extension, key.root);
1565
+ }
1566
+ };
1567
+ }
1568
+ });
1569
+
1570
+ // src/model/Model.ts
1571
+ function isDoubleArray(obj) {
1572
+ return Array.isArray(obj) && obj.length === 2;
1573
+ }
1574
+ function isTripleArray(obj) {
1575
+ return Array.isArray(obj) && obj.length === 3;
1576
+ }
1577
+ function isQuadArray(obj) {
1578
+ return Array.isArray(obj) && obj.length === 4;
1579
+ }
1580
+ var ITEM_GENERATED, BUILTIN_GENERATED, BUILTIN_ENTITY, DEFAULT_ELEMENTS;
1581
+ var init_Model = __esm({
1582
+ "src/model/Model.ts"() {
1583
+ ITEM_GENERATED = "item/generated";
1584
+ BUILTIN_GENERATED = "builtin/generated";
1585
+ BUILTIN_ENTITY = "builtin/entity";
1586
+ DEFAULT_ELEMENTS = [];
1587
+ }
1588
+ });
1589
+
1590
+ // ../../node_modules/ts-deepmerge/dist/index.js
1591
+ var require_dist = __commonJS({
1592
+ "../../node_modules/ts-deepmerge/dist/index.js"(exports2) {
1593
+ "use strict";
1594
+ var __assign = exports2 && exports2.__assign || function() {
1595
+ __assign = Object.assign || function(t) {
1596
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
1597
+ s = arguments[i];
1598
+ for (var p10 in s) if (Object.prototype.hasOwnProperty.call(s, p10))
1599
+ t[p10] = s[p10];
1600
+ }
1601
+ return t;
1602
+ };
1603
+ return __assign.apply(this, arguments);
1604
+ };
1605
+ var __read = exports2 && exports2.__read || function(o, n) {
1606
+ var m = typeof Symbol === "function" && o[Symbol.iterator];
1607
+ if (!m) return o;
1608
+ var i = m.call(o), r, ar = [], e;
1609
+ try {
1610
+ while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
1611
+ } catch (error) {
1612
+ e = { error };
1613
+ } finally {
1614
+ try {
1615
+ if (r && !r.done && (m = i["return"])) m.call(i);
1616
+ } finally {
1617
+ if (e) throw e.error;
1618
+ }
1619
+ }
1620
+ return ar;
1621
+ };
1622
+ var __spreadArray = exports2 && exports2.__spreadArray || function(to, from, pack) {
1623
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
1624
+ if (ar || !(i in from)) {
1625
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
1626
+ ar[i] = from[i];
1627
+ }
1628
+ }
1629
+ return to.concat(ar || Array.prototype.slice.call(from));
1630
+ };
1631
+ Object.defineProperty(exports2, "__esModule", { value: true });
1632
+ var isObject = function(obj) {
1633
+ if (typeof obj === "object" && obj !== null) {
1634
+ if (typeof Object.getPrototypeOf === "function") {
1635
+ var prototype = Object.getPrototypeOf(obj);
1636
+ return prototype === Object.prototype || prototype === null;
1637
+ }
1638
+ return Object.prototype.toString.call(obj) === "[object Object]";
1484
1639
  }
1485
- toNamespacedString() {
1486
- return this.namespace + ":" + this.getFullPath();
1640
+ return false;
1641
+ };
1642
+ var PROTECTED_KEYS = ["__proto__"];
1643
+ var merge10 = function() {
1644
+ var objects = [];
1645
+ for (var _i = 0; _i < arguments.length; _i++) {
1646
+ objects[_i] = arguments[_i];
1487
1647
  }
1488
- /** Returns a cache identifier that includes the explicit root or {@link AssetLoader.ROOT}. */
1489
- serialize() {
1490
- return this.toString();
1648
+ return objects.reduce(function(result, current) {
1649
+ Object.keys(current).forEach(function(key) {
1650
+ if (PROTECTED_KEYS.includes(key)) {
1651
+ return;
1652
+ }
1653
+ if (Array.isArray(result[key]) && Array.isArray(current[key])) {
1654
+ result[key] = merge10.options.mergeArrays ? Array.from(new Set(result[key].concat(current[key]))) : current[key];
1655
+ } else if (isObject(result[key]) && isObject(current[key])) {
1656
+ result[key] = merge10(result[key], current[key]);
1657
+ } else {
1658
+ result[key] = current[key];
1659
+ }
1660
+ });
1661
+ return result;
1662
+ }, {});
1663
+ };
1664
+ var defaultOptions = {
1665
+ mergeArrays: true
1666
+ };
1667
+ merge10.options = defaultOptions;
1668
+ merge10.withOptions = function(options) {
1669
+ var objects = [];
1670
+ for (var _i = 1; _i < arguments.length; _i++) {
1671
+ objects[_i - 1] = arguments[_i];
1491
1672
  }
1673
+ merge10.options = __assign({ mergeArrays: true }, options);
1674
+ var result = merge10.apply(void 0, __spreadArray([], __read(objects), false));
1675
+ merge10.options = defaultOptions;
1676
+ return result;
1492
1677
  };
1678
+ exports2.default = merge10;
1493
1679
  }
1494
1680
  });
1495
1681
 
@@ -1615,147 +1801,6 @@ var init_ModelMerger = __esm({
1615
1801
  }
1616
1802
  });
1617
1803
 
1618
- // src/assets/source/parser/AssetParsers.ts
1619
- var AssetParser;
1620
- var init_AssetParsers = __esm({
1621
- "src/assets/source/parser/AssetParsers.ts"() {
1622
- AssetParser = /* @__PURE__ */ ((AssetParser2) => {
1623
- AssetParser2["MODEL"] = "model";
1624
- AssetParser2["BLOCKSTATE"] = "blockstate";
1625
- AssetParser2["META"] = "meta";
1626
- AssetParser2["NBT"] = "nbt";
1627
- AssetParser2["IMAGE"] = "image";
1628
- AssetParser2["LIST"] = "list";
1629
- AssetParser2["JSON"] = "json";
1630
- return AssetParser2;
1631
- })(AssetParser || {});
1632
- }
1633
- });
1634
-
1635
- // src/assets/MineRenderData.ts
1636
- var object, release, compare, MineRenderData;
1637
- var init_MineRenderData = __esm({
1638
- "src/assets/MineRenderData.ts"() {
1639
- init_AssetKey();
1640
- init_AssetLoader();
1641
- init_AssetParsers();
1642
- init_Caching();
1643
- object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
1644
- release = (value) => /^\d+\.\d+(?:\.\d+)?$/.test(value) ? value.split(".").map(Number) : void 0;
1645
- compare = (left, right) => {
1646
- for (let i = 0; i < 3; i++) {
1647
- const difference = (left[i] ?? 0) - (right[i] ?? 0);
1648
- if (difference) return difference;
1649
- }
1650
- return 0;
1651
- };
1652
- MineRenderData = class {
1653
- /** Loads one registry. Missing files and invalid data reject the request. */
1654
- static async get(dataset, root) {
1655
- root = root?.replace(/\/+$/, "");
1656
- const scope = AssetLoader.persistentScope;
1657
- const defaultRoot = AssetLoader.ROOT;
1658
- const resolved = await this.resolve(root);
1659
- const entry = resolved.manifest.datasets[dataset];
1660
- if (!entry) throw new Error(`MineRender ${dataset} data is unavailable for ${resolved.version}: ${resolved.manifest.unavailable[dataset] ?? "not listed in the manifest"}`);
1661
- const file = entry.file;
1662
- const requestRoot = resolved.root === (root ?? defaultRoot).replace(/\/+$/, "") ? root : resolved.root;
1663
- return await this.cached(`${requestRoot === void 0 ? "sources" : "explicit"}:${resolved.root}/${file}:${entry.sha256}`, scope, defaultRoot, async () => {
1664
- const data = await this.load(file.slice(0, -5), requestRoot);
1665
- if (!this.validDataset(dataset, data)) throw new Error(`Invalid MineRender ${dataset} data at ${resolved.root}/minerender-data/${file}`);
1666
- return data;
1667
- });
1668
- }
1669
- /**
1670
- * Uses the exact version when published. Otherwise, selects the nearest patch in the same
1671
- * release line (older on ties), then the latest earlier release or the oldest published release.
1672
- * Unpublished snapshots reject.
1673
- */
1674
- static async resolve(root) {
1675
- const requestRoot = root?.replace(/\/+$/, "");
1676
- const defaultRoot = AssetLoader.ROOT;
1677
- root = (root ?? defaultRoot).replace(/\/+$/, "");
1678
- const scope = AssetLoader.persistentScope;
1679
- return this.cached(`resolve:${requestRoot === void 0 ? "sources" : "explicit"}:${root}`, scope, defaultRoot, async () => {
1680
- const requestedVersion = root.slice(root.lastIndexOf("/") + 1);
1681
- let manifest = await this.manifest(root, requestRoot !== void 0 && release(requestedVersion) ? requestedVersion : void 0, requestRoot);
1682
- if (manifest) return { requestedVersion, version: manifest.minecraftVersion, root, manifest };
1683
- const requested = release(requestedVersion);
1684
- if (!requested) {
1685
- throw new Error(`No MineRender data is published for ${requestedVersion}; automatic fallback requires a release`);
1686
- }
1687
- const base = root.slice(0, root.lastIndexOf("/"));
1688
- const index = await this.cached(`versions:${base}`, scope, defaultRoot, async () => {
1689
- const value = await this.load("versions", base);
1690
- if (!object(value) || !Array.isArray(value.versions) || !value.versions.every((entry) => object(entry) && typeof entry.name === "string")) {
1691
- throw new Error(`Invalid MineRender version index at ${base}/minerender-data/versions.json`);
1692
- }
1693
- return value.versions;
1694
- });
1695
- const versions = index.map((entry) => ({ name: entry.name, number: release(entry.name) })).filter((entry) => !!entry.number).sort((left, right) => compare(left.number, right.number));
1696
- const sameLine = versions.filter((entry) => entry.number[0] === requested[0] && entry.number[1] === requested[1]);
1697
- 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];
1698
- if (!selected) throw new Error(`No compatible MineRender data is published for ${requestedVersion}`);
1699
- const resolvedRoot = `${base}/${selected.name}`;
1700
- manifest = await this.manifest(resolvedRoot, selected.name, resolvedRoot);
1701
- if (!manifest) throw new Error(`Missing MineRender manifest for published version ${selected.name}`);
1702
- return { requestedVersion, version: selected.name, root: resolvedRoot, manifest };
1703
- });
1704
- }
1705
- static async manifest(root, expectedVersion, requestRoot) {
1706
- const data = await this.load("manifest", requestRoot);
1707
- if (data === void 0) return void 0;
1708
- 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) => {
1709
- 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);
1710
- }) || expectedVersion !== void 0 && data.minecraftVersion !== expectedVersion) {
1711
- throw new Error(`Invalid MineRender manifest at ${root}/minerender-data/manifest.json`);
1712
- }
1713
- return data;
1714
- }
1715
- static load(file, root) {
1716
- const key = new AssetKey("minerender-data", file, void 0, void 0, void 0, ".json", root);
1717
- key.rootType = void 0;
1718
- return AssetLoader.get(key, "json" /* JSON */);
1719
- }
1720
- static async cached(key, scope, defaultRoot, loader) {
1721
- const checkSources = () => {
1722
- if (AssetLoader.persistentScope !== scope || AssetLoader.ROOT !== defaultRoot) throw new Error("Asset sources changed while loading MineRender data; retry the request");
1723
- };
1724
- checkSources();
1725
- return await Caching.mineRenderDataCache.get(scope ? `${scope}
1726
- ${key}` : key, async () => {
1727
- const value = await loader();
1728
- checkSources();
1729
- return value;
1730
- });
1731
- }
1732
- static validDataset(dataset, data) {
1733
- if (!object(data)) return false;
1734
- 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)));
1735
- if (dataset === "legacyBlocks") return object(data.blocks) && Object.values(data.blocks).every((value) => typeof value === "string");
1736
- 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")));
1737
- return Object.values(data).every((value) => {
1738
- if (!object(value)) return false;
1739
- switch (dataset) {
1740
- case "itemDefaults":
1741
- return true;
1742
- case "blockStates":
1743
- return Object.values(value).every((property) => object(property) && "default" in property && Array.isArray(property.values));
1744
- case "blockTints":
1745
- 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));
1746
- case "fluids":
1747
- return ["water", "lava"].includes(value.kind) && typeof value.renderModel === "boolean" && (value.levelProperty === void 0 || typeof value.levelProperty === "string");
1748
- case "dyes":
1749
- return [value.id, value.textureDiffuseColor, value.fireworkColor, value.textColor].every(Number.isInteger);
1750
- default:
1751
- return false;
1752
- }
1753
- });
1754
- }
1755
- };
1756
- }
1757
- });
1758
-
1759
1804
  // src/assets/BannerPatterns.ts
1760
1805
  var DYE_COLORS, BannerPatterns;
1761
1806
  var init_BannerPatterns = __esm({
@@ -2078,6 +2123,7 @@ var init_ItemTints = __esm({
2078
2123
  var Models;
2079
2124
  var init_Models = __esm({
2080
2125
  "src/assets/Models.ts"() {
2126
+ init_Renames();
2081
2127
  init_Caching();
2082
2128
  init_ModelMerger();
2083
2129
  init_AssetLoader();
@@ -2411,6 +2457,11 @@ var init_Models = __esm({
2411
2457
  key.extension = ".json";
2412
2458
  }
2413
2459
  const preview = this.snapshotContext(key, context);
2460
+ if (key.type === "item") {
2461
+ const renamed = await Renames.key("items", key);
2462
+ const replacement = renamed && await this.getMerged(renamed, preview);
2463
+ if (replacement) return replacement;
2464
+ }
2414
2465
  const dataKey = await this.applyDefaults(key, preview);
2415
2466
  const keyStr = AssetLoader.persistentKey(key.serialize() + (key.type === "item" ? this.contextKey(preview) + dataKey : ""));
2416
2467
  const model = await Caching.mergedModelCache.get(keyStr, async () => {
@@ -2442,6 +2493,7 @@ var init_BlockStates = __esm({
2442
2493
  init_AssetKey();
2443
2494
  init_PersistentCache();
2444
2495
  init_AssetParsers();
2496
+ init_Renames();
2445
2497
  init_MineRenderData();
2446
2498
  BlockStates = class {
2447
2499
  static get PERSISTENT_CACHE() {
@@ -2482,6 +2534,9 @@ var init_BlockStates = __esm({
2482
2534
  if (!key.extension) {
2483
2535
  key.extension = ".json";
2484
2536
  }
2537
+ const renamed = await Renames.key("blocks", key);
2538
+ const replacement = renamed && await this.get(renamed);
2539
+ if (replacement) return replacement;
2485
2540
  const keyStr = key.serialize();
2486
2541
  return Caching.blockStateCache.get(keyStr, (k) => {
2487
2542
  return this.PERSISTENT_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
@@ -7688,6 +7743,7 @@ __export(index_node_exports, {
7688
7743
  PackMetadata: () => PackMetadata,
7689
7744
  PersistentCache: () => PersistentCache,
7690
7745
  PlayerHeadTextures: () => PlayerHeadTextures,
7746
+ Renames: () => Renames,
7691
7747
  Renderer: () => Renderer,
7692
7748
  RequestError: () => RequestError,
7693
7749
  Requests: () => Requests,
@@ -8228,6 +8284,7 @@ var Fonts = class {
8228
8284
  init_MineRenderData();
8229
8285
  init_ModelTextures();
8230
8286
  init_Models();
8287
+ init_Renames();
8231
8288
  init_source();
8232
8289
 
8233
8290
  // src/cache/index.ts
@@ -17496,18 +17553,23 @@ var SectionModels = class {
17496
17553
  return this.template(variant, choice, tints);
17497
17554
  }));
17498
17555
  }
17499
- /** Resolves section templates for one tint palette, with a synchronous weighted choice for each cell. */
17500
- async prepareState(blockState3, properties = {}, biomeColor) {
17556
+ /** Resolves shared templates; dynamic biome tints are supplied on section placements instead of cached geometry. */
17557
+ async prepareState(blockState3, properties = {}, biomeColor, dynamicBiomeTint = false) {
17501
17558
  const prepared = await this.state(blockState3, properties);
17502
17559
  if (!prepared) return void 0;
17503
17560
  let color2;
17504
17561
  const resolveColor = typeof biomeColor === "function" ? () => color2 ??= biomeColor() : biomeColor;
17505
17562
  const templates = /* @__PURE__ */ new Map();
17563
+ const source = dynamicBiomeTint && await BlockTints.biomeSource(blockState3.key);
17506
17564
  await Promise.all([...prepared.choices].map(async ([variant, choice]) => {
17507
17565
  const tints = await BlockTints.get(blockState3.key, prepared.state, choice.model, void 0, resolveColor);
17508
- templates.set(variant, await this.template(variant, choice, tints));
17566
+ const biomeTints = source ? await BlockTints.get(blockState3.key, prepared.state, choice.model, void 0, 16777215) : void 0;
17567
+ templates.set(variant, await this.template(variant, choice, tints, biomeTints));
17509
17568
  }));
17510
- return { pick: (position) => prepared.groups.map((group) => templates.get(BlockStateResolver.choose(group, position))) };
17569
+ return {
17570
+ pick: (position) => prepared.groups.map((group) => templates.get(BlockStateResolver.choose(group, position))),
17571
+ biomeTint: source ? [...templates.values()].some((template) => template.biomeTint?.some((value) => value === 1)) || void 0 : void 0
17572
+ };
17511
17573
  }
17512
17574
  async state(blockState3, properties) {
17513
17575
  let states = this.states.get(blockState3);
@@ -17550,10 +17612,10 @@ var SectionModels = class {
17550
17612
  }
17551
17613
  return { state, groups, choices: prepared };
17552
17614
  }
17553
- async template(variant, { model, atlas, rotation }, tints) {
17615
+ async template(variant, { model, atlas, rotation }, tints, biomeTints) {
17554
17616
  let models = this.models.get(model);
17555
17617
  if (!models) this.models.set(model, models = /* @__PURE__ */ new Map());
17556
- const key = JSON.stringify([rotation.x, rotation.y, rotation.z, variant.uvlock, tints]);
17618
+ const key = JSON.stringify([rotation.x, rotation.y, rotation.z, variant.uvlock, tints, biomeTints]);
17557
17619
  return cached(models, key, async () => {
17558
17620
  const object4 = new SectionModel(model, {
17559
17621
  instanceMeshes: false,
@@ -17567,11 +17629,13 @@ var SectionModels = class {
17567
17629
  object4.clear();
17568
17630
  const keep = [];
17569
17631
  const cullFaces = [];
17632
+ const biomeTint = biomeTints ? [] : void 0;
17570
17633
  for (const element of atlas.model.elements) {
17571
17634
  for (const face of CUBE_FACES) {
17572
17635
  const data = element.faces[face];
17573
17636
  keep.push(data !== void 0);
17574
17637
  if (!data) continue;
17638
+ biomeTint?.push(Number(data.tintindex !== void 0 && biomeTints?.[data.tintindex] !== void 0));
17575
17639
  const local = CUBE_FACES.findIndex((name) => name === data.cullface);
17576
17640
  if (local < 0) {
17577
17641
  cullFaces.push(0);
@@ -17587,6 +17651,7 @@ var SectionModels = class {
17587
17651
  geometry,
17588
17652
  atlas,
17589
17653
  cullFaces: new Uint8Array(cullFaces),
17654
+ biomeTint: biomeTint?.some(Boolean) ? new Uint8Array(biomeTint) : void 0,
17590
17655
  occludes: ModelCulling.isOpaqueFullCube(atlas) && ModelCulling.toLocalMask(63, rotation) === 63,
17591
17656
  layer: atlas.hasTranslucency ? 1 : 0,
17592
17657
  ambientOcclusion: model.ambientocclusion !== false
@@ -17606,6 +17671,7 @@ var SectionModels = class {
17606
17671
  };
17607
17672
 
17608
17673
  // src/world/_entities/WorldEntities.ts
17674
+ init_Renames();
17609
17675
  init_Entities();
17610
17676
  init_EntityObject();
17611
17677
 
@@ -17765,8 +17831,10 @@ var WorldEntities = class {
17765
17831
  try {
17766
17832
  if (!current()) return;
17767
17833
  const models = await Entities.getEntityList();
17768
- if (!models.includes(placement.key.path) || !current()) return;
17769
- const model = await Entities.getEntity(placement.key, placement.texture, { when: placement.when });
17834
+ const renamed = models.includes(placement.key.path) ? void 0 : await Renames.key("entities", placement.key);
17835
+ const key = renamed && models.includes(renamed.path) ? renamed : placement.key;
17836
+ if (!models.includes(key.path) || !current()) return;
17837
+ const model = await Entities.getEntity(key, placement.texture, { when: placement.when });
17770
17838
  if (!model || !current()) return;
17771
17839
  let tints = placement.tints;
17772
17840
  if (placement.woolDye) {
@@ -17781,7 +17849,7 @@ var WorldEntities = class {
17781
17849
  object4.rotation.y = placement.yaw;
17782
17850
  await object4.init();
17783
17851
  if (!current()) return;
17784
- placement.owner.set(object4, { type: placement.key.toNamespacedString(), position: placement.position });
17852
+ placement.owner.set(object4, { type: key.toNamespacedString(), position: placement.position });
17785
17853
  this.scene.add(object4);
17786
17854
  } catch (error) {
17787
17855
  console.warn(`Could not draw saved entity ${placement.key.toNamespacedString()}, skipping it`, error);
@@ -17813,6 +17881,132 @@ var WorldEntities = class {
17813
17881
  // src/world/MineRenderWorld.ts
17814
17882
  init_FluidQuads();
17815
17883
 
17884
+ // src/world/_biomes/BiomeBlend.ts
17885
+ init_AssetKey();
17886
+ init_AssetLoader();
17887
+ init_Caching();
17888
+ var MAX_TILES = 256;
17889
+ function validateBiomeBlendRadius(radius) {
17890
+ if (!Number.isInteger(radius) || radius < 0 || radius > 7) {
17891
+ throw new RangeError("biomeBlendRadius must be an integer from 0 to 7");
17892
+ }
17893
+ }
17894
+ var BiomeBlend = class {
17895
+ constructor(biomeAt, radius) {
17896
+ this.biomeAt = biomeAt;
17897
+ this.tiles = /* @__PURE__ */ new Map();
17898
+ this.generation = Caching.generation;
17899
+ validateBiomeBlendRadius(radius);
17900
+ this._radius = radius;
17901
+ }
17902
+ get radius() {
17903
+ return this._radius;
17904
+ }
17905
+ clear() {
17906
+ this.tiles.clear();
17907
+ }
17908
+ invalidateColumn(x, z) {
17909
+ for (const [key, tile] of this.tiles) {
17910
+ if (Math.abs(tile.x - x) <= 1 && Math.abs(tile.z - z) <= 1) this.tiles.delete(key);
17911
+ }
17912
+ }
17913
+ async getColor(kind, origin, position) {
17914
+ const [x, y, z] = position;
17915
+ const biome = this.biomeAt(x, y, z);
17916
+ if (biome === void 0) return void 0;
17917
+ if (this.generation !== Caching.generation) {
17918
+ this.clear();
17919
+ this.generation = Caching.generation;
17920
+ }
17921
+ const tileX = Math.floor(x / 16), tileZ = Math.floor(z / 16), quartY = Math.floor(y / 4);
17922
+ const root = origin?.root ?? AssetLoader.ROOT;
17923
+ const key = JSON.stringify([root, AssetLoader.persistentScope, kind, this._radius, tileX, quartY, tileZ]);
17924
+ let tile = this.tiles.get(key);
17925
+ if (tile) {
17926
+ this.tiles.delete(key);
17927
+ this.tiles.set(key, tile);
17928
+ } else {
17929
+ const snapshot = new AssetKey("minecraft", "", void 0, void 0, "assets", ".json", root);
17930
+ const colors = this.buildTile(kind, snapshot, tileX, quartY, tileZ, this._radius).then((result2) => {
17931
+ if (!result2.complete && this.tiles.get(key)?.colors === colors) this.tiles.delete(key);
17932
+ return result2;
17933
+ }, (error) => {
17934
+ if (this.tiles.get(key)?.colors === colors) this.tiles.delete(key);
17935
+ throw error;
17936
+ });
17937
+ tile = { x: tileX, z: tileZ, colors };
17938
+ this.tiles.set(key, tile);
17939
+ if (this.tiles.size > MAX_TILES) this.tiles.delete(this.tiles.keys().next().value);
17940
+ }
17941
+ const result = await tile.colors;
17942
+ if (result.failures.has(biome)) throw result.failures.get(biome);
17943
+ const color2 = result.colors[(z - tileZ * 16) * 16 + x - tileX * 16];
17944
+ return color2 === -1 ? void 0 : color2;
17945
+ }
17946
+ async buildTile(kind, origin, tileX, quartY, tileZ, radius) {
17947
+ const size = 16 + radius * 2, stride = size + 1;
17948
+ const startX = tileX * 16 - radius, startZ = tileZ * 16 - radius;
17949
+ const quartX = Math.floor(startX / 4), quartZ = Math.floor(startZ / 4);
17950
+ const quartSize = Math.floor((startX + size - 1) / 4) - quartX + 1;
17951
+ const ids = new Array(quartSize * quartSize);
17952
+ const biomes = /* @__PURE__ */ new Map();
17953
+ for (let z = 0; z < quartSize; z++) {
17954
+ for (let x = 0; x < quartSize; x++) {
17955
+ const id = this.biomeAt((quartX + x) * 4, quartY * 4, (quartZ + z) * 4);
17956
+ ids[z * quartSize + x] = id;
17957
+ if (id !== void 0 && !biomes.has(id)) {
17958
+ biomes.set(id, (async () => {
17959
+ const swamp = kind === "grass" && (await Biomes.get(id, origin))?.effects.grass_color_modifier === "swamp";
17960
+ return { color: swamp ? void 0 : await Biomes.getColor(id, kind, origin), swamp };
17961
+ })());
17962
+ }
17963
+ }
17964
+ }
17965
+ const failures = /* @__PURE__ */ new Map();
17966
+ const loaded = new Map(await Promise.all([...biomes].map(async ([id, color2]) => {
17967
+ try {
17968
+ return [id, await color2];
17969
+ } catch (error) {
17970
+ failures.set(id, error);
17971
+ return [id, { color: void 0, swamp: false }];
17972
+ }
17973
+ })));
17974
+ const complete = [...loaded.values()].every((value) => value.swamp || value.color !== void 0);
17975
+ const samples = new Int32Array(size * size).fill(-1);
17976
+ const sums = Array.from({ length: 4 }, () => new Uint32Array(stride * stride));
17977
+ for (let z = 0; z < size; z++) {
17978
+ const row = [0, 0, 0, 0];
17979
+ for (let x = 0; x < size; x++) {
17980
+ const id = ids[(Math.floor((startZ + z) / 4) - quartZ) * quartSize + Math.floor((startX + x) / 4) - quartX];
17981
+ const value = id === void 0 ? void 0 : loaded.get(id);
17982
+ const color2 = value?.swamp ? swampGrassColor(startX + x, startZ + z) : value?.color;
17983
+ if (color2 !== void 0) {
17984
+ samples[z * size + x] = color2;
17985
+ row[0] += color2 >> 16 & 255;
17986
+ row[1] += color2 >> 8 & 255;
17987
+ row[2] += color2 & 255;
17988
+ row[3]++;
17989
+ }
17990
+ for (let channel = 0; channel < 4; channel++) {
17991
+ sums[channel][(z + 1) * stride + x + 1] = sums[channel][z * stride + x + 1] + row[channel];
17992
+ }
17993
+ }
17994
+ }
17995
+ const colors = new Int32Array(256).fill(-1), width = radius * 2 + 1;
17996
+ for (let z = 0; z < 16; z++) {
17997
+ for (let x = 0; x < 16; x++) {
17998
+ if (samples[(z + radius) * size + x + radius] === -1) continue;
17999
+ const topLeft = z * stride + x, topRight = topLeft + width;
18000
+ const bottomLeft = (z + width) * stride + x, bottomRight = bottomLeft + width;
18001
+ const sum = (values) => values[bottomRight] - values[bottomLeft] - values[topRight] + values[topLeft];
18002
+ const count = sum(sums[3]);
18003
+ 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);
18004
+ }
18005
+ }
18006
+ return { colors, complete, failures };
18007
+ }
18008
+ };
18009
+
17816
18010
  // src/world/WorldLight.ts
17817
18011
  function copySectionLight(light) {
17818
18012
  const copy = {};
@@ -17876,12 +18070,17 @@ var MineRenderWorld = class {
17876
18070
  this.lightValidity = /* @__PURE__ */ new Map();
17877
18071
  this.highestSkySection = /* @__PURE__ */ new Map();
17878
18072
  this.ambientOcclusionPending = false;
18073
+ this.pendingBiomes = /* @__PURE__ */ new Map();
17879
18074
  this.pendingCulling = /* @__PURE__ */ new Map();
17880
18075
  if (options.savedLighting && !options.sectionMeshing) throw new TypeError("savedLighting requires sectionMeshing");
17881
18076
  if (options.ambientOcclusion && !options.sectionMeshing) throw new TypeError("ambientOcclusion requires sectionMeshing");
17882
18077
  if (options.ambientLight !== void 0 && (!Number.isFinite(options.ambientLight) || options.ambientLight < 0 || options.ambientLight > 1)) {
17883
18078
  throw new RangeError("ambientLight must be a number from 0 to 1");
17884
18079
  }
18080
+ this._biomeBlendRadius = options.biomeBlendRadius ?? 0;
18081
+ validateBiomeBlendRadius(this._biomeBlendRadius);
18082
+ if (this._biomeBlendRadius > 0 && !options.sectionMeshing) throw new TypeError("biomeBlendRadius requires sectionMeshing");
18083
+ if (this._biomeBlendRadius) this.biomeBlend = new BiomeBlend((x, y, z) => this.getBiomeAt(x, y, z), this._biomeBlendRadius);
17885
18084
  this.scene = scene;
17886
18085
  this.savedLighting = options.savedLighting ?? false;
17887
18086
  this.ambientLight = options.ambientLight ?? 0;
@@ -17922,6 +18121,65 @@ var MineRenderWorld = class {
17922
18121
  await this.updateCulling([]);
17923
18122
  this.ambientOcclusionPending = false;
17924
18123
  }
18124
+ /** Horizontal biome-color sampling radius, from 0 to 7 blocks. Zero disables blending. */
18125
+ get biomeBlendRadius() {
18126
+ return this._biomeBlendRadius;
18127
+ }
18128
+ /** Refreshes loaded grass, foliage, and water colors without reloading their source chunks. */
18129
+ async setBiomeBlendRadius(radius) {
18130
+ validateBiomeBlendRadius(radius);
18131
+ if (radius > 0 && !this.sectionModels) throw new TypeError("biomeBlendRadius requires sectionMeshing");
18132
+ if (radius !== this._biomeBlendRadius) {
18133
+ this._biomeBlendRadius = radius;
18134
+ this.biomeBlend?.clear();
18135
+ this.biomeBlend = radius ? new BiomeBlend((x, y, z) => this.getBiomeAt(x, y, z), radius) : void 0;
18136
+ for (const chunk of this._chunks.values()) this.pendingBiomes.set(chunk, void 0);
18137
+ }
18138
+ const failures = await this.refreshBiomes();
18139
+ if (failures.size) throw failures.values().next().value;
18140
+ }
18141
+ markBiomesChanged(x, z, y, includeColumn = true) {
18142
+ const queued = [];
18143
+ if (!this.biomeBlend) return queued;
18144
+ this.biomeBlend.invalidateColumn(x, z);
18145
+ for (const chunk of this._chunks.values()) {
18146
+ 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;
18147
+ queued.push(chunk);
18148
+ if (!this.pendingBiomes.has(chunk)) this.pendingBiomes.set(chunk, []);
18149
+ const columns = this.pendingBiomes.get(chunk);
18150
+ if (columns && !columns.some((column) => column.x === x && column.z === z)) columns.push({ x, z });
18151
+ }
18152
+ return queued;
18153
+ }
18154
+ async refreshBiomes() {
18155
+ if (this.biomeRefreshing) await this.biomeRefreshing;
18156
+ if (!this.pendingBiomes.size) return /* @__PURE__ */ new Map();
18157
+ return this.biomeRefreshing ??= Promise.resolve().then(async () => {
18158
+ const failures = /* @__PURE__ */ new Map();
18159
+ const batch = [...this.pendingBiomes.keys()];
18160
+ let sliceStart = performance.now();
18161
+ try {
18162
+ for (const chunk of batch) {
18163
+ const columns = this.pendingBiomes.get(chunk);
18164
+ this.pendingBiomes.delete(chunk);
18165
+ if (this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`) !== chunk) continue;
18166
+ try {
18167
+ await chunk.refreshBiomes(columns);
18168
+ } catch (error) {
18169
+ failures.set(chunk, error);
18170
+ if (this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`) === chunk) this.pendingBiomes.set(chunk, void 0);
18171
+ }
18172
+ if (performance.now() - sliceStart > 8) {
18173
+ await yieldToEventLoop();
18174
+ sliceStart = performance.now();
18175
+ }
18176
+ }
18177
+ return failures;
18178
+ } finally {
18179
+ this.biomeRefreshing = void 0;
18180
+ }
18181
+ });
18182
+ }
17925
18183
  getBlockAt(posOrX, y, z) {
17926
18184
  if (typeof posOrX == "number") {
17927
18185
  return this.getBlockAt(new import_three45.Vector3(posOrX, y, z));
@@ -18157,6 +18415,9 @@ var MineRenderWorld = class {
18157
18415
  }
18158
18416
  await placeEntities?.();
18159
18417
  } finally {
18418
+ this.markBiomesChanged(chunk.x, chunk.z, void 0, false);
18419
+ const failures = await this.refreshBiomes();
18420
+ if (failures.size) console.warn("Failed to refresh biome colors", failures.values().next().value);
18160
18421
  await this.updateCulling([...changes.values()]);
18161
18422
  }
18162
18423
  }
@@ -18173,11 +18434,14 @@ var MineRenderWorld = class {
18173
18434
  try {
18174
18435
  await this.clearChunkColumn(x, z);
18175
18436
  } finally {
18437
+ const failures = await this.refreshBiomes();
18438
+ if (failures.size) console.warn("Failed to refresh biome colors", failures.values().next().value);
18176
18439
  await this.updateCulling([]);
18177
18440
  }
18178
18441
  }
18179
18442
  async clearChunkColumn(x, z) {
18180
18443
  this.biomes.delete(`${x}_${z}`);
18444
+ this.markBiomesChanged(x, z, void 0, false);
18181
18445
  if (this.savedLighting) {
18182
18446
  for (const y of this.light.get(`${x}_${z}`)?.keys() ?? []) this.markSectionChanged({ x, y, z });
18183
18447
  }
@@ -18188,6 +18452,7 @@ var MineRenderWorld = class {
18188
18452
  for (const [key, section] of previous) {
18189
18453
  this._chunks.delete(key);
18190
18454
  this.pendingCulling.delete(section);
18455
+ this.pendingBiomes.delete(section);
18191
18456
  await section.clear(false);
18192
18457
  this.markSectionChanged(section);
18193
18458
  }
@@ -18222,6 +18487,8 @@ var MineRenderWorld = class {
18222
18487
  const chunks = [...this._chunks.values()];
18223
18488
  this._chunks.clear();
18224
18489
  this.biomes.clear();
18490
+ this.biomeBlend?.clear();
18491
+ this.pendingBiomes.clear();
18225
18492
  this.light.clear();
18226
18493
  this.lightValidity.clear();
18227
18494
  this.highestSkySection.clear();
@@ -18340,8 +18607,14 @@ var MineRenderWorld = class {
18340
18607
  column?.delete(y);
18341
18608
  if (!column?.size) this.biomes.delete(columnKey);
18342
18609
  }
18343
- await chunk.refreshBiomes();
18344
- await this._chunks.get(`${x}_${y + 1}_${z}`)?.refreshBiomes();
18610
+ if (this.biomeBlend) {
18611
+ const queued = this.markBiomesChanged(x, z, y);
18612
+ const failures = await this.refreshBiomes();
18613
+ for (const chunk2 of queued) if (failures.has(chunk2)) throw failures.get(chunk2);
18614
+ } else {
18615
+ await chunk.refreshBiomes();
18616
+ await this._chunks.get(`${x}_${y + 1}_${z}`)?.refreshBiomes();
18617
+ }
18345
18618
  },
18346
18619
  this.savedLighting ? (x2, y2, z2) => this.lightLevelAt(x2, y2, z2) : void 0,
18347
18620
  async () => {
@@ -18358,7 +18631,8 @@ var MineRenderWorld = class {
18358
18631
  await this.updateCulling([]);
18359
18632
  }
18360
18633
  },
18361
- this.ambientLight
18634
+ this.ambientLight,
18635
+ () => this.biomeBlend
18362
18636
  );
18363
18637
  this._chunks.set(key, chunk);
18364
18638
  }
@@ -18477,9 +18751,10 @@ var LIGHT_SAMPLE_OFFSETS = [0, 1, -1, 324, -324, 18, -18];
18477
18751
 
18478
18752
  // src/world/SectionGeometry.ts
18479
18753
  function buildSectionGeometry(input) {
18480
- const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize, fluids, light, ambientLight, ambientOcclusion } = input;
18754
+ const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize, fluids, light, ambientLight, ambientOcclusion, biomeTints } = input;
18481
18755
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
18482
18756
  if (count > indices.length || count > templates.length || count > cullMasks.length) throw new RangeError("Section entry count exceeds its arrays");
18757
+ if (biomeTints && biomeTints.length !== count * 3) throw new RangeError("Section biome tints must contain RGB for each placement");
18483
18758
  if (fluids && fluids.cells.length !== 5832) throw new RangeError("Section fluid cells must cover an 18\xD718\xD718 neighborhood");
18484
18759
  if (fluids?.waterTints && fluids.waterTints.length !== 4096 * 3) throw new RangeError("Section water tints must contain RGB for 4096 cells");
18485
18760
  if (light && (light.length !== 5832 || light.some((level) => level > 15 && level !== 255))) {
@@ -18494,7 +18769,7 @@ function buildSectionGeometry(input) {
18494
18769
  const cornerBrightness2 = ambientOcclusion ? new Float32Array(4) : void 0;
18495
18770
  for (const template of templateData) {
18496
18771
  const { quads } = template;
18497
- 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) {
18772
+ 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) {
18498
18773
  throw new RangeError("Section template arrays do not match its quad count");
18499
18774
  }
18500
18775
  }
@@ -18604,6 +18879,7 @@ function buildSectionGeometry(input) {
18604
18879
  blockIndices[indexOffset / 6] = indices[entry];
18605
18880
  if (ambientMask) quadAmbientOcclusion(template.positions, template.normals, quad, ambientMask, cornerBrightness2);
18606
18881
  const brightness = light ? lightBrightness(light[lightIndex + LIGHT_SAMPLE_OFFSETS[quadLightIndex(template.positions, template.normals, quad)]], ambientLight) : 1;
18882
+ const tint = biomeTints && template.biomeTint?.[quad] && !Number.isNaN(biomeTints[entry * 3]) ? entry * 3 : void 0;
18607
18883
  const start = vertex;
18608
18884
  for (let corner = 0; corner < 4; corner++, vertex++) {
18609
18885
  const source = quad * 4 + corner;
@@ -18620,9 +18896,9 @@ function buildSectionGeometry(input) {
18620
18896
  uvBounds[vertex * 4 + 2] = mapU(template.uvBounds[source * 4 + 2]);
18621
18897
  uvBounds[vertex * 4 + 3] = mapV(template.uvBounds[source * 4 + 3]);
18622
18898
  const factor = brightness * (ambientMask ? cornerBrightness2[corner] : 1);
18623
- colors[vertex * 3] = template.colors[source * 3] * factor;
18624
- colors[vertex * 3 + 1] = template.colors[source * 3 + 1] * factor;
18625
- colors[vertex * 3 + 2] = template.colors[source * 3 + 2] * factor;
18899
+ colors[vertex * 3] = (tint === void 0 ? template.colors[source * 3] : biomeTints[tint]) * factor;
18900
+ colors[vertex * 3 + 1] = (tint === void 0 ? template.colors[source * 3 + 1] : biomeTints[tint + 1]) * factor;
18901
+ colors[vertex * 3 + 2] = (tint === void 0 ? template.colors[source * 3 + 2] : biomeTints[tint + 2]) * factor;
18626
18902
  }
18627
18903
  for (let corner = 0; corner < 6; corner++) outputIndices[indexOffset++] = start + template.indices[quad * 6 + corner];
18628
18904
  }
@@ -18744,7 +19020,11 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
18744
19020
  ...lighting
18745
19021
  };
18746
19022
  for (let i = 0; i < entries.length; i++) {
18747
- const { index, template, cullMask } = entries[i];
19023
+ const { index, template, cullMask, biomeTint } = entries[i];
19024
+ if (biomeTint) {
19025
+ input.biomeTints ??= new Float32Array(entries.length * 3).fill(NaN);
19026
+ input.biomeTints.set(biomeTint, i * 3);
19027
+ }
18748
19028
  let id = templateIds.get(template);
18749
19029
  if (id === void 0) {
18750
19030
  let atlas = atlasIds.get(template.atlas);
@@ -18771,6 +19051,7 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
18771
19051
  colors: new Float32Array(quads * 12),
18772
19052
  indices: new Uint16Array(quads * 6),
18773
19053
  cullFaces: new Uint8Array(template.cullFaces),
19054
+ biomeTint: template.biomeTint ? new Uint8Array(template.biomeTint) : void 0,
18774
19055
  atlas: 0,
18775
19056
  layer: template.layer,
18776
19057
  ambientOcclusion: template.ambientOcclusion
@@ -18915,7 +19196,7 @@ init_CubeFace();
18915
19196
  init_InstanceReference();
18916
19197
  var WATER_TINT2 = new import_three47.Color(4159204).toArray();
18917
19198
  var Chunk = class _Chunk {
18918
- constructor(scene, x, y, z, onBlocksChanged, sectionModels, neighborCell, biomeAt, onBiomesChanged, lightAt, onLightCleared, ambientLight = 0) {
19199
+ constructor(scene, x, y, z, onBlocksChanged, sectionModels, neighborCell, biomeAt, onBiomesChanged, lightAt, onLightCleared, ambientLight = 0, biomeBlend) {
18919
19200
  this.onBlocksChanged = onBlocksChanged;
18920
19201
  this.sectionModels = sectionModels;
18921
19202
  this.neighborCell = neighborCell;
@@ -18924,6 +19205,7 @@ var Chunk = class _Chunk {
18924
19205
  this.lightAt = lightAt;
18925
19206
  this.onLightCleared = onLightCleared;
18926
19207
  this.ambientLight = ambientLight;
19208
+ this.biomeBlend = biomeBlend;
18927
19209
  this.data = new ChunkData();
18928
19210
  this.renderedBlocks = /* @__PURE__ */ new Map();
18929
19211
  this.objectBlocks = /* @__PURE__ */ new Set();
@@ -18933,6 +19215,7 @@ var Chunk = class _Chunk {
18933
19215
  this.blockPosition = new import_three47.Vector3();
18934
19216
  this.meshDirty = false;
18935
19217
  this.meshGeneration = 0;
19218
+ this.blockChanges = /* @__PURE__ */ new Set();
18936
19219
  this.scene = scene;
18937
19220
  this.x = x;
18938
19221
  this.y = y;
@@ -19031,10 +19314,20 @@ var Chunk = class _Chunk {
19031
19314
  return this.refreshBiomes();
19032
19315
  }
19033
19316
  /** Rebuilds biome-tinted blocks while preserving hidden blocks and unrelated render objects. */
19034
- async refreshBiomes() {
19317
+ refreshBiomes(columns) {
19318
+ return this.changeBlocks(() => this.refreshBiomeBlocks(columns), true);
19319
+ }
19320
+ async refreshBiomeBlocks(columns) {
19321
+ const blend = columns && this.biomeBlend?.();
19322
+ const ownColumn = columns?.some((column) => column.x === this.x && column.z === this.z);
19035
19323
  const affected = /* @__PURE__ */ new Map();
19036
19324
  const blocks = [];
19037
19325
  for (const index of this.renderedBlocks.keys()) {
19326
+ if (blend) {
19327
+ if (this.objectBlocks.has(index) && !ownColumn) continue;
19328
+ const [x, , z] = this.positionAt(index), radius = blend.radius;
19329
+ 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;
19330
+ }
19038
19331
  const block = this.data.get(index);
19039
19332
  const stateKey = ChunkData.paletteKey(block);
19040
19333
  let matches = affected.get(stateKey);
@@ -19049,10 +19342,32 @@ var Chunk = class _Chunk {
19049
19342
  }
19050
19343
  if (await matches) blocks.push({ index, block });
19051
19344
  }
19345
+ if (blend) {
19346
+ const groups = /* @__PURE__ */ new Map();
19347
+ for (const entry of blocks) {
19348
+ const key = ChunkData.paletteKey(entry.block);
19349
+ let group = groups.get(key);
19350
+ if (!group) groups.set(key, group = []);
19351
+ group.push(entry);
19352
+ }
19353
+ blocks.length = 0;
19354
+ const biomes = /* @__PURE__ */ new Map();
19355
+ for (const group of groups.values()) {
19356
+ const { contexts, fluid } = await this.resolveState(group[0].block, group.map((entry) => entry.index), biomes);
19357
+ for (const entry of group) {
19358
+ const context = contexts.get(entry.index);
19359
+ const current = this.sectionBlocks.get(entry.index)?.[0]?.biomeTint;
19360
+ const tint = context?.tint;
19361
+ const tintChanged = !!current !== !!tint || tint?.some((value, channel) => value !== current?.[channel]);
19362
+ 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])));
19363
+ if (this.objectBlocks.has(entry.index) || tintChanged || waterChanged) blocks.push(entry);
19364
+ }
19365
+ }
19366
+ }
19052
19367
  if (!blocks.length) return;
19053
19368
  const hidden = new Set(this.hiddenBlocks);
19054
19369
  try {
19055
- await this.placeBlocks(blocks);
19370
+ await this.replaceBlocks(blocks, true);
19056
19371
  } finally {
19057
19372
  for (const index of hidden) {
19058
19373
  this.hiddenBlocks.add(index);
@@ -19092,6 +19407,20 @@ var Chunk = class _Chunk {
19092
19407
  }
19093
19408
  /** Places cells in input order and returns changed world positions without refreshing neighbors. */
19094
19409
  async placeBlocks(blocks) {
19410
+ return this.changeBlocks(() => this.replaceBlocks(blocks));
19411
+ }
19412
+ changeBlocks(change, refreshBiomes = false) {
19413
+ const before = refreshBiomes ? Promise.allSettled(this.blockChanges) : this.biomeChange;
19414
+ const result = before ? before.catch(() => void 0).then(change) : change();
19415
+ const pending = result.finally(() => {
19416
+ this.blockChanges.delete(pending);
19417
+ if (this.biomeChange === pending) this.biomeChange = void 0;
19418
+ });
19419
+ this.blockChanges.add(pending);
19420
+ if (refreshBiomes) this.biomeChange = pending;
19421
+ return pending;
19422
+ }
19423
+ async replaceBlocks(blocks, preserveDataOnFailure = false) {
19095
19424
  const placements = blocks.map(({ index, block }) => ({
19096
19425
  index,
19097
19426
  key: block && !ChunkData.isAir(block) ? ChunkData.paletteKey(block) : void 0,
@@ -19111,6 +19440,10 @@ var Chunk = class _Chunk {
19111
19440
  const biomes = /* @__PURE__ */ new Map();
19112
19441
  const settled = await Promise.allSettled([...states.values()].map(({ stored, indices }) => this.resolveState(stored, indices, biomes)));
19113
19442
  const resolutions = new Map([...states.keys()].map((key, i) => [key, settled[i]]));
19443
+ if (preserveDataOnFailure) {
19444
+ const rejected = settled.find((result) => result.status === "rejected");
19445
+ if (rejected?.status === "rejected") throw rejected.reason;
19446
+ }
19114
19447
  const positions = [];
19115
19448
  let failed = false, failure;
19116
19449
  for (const { index, key, block } of placements) {
@@ -19148,7 +19481,7 @@ var Chunk = class _Chunk {
19148
19481
  }
19149
19482
  const templates = perBlock ? void 0 : fluid?.renderModel === false ? [] : (context?.prepared ?? prepared)?.pick(variantPosition);
19150
19483
  if (templates) {
19151
- this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0 })));
19484
+ this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0, biomeTint: context?.tint })));
19152
19485
  this.meshDirty = true;
19153
19486
  } else {
19154
19487
  object4 = await this.scene.addBlock(blockState3, {
@@ -19167,7 +19500,11 @@ var Chunk = class _Chunk {
19167
19500
  }, object: object4 });
19168
19501
  if (object4) this.objectBlocks.add(index);
19169
19502
  } catch (error) {
19170
- this.data.assign(index, 0);
19503
+ if (preserveDataOnFailure && readBlock) {
19504
+ this.renderedBlocks.set(index, { get block() {
19505
+ return readBlock();
19506
+ }, object: void 0 });
19507
+ } else this.data.assign(index, 0);
19171
19508
  this.fluidCells[index] = 0;
19172
19509
  object4?.removeFromScene();
19173
19510
  if (!failed) failure = error;
@@ -19178,7 +19515,10 @@ var Chunk = class _Chunk {
19178
19515
  return positions;
19179
19516
  }
19180
19517
  /** Replaces every cell from parsed section data. The caller refreshes neighbor culling. */
19181
- async placeSection(data) {
19518
+ placeSection(data) {
19519
+ return this.changeBlocks(() => this.replaceSection(data));
19520
+ }
19521
+ async replaceSection(data) {
19182
19522
  for (const info of this.renderedBlocks.values()) info.object?.removeFromScene();
19183
19523
  this.sectionBlocks.clear();
19184
19524
  this.fluidCells.fill(0);
@@ -19225,7 +19565,7 @@ var Chunk = class _Chunk {
19225
19565
  ];
19226
19566
  const templates = perBlock ? void 0 : fluid?.renderModel === false ? [] : (context?.prepared ?? prepared)?.pick(variantPosition);
19227
19567
  if (templates) {
19228
- this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0 })));
19568
+ this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0, biomeTint: context?.tint })));
19229
19569
  this.meshDirty = true;
19230
19570
  } else {
19231
19571
  perBlocks.push({ index, blockState: blockState3, biome: context?.biome });
@@ -19309,12 +19649,35 @@ var Chunk = class _Chunk {
19309
19649
  }
19310
19650
  async resolveState(stored, indices, biomes) {
19311
19651
  const blockState3 = await BlockStates.get(AssetKey.parse("blockstates", stored.type));
19312
- const perBlock = !blockState3 || !this.sectionModels || !!(blockState3.key && BlockEntities.entry(await BlockEntities.getIndex(blockState3.key.root), blockState3.key.toNamespacedString()));
19652
+ let perBlock = !blockState3 || !this.sectionModels || !!(blockState3.key && BlockEntities.entry(await BlockEntities.getIndex(blockState3.key.root), blockState3.key.toNamespacedString()));
19313
19653
  const fluid = await getBlockFluidState(blockState3?.key, stored.properties);
19314
19654
  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;
19315
19655
  const source = hasBiomes && blockState3 ? await BlockTints.biomeSource(blockState3.key) : void 0;
19316
19656
  const prepare = (color2) => perBlock || fluid?.renderModel === false ? void 0 : this.sectionModels.prepareState(blockState3, stored.properties, color2);
19317
19657
  const contexts = /* @__PURE__ */ new Map();
19658
+ const blend = this.biomeBlend?.();
19659
+ if (blend && !perBlock && blockState3 && (source || fluid?.kind === "water")) {
19660
+ const prepared2 = fluid?.renderModel === false ? void 0 : await this.sectionModels.prepareState(blockState3, stored.properties, void 0, true);
19661
+ if (!prepared2 && fluid?.renderModel !== false) {
19662
+ perBlock = true;
19663
+ } else {
19664
+ await Promise.all(indices.map(async (index) => {
19665
+ const position = this.biomePosition(index, blockState3, stored);
19666
+ const biomeColor = (kind) => blend.getColor(kind, blockState3.key, position);
19667
+ const context = {};
19668
+ if (source && prepared2?.biomeTint) {
19669
+ const color2 = await biomeColor(source);
19670
+ if (color2 !== void 0) context.tint = new import_three47.Color(color2).toArray();
19671
+ }
19672
+ if (fluid?.kind === "water") {
19673
+ const color2 = await biomeColor("water");
19674
+ if (color2 !== void 0 && color2 !== 4159204) context.waterTint = new import_three47.Color(color2).toArray();
19675
+ }
19676
+ contexts.set(index, context);
19677
+ }));
19678
+ return { blockState: blockState3, perBlock, fluid, prepared: prepared2, contexts, stored };
19679
+ }
19680
+ }
19318
19681
  const groups = /* @__PURE__ */ new Map();
19319
19682
  if (blockState3 && (source || fluid?.kind === "water")) {
19320
19683
  for (const index of indices) {
@@ -19362,7 +19725,11 @@ var Chunk = class _Chunk {
19362
19725
  this.waterTints.set(tint ?? WATER_TINT2, index * 3);
19363
19726
  }
19364
19727
  /** Changes visibility at world block coordinates while preserving the block's data. */
19365
- async setBlockVisibleAt(pos, visible) {
19728
+ setBlockVisibleAt(pos, visible) {
19729
+ const position = pos.clone();
19730
+ return this.changeBlocks(() => this.setBlockVisibility(position, visible));
19731
+ }
19732
+ async setBlockVisibility(pos, visible) {
19366
19733
  const index = _Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos));
19367
19734
  const block = this.renderedBlocks.get(index);
19368
19735
  if (!block || visible === !this.hiddenBlocks.has(index)) {
@@ -19532,6 +19899,7 @@ var Chunk = class _Chunk {
19532
19899
  }
19533
19900
  /** Removes stored blocks and render objects. Set `notify` to false to skip the owning world's update. */
19534
19901
  async clear(notify = true) {
19902
+ if (this.blockChanges.size) await Promise.allSettled(this.blockChanges);
19535
19903
  const positions = notify && this.onBlocksChanged ? [...this.renderedBlocks.keys()].map((index) => this.chunkPosToWorldPos(
19536
19904
  new import_three47.Vector3(index % 16, Math.floor(index / 256), Math.floor(index / 16) % 16)
19537
19905
  )) : [];
@@ -20011,6 +20379,7 @@ var NodeRenderer = class _NodeRenderer extends Renderer {
20011
20379
  PackMetadata,
20012
20380
  PersistentCache,
20013
20381
  PlayerHeadTextures,
20382
+ Renames,
20014
20383
  Renderer,
20015
20384
  RequestError,
20016
20385
  Requests,