minecraft-renderer 0.1.72 → 0.1.74

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.
Files changed (70) hide show
  1. package/README.md +1 -1
  2. package/dist/mesher.js +81 -81
  3. package/dist/mesher.js.map +3 -3
  4. package/dist/mesherWasm.js +1183 -943
  5. package/dist/minecraft-renderer.js +253 -80
  6. package/dist/minecraft-renderer.js.meta.json +1 -1
  7. package/dist/threeWorker.js +1735 -1002
  8. package/package.json +3 -3
  9. package/src/graphicsBackend/config.ts +4 -0
  10. package/src/graphicsBackend/rendererDefaultOptions.ts +2 -7
  11. package/src/graphicsBackend/rendererOptionsSync.ts +1 -1
  12. package/src/graphicsBackend/types.ts +1 -0
  13. package/src/lib/bakeLegacyLight.ts +17 -0
  14. package/src/lib/bindAbortableListener.ts +1 -1
  15. package/src/lib/blockEntityLightRegistry.test.ts +18 -0
  16. package/src/lib/blockEntityLightRegistry.ts +75 -0
  17. package/src/lib/blockEntityLighting.test.ts +30 -0
  18. package/src/lib/blockEntityLighting.ts +53 -0
  19. package/src/lib/createPlayerObject.ts +1 -1
  20. package/src/lib/worldrendererCommon.reconfigure.test.ts +4 -1
  21. package/src/lib/worldrendererCommon.removeColumn.test.ts +8 -4
  22. package/src/lib/worldrendererCommon.ts +15 -7
  23. package/src/mesher-shared/blockEntityMetadata.test.ts +33 -0
  24. package/src/mesher-shared/blockEntityMetadata.ts +19 -3
  25. package/src/mesher-shared/exportedGeometryTypes.ts +11 -0
  26. package/src/mesher-shared/models.ts +161 -92
  27. package/src/mesher-shared/shared.ts +15 -4
  28. package/src/mesher-shared/tests/liquidQuadInvariant.test.ts +40 -0
  29. package/src/mesher-shared/world.ts +12 -0
  30. package/src/mesher-shared/worldLighting.test.ts +54 -0
  31. package/src/playground/baseScene.ts +1 -1
  32. package/src/three/bannerRenderer.ts +14 -4
  33. package/src/three/chunkMeshManager.ts +663 -69
  34. package/src/three/cubeDrawSpans.ts +74 -0
  35. package/src/three/cubeMultiDraw.ts +119 -0
  36. package/src/three/documentRenderer.ts +0 -2
  37. package/src/three/entities.ts +7 -7
  38. package/src/three/entity/EntityMesh.ts +7 -5
  39. package/src/three/entity/gltfAnimationUtils.ts +5 -3
  40. package/src/three/globalBlockBuffer.ts +208 -12
  41. package/src/three/globalLegacyBuffer.ts +701 -0
  42. package/src/three/graphicsBackendBase.ts +9 -5
  43. package/src/three/itemMesh.ts +6 -3
  44. package/src/three/legacySectionCull.ts +85 -0
  45. package/src/three/modules/rain.ts +22 -21
  46. package/src/three/modules/sciFiWorldReveal.ts +347 -703
  47. package/src/three/modules/starfield.ts +19 -6
  48. package/src/three/sectionRaycastAabb.ts +25 -0
  49. package/src/three/shaders/cubeBlockShader.ts +80 -17
  50. package/src/three/shaders/legacyBlockShader.ts +292 -0
  51. package/src/three/skyboxRenderer.ts +1 -1
  52. package/src/three/tests/chunkMeshManagerLegacy.test.ts +286 -0
  53. package/src/three/tests/cubeDrawSpans.test.ts +73 -0
  54. package/src/three/tests/globalLegacyBuffer.test.ts +360 -0
  55. package/src/three/tests/legacySectionCull.test.ts +80 -0
  56. package/src/three/tests/signTextureCache.test.ts +83 -0
  57. package/src/three/threeJsMedia.ts +2 -2
  58. package/src/three/waypointSprite.ts +2 -2
  59. package/src/three/world/cursorBlock.ts +1 -0
  60. package/src/three/world/vr.ts +2 -2
  61. package/src/three/worldGeometryExport.ts +83 -26
  62. package/src/three/worldRendererThree.ts +100 -30
  63. package/src/wasm-mesher/bridge/render-from-wasm.ts +214 -72
  64. package/src/wasm-mesher/bridge/shaderCubeBridge.ts +18 -6
  65. package/src/wasm-mesher/runtime-build/wasm_mesher_bg.wasm +0 -0
  66. package/src/wasm-mesher/tests/sectionRaycastAabb.test.ts +20 -0
  67. package/src/wasm-mesher/tests/shaderCubeInstances.test.ts +80 -12
  68. package/src/wasm-mesher/worker/mesherWasm.ts +70 -14
  69. package/src/wasm-mesher/worker/mesherWasmLightDirty.test.ts +11 -0
  70. package/src/wasm-mesher/worker/mesherWasmLightDirty.ts +15 -0
