minecraft-renderer 0.1.100 → 0.1.102

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minecraft-renderer",
3
- "version": "0.1.100",
3
+ "version": "0.1.102",
4
4
  "description": "The most Modular Minecraft world renderer with Three.js WebGL backend",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -45,6 +45,17 @@ export function createCubeMultiDrawScratch(): CubeMultiDrawScratch {
45
45
  }
46
46
  }
47
47
 
48
+ /** Grow scratch arrays when culling leaves more disjoint spans than MAX_CUBE_SPANS. */
49
+ function ensureScratchCapacity(scratch: CubeMultiDrawScratch, minLength: number): void {
50
+ if (scratch.counts.length >= minLength) return
51
+ let newLen = scratch.counts.length
52
+ while (newLen < minLength) newLen *= 2
53
+ scratch.firsts = new Int32Array(newLen)
54
+ scratch.counts = new Int32Array(newLen)
55
+ scratch.instanceCounts = new Int32Array(newLen)
56
+ scratch.baseInstances = new Int32Array(newLen)
57
+ }
58
+
48
59
  export function detectMultiDrawCaps(gl: WebGL2RenderingContext): MultiDrawCaps {
49
60
  const tierA = gl.getExtension('WEBGL_multi_draw_instanced_base_vertex_base_instance')
50
61
  if (tierA) {
@@ -82,6 +93,7 @@ export function drawCubeSpans(
82
93
 
83
94
  if (caps.tier === 'A' && caps.ext) {
84
95
  const ext = caps.ext as MultiDrawInstancedExt
96
+ ensureScratchCapacity(scratch, drawCount)
85
97
  for (let i = 0; i < drawCount; i++) {
86
98
  const span = spans[i]!
87
99
  scratch.firsts[i] = 0
@@ -0,0 +1,118 @@
1
+ //@ts-nocheck
2
+ import { expect, test, vi } from 'vitest'
3
+ import { buildVisibleCubeSpans, MAX_CUBE_SPANS } from '../cubeDrawSpans'
4
+ import { createCubeMultiDrawScratch, detectMultiDrawCaps, drawCubeSpans } from '../cubeMultiDraw'
5
+ import { VERTICES_PER_FACE } from '../shaders/cubeBlockShader'
6
+
7
+ type MultiDrawArgs = [number, Int32Array, number, Int32Array, number, Int32Array, number, Int32Array, number, number]
8
+
9
+ function makeMockGl(opts?: { tier?: 'A' | 'B' }): WebGL2RenderingContext {
10
+ const multiDrawArraysInstancedBaseInstanceWEBGL = vi.fn((...args: MultiDrawArgs) => {
11
+ const [, firsts, , counts, , instanceCounts, , baseInstances, , drawCount] = args
12
+ // WebGL raises INVALID_OPERATION and draws nothing when an argument array is shorter than drawCount.
13
+ for (const array of [firsts, counts, instanceCounts, baseInstances]) {
14
+ if (array.length < drawCount) throw new Error(`argument array of length ${array.length} for drawCount ${drawCount}`)
15
+ }
16
+ })
17
+ const drawArraysInstancedBaseInstanceWEBGL = vi.fn()
18
+ return {
19
+ TRIANGLES: 0x0004,
20
+ getExtension: (name: string) => {
21
+ if (opts?.tier === 'A' && name === 'WEBGL_multi_draw_instanced_base_vertex_base_instance') {
22
+ return { multiDrawArraysInstancedBaseInstanceWEBGL }
23
+ }
24
+ if (opts?.tier === 'B' && name === 'WEBGL_draw_instanced_base_vertex_base_instance') {
25
+ return { drawArraysInstancedBaseInstanceWEBGL }
26
+ }
27
+ return null
28
+ }
29
+ } as unknown as WebGL2RenderingContext
30
+ }
31
+
32
+ /** Visible slots with a one-instance hole between each, so no two merge into one span. */
33
+ function scatteredSpans(count: number) {
34
+ const slots = Array.from({ length: count }, (_, i) => ({ start: i * 3, count: 2 }))
35
+ return buildVisibleCubeSpans(slots, count * 3)
36
+ }
37
+
38
+ test('drawCubeSpans: tier A submits one instanced draw per span', () => {
39
+ const gl = makeMockGl({ tier: 'A' })
40
+ const caps = detectMultiDrawCaps(gl)
41
+ const scratch = createCubeMultiDrawScratch()
42
+ const spans = [
43
+ { start: 4, count: 2 },
44
+ { start: 10, count: 5 }
45
+ ]
46
+
47
+ drawCubeSpans(gl, caps, spans, scratch)
48
+
49
+ expect(Array.from(scratch.firsts.subarray(0, 2))).toEqual([0, 0])
50
+ expect(Array.from(scratch.counts.subarray(0, 2))).toEqual([VERTICES_PER_FACE, VERTICES_PER_FACE])
51
+ expect(Array.from(scratch.instanceCounts.subarray(0, 2))).toEqual([2, 5])
52
+ expect(Array.from(scratch.baseInstances.subarray(0, 2))).toEqual([4, 10])
53
+
54
+ const ext = caps.ext as { multiDrawArraysInstancedBaseInstanceWEBGL: ReturnType<typeof vi.fn> }
55
+ expect(ext.multiDrawArraysInstancedBaseInstanceWEBGL).toHaveBeenCalledTimes(1)
56
+ expect(ext.multiDrawArraysInstancedBaseInstanceWEBGL).toHaveBeenCalledWith(
57
+ gl.TRIANGLES,
58
+ scratch.firsts,
59
+ 0,
60
+ scratch.counts,
61
+ 0,
62
+ scratch.instanceCounts,
63
+ 0,
64
+ scratch.baseInstances,
65
+ 0,
66
+ 2
67
+ )
68
+ })
69
+
70
+ test('drawCubeSpans: tier A grows scratch past MAX_CUBE_SPANS for fragmented visible sets', () => {
71
+ const gl = makeMockGl({ tier: 'A' })
72
+ const caps = detectMultiDrawCaps(gl)
73
+ const scratch = createCubeMultiDrawScratch()
74
+ expect(scratch.counts.length).toBe(MAX_CUBE_SPANS)
75
+
76
+ const spans = scatteredSpans(MAX_CUBE_SPANS + 6)
77
+ expect(spans.length).toBe(MAX_CUBE_SPANS + 6)
78
+
79
+ drawCubeSpans(gl, caps, spans, scratch)
80
+
81
+ for (const array of [scratch.firsts, scratch.counts, scratch.instanceCounts, scratch.baseInstances]) {
82
+ expect(array.length).toBeGreaterThanOrEqual(spans.length)
83
+ }
84
+ const last = spans.length - 1
85
+ expect(scratch.instanceCounts[last]).toBe(spans[last]!.count)
86
+ expect(scratch.baseInstances[last]).toBe(spans[last]!.start)
87
+ const ext = caps.ext as { multiDrawArraysInstancedBaseInstanceWEBGL: ReturnType<typeof vi.fn> }
88
+ expect(ext.multiDrawArraysInstancedBaseInstanceWEBGL.mock.calls[0]![9]).toBe(spans.length)
89
+ })
90
+
91
+ test('drawCubeSpans: tier A keeps grown scratch when the visible set shrinks and grows again', () => {
92
+ const gl = makeMockGl({ tier: 'A' })
93
+ const caps = detectMultiDrawCaps(gl)
94
+ const scratch = createCubeMultiDrawScratch()
95
+
96
+ drawCubeSpans(gl, caps, scatteredSpans(MAX_CUBE_SPANS + 1), scratch)
97
+ const grown = scratch.counts
98
+ drawCubeSpans(gl, caps, scatteredSpans(3), scratch)
99
+ expect(scratch.counts).toBe(grown)
100
+ drawCubeSpans(gl, caps, scatteredSpans(grown.length * 2 + 1), scratch)
101
+
102
+ expect(scratch.counts.length).toBeGreaterThanOrEqual(grown.length * 2 + 1)
103
+ const ext = caps.ext as { multiDrawArraysInstancedBaseInstanceWEBGL: ReturnType<typeof vi.fn> }
104
+ expect(ext.multiDrawArraysInstancedBaseInstanceWEBGL).toHaveBeenCalledTimes(3)
105
+ })
106
+
107
+ test('drawCubeSpans: tier B issues one base-instance draw per span', () => {
108
+ const gl = makeMockGl({ tier: 'B' })
109
+ const caps = detectMultiDrawCaps(gl)
110
+ const scratch = createCubeMultiDrawScratch()
111
+ const spans = scatteredSpans(MAX_CUBE_SPANS + 1)
112
+
113
+ drawCubeSpans(gl, caps, spans, scratch)
114
+
115
+ const ext = caps.ext as { drawArraysInstancedBaseInstanceWEBGL: ReturnType<typeof vi.fn> }
116
+ expect(ext.drawArraysInstancedBaseInstanceWEBGL).toHaveBeenCalledTimes(spans.length)
117
+ expect(ext.drawArraysInstancedBaseInstanceWEBGL).toHaveBeenLastCalledWith(gl.TRIANGLES, 0, VERTICES_PER_FACE, 2, (spans.length - 1) * 3)
118
+ })
@@ -48,6 +48,26 @@ export const getBlockMeta = (version: string): BlockMeta => {
48
48
  return meta
49
49
  }
50
50
 
51
+ const ceilLog2 = (count: number) => (count <= 1 ? 0 : 32 - Math.clz32(count - 1))
52
+
53
+ const globalPaletteBitsCache = new Map<string, { blocks: number; biomes: number }>()
54
+
55
+ /**
56
+ * Bits of the global block state and biome palettes, vanilla's
57
+ * `Strategy.globalPaletteBitsInMemory`: ceillog2 of the registry size.
58
+ * map_chunk stores direct (unpaletted) containers with them. The biome count
59
+ * is minecraft-data's vanilla registry; a server's own biomes can change it.
60
+ */
61
+ export const getGlobalPaletteBits = (version: string) => {
62
+ const cached = globalPaletteBitsCache.get(version)
63
+ if (cached) return cached
64
+ const mcData = MinecraftData(version)
65
+ const blockStates = mcData.blocksArray.reduce((max, block) => Math.max(max, block.maxStateId ?? 0), 0) + 1
66
+ const bits = { blocks: ceilLog2(blockStates), biomes: ceilLog2(mcData.biomesArray.length) }
67
+ globalPaletteBitsCache.set(version, bits)
68
+ return bits
69
+ }
70
+
51
71
  export interface ChunkConversionResult {
52
72
  blockStates: Uint16Array
53
73
  blockLight: Uint8Array
@@ -227,23 +227,23 @@ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembl
227
227
 
228
228
  export interface InitOutput {
229
229
  readonly memory: WebAssembly.Memory;
230
+ readonly computeWireframeEdges: (a: number, b: number, c: number, d: number) => [number, number];
231
+ readonly computeWireframeEdgesU16: (a: number, b: number, c: number, d: number) => [number, number];
232
+ readonly generateGeometryFromDump118: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number, s: number, t: number, u: number, v: number, w: number, x: number, y: number, z: number, a1: number, b1: number, c1: number, d1: number, e1: number, f1: number, g1: number, h1: number, i1: number, j1: number, k1: number) => any;
233
+ readonly generateGeometryFromMapChunkV18Plus: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number, s: number, t: number, u: number, v: number, w: number, x: number, y: number, z: number) => any;
234
+ readonly generateGeometryFromMapChunkV18PlusMulti: (a: any, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number, s: number, t: number, u: number, v: number, w: number, x: number, y: number, z: number, a1: number, b1: number, c1: number, d1: number) => any;
235
+ readonly generateGeometryFromParsedV16V17: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number, s: number, t: number, u: number, v: number, w: number, x: number, y: number, z: number, a1: number, b1: number, c1: number, d1: number, e1: number, f1: number, g1: number) => any;
236
+ readonly generateGeometryFromParsedV16V17Multi: (a: any, b: number, c: number, d: number, e: number, f: number, g: any, h: number, i: any, j: any, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number, s: number, t: number, u: number, v: number, w: number, x: number, y: number, z: number, a1: number, b1: number, c1: number, d1: number, e1: number, f1: number, g1: number, h1: number) => any;
230
237
  readonly generate_geometry: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number, s: number, t: number, u: number, v: number, w: number, x: number, y: number, z: number, a1: number, b1: number) => any;
231
238
  readonly generate_geometry_multi: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number, s: number, t: number, u: number, v: number, w: number, x: number, y: number, z: number, a1: number, b1: number, c1: number, d1: number, e1: number, f1: number) => any;
232
239
  readonly parseChunkDump118: (a: number, b: number, c: number, d: number, e: number) => any;
233
- readonly unpackLightSection118: (a: number, b: number) => [number, number];
234
- readonly parseChunkDump118NoMarshal: (a: number, b: number, c: number, d: number, e: number) => number;
235
- readonly parseChunkDump118FullColumnAll: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number) => any;
236
- readonly generateGeometryFromDump118: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number, s: number, t: number, u: number, v: number, w: number, x: number, y: number, z: number, a1: number, b1: number, c1: number, d1: number, e1: number, f1: number, g1: number, h1: number, i1: number, j1: number, k1: number) => any;
237
240
  readonly parseChunkDump118FullColumn: (a: number, b: number, c: number, d: number, e: number) => any;
