minecraft-renderer 0.1.0

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 (187) hide show
  1. package/README.md +297 -0
  2. package/dist/index.html +83 -0
  3. package/dist/static/image/arrow.6f27b59f.png +0 -0
  4. package/dist/static/image/blocksAtlasLatest.7850afa3.png +0 -0
  5. package/dist/static/image/blocksAtlasLegacy.5c76823d.png +0 -0
  6. package/dist/static/image/itemsAtlasLatest.36036f95.png +0 -0
  7. package/dist/static/image/itemsAtlasLegacy.dcb1b58d.png +0 -0
  8. package/dist/static/image/tipped_arrow.6f27b59f.png +0 -0
  9. package/dist/static/js/365.f05233ab.js +8462 -0
  10. package/dist/static/js/365.f05233ab.js.LICENSE.txt +52 -0
  11. package/dist/static/js/async/738.efa27644.js +1 -0
  12. package/dist/static/js/index.092ec5be.js +56 -0
  13. package/dist/static/js/lib-polyfill.98986ac5.js +1 -0
  14. package/dist/static/js/lib-react.5c9129e0.js +2 -0
  15. package/dist/static/js/lib-react.5c9129e0.js.LICENSE.txt +39 -0
  16. package/package.json +104 -0
  17. package/src/assets/destroy_stage_0.png +0 -0
  18. package/src/assets/destroy_stage_1.png +0 -0
  19. package/src/assets/destroy_stage_2.png +0 -0
  20. package/src/assets/destroy_stage_3.png +0 -0
  21. package/src/assets/destroy_stage_4.png +0 -0
  22. package/src/assets/destroy_stage_5.png +0 -0
  23. package/src/assets/destroy_stage_6.png +0 -0
  24. package/src/assets/destroy_stage_7.png +0 -0
  25. package/src/assets/destroy_stage_8.png +0 -0
  26. package/src/assets/destroy_stage_9.png +0 -0
  27. package/src/examples/README.md +146 -0
  28. package/src/examples/appViewerExample.ts +205 -0
  29. package/src/examples/initialMenuStart.ts +161 -0
  30. package/src/graphicsBackend/appViewer.ts +297 -0
  31. package/src/graphicsBackend/config.ts +119 -0
  32. package/src/graphicsBackend/index.ts +10 -0
  33. package/src/graphicsBackend/playerState.ts +61 -0
  34. package/src/graphicsBackend/types.ts +143 -0
  35. package/src/index.ts +97 -0
  36. package/src/lib/DebugGui.ts +190 -0
  37. package/src/lib/animationController.ts +85 -0
  38. package/src/lib/buildSharedConfig.mjs +1 -0
  39. package/src/lib/cameraBobbing.ts +94 -0
  40. package/src/lib/canvas2DOverlay.example.ts +361 -0
  41. package/src/lib/canvas2DOverlay.quickstart.ts +242 -0
  42. package/src/lib/canvas2DOverlay.ts +381 -0
  43. package/src/lib/cleanupDecorator.ts +29 -0
  44. package/src/lib/createPlayerObject.ts +55 -0
  45. package/src/lib/frameTimingCollector.ts +164 -0
  46. package/src/lib/guiRenderer.ts +283 -0
  47. package/src/lib/items.ts +140 -0
  48. package/src/lib/mesherlogReader.ts +131 -0
  49. package/src/lib/moreBlockDataGenerated.json +714 -0
  50. package/src/lib/preflatMap.json +1741 -0
  51. package/src/lib/simpleUtils.ts +40 -0
  52. package/src/lib/smoothSwitcher.ts +168 -0
  53. package/src/lib/spiral.ts +29 -0
  54. package/src/lib/ui/newStats.ts +120 -0
  55. package/src/lib/utils/proxy.ts +23 -0
  56. package/src/lib/utils/skins.ts +63 -0
  57. package/src/lib/utils.ts +76 -0
  58. package/src/lib/workerProxy.ts +342 -0
  59. package/src/lib/worldrendererCommon.ts +1088 -0
  60. package/src/mesher/mesher.ts +253 -0
  61. package/src/mesher/models.ts +769 -0
  62. package/src/mesher/modelsGeometryCommon.ts +142 -0
  63. package/src/mesher/shared.ts +80 -0
  64. package/src/mesher/standaloneRenderer.ts +270 -0
  65. package/src/mesher/test/a.ts +3 -0
  66. package/src/mesher/test/mesherTester.ts +76 -0
  67. package/src/mesher/test/playground.ts +19 -0
  68. package/src/mesher/test/test-perf.ts +74 -0
  69. package/src/mesher/test/tests.test.ts +56 -0
  70. package/src/mesher/world.ts +294 -0
  71. package/src/mesher/worldConstants.ts +1 -0
  72. package/src/modules/index.ts +11 -0
  73. package/src/modules/starfield.ts +313 -0
  74. package/src/modules/types.ts +110 -0
  75. package/src/playerState/playerState.ts +78 -0
  76. package/src/playerState/types.ts +36 -0
  77. package/src/playground/allEntitiesDebug.ts +170 -0
  78. package/src/playground/baseScene.ts +587 -0
  79. package/src/playground/mobileControls.tsx +268 -0
  80. package/src/playground/playground.html +83 -0
  81. package/src/playground/playground.ts +11 -0
  82. package/src/playground/playgroundUi.tsx +140 -0
  83. package/src/playground/reactUtils.ts +71 -0
  84. package/src/playground/scenes/allEntities.ts +13 -0
  85. package/src/playground/scenes/entities.ts +37 -0
  86. package/src/playground/scenes/floorRandom.ts +33 -0
  87. package/src/playground/scenes/frequentUpdates.ts +148 -0
  88. package/src/playground/scenes/geometryExport.ts +142 -0
  89. package/src/playground/scenes/index.ts +12 -0
  90. package/src/playground/scenes/lightingStarfield.ts +40 -0
  91. package/src/playground/scenes/main.ts +313 -0
  92. package/src/playground/scenes/railsCobweb.ts +14 -0
  93. package/src/playground/scenes/rotationIssue.ts +7 -0
  94. package/src/playground/scenes/slabsOptimization.ts +16 -0
  95. package/src/playground/scenes/transparencyIssue.ts +11 -0
  96. package/src/playground/shared.ts +79 -0
  97. package/src/resourcesManager/index.ts +5 -0
  98. package/src/resourcesManager/resourcesManager.ts +314 -0
  99. package/src/shims/minecraftData.ts +41 -0
  100. package/src/sign-renderer/index.html +21 -0
  101. package/src/sign-renderer/index.ts +216 -0
  102. package/src/sign-renderer/noop.js +1 -0
  103. package/src/sign-renderer/playground.ts +38 -0
  104. package/src/sign-renderer/tests.test.ts +69 -0
  105. package/src/sign-renderer/vite.config.ts +10 -0
  106. package/src/three/appShared.ts +75 -0
  107. package/src/three/bannerRenderer.ts +275 -0
  108. package/src/three/cameraShake.ts +120 -0
  109. package/src/three/cinimaticScript.ts +350 -0
  110. package/src/three/documentRenderer.ts +491 -0
  111. package/src/three/entities.ts +1580 -0
  112. package/src/three/entity/EntityMesh.ts +707 -0
  113. package/src/three/entity/animations.js +171 -0
  114. package/src/three/entity/armorModels.json +204 -0
  115. package/src/three/entity/armorModels.ts +36 -0
  116. package/src/three/entity/entities.json +6230 -0
  117. package/src/three/entity/exportedModels.js +38 -0
  118. package/src/three/entity/externalTextures.json +1 -0
  119. package/src/three/entity/models/allay.obj +325 -0
  120. package/src/three/entity/models/arrow.obj +60 -0
  121. package/src/three/entity/models/axolotl.obj +509 -0
  122. package/src/three/entity/models/blaze.obj +601 -0
  123. package/src/three/entity/models/boat.obj +417 -0
  124. package/src/three/entity/models/camel.obj +1061 -0
  125. package/src/three/entity/models/cat.obj +509 -0
  126. package/src/three/entity/models/chicken.obj +371 -0
  127. package/src/three/entity/models/cod.obj +371 -0
  128. package/src/three/entity/models/creeper.obj +279 -0
  129. package/src/three/entity/models/dolphin.obj +371 -0
  130. package/src/three/entity/models/ender_dragon.obj +2993 -0
  131. package/src/three/entity/models/enderman.obj +325 -0
  132. package/src/three/entity/models/endermite.obj +187 -0
  133. package/src/three/entity/models/fox.obj +463 -0
  134. package/src/three/entity/models/frog.obj +739 -0
  135. package/src/three/entity/models/ghast.obj +463 -0
  136. package/src/three/entity/models/goat.obj +601 -0
  137. package/src/three/entity/models/guardian.obj +1015 -0
  138. package/src/three/entity/models/horse.obj +1061 -0
  139. package/src/three/entity/models/llama.obj +509 -0
  140. package/src/three/entity/models/minecart.obj +233 -0
  141. package/src/three/entity/models/parrot.obj +509 -0
  142. package/src/three/entity/models/piglin.obj +739 -0
  143. package/src/three/entity/models/pillager.obj +371 -0
  144. package/src/three/entity/models/rabbit.obj +555 -0
  145. package/src/three/entity/models/sheep.obj +555 -0
  146. package/src/three/entity/models/shulker.obj +141 -0
  147. package/src/three/entity/models/sniffer.obj +693 -0
  148. package/src/three/entity/models/spider.obj +509 -0
  149. package/src/three/entity/models/tadpole.obj +95 -0
  150. package/src/three/entity/models/turtle.obj +371 -0
  151. package/src/three/entity/models/vex.obj +325 -0
  152. package/src/three/entity/models/villager.obj +509 -0
  153. package/src/three/entity/models/warden.obj +463 -0
  154. package/src/three/entity/models/witch.obj +647 -0
  155. package/src/three/entity/models/wolf.obj +509 -0
  156. package/src/three/entity/models/zombie_villager.obj +463 -0
  157. package/src/three/entity/objModels.js +1 -0
  158. package/src/three/fireworks.ts +661 -0
  159. package/src/three/fireworksRenderer.ts +434 -0
  160. package/src/three/globals.d.ts +7 -0
  161. package/src/three/graphicsBackend.ts +274 -0
  162. package/src/three/graphicsBackendOffThread.ts +107 -0
  163. package/src/three/hand.ts +89 -0
  164. package/src/three/holdingBlock.ts +926 -0
  165. package/src/three/index.ts +20 -0
  166. package/src/three/itemMesh.ts +427 -0
  167. package/src/three/modules.d.ts +14 -0
  168. package/src/three/panorama.ts +308 -0
  169. package/src/three/panoramaShared.ts +1 -0
  170. package/src/three/renderSlot.ts +82 -0
  171. package/src/three/skyboxRenderer.ts +406 -0
  172. package/src/three/starField.ts +13 -0
  173. package/src/three/threeJsMedia.ts +731 -0
  174. package/src/three/threeJsMethods.ts +15 -0
  175. package/src/three/threeJsParticles.ts +160 -0
  176. package/src/three/threeJsSound.ts +95 -0
  177. package/src/three/threeJsUtils.ts +90 -0
  178. package/src/three/waypointSprite.ts +435 -0
  179. package/src/three/waypoints.ts +163 -0
  180. package/src/three/world/cursorBlock.ts +172 -0
  181. package/src/three/world/vr.ts +257 -0
  182. package/src/three/worldGeometryExport.ts +259 -0
  183. package/src/three/worldGeometryHandler.ts +279 -0
  184. package/src/three/worldRendererThree.ts +1381 -0
  185. package/src/worldView/index.ts +6 -0
  186. package/src/worldView/types.ts +66 -0
  187. package/src/worldView/worldView.ts +424 -0
