@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.
Files changed (138) hide show
  1. package/app/src/renderer/bootstrap.ts +0 -6
  2. package/app/src/renderer/feature-log.ts +1 -9
  3. package/app/src/renderer/mcp-harness.ts +8 -11
  4. package/app/src/renderer/standard-automation-tools.ts +8 -145
  5. package/core/src/index.ts +2 -2
  6. package/core/src/module/host.ts +3 -6
  7. package/core/src/module/index.ts +1 -1
  8. package/core/src/module/module.ts +4 -47
  9. package/core/src/physics/interface.ts +5 -0
  10. package/core/src/platform/render-surface.ts +13 -0
  11. package/core/src/platform/rpc.ts +4 -1
  12. package/core/src/render/canvas-resize-watcher.ts +1 -10
  13. package/core/src/render/game-renderer.ts +164 -79
  14. package/core/src/render/index.ts +0 -2
  15. package/core/src/render/input-manager.ts +7 -7
  16. package/core/src/render/renderer-module-host.ts +2 -4
  17. package/core/src/sab/grid-buffer.ts +55 -2
  18. package/core/src/sab/record-buffer.ts +48 -5
  19. package/core/src/sab/sim-channel.ts +181 -17
  20. package/core/src/sab/slot-buffer.ts +55 -1
  21. package/core/src/sab/types.ts +11 -0
  22. package/core/src/telemetry/gpu-resource-tracker.ts +24 -0
  23. package/core/src/ui-font-scale.ts +5 -38
  24. package/core/src/util/index.ts +1 -0
  25. package/core/src/util/session-timer.ts +23 -0
  26. package/core/src/worker/base-worker-host.ts +2 -1
  27. package/core/src/worker/buffer-sync.ts +20 -13
  28. package/core/src/worker/rpc.ts +119 -5
  29. package/core/src/worker/sim-worker-base.ts +5 -2
  30. package/core/src/worker/task-pool.ts +4 -0
  31. package/dist/.dd-manifest.json +893 -908
  32. package/dist/app/src/renderer/bootstrap.js +0 -4
  33. package/dist/app/src/renderer/feature-log.js +1 -10
  34. package/dist/app/src/renderer/mcp-harness.js +8 -11
  35. package/dist/app/src/renderer/standard-automation-tools.js +6 -138
  36. package/dist/core/src/index.js +2 -2
  37. package/dist/core/src/module/host.js +3 -6
  38. package/dist/core/src/platform/rpc.js +2 -1
  39. package/dist/core/src/render/canvas-resize-watcher.js +1 -10
  40. package/dist/core/src/render/game-renderer.js +166 -74
  41. package/dist/core/src/render/index.js +0 -1
  42. package/dist/core/src/render/input-manager.js +7 -7
  43. package/dist/core/src/render/renderer-module-host.js +2 -4
  44. package/dist/core/src/sab/grid-buffer.js +53 -1
  45. package/dist/core/src/sab/record-buffer.js +49 -5
  46. package/dist/core/src/sab/sim-channel.js +173 -17
  47. package/dist/core/src/sab/slot-buffer.js +54 -1
  48. package/dist/core/src/telemetry/gpu-resource-tracker.js +26 -0
  49. package/dist/core/src/ui-font-scale.js +5 -38
  50. package/dist/core/src/util/index.js +1 -0
  51. package/dist/core/src/util/session-timer.js +22 -0
  52. package/dist/core/src/worker/base-worker-host.js +2 -1
  53. package/dist/core/src/worker/buffer-sync.js +16 -9
  54. package/dist/core/src/worker/rpc.js +105 -8
  55. package/dist/core/src/worker/sim-worker-base.js +5 -2
  56. package/dist/core/src/worker/task-pool.js +5 -0
  57. package/dist/libraries/blitz-ui/src/native-module.js +8 -1
  58. package/dist/libraries/devtools/src/backend.js +35 -9
  59. package/dist/libraries/devtools/src/collectors.js +73 -22
  60. package/dist/libraries/devtools/src/native-providers.js +0 -2
  61. package/dist/libraries/entities/src/model-renderer.js +87 -27
  62. package/dist/libraries/entities/src/shaders/model.wgsl +13 -4
  63. package/dist/libraries/networking/src/index.js +1 -0
  64. package/dist/libraries/networking/src/ws-server.js +387 -0
  65. package/dist/libraries/physics-rapier/src/api.js +15 -0
  66. package/dist/libraries/physics-rapier/src/backend.js +15 -0
  67. package/dist/libraries/physics-rapier/src/rapier-backend.js +5 -0
  68. package/dist/libraries/postfx/src/post-process-stack.js +1 -3
  69. package/dist/libraries/sand/src/materials.js +2 -32
  70. package/dist/libraries/sand/src/sand-world.js +0 -3
  71. package/dist/modules/devtools/src/api.js +8 -23
  72. package/dist/modules/devtools/src/blitz/fonts.js +75 -0
  73. package/dist/modules/devtools/src/blitz/host.js +124 -30
  74. package/dist/modules/devtools/src/blitz/panels/console.js +19 -7
  75. package/dist/modules/devtools/src/blitz/panels/elements.js +217 -28
  76. package/dist/modules/devtools/src/blitz/panels/gpu.js +5 -2
  77. package/dist/modules/devtools/src/blitz/panels/performance.js +26 -12
  78. package/dist/modules/devtools/src/blitz/panels/snapshot.js +31 -8
  79. package/dist/modules/devtools/src/blitz/panels/types.js +2 -1
  80. package/dist/modules/devtools/src/blitz/theme.js +26 -4
  81. package/dist/modules/devtools/src/data-bridge.js +0 -11
  82. package/dist/modules/devtools/src/index.js +0 -6
  83. package/dist/modules/devtools/src/init.js +8 -42
  84. package/dist/modules/devtools/src/scene-inspector.js +1 -4
  85. package/dist/modules/devtools/src/worker-sync.js +3 -11
  86. package/dist/modules/html-ui/src/doc-backend.js +52 -3
  87. package/dist/modules/html-ui/src/host.js +6 -3
  88. package/dist/test/src/vision-test.js +1 -3
  89. package/libraries/blitz-ui/native/Cargo.toml +6 -0
  90. package/libraries/blitz-ui/native-osr/Cargo.toml +3 -1
  91. package/libraries/blitz-ui/src/native-module.ts +8 -1
  92. package/libraries/devtools/src/backend.ts +38 -12
  93. package/libraries/devtools/src/collectors.ts +75 -22
  94. package/libraries/devtools/src/native-providers.ts +0 -2
  95. package/libraries/entities/src/model-renderer.ts +88 -27
  96. package/libraries/entities/src/shaders/model.wgsl +13 -4
  97. package/libraries/networking/src/index.ts +4 -0
  98. package/libraries/networking/src/ws-server.ts +397 -0
  99. package/libraries/physics-rapier/src/api.ts +15 -0
  100. package/libraries/physics-rapier/src/backend.ts +15 -0
  101. package/libraries/physics-rapier/src/rapier-backend.ts +6 -0
  102. package/libraries/postfx/src/post-process-stack.ts +1 -2
  103. package/libraries/sand/src/materials.ts +2 -32
  104. package/libraries/sand/src/sand-world.ts +0 -4
  105. package/modules/devtools/README.md +1 -3
  106. package/modules/devtools/src/api.ts +10 -34
  107. package/modules/devtools/src/blitz/fonts.ts +78 -0
  108. package/modules/devtools/src/blitz/host.ts +107 -31
  109. package/modules/devtools/src/blitz/panels/console.ts +20 -8
  110. package/modules/devtools/src/blitz/panels/elements.ts +212 -31
  111. package/modules/devtools/src/blitz/panels/gpu.ts +4 -2
  112. package/modules/devtools/src/blitz/panels/performance.ts +24 -12
  113. package/modules/devtools/src/blitz/panels/snapshot.ts +30 -8
  114. package/modules/devtools/src/blitz/panels/types.ts +2 -1
  115. package/modules/devtools/src/blitz/theme.ts +26 -4
  116. package/modules/devtools/src/data-bridge.ts +0 -16
  117. package/modules/devtools/src/index.ts +1 -11
  118. package/modules/devtools/src/init.ts +8 -51
  119. package/modules/devtools/src/scene-inspector.ts +2 -6
  120. package/modules/devtools/src/types.ts +5 -90
  121. package/modules/devtools/src/worker-sync.ts +3 -13
  122. package/modules/html-ui/src/doc-backend.ts +67 -4
  123. package/modules/html-ui/src/host.ts +11 -3
  124. package/modules/html-ui/src/protocol.ts +1 -1
  125. package/package.json +4 -5
  126. package/test/src/vision-test.ts +2 -4
  127. package/core/src/render/sab-canvas-overlay.ts +0 -122
  128. package/dist/core/src/render/sab-canvas-overlay.js +0 -83
  129. package/dist/modules/devtools/src/debug-overlay.js +0 -181
  130. package/dist/modules/devtools/src/doctor-panel.js +0 -163
  131. package/dist/modules/devtools/src/label-overlay.js +0 -129
  132. package/dist/modules/devtools/src/material-stats-panel.js +0 -171
  133. package/dist/modules/devtools/src/sim-stats-panel.js +0 -250
  134. package/modules/devtools/src/debug-overlay.ts +0 -238
  135. package/modules/devtools/src/doctor-panel.ts +0 -182
  136. package/modules/devtools/src/label-overlay.ts +0 -154
  137. package/modules/devtools/src/material-stats-panel.ts +0 -188
  138. package/modules/devtools/src/sim-stats-panel.ts +0 -272