238
- readonly generateGeometryFromMapChunkV18Plus: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number, s: number, t: number, u: number, v: number, w: number, x: number, y: number, z: number) => any;
239
- readonly parseMapChunkV18Plus: (a: number, b: number, c: number, d: number, e: number, f: number) => any;
241
+ readonly parseChunkDump118FullColumnAll: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number) => any;
242
+ readonly parseChunkDump118NoMarshal: (a: number, b: number, c: number, d: number, e: number) => number;
240
243
  readonly parseChunkSectionsV16V17: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number) => any;
241
- readonly generateGeometryFromParsedV16V17: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number, s: number, t: number, u: number, v: number, w: number, x: number, y: number, z: number, a1: number, b1: number, c1: number, d1: number, e1: number, f1: number, g1: number) => any;
242
- readonly generateGeometryFromMapChunkV18PlusMulti: (a: any, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number, s: number, t: number, u: number, v: number, w: number, x: number, y: number, z: number, a1: number, b1: number, c1: number, d1: number) => any;
243
- readonly generateGeometryFromParsedV16V17Multi: (a: any, b: number, c: number, d: number, e: number, f: number, g: any, h: number, i: any, j: any, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number, s: number, t: number, u: number, v: number, w: number, x: number, y: number, z: number, a1: number, b1: number, c1: number, d1: number, e1: number, f1: number, g1: number, h1: number) => any;
244
- readonly computeWireframeEdges: (a: number, b: number, c: number, d: number) => [number, number];
245
- readonly computeWireframeEdgesU16: (a: number, b: number, c: number, d: number) => [number, number];
244
+ readonly parseMapChunkV18Plus: (a: number, b: number, c: number, d: number, e: number, f: number) => any;
246
245
  readonly parseUpdateLightV17: (a: number, b: number, c: number) => any;
