minecraft-renderer 0.1.79 → 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 +27 -27
  6. package/dist/minecraft-renderer.js.meta.json +1 -1
  7. package/dist/threeWorker.js +133 -133
  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 +239 -282
  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 +167 -100
  126. package/src/three/globalLegacyBuffer.ts +257 -166
  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 +10 -16
  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 +34 -34
  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 +300 -41
  170. package/src/three/tests/legacyMultiDraw.test.ts +8 -29
  171. package/src/three/tests/pr3PerFrameCpuCuts.test.ts +106 -29
  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 +117 -129
  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,12 +1,8 @@
1
1
  //@ts-nocheck
2
2
  import { expect, test, vi } from 'vitest'
3
- import {
4
- createLegacyMultiDrawScratch,
5
- detectLegacyMultiDrawCaps,
6
- drawLegacySpans,
7
- } from '../legacyMultiDraw'
3
+ import { createLegacyMultiDrawScratch, detectLegacyMultiDrawCaps, drawLegacySpans } from '../legacyMultiDraw'
8
4
 
9
- function makeMockGl (opts?: { multiDraw?: boolean }): WebGL2RenderingContext {
5
+ function makeMockGl(opts?: { multiDraw?: boolean }): WebGL2RenderingContext {
10
6
  const multiDrawElementsWEBGL = vi.fn()
11
7
  const drawElements = vi.fn()
12
8
  return {
@@ -18,7 +14,7 @@ function makeMockGl (opts?: { multiDraw?: boolean }): WebGL2RenderingContext {
18
14
  }
19
15
  return null
20
16
  },
21
- drawElements,
17
+ drawElements
22
18
  } as unknown as WebGL2RenderingContext
23
19
  }
24
20
 
@@ -42,7 +38,7 @@ test('drawLegacySpans: tier A uses multiDrawElementsWEBGL with byte offsets', ()
42
38
  const scratch = createLegacyMultiDrawScratch()
43
39
  const spans = [
44
40
  { indexStart: 12, indexCount: 6 },
45
- { indexStart: 30, indexCount: 12 },
41
+ { indexStart: 30, indexCount: 12 }
46
42
  ]
47
43
 
48
44
  drawLegacySpans(gl, caps, spans, scratch)
@@ -54,13 +50,7 @@ test('drawLegacySpans: tier A uses multiDrawElementsWEBGL with byte offsets', ()
54
50
 
55
51
  const ext = caps.ext as { multiDrawElementsWEBGL: ReturnType<typeof vi.fn> }
56
52
  expect(ext.multiDrawElementsWEBGL).toHaveBeenCalledTimes(1)
57
- expect(ext.multiDrawElementsWEBGL).toHaveBeenCalledWith(
58
- gl.TRIANGLES,
59
- scratch.counts, 0,
60
- gl.UNSIGNED_INT,
61
- scratch.offsets, 0,
62
- 2,
63
- )
53
+ expect(ext.multiDrawElementsWEBGL).toHaveBeenCalledWith(gl.TRIANGLES, scratch.counts, 0, gl.UNSIGNED_INT, scratch.offsets, 0, 2)
64
54
  })
65
55
 
