minecraft-renderer 0.1.78 → 0.1.80

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 (200) hide show
  1. package/README.md +29 -25
  2. package/dist/mesher.js +36 -36
  3. package/dist/mesher.js.map +4 -4
  4. package/dist/mesherWasm.js +217 -217
  5. package/dist/minecraft-renderer.js +32 -32
  6. package/dist/minecraft-renderer.js.meta.json +1 -1
  7. package/dist/threeWorker.js +415 -415
  8. package/package.json +5 -2
  9. package/src/bundler/bundlePrepare.ts +3 -8
  10. package/src/graphicsBackend/appViewer.ts +10 -7
  11. package/src/graphicsBackend/config.ts +1 -5
  12. package/src/graphicsBackend/playerState.ts +35 -34
  13. package/src/graphicsBackend/preloadWorkers.ts +43 -41
  14. package/src/graphicsBackend/rendererDefaultOptions.ts +68 -70
  15. package/src/graphicsBackend/rendererOptionsSync.ts +24 -50
  16. package/src/graphicsBackend/types.ts +2 -2
  17. package/src/index.ts +6 -27
  18. package/src/lib/DebugGui.ts +34 -31
  19. package/src/lib/animationController.ts +9 -9
  20. package/src/lib/bakeLegacyLight.ts +1 -6
  21. package/src/lib/bindAbortableListener.test.ts +32 -12
  22. package/src/lib/bindAbortableListener.ts +14 -6
  23. package/src/lib/blockEntityLightRegistry.ts +11 -20
  24. package/src/lib/blockEntityLighting.test.ts +1 -6
  25. package/src/lib/blockEntityLighting.ts +5 -5
  26. package/src/lib/buildSharedConfig.mjs +4 -4
  27. package/src/lib/buildWorkerMcDataIndexes.test.ts +4 -4
  28. package/src/lib/buildWorkerMcDataIndexes.ts +11 -19
  29. package/src/lib/cameraBobbing.ts +1 -1
  30. package/src/lib/canvas2DOverlay.example.ts +13 -33
  31. package/src/lib/canvas2DOverlay.quickstart.ts +24 -54
  32. package/src/lib/canvas2DOverlay.ts +28 -47
  33. package/src/lib/cleanupDecorator.ts +2 -2
  34. package/src/lib/createPlayerObject.test.ts +2 -7
  35. package/src/lib/createPlayerObject.ts +18 -25
  36. package/src/lib/frameTimingCollector.ts +2 -5
  37. package/src/lib/guiRenderer.ts +57 -42
  38. package/src/lib/items.ts +15 -9
  39. package/src/lib/mesherlogReader.ts +5 -6
  40. package/src/lib/simpleUtils.ts +5 -5
  41. package/src/lib/skyLight.ts +3 -3
  42. package/src/lib/smoothSwitcher.ts +11 -18
  43. package/src/lib/ui/newStats.ts +10 -12
  44. package/src/lib/utils/proxy.ts +7 -1
  45. package/src/lib/utils/skins.ts +4 -4
  46. package/src/lib/utils.ts +1 -1
  47. package/src/lib/workerMessageSanitize.ts +5 -12
  48. package/src/lib/workerProxy.ts +52 -42
  49. package/src/lib/workerSyncOps.test.ts +58 -41
  50. package/src/lib/worldrendererCommon.reconfigure.test.ts +24 -21
  51. package/src/lib/worldrendererCommon.removeColumn.test.ts +11 -13
  52. package/src/lib/worldrendererCommon.ts +338 -211
  53. package/src/mesher-legacy/mesher.ts +9 -4
  54. package/src/mesher-legacy/test/a.ts +1 -1
  55. package/src/mesher-legacy/test/mesherTester.ts +2 -2
  56. package/src/mesher-legacy/test/playground.ts +1 -1
  57. package/src/mesher-legacy/test/run/test-js.ts +1 -1
  58. package/src/mesher-legacy/test/snapshotUtils.ts +3 -8
  59. package/src/mesher-legacy/test/test-perf.ts +1 -1
  60. package/src/mesher-legacy/test/tests.test.ts +2 -2
  61. package/src/mesher-shared/blockEntityMetadata.test.ts +7 -5
  62. package/src/mesher-shared/blockEntityMetadata.ts +28 -19
  63. package/src/mesher-shared/blockPropertiesForMeshing.ts +2 -2
  64. package/src/mesher-shared/computeHeightmap.ts +2 -4
  65. package/src/mesher-shared/exportedGeometryTypes.ts +3 -3
  66. package/src/mesher-shared/faceOcclusion.ts +324 -0
  67. package/src/mesher-shared/models.ts +80 -131
  68. package/src/mesher-shared/modelsGeometryCommon.ts +30 -15
  69. package/src/mesher-shared/shared.ts +46 -46
  70. package/src/mesher-shared/standaloneRenderer.ts +40 -44
  71. package/src/mesher-shared/tests/faceOcclusion.test.ts +179 -0
  72. package/src/mesher-shared/tests/liquidQuadInvariant.test.ts +12 -8
  73. package/src/mesher-shared/vertexShading.ts +2 -11
  74. package/src/mesher-shared/world.ts +19 -19
  75. package/src/mesher-shared/worldLighting.test.ts +2 -2
  76. package/src/performanceMonitor/PerformanceMonitor.ts +4 -13
  77. package/src/performanceMonitor/formatPerformanceFactorsDebug.ts +2 -2
  78. package/src/performanceMonitor/index.ts +1 -5
  79. package/src/performanceMonitor/types.ts +1 -1
  80. package/src/playerState/playerState.ts +55 -50
  81. package/src/playerState/types.ts +2 -8
  82. package/src/playground/allEntitiesDebug.ts +19 -16
  83. package/src/playground/baseScene.ts +29 -32
  84. package/src/playground/mobileControls.tsx +133 -129
  85. package/src/playground/playground.html +76 -73
  86. package/src/playground/playground.ts +14 -1
  87. package/src/playground/playgroundUi.tsx +100 -74
  88. package/src/playground/reactUtils.ts +3 -7
  89. package/src/playground/scenes/entities.ts +10 -7
  90. package/src/playground/scenes/frequentUpdates.ts +5 -5
  91. package/src/playground/scenes/geometryExport.ts +6 -20
  92. package/src/playground/scenes/highPosEntityUpdates.ts +2 -10
  93. package/src/playground/scenes/index.ts +1 -1
  94. package/src/playground/scenes/lightingStarfield.ts +18 -12
  95. package/src/playground/scenes/main.ts +15 -11
  96. package/src/playground/scenes/partialBlockCulling.ts +31 -0
  97. package/src/playground/scenes/railsCobweb.ts +1 -1
  98. package/src/playground/scenes/rotationIssue.ts +1 -1
  99. package/src/playground/scenes/slabsOptimization.ts +1 -2
  100. package/src/playground/scenes/transparencyIssue.ts +1 -1
  101. package/src/playground/shared.ts +2 -2
  102. package/src/resourcesManager/resourcesManager.ts +29 -54
  103. package/src/resourcesManager/resourcesManager.worker.test.ts +6 -10
  104. package/src/shims/minecraftData.ts +70 -24
  105. package/src/sign-renderer/index.html +10 -12
  106. package/src/sign-renderer/index.ts +29 -29
  107. package/src/sign-renderer/playground.ts +22 -15
  108. package/src/sign-renderer/tests.test.ts +16 -13
  109. package/src/sign-renderer/vite.config.ts +2 -2
  110. package/src/three/appShared.ts +46 -25
  111. package/src/three/bannerRenderer.ts +33 -61
  112. package/src/three/cameraShake.ts +15 -6
  113. package/src/three/chunkMeshManager.ts +329 -301
  114. package/src/three/cinimaticScript.ts +26 -15
  115. package/src/three/cubeDrawSpans.ts +27 -37
  116. package/src/three/cubeMultiDraw.ts +9 -22
  117. package/src/three/documentRenderer.ts +15 -18
  118. package/src/three/entities.ts +175 -150
  119. package/src/three/entity/EntityMesh.ts +114 -108
  120. package/src/three/entity/animations.js +3 -3
  121. package/src/three/entity/armorModels.ts +15 -15
  122. package/src/three/entity/gltfAnimationUtils.ts +2 -5
  123. package/src/three/fireworks.ts +34 -32
  124. package/src/three/fireworksRenderer.ts +32 -29
  125. package/src/three/globalBlockBuffer.ts +174 -99
  126. package/src/three/globalLegacyBuffer.ts +465 -136
  127. package/src/three/graphicsBackendBase.ts +19 -25
  128. package/src/three/graphicsBackendOffThread.ts +12 -15
  129. package/src/three/hand.ts +21 -21
  130. package/src/three/handLegacy.ts +3 -13
  131. package/src/three/holdingBlock.ts +116 -79
  132. package/src/three/holdingBlockFactory.ts +1 -4
  133. package/src/three/holdingBlockItemIdentity.test.ts +25 -17
  134. package/src/three/holdingBlockItemIdentity.ts +9 -6
  135. package/src/three/holdingBlockLegacy.ts +113 -132
  136. package/src/three/itemMesh.ts +20 -29
  137. package/src/three/legacyMultiDraw.ts +93 -0
  138. package/src/three/legacySectionCull.ts +8 -14
  139. package/src/three/menuBackground/activeView.ts +1 -4
  140. package/src/three/menuBackground/assetUrl.ts +1 -1
  141. package/src/three/menuBackground/classic.ts +4 -12
  142. package/src/three/menuBackground/gpuPreference.ts +1 -3
  143. package/src/three/menuBackground/index.ts +2 -11
  144. package/src/three/menuBackground/renderer.ts +1 -5
  145. package/src/three/menuBackground/types.ts +1 -4
  146. package/src/three/menuBackground/v2.ts +196 -96
  147. package/src/three/menuBackground/v2Meta.ts +3 -3
  148. package/src/three/menuBackground/worldBlocks.ts +37 -35
  149. package/src/three/modules/blockBreakParticles.ts +59 -28
  150. package/src/three/modules/cameraBobbing.ts +3 -6
  151. package/src/three/modules/index.ts +1 -1
  152. package/src/three/modules/rain.ts +10 -23
  153. package/src/three/modules/sciFiWorldReveal.ts +11 -25
  154. package/src/three/modules/starfield.ts +7 -13
  155. package/src/three/renderSlot.ts +28 -16
  156. package/src/three/rendererModuleSystem.ts +1 -3
  157. package/src/three/sceneOrigin.ts +96 -33
  158. package/src/three/sectionRaycastAabb.ts +30 -18
  159. package/src/three/shaderCubeMesh.ts +6 -17
  160. package/src/three/shaders/cubeBlockShader.ts +85 -96
  161. package/src/three/shaders/legacyBlockShader.ts +21 -43
  162. package/src/three/shaders/textureIndexMapping.ts +89 -95
  163. package/src/three/shaders/tintPalette.ts +156 -170
  164. package/src/three/signTextureCache.ts +9 -12
  165. package/src/three/skyboxRenderer.ts +62 -70
  166. package/src/three/tests/chunkMeshManagerLegacy.test.ts +133 -25
  167. package/src/three/tests/cubeDrawSpans.test.ts +55 -25
  168. package/src/three/tests/globalBlockBuffer.test.ts +217 -0
  169. package/src/three/tests/globalLegacyBuffer.test.ts +460 -58
  170. package/src/three/tests/legacyMultiDraw.test.ts +88 -0
  171. package/src/three/tests/pr3PerFrameCpuCuts.test.ts +370 -0
  172. package/src/three/tests/signTextureCache.test.ts +4 -4
  173. package/src/three/threeJsMedia.ts +55 -72
  174. package/src/three/threeJsParticles.ts +32 -41
  175. package/src/three/threeJsSound.ts +3 -3
  176. package/src/three/threeJsUtils.ts +3 -3
  177. package/src/three/threeWorker.ts +2 -2
  178. package/src/three/waypointSprite.ts +55 -82
  179. package/src/three/waypoints.ts +20 -23
  180. package/src/three/world/cursorBlock.ts +25 -12
  181. package/src/three/world/vr.ts +11 -17
  182. package/src/three/worldGeometryExport.ts +15 -34
  183. package/src/three/worldRendererThree.ts +131 -133
  184. package/src/wasm-mesher/bridge/convertChunk.ts +3 -9
  185. package/src/wasm-mesher/bridge/render-from-wasm.ts +204 -229
  186. package/src/wasm-mesher/bridge/shaderCubeBridge.ts +26 -67
  187. package/src/wasm-mesher/tests/cullingRegression.test.ts +307 -0
  188. package/src/wasm-mesher/tests/heightmapParity.test.ts +8 -18
  189. package/src/wasm-mesher/tests/mesherWasmConversionCache.test.ts +2 -2
  190. package/src/wasm-mesher/tests/sectionRaycastAabb.test.ts +17 -4
  191. package/src/wasm-mesher/tests/shaderCubeInstances.test.ts +103 -141
  192. package/src/wasm-mesher/tests/splitColumnWasmOutput.test.ts +14 -22
  193. package/src/wasm-mesher/worker/mesherWasm.ts +150 -141
  194. package/src/wasm-mesher/worker/mesherWasmConversionCache.ts +2 -8
  195. package/src/wasm-mesher/worker/mesherWasmLightDirty.test.ts +1 -3
  196. package/src/wasm-mesher/worker/mesherWasmLightDirty.ts +1 -5
  197. package/src/wasm-mesher/worker/mesherWasmRequestTracker.ts +42 -42
  198. package/src/worldView/worldView.spiral.test.ts +1 -1
  199. package/src/worldView/worldView.ts +34 -39
  200. package/src/worldView/worldViewWorkerBridge.test.ts +4 -4
