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
@@ -0,0 +1,88 @@
1
+ //@ts-nocheck
2
+ import { expect, test, vi } from 'vitest'
3
+ import { createLegacyMultiDrawScratch, detectLegacyMultiDrawCaps, drawLegacySpans } from '../legacyMultiDraw'
4
+
5
+ function makeMockGl(opts?: { multiDraw?: boolean }): WebGL2RenderingContext {
6
+ const multiDrawElementsWEBGL = vi.fn()
7
+ const drawElements = vi.fn()
8
+ return {
9
+ TRIANGLES: 0x0004,
10
+ UNSIGNED_INT: 0x1405,
11
+ getExtension: (name: string) => {
12
+ if (opts?.multiDraw && name === 'WEBGL_multi_draw') {
13
+ return { multiDrawElementsWEBGL }
14
+ }
15
+ return null
16
+ },
17
+ drawElements
18
+ } as unknown as WebGL2RenderingContext
19
+ }
20
+
21
+ test('detectLegacyMultiDrawCaps: tier A when WEBGL_multi_draw is available', () => {
22
+ const gl = makeMockGl({ multiDraw: true })
23
+ const caps = detectLegacyMultiDrawCaps(gl)
24
+ expect(caps.tier).toBe('A')
25
+ expect(caps.ext).not.toBeNull()
26
+ })
27
+
28
+ test('detectLegacyMultiDrawCaps: tier B when extension is missing', () => {
29
+ const gl = makeMockGl()
30
+ const caps = detectLegacyMultiDrawCaps(gl)
31
+ expect(caps.tier).toBe('B')
32
+ expect(caps.ext).toBeNull()
33
+ })
34
+
35
+ test('drawLegacySpans: tier A uses multiDrawElementsWEBGL with byte offsets', () => {
36
+ const gl = makeMockGl({ multiDraw: true })
37
+ const caps = detectLegacyMultiDrawCaps(gl)
38
+ const scratch = createLegacyMultiDrawScratch()
39
+ const spans = [
40
+ { indexStart: 12, indexCount: 6 },
41
+ { indexStart: 30, indexCount: 12 }
42
+ ]
43
+
44
+ drawLegacySpans(gl, caps, spans, scratch)
45
+
46
+ expect(scratch.counts[0]).toBe(6)
47
+ expect(scratch.counts[1]).toBe(12)
48
+ expect(scratch.offsets[0]).toBe(48)
49
+ expect(scratch.offsets[1]).toBe(120)
50
+
51
+ const ext = caps.ext as { multiDrawElementsWEBGL: ReturnType<typeof vi.fn> }
52
+ expect(ext.multiDrawElementsWEBGL).toHaveBeenCalledTimes(1)
53
+ expect(ext.multiDrawElementsWEBGL).toHaveBeenCalledWith(gl.TRIANGLES, scratch.counts, 0, gl.UNSIGNED_INT, scratch.offsets, 0, 2)
54
+ })
55
+
56
+ test('drawLegacySpans: tier B loops drawElements per span', () => {
57
+ const gl = makeMockGl()
58
+ const caps = detectLegacyMultiDrawCaps(gl)
59
+ const scratch = createLegacyMultiDrawScratch()
60
+ const spans = [{ indexStart: 4, indexCount: 6 }]
61
+
62
+ drawLegacySpans(gl, caps, spans, scratch)
63
+
64
+ expect((gl as unknown as { drawElements: ReturnType<typeof vi.fn> }).drawElements).toHaveBeenCalledWith(gl.TRIANGLES, 6, gl.UNSIGNED_INT, 16)
65
+ })
66
+
67
+ test('drawLegacySpans: grows scratch past MAX_OPAQUE_SPANS for uncapped blend sets', () => {
68
+ const gl = makeMockGl({ multiDraw: true })
69
+ const caps = detectLegacyMultiDrawCaps(gl)
70
+ const scratch = createLegacyMultiDrawScratch()
71
+ expect(scratch.counts.length).toBe(64)
72
+
73
+ const spanCount = 70
74
+ const spans = Array.from({ length: spanCount }, (_, i) => ({
75
+ indexStart: i * 6,
76
+ indexCount: 6
77
+ }))
78
+
79
+ drawLegacySpans(gl, caps, spans, scratch)
80
+
81
+ expect(scratch.counts.length).toBeGreaterThanOrEqual(spanCount)
82
+ expect(scratch.offsets.length).toBeGreaterThanOrEqual(spanCount)
83
+ expect(scratch.counts[69]).toBe(6)
84
+ expect(scratch.offsets[69]).toBe(69 * 6 * 4)
85
+
86
+ const ext = caps.ext as { multiDrawElementsWEBGL: ReturnType<typeof vi.fn> }
87
+ expect(ext.multiDrawElementsWEBGL).toHaveBeenCalledWith(gl.TRIANGLES, scratch.counts, 0, gl.UNSIGNED_INT, scratch.offsets, 0, spanCount)
88
+ })
@@ -0,0 +1,370 @@
1
+ //@ts-nocheck
2
+ import { test, expect, vi, afterEach } from 'vitest'
3
+ import * as THREE from 'three'
4
+
5
+ vi.mock('../entity/EntityMesh', () => ({
6
+ getMesh: vi.fn()
7
+ }))
8
+
9
+ import { ChunkMeshManager } from '../chunkMeshManager'
10
+ import type { GlobalLegacyBuffer } from '../globalLegacyBuffer'
11
+ import type { WorldRendererThree } from '../worldRendererThree'
12
+ import type { MesherGeometryOutput } from '../../mesher-shared/shared'
13
+
14
+ function makeQuadArrays() {
15
+ const positions = new Float32Array([-1, -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1])
16
+ const colors = new Float32Array(12).fill(1)
17
+ const skyLights = new Float32Array(4).fill(1)
18
+ const blockLights = new Float32Array(4).fill(0)
19
+ const uvs = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1])
20
+ const indices = new Uint32Array([0, 1, 2, 0, 2, 3])
21
+ return { positions, colors, skyLights, blockLights, uvs, indices }
22
+ }
23
+
24
+ function makeBlendOnlyGeometry(): MesherGeometryOutput {
25
+ const blend = makeQuadArrays()
26
+ return {
27
+ sectionYNumber: 0,
28
+ chunkKey: '0,0',
29
+ sectionStartY: 0,
30
+ sectionEndY: 16,
31
+ sectionStartX: 0,
32
+ sectionEndX: 16,
33
+ sectionStartZ: 0,
34
+ sectionEndZ: 16,
35
+ sx: 8,
36
+ sy: 8,
37
+ sz: 8,
38
+ positions: new Float32Array(0),
39
+ normals: new Float32Array(0),
40
+ colors: new Float32Array(0),
41
+ skyLights: new Float32Array(0),
42
+ blockLights: new Float32Array(0),
43
+ uvs: new Float32Array(0),
44
+ indices: new Uint32Array(0),
45
+ indicesCount: 0,
46
+ using32Array: true,
47
+ tiles: {},
48
+ heads: {},
49
+ signs: {},
50
+ banners: {},
51
+ hadErrors: false,
52
+ blocksCount: 1,
53
+ blend: {
54
+ positions: blend.positions,
55
+ normals: new Float32Array(12),
56
+ colors: blend.colors,
57
+ skyLights: blend.skyLights,
58
+ blockLights: blend.blockLights,
59
+ uvs: blend.uvs,
60
+ indices: blend.indices
61
+ }
62
+ }
63
+ }
64
+
65
+ function makeOpaqueOnlyGeometry(sx = 8, sy = 8, sz = 8): MesherGeometryOutput {
66
+ const opaque = makeQuadArrays()
67
+ return {
68
+ sectionYNumber: 0,
69
+ chunkKey: '0,0',
70
+ sectionStartY: 0,
71
+ sectionEndY: 16,
72
+ sectionStartX: 0,
73
+ sectionEndX: 16,
74
+ sectionStartZ: 0,
75
+ sectionEndZ: 16,
76
+ sx,
77
+ sy,
78
+ sz,
79
+ positions: opaque.positions,
80
+ normals: new Float32Array(12),
81
+ colors: opaque.colors,
82
+ skyLights: opaque.skyLights,
83
+ blockLights: opaque.blockLights,
84
+ uvs: opaque.uvs,
85
+ indices: opaque.indices,
86
+ indicesCount: 6,
87
+ using32Array: true,
88
+ tiles: {},
89
+ heads: {},
90
+ signs: {},
91
+ banners: {},
92
+ hadErrors: false,
93
+ blocksCount: 1
94
+ }
95
+ }
96
+
97
+ function makeMixedGeometry(): MesherGeometryOutput {
98
+ const opaque = makeQuadArrays()
99
+ const blend = makeQuadArrays()
100
+ return {
101
+ sectionYNumber: 0,
102
+ chunkKey: '0,0',
103
+ sectionStartY: 0,
104
+ sectionEndY: 16,
105
+ sectionStartX: 0,
106
+ sectionEndX: 16,
107
+ sectionStartZ: 0,
108
+ sectionEndZ: 16,
109
+ sx: 8,
110
+ sy: 8,
111
+ sz: 8,
112
+ positions: opaque.positions,
113
+ normals: new Float32Array(12),
114
+ colors: opaque.colors,
115
+ skyLights: opaque.skyLights,
116
+ blockLights: opaque.blockLights,
117
+ uvs: opaque.uvs,
118
+ indices: opaque.indices,
119
+ indicesCount: 6,
120
+ using32Array: true,
121
+ tiles: {},
122
+ heads: {},
123
+ signs: {},
124
+ banners: {},
125
+ hadErrors: false,
126
+ blocksCount: 2,
127
+ blend: {
128
+ positions: blend.positions,
129
+ normals: new Float32Array(12),
130
+ colors: blend.colors,
131
+ skyLights: blend.skyLights,
132
+ blockLights: blend.blockLights,
133
+ uvs: blend.uvs,
134
+ indices: blend.indices
135
+ }
136
+ }
137
+ }
138
+
139
+ function createManager(): ChunkMeshManager {
140
+ const scene = new THREE.Scene()
141
+ const material = new THREE.MeshBasicMaterial()
142
+ const worldRenderer = {
143
+ shaderCubeBlocksEnabled: () => false,
144
+ getModule: () => undefined,
145
+ sceneOrigin: {
146
+ track: () => {},
147
+ removeAndUntrack: () => {},
148
+ removeAndUntrackAll: () => {}
149
+ },
150
+ blockEntities: {},
151
+ worldRendererConfig: {}
152
+ } as unknown as WorldRendererThree
153
+ return new ChunkMeshManager(worldRenderer, scene, material, 256, 1)
154
+ }
155
+
156
+ type ManagerInternals = {
157
+ legacyCullSections: Map<string, { worldX: number; worldY: number; worldZ: number }>
158
+ registerLegacyCullSection: (key: string, wx: number, wy: number, wz: number) => void
159
+ maybeUnregisterLegacyCullSection: (key: string) => void
160
+ }
161
+
162
+ function getLegacyCullSections(manager: ChunkMeshManager): Map<string, { worldX: number; worldY: number; worldZ: number }> {
163
+ return (manager as unknown as ManagerInternals).legacyCullSections
164
+ }
165
+
166
+ function makeCamera(x: number, y: number, z: number): THREE.PerspectiveCamera {
167
+ const camera = new THREE.PerspectiveCamera(60, 1, 0.1, 1000)
168
+ camera.position.set(x, y, z)
169
+ camera.lookAt(8, 8, 8)
170
+ camera.updateMatrixWorld()
171
+ return camera
172
+ }
173
+
174
+ afterEach(() => {
175
+ vi.restoreAllMocks()
176
+ })
177
+
178
+ test('legacyCullSections: gains key on blend add, removed after cleanup', () => {
179
+ const manager = createManager()
180
+ const key = '0,0,0'
181
+
182
+ manager.updateSection(key, makeBlendOnlyGeometry())
183
+ expect(getLegacyCullSections(manager).has(key)).toBe(true)
184
+
185
+ manager.cleanupSection(key)
186
+ expect(getLegacyCullSections(manager).has(key)).toBe(false)
187
+
188
+ manager.dispose()
189
+ })
190
+
191
+ test('legacyCullSections: mixed opaque+blend keeps key until both buffers cleared', () => {
192
+ const manager = createManager()
193
+ const key = '0,0,0'
194
+
195
+ manager.updateSection(key, makeMixedGeometry())
196
+ expect(getLegacyCullSections(manager).has(key)).toBe(true)
197
+ expect(manager.globalLegacyBuffer?.hasSection(key)).toBe(true)
198
+ expect(manager.globalLegacyBlendBuffer?.hasSection(key)).toBe(true)
199
+
200
+ manager.globalLegacyBuffer?.removeSection(key)
201
+ expect(getLegacyCullSections(manager).has(key)).toBe(true)
202
+
203
+ manager.globalLegacyBlendBuffer?.removeSection(key)
204
+ ;(manager as unknown as ManagerInternals).maybeUnregisterLegacyCullSection(key)
205
+ expect(getLegacyCullSections(manager).has(key)).toBe(false)
206
+
207
+ manager.cleanupSection(key)
208
+ manager.dispose()
209
+ })
210
+
211
+ test('updateSectionCullAndSort: same visible set skips span rebuild on camera move', () => {
212
+ const manager = createManager()
213
+ const key = '0,0,0'
214
+ manager.updateSection(key, makeBlendOnlyGeometry())
215
+
216
+ const blendBuffer = manager.globalLegacyBlendBuffer!
217
+ const updateDrawSpansSpy = vi.spyOn(blendBuffer, 'updateDrawSpans')
218
+
219
+ const camera1 = makeCamera(8, 8, 20)
220
+ manager.updateSectionCullAndSort(camera1, 8, 8, 20)
221
+ expect(updateDrawSpansSpy).toHaveBeenCalledTimes(1)
222
+
223
+ while (blendBuffer.hasPendingUploads()) blendBuffer.uploadDirtyRange()
224
+
225
+ manager.updateSectionCullAndSort(camera1, 8, 8, 20)
226
+ expect(updateDrawSpansSpy).toHaveBeenCalledTimes(2)
227
+
228
+ const camera2 = makeCamera(8, 8, 18)
229
+ manager.updateSectionCullAndSort(camera2, 8, 8, 18)
230
+ expect(updateDrawSpansSpy).toHaveBeenCalledTimes(2)
231
+
232
+ manager.cleanupSection(key)
233
+ manager.dispose()
234
+ })
235
+
236
+ test('updateSectionCullAndSort: layoutVersion change forces span rebuild', () => {
237
+ const manager = createManager()
238
+ const key = '0,0,0'
239
+ manager.updateSection(key, makeBlendOnlyGeometry())
240
+
241
+ const blendBuffer = manager.globalLegacyBlendBuffer!
242
+ const updateDrawSpansSpy = vi.spyOn(blendBuffer, 'updateDrawSpans')
243
+
244
+ const camera = makeCamera(8, 8, 20)
245
+ manager.updateSectionCullAndSort(camera, 8, 8, 20)
246
+ expect(updateDrawSpansSpy).toHaveBeenCalledTimes(1)
247
+
248
+ blendBuffer.removeSection(key)
249
+ blendBuffer.addSection(key, makeBlendOnlyGeometry().blend!, 8, 8, 8)
250
+ ;(manager as unknown as ManagerInternals).registerLegacyCullSection(key, 8, 8, 8)
251
+
252
+ manager.updateSectionCullAndSort(camera, 8, 8, 20)
253
+ expect(updateDrawSpansSpy).toHaveBeenCalledTimes(2)
254
+
255
+ manager.cleanupSection(key)
256
+ manager.dispose()
257
+ })
258
+
259
+ function drainLegacyUploads(buffer: GlobalLegacyBuffer): void {
260
+ while (buffer.hasPendingUploads()) buffer.uploadDirtyRange()
261
+ }
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
+
344
+ test('updateSectionCullAndSort: defrag finalize forces span rebuild with static camera', () => {
345
+ const manager = createManager()
346
+ const keys = ['0,0,0', '1,0,0', '2,0,0'] as const
347
+
348
+ for (let i = 0; i < keys.length; i++) {
349
+ manager.updateSection(keys[i]!, makeOpaqueOnlyGeometry(8, 8, 8 + i * 8))
350
+ }
351
+
352
+ const opaqueBuf = manager.globalLegacyBuffer!
353
+ const updateDrawSpansSpy = vi.spyOn(opaqueBuf, 'updateDrawSpans')
354
+ const camera = makeCamera(8, 8, 20)
355
+
356
+ manager.updateSectionCullAndSort(camera, 8, 8, 20)
357
+ expect(updateDrawSpansSpy).toHaveBeenCalledTimes(1)
358
+
359
+ opaqueBuf.removeSection('1,0,0')
360
+ drainLegacyUploads(opaqueBuf)
361
+ opaqueBuf.compactStep()
362
+ drainLegacyUploads(opaqueBuf)
363
+ opaqueBuf.compactStep()
364
+
365
+ manager.updateSectionCullAndSort(camera, 8, 8, 20)
366
+ expect(updateDrawSpansSpy).toHaveBeenCalledTimes(2)
367
+
368
+ for (const key of keys) manager.cleanupSection(key)
369
+ manager.dispose()
370
+ })
@@ -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