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.
@@ -263,6 +263,10 @@ var _Caching, Caching;
263
263
  var init_Caching = __esm({
264
264
  "src/cache/Caching.ts"() {
265
265
  _Caching = class _Caching {
266
+ /** Changes when shared assets are cleared, so derived caches can discard their old values. */
267
+ static get generation() {
268
+ return this._generation;
269
+ }
266
270
  static createAssetCache() {
267
271
  return Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(30)).buildAsync();
268
272
  }
@@ -298,6 +302,7 @@ var init_Caching = __esm({
298
302
  }
299
303
  /** Invalidates in-memory entries so later lookups reload them. Persisted entries are retained. */
300
304
  static clear() {
305
+ this._generation++;
301
306
  for (const cache of this.all) {
302
307
  cache.invalidateAll();
303
308
  }
@@ -309,6 +314,7 @@ var init_Caching = __esm({
309
314
  * Use {@link shutdown} to also stop requests and the shared Ticker.
310
315
  */
311
316
  static end() {
317
+ this._generation++;
312
318
  for (const cache of this.all) {
313
319
  cache.invalidateAll();
314
320
  cache.end();
@@ -320,6 +326,7 @@ var init_Caching = __esm({
320
326
  };
321
327
  }
322
328
  };
329
+ _Caching._generation = 0;
323
330
  _Caching.rawImageCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(5)).buildAsync();
324
331
  _Caching.imageDataCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(10)).buildAsync();
325
332
  _Caching.canvasImageDataCache = Caches.builder().expireAfterWrite(Time.minutes(10)).expireAfterAccess(Time.minutes(5)).expirationInterval(Time.seconds(10)).buildAsync();
@@ -1208,118 +1215,6 @@ var init_Materials = __esm({
1208
1215
  }
1209
1216
  });
1210
1217
 
1211
- // src/model/Model.ts
1212
- function isDoubleArray(obj) {
1213
- return Array.isArray(obj) && obj.length === 2;
1214
- }
1215
- function isTripleArray(obj) {
1216
- return Array.isArray(obj) && obj.length === 3;
1217
- }
1218
- function isQuadArray(obj) {
1219
- return Array.isArray(obj) && obj.length === 4;
1220
- }
1221
- var ITEM_GENERATED, BUILTIN_GENERATED, BUILTIN_ENTITY, DEFAULT_ELEMENTS;
1222
- var init_Model = __esm({
1223
- "src/model/Model.ts"() {
1224
- ITEM_GENERATED = "item/generated";
1225
- BUILTIN_GENERATED = "builtin/generated";
1226
- BUILTIN_ENTITY = "builtin/entity";
1227
- DEFAULT_ELEMENTS = [];
1228
- }
1229
- });
1230
-
1231
- // ../../node_modules/ts-deepmerge/dist/index.js
1232
- var require_dist = __commonJS({
1233
- "../../node_modules/ts-deepmerge/dist/index.js"(exports) {
1234
- "use strict";
1235
- var __assign = exports && exports.__assign || function() {
1236
- __assign = Object.assign || function(t) {
1237
- for (var s, i = 1, n = arguments.length; i < n; i++) {
1238
- s = arguments[i];
1239
- for (var p11 in s) if (Object.prototype.hasOwnProperty.call(s, p11))
1240
- t[p11] = s[p11];
1241
- }
1242
- return t;
1243
- };
1244
- return __assign.apply(this, arguments);
1245
- };
1246
- var __read = exports && exports.__read || function(o, n) {
1247
- var m = typeof Symbol === "function" && o[Symbol.iterator];
1248
- if (!m) return o;
1249
- var i = m.call(o), r, ar = [], e;
1250
- try {
1251
- while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
1252
- } catch (error) {
1253
- e = { error };
1254
- } finally {
1255
- try {
1256
- if (r && !r.done && (m = i["return"])) m.call(i);
1257
- } finally {
1258
- if (e) throw e.error;
1259
- }
1260
- }
1261
- return ar;
1262
- };
1263
- var __spreadArray = exports && exports.__spreadArray || function(to, from, pack) {
1264
- if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
1265
- if (ar || !(i in from)) {
1266
- if (!ar) ar = Array.prototype.slice.call(from, 0, i);
1267
- ar[i] = from[i];
1268
- }
1269
- }
1270
- return to.concat(ar || Array.prototype.slice.call(from));
1271
- };
1272
- Object.defineProperty(exports, "__esModule", { value: true });
1273
- var isObject = function(obj) {
1274
- if (typeof obj === "object" && obj !== null) {
1275
- if (typeof Object.getPrototypeOf === "function") {
1276
- var prototype = Object.getPrototypeOf(obj);
1277
- return prototype === Object.prototype || prototype === null;
1278
- }
1279
- return Object.prototype.toString.call(obj) === "[object Object]";
1280
- }
1281
- return false;
1282
- };
1283
- var PROTECTED_KEYS = ["__proto__"];
1284
- var merge10 = function() {
1285
- var objects = [];
1286
- for (var _i = 0; _i < arguments.length; _i++) {
1287
- objects[_i] = arguments[_i];
1288
- }
1289
- return objects.reduce(function(result, current) {
1290
- Object.keys(current).forEach(function(key) {
1291
- if (PROTECTED_KEYS.includes(key)) {
1292
- return;
1293
- }
1294
- if (Array.isArray(result[key]) && Array.isArray(current[key])) {
1295
- result[key] = merge10.options.mergeArrays ? Array.from(new Set(result[key].concat(current[key]))) : current[key];
1296
- } else if (isObject(result[key]) && isObject(current[key])) {
1297
- result[key] = merge10(result[key], current[key]);
1298
- } else {
1299
- result[key] = current[key];
1300
- }
1301
- });
1302
- return result;
1303
- }, {});
1304
- };
1305
- var defaultOptions = {
1306
- mergeArrays: true
1307
- };
1308
- merge10.options = defaultOptions;
1309
- merge10.withOptions = function(options) {
1310
- var objects = [];
1311
- for (var _i = 1; _i < arguments.length; _i++) {
1312
- objects[_i - 1] = arguments[_i];
1313
- }
1314
- merge10.options = __assign({ mergeArrays: true }, options);
1315
- var result = merge10.apply(void 0, __spreadArray([], __read(objects), false));
1316
- merge10.options = defaultOptions;
1317
- return result;
1318
- };
1319
- exports.default = merge10;
1320
- }
1321
- });
1322
-
1323
1218
  // src/assets/AssetKey.ts
1324
1219
  function isResourceLocation(value) {
1325
1220
  return typeof value === "string" && /^(?:[a-z0-9_.-]+:)?[a-z0-9_./-]+$/.test(value);
@@ -1422,17 +1317,306 @@ var init_AssetKey = __esm({
1422
1317
  if (this.type) {
1423
1318
  p11.push(this.type);
1424
1319
  }
1425
- p11.push(this.path);
1426
- return p11.join("/");
1320
+ p11.push(this.path);
1321
+ return p11.join("/");
1322
+ }
1323
+ toNamespacedString() {
1324
+ return this.namespace + ":" + this.getFullPath();
1325
+ }
1326
+ /** Returns a cache identifier that includes the explicit root or {@link AssetLoader.ROOT}. */
1327
+ serialize() {
1328
+ return this.toString();
1329
+ }
1330
+ };
1331
+ }
1332
+ });
1333
+
1334
+ // src/assets/source/parser/AssetParsers.ts
1335
+ var AssetParser;
1336
+ var init_AssetParsers = __esm({
1337
+ "src/assets/source/parser/AssetParsers.ts"() {
1338
+ AssetParser = /* @__PURE__ */ ((AssetParser2) => {
1339
+ AssetParser2["MODEL"] = "model";
1340
+ AssetParser2["BLOCKSTATE"] = "blockstate";
1341
+ AssetParser2["META"] = "meta";
1342
+ AssetParser2["NBT"] = "nbt";
1343
+ AssetParser2["IMAGE"] = "image";
1344
+ AssetParser2["LIST"] = "list";
1345
+ AssetParser2["JSON"] = "json";
1346
+ return AssetParser2;
1347
+ })(AssetParser || {});
1348
+ }
1349
+ });
1350
+
1351
+ // src/assets/MineRenderData.ts
1352
+ var object, release, compare, MineRenderData;
1353
+ var init_MineRenderData = __esm({
1354
+ "src/assets/MineRenderData.ts"() {
1355
+ init_AssetKey();
1356
+ init_AssetLoader();
1357
+ init_AssetParsers();
1358
+ init_Caching();
1359
+ object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
1360
+ release = (value) => /^\d+\.\d+(?:\.\d+)?$/.test(value) ? value.split(".").map(Number) : void 0;
1361
+ compare = (left, right) => {
1362
+ for (let i = 0; i < 3; i++) {
1363
+ const difference = (left[i] ?? 0) - (right[i] ?? 0);
1364
+ if (difference) return difference;
1365
+ }
1366
+ return 0;
1367
+ };
1368
+ MineRenderData = class {
1369
+ /** Loads one registry. Missing files and invalid data reject the request. */
1370
+ static async get(dataset, root) {
1371
+ root = root?.replace(/\/+$/, "");
1372
+ const scope = AssetLoader.persistentScope;
1373
+ const defaultRoot = AssetLoader.ROOT;
1374
+ const resolved = await this.resolve(root);
1375
+ const entry = resolved.manifest.datasets[dataset];
1376
+ if (!entry) throw new Error(`MineRender ${dataset} data is unavailable for ${resolved.version}: ${resolved.manifest.unavailable[dataset] ?? "not listed in the manifest"}`);
1377
+ const file = entry.file;
1378
+ const requestRoot = resolved.root === (root ?? defaultRoot).replace(/\/+$/, "") ? root : resolved.root;
1379
+ return await this.cached(`${requestRoot === void 0 ? "sources" : "explicit"}:${resolved.root}/${file}:${entry.sha256}`, scope, defaultRoot, async () => {
1380
+ const data = await this.load(file.slice(0, -5), requestRoot);
1381
+ if (!this.validDataset(dataset, data)) throw new Error(`Invalid MineRender ${dataset} data at ${resolved.root}/minerender-data/${file}`);
1382
+ return data;
1383
+ });
1384
+ }
1385
+ /**
1386
+ * Uses the exact version when published. Otherwise, selects the nearest patch in the same
1387
+ * release line (older on ties), then the latest earlier release or the oldest published release.
1388
+ * Unpublished snapshots reject.
1389
+ */
1390
+ static async resolve(root) {
1391
+ const requestRoot = root?.replace(/\/+$/, "");
1392
+ const defaultRoot = AssetLoader.ROOT;
1393
+ root = (root ?? defaultRoot).replace(/\/+$/, "");
1394
+ const scope = AssetLoader.persistentScope;
1395
+ return this.cached(`resolve:${requestRoot === void 0 ? "sources" : "explicit"}:${root}`, scope, defaultRoot, async () => {
1396
+ const requestedVersion = root.slice(root.lastIndexOf("/") + 1);
1397
+ let manifest = await this.manifest(root, requestRoot !== void 0 && release(requestedVersion) ? requestedVersion : void 0, requestRoot);
1398
+ if (manifest) return { requestedVersion, version: manifest.minecraftVersion, root, manifest };
1399
+ const requested = release(requestedVersion);
1400
+ if (!requested) {
1401
+ throw new Error(`No MineRender data is published for ${requestedVersion}; automatic fallback requires a release`);
1402
+ }
1403
+ const base = root.slice(0, root.lastIndexOf("/"));
1404
+ const index = await this.cached(`versions:${base}`, scope, defaultRoot, async () => {
1405
+ const value = await this.load("versions", base);
1406
+ if (!object(value) || !Array.isArray(value.versions) || !value.versions.every((entry) => object(entry) && typeof entry.name === "string")) {
1407
+ throw new Error(`Invalid MineRender version index at ${base}/minerender-data/versions.json`);
1408
+ }
1409
+ return value.versions;
1410
+ });
1411
+ const versions = index.map((entry) => ({ name: entry.name, number: release(entry.name) })).filter((entry) => !!entry.number).sort((left, right) => compare(left.number, right.number));
1412
+ const sameLine = versions.filter((entry) => entry.number[0] === requested[0] && entry.number[1] === requested[1]);
1413
+ 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];
1414
+ if (!selected) throw new Error(`No compatible MineRender data is published for ${requestedVersion}`);
1415
+ const resolvedRoot = `${base}/${selected.name}`;
1416
+ manifest = await this.manifest(resolvedRoot, selected.name, resolvedRoot);
1417
+ if (!manifest) throw new Error(`Missing MineRender manifest for published version ${selected.name}`);
1418
+ return { requestedVersion, version: selected.name, root: resolvedRoot, manifest };
1419
+ });
1420
+ }
1421
+ static async manifest(root, expectedVersion, requestRoot) {
1422
+ const data = await this.load("manifest", requestRoot);
1423
+ if (data === void 0) return void 0;
1424
+ 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) => {
1425
+ 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);
1426
+ }) || expectedVersion !== void 0 && data.minecraftVersion !== expectedVersion) {
1427
+ throw new Error(`Invalid MineRender manifest at ${root}/minerender-data/manifest.json`);
1428
+ }
1429
+ return data;
1430
+ }
1431
+ static load(file, root) {
1432
+ const key = new AssetKey("minerender-data", file, void 0, void 0, void 0, ".json", root);
1433
+ key.rootType = void 0;
1434
+ return AssetLoader.get(key, "json" /* JSON */);
1435
+ }
1436
+ static async cached(key, scope, defaultRoot, loader) {
1437
+ const checkSources = () => {
1438
+ if (AssetLoader.persistentScope !== scope || AssetLoader.ROOT !== defaultRoot) throw new Error("Asset sources changed while loading MineRender data; retry the request");
1439
+ };
1440
+ checkSources();
1441
+ return await Caching.mineRenderDataCache.get(scope ? `${scope}
1442
+ ${key}` : key, async () => {
1443
+ const value = await loader();
1444
+ checkSources();
1445
+ return value;
1446
+ });
1447
+ }
1448
+ static validDataset(dataset, data) {
1449
+ if (!object(data)) return false;
1450
+ 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)));
1451
+ 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")));
1452
+ if (dataset === "legacyBlocks") return object(data.blocks) && Object.values(data.blocks).every((value) => typeof value === "string");
1453
+ 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")));
1454
+ return Object.values(data).every((value) => {
1455
+ if (!object(value)) return false;
1456
+ switch (dataset) {
1457
+ case "itemDefaults":
1458
+ return true;
1459
+ case "blockStates":
1460
+ return Object.values(value).every((property) => object(property) && "default" in property && Array.isArray(property.values));
1461
+ case "blockTints":
1462
+ 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));
1463
+ case "fluids":
1464
+ return ["water", "lava"].includes(value.kind) && typeof value.renderModel === "boolean" && (value.levelProperty === void 0 || typeof value.levelProperty === "string");
1465
+ case "dyes":
1466
+ return [value.id, value.textureDiffuseColor, value.fireworkColor, value.textColor].every(Number.isInteger);
1467
+ default:
1468
+ return false;
1469
+ }
1470
+ });
1471
+ }
1472
+ };
1473
+ }
1474
+ });
1475
+
1476
+ // src/assets/Renames.ts
1477
+ var Renames;
1478
+ var init_Renames = __esm({
1479
+ "src/assets/Renames.ts"() {
1480
+ init_AssetKey();
1481
+ init_MineRenderData();
1482
+ Renames = class {
1483
+ /** Loads the selected version's rename table, or `undefined` when its published data has none. */
1484
+ static async get(root) {
1485
+ const resolved = await MineRenderData.resolve(root);
1486
+ return resolved.manifest.datasets.renames ? MineRenderData.get("renames", root) : void 0;
1487
+ }
1488
+ /**
1489
+ * Returns the ID that replaced `id`, or `undefined` when `id` keeps its meaning.
1490
+ * Without a data version, an ID that the selected version still registers keeps its meaning.
1491
+ */
1492
+ static async resolve(kind, id, dataVersion, root) {
1493
+ const data = await this.get(root);
1494
+ if (!data || !Object.prototype.hasOwnProperty.call(data[kind], id)) return void 0;
1495
+ const entry = data[kind][id];
1496
+ if (dataVersion === void 0) return entry.registered ? void 0 : entry.ranges[entry.ranges.length - 1].to;
1497
+ if (dataVersion < data.baseDataVersion) return void 0;
1498
+ return entry.ranges.find((range) => dataVersion < range.before)?.to;
1499
+ }
1500
+ /** Returns a copy of `key` for the replacement ID, or `undefined` when the ID keeps its meaning. */
1501
+ static async key(kind, key) {
1502
+ const renamed = await this.resolve(kind, `${key.namespace}:${key.path}`, void 0, key.root);
1503
+ if (!renamed) return void 0;
1504
+ const [namespace, path] = renamed.split(":");
1505
+ return new AssetKey(namespace, path, key.assetType, key.type, key.rootType, key.extension, key.root);
1506
+ }
1507
+ };
1508
+ }
1509
+ });
1510
+
1511
+ // src/model/Model.ts
1512
+ function isDoubleArray(obj) {
1513
+ return Array.isArray(obj) && obj.length === 2;
1514
+ }
1515
+ function isTripleArray(obj) {
1516
+ return Array.isArray(obj) && obj.length === 3;
1517
+ }
1518
+ function isQuadArray(obj) {
1519
+ return Array.isArray(obj) && obj.length === 4;
1520
+ }
1521
+ var ITEM_GENERATED, BUILTIN_GENERATED, BUILTIN_ENTITY, DEFAULT_ELEMENTS;
1522
+ var init_Model = __esm({
1523
+ "src/model/Model.ts"() {
1524
+ ITEM_GENERATED = "item/generated";
1525
+ BUILTIN_GENERATED = "builtin/generated";
1526
+ BUILTIN_ENTITY = "builtin/entity";
1527
+ DEFAULT_ELEMENTS = [];
1528
+ }
1529
+ });
1530
+
1531
+ // ../../node_modules/ts-deepmerge/dist/index.js
1532
+ var require_dist = __commonJS({
1533
+ "../../node_modules/ts-deepmerge/dist/index.js"(exports) {
1534
+ "use strict";
1535
+ var __assign = exports && exports.__assign || function() {
1536
+ __assign = Object.assign || function(t) {
1537
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
1538
+ s = arguments[i];
1539
+ for (var p11 in s) if (Object.prototype.hasOwnProperty.call(s, p11))
1540
+ t[p11] = s[p11];
1541
+ }
1542
+ return t;
1543
+ };
1544
+ return __assign.apply(this, arguments);
1545
+ };
1546
+ var __read = exports && exports.__read || function(o, n) {
1547
+ var m = typeof Symbol === "function" && o[Symbol.iterator];
1548
+ if (!m) return o;
1549
+ var i = m.call(o), r, ar = [], e;
1550
+ try {
1551
+ while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
1552
+ } catch (error) {
1553
+ e = { error };
1554
+ } finally {
1555
+ try {
1556
+ if (r && !r.done && (m = i["return"])) m.call(i);
1557
+ } finally {
1558
+ if (e) throw e.error;
1559
+ }
1560
+ }
1561
+ return ar;
1562
+ };
1563
+ var __spreadArray = exports && exports.__spreadArray || function(to, from, pack) {
1564
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
1565
+ if (ar || !(i in from)) {
1566
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
1567
+ ar[i] = from[i];
1568
+ }
1569
+ }
1570
+ return to.concat(ar || Array.prototype.slice.call(from));
1571
+ };
1572
+ Object.defineProperty(exports, "__esModule", { value: true });
1573
+ var isObject = function(obj) {
1574
+ if (typeof obj === "object" && obj !== null) {
1575
+ if (typeof Object.getPrototypeOf === "function") {
1576
+ var prototype = Object.getPrototypeOf(obj);
1577
+ return prototype === Object.prototype || prototype === null;
1578
+ }
1579
+ return Object.prototype.toString.call(obj) === "[object Object]";
1427
1580
  }
1428
- toNamespacedString() {
1429
- return this.namespace + ":" + this.getFullPath();
1581
+ return false;
1582
+ };
1583
+ var PROTECTED_KEYS = ["__proto__"];
1584
+ var merge10 = function() {
1585
+ var objects = [];
1586
+ for (var _i = 0; _i < arguments.length; _i++) {
1587
+ objects[_i] = arguments[_i];
1430
1588
  }
1431
- /** Returns a cache identifier that includes the explicit root or {@link AssetLoader.ROOT}. */
1432
- serialize() {
1433
- return this.toString();
1589
+ return objects.reduce(function(result, current) {
1590
+ Object.keys(current).forEach(function(key) {
1591
+ if (PROTECTED_KEYS.includes(key)) {
1592
+ return;
1593
+ }
1594
+ if (Array.isArray(result[key]) && Array.isArray(current[key])) {
1595
+ result[key] = merge10.options.mergeArrays ? Array.from(new Set(result[key].concat(current[key]))) : current[key];
1596
+ } else if (isObject(result[key]) && isObject(current[key])) {
1597
+ result[key] = merge10(result[key], current[key]);
1598
+ } else {
1599
+ result[key] = current[key];
1600
+ }
1601
+ });
1602
+ return result;
1603
+ }, {});
1604
+ };
1605
+ var defaultOptions = {
1606
+ mergeArrays: true
1607
+ };
1608
+ merge10.options = defaultOptions;
1609
+ merge10.withOptions = function(options) {
1610
+ var objects = [];
1611
+ for (var _i = 1; _i < arguments.length; _i++) {
1612
+ objects[_i - 1] = arguments[_i];
1434
1613
  }
1614
+ merge10.options = __assign({ mergeArrays: true }, options);
1615
+ var result = merge10.apply(void 0, __spreadArray([], __read(objects), false));
1616
+ merge10.options = defaultOptions;
1617
+ return result;
1435
1618
  };
1619
+ exports.default = merge10;
1436
1620
  }
1437
1621
  });