246
+ readonly unpackLightSection118: (a: number, b: number) => [number, number];
247
247
  readonly __wbindgen_malloc: (a: number, b: number) => number;
248
248
  readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
249
249
  readonly __wbindgen_exn_store: (a: number) => void;
@@ -2,23 +2,23 @@
2
2
  /* tslint:disable */
3
3
  /* eslint-disable */
4
4
  export const memory: WebAssembly.Memory;
5
+ export const computeWireframeEdges: (a: number, b: number, c: number, d: number) => [number, number];
6
+ export const computeWireframeEdgesU16: (a: number, b: number, c: number, d: number) => [number, number];
7
+ export const generateGeometryFromDump118: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number, s: number, t: number, u: number, v: number, w: number, x: number, y: number, z: number, a1: number, b1: number, c1: number, d1: number, e1: number, f1: number, g1: number, h1: number, i1: number, j1: number, k1: number) => any;
8
+ export const generateGeometryFromMapChunkV18Plus: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number, s: number, t: number, u: number, v: number, w: number, x: number, y: number, z: number) => any;
9
+ export const generateGeometryFromMapChunkV18PlusMulti: (a: any, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number, s: number, t: number, u: number, v: number, w: number, x: number, y: number, z: number, a1: number, b1: number, c1: number, d1: number) => any;
10
+ export const generateGeometryFromParsedV16V17: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number, s: number, t: number, u: number, v: number, w: number, x: number, y: number, z: number, a1: number, b1: number, c1: number, d1: number, e1: number, f1: number, g1: number) => any;
11
+ export const generateGeometryFromParsedV16V17Multi: (a: any, b: number, c: number, d: number, e: number, f: number, g: any, h: number, i: any, j: any, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number, s: number, t: number, u: number, v: number, w: number, x: number, y: number, z: number, a1: number, b1: number, c1: number, d1: number, e1: number, f1: number, g1: number, h1: number) => any;
5
12
  export const generate_geometry: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number, s: number, t: number, u: number, v: number, w: number, x: number, y: number, z: number, a1: number, b1: number) => any;