66
56
  test('drawLegacySpans: tier B loops drawElements per span', () => {
@@ -71,12 +61,7 @@ test('drawLegacySpans: tier B loops drawElements per span', () => {
71
61
 
72
62
  drawLegacySpans(gl, caps, spans, scratch)
73
63
 
74
- expect((gl as unknown as { drawElements: ReturnType<typeof vi.fn> }).drawElements).toHaveBeenCalledWith(
75
- gl.TRIANGLES,
76
- 6,
77
- gl.UNSIGNED_INT,
78
- 16,
79
- )
64
+ expect((gl as unknown as { drawElements: ReturnType<typeof vi.fn> }).drawElements).toHaveBeenCalledWith(gl.TRIANGLES, 6, gl.UNSIGNED_INT, 16)
80
65
  })
81
66
 
82
67
  test('drawLegacySpans: grows scratch past MAX_OPAQUE_SPANS for uncapped blend sets', () => {
@@ -88,7 +73,7 @@ test('drawLegacySpans: grows scratch past MAX_OPAQUE_SPANS for uncapped blend se
88
73
  const spanCount = 70
89
74
  const spans = Array.from({ length: spanCount }, (_, i) => ({
90
75
  indexStart: i * 6,
91
- indexCount: 6,
76
+ indexCount: 6
92
77
  }))
93
78
 
94
79
  drawLegacySpans(gl, caps, spans, scratch)
@@ -99,11 +84,5 @@ test('drawLegacySpans: grows scratch past MAX_OPAQUE_SPANS for uncapped blend se
99
84
  expect(scratch.offsets[69]).toBe(69 * 6 * 4)
100
85
 
101
86
  const ext = caps.ext as { multiDrawElementsWEBGL: ReturnType<typeof vi.fn> }
102
- expect(ext.multiDrawElementsWEBGL).toHaveBeenCalledWith(
103
- gl.TRIANGLES,
104
- scratch.counts, 0,
105
- gl.UNSIGNED_INT,
106
- scratch.offsets, 0,
107
- spanCount,
108
- )
87
+ expect(ext.multiDrawElementsWEBGL).toHaveBeenCalledWith(gl.TRIANGLES, scratch.counts, 0, gl.UNSIGNED_INT, scratch.offsets, 0, spanCount)
109
88
  })
@@ -3,7 +3,7 @@ import { test, expect, vi, afterEach } from 'vitest'
3
3
  import * as THREE from 'three'
4
4
 
5
5
  vi.mock('../entity/EntityMesh', () => ({
6
- getMesh: vi.fn(),
6
+ getMesh: vi.fn()
7
7
  }))
8
8
 
9
9
  import { ChunkMeshManager } from '../chunkMeshManager'
@@ -11,13 +11,8 @@ import type { GlobalLegacyBuffer } from '../globalLegacyBuffer'
11
11
  import type { WorldRendererThree } from '../worldRendererThree'
12
12
  import type { MesherGeometryOutput } from '../../mesher-shared/shared'
13
13
 
14
- function makeQuadArrays () {
15
- const positions = new Float32Array([
16
- -1, -1, -1,
17
- -1, 1, -1,
18
- -1, 1, 1,
19
- -1, -1, 1,
20
- ])
14
+ function makeQuadArrays() {
15
+ const positions = new Float32Array([-1, -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1])
21
16
  const colors = new Float32Array(12).fill(1)
22
17
  const skyLights = new Float32Array(4).fill(1)
23
18
  const blockLights = new Float32Array(4).fill(0)
@@ -26,7 +21,7 @@ function makeQuadArrays () {
26
21
  return { positions, colors, skyLights, blockLights, uvs, indices }
27
22
  }
28
23
 
29
- function makeBlendOnlyGeometry (): MesherGeometryOutput {
24
+ function makeBlendOnlyGeometry(): MesherGeometryOutput {
30
25
  const blend = makeQuadArrays()
31
26
  return {
32
27
  sectionYNumber: 0,
@@ -62,12 +57,12 @@ function makeBlendOnlyGeometry (): MesherGeometryOutput {
62
57
  skyLights: blend.skyLights,
63
58
  blockLights: blend.blockLights,
64
59
  uvs: blend.uvs,
65
- indices: blend.indices,
66
- },
60
+ indices: blend.indices
61
+ }
67
62
  }
68
63
  }
69
64
 
70
- function makeOpaqueOnlyGeometry (sx = 8, sy = 8, sz = 8): MesherGeometryOutput {
65
+ function makeOpaqueOnlyGeometry(sx = 8, sy = 8, sz = 8): MesherGeometryOutput {
71
66
  const opaque = makeQuadArrays()
72
67
  return {
73
68
  sectionYNumber: 0,
@@ -95,11 +90,11 @@ function makeOpaqueOnlyGeometry (sx = 8, sy = 8, sz = 8): MesherGeometryOutput {
95
90
  signs: {},
96
91
  banners: {},
97
92
  hadErrors: false,
98
- blocksCount: 1,
93
+ blocksCount: 1
99
94
  }
100
95
  }
101
96
 
102
- function makeMixedGeometry (): MesherGeometryOutput {
97
+ function makeMixedGeometry(): MesherGeometryOutput {
103
98
  const opaque = makeQuadArrays()
104
99
  const blend = makeQuadArrays()
105
100
  return {
@@ -136,12 +131,12 @@ function makeMixedGeometry (): MesherGeometryOutput {
136
131
  skyLights: blend.skyLights,
137
132
  blockLights: blend.blockLights,
138
133
  uvs: blend.uvs,
139
- indices: blend.indices,
140
- },
134
+ indices: blend.indices
135
+ }
141
136
  }
142
137
  }
143
138
 
144
- function createManager (): ChunkMeshManager {
139
+ function createManager(): ChunkMeshManager {
145
140
  const scene = new THREE.Scene()
146
141
  const material = new THREE.MeshBasicMaterial()
147
142
  const worldRenderer = {
@@ -150,25 +145,25 @@ function createManager (): ChunkMeshManager {
150
145
  sceneOrigin: {
151
146
  track: () => {},
152
147
  removeAndUntrack: () => {},
153
- removeAndUntrackAll: () => {},
148
+ removeAndUntrackAll: () => {}
154
149
  },
155
150
  blockEntities: {},
156
- worldRendererConfig: {},
151
+ worldRendererConfig: {}
157
152
  } as unknown as WorldRendererThree
158
153
  return new ChunkMeshManager(worldRenderer, scene, material, 256, 1)
159
154
  }
160
155
 
161
156
  type ManagerInternals = {
162
- legacyCullSections: Map<string, { worldX: number, worldY: number, worldZ: number }>
157
+ legacyCullSections: Map<string, { worldX: number; worldY: number; worldZ: number }>
163
158
  registerLegacyCullSection: (key: string, wx: number, wy: number, wz: number) => void
164
159
  maybeUnregisterLegacyCullSection: (key: string) => void
165
160
  }
166
161
 
167
- function getLegacyCullSections (manager: ChunkMeshManager): Map<string, { worldX: number, worldY: number, worldZ: number }> {
162
+ function getLegacyCullSections(manager: ChunkMeshManager): Map<string, { worldX: number; worldY: number; worldZ: number }> {
168
163
  return (manager as unknown as ManagerInternals).legacyCullSections
169
164
  }
170
165
 
171
- function makeCamera (x: number, y: number, z: number): THREE.PerspectiveCamera {
166
+ function makeCamera(x: number, y: number, z: number): THREE.PerspectiveCamera {
172
167
  const camera = new THREE.PerspectiveCamera(60, 1, 0.1, 1000)
173
168
  camera.position.set(x, y, z)
174
169
  camera.lookAt(8, 8, 8)
@@ -225,9 +220,14 @@ test('updateSectionCullAndSort: same visible set skips span rebuild on camera mo
225
220
  manager.updateSectionCullAndSort(camera1, 8, 8, 20)
226
221
  expect(updateDrawSpansSpy).toHaveBeenCalledTimes(1)
227
222
 
223
+ while (blendBuffer.hasPendingUploads()) blendBuffer.uploadDirtyRange()
224
+
225
+ manager.updateSectionCullAndSort(camera1, 8, 8, 20)
226
+ expect(updateDrawSpansSpy).toHaveBeenCalledTimes(2)
227
+
228
228
  const camera2 = makeCamera(8, 8, 18)
229
229
  manager.updateSectionCullAndSort(camera2, 8, 8, 18)
230
- expect(updateDrawSpansSpy).toHaveBeenCalledTimes(1)
230
+ expect(updateDrawSpansSpy).toHaveBeenCalledTimes(2)
231
231
 
232
232
  manager.cleanupSection(key)
233
233
  manager.dispose()
@@ -246,11 +246,7 @@ test('updateSectionCullAndSort: layoutVersion change forces span rebuild', () =>
246
246
  expect(updateDrawSpansSpy).toHaveBeenCalledTimes(1)
247
247
 
248
248
  blendBuffer.removeSection(key)
249
- blendBuffer.addSection(
250
- key,
251
- makeBlendOnlyGeometry().blend!,
252
- 8, 8, 8,
253
- )
249
+ blendBuffer.addSection(key, makeBlendOnlyGeometry().blend!, 8, 8, 8)
254
250
  ;(manager as unknown as ManagerInternals).registerLegacyCullSection(key, 8, 8, 8)
255
251
 
256
252
  manager.updateSectionCullAndSort(camera, 8, 8, 20)
@@ -260,10 +256,91 @@ test('updateSectionCullAndSort: layoutVersion change forces span rebuild', () =>
260
256
  manager.dispose()
261
257
  })
262
258
 
263
- function drainLegacyUploads (buffer: GlobalLegacyBuffer): void {
259
+ function drainLegacyUploads(buffer: GlobalLegacyBuffer): void {
264
260
  while (buffer.hasPendingUploads()) buffer.uploadDirtyRange()
265
261
  }
266
262
 
263
+ function simulateRenderCullGate(manager: ChunkMeshManager, camera: THREE.PerspectiveCamera, x: number, y: number, z: number): void {
264
+ for (const buf of [manager.globalLegacyBuffer, manager.globalLegacyBlendBuffer, manager.globalBlockBuffer]) {
265
+ if (!buf) continue
266
+ buf.compactStep()
267
+ if (buf.hasPendingUploads()) buf.uploadDirtyRange()
268
+ }
269
+ manager.markCullDirtyIfBufferStateChanged()
270
+ if (manager.cullDirty) {
271
+ manager.updateSectionCullAndSort(camera, x, y, z)
272
+ manager.clearCullDirty()
273
+ }
274
+ }
275
+
276
+ test('markCullDirtyIfBufferStateChanged: finalize layout bump marks cull dirty', () => {
277
+ const manager = createManager()
278
+ const key = '0,0,0'
279
+ manager.updateSection(key, makeOpaqueOnlyGeometry(8, 8, 8))
280
+
281
+ const opaqueBuf = manager.globalLegacyBuffer!
282
+ drainLegacyUploads(opaqueBuf)
283
+
284
+ const camera = makeCamera(8, 8, 20)
285
+ manager.updateSectionCullAndSort(camera, 8, 8, 20)
286
+ manager.clearCullDirty()
287
+
288
+ opaqueBuf.addSection(
289
+ key,
290
+ {
291
+ positions: makeQuadArrays().positions,
292
+ colors: makeQuadArrays().colors,
293
+ skyLights: makeQuadArrays().skyLights,
294
+ blockLights: makeQuadArrays().blockLights,
295
+ uvs: makeQuadArrays().uvs,
296
+ indices: makeQuadArrays().indices
297
+ },
298
+ 16,
299
+ 8,
300
+ 8
301
+ )
302
+ drainLegacyUploads(opaqueBuf)
303
+ opaqueBuf.compactStep()
304
+ expect(opaqueBuf.hasPendingReplace()).toBe(false)
305
+
306
+ manager.markCullDirtyIfBufferStateChanged()
307
+ expect(manager.cullDirty).toBe(true)
308
+
309
+ manager.cleanupSection(key)
310
+ manager.dispose()
311
+ })
312
+
313
+ test('simulateRenderCullGate: finalize remesh refreshes visible spans without camera move', () => {
314
+ const manager = createManager()
315
+ const key = '0,0,0'
316
+ const camera = makeCamera(8, 8, 20)
317
+
318
+ manager.updateSection(key, makeOpaqueOnlyGeometry(8, 8, 8))
319
+ const opaqueBuf = manager.globalLegacyBuffer!
320
+ drainLegacyUploads(opaqueBuf)
321
+ simulateRenderCullGate(manager, camera, 8, 8, 20)
322
+
323
+ const slotA = opaqueBuf.getSectionSlot(key)!.start
324
+ opaqueBuf.updateDrawSpans([{ key, distSq: 1 }], 'opaque')
325
+ expect(opaqueBuf.getVisibleIndexSpans()[0]!.indexStart).toBe(slotA * 6)
326
+
327
+ manager.updateSection(key, makeOpaqueOnlyGeometry(16, 8, 8))
328
+ for (let i = 0; i < 32 && (manager.hasPendingBufferWork() || opaqueBuf.hasPendingReplace() || opaqueBuf.hasPendingUploads()); i++) {
329
+ simulateRenderCullGate(manager, camera, 8, 8, 20)
330
+ }
331
+
332
+ expect(opaqueBuf.hasPendingReplace()).toBe(false)
333
+ expect(opaqueBuf.hasPendingUploads()).toBe(false)
334
+
335
+ const drawStart = opaqueBuf.getSectionDrawStart(key)!
336
+ const spans = opaqueBuf.getVisibleIndexSpans()
337
+ expect(spans.length).toBeGreaterThan(0)
338
+ expect(spans[0]!.indexStart).toBe(drawStart * 6)
339
+
340
+ manager.cleanupSection(key)
341
+ manager.dispose()
342
+ })
343
+
267
344
  test('updateSectionCullAndSort: defrag finalize forces span rebuild with static camera', () => {
268
345
  const manager = createManager()
269
346
  const keys = ['0,0,0', '1,0,0', '2,0,0'] as const
@@ -4,21 +4,21 @@ import * as THREE from 'three'
4
4
 
5
5
  const renderSignMock = vi.fn()
6
6
  vi.mock('../../sign-renderer', () => ({
7
- renderSign: (...args: unknown[]) => renderSignMock(...args),
7
+ renderSign: (...args: unknown[]) => renderSignMock(...args)
8
8
  }))
9
9
 
10
10
  vi.mock('prismarine-chat', () => ({
11
- default: () => () => ({}),
11
+ default: () => () => ({})
12
12
  }))
13
13
 
14
14
  import { getSignTexture, releaseSignTexture, disposeAllSignTextures } from '../signTextureCache'
15
15
  import type { WorldRendererThree } from '../worldRendererThree'
16
16
 
17
- function createWorldRenderer (): WorldRendererThree {
17
+ function createWorldRenderer(): WorldRendererThree {
18
18
  return { version: '1.20' } as WorldRendererThree
19
19
  }
20
20
 
21
- function stubCanvas () {
21
+ function stubCanvas() {
22
22
  return { width: 64, height: 32 } as HTMLCanvasElement
23
23
  }
24
24
 
@@ -11,14 +11,14 @@ type ControlModeConfig = {
11
11
  }
12
12
 
13
13
  interface MediaProperties {
14
- position: { x: number, y: number, z: number }
15
- size: { width: number, height: number }
14
+ position: { x: number; y: number; z: number }
15
+ size: { width: number; height: number }
16
16
  src: string
17
17
  rotation?: 0 | 1 | 2 | 3 // 0-3 for 0°, 90°, 180°, 270°
18
18
  doubleSide?: boolean
19
19
  background?: number // Hexadecimal color (e.g., 0x000000 for black)
20
20
  opacity?: number // 0-1 value for transparency
21
- uvMapping?: { startU: number, endU: number, startV: number, endV: number }
21
+ uvMapping?: { startU: number; endU: number; startV: number; endV: number }
22
22
  allowOrigins?: string[] | boolean
23
23
  loop?: boolean
24
24
  volume?: number
@@ -34,12 +34,7 @@ interface MediaData {
34
34
  video: HTMLVideoElement | undefined
35
35
  pausedBecuaseHidden: boolean
36
36
  texture: THREE.Texture
37
- updateUVMapping: (config: {
38
- startU: number
39
- endU: number
40
- startV: number
41
- endV: number
42
- }) => void
37
+ updateUVMapping: (config: { startU: number; endU: number; startV: number; endV: number }) => void
43
38
  positionalAudio?: THREE.PositionalAudio
44
39
  hadAutoPlayError?: boolean
45
40
  ended?: boolean
@@ -95,7 +90,7 @@ export class ThreeJsMedia {
95
90
  ctx.clearRect(0, 0, canvas.width, canvas.height)
96
91
 
97
92
  // Add background color
98
- ctx.fillStyle = `rgba(${background >> 16 & 255}, ${background >> 8 & 255}, ${background & 255}, 1)`
93
+ ctx.fillStyle = `rgba(${(background >> 16) & 255}, ${(background >> 8) & 255}, ${background & 255}, 1)`
99
94
  ctx.fillRect(0, 0, canvas.width, canvas.height)
100
95
 
101
96
  // Add red text with size relative to canvas dimensions
@@ -167,7 +162,13 @@ export class ThreeJsMedia {
167
162
 
168
163
  const originSecurityError = props.allowOrigins !== undefined && !this.validateOrigin(props.src, props.allowOrigins)
169
164
  if (originSecurityError) {
170
- console.warn('Remote resource blocked due to security policy', props.src, 'allowed origins:', props.allowOrigins, 'you can control it with `remoteContentNotSameOrigin` option')
165
+ console.warn(
166
+ 'Remote resource blocked due to security policy',
167
+ props.src,
168
+ 'allowed origins:',
169
+ props.allowOrigins,
170
+ 'you can control it with `remoteContentNotSameOrigin` option'
171
+ )
171
172
  props.src = ''
172
173
  }
173
174
 
@@ -247,12 +248,11 @@ export class ThreeJsMedia {
247
248
  })
248
249
  }
249
250
 
250
-
251
251
  // Create background texture first
252
252
  const backgroundTexture = this.createBackgroundTexture(
253
253
  props.size.width,
254
254
  props.size.height,
255
- props.background,
255
+ props.background
256
256
  // props.opacity ?? 1
257
257
  )
258
258
 
@@ -278,30 +278,32 @@ export class ThreeJsMedia {
278
278
  if (video) {
279
279
  texture = new THREE.VideoTexture(video)
280
280
  } else if (workerVideoUnsupported) {
281
- texture = this.createErrorTexture(
282
- props.size.width,
283
- props.size.height,
284
- props.background,
285
- 'Video unavailable (multi-thread)',
286
- )
281
+ texture = this.createErrorTexture(props.size.width, props.size.height, props.background, 'Video unavailable (multi-thread)')
287
282
  } else if (isWebWorker) {
288
283
  const loaded = loadThreeJsTextureFromUrlSync(props.src)
289
284
  texture = loaded.texture
290
285
  texture.minFilter = THREE.NearestFilter
291
286
  texture.magFilter = THREE.NearestFilter
292
- void loaded.promise.then(() => {
293
- if (this.customMedia.get(id)?.texture === texture) {
294
- material.map = texture
295
- material.needsUpdate = true
296
- }
297
- }).catch(() => handleError())
287
+ void loaded.promise
288
+ .then(() => {
289
+ if (this.customMedia.get(id)?.texture === texture) {
290
+ material.map = texture
291
+ material.needsUpdate = true
292
+ }
293
+ })
294
+ .catch(() => handleError())
298
295
  } else {
299
- texture = new THREE.TextureLoader().load(props.src, () => {
300
- if (this.customMedia.get(id)?.texture === texture) {
301
- material.map = texture
302
- material.needsUpdate = true
303
- }
304
- }, undefined, () => handleError()) // todo cache
296
+ texture = new THREE.TextureLoader().load(
297
+ props.src,
298
+ () => {
299
+ if (this.customMedia.get(id)?.texture === texture) {
300
+ material.map = texture
301
+ material.needsUpdate = true
302
+ }
303
+ },
304
+ undefined,
305
+ () => handleError()
306
+ ) // todo cache
305
307
  }
306
308
  texture.minFilter = THREE.NearestFilter
307
309
  texture.magFilter = THREE.NearestFilter
@@ -312,7 +314,13 @@ export class ThreeJsMedia {
312
314
  // Create inner mesh for offsets
313
315
  const mesh = new THREE.Mesh(geometry, material)
314
316
 
315
- const { mesh: panel } = this.positionMeshExact(mesh, THREE.MathUtils.degToRad((props.rotation ?? 0) * 90), props.position, props.size.width, props.size.height)
317
+ const { mesh: panel } = this.positionMeshExact(
318
+ mesh,
319
+ THREE.MathUtils.degToRad((props.rotation ?? 0) * 90),
320
+ props.position,
321
+ props.size.width,
322
+ props.size.height
323
+ )
316
324
 
317
325
  scene.add(panel)
318
326
 
@@ -336,7 +344,7 @@ export class ThreeJsMedia {
336
344
  }
337
345
 
338
346
  // UV mapping configuration
339
- const updateUVMapping = (config: { startU: number, endU: number, startV: number, endV: number }) => {
347
+ const updateUVMapping = (config: { startU: number; endU: number; startV: number; endV: number }) => {
340
348
  const uvs = geometry.attributes.uv.array as Float32Array
341
349
  uvs[0] = config.startU
342
350
  uvs[1] = config.startV
@@ -368,7 +376,7 @@ export class ThreeJsMedia {
368
376
  if (videoData.destroyed) return
369
377
  console.error(`Failed to play video ${id}:`, err)
370
378
  // TODO!
371
- const t = /* translate ?? */(txt => txt)
379
+ const t = /* translate ?? */ txt => txt
372
380
  handleError(err.name === 'NotAllowedError' || err.name === 'AbortError' ? t('Waiting for user interaction') : t('Failed to auto play'))
373
381
  }
374
382
  }
@@ -398,14 +406,15 @@ export class ThreeJsMedia {
398
406
  return
399
407
  }
400
408
 
401
- void videoData.video.play()
409
+ void videoData.video
410
+ .play()
402
411
  .then(() => {
403
412
  videoData.hadAutoPlayError = false
404
413
  console.log(`Playing video ${id}`)
405
414
  })
406
415
  .catch(err => {
407
416
  if (videoData.pausedBecuaseHidden) return
408
- if (err.name === 'NotAllowedError' || err.name === 'AbortError' || err.message?.includes('not allowed') && !videoData.pausedBecuaseHidden) {
417
+ if (err.name === 'NotAllowedError' || err.name === 'AbortError' || (err.message?.includes('not allowed') && !videoData.pausedBecuaseHidden)) {
409
418
  videoData.hadAutoPlayError = true
410
419
  }
411
420
  videoData.handleError(err)
@@ -518,14 +527,7 @@ export class ThreeJsMedia {
518
527
  * @param depth Depth of the mesh (default: 1)
519
528
  * @returns The positioned mesh for chaining
520
529
  */
521
- positionMeshExact(
522
- mesh: THREE.Mesh,
523
- rotation: number,
524
- startPosition: { x: number, y: number, z: number },
525
- width: number,
526
- height: number,
527
- depth = 1
528
- ) {
530
+ positionMeshExact(mesh: THREE.Mesh, rotation: number, startPosition: { x: number; y: number; z: number }, width: number, height: number, depth = 1) {
529
531
  // avoid z-fighting with the ground plane
530
532
  if (rotation === 0) {
531
533
  startPosition.z += 0.001
@@ -536,7 +538,7 @@ export class ThreeJsMedia {
536
538
  if (rotation === Math.PI) {
537
539
  startPosition.z -= 0.001
538
540
  }
539
- if (rotation === 3 * Math.PI / 2) {
541
+ if (rotation === (3 * Math.PI) / 2) {
540
542
  startPosition.x += 0.001
541
543
  }
542
544
 
@@ -547,12 +549,11 @@ export class ThreeJsMedia {
547
549
  if (rotation === Math.PI) {
548
550
  startPosition.x += 1
549
551
  }
550
- if (rotation === 3 * Math.PI / 2) {
552
+ if (rotation === (3 * Math.PI) / 2) {
551
553
  startPosition.z += 1
552
554
  startPosition.x += 1
553
555
  }
554
556
 
555
-
556
557
  // First, clean up any previous transformations
557
558
  mesh.matrix.identity()
558
559
  mesh.position.set(0, 0, 0)
@@ -563,7 +564,7 @@ export class ThreeJsMedia {
563
564
  // We need to set up the proper orientation for our use case
564
565
  // Rotate the plane to face the correct direction based on the rotation parameter
565
566
  mesh.rotateY(rotation)
566
- if (rotation === Math.PI / 2 || rotation === 3 * Math.PI / 2) {
567
+ if (rotation === Math.PI / 2 || rotation === (3 * Math.PI) / 2) {
567
568
  mesh.rotateZ(-Math.PI)
568
569
  mesh.rotateX(-Math.PI)
569
570
  }
@@ -585,36 +586,24 @@ export class ThreeJsMedia {
585
586
  debugGroup.add(mesh)
586
587
 
587
588
  // Add a marker at the starting position (should be exactly at pos)
588
- const startMarker = new THREE.Mesh(
589
- new THREE.BoxGeometry(0.1, 0.1, 0.1),
590
- new THREE.MeshBasicMaterial({ color: 0xff_00_00 })
591
- )
589
+ const startMarker = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.1, 0.1), new THREE.MeshBasicMaterial({ color: 0xff_00_00 }))
592
590
  startMarker.position.set(startPosition.x, startPosition.y, startPosition.z)
593
591
  debugGroup.add(startMarker)
594
592
 
595
593
  // Add a marker at the end position (width units away in the rotated direction)
596
594
  const endX = startPosition.x + Math.cos(rotation) * width
597
595
  const endZ = startPosition.z + Math.sin(rotation) * width
598
- const endYMarker = new THREE.Mesh(
599
- new THREE.BoxGeometry(0.1, 0.1, 0.1),
600
- new THREE.MeshBasicMaterial({ color: 0x00_00_ff })
601
- )
596
+ const endYMarker = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.1, 0.1), new THREE.MeshBasicMaterial({ color: 0x00_00_ff }))
602
597
  endYMarker.position.set(startPosition.x, startPosition.y + height, startPosition.z)
603
598
  debugGroup.add(endYMarker)
604
599
 
605
600
  // Add a marker at the width endpoint
606
- const endWidthMarker = new THREE.Mesh(
607
- new THREE.BoxGeometry(0.1, 0.1, 0.1),
608
- new THREE.MeshBasicMaterial({ color: 0xff_ff_00 })
609
- )
601
+ const endWidthMarker = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.1, 0.1), new THREE.MeshBasicMaterial({ color: 0xff_ff_00 }))
610
602
  endWidthMarker.position.set(endX, startPosition.y, endZ)
611
603
  debugGroup.add(endWidthMarker)
612
604
 
613
605
  // Add a marker at the corner diagonal endpoint (both width and height)
614
- const endCornerMarker = new THREE.Mesh(
615
- new THREE.BoxGeometry(0.1, 0.1, 0.1),
616
- new THREE.MeshBasicMaterial({ color: 0xff_00_ff })
617
- )
606
+ const endCornerMarker = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.1, 0.1), new THREE.MeshBasicMaterial({ color: 0xff_00_ff }))
618
607
  endCornerMarker.position.set(endX, startPosition.y + height, endZ)
619
608
  debugGroup.add(endCornerMarker)
620
609
 
@@ -624,12 +613,7 @@ export class ThreeJsMedia {
624
613
  this.worldRenderer.sceneOrigin.toSceneY(startPosition.y),
625
614
  this.worldRenderer.sceneOrigin.toSceneZ(startPosition.z)
626
615
  )
627
- const directionHelper = new THREE.ArrowHelper(
628
- new THREE.Vector3(Math.cos(rotation), 0, Math.sin(rotation)),
629
- sceneStartPos,
630
- 1,
631
- 0xff_00_00
632
- )
616
+ const directionHelper = new THREE.ArrowHelper(new THREE.Vector3(Math.cos(rotation), 0, Math.sin(rotation)), sceneStartPos, 1, 0xff_00_00)
633
617
  debugGroup.add(directionHelper)
634
618
 
635
619
  return {
@@ -708,8 +692,7 @@ export class ThreeJsMedia {
708
692
  // Find if this object belongs to any media
709
693
  for (const [id, videoData] of this.customMedia.entries()) {
710
694
  // Check if the intersected object is part of our media mesh
711
- if (intersectedObject === videoData.mesh ||
712
- videoData.mesh.children.includes(intersectedObject)) {
695
+ if (intersectedObject === videoData.mesh || videoData.mesh.children.includes(intersectedObject)) {
713
696
  const { uv } = intersection
714
697
  if (uv) {
715
698
  const result = {