The source dump and browser console reveal three distinct issues that caused
the execution to halt:
* Missing ChunkRenderer Implementation in renderer.ts:
Line 10 of renderer.ts contained a placeholder comment: // ... [Keep the
ChunkRenderer class exactly as it was] .... Because the class body was omitted
during copy-paste, calling new ChunkRenderer(...) on line 29 threw an uncaught
ReferenceError.
* Node-exclusive worker_threads in Browser Context:
mesh_worker.ts imported parentPort from worker_threads. In the browser, Web
Workers operate within the global self scope, causing Vite to throw an
externalization error when building for the client.
* Mismatched Message Protocol & Missing Ring Buffer in renderer.ts:
renderer.ts commented out the SharedArrayBuffer initialization.
Furthermore, the worker emits { type: 'mesh', coords, vertices }, while the
tentative renderer script was listening for { type: 'CHUNK_MESH_READY',
chunkId, positions }.
Step 1: Update 3D-TS/mesh_worker.ts
Replace the contents of /mnt/mesh_shared/3d/3D-TS/mesh_worker.ts with the
following. This aligns CHUNK_SIZE to 8 (512 voxels) and switches from Node's
worker_threads to the standard Web Worker self API:
/ 3D-TS/mesh_worker.ts
import { RingBuffer } from './edit_queue';
console.log('[Worker] Loaded');
function generateCubeMesh(chunkData: Uint8Array, chunkSize: number):
Float32Array {
const vertices: number[] = [];
const corners: [number, number, number][] = [
[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0],
[0, 0, 1], [1, 0, 1], [1, 1, 1], [0, 1, 1]
];
const faces = [
[1, 5, 6, 2], [4, 0, 3, 7], [3, 2, 6, 7],
[0, 1, 5, 4], [4, 5, 6, 7], [0, 3, 2, 1]
];
for (let z = 0; z < chunkSize; z++) {
for (let y = 0; y < chunkSize; y++) {
for (let x = 0; x < chunkSize; x++) {
const idx = x + y * chunkSize + z * chunkSize * chunkSize;
if (chunkData[idx] === 1) {
const ox = x, oy = y, oz = z;
for (const face of faces) {
const c0 = corners[face[0]];
const c1 = corners[face[1]];
const c2 = corners[face[2]];
const c3 = corners[face[3]];
// Triangle 1
vertices.push(ox + c0[0], oy + c0[1], oz + c0[2]);
vertices.push(ox + c1[0], oy + c1[1], oz + c1[2]);
vertices.push(ox + c2[0], oy + c2[1], oz + c2[2]);
// Triangle 2
vertices.push(ox + c0[0], oy + c0[1], oz + c0[2]);
vertices.push(ox + c2[0], oy + c2[1], oz + c2[2]);
vertices.push(ox + c3[0], oy + c3[1], oz + c3[2]);
}
}
}
}
}
return new Float32Array(vertices);
}
let ringBuffer: RingBuffer | null = null;
const chunkMap = new Map<number, Uint8Array>();
const CMD_SET_VOXEL = 1;
const CHUNK_SIZE = 8; // Aligned with the 8x8x8 memory arena
function processCommand(cmd: [number, number, number, number]) {
const [cmdId, packedCoords, voxelIndex, voxelData] = cmd;
if (cmdId === CMD_SET_VOXEL) {
let chunkData = chunkMap.get(packedCoords);
if (!chunkData) {
chunkData = new Uint8Array(CHUNK_SIZE ** 3);
chunkMap.set(packedCoords, chunkData);
}
chunkData[voxelIndex] = voxelData;
const mesh = generateCubeMesh(chunkData, CHUNK_SIZE);
console.log(`[Worker] Generated mesh with ${mesh.length / 3} vertices`);
// Zero-copy transfer to main thread
self.postMessage(
{ type: 'mesh', coords: packedCoords, vertices: mesh },
[mesh.buffer]
);
}
}
function run() {
if (!ringBuffer) throw new Error('RingBuffer not initialised');
console.log('[Worker] Entering main loop');
while (true) {
const tail = ringBuffer.getTail();
let head = ringBuffer.getHead();
while (head < tail) {
const cmd = ringBuffer.pop();
if (cmd) processCommand(cmd);
head = ringBuffer.getHead();
}
ringBuffer.waitForTail(tail);
}
}
self.onmessage = (event: MessageEvent) => {
const msg = event.data;
console.log('[Worker] Received message:', msg);
if (msg.type === 'init') {
const { sab, capacity } = msg;
ringBuffer = new RingBuffer(sab, capacity);
run();
}
};
self.onerror = (err) => {
console.error('[Worker] Error:', err);
};
Step 2: Update web/src/renderer.ts
Replace the contents of /mnt/mesh_shared/3d/web/src/renderer.ts with the
complete, cohesive implementation below. This sets up the RingBuffer with a
SharedArrayBuffer, starts the worker, initializes Babylon.js with an
ArcRotateCamera for easy orbiting, and wires up the UI button to push voxel
edits directly into the shared memory.
/ web/src/renderer.ts
import {
Engine,
Scene,
Vector3,
ArcRotateCamera,
HemisphericLight,
Mesh,
VertexData,
StandardMaterial,
Color3
} from '@babylonjs/core';
import { RingBuffer } from '@core/edit_queue';
const CHUNK_SIZE = 8;
const CAPACITY = 64;
const TOTAL_INTS = 2 + CAPACITY * 4;
class ChunkRenderer {
private scene: Scene;
private chunkMeshes: Map<number, Mesh> = new Map();
constructor(scene: Scene, worker: Worker) {
this.scene = scene;