6
13
  export const generate_geometry_multi: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number, s: number, t: number, u: number, v: number, w: number, x: number, y: number, z: number, a1: number, b1: number, c1: number, d1: number, e1: number, f1: number) => any;
7
14
  export const parseChunkDump118: (a: number, b: number, c: number, d: number, e: number) => any;
8
- export const unpackLightSection118: (a: number, b: number) => [number, number];
9
- export const parseChunkDump118NoMarshal: (a: number, b: number, c: number, d: number, e: number) => number;
10
- export const parseChunkDump118FullColumnAll: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number) => any;
11
- export const generateGeometryFromDump118: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number, s: number, t: number, u: number, v: number, w: number, x: number, y: number, z: number, a1: number, b1: number, c1: number, d1: number, e1: number, f1: number, g1: number, h1: number, i1: number, j1: number, k1: number) => any;
12
15
  export const parseChunkDump118FullColumn: (a: number, b: number, c: number, d: number, e: number) => any;
13
- export const generateGeometryFromMapChunkV18Plus: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number, s: number, t: number, u: number, v: number, w: number, x: number, y: number, z: number) => any;
14
- export const parseMapChunkV18Plus: (a: number, b: number, c: number, d: number, e: number, f: number) => any;
16
+ export const parseChunkDump118FullColumnAll: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number) => any;
17
+ export const parseChunkDump118NoMarshal: (a: number, b: number, c: number, d: number, e: number) => number;
15
18
  export const parseChunkSectionsV16V17: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number) => any;