1438
1622
 
@@ -1558,147 +1742,6 @@ var init_ModelMerger = __esm({
1558
1742
  }
1559
1743
  });
1560
1744
 
1561
- // src/assets/source/parser/AssetParsers.ts
1562
- var AssetParser;
1563
- var init_AssetParsers = __esm({
1564
- "src/assets/source/parser/AssetParsers.ts"() {
1565
- AssetParser = /* @__PURE__ */ ((AssetParser2) => {
1566
- AssetParser2["MODEL"] = "model";
1567
- AssetParser2["BLOCKSTATE"] = "blockstate";
1568
- AssetParser2["META"] = "meta";
1569
- AssetParser2["NBT"] = "nbt";
1570
- AssetParser2["IMAGE"] = "image";
1571
- AssetParser2["LIST"] = "list";
1572
- AssetParser2["JSON"] = "json";
1573
- return AssetParser2;
1574
- })(AssetParser || {});
1575
- }
1576
- });
1577
-
1578
- // src/assets/MineRenderData.ts
1579
- var object, release, compare, MineRenderData;
1580
- var init_MineRenderData = __esm({
1581
- "src/assets/MineRenderData.ts"() {
1582
- init_AssetKey();
1583
- init_AssetLoader();
1584
- init_AssetParsers();
1585
- init_Caching();
1586
- object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
1587
- release = (value) => /^\d+\.\d+(?:\.\d+)?$/.test(value) ? value.split(".").map(Number) : void 0;
1588
- compare = (left, right) => {
1589
- for (let i = 0; i < 3; i++) {
1590
- const difference = (left[i] ?? 0) - (right[i] ?? 0);
1591
- if (difference) return difference;
1592
- }
1593
- return 0;
1594
- };
1595
- MineRenderData = class {
1596
- /** Loads one registry. Missing files and invalid data reject the request. */
1597
- static async get(dataset, root) {
1598
- root = root?.replace(/\/+$/, "");
1599
- const scope = AssetLoader.persistentScope;
1600
- const defaultRoot = AssetLoader.ROOT;
1601
- const resolved = await this.resolve(root);
1602
- const entry = resolved.manifest.datasets[dataset];
1603
- if (!entry) throw new Error(`MineRender ${dataset} data is unavailable for ${resolved.version}: ${resolved.manifest.unavailable[dataset] ?? "not listed in the manifest"}`);
1604
- const file = entry.file;
1605
- const requestRoot = resolved.root === (root ?? defaultRoot).replace(/\/+$/, "") ? root : resolved.root;
1606
- return await this.cached(`${requestRoot === void 0 ? "sources" : "explicit"}:${resolved.root}/${file}:${entry.sha256}`, scope, defaultRoot, async () => {
1607
- const data = await this.load(file.slice(0, -5), requestRoot);
1608
- if (!this.validDataset(dataset, data)) throw new Error(`Invalid MineRender ${dataset} data at ${resolved.root}/minerender-data/${file}`);
1609
- return data;
1610
- });
1611
- }
1612
- /**
1613
- * Uses the exact version when published. Otherwise, selects the nearest patch in the same
1614
- * release line (older on ties), then the latest earlier release or the oldest published release.
1615
- * Unpublished snapshots reject.
1616
- */
1617
- static async resolve(root) {
1618
- const requestRoot = root?.replace(/\/+$/, "");
1619
- const defaultRoot = AssetLoader.ROOT;
1620
- root = (root ?? defaultRoot).replace(/\/+$/, "");
1621
- const scope = AssetLoader.persistentScope;
1622
- return this.cached(`resolve:${requestRoot === void 0 ? "sources" : "explicit"}:${root}`, scope, defaultRoot, async () => {
1623
- const requestedVersion = root.slice(root.lastIndexOf("/") + 1);
1624
- let manifest = await this.manifest(root, requestRoot !== void 0 && release(requestedVersion) ? requestedVersion : void 0, requestRoot);
1625
- if (manifest) return { requestedVersion, version: manifest.minecraftVersion, root, manifest };
1626
- const requested = release(requestedVersion);
1627
- if (!requested) {
1628
- throw new Error(`No MineRender data is published for ${requestedVersion}; automatic fallback requires a release`);
1629
- }
1630
- const base = root.slice(0, root.lastIndexOf("/"));
1631
- const index = await this.cached(`versions:${base}`, scope, defaultRoot, async () => {
1632
- const value = await this.load("versions", base);
1633
- if (!object(value) || !Array.isArray(value.versions) || !value.versions.every((entry) => object(entry) && typeof entry.name === "string")) {
1634
- throw new Error(`Invalid MineRender version index at ${base}/minerender-data/versions.json`);
1635
- }
1636
- return value.versions;
1637
- });
1638
- const versions = index.map((entry) => ({ name: entry.name, number: release(entry.name) })).filter((entry) => !!entry.number).sort((left, right) => compare(left.number, right.number));
1639
- const sameLine = versions.filter((entry) => entry.number[0] === requested[0] && entry.number[1] === requested[1]);
1640
- 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];
1641
- if (!selected) throw new Error(`No compatible MineRender data is published for ${requestedVersion}`);
1642
- const resolvedRoot = `${base}/${selected.name}`;
1643
- manifest = await this.manifest(resolvedRoot, selected.name, resolvedRoot);
1644
- if (!manifest) throw new Error(`Missing MineRender manifest for published version ${selected.name}`);
1645
- return { requestedVersion, version: selected.name, root: resolvedRoot, manifest };
1646
- });
1647
- }
1648
- static async manifest(root, expectedVersion, requestRoot) {
1649
- const data = await this.load("manifest", requestRoot);
1650
- if (data === void 0) return void 0;
1651
- 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) => {
1652
- 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);
1653
- }) || expectedVersion !== void 0 && data.minecraftVersion !== expectedVersion) {
1654
- throw new Error(`Invalid MineRender manifest at ${root}/minerender-data/manifest.json`);
1655
- }
1656
- return data;
1657
- }
1658
- static load(file, root) {
1659
- const key = new AssetKey("minerender-data", file, void 0, void 0, void 0, ".json", root);
1660
- key.rootType = void 0;
1661
- return AssetLoader.get(key, "json" /* JSON */);
1662
- }
1663
- static async cached(key, scope, defaultRoot, loader) {
1664
- const checkSources = () => {
1665
- if (AssetLoader.persistentScope !== scope || AssetLoader.ROOT !== defaultRoot) throw new Error("Asset sources changed while loading MineRender data; retry the request");
1666
- };
1667
- checkSources();
1668
- return await Caching.mineRenderDataCache.get(scope ? `${scope}
1669
- ${key}` : key, async () => {
1670
- const value = await loader();
1671
- checkSources();
1672
- return value;
1673
- });
1674
- }
1675
- static validDataset(dataset, data) {
1676
- if (!object(data)) return false;
1677
- 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)));
1678
- if (dataset === "legacyBlocks") return object(data.blocks) && Object.values(data.blocks).every((value) => typeof value === "string");
1679
- 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")));
1680
- return Object.values(data).every((value) => {
1681
- if (!object(value)) return false;
1682
- switch (dataset) {
1683
- case "itemDefaults":
1684
- return true;
1685
- case "blockStates":
1686
- return Object.values(value).every((property) => object(property) && "default" in property && Array.isArray(property.values));
1687
- case "blockTints":
1688
- 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));
1689
- case "fluids":
1690
- return ["water", "lava"].includes(value.kind) && typeof value.renderModel === "boolean" && (value.levelProperty === void 0 || typeof value.levelProperty === "string");
1691
- case "dyes":
1692
- return [value.id, value.textureDiffuseColor, value.fireworkColor, value.textColor].every(Number.isInteger);
1693
- default:
1694
- return false;
1695
- }
1696
- });
1697
- }
1698
- };
1699
- }
1700
- });
1701
-
1702
1745
  // src/assets/BannerPatterns.ts