@@ -1,182 +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
-
9
- import type { CrossThreadReport, ModuleThreadInfo, PluginInfo } from "@downdraft/engine";
10
- import type { IDevToolsPanelExtension } from "./types";
11
-
12
- export interface DoctorPanelOptions {
13
- /** Snapshot provider for sim-side modules. */
14
- getSimModules?: () => ModuleThreadInfo[];
15
- /** Snapshot provider for renderer-side modules. */
16
- getRendererModules?: () => ModuleThreadInfo[];
17
- /** Snapshot provider for the cross-thread report (pre-built). */
18
- getReport?: () => CrossThreadReport | null;
19
- /** Snapshot provider for user-authored plugins (the modding system). */
20
- getPlugins?: () => PluginInfo[];
21
- }
22
-
23
- /** Escape untrusted strings before interpolating into innerHTML — mod
24
- * manifests are third-party content and must not be able to inject markup. */
25
- function esc(s: unknown): string {
26
- return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
27
- }
28
-
29
- export function createDoctorPanelExtension(opts: DoctorPanelOptions = {}): IDevToolsPanelExtension {
30
- return {
31
- id: "downdraft-doctor",
32
- tabLabel: "Doctor",
33
- tabTooltip: "Module graph diagnostics: resolved dependencies, resource table, version conflicts, leak detection",
34
- order: 5,
35
-
36
- html: `
37
- <div class="dd-doctor">
38
- <div class="dd-doctor__header">
39
- <h2>downdraft doctor</h2>
40
- <button class="dd-doctor__refresh">Refresh</button>
41
- </div>
42
- <div class="dd-doctor__summary"></div>
43
- <div class="dd-doctor__plugins"></div>
44
- <div class="dd-doctor__user-plugins"></div>
45
- <div class="dd-doctor__resources"></div>
46
- <div class="dd-doctor__issues"></div>
47
- </div>
48
- `,
49
-
50
- css: `
51
- .dd-doctor { padding: 12px; font-family: monospace; font-size: 12px; color: #ccc; }
52
- .dd-doctor h2 { margin: 0 0 8px; font-size: 16px; color: #4fc3f7; }
53
- .dd-doctor__header { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
54
- .dd-doctor__refresh { padding: 4px 12px; background: #333; color: #ccc; border: 1px solid #555; border-radius: 4px; cursor: pointer; }
55
- .dd-doctor__refresh:hover { background: #444; }
56
- .dd-doctor__summary { margin-bottom: 16px; padding: 8px; background: #1a1a2e; border-radius: 4px; }
57
- .dd-doctor__summary div { margin: 2px 0; }
58
- .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; }
59
- .dd-doctor__badge { display: inline-block; padding: 1px 6px; border-radius: 3px; font-size: 10px; margin-right: 4px; }
60
- .dd-doctor__badge--format { background: #2a3a4a; color: #64b5f6; }
61
- .dd-doctor__badge--tier { background: #3a2a4a; color: #ce93d8; }
62
- .dd-doctor__badge--thread { background: #2a4a3a; color: #81c784; }
63
- .dd-doctor__badge--perm { background: #4a3a2a; color: #ffb74d; }
64
- .dd-doctor__status--active { color: #81c784; }
65
- .dd-doctor__status--error { color: #ef5350; }
66
- .dd-doctor__status--loading, .dd-doctor__status--pending { color: #ffb74d; }
67
- .dd-doctor__status--disabled { color: #888; }
68
- .dd-doctor__table { width: 100%; border-collapse: collapse; margin-bottom: 12px; }
69
- .dd-doctor__table th { text-align: left; padding: 4px 8px; background: #1a1a2e; color: #888; font-weight: normal; }
70
- .dd-doctor__table td { padding: 4px 8px; border-bottom: 1px solid #222; }
71
- .dd-doctor__table .dd-thread-sim { color: #81c784; }
72
- .dd-doctor__table .dd-thread-renderer { color: #64b5f6; }
73
- .dd-doctor__table .dd-thread-shared { color: #ffb74d; }
74
- .dd-doctor__issue { padding: 6px 8px; margin: 4px 0; border-radius: 4px; }
75
- .dd-doctor__issue--error { background: #3e1e1e; color: #ef5350; }
76
- .dd-doctor__issue--warning { background: #3e3a1e; color: #ffb74d; }
77
- .dd-doctor__issue--ok { background: #1e3e2a; color: #81c784; }
78
- `,
79
-
80
- script: `(() => {
81
- const helpers = arguments[0];
82
- const container = helpers.container;
83
- const opts = window.__ddDoctorOpts || {};
84
- let refreshTimer = null;
85
-
86
- function render() {
87
- const simModules = opts.getSimModules ? opts.getSimModules() : [];
88
- const rendererModules = opts.getRendererModules ? opts.getRendererModules() : [];
89
- let report = opts.getReport ? opts.getReport() : null;
90
- if (!report && (simModules.length || rendererModules.length)) {
91
- // Build report inline if no pre-built report provider
92
- const { buildCrossThreadReport } = window.__ddCrossThread || {};
93
- if (buildCrossThreadReport) {
94
- report = buildCrossThreadReport(simModules, rendererModules);
95
- }
96
- }
97
- if (!report) {
98
- report = { modules: [...simModules, ...rendererModules], unresolved: [], shared: [], versionConflicts: [] };
99
- }
100
-
101
- // Summary
102
- const summary = container.querySelector('.dd-doctor__summary');
103
- const simCount = report.modules.filter(p => p.thread === 'sim').length;
104
- const rendererCount = report.modules.filter(p => p.thread === 'renderer').length;
105
- const sharedCount = report.shared.length;
106
- const unresolvedCount = report.unresolved.length;
107
- const conflictCount = report.versionConflicts.length;
108
- summary.innerHTML = [
109
- '<div>Sim modules: <b>' + simCount + '</b></div>',
110
- '<div>Renderer modules: <b>' + rendererCount + '</b></div>',
111
- '<div>Shared resources: <b>' + sharedCount + '</b></div>',
112
- '<div>Unresolved requires: <b style="color:' + (unresolvedCount ? '#ef5350' : '#81c784') + '">' + unresolvedCount + '</b></div>',
113
- '<div>Version conflicts: <b style="color:' + (conflictCount ? '#ef5350' : '#81c784') + '">' + conflictCount + '</b></div>',
114
- ].join('');
115
-
116
- // Modules table
117
- const pluginsDiv = container.querySelector('.dd-doctor__plugins');
118
- if (report.modules.length === 0) {
119
- pluginsDiv.innerHTML = '<h3>Modules</h3><p>No modules registered.</p>';
120
- } else {
121
- let html = '<h3>Modules (' + report.modules.length + ')</h3>';
122
- 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>';
123
- for (const p of report.modules) {
124
- const threadClass = 'dd-thread-' + esc(p.thread);
125
- 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>';
126
- }
127
- html += '</tbody></table>';
128
- pluginsDiv.innerHTML = html;
129
- }
130
-
131
- // User-authored plugins (modding system)
132
- const userPluginsDiv = container.querySelector('.dd-doctor__user-plugins');
133
- const plugins = opts.getPlugins ? opts.getPlugins() : [];
134
- if (plugins.length === 0) {
135
- userPluginsDiv.innerHTML = '';
136
- } else {
137
- let phtml = '<h3>Plugins (' + plugins.length + ')</h3>';
138
- 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>';
139
- for (const p of plugins) {
140
- const fmtBadge = '<span class="dd-doctor__badge dd-doctor__badge--format">' + esc(p.format) + '</span>';
141
- const tierBadge = '<span class="dd-doctor__badge dd-doctor__badge--tier">' + esc(p.tier) + '</span>';
142
- const threadBadge = '<span class="dd-doctor__badge dd-doctor__badge--thread">' + esc(p.thread) + '</span>';
143
- const permBadges = p.permissions.map(function(perm) { return '<span class="dd-doctor__badge dd-doctor__badge--perm">' + esc(perm) + '</span>'; }).join('');
144
- const statusClass = 'dd-doctor__status--' + esc(p.status);
145
- const statusText = esc(p.status + (p.error ? ': ' + p.error : ''));
146
- 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>';
147
- }
148
- phtml += '</tbody></table>';
149
- userPluginsDiv.innerHTML = phtml;
150
- }
151
-
152
- // Issues
153
- const issuesDiv = container.querySelector('.dd-doctor__issues');
154
- let issuesHtml = '<h3>Diagnostics</h3>';
155
- if (report.unresolved.length > 0) {
156
- issuesHtml += '<div class="dd-doctor__issue dd-doctor__issue--error">Unresolved requires: ' + esc(report.unresolved.join(', ')) + '</div>';
157
- }
158
- if (report.versionConflicts.length > 0) {
159
- for (const vc of report.versionConflicts) {
160
- issuesHtml += '<div class="dd-doctor__issue dd-doctor__issue--error">Version conflict: ' + esc(vc.name) + ' (sim=' + esc(vc.simVersion) + ', renderer=' + esc(vc.rendererVersion) + ')</div>';
161
- }
162
- }
163
- if (report.shared.length > 0) {
164
- issuesHtml += '<div class="dd-doctor__issue dd-doctor__issue--ok">Shared resources: ' + esc(report.shared.join(', ')) + '</div>';
165
- }
166
- if (report.unresolved.length === 0 && report.versionConflicts.length === 0) {
167
- issuesHtml += '<div class="dd-doctor__issue dd-doctor__issue--ok">✓ No issues detected</div>';
168
- }
169
- issuesDiv.innerHTML = issuesHtml;
170
- }
171
-
172
- container.querySelector('.dd-doctor__refresh').addEventListener('click', render);
173
- render();
174
-
175
- return {
176
- onActivate() { refreshTimer = setInterval(render, 2000); },
177
- onDeactivate() { if (refreshTimer) { clearInterval(refreshTimer); refreshTimer = null; } },
178
- onDestroy() { if (refreshTimer) clearInterval(refreshTimer); }
179
- };
180
- })`,
181
- };
182
- }
@@ -1,154 +0,0 @@
1
- // ============================================================================
2
- // LabelOverlay — HTML-based billboard labels for dev mode
3
- // Generic DOM management + projection; game provides labels via ILabelProvider.
4
- // ============================================================================
5
-
6
- import { calculateViewProj, type RenderSurface } from "@downdraft/engine";
7
- import type { ILabelProvider } from "./types";
8
-
9
- interface LabelEntry {
10
- el: HTMLDivElement;
11
- }
12
-
13
- const MAX_LABELS = 512;
14
-
15
- export class LabelOverlay {
16
- private container: HTMLDivElement;
17
- private labels: Map<string, LabelEntry> = new Map();
18
- private active = false;
19
- private canvas: HTMLCanvasElement;
20
- private provider: ILabelProvider | null = null;
21
-
22
- constructor(surface: RenderSurface) {
23
- // DOM-overlay widget — the canvas/element members it touches
24
- // (parentElement, 2d overlay canvas) are DOM-host concepts; on native
25
- // they degrade to the compat stubs (display:none anyway).
26
- const canvas = surface as unknown as HTMLCanvasElement;
27
- this.canvas = canvas;
28
- this.container = document.createElement("div");
29
- this.container.className = "label-overlay";
30
- this.container.style.cssText = `
31
- position: absolute;
32
- top: 0; left: 0;
33
- width: 100%; height: 100%;
34
- pointer-events: none;
35
- overflow: hidden;
36
- z-index: 0;
37
- display: none;
38
- `;
39
- const parent = canvas.parentElement;
40
- if (parent) {
41
- parent.style.position = parent.style.position || "relative";
42
- parent.appendChild(this.container);
43
- } else {
44
- document.body.appendChild(this.container);
45
- }
46
- }
47
-
48
- setProvider(provider: ILabelProvider): void {
49
- this.provider = provider;
50
- }
51
-
52
- setActive(active: boolean): void {
53
- this.active = active;
54
- this.container.style.display = active ? "block" : "none";
55
- if (!active) {
56
- this.clearLabels();
57
- }
58
- }
59
-
60
- isActive(): boolean {
61
- return this.active;
62
- }
63
-
64
- private clearLabels(): void {
65
- for (const entry of this.labels.values()) {
66
- entry.el.remove();
67
- }
68
- this.labels.clear();
69
- }
70
-
71
- private getOrCreateLabel(key: string): HTMLDivElement | null {
72
- let entry = this.labels.get(key);
73
- if (entry) return entry.el;
74
-
75
- if (this.labels.size >= MAX_LABELS) return null;
76
-
77
- const el = document.createElement("div");
78
- el.className = "dev-label";
79
- el.style.cssText = `
80
- position: absolute;
81
- font-family: monospace;
82
- font-size: 11px;
83
- color: #fff;
84
- background: rgba(0, 0, 0, 0.65);
85
- border-radius: 3px;
86
- padding: 1px 5px;
87
- white-space: nowrap;
88
- pointer-events: none;
89
- transform: translate(-50%, -100%);
90
- will-change: transform, left, top;
91
- border: 1px solid rgba(255, 255, 255, 0.15);
92
- `;
93
- this.container.appendChild(el);
94
- this.labels.set(key, { el });
95
- return el;
96
- }
97
-
98
- private projectToScreen(
99
- worldX: number, worldY: number, worldZ: number,
100
- viewProj: Float32Array,
101
- canvasW: number, canvasH: number,
102
- ): { x: number; y: number; behind: boolean } | null {
103
- const x = viewProj[0] * worldX + viewProj[4] * worldY + viewProj[8] * worldZ + viewProj[12];
104
- const y = viewProj[1] * worldX + viewProj[5] * worldY + viewProj[9] * worldZ + viewProj[13];
105
- const w = viewProj[3] * worldX + viewProj[7] * worldY + viewProj[11] * worldZ + viewProj[15];
106
-
107
- if (w <= 0) return { x: 0, y: 0, behind: true };
108
-
109
- const ndcX = x / w;
110
- const ndcY = y / w;
111
-
112
- const screenX = (ndcX + 1) * 0.5 * canvasW;
113
- const screenY = (1 - ndcY) * 0.5 * canvasH;
114
-
115
- return { x: screenX, y: screenY, behind: false };
116
- }
117
-
118
- update(camera: Parameters<typeof calculateViewProj>[0]): void {
119
- if (!this.active || !this.provider) return;
120
-
121
- const viewProj = calculateViewProj(camera);
122
- const canvasW = this.canvas.clientWidth;
123
- const canvasH = this.canvas.clientHeight;
124
-
125
- const labels = this.provider.getLabels();
126
- const usedKeys = new Set<string>();
127
-
128
- for (let _i = 0, _it = labels, _n = _it.length; _i < _n; _i++) { const label = _it[_i];
129
- usedKeys.add(label.key);
130
- const proj = this.projectToScreen(label.x, label.y, label.z, viewProj, canvasW, canvasH);
131
- if (!proj || proj.behind) continue;
132
-
133
- const el = this.getOrCreateLabel(label.key);
134
- if (!el) continue;
135
-
136
- el.textContent = label.text;
137
- el.style.left = `${proj.x}px`;
138
- el.style.top = `${proj.y}px`;
139
- el.style.color = label.color;
140
- el.style.display = "block";
141
- }
142
-
143
- for (const [key, entry] of this.labels.entries()) {
144
- if (!usedKeys.has(key)) {
145
- entry.el.style.display = "none";
146
- }
147
- }
148
- }
149
-
150
- destroy(): void {
151
- this.clearLabels();
152
- this.container.remove();
153
- }
154
- }
@@ -1,188 +0,0 @@
1
- // ============================================================================
2
- // createMaterialStatsPanelExtension — reusable DevTools "Materials" tab for
3
- // any game using the unified material system.
4
- //
5
- // Shows:
6
- // - Registered material count, material types breakdown
7
- // - Variant cache size, LRU hits/misses
8
- // - Shader graph node count, compiled WGSL size
9
- // - GPU texture count/bytes (from GPUResourceTracker)
10
- //
11
- // Gated by requiredMethods: ["getMaterialStats"]. The framework auto-provides
12
- // getMaterialStats() from the renderer's MaterialLibrary / variant cache
13
- // (feature-detected in createDevToolsRendererAdapter or registered via
14
- // devtools.registerDataFeed).
15
- // ============================================================================
16
-
17
- import type { IDevToolsPanelExtension } from "./types";
18
-
19
- export interface MaterialStatsPanelOptions {
20
- tabLabel?: string;
21
- tabTooltip?: string;
22
- order?: number;
23
- /** Optional callback to append custom rows to the stats grid. */
24
- extraRows?: (stats: any) => [string, string][];
25
- }
26
-
27
- export function createMaterialStatsPanelExtension(
28
- opts: MaterialStatsPanelOptions = {},
29
- ): IDevToolsPanelExtension {
30
- const tabLabel = opts.tabLabel ?? "Materials";
31
- const tabTooltip = opts.tabTooltip ?? "Material system stats (library, variants, GPU textures)";
32
- const order = opts.order ?? 25;
33
- const hasExtra = typeof opts.extraRows === "function";
34
- const extraRowsSrc = hasExtra ? opts.extraRows!.toString() : "null";
35
-
36
- const html = `
37
- <div class="panel-header">
38
- <h3>Materials</h3>
39
- <span id="mat-status-badge" class="perf-status">Waiting for data...</span>
40
- </div>
41
- <div id="mat-stats-panel">
42
- <div class="debug-section">
43
- <h4>Library</h4>
44
- <div id="mat-library-grid" class="debug-grid"></div>
45
- </div>
46
- <div class="debug-section">
47
- <h4>Variants</h4>
48
- <div id="mat-variants-grid" class="debug-grid"></div>
49
- </div>
50
- <div class="debug-section">
51
- <h4>GPU Textures</h4>
52
- <div id="mat-textures-grid" class="debug-grid"></div>
53
- </div>
54
- <div class="debug-section">
55
- <h4>Shader Graph</h4>
56
- <div id="mat-graph-grid" class="debug-grid"></div>
57
- </div>
58
- </div>
59
- `;
60
-
61
- const css = `
62
- #mat-stats-panel {
63
- flex: 1;
64
- overflow-y: auto;
65
- padding: 8px 12px;
66
- }
67
- #mat-stats-panel .debug-section {
68
- margin-bottom: 12px;
69
- }
70
- #mat-stats-panel .debug-section h4 {
71
- font-size: 12px;
72
- color: #569cd6;
73
- margin: 8px 0 4px 0;
74
- border-bottom: 1px solid #333;
75
- padding-bottom: 2px;
76
- }
77
- #mat-status-badge.running { color: #98c379; }
78
- #mat-status-badge.empty { color: #e5c07b; }
79
- `;
80
-
81
- const script = `
82
- (function(DevToolsPanel) {
83
- var callInspector = DevToolsPanel.callInspector;
84
- var evalInPage = DevToolsPanel.evalInPage;
85
- var escapeHtml = DevToolsPanel.escapeHtml;
86
- var fmtVal = DevToolsPanel.fmtVal;
87
- var debugGridHtml = DevToolsPanel.debugGridHtml;
88
-
89
- var extraRowsFn = ${extraRowsSrc};
90
-
91
- function refreshMatStats() {
92
- var libEl = document.getElementById("mat-library-grid");
93
- var varEl = document.getElementById("mat-variants-grid");
94
- var texEl = document.getElementById("mat-textures-grid");
95
- var graphEl = document.getElementById("mat-graph-grid");
96
- var statusEl = document.getElementById("mat-status-badge");
97
-
98
- evalInPage(
99
- "window.__sceneInspector ? JSON.stringify(window.__sceneInspector.getMaterialStats()) : null",
100
- function (result, err) {
101
- if (err || !result) {
102
- if (libEl) libEl.innerHTML = '<div class="debug-label">Not available</div>';
103
- if (statusEl) { statusEl.textContent = "Not available"; statusEl.className = "perf-status"; }
104
- return;
105
- }
106
- try {
107
- var s = JSON.parse(result);
108
- if (!s) {
109
- if (libEl) libEl.innerHTML = '<div class="debug-label">Not available</div>';
110
- return;
111
- }
112
-
113
- // Library stats
114
- var libRows = [
115
- ["Registered", s.library?.count ?? 0],
116
- ["Types", s.library?.types?.join(", ") ?? "—"],
117
- ];
118
- if (libEl) libEl.innerHTML = debugGridHtml(libRows);
119
-
120
- // Variant stats
121
- var varRows = [
122
- ["Cached Pipelines", s.variants?.cachedPipelines ?? 0],
123
- ["Max Pipelines", s.variants?.maxPipelines ?? 0],
124
- ["LRU Hits", s.variants?.lruHits ?? 0],
125
- ["LRU Misses", s.variants?.lruMisses ?? 0],
126
- ["Permutation Count", s.variants?.permutationCount ?? 0],
127
- ];
128
- if (varEl) varEl.innerHTML = debugGridHtml(varRows);
129
-
130
- // GPU textures
131
- var texRows = [
132
- ["Texture Count", s.textures?.count ?? 0],
133
- ["Texture Memory", fmtVal((s.textures?.bytes ?? 0) / 1048576, 1) + " MB"],
134
- ["Bindless Entries", s.textures?.bindlessEntries ?? 0],
135
- ];
136
- if (texEl) texEl.innerHTML = debugGridHtml(texRows);
137
-
138
- // Shader graph
139
- var graphRows = [
140
- ["Graph Materials", s.graph?.materialCount ?? 0],
141
- ["Total Nodes", s.graph?.totalNodes ?? 0],
142
- ["Total Connections", s.graph?.totalConnections ?? 0],
143
- ["Compiled WGSL Size", fmtVal((s.graph?.compiledWgslBytes ?? 0) / 1024, 1) + " KB"],
144
- ];
145
- if (graphEl) graphEl.innerHTML = debugGridHtml(graphRows);
146
-
147
- // Extra rows
148
- if (extraRowsFn && s) {
149
- try {
150
- var extra = extraRowsFn(s);
151
- if (extra && extra.length) {
152
- var extraHtml = debugGridHtml(extra);
153
- if (graphEl) graphEl.innerHTML += extraHtml;
154
- }
155
- } catch (e) { /* ignore */ }
156
- }
157
-
158
- if (statusEl) {
159
- var count = s.library?.count ?? 0;
160
- statusEl.textContent = count > 0 ? count + " materials" : "No materials";
161
- statusEl.className = "perf-status " + (count > 0 ? "running" : "empty");
162
- }
163
- } catch (e) {
164
- if (libEl) libEl.innerHTML = '<div class="debug-label">Error: ' + escapeHtml(String(e)) + '</div>';
165
- }
166
- }
167
- );
168
- }
169
-
170
- return {
171
- onActivate: function () { refreshMatStats(); },
172
- onRefresh: refreshMatStats,
173
- onDeactivate: function () {},
174
- };
175
- })
176
- `;
177
-
178
- return {
179
- id: "material-stats",
180
- tabLabel,
181
- tabTooltip,
182
- order,
183
- requiredMethods: ["getMaterialStats"],
184
- html,
185
- css,
186
- script,
187
- };
188
- }