16
- export const generateGeometryFromParsedV16V17: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number, s: number, t: number, u: number, v: number, w: number, x: number, y: number, z: number, a1: number, b1: number, c1: number, d1: number, e1: number, f1: number, g1: number) => any;
17
- export const generateGeometryFromMapChunkV18PlusMulti: (a: any, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number, s: number, t: number, u: number, v: number, w: number, x: number, y: number, z: number, a1: number, b1: number, c1: number, d1: number) => any;
18
- export const generateGeometryFromParsedV16V17Multi: (a: any, b: number, c: number, d: number, e: number, f: number, g: any, h: number, i: any, j: any, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number, s: number, t: number, u: number, v: number, w: number, x: number, y: number, z: number, a1: number, b1: number, c1: number, d1: number, e1: number, f1: number, g1: number, h1: number) => any;
19
- export const computeWireframeEdges: (a: number, b: number, c: number, d: number) => [number, number];
20
- export const computeWireframeEdgesU16: (a: number, b: number, c: number, d: number) => [number, number];
19
+ export const parseMapChunkV18Plus: (a: number, b: number, c: number, d: number, e: number, f: number) => any;
21
20
  export const parseUpdateLightV17: (a: number, b: number, c: number) => any;
21
+ export const unpackLightSection118: (a: number, b: number) => [number, number];
22
22
  export const __wbindgen_malloc: (a: number, b: number) => number;
23
23
  export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
24
24
  export const __wbindgen_exn_store: (a: number) => void;
@@ -0,0 +1,17 @@
1
+ //@ts-nocheck
2
+ import { describe, expect, test } from 'vitest'
3
+ import { getGlobalPaletteBits } from '../bridge/convertChunk'
4
+
5
+ describe('getGlobalPaletteBits', () => {
6
+ test('is ceillog2 of the block state and biome registry sizes', () => {
7
+ // 20342 states, 61 biomes
8
+ expect(getGlobalPaletteBits('1.18.2')).toEqual({ blocks: 15, biomes: 6 })
9
+ // 65 biomes need a seventh bit
10
+ expect(getGlobalPaletteBits('1.21.4')).toEqual({ blocks: 15, biomes: 7 })
11
+ })
12
+
13
+ test('needs no extra bit for a power-of-two registry', () => {
14
+ // 64 biomes fit in 6 bits (Mth.ceillog2(64) = 6)
15
+ expect(getGlobalPaletteBits('1.20.4').biomes).toBe(6)
16
+ })
17
+ })
@@ -1,6 +1,6 @@
1
1
  //@ts-nocheck
