minecraft-renderer 0.1.107 → 0.1.109

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 (95) hide show
  1. package/dist/lightOwnerWorker.js +9 -0
  2. package/dist/lightOwnerWorker.js.map +7 -0
  3. package/dist/mesher.js +1 -1
  4. package/dist/mesher.js.map +2 -2
  5. package/dist/mesherWasm.js +222 -222
  6. package/dist/minecraft-renderer.js +84 -84
  7. package/dist/minecraft-renderer.js.meta.json +1 -1
  8. package/dist/threeWorker.js +517 -517
  9. package/package.json +1 -1
  10. package/src/bundler/bundlePrepare.test.ts +25 -0
  11. package/src/bundler/bundlePrepare.ts +1 -1
  12. package/src/graphicsBackend/appViewer.ts +15 -0
  13. package/src/graphicsBackend/config.ts +5 -0
  14. package/src/graphicsBackend/rendererDefaultOptions.test.ts +51 -0
  15. package/src/graphicsBackend/rendererDefaultOptions.ts +13 -1
  16. package/src/graphicsBackend/rendererOptionsSync.ts +3 -3
  17. package/src/lib/blockEntityLightRegistry.ts +4 -0
  18. package/src/lib/worldrendererCommon.lightOwner.test.ts +916 -0
  19. package/src/lib/worldrendererCommon.removeColumn.test.ts +16 -0
  20. package/src/lib/worldrendererCommon.ts +329 -67
  21. package/src/lighting/cache/rendererLightCache.test.ts +158 -0
  22. package/src/lighting/cache/rendererLightCache.ts +248 -0
  23. package/src/lighting/coordinator/clientLightCoordinator.test.ts +102 -0
  24. package/src/lighting/coordinator/clientLightCoordinator.ts +574 -0
  25. package/src/lighting/debug/clientLightProbe.test.ts +24 -0
  26. package/src/lighting/debug/clientLightProbe.ts +20 -0
  27. package/src/lighting/debug/clientLightTrace.test.ts +271 -0
  28. package/src/lighting/debug/clientLightTrace.ts +335 -0
  29. package/src/lighting/entity/entityLightController.test.ts +216 -0
  30. package/src/lighting/entity/entityLightController.ts +322 -0
  31. package/src/lighting/owner/clientLightOwner.test.ts +467 -0
  32. package/src/lighting/owner/clientLightOwner.ts +523 -0
  33. package/src/lighting/owner/lightOwnerHost.test.ts +78 -0
  34. package/src/lighting/owner/lightOwnerHost.ts +202 -0
  35. package/src/lighting/owner/lightOwnerWorker.ts +154 -0
  36. package/src/lighting/owner/updateLight1171OwnerPath.test.ts +68 -0
  37. package/src/lighting/owner/updateLightToOwnerEvents.ts +49 -0
  38. package/src/lighting/tables/lightTables1171.generated.json +10 -0
  39. package/src/lighting/tables/lightTables1171.test.ts +168 -0
  40. package/src/lighting/tables/lightTables1171.ts +59 -0
  41. package/src/lighting/versioning/clientLightVersions.test.ts +241 -0
  42. package/src/lighting/versioning/clientLightVersions.ts +203 -0
  43. package/src/lighting/versioning/pendingSectionFlush.test.ts +214 -0
  44. package/src/lighting/versioning/pendingSectionFlush.ts +123 -0
  45. package/src/mesher-shared/lightNibblePack.ts +26 -0
  46. package/src/mesher-shared/mcDataWorkerShim.cjs +9 -7
  47. package/src/mesher-shared/modelsGeometryCommon.test.ts +6 -1
  48. package/src/mesher-shared/shared.ts +48 -1
  49. package/src/three/chunkMeshManager.ts +26 -0
  50. package/src/three/cloudCover.ts +22 -0
  51. package/src/three/cloudGeometry.ts +71 -0
  52. package/src/three/entities.ts +77 -11
  53. package/src/three/entity/EntityMesh.ts +1 -0
  54. package/src/three/entity/legWalkAnimation.test.ts +11 -2
  55. package/src/three/entity/legWalkAnimation.ts +1 -9
  56. package/src/three/globalBlockBuffer.ts +94 -2
  57. package/src/three/globalLegacyBuffer.ts +94 -2
  58. package/src/three/graphicsBackendBase.ts +2 -9
  59. package/src/three/modules/clouds.ts +98 -75
  60. package/src/three/tests/cloudGeometry.test.ts +87 -0
  61. package/src/three/tests/clouds.test.ts +65 -14
  62. package/src/three/tests/globalBlockBuffer.test.ts +98 -0
  63. package/src/three/tests/globalLegacyBuffer.test.ts +108 -0
  64. package/src/three/worldRendererThree.ts +75 -34
  65. package/src/wasm-mesher/bridge/render-from-wasm.ts +46 -1
  66. package/src/wasm-mesher/runtime-build/wasm_mesher.d.ts +31 -5
  67. package/src/wasm-mesher/runtime-build/wasm_mesher.js +180 -5
  68. package/src/wasm-mesher/runtime-build/wasm_mesher_bg.wasm +0 -0
  69. package/src/wasm-mesher/runtime-build/wasm_mesher_bg.wasm.d.ts +11 -0
  70. package/src/wasm-mesher/tests/mesherLightDependencies.test.ts +83 -0
  71. package/src/wasm-mesher/tests/mesherWasmColumnTickSelect.test.ts +39 -0
  72. package/src/wasm-mesher/tests/mesherWasmConversionCache.test.ts +30 -1
  73. package/src/wasm-mesher/tests/mesherWasmTickParseCache.test.ts +73 -0
  74. package/src/wasm-mesher/tests/mesherWasmTickSchedule.test.ts +51 -0
  75. package/src/wasm-mesher/tests/mesherWasmTopologyPostCache.test.ts +78 -0
  76. package/src/wasm-mesher/worker/clientLightDarkPublication.probe.test.ts +32 -0
  77. package/src/wasm-mesher/worker/mesherLightDependencies.ts +43 -0
  78. package/src/wasm-mesher/worker/mesherWasm.ownerLightSync.test.ts +198 -0
  79. package/src/wasm-mesher/worker/mesherWasm.ts +506 -96
  80. package/src/wasm-mesher/worker/mesherWasmChunkBuffer.ts +1 -0
  81. package/src/wasm-mesher/worker/mesherWasmColumnTickSelect.ts +47 -0
  82. package/src/wasm-mesher/worker/mesherWasmConversionCache.ts +16 -5
  83. package/src/wasm-mesher/worker/mesherWasmLightDirty.test.ts +17 -1
  84. package/src/wasm-mesher/worker/mesherWasmLightDirty.ts +10 -0
  85. package/src/wasm-mesher/worker/mesherWasmLightMerge.test.ts +208 -0
  86. package/src/wasm-mesher/worker/mesherWasmLightMerge.ts +228 -0
  87. package/src/wasm-mesher/worker/mesherWasmOwnerLight.test.ts +161 -0
  88. package/src/wasm-mesher/worker/mesherWasmOwnerLight.ts +112 -0
  89. package/src/wasm-mesher/worker/mesherWasmTickParseCache.ts +45 -0
  90. package/src/wasm-mesher/worker/mesherWasmTickSchedule.ts +44 -0
  91. package/src/wasm-mesher/worker/mesherWasmTopologyPostCache.ts +63 -0
  92. package/src/worldView/types.ts +1 -0
  93. package/src/worldView/worldView.geometryReload.test.ts +68 -0
  94. package/src/worldView/worldView.spiral.test.ts +1 -1
  95. package/src/worldView/worldView.ts +46 -1
