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
@@ -4,8 +4,24 @@ import * as nbt from 'prismarine-nbt'
4
4
  import { Vec3 } from 'vec3'
5
5
  import { MesherGeometryOutput } from '../mesher-shared/shared'
6
6
  import { getShaderCubeResources, SHADER_CUBES_WORDS_PER_FACE } from '../wasm-mesher/bridge/shaderCubeBridge'
7
- import { createCubeBlockMaterial, computeSectionOriginRel, setCubeSkyLevel, setCubeShadingTheme, setCubeLightmapParams, type BlockLightmapParams } from './shaders/cubeBlockShader'
8
- import { computeCameraRelativeUniforms, createGlobalLegacyBlendMaterial, createGlobalLegacyBlockMaterial, createLegacyBlockMaterial, setLegacyCameraOrigin, setLegacySkyLevel, setLegacyLightmapParams, type RenderOrigin } from './shaders/legacyBlockShader'
7
+ import {
8
+ createCubeBlockMaterial,
9
+ computeSectionOriginRel,
10
+ setCubeSkyLevel,
11
+ setCubeShadingTheme,
12
+ setCubeLightmapParams,
13
+ type BlockLightmapParams
14
+ } from './shaders/cubeBlockShader'
15
+ import {
16
+ computeCameraRelativeUniforms,
17
+ createGlobalLegacyBlendMaterial,
18
+ createGlobalLegacyBlockMaterial,
19
+ createLegacyBlockMaterial,
20
+ setLegacyCameraOrigin,
21
+ setLegacySkyLevel,
22
+ setLegacyLightmapParams,
23
+ type RenderOrigin
24
+ } from './shaders/legacyBlockShader'
9
25
  import { LEGACY_SECTION_HALF_EXTENT, sectionIntersectsFrustum, setupLegacySectionMatrix, updateLegacySectionCullState } from './legacySectionCull'
10
26
  import { createShaderCubeMesh, disposeShaderCubeMesh } from './shaderCubeMesh'
11
27
  import { GlobalBlockBuffer } from './globalBlockBuffer'
@@ -17,7 +33,7 @@ import {
17
33
  raycastAabb,
18
34
  raycastShaderBlocksAabb,
19
35
  sectionAabbIntersectsRay,
20
- type ShaderSectionRaycastEntry,
36
+ type ShaderSectionRaycastEntry
21
37
  } from './sectionRaycastAabb'
22
38
  import { getMesh } from './entity/EntityMesh'
23
39
  import type { WorldRendererThree } from './worldRendererThree'
