minecraft-renderer 0.1.79 → 0.1.81

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 (205) hide show
  1. package/README.md +66 -26
  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 +66 -66
  6. package/dist/minecraft-renderer.js.meta.json +1 -1
  7. package/dist/threeWorker.js +450 -450
  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 +349 -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/cameraCollisionBlockCache.ts +156 -0
  113. package/src/three/cameraCollisionSolidity.ts +50 -0
  114. package/src/three/cameraShake.ts +15 -6
  115. package/src/three/chunkMeshManager.ts +223 -390
  116. package/src/three/cinimaticScript.ts +26 -15
  117. package/src/three/cubeDrawSpans.ts +27 -37
  118. package/src/three/cubeMultiDraw.ts +9 -22
  119. package/src/three/documentRenderer.ts +15 -18
  120. package/src/three/entities.ts +175 -150
  121. package/src/three/entity/EntityMesh.ts +114 -108
  122. package/src/three/entity/animations.js +3 -3
  123. package/src/three/entity/armorModels.ts +15 -15
  124. package/src/three/entity/gltfAnimationUtils.ts +2 -5
  125. package/src/three/fireworks.ts +34 -32
  126. package/src/three/fireworksRenderer.ts +32 -29
  127. package/src/three/globalBlockBuffer.ts +167 -100
  128. package/src/three/globalLegacyBuffer.ts +248 -264
  129. package/src/three/graphicsBackendBase.ts +19 -25
  130. package/src/three/graphicsBackendOffThread.ts +12 -15
  131. package/src/three/hand.ts +21 -21
  132. package/src/three/handLegacy.ts +3 -13
  133. package/src/three/holdingBlock.ts +116 -79
  134. package/src/three/holdingBlockFactory.ts +1 -4
  135. package/src/three/holdingBlockItemIdentity.test.ts +25 -17
  136. package/src/three/holdingBlockItemIdentity.ts +9 -6
  137. package/src/three/holdingBlockLegacy.ts +113 -132
  138. package/src/three/itemMesh.ts +20 -29
  139. package/src/three/legacyMultiDraw.ts +10 -16
  140. package/src/three/legacySectionCull.ts +8 -14
  141. package/src/three/menuBackground/activeView.ts +1 -4
  142. package/src/three/menuBackground/assetUrl.ts +1 -1
  143. package/src/three/menuBackground/classic.ts +4 -12
  144. package/src/three/menuBackground/gpuPreference.ts +1 -3
  145. package/src/three/menuBackground/index.ts +2 -11
  146. package/src/three/menuBackground/renderer.ts +1 -5
  147. package/src/three/menuBackground/types.ts +1 -4
  148. package/src/three/menuBackground/v2.ts +196 -96
  149. package/src/three/menuBackground/v2Meta.ts +3 -3
  150. package/src/three/menuBackground/worldBlocks.ts +37 -35
  151. package/src/three/modules/blockBreakParticles.ts +59 -28
  152. package/src/three/modules/cameraBobbing.ts +3 -6
  153. package/src/three/modules/index.ts +1 -1
  154. package/src/three/modules/rain.ts +10 -23
  155. package/src/three/modules/sciFiWorldReveal.ts +11 -25
  156. package/src/three/modules/starfield.ts +7 -13
  157. package/src/three/renderSlot.ts +28 -16
  158. package/src/three/rendererModuleSystem.ts +1 -3
  159. package/src/three/sceneOrigin.ts +96 -33
  160. package/src/three/sectionRaycastAabb.ts +24 -110
  161. package/src/three/shaderCubeMesh.ts +6 -17
  162. package/src/three/shaders/cubeBlockShader.ts +85 -96
  163. package/src/three/shaders/legacyBlockShader.ts +21 -43
  164. package/src/three/shaders/textureIndexMapping.ts +89 -95
  165. package/src/three/shaders/tintPalette.ts +156 -170
  166. package/src/three/signTextureCache.ts +9 -12
  167. package/src/three/skyboxRenderer.ts +62 -70
  168. package/src/three/tests/chunkMeshManagerLegacy.test.ts +34 -64
  169. package/src/three/tests/cubeDrawSpans.test.ts +55 -25
  170. package/src/three/tests/globalBlockBuffer.test.ts +217 -0
  171. package/src/three/tests/globalLegacyBuffer.test.ts +300 -41
  172. package/src/three/tests/legacyMultiDraw.test.ts +8 -29
  173. package/src/three/tests/pr3PerFrameCpuCuts.test.ts +106 -29
  174. package/src/three/tests/signTextureCache.test.ts +4 -4
  175. package/src/three/thirdPersonVoxelRaycast.ts +120 -0
  176. package/src/three/threeJsMedia.ts +55 -72
  177. package/src/three/threeJsParticles.ts +32 -41
  178. package/src/three/threeJsSound.ts +3 -3
  179. package/src/three/threeJsUtils.ts +3 -3
  180. package/src/three/threeWorker.ts +2 -2
  181. package/src/three/waypointSprite.ts +55 -82
  182. package/src/three/waypoints.ts +20 -23
  183. package/src/three/world/cursorBlock.ts +25 -12
  184. package/src/three/world/vr.ts +11 -17
  185. package/src/three/worldGeometryExport.ts +15 -34
  186. package/src/three/worldRendererThree.ts +144 -166
  187. package/src/wasm-mesher/bridge/convertChunk.ts +3 -9
  188. package/src/wasm-mesher/bridge/render-from-wasm.ts +204 -229
  189. package/src/wasm-mesher/bridge/shaderCubeBridge.ts +26 -67
  190. package/src/wasm-mesher/tests/cameraCollisionBlockCache.test.ts +82 -0
  191. package/src/wasm-mesher/tests/cullingRegression.test.ts +307 -0
  192. package/src/wasm-mesher/tests/heightmapParity.test.ts +8 -18
  193. package/src/wasm-mesher/tests/mesherWasmConversionCache.test.ts +2 -2
  194. package/src/wasm-mesher/tests/sectionRaycastAabb.test.ts +1 -93
  195. package/src/wasm-mesher/tests/shaderCubeInstances.test.ts +103 -141
  196. package/src/wasm-mesher/tests/splitColumnWasmOutput.test.ts +14 -22
  197. package/src/wasm-mesher/tests/thirdPersonVoxelRaycast.test.ts +63 -0
  198. package/src/wasm-mesher/worker/mesherWasm.ts +150 -141
  199. package/src/wasm-mesher/worker/mesherWasmConversionCache.ts +2 -8
  200. package/src/wasm-mesher/worker/mesherWasmLightDirty.test.ts +1 -3
  201. package/src/wasm-mesher/worker/mesherWasmLightDirty.ts +1 -5
  202. package/src/wasm-mesher/worker/mesherWasmRequestTracker.ts +42 -42
  203. package/src/worldView/worldView.spiral.test.ts +1 -1
  204. package/src/worldView/worldView.ts +34 -39
  205. package/src/worldView/worldViewWorkerBridge.test.ts +4 -4