@@ -1,10 +1,10 @@
1
1
  //@ts-nocheck
2
2
  import { describe, expect, test } from 'vitest'
3
3
  import * as THREE from 'three'
4
- import { GlobalLegacyBuffer, MAX_OPAQUE_SPANS } from '../globalLegacyBuffer'
4
+ import { GlobalLegacyBuffer, MAX_OPAQUE_SPANS, carveSpansAroundPendingRanges } from '../globalLegacyBuffer'
5
5
  import { createGlobalLegacyBlockMaterial } from '../shaders/legacyBlockShader'
6
6
 
7
- function makeQuadGeometry (): {
7
+ function makeQuadGeometry(): {
8
8
  positions: Float32Array
9
9
  colors: Float32Array
10
10
  skyLights: Float32Array
@@ -13,42 +13,43 @@ function makeQuadGeometry (): {
13
13
  indices: Uint32Array
14
14
  } {
15
15
  return {
16
- positions: new Float32Array([
17
- -1, 0, -1,
18
- 1, 0, -1,
19
- 1, 0, 1,
20
- -1, 0, 1,
21
- ]),
22
- colors: new Float32Array([
23
- 1, 1, 1,
24
- 1, 1, 1,
25
- 1, 1, 1,
26
- 1, 1, 1,
27
- ]),
16
+ positions: new Float32Array([-1, 0, -1, 1, 0, -1, 1, 0, 1, -1, 0, 1]),
17
+ colors: new Float32Array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]),
28
18
  skyLights: new Float32Array([1, 1, 1, 1]),
29
19
  blockLights: new Float32Array([0, 0, 0, 0]),
30
- uvs: new Float32Array([
31
- 0, 0,
32
- 1, 0,
33
- 1, 1,
34
- 0, 1,
35
- ]),
36
- indices: new Uint32Array([0, 1, 2, 0, 2, 3]),
20
+ uvs: new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]),
21
+ indices: new Uint32Array([0, 1, 2, 0, 2, 3])
37
22
  }
