@forgeax/view 0.5.0-dev.13472.92dbf4b81984 → 0.5.0-dev.13598.ab27e992befc

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 (146) hide show
  1. package/README.md +25 -7
  2. package/dist/build-revision.json +1 -1
  3. package/dist/modules/backend.mjs +12 -12
  4. package/dist/modules/browser.mjs +16 -16
  5. package/dist/modules/chunks/{App-E3M52XTQ.mjs → App-4XQ5SNNT.mjs} +2 -2
  6. package/dist/modules/chunks/{chunk-3GIGTM7E.mjs → chunk-27XASYEH.mjs} +2 -2
  7. package/dist/modules/chunks/{chunk-UAGQZPAB.mjs → chunk-2Z5KEV6L.mjs} +2 -2
  8. package/dist/modules/chunks/{chunk-4NGODRIE.mjs → chunk-46UEGAXY.mjs} +4 -4
  9. package/dist/modules/chunks/{chunk-UZHTLAIZ.mjs → chunk-47RJSHLM.mjs} +2 -2
  10. package/dist/modules/chunks/{chunk-ACOMGSSB.mjs → chunk-5PHAXNFO.mjs} +2 -2
  11. package/dist/modules/chunks/{chunk-IXPBGENY.mjs → chunk-6I7RY73G.mjs} +3 -3
  12. package/dist/modules/chunks/{chunk-BCTPTLA6.mjs → chunk-7725LLBM.mjs} +3 -3
  13. package/dist/modules/chunks/{chunk-45ZU2HHR.mjs → chunk-B6QUJ33G.mjs} +27 -23
  14. package/dist/modules/chunks/chunk-B6QUJ33G.mjs.map +7 -0
  15. package/dist/modules/chunks/{chunk-PG4AJFDC.mjs → chunk-BGWAT7SI.mjs} +12 -12
  16. package/dist/modules/chunks/{chunk-FKLYSAVN.mjs → chunk-CXOD7FW6.mjs} +7 -4
  17. package/dist/modules/chunks/chunk-CXOD7FW6.mjs.map +7 -0
  18. package/dist/modules/chunks/{chunk-CRNEX7YH.mjs → chunk-D25SR5FS.mjs} +2 -2
  19. package/dist/modules/chunks/{chunk-L5EV6NEE.mjs → chunk-D5GUFI74.mjs} +6 -6
  20. package/dist/modules/chunks/{chunk-TBZZ37VI.mjs → chunk-DZRJQVJF.mjs} +4 -4
  21. package/dist/modules/chunks/{chunk-MHE5EBU4.mjs → chunk-E3PJGTLI.mjs} +22 -14
  22. package/dist/modules/chunks/chunk-E3PJGTLI.mjs.map +7 -0
  23. package/dist/modules/chunks/{chunk-3Q26HNFS.mjs → chunk-FICPCJKR.mjs} +2 -2
  24. package/dist/modules/chunks/{chunk-CWNB67UF.mjs → chunk-FMCHH5R4.mjs} +2 -2
  25. package/dist/modules/chunks/{chunk-MO5LILCP.mjs → chunk-HWZCLROY.mjs} +6 -2
  26. package/dist/modules/chunks/chunk-HWZCLROY.mjs.map +7 -0
  27. package/dist/modules/chunks/{chunk-TTFLJHQZ.mjs → chunk-I5KQUTEE.mjs} +6 -6
  28. package/dist/modules/chunks/{chunk-TTFLJHQZ.mjs.map → chunk-I5KQUTEE.mjs.map} +1 -1
  29. package/dist/modules/chunks/{chunk-2VJPRNYB.mjs → chunk-IZAHIOFU.mjs} +4 -4
  30. package/dist/modules/chunks/{chunk-IBNFKPW4.mjs → chunk-JK7K7RG3.mjs} +2 -2
  31. package/dist/modules/chunks/{chunk-BGK4ZEQ5.mjs → chunk-KX2WU5Z6.mjs} +2 -2
  32. package/dist/modules/chunks/{chunk-M5X56JWH.mjs → chunk-PW5CE2Y6.mjs} +3 -3
  33. package/dist/modules/chunks/{chunk-OHVTNEQ2.mjs → chunk-PZIAKZJC.mjs} +2 -2
  34. package/dist/modules/chunks/{chunk-WHVPE2P6.mjs → chunk-RJ4PIQRU.mjs} +4 -4
  35. package/dist/modules/chunks/{chunk-WHVPE2P6.mjs.map → chunk-RJ4PIQRU.mjs.map} +1 -1
  36. package/dist/modules/chunks/{chunk-CDO6LELS.mjs → chunk-SYE5RTD4.mjs} +3 -3
  37. package/dist/modules/chunks/{chunk-N2GEQFFK.mjs → chunk-U5J7OTEB.mjs} +2 -2
  38. package/dist/modules/chunks/{chunk-N2GEQFFK.mjs.map → chunk-U5J7OTEB.mjs.map} +2 -2
  39. package/dist/modules/chunks/{chunk-JZ3V27HQ.mjs → chunk-U64FRQKP.mjs} +3 -3
  40. package/dist/modules/chunks/{chunk-W46L4HSP.mjs → chunk-U6UMATPV.mjs} +3 -3
  41. package/dist/modules/chunks/{chunk-7ICJKZYO.mjs → chunk-VKNIYV2W.mjs} +26 -21
  42. package/dist/modules/chunks/chunk-VKNIYV2W.mjs.map +7 -0
  43. package/dist/modules/chunks/{chunk-7R6CGQDU.mjs → chunk-VW2ZF3P4.mjs} +4 -4
  44. package/dist/modules/chunks/{chunk-AIIYGGGC.mjs → chunk-X4NNZ5X6.mjs} +2 -2
  45. package/dist/modules/chunks/{chunk-5AKXNYKX.mjs → chunk-XE6ITXHT.mjs} +2 -2
  46. package/dist/modules/chunks/{chunk-KSF2EQTE.mjs → chunk-YCMZAYBM.mjs} +2 -2
  47. package/dist/modules/chunks/chunk-YCMZAYBM.mjs.map +7 -0
  48. package/dist/modules/chunks/{chunk-6WCG6WPC.mjs → chunk-YNHEYXDG.mjs} +3 -3
  49. package/dist/modules/chunks/{chunk-7MJUWNVI.mjs → chunk-ZGOGO7SS.mjs} +1 -1
  50. package/dist/modules/chunks/{chunk-VXSD4FK5.mjs → chunk-ZHO2G4JR.mjs} +3 -3
  51. package/dist/modules/cli-contributions.mjs +3 -3
  52. package/dist/modules/cli.mjs +2 -2
  53. package/dist/modules/command-executor.mjs +2 -2
  54. package/dist/modules/embedded.mjs +100 -21
  55. package/dist/modules/embedded.mjs.map +3 -3
  56. package/dist/modules/frontend-root.mjs +15 -15
  57. package/dist/modules/index.mjs +26 -26
  58. package/dist/modules/plugin.mjs +14 -14
  59. package/dist/modules/plugins/asset/frontend.mjs +4 -4
  60. package/dist/modules/plugins/asset/identity.mjs +3 -3
  61. package/dist/modules/plugins/asset/ui.mjs +2 -2
  62. package/dist/modules/plugins/content-browser/ui.mjs +2 -2
  63. package/dist/modules/plugins/inspector/ui.mjs +2 -2
  64. package/dist/modules/plugins/material/frontend.mjs +4 -4
  65. package/dist/modules/plugins/material/identity.mjs +3 -3
  66. package/dist/modules/plugins/material/ui.mjs +5 -5
  67. package/dist/modules/plugins/mesh/frontend.mjs +4 -4
  68. package/dist/modules/plugins/mesh/identity.mjs +3 -3
  69. package/dist/modules/plugins/mesh/ui.mjs +5 -5
  70. package/dist/modules/plugins/plugins/ui/panel.mjs +2 -2
  71. package/dist/modules/plugins/plugins/ui.mjs +3 -3
  72. package/dist/modules/plugins/scene-tree/ui.mjs +2 -2
  73. package/dist/modules/plugins/texture/frontend.mjs +4 -4
  74. package/dist/modules/plugins/texture/identity.mjs +3 -3
  75. package/dist/modules/plugins/texture/ui.mjs +5 -5
  76. package/dist/modules/plugins/viewport/frontend.mjs +1 -1
  77. package/dist/modules/product.mjs +14 -14
  78. package/dist/modules/shell/frontend.mjs +1 -1
  79. package/dist/modules/start-cli.mjs +1 -1
  80. package/dist/modules/start.mjs +1 -1
  81. package/dist/modules/viewer/catalog/backend.mjs +10 -10
  82. package/dist/modules/viewer/catalog/frontend.mjs +14 -14
  83. package/dist/modules/viewer/catalog/shared.mjs +7 -7
  84. package/dist/modules/viewer/catalog.mjs +17 -17
  85. package/dist/modules/viewer-host/frontend.mjs +16 -16
  86. package/dist/modules/viewer.mjs +17 -17
  87. package/dist/modules/workspace/backend.mjs +2 -2
  88. package/dist/modules/workspace/broker.mjs +1 -1
  89. package/dist/modules/workspace/preview-surface.mjs +1 -1
  90. package/dist/modules/workspace/resident.mjs +1 -1
  91. package/dist/modules/workspace/resource-page.mjs +2 -2
  92. package/dist/viewer/assets/{embedded-Dv06jc6s.js → embedded-Ch4NawY-.js} +2 -2
  93. package/dist/viewer/assets/{embedded-Dv06jc6s.js.map → embedded-Ch4NawY-.js.map} +1 -1
  94. package/dist/viewer/assets/main-DUQJ1NlR.js +22 -0
  95. package/dist/viewer/assets/main-DUQJ1NlR.js.map +1 -0
  96. package/dist/viewer/assets/{panels-lazy-DjINjNT-.js → panels-lazy-DbOAvYN1.js} +3 -3
  97. package/dist/viewer/assets/panels-lazy-DbOAvYN1.js.map +1 -0
  98. package/dist/viewer/assets/panels-ui-DICIsCag.js +12 -0
  99. package/dist/viewer/assets/panels-ui-DICIsCag.js.map +1 -0
  100. package/dist/viewer/assets/style-gOtSMZif.css +2 -0
  101. package/dist/viewer/index.html +4 -4
  102. package/dist/viewer/panels.js +1 -1
  103. package/package.json +3 -4
  104. package/scripts/browser-proof-workload.mjs +0 -1
  105. package/scripts/verify-runtime-content-engine.mjs +12 -6
  106. package/dist/modules/chunks/chunk-45ZU2HHR.mjs.map +0 -7
  107. package/dist/modules/chunks/chunk-7ICJKZYO.mjs.map +0 -7
  108. package/dist/modules/chunks/chunk-FKLYSAVN.mjs.map +0 -7
  109. package/dist/modules/chunks/chunk-KSF2EQTE.mjs.map +0 -7
  110. package/dist/modules/chunks/chunk-MHE5EBU4.mjs.map +0 -7
  111. package/dist/modules/chunks/chunk-MO5LILCP.mjs.map +0 -7
  112. package/dist/viewer/assets/main-De95aP3K.js +0 -22
  113. package/dist/viewer/assets/main-De95aP3K.js.map +0 -1
  114. package/dist/viewer/assets/panels-lazy-DjINjNT-.js.map +0 -1
  115. package/dist/viewer/assets/panels-ui-DEJVGEKx.js +0 -12
  116. package/dist/viewer/assets/panels-ui-DEJVGEKx.js.map +0 -1
  117. package/dist/viewer/assets/style-BM0losky.css +0 -2
  118. package/scripts/verify-game3d-workspace.mjs +0 -318
  119. /package/dist/modules/chunks/{App-E3M52XTQ.mjs.map → App-4XQ5SNNT.mjs.map} +0 -0
  120. /package/dist/modules/chunks/{chunk-3GIGTM7E.mjs.map → chunk-27XASYEH.mjs.map} +0 -0
  121. /package/dist/modules/chunks/{chunk-UAGQZPAB.mjs.map → chunk-2Z5KEV6L.mjs.map} +0 -0
  122. /package/dist/modules/chunks/{chunk-4NGODRIE.mjs.map → chunk-46UEGAXY.mjs.map} +0 -0
  123. /package/dist/modules/chunks/{chunk-UZHTLAIZ.mjs.map → chunk-47RJSHLM.mjs.map} +0 -0
  124. /package/dist/modules/chunks/{chunk-ACOMGSSB.mjs.map → chunk-5PHAXNFO.mjs.map} +0 -0
  125. /package/dist/modules/chunks/{chunk-IXPBGENY.mjs.map → chunk-6I7RY73G.mjs.map} +0 -0
  126. /package/dist/modules/chunks/{chunk-BCTPTLA6.mjs.map → chunk-7725LLBM.mjs.map} +0 -0
  127. /package/dist/modules/chunks/{chunk-PG4AJFDC.mjs.map → chunk-BGWAT7SI.mjs.map} +0 -0
  128. /package/dist/modules/chunks/{chunk-CRNEX7YH.mjs.map → chunk-D25SR5FS.mjs.map} +0 -0
  129. /package/dist/modules/chunks/{chunk-L5EV6NEE.mjs.map → chunk-D5GUFI74.mjs.map} +0 -0
  130. /package/dist/modules/chunks/{chunk-TBZZ37VI.mjs.map → chunk-DZRJQVJF.mjs.map} +0 -0
  131. /package/dist/modules/chunks/{chunk-3Q26HNFS.mjs.map → chunk-FICPCJKR.mjs.map} +0 -0
  132. /package/dist/modules/chunks/{chunk-CWNB67UF.mjs.map → chunk-FMCHH5R4.mjs.map} +0 -0
  133. /package/dist/modules/chunks/{chunk-2VJPRNYB.mjs.map → chunk-IZAHIOFU.mjs.map} +0 -0
  134. /package/dist/modules/chunks/{chunk-IBNFKPW4.mjs.map → chunk-JK7K7RG3.mjs.map} +0 -0
  135. /package/dist/modules/chunks/{chunk-BGK4ZEQ5.mjs.map → chunk-KX2WU5Z6.mjs.map} +0 -0
  136. /package/dist/modules/chunks/{chunk-M5X56JWH.mjs.map → chunk-PW5CE2Y6.mjs.map} +0 -0
  137. /package/dist/modules/chunks/{chunk-OHVTNEQ2.mjs.map → chunk-PZIAKZJC.mjs.map} +0 -0
  138. /package/dist/modules/chunks/{chunk-CDO6LELS.mjs.map → chunk-SYE5RTD4.mjs.map} +0 -0
  139. /package/dist/modules/chunks/{chunk-JZ3V27HQ.mjs.map → chunk-U64FRQKP.mjs.map} +0 -0
  140. /package/dist/modules/chunks/{chunk-W46L4HSP.mjs.map → chunk-U6UMATPV.mjs.map} +0 -0
  141. /package/dist/modules/chunks/{chunk-7R6CGQDU.mjs.map → chunk-VW2ZF3P4.mjs.map} +0 -0
  142. /package/dist/modules/chunks/{chunk-AIIYGGGC.mjs.map → chunk-X4NNZ5X6.mjs.map} +0 -0
  143. /package/dist/modules/chunks/{chunk-5AKXNYKX.mjs.map → chunk-XE6ITXHT.mjs.map} +0 -0
  144. /package/dist/modules/chunks/{chunk-6WCG6WPC.mjs.map → chunk-YNHEYXDG.mjs.map} +0 -0
  145. /package/dist/modules/chunks/{chunk-7MJUWNVI.mjs.map → chunk-ZGOGO7SS.mjs.map} +0 -0
  146. /package/dist/modules/chunks/{chunk-VXSD4FK5.mjs.map → chunk-ZHO2G4JR.mjs.map} +0 -0