@@ -0,0 +1,217 @@
1
+ //@ts-nocheck
2
+ import { describe, expect, test } from 'vitest'
3
+ import * as THREE from 'three'
4
+ import { GlobalBlockBuffer } from '../globalBlockBuffer'
5
+ import { buildVisibleCubeSpans } from '../cubeDrawSpans'
6
+ import { createCubeBlockMaterial } from '../shaders/cubeBlockShader'
7
+ import { packWord3 } from '../../wasm-mesher/bridge/shaderCubeBridge'
8
+
9
+ type BufferInternals = {
10
+ pendingRanges: Array<{ start: number; end: number }>
11
+ }
12
+
13
+ function getInternals(buffer: GlobalBlockBuffer): BufferInternals {
14
+ return buffer as unknown as BufferInternals
15
+ }
16
+
17
+ function drainUploads(buffer: GlobalBlockBuffer): void {
18
+ while (getInternals(buffer).pendingRanges.length) buffer.uploadDirtyRange()
19
+ }
20
+
21
+ function makeSectionWords(faceW0: number[]): Uint32Array {
22
+ const words = new Uint32Array(faceW0.length * 4)
23
+ for (let i = 0; i < faceW0.length; i++) {
24
+ words[i * 4] = faceW0[i]!
25
+ words[i * 4 + 1] = 0
26
+ words[i * 4 + 2] = 0
27
+ words[i * 4 + 3] = packWord3(0, 0)
28
+ }
29
+ return words
30
+ }
31
+
32
+ function finishCurrentMove(buffer: GlobalBlockBuffer): void {
33
+ drainUploads(buffer)
34
+ buffer.compactStep()
35
+ }
36
+
37
+ test('GlobalBlockBuffer: new section gated until upload completes', () => {
38
+ const scene = new THREE.Scene()
39
+ const mat = createCubeBlockMaterial()
40
+ const buffer = new GlobalBlockBuffer(mat, scene)
41
+
42
+ buffer.addSection('a', makeSectionWords([1]), 1)
43
+ expect(buffer.getSectionDrawStart('a')).toBeUndefined()
44
+
45
+ drainUploads(buffer)
46
+ expect(buffer.getSectionDrawStart('a')).toBe(0)
47
+
48
+ buffer.dispose()
49
+ mat.dispose()
50
+ })
51
+
52
+ test('GlobalBlockBuffer: remesh double-buffers old geometry until upload', () => {
53
+ const scene = new THREE.Scene()
54
+ const mat = createCubeBlockMaterial()
55
+ const buffer = new GlobalBlockBuffer(mat, scene)
56
+
57
+ buffer.addSection('a', makeSectionWords([10]), 1)
58
+ drainUploads(buffer)
59
+ const oldStart = buffer.getSectionSlot('a')!.start
60
+
61
+ buffer.addSection('a', makeSectionWords([20]), 1)
62
+ expect(buffer.getSectionDrawStart('a')).toBe(oldStart)
63
+ expect(buffer.hasPendingReplace()).toBe(true)
64
+
65
+ const w0 = buffer.mesh.geometry.getAttribute('a_w0').array as Uint32Array
66
+ expect(w0[oldStart]).toBe(10)
67
+
68
+ const epochBefore = buffer.getUploadEpoch()
69
+ drainUploads(buffer)
70
+ buffer.compactStep()
71
+ expect(buffer.hasPendingReplace()).toBe(false)
72
+ expect(buffer.getUploadEpoch()).toBeGreaterThan(epochBefore)
73
+ finishCurrentMove(buffer)
74
+ expect(buffer.getSectionDrawStart('a')).toBe(buffer.getSectionSlot('a')!.start)
75
+ expect(w0[buffer.getSectionSlot('a')!.start]).toBe(20)
76
+
77
+ buffer.dispose()
78
+ mat.dispose()
79
+ })
80
+
81
+ test('GlobalBlockBuffer: remesh before previous upload completes keeps fully-uploaded fallback', () => {
82
+ const scene = new THREE.Scene()
83
+ const mat = createCubeBlockMaterial()
84
+ const buffer = new GlobalBlockBuffer(mat, scene)
85
+
86
+ buffer.addSection('a', makeSectionWords([10]), 1)
87
+ drainUploads(buffer)
88
+ buffer.compactStep()
89
+ const slotA = buffer.getSectionSlot('a')!.start
90
+
91
+ buffer.addSection('a', makeSectionWords([20]), 1)
92
+ expect(buffer.hasPendingReplace()).toBe(true)
93
+ expect(buffer.hasPendingUploads()).toBe(true)
94
+
95
+ buffer.addSection('a', makeSectionWords([30]), 1)
96
+
97
+ const drawStart = buffer.getSectionDrawStart('a')
98
+ const drawCount = buffer.getSectionDrawCount('a')!
99
+ expect(drawStart).toBe(slotA)
100
+ expect(buffer.isRangeFullyUploaded(drawStart!, drawStart! + drawCount - 1)).toBe(true)
101
+
102
+ const spans = buildVisibleCubeSpans(
103
+ [{ start: drawStart!, count: drawCount }],
104
+ buffer.getHighWatermark(),
105
+ false,
106
+ (start, end) => buffer.isRangeFullyUploaded(start, end),
107
+ buffer.getPendingDirtyRanges()
108
+ )
109
+ expect(spans.length).toBeGreaterThan(0)
110
+ expect(spans.some(s => s.start <= drawStart! && s.start + s.count > drawStart!)).toBe(true)
111
+
112
+ const w0 = buffer.mesh.geometry.getAttribute('a_w0').array as Uint32Array
113
+ expect(w0[slotA]).toBe(10)
114
+
115
+ drainUploads(buffer)
116
+ buffer.compactStep()
117
+ finishCurrentMove(buffer)
118
+ const slotC = buffer.getSectionSlot('a')!.start
119
+ expect(buffer.getSectionDrawStart('a')).toBe(slotC)
120
+ expect(w0[slotC]).toBe(30)
121
+
122
+ buffer.dispose()
123
+ mat.dispose()
124
+ })
125
+
126
+ test('GlobalBlockBuffer: remesh stays drawable when slot kept (updateSection flow)', () => {
127
+ const scene = new THREE.Scene()
128
+ const mat = createCubeBlockMaterial()
129
+ const buffer = new GlobalBlockBuffer(mat, scene)
130
+
131
+ buffer.addSection('a', makeSectionWords([10]), 1)
132
+ drainUploads(buffer)
133
+ expect(buffer.getSectionDrawStart('a')).toBe(0)
134
+
135
+ // updateSection forRemesh: do NOT removeSection before addSection
136
+ buffer.addSection('a', makeSectionWords([20]), 1)
137
+ expect(buffer.getSectionDrawStart('a')).toBeDefined()
138
+ expect(buffer.hasPendingReplace()).toBe(true)
139
+
140
+ drainUploads(buffer)
141
+ buffer.compactStep()
142
+ expect(buffer.hasPendingReplace()).toBe(false)
143
+ finishCurrentMove(buffer)
144
+ expect(buffer.getSectionDrawStart('a')).toBe(buffer.getSectionSlot('a')!.start)
145
+
146
+ buffer.dispose()
147
+ mat.dispose()
148
+ })
149
+
150
+ test('GlobalBlockBuffer: pre-remove before remesh leaves drawable hole until upload', () => {
151
+ const scene = new THREE.Scene()
152
+ const mat = createCubeBlockMaterial()
153
+ const buffer = new GlobalBlockBuffer(mat, scene)
154
+
155
+ buffer.addSection('a', makeSectionWords([10]), 1)
156
+ drainUploads(buffer)
157
+
158
+ buffer.removeSection('a')
159
+ buffer.addSection('a', makeSectionWords([20]), 1)
160
+ expect(buffer.getSectionDrawStart('a')).toBeUndefined()
161
+ expect(buffer.hasPendingReplace()).toBe(false)
162
+
163
+ drainUploads(buffer)
164
+ expect(buffer.getSectionDrawStart('a')).toBeDefined()
165
+
166
+ buffer.dispose()
167
+ mat.dispose()
168
+ })
169
+
170
+ test('GlobalBlockBuffer: canUseFullDrawShortcut false while uploads pending', () => {
171
+ const scene = new THREE.Scene()
172
+ const mat = createCubeBlockMaterial()
173
+ const buffer = new GlobalBlockBuffer(mat, scene)
174
+
175
+ buffer.addSection('a', makeSectionWords([1, 2, 3]), 3)
176
+ expect(buffer.canUseFullDrawShortcut()).toBe(false)
177
+
178
+ drainUploads(buffer)
179
+ expect(buffer.canUseFullDrawShortcut()).toBe(true)
180
+
181
+ buffer.dispose()
182
+ mat.dispose()
183
+ })
184
+
185
+ test('GlobalBlockBuffer: uploadEpoch increments on partial upload advance', () => {
186
+ const scene = new THREE.Scene()
187
+ const mat = createCubeBlockMaterial()
188
+ const buffer = new GlobalBlockBuffer(mat, scene)
189
+
190
+ const faceCount = 20_000
191
+ const words = new Uint32Array(faceCount * 4)
192
+ for (let i = 0; i < faceCount; i++) {
193
+ words[i * 4] = i + 1
194
+ }
195
+ buffer.addSection('a', words, faceCount)
196
+ const epoch0 = buffer.getUploadEpoch()
197
+ buffer.uploadDirtyRange()
198
+ expect(buffer.getUploadEpoch()).toBe(epoch0 + 1)
199
+ expect(buffer.hasPendingUploads()).toBe(true)
200
+
201
+ buffer.dispose()
202
+ mat.dispose()
203
+ })
204
+
205
+ test('GlobalBlockBuffer: uploadEpoch increments when dirty range drains', () => {
206
+ const scene = new THREE.Scene()
207
+ const mat = createCubeBlockMaterial()
208
+ const buffer = new GlobalBlockBuffer(mat, scene)
209
+
210
+ const epoch0 = buffer.getUploadEpoch()
211
+ buffer.addSection('a', makeSectionWords([1]), 1)
212
+ drainUploads(buffer)
213
+ expect(buffer.getUploadEpoch()).toBeGreaterThan(epoch0)
214
+
215
+ buffer.dispose()
216
+ mat.dispose()
217
+ })
@@ -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,50 +13,35 @@ 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 }>
42
- pendingMove: { key: string, oldStart: number, newStart: number, count: number } | null
26
+ pendingRanges: Array<{ start: number; end: number }>
27
+ pendingMove: { key: string; oldStart: number; newStart: number; count: number } | null
43
28
  growCapacity: (minQuads: number) => void
