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.
@@ -264,6 +264,10 @@ var init_Caching = __esm({
264
264
  import_loading_cache = require("@inventivetalent/loading-cache");
265
265
  import_time = require("@inventivetalent/time");
266
266
  _Caching = class _Caching {
267
+ /** Changes when shared assets are cleared, so derived caches can discard their old values. */
268
+ static get generation() {
269
+ return this._generation;
270
+ }
267
271
  static createAssetCache() {
268
272
  return import_loading_cache.Caches.builder().expireAfterWrite(import_time.Time.minutes(10)).expireAfterAccess(import_time.Time.minutes(5)).expirationInterval(import_time.Time.seconds(30)).buildAsync();
269
273
  }
@@ -299,6 +303,7 @@ var init_Caching = __esm({
299
303
  }
300
304
  /** Invalidates in-memory entries so later lookups reload them. Persisted entries are retained. */
301
305
  static clear() {
306
+ this._generation++;
302
307
  for (const cache of this.all) {
303
308
  cache.invalidateAll();
304
309
  }
@@ -310,6 +315,7 @@ var init_Caching = __esm({
310
315
  * Use {@link shutdown} to also stop requests and the shared Ticker.
311
316
  */
312
317
  static end() {
318
+ this._generation++;
313
319
  for (const cache of this.all) {
314
320
  cache.invalidateAll();
315
321
  cache.end();
@@ -321,6 +327,7 @@ var init_Caching = __esm({
321
327
  };
322
328
  }
323
329
  };
330
+ _Caching._generation = 0;
324
331
  _Caching.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();
325
332
  _Caching.imageDataCache = import_loading_cache.Caches.builder().expireAfterWrite(import_time.Time.minutes(10)).expireAfterAccess(import_time.Time.minutes(5)).expirationInterval(import_time.Time.seconds(10)).buildAsync();
326
333
  _Caching.canvasImageDataCache = import_loading_cache.Caches.builder().expireAfterWrite(import_time.Time.minutes(10)).expireAfterAccess(import_time.Time.minutes(5)).expirationInterval(import_time.Time.seconds(10)).buildAsync();
@@ -1209,118 +1216,6 @@ var init_Materials = __esm({
1209
1216
  }
1210
1217
  });
1211
1218
 
1212
- // src/model/Model.ts
1213
- function isDoubleArray(obj) {
1214
- return Array.isArray(obj) && obj.length === 2;
1215
- }
1216
- function isTripleArray(obj) {
1217
- return Array.isArray(obj) && obj.length === 3;
1218
- }
1219
- function isQuadArray(obj) {
1220
- return Array.isArray(obj) && obj.length === 4;
1221
- }
1222
- var ITEM_GENERATED, BUILTIN_GENERATED, BUILTIN_ENTITY, DEFAULT_ELEMENTS;
1223
- var init_Model = __esm({
1224
- "src/model/Model.ts"() {
1225
- ITEM_GENERATED = "item/generated";
1226
- BUILTIN_GENERATED = "builtin/generated";
1227
- BUILTIN_ENTITY = "builtin/entity";
1228
- DEFAULT_ELEMENTS = [];
1229
- }
1230
- });
1231
-
1232
- // ../../node_modules/ts-deepmerge/dist/index.js
1233
- var require_dist = __commonJS({
1234
- "../../node_modules/ts-deepmerge/dist/index.js"(exports) {
1235
- "use strict";
1236
- var __assign = exports && exports.__assign || function() {
1237
- __assign = Object.assign || function(t) {
1238
- for (var s, i = 1, n = arguments.length; i < n; i++) {
1239
- s = arguments[i];
1240
- for (var p11 in s) if (Object.prototype.hasOwnProperty.call(s, p11))
1241
- t[p11] = s[p11];
1242
- }
1243
- return t;
1244
- };
1245
- return __assign.apply(this, arguments);
1246
- };
1247
- var __read = exports && exports.__read || function(o, n) {
1248
- var m = typeof Symbol === "function" && o[Symbol.iterator];
1249
- if (!m) return o;
1250
- var i = m.call(o), r, ar = [], e;
1251
- try {
1252
- while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
1253
- } catch (error) {
1254
- e = { error };
1255
- } finally {
1256
- try {
1257
- if (r && !r.done && (m = i["return"])) m.call(i);
1258
- } finally {
1259
- if (e) throw e.error;
1260
- }
1261
- }
1262
- return ar;
1263
- };
1264
- var __spreadArray = exports && exports.__spreadArray || function(to, from, pack) {
1265
- if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
1266
- if (ar || !(i in from)) {
1267
- if (!ar) ar = Array.prototype.slice.call(from, 0, i);
1268
- ar[i] = from[i];
1269
- }
1270
- }
1271
- return to.concat(ar || Array.prototype.slice.call(from));
1272
- };
1273
- Object.defineProperty(exports, "__esModule", { value: true });
1274
- var isObject = function(obj) {
1275
- if (typeof obj === "object" && obj !== null) {
1276
- if (typeof Object.getPrototypeOf === "function") {
1277
- var prototype = Object.getPrototypeOf(obj);
1278
- return prototype === Object.prototype || prototype === null;
1279
- }
1280
- return Object.prototype.toString.call(obj) === "[object Object]";
1281
- }
1282
- return false;
1283
- };
1284
- var PROTECTED_KEYS = ["__proto__"];
1285
- var merge10 = function() {
1286
- var objects = [];
1287
- for (var _i = 0; _i < arguments.length; _i++) {
1288
- objects[_i] = arguments[_i];
1289
- }
1290
- return objects.reduce(function(result, current) {
1291
- Object.keys(current).forEach(function(key) {
1292
- if (PROTECTED_KEYS.includes(key)) {
1293
- return;
1294
- }
1295
- if (Array.isArray(result[key]) && Array.isArray(current[key])) {
1296
- result[key] = merge10.options.mergeArrays ? Array.from(new Set(result[key].concat(current[key]))) : current[key];
1297
- } else if (isObject(result[key]) && isObject(current[key])) {
1298
- result[key] = merge10(result[key], current[key]);
1299
- } else {
1300
- result[key] = current[key];
1301
- }
1302
- });
1303
- return result;
1304
- }, {});
1305
- };
1306
- var defaultOptions = {
1307
- mergeArrays: true
1308
- };
1309
- merge10.options = defaultOptions;
1310
- merge10.withOptions = function(options) {
1311
- var objects = [];
1312
- for (var _i = 1; _i < arguments.length; _i++) {
1313
- objects[_i - 1] = arguments[_i];
1314
- }
1315
- merge10.options = __assign({ mergeArrays: true }, options);
1316
- var result = merge10.apply(void 0, __spreadArray([], __read(objects), false));
1317
- merge10.options = defaultOptions;
1318
- return result;
1319
- };
1320
- exports.default = merge10;
1321
- }
1322
- });
1323
-
1324
1219
  // src/assets/AssetKey.ts
1325
1220
  function isResourceLocation(value) {
1326
1221
  return typeof value === "string" && /^(?:[a-z0-9_.-]+:)?[a-z0-9_./-]+$/.test(value);
@@ -1423,17 +1318,306 @@ var init_AssetKey = __esm({
1423
1318
  if (this.type) {
1424
1319
  p11.push(this.type);
1425
1320
  }
1426
- p11.push(this.path);
1427
- return p11.join("/");
1321
+ p11.push(this.path);
1322
+ return p11.join("/");
1323
+ }
1324
+ toNamespacedString() {
1325
+ return this.namespace + ":" + this.getFullPath();
1326
+ }
1327
+ /** Returns a cache identifier that includes the explicit root or {@link AssetLoader.ROOT}. */
1328
+ serialize() {
1329
+ return this.toString();
1330
+ }
1331
+ };
1332
+ }
1333
+ });
1334
+
1335
+ // src/assets/source/parser/AssetParsers.ts
1336
+ var AssetParser;
1337
+ var init_AssetParsers = __esm({
1338
+ "src/assets/source/parser/AssetParsers.ts"() {
1339
+ AssetParser = /* @__PURE__ */ ((AssetParser2) => {
1340
+ AssetParser2["MODEL"] = "model";
1341
+ AssetParser2["BLOCKSTATE"] = "blockstate";
1342
+ AssetParser2["META"] = "meta";
1343
+ AssetParser2["NBT"] = "nbt";
1344
+ AssetParser2["IMAGE"] = "image";
1345
+ AssetParser2["LIST"] = "list";
1346
+ AssetParser2["JSON"] = "json";
1347
+ return AssetParser2;
1348
+ })(AssetParser || {});
1349
+ }
1350
+ });
1351
+
1352
+ // src/assets/MineRenderData.ts
1353
+ var object, release, compare, MineRenderData;
1354
+ var init_MineRenderData = __esm({
1355
+ "src/assets/MineRenderData.ts"() {
1356
+ init_AssetKey();
1357
+ init_AssetLoader();
1358
+ init_AssetParsers();
1359
+ init_Caching();
1360
+ object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
1361
+ release = (value) => /^\d+\.\d+(?:\.\d+)?$/.test(value) ? value.split(".").map(Number) : void 0;
1362
+ compare = (left, right) => {
1363
+ for (let i = 0; i < 3; i++) {
1364
+ const difference = (left[i] ?? 0) - (right[i] ?? 0);
1365
+ if (difference) return difference;
1366
+ }
1367
+ return 0;
1368
+ };
1369
+ MineRenderData = class {
1370
+ /** Loads one registry. Missing files and invalid data reject the request. */
1371
+ static async get(dataset, root) {
1372
+ root = root?.replace(/\/+$/, "");
1373
+ const scope = AssetLoader.persistentScope;
1374
+ const defaultRoot = AssetLoader.ROOT;
1375
+ const resolved = await this.resolve(root);
1376
+ const entry = resolved.manifest.datasets[dataset];
1377
+ if (!entry) throw new Error(`MineRender ${dataset} data is unavailable for ${resolved.version}: ${resolved.manifest.unavailable[dataset] ?? "not listed in the manifest"}`);
1378
+ const file = entry.file;
1379
+ const requestRoot = resolved.root === (root ?? defaultRoot).replace(/\/+$/, "") ? root : resolved.root;
1380
+ return await this.cached(`${requestRoot === void 0 ? "sources" : "explicit"}:${resolved.root}/${file}:${entry.sha256}`, scope, defaultRoot, async () => {
1381
+ const data = await this.load(file.slice(0, -5), requestRoot);
1382
+ if (!this.validDataset(dataset, data)) throw new Error(`Invalid MineRender ${dataset} data at ${resolved.root}/minerender-data/${file}`);
1383
+ return data;
1384
+ });
1385
+ }
1386
+ /**
1387
+ * Uses the exact version when published. Otherwise, selects the nearest patch in the same
1388
+ * release line (older on ties), then the latest earlier release or the oldest published release.
1389
+ * Unpublished snapshots reject.
1390
+ */
1391
+ static async resolve(root) {
1392
+ const requestRoot = root?.replace(/\/+$/, "");
1393
+ const defaultRoot = AssetLoader.ROOT;
1394
+ root = (root ?? defaultRoot).replace(/\/+$/, "");
1395
+ const scope = AssetLoader.persistentScope;
1396
+ return this.cached(`resolve:${requestRoot === void 0 ? "sources" : "explicit"}:${root}`, scope, defaultRoot, async () => {
1397
+ const requestedVersion = root.slice(root.lastIndexOf("/") + 1);
1398
+ let manifest = await this.manifest(root, requestRoot !== void 0 && release(requestedVersion) ? requestedVersion : void 0, requestRoot);
1399
+ if (manifest) return { requestedVersion, version: manifest.minecraftVersion, root, manifest };
1400
+ const requested = release(requestedVersion);
1401
+ if (!requested) {
1402
+ throw new Error(`No MineRender data is published for ${requestedVersion}; automatic fallback requires a release`);
1403
+ }
1404
+ const base = root.slice(0, root.lastIndexOf("/"));
1405
+ const index = await this.cached(`versions:${base}`, scope, defaultRoot, async () => {
1406
+ const value = await this.load("versions", base);
1407
+ if (!object(value) || !Array.isArray(value.versions) || !value.versions.every((entry) => object(entry) && typeof entry.name === "string")) {
1408
+ throw new Error(`Invalid MineRender version index at ${base}/minerender-data/versions.json`);
1409
+ }
1410
+ return value.versions;
1411
+ });
1412
+ const versions = index.map((entry) => ({ name: entry.name, number: release(entry.name) })).filter((entry) => !!entry.number).sort((left, right) => compare(left.number, right.number));
1413
+ const sameLine = versions.filter((entry) => entry.number[0] === requested[0] && entry.number[1] === requested[1]);
1414
+ 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];
1415
+ if (!selected) throw new Error(`No compatible MineRender data is published for ${requestedVersion}`);
1416
+ const resolvedRoot = `${base}/${selected.name}`;
1417
+ manifest = await this.manifest(resolvedRoot, selected.name, resolvedRoot);
1418
+ if (!manifest) throw new Error(`Missing MineRender manifest for published version ${selected.name}`);
1419
+ return { requestedVersion, version: selected.name, root: resolvedRoot, manifest };
1420
+ });
1421
+ }
1422
+ static async manifest(root, expectedVersion, requestRoot) {
1423
+ const data = await this.load("manifest", requestRoot);
1424
+ if (data === void 0) return void 0;
1425
+ 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) => {
1426
+ 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);
1427
+ }) || expectedVersion !== void 0 && data.minecraftVersion !== expectedVersion) {
1428
+ throw new Error(`Invalid MineRender manifest at ${root}/minerender-data/manifest.json`);
1429
+ }
1430
+ return data;
1431
+ }
1432
+ static load(file, root) {
1433
+ const key = new AssetKey("minerender-data", file, void 0, void 0, void 0, ".json", root);
1434
+ key.rootType = void 0;
1435
+ return AssetLoader.get(key, "json" /* JSON */);
1436
+ }
1437
+ static async cached(key, scope, defaultRoot, loader) {
1438
+ const checkSources = () => {
1439
+ if (AssetLoader.persistentScope !== scope || AssetLoader.ROOT !== defaultRoot) throw new Error("Asset sources changed while loading MineRender data; retry the request");
1440
+ };
1441
+ checkSources();
1442
+ return await Caching.mineRenderDataCache.get(scope ? `${scope}
1443
+ ${key}` : key, async () => {
1444
+ const value = await loader();
1445
+ checkSources();
1446
+ return value;
1447
+ });
1448
+ }
1449
+ static validDataset(dataset, data) {
1450
+ if (!object(data)) return false;
1451
+ 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)));
1452
+ 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")));
1453
+ if (dataset === "legacyBlocks") return object(data.blocks) && Object.values(data.blocks).every((value) => typeof value === "string");
1454
+ 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")));
1455
+ return Object.values(data).every((value) => {
1456
+ if (!object(value)) return false;
1457
+ switch (dataset) {
1458
+ case "itemDefaults":
1459
+ return true;
1460
+ case "blockStates":
1461
+ return Object.values(value).every((property) => object(property) && "default" in property && Array.isArray(property.values));
1462
+ case "blockTints":
1463
+ 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));
1464
+ case "fluids":
1465
+ return ["water", "lava"].includes(value.kind) && typeof value.renderModel === "boolean" && (value.levelProperty === void 0 || typeof value.levelProperty === "string");
1466
+ case "dyes":
1467
+ return [value.id, value.textureDiffuseColor, value.fireworkColor, value.textColor].every(Number.isInteger);
1468
+ default:
1469
+ return false;
1470
+ }
1471
+ });
1472
+ }
1473
+ };
1474
+ }
1475
+ });
1476
+
1477
+ // src/assets/Renames.ts
1478
+ var Renames;
1479
+ var init_Renames = __esm({
1480
+ "src/assets/Renames.ts"() {
1481
+ init_AssetKey();
1482
+ init_MineRenderData();
1483
+ Renames = class {
1484
+ /** Loads the selected version's rename table, or `undefined` when its published data has none. */
1485
+ static async get(root) {
1486
+ const resolved = await MineRenderData.resolve(root);
1487
+ return resolved.manifest.datasets.renames ? MineRenderData.get("renames", root) : void 0;
1488
+ }
1489
+ /**
1490
+ * Returns the ID that replaced `id`, or `undefined` when `id` keeps its meaning.
1491
+ * Without a data version, an ID that the selected version still registers keeps its meaning.
1492
+ */
1493
+ static async resolve(kind, id, dataVersion, root) {
1494
+ const data = await this.get(root);
1495
+ if (!data || !Object.prototype.hasOwnProperty.call(data[kind], id)) return void 0;
1496
+ const entry = data[kind][id];
1497
+ if (dataVersion === void 0) return entry.registered ? void 0 : entry.ranges[entry.ranges.length - 1].to;
1498
+ if (dataVersion < data.baseDataVersion) return void 0;
1499
+ return entry.ranges.find((range) => dataVersion < range.before)?.to;
1500
+ }
1501
+ /** Returns a copy of `key` for the replacement ID, or `undefined` when the ID keeps its meaning. */
1502
+ static async key(kind, key) {
1503
+ const renamed = await this.resolve(kind, `${key.namespace}:${key.path}`, void 0, key.root);
1504
+ if (!renamed) return void 0;
1505
+ const [namespace, path] = renamed.split(":");
1506
+ return new AssetKey(namespace, path, key.assetType, key.type, key.rootType, key.extension, key.root);
1507
+ }
1508
+ };
1509
+ }
1510
+ });
1511
+
1512
+ // src/model/Model.ts
1513
+ function isDoubleArray(obj) {
1514
+ return Array.isArray(obj) && obj.length === 2;
1515
+ }
1516
+ function isTripleArray(obj) {
1517
+ return Array.isArray(obj) && obj.length === 3;
1518
+ }
1519
+ function isQuadArray(obj) {
1520
+ return Array.isArray(obj) && obj.length === 4;
1521
+ }
1522
+ var ITEM_GENERATED, BUILTIN_GENERATED, BUILTIN_ENTITY, DEFAULT_ELEMENTS;
1523
+ var init_Model = __esm({
1524
+ "src/model/Model.ts"() {
1525
+ ITEM_GENERATED = "item/generated";
1526
+ BUILTIN_GENERATED = "builtin/generated";
1527
+ BUILTIN_ENTITY = "builtin/entity";
1528
+ DEFAULT_ELEMENTS = [];
1529
+ }
1530
+ });
1531
+
1532
+ // ../../node_modules/ts-deepmerge/dist/index.js
1533
+ var require_dist = __commonJS({
1534
+ "../../node_modules/ts-deepmerge/dist/index.js"(exports) {
1535
+ "use strict";
1536
+ var __assign = exports && exports.__assign || function() {
1537
+ __assign = Object.assign || function(t) {
1538
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
1539
+ s = arguments[i];
1540
+ for (var p11 in s) if (Object.prototype.hasOwnProperty.call(s, p11))
1541
+ t[p11] = s[p11];
1542
+ }
1543
+ return t;
1544
+ };
1545
+ return __assign.apply(this, arguments);
1546
+ };
1547
+ var __read = exports && exports.__read || function(o, n) {
1548
+ var m = typeof Symbol === "function" && o[Symbol.iterator];
1549
+ if (!m) return o;
1550
+ var i = m.call(o), r, ar = [], e;
1551
+ try {
1552
+ while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
1553
+ } catch (error) {
1554
+ e = { error };
1555
+ } finally {
1556
+ try {
1557
+ if (r && !r.done && (m = i["return"])) m.call(i);
1558
+ } finally {
1559
+ if (e) throw e.error;
1560
+ }
1561
+ }
1562
+ return ar;
1563
+ };
1564
+ var __spreadArray = exports && exports.__spreadArray || function(to, from, pack) {
1565
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
1566
+ if (ar || !(i in from)) {
1567
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
1568
+ ar[i] = from[i];
1569
+ }
1570
+ }
1571
+ return to.concat(ar || Array.prototype.slice.call(from));
1572
+ };
1573
+ Object.defineProperty(exports, "__esModule", { value: true });
1574
+ var isObject = function(obj) {
1575
+ if (typeof obj === "object" && obj !== null) {
1576
+ if (typeof Object.getPrototypeOf === "function") {
1577
+ var prototype = Object.getPrototypeOf(obj);
1578
+ return prototype === Object.prototype || prototype === null;
1579
+ }
1580
+ return Object.prototype.toString.call(obj) === "[object Object]";
1428
1581
  }
1429
- toNamespacedString() {
1430
- return this.namespace + ":" + this.getFullPath();
1582
+ return false;
1583
+ };
1584
+ var PROTECTED_KEYS = ["__proto__"];
1585
+ var merge10 = function() {
1586
+ var objects = [];
1587
+ for (var _i = 0; _i < arguments.length; _i++) {
1588
+ objects[_i] = arguments[_i];
1431
1589
  }
1432
- /** Returns a cache identifier that includes the explicit root or {@link AssetLoader.ROOT}. */
1433
- serialize() {
1434
- return this.toString();
1590
+ return objects.reduce(function(result, current) {
1591
+ Object.keys(current).forEach(function(key) {
1592
+ if (PROTECTED_KEYS.includes(key)) {
1593
+ return;
1594
+ }
1595
+ if (Array.isArray(result[key]) && Array.isArray(current[key])) {
1596
+ result[key] = merge10.options.mergeArrays ? Array.from(new Set(result[key].concat(current[key]))) : current[key];
1597
+ } else if (isObject(result[key]) && isObject(current[key])) {
1598
+ result[key] = merge10(result[key], current[key]);
1599
+ } else {
1600
+ result[key] = current[key];
1601
+ }
1602
+ });
1603
+ return result;
1604
+ }, {});
1605
+ };
1606
+ var defaultOptions = {
1607
+ mergeArrays: true
1608
+ };
1609
+ merge10.options = defaultOptions;
1610
+ merge10.withOptions = function(options) {
1611
+ var objects = [];
1612
+ for (var _i = 1; _i < arguments.length; _i++) {
1613
+ objects[_i - 1] = arguments[_i];
1435
1614
  }
1615
+ merge10.options = __assign({ mergeArrays: true }, options);
1616
+ var result = merge10.apply(void 0, __spreadArray([], __read(objects), false));
1617
+ merge10.options = defaultOptions;
1618
+ return result;
1436
1619
  };
1620
+ exports.default = merge10;
1437
1621
  }
1438
1622
  });