@@ -16,12 +16,19 @@ import {
16
16
  SHADER_CUBES_WORDS_PER_FACE,
17
17
  } from '../bridge/shaderCubeBridge'
18
18
  import { GlobalBlockBuffer } from '../../three/globalBlockBuffer'
19
- import { createCubeBlockMaterial } from '../../three/shaders/cubeBlockShader'
19
+ import { buildVisibleCubeSpans } from '../../three/cubeDrawSpans'
20
+ import { createCubeBlockMaterial, computeSectionOriginRel } from '../../three/shaders/cubeBlockShader'
20
21
  import * as THREE from 'three'
21
22
  import { renderWasmOutputToGeometry } from '../bridge/render-from-wasm'
22
23
 
23
24
  const VERSION = '1.16.5'
24
25
  const STONE = 1
26
+
27
+ function requireShaderCubeResources() {
28
+ const resources = getShaderCubeResources()
29
+ if (!resources) throw new Error('shader cube resources unavailable in test')
30
+ return resources
31
+ }
25
32
  /** mc-assets blocksAtlases.json → stone */
26
33
  const STONE_ATLAS_TILE_INDEX = 552
27
34
 
@@ -38,7 +45,7 @@ test('packWord2: AO diagonal flip sets bit 12', () => {
38
45
  light_data: [[1, 1, 1, 1]],
39
46
  light_combined: [[255, 255, 255, 255]],
40
47
  }
41
- const { textureIndexMapping, tintPalette } = getShaderCubeResources()
48
+ const { textureIndexMapping, tintPalette } = requireShaderCubeResources()
42
49
  const model = {
43
50
  elements: [{
44
51
  faces: {
@@ -77,7 +84,7 @@ test('packWord0: section-local lx/ly/lz and face id', () => {
77
84
  light_data: [[0.5, 0.5, 0.5, 0.5]],
78
85
  light_combined: [[128, 128, 128, 128]],
79
86
  }
80
- const { textureIndexMapping, tintPalette } = getShaderCubeResources()
87
+ const { textureIndexMapping, tintPalette } = requireShaderCubeResources()
81
88
  const model = {
82
89
  elements: [{
83
90
  faces: {
@@ -110,7 +117,7 @@ test('packWord0: section-local lx/ly/lz and face id', () => {
110
117
  })
111
118
 
112
119
  test('isShaderCubeBlock: rejects model rotation and sectionHeight !== 16', () => {
113
- const { textureIndexMapping } = getShaderCubeResources()
120
+ const { textureIndexMapping } = requireShaderCubeResources()
114
121
  const baseModel = {
115
122
  elements: [{
116
123
  faces: {
@@ -221,7 +228,7 @@ test('south face: AO corners remapped to shader order (elemFaces [0,3,1,2] → s
221
228
  light_data: [[1, 1, 1, 1]],
222
229
  light_combined: [[10, 20, 30, 40]],
223
230
  }
224
- const { textureIndexMapping, tintPalette } = getShaderCubeResources()
231
+ const { textureIndexMapping, tintPalette } = requireShaderCubeResources()
225
232
  const model = { elements: [{ faces: SIX_FACE_TEXTURES }] }
226
233
  tryBuildShaderCubeInstances(
227
234
  block,
@@ -247,7 +254,7 @@ test('south face: diagonal flip uses remapped AO (differs from raw elemFaces for
247
254
  light_data: [[1, 1, 1, 1]],
248
255
  light_combined: [[255, 255, 255, 255]],
249
256
  }
250
- const { textureIndexMapping, tintPalette } = getShaderCubeResources()
257
+ const { textureIndexMapping, tintPalette } = requireShaderCubeResources()
251
258
  const model = { elements: [{ faces: SIX_FACE_TEXTURES }] }
252
259
  const opts = {
253
260
  sectionOrigin: { x: 0, y: 0, z: 0 },
@@ -284,7 +291,7 @@ test('doAO false: full bright AO/light and no diagonal flip', () => {
284
291
  light_data: [[0, 0, 0, 0]],
285
292
  light_combined: [[0, 0, 0, 0]],
286
293
  }
287
- const { textureIndexMapping, tintPalette } = getShaderCubeResources()
294
+ const { textureIndexMapping, tintPalette } = requireShaderCubeResources()
288
295
  const model = { elements: [{ faces: SIX_FACE_TEXTURES }] }
289
296
  tryBuildShaderCubeInstances(
290
297
  block,
@@ -328,7 +335,7 @@ test.each(SECTION_ORIGIN_ROUND_TRIP_CASES)(
328
335
  light_data: [[1, 1, 1, 1]],
329
336
  light_combined: [[255, 255, 255, 255]],
330
337
  }
331
- const { textureIndexMapping, tintPalette } = getShaderCubeResources()
338
+ const { textureIndexMapping, tintPalette } = requireShaderCubeResources()
332
339
  const model = { elements: [{ faces: SIX_FACE_TEXTURES }] }
333
340
  tryBuildShaderCubeInstances(
334
341
  block,
@@ -349,6 +356,38 @@ test('packWord2Empty: bit 18 set regardless of high X/Z bits in word2', () => {
349
356
  expect(withHighBits & (1 << WORD2.EMPTY_SHIFT)).not.toBe(0)
350
357
  })
351
358
 
359
+ test('section index relative decode past 2^20: exact integer subtract', () => {
360
+ const sectionBlockX = 21_050_000
361
+ const renderOrigin = { x: 21_000_000, y: 0, z: 0 }
362
+ const words: number[] = []
363
+ const block = {
364
+ position: [0, 0, 0] as [number, number, number],
365
+ visible_faces: 1 << 2,
366
+ ao_data: [[3, 3, 3, 3]],
367
+ light_data: [[1, 1, 1, 1]],
368
+ light_combined: [[255, 255, 255, 255]],
369
+ }
370
+ const { textureIndexMapping, tintPalette } = requireShaderCubeResources()
371
+ const model = { elements: [{ faces: SIX_FACE_TEXTURES }] }
372
+ tryBuildShaderCubeInstances(
373
+ block,
374
+ { blockName: 'stone', blockProps: {}, isCube: true, model },
375
+ model,
376
+ {
377
+ sectionOrigin: { x: sectionBlockX, y: 0, z: 0 },
378
+ sectionHeight: 16,
379
+ tintPalette,
380
+ textureIndexMapping,
381
+ },
382
+ words,
383
+ )
384
+ const base = decodeSectionBaseFromWords(words[2]!, words[3]!)
385
+ const sX = base.x / 16
386
+ const sectionOriginRel = computeSectionOriginRel(renderOrigin)
387
+ const sXr = sX - sectionOriginRel.x
388
+ expect(sXr * 16).toBe(sectionBlockX - renderOrigin.x)
389
+ })
390
+
352
391
  test('GlobalBlockBuffer: free-list reuses slot with EMPTY sentinel', () => {
353
392
  const scene = new THREE.Scene()
354
393
  const mat = createCubeBlockMaterial()
@@ -661,6 +700,35 @@ test('GlobalBlockBuffer: takeSectionData reads relocated section slot', () => {
661
700
  mat.dispose()
662
701
  })
663
702
 
703
+ test('GlobalBlockBuffer: pendingMove draw start uses oldStart for visible spans', () => {
704
+ const scene = new THREE.Scene()
705
+ const mat = createCubeBlockMaterial()
706
+ const buffer = new GlobalBlockBuffer(mat, scene)
707
+
708
+ buffer.addSection('a', makeSectionWords([10]), 1)
709
+ buffer.addSection('b', makeSectionWords([20]), 1)
710
+ buffer.addSection('c', makeSectionWords([30]), 1)
711
+ buffer.removeSection('b')
712
+ drainAllUploads(buffer)
713
+
714
+ buffer.compactStep()
715
+ const move = buffer.getPendingMove()
716
+ expect(move?.key).toBe('c')
717
+
718
+ const slotStart = buffer.getSectionSlot('c')!.start
719
+ expect(buffer.getSectionDrawStart('c')).toBe(move!.oldStart)
720
+ expect(buffer.getSectionDrawStart('c')).not.toBe(slotStart)
721
+
722
+ const spans = buildVisibleCubeSpans(
723
+ [{ start: buffer.getSectionDrawStart('c')!, count: 1 }],
724
+ buffer.getHighWatermark(),
725
+ )
726
+ expect(spans[0]?.start).toBe(move!.oldStart)
727
+
728
+ buffer.dispose()
729
+ mat.dispose()
730
+ })
731
+
664
732
  test('GlobalBlockBuffer: uploadDirtyRange budgets large dirty span across frames', () => {
665
733
  const scene = new THREE.Scene()
666
734
  const mat = createCubeBlockMaterial()
@@ -679,12 +747,12 @@ test('GlobalBlockBuffer: uploadDirtyRange budgets large dirty span across frames
679
747
 
680
748
  const w0Attr = buffer.mesh.geometry.getAttribute('a_w0') as THREE.InstancedBufferAttribute
681
749
  buffer.uploadDirtyRange()
682
- expect(w0Attr.updateRange.offset).toBe(0)
683
- expect(w0Attr.updateRange.count).toBe(15_000)
750
+ expect(w0Attr.updateRanges[0].start).toBe(0)
751
+ expect(w0Attr.updateRanges[0].count).toBe(15_000)
684
752
 
685
753
  buffer.uploadDirtyRange()
686
- expect(w0Attr.updateRange.offset).toBe(15_000)
687
- expect(w0Attr.updateRange.count).toBe(5_000)
754
+ expect(w0Attr.updateRanges[0].start).toBe(15_000)
755
+ expect(w0Attr.updateRanges[0].count).toBe(5_000)
688
756
 
689
757
  buffer.uploadDirtyRange()
690
758
  expect((buffer as unknown as { pendingRanges: unknown[] }).pendingRanges).toHaveLength(0)
@@ -8,6 +8,7 @@ import { worldColumnKey, World } from '../../mesher-shared/world'
8
8
  import { handleGetHeightmap, EMPTY_COLUMN_HEIGHTMAP_SENTINEL } from '../../mesher-shared/computeHeightmap'
9
9
  import { collectBlockEntityMetadata, type SignMeta, type HeadMeta, type BannerMeta } from '../../mesher-shared/blockEntityMetadata'
10
10
  import { SectionRequestTracker } from './mesherWasmRequestTracker'
11
+ import { sectionYsForLightColumnDirty } from './mesherWasmLightDirty'
11
12
  import {
12
13
  CONVERSION_CACHE_LIMIT,
13
14
  clearConversionCache,
@@ -18,6 +19,7 @@ import {
18
19
 
19
20
  let wasm: typeof import('../runtime-build/wasm_mesher.js') | null = null
20
21
  let wasmInitialized = false
22
+ let wasmReady = false // true ONLY after wasm.default() instantiates the module; gates light-packet parsing
21
23
 
22
24
  // Pending raw `update_light` packets that arrived before WASM finished
23
25
  // loading. Parsed and drained once `initWasm` resolves. Without this queue
@@ -33,7 +35,7 @@ const pendingUpdateLightV17: Array<{ rawPacket: Uint8Array, numSections: number
33
35
  const pendingUpdateLightV16: Array<{ rawPacket: Uint8Array }> = []
34
36
 
35
37
  function processUpdateLightV17 (rawPacket: Uint8Array, numSections: number): void {
36
- if (!wasm || !(wasm as any).parseUpdateLightV17) {
38
+ if (!wasmReady) {
37
39
  pendingUpdateLightV17.push({ rawPacket, numSections })
38
40
  return
39
41
  }
@@ -41,11 +43,13 @@ function processUpdateLightV17 (rawPacket: Uint8Array, numSections: number): voi
41
43
  const parsed: any = (wasm as any).parseUpdateLightV17(rawPacket, numSections)
42
44
  const x = (parsed.x as number) * 16
43
45
  const z = (parsed.z as number) * 16
46
+ const skyLight = parsed.skyLight as Uint8Array
44
47
  updateLightV17Cache.set(rawCacheKey(x, z), {
45
- skyLight: parsed.skyLight as Uint8Array,
48
+ skyLight,
46
49
  blockLight: parsed.blockLight as Uint8Array,
47
50
  })
48
51
  invalidateConversion(x, z)
52
+ dirtyColumnSectionsForLightUpdate(x, z)
49
53
  } catch (err) {
50
54
  console.warn('[WASM Mesher] parseUpdateLightV17 failed:', err)
51
55
  }
@@ -57,7 +61,7 @@ function processUpdateLightV17 (rawPacket: Uint8Array, numSections: number): voi
57
61
  // chunk cache has the entry, and crossing the streams could mismatch a
58
62
  // stale 1.17 column with 1.16 light or vice versa during version switches.
59
63
  function processUpdateLightV16 (rawPacket: Uint8Array): void {
60
- if (!wasm || !(wasm as any).parseUpdateLightV17) {
64
+ if (!wasmReady) {
61
65
  pendingUpdateLightV16.push({ rawPacket })
62
66
  return
63
67
  }
@@ -70,6 +74,7 @@ function processUpdateLightV16 (rawPacket: Uint8Array): void {
70
74
  blockLight: parsed.blockLight as Uint8Array,
71
75
  })
72
76
  invalidateConversion(x, z)
77
+ dirtyColumnSectionsForLightUpdate(x, z)
73
78
  } catch (err) {
74
79
  console.warn('[WASM Mesher] parseUpdateLightV17 (v16) failed:', err)
75
80
  }
@@ -81,6 +86,7 @@ async function initWasm() {
81
86
  wasmInitialized = true
82
87
  wasm = await import('../runtime-build/wasm_mesher.js')
83
88
  await wasm.default('/wasm_mesher_bg.wasm') as any
89
+ wasmReady = true // instance is now usable; drained packets below will pass the guard
84
90
 
85
91
  if (pendingUpdateLightV17.length > 0) {
86
92
  console.log('[WASM Mesher] draining', pendingUpdateLightV17.length, 'pending update_light v17 packets')
@@ -94,7 +100,10 @@ async function initWasm() {
94
100
  for (const item of queue) processUpdateLightV16(item.rawPacket)
95
101
  }
96
102
  } catch (err) {
97
- console.error('Failed to initialize WASM mesher:', err)
103
+ console.error(
104
+ '[WASM Mesher] Failed to initialize WASM mesher — block lighting may stay at full brightness:',
105
+ err,
106
+ )
98
107
  wasmInitialized = true // Don't try to initialize again
99
108
  // Don't throw - allow worker to continue without WASM (will fail on first use)
100
109
  }
@@ -195,6 +204,15 @@ function setSectionDirty(pos: Vec3, value = true) {
195
204
  }
196
205
  }
197
206
 
207
+ /** Re-mesh every section in a column after `update_light` updates the light cache. */
208
+ function dirtyColumnSectionsForLightUpdate (x: number, z: number) {
209
+ const worldMinY = config?.worldMinY ?? 0
210
+ const worldMaxY = config?.worldMaxY ?? 256
211
+ for (const y of sectionYsForLightColumnDirty(worldMinY, worldMaxY, SECTION_HEIGHT)) {
212
+ setSectionDirty(new Vec3(x, y, z))
213
+ }
214
+ }
215
+
198
216
  const softCleanup = () => {
199
217
  world = new World(world.config.version)
200
218
  globalThis.world = world
@@ -340,7 +358,7 @@ const convertParsedV17ToWasm = (
340
358
  } else {
341
359
  blockLight = new Uint8Array(totalBlocks)
342
360
  skyLight = new Uint8Array(totalBlocks)
343
- skyLight.fill(15)
361
+ skyLight.fill(config?.skyLight ?? 15)
344
362
  }
345
363
  const biomesArray: Uint8Array = parsed.biomes
346
364
  let blockCount = 0
@@ -406,7 +424,7 @@ const convertParsedV16ToWasm = (
406
424
  } else {
407
425
  blockLight = new Uint8Array(totalBlocks)
408
426
  skyLight = new Uint8Array(totalBlocks)
409
- skyLight.fill(15)
427
+ skyLight.fill(config?.skyLight ?? 15)
410
428
  }
411
429
  const biomesArray: Uint8Array = parsed.biomes
412
430
  let blockCount = 0
@@ -465,7 +483,7 @@ const meshColumnFromRawV18Plus = (
465
483
  meta.occludingBlocks,
466
484
  config?.enableLighting !== false,
467
485
  config?.smoothLighting !== false,
468
- config?.skyLight || 15
486
+ config?.skyLight ?? 15
469
487
  )
470
488
  } catch (err) {
471
489
  console.warn('[WASM Mesher] generateGeometryFromMapChunkV18Plus failed, falling back:', err)
@@ -511,7 +529,7 @@ const meshColumnFromParsedV16V17 = (
511
529
  meta.occludingBlocks,
512
530
  config?.enableLighting !== false,
513
531
  config?.smoothLighting !== false,
514
- config?.skyLight || 15
532
+ config?.skyLight ?? 15
515
533
  )
516
534
  } catch (err) {
517
535
  console.warn('[WASM Mesher] generateGeometryFromParsedV16V17 failed, falling back:', err)
@@ -572,7 +590,7 @@ const meshMultiColumnsFromRawV18Plus = (
572
590
  meta.occludingBlocks,
573
591
  config?.enableLighting !== false,
574
592
  config?.smoothLighting !== false,
575
- config?.skyLight || 15
593
+ config?.skyLight ?? 15
576
594
  )
577
595
  } catch (err) {
578
596
  console.warn('[WASM Mesher] generateGeometryFromMapChunkV18PlusMulti failed:', err)
@@ -672,7 +690,7 @@ const meshMultiColumnsFromParsedV16V17 = (
672
690
  meta.occludingBlocks,
673
691
  config?.enableLighting !== false,
674
692
  config?.smoothLighting !== false,
675
- config?.skyLight || 15
693
+ config?.skyLight ?? 15
676
694
  )
677
695
  } catch (err) {
678
696
  console.warn('[WASM Mesher] generateGeometryFromParsedV16V17Multi failed:', err)
@@ -976,6 +994,8 @@ function makeEmptyColumnGeometry(sx: number, sy: number, sz: number, sectionHeig
976
994
  positions: new Float32Array(0),
977
995
  normals: new Float32Array(0),
978
996
  colors: new Float32Array(0),
997
+ skyLights: new Float32Array(0),
998
+ blockLights: new Float32Array(0),
979
999
  uvs: new Float32Array(0),
980
1000
  indices: new Uint32Array(0),
981
1001
  indicesCount: 0,
@@ -1033,6 +1053,7 @@ function processColumnTick() {
1033
1053
  let preCacheHits = 0
1034
1054
  let preCacheMisses = 0
1035
1055
  let hadError = false
1056
+ let columnMeshPath = 'none'
1036
1057
  // Outer-scope timestamps so we can finalize `processTime` and
1037
1058
  // `postPhase` AFTER the per-section emit loop runs (the loop builds
1038
1059
  // typed arrays, walks block-entity metadata, and calls postMessage —
@@ -1052,6 +1073,7 @@ function processColumnTick() {
1052
1073
  let wasmResult: any
1053
1074
  let t1 = 0
1054
1075
  let usedFusedPath = false
1076
+ columnMeshPath = 'none'
1055
1077
 
1056
1078
  const meta = getBlockMeta(version)
1057
1079
 
@@ -1067,6 +1089,7 @@ function processColumnTick() {
1067
1089
 
1068
1090
  if (rawEntry) {
1069
1091
  wasmResult = meshColumnFromRawV18Plus(rawEntry, x, z, worldMinY, worldMaxY, meta)
1092
+ if (wasmResult) columnMeshPath = 'v18_fused'
1070
1093
  } else if (v17Entry) {
1071
1094
  const v17Light = updateLightV17Cache.get(rawCacheKey(x, z))
1072
1095
  wasmResult = meshColumnFromParsedV16V17(
@@ -1075,6 +1098,7 @@ function processColumnTick() {
1075
1098
  v17Light?.skyLight ?? null, v17Light?.blockLight ?? null,
1076
1099
  x, z, worldMinY, worldMaxY, meta
1077
1100
  )
1101
+ if (wasmResult) columnMeshPath = 'v17_fused'
1078
1102
  } else if (v16Entry) {
1079
1103
  const v16Light = updateLightV16Cache.get(rawCacheKey(x, z))
1080
1104
  const bitMapLoHi = new Uint32Array([v16Entry.bitMap >>> 0, 0])
@@ -1084,6 +1108,7 @@ function processColumnTick() {
1084
1108
  v16Light?.skyLight ?? null, v16Light?.blockLight ?? null,
1085
1109
  x, z, worldMinY, worldMaxY, meta
1086
1110
  )
1111
+ if (wasmResult) columnMeshPath = 'v16_fused'
1087
1112
  }
1088
1113
 
1089
1114
  if (wasmResult) {
@@ -1104,6 +1129,7 @@ function processColumnTick() {
1104
1129
  wasmResult = meshMultiColumnsFromRawV18Plus(chunksToUse, x, z, worldMinY, worldMaxY, meta)
1105
1130
  ?? meshMultiColumnsFromParsedV16V17(chunksToUse, x, z, worldMinY, worldMaxY, meta)
1106
1131
  if (wasmResult) {
1132
+ columnMeshPath = 'multi_fused'
1107
1133
  usedFusedPath = true
1108
1134
  t1 = performance.now()
1109
1135
  wasmPhase = t1 - t0
@@ -1181,8 +1207,9 @@ function processColumnTick() {
1181
1207
  invisibleBlocks, transparentBlocks, noAoBlocks, cullIdenticalBlocks, occludingBlocks,
1182
1208
  config?.enableLighting !== false,
1183
1209
  config?.smoothLighting !== false,
1184
- config?.skyLight || 15
1210
+ config?.skyLight ?? 15
1185
1211
  )
1212
+ columnMeshPath = 'two_step_single'
1186
1213
  } else {
1187
1214
  const tBuildStart = performance.now()
1188
1215
  const perChunkLen = conversions[0].blockStates.length
@@ -1214,8 +1241,9 @@ function processColumnTick() {
1214
1241
  invisibleBlocks, transparentBlocks, noAoBlocks, cullIdenticalBlocks, occludingBlocks,
1215
1242
  config?.enableLighting !== false,
1216
1243
  config?.smoothLighting !== false,
1217
- config?.skyLight || 15
1244
+ config?.skyLight ?? 15
1218
1245
  )
1246
+ columnMeshPath = 'two_step_multi'
1219
1247
  }
1220
1248
  }
1221
1249
 
@@ -1306,14 +1334,16 @@ function processColumnTick() {
1306
1334
  for (cursor.x = sx; cursor.x < sx + 16; cursor.x++) {
1307
1335
  const b = world.getBlock(cursor)
1308
1336
  if (!b) continue
1309
- collectBlockEntityMetadata(b, cursor.x, cursor.y, cursor.z, beTarget, beOpts)
1337
+ collectBlockEntityMetadata(b, cursor.x, cursor.y, cursor.z, beTarget, beOpts, world)
1310
1338
  }
1311
1339
  }
1312
1340
  }
1313
1341
 
1314
1342
  let geometry: MesherGeometryOutput
1315
1343
  let transferable: any[] = []
1316
- const hasLegacyMesh = (exported?.geometry.indices.length ?? 0) > 0
1344
+ const hasOpaqueMesh = (exported?.geometry.indices.length ?? 0) > 0
1345
+ const hasBlendMesh = (exported?.blendGeometry?.indices.length ?? 0) > 0
1346
+ const hasLegacyMesh = hasOpaqueMesh || hasBlendMesh
1317
1347
  const hasShaderCubes = (exported?.shaderCubes?.count ?? 0) > 0
1318
1348
  if (exported && (hasLegacyMesh || hasShaderCubes)) {
1319
1349
  const maxIndex = exported.geometry.indices.length > 0
@@ -1335,6 +1365,8 @@ function processColumnTick() {
1335
1365
  positions: new Float32Array(exported.geometry.positions),
1336
1366
  normals: new Float32Array(exported.geometry.normals),
1337
1367
  colors: new Float32Array(exported.geometry.colors),
1368
+ skyLights: new Float32Array(exported.geometry.skyLights),
1369
+ blockLights: new Float32Array(exported.geometry.blockLights),
1338
1370
  uvs: new Float32Array(exported.geometry.uvs),
1339
1371
  indices: using32Array
1340
1372
  ? new Uint32Array(exported.geometry.indices)
@@ -1360,13 +1392,37 @@ function processColumnTick() {
1360
1392
  formatVersion: 3,
1361
1393
  }
1362
1394
  }
1395
+ if (exported.blendGeometry && hasBlendMesh) {
1396
+ const blendMax = Math.max(...exported.blendGeometry.indices)
1397
+ geometry.blend = {
1398
+ positions: new Float32Array(exported.blendGeometry.positions),
1399
+ normals: new Float32Array(exported.blendGeometry.normals),
1400
+ colors: new Float32Array(exported.blendGeometry.colors),
1401
+ skyLights: new Float32Array(exported.blendGeometry.skyLights),
1402
+ blockLights: new Float32Array(exported.blendGeometry.blockLights),
1403
+ uvs: new Float32Array(exported.blendGeometry.uvs),
1404
+ indices: blendMax > 65535
1405
+ ? new Uint32Array(exported.blendGeometry.indices)
1406
+ : new Uint16Array(exported.blendGeometry.indices),
1407
+ }
1408
+ }
1363
1409
  transferable = [
1364
1410
  geometry.positions?.buffer,
1365
1411
  geometry.normals?.buffer,
1366
1412
  geometry.colors?.buffer,
1413
+ geometry.skyLights?.buffer,
1414
+ geometry.blockLights?.buffer,
1367
1415
  geometry.uvs?.buffer,
1368
1416
  //@ts-ignore
1369
1417
  geometry.indices?.buffer,
1418
+ geometry.blend?.positions?.buffer,
1419
+ geometry.blend?.normals?.buffer,
1420
+ geometry.blend?.colors?.buffer,
1421
+ geometry.blend?.skyLights?.buffer,
1422
+ geometry.blend?.blockLights?.buffer,
1423
+ geometry.blend?.uvs?.buffer,
1424
+ //@ts-ignore
1425
+ geometry.blend?.indices?.buffer,
1370
1426
  geometry.shaderCubes?.words?.buffer,
1371
1427
  ].filter(Boolean)
1372
1428
 
@@ -0,0 +1,11 @@
1
+ //@ts-nocheck
2
+ import { describe, expect, it } from 'vitest'
3
+ import { sectionYsForLightColumnDirty } from './mesherWasmLightDirty'
4
+
5
+ describe('sectionYsForLightColumnDirty', () => {
6
+ it('covers every section in a 256-high overworld column', () => {
7
+ expect(sectionYsForLightColumnDirty(0, 256)).toEqual(
8
+ Array.from({ length: 16 }, (_, i) => i * 16),
9
+ )
10
+ })
11
+ })
@@ -0,0 +1,15 @@
1
+ //@ts-nocheck
2
+ import { SECTION_HEIGHT } from '../../mesher-shared/shared'
3
+
4
+ /** Section Y values to dirty for a column after `update_light` updates the light cache. */
5
+ export function sectionYsForLightColumnDirty (
6
+ worldMinY: number,
7
+ worldMaxY: number,
8
+ sectionHeight = SECTION_HEIGHT,
9
+ ): number[] {
10
+ const ys: number[] = []
11
+ for (let y = worldMinY; y < worldMaxY; y += sectionHeight) {
12
+ ys.push(y)
13
+ }
14
+ return ys
15
+ }