44
29
  }
45
30
 
46
- function getInternals (buffer: GlobalLegacyBuffer): BufferInternals {
31
+ function getInternals(buffer: GlobalLegacyBuffer): BufferInternals {
47
32
  return buffer as unknown as BufferInternals
48
33
  }
49
34
 
50
- function drainUploads (buffer: GlobalLegacyBuffer): void {
35
+ function drainUploads(buffer: GlobalLegacyBuffer): void {
51
36
  while (getInternals(buffer).pendingRanges.length) buffer.uploadDirtyRange()
52
37
  }
53
38
 
54
- function finishCurrentMove (buffer: GlobalLegacyBuffer): void {
39
+ function finishCurrentMove(buffer: GlobalLegacyBuffer): void {
55
40
  drainUploads(buffer)
56
41
  buffer.compactStep()
57
42
  }
58
43
 
59
- function readSectionIndices (buffer: GlobalLegacyBuffer, key: string): number[] {
44
+ function readSectionIndices(buffer: GlobalLegacyBuffer, key: string): number[] {
60
45
  const slot = buffer.getSectionSlot(key)
61
46
  if (!slot) throw new Error(`missing section ${key}`)
62
47
  const indexAttr = buffer.mesh.geometry.index!.array as Uint32Array
@@ -207,7 +192,14 @@ test('GlobalLegacyBuffer: updateDrawSpans opaque merges nearby spans', () => {
207
192
 
208
193
  buffer.addSection('a', geo, 0, 0, 0)
209
194
  buffer.addSection('b', geo, 16, 0, 0)
210
- buffer.updateDrawSpans([{ key: 'a', distSq: 1 }, { key: 'b', distSq: 4 }], 'opaque')
195
+ drainUploads(buffer)
196
+ buffer.updateDrawSpans(
197
+ [
198
+ { key: 'a', distSq: 1 },
199
+ { key: 'b', distSq: 4 }
200
+ ],
201
+ 'opaque'
202
+ )
211
203
 
212
204
  const spans = buffer.getVisibleIndexSpans()
213
205
  expect(spans.length).toBe(1)
@@ -226,7 +218,15 @@ test('GlobalLegacyBuffer: updateDrawSpans opaque full draw when most quads visib
226
218
  buffer.addSection('a', geo, 0, 0, 0)
227
219
  buffer.addSection('b', geo, 16, 0, 0)
228
220
  buffer.addSection('c', geo, 32, 0, 0)
229
- buffer.updateDrawSpans([{ key: 'a', distSq: 1 }, { key: 'b', distSq: 2 }, { key: 'c', distSq: 3 }], 'opaque')
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
230
 
231
231
  const spans = buffer.getVisibleIndexSpans()
232
232
  expect(spans.length).toBe(1)
@@ -245,10 +245,14 @@ test('GlobalLegacyBuffer: updateDrawSpans sortedBlend orders back-to-front', ()
245
245
 
246
246
  buffer.addSection('near', geo, 0, 0, 0)
247
247
  buffer.addSection('far', geo, 16, 0, 0)
248
- buffer.updateDrawSpans([
249
- { key: 'near', distSq: 1 },
250
- { key: 'far', distSq: 100 },
251
- ], 'sortedBlend')
248
+ drainUploads(buffer)
249
+ buffer.updateDrawSpans(
250
+ [
251
+ { key: 'near', distSq: 1 },
252
+ { key: 'far', distSq: 100 }
253
+ ],
254
+ 'sortedBlend'
255
+ )
252
256
 
253
257
  const spans = buffer.getVisibleIndexSpans()
254
258
  expect(spans.length).toBe(2)
@@ -266,6 +270,7 @@ test('GlobalLegacyBuffer: updateDrawSpans skips missing keys', () => {
266
270
  const geo = makeQuadGeometry()
267
271
 
268
272
  buffer.addSection('a', geo, 0, 0, 0)
273
+ drainUploads(buffer)
269
274
  buffer.updateDrawSpans([{ key: 'missing', distSq: 1 }], 'opaque')
270
275
 
271
276
  expect(buffer.getVisibleIndexSpans().length).toBe(0)
@@ -281,6 +286,7 @@ test('GlobalLegacyBuffer: reset clears visible spans', () => {
281
286
  const geo = makeQuadGeometry()
282
287
 
283
288
  buffer.addSection('a', geo, 0, 0, 0)
289
+ drainUploads(buffer)
284
290
  buffer.updateDrawSpans([{ key: 'a', distSq: 1 }], 'opaque')
285
291
  expect(buffer.getVisibleIndexSpans().length).toBeGreaterThan(0)
286
292
 
@@ -291,14 +297,14 @@ test('GlobalLegacyBuffer: reset clears visible spans', () => {
291
297
  mat.dispose()
292
298
  })
293
299
 
294
- test('GlobalLegacyBuffer: updateDrawSpans opaque caps at MAX_OPAQUE_SPANS with full coverage', () => {
300
+ test('GlobalLegacyBuffer: updateDrawSpans opaque does not bridge interior gaps', () => {
295
301
  const scene = new THREE.Scene()
296
302
  const mat = createGlobalLegacyBlockMaterial()
297
303
  const visibleSectionCount = MAX_OPAQUE_SPANS + 5
298
304
  const padQuads = 257
299
305
  const buffer = new GlobalLegacyBuffer(mat, scene, {
300
306
  initialCapacityQuads: visibleSectionCount * (padQuads + 1) + padQuads,
301
- growthIncrementQuads: 1024,
307
+ growthIncrementQuads: 1024
302
308
  })
303
309
  const geo = makeQuadGeometry()
304
310
  const padGeo = {
@@ -308,13 +314,13 @@ test('GlobalLegacyBuffer: updateDrawSpans opaque caps at MAX_OPAQUE_SPANS with f
308
314
  skyLights: new Float32Array(padQuads * 4).fill(1),
309
315
  blockLights: new Float32Array(padQuads * 4).fill(0),
310
316
  uvs: new Float32Array(padQuads * 4 * 2),
311
- indices: new Uint32Array(padQuads * 6),
317
+ indices: new Uint32Array(padQuads * 6)
312
318
  }
313
319
  for (let q = 0; q < padQuads; q++) {
314
320
  const vb = q * 4
315
321
  padGeo.indices.set([vb, vb + 1, vb + 2, vb, vb + 2, vb + 3], q * 6)
316
322
  }
317
- const visible: Array<{ key: string, distSq: number }> = []
323
+ const visible: Array<{ key: string; distSq: number }> = []
318
324
 
319
325
  for (let i = 0; i < visibleSectionCount; i++) {
320
326
  const key = `s${i}`
@@ -331,10 +337,11 @@ test('GlobalLegacyBuffer: updateDrawSpans opaque caps at MAX_OPAQUE_SPANS with f
331
337
  expect(next.start - (cur.start + cur.count)).toBeGreaterThan(256)
332
338
  }
333
339
 
340
+ drainUploads(buffer)
334
341
  buffer.updateDrawSpans(visible, 'opaque')
335
342
 
336
343
  const spans = buffer.getVisibleIndexSpans()
337
- expect(spans.length).toBe(MAX_OPAQUE_SPANS)
344
+ expect(spans.length).toBe(visibleSectionCount)
338
345
 
339
346
  const covered = new Set<number>()
340
347
  for (const span of spans) {
@@ -366,7 +373,7 @@ test('GlobalLegacyBuffer: addSection rejects non-quad geometry', () => {
366
373
  skyLights: new Float32Array(3).fill(1),
367
374
  blockLights: new Float32Array(3).fill(0),
368
375
  uvs: new Float32Array(6),
369
- indices: new Uint32Array([0, 1, 2]),
376
+ indices: new Uint32Array([0, 1, 2])
370
377
  }
371
378
  expect(buffer.addSection('bad', bad, 0, 0, 0)).toBe(false)
372
379
 
@@ -469,10 +476,10 @@ test('GlobalLegacyBuffer: sortedBlend accepts more than MAX_OPAQUE_SPANS section
469
476
  const sectionCount = MAX_OPAQUE_SPANS + 6
470
477
  const buffer = new GlobalLegacyBuffer(mat, scene, {
471
478
  initialCapacityQuads: sectionCount + 4,
472
- growthIncrementQuads: 64,
479
+ growthIncrementQuads: 64
473
480
  })
474
481
  const geo = makeQuadGeometry()
475
- const visible: Array<{ key: string, distSq: number }> = []
482
+ const visible: Array<{ key: string; distSq: number }> = []
476
483
 
477
484
  for (let i = 0; i < sectionCount; i++) {
478
485
  const key = `s${i}`
@@ -480,6 +487,7 @@ test('GlobalLegacyBuffer: sortedBlend accepts more than MAX_OPAQUE_SPANS section
480
487
  visible.push({ key, distSq: sectionCount - i })
481
488
  }
482
489
 
490
+ drainUploads(buffer)
483
491
  buffer.updateDrawSpans(visible, 'sortedBlend')
484
492
  expect(buffer.getVisibleIndexSpans().length).toBe(sectionCount)
485
493
 
@@ -487,6 +495,257 @@ test('GlobalLegacyBuffer: sortedBlend accepts more than MAX_OPAQUE_SPANS section
487
495
  mat.dispose()
488
496
  })
489
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
+
490
749
  test('GlobalLegacyBuffer: suppressThreeDraw uses minimal non-zero draw range', () => {
491
750
  const scene = new THREE.Scene()
492
751
  const mat = createGlobalLegacyBlockMaterial()