@downdraft/engine 0.3.0 → 0.4.0-alpha.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.
- package/app/src/renderer/bootstrap.ts +0 -6
- package/app/src/renderer/feature-log.ts +1 -9
- package/app/src/renderer/mcp-harness.ts +8 -11
- package/app/src/renderer/standard-automation-tools.ts +8 -145
- package/core/src/module/host.ts +3 -6
- package/core/src/module/index.ts +1 -1
- package/core/src/module/module.ts +4 -47
- package/core/src/physics/interface.ts +5 -0
- package/core/src/platform/render-surface.ts +13 -0
- package/core/src/render/canvas-resize-watcher.ts +1 -10
- package/core/src/render/game-renderer.ts +40 -18
- package/core/src/render/index.ts +0 -2
- package/core/src/render/input-manager.ts +7 -7
- package/core/src/render/renderer-module-host.ts +2 -4
- package/core/src/ui-font-scale.ts +5 -38
- package/core/src/worker/rpc.ts +65 -0
- package/dist/.dd-manifest.json +859 -884
- package/dist/app/src/renderer/bootstrap.js +0 -4
- package/dist/app/src/renderer/feature-log.js +1 -10
- package/dist/app/src/renderer/mcp-harness.js +8 -11
- package/dist/app/src/renderer/standard-automation-tools.js +6 -138
- package/dist/core/src/module/host.js +3 -6
- package/dist/core/src/render/canvas-resize-watcher.js +1 -10
- package/dist/core/src/render/game-renderer.js +44 -18
- package/dist/core/src/render/index.js +0 -1
- package/dist/core/src/render/input-manager.js +7 -7
- package/dist/core/src/render/renderer-module-host.js +2 -4
- package/dist/core/src/ui-font-scale.js +5 -38
- package/dist/core/src/worker/rpc.js +59 -0
- package/dist/libraries/blitz-ui/src/native-module.js +8 -1
- package/dist/libraries/devtools/src/native-providers.js +0 -2
- package/dist/libraries/entities/src/model-renderer.js +87 -27
- package/dist/libraries/entities/src/shaders/model.wgsl +13 -4
- package/dist/libraries/networking/src/index.js +1 -0
- package/dist/libraries/networking/src/ws-server.js +387 -0
- package/dist/libraries/physics-rapier/src/api.js +15 -0
- package/dist/libraries/physics-rapier/src/backend.js +15 -0
- package/dist/libraries/physics-rapier/src/rapier-backend.js +5 -0
- package/dist/libraries/postfx/src/post-process-stack.js +1 -3
- package/dist/libraries/sand/src/materials.js +2 -32
- package/dist/libraries/sand/src/sand-world.js +0 -3
- package/dist/modules/devtools/src/api.js +8 -23
- package/dist/modules/devtools/src/data-bridge.js +0 -11
- package/dist/modules/devtools/src/index.js +0 -6
- package/dist/modules/devtools/src/init.js +8 -42
- package/dist/modules/devtools/src/scene-inspector.js +1 -4
- package/dist/modules/devtools/src/worker-sync.js +3 -11
- package/dist/test/src/vision-test.js +1 -3
- package/libraries/blitz-ui/src/native-module.ts +8 -1
- package/libraries/devtools/src/native-providers.ts +0 -2
- package/libraries/entities/src/model-renderer.ts +88 -27
- package/libraries/entities/src/shaders/model.wgsl +13 -4
- package/libraries/networking/src/index.ts +4 -0
- package/libraries/networking/src/ws-server.ts +397 -0
- package/libraries/physics-rapier/src/api.ts +15 -0
- package/libraries/physics-rapier/src/backend.ts +15 -0
- package/libraries/physics-rapier/src/rapier-backend.ts +6 -0
- package/libraries/postfx/src/post-process-stack.ts +1 -2
- package/libraries/sand/src/materials.ts +2 -32
- package/libraries/sand/src/sand-world.ts +0 -4
- package/modules/devtools/README.md +1 -3
- package/modules/devtools/src/api.ts +10 -34
- package/modules/devtools/src/data-bridge.ts +0 -16
- package/modules/devtools/src/index.ts +1 -11
- package/modules/devtools/src/init.ts +8 -51
- package/modules/devtools/src/scene-inspector.ts +2 -6
- package/modules/devtools/src/types.ts +5 -90
- package/modules/devtools/src/worker-sync.ts +3 -13
- package/package.json +4 -5
- package/test/src/vision-test.ts +2 -4
- package/core/src/render/sab-canvas-overlay.ts +0 -122
- package/dist/core/src/render/sab-canvas-overlay.js +0 -83
- package/dist/modules/devtools/src/debug-overlay.js +0 -181
- package/dist/modules/devtools/src/doctor-panel.js +0 -163
- package/dist/modules/devtools/src/label-overlay.js +0 -129
- package/dist/modules/devtools/src/material-stats-panel.js +0 -171
- package/dist/modules/devtools/src/sim-stats-panel.js +0 -250
- package/modules/devtools/src/debug-overlay.ts +0 -238
- package/modules/devtools/src/doctor-panel.ts +0 -182
- package/modules/devtools/src/label-overlay.ts +0 -154
- package/modules/devtools/src/material-stats-panel.ts +0 -188
- package/modules/devtools/src/sim-stats-panel.ts +0 -272
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// ws-server.ts — minimal tri-runtime WebSocket server for host-authoritative
|
|
3
|
+
// LAN/WAN sessions. The networking library ships client transports
|
|
4
|
+
// (WebSocketTransport, WebRTCTransport) but had no listener — this fills the
|
|
5
|
+
// host side so a game instance can accept direct connections without any
|
|
6
|
+
// external service.
|
|
7
|
+
//
|
|
8
|
+
// Runtime selection is capability-based (see platform/runtime.ts):
|
|
9
|
+
// Bun → Bun.serve({ fetch, websocket })
|
|
10
|
+
// Deno → Deno.serve + Deno.upgradeWebSocket
|
|
11
|
+
// Node → node:http + a hand-rolled RFC6455 handshake/framer (pure JS,
|
|
12
|
+
// no native deps — also the fallback for any runtime with node:http)
|
|
13
|
+
//
|
|
14
|
+
// Semantics intentionally mirror the browser WebSocket surface: text frames
|
|
15
|
+
// arrive as `string`, binary frames as `Uint8Array`. Ping/pong/close and
|
|
16
|
+
// fragmented-frame reassembly are handled internally. Message cap matches
|
|
17
|
+
// WebSocketTransport's 16 MiB.
|
|
18
|
+
// ============================================================================
|
|
19
|
+
import { createLogger } from "@downdraft/engine/util/logger";
|
|
20
|
+
const log = createLogger();
|
|
21
|
+
const WS_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
22
|
+
const MAX_MESSAGE_SIZE = 16 * 1024 * 1024;
|
|
23
|
+
function makeServerBase(handlers) {
|
|
24
|
+
const conns = new Set();
|
|
25
|
+
let nextId = 1;
|
|
26
|
+
const deliver = (conn, data) => {
|
|
27
|
+
const size = typeof data === "string" ? data.length : data.byteLength;
|
|
28
|
+
if (size > MAX_MESSAGE_SIZE) {
|
|
29
|
+
conn.close(1009, "message too large");
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
handlers.onMessage?.(conn, data);
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
log.error("WsServer", `onMessage threw: ${e}`);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const drop = (conn, code, reason) => {
|
|
40
|
+
if (!conns.delete(conn))
|
|
41
|
+
return;
|
|
42
|
+
try {
|
|
43
|
+
handlers.onClose?.(conn, code, reason);
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
log.error("WsServer", `onClose threw: ${e}`);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
return { conns, nextId: () => nextId++, deliver, drop };
|
|
50
|
+
}
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Bun — Bun.serve with the `websocket` handler map
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
function startBunServer(opts) {
|
|
55
|
+
const Bun = globalThis.Bun;
|
|
56
|
+
const base = makeServerBase(opts);
|
|
57
|
+
const server = Bun.serve({
|
|
58
|
+
port: opts.port,
|
|
59
|
+
hostname: opts.host ?? "0.0.0.0",
|
|
60
|
+
fetch(req, srv) {
|
|
61
|
+
const ip = srv.requestIP(req);
|
|
62
|
+
const conn = {
|
|
63
|
+
id: base.nextId(),
|
|
64
|
+
remoteAddress: `${ip?.address ?? "?"}:${ip?.port ?? "?"}`,
|
|
65
|
+
raw: null,
|
|
66
|
+
send(data) { if (this.raw)
|
|
67
|
+
this.raw.send(data instanceof ArrayBuffer ? new Uint8Array(data) : data); },
|
|
68
|
+
close(code = 1000, reason = "") { if (this.raw)
|
|
69
|
+
this.raw.close(code, reason); },
|
|
70
|
+
};
|
|
71
|
+
return srv.upgrade(req, { data: { conn } }) ? undefined : new Response("WebSocket upgrade required", { status: 426 });
|
|
72
|
+
},
|
|
73
|
+
websocket: {
|
|
74
|
+
open(ws) {
|
|
75
|
+
const conn = ws.data.conn;
|
|
76
|
+
conn.raw = ws;
|
|
77
|
+
base.conns.add(conn);
|
|
78
|
+
try {
|
|
79
|
+
opts.onConnection?.(conn);
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
log.error("WsServer", `onConnection threw: ${e}`);
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
message(ws, msg) {
|
|
86
|
+
const conn = ws.data.conn;
|
|
87
|
+
base.deliver(conn, typeof msg === "string" ? msg : new Uint8Array(msg));
|
|
88
|
+
},
|
|
89
|
+
close(ws, code, reason) {
|
|
90
|
+
base.drop(ws.data.conn, code, reason);
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
return Promise.resolve({
|
|
95
|
+
port: server.port,
|
|
96
|
+
host: opts.host ?? "0.0.0.0",
|
|
97
|
+
connections: () => [...base.conns],
|
|
98
|
+
async close() {
|
|
99
|
+
[...base.conns].forEach((c) => c.close(1001, "server shutdown"));
|
|
100
|
+
await server.stop(true);
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
// Deno — Deno.serve + Deno.upgradeWebSocket
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
function startDenoServer(opts) {
|
|
108
|
+
const Deno = globalThis.Deno;
|
|
109
|
+
const base = makeServerBase(opts);
|
|
110
|
+
const server = Deno.serve({ port: opts.port, hostname: opts.host ?? "0.0.0.0" }, (req, info) => {
|
|
111
|
+
if (req.headers.get("upgrade")?.toLowerCase() !== "websocket") {
|
|
112
|
+
return new Response("WebSocket upgrade required", { status: 426 });
|
|
113
|
+
}
|
|
114
|
+
const { socket, response } = Deno.upgradeWebSocket(req);
|
|
115
|
+
const conn = {
|
|
116
|
+
id: base.nextId(),
|
|
117
|
+
remoteAddress: info?.remoteAddr ? `${info.remoteAddr.hostname}:${info.remoteAddr.port}` : "?",
|
|
118
|
+
raw: socket,
|
|
119
|
+
send(data) { try {
|
|
120
|
+
socket.send(data);
|
|
121
|
+
}
|
|
122
|
+
catch { /* closing */ } },
|
|
123
|
+
close(code = 1000, reason = "") { try {
|
|
124
|
+
socket.close(code, reason);
|
|
125
|
+
}
|
|
126
|
+
catch { /* already closed */ } },
|
|
127
|
+
};
|
|
128
|
+
socket.binaryType = "arraybuffer";
|
|
129
|
+
socket.onopen = () => {
|
|
130
|
+
base.conns.add(conn);
|
|
131
|
+
try {
|
|
132
|
+
opts.onConnection?.(conn);
|
|
133
|
+
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
log.error("WsServer", `onConnection threw: ${e}`);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
socket.onmessage = (e) => {
|
|
139
|
+
base.deliver(conn, typeof e.data === "string" ? e.data : new Uint8Array(e.data));
|
|
140
|
+
};
|
|
141
|
+
socket.onclose = (e) => base.drop(conn, e.code, e.reason);
|
|
142
|
+
socket.onerror = () => base.drop(conn, 1006, "socket error");
|
|
143
|
+
return response;
|
|
144
|
+
});
|
|
145
|
+
const bound = server.addr;
|
|
146
|
+
return Promise.resolve({
|
|
147
|
+
port: bound.port,
|
|
148
|
+
host: bound.hostname,
|
|
149
|
+
connections: () => [...base.conns],
|
|
150
|
+
async close() {
|
|
151
|
+
[...base.conns].forEach((c) => c.close(1001, "server shutdown"));
|
|
152
|
+
await server.shutdown();
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
// Node (and generic fallback) — node:http + hand-rolled RFC6455
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
/** Encode one server→client frame. Server frames are never masked. */
|
|
160
|
+
function encodeWsFrame(opcode, payload) {
|
|
161
|
+
const len = payload.byteLength;
|
|
162
|
+
let header;
|
|
163
|
+
if (len < 126) {
|
|
164
|
+
header = new Uint8Array(2);
|
|
165
|
+
header[1] = len;
|
|
166
|
+
}
|
|
167
|
+
else if (len <= 0xffff) {
|
|
168
|
+
header = new Uint8Array(4);
|
|
169
|
+
header[1] = 126;
|
|
170
|
+
header[2] = len >>> 8;
|
|
171
|
+
header[3] = len & 0xff;
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
header = new Uint8Array(10);
|
|
175
|
+
header[1] = 127;
|
|
176
|
+
const dv = new DataView(header.buffer);
|
|
177
|
+
dv.setUint32(2, Math.floor(len / 0x100000000));
|
|
178
|
+
dv.setUint32(6, len >>> 0);
|
|
179
|
+
}
|
|
180
|
+
header[0] = 0x80 | (opcode & 0x0f);
|
|
181
|
+
const out = new Uint8Array(header.length + len);
|
|
182
|
+
out.set(header, 0);
|
|
183
|
+
out.set(payload, header.length);
|
|
184
|
+
return out;
|
|
185
|
+
}
|
|
186
|
+
const OP_CONT = 0x0, OP_TEXT = 0x1, OP_BIN = 0x2, OP_CLOSE = 0x8, OP_PING = 0x9, OP_PONG = 0xa;
|
|
187
|
+
async function startNodeServer(opts) {
|
|
188
|
+
const http = await import("node:http");
|
|
189
|
+
const crypto = await import("node:crypto");
|
|
190
|
+
const base = makeServerBase(opts);
|
|
191
|
+
const httpServer = http.createServer((_req, res) => {
|
|
192
|
+
res.writeHead(426, { "Content-Type": "text/plain" });
|
|
193
|
+
res.end("WebSocket upgrade required");
|
|
194
|
+
});
|
|
195
|
+
httpServer.on("upgrade", (req, socket) => {
|
|
196
|
+
socket.setNoDelay(true);
|
|
197
|
+
const key = req.headers["sec-websocket-key"];
|
|
198
|
+
const upgrade = req.headers.upgrade?.toLowerCase();
|
|
199
|
+
if (upgrade !== "websocket" || typeof key !== "string") {
|
|
200
|
+
socket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
|
|
201
|
+
socket.destroy();
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const accept = crypto.createHash("sha1").update(key + WS_GUID).digest("base64");
|
|
205
|
+
socket.write("HTTP/1.1 101 Switching Protocols\r\n" +
|
|
206
|
+
"Upgrade: websocket\r\n" +
|
|
207
|
+
"Connection: Upgrade\r\n" +
|
|
208
|
+
`Sec-WebSocket-Accept: ${accept}\r\n\r\n`);
|
|
209
|
+
let closed = false;
|
|
210
|
+
const conn = {
|
|
211
|
+
id: base.nextId(),
|
|
212
|
+
remoteAddress: `${socket.remoteAddress ?? "?"}:${socket.remotePort ?? "?"}`,
|
|
213
|
+
raw: socket,
|
|
214
|
+
send(data) {
|
|
215
|
+
if (closed || socket.destroyed)
|
|
216
|
+
return;
|
|
217
|
+
if (typeof data === "string") {
|
|
218
|
+
socket.write(Buffer.from(encodeWsFrame(OP_TEXT, Buffer.from(data, "utf8"))));
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
const bytes = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
|
|
222
|
+
socket.write(Buffer.from(encodeWsFrame(OP_BIN, bytes)));
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
close(code = 1000, reason = "") {
|
|
226
|
+
if (closed)
|
|
227
|
+
return;
|
|
228
|
+
closed = true;
|
|
229
|
+
const payload = new Uint8Array(2 + reason.length);
|
|
230
|
+
payload[0] = code >>> 8;
|
|
231
|
+
payload[1] = code & 0xff;
|
|
232
|
+
payload.set(new TextEncoder().encode(reason), 2);
|
|
233
|
+
try {
|
|
234
|
+
socket.write(Buffer.from(encodeWsFrame(OP_CLOSE, payload)));
|
|
235
|
+
socket.end();
|
|
236
|
+
}
|
|
237
|
+
catch { /* socket already gone */ }
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
// ── Incremental frame parser ──
|
|
241
|
+
let buf = Buffer.alloc(0);
|
|
242
|
+
let fragOp = -1;
|
|
243
|
+
const frags = [];
|
|
244
|
+
const finishMessage = (op, payload) => {
|
|
245
|
+
if (payload.byteLength > MAX_MESSAGE_SIZE) {
|
|
246
|
+
conn.close(1009, "message too large");
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (op === OP_TEXT)
|
|
250
|
+
base.deliver(conn, payload.toString("utf8"));
|
|
251
|
+
else
|
|
252
|
+
base.deliver(conn, new Uint8Array(payload.buffer, payload.byteOffset, payload.byteLength));
|
|
253
|
+
};
|
|
254
|
+
const handleFrame = (fin, op, payload) => {
|
|
255
|
+
if (op === OP_CLOSE) {
|
|
256
|
+
const code = payload.byteLength >= 2 ? payload.readUInt16BE(0) : 1000;
|
|
257
|
+
const reason = payload.byteLength > 2 ? payload.subarray(2).toString("utf8") : "";
|
|
258
|
+
conn.close(code, reason);
|
|
259
|
+
base.drop(conn, code, reason);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (op === OP_PING) {
|
|
263
|
+
if (!closed && !socket.destroyed)
|
|
264
|
+
socket.write(Buffer.from(encodeWsFrame(OP_PONG, payload)));
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (op === OP_PONG)
|
|
268
|
+
return;
|
|
269
|
+
if (op === OP_CONT && fragOp < 0) {
|
|
270
|
+
conn.close(1002, "unexpected continuation");
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if ((op === OP_TEXT || op === OP_BIN) && fragOp >= 0) {
|
|
274
|
+
conn.close(1002, "interleaved frame");
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (op !== OP_CONT)
|
|
278
|
+
fragOp = op;
|
|
279
|
+
frags.push(payload);
|
|
280
|
+
if (!fin)
|
|
281
|
+
return;
|
|
282
|
+
const full = Buffer.concat(frags);
|
|
283
|
+
frags.length = 0;
|
|
284
|
+
const finalOp = fragOp;
|
|
285
|
+
fragOp = -1;
|
|
286
|
+
finishMessage(finalOp, full);
|
|
287
|
+
};
|
|
288
|
+
socket.on("data", (chunk) => {
|
|
289
|
+
buf = buf.byteLength ? Buffer.concat([buf, chunk]) : chunk;
|
|
290
|
+
// Parse as many complete frames as the buffer holds.
|
|
291
|
+
for (;;) {
|
|
292
|
+
if (buf.byteLength < 2)
|
|
293
|
+
return;
|
|
294
|
+
const b0 = buf[0], b1 = buf[1];
|
|
295
|
+
const fin = (b0 & 0x80) !== 0;
|
|
296
|
+
const op = b0 & 0x0f;
|
|
297
|
+
const masked = (b1 & 0x80) !== 0;
|
|
298
|
+
let len = b1 & 0x7f;
|
|
299
|
+
let off = 2;
|
|
300
|
+
if (len === 126) {
|
|
301
|
+
if (buf.byteLength < off + 2)
|
|
302
|
+
return;
|
|
303
|
+
len = buf.readUInt16BE(off);
|
|
304
|
+
off += 2;
|
|
305
|
+
}
|
|
306
|
+
else if (len === 127) {
|
|
307
|
+
if (buf.byteLength < off + 8)
|
|
308
|
+
return;
|
|
309
|
+
const hi = buf.readUInt32BE(off), lo = buf.readUInt32BE(off + 4);
|
|
310
|
+
off += 8;
|
|
311
|
+
if (hi > 0 || lo > MAX_MESSAGE_SIZE) {
|
|
312
|
+
conn.close(1009, "message too large");
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
len = lo;
|
|
316
|
+
}
|
|
317
|
+
const maskLen = masked ? 4 : 0;
|
|
318
|
+
if (buf.byteLength < off + maskLen + len)
|
|
319
|
+
return;
|
|
320
|
+
let payload = buf.subarray(off + maskLen, off + maskLen + len);
|
|
321
|
+
if (masked) {
|
|
322
|
+
const mask = buf.subarray(off, off + 4);
|
|
323
|
+
const unmasked = Buffer.allocUnsafe(payload.byteLength);
|
|
324
|
+
for (let i = 0; i < payload.byteLength; i++)
|
|
325
|
+
unmasked[i] = payload[i] ^ mask[i & 3];
|
|
326
|
+
payload = unmasked;
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
payload = Buffer.from(payload); // detach — subarray aliases buf
|
|
330
|
+
}
|
|
331
|
+
buf = buf.subarray(off + maskLen + len);
|
|
332
|
+
handleFrame(fin, op, payload);
|
|
333
|
+
if (closed)
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
const onSocketEnd = () => base.drop(conn, 1006, "socket closed");
|
|
338
|
+
socket.on("close", onSocketEnd);
|
|
339
|
+
socket.on("error", onSocketEnd);
|
|
340
|
+
base.conns.add(conn);
|
|
341
|
+
try {
|
|
342
|
+
opts.onConnection?.(conn);
|
|
343
|
+
}
|
|
344
|
+
catch (e) {
|
|
345
|
+
log.error("WsServer", `onConnection threw: ${e}`);
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
await new Promise((resolve, reject) => {
|
|
349
|
+
httpServer.once("error", reject);
|
|
350
|
+
httpServer.listen(opts.port, opts.host ?? "0.0.0.0", () => {
|
|
351
|
+
httpServer.removeListener("error", reject);
|
|
352
|
+
resolve();
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
const addr = httpServer.address();
|
|
356
|
+
const boundPort = typeof addr === "object" && addr ? addr.port : opts.port;
|
|
357
|
+
return {
|
|
358
|
+
port: boundPort,
|
|
359
|
+
host: opts.host ?? "0.0.0.0",
|
|
360
|
+
connections: () => [...base.conns],
|
|
361
|
+
async close() {
|
|
362
|
+
[...base.conns].forEach((c) => c.close(1001, "server shutdown"));
|
|
363
|
+
await new Promise((resolve) => httpServer.close(() => resolve()));
|
|
364
|
+
// httpServer.close waits for open sockets; force-destroy stragglers.
|
|
365
|
+
[...base.conns].forEach((c) => c.raw.destroy());
|
|
366
|
+
base.conns.clear();
|
|
367
|
+
},
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
// ---------------------------------------------------------------------------
|
|
371
|
+
// Entry point
|
|
372
|
+
// ---------------------------------------------------------------------------
|
|
373
|
+
/**
|
|
374
|
+
* Start a WebSocket server. Resolves once the socket is bound and listening.
|
|
375
|
+
* Runtime is picked by capability: Bun.serve → Deno.serve → node:http.
|
|
376
|
+
*/
|
|
377
|
+
export function createWebSocketServer(opts) {
|
|
378
|
+
const g = globalThis;
|
|
379
|
+
const bun = g.Bun;
|
|
380
|
+
if (typeof bun?.serve === "function")
|
|
381
|
+
return startBunServer(opts);
|
|
382
|
+
const deno = g.Deno;
|
|
383
|
+
if (typeof deno?.serve === "function" && typeof deno.upgradeWebSocket === "function") {
|
|
384
|
+
return startDenoServer(opts);
|
|
385
|
+
}
|
|
386
|
+
return startNodeServer(opts);
|
|
387
|
+
}
|
|
@@ -235,6 +235,21 @@ export class UniversalPhysicsAPI {
|
|
|
235
235
|
setIntegrationDt(realmId, dt) {
|
|
236
236
|
this.backend.setIntegrationDt(realmId, dt);
|
|
237
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Change gravity across all realms (applies to near/mid/far). Best-effort:
|
|
240
|
+
* backends that can't mutate realm gravity omit `setRealmGravity`, in which
|
|
241
|
+
* case callers should fall back to per-body `gravityScale` in BodyDesc.
|
|
242
|
+
*/
|
|
243
|
+
setGravity(gravity) {
|
|
244
|
+
[RealmTier.Near, RealmTier.Mid, RealmTier.Far].forEach((tier) => {
|
|
245
|
+
try {
|
|
246
|
+
const realm = this.realmManager.getRealm(tier);
|
|
247
|
+
this.backend.setRealmGravity?.(realm.id, gravity);
|
|
248
|
+
}
|
|
249
|
+
catch { /* realm tier not initialized */ }
|
|
250
|
+
});
|
|
251
|
+
this.config.gravity = gravity;
|
|
252
|
+
}
|
|
238
253
|
// --- Realm ---
|
|
239
254
|
getRealmTier(body) {
|
|
240
255
|
return this.realmManager.getRealmForBody(body);
|
|
@@ -442,6 +442,21 @@ export class RapierPhysicsBackend {
|
|
|
442
442
|
this.lib.setIntegrationDt(realmId, dt);
|
|
443
443
|
}
|
|
444
444
|
}
|
|
445
|
+
/**
|
|
446
|
+
* Change a live realm's gravity. Updates the stored realm config (the
|
|
447
|
+
* no-lib JS fallback stepper reads it) and forwards to the lib when it
|
|
448
|
+
* supports runtime gravity (the WASM Rapier world.gravity is mutable; the
|
|
449
|
+
* native FFI lib may not implement it — callers should treat this as
|
|
450
|
+
* best-effort).
|
|
451
|
+
*/
|
|
452
|
+
setRealmGravity(realmId, gravity) {
|
|
453
|
+
const realm = this.realms.get(realmId);
|
|
454
|
+
if (realm)
|
|
455
|
+
realm.config.gravity = gravity;
|
|
456
|
+
if (this.lib && this.lib.setRealmGravity) {
|
|
457
|
+
this.lib.setRealmGravity(realmId, gravity);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
445
460
|
setSleepThresholds(realmId, linearThreshold, angularThreshold) {
|
|
446
461
|
if (this.lib) {
|
|
447
462
|
this.lib.setSleepThresholds(realmId, linearThreshold, angularThreshold);
|
|
@@ -154,6 +154,11 @@ async function doLoadPhysicsLib() {
|
|
|
154
154
|
entityByBodyHandle.delete(id);
|
|
155
155
|
bodyIdByHandle.delete(id);
|
|
156
156
|
},
|
|
157
|
+
setRealmGravity(id, gravity) {
|
|
158
|
+
const world = realms.get(id);
|
|
159
|
+
if (world)
|
|
160
|
+
world.gravity = { x: gravity[0], y: gravity[1], z: gravity[2] };
|
|
161
|
+
},
|
|
157
162
|
createBody(realmId, bodyId, desc, entity) {
|
|
158
163
|
const world = realms.get(realmId);
|
|
159
164
|
if (!world)
|
|
@@ -443,9 +443,7 @@ export class PostProcessStack {
|
|
|
443
443
|
createGlyphAtlas() {
|
|
444
444
|
const chars = " .:-=+*#%@";
|
|
445
445
|
const gw = 16, gh = 16, cols = chars.length;
|
|
446
|
-
const canvas =
|
|
447
|
-
canvas.width = cols * gw;
|
|
448
|
-
canvas.height = gh;
|
|
446
|
+
const canvas = new OffscreenCanvas(cols * gw, gh);
|
|
449
447
|
const ctx = canvas.getContext("2d");
|
|
450
448
|
ctx.fillStyle = "black";
|
|
451
449
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
@@ -72,32 +72,18 @@ export var Material;
|
|
|
72
72
|
// --- Ingredients ---
|
|
73
73
|
// Ice (frozen water — produced by cooling)
|
|
74
74
|
Material[Material["Ice"] = 64] = "Ice";
|
|
75
|
+
// (IDs 67–68, 71–73, 76–77, 79–81, 83–88 were inert alchemy materials —
|
|
76
|
+
// retired; keep the gaps so old saves don't remap onto new materials.)
|
|
75
77
|
// Liquids
|
|
76
78
|
Material[Material["Ether"] = 65] = "Ether";
|
|
77
79
|
Material[Material["Blood"] = 66] = "Blood";
|
|
78
|
-
Material[Material["Syrup"] = 67] = "Syrup";
|
|
79
|
-
Material[Material["NightshadeExtract"] = 68] = "NightshadeExtract";
|
|
80
80
|
Material[Material["TrollBlood"] = 69] = "TrollBlood";
|
|
81
81
|
Material[Material["LiquidShadow"] = 70] = "LiquidShadow";
|
|
82
|
-
Material[Material["LoveEssence"] = 71] = "LoveEssence";
|
|
83
|
-
Material[Material["HateEssence"] = 72] = "HateEssence";
|
|
84
|
-
Material[Material["DreamMist"] = 73] = "DreamMist";
|
|
85
82
|
Material[Material["VoidEssence"] = 74] = "VoidEssence";
|
|
86
83
|
// Powders (solid, fall)
|
|
87
84
|
Material[Material["Sulfur"] = 75] = "Sulfur";
|
|
88
|
-
Material[Material["GroundEyeOfNewt"] = 76] = "GroundEyeOfNewt";
|
|
89
|
-
Material[Material["GroundBatWing"] = 77] = "GroundBatWing";
|
|
90
85
|
Material[Material["BoneDust"] = 78] = "BoneDust";
|
|
91
|
-
Material[Material["IronFilings"] = 79] = "IronFilings";
|
|
92
|
-
Material[Material["MoonstoneDust"] = 80] = "MoonstoneDust";
|
|
93
|
-
Material[Material["CrystalDust"] = 81] = "CrystalDust";
|
|
94
86
|
Material[Material["MushroomSpores"] = 82] = "MushroomSpores";
|
|
95
|
-
Material[Material["DragonScale"] = 83] = "DragonScale";
|
|
96
|
-
Material[Material["PhoenixFeather"] = 84] = "PhoenixFeather";
|
|
97
|
-
Material[Material["UnicornHorn"] = 85] = "UnicornHorn";
|
|
98
|
-
Material[Material["MandrakeRoot"] = 86] = "MandrakeRoot";
|
|
99
|
-
Material[Material["SpiderSilk"] = 87] = "SpiderSilk";
|
|
100
|
-
Material[Material["GraveDust"] = 88] = "GraveDust";
|
|
101
87
|
Material[Material["StarShard"] = 89] = "StarShard";
|
|
102
88
|
Material[Material["TimeSand"] = 90] = "TimeSand";
|
|
103
89
|
// Gases
|
|
@@ -232,29 +218,13 @@ export const MATERIALS = {
|
|
|
232
218
|
// --- Liquids ---
|
|
233
219
|
[Material.Ether]: def(65, "Ether", [0.55, 0.45, 0.85, 0.8], { gravity: 1.5, gravityDir: 1, density: 0.7, liquid: true, flammable: true, burnTime: 40, albedo: 0.2, reflectivity: 0.4, brightness: 1.1 }),
|
|
234
220
|
[Material.Blood]: def(66, "Blood", [0.7, 0.05, 0.05, 0.95], { gravity: 2, gravityDir: 1, density: 1.06, liquid: true, albedo: 0.3, reflectivity: 0.2, brightness: 0.8 }),
|
|
235
|
-
[Material.Syrup]: def(67, "Syrup", [0.6, 0.4, 0.15, 0.95], { gravity: 1, gravityDir: 1, density: 1.4, liquid: true, albedo: 0.3, reflectivity: 0.3, brightness: 0.9 }),
|
|
236
|
-
[Material.NightshadeExtract]: def(68, "Nightshade Extract", [0.3, 0.05, 0.35, 0.95], { gravity: 2, gravityDir: 1, density: 1.1, liquid: true, albedo: 0.2, reflectivity: 0.1, brightness: 0.7 }),
|
|
237
221
|
[Material.TrollBlood]: def(69, "Troll Blood", [0.4, 0.2, 0.15, 0.95], { gravity: 2, gravityDir: 1, density: 1.2, liquid: true, albedo: 0.25, reflectivity: 0.15, brightness: 0.8 }),
|
|
238
222
|
[Material.LiquidShadow]: def(70, "Liquid Shadow", [0.05, 0.05, 0.12, 0.98], { gravity: 3, gravityDir: 1, density: 2.0, liquid: true, albedo: 0.0, reflectivity: 0.0, brightness: 0.3 }),
|
|
239
|
-
[Material.LoveEssence]: def(71, "Love Essence", [0.95, 0.4, 0.6, 0.9], { gravity: 1.5, gravityDir: 1, density: 0.9, liquid: true, albedo: 0.3, reflectivity: 0.4, brightness: 1.0 }),
|
|
240
|
-
[Material.HateEssence]: def(72, "Hate Essence", [0.2, 0.1, 0.15, 0.9], { gravity: 2, gravityDir: 1, density: 1.3, liquid: true, albedo: 0.2, reflectivity: 0.2, brightness: 0.7 }),
|
|
241
|
-
[Material.DreamMist]: def(73, "Dream Mist", [0.6, 0.7, 0.95, 0.6], { gravity: 1, gravityDir: -1, density: 0.3, liquid: true, albedo: 0.2, reflectivity: 0.5, brightness: 1.2 }),
|
|
242
223
|
[Material.VoidEssence]: def(74, "Void Essence", [0.1, 0.0, 0.2, 0.95], { gravity: 4, gravityDir: 1, density: 3.0, liquid: true, albedo: 0.0, reflectivity: 0.1, brightness: 0.5 }),
|
|
243
224
|
// --- Alchemy game: powders (solid, fall) ---
|
|
244
225
|
[Material.Sulfur]: def(75, "Sulfur", [0.9, 0.85, 0.2, 1.0], { gravity: 1, gravityDir: 1, density: 2.0, solid: true, flammable: true, burnTime: 60, albedo: 0.5, reflectivity: 0.1, brightness: 0.9 }),
|
|
245
|
-
[Material.GroundEyeOfNewt]: def(76, "Ground Eye of Newt", [0.5, 0.35, 0.2, 1.0], { gravity: 1, gravityDir: 1, density: 1.2, solid: true, albedo: 0.4, brightness: 0.8 }),
|
|
246
|
-
[Material.GroundBatWing]: def(77, "Ground Bat Wing", [0.3, 0.25, 0.35, 1.0], { gravity: 0.8, gravityDir: 1, density: 0.6, solid: true, albedo: 0.3, brightness: 0.7 }),
|
|
247
226
|
[Material.BoneDust]: def(78, "Bone Dust", [0.85, 0.82, 0.75, 1.0], { gravity: 1, gravityDir: 1, density: 1.5, solid: true, albedo: 0.5, brightness: 0.8 }),
|
|
248
|
-
[Material.IronFilings]: def(79, "Iron Filings", [0.55, 0.55, 0.6, 1.0], { gravity: 1.5, gravityDir: 1, density: 7.8, solid: true, magnetic: true, albedo: 0.5, reflectivity: 0.3 }),
|
|
249
|
-
[Material.MoonstoneDust]: def(80, "Moonstone Dust", [0.8, 0.85, 0.95, 1.0], { gravity: 1, gravityDir: 1, density: 1.0, solid: true, albedo: 0.4, reflectivity: 0.5, brightness: 1.3 }),
|
|
250
|
-
[Material.CrystalDust]: def(81, "Crystal Dust", [0.6, 0.9, 0.95, 1.0], { gravity: 1, gravityDir: 1, density: 2.5, solid: true, albedo: 0.3, reflectivity: 0.6, brightness: 1.2 }),
|
|
251
227
|
[Material.MushroomSpores]: def(82, "Mushroom Spores", [0.6, 0.5, 0.4, 0.9], { gravity: 0.5, gravityDir: 1, density: 0.4, solid: true, albedo: 0.3, brightness: 0.8 }),
|
|
252
|
-
[Material.DragonScale]: def(83, "Dragon Scale", [0.5, 0.15, 0.1, 1.0], { gravity: 1.5, gravityDir: 1, density: 5.0, solid: true, albedo: 0.4, reflectivity: 0.3, brightness: 1.0 }),
|
|
253
|
-
[Material.PhoenixFeather]: def(84, "Phoenix Feather", [0.95, 0.5, 0.15, 0.9], { gravity: 0.3, gravityDir: 1, density: 0.3, solid: true, flammable: true, burnTime: 200, albedo: 0.4, brightness: 1.4 }),
|
|
254
|
-
[Material.UnicornHorn]: def(85, "Unicorn Horn", [0.95, 0.9, 1.0, 1.0], { gravity: 1, gravityDir: 1, density: 1.8, solid: true, albedo: 0.5, reflectivity: 0.5, brightness: 1.5 }),
|
|
255
|
-
[Material.MandrakeRoot]: def(86, "Mandrake Root", [0.5, 0.4, 0.25, 1.0], { gravity: 1, gravityDir: 1, density: 1.3, solid: true, albedo: 0.4, brightness: 0.7 }),
|
|
256
|
-
[Material.SpiderSilk]: def(87, "Spider Silk", [0.9, 0.9, 0.85, 0.8], { gravity: 0.2, gravityDir: 1, density: 0.2, solid: true, albedo: 0.4, reflectivity: 0.2, brightness: 0.9 }),
|
|
257
|
-
[Material.GraveDust]: def(88, "Grave Dust", [0.3, 0.28, 0.25, 1.0], { gravity: 1, gravityDir: 1, density: 1.8, solid: true, albedo: 0.3, brightness: 0.6 }),
|
|
258
228
|
[Material.StarShard]: def(89, "Star Shard", [0.7, 0.8, 1.0, 1.0], { gravity: 1, gravityDir: 1, density: 2.0, solid: true, albedo: 0.3, reflectivity: 0.7, brightness: 1.6 }),
|
|
259
229
|
[Material.TimeSand]: def(90, "Time Sand", [0.4, 0.3, 0.6, 0.9], { gravity: 1, gravityDir: 1, density: 1.5, solid: true, albedo: 0.3, reflectivity: 0.4, brightness: 1.2 }),
|
|
260
230
|
// --- Alchemy game: gases + byproducts ---
|
|
@@ -2207,9 +2207,6 @@ export class SandWorld {
|
|
|
2207
2207
|
continue;
|
|
2208
2208
|
}
|
|
2209
2209
|
}
|
|
2210
|
-
// --- PhoenixFeather near fire/lava: glows brighter, resists burning ---
|
|
2211
|
-
// (flammable but long burnTime already set; here we just let it shimmer)
|
|
2212
|
-
// --- DragonScale: very dense, sinks through liquids (handled by density) ---
|
|
2213
2210
|
// --- LiquidShadow + light source (fire/plasma) → dissipates to smoke ---
|
|
2214
2211
|
if (mat === Material.LiquidShadow) {
|
|
2215
2212
|
let lightNi = -1;
|
|
@@ -3,16 +3,15 @@
|
|
|
3
3
|
// engine systems, regardless of which thread they run on.
|
|
4
4
|
//
|
|
5
5
|
// Realm detection determines transport:
|
|
6
|
-
// - Main realm:
|
|
6
|
+
// - Main realm: feeds/commands registered directly on
|
|
7
7
|
// window.__sceneInspector via DevToolsDataBridge.
|
|
8
|
-
// - Worker realm:
|
|
9
|
-
//
|
|
10
|
-
// synchronous reads). Commands are forwarded via worker RPC.
|
|
11
|
-
// declarations are synced to the renderer via a one-time manifest RPC.
|
|
8
|
+
// - Worker realm: feeds/commands registered in a worker-side registry.
|
|
9
|
+
// Data feeds are written to a devtools SAB region (zero-copy,
|
|
10
|
+
// synchronous reads). Commands are forwarded via worker RPC.
|
|
12
11
|
//
|
|
13
12
|
// Plugins import `devtools` from @downdraft/engine/modules/devtools and call
|
|
14
|
-
//
|
|
15
|
-
//
|
|
13
|
+
// registerDataFeed/registerCommand/registerSABStat — the same API works in
|
|
14
|
+
// both realms.
|
|
16
15
|
// ============================================================================
|
|
17
16
|
import { resourceToken } from "@downdraft/engine/ecs/resource";
|
|
18
17
|
import { createLogger } from "@downdraft/engine/util/logger";
|
|
@@ -50,7 +49,7 @@ export const DEVTOOLS_REALM = detectRealm();
|
|
|
50
49
|
// synchronously. No RPC polling needed.
|
|
51
50
|
//
|
|
52
51
|
// Layout:
|
|
53
|
-
// [0..3] manifestVersion: Uint32 (bumped when
|
|
52
|
+
// [0..3] manifestVersion: Uint32 (bumped when feeds/commands change)
|
|
54
53
|
// [4..7] feedCount: Uint32
|
|
55
54
|
// [8..] feed entries: each is [lengthBytes: Uint32][JSON bytes...]
|
|
56
55
|
// (lengthBytes = 0 means "not yet written" / "unavailable")
|
|
@@ -81,8 +80,6 @@ export function allocateDevToolsSAB(maxFeedBlobBytes, statsBytes) {
|
|
|
81
80
|
}
|
|
82
81
|
class DevToolsAPIImpl {
|
|
83
82
|
realm;
|
|
84
|
-
panels = new Map();
|
|
85
|
-
toggles = new Map();
|
|
86
83
|
dataFeeds = new Map();
|
|
87
84
|
dataFeedNames = []; // ordered list for feedIndex assignment
|
|
88
85
|
commands = new Map();
|
|
@@ -99,14 +96,6 @@ class DevToolsAPIImpl {
|
|
|
99
96
|
constructor(realm) {
|
|
100
97
|
this.realm = realm;
|
|
101
98
|
}
|
|
102
|
-
registerPanel(ext) {
|
|
103
|
-
this.panels.set(ext.id, ext);
|
|
104
|
-
this.bumpManifest();
|
|
105
|
-
}
|
|
106
|
-
registerOverlayToggle(toggle) {
|
|
107
|
-
this.toggles.set(toggle.id, toggle);
|
|
108
|
-
this.bumpManifest();
|
|
109
|
-
}
|
|
110
99
|
registerDataFeed(name, fn, writeRateHz = 10) {
|
|
111
100
|
if (this.dataFeeds.has(name)) {
|
|
112
101
|
// Update fn/rate, keep feedIndex and hasWritten
|
|
@@ -183,8 +172,6 @@ class DevToolsAPIImpl {
|
|
|
183
172
|
}
|
|
184
173
|
getManifest() {
|
|
185
174
|
return {
|
|
186
|
-
panels: Array.from(this.panels.values()),
|
|
187
|
-
toggles: Array.from(this.toggles.values()),
|
|
188
175
|
dataFeeds: this.dataFeedNames.map((name, i) => ({
|
|
189
176
|
name,
|
|
190
177
|
feedIndex: i,
|
|
@@ -276,8 +263,6 @@ class DevToolsAPIImpl {
|
|
|
276
263
|
return Array.from(this.sabStats.keys());
|
|
277
264
|
}
|
|
278
265
|
clear() {
|
|
279
|
-
this.panels.clear();
|
|
280
|
-
this.toggles.clear();
|
|
281
266
|
this.dataFeeds.clear();
|
|
282
267
|
this.dataFeedNames = [];
|
|
283
268
|
this.commands.clear();
|
|
@@ -301,7 +286,7 @@ const _devtools = new DevToolsAPIImpl(DEVTOOLS_REALM);
|
|
|
301
286
|
* the appropriate transport for data feeds (direct call vs SAB) and
|
|
302
287
|
* commands (direct call vs RPC forwarding).
|
|
303
288
|
*
|
|
304
|
-
* Plugins import this and call
|
|
289
|
+
* Plugins import this and call registerDataFeed/registerCommand.
|
|
305
290
|
* The same code works in both realms.
|
|
306
291
|
*/
|
|
307
292
|
export const devtools = _devtools;
|
|
@@ -28,14 +28,6 @@ export class DevToolsDataBridge {
|
|
|
28
28
|
getPerformanceMetricsProvider() {
|
|
29
29
|
return null;
|
|
30
30
|
}
|
|
31
|
-
/** Games override this to declare custom DevTools panel tabs. */
|
|
32
|
-
getPanelExtensions() {
|
|
33
|
-
return [];
|
|
34
|
-
}
|
|
35
|
-
/** Games override this to declare custom overlay toggles. */
|
|
36
|
-
getOverlayToggles() {
|
|
37
|
-
return [];
|
|
38
|
-
}
|
|
39
31
|
/** Games override this to expose sim stats + controls (pause/step/speed/clear). */
|
|
40
32
|
getSimStatsProvider() {
|
|
41
33
|
return null;
|
|
@@ -208,9 +200,6 @@ export class DevToolsDataBridge {
|
|
|
208
200
|
getGPUAdapterInfo: () => {
|
|
209
201
|
return this.cachedGpuAdapterInfo;
|
|
210
202
|
},
|
|
211
|
-
// --- Panel extensions (game-specific tabs and overlay toggles) ---
|
|
212
|
-
getPanelExtensions: () => this.getPanelExtensions(),
|
|
213
|
-
getOverlayToggles: () => this.getOverlayToggles(),
|
|
214
203
|
// --- Sim stats & controls (optional — games implement ISimStatsProvider) ---
|
|
215
204
|
getSimStats: () => this.getSimStatsProvider()?.getSimStats() ?? null,
|
|
216
205
|
pauseSim: () => { this.getSimStatsProvider()?.pauseSim(); },
|
|
@@ -8,17 +8,13 @@ export { SkeletonRenderer } from "./skeleton-renderer.js";
|
|
|
8
8
|
export { useDebugStore } from "./debug-store.js";
|
|
9
9
|
export { useSceneStore } from "./scene-store.js";
|
|
10
10
|
// Debug overlays (generic rendering, game provides data via interfaces)
|
|
11
|
-
export { DebugOverlay } from "./debug-overlay.js";
|
|
12
11
|
export { DebugRaycast } from "./debug-raycast.js";
|
|
13
|
-
export { LabelOverlay } from "./label-overlay.js";
|
|
14
12
|
export { SceneSync } from "./scene-sync.js";
|
|
15
13
|
// Asset utilities
|
|
16
14
|
export { asyncFetchArrayBuffer, bufferCache, evictThumbnailCache, findBinForGLTF, findMTLForOBJ, syncFetchArrayBuffer, thumbnailCache } from "./asset-utils.js";
|
|
17
15
|
// Data bridge (non-abstract, data-feeds only) & base inspector & interfaces
|
|
18
16
|
export { DevToolsDataBridge } from "./data-bridge.js";
|
|
19
|
-
export { createDoctorPanelExtension } from "./doctor-panel.js";
|
|
20
17
|
export { BaseSceneInspector } from "./scene-inspector.js";
|
|
21
|
-
export { createSimStatsPanelExtension } from "./sim-stats-panel.js";
|
|
22
18
|
// Unified DevTools API — single registration surface for plugins, games, and
|
|
23
19
|
// engine systems. Auto-detects realm (main vs worker) and chooses transport.
|
|
24
20
|
export { allocateDevToolsSAB, computeDevToolsSABLayout, devtools, DEVTOOLS_REALM, DEVTOOLS_SAB_MAX_FEEDS } from "./api.js";
|
|
@@ -39,8 +35,6 @@ export { ProfilingBridge } from "./profiling-bridge.js";
|
|
|
39
35
|
export { wireProfilingBridge } from "./profiling-hooks.js";
|
|
40
36
|
// Debug view descriptors — declarative registration of profiler overlay views
|
|
41
37
|
export { BUILTIN_VIEW_DESCRIPTORS } from "./debug-view-descriptors.js";
|
|
42
|
-
// Material stats panel — reusable DevTools tab for the material system
|
|
43
|
-
export { createMaterialStatsPanelExtension } from "./material-stats-panel.js";
|
|
44
38
|
// Blitz devtools — docked in-window DevTools UI (F12)
|
|
45
39
|
export { BlitzDevtoolsHost, createSelfHostedDevtoolsUi } from "./blitz/host.js";
|
|
46
40
|
export { BlitzDevtoolsTok, createDevtoolsUiModule } from "./blitz/module.js";
|