1439
1623
 
@@ -1559,147 +1743,6 @@ var init_ModelMerger = __esm({
1559
1743
  }
1560
1744
  });
1561
1745
 
1562
- // src/assets/source/parser/AssetParsers.ts
1563
- var AssetParser;
1564
- var init_AssetParsers = __esm({
1565
- "src/assets/source/parser/AssetParsers.ts"() {
1566
- AssetParser = /* @__PURE__ */ ((AssetParser2) => {
1567
- AssetParser2["MODEL"] = "model";
1568
- AssetParser2["BLOCKSTATE"] = "blockstate";
1569
- AssetParser2["META"] = "meta";
1570
- AssetParser2["NBT"] = "nbt";
1571
- AssetParser2["IMAGE"] = "image";
1572
- AssetParser2["LIST"] = "list";
1573
- AssetParser2["JSON"] = "json";
1574
- return AssetParser2;
1575
- })(AssetParser || {});
1576
- }
1577
- });
1578
-
1579
- // src/assets/MineRenderData.ts
1580
- var object, release, compare, MineRenderData;
1581
- var init_MineRenderData = __esm({
1582
- "src/assets/MineRenderData.ts"() {
1583
- init_AssetKey();
1584
- init_AssetLoader();
1585
- init_AssetParsers();
1586
- init_Caching();
1587
- object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
1588
- release = (value) => /^\d+\.\d+(?:\.\d+)?$/.test(value) ? value.split(".").map(Number) : void 0;
1589
- compare = (left, right) => {
1590
- for (let i = 0; i < 3; i++) {
1591
- const difference = (left[i] ?? 0) - (right[i] ?? 0);
1592
- if (difference) return difference;
1593
- }
1594
- return 0;
1595
- };
1596
- MineRenderData = class {
1597
- /** Loads one registry. Missing files and invalid data reject the request. */
1598
- static async get(dataset, root) {
1599
- root = root?.replace(/\/+$/, "");
1600
- const scope = AssetLoader.persistentScope;
1601
- const defaultRoot = AssetLoader.ROOT;
1602
- const resolved = await this.resolve(root);
1603
- const entry = resolved.manifest.datasets[dataset];
1604
- if (!entry) throw new Error(`MineRender ${dataset} data is unavailable for ${resolved.version}: ${resolved.manifest.unavailable[dataset] ?? "not listed in the manifest"}`);
1605
- const file = entry.file;
1606
- const requestRoot = resolved.root === (root ?? defaultRoot).replace(/\/+$/, "") ? root : resolved.root;
1607
- return await this.cached(`${requestRoot === void 0 ? "sources" : "explicit"}:${resolved.root}/${file}:${entry.sha256}`, scope, defaultRoot, async () => {
1608
- const data = await this.load(file.slice(0, -5), requestRoot);
1609
- if (!this.validDataset(dataset, data)) throw new Error(`Invalid MineRender ${dataset} data at ${resolved.root}/minerender-data/${file}`);
1610
- return data;
1611
- });
1612
- }
1613
- /**
1614
- * Uses the exact version when published. Otherwise, selects the nearest patch in the same
1615
- * release line (older on ties), then the latest earlier release or the oldest published release.
1616
- * Unpublished snapshots reject.
1617
- */
1618
- static async resolve(root) {
1619
- const requestRoot = root?.replace(/\/+$/, "");
1620
- const defaultRoot = AssetLoader.ROOT;
1621
- root = (root ?? defaultRoot).replace(/\/+$/, "");
1622
- const scope = AssetLoader.persistentScope;
1623
- return this.cached(`resolve:${requestRoot === void 0 ? "sources" : "explicit"}:${root}`, scope, defaultRoot, async () => {
1624
- const requestedVersion = root.slice(root.lastIndexOf("/") + 1);
1625
- let manifest = await this.manifest(root, requestRoot !== void 0 && release(requestedVersion) ? requestedVersion : void 0, requestRoot);
1626
- if (manifest) return { requestedVersion, version: manifest.minecraftVersion, root, manifest };
1627
- const requested = release(requestedVersion);
1628
- if (!requested) {
1629
- throw new Error(`No MineRender data is published for ${requestedVersion}; automatic fallback requires a release`);
1630
- }
1631
- const base = root.slice(0, root.lastIndexOf("/"));
1632
- const index = await this.cached(`versions:${base}`, scope, defaultRoot, async () => {
1633
- const value = await this.load("versions", base);
1634
- if (!object(value) || !Array.isArray(value.versions) || !value.versions.every((entry) => object(entry) && typeof entry.name === "string")) {
1635
- throw new Error(`Invalid MineRender version index at ${base}/minerender-data/versions.json`);
1636
- }
1637
- return value.versions;
1638
- });
1639
- const versions = index.map((entry) => ({ name: entry.name, number: release(entry.name) })).filter((entry) => !!entry.number).sort((left, right) => compare(left.number, right.number));
1640
- const sameLine = versions.filter((entry) => entry.number[0] === requested[0] && entry.number[1] === requested[1]);
1641
- 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];
1642
- if (!selected) throw new Error(`No compatible MineRender data is published for ${requestedVersion}`);
1643
- const resolvedRoot = `${base}/${selected.name}`;
1644
- manifest = await this.manifest(resolvedRoot, selected.name, resolvedRoot);
1645
- if (!manifest) throw new Error(`Missing MineRender manifest for published version ${selected.name}`);
1646
- return { requestedVersion, version: selected.name, root: resolvedRoot, manifest };
1647
- });
1648
- }
1649
- static async manifest(root, expectedVersion, requestRoot) {
1650
- const data = await this.load("manifest", requestRoot);
1651
- if (data === void 0) return void 0;
1652
- 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) => {
1653
- 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);
1654
- }) || expectedVersion !== void 0 && data.minecraftVersion !== expectedVersion) {
1655
- throw new Error(`Invalid MineRender manifest at ${root}/minerender-data/manifest.json`);
1656
- }
1657
- return data;
1658
- }
1659
- static load(file, root) {
1660
- const key = new AssetKey("minerender-data", file, void 0, void 0, void 0, ".json", root);
1661
- key.rootType = void 0;
1662
- return AssetLoader.get(key, "json" /* JSON */);
1663
- }
1664
- static async cached(key, scope, defaultRoot, loader) {
1665
- const checkSources = () => {
1666
- if (AssetLoader.persistentScope !== scope || AssetLoader.ROOT !== defaultRoot) throw new Error("Asset sources changed while loading MineRender data; retry the request");
1667
- };
1668
- checkSources();
1669
- return await Caching.mineRenderDataCache.get(scope ? `${scope}
1670
- ${key}` : key, async () => {
1671
- const value = await loader();
1672
- checkSources();
1673
- return value;
1674
- });
1675
- }
1676
- static validDataset(dataset, data) {
1677
- if (!object(data)) return false;
1678
- 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)));
1679
- if (dataset === "legacyBlocks") return object(data.blocks) && Object.values(data.blocks).every((value) => typeof value === "string");
1680
- 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")));
1681
- return Object.values(data).every((value) => {
1682
- if (!object(value)) return false;
1683
- switch (dataset) {
1684
- case "itemDefaults":
1685
- return true;
1686
- case "blockStates":
1687
- return Object.values(value).every((property) => object(property) && "default" in property && Array.isArray(property.values));
1688
- case "blockTints":
1689
- 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));
1690
- case "fluids":
1691
- return ["water", "lava"].includes(value.kind) && typeof value.renderModel === "boolean" && (value.levelProperty === void 0 || typeof value.levelProperty === "string");
1692
- case "dyes":
1693
- return [value.id, value.textureDiffuseColor, value.fireworkColor, value.textColor].every(Number.isInteger);
1694
- default:
1695
- return false;
1696
- }
1697
- });
1698
- }
1699
- };
1700
- }
1701
- });
1702
-
1703
1746
  // src/assets/BannerPatterns.ts