2
2
  import { Vec3 } from 'vec3'
3
- import { convertChunkToWasm, getBlockMeta, type ChunkConversionResult } from '../bridge/convertChunk'
3
+ import { convertChunkToWasm, getBlockMeta, getGlobalPaletteBits, type ChunkConversionResult } from '../bridge/convertChunk'
4
4
  import { extractColumnHeightmap, splitColumnWasmOutputToSections } from '../bridge/render-from-wasm'
5
5
  import { createGeometryIndexArray } from '../bridge/geometryIndices'
6
6
  import { setBlockStatesData as setMesherData } from '../../mesher-shared/models'
@@ -402,13 +402,10 @@ const convertRawMapChunkToWasm = (raw: RawMapChunkEntry, version: string): Chunk
402
402
  // 1.18 introduced the new chunk format; on earlier protocols the packet
403
403
  // shape differs and our parser would throw. Fall back to the JS path.
404
404
  if (raw.protocol < 757) return null
405
- // max_bits_per_block / max_bits_per_biome match the parity-tested defaults
406
- // in `wasm-mesher/src/parser_v18plus.rs` (see Stage 2 fixtures).
407
- const MAX_BITS_PER_BLOCK = 8
408
- const MAX_BITS_PER_BIOME = 3
405
+ const globalBits = getGlobalPaletteBits(version)
409
406
  let parsed: any
410
407
  try {
411
- parsed = (wasm as any).parseMapChunkV18Plus(raw.rawPacket, raw.numSections, MAX_BITS_PER_BLOCK, MAX_BITS_PER_BIOME, raw.protocol)
408
+ parsed = (wasm as any).parseMapChunkV18Plus(raw.rawPacket, raw.numSections, globalBits.blocks, globalBits.biomes, raw.protocol)
412
409
  } catch (err) {
413
410
  console.warn('[WASM Mesher] parseMapChunkV18Plus failed, falling back:', err)
414
411
  return null
@@ -541,9 +538,6 @@ const convertParsedV16ToWasm = (entry: ParsedV16Entry, lightEntry: UpdateLightV1
541
538
  // Fused parse+mesh helpers (single WASM call, no typed arrays in JS heap)
542
539
  // ---------------------------------------------------------------------------
543
540
 
544
- const MAX_BITS_PER_BLOCK = 8
545
- const MAX_BITS_PER_BIOME = 3
546
-
547
541
  /// Fused single-column mesh for 1.18+ raw map_chunk.
548
542
  /// Returns the GeometryOutput directly (or null on failure → fallback).
549
543
  const meshColumnFromRawV18Plus = (
@@ -557,12 +551,13 @@ const meshColumnFromRawV18Plus = (
557
551
  if (!wasm || !(wasm as any).generateGeometryFromMapChunkV18Plus) return null
558
552
  if (raw.protocol < 757) return null
559
553
  const columnHeight = worldMaxY - worldMinY
554
+ const globalBits = getGlobalPaletteBits(version)
560
555
  try {
561
556
  return (wasm as any).generateGeometryFromMapChunkV18Plus(
562
557
  raw.rawPacket,
563
558
  raw.numSections,
564
- MAX_BITS_PER_BLOCK,
565
- MAX_BITS_PER_BIOME,
559
+ globalBits.blocks,
560
+ globalBits.biomes,
566
561
  raw.protocol,
567
562
  x,
568
563
  worldMinY,
@@ -670,12 +665,13 @@ const meshMultiColumnsFromRawV18Plus = (
670
665
  }
671
666
 
672
667
  const columnHeight = worldMaxY - worldMinY
668
+ const globalBits = getGlobalPaletteBits(version)
673
669
  try {
674
670
  return (wasm as any).generateGeometryFromMapChunkV18PlusMulti(
675
671
  rawPackets,
676
672
  numSectionsList,
677
- MAX_BITS_PER_BLOCK,
678
- MAX_BITS_PER_BIOME,
673
+ globalBits.blocks,
674
+ globalBits.biomes,
679
675
  protocol,
680
676
  chunkXs,
681
677
  chunkZs,