@@ -0,0 +1,6 @@
1
+ /**
2
+ * WorldView module - manages world data communication between the renderer and world provider.
3
+ */
4
+
5
+ export * from './types'
6
+ export * from './worldView'
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Shared WorldView types used by both main thread and mesher worker.
3
+ */
4
+
5
+ import { Vec3 } from 'vec3'
6
+
7
+ /** Chunk position key format: "x,z" e.g. "16,16" */
8
+ export type ChunkPosKey = string
9
+
10
+ /** Chunk position object */
11
+ export type ChunkPos = { x: number; z: number }
12
+
13
+ /** World size parameters sent with chunk data */
14
+ export interface WorldSizeParams {
15
+ minY: number
16
+ worldHeight: number
17
+ }
18
+
19
+ /** Block update event data */
20
+ export interface BlockUpdateData {
21
+ pos: Vec3
22
+ stateId: number
23
+ }
24
+
25
+ /** Chunk load event data */
26
+ export interface LoadChunkData {
27
+ x: number
28
+ z: number
29
+ chunk: string
30
+ blockEntities: any
31
+ worldConfig: WorldSizeParams
32
+ isLightUpdate: boolean
33
+ }
34
+
35
+ /** Chunk unload event data */
36
+ export interface UnloadChunkData {
37
+ x: number
38
+ z: number
39
+ }
40
+
41
+ /** Biome update event data */
42
+ export interface BiomeUpdateData {
43
+ biome: any
44
+ }
45
+
46
+ /**
47
+ * WorldView events emitted to the renderer.
48
+ */
49
+ export type WorldViewEvents = {
50
+ chunkPosUpdate: (data: { pos: Vec3 }) => void
51
+ blockUpdate: (data: BlockUpdateData) => void
52
+ entity: (data: any) => void
53
+ entityMoved: (data: any) => void
54
+ playerEntity: (data: any) => void
55
+ time: (data: number) => void
56
+ renderDistance: (viewDistance: number) => void
57
+ blockEntities: (data: Record<string, any> | { blockEntities: Record<string, any> }) => void
58
+ markAsLoaded: (data: ChunkPos) => void
59
+ unloadChunk: (data: UnloadChunkData) => void
60
+ loadChunk: (data: LoadChunkData) => void
61
+ updateLight: (data: { pos: Vec3 }) => void
62
+ onWorldSwitch: () => void
63
+ end: () => void
64
+ biomeUpdate: (data: BiomeUpdateData) => void
65
+ biomeReset: () => void
66
+ }
@@ -0,0 +1,424 @@
1
+ /**
2
+ * WorldView - World data emitter for the renderer.
3
+ * Renamed from WorldDataEmitter for clarity.
4
+ *
5
+ * Handles chunk loading/unloading, block updates, and world events.
6
+ */
7
+
8
+ import { EventEmitter } from 'events'
9
+ import { Vec3 } from 'vec3'
10
+ import TypedEmitter from 'typed-emitter'
11
+ import type { WorldViewEvents, ChunkPosKey, WorldSizeParams } from './types'
12
+
13
+ /**
14
+ * Helper to calculate chunk position from absolute position.
15
+ */
16
+ export const chunkPos = (pos: { x: number; z: number } | Vec3): [number, number] => {
17
+ return [Math.floor(pos.x / 16), Math.floor(pos.z / 16)]
18
+ }
19
+
20
+ /**
21
+ * Helper to calculate section position from absolute position.
22
+ */
23
+ export const sectionPos = (pos: { x: number; y: number; z: number }): [number, number, number] => {
24
+ return [Math.floor(pos.x / 16), Math.floor(pos.y / 16), Math.floor(pos.z / 16)]
25
+ }
26
+
27
+ /**
28
+ * Generates a spiral matrix of chunk positions for loading chunks in order from center.
29
+ */
30
+ export const generateSpiralMatrix = (viewDistance: number): [number, number][] => {
31
+ const result: [number, number][] = []
32
+ const size = viewDistance * 2 + 1
33
+ let x = 0
34
+ let z = 0
35
+ let dx = 0
36
+ let dz = -1
37
+
38
+ for (let i = 0; i < size * size; i++) {
39
+ if (-viewDistance <= x && x <= viewDistance && -viewDistance <= z && z <= viewDistance) {
40
+ result.push([x, z])
41
+ }
42
+ if (x === z || (x < 0 && x === -z) || (x > 0 && x === 1 - z)) {
43
+ const temp = dx
44
+ dx = -dz
45
+ dz = temp
46
+ }
47
+ x += dx
48
+ z += dz
49
+ }
50
+
51
+ return result
52
+ }
53
+
54
+ /**
55
+ * Delayed iterator for chunk loading with configurable delay.
56
+ */
57
+ export const delayedIterator = async <T>(
58
+ arr: T[],
59
+ delay: number,
60
+ exec: (item: T, index: number) => Promise<void>,
61
+ chunkSize = 1
62
+ ): Promise<void> => {
63
+ for (let i = 0; i < arr.length; i += chunkSize) {
64
+ if (delay) {
65
+ await new Promise(resolve => setTimeout(resolve, delay))
66
+ }
67
+ await exec(arr[i], i)
68
+ }
69
+ }
70
+
71
+ /**
72
+ * WorldView for worker thread communication.
73
+ * This is a lightweight version that receives events from the main thread.
74
+ */
75
+ export class WorldViewWorker extends (EventEmitter as new () => TypedEmitter<WorldViewEvents>) {
76
+ static readonly restorerName = 'WorldViewWorker'
77
+
78
+ static restoreTransferred(data: any, worker?: Worker): WorldViewWorker {
79
+ const worldView = new WorldViewWorker()
80
+ if (worker) {
81
+ worker.addEventListener('message', ({ data }) => {
82
+ if (data.class === WorldViewWorker.restorerName) {
83
+ if (data.type === 'event') {
84
+ worldView.emit(data.eventName, ...data.args)
85
+ }
86
+ }
87
+ })
88
+ }
89
+ return worldView
90
+ }
91
+ }
92
+
93
+ /**
94
+ * World data provider interface for different world implementations.
95
+ */
96
+ export interface WorldProvider {
97
+ getColumnAt(pos: Vec3): any | null
98
+ setBlockStateId(pos: Vec3, stateId: number): void | Promise<void>
99
+ getBiome?(pos: Vec3): number
100
+ }
101
+
102
+ /**
103
+ * WorldView - Main world data emitter for the renderer.
104
+ *
105
+ * Responsible for:
106
+ * - Loading/unloading chunks based on view distance
107
+ * - Emitting block updates to the renderer
108
+ * - Managing loaded chunks state
109
+ * - Spiral chunk loading for optimal player experience
110
+ */
111
+ export class WorldView extends (EventEmitter as new () => TypedEmitter<WorldViewEvents>) {
112
+ spiralNumber = 0
113
+ gotPanicLastTime = false
114
+ panicChunksReload = () => { }
115
+ loadedChunks: Record<ChunkPosKey, boolean> = {}
116
+ private inLoading = false
117
+ private chunkReceiveTimes: number[] = []
118
+ private lastChunkReceiveTime = 0
119
+ public lastChunkReceiveTimeAvg = 0
120
+ private panicTimeout?: ReturnType<typeof setTimeout>
121
+ readonly lastPos: Vec3
122
+ private eventListeners: Record<string, any> = {}
123
+ debugChunksInfo: Record<ChunkPosKey, {
124
+ loads: Array<{
125
+ dataLength: number
126
+ reason: string
127
+ time: number
128
+ }>
129
+ }> = {}
130
+
131
+ waitingSpiralChunksLoad: Record<ChunkPosKey, (value: boolean) => void> = {}
132
+
133
+ addWaitTime = 1
134
+ keepChunksDistance = 0
135
+ isPlayground = false
136
+ allowPositionUpdate = true
137
+
138
+ constructor(
139
+ public world: WorldProvider,
140
+ public viewDistance: number,
141
+ position: Vec3 = new Vec3(0, 0, 0)
142
+ ) {
143
+ super()
144
+ this.lastPos = new Vec3(0, 0, 0).update(position)
145
+ }
146
+
147
+ /**
148
+ * Prepare this WorldView for transfer to a worker thread.
149
+ */
150
+ prepareForTransfer(worker?: Worker): { __restorer: string } {
151
+ if (worker) {
152
+ const oldEmit = this.emit.bind(this) as any
153
+ this.emit = ((eventName: keyof WorldViewEvents, ...args: any[]) => {
154
+ oldEmit(eventName, ...args)
155
+ worker.postMessage({
156
+ class: WorldViewWorker.restorerName,
157
+ type: 'event',
158
+ eventName,
159
+ args,
160
+ })
161
+ }) as any
162
+ }
163
+ return {
164
+ __restorer: WorldViewWorker.restorerName,
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Set a block state and emit update to renderer.
170
+ */
171
+ setBlockStateId(position: Vec3, stateId: number): void {
172
+ const val = this.world.setBlockStateId(position, stateId)
173
+ if (val && typeof (val as any).then === 'function') {
174
+ throw new Error('setBlockStateId returned promise (not supported)')
175
+ }
176
+ this.emit('blockUpdate', { pos: position, stateId })
177
+ }
178
+
179
+ /**
180
+ * Update the view distance and notify renderer.
181
+ */
182
+ updateViewDistance(viewDistance: number): void {
183
+ this.viewDistance = viewDistance
184
+ this.emit('renderDistance', viewDistance)
185
+ }
186
+
187
+ /**
188
+ * Initialize the world view and start loading chunks.
189
+ */
190
+ async init(pos: Vec3, bot?: any): Promise<void> {
191
+ console.log('WorldView init')
192
+ this.updateViewDistance(this.viewDistance)
193
+ this.emit('chunkPosUpdate', { pos })
194
+
195
+ // Emit time and player entity if bot is provided
196
+ if (bot?.time?.timeOfDay !== undefined) {
197
+ this.emit('time', bot.time.timeOfDay)
198
+ }
199
+ if (bot?.entity) {
200
+ this.emit('playerEntity', bot.entity)
201
+ }
202
+
203
+ // Emit block entities if not in offscreen/worker context
204
+ this.emitterGotConnected(bot)
205
+
206
+ const [botX, botZ] = chunkPos(pos)
207
+ const positions = generateSpiralMatrix(this.viewDistance).map(
208
+ ([x, z]) => new Vec3((botX + x) * 16, 0, (botZ + z) * 16)
209
+ )
210
+
211
+ this.lastPos.update(pos)
212
+ await this._loadChunks(positions, pos)
213
+ }
214
+
215
+ private chunkProgress(): void {
216
+ if (this.panicTimeout) clearTimeout(this.panicTimeout)
217
+ if (this.chunkReceiveTimes.length >= 5) {
218
+ const avgReceiveTime = this.chunkReceiveTimes.reduce((a, b) => a + b, 0) / this.chunkReceiveTimes.length
219
+ this.lastChunkReceiveTimeAvg = avgReceiveTime
220
+ const timeoutDelay = avgReceiveTime * 2 + 1000
221
+
222
+ if (this.panicTimeout) clearTimeout(this.panicTimeout)
223
+
224
+ this.panicTimeout = setTimeout(() => {
225
+ if (!this.gotPanicLastTime && this.inLoading) {
226
+ console.warn('Chunk loading seems stuck, triggering panic reload')
227
+ this.gotPanicLastTime = true
228
+ this.panicChunksReload()
229
+ }
230
+ }, timeoutDelay)
231
+ }
232
+ }
233
+
234
+ private async _loadChunks(positions: Vec3[], centerPos: Vec3): Promise<void> {
235
+ this.spiralNumber++
236
+ const { spiralNumber } = this
237
+
238
+ // Stop loading previous chunks
239
+ for (const pos of Object.keys(this.waitingSpiralChunksLoad)) {
240
+ this.waitingSpiralChunksLoad[pos](false)
241
+ delete this.waitingSpiralChunksLoad[pos]
242
+ }
243
+
244
+ let continueLoading = true
245
+ this.inLoading = true
246
+
247
+ await delayedIterator(positions, this.addWaitTime, async (pos) => {
248
+ if (!continueLoading || this.loadedChunks[`${pos.x},${pos.z}`]) return
249
+
250
+ // Wait for chunk to be available from server
251
+ if (!this.world.getColumnAt(pos)) {
252
+ continueLoading = await new Promise<boolean>(resolve => {
253
+ this.waitingSpiralChunksLoad[`${pos.x},${pos.z}`] = resolve
254
+ })
255
+ }
256
+ if (!continueLoading) return
257
+ await this.loadChunk(pos, undefined, `spiral ${spiralNumber} from ${centerPos.x},${centerPos.z}`)
258
+ this.chunkProgress()
259
+ })
260
+
261
+ if (this.panicTimeout) clearTimeout(this.panicTimeout)
262
+ this.inLoading = false
263
+ this.gotPanicLastTime = false
264
+ this.chunkReceiveTimes = []
265
+ this.lastChunkReceiveTime = 0
266
+ }
267
+
268
+ /**
269
+ * Load a chunk at the given position.
270
+ */
271
+ async loadChunk(
272
+ pos: { x: number; z: number; y?: number },
273
+ isLightUpdate = false,
274
+ reason = 'spiral'
275
+ ): Promise<void> {
276
+ const [botX, botZ] = chunkPos(this.lastPos)
277
+ const dx = Math.abs(botX - Math.floor(pos.x / 16))
278
+ const dz = Math.abs(botZ - Math.floor(pos.z / 16))
279
+
280
+ if (dx <= this.viewDistance && dz <= this.viewDistance) {
281
+ const column = await this.world.getColumnAt(
282
+ pos.y !== undefined ? (pos as Vec3) : new Vec3(pos.x, 0, pos.z)
283
+ )
284
+
285
+ if (column) {
286
+ const chunk = column.toJson()
287
+ const worldConfig: WorldSizeParams = {
288
+ minY: column.minY ?? 0,
289
+ worldHeight: column.worldHeight ?? 256,
290
+ }
291
+
292
+ this.emit('loadChunk', {
293
+ x: pos.x,
294
+ z: pos.z,
295
+ chunk,
296
+ blockEntities: column.blockEntities,
297
+ worldConfig,
298
+ isLightUpdate
299
+ })
300
+ this.loadedChunks[`${pos.x},${pos.z}`] = true
301
+
302
+ this.debugChunksInfo[`${pos.x},${pos.z}`] ??= { loads: [] }
303
+ this.debugChunksInfo[`${pos.x},${pos.z}`].loads.push({
304
+ dataLength: chunk.length,
305
+ reason,
306
+ time: Date.now(),
307
+ })
308
+ } else if (this.isPlayground) {
309
+ this.emit('markAsLoaded', { x: pos.x, z: pos.z })
310
+ }
311
+ }
312
+ }
313
+
314
+ /**
315
+ * Unload all chunks.
316
+ */
317
+ unloadAllChunks(): void {
318
+ for (const coords of Object.keys(this.loadedChunks)) {
319
+ const [x, z] = coords.split(',').map(Number)
320
+ this.unloadChunk({ x, z })
321
+ }
322
+ }
323
+
324
+ /**
325
+ * Unload a specific chunk.
326
+ */
327
+ unloadChunk(pos: { x: number; z: number }): void {
328
+ this.emit('unloadChunk', { x: pos.x, z: pos.z })
329
+ delete this.loadedChunks[`${pos.x},${pos.z}`]
330
+ delete this.debugChunksInfo[`${pos.x},${pos.z}`]
331
+ }
332
+
333
+ /**
334
+ * Emit block entities when connected.
335
+ * Only works in main thread (not offscreen/worker context).
336
+ */
337
+ emitterGotConnected(bot?: any): void {
338
+ // Skip if in offscreen/worker context
339
+ const isOffscreen = typeof (globalThis as any).WorkerGlobalScope !== 'undefined' &&
340
+ globalThis instanceof (globalThis as any).WorkerGlobalScope
341
+
342
+ if (isOffscreen || !bot) return
343
+
344
+ this.emit('blockEntities', new Proxy({}, {
345
+ get(_target, posKey, receiver) {
346
+ if (typeof posKey !== 'string') return
347
+ const [x, y, z] = posKey.split(',').map(Number)
348
+ return bot.world.getBlock(new Vec3(x, y, z))?.entity
349
+ },
350
+ }))
351
+ }
352
+
353
+ private lastBiomeId: number | null = null
354
+
355
+ private updateBiome(pos: Vec3): void {
356
+ try {
357
+ if (!this.world.getBiome) return
358
+ const biomeId = this.world.getBiome(pos)
359
+ if (biomeId !== this.lastBiomeId) {
360
+ this.lastBiomeId = biomeId
361
+ // Note: Biome data lookup would need to be provided externally
362
+ // This is a simplified version
363
+ this.emit('biomeReset')
364
+ }
365
+ } catch (e) {
366
+ console.error('error updating biome', e)
367
+ }
368
+ }
369
+
370
+ private lastPosCheck: Vec3 | null = null
371
+
372
+ /**
373
+ * Update position and load/unload chunks as needed.
374
+ */
375
+ async updatePosition(pos: Vec3, force = false): Promise<void> {
376
+ if (!this.allowPositionUpdate) return
377
+ const posFloored = pos.floored()
378
+ if (!force && this.lastPosCheck && this.lastPosCheck.equals(posFloored)) return
379
+ this.lastPosCheck = posFloored
380
+
381
+ this.updateBiome(pos)
382
+
383
+ const [lastX, lastZ] = chunkPos(this.lastPos)
384
+ const [botX, botZ] = chunkPos(pos)
385
+
386
+ if (lastX !== botX || lastZ !== botZ || force) {
387
+ this.emit('chunkPosUpdate', { pos })
388
+
389
+ // Unload chunks that are no longer in view
390
+ const chunksToUnload: Vec3[] = []
391
+ const viewDistanceWithBuffer = this.viewDistance + this.keepChunksDistance
392
+
393
+ for (const coords of Object.keys(this.loadedChunks)) {
394
+ const [x, z] = coords.split(',').map(Number)
395
+ const p = new Vec3(x, 0, z)
396
+ const [chunkX, chunkZ] = chunkPos(p)
397
+ const dx = Math.abs(botX - chunkX)
398
+ const dz = Math.abs(botZ - chunkZ)
399
+ if (dx > viewDistanceWithBuffer || dz > viewDistanceWithBuffer) {
400
+ chunksToUnload.push(p)
401
+ }
402
+ }
403
+
404
+ for (const p of chunksToUnload) {
405
+ this.unloadChunk(p)
406
+ }
407
+
408
+ // Load new chunks
409
+ const positions = generateSpiralMatrix(this.viewDistance)
410
+ .map(([x, z]) => {
411
+ const newPos = new Vec3((botX + x) * 16, 0, (botZ + z) * 16)
412
+ if (!this.loadedChunks[`${newPos.x},${newPos.z}`]) return newPos
413
+ return undefined!
414
+ })
415
+ .filter(a => !!a)
416
+
417
+ this.lastPos.update(pos)
418
+ void this._loadChunks(positions, pos)
419
+ } else {
420
+ this.emit('chunkPosUpdate', { pos })
421
+ this.lastPos.update(pos)
422
+ }
423
+ }
424
+ }