Download Game! Currently 55 players and visitors. Last logged in:ArnoldiMahjongWhlxchCavalier

Blitzer's Blog >> 72148

Back to blogs index
Posted: 06 Sep 2026 13:41 [ permalink ]
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;