minecraft-renderer 0.1.106 → 0.1.108
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/dist/lightOwnerWorker.js +9 -0
- package/dist/lightOwnerWorker.js.map +7 -0
- package/dist/mesher.js +1 -1
- package/dist/mesher.js.map +2 -2
- package/dist/mesherWasm.js +222 -222
- package/dist/minecraft-renderer.js +105 -76
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +537 -508
- package/package.json +1 -1
- package/src/bundler/bundlePrepare.test.ts +25 -0
- package/src/bundler/bundlePrepare.ts +1 -1
- package/src/graphicsBackend/appViewer.ts +15 -0
- package/src/graphicsBackend/config.ts +8 -0
- package/src/graphicsBackend/rendererDefaultOptions.test.ts +51 -0
- package/src/graphicsBackend/rendererDefaultOptions.ts +18 -1
- package/src/graphicsBackend/rendererOptionsSync.ts +4 -3
- package/src/lib/blockEntityLightRegistry.ts +4 -0
- package/src/lib/skyLight.ts +17 -0
- package/src/lib/worldrendererCommon.lightOwner.test.ts +916 -0
- package/src/lib/worldrendererCommon.removeColumn.test.ts +16 -0
- package/src/lib/worldrendererCommon.ts +340 -67
- package/src/lighting/cache/rendererLightCache.test.ts +158 -0
- package/src/lighting/cache/rendererLightCache.ts +248 -0
- package/src/lighting/coordinator/clientLightCoordinator.test.ts +102 -0
- package/src/lighting/coordinator/clientLightCoordinator.ts +574 -0
- package/src/lighting/debug/clientLightProbe.test.ts +24 -0
- package/src/lighting/debug/clientLightProbe.ts +20 -0
- package/src/lighting/debug/clientLightTrace.test.ts +271 -0
- package/src/lighting/debug/clientLightTrace.ts +335 -0
- package/src/lighting/entity/entityLightController.test.ts +216 -0
- package/src/lighting/entity/entityLightController.ts +322 -0
- package/src/lighting/owner/clientLightOwner.test.ts +467 -0
- package/src/lighting/owner/clientLightOwner.ts +523 -0
- package/src/lighting/owner/lightOwnerHost.test.ts +78 -0
- package/src/lighting/owner/lightOwnerHost.ts +202 -0
- package/src/lighting/owner/lightOwnerWorker.ts +154 -0
- package/src/lighting/owner/updateLight1171OwnerPath.test.ts +68 -0
- package/src/lighting/owner/updateLightToOwnerEvents.ts +49 -0
- package/src/lighting/tables/lightTables1171.generated.json +10 -0
- package/src/lighting/tables/lightTables1171.test.ts +168 -0
- package/src/lighting/tables/lightTables1171.ts +59 -0
- package/src/lighting/versioning/clientLightVersions.test.ts +241 -0
- package/src/lighting/versioning/clientLightVersions.ts +203 -0
- package/src/lighting/versioning/pendingSectionFlush.test.ts +214 -0
- package/src/lighting/versioning/pendingSectionFlush.ts +123 -0
- package/src/mesher-shared/lightNibblePack.ts +26 -0
- package/src/mesher-shared/mcDataWorkerShim.cjs +9 -7
- package/src/mesher-shared/modelsGeometryCommon.test.ts +6 -1
- package/src/mesher-shared/shared.ts +48 -1
- package/src/three/chunkMeshManager.ts +26 -0
- package/src/three/entities.ts +97 -8
- package/src/three/entity/EntityMesh.ts +1 -0
- package/src/three/entity/legWalkAnimation.test.ts +154 -0
- package/src/three/entity/legWalkAnimation.ts +193 -0
- package/src/three/globalBlockBuffer.ts +94 -2
- package/src/three/globalLegacyBuffer.ts +94 -2
- package/src/three/graphicsBackendBase.ts +2 -9
- package/src/three/modules/clouds.ts +191 -0
- package/src/three/modules/index.ts +4 -0
- package/src/three/modules/sunMoon.ts +151 -0
- package/src/three/skyboxRenderer.ts +15 -8
- package/src/three/tests/clouds.test.ts +59 -0
- package/src/three/tests/globalBlockBuffer.test.ts +98 -0
- package/src/three/tests/globalLegacyBuffer.test.ts +108 -0
- package/src/three/tests/waterFog.test.ts +25 -0
- package/src/three/waterFog.ts +28 -0
- package/src/three/worldRendererThree.ts +84 -39
- package/src/wasm-mesher/bridge/render-from-wasm.ts +46 -1
- package/src/wasm-mesher/runtime-build/wasm_mesher.d.ts +31 -5
- package/src/wasm-mesher/runtime-build/wasm_mesher.js +180 -5
- package/src/wasm-mesher/runtime-build/wasm_mesher_bg.wasm +0 -0
- package/src/wasm-mesher/runtime-build/wasm_mesher_bg.wasm.d.ts +11 -0
- package/src/wasm-mesher/tests/mesherLightDependencies.test.ts +83 -0
- package/src/wasm-mesher/tests/mesherWasmColumnTickSelect.test.ts +39 -0
- package/src/wasm-mesher/tests/mesherWasmConversionCache.test.ts +30 -1
- package/src/wasm-mesher/tests/mesherWasmTickParseCache.test.ts +73 -0
- package/src/wasm-mesher/tests/mesherWasmTickSchedule.test.ts +51 -0
- package/src/wasm-mesher/tests/mesherWasmTopologyPostCache.test.ts +78 -0
- package/src/wasm-mesher/worker/clientLightDarkPublication.probe.test.ts +32 -0
- package/src/wasm-mesher/worker/mesherLightDependencies.ts +43 -0
- package/src/wasm-mesher/worker/mesherWasm.ownerLightSync.test.ts +198 -0
- package/src/wasm-mesher/worker/mesherWasm.ts +506 -96
- package/src/wasm-mesher/worker/mesherWasmChunkBuffer.ts +1 -0
- package/src/wasm-mesher/worker/mesherWasmColumnTickSelect.ts +47 -0
- package/src/wasm-mesher/worker/mesherWasmConversionCache.ts +16 -5
- package/src/wasm-mesher/worker/mesherWasmLightDirty.test.ts +17 -1
- package/src/wasm-mesher/worker/mesherWasmLightDirty.ts +10 -0
- package/src/wasm-mesher/worker/mesherWasmLightMerge.test.ts +208 -0
- package/src/wasm-mesher/worker/mesherWasmLightMerge.ts +228 -0
- package/src/wasm-mesher/worker/mesherWasmOwnerLight.test.ts +161 -0
- package/src/wasm-mesher/worker/mesherWasmOwnerLight.ts +112 -0
- package/src/wasm-mesher/worker/mesherWasmTickParseCache.ts +45 -0
- package/src/wasm-mesher/worker/mesherWasmTickSchedule.ts +44 -0
- package/src/wasm-mesher/worker/mesherWasmTopologyPostCache.ts +63 -0
- package/src/worldView/types.ts +3 -0
- package/src/worldView/worldView.geometryReload.test.ts +68 -0
- package/src/worldView/worldView.spiral.test.ts +1 -1
- package/src/worldView/worldView.ts +49 -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
|
+
}
|