38
23
  }
39
24
 
40
25
  type BufferInternals = {
41
- pendingRanges: Array<{ start: number, end: number }>
26
+ pendingRanges: Array<{ start: number; end: number }>
27
+ pendingMove: { key: string; oldStart: number; newStart: number; count: number } | null
28
+ growCapacity: (minQuads: number) => void
42
29
  }
43
30
 
44
- function getInternals (buffer: GlobalLegacyBuffer): BufferInternals {
31
+ function getInternals(buffer: GlobalLegacyBuffer): BufferInternals {
45
32
  return buffer as unknown as BufferInternals
46
33
  }
47
34
 
48
- function drainUploads (buffer: GlobalLegacyBuffer): void {
35
+ function drainUploads(buffer: GlobalLegacyBuffer): void {
49
36
  while (getInternals(buffer).pendingRanges.length) buffer.uploadDirtyRange()
50
37
  }
51
38
 
39
+ function finishCurrentMove(buffer: GlobalLegacyBuffer): void {
40
+ drainUploads(buffer)
41
+ buffer.compactStep()
42
+ }
43
+
44
+ function readSectionIndices(buffer: GlobalLegacyBuffer, key: string): number[] {
45
+ const slot = buffer.getSectionSlot(key)
46
+ if (!slot) throw new Error(`missing section ${key}`)
47
+ const indexAttr = buffer.mesh.geometry.index!.array as Uint32Array
48
+ const base = slot.start * 6
49
+ const len = slot.count * 6
50
+ return Array.from(indexAttr.slice(base, base + len))
51
+ }
52
+
52
53
  test('GlobalLegacyBuffer: slot reuse and a_origin fill', () => {
53
54
  const scene = new THREE.Scene()
54
55
  const mat = createGlobalLegacyBlockMaterial()
@@ -191,11 +192,18 @@ test('GlobalLegacyBuffer: updateDrawSpans opaque merges nearby spans', () => {
191
192
 
192
193
  buffer.addSection('a', geo, 0, 0, 0)
193
194
  buffer.addSection('b', geo, 16, 0, 0)
194
- buffer.updateDrawSpans([{ key: 'a', distSq: 1 }, { key: 'b', distSq: 4 }], 'opaque')
195
-
196
- const groups = buffer.mesh.geometry.groups
197
- expect(groups.length).toBe(1)
198
- expect(groups[0]!.count).toBe(12)
195
+ drainUploads(buffer)
196
+ buffer.updateDrawSpans(
197
+ [
198
+ { key: 'a', distSq: 1 },
199
+ { key: 'b', distSq: 4 }
200
+ ],
201
+ 'opaque'
202
+ )
203
+
204
+ const spans = buffer.getVisibleIndexSpans()
205
+ expect(spans.length).toBe(1)
206
+ expect(spans[0]!.indexCount).toBe(12)
199
207
 
200
208
  buffer.dispose()
201
209
  mat.dispose()
@@ -210,12 +218,20 @@ test('GlobalLegacyBuffer: updateDrawSpans opaque full draw when most quads visib
210
218
  buffer.addSection('a', geo, 0, 0, 0)
211
219
  buffer.addSection('b', geo, 16, 0, 0)
212
220
  buffer.addSection('c', geo, 32, 0, 0)
213
- buffer.updateDrawSpans([{ key: 'a', distSq: 1 }, { key: 'b', distSq: 2 }, { key: 'c', distSq: 3 }], 'opaque')
214
-
215
- const groups = buffer.mesh.geometry.groups
216
- expect(groups.length).toBe(1)
217
- expect(groups[0]!.start).toBe(0)
218
- expect(groups[0]!.count).toBe(18)
221
+ drainUploads(buffer)
222
+ buffer.updateDrawSpans(
223
+ [
224
+ { key: 'a', distSq: 1 },
225
+ { key: 'b', distSq: 2 },
226
+ { key: 'c', distSq: 3 }
227
+ ],
228
+ 'opaque'
229
+ )
230
+
231
+ const spans = buffer.getVisibleIndexSpans()
232
+ expect(spans.length).toBe(1)
233
+ expect(spans[0]!.indexStart).toBe(0)
234
+ expect(spans[0]!.indexCount).toBe(18)
219
235
 
220
236
  buffer.dispose()
221
237
  mat.dispose()
@@ -229,15 +245,19 @@ test('GlobalLegacyBuffer: updateDrawSpans sortedBlend orders back-to-front', ()
229
245
 
230
246
  buffer.addSection('near', geo, 0, 0, 0)
231
247
  buffer.addSection('far', geo, 16, 0, 0)
232
- buffer.updateDrawSpans([
233
- { key: 'near', distSq: 1 },
234
- { key: 'far', distSq: 100 },
235
- ], 'sortedBlend')
236
-
237
- const groups = buffer.mesh.geometry.groups
238
- expect(groups.length).toBe(2)
239
- expect(groups[0]!.start).toBe(6)
240
- expect(groups[1]!.start).toBe(0)
248
+ drainUploads(buffer)
249
+ buffer.updateDrawSpans(
250
+ [
251
+ { key: 'near', distSq: 1 },
252
+ { key: 'far', distSq: 100 }
253
+ ],
254
+ 'sortedBlend'
255
+ )
256
+
257
+ const spans = buffer.getVisibleIndexSpans()
258
+ expect(spans.length).toBe(2)
259
+ expect(spans[0]!.indexStart).toBe(6)
260
+ expect(spans[1]!.indexStart).toBe(0)
241
261
 
242
262
  buffer.dispose()
243
263
  mat.dispose()
@@ -250,40 +270,41 @@ test('GlobalLegacyBuffer: updateDrawSpans skips missing keys', () => {
250
270
  const geo = makeQuadGeometry()
251
271
 
252
272
  buffer.addSection('a', geo, 0, 0, 0)
273
+ drainUploads(buffer)
253
274
  buffer.updateDrawSpans([{ key: 'missing', distSq: 1 }], 'opaque')
254
275
 
255
- expect(buffer.mesh.geometry.groups.length).toBe(0)
256
- expect(buffer.mesh.geometry.drawRange.count).toBe(0)
276
+ expect(buffer.getVisibleIndexSpans().length).toBe(0)
257
277
 
258
278
  buffer.dispose()
259
279
  mat.dispose()
260
280
  })
261
281
 
262
- test('GlobalLegacyBuffer: reset clears groups', () => {
282
+ test('GlobalLegacyBuffer: reset clears visible spans', () => {
263
283
  const scene = new THREE.Scene()
264
284
  const mat = createGlobalLegacyBlockMaterial()
265
285
  const buffer = new GlobalLegacyBuffer(mat, scene)
266
286
  const geo = makeQuadGeometry()
267
287
 
268
288
  buffer.addSection('a', geo, 0, 0, 0)
289
+ drainUploads(buffer)
269
290
  buffer.updateDrawSpans([{ key: 'a', distSq: 1 }], 'opaque')
270
- expect(buffer.mesh.geometry.groups.length).toBeGreaterThan(0)
291
+ expect(buffer.getVisibleIndexSpans().length).toBeGreaterThan(0)
271
292
 
272
293
  buffer.reset()
273
- expect(buffer.mesh.geometry.groups.length).toBe(0)
294
+ expect(buffer.getVisibleIndexSpans().length).toBe(0)
274
295
 
275
296
  buffer.dispose()
276
297
  mat.dispose()
277
298
  })
278
299
 
279
- test('GlobalLegacyBuffer: updateDrawSpans opaque caps at MAX_OPAQUE_SPANS with full coverage', () => {
300
+ test('GlobalLegacyBuffer: updateDrawSpans opaque does not bridge interior gaps', () => {
280
301
  const scene = new THREE.Scene()
281
302
  const mat = createGlobalLegacyBlockMaterial()
282
303
  const visibleSectionCount = MAX_OPAQUE_SPANS + 5
283
304
  const padQuads = 257
284
305
  const buffer = new GlobalLegacyBuffer(mat, scene, {
285
306
  initialCapacityQuads: visibleSectionCount * (padQuads + 1) + padQuads,
286
- growthIncrementQuads: 1024,
307
+ growthIncrementQuads: 1024
287
308
  })
288
309
  const geo = makeQuadGeometry()
289
310
  const padGeo = {
@@ -293,13 +314,13 @@ test('GlobalLegacyBuffer: updateDrawSpans opaque caps at MAX_OPAQUE_SPANS with f
293
314
  skyLights: new Float32Array(padQuads * 4).fill(1),
294
315
  blockLights: new Float32Array(padQuads * 4).fill(0),
295
316
  uvs: new Float32Array(padQuads * 4 * 2),
296
- indices: new Uint32Array(padQuads * 6),
317
+ indices: new Uint32Array(padQuads * 6)
297
318
  }
298
319
  for (let q = 0; q < padQuads; q++) {
299
320
  const vb = q * 4
300
321
  padGeo.indices.set([vb, vb + 1, vb + 2, vb, vb + 2, vb + 3], q * 6)
301
322
  }
302
- const visible: Array<{ key: string, distSq: number }> = []
323
+ const visible: Array<{ key: string; distSq: number }> = []
303
324
 
304
325
  for (let i = 0; i < visibleSectionCount; i++) {
305
326
  const key = `s${i}`
@@ -316,14 +337,15 @@ test('GlobalLegacyBuffer: updateDrawSpans opaque caps at MAX_OPAQUE_SPANS with f
316
337
  expect(next.start - (cur.start + cur.count)).toBeGreaterThan(256)
317
338
  }
318
339
 
340
+ drainUploads(buffer)
319
341
  buffer.updateDrawSpans(visible, 'opaque')
320
342
 
321
- const groups = buffer.mesh.geometry.groups
322
- expect(groups.length).toBe(MAX_OPAQUE_SPANS)
343
+ const spans = buffer.getVisibleIndexSpans()
344
+ expect(spans.length).toBe(visibleSectionCount)
323
345
 
324
346
  const covered = new Set<number>()
325
- for (const group of groups) {
326
- for (let idx = group.start; idx < group.start + group.count; idx++) {
347
+ for (const span of spans) {
348
+ for (let idx = span.indexStart; idx < span.indexStart + span.indexCount; idx++) {
327
349
  covered.add(idx)
328
350
  }
329
351
  }
@@ -351,10 +373,390 @@ test('GlobalLegacyBuffer: addSection rejects non-quad geometry', () => {
351
373
  skyLights: new Float32Array(3).fill(1),
352
374
  blockLights: new Float32Array(3).fill(0),
353
375
  uvs: new Float32Array(6),
354
- indices: new Uint32Array([0, 1, 2]),
376
+ indices: new Uint32Array([0, 1, 2])
355
377
  }
356
378
  expect(buffer.addSection('bad', bad, 0, 0, 0)).toBe(false)
357
379
 
358
380
  buffer.dispose()
359
381
  mat.dispose()
360
382
  })
383
+
384
+ test('GlobalLegacyBuffer: compaction lowers watermark after interior-hole churn', () => {
385
+ const scene = new THREE.Scene()
386
+ const mat = createGlobalLegacyBlockMaterial()
387
+ const buffer = new GlobalLegacyBuffer(mat, scene)
388
+ const geo = makeQuadGeometry()
389
+
390
+ buffer.addSection('a', geo, 0, 0, 0)
391
+ buffer.addSection('b', geo, 16, 0, 0)
392
+ buffer.addSection('c', geo, 32, 0, 0)
393
+ expect(buffer.getHighWatermark()).toBe(3)
394
+
395
+ buffer.removeSection('b')
396
+ drainUploads(buffer)
397
+ expect(buffer.getHighWatermark()).toBe(3)
398
+
399
+ buffer.compactStep()
400
+ finishCurrentMove(buffer)
401
+
402
+ expect(buffer.getHighWatermark()).toBe(2)
403
+ expect(buffer.getSectionSlot('c')).toEqual({ start: 1, count: 1 })
404
+ const slotC = buffer.getSectionSlot('c')!
405
+ const indices = readSectionIndices(buffer, 'c')
406
+ expect(indices[0]).toBe(slotC.start * 4)
407
+
408
+ buffer.dispose()
409
+ mat.dispose()
410
+ })
411
+
412
+ test('GlobalLegacyBuffer: grow during in-flight move preserves section data', () => {
413
+ const scene = new THREE.Scene()
414
+ const mat = createGlobalLegacyBlockMaterial()
415
+ const buffer = new GlobalLegacyBuffer(mat, scene, { initialCapacityQuads: 4, growthIncrementQuads: 8 })
416
+ const geo = makeQuadGeometry()
417
+
418
+ buffer.addSection('a', geo, 0, 0, 0)
419
+ buffer.addSection('b', geo, 16, 0, 0)
420
+ buffer.addSection('c', geo, 32, 0, 0)
421
+ buffer.removeSection('b')
422
+ drainUploads(buffer)
423
+
424
+ buffer.compactStep()
425
+ expect(buffer.getPendingMove()).not.toBeNull()
426
+ expect(buffer.hasSection('c')).toBe(true)
427
+
428
+ getInternals(buffer).growCapacity(16)
429
+
430
+ expect(buffer.getPendingMove()).toBeNull()
431
+ expect(buffer.hasSection('c')).toBe(true)
432
+ const slotC = buffer.getSectionSlot('c')!
433
+ const indices = readSectionIndices(buffer, 'c')
434
+ expect(indices[0]).toBe(slotC.start * 4)
435
+ expect(buffer.getCapacityQuads()).toBeGreaterThanOrEqual(16)
436
+
437
+ buffer.dispose()
438
+ mat.dispose()
439
+ })
440
+
441
+ test('GlobalLegacyBuffer: finalize move bumps layoutVersion and updates draw spans', () => {
442
+ const scene = new THREE.Scene()
443
+ const mat = createGlobalLegacyBlockMaterial()
444
+ const buffer = new GlobalLegacyBuffer(mat, scene)
445
+ const geo = makeQuadGeometry()
446
+
447
+ buffer.addSection('a', geo, 0, 0, 0)
448
+ buffer.addSection('b', geo, 16, 0, 0)
449
+ buffer.addSection('c', geo, 32, 0, 0)
450
+ buffer.removeSection('b')
451
+ drainUploads(buffer)
452
+
453
+ buffer.compactStep()
454
+ expect(buffer.getPendingMove()).not.toBeNull()
455
+ const versionAfterMove = buffer.getLayoutVersion()
456
+
457
+ const visible = [{ key: 'c', distSq: 1 }]
458
+ buffer.updateDrawSpans(visible, 'opaque')
459
+ expect(buffer.getVisibleIndexSpans()[0]!.indexStart).toBe(2 * 6)
460
+
461
+ drainUploads(buffer)
462
+ buffer.compactStep()
463
+ expect(buffer.getPendingMove()).toBeNull()
464
+ expect(buffer.getLayoutVersion()).toBe(versionAfterMove + 1)
465
+
466
+ buffer.updateDrawSpans(visible, 'opaque')
467
+ expect(buffer.getVisibleIndexSpans()[0]!.indexStart).toBe(1 * 6)
468
+
469
+ buffer.dispose()
470
+ mat.dispose()
471
+ })
472
+
473
+ test('GlobalLegacyBuffer: sortedBlend accepts more than MAX_OPAQUE_SPANS sections', () => {
474
+ const scene = new THREE.Scene()
475
+ const mat = createGlobalLegacyBlockMaterial()
476
+ const sectionCount = MAX_OPAQUE_SPANS + 6
477
+ const buffer = new GlobalLegacyBuffer(mat, scene, {
478
+ initialCapacityQuads: sectionCount + 4,
479
+ growthIncrementQuads: 64
480
+ })
481
+ const geo = makeQuadGeometry()
482
+ const visible: Array<{ key: string; distSq: number }> = []
483
+
484
+ for (let i = 0; i < sectionCount; i++) {
485
+ const key = `s${i}`
486
+ buffer.addSection(key, geo, i * 16, 0, 0)
487
+ visible.push({ key, distSq: sectionCount - i })
488
+ }
489
+
490
+ drainUploads(buffer)
491
+ buffer.updateDrawSpans(visible, 'sortedBlend')
492
+ expect(buffer.getVisibleIndexSpans().length).toBe(sectionCount)
493
+
494
+ buffer.dispose()
495
+ mat.dispose()
496
+ })
497
+
498
+ test('GlobalLegacyBuffer: new section gated until upload completes', () => {
499
+ const scene = new THREE.Scene()
500
+ const mat = createGlobalLegacyBlockMaterial()
501
+ const buffer = new GlobalLegacyBuffer(mat, scene)
502
+ const geo = makeQuadGeometry()
503
+
504
+ buffer.addSection('a', geo, 0, 0, 0)
505
+ expect(buffer.getSectionDrawStart('a')).toBeUndefined()
506
+ buffer.updateDrawSpans([{ key: 'a', distSq: 1 }], 'opaque')
507
+ expect(buffer.getVisibleIndexSpans().length).toBe(0)
508
+
509
+ drainUploads(buffer)
510
+ expect(buffer.getSectionDrawStart('a')).toBe(0)
511
+ buffer.updateDrawSpans([{ key: 'a', distSq: 1 }], 'opaque')
512
+ expect(buffer.getVisibleIndexSpans().length).toBe(1)
513
+
514
+ buffer.dispose()
515
+ mat.dispose()
516
+ })
517
+
518
+ test('GlobalLegacyBuffer: remesh double-buffers old geometry until upload', () => {
519
+ const scene = new THREE.Scene()
520
+ const mat = createGlobalLegacyBlockMaterial()
521
+ const buffer = new GlobalLegacyBuffer(mat, scene)
522
+ const geo = makeQuadGeometry()
523
+
524
+ buffer.addSection('a', geo, 0, 0, 0)
525
+ drainUploads(buffer)
526
+ const oldStart = buffer.getSectionSlot('a')!.start
527
+
528
+ buffer.addSection('a', geo, 16, 0, 0)
529
+ expect(buffer.getSectionDrawStart('a')).toBe(oldStart)
530
+ expect(buffer.hasPendingReplace()).toBe(true)
531
+
532
+ const indexAttr = buffer.mesh.geometry.index!.array as Uint32Array
533
+ expect(indexAttr[oldStart * 6 + 1]).not.toBe(0)
534
+
535
+ buffer.updateDrawSpans([{ key: 'a', distSq: 1 }], 'opaque')
536
+ expect(buffer.getVisibleIndexSpans()[0]!.indexStart).toBe(oldStart * 6)
537
+
538
+ const epochBefore = buffer.getUploadEpoch()
539
+ drainUploads(buffer)
540
+ buffer.compactStep()
541
+ expect(buffer.hasPendingReplace()).toBe(false)
542
+ expect(buffer.getUploadEpoch()).toBeGreaterThan(epochBefore)
543
+ finishCurrentMove(buffer)
544
+ expect(buffer.getSectionDrawStart('a')).toBe(buffer.getSectionSlot('a')!.start)
545
+
546
+ buffer.dispose()
547
+ mat.dispose()
548
+ })
549
+
550
+ test('GlobalLegacyBuffer: remesh before previous upload completes keeps fully-uploaded fallback', () => {
551
+ const scene = new THREE.Scene()
552
+ const mat = createGlobalLegacyBlockMaterial()
553
+ const buffer = new GlobalLegacyBuffer(mat, scene)
554
+ const geo = makeQuadGeometry()
555
+
556
+ buffer.addSection('a', geo, 0, 0, 0)
557
+ drainUploads(buffer)
558
+ buffer.compactStep()
559
+ const slotA = buffer.getSectionSlot('a')!.start
560
+
561
+ buffer.addSection('a', geo, 16, 0, 0)
562
+ expect(buffer.hasPendingReplace()).toBe(true)
563
+ expect(buffer.hasPendingUploads()).toBe(true)
564
+
565
+ buffer.addSection('a', geo, 32, 0, 0)
566
+
567
+ const drawStart = buffer.getSectionDrawStart('a')
568
+ const drawCount = buffer.getSectionDrawCount('a')!
569
+ expect(drawStart).toBe(slotA)
570
+ expect(buffer.isRangeFullyUploaded(drawStart!, drawStart! + drawCount - 1)).toBe(true)
571
+
572
+ buffer.updateDrawSpans([{ key: 'a', distSq: 1 }], 'opaque')
573
+ const spans = buffer.getVisibleIndexSpans()
574
+ expect(spans.length).toBeGreaterThan(0)
575
+ expect(spans.some(s => s.indexStart <= slotA * 6 && s.indexStart + s.indexCount > slotA * 6)).toBe(true)
576
+
577
+ const indexAttr = buffer.mesh.geometry.index!.array as Uint32Array
578
+ expect(indexAttr[slotA * 6 + 1]).not.toBe(0)
579
+
580
+ drainUploads(buffer)
581
+ buffer.compactStep()
582
+ finishCurrentMove(buffer)
583
+ const slotC = buffer.getSectionSlot('a')!.start
584
+ expect(buffer.getSectionDrawStart('a')).toBe(slotC)
585
+ expect(indexAttr[slotC * 6 + 1]).not.toBe(0)
586
+
587
+ buffer.dispose()
588
+ mat.dispose()
589
+ })
590
+
591
+ test('GlobalLegacyBuffer: full-draw blocked when uploads pending', () => {
592
+ const scene = new THREE.Scene()
593
+ const mat = createGlobalLegacyBlockMaterial()
594
+ const buffer = new GlobalLegacyBuffer(mat, scene, { initialCapacityQuads: 8, growthIncrementQuads: 8 })
595
+ const geo = makeQuadGeometry()
596
+
597
+ buffer.addSection('a', geo, 0, 0, 0)
598
+ buffer.addSection('b', geo, 16, 0, 0)
599
+ buffer.addSection('c', geo, 32, 0, 0)
600
+ drainUploads(buffer)
601
+ buffer.addSection('d', geo, 48, 0, 0)
602
+
603
+ buffer.updateDrawSpans(
604
+ [
605
+ { key: 'a', distSq: 1 },
606
+ { key: 'b', distSq: 2 },
607
+ { key: 'c', distSq: 3 }
608
+ ],
609
+ 'opaque'
610
+ )
611
+
612
+ const spans = buffer.getVisibleIndexSpans()
613
+ expect(spans.length).toBeGreaterThan(0)
614
+ expect(spans.some(s => s.indexStart === 0 && s.indexCount === buffer.getHighWatermark() * 6)).toBe(false)
615
+ expect(spans.reduce((sum, s) => sum + s.indexCount, 0)).toBe(18)
616
+
617
+ buffer.dispose()
618
+ mat.dispose()
619
+ })
620
+
621
+ test('GlobalLegacyBuffer: full-draw allowed when buffer is clean', () => {
622
+ const scene = new THREE.Scene()
623
+ const mat = createGlobalLegacyBlockMaterial()
624
+ const buffer = new GlobalLegacyBuffer(mat, scene, { initialCapacityQuads: 4, growthIncrementQuads: 4 })
625
+ const geo = makeQuadGeometry()
626
+
627
+ buffer.addSection('a', geo, 0, 0, 0)
628
+ buffer.addSection('b', geo, 16, 0, 0)
629
+ buffer.addSection('c', geo, 32, 0, 0)
630
+ drainUploads(buffer)
631
+
632
+ buffer.updateDrawSpans(
633
+ [
634
+ { key: 'a', distSq: 1 },
635
+ { key: 'b', distSq: 2 },
636
+ { key: 'c', distSq: 3 }
637
+ ],
638
+ 'opaque'
639
+ )
640
+
641
+ const spans = buffer.getVisibleIndexSpans()
642
+ expect(spans.length).toBe(1)
643
+ expect(spans[0]!.indexStart).toBe(0)
644
+ expect(spans[0]!.indexCount).toBe(buffer.getHighWatermark() * 6)
645
+
646
+ buffer.dispose()
647
+ mat.dispose()
648
+ })
649
+
650
+ test('GlobalLegacyBuffer: uploadEpoch increments on partial upload advance', () => {
651
+ const scene = new THREE.Scene()
652
+ const mat = createGlobalLegacyBlockMaterial()
653
+ const buffer = new GlobalLegacyBuffer(mat, scene)
654
+ const geo = makeQuadGeometry()
655
+
656
+ for (let i = 0; i < 6000; i++) {
657
+ buffer.addSection(`s${i}`, geo, i * 16, 0, 0)
658
+ }
659
+ const epoch0 = buffer.getUploadEpoch()
660
+ buffer.uploadDirtyRange()
661
+ expect(buffer.getUploadEpoch()).toBe(epoch0 + 1)
662
+ expect(buffer.hasPendingUploads()).toBe(true)
663
+ buffer.uploadDirtyRange()
664
+ expect(buffer.getUploadEpoch()).toBe(epoch0 + 2)
665
+
666
+ buffer.dispose()
667
+ mat.dispose()
668
+ })
669
+
670
+ test('GlobalLegacyBuffer: carveSpansAroundPendingRanges splits merged span', () => {
671
+ const carved = carveSpansAroundPendingRanges([{ start: 0, count: 100 }], [{ start: 40, end: 59 }])
672
+ expect(carved).toEqual([
673
+ { start: 0, count: 40 },
674
+ { start: 60, count: 40 }
675
+ ])
676
+ })
677
+
678
+ test('GlobalLegacyBuffer: uploadEpoch increments when dirty range drains', () => {
679
+ const scene = new THREE.Scene()
680
+ const mat = createGlobalLegacyBlockMaterial()
681
+ const buffer = new GlobalLegacyBuffer(mat, scene)
682
+ const geo = makeQuadGeometry()
683
+
684
+ const epoch0 = buffer.getUploadEpoch()
685
+ buffer.addSection('a', geo, 0, 0, 0)
686
+ drainUploads(buffer)
687
+ expect(buffer.getUploadEpoch()).toBeGreaterThan(epoch0)
688
+
689
+ buffer.dispose()
690
+ mat.dispose()
691
+ })
692
+
693
+ test('GlobalLegacyBuffer: mergeOpaqueSpans only merges adjacent slots', () => {
694
+ const scene = new THREE.Scene()
695
+ const mat = createGlobalLegacyBlockMaterial()
696
+ const buffer = new GlobalLegacyBuffer(mat, scene, { initialCapacityQuads: 16, growthIncrementQuads: 8 })
697
+ const geo = makeQuadGeometry()
698
+
699
+ buffer.addSection('a', geo, 0, 0, 0)
700
+ buffer.addSection('b', geo, 16, 0, 0)
701
+ buffer.addSection('c', geo, 32, 0, 0)
702
+ drainUploads(buffer)
703
+
704
+ buffer.updateDrawSpans(
705
+ [
706
+ { key: 'a', distSq: 1 },
707
+ { key: 'b', distSq: 2 },
708
+ { key: 'c', distSq: 3 }
709
+ ],
710
+ 'opaque'
711
+ )
712
+ expect(buffer.getVisibleIndexSpans().length).toBe(1)
713
+
714
+ buffer.dispose()
715
+ mat.dispose()
716
+ })
717
+
718
+ test('GlobalLegacyBuffer: mergeOpaqueSpans skips gap with pending upload', () => {
719
+ const scene = new THREE.Scene()
720
+ const mat = createGlobalLegacyBlockMaterial()
721
+ const buffer = new GlobalLegacyBuffer(mat, scene, { initialCapacityQuads: 8, growthIncrementQuads: 8 })
722
+ const geo = makeQuadGeometry()
723
+
724
+ buffer.addSection('a', geo, 0, 0, 0)
725
+ buffer.addSection('b', geo, 16, 0, 0)
726
+ buffer.addSection('c', geo, 32, 0, 0)
727
+ drainUploads(buffer)
728
+ buffer.removeSection('b')
729
+ drainUploads(buffer)
730
+
731
+ buffer.addSection('b', geo, 16, 0, 0)
732
+ buffer.updateDrawSpans(
733
+ [
734
+ { key: 'a', distSq: 1 },
735
+ { key: 'c', distSq: 2 }
736
+ ],
737
+ 'opaque'
738
+ )
739
+
740
+ const spans = buffer.getVisibleIndexSpans()
741
+ expect(spans.length).toBe(2)
742
+ expect(spans[0]!.indexCount).toBe(6)
743
+ expect(spans[1]!.indexCount).toBe(6)
744
+
745
+ buffer.dispose()
746
+ mat.dispose()
747
+ })
748
+
749
+ test('GlobalLegacyBuffer: suppressThreeDraw uses minimal non-zero draw range', () => {
750
+ const scene = new THREE.Scene()
751
+ const mat = createGlobalLegacyBlockMaterial()
752
+ const buffer = new GlobalLegacyBuffer(mat, scene)
753
+ const geo = makeQuadGeometry()
754
+
755
+ buffer.addSection('a', geo, 0, 0, 0)
756
+ buffer.suppressThreeDraw()
757
+ expect(buffer.mesh.geometry.drawRange.start).toBe(0)
758
+ expect(buffer.mesh.geometry.drawRange.count).toBe(3)
759
+
760
+ buffer.dispose()
761
+ mat.dispose()
762
+ })