Three.js WebGPU Client — Stage 1: Observer

Supersedes: 20260425-threejs-webgpu-zone-client.md (split)

Superseded by: 20260425-godot-observer.md

  • Status: superseded
  • Deciders: V-Sekai, fire
  • Tags: V-Sekai, Threejs, WebGPU, WebTransport, Observer, ZoneServer, 20260425-threejs-observer

The Context

The zone server speaks WebTransport. The CH_INTEREST packet format is fully specified in fabric_zone_types.h (100 bytes per entity, fixed offsets). A read-only observer client needs no Godot runtime, no wasm build, and no service worker.

The Problem Statement

The operator view needs a browser client that starts in minutes, not after a multi-minute wasm compile. Stage 1 delivers that: connect, parse datagrams, render entities.

Design

Wire protocol — CH_INTEREST (100 bytes, read only)

Offset  Size  Field
  0       4   global_id (int32)
  4      24   cx, cy, cz (float64 × 3)
 28      12   vx, vy, vz (int16 × 3, scale 1/100 m/s)
 34       6   ax, ay, az (int16 × 3, scale 1/1000 m/s²)
 40       4   hlc = tick(24b) | counter(8b)
 44      56   payload[14] (uint32 × 14)

payload[0]: entity_class(8b) | owner_id(16b) | state_flags(8b)

TypeScript packet parser

function parseInterest(buf: ArrayBuffer): Entity[] {
  const view = new DataView(buf);
  const out: Entity[] = [];
  for (let i = 0; i < buf.byteLength / 100; i++) {
    const o = i * 100;
    out.push({
      id:          view.getInt32(o, true),
      pos:         [view.getFloat64(o+4,true), view.getFloat64(o+12,true), view.getFloat64(o+20,true)],
      entityClass: (view.getUint32(o+44,true) >> 24) & 0xff,
      payload:     new Uint32Array(buf, o+44, 14),
    });
  }
  return out;
}

WebTransport connection

const wt = new WebTransport(`https://${host}/zone`);
await wt.ready;
const reader = wt.datagrams.readable.getReader();
(function pump(r) {
  if (r.done) return;
  updateScene(parseInterest(r.value.buffer));
  reader.read().then(pump);
})(await reader.read());

Reader captured once; exclusive-lock invariant holds (proved in WebTransport audit).

Three.js WebGPU scene

Orthographic camera at SWING_ELEVATION twist/swing, one InstancedMesh per entity class (1800 entities, one draw call). Operator overlay (load bars, dot clustering) is a <canvas> over the Three.js canvas per 20260425-operator-overlay.md.

The Downsides

Read-only: no entity input. Stage 2 adds the CH_PLAYER write path.

The Road Not Taken

Stage 2 features (libriscv, taskweft WASM, WebXR) are deferred; adding them to Stage 1 would delay shipping the operator view.

Status

Status: Superseded by 20260425-godot-observer.md. Not being built.

Decision Makers

  • iFire

Further Reading

(threejswebgpu2026?): “What’s New in Three.js (2026).” utsubo.com. https://www.utsubo.com/blog/threejs-2026-what-changed

(threejswebgpurenderer?): “WebGPURenderer — three.js docs.” Three.js. https://threejs.org/docs/pages/WebGPURenderer.html