@@ -39,7 +55,7 @@ export interface SectionObject extends THREE.Group {
39
55
  /** Per-section instanced shader mesh (sci-fi reveal defer only). */
40
56
  shaderMesh?: THREE.Mesh<THREE.InstancedBufferGeometry, THREE.ShaderMaterial>
41
57
  /** Shader cube words kept for migration to global buffer after reveal. */
42
- deferredShaderCubes?: { words: Uint32Array, count: number }
58
+ deferredShaderCubes?: { words: Uint32Array; count: number }
43
59
  /** Opaque legacy geometry deferred from global buffer during sci-fi reveal. */
44
60
  deferredLegacyOpaque?: LegacySectionGeometry
45
61
  /** Blend legacy geometry deferred from global buffer during sci-fi reveal. */
@@ -139,10 +155,11 @@ export class ChunkMeshManager {
139
155
  private readonly _legacyCullBox = new THREE.Box3()
140
156
  private readonly _legacyCullBoxMin = new THREE.Vector3()
141
157
  private readonly _legacyCullBoxMax = new THREE.Vector3()
142
- private readonly _visibleSectionSpans: Array<{ key: string, distSq: number }> = []
158
+ private readonly _visibleSectionSpans: Array<{ key: string; distSq: number }> = []
143
159
  /** Sections with geometry in global legacy opaque and/or blend buffers — cull/raycast scan only these. */
144
- private readonly legacyCullSections = new Map<string, { worldX: number, worldY: number, worldZ: number }>()
160
+ private readonly legacyCullSections = new Map<string, { worldX: number; worldY: number; worldZ: number }>()
145
161
  private _lastCullFingerprint = ''
162
+ private lastBufferStateKey = ''
146
163
  /** Drives per-frame cull + span rebuild; cleared after updateSectionCullAndSort. */
147
164
  cullDirty = true
148
165
  private readonly _lastCullCamPos = new THREE.Vector3()
@@ -173,20 +190,20 @@ export class ChunkMeshManager {
173
190
  private lastPerformanceCheck = 0
174
191
  private readonly performanceCheckInterval = 2000 // Check every 2 seconds
175
192
 
176
- get performanceOverrideDistance () {
193
+ get performanceOverrideDistance() {
177
194
  return this._performanceOverrideDistance ?? 0
178
195
  }
179
- set performanceOverrideDistance (value: number | undefined) {
196
+ set performanceOverrideDistance(value: number | undefined) {
180
197
  this._performanceOverrideDistance = value
181
198
  this.updateSectionsVisibility()
182
199
  }
183
200
 
184
- constructor (
201
+ constructor(
185
202
  public worldRenderer: WorldRendererThree,
186
203
  public scene: THREE.Object3D,
187
204
  public material: THREE.Material,
188
205
  public worldHeight: number,
189
- viewDistance = 3,
206
+ viewDistance = 3
190
207
  ) {
191
208
  this.updateViewDistance(viewDistance)
192
209
  this.signHeadsRenderer = new SignHeadsRenderer(worldRenderer)
@@ -194,7 +211,7 @@ export class ChunkMeshManager {
194
211
  this.initializePool()
195
212
  }
196
213
 
197
- private initializePool () {
214
+ private initializePool() {
198
215
  // Create initial pool
199
216
  for (let i = 0; i < this.poolSize; i++) {
200
217
  const geometry = new THREE.BufferGeometry()
@@ -215,18 +232,18 @@ export class ChunkMeshManager {
215
232
  }
216
233
 
217
234
  /** True when section has legacy vertices and/or GPU shader cube instances. */
218
- sectionHasRenderableContent (geometryData: MesherGeometryOutput): boolean {
235
+ sectionHasRenderableContent(geometryData: MesherGeometryOutput): boolean {
219
236
  if (geometryData.positions.length > 0) return true
220
237
  if ((geometryData.blend?.positions.length ?? 0) > 0) return true
221
238
  if (!this.isShaderCubesGpuEnabled()) return false
222
239
  return (geometryData.shaderCubes?.count ?? 0) > 0
223
240
  }
224
241
 
225
- isShaderCubesGpuEnabled (): boolean {
242
+ isShaderCubesGpuEnabled(): boolean {
226
243
  return this.worldRenderer.shaderCubeBlocksEnabled()
227
244
  }
228
245
 
229
- syncCubeShaderUniforms (): void {
246
+ syncCubeShaderUniforms(): void {
230
247
  if (!this.isShaderCubesGpuEnabled()) return
231
248
  const mat = this.cubeShaderMaterial ?? this.getCubeShaderMaterial()
232
249
  if (!mat) return
@@ -243,7 +260,7 @@ export class ChunkMeshManager {
243
260
  mat.needsUpdate = true
244
261
  }
245
262
 
246
- syncLegacyShaderUniforms (): void {
263
+ syncLegacyShaderUniforms(): void {
247
264
  const atlas = (this.material as THREE.MeshBasicMaterial).map ?? null
248
265
  if (this.legacyShaderMaterial) {
249
266
  this.legacyShaderMaterial.uniforms.u_atlas.value = atlas
@@ -260,7 +277,7 @@ export class ChunkMeshManager {
260
277
  }
261
278
 
262
279
  /** Render-time sky light cap (0–1, from time-of-day / 15). */
263
- setSkyLevel (value: number): void {
280
+ setSkyLevel(value: number): void {
264
281
  const cube = this.cubeShaderMaterial ?? (this.isShaderCubesGpuEnabled() ? this.getCubeShaderMaterial() : null)
265
282
  if (cube) setCubeSkyLevel(cube, value)
266
283
  if (this.legacyShaderMaterial) setLegacySkyLevel(this.legacyShaderMaterial, value)
@@ -269,13 +286,13 @@ export class ChunkMeshManager {
269
286
  this.blockEntityLightRegistry.setSkyLevel(value)
270
287
  }
271
288
 
272
- setShadingTheme (theme: 'vanilla' | 'high-contrast', cardinalLight: string): void {
289
+ setShadingTheme(theme: 'vanilla' | 'high-contrast', cardinalLight: string): void {
273
290
  const cube = this.cubeShaderMaterial ?? (this.isShaderCubesGpuEnabled() ? this.getCubeShaderMaterial() : null)
274
291
  if (cube) setCubeShadingTheme(cube, theme, cardinalLight)
275
292
  }
276
293
 
277
294
  /** Vanilla-like lightmap curve params (live tuning via window.setBlockLightmap). */
278
- setBlockLightmapParams (params: BlockLightmapParams): void {
295
+ setBlockLightmapParams(params: BlockLightmapParams): void {
279
296
  const cube = this.cubeShaderMaterial ?? (this.isShaderCubesGpuEnabled() ? this.getCubeShaderMaterial() : null)
280
297
  if (cube) setCubeLightmapParams(cube, params)
281
298
  if (this.legacyShaderMaterial) setLegacyLightmapParams(this.legacyShaderMaterial, params)
@@ -284,7 +301,7 @@ export class ChunkMeshManager {
284
301
  this.blockEntityLightRegistry.setLightmapParams(params)
285
302
  }
286
303
 
287
- private getLegacyShaderMaterial (): THREE.ShaderMaterial {
304
+ private getLegacyShaderMaterial(): THREE.ShaderMaterial {
288
305
  if (!this.legacyShaderMaterial) {
289
306
  this.legacyShaderMaterial = createLegacyBlockMaterial()
290
307
  this.syncLegacyShaderUniforms()
@@ -292,7 +309,7 @@ export class ChunkMeshManager {
292
309
  return this.legacyShaderMaterial
293
310
  }
294
311
 
295
- private getGlobalLegacyShaderMaterial (): THREE.ShaderMaterial {
312
+ private getGlobalLegacyShaderMaterial(): THREE.ShaderMaterial {
296
313
  if (!this.globalLegacyShaderMaterial) {
297
314
  this.globalLegacyShaderMaterial = createGlobalLegacyBlockMaterial()
298
315
  this.syncLegacyShaderUniforms()
@@ -300,18 +317,15 @@ export class ChunkMeshManager {
300
317
  return this.globalLegacyShaderMaterial
301
318
  }
302
319
 
303
- private getGlobalLegacyBuffer (): GlobalLegacyBuffer {
320
+ private getGlobalLegacyBuffer(): GlobalLegacyBuffer {
304
321
  if (!this.globalLegacyBuffer) {
305
- this.globalLegacyBuffer = new GlobalLegacyBuffer(
306
- this.getGlobalLegacyShaderMaterial(),
307
- this.scene,
308
- )
322
+ this.globalLegacyBuffer = new GlobalLegacyBuffer(this.getGlobalLegacyShaderMaterial(), this.scene)
309
323
  this.globalLegacyBuffer.setRenderOrigin(this.renderOrigin)
310
324
  }
311
325
  return this.globalLegacyBuffer
312
326
  }
313
327
 
314
- private getGlobalLegacyBlendShaderMaterial (): THREE.ShaderMaterial {
328
+ private getGlobalLegacyBlendShaderMaterial(): THREE.ShaderMaterial {
315
329
  if (!this.globalLegacyBlendShaderMaterial) {
316
330
  this.globalLegacyBlendShaderMaterial = createGlobalLegacyBlendMaterial()
317
331
  this.syncLegacyShaderUniforms()
@@ -319,32 +333,28 @@ export class ChunkMeshManager {
319
333
  return this.globalLegacyBlendShaderMaterial
320
334
  }
321
335
 
322
- private getGlobalLegacyBlendBuffer (): GlobalLegacyBuffer {
336
+ private getGlobalLegacyBlendBuffer(): GlobalLegacyBuffer {
323
337
  if (!this.globalLegacyBlendBuffer) {
324
- this.globalLegacyBlendBuffer = new GlobalLegacyBuffer(
325
- this.getGlobalLegacyBlendShaderMaterial(),
326
- this.scene,
327
- {
328
- name: 'globalLegacyBlend',
329
- initialCapacityQuads: 32_000,
330
- growthIncrementQuads: 32_000,
331
- },
332
- )
338
+ this.globalLegacyBlendBuffer = new GlobalLegacyBuffer(this.getGlobalLegacyBlendShaderMaterial(), this.scene, {
339
+ name: 'globalLegacyBlend',
340
+ initialCapacityQuads: 32_000,
341
+ growthIncrementQuads: 32_000
342
+ })
333
343
  this.globalLegacyBlendBuffer.setRenderOrigin(this.renderOrigin)
334
344
  }
335
345
  return this.globalLegacyBlendBuffer
336
346
  }
337
347
 
338
- getRenderOrigin (): Readonly<RenderOrigin> {
348
+ getRenderOrigin(): Readonly<RenderOrigin> {
339
349
  return this.renderOrigin
340
350
  }
341
351
 
342
- maybeRebase (camera: RenderOrigin): void {
352
+ maybeRebase(camera: RenderOrigin): void {
343
353
  const R = this.renderOrigin
344
354
  if (
345
- Math.abs(camera.x - R.x) <= ChunkMeshManager.REBASE_THRESHOLD
346
- && Math.abs(camera.y - R.y) <= ChunkMeshManager.REBASE_THRESHOLD
347
- && Math.abs(camera.z - R.z) <= ChunkMeshManager.REBASE_THRESHOLD
355
+ Math.abs(camera.x - R.x) <= ChunkMeshManager.REBASE_THRESHOLD &&
356
+ Math.abs(camera.y - R.y) <= ChunkMeshManager.REBASE_THRESHOLD &&
357
+ Math.abs(camera.z - R.z) <= ChunkMeshManager.REBASE_THRESHOLD
348
358
  ) {
349
359
  return
350
360
  }
@@ -352,12 +362,12 @@ export class ChunkMeshManager {
352
362
  const newOrigin: RenderOrigin = {
353
363
  x: Math.round(camera.x / 16) * 16,
354
364
  y: Math.round(camera.y / 16) * 16,
355
- z: Math.round(camera.z / 16) * 16,
365
+ z: Math.round(camera.z / 16) * 16
356
366
  }
357
367
  const delta: RenderOrigin = {
358
368
  x: newOrigin.x - R.x,
359
369
  y: newOrigin.y - R.y,
360
- z: newOrigin.z - R.z,
370
+ z: newOrigin.z - R.z
361
371
  }
362
372
 
363
373
  this.globalLegacyBuffer?.rebase(delta)
@@ -368,13 +378,7 @@ export class ChunkMeshManager {
368
378
  if (!sectionKey) continue
369
379
  const sectionObject = this.sectionObjects[sectionKey]
370
380
  if (!sectionObject) continue
371
- setupLegacySectionMatrix(
372
- poolEntry.mesh,
373
- sectionObject.worldX ?? 0,
374
- sectionObject.worldY ?? 0,
375
- sectionObject.worldZ ?? 0,
376
- newOrigin,
377
- )
381
+ setupLegacySectionMatrix(poolEntry.mesh, sectionObject.worldX ?? 0, sectionObject.worldY ?? 0, sectionObject.worldZ ?? 0, newOrigin)
378
382
  }
379
383
 
380
384
  this.renderOrigin = newOrigin
@@ -383,15 +387,15 @@ export class ChunkMeshManager {
383
387
  }
384
388
 
385
389
  /** Whether a section still holds a pooled legacy mesh (defer / invariant fallback). */
386
- sectionUsesPooledLegacyMesh (sectionKey: string): boolean {
390
+ sectionUsesPooledLegacyMesh(sectionKey: string): boolean {
387
391
  return this.activeSections.has(sectionKey)
388
392
  }
389
393
 
390
- private registerLegacyCullSection (sectionKey: string, worldX: number, worldY: number, worldZ: number): void {
394
+ private registerLegacyCullSection(sectionKey: string, worldX: number, worldY: number, worldZ: number): void {
391
395
  this.legacyCullSections.set(sectionKey, { worldX, worldY, worldZ })
392
396
  }
393
397
 
394
- private maybeUnregisterLegacyCullSection (sectionKey: string): void {
398
+ private maybeUnregisterLegacyCullSection(sectionKey: string): void {
395
399
  const inOpaque = this.globalLegacyBuffer?.hasSection(sectionKey) ?? false
396
400
  const inBlend = this.globalLegacyBlendBuffer?.hasSection(sectionKey) ?? false
397
401
  if (!inOpaque && !inBlend) {
@@ -399,11 +403,7 @@ export class ChunkMeshManager {
399
403
  }
400
404
  }
401
405
 
402
- private updatePooledLegacyCullState (
403
- cameraWorldX: number,
404
- cameraWorldY: number,
405
- cameraWorldZ: number,
406
- ): void {
406
+ private updatePooledLegacyCullState(cameraWorldX: number, cameraWorldY: number, cameraWorldZ: number): void {
407
407
  for (const poolEntry of this.activeSections.values()) {
408
408
  const sectionKey = poolEntry.sectionKey
409
409
  if (!sectionKey) continue
@@ -421,7 +421,7 @@ export class ChunkMeshManager {
421
421
  this._legacyCullFrustum,
422
422
  this._legacyCullBox,
423
423
  this._legacyCullBoxMin,
424
- this._legacyCullBoxMax,
424
+ this._legacyCullBoxMax
425
425
  )
426
426
  }
427
427
  }
@@ -429,7 +429,7 @@ export class ChunkMeshManager {
429
429
  /**
430
430
  * Shared section visibility + span groups for global legacy and cube buffers.
431
431
  */
432
- updateSectionCullAndSort (camera: THREE.Camera, cameraWorldX: number, cameraWorldY: number, cameraWorldZ: number): void {
432
+ updateSectionCullAndSort(camera: THREE.Camera, cameraWorldX: number, cameraWorldY: number, cameraWorldZ: number): void {
433
433
  this._legacyCullProjScreen.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse)
434
434
  this._legacyCullFrustum.setFromProjectionMatrix(this._legacyCullProjScreen)
435
435
 
@@ -449,7 +449,7 @@ export class ChunkMeshManager {
449
449
  this._legacyCullFrustum,
450
450
  this._legacyCullBox,
451
451
  this._legacyCullBoxMin,
452
- this._legacyCullBoxMax,
452
+ this._legacyCullBoxMax
453
453
  )
454
454
  if (inFrustum) {
455
455
  visible.push({ key: sectionKey, distSq })
@@ -471,7 +471,7 @@ export class ChunkMeshManager {
471
471
  const blendKeys = blendVisible.map(v => v.key)
472
472
 
473
473
  const cubeVisibleKeys: string[] = []
474
- const visibleSlots: Array<{ start: number, count: number }> = []
474
+ const visibleSlots: Array<{ start: number; count: number }> = []
475
475
  if (gb) {
476
476
  gb.forEachSectionSlot((key, slot) => {
477
477
  const sectionObject = this.sectionObjects[key]
@@ -491,15 +491,16 @@ export class ChunkMeshManager {
491
491
  this._legacyCullFrustum,
492
492
  this._legacyCullBox,
493
493
  this._legacyCullBoxMin,
494
- this._legacyCullBoxMax,
494
+ this._legacyCullBoxMax
495
495
  )
496
496
  if (!inFrustum) {
497
497
  return
498
498
  }
499
499
  cubeVisibleKeys.push(key)
500
500
  const drawStart = gb.getSectionDrawStart(key)
501
- if (drawStart !== undefined) {
502
- visibleSlots.push({ start: drawStart, count: slot.count })
501
+ const drawCount = gb.getSectionDrawCount(key)
502
+ if (drawStart !== undefined && drawCount !== undefined) {
503
+ visibleSlots.push({ start: drawStart, count: drawCount })
503
504
  }
504
505
  })
505
506
  }
@@ -512,9 +513,12 @@ export class ChunkMeshManager {
512
513
  opaqueBuf?.getLayoutVersion() ?? 0,
513
514
  blendBuf?.getLayoutVersion() ?? 0,
514
515
  gb?.getLayoutVersion() ?? 0,
516
+ opaqueBuf?.getUploadEpoch() ?? 0,
517
+ blendBuf?.getUploadEpoch() ?? 0,
518
+ gb?.getUploadEpoch() ?? 0
515
519
  ].join('|')
516
520
 
517
- if (fingerprint === this._lastCullFingerprint) {
521
+ if (fingerprint === this._lastCullFingerprint && !this.hasPendingBufferWork()) {
518
522
  this.updatePooledLegacyCullState(cameraWorldX, cameraWorldY, cameraWorldZ)
519
523
  return
520
524
  }
@@ -524,19 +528,53 @@ export class ChunkMeshManager {
524
528
  blendBuf?.updateDrawSpans(visible, 'sortedBlend')
525
529
 
526
530
  if (gb) {
527
- const spans = buildVisibleCubeSpans(visibleSlots, gb.getHighWatermark())
531
+ const spans = buildVisibleCubeSpans(
532
+ visibleSlots,
533
+ gb.getHighWatermark(),
534
+ gb.canUseFullDrawShortcut(),
535
+ (start, end) => gb.isRangeFullyUploaded(start, end),
536
+ gb.getPendingDirtyRanges()
537
+ )
528
538
  gb.setVisibleSpans(spans)
529
539
  }
530
540
 
541
+ this.lastBufferStateKey = this.bufferStateKey()
531
542
  this.updatePooledLegacyCullState(cameraWorldX, cameraWorldY, cameraWorldZ)
532
543
  }
533
544
 
534
- markCullDirty (): void {
545
+ private bufferStateKey(): string {
546
+ const b = this.globalBlockBuffer
547
+ const o = this.globalLegacyBuffer
548
+ const bl = this.globalLegacyBlendBuffer
549
+ return [
550
+ o?.getLayoutVersion() ?? 0,
551
+ o?.getUploadEpoch() ?? 0,
552
+ bl?.getLayoutVersion() ?? 0,
553
+ bl?.getUploadEpoch() ?? 0,
554
+ b?.getLayoutVersion() ?? 0,
555
+ b?.getUploadEpoch() ?? 0
556
+ ].join('|')
557
+ }
558
+
559
+ /** Mark cull dirty when any buffer's layout or upload state changed since the last cull. */
560
+ markCullDirtyIfBufferStateChanged(): void {
561
+ const key = this.bufferStateKey()
562
+ if (key !== this.lastBufferStateKey) {
563
+ this.markCullDirty()
564
+ }
565
+ }
566
+
567
+ markCullDirty(): void {
535
568
  this.cullDirty = true
536
569
  }
537
570
 
571
+ hasPendingBufferWork(): boolean {
572
+ const buffers = [this.globalLegacyBuffer, this.globalLegacyBlendBuffer, this.globalBlockBuffer]
573
+ return buffers.some(b => b != null && (b.hasPendingUploads() || b.hasPendingReplace() || b.getPendingMove() != null))
574
+ }
575
+
538
576
  /** Compare camera pose; mark cull dirty when position or rotation changed. */
539
- updateCullDirtyFromCamera (camera: THREE.Camera, cameraWorldX: number, cameraWorldY: number, cameraWorldZ: number): void {
577
+ updateCullDirtyFromCamera(camera: THREE.Camera, cameraWorldX: number, cameraWorldY: number, cameraWorldZ: number): void {
540
578
  camera.getWorldQuaternion(this._cullViewQuat)
541
579
  if (!this._cullCamInitialized) {
542
580
  this._lastCullCamPos.set(cameraWorldX, cameraWorldY, cameraWorldZ)
@@ -545,10 +583,7 @@ export class ChunkMeshManager {
545
583
  this.cullDirty = true
546
584
  return
547
585
  }
548
- const posChanged =
549
- this._lastCullCamPos.x !== cameraWorldX ||
550
- this._lastCullCamPos.y !== cameraWorldY ||
551
- this._lastCullCamPos.z !== cameraWorldZ
586
+ const posChanged = this._lastCullCamPos.x !== cameraWorldX || this._lastCullCamPos.y !== cameraWorldY || this._lastCullCamPos.z !== cameraWorldZ
552
587
  const quatChanged =
553
588
  this._lastCullCamQuat.x !== this._cullViewQuat.x ||
554
589
  this._lastCullCamQuat.y !== this._cullViewQuat.y ||
@@ -561,11 +596,11 @@ export class ChunkMeshManager {
561
596
  }
562
597
  }
563
598
 
564
- clearCullDirty (): void {
599
+ clearCullDirty(): void {
565
600
  this.cullDirty = false
566
601
  }
567
602
 
568
- setLegacyCameraOrigin (x: number, y: number, z: number): void {
603
+ setLegacyCameraOrigin(x: number, y: number, z: number): void {
569
604
  const R = this.renderOrigin
570
605
  setLegacyCameraOrigin(this.getLegacyShaderMaterial(), R, x, y, z)
571
606
  setLegacyCameraOrigin(this.getGlobalLegacyShaderMaterial(), R, x, y, z)
@@ -592,7 +627,7 @@ export class ChunkMeshManager {
592
627
  }
593
628
  }
594
629
 
595
- private getCubeShaderMaterial (): THREE.ShaderMaterial | null {
630
+ private getCubeShaderMaterial(): THREE.ShaderMaterial | null {
596
631
  if (!this.isShaderCubesGpuEnabled()) return null
597
632
  if (!this.cubeShaderMaterial) {
598
633
  this.cubeShaderMaterial = createCubeBlockMaterial()
@@ -603,7 +638,7 @@ export class ChunkMeshManager {
603
638
  return this.cubeShaderMaterial
604
639
  }
605
640
 
606
- private getGlobalBlockBuffer (): GlobalBlockBuffer | null {
641
+ private getGlobalBlockBuffer(): GlobalBlockBuffer | null {
607
642
  const mat = this.getCubeShaderMaterial()
608
643
  if (!mat) return null
609
644
  if (!this.globalBlockBuffer) {
@@ -612,12 +647,12 @@ export class ChunkMeshManager {
612
647
  return this.globalBlockBuffer
613
648
  }
614
649
 
615
- private shouldDeferLegacyOpaqueToPerSection (sectionKey: string): boolean {
650
+ private shouldDeferLegacyOpaqueToPerSection(sectionKey: string): boolean {
616
651
  return this.shouldDeferShaderToPerSection(sectionKey)
617
652
  }
618
653
 
619
654
  /** Sci-fi reveal keeps geometry off global buffers until the section finishes reveal. */
620
- private shouldDeferShaderToPerSection (sectionKey: string): boolean {
655
+ private shouldDeferShaderToPerSection(sectionKey: string): boolean {
621
656
  const sciFi = this.worldRenderer.getModule<{
622
657
  shouldDeferSectionGeometry?: (key: string) => boolean
623
658
  }>('futuristicReveal')
@@ -627,7 +662,7 @@ export class ChunkMeshManager {
627
662
  /**
628
663
  * Move deferred per-section shader cubes into the global buffer after reveal completes.
629
664
  */
630
- migrateDeferredShaderToGlobal (sectionKey: string): void {
665
+ migrateDeferredShaderToGlobal(sectionKey: string): void {
631
666
  const section = this.sectionObjects[sectionKey]
632
667
  if (!section?.deferredShaderCubes) return
633
668
 
@@ -660,7 +695,7 @@ export class ChunkMeshManager {
660
695
  /**
661
696
  * Move deferred per-section opaque legacy into the global buffer after reveal completes.
662
697
  */
663
- migrateDeferredLegacyToGlobal (sectionKey: string): void {
698
+ migrateDeferredLegacyToGlobal(sectionKey: string): void {
664
699
  const section = this.sectionObjects[sectionKey]
665
700
  if (!section) return
666
701
 
@@ -670,13 +705,7 @@ export class ChunkMeshManager {
670
705
  const wy = section.worldY
671
706
  const wz = section.worldZ
672
707
  if (wx !== undefined && wy !== undefined && wz !== undefined) {
673
- const added = this.getGlobalLegacyBuffer().addSection(
674
- sectionKey,
675
- { positions, colors, skyLights, blockLights, uvs, indices },
676
- wx,
677
- wy,
678
- wz,
679
- )
708
+ const added = this.getGlobalLegacyBuffer().addSection(sectionKey, { positions, colors, skyLights, blockLights, uvs, indices }, wx, wy, wz)
680
709
  if (added) {
681
710
  this.registerLegacyCullSection(sectionKey, wx, wy, wz)
682
711
  }
@@ -690,13 +719,7 @@ export class ChunkMeshManager {
690
719
  const wy = section.worldY
691
720
  const wz = section.worldZ
692
721
  if (wx !== undefined && wy !== undefined && wz !== undefined) {
693
- const added = this.getGlobalLegacyBlendBuffer().addSection(
694
- sectionKey,
695
- { positions, colors, skyLights, blockLights, uvs, indices },
696
- wx,
697
- wy,
698
- wz,
699
- )
722
+ const added = this.getGlobalLegacyBlendBuffer().addSection(sectionKey, { positions, colors, skyLights, blockLights, uvs, indices }, wx, wy, wz)
700
723
  if (added) {
701
724
  this.registerLegacyCullSection(sectionKey, wx, wy, wz)
702
725
  }
@@ -709,17 +732,13 @@ export class ChunkMeshManager {
709
732
  const hadLegacyAsPrimary = section.mesh === this.activeSections.get(sectionKey)?.mesh
710
733
  this.releasePooledMesh(sectionKey)
711
734
  if (hadLegacyAsPrimary) {
712
- section.mesh = section.shaderMesh as unknown as THREE.Mesh<THREE.BufferGeometry, THREE.Material> | undefined ?? undefined
735
+ section.mesh = (section.shaderMesh as unknown as THREE.Mesh<THREE.BufferGeometry, THREE.Material> | undefined) ?? undefined
713
736
  }
714
737
  }
715
738
  this.markCullDirty()
716
739
  }
717
740
 
718
- raycastGlobalLegacySections (
719
- raycaster: THREE.Raycaster,
720
- origin: THREE.Vector3,
721
- maxCenterDistance: number,
722
- ): number | undefined {
741
+ raycastGlobalLegacySections(raycaster: THREE.Raycaster, origin: THREE.Vector3, maxCenterDistance: number): number | undefined {
723
742
  const maxDistSq = maxCenterDistance * maxCenterDistance
724
743
  const dirX = raycaster.ray.direction.x
725
744
  const dirY = raycaster.ray.direction.y
@@ -734,19 +753,8 @@ export class ChunkMeshManager {
734
753
  const dy = (section.worldY ?? 0) - origin.y
735
754
  const dz = (section.worldZ ?? 0) - origin.z
736
755
  if (dx * dx + dy * dy + dz * dz > maxDistSq) continue
737
- if (!sectionAabbIntersectsRay(
738
- section.worldX,
739
- section.worldY ?? 0,
740
- section.worldZ ?? 0,
741
- origin.x,
742
- origin.y,
743
- origin.z,
744
- dirX,
745
- dirY,
746
- dirZ,
747
- far,
748
- halfExtent,
749
- )) continue
756
+ if (!sectionAabbIntersectsRay(section.worldX, section.worldY ?? 0, section.worldZ ?? 0, origin.x, origin.y, origin.z, dirX, dirY, dirZ, far, halfExtent))
757
+ continue
750
758
  candidates.push(key)
751
759
  }
752
760
 
@@ -759,13 +767,13 @@ export class ChunkMeshManager {
759
767
  return hits[0]?.distance
760
768
  }
761
769
 
762
- registerShaderSectionRaycastBox (
770
+ registerShaderSectionRaycastBox(
763
771
  sectionKey: string,
764
772
  words: Uint32Array,
765
773
  faceCount: number,
766
774
  sectionCenterX: number,
767
775
  sectionCenterY: number,
768
- sectionCenterZ: number,
776
+ sectionCenterZ: number
769
777
  ): void {
770
778
  const box = computeShaderSectionRaycastAabb(words, faceCount, sectionCenterX, sectionCenterY, sectionCenterZ)
771
779
  if (box) {
@@ -773,24 +781,19 @@ export class ChunkMeshManager {
773
781
  box,
774
782
  sectionCenterX,
775
783
  sectionCenterY,
776
- sectionCenterZ,
784
+ sectionCenterZ
777
785
  })
778
786
  } else {
779
787
  this.shaderSectionRaycastBoxes.delete(sectionKey)
780
788
  }
781
789
  }
782
790
 
783
- unregisterShaderSectionRaycastBox (sectionKey: string): void {
791
+ unregisterShaderSectionRaycastBox(sectionKey: string): void {
784
792
  this.shaderSectionRaycastBoxes.delete(sectionKey)
785
793
  }
786
794
 
787
795
  /** Closest hit against registered shader-cube AABBs (world-space ray). */
788
- raycastShaderSectionAABBs (
789
- originWorld: THREE.Vector3,
790
- direction: THREE.Vector3,
791
- maxDist: number,
792
- maxCenterDistance = 80,
793
- ): number | undefined {
796
+ raycastShaderSectionAABBs(originWorld: THREE.Vector3, direction: THREE.Vector3, maxDist: number, maxCenterDistance = 80): number | undefined {
794
797
  const ox = originWorld.x
795
798
  const oy = originWorld.y
796
799
  const oz = originWorld.z
@@ -818,11 +821,7 @@ export class ChunkMeshManager {
818
821
  const dcz = box.cz - oz
819
822
  if (dcx * dcx + dcy * dcy + dcz * dcz > maxCenterDistSq) continue
820
823
 
821
- let t = raycastAabb(
822
- ox, oy, oz, dx, dy, dz,
823
- box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ,
824
- closest,
825
- )
824
+ let t = raycastAabb(ox, oy, oz, dx, dy, dz, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, closest)
826
825
  if (t === undefined && isPointInsideAabb(ox, oy, oz, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ)) {
827
826
  const gb = this.globalBlockBuffer
828
827
  const slot = gb?.getSectionSlot(key)
@@ -835,10 +834,15 @@ export class ChunkMeshManager {
835
834
  entry.sectionCenterX,
836
835
  entry.sectionCenterY,
837
836
  entry.sectionCenterZ,
838
- ox, oy, oz, dx, dy, dz,
837
+ ox,
838
+ oy,
839
+ oz,
840
+ dx,
841
+ dy,
842
+ dz,
839
843
  closest,
840
844
  this.blockRaycastVisitGen,
841
- this.blockRaycastVisitStamp,
845
+ this.blockRaycastVisitStamp
842
846
  )
843
847
  } else {
844
848
  const def = this.sectionObjects[key]?.deferredShaderCubes
@@ -851,10 +855,15 @@ export class ChunkMeshManager {
851
855
  entry.sectionCenterX,
852
856
  entry.sectionCenterY,
853
857
  entry.sectionCenterZ,
854
- ox, oy, oz, dx, dy, dz,
858
+ ox,
859
+ oy,
860
+ oz,
861
+ dx,
862
+ dy,
863
+ dz,
855
864
  closest,
856
865
  this.blockRaycastVisitGen,
857
- this.blockRaycastVisitStamp,
866
+ this.blockRaycastVisitStamp
858
867
  )
859
868
  }
860
869
  }
@@ -871,12 +880,12 @@ export class ChunkMeshManager {
871
880
  /**
872
881
  * Update or create a section with new geometry data
873
882
  */
874
- private uploadLegacyPooledMesh (
883
+ private uploadLegacyPooledMesh(
875
884
  poolEntry: ChunkMeshPool,
876
885
  geometryData: MesherGeometryOutput | MesherGeometryOutput['blend'],
877
886
  sx: number,
878
887
  sy: number,
879
- sz: number,
888
+ sz: number
880
889
  ): THREE.Mesh<THREE.BufferGeometry, THREE.Material> {
881
890
  const { mesh } = poolEntry
882
891
  const geo = geometryData!
@@ -887,14 +896,8 @@ export class ChunkMeshManager {
887
896
  this.updateGeometryAttribute(mesh.geometry, 'a_blockLight', geo.blockLights, 1)
888
897
  this.updateGeometryAttribute(mesh.geometry, 'uv', geo.uvs, 2)
889
898
  mesh.geometry.index = new THREE.BufferAttribute(geo.indices as Uint32Array | Uint16Array, 1)
890
- mesh.geometry.boundingBox = new THREE.Box3(
891
- new THREE.Vector3(-8, -8, -8),
892
- new THREE.Vector3(8, 8, 8),
893
- )
894
- mesh.geometry.boundingSphere = new THREE.Sphere(
895
- new THREE.Vector3(0, 0, 0),
896
- Math.sqrt(3 * 8 ** 2),
897
- )
899
+ mesh.geometry.boundingBox = new THREE.Box3(new THREE.Vector3(-8, -8, -8), new THREE.Vector3(8, 8, 8))
900
+ mesh.geometry.boundingSphere = new THREE.Sphere(new THREE.Vector3(0, 0, 0), Math.sqrt(3 * 8 ** 2))
898
901
  setupLegacySectionMatrix(mesh, sx, sy, sz, this.renderOrigin)
899
902
  mesh.visible = false
900
903
  mesh.name = 'mesh'
@@ -902,7 +905,7 @@ export class ChunkMeshManager {
902
905
  return mesh as THREE.Mesh<THREE.BufferGeometry, THREE.Material>
903
906
  }
904
907
 
905
- private acquirePooledSectionMesh (sectionKey: string): ChunkMeshPool | null {
908
+ private acquirePooledSectionMesh(sectionKey: string): ChunkMeshPool | null {
906
909
  let poolEntry = this.activeSections.get(sectionKey)
907
910
  if (!poolEntry) {
908
911
  poolEntry = this.acquireMesh()
@@ -916,12 +919,18 @@ export class ChunkMeshManager {
916
919
  return poolEntry
917
920
  }
918
921
 
919
- updateSection (sectionKey: string, geometryData: MesherGeometryOutput): SectionObject | null {
922
+ updateSection(sectionKey: string, geometryData: MesherGeometryOutput): SectionObject | null {
920
923
  const hasOpaque = geometryData.positions.length > 0
921
924
  const hasBlend = (geometryData.blend?.positions.length ?? 0) > 0
922
925
  const hasLegacy = hasOpaque || hasBlend
923
926
  const shaderData = geometryData.shaderCubes
924
927
  const hasShader = this.isShaderCubesGpuEnabled() && (shaderData?.count ?? 0) > 0
928
+ const deferOpaque = hasOpaque && this.shouldDeferLegacyOpaqueToPerSection(sectionKey)
929
+ const deferBlend = hasBlend && this.shouldDeferLegacyOpaqueToPerSection(sectionKey)
930
+ const deferShader = hasShader && this.shouldDeferShaderToPerSection(sectionKey)
931
+ const willAddOpaqueGlobal = hasOpaque && !deferOpaque
932
+ const willAddBlendGlobal = hasBlend && !deferBlend
933
+ const willAddCubeGlobal = hasShader && !!shaderData && !deferShader
925
934
 
926
935
  if (!hasLegacy && !hasShader) {
927
936
  this.releaseSection(sectionKey)
@@ -930,10 +939,19 @@ export class ChunkMeshManager {
930
939
 
931
940
  // Remove existing section object from scene if it exists
932
941
  let sectionObject = this.sectionObjects[sectionKey]
942
+ const wasRemesh = sectionObject != null
933
943
  if (sectionObject) {
934
944
  this.cleanupSection(sectionKey, { forRemesh: true })
935
945
  }
936
946
 
947
+ if (wasRemesh) {
948
+ if (!willAddCubeGlobal) this.globalBlockBuffer?.removeSection(sectionKey)
949
+ if (!willAddOpaqueGlobal) this.globalLegacyBuffer?.removeSection(sectionKey)
950
+ if (!willAddBlendGlobal) this.globalLegacyBlendBuffer?.removeSection(sectionKey)
951
+ if (!(hasShader && shaderData)) this.unregisterShaderSectionRaycastBox(sectionKey)
952
+ if (!willAddOpaqueGlobal && !willAddBlendGlobal) this.maybeUnregisterLegacyCullSection(sectionKey)
953
+ }
954
+
937
955
  if (!hasBlend) {
938
956
  this.releasePooledMesh(sectionKey)
939
957
  }
@@ -950,9 +968,8 @@ export class ChunkMeshManager {
950
968
  skyLights: geometryData.skyLights as Float32Array,
951
969
  blockLights: geometryData.blockLights as Float32Array,
952
970
  uvs: geometryData.uvs as Float32Array,
953
- indices: geometryData.indices as Uint32Array | Uint16Array,
971
+ indices: geometryData.indices as Uint32Array | Uint16Array
954
972
  }
955
- const deferOpaque = this.shouldDeferLegacyOpaqueToPerSection(sectionKey)
956
973
  if (deferOpaque) {
957
974
  deferredLegacyOpaque = {
958
975
  positions: new Float32Array(opaqueGeo.positions),
@@ -960,9 +977,7 @@ export class ChunkMeshManager {
960
977
  skyLights: new Float32Array(opaqueGeo.skyLights),
961
978
  blockLights: new Float32Array(opaqueGeo.blockLights),
962
979
  uvs: new Float32Array(opaqueGeo.uvs),
963
- indices: opaqueGeo.indices instanceof Uint32Array
964
- ? new Uint32Array(opaqueGeo.indices)
965
- : new Uint16Array(opaqueGeo.indices),
980
+ indices: opaqueGeo.indices instanceof Uint32Array ? new Uint32Array(opaqueGeo.indices) : new Uint16Array(opaqueGeo.indices)
966
981
  }
967
982
  if (!hasBlend) {
968
983
  const poolEntry = this.acquirePooledSectionMesh(sectionKey)
@@ -970,13 +985,7 @@ export class ChunkMeshManager {
970
985
  legacyMesh = this.uploadLegacyPooledMesh(poolEntry, geometryData, geometryData.sx, geometryData.sy, geometryData.sz)
971
986
  }
972
987
  } else {
973
- const added = this.getGlobalLegacyBuffer().addSection(
974
- sectionKey,
975
- opaqueGeo,
976
- geometryData.sx,
977
- geometryData.sy,
978
- geometryData.sz,
979
- )
988
+ const added = this.getGlobalLegacyBuffer().addSection(sectionKey, opaqueGeo, geometryData.sx, geometryData.sy, geometryData.sz)
980
989
  if (added) {
981
990
  this.registerLegacyCullSection(sectionKey, geometryData.sx, geometryData.sy, geometryData.sz)
982
991
  } else {
@@ -994,9 +1003,8 @@ export class ChunkMeshManager {
994
1003
  skyLights: geometryData.blend.skyLights as Float32Array,
995
1004
  blockLights: geometryData.blend.blockLights as Float32Array,
996
1005
  uvs: geometryData.blend.uvs as Float32Array,
997
- indices: geometryData.blend.indices as Uint32Array | Uint16Array,
1006
+ indices: geometryData.blend.indices as Uint32Array | Uint16Array
998
1007
  }
999
- const deferBlend = this.shouldDeferLegacyOpaqueToPerSection(sectionKey)
1000
1008
  if (deferBlend) {
1001
1009
  deferredLegacyBlend = {
1002
1010
  positions: new Float32Array(blendGeo.positions),
@@ -1004,42 +1012,22 @@ export class ChunkMeshManager {
1004
1012
  skyLights: new Float32Array(blendGeo.skyLights),
1005
1013
  blockLights: new Float32Array(blendGeo.blockLights),
1006
1014
  uvs: new Float32Array(blendGeo.uvs),
1007
- indices: blendGeo.indices instanceof Uint32Array
1008
- ? new Uint32Array(blendGeo.indices)
1009
- : new Uint16Array(blendGeo.indices),
1015
+ indices: blendGeo.indices instanceof Uint32Array ? new Uint32Array(blendGeo.indices) : new Uint16Array(blendGeo.indices)
1010
1016
  }
1011
1017
  const poolEntry = this.acquirePooledSectionMesh(sectionKey)
1012
1018
  if (!poolEntry) return null
1013
- const blendMesh = this.uploadLegacyPooledMesh(
1014
- poolEntry,
1015
- geometryData.blend,
1016
- geometryData.sx,
1017
- geometryData.sy,
1018
- geometryData.sz,
1019
- )
1019
+ const blendMesh = this.uploadLegacyPooledMesh(poolEntry, geometryData.blend, geometryData.sx, geometryData.sy, geometryData.sz)
1020
1020
  legacyMesh = legacyMesh ?? blendMesh
1021
1021
  hasBlendMesh = true
1022
1022
  } else {
1023
- const added = this.getGlobalLegacyBlendBuffer().addSection(
1024
- sectionKey,
1025
- blendGeo,
1026
- geometryData.sx,
1027
- geometryData.sy,
1028
- geometryData.sz,
1029
- )
1023
+ const added = this.getGlobalLegacyBlendBuffer().addSection(sectionKey, blendGeo, geometryData.sx, geometryData.sy, geometryData.sz)
1030
1024
  if (added) {
1031
1025
  this.registerLegacyCullSection(sectionKey, geometryData.sx, geometryData.sy, geometryData.sz)
1032
1026
  } else {
1033
1027
  console.warn(`ChunkMeshManager: blend invariant violation for section ${sectionKey}, using pooled mesh fallback`)
1034
1028
  const poolEntry = this.acquirePooledSectionMesh(sectionKey)
1035
1029
  if (!poolEntry) return null
1036
- const blendMesh = this.uploadLegacyPooledMesh(
1037
- poolEntry,
1038
- geometryData.blend,
1039
- geometryData.sx,
1040
- geometryData.sy,
1041
- geometryData.sz,
1042
- )
1030
+ const blendMesh = this.uploadLegacyPooledMesh(poolEntry, geometryData.blend, geometryData.sx, geometryData.sy, geometryData.sz)
1043
1031
  legacyMesh = legacyMesh ?? blendMesh
1044
1032
  hasBlendMesh = true
1045
1033
  }
@@ -1048,7 +1036,6 @@ export class ChunkMeshManager {
1048
1036
 
1049
1037
  const cubeMaterial = hasShader ? this.getCubeShaderMaterial() : null
1050
1038
  let shaderMesh: THREE.Mesh<THREE.InstancedBufferGeometry, THREE.ShaderMaterial> | undefined
1051
- const deferShader = hasShader && this.shouldDeferShaderToPerSection(sectionKey)
1052
1039
  if (hasShader && shaderData) {
1053
1040
  if (deferShader && cubeMaterial) {
1054
1041
  shaderMesh = createShaderCubeMesh(shaderData, cubeMaterial)
@@ -1069,7 +1056,7 @@ export class ChunkMeshManager {
1069
1056
  if (shaderData) {
1070
1057
  sectionObject.deferredShaderCubes = {
1071
1058
  words: shaderData.words,
1072
- count: shaderData.count,
1059
+ count: shaderData.count
1073
1060
  }
1074
1061
  }
1075
1062
  if (!sectionObject.mesh) {
@@ -1083,14 +1070,7 @@ export class ChunkMeshManager {
1083
1070
  sectionObject.deferredLegacyBlend = deferredLegacyBlend
1084
1071
  }
1085
1072
  if (hasShader && shaderData) {
1086
- this.registerShaderSectionRaycastBox(
1087
- sectionKey,
1088
- shaderData.words,
1089
- shaderData.count,
1090
- geometryData.sx,
1091
- geometryData.sy,
1092
- geometryData.sz,
1093
- )
1073
+ this.registerShaderSectionRaycastBox(sectionKey, shaderData.words, shaderData.count, geometryData.sx, geometryData.sy, geometryData.sz)
1094
1074
  }
1095
1075
 
1096
1076
  let tilesCount = 0
@@ -1165,20 +1145,12 @@ export class ChunkMeshManager {
1165
1145
  const bannerTexture = getBannerTexture(this.worldRenderer, blockName, nbt.simplify(bannerBlockEntity))
1166
1146
  if (!bannerTexture) continue
1167
1147
  const skyLevel = this.blockEntityLightRegistry.getSkyLevel()
1168
- const banner = createBannerMesh(
1169
- new Vec3(+x, +y, +z),
1170
- rotation,
1171
- isWall,
1172
- bannerTexture,
1173
- blockLightNorm,
1174
- skyLightNorm,
1175
- skyLevel,
1176
- )
1148
+ const banner = createBannerMesh(new Vec3(+x, +y, +z), rotation, isWall, bannerTexture, blockLightNorm, skyLightNorm, skyLevel)
1177
1149
  if (banner.bannerMaterial) {
1178
1150
  this.blockEntityLightRegistry.register({
1179
1151
  material: banner.bannerMaterial,
1180
1152
  blockLightNorm,
1181
- skyLightNorm,
1153
+ skyLightNorm
1182
1154
  })
1183
1155
  }
1184
1156
  const { x: bwx, y: bwy, z: bwz } = banner.position
@@ -1212,8 +1184,7 @@ export class ChunkMeshManager {
1212
1184
  // setting so the near-first reveal gate has sections to hold.
1213
1185
  const chunkCoords = sectionKey.split(',')
1214
1186
  const chunkKey = `${chunkCoords[0]},${chunkCoords[2]}`
1215
- const renderByChunks = !!this.worldRenderer.displayOptions
1216
- ?.inWorldRenderingConfig?._renderByChunks
1187
+ const renderByChunks = !!this.worldRenderer.displayOptions?.inWorldRenderingConfig?._renderByChunks
1217
1188
  const forceBatchForWasm = !!this.worldRenderer.worldRendererConfig?.wasmMesher
1218
1189
  if ((renderByChunks || forceBatchForWasm) && !this.worldRenderer.finishedChunks[chunkKey]) {
1219
1190
  sectionObject.visible = false
@@ -1234,7 +1205,7 @@ export class ChunkMeshManager {
1234
1205
  * reveal is deferred (parked in `pendingNearReveal`) and re-checked on
1235
1206
  * the next chunkFinished / player-move / grace-expiry.
1236
1207
  */
1237
- finishChunkDisplay (chunkKey: string): void {
1208
+ finishChunkDisplay(chunkKey: string): void {
1238
1209
  const sectionKeys = this.waitingChunksToDisplay[chunkKey]
1239
1210
  if (!sectionKeys) {
1240
1211
  // No held sections (empty column / non-batched path) — but the
@@ -1253,7 +1224,7 @@ export class ChunkMeshManager {
1253
1224
  this.tryRevealPending()
1254
1225
  }
1255
1226
 
1256
- private flushChunkDisplay (chunkKey: string): void {
1227
+ private flushChunkDisplay(chunkKey: string): void {
1257
1228
  const sectionKeys = this.waitingChunksToDisplay[chunkKey]
1258
1229
  this.pendingNearReveal.delete(chunkKey)
1259
1230
  this.clearNearRevealTimer(chunkKey)
@@ -1273,7 +1244,7 @@ export class ChunkMeshManager {
1273
1244
  // Single pass is enough — pending entries are already finished, so flushing
1274
1245
  // one cannot un-block another via this code path (cascading happens via
1275
1246
  // chunkFinished events and per-pending grace timers).
1276
- tryRevealPending (): void {
1247
+ tryRevealPending(): void {
1277
1248
  if (this.pendingNearReveal.size === 0) return
1278
1249
  const now = Date.now()
1279
1250
  for (const [chunkKey, enqueuedAt] of [...this.pendingNearReveal]) {
@@ -1285,7 +1256,7 @@ export class ChunkMeshManager {
1285
1256
 
1286
1257
  // Drop gate state for an unloaded column and re-evaluate any farther
1287
1258
  // chunks that may have been blocked by it.
1288
- onChunkRemovedFromGate (chunkKey: string): void {
1259
+ onChunkRemovedFromGate(chunkKey: string): void {
1289
1260
  this.pendingNearReveal.delete(chunkKey)
1290
1261
  this.clearNearRevealTimer(chunkKey)
1291
1262
  this.clearExpectedGraceTimer(chunkKey)
@@ -1293,7 +1264,7 @@ export class ChunkMeshManager {
1293
1264
  this.tryRevealPending()
1294
1265
  }
1295
1266
 
1296
- private isWasmGateActive (): boolean {
1267
+ private isWasmGateActive(): boolean {
1297
1268
  return !!this.worldRenderer.worldRendererConfig?.wasmMesher
1298
1269
  }
1299
1270
 
@@ -1307,7 +1278,7 @@ export class ChunkMeshManager {
1307
1278
  * - After grace: only actually-loaded-but-not-finished columns block,
1308
1279
  * so a never-arriving column does not freeze the view.
1309
1280
  */
1310
- private isBlockedByNearer (chunkKey: string, ageMs: number): boolean {
1281
+ private isBlockedByNearer(chunkKey: string, ageMs: number): boolean {
1311
1282
  const viewer = this.worldRenderer.viewerChunkPosition
1312
1283
  if (!viewer) return false
1313
1284
  const ownParts = chunkKey.split(',')
@@ -1354,7 +1325,7 @@ export class ChunkMeshManager {
1354
1325
  return false
1355
1326
  }
1356
1327
 
1357
- private armNearRevealTimer (chunkKey: string): void {
1328
+ private armNearRevealTimer(chunkKey: string): void {
1358
1329
  if (this.nearRevealTimers.has(chunkKey)) return
1359
1330
  const timer = setTimeout(() => {
1360
1331
  this.nearRevealTimers.delete(chunkKey)
@@ -1366,7 +1337,7 @@ export class ChunkMeshManager {
1366
1337
  this.nearRevealTimers.set(chunkKey, timer)
1367
1338
  }
1368
1339
 
1369
- private clearNearRevealTimer (chunkKey: string): void {
1340
+ private clearNearRevealTimer(chunkKey: string): void {
1370
1341
  const timer = this.nearRevealTimers.get(chunkKey)
1371
1342
  if (timer) {
1372
1343
  clearTimeout(timer)
@@ -1379,7 +1350,7 @@ export class ChunkMeshManager {
1379
1350
  * "expected but never arrived" positions stop blocking promptly,
1380
1351
  * without waiting for the next chunkFinished / player-move event.
1381
1352
  */
1382
- private armExpectedGraceTimer (chunkKey: string): void {
1353
+ private armExpectedGraceTimer(chunkKey: string): void {
1383
1354
  if (this.nearRevealGraceTimers.has(chunkKey)) return
1384
1355
  const timer = setTimeout(() => {
1385
1356
  this.nearRevealGraceTimers.delete(chunkKey)
@@ -1389,7 +1360,7 @@ export class ChunkMeshManager {
1389
1360
  this.nearRevealGraceTimers.set(chunkKey, timer)
1390
1361
  }
1391
1362
 
1392
- private clearExpectedGraceTimer (chunkKey: string): void {
1363
+ private clearExpectedGraceTimer(chunkKey: string): void {
1393
1364
  const timer = this.nearRevealGraceTimers.get(chunkKey)
1394
1365
  if (timer) {
1395
1366
  clearTimeout(timer)
@@ -1397,7 +1368,7 @@ export class ChunkMeshManager {
1397
1368
  }
1398
1369
  }
1399
1370
 
1400
- cleanupSection (sectionKey: string, opts?: { forRemesh?: boolean }) {
1371
+ cleanupSection(sectionKey: string, opts?: { forRemesh?: boolean }) {
1401
1372
  // Remove section object from scene
1402
1373
  const sectionObject = this.sectionObjects[sectionKey]
1403
1374
  if (sectionObject) {
@@ -1416,7 +1387,7 @@ export class ChunkMeshManager {
1416
1387
  // Cleanup banner textures before disposing
1417
1388
  if (sectionObject.bannersContainer) {
1418
1389
  for (const child of sectionObject.bannersContainer.children) {
1419
- const banner = child as THREE.Group & { bannerMaterial?: THREE.MeshBasicMaterial, bannerTexture?: THREE.Texture }
1390
+ const banner = child as THREE.Group & { bannerMaterial?: THREE.MeshBasicMaterial; bannerTexture?: THREE.Texture }
1420
1391
  if (banner.bannerMaterial) {
1421
1392
  this.blockEntityLightRegistry.unregister(banner.bannerMaterial)
1422
1393
  }
@@ -1426,11 +1397,13 @@ export class ChunkMeshManager {
1426
1397
  }
1427
1398
  this.disposeContainer(sectionObject.bannersContainer)
1428
1399
  }
1429
- this.globalBlockBuffer?.removeSection(sectionKey)
1430
- this.globalLegacyBuffer?.removeSection(sectionKey)
1431
- this.globalLegacyBlendBuffer?.removeSection(sectionKey)
1432
- this.maybeUnregisterLegacyCullSection(sectionKey)
1433
- this.unregisterShaderSectionRaycastBox(sectionKey)
1400
+ if (!opts?.forRemesh) {
1401
+ this.globalBlockBuffer?.removeSection(sectionKey)
1402
+ this.globalLegacyBuffer?.removeSection(sectionKey)
1403
+ this.globalLegacyBlendBuffer?.removeSection(sectionKey)
1404
+ this.maybeUnregisterLegacyCullSection(sectionKey)
1405
+ this.unregisterShaderSectionRaycastBox(sectionKey)
1406
+ }
1434
1407
  this.markCullDirty()
1435
1408
  delete sectionObject.deferredLegacyOpaque
1436
1409
  delete sectionObject.deferredLegacyBlend
@@ -1469,8 +1442,7 @@ export class ChunkMeshManager {
1469
1442
  }
1470
1443
  delete this.sectionObjects[sectionKey]
1471
1444
  if (!opts?.forRemesh) {
1472
- this.worldRenderer.getModule<{ onSectionRemoved?: (key: string) => void }>('futuristicReveal')
1473
- ?.onSectionRemoved?.(sectionKey)
1445
+ this.worldRenderer.getModule<{ onSectionRemoved?: (key: string) => void }>('futuristicReveal')?.onSectionRemoved?.(sectionKey)
1474
1446
  }
1475
1447
  }
1476
1448
  }
@@ -1478,7 +1450,7 @@ export class ChunkMeshManager {
1478
1450
  /**
1479
1451
  * Release a section and return its mesh to the pool
1480
1452
  */
1481
- private releasePooledMesh (sectionKey: string): void {
1453
+ private releasePooledMesh(sectionKey: string): void {
1482
1454
  const poolEntry = this.activeSections.get(sectionKey)
1483
1455
  if (!poolEntry) return
1484
1456
 
@@ -1491,7 +1463,7 @@ export class ChunkMeshManager {
1491
1463
  this.cleanupExcessMeshes()
1492
1464
  }
1493
1465
 
1494
- releaseSection (sectionKey: string): boolean {
1466
+ releaseSection(sectionKey: string): boolean {
1495
1467
  this.cleanupSection(sectionKey)
1496
1468
 
1497
1469
  const poolEntry = this.activeSections.get(sectionKey)
@@ -1519,14 +1491,14 @@ export class ChunkMeshManager {
1519
1491
  /**
1520
1492
  * Get section object if it exists
1521
1493
  */
1522
- getSectionObject (sectionKey: string): SectionObject | undefined {
1494
+ getSectionObject(sectionKey: string): SectionObject | undefined {
1523
1495
  return this.sectionObjects[sectionKey]
1524
1496
  }
1525
1497
 
1526
1498
  /**
1527
1499
  * Update box helper for a section
1528
1500
  */
1529
- updateBoxHelper (sectionKey: string, showChunkBorders: boolean, chunkBoxMaterial: THREE.Material = this.chunkBoxMaterial) {
1501
+ updateBoxHelper(sectionKey: string, showChunkBorders: boolean, chunkBoxMaterial: THREE.Material = this.chunkBoxMaterial) {
1530
1502
  const sectionObject = this.sectionObjects[sectionKey]
1531
1503
  if (!sectionObject) return
1532
1504
 
@@ -1562,7 +1534,7 @@ export class ChunkMeshManager {
1562
1534
  * after the move from `WorldBlockGeometry` (which created the helpers
1563
1535
  * eagerly per section) to the pooled `ChunkMeshManager`.
1564
1536
  */
1565
- updateAllBoxHelpers (showChunkBorders: boolean) {
1537
+ updateAllBoxHelpers(showChunkBorders: boolean) {
1566
1538
  for (const sectionKey of Object.keys(this.sectionObjects)) {
1567
1539
  this.updateBoxHelper(sectionKey, showChunkBorders)
1568
1540
  }
@@ -1571,21 +1543,21 @@ export class ChunkMeshManager {
1571
1543
  /**
1572
1544
  * Get mesh for section if it exists
1573
1545
  */
1574
- getSectionMesh (sectionKey: string): THREE.Mesh | undefined {
1546
+ getSectionMesh(sectionKey: string): THREE.Mesh | undefined {
1575
1547
  return this.activeSections.get(sectionKey)?.mesh
1576
1548
  }
1577
1549
 
1578
1550
  /**
1579
1551
  * Check if section is managed by this pool
1580
1552
  */
1581
- hasSection (sectionKey: string): boolean {
1553
+ hasSection(sectionKey: string): boolean {
1582
1554
  return this.activeSections.has(sectionKey)
1583
1555
  }
1584
1556
 
1585
1557
  /**
1586
1558
  * Update pool size based on new view distance
1587
1559
  */
1588
- updateViewDistance (maxViewDistance: number) {
1560
+ updateViewDistance(maxViewDistance: number) {
1589
1561
  // Calculate dynamic pool size based on view distance
1590
1562
  const chunksInView = (maxViewDistance * 2 + 1) ** 2
1591
1563
  const maxSectionsPerChunk = this.worldHeight / 16
@@ -1606,7 +1578,7 @@ export class ChunkMeshManager {
1606
1578
  /**
1607
1579
  * Get pool statistics
1608
1580
  */
1609
- getGlobalBufferStats (): GlobalBufferStats {
1581
+ getGlobalBufferStats(): GlobalBufferStats {
1610
1582
  const snapshotLegacy = (buffer: GlobalLegacyBuffer | null): GlobalBufferSlotStats | null => {
1611
1583
  if (!buffer) return null
1612
1584
  return {
@@ -1614,7 +1586,7 @@ export class ChunkMeshManager {
1614
1586
  capacity: buffer.getCapacityQuads(),
1615
1587
  sections: buffer.getSectionCount(),
1616
1588
  usedBytes: buffer.getUsedMemoryBytes(),
1617
- capacityBytes: buffer.getMemoryBytes(),
1589
+ capacityBytes: buffer.getMemoryBytes()
1618
1590
  }
1619
1591
  }
1620
1592
 
@@ -1626,17 +1598,17 @@ export class ChunkMeshManager {
1626
1598
  capacity: cubes.getCapacityFaces(),
1627
1599
  sections: cubes.getSectionCount(),
1628
1600
  usedBytes: cubes.getUsedMemoryBytes(),
1629
- capacityBytes: cubes.getMemoryBytes(),
1601
+ capacityBytes: cubes.getMemoryBytes()
1630
1602
  }
1631
1603
  : null,
1632
1604
  legacyOpaque: snapshotLegacy(this.globalLegacyBuffer),
1633
- legacyBlend: snapshotLegacy(this.globalLegacyBlendBuffer),
1605
+ legacyBlend: snapshotLegacy(this.globalLegacyBlendBuffer)
1634
1606
  }
1635
1607
  }
1636
1608
 
1637
- getStats () {
1609
+ getStats() {
1638
1610
  const freeCount = this.meshPool.filter(entry => !entry.inUse).length
1639
- const hitRate = this.hits + this.misses > 0 ? (this.hits / (this.hits + this.misses) * 100).toFixed(1) : '0'
1611
+ const hitRate = this.hits + this.misses > 0 ? ((this.hits / (this.hits + this.misses)) * 100).toFixed(1) : '0'
1640
1612
  const memoryUsage = this.getEstimatedMemoryUsage()
1641
1613
 
1642
1614
  return {
@@ -1653,21 +1625,21 @@ export class ChunkMeshManager {
1653
1625
  /**
1654
1626
  * Get total tiles rendered
1655
1627
  */
1656
- getTotalTiles (): number {
1628
+ getTotalTiles(): number {
1657
1629
  return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj.tilesCount || 0), 0)
1658
1630
  }
1659
1631
 
1660
1632
  /**
1661
1633
  * Get total blocks rendered
1662
1634
  */
1663
- getTotalBlocks (): number {
1635
+ getTotalBlocks(): number {
1664
1636
  return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj.blocksCount || 0), 0)
1665
1637
  }
1666
1638
 
1667
1639
  /**
1668
1640
  * Estimate memory usage in MB
1669
1641
  */
1670
- getEstimatedMemoryUsage (): { total: string, breakdown: any } {
1642
+ getEstimatedMemoryUsage(): { total: string; breakdown: any } {
1671
1643
  let totalBytes = 0
1672
1644
  let positionBytes = 0
1673
1645
  let normalBytes = 0
@@ -1756,7 +1728,7 @@ export class ChunkMeshManager {
1756
1728
  color: `${(colorBytes / (1024 * 1024)).toFixed(2)} MB`,
1757
1729
  uv: `${(uvBytes / (1024 * 1024)).toFixed(2)} MB`,
1758
1730
  index: `${(indexBytes / (1024 * 1024)).toFixed(2)} MB`,
1759
- shaderInstances: `${(shaderInstanceBytes / (1024 * 1024)).toFixed(2)} MB`,
1731
+ shaderInstances: `${(shaderInstanceBytes / (1024 * 1024)).toFixed(2)} MB`
1760
1732
  }
1761
1733
  }
1762
1734
  }
@@ -1764,7 +1736,7 @@ export class ChunkMeshManager {
1764
1736
  /**
1765
1737
  * Cleanup and dispose resources
1766
1738
  */
1767
- dispose () {
1739
+ dispose() {
1768
1740
  // Release all active sections (snapshot keys to avoid mutating map during iteration)
1769
1741
  const activeKeys = [...this.activeSections.keys()]
1770
1742
  for (const sectionKey of activeKeys) {
@@ -1783,6 +1755,7 @@ export class ChunkMeshManager {
1783
1755
  this.activeSections.clear()
1784
1756
  this.chunkBoxMaterial.dispose()
1785
1757
  this.shaderSectionRaycastBoxes.clear()
1758
+ this.lastBufferStateKey = ''
1786
1759
  this.globalBlockBuffer?.dispose()
1787
1760
  this.globalBlockBuffer = null
1788
1761
  this.globalLegacyBuffer?.dispose()
@@ -1807,7 +1780,7 @@ export class ChunkMeshManager {
1807
1780
 
1808
1781
  // Private helper methods
1809
1782
 
1810
- private acquireMesh (): ChunkMeshPool | undefined {
1783
+ private acquireMesh(): ChunkMeshPool | undefined {
1811
1784
  if (this.bypassPooling) {
1812
1785
  const entry: ChunkMeshPool = {
1813
1786
  mesh: new THREE.Mesh(new THREE.BufferGeometry(), this.getLegacyShaderMaterial()),
@@ -1854,7 +1827,7 @@ export class ChunkMeshManager {
1854
1827
  throw new Error('ChunkMeshManager: Failed to acquire mesh after pool expansion')
1855
1828
  }
1856
1829
 
1857
- private expandPool (newSize: number) {
1830
+ private expandPool(newSize: number) {
1858
1831
  const currentLength = this.meshPool.length
1859
1832
  this.poolSize = newSize
1860
1833
 
@@ -1877,12 +1850,7 @@ export class ChunkMeshManager {
1877
1850
  }
1878
1851
  }
1879
1852
 
1880
- private updateGeometryAttribute (
1881
- geometry: THREE.BufferGeometry,
1882
- name: string,
1883
- array: Float32Array,
1884
- itemSize: number
1885
- ) {
1853
+ private updateGeometryAttribute(geometry: THREE.BufferGeometry, name: string, array: Float32Array, itemSize: number) {
1886
1854
  const attribute = geometry.getAttribute(name)
1887
1855
 
1888
1856
  if (attribute && attribute.count === array.length / itemSize) {
@@ -1895,7 +1863,7 @@ export class ChunkMeshManager {
1895
1863
  }
1896
1864
  }
1897
1865
 
1898
- private clearGeometry (geometry: THREE.BufferGeometry) {
1866
+ private clearGeometry(geometry: THREE.BufferGeometry) {
1899
1867
  const attributes = ['position', 'normal', 'color', 'a_skyLight', 'a_blockLight', 'uv']
1900
1868
  for (const name of attributes) {
1901
1869
  if (geometry.hasAttribute(name)) {
@@ -1909,7 +1877,7 @@ export class ChunkMeshManager {
1909
1877
  geometry.boundingSphere = null
1910
1878
  }
1911
1879
 
1912
- private cleanupExcessMeshes () {
1880
+ private cleanupExcessMeshes() {
1913
1881
  // If pool size exceeds max and we have free meshes, remove some
1914
1882
  if (this.poolSize > this.maxPoolSize) {
1915
1883
  const freeCount = this.meshPool.filter(entry => !entry.inUse).length
@@ -1929,14 +1897,14 @@ export class ChunkMeshManager {
1929
1897
  }
1930
1898
  }
1931
1899
 
1932
- private disposeContainer (container: THREE.Group, cleanTextures = true) {
1900
+ private disposeContainer(container: THREE.Group, cleanTextures = true) {
1933
1901
  disposeObject(container, cleanTextures)
1934
1902
  }
1935
1903
 
1936
1904
  /**
1937
1905
  * Record render time for performance monitoring
1938
1906
  */
1939
- recordRenderTime (renderTime: number): void {
1907
+ recordRenderTime(renderTime: number): void {
1940
1908
  this.renderTimes.push(renderTime)
1941
1909
  if (this.renderTimes.length > this.maxRenderTimeSamples) {
1942
1910
  this.renderTimes.shift()
@@ -1953,31 +1921,31 @@ export class ChunkMeshManager {
1953
1921
  /**
1954
1922
  * Get current effective render distance
1955
1923
  */
1956
- getEffectiveRenderDistance (): number {
1924
+ getEffectiveRenderDistance(): number {
1957
1925
  return this.performanceOverrideDistance || this.worldRenderer.viewDistance
1958
1926
  }
1959
1927
 
1960
1928
  /**
1961
1929
  * Force reset performance override
1962
1930
  */
1963
- resetPerformanceOverride (): void {
1931
+ resetPerformanceOverride(): void {
1964
1932
  this.performanceOverrideDistance = undefined
1965
1933
  this.renderTimes.length = 0
1966
1934
  console.log('ChunkMeshManager: Performance override reset')
1967
1935
  }
1968
1936
 
1969
1937
  /**
1970
- * Get average render time
1971
- */
1972
- getAverageRenderTime (): number {
1938
+ * Get average render time
1939
+ */
1940
+ getAverageRenderTime(): number {
1973
1941
  if (this.renderTimes.length === 0) return 0
1974
1942
  return this.renderTimes.reduce((sum, time) => sum + time, 0) / this.renderTimes.length
1975
1943
  }
1976
1944
 
1977
1945
  /**
1978
- * Check if performance is degraded and adjust render distance
1979
- */
1980
- private checkPerformance (): void {
1946
+ * Check if performance is degraded and adjust render distance
1947
+ */
1948
+ private checkPerformance(): void {
1981
1949
  if (this.renderTimes.length < this.maxRenderTimeSamples) return
1982
1950
 
1983
1951
  const avgRenderTime = this.getAverageRenderTime()
@@ -2008,7 +1976,7 @@ export class ChunkMeshManager {
2008
1976
  /**
2009
1977
  * Hide sections beyond performance override distance
2010
1978
  */
2011
- updateSectionsVisibility (): void {
1979
+ updateSectionsVisibility(): void {
2012
1980
  const cameraPos = this.worldRenderer.cameraSectionPos
2013
1981
  for (const [sectionKey, sectionObject] of Object.entries(this.sectionObjects)) {
2014
1982
  // Don't override "Batch Chunks Display" hiding — those sections must
@@ -2035,16 +2003,14 @@ export class ChunkMeshManager {
2035
2003
  }
2036
2004
  }
2037
2005
 
2038
-
2039
2006
  class SignHeadsRenderer {
2040
- constructor (public worldRendererThree: WorldRendererThree) {
2041
- }
2007
+ constructor(public worldRendererThree: WorldRendererThree) {}
2042
2008
 
2043
- dispose () {
2009
+ dispose() {
2044
2010
  disposeAllSignTextures()
2045
2011
  }
2046
2012
 
2047
- renderHead (position: Vec3, rotation: number, isWall: boolean, blockEntity) {
2013
+ renderHead(position: Vec3, rotation: number, isWall: boolean, blockEntity) {
2048
2014
  let textureData: string
2049
2015
  if (blockEntity.SkullOwner) {
2050
2016
  textureData = blockEntity.SkullOwner.Properties?.textures?.[0]?.Value
@@ -2058,8 +2024,7 @@ class SignHeadsRenderer {
2058
2024
  let skinUrl = decodedData.textures?.SKIN?.url
2059
2025
  const { skinTexturesProxy } = this.worldRendererThree.worldRendererConfig
2060
2026
  if (skinTexturesProxy) {
2061
- skinUrl = skinUrl?.replace('http://textures.minecraft.net/', skinTexturesProxy)
2062
- .replace('https://textures.minecraft.net/', skinTexturesProxy)
2027
+ skinUrl = skinUrl?.replace('http://textures.minecraft.net/', skinTexturesProxy).replace('https://textures.minecraft.net/', skinTexturesProxy)
2063
2028
  }
2064
2029
 
2065
2030
  const mesh = getMesh(this.worldRendererThree, skinUrl, armorModel.head as any)
@@ -2072,11 +2037,7 @@ class SignHeadsRenderer {
2072
2037
  group.add(mesh)
2073
2038
  this.worldRendererThree.sceneOrigin.track(group)
2074
2039
  group.position.set(position.x + 0.5, position.y + 0.045, position.z + 0.5)
2075
- group.rotation.set(
2076
- 0,
2077
- -THREE.MathUtils.degToRad(rotation * (isWall ? 90 : 45 / 2)),
2078
- 0
2079
- )
2040
+ group.rotation.set(0, -THREE.MathUtils.degToRad(rotation * (isWall ? 90 : 45 / 2)), 0)
2080
2041
  group.scale.set(0.8, 0.8, 0.8)
2081
2042
  return group
2082
2043
  } catch (err) {
@@ -2084,7 +2045,7 @@ class SignHeadsRenderer {
2084
2045
  }
2085
2046
  }
2086
2047
 
2087
- renderSign (position: Vec3, rotation: number, isWall: boolean, isHanging: boolean, blockEntity) {
2048
+ renderSign(position: Vec3, rotation: number, isWall: boolean, isHanging: boolean, blockEntity) {
2088
2049
  const tex = getSignTexture(this.worldRendererThree, blockEntity, isHanging)
2089
2050
 
2090
2051
  if (!tex) return
@@ -2105,11 +2066,7 @@ class SignHeadsRenderer {
2105
2066
  }
2106
2067
 
2107
2068
  const group = new THREE.Group() as THREE.Group & { signTexture?: THREE.Texture }
2108
- group.rotation.set(
2109
- 0,
2110
- -THREE.MathUtils.degToRad(rotation * (isWall ? 90 : 45 / 2)),
2111
- 0
2112
- )
2069
+ group.rotation.set(0, -THREE.MathUtils.degToRad(rotation * (isWall ? 90 : 45 / 2)), 0)
2113
2070
  group.add(mesh)
2114
2071
  group.signTexture = tex
2115
2072
  const height = (isHanging ? 10 : 8) / 16