@@ -0,0 +1,202 @@
1
+ //@ts-nocheck
2
+ import { RendererLightCache } from '../cache/rendererLightCache'
3
+
4
+ export type LightPublicationSection = {
5
+ sx: number
6
+ sy: number
7
+ sz: number
8
+ blockLight: Uint8Array
9
+ skyLight?: Uint8Array
10
+ }
11
+
12
+ export type LightPublication = {
13
+ worldGeneration: number
14
+ publicationVersion: number
15
+ sections: LightPublicationSection[]
16
+ /** Flat world-origin triples, independent of the section payload coordinates. */
17
+ dirtyMeshSections?: Int32Array
18
+ }
19
+
20
+ export type LightOwnerEvent =
21
+ | { type: 'ingestBlockSection'; sx: number; sy: number; sz: number; states: Uint16Array }
22
+ | { type: 'setAvailability'; sx: number; sy: number; sz: number; availability: 'loaded' | 'lightOnly' | 'unloaded' }
23
+ | {
24
+ type: 'serverLight'
25
+ sx: number
26
+ sy: number
27
+ sz: number
28
+ channel: 'block' | 'sky'
29
+ kind: 'omitted' | 'empty' | 'data'
30
+ trusted?: boolean
31
+ data?: Uint8Array
32
+ }
33
+ | { type: 'blockChange'; x: number; y: number; z: number; stateId: number }
34
+ | { type: 'unloadColumn'; sx: number; sz: number }
35
+
36
+ export type LightEngineBackend = {
37
+ setLightTables(tables: { emission: Uint8Array; opacity: Uint8Array; occupancy?: Uint8Array }): void
38
+ setSkyLightEnabled?(enabled: boolean): void
39
+ pushEvent(event: LightOwnerEvent): void
40
+ step(budgetMs: number): boolean
41
+ pollCompletedPublication(): LightPublication | null
42
+ }
43
+
44
+ export type PublicationGate = {
45
+ acceptedGeneration: number
46
+ lastVersion: number
47
+ }
48
+
49
+ export type ApplyPublicationResult = { applied: boolean; lastVersion: number; acceptedGeneration: number }
50
+
51
+ /** Engine ABI uses section indices; RendererLightCache keys are world origins. */
52
+ export function sectionIndexToWorldOrigin(sx: number, sy: number, sz: number) {
53
+ return { sx: sx * 16, sy: sy * 16, sz: sz * 16 }
54
+ }
55
+
56
+ export function applyLightPublication(
57
+ cache: RendererLightCache,
58
+ publication: LightPublication,
59
+ gate: PublicationGate,
60
+ coords: 'world' | 'section-index' = 'world'
61
+ ): ApplyPublicationResult {
62
+ if (publication.worldGeneration < gate.acceptedGeneration) {
63
+ return { applied: false, lastVersion: gate.lastVersion, acceptedGeneration: gate.acceptedGeneration }
64
+ }
65
+ if (publication.worldGeneration === gate.acceptedGeneration && publication.publicationVersion <= gate.lastVersion) {
66
+ return { applied: false, lastVersion: gate.lastVersion, acceptedGeneration: gate.acceptedGeneration }
67
+ }
68
+ const sections = publication.sections.map(section => {
69
+ const origin = coords === 'section-index' ? sectionIndexToWorldOrigin(section.sx, section.sy, section.sz) : section
70
+ return {
71
+ sx: origin.sx,
72
+ sy: origin.sy,
73
+ sz: origin.sz,
74
+ blockLight: section.blockLight,
75
+ skyLight: section.skyLight
76
+ }
77
+ })
78
+ cache.ingestPackedSections(sections)
79
+ return { applied: true, lastVersion: publication.publicationVersion, acceptedGeneration: publication.worldGeneration }
80
+ }
81
+
82
+ /** Production spawn stays behind `enableClientLightOwner` (default off). */
83
+ export const CLIENT_LIGHT_OWNER_FLAG = 'enableClientLightOwner' as const
84
+
85
+ export class LightOwnerHost {
86
+ private lastVersion = 0
87
+ private generation: number
88
+
89
+ private constructor(
90
+ private readonly cache: RendererLightCache,
91
+ private readonly backend: LightEngineBackend,
92
+ opts: { worldMinY: number; worldHeight: number; generation?: number }
93
+ ) {
94
+ this.generation = opts.generation ?? 1
95
+ this.cache.setWorldBounds(opts.worldMinY, opts.worldHeight)
96
+ }
97
+
98
+ static create(cache: RendererLightCache, backend: LightEngineBackend, opts: { worldMinY: number; worldHeight: number; generation?: number }) {
99
+ return new LightOwnerHost(cache, backend, opts)
100
+ }
101
+
102
+ static async createInProcess(cache: RendererLightCache, opts: { worldMinY: number; worldHeight: number; generation?: number }) {
103
+ const backend = await createWasmLightBackend(opts.worldMinY, opts.worldHeight)
104
+ return new LightOwnerHost(cache, backend, opts)
105
+ }
106
+
107
+ setLightTables(tables: { emission: Uint8Array; opacity: Uint8Array; occupancy?: Uint8Array }) {
108
+ this.backend.setLightTables(tables)
109
+ }
110
+
111
+ setSkyLightEnabled(enabled: boolean) {
112
+ this.backend.setSkyLightEnabled?.(enabled)
113
+ }
114
+
115
+ pushEvent(event: LightOwnerEvent) {
116
+ this.backend.pushEvent(event)
117
+ }
118
+
119
+ async stepUntilIdle(maxSlices = 32, budgetMs = 16): Promise<LightPublication | undefined> {
120
+ let last: LightPublication | undefined
121
+ for (let i = 0; i < maxSlices; i++) {
122
+ const remaining = this.backend.step(budgetMs)
123
+ const publication = this.backend.pollCompletedPublication()
124
+ if (publication) {
125
+ const result = applyLightPublication(this.cache, publication, { acceptedGeneration: this.generation, lastVersion: this.lastVersion }, 'section-index')
126
+ if (result.applied) {
127
+ this.lastVersion = result.lastVersion
128
+ this.generation = result.acceptedGeneration
129
+ last = publication
130
+ }
131
+ }
132
+ if (!remaining) break
133
+ }
134
+ return last
135
+ }
136
+ }
137
+
138
+ async function createWasmLightBackend(worldMinY: number, worldHeight: number): Promise<LightEngineBackend> {
139
+ const { readFileSync } = await import('node:fs')
140
+ const { dirname, join } = await import('node:path')
141
+ const { fileURLToPath } = await import('node:url')
142
+ const wasmModule = await import('../../wasm-mesher/runtime-build/wasm_mesher.js')
143
+ const wasmDir = dirname(fileURLToPath(import.meta.url))
144
+ const wasmBytes = readFileSync(join(wasmDir, '../../wasm-mesher/runtime-build/wasm_mesher_bg.wasm'))
145
+ try {
146
+ if (typeof wasmModule.initSync === 'function') {
147
+ wasmModule.initSync(wasmBytes)
148
+ } else {
149
+ await wasmModule.default(wasmBytes)
150
+ }
151
+ } catch {
152
+ // wasm already initialized in this vitest worker
153
+ }
154
+ const Engine = (wasmModule as { JsLightEngine?: new (minY: number, height: number) => WasmEngine }).JsLightEngine
155
+ if (!Engine) {
156
+ throw new Error('JsLightEngine is not exported from wasm_mesher')
157
+ }
158
+ const engine = new Engine(worldMinY, worldHeight)
159
+ return {
160
+ setLightTables({ emission, opacity, occupancy }) {
161
+ engine.setLightTables(emission, opacity)
162
+ if (occupancy && engine.setOcclusionTable) engine.setOcclusionTable(occupancy)
163
+ },
164
+ setSkyLightEnabled(enabled) {
165
+ engine.setSkyLightEnabled?.(enabled)
166
+ },
167
+ pushEvent(event) {
168
+ engine.pushEvent(event)
169
+ },
170
+ step(budgetMs) {
171
+ return engine.step(budgetMs)
172
+ },
173
+ pollCompletedPublication() {
174
+ const raw = engine.pollCompletedPublication()
175
+ if (!raw) return null
176
+ return {
177
+ worldGeneration: raw.worldGeneration,
178
+ publicationVersion: raw.publicationVersion,
179
+ sections: raw.sections.map((section: { sx: number; sy: number; sz: number; blockLight: Uint8Array; skyLight?: Uint8Array }) => ({
180
+ sx: section.sx,
181
+ sy: section.sy,
182
+ sz: section.sz,
183
+ blockLight: section.blockLight,
184
+ skyLight: section.skyLight
185
+ }))
186
+ }
187
+ }
188
+ }
189
+ }
190
+
191
+ type WasmEngine = {
192
+ setLightTables(emission: Uint8Array, opacity: Uint8Array): void
193
+ setOcclusionTable?(occupancy: Uint8Array): void
194
+ setSkyLightEnabled?(enabled: boolean): void
195
+ pushEvent(event: LightOwnerEvent): void
196
+ step(budgetMs: number): boolean
197
+ pollCompletedPublication(): null | {
198
+ worldGeneration: number
199
+ publicationVersion: number
200
+ sections: Array<{ sx: number; sy: number; sz: number; blockLight: Uint8Array; skyLight?: Uint8Array }>
201
+ }
202
+ }
@@ -0,0 +1,154 @@
1
+ //@ts-nocheck
2
+ /**
3
+ * Dedicated light-owner worker. Mesh workers stay read-only.
4
+ * Protocol: init / setLightTables / setSkyLightEnabled / pushEvent /
5
+ * setUpdateLightV17 / setUpdateLightV16 / step / poll.
6
+ * Messages arriving before init completes are queued.
7
+ */
8
+
9
+ import type { LightOwnerEvent, LightPublication } from './lightOwnerHost'
10
+ import { eventsFromWasmUpdateLight } from './updateLightToOwnerEvents'
11
+ import {
12
+ CLIENT_LIGHT_TRACE_MESSAGE,
13
+ armClientLightTrace,
14
+ enableClientLightTrace,
15
+ isClientLightTraceArmed,
16
+ isClientLightTraceEnabled,
17
+ postClientLightTrace
18
+ } from '../debug/clientLightTrace'
19
+
20
+ type WasmEngine = {
21
+ readonly fallbackRelights?: number
22
+ setLightTables(emission: Uint8Array, opacity: Uint8Array): void
23
+ setOcclusionTable?(occupancy: Uint8Array): void
24
+ setSkyLightEnabled?(enabled: boolean): void
25
+ pushEvent(event: LightOwnerEvent): void
26
+ step(budgetMs: number): boolean
27
+ pollCompletedPublication(): LightPublication | null
28
+ }
29
+
30
+ let engine: WasmEngine | null = null
31
+ let wasm: any = null
32
+ let worldMinY = 0
33
+ const pending: any[] = []
34
+ let localContinue: ReturnType<typeof setTimeout> | null = null
35
+ let stepGeneration = 0
36
+
37
+ const ctx = self as unknown as DedicatedWorkerGlobalScope
38
+ const postTrace = (message: { type: typeof CLIENT_LIGHT_TRACE_MESSAGE; events?: unknown; event?: unknown }) => {
39
+ ctx.postMessage(message)
40
+ }
41
+
42
+ function runOwnerStepSlice(budgetMs: number) {
43
+ localContinue = null
44
+ const remaining = engine?.step(budgetMs) ?? false
45
+ const publication = engine?.pollCompletedPublication() ?? null
46
+ ctx.postMessage({ type: 'stepped', remaining, publication, stepGeneration, fallbackRelights: engine?.fallbackRelights ?? 0 })
47
+ if (publication && isClientLightTraceEnabled() && isClientLightTraceArmed()) {
48
+ postClientLightTrace(postTrace, {
49
+ phase: 'ownerComplete',
50
+ lightVersion: publication.publicationVersion,
51
+ worldGeneration: publication.worldGeneration,
52
+ queueDepth: remaining ? 1 : 0,
53
+ fallbackRelights: engine?.fallbackRelights ?? 0
54
+ })
55
+ }
56
+ if (remaining) {
57
+ localContinue = setTimeout(() => {
58
+ runOwnerStepSlice(budgetMs)
59
+ }, 0)
60
+ }
61
+ }
62
+
63
+ async function handle(data: any) {
64
+ if (!data || typeof data !== 'object') return
65
+ switch (data.type) {
66
+ case 'init': {
67
+ wasm = await import('../../wasm-mesher/runtime-build/wasm_mesher.js')
68
+ await wasm.default('/wasm_mesher_bg.wasm')
69
+ const Engine = (wasm as unknown as { JsLightEngine: new (minY: number, height: number) => WasmEngine }).JsLightEngine
70
+ worldMinY = data.worldMinY ?? 0
71
+ engine = new Engine(worldMinY, data.worldHeight ?? 256)
72
+ ctx.postMessage({ type: 'ready' })
73
+ const queued = pending.splice(0, pending.length)
74
+ for (const message of queued) await handle(message)
75
+ break
76
+ }
77
+ case 'setLightTables': {
78
+ engine?.setLightTables(data.emission, data.opacity)
79
+ if (data.occupancy) engine?.setOcclusionTable?.(data.occupancy)
80
+ ctx.postMessage({ type: 'tablesSet' })
81
+ break
82
+ }
83
+ case 'setSkyLightEnabled': {
84
+ engine?.setSkyLightEnabled?.(Boolean(data.enabled))
85
+ break
86
+ }
87
+ case 'pushEvent': {
88
+ engine?.pushEvent(data.event)
89
+ const event = data.event as LightOwnerEvent | undefined
90
+ const currentColumn =
91
+ event && 'x' in event && typeof event.x === 'number'
92
+ ? `${Math.floor(event.x / 16)},${Math.floor((event as { z: number }).z / 16)}`
93
+ : event && 'sx' in event && typeof event.sx === 'number'
94
+ ? `${event.sx},${(event as { sz: number }).sz}`
95
+ : undefined
96
+ if (isClientLightTraceEnabled() && isClientLightTraceArmed()) {
97
+ postClientLightTrace(postTrace, {
98
+ phase: 'ownerAdmit',
99
+ currentColumn,
100
+ queueDepth: pending.length
101
+ })
102
+ }
103
+ break
104
+ }
105
+ case 'clientLightTraceConfig': {
106
+ enableClientLightTrace(Boolean(data.enabled))
107
+ armClientLightTrace(Boolean(data.armed))
108
+ break
109
+ }
110
+ case 'setUpdateLightV17': {
111
+ if (!wasm || !engine) break
112
+ const parsed = wasm.parseUpdateLightV17(data.rawPacket as Uint8Array, data.numSections as number)
113
+ for (const event of eventsFromWasmUpdateLight(parsed, data.numSections as number, worldMinY)) {
114
+ engine.pushEvent(event)
115
+ }
116
+ break
117
+ }
118
+ case 'setUpdateLightV16': {
119
+ if (!wasm || !engine) break
120
+ const parsed = wasm.parseUpdateLightV17(data.rawPacket as Uint8Array, 16)
121
+ for (const event of eventsFromWasmUpdateLight(parsed, 16, worldMinY)) {
122
+ engine.pushEvent(event)
123
+ }
124
+ break
125
+ }
126
+ case 'step': {
127
+ if (typeof data.stepGeneration === 'number') stepGeneration = data.stepGeneration
128
+ if (localContinue) break
129
+ runOwnerStepSlice(data.budgetMs ?? 5)
130
+ break
131
+ }
132
+ case 'poll': {
133
+ const publication = engine?.pollCompletedPublication() ?? null
134
+ ctx.postMessage({ type: 'publication', publication })
135
+ break
136
+ }
137
+ default:
138
+ break
139
+ }
140
+ }
141
+
142
+ ctx.onmessage = async (message: MessageEvent) => {
143
+ const data = message.data
144
+ if (!data || typeof data !== 'object') return
145
+ try {
146
+ if (!engine && data.type !== 'init') {
147
+ pending.push(data)
148
+ return
149
+ }
150
+ await handle(data)
151
+ } catch (err) {
152
+ ctx.postMessage({ type: 'error', error: err instanceof Error ? err.message : String(err) })
153
+ }
154
+ }
@@ -0,0 +1,68 @@
1
+ //@ts-nocheck
2
+ import { readFileSync } from 'node:fs'
3
+ import { dirname, join } from 'node:path'
4
+ import { fileURLToPath } from 'node:url'
5
+ import { describe, expect, it } from 'vitest'
6
+ import { RendererLightCache } from '../cache/rendererLightCache'
7
+ import { applyLightPublication } from './lightOwnerHost'
8
+ import { isLightSectionPresent, mergeUpdateLight, parsedUpdateLightFromWasm, worldSectionMaskBit } from '../../wasm-mesher/worker/mesherWasmLightMerge'
9
+
10
+ const NUM_SECTIONS = 16
11
+
12
+ function packedNibble(value: number): Uint8Array {
13
+ const n = value & 0x0f
14
+ return new Uint8Array(2048).fill(n | (n << 4))
15
+ }
16
+
17
+ describe('1.17.1 fixture owner path (scenario б)', () => {
18
+ it('keeps an omitted neighbor after the captured empty-block packet', async () => {
19
+ const wasmDir = dirname(fileURLToPath(import.meta.url))
20
+ const wasmModule = await import('../../wasm-mesher/runtime-build/wasm_mesher.js')
21
+ const wasmBytes = readFileSync(join(wasmDir, '../../wasm-mesher/runtime-build/wasm_mesher_bg.wasm'))
22
+ try {
23
+ wasmModule.initSync(wasmBytes)
24
+ } catch {
25
+ // already initialized
26
+ }
27
+
28
+ const fixture = readFileSync(join(wasmDir, '../../../chunk-packet-fixtures/fixtures/map_chunk/1.17.1/5_0.update_light.bin'))
29
+ const parsed = parsedUpdateLightFromWasm((wasmModule as any).parseUpdateLightV17(fixture, NUM_SECTIONS), NUM_SECTIONS)
30
+ expect(parsed.x).toBe(7)
31
+ expect(parsed.z).toBe(0)
32
+
33
+ const seed = mergeUpdateLight(undefined, {
34
+ ...parsed,
35
+ blockLightMask: new Uint32Array([1 << 7, 0]),
36
+ emptyBlockLightMask: new Uint32Array(2),
37
+ blockLight: (() => {
38
+ const column = new Uint8Array(NUM_SECTIONS * 4096)
39
+ column.fill(7, 6 * 4096, 7 * 4096)
40
+ return column
41
+ })()
42
+ })
43
+ expect(isLightSectionPresent(seed.blockPresent, worldSectionMaskBit(6))).toBe(true)
44
+
45
+ const merged = mergeUpdateLight(seed, parsed)
46
+ expect(isLightSectionPresent(merged.blockPresent, worldSectionMaskBit(6))).toBe(true)
47
+ expect(merged.blockLight[6 * 4096]).toBe(7)
48
+ expect(isLightSectionPresent(merged.blockPresent, worldSectionMaskBit(0))).toBe(true)
49
+ expect(merged.blockLight[0]).toBe(0)
50
+
51
+ const cache = new RendererLightCache('1.17.1')
52
+ cache.setWorldBounds(0, 256)
53
+ applyLightPublication(
54
+ cache,
55
+ {
56
+ worldGeneration: 1,
57
+ publicationVersion: 1,
58
+ sections: [
59
+ { sx: 112, sy: 96, sz: 0, blockLight: packedNibble(7) },
60
+ { sx: 112, sy: 0, sz: 0, blockLight: packedNibble(0) }
61
+ ]
62
+ },
63
+ { acceptedGeneration: 1, lastVersion: 0 }
64
+ )
65
+ expect(cache.getLight(112, 96, 0).block).toBeCloseTo(9 / 15, 5)
66
+ expect(cache.getLight(112, 0, 0).block).toBeCloseTo(2 / 15, 5)
67
+ })
68
+ })
@@ -0,0 +1,49 @@
1
+ //@ts-nocheck
2
+ import { maskBitGet, parsedUpdateLightFromWasm, type ParsedUpdateLight } from '../../wasm-mesher/worker/mesherWasmLightMerge'
3
+ import { packUnpackedLightSection } from '../../mesher-shared/lightNibblePack'
4
+ import type { LightOwnerEvent } from './lightOwnerHost'
5
+
6
+ export function eventsFromParsedUpdateLight(parsed: ParsedUpdateLight, worldMinY: number): LightOwnerEvent[] {
7
+ const sx = parsed.x
8
+ const sz = parsed.z
9
+ const baseSy = Math.floor(worldMinY / 16)
10
+ const events: LightOwnerEvent[] = []
11
+ const pushChannel = (channel: 'block' | 'sky', dataMask: Uint32Array, emptyMask: Uint32Array, column: Uint8Array, below?: Uint8Array, above?: Uint8Array) => {
12
+ const totalBits = parsed.numSections + 2
13
+ for (let bit = 0; bit < totalBits; bit++) {
14
+ const hasData = maskBitGet(dataMask, bit)
15
+ const hasEmpty = maskBitGet(emptyMask, bit)
16
+ if (!hasData && !hasEmpty) continue
17
+ const sy = baseSy + bit - 1
18
+ if (hasEmpty) {
19
+ events.push({ type: 'serverLight', sx, sy, sz, channel, kind: 'empty', trusted: parsed.trustEdges })
20
+ continue
21
+ }
22
+ let unpacked: Uint8Array | undefined
23
+ if (bit === 0) unpacked = below
24
+ else if (bit === parsed.numSections + 1) unpacked = above
25
+ else unpacked = column.subarray((bit - 1) * 4096, bit * 4096)
26
+ if (!unpacked || unpacked.length < 4096) {
27
+ events.push({ type: 'serverLight', sx, sy, sz, channel, kind: 'empty', trusted: parsed.trustEdges })
28
+ continue
29
+ }
30
+ events.push({
31
+ type: 'serverLight',
32
+ sx,
33
+ sy,
34
+ sz,
35
+ channel,
36
+ kind: 'data',
37
+ trusted: parsed.trustEdges,
38
+ data: packUnpackedLightSection(unpacked)
39
+ })
40
+ }
41
+ }
42
+ pushChannel('block', parsed.blockLightMask, parsed.emptyBlockLightMask, parsed.blockLight, parsed.blockBelow, parsed.blockAbove)
43
+ pushChannel('sky', parsed.skyLightMask, parsed.emptySkyLightMask, parsed.skyLight, parsed.skyBelow, parsed.skyAbove)
44
+ return events
45
+ }
46
+
47
+ export function eventsFromWasmUpdateLight(parsedJs: unknown, numSections: number, worldMinY: number): LightOwnerEvent[] {
48
+ return eventsFromParsedUpdateLight(parsedUpdateLightFromWasm(parsedJs, numSections), worldMinY)
49
+ }