1703
1746
  var DYE_COLORS, BannerPatterns;
1704
1747
  var init_BannerPatterns = __esm({
@@ -2021,6 +2064,7 @@ var init_ItemTints = __esm({
2021
2064
  var Models;
2022
2065
  var init_Models = __esm({
2023
2066
  "src/assets/Models.ts"() {
2067
+ init_Renames();
2024
2068
  init_Caching();
2025
2069
  init_ModelMerger();
2026
2070
  init_AssetLoader();
@@ -2350,6 +2394,11 @@ var init_Models = __esm({
2350
2394
  key.extension = ".json";
2351
2395
  }
2352
2396
  const preview = this.snapshotContext(key, context);
2397
+ if (key.type === "item") {
2398
+ const renamed = await Renames.key("items", key);
2399
+ const replacement = renamed && await this.getMerged(renamed, preview);
2400
+ if (replacement) return replacement;
2401
+ }
2353
2402
  const dataKey = await this.applyDefaults(key, preview);
2354
2403
  const keyStr = AssetLoader.persistentKey(key.serialize() + (key.type === "item" ? this.contextKey(preview) + dataKey : ""));
2355
2404
  const model = await Caching.mergedModelCache.get(keyStr, async () => {
@@ -2383,6 +2432,7 @@ var init_BlockStates = __esm({
2383
2432
  init_AssetKey();
2384
2433
  init_PersistentCache();
2385
2434
  init_AssetParsers();
2435
+ init_Renames();
2386
2436
  init_MineRenderData();
2387
2437
  BlockStates = class {
2388
2438
  static get PERSISTENT_CACHE() {
@@ -2423,6 +2473,9 @@ var init_BlockStates = __esm({
2423
2473
  if (!key.extension) {
2424
2474
  key.extension = ".json";
2425
2475
  }
2476
+ const renamed = await Renames.key("blocks", key);
2477
+ const replacement = renamed && await this.get(renamed);
2478
+ if (replacement) return replacement;
2426
2479
  const keyStr = key.serialize();
2427
2480
  return Caching.blockStateCache.get(keyStr, (k) => {
2428
2481
  return this.PERSISTENT_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
@@ -7984,6 +8037,7 @@ var Fonts = class {
7984
8037
  init_MineRenderData();
7985
8038
  init_ModelTextures();
7986
8039
  init_Models();
8040
+ init_Renames();
7987
8041
  init_source();
7988
8042
 
7989
8043
  // src/cache/index.ts
@@ -17298,18 +17352,23 @@ var SectionModels = class {
17298
17352
  return this.template(variant, choice, tints);
17299
17353
  }));
17300
17354
  }
17301
- /** Resolves section templates for one tint palette, with a synchronous weighted choice for each cell. */
17302
- async prepareState(blockState3, properties = {}, biomeColor) {
17355
+ /** Resolves shared templates; dynamic biome tints are supplied on section placements instead of cached geometry. */
17356
+ async prepareState(blockState3, properties = {}, biomeColor, dynamicBiomeTint = false) {
17303
17357
  const prepared = await this.state(blockState3, properties);
17304
17358
  if (!prepared) return void 0;
17305
17359
  let color2;
17306
17360
  const resolveColor = typeof biomeColor === "function" ? () => color2 ?? (color2 = biomeColor()) : biomeColor;
17307
17361
  const templates = /* @__PURE__ */ new Map();
17362
+ const source = dynamicBiomeTint && await BlockTints.biomeSource(blockState3.key);
17308
17363
  await Promise.all([...prepared.choices].map(async ([variant, choice]) => {
17309
17364
  const tints = await BlockTints.get(blockState3.key, prepared.state, choice.model, void 0, resolveColor);
17310
- templates.set(variant, await this.template(variant, choice, tints));
17365
+ const biomeTints = source ? await BlockTints.get(blockState3.key, prepared.state, choice.model, void 0, 16777215) : void 0;
17366
+ templates.set(variant, await this.template(variant, choice, tints, biomeTints));
17311
17367
  }));
17312
- return { pick: (position) => prepared.groups.map((group) => templates.get(BlockStateResolver.choose(group, position))) };
17368
+ return {
17369
+ pick: (position) => prepared.groups.map((group) => templates.get(BlockStateResolver.choose(group, position))),
17370
+ biomeTint: source ? [...templates.values()].some((template) => template.biomeTint?.some((value) => value === 1)) || void 0 : void 0
17371
+ };
17313
17372
  }
17314
17373
  async state(blockState3, properties) {
17315
17374
  let states = this.states.get(blockState3);
@@ -17352,10 +17411,10 @@ var SectionModels = class {
17352
17411
  }
17353
17412
  return { state, groups, choices: prepared };
17354
17413
  }
17355
- async template(variant, { model, atlas, rotation }, tints) {
17414
+ async template(variant, { model, atlas, rotation }, tints, biomeTints) {
17356
17415
  let models = this.models.get(model);
17357
17416
  if (!models) this.models.set(model, models = /* @__PURE__ */ new Map());
17358
- const key = JSON.stringify([rotation.x, rotation.y, rotation.z, variant.uvlock, tints]);
17417
+ const key = JSON.stringify([rotation.x, rotation.y, rotation.z, variant.uvlock, tints, biomeTints]);
17359
17418
  return cached(models, key, async () => {
17360
17419
  const object4 = new SectionModel(model, {
17361
17420
  instanceMeshes: false,
@@ -17369,11 +17428,13 @@ var SectionModels = class {
17369
17428
  object4.clear();
17370
17429
  const keep = [];
17371
17430
  const cullFaces = [];
17431
+ const biomeTint = biomeTints ? [] : void 0;
17372
17432
  for (const element of atlas.model.elements) {
17373
17433
  for (const face of CUBE_FACES) {
17374
17434
  const data = element.faces[face];
17375
17435
  keep.push(data !== void 0);
17376
17436
  if (!data) continue;
17437
+ biomeTint?.push(Number(data.tintindex !== void 0 && biomeTints?.[data.tintindex] !== void 0));
17377
17438
  const local = CUBE_FACES.findIndex((name) => name === data.cullface);
17378
17439
  if (local < 0) {
17379
17440
  cullFaces.push(0);
@@ -17389,6 +17450,7 @@ var SectionModels = class {
17389
17450
  geometry,
17390
17451
  atlas,
17391
17452
  cullFaces: new Uint8Array(cullFaces),
17453
+ biomeTint: biomeTint?.some(Boolean) ? new Uint8Array(biomeTint) : void 0,
17392
17454
  occludes: ModelCulling.isOpaqueFullCube(atlas) && ModelCulling.toLocalMask(63, rotation) === 63,
17393
17455
  layer: atlas.hasTranslucency ? 1 : 0,
17394
17456
  ambientOcclusion: model.ambientocclusion !== false
@@ -17408,6 +17470,7 @@ var SectionModels = class {
17408
17470
  };
17409
17471
 
17410
17472
  // src/world/_entities/WorldEntities.ts
17473
+ init_Renames();
17411
17474
  init_Entities();
17412
17475
  init_EntityObject();
17413
17476
 
@@ -17567,8 +17630,10 @@ var WorldEntities = class {
17567
17630
  try {
17568
17631
  if (!current()) return;
17569
17632
  const models = await Entities.getEntityList();
17570
- if (!models.includes(placement.key.path) || !current()) return;
17571
- const model = await Entities.getEntity(placement.key, placement.texture, { when: placement.when });
17633
+ const renamed = models.includes(placement.key.path) ? void 0 : await Renames.key("entities", placement.key);
17634
+ const key = renamed && models.includes(renamed.path) ? renamed : placement.key;
17635
+ if (!models.includes(key.path) || !current()) return;
17636
+ const model = await Entities.getEntity(key, placement.texture, { when: placement.when });
17572
17637
  if (!model || !current()) return;
17573
17638
  let tints = placement.tints;
17574
17639
  if (placement.woolDye) {
@@ -17583,7 +17648,7 @@ var WorldEntities = class {
17583
17648
  object4.rotation.y = placement.yaw;
17584
17649
  await object4.init();
17585
17650
  if (!current()) return;
17586
- placement.owner.set(object4, { type: placement.key.toNamespacedString(), position: placement.position });
17651
+ placement.owner.set(object4, { type: key.toNamespacedString(), position: placement.position });
17587
17652
  this.scene.add(object4);
17588
17653
  } catch (error) {
17589
17654
  console.warn(`Could not draw saved entity ${placement.key.toNamespacedString()}, skipping it`, error);
@@ -17615,6 +17680,132 @@ var WorldEntities = class {
17615
17680
  // src/world/MineRenderWorld.ts
17616
17681
  init_FluidQuads();
17617
17682
 
17683
+ // src/world/_biomes/BiomeBlend.ts
17684
+ init_AssetKey();
17685
+ init_AssetLoader();
17686
+ init_Caching();
17687
+ var MAX_TILES = 256;
17688
+ function validateBiomeBlendRadius(radius) {
17689
+ if (!Number.isInteger(radius) || radius < 0 || radius > 7) {
17690
+ throw new RangeError("biomeBlendRadius must be an integer from 0 to 7");
17691
+ }
17692
+ }
17693
+ var BiomeBlend = class {
17694
+ constructor(biomeAt, radius) {
17695
+ this.biomeAt = biomeAt;
17696
+ this.tiles = /* @__PURE__ */ new Map();
17697
+ this.generation = Caching.generation;
17698
+ validateBiomeBlendRadius(radius);
17699
+ this._radius = radius;
17700
+ }
17701
+ get radius() {
17702
+ return this._radius;
17703
+ }
17704
+ clear() {
17705
+ this.tiles.clear();
17706
+ }
17707
+ invalidateColumn(x, z) {
17708
+ for (const [key, tile] of this.tiles) {
17709
+ if (Math.abs(tile.x - x) <= 1 && Math.abs(tile.z - z) <= 1) this.tiles.delete(key);
17710
+ }
17711
+ }
17712
+ async getColor(kind, origin, position) {
17713
+ const [x, y, z] = position;
17714
+ const biome = this.biomeAt(x, y, z);
17715
+ if (biome === void 0) return void 0;
17716
+ if (this.generation !== Caching.generation) {
17717
+ this.clear();
17718
+ this.generation = Caching.generation;
17719
+ }
17720
+ const tileX = Math.floor(x / 16), tileZ = Math.floor(z / 16), quartY = Math.floor(y / 4);
17721
+ const root = origin?.root ?? AssetLoader.ROOT;
17722
+ const key = JSON.stringify([root, AssetLoader.persistentScope, kind, this._radius, tileX, quartY, tileZ]);
17723
+ let tile = this.tiles.get(key);
17724
+ if (tile) {
17725
+ this.tiles.delete(key);
17726
+ this.tiles.set(key, tile);
17727
+ } else {
17728
+ const snapshot = new AssetKey("minecraft", "", void 0, void 0, "assets", ".json", root);
17729
+ const colors = this.buildTile(kind, snapshot, tileX, quartY, tileZ, this._radius).then((result2) => {
17730
+ if (!result2.complete && this.tiles.get(key)?.colors === colors) this.tiles.delete(key);
17731
+ return result2;
17732
+ }, (error) => {
17733
+ if (this.tiles.get(key)?.colors === colors) this.tiles.delete(key);
17734
+ throw error;
17735
+ });
17736
+ tile = { x: tileX, z: tileZ, colors };
17737
+ this.tiles.set(key, tile);
17738
+ if (this.tiles.size > MAX_TILES) this.tiles.delete(this.tiles.keys().next().value);
17739
+ }
17740
+ const result = await tile.colors;
17741
+ if (result.failures.has(biome)) throw result.failures.get(biome);
17742
+ const color2 = result.colors[(z - tileZ * 16) * 16 + x - tileX * 16];
17743
+ return color2 === -1 ? void 0 : color2;
17744
+ }
17745
+ async buildTile(kind, origin, tileX, quartY, tileZ, radius) {
17746
+ const size = 16 + radius * 2, stride = size + 1;
17747
+ const startX = tileX * 16 - radius, startZ = tileZ * 16 - radius;
17748
+ const quartX = Math.floor(startX / 4), quartZ = Math.floor(startZ / 4);
17749
+ const quartSize = Math.floor((startX + size - 1) / 4) - quartX + 1;
17750
+ const ids = new Array(quartSize * quartSize);
17751
+ const biomes = /* @__PURE__ */ new Map();
17752
+ for (let z = 0; z < quartSize; z++) {
17753
+ for (let x = 0; x < quartSize; x++) {
17754
+ const id = this.biomeAt((quartX + x) * 4, quartY * 4, (quartZ + z) * 4);
17755
+ ids[z * quartSize + x] = id;
17756
+ if (id !== void 0 && !biomes.has(id)) {
17757
+ biomes.set(id, (async () => {
17758
+ const swamp = kind === "grass" && (await Biomes.get(id, origin))?.effects.grass_color_modifier === "swamp";
17759
+ return { color: swamp ? void 0 : await Biomes.getColor(id, kind, origin), swamp };
17760
+ })());
17761
+ }
17762
+ }
17763
+ }
17764
+ const failures = /* @__PURE__ */ new Map();
17765
+ const loaded = new Map(await Promise.all([...biomes].map(async ([id, color2]) => {
17766
+ try {
17767
+ return [id, await color2];
17768
+ } catch (error) {
17769
+ failures.set(id, error);
17770
+ return [id, { color: void 0, swamp: false }];
17771
+ }
17772
+ })));
17773
+ const complete = [...loaded.values()].every((value) => value.swamp || value.color !== void 0);
17774
+ const samples = new Int32Array(size * size).fill(-1);
17775
+ const sums = Array.from({ length: 4 }, () => new Uint32Array(stride * stride));
17776
+ for (let z = 0; z < size; z++) {
17777
+ const row = [0, 0, 0, 0];
17778
+ for (let x = 0; x < size; x++) {
17779
+ const id = ids[(Math.floor((startZ + z) / 4) - quartZ) * quartSize + Math.floor((startX + x) / 4) - quartX];
17780
+ const value = id === void 0 ? void 0 : loaded.get(id);
17781
+ const color2 = value?.swamp ? swampGrassColor(startX + x, startZ + z) : value?.color;
17782
+ if (color2 !== void 0) {
17783
+ samples[z * size + x] = color2;
17784
+ row[0] += color2 >> 16 & 255;
17785
+ row[1] += color2 >> 8 & 255;
17786
+ row[2] += color2 & 255;
17787
+ row[3]++;
17788
+ }
17789
+ for (let channel = 0; channel < 4; channel++) {
17790
+ sums[channel][(z + 1) * stride + x + 1] = sums[channel][z * stride + x + 1] + row[channel];
17791
+ }
17792
+ }
17793
+ }
17794
+ const colors = new Int32Array(256).fill(-1), width = radius * 2 + 1;
17795
+ for (let z = 0; z < 16; z++) {
17796
+ for (let x = 0; x < 16; x++) {
17797
+ if (samples[(z + radius) * size + x + radius] === -1) continue;
17798
+ const topLeft = z * stride + x, topRight = topLeft + width;
17799
+ const bottomLeft = (z + width) * stride + x, bottomRight = bottomLeft + width;
17800
+ const sum = (values) => values[bottomRight] - values[bottomLeft] - values[topRight] + values[topLeft];
17801
+ const count = sum(sums[3]);
17802
+ 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);
17803
+ }
17804
+ }
17805
+ return { colors, complete, failures };
17806
+ }
17807
+ };
17808
+
17618
17809
  // src/world/WorldLight.ts
17619
17810
  function copySectionLight(light) {
17620
17811
  const copy = {};
@@ -17678,12 +17869,17 @@ var MineRenderWorld = class {
17678
17869
  this.lightValidity = /* @__PURE__ */ new Map();
17679
17870
  this.highestSkySection = /* @__PURE__ */ new Map();
17680
17871
  this.ambientOcclusionPending = false;
17872
+ this.pendingBiomes = /* @__PURE__ */ new Map();
17681
17873
  this.pendingCulling = /* @__PURE__ */ new Map();
17682
17874
  if (options.savedLighting && !options.sectionMeshing) throw new TypeError("savedLighting requires sectionMeshing");
17683
17875
  if (options.ambientOcclusion && !options.sectionMeshing) throw new TypeError("ambientOcclusion requires sectionMeshing");
17684
17876
  if (options.ambientLight !== void 0 && (!Number.isFinite(options.ambientLight) || options.ambientLight < 0 || options.ambientLight > 1)) {
17685
17877
  throw new RangeError("ambientLight must be a number from 0 to 1");
17686
17878
  }
17879
+ this._biomeBlendRadius = options.biomeBlendRadius ?? 0;
17880
+ validateBiomeBlendRadius(this._biomeBlendRadius);
17881
+ if (this._biomeBlendRadius > 0 && !options.sectionMeshing) throw new TypeError("biomeBlendRadius requires sectionMeshing");
17882
+ if (this._biomeBlendRadius) this.biomeBlend = new BiomeBlend((x, y, z) => this.getBiomeAt(x, y, z), this._biomeBlendRadius);
17687
17883
  this.scene = scene;
17688
17884
  this.savedLighting = options.savedLighting ?? false;
17689
17885
  this.ambientLight = options.ambientLight ?? 0;
@@ -17724,6 +17920,65 @@ var MineRenderWorld = class {
17724
17920
  await this.updateCulling([]);
17725
17921
  this.ambientOcclusionPending = false;
17726
17922
  }
17923
+ /** Horizontal biome-color sampling radius, from 0 to 7 blocks. Zero disables blending. */
17924
+ get biomeBlendRadius() {
17925
+ return this._biomeBlendRadius;
17926
+ }
17927
+ /** Refreshes loaded grass, foliage, and water colors without reloading their source chunks. */
17928
+ async setBiomeBlendRadius(radius) {
17929
+ validateBiomeBlendRadius(radius);
17930
+ if (radius > 0 && !this.sectionModels) throw new TypeError("biomeBlendRadius requires sectionMeshing");
17931
+ if (radius !== this._biomeBlendRadius) {
17932
+ this._biomeBlendRadius = radius;
17933
+ this.biomeBlend?.clear();
17934
+ this.biomeBlend = radius ? new BiomeBlend((x, y, z) => this.getBiomeAt(x, y, z), radius) : void 0;
17935
+ for (const chunk of this._chunks.values()) this.pendingBiomes.set(chunk, void 0);
17936
+ }
17937
+ const failures = await this.refreshBiomes();
17938
+ if (failures.size) throw failures.values().next().value;
17939
+ }
17940
+ markBiomesChanged(x, z, y, includeColumn = true) {
17941
+ const queued = [];
17942
+ if (!this.biomeBlend) return queued;
17943
+ this.biomeBlend.invalidateColumn(x, z);
17944
+ for (const chunk of this._chunks.values()) {
17945
+ 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;
17946
+ queued.push(chunk);
17947
+ if (!this.pendingBiomes.has(chunk)) this.pendingBiomes.set(chunk, []);
17948
+ const columns = this.pendingBiomes.get(chunk);
17949
+ if (columns && !columns.some((column) => column.x === x && column.z === z)) columns.push({ x, z });
17950
+ }
17951
+ return queued;
17952
+ }
17953
+ async refreshBiomes() {
17954
+ if (this.biomeRefreshing) await this.biomeRefreshing;
17955
+ if (!this.pendingBiomes.size) return /* @__PURE__ */ new Map();
17956
+ return this.biomeRefreshing ?? (this.biomeRefreshing = Promise.resolve().then(async () => {
17957
+ const failures = /* @__PURE__ */ new Map();
17958
+ const batch = [...this.pendingBiomes.keys()];
17959
+ let sliceStart = performance.now();
17960
+ try {
17961
+ for (const chunk of batch) {
17962
+ const columns = this.pendingBiomes.get(chunk);
17963
+ this.pendingBiomes.delete(chunk);
17964
+ if (this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`) !== chunk) continue;
17965
+ try {
17966
+ await chunk.refreshBiomes(columns);
17967
+ } catch (error) {
17968
+ failures.set(chunk, error);
17969
+ if (this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`) === chunk) this.pendingBiomes.set(chunk, void 0);
17970
+ }
17971
+ if (performance.now() - sliceStart > 8) {
17972
+ await yieldToEventLoop();
17973
+ sliceStart = performance.now();
17974
+ }
17975
+ }
17976
+ return failures;
17977
+ } finally {
17978
+ this.biomeRefreshing = void 0;
17979
+ }
17980
+ }));
17981
+ }
17727
17982
  getBlockAt(posOrX, y, z) {
17728
17983
  if (typeof posOrX == "number") {
17729
17984
  return this.getBlockAt(new Vector319(posOrX, y, z));
@@ -17959,6 +18214,9 @@ var MineRenderWorld = class {
17959
18214
  }
17960
18215
  await placeEntities?.();
17961
18216
  } finally {
18217
+ this.markBiomesChanged(chunk.x, chunk.z, void 0, false);
18218
+ const failures = await this.refreshBiomes();
18219
+ if (failures.size) console.warn("Failed to refresh biome colors", failures.values().next().value);
17962
18220
  await this.updateCulling([...changes.values()]);
17963
18221
  }
17964
18222
  }
@@ -17975,11 +18233,14 @@ var MineRenderWorld = class {
17975
18233
  try {
17976
18234
  await this.clearChunkColumn(x, z);
17977
18235
  } finally {
18236
+ const failures = await this.refreshBiomes();
18237
+ if (failures.size) console.warn("Failed to refresh biome colors", failures.values().next().value);
17978
18238
  await this.updateCulling([]);
17979
18239
  }
17980
18240
  }
17981
18241
  async clearChunkColumn(x, z) {
17982
18242
  this.biomes.delete(`${x}_${z}`);
18243
+ this.markBiomesChanged(x, z, void 0, false);
17983
18244
  if (this.savedLighting) {
17984
18245
  for (const y of this.light.get(`${x}_${z}`)?.keys() ?? []) this.markSectionChanged({ x, y, z });
17985
18246
  }
@@ -17990,6 +18251,7 @@ var MineRenderWorld = class {
17990
18251
  for (const [key, section] of previous) {
17991
18252
  this._chunks.delete(key);
17992
18253
  this.pendingCulling.delete(section);
18254
+ this.pendingBiomes.delete(section);
17993
18255
  await section.clear(false);
17994
18256
  this.markSectionChanged(section);
17995
18257
  }
@@ -18024,6 +18286,8 @@ var MineRenderWorld = class {
18024
18286
  const chunks = [...this._chunks.values()];
18025
18287
  this._chunks.clear();
18026
18288
  this.biomes.clear();
18289
+ this.biomeBlend?.clear();
18290
+ this.pendingBiomes.clear();
18027
18291
  this.light.clear();
18028
18292
  this.lightValidity.clear();
18029
18293
  this.highestSkySection.clear();
@@ -18142,8 +18406,14 @@ var MineRenderWorld = class {
18142
18406
  column?.delete(y);
18143
18407
  if (!column?.size) this.biomes.delete(columnKey);
18144
18408
  }
18145
- await chunk.refreshBiomes();
18146
- await this._chunks.get(`${x}_${y + 1}_${z}`)?.refreshBiomes();
18409
+ if (this.biomeBlend) {
18410
+ const queued = this.markBiomesChanged(x, z, y);
18411
+ const failures = await this.refreshBiomes();
18412
+ for (const chunk2 of queued) if (failures.has(chunk2)) throw failures.get(chunk2);
18413
+ } else {
18414
+ await chunk.refreshBiomes();
18415
+ await this._chunks.get(`${x}_${y + 1}_${z}`)?.refreshBiomes();
18416
+ }
18147
18417
  },
18148
18418
  this.savedLighting ? (x2, y2, z2) => this.lightLevelAt(x2, y2, z2) : void 0,
18149
18419
  async () => {
@@ -18160,7 +18430,8 @@ var MineRenderWorld = class {
18160
18430
  await this.updateCulling([]);
18161
18431
  }
18162
18432
  },
18163
- this.ambientLight
18433
+ this.ambientLight,
18434
+ () => this.biomeBlend
18164
18435
  );
18165
18436
  this._chunks.set(key, chunk);
18166
18437
  }
@@ -18279,9 +18550,10 @@ var LIGHT_SAMPLE_OFFSETS = [0, 1, -1, 324, -324, 18, -18];
18279
18550
 
18280
18551
  // src/world/SectionGeometry.ts
18281
18552
  function buildSectionGeometry(input) {
18282
- const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize, fluids, light, ambientLight, ambientOcclusion } = input;
18553
+ const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize, fluids, light, ambientLight, ambientOcclusion, biomeTints } = input;
18283
18554
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
18284
18555
  if (count > indices.length || count > templates.length || count > cullMasks.length) throw new RangeError("Section entry count exceeds its arrays");
18556
+ if (biomeTints && biomeTints.length !== count * 3) throw new RangeError("Section biome tints must contain RGB for each placement");
18285
18557
  if (fluids && fluids.cells.length !== 5832) throw new RangeError("Section fluid cells must cover an 18\xD718\xD718 neighborhood");
18286
18558
  if (fluids?.waterTints && fluids.waterTints.length !== 4096 * 3) throw new RangeError("Section water tints must contain RGB for 4096 cells");
18287
18559
  if (light && (light.length !== 5832 || light.some((level) => level > 15 && level !== 255))) {
@@ -18296,7 +18568,7 @@ function buildSectionGeometry(input) {
18296
18568
  const cornerBrightness2 = ambientOcclusion ? new Float32Array(4) : void 0;
18297
18569
  for (const template of templateData) {
18298
18570
  const { quads } = template;
18299
- 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) {
18571
+ 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) {
18300
18572
  throw new RangeError("Section template arrays do not match its quad count");
18301
18573
  }
18302
18574
  }
@@ -18406,6 +18678,7 @@ function buildSectionGeometry(input) {
18406
18678
  blockIndices[indexOffset / 6] = indices[entry];
18407
18679
  if (ambientMask) quadAmbientOcclusion(template.positions, template.normals, quad, ambientMask, cornerBrightness2);
18408
18680
  const brightness = light ? lightBrightness(light[lightIndex + LIGHT_SAMPLE_OFFSETS[quadLightIndex(template.positions, template.normals, quad)]], ambientLight) : 1;
18681
+ const tint = biomeTints && template.biomeTint?.[quad] && !Number.isNaN(biomeTints[entry * 3]) ? entry * 3 : void 0;
18409
18682
  const start = vertex;
18410
18683
  for (let corner = 0; corner < 4; corner++, vertex++) {
18411
18684
  const source = quad * 4 + corner;
@@ -18422,9 +18695,9 @@ function buildSectionGeometry(input) {
18422
18695
  uvBounds[vertex * 4 + 2] = mapU(template.uvBounds[source * 4 + 2]);
18423
18696
  uvBounds[vertex * 4 + 3] = mapV(template.uvBounds[source * 4 + 3]);
18424
18697
  const factor = brightness * (ambientMask ? cornerBrightness2[corner] : 1);
18425
- colors[vertex * 3] = template.colors[source * 3] * factor;
18426
- colors[vertex * 3 + 1] = template.colors[source * 3 + 1] * factor;
18427
- colors[vertex * 3 + 2] = template.colors[source * 3 + 2] * factor;
18698
+ colors[vertex * 3] = (tint === void 0 ? template.colors[source * 3] : biomeTints[tint]) * factor;
18699
+ colors[vertex * 3 + 1] = (tint === void 0 ? template.colors[source * 3 + 1] : biomeTints[tint + 1]) * factor;
18700
+ colors[vertex * 3 + 2] = (tint === void 0 ? template.colors[source * 3 + 2] : biomeTints[tint + 2]) * factor;
18428
18701
  }
18429
18702
  for (let corner = 0; corner < 6; corner++) outputIndices[indexOffset++] = start + template.indices[quad * 6 + corner];
18430
18703
  }
@@ -18545,7 +18818,11 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
18545
18818
  ...lighting
18546
18819
  };
18547
18820
  for (let i = 0; i < entries.length; i++) {
18548
- const { index, template, cullMask } = entries[i];
18821
+ const { index, template, cullMask, biomeTint } = entries[i];
18822
+ if (biomeTint) {
18823
+ input.biomeTints ?? (input.biomeTints = new Float32Array(entries.length * 3).fill(NaN));
18824
+ input.biomeTints.set(biomeTint, i * 3);
18825
+ }
18549
18826
  let id = templateIds.get(template);
18550
18827
  if (id === void 0) {
18551
18828
  let atlas = atlasIds.get(template.atlas);
@@ -18572,6 +18849,7 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
18572
18849
  colors: new Float32Array(quads * 12),
18573
18850
  indices: new Uint16Array(quads * 6),
18574
18851
  cullFaces: new Uint8Array(template.cullFaces),
18852
+ biomeTint: template.biomeTint ? new Uint8Array(template.biomeTint) : void 0,
18575
18853
  atlas: 0,
18576
18854
  layer: template.layer,
18577
18855
  ambientOcclusion: template.ambientOcclusion
@@ -18716,7 +18994,7 @@ init_CubeFace();
18716
18994
  init_InstanceReference();
18717
18995
  var WATER_TINT2 = new Color13(4159204).toArray();
18718
18996
  var Chunk = class _Chunk {
18719
- constructor(scene, x, y, z, onBlocksChanged, sectionModels, neighborCell, biomeAt, onBiomesChanged, lightAt, onLightCleared, ambientLight = 0) {
18997
+ constructor(scene, x, y, z, onBlocksChanged, sectionModels, neighborCell, biomeAt, onBiomesChanged, lightAt, onLightCleared, ambientLight = 0, biomeBlend) {
18720
18998
  this.onBlocksChanged = onBlocksChanged;
18721
18999
  this.sectionModels = sectionModels;
18722
19000
  this.neighborCell = neighborCell;
@@ -18725,6 +19003,7 @@ var Chunk = class _Chunk {
18725
19003
  this.lightAt = lightAt;
18726
19004
  this.onLightCleared = onLightCleared;
18727
19005
  this.ambientLight = ambientLight;
19006
+ this.biomeBlend = biomeBlend;
18728
19007
  this.data = new ChunkData();
18729
19008
  this.renderedBlocks = /* @__PURE__ */ new Map();
18730
19009
  this.objectBlocks = /* @__PURE__ */ new Set();
@@ -18734,6 +19013,7 @@ var Chunk = class _Chunk {
18734
19013
  this.blockPosition = new Vector320();
18735
19014
  this.meshDirty = false;
18736
19015
  this.meshGeneration = 0;
19016
+ this.blockChanges = /* @__PURE__ */ new Set();
18737
19017
  this.scene = scene;
18738
19018
  this.x = x;
18739
19019
  this.y = y;
@@ -18832,10 +19112,20 @@ var Chunk = class _Chunk {
18832
19112
  return this.refreshBiomes();
18833
19113
  }
18834
19114
  /** Rebuilds biome-tinted blocks while preserving hidden blocks and unrelated render objects. */
18835
- async refreshBiomes() {
19115
+ refreshBiomes(columns) {
19116
+ return this.changeBlocks(() => this.refreshBiomeBlocks(columns), true);
19117
+ }
19118
+ async refreshBiomeBlocks(columns) {
19119
+ const blend = columns && this.biomeBlend?.();
19120
+ const ownColumn = columns?.some((column) => column.x === this.x && column.z === this.z);
18836
19121
  const affected = /* @__PURE__ */ new Map();
18837
19122
  const blocks = [];
18838
19123
  for (const index of this.renderedBlocks.keys()) {
19124
+ if (blend) {
19125
+ if (this.objectBlocks.has(index) && !ownColumn) continue;
19126
+ const [x, , z] = this.positionAt(index), radius = blend.radius;
19127
+ 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;
19128
+ }
18839
19129
  const block = this.data.get(index);
18840
19130
  const stateKey = ChunkData.paletteKey(block);
18841
19131
  let matches = affected.get(stateKey);
@@ -18850,10 +19140,32 @@ var Chunk = class _Chunk {
18850
19140
  }
18851
19141
  if (await matches) blocks.push({ index, block });
18852
19142
  }
19143
+ if (blend) {
19144
+ const groups = /* @__PURE__ */ new Map();
19145
+ for (const entry of blocks) {
19146
+ const key = ChunkData.paletteKey(entry.block);
19147
+ let group = groups.get(key);
19148
+ if (!group) groups.set(key, group = []);
19149
+ group.push(entry);
19150
+ }
19151
+ blocks.length = 0;
19152
+ const biomes = /* @__PURE__ */ new Map();
19153
+ for (const group of groups.values()) {
19154
+ const { contexts, fluid } = await this.resolveState(group[0].block, group.map((entry) => entry.index), biomes);
19155
+ for (const entry of group) {
19156
+ const context = contexts.get(entry.index);
19157
+ const current = this.sectionBlocks.get(entry.index)?.[0]?.biomeTint;
19158
+ const tint = context?.tint;
19159
+ const tintChanged = !!current !== !!tint || tint?.some((value, channel) => value !== current?.[channel]);
19160
+ 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])));
19161
+ if (this.objectBlocks.has(entry.index) || tintChanged || waterChanged) blocks.push(entry);
19162
+ }
19163
+ }
19164
+ }
18853
19165
  if (!blocks.length) return;
18854
19166
  const hidden = new Set(this.hiddenBlocks);
18855
19167
  try {
18856
- await this.placeBlocks(blocks);
19168
+ await this.replaceBlocks(blocks, true);
18857
19169
  } finally {
18858
19170
  for (const index of hidden) {
18859
19171
  this.hiddenBlocks.add(index);
@@ -18893,6 +19205,20 @@ var Chunk = class _Chunk {
18893
19205
  }
18894
19206
  /** Places cells in input order and returns changed world positions without refreshing neighbors. */
18895
19207
  async placeBlocks(blocks) {
19208
+ return this.changeBlocks(() => this.replaceBlocks(blocks));
19209
+ }
19210
+ changeBlocks(change, refreshBiomes = false) {
19211
+ const before = refreshBiomes ? Promise.allSettled(this.blockChanges) : this.biomeChange;
19212
+ const result = before ? before.catch(() => void 0).then(change) : change();
19213
+ const pending = result.finally(() => {
19214
+ this.blockChanges.delete(pending);
19215
+ if (this.biomeChange === pending) this.biomeChange = void 0;
19216
+ });
19217
+ this.blockChanges.add(pending);
19218
+ if (refreshBiomes) this.biomeChange = pending;
19219
+ return pending;
19220
+ }
19221
+ async replaceBlocks(blocks, preserveDataOnFailure = false) {
18896
19222
  const placements = blocks.map(({ index, block }) => ({
18897
19223
  index,
18898
19224
  key: block && !ChunkData.isAir(block) ? ChunkData.paletteKey(block) : void 0,
@@ -18912,6 +19238,10 @@ var Chunk = class _Chunk {
18912
19238
  const biomes = /* @__PURE__ */ new Map();
18913
19239
  const settled = await Promise.allSettled([...states.values()].map(({ stored, indices }) => this.resolveState(stored, indices, biomes)));
18914
19240
  const resolutions = new Map([...states.keys()].map((key, i) => [key, settled[i]]));
19241
+ if (preserveDataOnFailure) {
19242
+ const rejected = settled.find((result) => result.status === "rejected");
19243
+ if (rejected?.status === "rejected") throw rejected.reason;
19244
+ }
18915
19245
  const positions = [];
18916
19246
  let failed = false, failure;
18917
19247
  for (const { index, key, block } of placements) {
@@ -18949,7 +19279,7 @@ var Chunk = class _Chunk {
18949
19279
  }
18950
19280
  const templates = perBlock ? void 0 : fluid?.renderModel === false ? [] : (context?.prepared ?? prepared)?.pick(variantPosition);
18951
19281
  if (templates) {
18952
- this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0 })));
19282
+ this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0, biomeTint: context?.tint })));
18953
19283
  this.meshDirty = true;
18954
19284
  } else {
18955
19285
  object4 = await this.scene.addBlock(blockState3, {
@@ -18968,7 +19298,11 @@ var Chunk = class _Chunk {
18968
19298
  }, object: object4 });
18969
19299
  if (object4) this.objectBlocks.add(index);
18970
19300
  } catch (error) {
18971
- this.data.assign(index, 0);
19301
+ if (preserveDataOnFailure && readBlock) {
19302
+ this.renderedBlocks.set(index, { get block() {
19303
+ return readBlock();
19304
+ }, object: void 0 });
19305
+ } else this.data.assign(index, 0);
18972
19306
  this.fluidCells[index] = 0;
18973
19307
  object4?.removeFromScene();
18974
19308
  if (!failed) failure = error;
@@ -18979,7 +19313,10 @@ var Chunk = class _Chunk {
18979
19313
  return positions;
18980
19314
  }
18981
19315
  /** Replaces every cell from parsed section data. The caller refreshes neighbor culling. */
18982
- async placeSection(data) {
19316
+ placeSection(data) {
19317
+ return this.changeBlocks(() => this.replaceSection(data));
19318
+ }
19319
+ async replaceSection(data) {
18983
19320
  for (const info of this.renderedBlocks.values()) info.object?.removeFromScene();
18984
19321
  this.sectionBlocks.clear();
18985
19322
  this.fluidCells.fill(0);
@@ -19026,7 +19363,7 @@ var Chunk = class _Chunk {
19026
19363
  ];
19027
19364
  const templates = perBlock ? void 0 : fluid?.renderModel === false ? [] : (context?.prepared ?? prepared)?.pick(variantPosition);
19028
19365
  if (templates) {
19029
- this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0 })));
19366
+ this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0, biomeTint: context?.tint })));
19030
19367
  this.meshDirty = true;
19031
19368
  } else {
19032
19369
  perBlocks.push({ index, blockState: blockState3, biome: context?.biome });
@@ -19110,12 +19447,35 @@ var Chunk = class _Chunk {
19110
19447
  }
19111
19448
  async resolveState(stored, indices, biomes) {
19112
19449
  const blockState3 = await BlockStates.get(AssetKey.parse("blockstates", stored.type));
19113
- const perBlock = !blockState3 || !this.sectionModels || !!(blockState3.key && BlockEntities.entry(await BlockEntities.getIndex(blockState3.key.root), blockState3.key.toNamespacedString()));
19450
+ let perBlock = !blockState3 || !this.sectionModels || !!(blockState3.key && BlockEntities.entry(await BlockEntities.getIndex(blockState3.key.root), blockState3.key.toNamespacedString()));
19114
19451
  const fluid = await getBlockFluidState(blockState3?.key, stored.properties);
19115
19452
  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;
19116
19453
  const source = hasBiomes && blockState3 ? await BlockTints.biomeSource(blockState3.key) : void 0;
19117
19454
  const prepare = (color2) => perBlock || fluid?.renderModel === false ? void 0 : this.sectionModels.prepareState(blockState3, stored.properties, color2);
19118
19455
  const contexts = /* @__PURE__ */ new Map();
19456
+ const blend = this.biomeBlend?.();
19457
+ if (blend && !perBlock && blockState3 && (source || fluid?.kind === "water")) {
19458
+ const prepared2 = fluid?.renderModel === false ? void 0 : await this.sectionModels.prepareState(blockState3, stored.properties, void 0, true);
19459
+ if (!prepared2 && fluid?.renderModel !== false) {
19460
+ perBlock = true;
19461
+ } else {
19462
+ await Promise.all(indices.map(async (index) => {
19463
+ const position = this.biomePosition(index, blockState3, stored);
19464
+ const biomeColor = (kind) => blend.getColor(kind, blockState3.key, position);
19465
+ const context = {};
19466
+ if (source && prepared2?.biomeTint) {
19467
+ const color2 = await biomeColor(source);
19468
+ if (color2 !== void 0) context.tint = new Color13(color2).toArray();
19469
+ }
19470
+ if (fluid?.kind === "water") {
19471
+ const color2 = await biomeColor("water");
19472
+ if (color2 !== void 0 && color2 !== 4159204) context.waterTint = new Color13(color2).toArray();
19473
+ }
19474
+ contexts.set(index, context);
19475
+ }));
19476
+ return { blockState: blockState3, perBlock, fluid, prepared: prepared2, contexts, stored };
19477
+ }
19478
+ }
19119
19479
  const groups = /* @__PURE__ */ new Map();
19120
19480
  if (blockState3 && (source || fluid?.kind === "water")) {
19121
19481
  for (const index of indices) {
@@ -19163,7 +19523,11 @@ var Chunk = class _Chunk {
19163
19523
  this.waterTints.set(tint ?? WATER_TINT2, index * 3);
19164
19524
  }
19165
19525
  /** Changes visibility at world block coordinates while preserving the block's data. */
19166
- async setBlockVisibleAt(pos, visible) {
19526
+ setBlockVisibleAt(pos, visible) {
19527
+ const position = pos.clone();
19528
+ return this.changeBlocks(() => this.setBlockVisibility(position, visible));
19529
+ }
19530
+ async setBlockVisibility(pos, visible) {
19167
19531
  const index = _Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos));
19168
19532
  const block = this.renderedBlocks.get(index);
19169
19533
  if (!block || visible === !this.hiddenBlocks.has(index)) {
@@ -19333,6 +19697,7 @@ var Chunk = class _Chunk {
19333
19697
  }
19334
19698
  /** Removes stored blocks and render objects. Set `notify` to false to skip the owning world's update. */
19335
19699
  async clear(notify = true) {
19700
+ if (this.blockChanges.size) await Promise.allSettled(this.blockChanges);
19336
19701
  const positions = notify && this.onBlocksChanged ? [...this.renderedBlocks.keys()].map((index) => this.chunkPosToWorldPos(
19337
19702
  new Vector320(index % 16, Math.floor(index / 256), Math.floor(index / 16) % 16)
19338
19703
  )) : [];
@@ -19646,6 +20011,7 @@ export {
19646
20011
  PackMetadata,
19647
20012
  PersistentCache,
19648
20013
  PlayerHeadTextures,
20014
+ Renames,
19649
20015
  Renderer,
19650
20016
  RequestError,
19651
20017
  Requests,