@@ -1,318 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import { browserProofWorkload } from './browser-proof-workload.mjs';
3
- const workload = browserProofWorkload();
4
- import { createHash } from 'node:crypto';
5
- import { cp, mkdir, mkdtemp, readFile, realpath, rm, symlink, writeFile } from 'node:fs/promises';
6
- import { tmpdir } from 'node:os';
7
- import { dirname, join, resolve } from 'node:path';
8
- import { fileURLToPath, pathToFileURL } from 'node:url';
9
- import { chromium } from 'playwright';
10
- import { connectViewWorkspace } from '@forgeax/view/workspace/resident';
11
-
12
- const checkout = resolve(process.env.FORGEAX_ENGINE_CHECKOUT ?? '.ci/forgeax-engine');
13
- const localEngine = process.env.FORGEAX_LOCAL_ENGINE === '1';
14
- const engine = localEngine ? join(checkout, 'packages/engine') : dirname(fileURLToPath(import.meta.resolve('@forgeax/engine/package.json')));
15
- const { runUnifiedCli } = await import(localEngine ? pathToFileURL(join(engine, 'dist/facades/devkit.mjs')).href : '@forgeax/engine/devkit');
16
- const view = resolve(import.meta.dirname, '..');
17
- const root = await mkdtemp(join(tmpdir(), 'view-game3d-proof-'));
18
- const viewStartInput = JSON.stringify({ root, frontendConfig: { layout: { responsiveAutoTuck: false } } });
19
- let browser;
20
- let page;
21
- let workspace;
22
- let backendPid;
23
- const command = async (...args) => {
24
- const result = await runUnifiedCli([...args, '--root', root, '--json']);
25
- assert.equal(result.ok, true, JSON.stringify(result));
26
- return result.value;
27
- };
28
- const errors = [];
29
- const requests = new Set();
30
- const requestStarts = new WeakMap();
31
- const failuresDuringViewStop = [];
32
- let stoppingView = false;
33
- let phase = 'startup';
34
- async function assertDetached(frame) {
35
- const deadline = Date.now() + 10_000;
36
- while (!frame.isDetached() && Date.now() < deadline) await new Promise(resolve => setTimeout(resolve, 50));
37
- assert.equal(frame.isDetached(), true, 'Play stop must remove its game document before View restarts');
38
- }
39
- try {
40
- await cp(join(checkout, 'templates/game-3d/assets'), join(root, 'assets'), { recursive: true });
41
- if (workload.shadowMapSize !== 2048) {
42
- const scenePath = join(root, 'assets/scene.pack.ts');
43
- const scene = await readFile(scenePath, 'utf8');
44
- assert.ok(scene.includes('mapSize: 2048,'), 'the formal Game 3D shadow fixture must be recognized');
45
- await writeFile(scenePath, scene.replace('mapSize: 2048,', `mapSize: ${workload.shadowMapSize},`));
46
- }
47
- const manifest = JSON.parse(await readFile(join(checkout, 'templates/game-3d/forge.json'), 'utf8'));
48
- await writeFile(join(root, 'forge.json'), JSON.stringify(manifest));
49
- await writeFile(join(root, 'package.json'), JSON.stringify({ name: 'view-game3d-proof', type: 'module', dependencies: { '@forgeax/engine': '*', '@forgeax/view': '*' } }));
50
- await mkdir(join(root, 'node_modules/@forgeax'), { recursive: true });
51
- await symlink(engine, join(root, 'node_modules/@forgeax/engine'), 'dir');
52
- await symlink(view, join(root, 'node_modules/@forgeax/view'), 'dir');
53
- if (localEngine) {
54
- const binding = await command('project', 'engine', 'use-local', checkout);
55
- assert.equal(binding.mode, 'local');
56
- assert.equal(binding.resolved.root, checkout);
57
- assert.equal((await command('project', 'engine', 'check')).healthy, true);
58
- console.error('direct local Engine binding', binding.resolved.entry);
59
- }
60
- browser = await chromium.launch({
61
- ...(process.env.FORGEAX_BROWSER_EXECUTABLE ? { executablePath: process.env.FORGEAX_BROWSER_EXECUTABLE } : {}),
62
- ...(!process.env.FORGEAX_BROWSER_EXECUTABLE && process.env.FORGEAX_CHROME_CHANNEL
63
- ? { channel: process.env.FORGEAX_CHROME_CHANNEL }
64
- : {}),
65
- headless: false,
66
- args: ['--enable-unsafe-webgpu', '--ignore-gpu-blocklist', '--enable-features=Vulkan,UseSkiaRenderer,SharedArrayBuffer', '--use-vulkan=swiftshader', '--use-angle=swiftshader', '--disable-gpu-driver-bug-workarounds', '--disable-dawn-features=disallow_unsafe_apis,tiered_adapter_limits'],
67
- chromiumSandbox: true,
68
- });
69
- page = await browser.newPage({ viewport: workload.gameViewport });
70
- await page.addInitScript(() => { try { localStorage.setItem('forgeax.view.locale', 'en'); } catch {} });
71
- await page.addInitScript(() => {
72
- const progress = globalThis.__gpuProof = { stage: 'document', calls: {}, resolved: {}, pending: {} };
73
- const gpu = navigator.gpu;
74
- if (!gpu) return;
75
- const requestAdapter = gpu.requestAdapter.bind(gpu);
76
- gpu.requestAdapter = async (...args) => {
77
- progress.stage = 'adapter-requested';
78
- const adapter = await requestAdapter(...args);
79
- progress.stage = 'adapter-ready';
80
- if (!adapter) return adapter;
81
- const requestDevice = adapter.requestDevice.bind(adapter);
82
- adapter.requestDevice = async (...args) => {
83
- progress.stage = 'device-requested';
84
- const device = await requestDevice(...args);
85
- progress.stage = 'device-ready';
86
- for (const name of ['createShaderModule', 'createRenderPipeline', 'createComputePipeline', 'createRenderPipelineAsync', 'createComputePipelineAsync', 'popErrorScope']) {
87
- const original = device[name].bind(device);
88
- device[name] = (...args) => {
89
- progress.calls[name] = (progress.calls[name] ?? 0) + 1;
90
- const result = original(...args);
91
- if (name === 'createShaderModule') {
92
- const compilation = result.getCompilationInfo.bind(result);
93
- result.getCompilationInfo = async () => {
94
- progress.calls.compilation = (progress.calls.compilation ?? 0) + 1;
95
- const key = String(progress.calls.compilation);
96
- progress.pending[key] = { label: args[0].label, bytes: args[0].code.length, started: Date.now() };
97
- const info = await compilation();
98
- delete progress.pending[key];
99
- progress.resolved.compilation = (progress.resolved.compilation ?? 0) + 1;
100
- return info;
101
- };
102
- }
103
- if (result?.then) return result.then(value => { progress.resolved[name] = (progress.resolved[name] ?? 0) + 1; return value; });
104
- progress.resolved[name] = (progress.resolved[name] ?? 0) + 1;
105
- return result;
106
- };
107
- }
108
- return device;
109
- };
110
- return adapter;
111
- };
112
- });
113
- page.on('request', request => {
114
- requests.add(request);
115
- const url = new URL(request.url());
116
- let frame;
117
- try { frame = request.frame(); } catch {}
118
- const frameUrl = frame?.url();
119
- requestStarts.set(request, {
120
- phase, origin: url.origin, path: url.pathname,
121
- mainFrame: frame === page.mainFrame(),
122
- targetId: frameUrl?.startsWith('http') ? new URL(frameUrl).searchParams.get('forgeaxWorkspaceTarget') : null,
123
- });
124
- });
125
- page.on('requestfinished', request => requests.delete(request));
126
- page.on('requestfailed', request => {
127
- requests.delete(request);
128
- const path = new URL(request.url()).pathname;
129
- if (stoppingView) failuresDuringViewStop.push(path);
130
- console.error('REQUEST', { ...requestStarts.get(request), failedDuring: phase, failure: request.failure() });
131
- });
132
- page.on('pageerror', error => { errors.push(error.message); console.error('PAGE', error.message); });
133
- page.on('console', message => { if (message.type() === 'error') {
134
- if (!(stoppingView && message.text() === 'Failed to load resource: net::ERR_CONNECTION_REFUSED')) errors.push(message.text());
135
- console.error('CONSOLE', message.text());
136
- } });
137
- backendPid = (await command('backend', 'start', '--host-pack', join(view, 'host.pack.json'))).pid;
138
- const viewState = await command('view', 'start', '--input', viewStartInput);
139
- const bootstrap = await fetch(new URL('/__forgeax/view/bootstrap', viewState.url)).then(response => response.json());
140
- workspace = await connectViewWorkspace({ endpoint: bootstrap.endpoint, token: bootstrap.token, clientId: 'game3d-proof' });
141
- await page.goto(viewState.url);
142
- assert.equal(new URL(viewState.url).searchParams.get('project'), await realpath(root));
143
- await page.waitForFunction(() => !!globalThis.__forgeaxViewReady?.mounted, undefined, { timeout: 60_000 });
144
- await page.waitForURL(url => url.searchParams.has('forgeaxWorkspaceTarget'), { timeout: 300_000 });
145
- await workspace.listAssets(manifest.id);
146
- const originalWorkspaceTarget = new URL(page.url()).searchParams.get('forgeaxWorkspaceTarget');
147
- console.error('default backend -> View URL -> automatic project opening passed');
148
- const content = page.getByTestId('panel-content-browser');
149
- // The initial folder is assets; explicitly exercise root-to-assets navigation.
150
- await content.locator('.cb-address-crumb > [data-action="content-browser.navigate"]').click();
151
- await content.locator('.cb-grid-folder').filter({ hasText: /^assets$/ }).click();
152
- await content.locator('[data-action="content-browser.activate"]').click();
153
- const sceneAsset = (await workspace.getState()).assets.find(asset => asset.kind === 'scene');
154
- assert.ok(sceneAsset);
155
- const sceneName = (sceneAsset.path ?? sceneAsset.sourcePath ?? '').toLowerCase().endsWith('.pack.ts')
156
- ? sceneAsset.sourceKey ?? sceneAsset.name ?? sceneAsset.label ?? sceneAsset.guid
157
- : sceneAsset.name ?? sceneAsset.label ?? sceneAsset.guid;
158
- const scene = content.locator('.cb-grid-item').filter({ has: page.getByText(sceneName, { exact: true }) });
159
- await scene.click();
160
- await page.screenshot({ path: '/tmp/view-game3d-open-asset.png' });
161
- await scene.press('Enter');
162
- await page.getByTestId('panel-hierarchy').getByText('Ground', { exact: true }).waitFor({ timeout: 60_000 });
163
- const opened = await workspace.getState();
164
- const target = opened.preview.target;
165
- const args = { targetId: target.targetId, targetGeneration: target.generation };
166
- const tree = await workspace.getSceneTree(args);
167
- const named = name => { const node = tree.nodes.find(row => row.name === name); assert.ok(node, `game-3d must expose ${name}`); return node; };
168
- const player = named('Player');
169
- const body = named('Player Body');
170
- const ground = named('Ground');
171
- assert.equal(body.parentId, player.entityId);
172
- assert.ok(ground.componentNames.includes('MeshFilter'));
173
- await page.getByTestId('panel-hierarchy').getByText('Ground', { exact: true }).waitFor();
174
- assert.equal(await page.getByTestId('panel-content-browser').getByText('..', { exact: true }).count(), 0, 'external sources must not become parent-navigation folders');
175
- await workspace.call('scene.select', { ...args, entityId: body.entityId });
176
- await page.waitForFunction(id => document.querySelector('[data-testid="panel-inspector"]')?.getAttribute('data-entity-id') === id, body.entityId);
177
- await page.getByText('Player Body', { exact: true }).first().waitFor();
178
- const inspected = await workspace.call('entity.inspect', { ...args, entityId: body.entityId });
179
- assert.ok(inspected.components.some(component => component.name === 'Transform'));
180
- console.error('PR 3281: real game-3d hierarchy, parent relation, components and View inspection passed');
181
- await page.locator('[data-action="editor.play"]').click();
182
- await page.getByRole('status').filter({ hasText: 'Preparing Play' }).waitFor();
183
- await page.locator('[data-action="editor.stop"]').click();
184
- await page.waitForFunction(() => document.querySelector('[data-action="editor.play"]')?.disabled === false);
185
- assert.equal((await workspace.getState()).play, null);
186
- console.error('Play preparation cancellation through the toolbar passed');
187
- const preparationStarted = performance.now();
188
- const play = await workspace.call('play.start', { projectId: manifest.id });
189
- console.error('Play snapshot and target prepared', { waitMs: Math.round(performance.now() - preparationStarted) });
190
- const playArgs = { targetId: play.play.target.targetId, targetGeneration: play.play.target.generation };
191
- console.error('workspace target identities', {
192
- editor: { targetId: target.targetId, origin: new URL(target.url).origin },
193
- play: { targetId: play.play.target.targetId, origin: new URL(play.play.target.url).origin },
194
- });
195
- const waitingSince = performance.now();
196
- const progress = setInterval(() => {
197
- const frame = page.frames().find(frame => frame.url().includes(playArgs.targetId));
198
- void frame?.evaluate(() => globalThis.__gpuProof).then(value => console.error('game GPU progress', JSON.stringify(value))).catch(() => {});
199
- }, 15_000);
200
- try { await workspace.call('play.ready', playArgs); }
201
- catch (error) {
202
- console.error('pending requests', [...requests].map(request => new URL(request.url()).pathname));
203
- console.error('game-3d startup documents', JSON.stringify(await Promise.all(page.frames().map(async frame => ({ url: frame.url().replace(/forgeaxWorkspaceToken=[^&]+/, 'forgeaxWorkspaceToken=REDACTED'), state: await frame.evaluate(() => ({ title: document.title, gpu: globalThis.__gpuProof, text: document.body.innerText.slice(0, 2000), bootstrap: globalThis.__forgeaxRendererBootstrap, execution: globalThis.__forgeaxGameInspection?.renderer(), plugins: globalThis.__forgeaxPluginProjection?.current?.inspect()?.live?.map(({ id, fiberState, requiredServices, providedServices, error }) => ({ id, fiberState, requiredServices, providedServices, error })) })).catch(() => null) }))), null, 2));
204
- throw error;
205
- } finally { clearInterval(progress); }
206
- console.error('game tools ready', { waitMs: Math.round(performance.now() - waitingSince) });
207
- const editorFrame = await page.evaluate(() => document.documentElement.dataset.forgeaxFrameSubmitted);
208
- await page.waitForTimeout(400);
209
- assert.equal(await page.evaluate(() => document.documentElement.dataset.forgeaxFrameSubmitted), editorFrame, 'hidden editor preview must release continuous rendering');
210
- const hiddenCapture = await workspace.capture(args);
211
- assert.match(hiddenCapture.png, /^data:image\/png/);
212
- assert.equal((await workspace.getSceneTree(args)).worldId, target.worldId);
213
- console.error('hidden preview capture and stable World passed');
214
- const game = page.frames().find(frame => frame.url().includes(playArgs.targetId));
215
- assert.ok(game);
216
- try {
217
- await game.waitForFunction(() => Number(document.documentElement.dataset.forgeaxFrameCompleted ?? 0) >= 60, undefined, { timeout: 180_000 });
218
- } catch (error) {
219
- console.error('game frame failure', JSON.stringify(await game.evaluate(() => ({
220
- submitted: document.documentElement.dataset.forgeaxFrameSubmitted,
221
- visibility: document.visibilityState,
222
- gpu: globalThis.__gpuProof,
223
- renderer: globalThis.__forgeaxGameInspection?.renderer(),
224
- bootstrap: globalThis.__forgeaxRendererBootstrap,
225
- }))));
226
- throw error;
227
- }
228
- const liveTree = await workspace.getSceneTree(playArgs);
229
- const livePlayer = liveTree.nodes.find(row => row.name === 'Player');
230
- assert.ok(livePlayer);
231
- const position = async () => (await workspace.call('entity.inspect', { ...playArgs, entityId: livePlayer.entityId })).components.find(component => component.name === 'Transform').values.pos;
232
- const playerInspection = await workspace.call('entity.inspect', { ...playArgs, entityId: livePlayer.entityId });
233
- assert.ok(playerInspection.components.some(component => component.name === 'Game3dThirdPersonRig'), 'the ordinary player plugin must be active');
234
- await game.evaluate(() => {
235
- globalThis.__inputTrace = [];
236
- for (const type of ['pointerdown', 'keydown', 'keyup']) window.addEventListener(type, event => {
237
- globalThis.__inputTrace.push({ type, code: event.code, target: event.target.tagName, focused: document.hasFocus() });
238
- }, { capture: true });
239
- });
240
- const canvas = game.locator('canvas');
241
- await canvas.click();
242
- const walk = async () => {
243
- const frame = await game.evaluate(() => Number(document.documentElement.dataset.forgeaxFrameSubmitted));
244
- await page.keyboard.down('w');
245
- try { await game.waitForFunction(before => Number(document.documentElement.dataset.forgeaxFrameSubmitted) >= before + 4, frame, { timeout: 30_000 }); }
246
- finally { await page.keyboard.up('w'); }
247
- await page.waitForTimeout(100);
248
- };
249
- const before = await position();
250
- await walk();
251
- const after = await position();
252
- console.error('game movement', { before, after, events: await game.evaluate(() => globalThis.__inputTrace) });
253
- assert.ok(Math.hypot(after[0] - before[0], after[2] - before[2]) > 0.01, 'real game input moves the player');
254
- await workspace.call('target.control', { ...playArgs, mode: 'observer' });
255
- const ejected = await position();
256
- await walk();
257
- const whileObserving = await position();
258
- assert.ok(Math.hypot(whileObserving[0] - ejected[0], whileObserving[2] - ejected[2]) < 0.01, 'Eject blocks game input');
259
- await workspace.call('target.control', { ...playArgs, mode: 'player' });
260
- await walk();
261
- const resumed = await position();
262
- assert.ok(Math.hypot(resumed[0] - whileObserving[0], resumed[2] - whileObserving[2]) > 0.01, 'Possess restores game input');
263
- assert.equal((await workspace.getSceneTree(playArgs)).worldId, liveTree.worldId);
264
- await page.screenshot({ path: '/tmp/view-game3d-play.png' });
265
- phase = 'play-stop';
266
- await workspace.call('play.stop', playArgs);
267
- await assertDetached(game);
268
- assert.equal((await workspace.getState()).preview.target.targetId, target.targetId);
269
- assert.deepEqual(errors, []);
270
- workspace.close('View restart proof'); workspace = undefined;
271
- stoppingView = true;
272
- phase = 'view-stop';
273
- await command('view', 'stop');
274
- assert.equal((await command('backend', 'status')).phase, 'ready');
275
- const restarted = await command('view', 'start', '--input', viewStartInput);
276
- phase = 'view-restart';
277
- await page.goto(restarted.url);
278
- await page.waitForURL(url => url.searchParams.has('forgeaxWorkspaceTarget'), { timeout: 300_000 });
279
- stoppingView = false;
280
- phase = 'restarted';
281
- assert.notEqual(new URL(page.url()).searchParams.get('forgeaxWorkspaceTarget'), originalWorkspaceTarget);
282
- console.error('View restart preserves backend; the returned URL directly opens a new target');
283
- assert.ok(failuresDuringViewStop.every(path => path === '/__forgeax/view/health'), 'only the stopped View health probe may refuse a request');
284
- assert.deepEqual(errors, []);
285
- console.log(JSON.stringify({ passed: true, boundary: 'game-3d-hierarchy-and-control', entities: tree.nodes.length, worldId: liveTree.worldId, workload }));
286
- } catch (error) {
287
- await page?.screenshot({ path: '/tmp/view-game3d-failure.png' }).catch(() => {});
288
- console.error('Game 3D failure documents', JSON.stringify(await Promise.all((page?.frames() ?? []).map(async frame => ({
289
- url: frame.url().replace(/forgeaxWorkspaceToken=[^&]+/, 'forgeaxWorkspaceToken=REDACTED'),
290
- state: await frame.evaluate(() => JSON.parse(JSON.stringify({
291
- gpu: globalThis.__gpuProof, submitted: document.documentElement.dataset.forgeaxFrameSubmitted,
292
- completed: document.documentElement.dataset.forgeaxFrameCompleted,
293
- bootstrap: globalThis.__forgeaxRendererBootstrap, execution: globalThis.__forgeaxGameInspection?.renderer(),
294
- canvases: Array.from(document.querySelectorAll('canvas'), canvas => ({ width: canvas.width, height: canvas.height, visible: canvas.checkVisibility() })),
295
- }, (_key, value) => value instanceof Error ? { ...value, message: value.message } : value))).catch(() => null),
296
- }))), null, 2));
297
- console.error('View failure state', await page?.locator('body').innerText().catch(() => 'Page unavailable'));
298
- // [ci-diag] the detached backend writes to its own log file, not the job log.
299
- const backendLog = join(tmpdir(), 'forgeax-backend', createHash('sha256').update(resolve(root)).digest('hex'), 'backend.log');
300
- console.error('BACKEND-LOG-TAIL', await readFile(backendLog, 'utf8')
301
- .then(text => text.split('\n').slice(-150).join('\n'))
302
- .catch(() => '<unavailable>'));
303
- throw error;
304
- } finally {
305
- workspace?.close('game3d proof complete');
306
- await browser?.close();
307
- await command('backend', 'stop');
308
- if (backendPid) {
309
- const deadline = Date.now() + 5000;
310
- const alive = () => {
311
- try { process.kill(backendPid, 0); return true; }
312
- catch (error) { if (error.code === 'ESRCH') return false; throw error; }
313
- };
314
- while (alive() && Date.now() < deadline) await new Promise(resolve => setTimeout(resolve, 50));
315
- assert.equal(alive(), false, 'the project backend process must exit after its plugins dispose');
316
- }
317
- await rm(root, { recursive: true, force: true });
318
- }