1704
1747
  var DYE_COLORS, BannerPatterns;
1705
1748
  var init_BannerPatterns = __esm({
@@ -2022,6 +2065,7 @@ var init_ItemTints = __esm({
2022
2065
  var Models;
2023
2066
  var init_Models = __esm({
2024
2067
  "src/assets/Models.ts"() {
2068
+ init_Renames();
2025
2069
  init_Caching();
2026
2070
  init_ModelMerger();
2027
2071
  init_AssetLoader();
@@ -2351,6 +2395,11 @@ var init_Models = __esm({
2351
2395
  key.extension = ".json";
2352
2396
  }
2353
2397
  const preview = this.snapshotContext(key, context);
2398
+ if (key.type === "item") {
2399
+ const renamed = await Renames.key("items", key);
2400
+ const replacement = renamed && await this.getMerged(renamed, preview);
2401
+ if (replacement) return replacement;
2402
+ }
2354
2403
  const dataKey = await this.applyDefaults(key, preview);
2355
2404
  const keyStr = AssetLoader.persistentKey(key.serialize() + (key.type === "item" ? this.contextKey(preview) + dataKey : ""));
2356
2405
  const model = await Caching.mergedModelCache.get(keyStr, async () => {
@@ -2384,6 +2433,7 @@ var init_BlockStates = __esm({
2384
2433
  init_AssetKey();
2385
2434
  init_PersistentCache();
2386
2435
  init_AssetParsers();
2436
+ init_Renames();
2387
2437
  init_MineRenderData();
2388
2438
  BlockStates = class {
2389
2439
  static get PERSISTENT_CACHE() {
@@ -2424,6 +2474,9 @@ var init_BlockStates = __esm({
2424
2474
  if (!key.extension) {
2425
2475
  key.extension = ".json";
2426
2476
  }
2477
+ const renamed = await Renames.key("blocks", key);
2478
+ const replacement = renamed && await this.get(renamed);
2479
+ if (replacement) return replacement;
2427
2480
  const keyStr = key.serialize();
2428
2481
  return Caching.blockStateCache.get(keyStr, (k) => {
2429
2482
  return this.PERSISTENT_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
@@ -7587,6 +7640,7 @@ __export(index_browser_exports, {
7587
7640
  PackMetadata: () => PackMetadata,
7588
7641
  PersistentCache: () => PersistentCache,
7589
7642
  PlayerHeadTextures: () => PlayerHeadTextures,
7643
+ Renames: () => Renames,
7590
7644
  Renderer: () => Renderer,
7591
7645
  RequestError: () => RequestError,
7592
7646
  Requests: () => Requests,
@@ -8196,6 +8250,7 @@ var Fonts = class {
8196
8250
  init_MineRenderData();
8197
8251
  init_ModelTextures();
8198
8252
  init_Models();
8253
+ init_Renames();
8199
8254
  init_source();
8200
8255
 
8201
8256
  // src/cache/index.ts
@@ -17461,18 +17516,23 @@ var SectionModels = class {
17461
17516
  return this.template(variant, choice, tints);
17462
17517
  }));
17463
17518
  }
17464
- /** Resolves section templates for one tint palette, with a synchronous weighted choice for each cell. */
17465
- async prepareState(blockState3, properties = {}, biomeColor) {
17519
+ /** Resolves shared templates; dynamic biome tints are supplied on section placements instead of cached geometry. */
17520
+ async prepareState(blockState3, properties = {}, biomeColor, dynamicBiomeTint = false) {
17466
17521
  const prepared = await this.state(blockState3, properties);
17467
17522
  if (!prepared) return void 0;
17468
17523
  let color2;
17469
17524
  const resolveColor = typeof biomeColor === "function" ? () => color2 ?? (color2 = biomeColor()) : biomeColor;
17470
17525
  const templates = /* @__PURE__ */ new Map();
17526
+ const source = dynamicBiomeTint && await BlockTints.biomeSource(blockState3.key);
17471
17527
  await Promise.all([...prepared.choices].map(async ([variant, choice]) => {
17472
17528
  const tints = await BlockTints.get(blockState3.key, prepared.state, choice.model, void 0, resolveColor);
17473
- templates.set(variant, await this.template(variant, choice, tints));
17529
+ const biomeTints = source ? await BlockTints.get(blockState3.key, prepared.state, choice.model, void 0, 16777215) : void 0;
17530
+ templates.set(variant, await this.template(variant, choice, tints, biomeTints));
17474
17531
  }));
17475
- return { pick: (position) => prepared.groups.map((group) => templates.get(BlockStateResolver.choose(group, position))) };
17532
+ return {
17533
+ pick: (position) => prepared.groups.map((group) => templates.get(BlockStateResolver.choose(group, position))),
17534
+ biomeTint: source ? [...templates.values()].some((template) => template.biomeTint?.some((value) => value === 1)) || void 0 : void 0
17535
+ };
17476
17536
  }
17477
17537
  async state(blockState3, properties) {
17478
17538
  let states = this.states.get(blockState3);
@@ -17515,10 +17575,10 @@ var SectionModels = class {
17515
17575
  }
17516
17576
  return { state, groups, choices: prepared };
17517
17577
  }
17518
- async template(variant, { model, atlas, rotation }, tints) {
17578
+ async template(variant, { model, atlas, rotation }, tints, biomeTints) {
17519
17579
  let models = this.models.get(model);
17520
17580
  if (!models) this.models.set(model, models = /* @__PURE__ */ new Map());
17521
- const key = JSON.stringify([rotation.x, rotation.y, rotation.z, variant.uvlock, tints]);
17581
+ const key = JSON.stringify([rotation.x, rotation.y, rotation.z, variant.uvlock, tints, biomeTints]);
17522
17582
  return cached(models, key, async () => {
17523
17583
  const object4 = new SectionModel(model, {
17524
17584
  instanceMeshes: false,
@@ -17532,11 +17592,13 @@ var SectionModels = class {
17532
17592
  object4.clear();
17533
17593
  const keep = [];
17534
17594
  const cullFaces = [];
17595
+ const biomeTint = biomeTints ? [] : void 0;
17535
17596
  for (const element of atlas.model.elements) {
17536
17597
  for (const face of CUBE_FACES) {
17537
17598
  const data = element.faces[face];
17538
17599
  keep.push(data !== void 0);
17539
17600
  if (!data) continue;
17601
+ biomeTint?.push(Number(data.tintindex !== void 0 && biomeTints?.[data.tintindex] !== void 0));
17540
17602
  const local = CUBE_FACES.findIndex((name) => name === data.cullface);
17541
17603
  if (local < 0) {
17542
17604
  cullFaces.push(0);
@@ -17552,6 +17614,7 @@ var SectionModels = class {
17552
17614
  geometry,
17553
17615
  atlas,
17554
17616
  cullFaces: new Uint8Array(cullFaces),
17617
+ biomeTint: biomeTint?.some(Boolean) ? new Uint8Array(biomeTint) : void 0,
17555
17618
  occludes: ModelCulling.isOpaqueFullCube(atlas) && ModelCulling.toLocalMask(63, rotation) === 63,
17556
17619
  layer: atlas.hasTranslucency ? 1 : 0,
17557
17620
  ambientOcclusion: model.ambientocclusion !== false
@@ -17571,6 +17634,7 @@ var SectionModels = class {
17571
17634
  };
17572
17635
 
17573
17636
  // src/world/_entities/WorldEntities.ts
17637
+ init_Renames();
17574
17638
  init_Entities();
17575
17639
  init_EntityObject();
17576
17640
 
@@ -17730,8 +17794,10 @@ var WorldEntities = class {
17730
17794
  try {
17731
17795
  if (!current()) return;
17732
17796
  const models = await Entities.getEntityList();
17733
- if (!models.includes(placement.key.path) || !current()) return;
17734
- const model = await Entities.getEntity(placement.key, placement.texture, { when: placement.when });
17797
+ const renamed = models.includes(placement.key.path) ? void 0 : await Renames.key("entities", placement.key);
17798
+ const key = renamed && models.includes(renamed.path) ? renamed : placement.key;
17799
+ if (!models.includes(key.path) || !current()) return;
17800
+ const model = await Entities.getEntity(key, placement.texture, { when: placement.when });
17735
17801
  if (!model || !current()) return;
17736
17802
  let tints = placement.tints;
17737
17803
  if (placement.woolDye) {
@@ -17746,7 +17812,7 @@ var WorldEntities = class {
17746
17812
  object4.rotation.y = placement.yaw;
17747
17813
  await object4.init();
17748
17814
  if (!current()) return;
17749
- placement.owner.set(object4, { type: placement.key.toNamespacedString(), position: placement.position });
17815
+ placement.owner.set(object4, { type: key.toNamespacedString(), position: placement.position });
17750
17816
  this.scene.add(object4);
17751
17817
  } catch (error) {
17752
17818
  console.warn(`Could not draw saved entity ${placement.key.toNamespacedString()}, skipping it`, error);
@@ -17778,6 +17844,132 @@ var WorldEntities = class {
17778
17844
  // src/world/MineRenderWorld.ts
17779
17845
  init_FluidQuads();
17780
17846
 
17847
+ // src/world/_biomes/BiomeBlend.ts
17848
+ init_AssetKey();
17849
+ init_AssetLoader();
17850
+ init_Caching();
17851
+ var MAX_TILES = 256;
17852
+ function validateBiomeBlendRadius(radius) {
17853
+ if (!Number.isInteger(radius) || radius < 0 || radius > 7) {
17854
+ throw new RangeError("biomeBlendRadius must be an integer from 0 to 7");
17855
+ }
17856
+ }
17857
+ var BiomeBlend = class {
17858
+ constructor(biomeAt, radius) {
17859
+ this.biomeAt = biomeAt;
17860
+ this.tiles = /* @__PURE__ */ new Map();
17861
+ this.generation = Caching.generation;
17862
+ validateBiomeBlendRadius(radius);
17863
+ this._radius = radius;
17864
+ }
17865
+ get radius() {
17866
+ return this._radius;
17867
+ }
17868
+ clear() {
17869
+ this.tiles.clear();
17870
+ }
17871
+ invalidateColumn(x, z) {
17872
+ for (const [key, tile] of this.tiles) {
17873
+ if (Math.abs(tile.x - x) <= 1 && Math.abs(tile.z - z) <= 1) this.tiles.delete(key);
17874
+ }
17875
+ }
17876
+ async getColor(kind, origin, position) {
17877
+ const [x, y, z] = position;
17878
+ const biome = this.biomeAt(x, y, z);
17879
+ if (biome === void 0) return void 0;
17880
+ if (this.generation !== Caching.generation) {
17881
+ this.clear();
17882
+ this.generation = Caching.generation;
17883
+ }
17884
+ const tileX = Math.floor(x / 16), tileZ = Math.floor(z / 16), quartY = Math.floor(y / 4);
17885
+ const root = origin?.root ?? AssetLoader.ROOT;
17886
+ const key = JSON.stringify([root, AssetLoader.persistentScope, kind, this._radius, tileX, quartY, tileZ]);
17887
+ let tile = this.tiles.get(key);
17888
+ if (tile) {
17889
+ this.tiles.delete(key);
17890
+ this.tiles.set(key, tile);
17891
+ } else {
17892
+ const snapshot = new AssetKey("minecraft", "", void 0, void 0, "assets", ".json", root);
17893
+ const colors = this.buildTile(kind, snapshot, tileX, quartY, tileZ, this._radius).then((result2) => {
17894
+ if (!result2.complete && this.tiles.get(key)?.colors === colors) this.tiles.delete(key);
17895
+ return result2;
17896
+ }, (error) => {
17897
+ if (this.tiles.get(key)?.colors === colors) this.tiles.delete(key);
17898
+ throw error;
17899
+ });
17900
+ tile = { x: tileX, z: tileZ, colors };
17901
+ this.tiles.set(key, tile);
17902
+ if (this.tiles.size > MAX_TILES) this.tiles.delete(this.tiles.keys().next().value);
17903
+ }
17904
+ const result = await tile.colors;
17905
+ if (result.failures.has(biome)) throw result.failures.get(biome);
17906
+ const color2 = result.colors[(z - tileZ * 16) * 16 + x - tileX * 16];
17907
+ return color2 === -1 ? void 0 : color2;
17908
+ }
17909
+ async buildTile(kind, origin, tileX, quartY, tileZ, radius) {
17910
+ const size = 16 + radius * 2, stride = size + 1;
17911
+ const startX = tileX * 16 - radius, startZ = tileZ * 16 - radius;
17912
+ const quartX = Math.floor(startX / 4), quartZ = Math.floor(startZ / 4);
17913
+ const quartSize = Math.floor((startX + size - 1) / 4) - quartX + 1;
17914
+ const ids = new Array(quartSize * quartSize);
17915
+ const biomes = /* @__PURE__ */ new Map();
17916
+ for (let z = 0; z < quartSize; z++) {
17917
+ for (let x = 0; x < quartSize; x++) {
17918
+ const id = this.biomeAt((quartX + x) * 4, quartY * 4, (quartZ + z) * 4);
17919
+ ids[z * quartSize + x] = id;
17920
+ if (id !== void 0 && !biomes.has(id)) {
17921
+ biomes.set(id, (async () => {
17922
+ const swamp = kind === "grass" && (await Biomes.get(id, origin))?.effects.grass_color_modifier === "swamp";
17923
+ return { color: swamp ? void 0 : await Biomes.getColor(id, kind, origin), swamp };
17924
+ })());
17925
+ }
17926
+ }
17927
+ }
17928
+ const failures = /* @__PURE__ */ new Map();
17929
+ const loaded = new Map(await Promise.all([...biomes].map(async ([id, color2]) => {
17930
+ try {
17931
+ return [id, await color2];
17932
+ } catch (error) {
17933
+ failures.set(id, error);
17934
+ return [id, { color: void 0, swamp: false }];
17935
+ }
17936
+ })));
17937
+ const complete = [...loaded.values()].every((value) => value.swamp || value.color !== void 0);
17938
+ const samples = new Int32Array(size * size).fill(-1);
17939
+ const sums = Array.from({ length: 4 }, () => new Uint32Array(stride * stride));
17940
+ for (let z = 0; z < size; z++) {
17941
+ const row = [0, 0, 0, 0];
17942
+ for (let x = 0; x < size; x++) {
17943
+ const id = ids[(Math.floor((startZ + z) / 4) - quartZ) * quartSize + Math.floor((startX + x) / 4) - quartX];
17944
+ const value = id === void 0 ? void 0 : loaded.get(id);
17945
+ const color2 = value?.swamp ? swampGrassColor(startX + x, startZ + z) : value?.color;
17946
+ if (color2 !== void 0) {
17947
+ samples[z * size + x] = color2;
17948
+ row[0] += color2 >> 16 & 255;
17949
+ row[1] += color2 >> 8 & 255;
17950
+ row[2] += color2 & 255;
17951
+ row[3]++;
17952
+ }
17953
+ for (let channel = 0; channel < 4; channel++) {
17954
+ sums[channel][(z + 1) * stride + x + 1] = sums[channel][z * stride + x + 1] + row[channel];
17955
+ }
17956
+ }
17957
+ }
17958
+ const colors = new Int32Array(256).fill(-1), width = radius * 2 + 1;
17959
+ for (let z = 0; z < 16; z++) {
17960
+ for (let x = 0; x < 16; x++) {
17961
+ if (samples[(z + radius) * size + x + radius] === -1) continue;
17962
+ const topLeft = z * stride + x, topRight = topLeft + width;
17963
+ const bottomLeft = (z + width) * stride + x, bottomRight = bottomLeft + width;
17964
+ const sum = (values) => values[bottomRight] - values[bottomLeft] - values[topRight] + values[topLeft];
17965
+ const count = sum(sums[3]);
17966
+ 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);
17967
+ }
17968
+ }
17969
+ return { colors, complete, failures };
17970
+ }
17971
+ };
17972
+
17781
17973
  // src/world/WorldLight.ts
17782
17974
  function copySectionLight(light) {
17783
17975
  const copy = {};
@@ -17841,12 +18033,17 @@ var MineRenderWorld = class {
17841
18033
  this.lightValidity = /* @__PURE__ */ new Map();
17842
18034
  this.highestSkySection = /* @__PURE__ */ new Map();
17843
18035
  this.ambientOcclusionPending = false;
18036
+ this.pendingBiomes = /* @__PURE__ */ new Map();
17844
18037
  this.pendingCulling = /* @__PURE__ */ new Map();
17845
18038
  if (options.savedLighting && !options.sectionMeshing) throw new TypeError("savedLighting requires sectionMeshing");
17846
18039
  if (options.ambientOcclusion && !options.sectionMeshing) throw new TypeError("ambientOcclusion requires sectionMeshing");
17847
18040
  if (options.ambientLight !== void 0 && (!Number.isFinite(options.ambientLight) || options.ambientLight < 0 || options.ambientLight > 1)) {
17848
18041
  throw new RangeError("ambientLight must be a number from 0 to 1");
17849
18042
  }
18043
+ this._biomeBlendRadius = options.biomeBlendRadius ?? 0;
18044
+ validateBiomeBlendRadius(this._biomeBlendRadius);
18045
+ if (this._biomeBlendRadius > 0 && !options.sectionMeshing) throw new TypeError("biomeBlendRadius requires sectionMeshing");
18046
+ if (this._biomeBlendRadius) this.biomeBlend = new BiomeBlend((x, y, z) => this.getBiomeAt(x, y, z), this._biomeBlendRadius);
17850
18047
  this.scene = scene;
17851
18048
  this.savedLighting = options.savedLighting ?? false;
17852
18049
  this.ambientLight = options.ambientLight ?? 0;
@@ -17887,6 +18084,65 @@ var MineRenderWorld = class {
17887
18084
  await this.updateCulling([]);
17888
18085
  this.ambientOcclusionPending = false;
17889
18086
  }
18087
+ /** Horizontal biome-color sampling radius, from 0 to 7 blocks. Zero disables blending. */
18088
+ get biomeBlendRadius() {
18089
+ return this._biomeBlendRadius;
18090
+ }
18091
+ /** Refreshes loaded grass, foliage, and water colors without reloading their source chunks. */
18092
+ async setBiomeBlendRadius(radius) {
18093
+ validateBiomeBlendRadius(radius);
18094
+ if (radius > 0 && !this.sectionModels) throw new TypeError("biomeBlendRadius requires sectionMeshing");
18095
+ if (radius !== this._biomeBlendRadius) {
18096
+ this._biomeBlendRadius = radius;
18097
+ this.biomeBlend?.clear();
18098
+ this.biomeBlend = radius ? new BiomeBlend((x, y, z) => this.getBiomeAt(x, y, z), radius) : void 0;
18099
+ for (const chunk of this._chunks.values()) this.pendingBiomes.set(chunk, void 0);
18100
+ }
18101
+ const failures = await this.refreshBiomes();
18102
+ if (failures.size) throw failures.values().next().value;
18103
+ }
18104
+ markBiomesChanged(x, z, y, includeColumn = true) {
18105
+ const queued = [];
18106
+ if (!this.biomeBlend) return queued;
18107
+ this.biomeBlend.invalidateColumn(x, z);
18108
+ for (const chunk of this._chunks.values()) {
18109
+ 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;
18110
+ queued.push(chunk);
18111
+ if (!this.pendingBiomes.has(chunk)) this.pendingBiomes.set(chunk, []);
18112
+ const columns = this.pendingBiomes.get(chunk);
18113
+ if (columns && !columns.some((column) => column.x === x && column.z === z)) columns.push({ x, z });
18114
+ }
18115
+ return queued;
18116
+ }
18117
+ async refreshBiomes() {
18118
+ if (this.biomeRefreshing) await this.biomeRefreshing;
18119
+ if (!this.pendingBiomes.size) return /* @__PURE__ */ new Map();
18120
+ return this.biomeRefreshing ?? (this.biomeRefreshing = Promise.resolve().then(async () => {
18121
+ const failures = /* @__PURE__ */ new Map();
18122
+ const batch = [...this.pendingBiomes.keys()];
18123
+ let sliceStart = performance.now();
18124
+ try {
18125
+ for (const chunk of batch) {
18126
+ const columns = this.pendingBiomes.get(chunk);
18127
+ this.pendingBiomes.delete(chunk);
18128
+ if (this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`) !== chunk) continue;
18129
+ try {
18130
+ await chunk.refreshBiomes(columns);
18131
+ } catch (error) {
18132
+ failures.set(chunk, error);
18133
+ if (this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`) === chunk) this.pendingBiomes.set(chunk, void 0);
18134
+ }
18135
+ if (performance.now() - sliceStart > 8) {
18136
+ await yieldToEventLoop();
18137
+ sliceStart = performance.now();
18138
+ }
18139
+ }
18140
+ return failures;
18141
+ } finally {
18142
+ this.biomeRefreshing = void 0;
18143
+ }
18144
+ }));
18145
+ }
17890
18146
  getBlockAt(posOrX, y, z) {
17891
18147
  if (typeof posOrX == "number") {
17892
18148
  return this.getBlockAt(new import_three45.Vector3(posOrX, y, z));
@@ -18122,6 +18378,9 @@ var MineRenderWorld = class {
18122
18378
  }
18123
18379
  await placeEntities?.();
18124
18380
  } finally {
18381
+ this.markBiomesChanged(chunk.x, chunk.z, void 0, false);
18382
+ const failures = await this.refreshBiomes();
18383
+ if (failures.size) console.warn("Failed to refresh biome colors", failures.values().next().value);
18125
18384
  await this.updateCulling([...changes.values()]);
18126
18385
  }
18127
18386
  }
@@ -18138,11 +18397,14 @@ var MineRenderWorld = class {
18138
18397
  try {
18139
18398
  await this.clearChunkColumn(x, z);
18140
18399
  } finally {
18400
+ const failures = await this.refreshBiomes();
18401
+ if (failures.size) console.warn("Failed to refresh biome colors", failures.values().next().value);
18141
18402
  await this.updateCulling([]);
18142
18403
  }
18143
18404
  }
18144
18405
  async clearChunkColumn(x, z) {
18145
18406
  this.biomes.delete(`${x}_${z}`);
18407
+ this.markBiomesChanged(x, z, void 0, false);
18146
18408
  if (this.savedLighting) {
18147
18409
  for (const y of this.light.get(`${x}_${z}`)?.keys() ?? []) this.markSectionChanged({ x, y, z });
18148
18410
  }
@@ -18153,6 +18415,7 @@ var MineRenderWorld = class {
18153
18415
  for (const [key, section] of previous) {
18154
18416
  this._chunks.delete(key);
18155
18417
  this.pendingCulling.delete(section);
18418
+ this.pendingBiomes.delete(section);
18156
18419
  await section.clear(false);
18157
18420
  this.markSectionChanged(section);
18158
18421
  }
@@ -18187,6 +18450,8 @@ var MineRenderWorld = class {
18187
18450
  const chunks = [...this._chunks.values()];
18188
18451
  this._chunks.clear();
18189
18452
  this.biomes.clear();
18453
+ this.biomeBlend?.clear();
18454
+ this.pendingBiomes.clear();
18190
18455
  this.light.clear();
18191
18456
  this.lightValidity.clear();
18192
18457
  this.highestSkySection.clear();
@@ -18305,8 +18570,14 @@ var MineRenderWorld = class {
18305
18570
  column?.delete(y);
18306
18571
  if (!column?.size) this.biomes.delete(columnKey);
18307
18572
  }
18308
- await chunk.refreshBiomes();
18309
- await this._chunks.get(`${x}_${y + 1}_${z}`)?.refreshBiomes();
18573
+ if (this.biomeBlend) {
18574
+ const queued = this.markBiomesChanged(x, z, y);
18575
+ const failures = await this.refreshBiomes();
18576
+ for (const chunk2 of queued) if (failures.has(chunk2)) throw failures.get(chunk2);
18577
+ } else {
18578
+ await chunk.refreshBiomes();
18579
+ await this._chunks.get(`${x}_${y + 1}_${z}`)?.refreshBiomes();
18580
+ }
18310
18581
  },
18311
18582
  this.savedLighting ? (x2, y2, z2) => this.lightLevelAt(x2, y2, z2) : void 0,
18312
18583
  async () => {
@@ -18323,7 +18594,8 @@ var MineRenderWorld = class {
18323
18594
  await this.updateCulling([]);
18324
18595
  }
18325
18596
  },
18326
- this.ambientLight
18597
+ this.ambientLight,
18598
+ () => this.biomeBlend
18327
18599
  );
18328
18600
  this._chunks.set(key, chunk);
18329
18601
  }
@@ -18442,9 +18714,10 @@ var LIGHT_SAMPLE_OFFSETS = [0, 1, -1, 324, -324, 18, -18];
18442
18714
 
18443
18715
  // src/world/SectionGeometry.ts
18444
18716
  function buildSectionGeometry(input) {
18445
- const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize, fluids, light, ambientLight, ambientOcclusion } = input;
18717
+ const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize, fluids, light, ambientLight, ambientOcclusion, biomeTints } = input;
18446
18718
  if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
18447
18719
  if (count > indices.length || count > templates.length || count > cullMasks.length) throw new RangeError("Section entry count exceeds its arrays");
18720
+ if (biomeTints && biomeTints.length !== count * 3) throw new RangeError("Section biome tints must contain RGB for each placement");
18448
18721
  if (fluids && fluids.cells.length !== 5832) throw new RangeError("Section fluid cells must cover an 18\xD718\xD718 neighborhood");
18449
18722
  if (fluids?.waterTints && fluids.waterTints.length !== 4096 * 3) throw new RangeError("Section water tints must contain RGB for 4096 cells");
18450
18723
  if (light && (light.length !== 5832 || light.some((level) => level > 15 && level !== 255))) {
@@ -18459,7 +18732,7 @@ function buildSectionGeometry(input) {
18459
18732
  const cornerBrightness2 = ambientOcclusion ? new Float32Array(4) : void 0;
18460
18733
  for (const template of templateData) {
18461
18734
  const { quads } = template;
18462
- 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) {
18735
+ 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) {
18463
18736
  throw new RangeError("Section template arrays do not match its quad count");
18464
18737
  }
18465
18738
  }
@@ -18569,6 +18842,7 @@ function buildSectionGeometry(input) {
18569
18842
  blockIndices[indexOffset / 6] = indices[entry];
18570
18843
  if (ambientMask) quadAmbientOcclusion(template.positions, template.normals, quad, ambientMask, cornerBrightness2);
18571
18844
  const brightness = light ? lightBrightness(light[lightIndex + LIGHT_SAMPLE_OFFSETS[quadLightIndex(template.positions, template.normals, quad)]], ambientLight) : 1;
18845
+ const tint = biomeTints && template.biomeTint?.[quad] && !Number.isNaN(biomeTints[entry * 3]) ? entry * 3 : void 0;
18572
18846
  const start = vertex;
18573
18847
  for (let corner = 0; corner < 4; corner++, vertex++) {
18574
18848
  const source = quad * 4 + corner;
@@ -18585,9 +18859,9 @@ function buildSectionGeometry(input) {
18585
18859
  uvBounds[vertex * 4 + 2] = mapU(template.uvBounds[source * 4 + 2]);
18586
18860
  uvBounds[vertex * 4 + 3] = mapV(template.uvBounds[source * 4 + 3]);
18587
18861
  const factor = brightness * (ambientMask ? cornerBrightness2[corner] : 1);
18588
- colors[vertex * 3] = template.colors[source * 3] * factor;
18589
- colors[vertex * 3 + 1] = template.colors[source * 3 + 1] * factor;
18590
- colors[vertex * 3 + 2] = template.colors[source * 3 + 2] * factor;
18862
+ colors[vertex * 3] = (tint === void 0 ? template.colors[source * 3] : biomeTints[tint]) * factor;
18863
+ colors[vertex * 3 + 1] = (tint === void 0 ? template.colors[source * 3 + 1] : biomeTints[tint + 1]) * factor;
18864
+ colors[vertex * 3 + 2] = (tint === void 0 ? template.colors[source * 3 + 2] : biomeTints[tint + 2]) * factor;
18591
18865
  }
18592
18866
  for (let corner = 0; corner < 6; corner++) outputIndices[indexOffset++] = start + template.indices[quad * 6 + corner];
18593
18867
  }
@@ -18708,7 +18982,11 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
18708
18982
  ...lighting
18709
18983
  };
18710
18984
  for (let i = 0; i < entries.length; i++) {
18711
- const { index, template, cullMask } = entries[i];
18985
+ const { index, template, cullMask, biomeTint } = entries[i];
18986
+ if (biomeTint) {
18987
+ input.biomeTints ?? (input.biomeTints = new Float32Array(entries.length * 3).fill(NaN));
18988
+ input.biomeTints.set(biomeTint, i * 3);
18989
+ }
18712
18990
  let id = templateIds.get(template);
18713
18991
  if (id === void 0) {
18714
18992
  let atlas = atlasIds.get(template.atlas);
@@ -18735,6 +19013,7 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
18735
19013
  colors: new Float32Array(quads * 12),
18736
19014
  indices: new Uint16Array(quads * 6),
18737
19015
  cullFaces: new Uint8Array(template.cullFaces),
19016
+ biomeTint: template.biomeTint ? new Uint8Array(template.biomeTint) : void 0,
18738
19017
  atlas: 0,
18739
19018
  layer: template.layer,
18740
19019
  ambientOcclusion: template.ambientOcclusion
@@ -18879,7 +19158,7 @@ init_CubeFace();
18879
19158
  init_InstanceReference();
18880
19159
  var WATER_TINT2 = new import_three47.Color(4159204).toArray();
18881
19160
  var Chunk = class _Chunk {
18882
- constructor(scene, x, y, z, onBlocksChanged, sectionModels, neighborCell, biomeAt, onBiomesChanged, lightAt, onLightCleared, ambientLight = 0) {
19161
+ constructor(scene, x, y, z, onBlocksChanged, sectionModels, neighborCell, biomeAt, onBiomesChanged, lightAt, onLightCleared, ambientLight = 0, biomeBlend) {
18883
19162
  this.onBlocksChanged = onBlocksChanged;
18884
19163
  this.sectionModels = sectionModels;
18885
19164
  this.neighborCell = neighborCell;
@@ -18888,6 +19167,7 @@ var Chunk = class _Chunk {
18888
19167
  this.lightAt = lightAt;
18889
19168
  this.onLightCleared = onLightCleared;
18890
19169
  this.ambientLight = ambientLight;
19170
+ this.biomeBlend = biomeBlend;
18891
19171
  this.data = new ChunkData();
18892
19172
  this.renderedBlocks = /* @__PURE__ */ new Map();
18893
19173
  this.objectBlocks = /* @__PURE__ */ new Set();
@@ -18897,6 +19177,7 @@ var Chunk = class _Chunk {
18897
19177
  this.blockPosition = new import_three47.Vector3();
18898
19178
  this.meshDirty = false;
18899
19179
  this.meshGeneration = 0;
19180
+ this.blockChanges = /* @__PURE__ */ new Set();
18900
19181
  this.scene = scene;
18901
19182
  this.x = x;
18902
19183
  this.y = y;
@@ -18995,10 +19276,20 @@ var Chunk = class _Chunk {
18995
19276
  return this.refreshBiomes();
18996
19277
  }
18997
19278
  /** Rebuilds biome-tinted blocks while preserving hidden blocks and unrelated render objects. */
18998
- async refreshBiomes() {
19279
+ refreshBiomes(columns) {
19280
+ return this.changeBlocks(() => this.refreshBiomeBlocks(columns), true);
19281
+ }
19282
+ async refreshBiomeBlocks(columns) {
19283
+ const blend = columns && this.biomeBlend?.();
19284
+ const ownColumn = columns?.some((column) => column.x === this.x && column.z === this.z);
18999
19285
  const affected = /* @__PURE__ */ new Map();
19000
19286
  const blocks = [];
19001
19287
  for (const index of this.renderedBlocks.keys()) {
19288
+ if (blend) {
19289
+ if (this.objectBlocks.has(index) && !ownColumn) continue;
19290
+ const [x, , z] = this.positionAt(index), radius = blend.radius;
19291
+ 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;
19292
+ }
19002
19293
  const block = this.data.get(index);
19003
19294
  const stateKey = ChunkData.paletteKey(block);
19004
19295
  let matches = affected.get(stateKey);
@@ -19013,10 +19304,32 @@ var Chunk = class _Chunk {
19013
19304
  }
19014
19305
  if (await matches) blocks.push({ index, block });
19015
19306
  }
19307
+ if (blend) {
19308
+ const groups = /* @__PURE__ */ new Map();
19309
+ for (const entry of blocks) {
19310
+ const key = ChunkData.paletteKey(entry.block);
19311
+ let group = groups.get(key);
19312
+ if (!group) groups.set(key, group = []);
19313
+ group.push(entry);
19314
+ }
19315
+ blocks.length = 0;
19316
+ const biomes = /* @__PURE__ */ new Map();
19317
+ for (const group of groups.values()) {
19318
+ const { contexts, fluid } = await this.resolveState(group[0].block, group.map((entry) => entry.index), biomes);
19319
+ for (const entry of group) {
19320
+ const context = contexts.get(entry.index);
19321
+ const current = this.sectionBlocks.get(entry.index)?.[0]?.biomeTint;
19322
+ const tint = context?.tint;
19323
+ const tintChanged = !!current !== !!tint || tint?.some((value, channel) => value !== current?.[channel]);
19324
+ 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])));
19325
+ if (this.objectBlocks.has(entry.index) || tintChanged || waterChanged) blocks.push(entry);
19326
+ }
19327
+ }
19328
+ }
19016
19329
  if (!blocks.length) return;
19017
19330
  const hidden = new Set(this.hiddenBlocks);
19018
19331
  try {
19019
- await this.placeBlocks(blocks);
19332
+ await this.replaceBlocks(blocks, true);
19020
19333
  } finally {
19021
19334
  for (const index of hidden) {
19022
19335
  this.hiddenBlocks.add(index);
@@ -19056,6 +19369,20 @@ var Chunk = class _Chunk {
19056
19369
  }
19057
19370
  /** Places cells in input order and returns changed world positions without refreshing neighbors. */
19058
19371
  async placeBlocks(blocks) {
19372
+ return this.changeBlocks(() => this.replaceBlocks(blocks));
19373
+ }
19374
+ changeBlocks(change, refreshBiomes = false) {
19375
+ const before = refreshBiomes ? Promise.allSettled(this.blockChanges) : this.biomeChange;
19376
+ const result = before ? before.catch(() => void 0).then(change) : change();
19377
+ const pending = result.finally(() => {
19378
+ this.blockChanges.delete(pending);
19379
+ if (this.biomeChange === pending) this.biomeChange = void 0;
19380
+ });
19381
+ this.blockChanges.add(pending);
19382
+ if (refreshBiomes) this.biomeChange = pending;
19383
+ return pending;
19384
+ }
19385
+ async replaceBlocks(blocks, preserveDataOnFailure = false) {
19059
19386
  const placements = blocks.map(({ index, block }) => ({
19060
19387
  index,
19061
19388
  key: block && !ChunkData.isAir(block) ? ChunkData.paletteKey(block) : void 0,
@@ -19075,6 +19402,10 @@ var Chunk = class _Chunk {
19075
19402
  const biomes = /* @__PURE__ */ new Map();
19076
19403
  const settled = await Promise.allSettled([...states.values()].map(({ stored, indices }) => this.resolveState(stored, indices, biomes)));
19077
19404
  const resolutions = new Map([...states.keys()].map((key, i) => [key, settled[i]]));
19405
+ if (preserveDataOnFailure) {
19406
+ const rejected = settled.find((result) => result.status === "rejected");
19407
+ if (rejected?.status === "rejected") throw rejected.reason;
19408
+ }
19078
19409
  const positions = [];
19079
19410
  let failed = false, failure;
19080
19411
  for (const { index, key, block } of placements) {
@@ -19112,7 +19443,7 @@ var Chunk = class _Chunk {
19112
19443
  }
19113
19444
  const templates = perBlock ? void 0 : fluid?.renderModel === false ? [] : (context?.prepared ?? prepared)?.pick(variantPosition);
19114
19445
  if (templates) {
19115
- this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0 })));
19446
+ this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0, biomeTint: context?.tint })));
19116
19447
  this.meshDirty = true;
19117
19448
  } else {
19118
19449
  object4 = await this.scene.addBlock(blockState3, {
@@ -19131,7 +19462,11 @@ var Chunk = class _Chunk {
19131
19462
  }, object: object4 });
19132
19463
  if (object4) this.objectBlocks.add(index);
19133
19464
  } catch (error) {
19134
- this.data.assign(index, 0);
19465
+ if (preserveDataOnFailure && readBlock) {
19466
+ this.renderedBlocks.set(index, { get block() {
19467
+ return readBlock();
19468
+ }, object: void 0 });
19469
+ } else this.data.assign(index, 0);
19135
19470
  this.fluidCells[index] = 0;
19136
19471
  object4?.removeFromScene();
19137
19472
  if (!failed) failure = error;
@@ -19142,7 +19477,10 @@ var Chunk = class _Chunk {
19142
19477
  return positions;
19143
19478
  }
19144
19479
  /** Replaces every cell from parsed section data. The caller refreshes neighbor culling. */
19145
- async placeSection(data) {
19480
+ placeSection(data) {
19481
+ return this.changeBlocks(() => this.replaceSection(data));
19482
+ }
19483
+ async replaceSection(data) {
19146
19484
  for (const info of this.renderedBlocks.values()) info.object?.removeFromScene();
19147
19485
  this.sectionBlocks.clear();
19148
19486
  this.fluidCells.fill(0);
@@ -19189,7 +19527,7 @@ var Chunk = class _Chunk {
19189
19527
  ];
19190
19528
  const templates = perBlock ? void 0 : fluid?.renderModel === false ? [] : (context?.prepared ?? prepared)?.pick(variantPosition);
19191
19529
  if (templates) {
19192
- this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0 })));
19530
+ this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0, biomeTint: context?.tint })));
19193
19531
  this.meshDirty = true;
19194
19532
  } else {
19195
19533
  perBlocks.push({ index, blockState: blockState3, biome: context?.biome });
@@ -19273,12 +19611,35 @@ var Chunk = class _Chunk {
19273
19611
  }
19274
19612
  async resolveState(stored, indices, biomes) {
19275
19613
  const blockState3 = await BlockStates.get(AssetKey.parse("blockstates", stored.type));
19276
- const perBlock = !blockState3 || !this.sectionModels || !!(blockState3.key && BlockEntities.entry(await BlockEntities.getIndex(blockState3.key.root), blockState3.key.toNamespacedString()));
19614
+ let perBlock = !blockState3 || !this.sectionModels || !!(blockState3.key && BlockEntities.entry(await BlockEntities.getIndex(blockState3.key.root), blockState3.key.toNamespacedString()));
19277
19615
  const fluid = await getBlockFluidState(blockState3?.key, stored.properties);
19278
19616
  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;
19279
19617
  const source = hasBiomes && blockState3 ? await BlockTints.biomeSource(blockState3.key) : void 0;
19280
19618
  const prepare = (color2) => perBlock || fluid?.renderModel === false ? void 0 : this.sectionModels.prepareState(blockState3, stored.properties, color2);
19281
19619
  const contexts = /* @__PURE__ */ new Map();
19620
+ const blend = this.biomeBlend?.();
19621
+ if (blend && !perBlock && blockState3 && (source || fluid?.kind === "water")) {
19622
+ const prepared2 = fluid?.renderModel === false ? void 0 : await this.sectionModels.prepareState(blockState3, stored.properties, void 0, true);
19623
+ if (!prepared2 && fluid?.renderModel !== false) {
19624
+ perBlock = true;
19625
+ } else {
19626
+ await Promise.all(indices.map(async (index) => {
19627
+ const position = this.biomePosition(index, blockState3, stored);
19628
+ const biomeColor = (kind) => blend.getColor(kind, blockState3.key, position);
19629
+ const context = {};
19630
+ if (source && prepared2?.biomeTint) {
19631
+ const color2 = await biomeColor(source);
19632
+ if (color2 !== void 0) context.tint = new import_three47.Color(color2).toArray();
19633
+ }
19634
+ if (fluid?.kind === "water") {
19635
+ const color2 = await biomeColor("water");
19636
+ if (color2 !== void 0 && color2 !== 4159204) context.waterTint = new import_three47.Color(color2).toArray();
19637
+ }
19638
+ contexts.set(index, context);
19639
+ }));
19640
+ return { blockState: blockState3, perBlock, fluid, prepared: prepared2, contexts, stored };
19641
+ }
19642
+ }
19282
19643
  const groups = /* @__PURE__ */ new Map();
19283
19644
  if (blockState3 && (source || fluid?.kind === "water")) {
19284
19645
  for (const index of indices) {
@@ -19326,7 +19687,11 @@ var Chunk = class _Chunk {
19326
19687
  this.waterTints.set(tint ?? WATER_TINT2, index * 3);
19327
19688
  }
19328
19689
  /** Changes visibility at world block coordinates while preserving the block's data. */
19329
- async setBlockVisibleAt(pos, visible) {
19690
+ setBlockVisibleAt(pos, visible) {
19691
+ const position = pos.clone();
19692
+ return this.changeBlocks(() => this.setBlockVisibility(position, visible));
19693
+ }
19694
+ async setBlockVisibility(pos, visible) {
19330
19695
  const index = _Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos));
19331
19696
  const block = this.renderedBlocks.get(index);
19332
19697
  if (!block || visible === !this.hiddenBlocks.has(index)) {
@@ -19496,6 +19861,7 @@ var Chunk = class _Chunk {
19496
19861
  }
19497
19862
  /** Removes stored blocks and render objects. Set `notify` to false to skip the owning world's update. */
19498
19863
  async clear(notify = true) {
19864
+ if (this.blockChanges.size) await Promise.allSettled(this.blockChanges);
19499
19865
  const positions = notify && this.onBlocksChanged ? [...this.renderedBlocks.keys()].map((index) => this.chunkPosToWorldPos(
19500
19866
  new import_three47.Vector3(index % 16, Math.floor(index / 256), Math.floor(index / 16) % 16)
19501
19867
  )) : [];