@downdraft/engine 0.2.0 → 0.3.1
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/index.ts +2 -2
- 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/platform/rpc.ts +4 -1
- package/core/src/render/canvas-resize-watcher.ts +1 -10
- package/core/src/render/game-renderer.ts +164 -79
- 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/sab/grid-buffer.ts +55 -2
- package/core/src/sab/record-buffer.ts +48 -5
- package/core/src/sab/sim-channel.ts +181 -17
- package/core/src/sab/slot-buffer.ts +55 -1
- package/core/src/sab/types.ts +11 -0
- package/core/src/telemetry/gpu-resource-tracker.ts +24 -0
- package/core/src/ui-font-scale.ts +5 -38
- package/core/src/util/index.ts +1 -0
- package/core/src/util/session-timer.ts +23 -0
- package/core/src/worker/base-worker-host.ts +2 -1
- package/core/src/worker/buffer-sync.ts +20 -13
- package/core/src/worker/rpc.ts +119 -5
- package/core/src/worker/sim-worker-base.ts +5 -2
- package/core/src/worker/task-pool.ts +4 -0
- package/dist/.dd-manifest.json +893 -908
- 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/index.js +2 -2
- package/dist/core/src/module/host.js +3 -6
- package/dist/core/src/platform/rpc.js +2 -1
- package/dist/core/src/render/canvas-resize-watcher.js +1 -10
- package/dist/core/src/render/game-renderer.js +166 -74
- 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/sab/grid-buffer.js +53 -1
- package/dist/core/src/sab/record-buffer.js +49 -5
- package/dist/core/src/sab/sim-channel.js +173 -17
- package/dist/core/src/sab/slot-buffer.js +54 -1
- package/dist/core/src/telemetry/gpu-resource-tracker.js +26 -0
- package/dist/core/src/ui-font-scale.js +5 -38
- package/dist/core/src/util/index.js +1 -0
- package/dist/core/src/util/session-timer.js +22 -0
- package/dist/core/src/worker/base-worker-host.js +2 -1
- package/dist/core/src/worker/buffer-sync.js +16 -9
- package/dist/core/src/worker/rpc.js +105 -8
- package/dist/core/src/worker/sim-worker-base.js +5 -2
- package/dist/core/src/worker/task-pool.js +5 -0
- package/dist/libraries/blitz-ui/src/native-module.js +8 -1
- package/dist/libraries/devtools/src/backend.js +35 -9
- package/dist/libraries/devtools/src/collectors.js +73 -22
- 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/blitz/fonts.js +75 -0
- package/dist/modules/devtools/src/blitz/host.js +124 -30
- package/dist/modules/devtools/src/blitz/panels/console.js +19 -7
- package/dist/modules/devtools/src/blitz/panels/elements.js +217 -28
- package/dist/modules/devtools/src/blitz/panels/gpu.js +5 -2
- package/dist/modules/devtools/src/blitz/panels/performance.js +26 -12
- package/dist/modules/devtools/src/blitz/panels/snapshot.js +31 -8
- package/dist/modules/devtools/src/blitz/panels/types.js +2 -1
- package/dist/modules/devtools/src/blitz/theme.js +26 -4
- 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/modules/html-ui/src/doc-backend.js +52 -3
- package/dist/modules/html-ui/src/host.js +6 -3
- package/dist/test/src/vision-test.js +1 -3
- package/libraries/blitz-ui/native/Cargo.toml +6 -0
- package/libraries/blitz-ui/native-osr/Cargo.toml +3 -1
- package/libraries/blitz-ui/src/native-module.ts +8 -1
- package/libraries/devtools/src/backend.ts +38 -12
- package/libraries/devtools/src/collectors.ts +75 -22
- 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/blitz/fonts.ts +78 -0
- package/modules/devtools/src/blitz/host.ts +107 -31
- package/modules/devtools/src/blitz/panels/console.ts +20 -8
- package/modules/devtools/src/blitz/panels/elements.ts +212 -31
- package/modules/devtools/src/blitz/panels/gpu.ts +4 -2
- package/modules/devtools/src/blitz/panels/performance.ts +24 -12
- package/modules/devtools/src/blitz/panels/snapshot.ts +30 -8
- package/modules/devtools/src/blitz/panels/types.ts +2 -1
- package/modules/devtools/src/blitz/theme.ts +26 -4
- 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/modules/html-ui/src/doc-backend.ts +67 -4
- package/modules/html-ui/src/host.ts +11 -3
- package/modules/html-ui/src/protocol.ts +1 -1
- 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
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// SabCanvasOverlay — DOM <canvas> overlay backed by a SharedArrayBuffer bitmap
|
|
3
|
-
//
|
|
4
|
-
// Generalized from overburden's MapCanvas. Provides the generic shell:
|
|
5
|
-
// - A visible <canvas> fixed-positioned over the game canvas
|
|
6
|
-
// (pointer-events: none, configurable z-index, hidden while opacity <= 0).
|
|
7
|
-
// - An offscreen "bitmap" canvas at a fixed data resolution that is repainted
|
|
8
|
-
// only when a SAB sequence counter changes (via getSeq + redrawBitmap).
|
|
9
|
-
// - Per-frame window resize tracking and opacity control.
|
|
10
|
-
//
|
|
11
|
-
// The game supplies the compositing + drawing logic via callbacks; the overlay
|
|
12
|
-
// owns all DOM plumbing.
|
|
13
|
-
// ============================================================================
|
|
14
|
-
export class SabCanvasOverlay {
|
|
15
|
-
canvas;
|
|
16
|
-
ctx;
|
|
17
|
-
/** Offscreen bitmap canvas (fixed data resolution). Null when unconfigured. */
|
|
18
|
-
bitmapCanvas = null;
|
|
19
|
-
bitmapCtx = null;
|
|
20
|
-
opts;
|
|
21
|
-
lastSeq = -1;
|
|
22
|
-
constructor(opts) {
|
|
23
|
-
this.opts = opts;
|
|
24
|
-
this.canvas = document.createElement("canvas");
|
|
25
|
-
this.canvas.id = opts.id ?? "sab-canvas-overlay";
|
|
26
|
-
this.canvas.style.position = "fixed";
|
|
27
|
-
this.canvas.style.top = "0";
|
|
28
|
-
this.canvas.style.left = "0";
|
|
29
|
-
this.canvas.style.width = "100%";
|
|
30
|
-
this.canvas.style.height = "100%";
|
|
31
|
-
this.canvas.style.pointerEvents = "none";
|
|
32
|
-
this.canvas.style.zIndex = String(opts.zIndex ?? 55);
|
|
33
|
-
this.canvas.style.display = "none"; // hidden until opacity > 0
|
|
34
|
-
this.canvas.style.imageRendering = opts.imageRendering ?? "pixelated";
|
|
35
|
-
this.ctx = this.canvas.getContext("2d");
|
|
36
|
-
if (opts.bitmapWidth !== undefined && opts.bitmapHeight !== undefined) {
|
|
37
|
-
this.bitmapCanvas = document.createElement("canvas");
|
|
38
|
-
this.bitmapCanvas.width = opts.bitmapWidth;
|
|
39
|
-
this.bitmapCanvas.height = opts.bitmapHeight;
|
|
40
|
-
this.bitmapCtx = this.bitmapCanvas.getContext("2d");
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
/** Attach the canvas to the DOM (call once during init). */
|
|
44
|
-
mount(parent = document.body) {
|
|
45
|
-
parent.appendChild(this.canvas);
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Per-frame update. Call from the render loop.
|
|
49
|
-
* Hides the canvas while `opacity <= 0`; otherwise resizes the canvas to the
|
|
50
|
-
* window, repaints the bitmap when the seq counter advanced, and invokes
|
|
51
|
-
* the draw callback.
|
|
52
|
-
*/
|
|
53
|
-
update(opacity) {
|
|
54
|
-
if (opacity <= 0) {
|
|
55
|
-
this.canvas.style.display = "none";
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
this.canvas.style.display = "block";
|
|
59
|
-
this.canvas.style.opacity = String(opacity);
|
|
60
|
-
// Resize the visible canvas to match the window if needed.
|
|
61
|
-
const w = window.innerWidth;
|
|
62
|
-
const h = window.innerHeight;
|
|
63
|
-
if (this.canvas.width !== w || this.canvas.height !== h) {
|
|
64
|
-
this.canvas.width = w;
|
|
65
|
-
this.canvas.height = h;
|
|
66
|
-
}
|
|
67
|
-
// Repaint the bitmap if the backing data has a new seq value.
|
|
68
|
-
const seq = this.opts.getSeq?.();
|
|
69
|
-
if (this.bitmapCtx && (seq === undefined || seq !== this.lastSeq)) {
|
|
70
|
-
if (seq !== undefined)
|
|
71
|
-
this.lastSeq = seq;
|
|
72
|
-
this.opts.redrawBitmap?.(this.bitmapCtx, this.bitmapCanvas.width, this.bitmapCanvas.height);
|
|
73
|
-
}
|
|
74
|
-
this.opts.draw(this.ctx, this.bitmapCanvas, w, h);
|
|
75
|
-
}
|
|
76
|
-
/** Mark the bitmap stale so it repaints on the next visible frame. */
|
|
77
|
-
invalidate() {
|
|
78
|
-
this.lastSeq = -1;
|
|
79
|
-
}
|
|
80
|
-
dispose() {
|
|
81
|
-
this.canvas.remove();
|
|
82
|
-
}
|
|
83
|
-
}
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// DebugOverlay — 2D canvas overlay for chunk grid + velocity arrows
|
|
3
|
-
// Generic rendering logic; game provides data via IDebugOverlayData interface.
|
|
4
|
-
// ============================================================================
|
|
5
|
-
import { calculateViewProj, CanvasResizeWatcher } from "@downdraft/engine";
|
|
6
|
-
export class DebugOverlay {
|
|
7
|
-
canvas;
|
|
8
|
-
overlay;
|
|
9
|
-
ctx;
|
|
10
|
-
showChunkGrid = false;
|
|
11
|
-
showVelocityArrows = false;
|
|
12
|
-
resizeWatcher;
|
|
13
|
-
constructor(surface) {
|
|
14
|
-
// DOM-overlay widget — the canvas/element members it touches
|
|
15
|
-
// (parentElement, 2d overlay canvas) are DOM-host concepts; on native
|
|
16
|
-
// they degrade to the compat stubs (display:none anyway).
|
|
17
|
-
const canvas = surface;
|
|
18
|
-
this.canvas = canvas;
|
|
19
|
-
this.overlay = document.createElement("canvas");
|
|
20
|
-
this.overlay.className = "debug-overlay";
|
|
21
|
-
this.overlay.style.cssText = `
|
|
22
|
-
position: absolute;
|
|
23
|
-
top: 0; left: 0;
|
|
24
|
-
width: 100%; height: 100%;
|
|
25
|
-
pointer-events: none;
|
|
26
|
-
z-index: 0;
|
|
27
|
-
display: none;
|
|
28
|
-
`;
|
|
29
|
-
const parent = canvas.parentElement;
|
|
30
|
-
if (parent) {
|
|
31
|
-
parent.style.position = parent.style.position || "relative";
|
|
32
|
-
parent.appendChild(this.overlay);
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
document.body.appendChild(this.overlay);
|
|
36
|
-
}
|
|
37
|
-
this.ctx = this.overlay.getContext("2d");
|
|
38
|
-
this.resizeWatcher = new CanvasResizeWatcher(canvas, {
|
|
39
|
-
onResize: (w, h) => {
|
|
40
|
-
if (this.overlay.width !== w || this.overlay.height !== h) {
|
|
41
|
-
this.overlay.width = w;
|
|
42
|
-
this.overlay.height = h;
|
|
43
|
-
}
|
|
44
|
-
},
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
setShowChunkGrid(show) {
|
|
48
|
-
this.showChunkGrid = show;
|
|
49
|
-
this.updateDisplay();
|
|
50
|
-
}
|
|
51
|
-
setShowVelocityArrows(show) {
|
|
52
|
-
this.showVelocityArrows = show;
|
|
53
|
-
this.updateDisplay();
|
|
54
|
-
}
|
|
55
|
-
isChunkGridVisible() {
|
|
56
|
-
return this.showChunkGrid;
|
|
57
|
-
}
|
|
58
|
-
isVelocityArrowsVisible() {
|
|
59
|
-
return this.showVelocityArrows;
|
|
60
|
-
}
|
|
61
|
-
updateDisplay() {
|
|
62
|
-
const active = this.showChunkGrid || this.showVelocityArrows;
|
|
63
|
-
this.overlay.style.display = active ? "block" : "none";
|
|
64
|
-
if (!active) {
|
|
65
|
-
this.ctx.clearRect(0, 0, this.overlay.width, this.overlay.height);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
projectToScreen(worldX, worldY, worldZ, viewProj, canvasW, canvasH) {
|
|
69
|
-
const x = viewProj[0] * worldX + viewProj[4] * worldY + viewProj[8] * worldZ + viewProj[12];
|
|
70
|
-
const y = viewProj[1] * worldX + viewProj[5] * worldY + viewProj[9] * worldZ + viewProj[13];
|
|
71
|
-
const w = viewProj[3] * worldX + viewProj[7] * worldY + viewProj[11] * worldZ + viewProj[15];
|
|
72
|
-
if (w <= 0)
|
|
73
|
-
return { x: 0, y: 0, behind: true };
|
|
74
|
-
const ndcX = x / w;
|
|
75
|
-
const ndcY = y / w;
|
|
76
|
-
const screenX = (ndcX + 1) * 0.5 * canvasW;
|
|
77
|
-
const screenY = (1 - ndcY) * 0.5 * canvasH;
|
|
78
|
-
return { x: screenX, y: screenY, behind: false };
|
|
79
|
-
}
|
|
80
|
-
update(camera, data) {
|
|
81
|
-
if (!this.showChunkGrid && !this.showVelocityArrows)
|
|
82
|
-
return;
|
|
83
|
-
const ctx = this.ctx;
|
|
84
|
-
const canvasW = this.overlay.width;
|
|
85
|
-
const canvasH = this.overlay.height;
|
|
86
|
-
ctx.clearRect(0, 0, canvasW, canvasH);
|
|
87
|
-
const viewProj = calculateViewProj(camera);
|
|
88
|
-
if (this.showChunkGrid) {
|
|
89
|
-
this.renderChunkGrid(ctx, camera, viewProj, canvasW, canvasH, data);
|
|
90
|
-
}
|
|
91
|
-
if (this.showVelocityArrows) {
|
|
92
|
-
this.renderVelocityArrows(ctx, viewProj, canvasW, canvasH, data);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
renderChunkGrid(ctx, camera, viewProj, canvasW, canvasH, data) {
|
|
96
|
-
const playerPos = data.getPlayerPosition();
|
|
97
|
-
if (!playerPos)
|
|
98
|
-
return;
|
|
99
|
-
const { chunkSize, chunksVisible } = data.getChunkGridConfig();
|
|
100
|
-
const playerChunkX = Math.floor(playerPos.x / chunkSize);
|
|
101
|
-
const playerChunkZ = Math.floor(playerPos.z / chunkSize);
|
|
102
|
-
const half = Math.ceil(chunksVisible / 2);
|
|
103
|
-
ctx.strokeStyle = "rgba(100, 200, 255, 0.35)";
|
|
104
|
-
ctx.lineWidth = 1;
|
|
105
|
-
ctx.font = "10px monospace";
|
|
106
|
-
ctx.fillStyle = "rgba(100, 200, 255, 0.6)";
|
|
107
|
-
for (let cx = playerChunkX - half; cx <= playerChunkX + half; cx++) {
|
|
108
|
-
for (let cz = playerChunkZ - half; cz <= playerChunkZ + half; cz++) {
|
|
109
|
-
const worldX = cx * chunkSize;
|
|
110
|
-
const worldZ = cz * chunkSize;
|
|
111
|
-
const corners = [
|
|
112
|
-
this.projectToScreen(worldX, 0, worldZ, viewProj, canvasW, canvasH),
|
|
113
|
-
this.projectToScreen(worldX + chunkSize, 0, worldZ, viewProj, canvasW, canvasH),
|
|
114
|
-
this.projectToScreen(worldX + chunkSize, 0, worldZ + chunkSize, viewProj, canvasW, canvasH),
|
|
115
|
-
this.projectToScreen(worldX, 0, worldZ + chunkSize, viewProj, canvasW, canvasH),
|
|
116
|
-
];
|
|
117
|
-
if (corners.some(c => c === null || c.behind))
|
|
118
|
-
continue;
|
|
119
|
-
ctx.beginPath();
|
|
120
|
-
ctx.moveTo(corners[0].x, corners[0].y);
|
|
121
|
-
ctx.lineTo(corners[1].x, corners[1].y);
|
|
122
|
-
ctx.lineTo(corners[2].x, corners[2].y);
|
|
123
|
-
ctx.lineTo(corners[3].x, corners[3].y);
|
|
124
|
-
ctx.closePath();
|
|
125
|
-
ctx.stroke();
|
|
126
|
-
const centerProj = this.projectToScreen(worldX + chunkSize / 2, 0, worldZ + chunkSize / 2, viewProj, canvasW, canvasH);
|
|
127
|
-
if (centerProj && !centerProj.behind) {
|
|
128
|
-
const dx = (worldX + chunkSize / 2) - camera.position[0];
|
|
129
|
-
const dz = (worldZ + chunkSize / 2) - camera.position[2];
|
|
130
|
-
const dist = Math.sqrt(dx * dx + dz * dz);
|
|
131
|
-
if (dist < chunkSize * half) {
|
|
132
|
-
ctx.fillText(`${cx},${cz}`, centerProj.x - 15, centerProj.y);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
renderVelocityArrows(ctx, viewProj, canvasW, canvasH, data) {
|
|
139
|
-
const entityCount = data.getEntityCount();
|
|
140
|
-
ctx.strokeStyle = "rgba(255, 200, 50, 0.8)";
|
|
141
|
-
ctx.fillStyle = "rgba(255, 200, 50, 0.8)";
|
|
142
|
-
ctx.lineWidth = 2;
|
|
143
|
-
for (let i = 0; i < entityCount; i++) {
|
|
144
|
-
const pos = data.getEntityPosition(i);
|
|
145
|
-
const vel = data.getEntityVelocity(i);
|
|
146
|
-
if (!pos || !vel)
|
|
147
|
-
continue;
|
|
148
|
-
const speed = Math.sqrt(vel.x * vel.x + vel.y * vel.y + vel.z * vel.z);
|
|
149
|
-
if (speed < 0.1)
|
|
150
|
-
continue;
|
|
151
|
-
const scale = 2.0;
|
|
152
|
-
const endX = pos.x + vel.x * scale;
|
|
153
|
-
const endY = pos.y + vel.y * scale;
|
|
154
|
-
const endZ = pos.z + vel.z * scale;
|
|
155
|
-
const startProj = this.projectToScreen(pos.x, pos.y, pos.z, viewProj, canvasW, canvasH);
|
|
156
|
-
const endProj = this.projectToScreen(endX, endY, endZ, viewProj, canvasW, canvasH);
|
|
157
|
-
if (!startProj || startProj.behind || !endProj || endProj.behind)
|
|
158
|
-
continue;
|
|
159
|
-
ctx.beginPath();
|
|
160
|
-
ctx.moveTo(startProj.x, startProj.y);
|
|
161
|
-
ctx.lineTo(endProj.x, endProj.y);
|
|
162
|
-
ctx.stroke();
|
|
163
|
-
const dx = endProj.x - startProj.x;
|
|
164
|
-
const dy = endProj.y - startProj.y;
|
|
165
|
-
const angle = Math.atan2(dy, dx);
|
|
166
|
-
const headLen = 6;
|
|
167
|
-
ctx.beginPath();
|
|
168
|
-
ctx.moveTo(endProj.x, endProj.y);
|
|
169
|
-
ctx.lineTo(endProj.x - headLen * Math.cos(angle - Math.PI / 6), endProj.y - headLen * Math.sin(angle - Math.PI / 6));
|
|
170
|
-
ctx.lineTo(endProj.x - headLen * Math.cos(angle + Math.PI / 6), endProj.y - headLen * Math.sin(angle + Math.PI / 6));
|
|
171
|
-
ctx.closePath();
|
|
172
|
-
ctx.fill();
|
|
173
|
-
ctx.font = "10px monospace";
|
|
174
|
-
ctx.fillText(`${speed.toFixed(1)} m/s`, endProj.x + 4, endProj.y - 4);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
destroy() {
|
|
178
|
-
this.resizeWatcher.destroy();
|
|
179
|
-
this.overlay.remove();
|
|
180
|
-
}
|
|
181
|
-
}
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// downdraft doctor — devtools panel for module graph diagnostics
|
|
3
|
-
//
|
|
4
|
-
// Displays the resolved module graph, resource table, system schedule,
|
|
5
|
-
// diagnostics (unresolved requires, version conflicts, leaks), and the
|
|
6
|
-
// cross-thread dependency report (sim + renderer).
|
|
7
|
-
// ============================================================================
|
|
8
|
-
/** Escape untrusted strings before interpolating into innerHTML — mod
|
|
9
|
-
* manifests are third-party content and must not be able to inject markup. */
|
|
10
|
-
function esc(s) {
|
|
11
|
-
return String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
12
|
-
}
|
|
13
|
-
export function createDoctorPanelExtension(opts = {}) {
|
|
14
|
-
return {
|
|
15
|
-
id: "downdraft-doctor",
|
|
16
|
-
tabLabel: "Doctor",
|
|
17
|
-
tabTooltip: "Module graph diagnostics: resolved dependencies, resource table, version conflicts, leak detection",
|
|
18
|
-
order: 5,
|
|
19
|
-
html: `
|
|
20
|
-
<div class="dd-doctor">
|
|
21
|
-
<div class="dd-doctor__header">
|
|
22
|
-
<h2>downdraft doctor</h2>
|
|
23
|
-
<button class="dd-doctor__refresh">Refresh</button>
|
|
24
|
-
</div>
|
|
25
|
-
<div class="dd-doctor__summary"></div>
|
|
26
|
-
<div class="dd-doctor__plugins"></div>
|
|
27
|
-
<div class="dd-doctor__user-plugins"></div>
|
|
28
|
-
<div class="dd-doctor__resources"></div>
|
|
29
|
-
<div class="dd-doctor__issues"></div>
|
|
30
|
-
</div>
|
|
31
|
-
`,
|
|
32
|
-
css: `
|
|
33
|
-
.dd-doctor { padding: 12px; font-family: monospace; font-size: 12px; color: #ccc; }
|
|
34
|
-
.dd-doctor h2 { margin: 0 0 8px; font-size: 16px; color: #4fc3f7; }
|
|
35
|
-
.dd-doctor__header { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
|
|
36
|
-
.dd-doctor__refresh { padding: 4px 12px; background: #333; color: #ccc; border: 1px solid #555; border-radius: 4px; cursor: pointer; }
|
|
37
|
-
.dd-doctor__refresh:hover { background: #444; }
|
|
38
|
-
.dd-doctor__summary { margin-bottom: 16px; padding: 8px; background: #1a1a2e; border-radius: 4px; }
|
|
39
|
-
.dd-doctor__summary div { margin: 2px 0; }
|
|
40
|
-
.dd-doctor__plugins h3, .dd-doctor__resources h3, .dd-doctor__issues h3, .dd-doctor__user-plugins h3 { color: #4fc3f7; margin: 12px 0 4px; font-size: 13px; }
|
|
41
|
-
.dd-doctor__badge { display: inline-block; padding: 1px 6px; border-radius: 3px; font-size: 10px; margin-right: 4px; }
|
|
42
|
-
.dd-doctor__badge--format { background: #2a3a4a; color: #64b5f6; }
|
|
43
|
-
.dd-doctor__badge--tier { background: #3a2a4a; color: #ce93d8; }
|
|
44
|
-
.dd-doctor__badge--thread { background: #2a4a3a; color: #81c784; }
|
|
45
|
-
.dd-doctor__badge--perm { background: #4a3a2a; color: #ffb74d; }
|
|
46
|
-
.dd-doctor__status--active { color: #81c784; }
|
|
47
|
-
.dd-doctor__status--error { color: #ef5350; }
|
|
48
|
-
.dd-doctor__status--loading, .dd-doctor__status--pending { color: #ffb74d; }
|
|
49
|
-
.dd-doctor__status--disabled { color: #888; }
|
|
50
|
-
.dd-doctor__table { width: 100%; border-collapse: collapse; margin-bottom: 12px; }
|
|
51
|
-
.dd-doctor__table th { text-align: left; padding: 4px 8px; background: #1a1a2e; color: #888; font-weight: normal; }
|
|
52
|
-
.dd-doctor__table td { padding: 4px 8px; border-bottom: 1px solid #222; }
|
|
53
|
-
.dd-doctor__table .dd-thread-sim { color: #81c784; }
|
|
54
|
-
.dd-doctor__table .dd-thread-renderer { color: #64b5f6; }
|
|
55
|
-
.dd-doctor__table .dd-thread-shared { color: #ffb74d; }
|
|
56
|
-
.dd-doctor__issue { padding: 6px 8px; margin: 4px 0; border-radius: 4px; }
|
|
57
|
-
.dd-doctor__issue--error { background: #3e1e1e; color: #ef5350; }
|
|
58
|
-
.dd-doctor__issue--warning { background: #3e3a1e; color: #ffb74d; }
|
|
59
|
-
.dd-doctor__issue--ok { background: #1e3e2a; color: #81c784; }
|
|
60
|
-
`,
|
|
61
|
-
script: `(() => {
|
|
62
|
-
const helpers = arguments[0];
|
|
63
|
-
const container = helpers.container;
|
|
64
|
-
const opts = window.__ddDoctorOpts || {};
|
|
65
|
-
let refreshTimer = null;
|
|
66
|
-
|
|
67
|
-
function render() {
|
|
68
|
-
const simModules = opts.getSimModules ? opts.getSimModules() : [];
|
|
69
|
-
const rendererModules = opts.getRendererModules ? opts.getRendererModules() : [];
|
|
70
|
-
let report = opts.getReport ? opts.getReport() : null;
|
|
71
|
-
if (!report && (simModules.length || rendererModules.length)) {
|
|
72
|
-
// Build report inline if no pre-built report provider
|
|
73
|
-
const { buildCrossThreadReport } = window.__ddCrossThread || {};
|
|
74
|
-
if (buildCrossThreadReport) {
|
|
75
|
-
report = buildCrossThreadReport(simModules, rendererModules);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
if (!report) {
|
|
79
|
-
report = { modules: [...simModules, ...rendererModules], unresolved: [], shared: [], versionConflicts: [] };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Summary
|
|
83
|
-
const summary = container.querySelector('.dd-doctor__summary');
|
|
84
|
-
const simCount = report.modules.filter(p => p.thread === 'sim').length;
|
|
85
|
-
const rendererCount = report.modules.filter(p => p.thread === 'renderer').length;
|
|
86
|
-
const sharedCount = report.shared.length;
|
|
87
|
-
const unresolvedCount = report.unresolved.length;
|
|
88
|
-
const conflictCount = report.versionConflicts.length;
|
|
89
|
-
summary.innerHTML = [
|
|
90
|
-
'<div>Sim modules: <b>' + simCount + '</b></div>',
|
|
91
|
-
'<div>Renderer modules: <b>' + rendererCount + '</b></div>',
|
|
92
|
-
'<div>Shared resources: <b>' + sharedCount + '</b></div>',
|
|
93
|
-
'<div>Unresolved requires: <b style="color:' + (unresolvedCount ? '#ef5350' : '#81c784') + '">' + unresolvedCount + '</b></div>',
|
|
94
|
-
'<div>Version conflicts: <b style="color:' + (conflictCount ? '#ef5350' : '#81c784') + '">' + conflictCount + '</b></div>',
|
|
95
|
-
].join('');
|
|
96
|
-
|
|
97
|
-
// Modules table
|
|
98
|
-
const pluginsDiv = container.querySelector('.dd-doctor__plugins');
|
|
99
|
-
if (report.modules.length === 0) {
|
|
100
|
-
pluginsDiv.innerHTML = '<h3>Modules</h3><p>No modules registered.</p>';
|
|
101
|
-
} else {
|
|
102
|
-
let html = '<h3>Modules (' + report.modules.length + ')</h3>';
|
|
103
|
-
html += '<table class="dd-doctor__table"><thead><tr><th>Name</th><th>Version</th><th>Thread</th><th>Provides</th><th>Requires</th><th>Status</th></tr></thead><tbody>';
|
|
104
|
-
for (const p of report.modules) {
|
|
105
|
-
const threadClass = 'dd-thread-' + esc(p.thread);
|
|
106
|
-
html += '<tr><td>' + esc(p.name) + '</td><td>' + esc(p.version) + '</td><td class="' + threadClass + '">' + esc(p.thread) + '</td><td>' + esc(p.provides.join(', ') || '—') + '</td><td>' + esc(p.requires.join(', ') || '—') + '</td><td>' + (p.active ? '✓ active' : 'inactive') + '</td></tr>';
|
|
107
|
-
}
|
|
108
|
-
html += '</tbody></table>';
|
|
109
|
-
pluginsDiv.innerHTML = html;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// User-authored plugins (modding system)
|
|
113
|
-
const userPluginsDiv = container.querySelector('.dd-doctor__user-plugins');
|
|
114
|
-
const plugins = opts.getPlugins ? opts.getPlugins() : [];
|
|
115
|
-
if (plugins.length === 0) {
|
|
116
|
-
userPluginsDiv.innerHTML = '';
|
|
117
|
-
} else {
|
|
118
|
-
let phtml = '<h3>Plugins (' + plugins.length + ')</h3>';
|
|
119
|
-
phtml += '<table class="dd-doctor__table"><thead><tr><th>ID</th><th>Version</th><th>Format</th><th>Tier</th><th>Thread</th><th>Permissions</th><th>Status</th></tr></thead><tbody>';
|
|
120
|
-
for (const p of plugins) {
|
|
121
|
-
const fmtBadge = '<span class="dd-doctor__badge dd-doctor__badge--format">' + esc(p.format) + '</span>';
|
|
122
|
-
const tierBadge = '<span class="dd-doctor__badge dd-doctor__badge--tier">' + esc(p.tier) + '</span>';
|
|
123
|
-
const threadBadge = '<span class="dd-doctor__badge dd-doctor__badge--thread">' + esc(p.thread) + '</span>';
|
|
124
|
-
const permBadges = p.permissions.map(function(perm) { return '<span class="dd-doctor__badge dd-doctor__badge--perm">' + esc(perm) + '</span>'; }).join('');
|
|
125
|
-
const statusClass = 'dd-doctor__status--' + esc(p.status);
|
|
126
|
-
const statusText = esc(p.status + (p.error ? ': ' + p.error : ''));
|
|
127
|
-
phtml += '<tr><td>' + esc(p.id) + '</td><td>' + esc(p.version) + '</td><td>' + fmtBadge + '</td><td>' + tierBadge + '</td><td>' + threadBadge + '</td><td>' + (permBadges || '—') + '</td><td class="' + statusClass + '">' + statusText + '</td></tr>';
|
|
128
|
-
}
|
|
129
|
-
phtml += '</tbody></table>';
|
|
130
|
-
userPluginsDiv.innerHTML = phtml;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Issues
|
|
134
|
-
const issuesDiv = container.querySelector('.dd-doctor__issues');
|
|
135
|
-
let issuesHtml = '<h3>Diagnostics</h3>';
|
|
136
|
-
if (report.unresolved.length > 0) {
|
|
137
|
-
issuesHtml += '<div class="dd-doctor__issue dd-doctor__issue--error">Unresolved requires: ' + esc(report.unresolved.join(', ')) + '</div>';
|
|
138
|
-
}
|
|
139
|
-
if (report.versionConflicts.length > 0) {
|
|
140
|
-
for (const vc of report.versionConflicts) {
|
|
141
|
-
issuesHtml += '<div class="dd-doctor__issue dd-doctor__issue--error">Version conflict: ' + esc(vc.name) + ' (sim=' + esc(vc.simVersion) + ', renderer=' + esc(vc.rendererVersion) + ')</div>';
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
if (report.shared.length > 0) {
|
|
145
|
-
issuesHtml += '<div class="dd-doctor__issue dd-doctor__issue--ok">Shared resources: ' + esc(report.shared.join(', ')) + '</div>';
|
|
146
|
-
}
|
|
147
|
-
if (report.unresolved.length === 0 && report.versionConflicts.length === 0) {
|
|
148
|
-
issuesHtml += '<div class="dd-doctor__issue dd-doctor__issue--ok">✓ No issues detected</div>';
|
|
149
|
-
}
|
|
150
|
-
issuesDiv.innerHTML = issuesHtml;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
container.querySelector('.dd-doctor__refresh').addEventListener('click', render);
|
|
154
|
-
render();
|
|
155
|
-
|
|
156
|
-
return {
|
|
157
|
-
onActivate() { refreshTimer = setInterval(render, 2000); },
|
|
158
|
-
onDeactivate() { if (refreshTimer) { clearInterval(refreshTimer); refreshTimer = null; } },
|
|
159
|
-
onDestroy() { if (refreshTimer) clearInterval(refreshTimer); }
|
|
160
|
-
};
|
|
161
|
-
})`,
|
|
162
|
-
};
|
|
163
|
-
}
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// LabelOverlay — HTML-based billboard labels for dev mode
|
|
3
|
-
// Generic DOM management + projection; game provides labels via ILabelProvider.
|
|
4
|
-
// ============================================================================
|
|
5
|
-
import { calculateViewProj } from "@downdraft/engine";
|
|
6
|
-
const MAX_LABELS = 512;
|
|
7
|
-
export class LabelOverlay {
|
|
8
|
-
container;
|
|
9
|
-
labels = new Map();
|
|
10
|
-
active = false;
|
|
11
|
-
canvas;
|
|
12
|
-
provider = null;
|
|
13
|
-
constructor(surface) {
|
|
14
|
-
// DOM-overlay widget — the canvas/element members it touches
|
|
15
|
-
// (parentElement, 2d overlay canvas) are DOM-host concepts; on native
|
|
16
|
-
// they degrade to the compat stubs (display:none anyway).
|
|
17
|
-
const canvas = surface;
|
|
18
|
-
this.canvas = canvas;
|
|
19
|
-
this.container = document.createElement("div");
|
|
20
|
-
this.container.className = "label-overlay";
|
|
21
|
-
this.container.style.cssText = `
|
|
22
|
-
position: absolute;
|
|
23
|
-
top: 0; left: 0;
|
|
24
|
-
width: 100%; height: 100%;
|
|
25
|
-
pointer-events: none;
|
|
26
|
-
overflow: hidden;
|
|
27
|
-
z-index: 0;
|
|
28
|
-
display: none;
|
|
29
|
-
`;
|
|
30
|
-
const parent = canvas.parentElement;
|
|
31
|
-
if (parent) {
|
|
32
|
-
parent.style.position = parent.style.position || "relative";
|
|
33
|
-
parent.appendChild(this.container);
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
36
|
-
document.body.appendChild(this.container);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
setProvider(provider) {
|
|
40
|
-
this.provider = provider;
|
|
41
|
-
}
|
|
42
|
-
setActive(active) {
|
|
43
|
-
this.active = active;
|
|
44
|
-
this.container.style.display = active ? "block" : "none";
|
|
45
|
-
if (!active) {
|
|
46
|
-
this.clearLabels();
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
isActive() {
|
|
50
|
-
return this.active;
|
|
51
|
-
}
|
|
52
|
-
clearLabels() {
|
|
53
|
-
for (const entry of this.labels.values()) {
|
|
54
|
-
entry.el.remove();
|
|
55
|
-
}
|
|
56
|
-
this.labels.clear();
|
|
57
|
-
}
|
|
58
|
-
getOrCreateLabel(key) {
|
|
59
|
-
let entry = this.labels.get(key);
|
|
60
|
-
if (entry)
|
|
61
|
-
return entry.el;
|
|
62
|
-
if (this.labels.size >= MAX_LABELS)
|
|
63
|
-
return null;
|
|
64
|
-
const el = document.createElement("div");
|
|
65
|
-
el.className = "dev-label";
|
|
66
|
-
el.style.cssText = `
|
|
67
|
-
position: absolute;
|
|
68
|
-
font-family: monospace;
|
|
69
|
-
font-size: 11px;
|
|
70
|
-
color: #fff;
|
|
71
|
-
background: rgba(0, 0, 0, 0.65);
|
|
72
|
-
border-radius: 3px;
|
|
73
|
-
padding: 1px 5px;
|
|
74
|
-
white-space: nowrap;
|
|
75
|
-
pointer-events: none;
|
|
76
|
-
transform: translate(-50%, -100%);
|
|
77
|
-
will-change: transform, left, top;
|
|
78
|
-
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
79
|
-
`;
|
|
80
|
-
this.container.appendChild(el);
|
|
81
|
-
this.labels.set(key, { el });
|
|
82
|
-
return el;
|
|
83
|
-
}
|
|
84
|
-
projectToScreen(worldX, worldY, worldZ, viewProj, canvasW, canvasH) {
|
|
85
|
-
const x = viewProj[0] * worldX + viewProj[4] * worldY + viewProj[8] * worldZ + viewProj[12];
|
|
86
|
-
const y = viewProj[1] * worldX + viewProj[5] * worldY + viewProj[9] * worldZ + viewProj[13];
|
|
87
|
-
const w = viewProj[3] * worldX + viewProj[7] * worldY + viewProj[11] * worldZ + viewProj[15];
|
|
88
|
-
if (w <= 0)
|
|
89
|
-
return { x: 0, y: 0, behind: true };
|
|
90
|
-
const ndcX = x / w;
|
|
91
|
-
const ndcY = y / w;
|
|
92
|
-
const screenX = (ndcX + 1) * 0.5 * canvasW;
|
|
93
|
-
const screenY = (1 - ndcY) * 0.5 * canvasH;
|
|
94
|
-
return { x: screenX, y: screenY, behind: false };
|
|
95
|
-
}
|
|
96
|
-
update(camera) {
|
|
97
|
-
if (!this.active || !this.provider)
|
|
98
|
-
return;
|
|
99
|
-
const viewProj = calculateViewProj(camera);
|
|
100
|
-
const canvasW = this.canvas.clientWidth;
|
|
101
|
-
const canvasH = this.canvas.clientHeight;
|
|
102
|
-
const labels = this.provider.getLabels();
|
|
103
|
-
const usedKeys = new Set();
|
|
104
|
-
for (let _i = 0, _it = labels, _n = _it.length; _i < _n; _i++) {
|
|
105
|
-
const label = _it[_i];
|
|
106
|
-
usedKeys.add(label.key);
|
|
107
|
-
const proj = this.projectToScreen(label.x, label.y, label.z, viewProj, canvasW, canvasH);
|
|
108
|
-
if (!proj || proj.behind)
|
|
109
|
-
continue;
|
|
110
|
-
const el = this.getOrCreateLabel(label.key);
|
|
111
|
-
if (!el)
|
|
112
|
-
continue;
|
|
113
|
-
el.textContent = label.text;
|
|
114
|
-
el.style.left = `${proj.x}px`;
|
|
115
|
-
el.style.top = `${proj.y}px`;
|
|
116
|
-
el.style.color = label.color;
|
|
117
|
-
el.style.display = "block";
|
|
118
|
-
}
|
|
119
|
-
for (const [key, entry] of this.labels.entries()) {
|
|
120
|
-
if (!usedKeys.has(key)) {
|
|
121
|
-
entry.el.style.display = "none";
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
destroy() {
|
|
126
|
-
this.clearLabels();
|
|
127
|
-
this.container.remove();
|
|
128
|
-
}
|
|
129
|
-
}
|