@jshookmcp/jshook 0.2.8 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/README.md +36 -5
  2. package/README.zh.md +36 -5
  3. package/dist/{AntiCheatDetector-S8VRj-dD.mjs → AntiCheatDetector-CqGDXmfc.mjs} +160 -54
  4. package/dist/{CodeInjector-4Z3ngPoX.mjs → CodeInjector-BdjRfNx7.mjs} +5 -5
  5. package/dist/ConsoleMonitor-DykL3IAw.mjs +2269 -0
  6. package/dist/{DarwinAPI-B8hg_yhz.mjs → DarwinAPI-ETyy0xyo.mjs} +1 -1
  7. package/dist/DetailedDataManager-HT49OrvF.mjs +217 -0
  8. package/dist/EventBus-DFKvADm3.mjs +141 -0
  9. package/dist/EvidenceGraphBridge-318Oi0Lf.mjs +153 -0
  10. package/dist/{ExtensionManager-D5-bO9D8.mjs → ExtensionManager-BDMsY2Dz.mjs} +27 -13
  11. package/dist/{FingerprintManager-BVxFJL2-.mjs → FingerprintManager-BN4UQWnX.mjs} +1 -1
  12. package/dist/{HardwareBreakpoint-DK1yjWkV.mjs → HardwareBreakpoint-Cc2AFq1Y.mjs} +3 -3
  13. package/dist/{HeapAnalyzer-CEbo10xU.mjs → HeapAnalyzer-DruMgsgj.mjs} +21 -21
  14. package/dist/HookGeneratorBuilders.core.generators.storage-CTbB4Lcx.mjs +566 -0
  15. package/dist/InstrumentationSession-DLH0vd-z.mjs +244 -0
  16. package/dist/{MemoryController-DdtnBdD4.mjs → MemoryController-CMtviNW_.mjs} +3 -3
  17. package/dist/{MemoryScanSession-RMixN3bX.mjs → MemoryScanSession-ITgb_NMi.mjs} +81 -78
  18. package/dist/{MemoryScanner-QjK4ld0B.mjs → MemoryScanner-CiL7Z3ey.mjs} +50 -21
  19. package/dist/{NativeMemoryManager.impl-CB6gJ0NM.mjs → NativeMemoryManager.impl-D9Lkovvn.mjs} +20 -56
  20. package/dist/{NativeMemoryManager.utils-BML4q1ry.mjs → NativeMemoryManager.utils-BBlAixF5.mjs} +1 -1
  21. package/dist/{PEAnalyzer-CK0xe0Fs.mjs → PEAnalyzer-DMQ44gen.mjs} +16 -16
  22. package/dist/PageController-BPJNqqBN.mjs +431 -0
  23. package/dist/{PointerChainEngine-Cd73qu5b.mjs → PointerChainEngine-K7wN8Z-w.mjs} +10 -7
  24. package/dist/PrerequisiteError-TuyZIs6n.mjs +20 -0
  25. package/dist/ProcessRegistry-zGg12QbE.mjs +74 -0
  26. package/dist/ResponseBuilder-CJXWmWNw.mjs +143 -0
  27. package/dist/ReverseEvidenceGraph-C02-gXOh.mjs +269 -0
  28. package/dist/ScriptManager-ZuWD-0Jg.mjs +3003 -0
  29. package/dist/{Speedhack-CeF0XmEz.mjs → Speedhack-D-z0umeT.mjs} +2 -2
  30. package/dist/{StructureAnalyzer-D4GkMduU.mjs → StructureAnalyzer-Cav5AVSL.mjs} +9 -6
  31. package/dist/ToolCatalog-5OJdMiF0.mjs +582 -0
  32. package/dist/ToolError-jh9whhMd.mjs +15 -0
  33. package/dist/ToolProbe-DbCFGyrg.mjs +45 -0
  34. package/dist/ToolRegistry-B9krbTtI.mjs +180 -0
  35. package/dist/ToolRouter.policy-BGDAGyeH.mjs +344 -0
  36. package/dist/TraceRecorder-B41Z5XBj.mjs +1286 -0
  37. package/dist/{Win32API-Bc0QnQsN.mjs → Win32API-C2kjj0ze.mjs} +19 -13
  38. package/dist/{Win32Debug-DUHt9XUn.mjs → Win32Debug-CKrGOTpo.mjs} +3 -3
  39. package/dist/WorkflowEngine-DJ6M4opp.mjs +569 -0
  40. package/dist/analysis-BHeJW2Nb.mjs +1234 -0
  41. package/dist/antidebug-BRKeyt27.mjs +1081 -0
  42. package/dist/artifactRetention-CPXkUJXp.mjs +598 -0
  43. package/dist/artifacts-DkfosXH3.mjs +59 -0
  44. package/dist/authorization-schema-DRqyJMSk.mjs +31 -0
  45. package/dist/betterSqlite3-DLSBZodi.mjs +74 -0
  46. package/dist/binary-instrument--V3MAhJ4.mjs +971 -0
  47. package/dist/bind-helpers-ClV34xdn.mjs +42 -0
  48. package/dist/boringssl-inspector-Bo_LOLaS.mjs +180 -0
  49. package/dist/browser-Dx3_S2cG.mjs +4369 -0
  50. package/dist/capabilities-CcHlvWgK.mjs +33 -0
  51. package/dist/concurrency-Drev_Vz9.mjs +41 -0
  52. package/dist/{constants-CCvsN80K.mjs → constants-CDZLOoVv.mjs} +105 -48
  53. package/dist/coordination-DgItD9DL.mjs +259 -0
  54. package/dist/debugger-RS3RSAqs.mjs +1288 -0
  55. package/dist/definitions-BEoYofW5.mjs +47 -0
  56. package/dist/definitions-BRaefg3u.mjs +365 -0
  57. package/dist/definitions-BbkvZkiv.mjs +96 -0
  58. package/dist/definitions-BtWSHJ3o.mjs +17 -0
  59. package/dist/definitions-C1gCHO0i.mjs +43 -0
  60. package/dist/definitions-CDOg_b-l.mjs +138 -0
  61. package/dist/definitions-CVPD9hzZ.mjs +54 -0
  62. package/dist/definitions-Cea8Lgl7.mjs +94 -0
  63. package/dist/definitions-DAgIyjxM.mjs +10 -0
  64. package/dist/definitions-DJA27nsL.mjs +66 -0
  65. package/dist/definitions-DKPFU3LW.mjs +25 -0
  66. package/dist/definitions-DPRpZQ96.mjs +47 -0
  67. package/dist/definitions-DUE5gmdn.mjs +18 -0
  68. package/dist/definitions-DYVjOtxa.mjs +26 -0
  69. package/dist/definitions-DcYLVLCo.mjs +37 -0
  70. package/dist/definitions-Pp5LI2H4.mjs +27 -0
  71. package/dist/definitions-j9KdHVNR.mjs +14 -0
  72. package/dist/definitions-uzkjBwa7.mjs +258 -0
  73. package/dist/definitions-va-AnLuQ.mjs +28 -0
  74. package/dist/encoding-DJeqHmpd.mjs +1079 -0
  75. package/dist/evidence-graph-bridge-DcYizFk2.mjs +136 -0
  76. package/dist/{factory-CibqTNC8.mjs → factory-C90tBff6.mjs} +41 -56
  77. package/dist/flat-target-session-Dgax2Cy3.mjs +29 -0
  78. package/dist/graphql-CoHrhweh.mjs +1197 -0
  79. package/dist/handlers-4jmR0nMs.mjs +898 -0
  80. package/dist/handlers-BAHPxcch.mjs +789 -0
  81. package/dist/handlers-BOs9b907.mjs +2600 -0
  82. package/dist/handlers-BWXEy6ef.mjs +917 -0
  83. package/dist/handlers-Bndn6QvE.mjs +111 -0
  84. package/dist/handlers-BqC4bD4s.mjs +681 -0
  85. package/dist/handlers-BtYq60bM2.mjs +276 -0
  86. package/dist/handlers-BzgcB4iv.mjs +799 -0
  87. package/dist/handlers-CRyRWj2b.mjs +859 -0
  88. package/dist/handlers-CVv2H1uq.mjs +592 -0
  89. package/dist/handlers-Dl5a7JS4.mjs +572 -0
  90. package/dist/handlers-Dx2d7jt7.mjs +2537 -0
  91. package/dist/handlers-Dz9PYsCa.mjs +2805 -0
  92. package/dist/handlers-HujRKC3b.mjs +661 -0
  93. package/dist/handlers.impl-XWXkQfyi.mjs +807 -0
  94. package/dist/hooks-B1B8NRHL.mjs +898 -0
  95. package/dist/index.mjs +491 -259
  96. package/dist/{logger-BmWzC2lM.mjs → logger-Dh_xb7_2.mjs} +14 -6
  97. package/dist/maintenance-PRMkLVRW.mjs +835 -0
  98. package/dist/manifest-67Bok-Si.mjs +58 -0
  99. package/dist/manifest-6lNTMZAB2.mjs +87 -0
  100. package/dist/manifest-B2duEHiH.mjs +90 -0
  101. package/dist/manifest-B6EY9Vm8.mjs +57 -0
  102. package/dist/manifest-B6nKSbyY.mjs +95 -0
  103. package/dist/manifest-BL8AQNPF.mjs +106 -0
  104. package/dist/manifest-BSZvJJmV.mjs +47 -0
  105. package/dist/manifest-BU7qzUyX.mjs +418 -0
  106. package/dist/manifest-Bl62e8WK.mjs +49 -0
  107. package/dist/manifest-Bo5cXjdt.mjs +82 -0
  108. package/dist/manifest-BpS4gtUK.mjs +1347 -0
  109. package/dist/manifest-Bv65_e2W.mjs +101 -0
  110. package/dist/manifest-BytNIF4Z.mjs +117 -0
  111. package/dist/manifest-C-xtsjS3.mjs +81 -0
  112. package/dist/manifest-CDYl7OhA.mjs +66 -0
  113. package/dist/manifest-CRZ3xmkD.mjs +61 -0
  114. package/dist/manifest-CoW6u4Tp.mjs +132 -0
  115. package/dist/manifest-Cq5zN_8A.mjs +50 -0
  116. package/dist/manifest-D7YZM_2e.mjs +194 -0
  117. package/dist/manifest-DE_VrAeQ.mjs +314 -0
  118. package/dist/manifest-DGsXSCpT.mjs +39 -0
  119. package/dist/manifest-DJ2vfEuW.mjs +156 -0
  120. package/dist/manifest-DPXDYhEu.mjs +80 -0
  121. package/dist/manifest-Dd4fQb0a.mjs +322 -0
  122. package/dist/manifest-Deq6opGg.mjs +223 -0
  123. package/dist/manifest-DfJTafJK.mjs +37 -0
  124. package/dist/manifest-DgOdgN_j.mjs +50 -0
  125. package/dist/manifest-DlbMW4v4.mjs +47 -0
  126. package/dist/manifest-DmVfbH0w.mjs +374 -0
  127. package/dist/manifest-Dog6Ddjr.mjs +109 -0
  128. package/dist/manifest-DvgU5FWb.mjs +58 -0
  129. package/dist/manifest-HsfDBs7j.mjs +50 -0
  130. package/dist/manifest-I8oQHvCG.mjs +186 -0
  131. package/dist/manifest-NvH_a-av.mjs +786 -0
  132. package/dist/manifest-cEJU1v0Z.mjs +129 -0
  133. package/dist/manifest-wOl5XLB12.mjs +112 -0
  134. package/dist/modules-tZozf0LQ.mjs +10635 -0
  135. package/dist/mojo-ipc-DXNEXEqb.mjs +640 -0
  136. package/dist/network-CPVvwvFg.mjs +3852 -0
  137. package/dist/{artifacts-BbdOMET5.mjs → outputPaths-um7lCRY3.mjs} +219 -216
  138. package/dist/parse-args-B4cY5Vx5.mjs +39 -0
  139. package/dist/platform-CYeFoTWp.mjs +2161 -0
  140. package/dist/process-BTbgcVc6.mjs +1306 -0
  141. package/dist/proxy-r8YN6nP1.mjs +192 -0
  142. package/dist/registry-Bl8ZQW61.mjs +34 -0
  143. package/dist/response-CWhh2aLo.mjs +34 -0
  144. package/dist/server/plugin-api.mjs +2 -2
  145. package/dist/shared-state-board-BoZnSoj-.mjs +586 -0
  146. package/dist/sourcemap-BIDHUVXy.mjs +934 -0
  147. package/dist/ssrf-policy-Dsqd-DTX.mjs +166 -0
  148. package/dist/streaming-Dal6utPp.mjs +725 -0
  149. package/dist/tool-builder-BHJp32mV.mjs +186 -0
  150. package/dist/transform-DRVgGG90.mjs +1011 -0
  151. package/dist/types-Bx92KJfT.mjs +4 -0
  152. package/dist/wasm-BYx5UOeG.mjs +1044 -0
  153. package/dist/webcrack-Be0_FccV.mjs +747 -0
  154. package/dist/workflow-BpuKEtvn.mjs +725 -0
  155. package/package.json +82 -49
  156. package/dist/ExtensionManager-CPTJhHFg.mjs +0 -2
  157. package/dist/ToolCatalog-Bq4V2sbJ.mjs +0 -67201
  158. package/dist/{CacheAdapters-CzFNpD9a.mjs → CacheAdapters-jJFy20G-.mjs} +0 -0
  159. package/dist/{StealthVerifier-BzBCFiwx.mjs → StealthVerifier-BWmPgQsv.mjs} +0 -0
  160. package/dist/{VersionDetector-CNXcvD46.mjs → VersionDetector-K3V4vGsw.mjs} +0 -0
  161. package/dist/{formatAddress-ChCSIRWT.mjs → formatAddress-nnMvEohD.mjs} +0 -0
  162. package/dist/{types-BBjOqye-.mjs → types-DDBWs9UP.mjs} +1 -1
@@ -0,0 +1,2805 @@
1
+ import { n as asJsonResponse } from "./response-CWhh2aLo.mjs";
2
+ import { r as argNumber } from "./parse-args-B4cY5Vx5.mjs";
3
+ //#region src/server/domains/canvas/adapters/cocos-adapter.ts
4
+ /**
5
+ * Generates a self-contained JS string that traverses the Cocos Creator scene graph
6
+ * via cc.director.getScene() using DFS and returns a serialisable scene tree.
7
+ *
8
+ * Supports both v2 (cc.Node._children) and v3 (cc.Node.children) APIs.
9
+ * World bounds are computed from worldPosition + contentSize (v3) or getBoundingBox() (v2).
10
+ *
11
+ * @param opts - Dump options (maxDepth, onlyInteractive, onlyVisible)
12
+ */
13
+ function buildCocosSceneTreeDumpPayload(opts) {
14
+ const maxDepth = opts.maxDepth ?? 20;
15
+ const onlyInteractive = opts.onlyInteractive ?? false;
16
+ return `(function() {
17
+ function getChildren(node) {
18
+ if (!node) return [];
19
+ // v3 uses node.children, v2 uses node._children
20
+ if (node.children && Array.isArray(node.children)) return node.children;
21
+ if (node._children && Array.isArray(node._children)) return node._children;
22
+ return [];
23
+ }
24
+
25
+ function getNumChildren(node) {
26
+ if (!node) return 0;
27
+ if (node.children && typeof node.children.length === 'number') return node.children.length;
28
+ if (node._children && typeof node._children.length === 'number') return node._children.length;
29
+ return 0;
30
+ }
31
+
32
+ // Determine if we are on Cocos v3 (has cc.Scene) vs v2
33
+ var isV3 = !!(window.cc && window.cc.Scene);
34
+
35
+ // Cocos v3 node properties
36
+ function getNodeId(node, idx) {
37
+ try {
38
+ if (node.uuid !== undefined && node.uuid !== null && node.uuid !== '') return String(node.uuid);
39
+ } catch(e) {}
40
+ try {
41
+ if (node._uuid !== undefined && node._uuid !== null && node._uuid !== '') return String(node._uuid);
42
+ } catch(e) {}
43
+ return (node.constructor ? node.constructor.name : 'Node') + '_' + idx;
44
+ }
45
+
46
+ function safeProp(node, key, fallback) {
47
+ try { var v = node[key]; return v === undefined || v === null ? fallback : v; } catch(e) { return fallback; }
48
+ }
49
+
50
+ function getWorldBounds(node) {
51
+ if (!node) return { x: 0, y: 0, width: 0, height: 0 };
52
+ try {
53
+ if (isV3) {
54
+ // v3: use worldPosition + contentSize
55
+ var wp = node.worldPosition;
56
+ var cs = node.contentSize;
57
+ if (wp && cs) {
58
+ return {
59
+ x: wp.x - (cs.width / 2),
60
+ y: wp.y - (cs.height / 2),
61
+ width: cs.width,
62
+ height: cs.height
63
+ };
64
+ }
65
+ // fallback: use position + scale + contentSize
66
+ var pos = node.position;
67
+ var sx = safeProp(node, 'scaleX', 1);
68
+ var sy = safeProp(node, 'scaleY', 1);
69
+ var w = cs ? cs.width : safeProp(node, 'width', 0);
70
+ var h = cs ? cs.height : safeProp(node, 'height', 0);
71
+ return {
72
+ x: pos ? pos.x : safeProp(node, 'x', 0),
73
+ y: pos ? pos.y : safeProp(node, 'y', 0),
74
+ width: Math.abs(w * sx),
75
+ height: Math.abs(h * sy)
76
+ };
77
+ } else {
78
+ // v2: use getBoundingBox()
79
+ if (typeof node.getBoundingBox === 'function') {
80
+ var bb = node.getBoundingBox();
81
+ if (bb) return { x: bb.x, y: bb.y, width: bb.width, height: bb.height };
82
+ }
83
+ // v2 fallback
84
+ var px = safeProp(node, 'x', 0) || 0;
85
+ var py = safeProp(node, 'y', 0) || 0;
86
+ var sx2 = safeProp(node, 'scaleX', 1);
87
+ var sy2 = safeProp(node, 'scaleY', 1);
88
+ var w2 = safeProp(node, '_contentSize') && safeProp(node._contentSize, 'width', 0) || safeProp(node, 'width', 0);
89
+ var h2 = safeProp(node, '_contentSize') && safeProp(node._contentSize, 'height', 0) || safeProp(node, 'height', 0);
90
+ return {
91
+ x: px, y: py,
92
+ width: Math.abs(w2 * sx2),
93
+ height: Math.abs(h2 * sy2)
94
+ };
95
+ }
96
+ } catch(e) {
97
+ return {
98
+ x: safeProp(node, 'x', 0),
99
+ y: safeProp(node, 'y', 0),
100
+ width: safeProp(node, 'width', 0) || 0,
101
+ height: safeProp(node, 'height', 0) || 0
102
+ };
103
+ }
104
+ }
105
+
106
+ function isInteractive(node) {
107
+ try {
108
+ // v3: check for Button component or _eventMask
109
+ if (isV3) {
110
+ var comps = node.components;
111
+ if (comps && Array.isArray(comps)) {
112
+ for (var ci = 0; ci < comps.length; ci++) {
113
+ var c = comps[ci];
114
+ if (c && c.constructor && (c.constructor.name === 'Button' || c.constructor.name === 'UICanvas' || c.constructor.name === 'Sprite')) {
115
+ return true;
116
+ }
117
+ }
118
+ }
119
+ if (node._eventMask !== undefined) return !!(node._eventMask & 1);
120
+ } else {
121
+ // v2: check mouseEnabled
122
+ return !!(safeProp(node, 'mouseEnabled', true));
123
+ }
124
+ } catch(e) {}
125
+ return !!(safeProp(node, 'mouseEnabled', true));
126
+ }
127
+
128
+ function getActive(node) {
129
+ if (isV3) {
130
+ return !!(safeProp(node, 'active', true));
131
+ } else {
132
+ return !!(safeProp(node, '_active', true));
133
+ }
134
+ }
135
+
136
+ function getName(node) {
137
+ if (isV3) {
138
+ return safeProp(node, 'name', undefined);
139
+ } else {
140
+ return safeProp(node, '_name', undefined);
141
+ }
142
+ }
143
+
144
+ function getPosition(node) {
145
+ if (isV3) {
146
+ var p = node.position;
147
+ if (p && typeof p.x === 'number') return { x: p.x, y: p.y };
148
+ return { x: safeProp(node, 'x', 0), y: safeProp(node, 'y', 0) };
149
+ } else {
150
+ return { x: safeProp(node, 'x', 0), y: safeProp(node, 'y', 0) };
151
+ }
152
+ }
153
+
154
+ function getScale(node) {
155
+ return {
156
+ x: safeProp(node, 'scaleX', 1),
157
+ y: safeProp(node, 'scaleY', 1)
158
+ };
159
+ }
160
+
161
+ function getRotation(node) {
162
+ if (isV3) {
163
+ return {
164
+ x: safeProp(node, 'rotationX', 0),
165
+ y: safeProp(node, 'rotationY', 0),
166
+ z: safeProp(node, 'rotation', 0)
167
+ };
168
+ } else {
169
+ return {
170
+ x: safeProp(node, 'rotationX', 0),
171
+ y: safeProp(node, 'rotationY', 0),
172
+ z: safeProp(node, 'rotation', 0)
173
+ };
174
+ }
175
+ }
176
+
177
+ function getType(node) {
178
+ if (isV3) {
179
+ return node.constructor ? node.constructor.name : 'Node';
180
+ } else {
181
+ // v2: use _className if available
182
+ return safeProp(node, '_className', node.constructor ? node.constructor.name : 'Node');
183
+ }
184
+ }
185
+
186
+ var totalNodes = 0;
187
+
188
+ function traverse(node, depth, path) {
189
+ if (!node || depth > ${maxDepth}) return null;
190
+ totalNodes++;
191
+
192
+ var active = getActive(node);
193
+ var interactive = isInteractive(node);
194
+ var visible = active;
195
+
196
+ if (${opts.onlyVisible ?? false} && !visible) return null;
197
+ if (${onlyInteractive} && !interactive) return null;
198
+
199
+ var wb = getWorldBounds(node);
200
+ var pos = getPosition(node);
201
+ var scale = getScale(node);
202
+ var rot = getRotation(node);
203
+ var numC = getNumChildren(node);
204
+ var children = null;
205
+
206
+ if (numC > 0) {
207
+ children = [];
208
+ var nodeChildren = getChildren(node);
209
+ for (var i = 0; i < nodeChildren.length; i++) {
210
+ var cn = nodeChildren[i];
211
+ if (!cn) continue;
212
+ var childPath = path ? path + '/' + getNodeId(cn, i) : getNodeId(cn, i);
213
+ var sub = traverse(cn, depth + 1, childPath);
214
+ if (sub) children.push(sub);
215
+ }
216
+ }
217
+
218
+ var alpha = safeProp(node, 'opacity', 1);
219
+ if (alpha === undefined || alpha === null) alpha = safeProp(node, 'alpha', 1);
220
+
221
+ var width = safeProp(node, 'width', 0) || 0;
222
+ var height = safeProp(node, 'height', 0) || 0;
223
+ if (isV3 && node.contentSize) {
224
+ width = safeProp(node.contentSize, 'width', 0) || width;
225
+ height = safeProp(node.contentSize, 'height', 0) || height;
226
+ } else if (!isV3 && node._contentSize) {
227
+ width = safeProp(node._contentSize, 'width', 0) || width;
228
+ height = safeProp(node._contentSize, 'height', 0) || height;
229
+ }
230
+
231
+ var result = {
232
+ id: getNodeId(node, 0),
233
+ type: getType(node),
234
+ name: getName(node),
235
+ visible: visible,
236
+ interactive: interactive,
237
+ alpha: alpha,
238
+ x: pos.x,
239
+ y: pos.y,
240
+ width: width,
241
+ height: height,
242
+ worldBounds: wb,
243
+ path: path || getNodeId(node, 0),
244
+ customData: {
245
+ scaleX: scale.x,
246
+ scaleY: scale.y,
247
+ rotationX: rot.x,
248
+ rotationY: rot.y,
249
+ rotation: rot.z,
250
+ active: active,
251
+ parent: node.parent ? getNodeId(node.parent, -1) : null,
252
+ }
253
+ };
254
+
255
+ if (children && children.length > 0) result.children = children;
256
+ return result;
257
+ }
258
+
259
+ if (!window.cc || !window.cc.director) {
260
+ return {
261
+ engine: 'CocosCreator',
262
+ version: window.cc && window.cc.ENGINE_VERSION ? window.cc.ENGINE_VERSION : undefined,
263
+ canvas: { width: 0, height: 0, dpr: 1, contextType: 'unknown' },
264
+ sceneTree: null,
265
+ totalNodes: 0,
266
+ completeness: 'partial',
267
+ error: 'cc.director not found'
268
+ };
269
+ }
270
+
271
+ var scene = window.cc.director.getScene ? window.cc.director.getScene() : null;
272
+
273
+ var canvasEl = document.querySelector('canvas');
274
+ var cocosVersion = window.cc.ENGINE_VERSION || (isV3 ? '3.x' : '2.x');
275
+ var canvasInfo = {
276
+ width: canvasEl ? canvasEl.width : safeProp(scene, 'width', 0) || 0,
277
+ height: canvasEl ? canvasEl.height : safeProp(scene, 'height', 0) || 0,
278
+ dpr: window.devicePixelRatio || 1,
279
+ contextType: 'unknown'
280
+ };
281
+ if (canvasEl) {
282
+ var gl = canvasEl.getContext('webgl2') || canvasEl.getContext('webgl');
283
+ canvasInfo.contextType = gl ? (gl instanceof WebGL2RenderingContext ? 'webgl2' : 'webgl') : '2d';
284
+ }
285
+
286
+ var sceneTree = scene ? traverse(scene, 0, 'cc.director.getScene()') : null;
287
+
288
+ return {
289
+ engine: 'CocosCreator',
290
+ version: cocosVersion,
291
+ canvas: canvasInfo,
292
+ sceneTree: sceneTree,
293
+ totalNodes: totalNodes,
294
+ completeness: sceneTree ? 'full' : 'partial',
295
+ _meta: { isV3: isV3 }
296
+ };
297
+ })()`;
298
+ }
299
+ /**
300
+ * Generates a self-contained JS string that:
301
+ * 1. Transforms screen coordinates → canvas coordinates
302
+ * 2. Flips Y axis (Cocos uses bottom-left origin, browser uses top-left)
303
+ * 3. Runs hit test via engine (v3) or recursive DFS bounds check (v2 / fallback)
304
+ * 4. Returns all candidates sorted by depth (topmost first)
305
+ *
306
+ * @param opts - Pick options (x, y, canvasId)
307
+ */
308
+ function buildCocosHitTestPayload(opts) {
309
+ const x = opts.x;
310
+ const y = opts.y;
311
+ const canvasId = opts.canvasId;
312
+ return `(function() {
313
+ function getChildren(node) {
314
+ if (!node) return [];
315
+ if (node.children && Array.isArray(node.children)) return node.children;
316
+ if (node._children && Array.isArray(node._children)) return node._children;
317
+ return [];
318
+ }
319
+
320
+ function getNumChildren(node) {
321
+ if (!node) return 0;
322
+ if (node.children && typeof node.children.length === 'number') return node.children.length;
323
+ if (node._children && typeof node._children.length === 'number') return node._children.length;
324
+ return 0;
325
+ }
326
+
327
+ function getNodeId(node, idx) {
328
+ try {
329
+ if (node.uuid !== undefined && node.uuid !== null && node.uuid !== '') return String(node.uuid);
330
+ } catch(e) {}
331
+ try {
332
+ if (node._uuid !== undefined && node._uuid !== null && node._uuid !== '') return String(node._uuid);
333
+ } catch(e) {}
334
+ return (node.constructor ? node.constructor.name : 'Node') + '_' + idx;
335
+ }
336
+
337
+ function safeProp(node, key, fallback) {
338
+ try { var v = node[key]; return v === undefined || v === null ? fallback : v; } catch(e) { return fallback; }
339
+ }
340
+
341
+ function getWorldBounds(node) {
342
+ if (!node) return { x: 0, y: 0, width: 0, height: 0 };
343
+ try {
344
+ var isV3 = !!(window.cc && window.cc.Scene);
345
+ if (isV3) {
346
+ var wp = node.worldPosition;
347
+ var cs = node.contentSize;
348
+ if (wp && cs) {
349
+ return {
350
+ x: wp.x - (cs.width / 2),
351
+ y: wp.y - (cs.height / 2),
352
+ width: cs.width,
353
+ height: cs.height
354
+ };
355
+ }
356
+ var pos = node.position;
357
+ var sx = safeProp(node, 'scaleX', 1);
358
+ var sy = safeProp(node, 'scaleY', 1);
359
+ var w = cs ? cs.width : safeProp(node, 'width', 0);
360
+ var h = cs ? cs.height : safeProp(node, 'height', 0);
361
+ return {
362
+ x: pos ? pos.x : safeProp(node, 'x', 0),
363
+ y: pos ? pos.y : safeProp(node, 'y', 0),
364
+ width: Math.abs(w * sx),
365
+ height: Math.abs(h * sy)
366
+ };
367
+ } else {
368
+ if (typeof node.getBoundingBox === 'function') {
369
+ var bb = node.getBoundingBox();
370
+ if (bb) return { x: bb.x, y: bb.y, width: bb.width, height: bb.height };
371
+ }
372
+ var px = safeProp(node, 'x', 0) || 0;
373
+ var py = safeProp(node, 'y', 0) || 0;
374
+ var sx2 = safeProp(node, 'scaleX', 1);
375
+ var sy2 = safeProp(node, 'scaleY', 1);
376
+ var w2 = safeProp(node, '_contentSize') ? safeProp(node._contentSize, 'width', 0) : safeProp(node, 'width', 0);
377
+ var h2 = safeProp(node, '_contentSize') ? safeProp(node._contentSize, 'height', 0) : safeProp(node, 'height', 0);
378
+ return {
379
+ x: px, y: py,
380
+ width: Math.abs(w2 * sx2),
381
+ height: Math.abs(h2 * sy2)
382
+ };
383
+ }
384
+ } catch(e) {
385
+ return {
386
+ x: safeProp(node, 'x', 0),
387
+ y: safeProp(node, 'y', 0),
388
+ width: safeProp(node, 'width', 0) || 0,
389
+ height: safeProp(node, 'height', 0) || 0
390
+ };
391
+ }
392
+ }
393
+
394
+ function isInteractive(node) {
395
+ try {
396
+ var isV3 = !!(window.cc && window.cc.Scene);
397
+ if (isV3) {
398
+ var comps = node.components;
399
+ if (comps && Array.isArray(comps)) {
400
+ for (var ci = 0; ci < comps.length; ci++) {
401
+ var c = comps[ci];
402
+ if (c && c.constructor && (c.constructor.name === 'Button' || c.constructor.name === 'Sprite')) {
403
+ return true;
404
+ }
405
+ }
406
+ }
407
+ if (node._eventMask !== undefined) return !!(node._eventMask & 1);
408
+ } else {
409
+ return !!(safeProp(node, 'mouseEnabled', true));
410
+ }
411
+ } catch(e) {}
412
+ return !!(safeProp(node, 'mouseEnabled', true));
413
+ }
414
+
415
+ function getActive(node) {
416
+ var isV3 = !!(window.cc && window.cc.Scene);
417
+ if (isV3) return !!(safeProp(node, 'active', true));
418
+ return !!(safeProp(node, '_active', true));
419
+ }
420
+
421
+ function getName(node) {
422
+ var isV3 = !!(window.cc && window.cc.Scene);
423
+ if (isV3) return safeProp(node, 'name', undefined);
424
+ return safeProp(node, '_name', undefined);
425
+ }
426
+
427
+ function getPosition(node) {
428
+ var isV3 = !!(window.cc && window.cc.Scene);
429
+ if (isV3) {
430
+ var p = node.position;
431
+ if (p && typeof p.x === 'number') return { x: p.x, y: p.y };
432
+ return { x: safeProp(node, 'x', 0), y: safeProp(node, 'y', 0) };
433
+ }
434
+ return { x: safeProp(node, 'x', 0), y: safeProp(node, 'y', 0) };
435
+ }
436
+
437
+ function getType(node) {
438
+ var isV3 = !!(window.cc && window.cc.Scene);
439
+ if (isV3) return node.constructor ? node.constructor.name : 'Node';
440
+ return safeProp(node, '_className', node.constructor ? node.constructor.name : 'Node');
441
+ }
442
+
443
+ function nodePath(node) {
444
+ var parts = [];
445
+ var cur = node;
446
+ var scene = window.cc && window.cc.director ? window.cc.director.getScene() : null;
447
+ while (cur && cur !== scene) {
448
+ var name = getName(cur) || getNodeId(cur, 0);
449
+ parts.unshift(name);
450
+ cur = cur.parent;
451
+ }
452
+ parts.unshift('cc.director.getScene()');
453
+ return parts.join('/');
454
+ }
455
+
456
+ var sx = ${x}, sy = ${y};
457
+
458
+ // Find the target canvas
459
+ var canvases = Array.from(document.querySelectorAll('canvas'));
460
+ var targetCanvas = null;
461
+ ${canvasId ? `targetCanvas = document.getElementById(${JSON.stringify(canvasId)}) || canvases[parseInt(${JSON.stringify(canvasId)})] || null;` : `
462
+ for (var ci = canvases.length - 1; ci >= 0; ci--) {
463
+ var r = canvases[ci].getBoundingClientRect();
464
+ if (sx >= r.left && sx <= r.right && sy >= r.top && sy <= r.bottom) {
465
+ targetCanvas = canvases[ci];
466
+ break;
467
+ }
468
+ }`}
469
+
470
+ if (!window.cc || !window.cc.director) {
471
+ return {
472
+ success: false, picked: null, candidates: [], coordinates: {
473
+ screen: { x: sx, y: sy }, canvas: { x: 0, y: 0 }
474
+ },
475
+ hitTestMethod: 'none'
476
+ };
477
+ }
478
+
479
+ var canvasWidth = targetCanvas ? targetCanvas.width : 1920;
480
+ var canvasHeight = targetCanvas ? targetCanvas.height : 1080;
481
+
482
+ // Screen → canvas (CSS pixels to canvas pixels)
483
+ var canvasX = sx, canvasY = sy;
484
+ if (targetCanvas) {
485
+ var rect = targetCanvas.getBoundingClientRect();
486
+ canvasX = (sx - rect.left) * (targetCanvas.width / rect.width);
487
+ canvasY = (sy - rect.top) * (targetCanvas.height / rect.height);
488
+ }
489
+
490
+ // Canvas → Cocos: FLIP Y because Cocos uses bottom-left origin
491
+ var cocosX = canvasX;
492
+ var cocosY = canvasHeight - canvasY;
493
+
494
+ var scene = window.cc.director.getScene ? window.cc.director.getScene() : null;
495
+ var candidates = [];
496
+ var hitTestMethod = 'none';
497
+ var enginePicked = null;
498
+
499
+ // Try v3 native hitTest via cc.Node.EventListener.POSITION
500
+ if (scene && typeof scene.hitTest === 'function') {
501
+ try {
502
+ var nativeHit = scene.hitTest({ x: cocosX, y: cocosY });
503
+ if (nativeHit) {
504
+ enginePicked = nativeHit;
505
+ hitTestMethod = 'engine';
506
+ }
507
+ } catch(e) {}
508
+ }
509
+
510
+ // Recursive DFS hit test — always available
511
+ function hitTestDfs(node, depth, accPath) {
512
+ if (!node) return;
513
+
514
+ var active = getActive(node);
515
+ if (!active) return;
516
+
517
+ var wb = getWorldBounds(node);
518
+
519
+ // Convert screen coordinates to node local
520
+ var cur = node;
521
+ var screenPt = { x: sx, y: sy };
522
+ while (cur) {
523
+ if (cur.globalToLocal) {
524
+ try { screenPt = cur.globalToLocal(screenPt); } catch(e) { break; }
525
+ }
526
+ cur = cur.parent;
527
+ }
528
+ var lx = screenPt.x, ly = screenPt.y;
529
+
530
+ var pos = getPosition(node);
531
+ var nx = pos.x, ny = pos.y;
532
+ var sx2 = safeProp(node, 'scaleX', 1);
533
+ var sy2 = safeProp(node, 'scaleY', 1);
534
+ var w = safeProp(node, 'width', 0) || wb.width;
535
+ var h = safeProp(node, 'height', 0) || wb.height;
536
+
537
+ var inBounds = lx >= nx && lx <= nx + w * Math.abs(sx2) &&
538
+ ly >= ny && ly <= ny + h * Math.abs(sy2);
539
+
540
+ var interactive = isInteractive(node);
541
+
542
+ if (inBounds && interactive) {
543
+ var path = accPath || getNodeId(node, 0);
544
+ var alpha = safeProp(node, 'opacity', 1);
545
+ if (alpha === undefined) alpha = safeProp(node, 'alpha', 1);
546
+
547
+ candidates.push({
548
+ node: {
549
+ id: getNodeId(node, 0),
550
+ type: getType(node),
551
+ name: getName(node),
552
+ visible: active,
553
+ interactive: interactive,
554
+ alpha: alpha,
555
+ x: nx, y: ny,
556
+ width: w * Math.abs(sx2),
557
+ height: h * Math.abs(sy2),
558
+ worldBounds: wb,
559
+ path: path
560
+ },
561
+ depth: depth
562
+ });
563
+ }
564
+
565
+ var nodeChildren = getChildren(node);
566
+ for (var i = 0; i < nodeChildren.length; i++) {
567
+ var cn = nodeChildren[i];
568
+ if (!cn) continue;
569
+ var childPath = accPath ? accPath + '/' + getNodeId(cn, i) : getNodeId(cn, i);
570
+ hitTestDfs(cn, depth + 1, childPath);
571
+ }
572
+ }
573
+
574
+ if (scene) hitTestDfs(scene, 0, 'cc.director.getScene()');
575
+
576
+ // Use engine pick if available, otherwise topmost DFS candidate
577
+ var picked = enginePicked;
578
+ var finalMethod = hitTestMethod;
579
+
580
+ if (!picked && candidates.length > 0) {
581
+ candidates.sort(function(a, b) { return a.depth - b.depth; });
582
+ picked = candidates[0].node;
583
+ finalMethod = 'manual';
584
+ }
585
+
586
+ return {
587
+ success: !!picked,
588
+ picked: picked,
589
+ candidates: candidates,
590
+ coordinates: {
591
+ screen: { x: sx, y: sy },
592
+ canvas: { x: canvasX, y: canvasY },
593
+ stage: { x: cocosX, y: cocosY }
594
+ },
595
+ hitTestMethod: finalMethod
596
+ };
597
+ })()`;
598
+ }
599
+ /**
600
+ * Cocos Creator canvas engine adapter.
601
+ *
602
+ * Handles both Cocos Creator 2.x and 3.x. Version is resolved from cc.ENGINE_VERSION
603
+ * at detect() time.
604
+ */
605
+ var CocosCanvasAdapter = class {
606
+ id = "cocos";
607
+ engine = "CocosCreator";
608
+ version;
609
+ constructor() {
610
+ this.version = void 0;
611
+ }
612
+ async detect(env) {
613
+ try {
614
+ const result = await env.pageController.evaluate(`
615
+ (function() {
616
+ if (typeof window.cc === 'undefined' || window.cc === null) {
617
+ return { present: false, hasDirector: false };
618
+ }
619
+ var cocos = window.cc;
620
+ var hasDirector = !!(cocos.director);
621
+ var isV3 = !!(cocos.Scene);
622
+ var versionStr = cocos.ENGINE_VERSION || (isV3 ? '3.x' : '2.x');
623
+ var major = isV3 ? 3 : 2;
624
+ return {
625
+ present: true,
626
+ hasDirector: hasDirector,
627
+ version: versionStr,
628
+ versionMajor: major
629
+ };
630
+ })()
631
+ `);
632
+ if (!result.present || !result.hasDirector) return null;
633
+ const evidence = ["window.cc detected", "cc.director present"];
634
+ if (result.versionMajor === 2) evidence.push("Cocos Creator v2 API");
635
+ else if (result.versionMajor === 3) evidence.push("Cocos Creator v3 API");
636
+ return {
637
+ engine: this.engine,
638
+ version: result.version,
639
+ confidence: .95,
640
+ evidence,
641
+ adapterId: this.id
642
+ };
643
+ } catch {
644
+ return null;
645
+ }
646
+ }
647
+ async dumpScene(env, opts) {
648
+ const payload = buildCocosSceneTreeDumpPayload(opts);
649
+ const raw = await env.pageController.evaluate(payload);
650
+ return {
651
+ engine: raw.engine,
652
+ version: raw.version,
653
+ canvas: raw.canvas,
654
+ sceneTree: raw.sceneTree ?? {
655
+ id: "empty",
656
+ type: "Scene",
657
+ visible: true,
658
+ interactive: false,
659
+ alpha: 1,
660
+ x: 0,
661
+ y: 0,
662
+ width: raw.canvas?.width ?? 0,
663
+ height: raw.canvas?.height ?? 0,
664
+ worldBounds: {
665
+ x: 0,
666
+ y: 0,
667
+ width: raw.canvas?.width ?? 0,
668
+ height: raw.canvas?.height ?? 0
669
+ },
670
+ path: "cc.director.getScene()"
671
+ },
672
+ totalNodes: raw.totalNodes,
673
+ completeness: raw.completeness === "full" ? "full" : "partial"
674
+ };
675
+ }
676
+ async pickAt(env, opts) {
677
+ const payload = buildCocosHitTestPayload(opts);
678
+ const result = await env.pageController.evaluate(payload);
679
+ return {
680
+ success: result.success,
681
+ picked: result.picked,
682
+ candidates: result.candidates,
683
+ coordinates: result.coordinates,
684
+ hitTestMethod: result.hitTestMethod
685
+ };
686
+ }
687
+ };
688
+ //#endregion
689
+ //#region src/server/domains/canvas/adapters/laya-adapter.ts
690
+ /**
691
+ * Generates a self-contained JS string that traverses Laya.stage via DFS and
692
+ * returns a serialisable scene tree with worldBounds computed via localToGlobal().
693
+ *
694
+ * @param opts - Dump options (maxDepth, onlyInteractive, onlyVisible)
695
+ */
696
+ function buildLayaSceneTreeDumpPayload(opts) {
697
+ const maxDepth = opts.maxDepth ?? 20;
698
+ const onlyInteractive = opts.onlyInteractive ?? false;
699
+ return `(function() {
700
+ function getChildren(node) {
701
+ if (!node) return [];
702
+ if (node.children && node.numChildren !== undefined) return node.children;
703
+ if (node._children) return node._children;
704
+ return [];
705
+ }
706
+
707
+ function getNumChildren(node) {
708
+ if (!node) return 0;
709
+ if (node.numChildren !== undefined) return node.numChildren;
710
+ if (node._children) return node._children.length;
711
+ if (node.children) return Array.isArray(node.children) ? node.children.length : 0;
712
+ return 0;
713
+ }
714
+
715
+ function nodeId(node, idx) {
716
+ if (node.id !== undefined && node.id !== null && node.id !== '') return String(node.id);
717
+ return (node.constructor ? node.constructor.name : 'Node') + '_' + idx;
718
+ }
719
+
720
+ function safeProp(node, key, fallback) {
721
+ try { var v = node[key]; return v === undefined || v === null ? fallback : v; } catch(e) { return fallback; }
722
+ }
723
+
724
+ function localToGlobalRect(node) {
725
+ if (!node) return { x: 0, y: 0, width: 0, height: 0 };
726
+ try {
727
+ var p0 = { x: 0, y: 0 };
728
+ var p1 = { x: safeProp(node, 'width', 0), y: safeProp(node, 'height', 0) };
729
+ var lp0 = node.localToGlobal ? node.localToGlobal(p0) : p0;
730
+ var lp1 = node.localToGlobal ? node.localToGlobal(p1) : p1;
731
+ var w = Math.abs(lp1.x - lp0.x) || safeProp(node, 'width', 0);
732
+ var h = Math.abs(lp1.y - lp0.y) || safeProp(node, 'height', 0);
733
+ return { x: lp0.x, y: lp0.y, width: w, height: h };
734
+ } catch(e) {
735
+ return { x: safeProp(node, 'x', 0), y: safeProp(node, 'y', 0),
736
+ width: safeProp(node, 'width', 0), height: safeProp(node, 'height', 0) };
737
+ }
738
+ }
739
+
740
+ var totalNodes = 0;
741
+
742
+ function traverse(node, depth, path) {
743
+ if (!node || depth > ${maxDepth}) return null;
744
+ totalNodes++;
745
+
746
+ var interactive = !!(safeProp(node, 'mouseEnabled', true));
747
+ var visible = !!(safeProp(node, 'visible', true));
748
+
749
+ if (${opts.onlyVisible ?? false} && !visible) return null;
750
+ if (${onlyInteractive} && !interactive) return null;
751
+
752
+ var wb = localToGlobalRect(node);
753
+ var numC = getNumChildren(node);
754
+ var children = null;
755
+
756
+ if (numC > 0) {
757
+ children = [];
758
+ var nodeChildren = getChildren(node);
759
+ for (var i = 0; i < nodeChildren.length; i++) {
760
+ var cn = nodeChildren[i];
761
+ if (!cn) continue;
762
+ var childPath = path ? path + '/' + nodeId(cn, i) : nodeId(cn, i);
763
+ var sub = traverse(cn, depth + 1, childPath);
764
+ if (sub) children.push(sub);
765
+ }
766
+ }
767
+
768
+ var result = {
769
+ id: nodeId(node, 0),
770
+ type: node.constructor ? node.constructor.name : 'Node',
771
+ name: safeProp(node, 'name', undefined),
772
+ visible: visible,
773
+ interactive: interactive,
774
+ mouseEnabled: safeProp(node, 'mouseEnabled', undefined),
775
+ alpha: safeProp(node, 'alpha', 1),
776
+ x: safeProp(node, 'x', 0),
777
+ y: safeProp(node, 'y', 0),
778
+ width: safeProp(node, 'width', 0),
779
+ height: safeProp(node, 'height', 0),
780
+ worldBounds: wb,
781
+ path: path || nodeId(node, 0),
782
+ customData: {
783
+ scaleX: safeProp(node, 'scaleX', 1),
784
+ scaleY: safeProp(node, 'scaleY', 1),
785
+ rotation: safeProp(node, 'rotation', 0),
786
+ pivotX: safeProp(node, 'pivotX', 0),
787
+ pivotY: safeProp(node, 'pivotY', 0),
788
+ mouseThrough: safeProp(node, 'mouseThrough', false),
789
+ hitArea: !!node.hitArea,
790
+ hitTestPrior: safeProp(node, 'hitTestPrior', undefined),
791
+ }
792
+ };
793
+
794
+ if (children && children.length > 0) result.children = children;
795
+ return result;
796
+ }
797
+
798
+ if (!window.Laya || !window.Laya.stage) {
799
+ return { engine: 'LayaAir', version: window.Laya ? window.Laya.version : undefined,
800
+ canvas: { width: 0, height: 0, dpr: 1, contextType: 'unknown' },
801
+ sceneTree: null, totalNodes: 0, completeness: 'partial',
802
+ error: 'Laya.stage not found' };
803
+ }
804
+
805
+ var stage = window.Laya.stage;
806
+ var layaVersion = window.Laya.version || '2.x';
807
+ var isLaya3 = !!(window.Laya.InputManager);
808
+ var scaleX = stage.clientScaleX !== undefined ? stage.clientScaleX : 1;
809
+ var scaleY = stage.clientScaleY !== undefined ? stage.clientScaleY : 1;
810
+
811
+ var canvasEl = document.querySelector('canvas');
812
+ var canvasInfo = { width: canvasEl ? canvasEl.width : safeProp(stage, 'width', 0),
813
+ height: canvasEl ? canvasEl.height : safeProp(stage, 'height', 0),
814
+ dpr: window.devicePixelRatio || 1,
815
+ contextType: 'unknown' };
816
+ if (canvasEl) {
817
+ var gl = canvasEl.getContext('webgl2') || canvasEl.getContext('webgl');
818
+ canvasInfo.contextType = gl ? (gl instanceof WebGL2RenderingContext ? 'webgl2' : 'webgl') : '2d';
819
+ }
820
+
821
+ var sceneTree = traverse(stage, 0, 'Laya.stage');
822
+
823
+ return {
824
+ engine: 'LayaAir',
825
+ version: layaVersion,
826
+ canvas: canvasInfo,
827
+ sceneTree: sceneTree,
828
+ totalNodes: totalNodes,
829
+ completeness: 'full',
830
+ _meta: { isLaya3: isLaya3, scaleX: scaleX, scaleY: scaleY }
831
+ };
832
+ })()`;
833
+ }
834
+ /**
835
+ * Generates a self-contained JS string that:
836
+ * 1. Transforms screen coordinates → stage coordinates using clientScaleX/Y
837
+ * 2. Runs hit test via Stage.hitTest (3.x) or recursive DFS bounds check (2.x)
838
+ * 3. Returns all candidates sorted by depth (topmost first)
839
+ *
840
+ * @param opts - Pick options (x, y, canvasId)
841
+ */
842
+ function buildLayaHitTestPayload(opts) {
843
+ const x = opts.x;
844
+ const y = opts.y;
845
+ const canvasId = opts.canvasId;
846
+ return `(function() {
847
+ function getChildren(node) {
848
+ if (!node) return [];
849
+ if (node.children && node.numChildren !== undefined) return node.children;
850
+ if (node._children) return node._children;
851
+ return [];
852
+ }
853
+
854
+ function getNumChildren(node) {
855
+ if (!node) return 0;
856
+ if (node.numChildren !== undefined) return node.numChildren;
857
+ if (node._children) return node._children.length;
858
+ if (node.children) return Array.isArray(node.children) ? node.children.length : 0;
859
+ return 0;
860
+ }
861
+
862
+ function nodeId(node, idx) {
863
+ if (node.id !== undefined && node.id !== null && node.id !== '') return String(node.id);
864
+ return (node.constructor ? node.constructor.name : 'Node') + '_' + idx;
865
+ }
866
+
867
+ function safeProp(node, key, fallback) {
868
+ try { var v = node[key]; return v === undefined || v === null ? fallback : v; } catch(e) { return fallback; }
869
+ }
870
+
871
+ function localToGlobalRect(node) {
872
+ if (!node) return { x: 0, y: 0, width: 0, height: 0 };
873
+ try {
874
+ var p0 = { x: 0, y: 0 };
875
+ var p1 = { x: safeProp(node, 'width', 0), y: safeProp(node, 'height', 0) };
876
+ var lp0 = node.localToGlobal ? node.localToGlobal(p0) : p0;
877
+ var lp1 = node.localToGlobal ? node.localToGlobal(p1) : p1;
878
+ var w = Math.abs(lp1.x - lp0.x) || safeProp(node, 'width', 0);
879
+ var h = Math.abs(lp1.y - lp0.y) || safeProp(node, 'height', 0);
880
+ return { x: lp0.x, y: lp0.y, width: w, height: h };
881
+ } catch(e) {
882
+ return { x: safeProp(node, 'x', 0), y: safeProp(node, 'y', 0),
883
+ width: safeProp(node, 'width', 0), height: safeProp(node, 'height', 0) };
884
+ }
885
+ }
886
+
887
+ function nodePath(node) {
888
+ var parts = [];
889
+ var cur = node;
890
+ while (cur && cur !== window.Laya.stage) {
891
+ var name = cur.name || nodeId(cur, 0);
892
+ parts.unshift(name);
893
+ cur = cur.parent;
894
+ }
895
+ parts.unshift('Laya.stage');
896
+ return parts.join('/');
897
+ }
898
+
899
+ var sx = ${x}, sy = ${y};
900
+
901
+ // Find the target canvas
902
+ var canvases = Array.from(document.querySelectorAll('canvas'));
903
+ var targetCanvas = null;
904
+ ${canvasId ? `targetCanvas = document.getElementById(${JSON.stringify(canvasId)}) || canvases[parseInt(${JSON.stringify(canvasId)})] || null;` : `
905
+ for (var ci = canvases.length - 1; ci >= 0; ci--) {
906
+ var r = canvases[ci].getBoundingClientRect();
907
+ if (sx >= r.left && sx <= r.right && sy >= r.top && sy <= r.bottom) {
908
+ targetCanvas = canvases[ci];
909
+ break;
910
+ }
911
+ }`}
912
+
913
+ if (!window.Laya || !window.Laya.stage) {
914
+ return { success: false, picked: null, candidates: [], coordinates: {
915
+ screen: { x: sx, y: sy }, canvas: { x: 0, y: 0 } }, hitTestMethod: 'none' };
916
+ }
917
+
918
+ var stage = window.Laya.stage;
919
+ var isLaya3 = !!(window.Laya.InputManager);
920
+ var scaleX = stage.clientScaleX !== undefined ? stage.clientScaleX : 1;
921
+ var scaleY = stage.clientScaleY !== undefined ? stage.clientScaleY : 1;
922
+
923
+ // Screen → canvas
924
+ var canvasX = sx, canvasY = sy;
925
+ if (targetCanvas) {
926
+ var rect = targetCanvas.getBoundingClientRect();
927
+ canvasX = (sx - rect.left) * (targetCanvas.width / rect.width);
928
+ canvasY = (sy - rect.top) * (targetCanvas.height / rect.height);
929
+ }
930
+
931
+ // Canvas → stage: use mouseX/mouseY when available (set by Laya's event system)
932
+ var stageX = safeProp(stage, 'mouseX', canvasX / (scaleX || 1));
933
+ var stageY = safeProp(stage, 'mouseY', canvasY / (scaleY || 1));
934
+
935
+ var candidates = [];
936
+
937
+ // Try engine-native hitTest first (3.x)
938
+ var hitTestMethod = 'none';
939
+ var enginePicked = null;
940
+
941
+ if (isLaya3 && typeof stage.hitTest === 'function') {
942
+ try {
943
+ var nativeHit = stage.hitTest({ x: stageX, y: stageY });
944
+ if (nativeHit) {
945
+ enginePicked = nativeHit;
946
+ hitTestMethod = 'engine';
947
+ }
948
+ } catch(e) {}
949
+ }
950
+
951
+ // Recursive DFS hit test (always available; 2.x fallback for 3.x too)
952
+ function hitTestDfs(node, depth, accPath) {
953
+ if (!node || !safeProp(node, 'visible', true)) return;
954
+
955
+ var wb = localToGlobalRect(node);
956
+ var mx = sx, my = sy;
957
+
958
+ // Convert screen → node local using parent chain
959
+ var cur = node;
960
+ var screenPt = { x: sx, y: sy };
961
+ while (cur) {
962
+ if (cur.globalToLocal) {
963
+ try { screenPt = cur.globalToLocal(screenPt); } catch(e) { break; }
964
+ }
965
+ cur = cur.parent;
966
+ }
967
+ var lx = screenPt.x, ly = screenPt.y;
968
+
969
+ // Check bounds against node's local coordinate frame
970
+ var nw = safeProp(node, 'width', 0) || (wb.width / (safeProp(node, 'scaleX', 1) || 1));
971
+ var nh = safeProp(node, 'height', 0) || (wb.height / (safeProp(node, 'scaleY', 1) || 1));
972
+ var nx = safeProp(node, 'x', 0), ny = safeProp(node, 'y', 0);
973
+ var pivotX = safeProp(node, 'pivotX', 0), pivotY = safeProp(node, 'pivotY', 0);
974
+
975
+ var inBounds = lx >= nx - pivotX && lx <= nx - pivotX + nw &&
976
+ ly >= ny - pivotY && ly <= ny - pivotY + nh;
977
+
978
+ var interactive = !!(safeProp(node, 'mouseEnabled', true));
979
+
980
+ if (inBounds && interactive) {
981
+ var path = accPath ? accPath + '/' + nodeId(node, 0) : nodeId(node, 0);
982
+ candidates.push({
983
+ node: {
984
+ id: nodeId(node, 0),
985
+ type: node.constructor ? node.constructor.name : 'Node',
986
+ name: safeProp(node, 'name', undefined),
987
+ visible: !!(safeProp(node, 'visible', true)),
988
+ interactive: interactive,
989
+ mouseEnabled: safeProp(node, 'mouseEnabled', undefined),
990
+ alpha: safeProp(node, 'alpha', 1),
991
+ x: nx, y: ny,
992
+ width: nw, height: nh,
993
+ worldBounds: wb,
994
+ path: path
995
+ },
996
+ depth: depth
997
+ });
998
+ }
999
+
1000
+ var nodeChildren = getChildren(node);
1001
+ for (var i = 0; i < nodeChildren.length; i++) {
1002
+ var cn = nodeChildren[i];
1003
+ if (!cn) continue;
1004
+ var childPath = accPath ? accPath + '/' + nodeId(cn, i) : nodeId(cn, i);
1005
+ hitTestDfs(cn, depth + 1, childPath);
1006
+ }
1007
+ }
1008
+
1009
+ hitTestDfs(stage, 0, 'Laya.stage');
1010
+
1011
+ // Use engine pick if available, otherwise topmost DFS candidate
1012
+ var picked = enginePicked;
1013
+ var finalMethod = hitTestMethod;
1014
+
1015
+ if (!picked && candidates.length > 0) {
1016
+ // Sort by depth ascending (deepest/nested first = topmost)
1017
+ candidates.sort(function(a, b) { return a.depth - b.depth; });
1018
+ picked = candidates[0].node;
1019
+ finalMethod = 'manual';
1020
+ }
1021
+
1022
+ return {
1023
+ success: !!picked,
1024
+ picked: picked,
1025
+ candidates: candidates,
1026
+ coordinates: {
1027
+ screen: { x: sx, y: sy },
1028
+ canvas: { x: canvasX, y: canvasY },
1029
+ stage: { x: stageX, y: stageY }
1030
+ },
1031
+ hitTestMethod: finalMethod
1032
+ };
1033
+ })()`;
1034
+ }
1035
+ /**
1036
+ * LayaAir canvas engine adapter.
1037
+ *
1038
+ * Handles both LayaAir 2.x and 3.x. Version is resolved lazily from window.Laya.version
1039
+ * at first detect() call.
1040
+ */
1041
+ var LayaCanvasAdapter = class {
1042
+ id = "laya";
1043
+ engine = "LayaAir";
1044
+ version;
1045
+ constructor() {
1046
+ this.version = void 0;
1047
+ }
1048
+ async detect(env) {
1049
+ try {
1050
+ const result = await env.pageController.evaluate(`
1051
+ (function() {
1052
+ if (typeof window.Laya === 'undefined' || window.Laya === null) {
1053
+ return { present: false, hasStage: false, laya2: false, laya3: false };
1054
+ }
1055
+ var laya = window.Laya;
1056
+ var hasStage = !!(laya.stage);
1057
+ var laya2 = !!(laya.MouseManager);
1058
+ var laya3 = !!(laya.InputManager);
1059
+ var version = laya.version || (laya2 ? '2.x' : laya3 ? '3.x' : undefined);
1060
+ return { present: true, hasStage: hasStage, version: version,
1061
+ laya2: laya2, laya3: laya3 };
1062
+ })()
1063
+ `);
1064
+ if (!result.present || !result.hasStage) return null;
1065
+ const evidence = ["window.Laya is defined"];
1066
+ if (result.laya2) evidence.push("Laya.MouseManager detected (LayaAir 2.x)");
1067
+ if (result.laya3) evidence.push("Laya.InputManager detected (LayaAir 3.x)");
1068
+ evidence.push("Laya.stage is present");
1069
+ return {
1070
+ engine: this.engine,
1071
+ version: result.version,
1072
+ confidence: .95,
1073
+ evidence,
1074
+ adapterId: this.id
1075
+ };
1076
+ } catch {
1077
+ return null;
1078
+ }
1079
+ }
1080
+ async dumpScene(env, opts) {
1081
+ const payload = buildLayaSceneTreeDumpPayload(opts);
1082
+ const raw = await env.pageController.evaluate(payload);
1083
+ return {
1084
+ engine: raw.engine,
1085
+ version: raw.version,
1086
+ canvas: raw.canvas,
1087
+ sceneTree: raw.sceneTree ?? {
1088
+ id: "empty",
1089
+ type: "Stage",
1090
+ visible: true,
1091
+ interactive: false,
1092
+ alpha: 1,
1093
+ x: 0,
1094
+ y: 0,
1095
+ width: raw.canvas?.width ?? 0,
1096
+ height: raw.canvas?.height ?? 0,
1097
+ worldBounds: {
1098
+ x: 0,
1099
+ y: 0,
1100
+ width: raw.canvas?.width ?? 0,
1101
+ height: raw.canvas?.height ?? 0
1102
+ },
1103
+ path: "Laya.stage"
1104
+ },
1105
+ totalNodes: raw.totalNodes,
1106
+ completeness: raw.completeness === "full" ? "full" : "partial"
1107
+ };
1108
+ }
1109
+ async pickAt(env, opts) {
1110
+ const payload = buildLayaHitTestPayload(opts);
1111
+ const result = await env.pageController.evaluate(payload);
1112
+ return {
1113
+ success: result.success,
1114
+ picked: result.picked,
1115
+ candidates: result.candidates,
1116
+ coordinates: result.coordinates,
1117
+ hitTestMethod: result.hitTestMethod
1118
+ };
1119
+ }
1120
+ async traceClick(env, opts, services) {
1121
+ const { debuggerManager, traceRecorder, evidenceStore } = services;
1122
+ let traceId = "no-recording";
1123
+ try {
1124
+ traceId = (await traceRecorder.start(null, null, { recordMemoryDeltas: false })).sessionId;
1125
+ } catch {}
1126
+ await debuggerManager.enable();
1127
+ await debuggerManager.ensureAdvancedFeatures();
1128
+ const patchResult = await env.pageController.evaluate(`
1129
+ (function() {
1130
+ var k = Symbol.for('__laya_event_patched');
1131
+ var instrumented = [];
1132
+ function patchTarget(proto) {
1133
+ if (!proto || !proto.event || proto[k]) return;
1134
+ var original = proto.event;
1135
+ if (typeof original !== 'function') return;
1136
+ Object.defineProperty(proto, 'event', {
1137
+ value: function(type, data) {
1138
+ if (!this[k]) this[k] = new Set();
1139
+ if (this[k].has(type)) return; // idempotent
1140
+ this[k].add(type);
1141
+ return original.call(this, type, data);
1142
+ },
1143
+ configurable: true,
1144
+ enumerable: false
1145
+ });
1146
+ instrumented.push(proto.constructor ? proto.constructor.name : 'EventDispatcher');
1147
+ }
1148
+
1149
+ // Walk prototype chain
1150
+ var seen = new Set();
1151
+ var queue = [window.Laya && window.Laya.EventDispatcher ? window.Laya.EventDispatcher.prototype : null];
1152
+ while (queue.length) {
1153
+ var p = queue.shift();
1154
+ if (!p || seen.has(p)) continue;
1155
+ seen.add(p);
1156
+ patchTarget(p);
1157
+ if (Object.getPrototypeOf) {
1158
+ var mp = Object.getPrototypeOf(p);
1159
+ if (mp) queue.push(mp);
1160
+ }
1161
+ }
1162
+
1163
+ return { success: true, instrumented: instrumented };
1164
+ })()
1165
+ `);
1166
+ const breakpointType = opts.breakpointType ?? "click";
1167
+ await debuggerManager.getEventManager().setEventListenerBreakpoint(breakpointType);
1168
+ const domEventChain = await env.pageController.evaluate(`
1169
+ (function() {
1170
+ var ev = new PointerEvent(${JSON.stringify(breakpointType)}, {
1171
+ view: window, bubbles: true, cancelable: true,
1172
+ clientX: ${opts.targetNodeId ?? 0}, clientY: 0, pointerId: 1
1173
+ });
1174
+ var events = [];
1175
+ var el = document.elementFromPoint(0, 0);
1176
+ if (el) el.dispatchEvent(ev);
1177
+ events.push(${JSON.stringify(breakpointType)});
1178
+ return events;
1179
+ })()
1180
+ `);
1181
+ const pausedState = await debuggerManager.waitForPaused(5e3);
1182
+ const handlerFrames = pausedState?.callFrames ? pausedState.callFrames.slice(0, opts.maxFrames ?? 50).map((f) => ({
1183
+ functionName: f.functionName || "(anonymous)",
1184
+ scriptUrl: f.url,
1185
+ lineNumber: f.location?.lineNumber,
1186
+ columnNumber: f.location?.columnNumber
1187
+ })) : [];
1188
+ await debuggerManager.resume();
1189
+ evidenceStore.addNode("function", `laya-click-trace:${traceId}`, {
1190
+ engine: this.engine,
1191
+ version: this.version,
1192
+ handlersFound: handlerFrames.length,
1193
+ instrumentedPrototypes: patchResult?.instrumented ?? []
1194
+ });
1195
+ try {
1196
+ await traceRecorder.stop();
1197
+ } catch {}
1198
+ return {
1199
+ inputFlow: domEventChain,
1200
+ hitTarget: null,
1201
+ domEventChain: handlerFrames.map((f) => ({
1202
+ type: breakpointType,
1203
+ target: f.scriptUrl,
1204
+ phase: "at-target"
1205
+ })),
1206
+ engineDispatchChain: patchResult?.instrumented ?? [],
1207
+ handlerFrames,
1208
+ handlersTriggered: handlerFrames.map((f) => ({
1209
+ functionName: f.functionName,
1210
+ scriptUrl: f.scriptUrl,
1211
+ lineNumber: f.lineNumber
1212
+ })),
1213
+ networkEmitted: []
1214
+ };
1215
+ }
1216
+ };
1217
+ //#endregion
1218
+ //#region src/server/domains/canvas/adapters/phaser-adapter.ts
1219
+ /**
1220
+ * Generates a self-contained JS string that traverses Phaser scenes via DFS and
1221
+ * returns a serialisable scene tree with worldBounds computed via getBounds().
1222
+ *
1223
+ * @param opts - Dump options (maxDepth, onlyInteractive, onlyVisible)
1224
+ */
1225
+ function buildPhaserSceneTreeDumpPayload(opts) {
1226
+ const maxDepth = opts.maxDepth ?? 20;
1227
+ const onlyInteractive = opts.onlyInteractive ?? false;
1228
+ return `(function() {
1229
+ function getChildren(node) {
1230
+ if (!node) return [];
1231
+ if (node.list && Array.isArray(node.list)) return node.list;
1232
+ if (node.children && Array.isArray(node.children)) return node.children;
1233
+ return [];
1234
+ }
1235
+
1236
+ function getNumChildren(node) {
1237
+ if (!node) return 0;
1238
+ if (node.list) return node.list.length;
1239
+ if (node.numChildren !== undefined) return node.numChildren;
1240
+ if (node.children && Array.isArray(node.children)) return node.children.length;
1241
+ return 0;
1242
+ }
1243
+
1244
+ function nodeId(node, idx) {
1245
+ if (node.id !== undefined && node.id !== null && node.id !== '') return String(node.id);
1246
+ return (node.constructor ? node.constructor.name : 'DisplayObject') + '_' + idx;
1247
+ }
1248
+
1249
+ function safeProp(node, key, fallback) {
1250
+ try { var v = node[key]; return v === undefined || v === null ? fallback : v; } catch(e) { return fallback; }
1251
+ }
1252
+
1253
+ function nodeBounds(node) {
1254
+ if (!node) return { x: 0, y: 0, width: 0, height: 0 };
1255
+ try {
1256
+ if (typeof node.getBounds === 'function') {
1257
+ var b = node.getBounds();
1258
+ return { x: b.x, y: b.y, width: b.width, height: b.height };
1259
+ }
1260
+ } catch(e) {}
1261
+ return {
1262
+ x: safeProp(node, 'x', 0),
1263
+ y: safeProp(node, 'y', 0),
1264
+ width: safeProp(node, 'width', 0) || safeProp(node, 'displayWidth', 0),
1265
+ height: safeProp(node, 'height', 0) || safeProp(node, 'displayHeight', 0),
1266
+ };
1267
+ }
1268
+
1269
+ var totalNodes = 0;
1270
+
1271
+ function traverse(node, depth, path) {
1272
+ if (!node || depth > ${maxDepth}) return null;
1273
+ totalNodes++;
1274
+
1275
+ var interactive = !!(node.input && node.input.enabled);
1276
+ var visible = !!(safeProp(node, 'visible', true));
1277
+
1278
+ if (${opts.onlyVisible ?? false} && !visible) return null;
1279
+ if (${onlyInteractive} && !interactive) return null;
1280
+
1281
+ var wb = nodeBounds(node);
1282
+ var numC = getNumChildren(node);
1283
+ var children = null;
1284
+
1285
+ if (numC > 0) {
1286
+ children = [];
1287
+ var nodeChildren = getChildren(node);
1288
+ for (var i = 0; i < nodeChildren.length; i++) {
1289
+ var cn = nodeChildren[i];
1290
+ if (!cn) continue;
1291
+ var childPath = path ? path + '/' + nodeId(cn, i) : nodeId(cn, i);
1292
+ var sub = traverse(cn, depth + 1, childPath);
1293
+ if (sub) children.push(sub);
1294
+ }
1295
+ }
1296
+
1297
+ var result = {
1298
+ id: nodeId(node, 0),
1299
+ type: node.constructor ? node.constructor.name : 'DisplayObject',
1300
+ name: safeProp(node, 'name', undefined),
1301
+ visible: visible,
1302
+ interactive: interactive,
1303
+ alpha: safeProp(node, 'alpha', 1),
1304
+ x: safeProp(node, 'x', 0),
1305
+ y: safeProp(node, 'y', 0),
1306
+ width: safeProp(node, 'width', 0) || safeProp(node, 'displayWidth', 0),
1307
+ height: safeProp(node, 'height', 0) || safeProp(node, 'displayHeight', 0),
1308
+ worldBounds: wb,
1309
+ path: path || nodeId(node, 0),
1310
+ };
1311
+
1312
+ if (children && children.length > 0) result.children = children;
1313
+ return result;
1314
+ }
1315
+
1316
+ if (typeof window.Phaser === 'undefined' || !window.Phaser.GAMES || window.Phaser.GAMES.length === 0) {
1317
+ return { engine: 'Phaser',
1318
+ version: typeof window.Phaser !== 'undefined' ? window.Phaser.VERSION : undefined,
1319
+ canvas: { width: 0, height: 0, dpr: 1, contextType: 'unknown' },
1320
+ sceneTree: null, totalNodes: 0, completeness: 'partial',
1321
+ error: 'Phaser.GAMES has no entries' };
1322
+ }
1323
+
1324
+ var game = window.Phaser.GAMES[0];
1325
+ if (!game || !game.scene || !game.canvas) {
1326
+ return { engine: 'Phaser',
1327
+ version: window.Phaser.VERSION || undefined,
1328
+ canvas: { width: 0, height: 0, dpr: 1, contextType: 'unknown' },
1329
+ sceneTree: null, totalNodes: 0, completeness: 'partial',
1330
+ error: 'Game or canvas not accessible' };
1331
+ }
1332
+
1333
+ var canvasEl = game.canvas;
1334
+ var dpr = window.devicePixelRatio || 1;
1335
+ var gl = null;
1336
+ try { gl = canvasEl.getContext('webgl2') || canvasEl.getContext('webgl'); } catch(e) {}
1337
+ var contextType = '2d';
1338
+ if (gl) contextType = gl instanceof WebGL2RenderingContext ? 'webgl2' : 'webgl';
1339
+
1340
+ var canvasInfo = {
1341
+ width: canvasEl.width || safeProp(game.scale, 'width', 0),
1342
+ height: canvasEl.height || safeProp(game.scale, 'height', 0),
1343
+ dpr: dpr,
1344
+ contextType: contextType,
1345
+ };
1346
+
1347
+ var sceneTree = null;
1348
+ var scenes = game.scene ? game.scene.scenes : [];
1349
+
1350
+ if (scenes && scenes.length > 0) {
1351
+ for (var si = 0; si < scenes.length; si++) {
1352
+ var sc = scenes[si];
1353
+ if (!sc || !sc.sys) continue;
1354
+ var status = safeProp(sc.sys, 'settings', {}).status;
1355
+ // Only process active scenes (Phaser 3.x status: 0=init,1=start,2=loading,3=created,4=shutdown)
1356
+ if (status !== undefined && status !== 4) {
1357
+ var displayList = sc.sys && sc.sys.displayList;
1358
+ if (displayList) {
1359
+ var scenePath = 'Phaser.Game.scenes[' + si + ']';
1360
+ sceneTree = traverse(displayList, 0, scenePath);
1361
+ }
1362
+ break;
1363
+ }
1364
+ }
1365
+ }
1366
+
1367
+ return {
1368
+ engine: 'Phaser',
1369
+ version: window.Phaser.VERSION || undefined,
1370
+ canvas: canvasInfo,
1371
+ sceneTree: sceneTree,
1372
+ totalNodes: totalNodes,
1373
+ completeness: sceneTree ? 'full' : 'partial',
1374
+ };
1375
+ })()`;
1376
+ }
1377
+ /**
1378
+ * Generates a self-contained JS string that:
1379
+ * 1. Transforms screen coordinates to canvas coordinates
1380
+ * 2. Runs hit test via scene.input.hitTestPointer() (Phaser's native method)
1381
+ * 3. Falls back to recursive bounds check using getBounds() / getTopLeft/getBottomRight
1382
+ * 4. Returns all candidates sorted by depth (topmost first)
1383
+ *
1384
+ * @param opts - Pick options (x, y, canvasId)
1385
+ */
1386
+ function buildPhaserHitTestPayload(opts) {
1387
+ const x = opts.x;
1388
+ const y = opts.y;
1389
+ const canvasId = opts.canvasId;
1390
+ return `(function() {
1391
+ function getChildren(node) {
1392
+ if (!node) return [];
1393
+ if (node.list && Array.isArray(node.list)) return node.list;
1394
+ if (node.children && Array.isArray(node.children)) return node.children;
1395
+ return [];
1396
+ }
1397
+
1398
+ function getNumChildren(node) {
1399
+ if (!node) return 0;
1400
+ if (node.list) return node.list.length;
1401
+ if (node.numChildren !== undefined) return node.numChildren;
1402
+ if (node.children && Array.isArray(node.children)) return node.children.length;
1403
+ return 0;
1404
+ }
1405
+
1406
+ function nodeId(node, idx) {
1407
+ if (node.id !== undefined && node.id !== null && node.id !== '') return String(node.id);
1408
+ return (node.constructor ? node.constructor.name : 'DisplayObject') + '_' + idx;
1409
+ }
1410
+
1411
+ function safeProp(node, key, fallback) {
1412
+ try { var v = node[key]; return v === undefined || v === null ? fallback : v; } catch(e) { return fallback; }
1413
+ }
1414
+
1415
+ function nodeBounds(node) {
1416
+ if (!node) return { x: 0, y: 0, width: 0, height: 0 };
1417
+ try {
1418
+ if (typeof node.getBounds === 'function') {
1419
+ var b = node.getBounds();
1420
+ return { x: b.x, y: b.y, width: b.width, height: b.height };
1421
+ }
1422
+ } catch(e) {}
1423
+ return {
1424
+ x: safeProp(node, 'x', 0),
1425
+ y: safeProp(node, 'y', 0),
1426
+ width: safeProp(node, 'width', 0) || safeProp(node, 'displayWidth', 0),
1427
+ height: safeProp(node, 'height', 0) || safeProp(node, 'displayHeight', 0),
1428
+ };
1429
+ }
1430
+
1431
+ function nodePath(node, sceneIdx) {
1432
+ var parts = [];
1433
+ var cur = node;
1434
+ while (cur) {
1435
+ var name = cur.name || nodeId(cur, 0);
1436
+ parts.unshift(name);
1437
+ cur = cur.parent;
1438
+ }
1439
+ return 'Phaser.Game.scenes[' + sceneIdx + ']/' + parts.join('/');
1440
+ }
1441
+
1442
+ var sx = ${x}, sy = ${y};
1443
+
1444
+ // Find the target canvas
1445
+ var canvases = Array.from(document.querySelectorAll('canvas'));
1446
+ var targetCanvas = null;
1447
+ ${canvasId ? `targetCanvas = document.getElementById(${JSON.stringify(canvasId)}) || canvases[parseInt(${JSON.stringify(canvasId)})] || null;` : `
1448
+ for (var ci = canvases.length - 1; ci >= 0; ci--) {
1449
+ var r = canvases[ci].getBoundingClientRect();
1450
+ if (sx >= r.left && sx <= r.right && sy >= r.top && sy <= r.bottom) {
1451
+ targetCanvas = canvases[ci];
1452
+ break;
1453
+ }
1454
+ }`}
1455
+
1456
+ if (typeof window.Phaser === 'undefined' || !window.Phaser.GAMES || window.Phaser.GAMES.length === 0) {
1457
+ return { success: false, picked: null, candidates: [], coordinates: {
1458
+ screen: { x: sx, y: sy }, canvas: { x: 0, y: 0 } }, hitTestMethod: 'none' };
1459
+ }
1460
+
1461
+ var game = window.Phaser.GAMES[0];
1462
+ var canvasX = sx, canvasY = sy;
1463
+ if (targetCanvas) {
1464
+ var rect = targetCanvas.getBoundingClientRect();
1465
+ canvasX = (sx - rect.left) * (targetCanvas.width / rect.width);
1466
+ canvasY = (sy - rect.top) * (targetCanvas.height / rect.height);
1467
+ }
1468
+
1469
+ var candidates = [];
1470
+ var hitTestMethod = 'none';
1471
+ var enginePicked = null;
1472
+ var sceneIdx = 0;
1473
+
1474
+ // Try engine-native hit test via scene.input.hitTestPointer
1475
+ if (game && game.scene && game.scene.scenes) {
1476
+ var scenes = game.scene.scenes;
1477
+ for (var si = 0; si < scenes.length; si++) {
1478
+ var sc = scenes[si];
1479
+ if (!sc || !sc.sys) continue;
1480
+ var status = safeProp(sc.sys, 'settings', {}).status;
1481
+ if (status !== undefined && status !== 4) {
1482
+ // Scene is active
1483
+ if (sc.input && typeof sc.input.hitTestPointer === 'function') {
1484
+ try {
1485
+ var nativeHit = sc.input.hitTestPointer({ x: canvasX, y: canvasY });
1486
+ if (nativeHit && nativeHit.length > 0) {
1487
+ enginePicked = nativeHit[0];
1488
+ hitTestMethod = 'engine';
1489
+ sceneIdx = si;
1490
+ }
1491
+ } catch(e) {}
1492
+ }
1493
+ break;
1494
+ }
1495
+ }
1496
+ }
1497
+
1498
+ // Recursive DFS fallback bounds check
1499
+ function hitTestDfs(node, depth, accPath) {
1500
+ if (!node || !safeProp(node, 'visible', true)) return;
1501
+
1502
+ var wb = nodeBounds(node);
1503
+
1504
+ // Convert screen → node local using parent chain
1505
+ var cur = node;
1506
+ var screenPt = { x: sx, y: sy };
1507
+ while (cur) {
1508
+ if (cur.globalToLocal) {
1509
+ try { screenPt = cur.globalToLocal(screenPt); } catch(e) { break; }
1510
+ }
1511
+ cur = cur.parent;
1512
+ }
1513
+ var lx = screenPt.x, ly = screenPt.y;
1514
+
1515
+ var nw = safeProp(node, 'width', 0) || safeProp(node, 'displayWidth', 0) || wb.width;
1516
+ var nh = safeProp(node, 'height', 0) || safeProp(node, 'displayHeight', 0) || wb.height;
1517
+ var nx = safeProp(node, 'x', 0), ny = safeProp(node, 'y', 0);
1518
+ var pivotX = safeProp(node, 'pivotX', 0), pivotY = safeProp(node, 'pivotY', 0);
1519
+
1520
+ var inBounds = lx >= nx - pivotX && lx <= nx - pivotX + nw &&
1521
+ ly >= ny - pivotY && ly <= ny - pivotY + nh;
1522
+
1523
+ var interactive = !!(node.input && node.input.enabled);
1524
+
1525
+ if (inBounds && interactive) {
1526
+ var path = accPath || nodePath(node, sceneIdx);
1527
+ candidates.push({
1528
+ node: {
1529
+ id: nodeId(node, 0),
1530
+ type: node.constructor ? node.constructor.name : 'DisplayObject',
1531
+ name: safeProp(node, 'name', undefined),
1532
+ visible: !!(safeProp(node, 'visible', true)),
1533
+ interactive: interactive,
1534
+ alpha: safeProp(node, 'alpha', 1),
1535
+ x: nx, y: ny,
1536
+ width: nw, height: nh,
1537
+ worldBounds: wb,
1538
+ path: path,
1539
+ },
1540
+ depth: depth
1541
+ });
1542
+ }
1543
+
1544
+ var nodeChildren = getChildren(node);
1545
+ for (var i = 0; i < nodeChildren.length; i++) {
1546
+ var cn = nodeChildren[i];
1547
+ if (!cn) continue;
1548
+ var childPath = accPath ? accPath + '/' + nodeId(cn, i) : nodePath(cn, sceneIdx);
1549
+ hitTestDfs(cn, depth + 1, childPath);
1550
+ }
1551
+ }
1552
+
1553
+ // Traverse displayList for manual hit test
1554
+ if (game && game.scene && game.scene.scenes) {
1555
+ var scenes2 = game.scene.scenes;
1556
+ for (var sj = 0; sj < scenes2.length; sj++) {
1557
+ var scj = scenes2[sj];
1558
+ if (!scj || !scj.sys) continue;
1559
+ var statusj = safeProp(scj.sys, 'settings', {}).status;
1560
+ if (statusj !== undefined && statusj !== 4) {
1561
+ sceneIdx = sj;
1562
+ var displayList = scj.sys && scj.sys.displayList;
1563
+ if (displayList) {
1564
+ hitTestDfs(displayList, 0, 'Phaser.Game.scenes[' + sj + ']');
1565
+ }
1566
+ break;
1567
+ }
1568
+ }
1569
+ }
1570
+
1571
+ // Use engine pick if available, otherwise topmost DFS candidate
1572
+ var picked = enginePicked ? {
1573
+ id: enginePicked.id !== undefined ? String(enginePicked.id) : nodeId(enginePicked, 0),
1574
+ type: enginePicked.constructor ? enginePicked.constructor.name : 'DisplayObject',
1575
+ name: safeProp(enginePicked, 'name', undefined),
1576
+ visible: !!(safeProp(enginePicked, 'visible', true)),
1577
+ interactive: !!(enginePicked.input && enginePicked.input.enabled),
1578
+ alpha: safeProp(enginePicked, 'alpha', 1),
1579
+ x: safeProp(enginePicked, 'x', 0),
1580
+ y: safeProp(enginePicked, 'y', 0),
1581
+ width: safeProp(enginePicked, 'width', 0) || safeProp(enginePicked, 'displayWidth', 0),
1582
+ height: safeProp(enginePicked, 'height', 0) || safeProp(enginePicked, 'displayHeight', 0),
1583
+ worldBounds: nodeBounds(enginePicked),
1584
+ path: 'Phaser.Game.scenes[' + sceneIdx + ']/hit',
1585
+ } : null;
1586
+
1587
+ if (!picked && candidates.length > 0) {
1588
+ candidates.sort(function(a, b) { return a.depth - b.depth; });
1589
+ picked = candidates[0].node;
1590
+ hitTestMethod = 'manual';
1591
+ }
1592
+
1593
+ return {
1594
+ success: !!picked,
1595
+ picked: picked,
1596
+ candidates: candidates,
1597
+ coordinates: {
1598
+ screen: { x: sx, y: sy },
1599
+ canvas: { x: canvasX, y: canvasY },
1600
+ },
1601
+ hitTestMethod: hitTestMethod
1602
+ };
1603
+ })()`;
1604
+ }
1605
+ /**
1606
+ * Phaser 3.x canvas engine adapter.
1607
+ *
1608
+ * Detection checks window.Phaser and Phaser.GAMES.length > 0.
1609
+ * dumpScene() traverses Phaser.GAMES[0].scene.scenes active scenes.
1610
+ * pickAt() uses scene.input.hitTestPointer() with recursive DFS fallback.
1611
+ */
1612
+ var PhaserCanvasAdapter = class {
1613
+ id = "phaser";
1614
+ engine = "Phaser";
1615
+ version;
1616
+ constructor() {
1617
+ this.version = void 0;
1618
+ }
1619
+ async detect(env) {
1620
+ try {
1621
+ const result = await env.pageController.evaluate(`
1622
+ (function() {
1623
+ if (typeof window.Phaser === 'undefined' || window.Phaser === null) {
1624
+ return { present: false, hasGames: false };
1625
+ }
1626
+ var hasGames = !!(window.Phaser.GAMES && window.Phaser.GAMES.length > 0);
1627
+ return {
1628
+ present: true,
1629
+ hasGames: hasGames,
1630
+ version: window.Phaser.VERSION || undefined,
1631
+ };
1632
+ })()
1633
+ `);
1634
+ if (!result.present || !result.hasGames) return null;
1635
+ const evidence = ["window.Phaser detected"];
1636
+ evidence.push("Phaser.GAMES has entries");
1637
+ return {
1638
+ engine: this.engine,
1639
+ version: result.version,
1640
+ confidence: .95,
1641
+ evidence,
1642
+ adapterId: this.id
1643
+ };
1644
+ } catch {
1645
+ return null;
1646
+ }
1647
+ }
1648
+ async dumpScene(env, opts) {
1649
+ const payload = buildPhaserSceneTreeDumpPayload(opts);
1650
+ const raw = await env.pageController.evaluate(payload);
1651
+ return {
1652
+ engine: raw.engine,
1653
+ version: raw.version,
1654
+ canvas: raw.canvas,
1655
+ sceneTree: raw.sceneTree ?? {
1656
+ id: "empty",
1657
+ type: "Game",
1658
+ visible: true,
1659
+ interactive: false,
1660
+ alpha: 1,
1661
+ x: 0,
1662
+ y: 0,
1663
+ width: raw.canvas?.width ?? 0,
1664
+ height: raw.canvas?.height ?? 0,
1665
+ worldBounds: {
1666
+ x: 0,
1667
+ y: 0,
1668
+ width: raw.canvas?.width ?? 0,
1669
+ height: raw.canvas?.height ?? 0
1670
+ },
1671
+ path: "Phaser.Game"
1672
+ },
1673
+ totalNodes: raw.totalNodes,
1674
+ completeness: raw.completeness === "full" ? "full" : "partial"
1675
+ };
1676
+ }
1677
+ async pickAt(env, opts) {
1678
+ const payload = buildPhaserHitTestPayload(opts);
1679
+ const result = await env.pageController.evaluate(payload);
1680
+ return {
1681
+ success: result.success,
1682
+ picked: result.picked,
1683
+ candidates: result.candidates,
1684
+ coordinates: result.coordinates,
1685
+ hitTestMethod: result.hitTestMethod
1686
+ };
1687
+ }
1688
+ };
1689
+ //#endregion
1690
+ //#region src/server/domains/canvas/adapters/pixi-adapter.ts
1691
+ /**
1692
+ * Generates a self-contained JS string that traverses the PIXI scene graph via DFS
1693
+ * and returns a serialisable scene tree with worldBounds computed via getBounds().
1694
+ *
1695
+ * Handles both PIXI v7 (interactive property) and v8 (eventMode property).
1696
+ *
1697
+ * @param opts - Dump options (maxDepth, onlyInteractive, onlyVisible)
1698
+ */
1699
+ function buildPixiSceneTreeDumpPayload(opts) {
1700
+ const maxDepth = opts.maxDepth ?? 20;
1701
+ const onlyInteractive = opts.onlyInteractive ?? false;
1702
+ return `(function() {
1703
+ function getChildren(node) {
1704
+ if (!node) return [];
1705
+ if (node.children && node.numChildren !== undefined) return node.children;
1706
+ if (Array.isArray(node.children)) return node.children;
1707
+ return [];
1708
+ }
1709
+
1710
+ function getNumChildren(node) {
1711
+ if (!node) return 0;
1712
+ if (node.numChildren !== undefined) return node.numChildren;
1713
+ if (node.children && Array.isArray(node.children)) return node.children.length;
1714
+ return 0;
1715
+ }
1716
+
1717
+ function nodeId(node, idx) {
1718
+ if (node.uid !== undefined && node.uid !== null) return 'uid_' + node.uid;
1719
+ if (node._uid !== undefined && node._uid !== null) return 'uid_' + node._uid;
1720
+ if (node.id !== undefined && node.id !== null && node.id !== '') return String(node.id);
1721
+ return (node.constructor ? node.constructor.name : 'DisplayObject') + '_' + idx;
1722
+ }
1723
+
1724
+ function safeProp(node, key, fallback) {
1725
+ try { var v = node[key]; return v === undefined || v === null ? fallback : v; } catch(e) { return fallback; }
1726
+ }
1727
+
1728
+ function getWorldBounds(node, stage) {
1729
+ if (!node) return { x: 0, y: 0, width: 0, height: 0 };
1730
+ try {
1731
+ if (typeof node.getBounds === 'function') {
1732
+ var b = node.getBounds(stage);
1733
+ return { x: b.x, y: b.y, width: b.width, height: b.height };
1734
+ }
1735
+ } catch(e) {}
1736
+ return {
1737
+ x: safeProp(node, 'x', 0),
1738
+ y: safeProp(node, 'y', 0),
1739
+ width: safeProp(node, 'width', 0) || safeProp(node, 'getBounds', { width: 0 }).width,
1740
+ height: safeProp(node, 'height', 0) || safeProp(node, 'getBounds', { height: 0 }).height
1741
+ };
1742
+ }
1743
+
1744
+ var totalNodes = 0;
1745
+
1746
+ function traverse(node, depth, path) {
1747
+ if (!node || depth > ${maxDepth}) return null;
1748
+ totalNodes++;
1749
+
1750
+ var visible = !!(safeProp(node, 'visible', true));
1751
+ // PIXI v7 uses 'interactive', v8 uses 'eventMode'
1752
+ var interactive = !!(safeProp(node, 'interactive', false)) || !!(safeProp(node, 'eventMode', 'none') !== 'none');
1753
+
1754
+ if (${opts.onlyVisible ?? false} && !visible) return null;
1755
+ if (${onlyInteractive} && !interactive) return null;
1756
+
1757
+ var numC = getNumChildren(node);
1758
+ var children = null;
1759
+
1760
+ if (numC > 0) {
1761
+ children = [];
1762
+ var nodeChildren = getChildren(node);
1763
+ for (var i = 0; i < nodeChildren.length; i++) {
1764
+ var cn = nodeChildren[i];
1765
+ if (!cn) continue;
1766
+ var childPath = path ? path + '/' + nodeId(cn, i) : nodeId(cn, i);
1767
+ var sub = traverse(cn, depth + 1, childPath);
1768
+ if (sub) children.push(sub);
1769
+ }
1770
+ }
1771
+
1772
+ var eventMode = safeProp(node, 'eventMode', null);
1773
+ var result = {
1774
+ id: nodeId(node, 0),
1775
+ type: node.constructor ? node.constructor.name : 'DisplayObject',
1776
+ name: safeProp(node, 'name', undefined),
1777
+ visible: visible,
1778
+ interactive: interactive,
1779
+ alpha: safeProp(node, 'alpha', 1),
1780
+ x: safeProp(node, 'x', 0),
1781
+ y: safeProp(node, 'y', 0),
1782
+ width: safeProp(node, 'width', 0),
1783
+ height: safeProp(node, 'height', 0),
1784
+ worldBounds: getWorldBounds(node, null),
1785
+ path: path || nodeId(node, 0),
1786
+ customData: {
1787
+ eventMode: eventMode,
1788
+ rotation: safeProp(node, 'rotation', 0),
1789
+ scaleX: safeProp(node, 'scaleX', 1),
1790
+ scaleY: safeProp(node, 'scaleY', 1),
1791
+ pivotX: safeProp(node, 'pivotX', 0),
1792
+ pivotY: safeProp(node, 'pivotY', 0),
1793
+ sortDirty: !!(node.sortDirty),
1794
+ }
1795
+ };
1796
+
1797
+ if (children && children.length > 0) result.children = children;
1798
+ return result;
1799
+ }
1800
+
1801
+ // Find PIXI app: scan canvas elements for _pixiApp property, or check window.__pixiApp
1802
+ var app = null;
1803
+ if (window.__pixiApp) {
1804
+ app = window.__pixiApp;
1805
+ } else {
1806
+ var canvases = document.querySelectorAll('canvas');
1807
+ for (var ci = 0; ci < canvases.length; ci++) {
1808
+ if (canvases[ci]._pixiApp) { app = canvases[ci]._pixiApp; break; }
1809
+ }
1810
+ }
1811
+
1812
+ if (!app || !app.stage) {
1813
+ var pixiVersion = window.PIXI ? (window.PIXI.VERSION || window.PIXI.version) : undefined;
1814
+ return {
1815
+ engine: 'PixiJS',
1816
+ version: pixiVersion,
1817
+ canvas: { width: 0, height: 0, dpr: 1, contextType: 'unknown' },
1818
+ sceneTree: null,
1819
+ totalNodes: 0,
1820
+ completeness: 'partial',
1821
+ error: 'PIXI.Application or stage not found'
1822
+ };
1823
+ }
1824
+
1825
+ var stage = app.stage;
1826
+ var pixiVersion = window.PIXI ? (window.PIXI.VERSION || window.PIXI.version) : undefined;
1827
+ var canvasEl = app.view || null;
1828
+ var canvasInfo = {
1829
+ width: canvasEl ? (canvasEl.width || 0) : 0,
1830
+ height: canvasEl ? (canvasEl.height || 0) : 0,
1831
+ dpr: window.devicePixelRatio || 1,
1832
+ contextType: 'webgl2'
1833
+ };
1834
+
1835
+ if (canvasEl) {
1836
+ var gl = canvasEl.getContext('webgl2') || canvasEl.getContext('webgl');
1837
+ if (!gl) {
1838
+ var ctx = canvasEl.getContext('2d');
1839
+ canvasInfo.contextType = ctx ? '2d' : 'webgl';
1840
+ }
1841
+ }
1842
+
1843
+ var sceneTree = traverse(stage, 0, 'PIXI.Application.stage');
1844
+
1845
+ return {
1846
+ engine: 'PixiJS',
1847
+ version: pixiVersion,
1848
+ canvas: canvasInfo,
1849
+ sceneTree: sceneTree,
1850
+ totalNodes: totalNodes,
1851
+ completeness: 'full'
1852
+ };
1853
+ })()`;
1854
+ }
1855
+ /**
1856
+ * Generates a self-contained JS string that:
1857
+ * 1. Transforms screen coordinates → canvas coordinates
1858
+ * 2. Runs hit test via Stage.hitTest() if available (PIXI v7+), or DFS with getBounds()
1859
+ * 3. Returns all candidates sorted by depth (topmost first)
1860
+ *
1861
+ * @param opts - Pick options (x, y, canvasId)
1862
+ */
1863
+ function buildPixiHitTestPayload(opts) {
1864
+ const x = opts.x;
1865
+ const y = opts.y;
1866
+ const canvasId = opts.canvasId;
1867
+ return `(function() {
1868
+ function getChildren(node) {
1869
+ if (!node) return [];
1870
+ if (node.children && node.numChildren !== undefined) return node.children;
1871
+ if (Array.isArray(node.children)) return node.children;
1872
+ return [];
1873
+ }
1874
+
1875
+ function getNumChildren(node) {
1876
+ if (!node) return 0;
1877
+ if (node.numChildren !== undefined) return node.numChildren;
1878
+ if (node.children && Array.isArray(node.children)) return node.children.length;
1879
+ return 0;
1880
+ }
1881
+
1882
+ function nodeId(node, idx) {
1883
+ if (node.uid !== undefined && node.uid !== null) return 'uid_' + node.uid;
1884
+ if (node._uid !== undefined && node._uid !== null) return 'uid_' + node._uid;
1885
+ if (node.id !== undefined && node.id !== null && node.id !== '') return String(node.id);
1886
+ return (node.constructor ? node.constructor.name : 'DisplayObject') + '_' + idx;
1887
+ }
1888
+
1889
+ function safeProp(node, key, fallback) {
1890
+ try { var v = node[key]; return v === undefined || v === null ? fallback : v; } catch(e) { return fallback; }
1891
+ }
1892
+
1893
+ function getWorldBounds(node, stage) {
1894
+ if (!node) return { x: 0, y: 0, width: 0, height: 0 };
1895
+ try {
1896
+ if (typeof node.getBounds === 'function') {
1897
+ var b = node.getBounds(stage || true);
1898
+ return { x: b.x, y: b.y, width: b.width, height: b.height };
1899
+ }
1900
+ } catch(e) {}
1901
+ return {
1902
+ x: safeProp(node, 'x', 0),
1903
+ y: safeProp(node, 'y', 0),
1904
+ width: safeProp(node, 'width', 0),
1905
+ height: safeProp(node, 'height', 0)
1906
+ };
1907
+ }
1908
+
1909
+ function nodePath(node) {
1910
+ var parts = [];
1911
+ var cur = node;
1912
+ while (cur && cur.parent && cur.parent !== cur) {
1913
+ var name = cur.name || nodeId(cur, 0);
1914
+ parts.unshift(name);
1915
+ cur = cur.parent;
1916
+ if (!cur) break;
1917
+ }
1918
+ parts.unshift('PIXI.Application.stage');
1919
+ return parts.join('/');
1920
+ }
1921
+
1922
+ var sx = ${x}, sy = ${y};
1923
+
1924
+ // Find the target canvas
1925
+ var canvases = Array.from(document.querySelectorAll('canvas'));
1926
+ var targetCanvas = null;
1927
+ ${canvasId ? `targetCanvas = document.getElementById(${JSON.stringify(canvasId)}) || null;` : `
1928
+ for (var ci = canvases.length - 1; ci >= 0; ci--) {
1929
+ var r = canvases[ci].getBoundingClientRect();
1930
+ if (sx >= r.left && sx <= r.right && sy >= r.top && sy <= r.bottom) {
1931
+ targetCanvas = canvases[ci];
1932
+ break;
1933
+ }
1934
+ }`}
1935
+
1936
+ // Find PIXI app
1937
+ var app = null;
1938
+ if (window.__pixiApp) {
1939
+ app = window.__pixiApp;
1940
+ } else {
1941
+ for (var ci2 = 0; ci2 < canvases.length; ci2++) {
1942
+ if (canvases[ci2]._pixiApp) { app = canvases[ci2]._pixiApp; break; }
1943
+ }
1944
+ }
1945
+
1946
+ if (!app || !app.stage) {
1947
+ return {
1948
+ success: false,
1949
+ picked: null,
1950
+ candidates: [],
1951
+ coordinates: { screen: { x: sx, y: sy }, canvas: { x: sx, y: sy } },
1952
+ hitTestMethod: 'none'
1953
+ };
1954
+ }
1955
+
1956
+ var stage = app.stage;
1957
+
1958
+ // Screen → canvas
1959
+ var canvasX = sx, canvasY = sy;
1960
+ if (targetCanvas) {
1961
+ var rect = targetCanvas.getBoundingClientRect();
1962
+ canvasX = (sx - rect.left) * ((targetCanvas.width || 1) / (rect.width || 1));
1963
+ canvasY = (sy - rect.top) * ((targetCanvas.height || 1) / (rect.height || 1));
1964
+ }
1965
+
1966
+ var candidates = [];
1967
+ var hitTestMethod = 'none';
1968
+ var enginePicked = null;
1969
+
1970
+ // Try engine-native hitTest first (PIXI v7+)
1971
+ if (typeof stage.hitTest === 'function') {
1972
+ try {
1973
+ var nativeHit = stage.hitTest(canvasX, canvasY);
1974
+ if (nativeHit) {
1975
+ enginePicked = nativeHit;
1976
+ hitTestMethod = 'engine';
1977
+ }
1978
+ } catch(e) {}
1979
+ }
1980
+
1981
+ // Recursive DFS hit test (always available; fallback for all versions)
1982
+ function hitTestDfs(node, depth, accPath) {
1983
+ if (!node) return;
1984
+ var visible = safeProp(node, 'visible', true);
1985
+ if (!visible) return;
1986
+
1987
+ var wb = getWorldBounds(node, stage);
1988
+ var lx = canvasX, ly = canvasY;
1989
+
1990
+ // Convert canvas → node local using parent chain
1991
+ var cur = node;
1992
+ var screenPt = { x: canvasX, y: canvasY };
1993
+ while (cur) {
1994
+ if (typeof cur.worldTransform !== 'undefined') {
1995
+ try {
1996
+ var wt = cur.worldTransform;
1997
+ var det = wt.a * wt.d - wt.b * wt.c;
1998
+ if (Math.abs(det) > 1e-10) {
1999
+ var invX = (wt.d * screenPt.x - wt.b * screenPt.y + wt.c * wt.ty - wt.d * wt.tx) / det;
2000
+ var invY = (-wt.b * screenPt.x + wt.a * screenPt.y - wt.a * wt.ty + wt.c * wt.tx) / det;
2001
+ screenPt = { x: invX, y: invY };
2002
+ }
2003
+ } catch(e) { break; }
2004
+ }
2005
+ cur = cur.parent;
2006
+ }
2007
+ lx = screenPt.x;
2008
+ ly = screenPt.y;
2009
+
2010
+ var nw = safeProp(node, 'width', 0) || wb.width;
2011
+ var nh = safeProp(node, 'height', 0) || wb.height;
2012
+ var nx = safeProp(node, 'x', 0), ny = safeProp(node, 'y', 0);
2013
+ var pivotX = safeProp(node, 'pivotX', 0), pivotY = safeProp(node, 'pivotY', 0);
2014
+ var scaleX = safeProp(node, 'scaleX', 1), scaleY = safeProp(node, 'scaleY', 1);
2015
+ var rotation = safeProp(node, 'rotation', 0);
2016
+
2017
+ // Simple AABB bounds check in node local space
2018
+ var halfW = (nw * Math.abs(scaleX)) / 2;
2019
+ var halfH = (nh * Math.abs(scaleY)) / 2;
2020
+ var centerX = nx + pivotX * scaleX;
2021
+ var centerY = ny + pivotY * scaleY;
2022
+
2023
+ // Apply rotation
2024
+ var cosR = Math.cos(rotation), sinR = Math.sin(rotation);
2025
+ var rotLx = (lx - centerX) * cosR + (ly - centerY) * sinR;
2026
+ var rotLy = -(lx - centerX) * sinR + (ly - centerY) * cosR;
2027
+
2028
+ var inBounds = Math.abs(rotLx) <= halfW && Math.abs(rotLy) <= halfH;
2029
+
2030
+ // PIXI v7 uses 'interactive', v8 uses 'eventMode'
2031
+ var interactive = !!(safeProp(node, 'interactive', false)) ||
2032
+ !!(safeProp(node, 'eventMode', 'none') !== 'none');
2033
+
2034
+ if (inBounds && interactive) {
2035
+ var path = accPath || nodeId(node, 0);
2036
+ candidates.push({
2037
+ node: {
2038
+ id: nodeId(node, 0),
2039
+ type: node.constructor ? node.constructor.name : 'DisplayObject',
2040
+ name: safeProp(node, 'name', undefined),
2041
+ visible: visible,
2042
+ interactive: interactive,
2043
+ alpha: safeProp(node, 'alpha', 1),
2044
+ x: nx, y: ny,
2045
+ width: nw, height: nh,
2046
+ worldBounds: wb,
2047
+ path: path
2048
+ },
2049
+ depth: depth
2050
+ });
2051
+ }
2052
+
2053
+ var nodeChildren = getChildren(node);
2054
+ for (var i = 0; i < nodeChildren.length; i++) {
2055
+ var cn = nodeChildren[i];
2056
+ if (!cn) continue;
2057
+ var childPath = accPath ? accPath + '/' + nodeId(cn, i) : nodeId(cn, i);
2058
+ hitTestDfs(cn, depth + 1, childPath);
2059
+ }
2060
+ }
2061
+
2062
+ hitTestDfs(stage, 0, 'PIXI.Application.stage');
2063
+
2064
+ // Use engine pick if available, otherwise topmost DFS candidate
2065
+ var picked = enginePicked;
2066
+ var finalMethod = hitTestMethod;
2067
+
2068
+ if (!picked && candidates.length > 0) {
2069
+ candidates.sort(function(a, b) { return a.depth - b.depth; });
2070
+ picked = candidates[0].node;
2071
+ finalMethod = 'manual';
2072
+ }
2073
+
2074
+ return {
2075
+ success: !!picked,
2076
+ picked: picked,
2077
+ candidates: candidates,
2078
+ coordinates: {
2079
+ screen: { x: sx, y: sy },
2080
+ canvas: { x: canvasX, y: canvasY }
2081
+ },
2082
+ hitTestMethod: finalMethod
2083
+ };
2084
+ })()`;
2085
+ }
2086
+ /**
2087
+ * PixiJS canvas engine adapter.
2088
+ *
2089
+ * Handles both PixiJS v7 (interactive) and v8 (eventMode). Version is resolved
2090
+ * lazily from window.PIXI.VERSION at detect() time.
2091
+ */
2092
+ var PixiJSCanvasAdapter = class {
2093
+ id = "pixi";
2094
+ engine = "PixiJS";
2095
+ version;
2096
+ constructor() {
2097
+ this.version = void 0;
2098
+ }
2099
+ async detect(env) {
2100
+ try {
2101
+ const result = await env.pageController.evaluate(`
2102
+ (function() {
2103
+ if (typeof window.PIXI === 'undefined' || window.PIXI === null) {
2104
+ return { present: false, hasApp: false };
2105
+ }
2106
+ var pixi = window.PIXI;
2107
+ var hasApp = !!(pixi.Application);
2108
+ var version = pixi.VERSION || pixi.version;
2109
+ return { present: true, hasApp: hasApp, version: version };
2110
+ })()
2111
+ `);
2112
+ if (!result.present || !result.hasApp) return null;
2113
+ const evidence = ["window.PIXI detected"];
2114
+ if (result.version) evidence.push("PIXI.VERSION: " + result.version);
2115
+ evidence.push("PIXI.Application detected");
2116
+ return {
2117
+ engine: this.engine,
2118
+ version: result.version,
2119
+ confidence: .95,
2120
+ evidence,
2121
+ adapterId: this.id
2122
+ };
2123
+ } catch {
2124
+ return null;
2125
+ }
2126
+ }
2127
+ async dumpScene(env, opts) {
2128
+ const payload = buildPixiSceneTreeDumpPayload(opts);
2129
+ const raw = await env.pageController.evaluate(payload);
2130
+ return {
2131
+ engine: raw.engine,
2132
+ version: raw.version,
2133
+ canvas: raw.canvas,
2134
+ sceneTree: raw.sceneTree ?? {
2135
+ id: "empty",
2136
+ type: "Container",
2137
+ visible: true,
2138
+ interactive: false,
2139
+ alpha: 1,
2140
+ x: 0,
2141
+ y: 0,
2142
+ width: raw.canvas?.width ?? 0,
2143
+ height: raw.canvas?.height ?? 0,
2144
+ worldBounds: {
2145
+ x: 0,
2146
+ y: 0,
2147
+ width: raw.canvas?.width ?? 0,
2148
+ height: raw.canvas?.height ?? 0
2149
+ },
2150
+ path: "PIXI.Application.stage"
2151
+ },
2152
+ totalNodes: raw.totalNodes,
2153
+ completeness: raw.completeness === "full" ? "full" : "partial"
2154
+ };
2155
+ }
2156
+ async pickAt(env, opts) {
2157
+ const payload = buildPixiHitTestPayload(opts);
2158
+ const result = await env.pageController.evaluate(payload);
2159
+ return {
2160
+ success: result.success,
2161
+ picked: result.picked,
2162
+ candidates: result.candidates,
2163
+ coordinates: result.coordinates,
2164
+ hitTestMethod: result.hitTestMethod
2165
+ };
2166
+ }
2167
+ };
2168
+ //#endregion
2169
+ //#region src/server/domains/canvas/handlers/shared.ts
2170
+ /**
2171
+ * Shared utilities for canvas tool handlers.
2172
+ *
2173
+ * Engine anchor patterns, adapter registry, probe env builder, and shared helpers.
2174
+ */
2175
+ /** Well-known engine global anchors and their adapter IDs. */
2176
+ const ENGINE_ANCHORS = [
2177
+ {
2178
+ pattern: "Laya",
2179
+ adapterId: "laya",
2180
+ engine: "LayaAir"
2181
+ },
2182
+ {
2183
+ pattern: "PIXI",
2184
+ adapterId: "pixi",
2185
+ engine: "PixiJS"
2186
+ },
2187
+ {
2188
+ pattern: "Phaser",
2189
+ adapterId: "phaser",
2190
+ engine: "Phaser"
2191
+ },
2192
+ {
2193
+ pattern: "cc",
2194
+ adapterId: "cocos",
2195
+ engine: "CocosCreator"
2196
+ },
2197
+ {
2198
+ pattern: "legacyCC",
2199
+ adapterId: "cocos",
2200
+ engine: "CocosCreator"
2201
+ },
2202
+ {
2203
+ pattern: "BABYLON",
2204
+ adapterId: "babylon",
2205
+ engine: "Babylon.js"
2206
+ },
2207
+ {
2208
+ pattern: "THREE",
2209
+ adapterId: "three",
2210
+ engine: "Three.js"
2211
+ },
2212
+ {
2213
+ pattern: "createUnityInstance",
2214
+ adapterId: "unity",
2215
+ engine: "UnityWebGL"
2216
+ }
2217
+ ];
2218
+ /** Adapter registry — registered per engine. */
2219
+ const adapterRegistry = /* @__PURE__ */ new Map();
2220
+ const adapterFactories = {
2221
+ laya: () => new LayaCanvasAdapter(),
2222
+ pixi: () => new PixiJSCanvasAdapter(),
2223
+ phaser: () => new PhaserCanvasAdapter(),
2224
+ cocos: () => new CocosCanvasAdapter()
2225
+ };
2226
+ function resolveAdapter(detection) {
2227
+ if (!detection) return null;
2228
+ if (adapterRegistry.has(detection.adapterId)) return adapterRegistry.get(detection.adapterId);
2229
+ const factory = adapterFactories[detection.adapterId];
2230
+ if (!factory) return null;
2231
+ const adapter = factory();
2232
+ adapterRegistry.set(detection.adapterId, adapter);
2233
+ return adapter;
2234
+ }
2235
+ function buildEnv(pageController) {
2236
+ return {
2237
+ pageController,
2238
+ cdpSession: null,
2239
+ tabId: "default"
2240
+ };
2241
+ }
2242
+ async function fingerprintCanvas(pageController, canvasId) {
2243
+ try {
2244
+ const result = await pageController.evaluate(`
2245
+ (function() {
2246
+ const hits = [];
2247
+ ${ENGINE_ANCHORS.map(({ pattern, adapterId, engine }) => `
2248
+ try {
2249
+ if (window[${JSON.stringify(pattern)}] !== undefined) {
2250
+ const v = window[${JSON.stringify(pattern)}];
2251
+ hits.push({
2252
+ engine: ${JSON.stringify(engine)},
2253
+ adapterId: ${JSON.stringify(adapterId)},
2254
+ version: v.version || v.VERSION || (v.Laya && v.Laya.version) || undefined
2255
+ });
2256
+ }
2257
+ } catch(e) {}
2258
+ `).join("")}
2259
+
2260
+ if (hits.length === 0) {
2261
+ return { hits: hits, selected: null, selectedEvidence: [] };
2262
+ }
2263
+
2264
+ var targetCanvas = null;
2265
+ if (${canvasId !== void 0}) {
2266
+ var canvases = Array.from(document.querySelectorAll('canvas'));
2267
+ var requestedId = ${JSON.stringify(canvasId ?? null)};
2268
+ var requestedIndex = Number.parseInt(requestedId, 10);
2269
+ targetCanvas =
2270
+ document.getElementById(requestedId) ||
2271
+ (Number.isNaN(requestedIndex) ? null : canvases[requestedIndex] || null);
2272
+ }
2273
+
2274
+ if (!targetCanvas || hits.length === 1) {
2275
+ return { hits: hits, selected: hits[0] || null, selectedEvidence: [] };
2276
+ }
2277
+
2278
+ function canvasEquals(candidate) {
2279
+ return candidate === targetCanvas;
2280
+ }
2281
+
2282
+ function collectCanvasEvidence(adapterId) {
2283
+ var evidence = [];
2284
+
2285
+ if (adapterId === 'pixi') {
2286
+ if (targetCanvas._pixiApp) {
2287
+ evidence.push('target canvas owns _pixiApp');
2288
+ }
2289
+ if (window.__pixiApp && canvasEquals(window.__pixiApp.view)) {
2290
+ evidence.push('target canvas matches window.__pixiApp.view');
2291
+ }
2292
+ if (window.__PIXI_APP__ && canvasEquals(window.__PIXI_APP__.view)) {
2293
+ evidence.push('target canvas matches window.__PIXI_APP__.view');
2294
+ }
2295
+ }
2296
+
2297
+ if (adapterId === 'phaser' && window.Phaser && Array.isArray(window.Phaser.GAMES)) {
2298
+ if (window.Phaser.GAMES.some(function(game) { return game && canvasEquals(game.canvas); })) {
2299
+ evidence.push('target canvas matches Phaser.GAMES[].canvas');
2300
+ }
2301
+ }
2302
+
2303
+ if (adapterId === 'cocos') {
2304
+ var cocos = window.cc || window.legacyCC;
2305
+ var game = cocos && cocos.game;
2306
+ if (game) {
2307
+ if (canvasEquals(game.canvas) || canvasEquals(game._canvas)) {
2308
+ evidence.push('target canvas matches cc.game canvas');
2309
+ }
2310
+ if (game.container && typeof game.container.contains === 'function' && game.container.contains(targetCanvas)) {
2311
+ evidence.push('target canvas is inside cc.game.container');
2312
+ }
2313
+ }
2314
+ }
2315
+
2316
+ if (adapterId === 'laya' && window.Laya) {
2317
+ var candidates = [
2318
+ window.Laya.Browser && window.Laya.Browser.canvas,
2319
+ window.Laya.Render && window.Laya.Render._mainCanvas && (window.Laya.Render._mainCanvas.source || window.Laya.Render._mainCanvas._source || window.Laya.Render._mainCanvas),
2320
+ window.Laya.Render && window.Laya.Render._context && window.Laya.Render._context.canvas && (window.Laya.Render._context.canvas.source || window.Laya.Render._context.canvas),
2321
+ window.Laya.stage && window.Laya.stage._canvas && (window.Laya.stage._canvas.source || window.Laya.stage._canvas._source || window.Laya.stage._canvas),
2322
+ window.Laya.stage && window.Laya.stage.canvas && (window.Laya.stage.canvas.source || window.Laya.stage.canvas._source || window.Laya.stage.canvas)
2323
+ ].filter(Boolean);
2324
+ if (candidates.some(canvasEquals)) {
2325
+ evidence.push('target canvas matches Laya render canvas');
2326
+ }
2327
+ }
2328
+
2329
+ return evidence;
2330
+ }
2331
+
2332
+ for (var i = 0; i < hits.length; i++) {
2333
+ var selectedEvidence = collectCanvasEvidence(hits[i].adapterId);
2334
+ if (selectedEvidence.length > 0) {
2335
+ return {
2336
+ hits: hits,
2337
+ selected: hits[i],
2338
+ selectedEvidence: selectedEvidence
2339
+ };
2340
+ }
2341
+ }
2342
+
2343
+ return { hits: hits, selected: hits[0] || null, selectedEvidence: [] };
2344
+ })()
2345
+ `);
2346
+ if (!result.selected) return null;
2347
+ const hit = result.selected;
2348
+ return {
2349
+ engine: hit.engine,
2350
+ version: hit.version,
2351
+ confidence: result.selectedEvidence.length > 0 ? .95 : .9,
2352
+ evidence: ["window global detected", ...result.selectedEvidence],
2353
+ adapterId: hit.adapterId
2354
+ };
2355
+ } catch {
2356
+ return null;
2357
+ }
2358
+ }
2359
+ //#endregion
2360
+ //#region src/server/domains/canvas/handlers/fingerprint.ts
2361
+ async function handleFingerprint(pageController, args) {
2362
+ const canvasId = args["canvasId"];
2363
+ const globalScan = await pageController.evaluate(`
2364
+ (function() {
2365
+ const results = [];
2366
+ ${ENGINE_ANCHORS.map(({ pattern, adapterId, engine }) => `
2367
+ try {
2368
+ const global = window[${JSON.stringify(pattern)}];
2369
+ if (global !== undefined) {
2370
+ results.push({
2371
+ pattern: ${JSON.stringify(pattern)},
2372
+ adapterId: ${JSON.stringify(adapterId)},
2373
+ engine: ${JSON.stringify(engine)},
2374
+ present: true,
2375
+ version: (global.version || global.VERSION || global.Laya?.version || undefined)
2376
+ });
2377
+ }
2378
+ } catch(e) {}
2379
+ `).join("")}
2380
+ return results;
2381
+ })()
2382
+ `);
2383
+ const candidates = [];
2384
+ for (const hit of globalScan) candidates.push({
2385
+ engine: hit.engine,
2386
+ version: hit.version,
2387
+ confidence: .9,
2388
+ evidence: [`global window.${hit.pattern} is defined`],
2389
+ adapterId: hit.adapterId
2390
+ });
2391
+ const canvasInfo = await pageController.evaluate(`
2392
+ (function() {
2393
+ return Array.from(document.querySelectorAll('canvas')).map(function(c) {
2394
+ var ctx = c.getContext('webgl2') || c.getContext('webgl') || c.getContext('2d');
2395
+ var info = {
2396
+ id: c.id || '',
2397
+ width: c.width,
2398
+ height: c.height,
2399
+ contextType: ctx ? ctx.constructor.name : 'none'
2400
+ };
2401
+ if (ctx && (ctx instanceof WebGLRenderingContext || ctx instanceof WebGL2RenderingContext)) {
2402
+ var debugInfo = ctx.getExtension('WEBGL_debug_renderer_info');
2403
+ if (debugInfo) {
2404
+ info.renderer = ctx.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
2405
+ }
2406
+ }
2407
+ return info;
2408
+ });
2409
+ })()
2410
+ `);
2411
+ if (await pageController.evaluate(`
2412
+ !!(requestAnimationFrame.toString().includes('[native]') &&
2413
+ (window.__canvasEngineHint || document.querySelector('[data-engine]')))
2414
+ `) && candidates.length === 0) candidates.push({
2415
+ engine: "Unknown Canvas Engine",
2416
+ confidence: .3,
2417
+ evidence: ["requestAnimationFrame hook detected", "canvas elements found"],
2418
+ adapterId: "none"
2419
+ });
2420
+ return {
2421
+ candidates,
2422
+ canvasCount: canvasInfo.length,
2423
+ canvasDetails: canvasId !== void 0 ? canvasInfo.filter((c) => c.id === canvasId || canvasInfo.indexOf(c).toString() === canvasId) : canvasInfo,
2424
+ fingerprintComplete: candidates.length > 0
2425
+ };
2426
+ }
2427
+ //#endregion
2428
+ //#region src/server/domains/canvas/handlers/scene-dump.ts
2429
+ async function handleSceneDump(pageController, args) {
2430
+ const canvasId = args["canvasId"];
2431
+ const opts = {
2432
+ canvasId,
2433
+ maxDepth: args["maxDepth"] ?? 20,
2434
+ onlyInteractive: args["onlyInteractive"] ?? false,
2435
+ onlyVisible: args["onlyVisible"] ?? false
2436
+ };
2437
+ const detection = await fingerprintCanvas(pageController, canvasId);
2438
+ if (!detection) return partialSceneDump(pageController, canvasId);
2439
+ const adapter = resolveAdapter(detection);
2440
+ if (!adapter) return partialSceneDump(pageController, canvasId);
2441
+ return adapter.dumpScene(buildEnv(pageController), opts);
2442
+ }
2443
+ async function partialSceneDump(pageController, canvasId) {
2444
+ const canvases = await pageController.evaluate(`
2445
+ (function() {
2446
+ return Array.from(document.querySelectorAll('canvas')).map(function(c, i) {
2447
+ var ctx2d = c.getContext('2d');
2448
+ var ctxWebgl = c.getContext('webgl') || c.getContext('webgl2');
2449
+ return {
2450
+ id: c.id || String(i),
2451
+ width: c.width,
2452
+ height: c.height,
2453
+ dpr: window.devicePixelRatio || 1,
2454
+ contextType: ctx2d ? '2d' : (ctxWebgl ? 'webgl' : 'unknown')
2455
+ };
2456
+ });
2457
+ })()
2458
+ `);
2459
+ return {
2460
+ engine: "unknown",
2461
+ version: void 0,
2462
+ canvas: (canvasId ? canvases.filter((c) => c.id === canvasId || canvases.indexOf(c).toString() === canvasId) : canvases)[0] ?? {
2463
+ id: canvasId ?? "",
2464
+ width: 0,
2465
+ height: 0,
2466
+ dpr: 1,
2467
+ contextType: "unknown"
2468
+ },
2469
+ sceneTree: null,
2470
+ totalNodes: 0,
2471
+ completeness: "partial",
2472
+ partialReason: "No canvas engine detected — only DOM canvas metadata returned"
2473
+ };
2474
+ }
2475
+ //#endregion
2476
+ //#region src/server/domains/canvas/handlers/pick.ts
2477
+ async function handlePick(pageController, args) {
2478
+ const x = args["x"];
2479
+ const y = args["y"];
2480
+ const canvasId = args["canvasId"];
2481
+ const highlight = args["highlight"] ?? false;
2482
+ const opts = {
2483
+ x,
2484
+ y,
2485
+ canvasId
2486
+ };
2487
+ const coordInfo = await pageController.evaluate(`
2488
+ (function() {
2489
+ var sx = ${x}, sy = ${y};
2490
+ var canvases = Array.from(document.querySelectorAll('canvas'));
2491
+ var target = null;
2492
+ ${canvasId ? `target = document.getElementById(${JSON.stringify(canvasId)}) || canvases[parseInt(${JSON.stringify(canvasId)})];` : `
2493
+ for (var i = canvases.length - 1; i >= 0; i--) {
2494
+ var r = canvases[i].getBoundingClientRect();
2495
+ if (sx >= r.left && sx <= r.right && sy >= r.top && sy <= r.bottom) {
2496
+ target = canvases[i];
2497
+ break;
2498
+ }
2499
+ }
2500
+ `}
2501
+ if (!target) return { screen: { x: sx, y: sy }, canvasX: sx, canvasY: sy };
2502
+ var rect = target.getBoundingClientRect();
2503
+ var dpr = window.devicePixelRatio || 1;
2504
+ return {
2505
+ screen: { x: sx, y: sy },
2506
+ canvasRect: { left: rect.left, top: rect.top, width: rect.width, height: rect.height },
2507
+ canvasX: (sx - rect.left) * (target.width / rect.width),
2508
+ canvasY: (sy - rect.top) * (target.height / rect.height)
2509
+ };
2510
+ })()
2511
+ `);
2512
+ const detection = await fingerprintCanvas(pageController, canvasId);
2513
+ if (!detection) return {
2514
+ success: false,
2515
+ picked: null,
2516
+ candidates: [],
2517
+ coordinates: {
2518
+ screen: coordInfo.screen,
2519
+ canvas: {
2520
+ x: coordInfo.canvasX,
2521
+ y: coordInfo.canvasY
2522
+ }
2523
+ },
2524
+ hitTestMethod: "none"
2525
+ };
2526
+ const adapter = resolveAdapter(detection);
2527
+ if (!adapter) return {
2528
+ success: false,
2529
+ picked: null,
2530
+ candidates: [],
2531
+ coordinates: {
2532
+ screen: coordInfo.screen,
2533
+ canvas: {
2534
+ x: coordInfo.canvasX,
2535
+ y: coordInfo.canvasY
2536
+ }
2537
+ },
2538
+ hitTestMethod: "none"
2539
+ };
2540
+ const result = await adapter.pickAt(buildEnv(pageController), opts);
2541
+ if (highlight && result.picked) await highlightNode(pageController, result.picked.worldBounds).catch(() => {});
2542
+ return result;
2543
+ }
2544
+ async function highlightNode(pageController, bounds) {
2545
+ await pageController.evaluate(`
2546
+ (function() {
2547
+ var existing = document.getElementById('__canvas-highlight');
2548
+ if (existing) existing.remove();
2549
+ var div = document.createElement('div');
2550
+ div.id = '__canvas-highlight';
2551
+ Object.assign(div.style, {
2552
+ position: 'fixed', left: ${bounds.x} + 'px', top: ${bounds.y} + 'px',
2553
+ width: ${bounds.width} + 'px', height: ${bounds.height} + 'px',
2554
+ border: '2px solid #00ff88', pointerEvents: 'none', zIndex: 2147483647,
2555
+ background: 'rgba(0,255,136,0.15)', boxSizing: 'border-box'
2556
+ });
2557
+ document.body.appendChild(div);
2558
+ setTimeout(function() { div.remove(); }, 3000);
2559
+ })()
2560
+ `);
2561
+ }
2562
+ //#endregion
2563
+ //#region src/server/domains/canvas/handlers/trace.ts
2564
+ async function handleTraceClick(pageController, debuggerManager, evidenceStore, args) {
2565
+ const x = args["x"];
2566
+ const y = args["y"];
2567
+ const canvasId = args["canvasId"];
2568
+ const maxFrames = args["maxFrames"] ?? 50;
2569
+ const breakpointType = args["breakpointType"] ?? "click";
2570
+ await debuggerManager.enable();
2571
+ await debuggerManager.ensureAdvancedFeatures();
2572
+ const eventMgr = debuggerManager.getEventManager();
2573
+ const breakpointId = await eventMgr.setEventListenerBreakpoint(breakpointType);
2574
+ let cleanupToken;
2575
+ let pendingDispatch;
2576
+ let primaryError;
2577
+ let cleanupError;
2578
+ let traceResult;
2579
+ try {
2580
+ const dispatch = await dispatchCanvasClick(pageController, x, y, canvasId, breakpointType);
2581
+ const dispatchResult = dispatch.result;
2582
+ pendingDispatch = dispatch.completion;
2583
+ cleanupToken = dispatchResult.cleanupToken;
2584
+ const pausedState = await debuggerManager.waitForPaused(5e3);
2585
+ let stackFrames = [];
2586
+ if (pausedState?.callFrames) stackFrames = pausedState.callFrames.slice(0, maxFrames).map((frame) => ({
2587
+ functionName: frame.functionName || "(anonymous)",
2588
+ scriptUrl: frame.url,
2589
+ lineNumber: frame.location?.lineNumber,
2590
+ columnNumber: frame.location?.columnNumber
2591
+ }));
2592
+ recordEvidence(evidenceStore, "canvas_trace", {
2593
+ engine: dispatchResult.engine ?? "unknown",
2594
+ x,
2595
+ y,
2596
+ handlerCount: stackFrames.length
2597
+ });
2598
+ traceResult = {
2599
+ inputFlow: dispatchResult.domEventChain ?? [],
2600
+ hitTarget: null,
2601
+ domEventChain: stackFrames.map((f) => ({
2602
+ type: breakpointType,
2603
+ target: f.scriptUrl,
2604
+ phase: "at-target"
2605
+ })),
2606
+ engineDispatchChain: dispatchResult.engineChain ?? [],
2607
+ handlerFrames: stackFrames,
2608
+ handlersTriggered: stackFrames.map((f) => ({
2609
+ functionName: f.functionName,
2610
+ scriptUrl: f.scriptUrl,
2611
+ lineNumber: f.lineNumber
2612
+ })),
2613
+ networkEmitted: []
2614
+ };
2615
+ } catch (error) {
2616
+ primaryError = error;
2617
+ } finally {
2618
+ try {
2619
+ await debuggerManager.resume();
2620
+ } catch {}
2621
+ if (pendingDispatch) try {
2622
+ await pendingDispatch;
2623
+ } catch (error) {
2624
+ if (!primaryError) cleanupError = error;
2625
+ }
2626
+ await cleanupCanvasTraceTarget(pageController, cleanupToken);
2627
+ if (typeof eventMgr.removeEventListenerBreakpoint === "function") try {
2628
+ await eventMgr.removeEventListenerBreakpoint(breakpointId);
2629
+ } catch {}
2630
+ }
2631
+ if (primaryError) throw primaryError;
2632
+ if (cleanupError) throw cleanupError;
2633
+ if (!traceResult) throw new Error("Canvas trace did not produce a result.");
2634
+ return traceResult;
2635
+ }
2636
+ async function dispatchCanvasClick(pageController, x, y, canvasId, breakpointType = "click") {
2637
+ if (breakpointType === "click" && typeof pageController.click === "function") return dispatchRealCanvasClick(pageController, x, y, canvasId, breakpointType);
2638
+ return dispatchSyntheticCanvasClick(pageController, x, y, canvasId, breakpointType);
2639
+ }
2640
+ async function dispatchRealCanvasClick(pageController, x, y, canvasId, breakpointType = "click") {
2641
+ const dispatchSequence = getDispatchSequence(breakpointType);
2642
+ const cleanupToken = `jshook-trace-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
2643
+ const prepared = await pageController.evaluate(`
2644
+ (function() {
2645
+ var canvases = ${canvasId ? `[document.getElementById(${JSON.stringify(canvasId)})]` : "Array.from(document.querySelectorAll(\"canvas\"))"};
2646
+ var target = null;
2647
+ if (${!!canvasId}) {
2648
+ target = document.getElementById(${JSON.stringify(canvasId)});
2649
+ } else {
2650
+ for (var i = canvases.length - 1; i >= 0; i--) {
2651
+ var r = canvases[i].getBoundingClientRect();
2652
+ if (${x} >= r.left && ${x} <= r.right && ${y} >= r.top && ${y} <= r.bottom) {
2653
+ target = canvases[i];
2654
+ break;
2655
+ }
2656
+ }
2657
+ }
2658
+ if (!target) return { domEventChain: [], pickedNode: null, engine: undefined, engineChain: [] };
2659
+
2660
+ target.setAttribute('data-jshook-trace-token', ${JSON.stringify(cleanupToken)});
2661
+ var rect = target.getBoundingClientRect();
2662
+ var offsetX = Math.max(0, Math.min(${x} - rect.left, Math.max(rect.width - 1, 0)));
2663
+ var offsetY = Math.max(0, Math.min(${y} - rect.top, Math.max(rect.height - 1, 0)));
2664
+ return {
2665
+ domEventChain: ${JSON.stringify(dispatchSequence)},
2666
+ pickedNode: target.id ? { id: String(target.id) } : null,
2667
+ engine: window.Laya ? 'LayaAir' : (window.PIXI ? 'PixiJS' : undefined),
2668
+ engineChain: [],
2669
+ cleanupToken: ${JSON.stringify(cleanupToken)},
2670
+ selector: 'canvas[data-jshook-trace-token="${cleanupToken}"]',
2671
+ offset: { x: offsetX, y: offsetY }
2672
+ };
2673
+ })()
2674
+ `);
2675
+ if (!prepared.selector || !prepared.offset) return {
2676
+ result: prepared,
2677
+ completion: Promise.resolve()
2678
+ };
2679
+ return {
2680
+ result: prepared,
2681
+ completion: pageController.click?.(prepared.selector, {
2682
+ button: "left",
2683
+ clickCount: 1,
2684
+ offset: prepared.offset
2685
+ }) ?? Promise.resolve()
2686
+ };
2687
+ }
2688
+ async function dispatchSyntheticCanvasClick(pageController, x, y, canvasId, breakpointType = "click") {
2689
+ const dispatchSequence = getDispatchSequence(breakpointType);
2690
+ const script = `
2691
+ (function() {
2692
+ var canvases = ${canvasId ? `[document.getElementById(${JSON.stringify(canvasId)})]` : "Array.from(document.querySelectorAll(\"canvas\"))"};
2693
+ var target = null;
2694
+ if (${!!canvasId}) {
2695
+ target = document.getElementById(${JSON.stringify(canvasId)});
2696
+ } else {
2697
+ for (var i = canvases.length - 1; i >= 0; i--) {
2698
+ var r = canvases[i].getBoundingClientRect();
2699
+ if (${x} >= r.left && ${x} <= r.right && ${y} >= r.top && ${y} <= r.bottom) {
2700
+ target = canvases[i];
2701
+ break;
2702
+ }
2703
+ }
2704
+ }
2705
+ if (!target) return { domEventChain: [], pickedNode: null, engine: undefined, engineChain: [] };
2706
+
2707
+ var rect = target.getBoundingClientRect();
2708
+ var cx = (${x} - rect.left) * (target.width / rect.width);
2709
+ var cy = (${y} - rect.top) * (target.height / rect.height);
2710
+
2711
+ var events = [];
2712
+ ${JSON.stringify(dispatchSequence)}.forEach(function(type) {
2713
+ var EventCtor = type.indexOf('pointer') === 0 && typeof PointerEvent === 'function'
2714
+ ? PointerEvent
2715
+ : MouseEvent;
2716
+ var e = new EventCtor(type, {
2717
+ view: window,
2718
+ bubbles: true,
2719
+ cancelable: true,
2720
+ clientX: ${x},
2721
+ clientY: ${y},
2722
+ button: 0,
2723
+ buttons: 1,
2724
+ pointerId: 1
2725
+ });
2726
+ target.dispatchEvent(e);
2727
+ events.push(type);
2728
+ });
2729
+ return {
2730
+ domEventChain: events,
2731
+ pickedNode: null,
2732
+ engine: window.Laya ? 'LayaAir' : (window.PIXI ? 'PixiJS' : undefined),
2733
+ engineChain: []
2734
+ };
2735
+ })()
2736
+ `;
2737
+ return {
2738
+ result: await pageController.evaluate(script),
2739
+ completion: Promise.resolve()
2740
+ };
2741
+ }
2742
+ async function cleanupCanvasTraceTarget(pageController, cleanupToken) {
2743
+ if (!cleanupToken) return;
2744
+ try {
2745
+ await pageController.evaluate(`
2746
+ (function() {
2747
+ var target = document.querySelector('canvas[data-jshook-trace-token="${cleanupToken}"]');
2748
+ if (target) {
2749
+ target.removeAttribute('data-jshook-trace-token');
2750
+ }
2751
+ return true;
2752
+ })()
2753
+ `);
2754
+ } catch {}
2755
+ }
2756
+ function getDispatchSequence(breakpointType) {
2757
+ if (breakpointType === "mousedown") return ["pointerdown", "mousedown"];
2758
+ if (breakpointType === "pointerdown") return ["pointerdown"];
2759
+ return [
2760
+ "pointerdown",
2761
+ "mousedown",
2762
+ "pointerup",
2763
+ "mouseup",
2764
+ "click"
2765
+ ];
2766
+ }
2767
+ function recordEvidence(evidenceStore, label, metadata) {
2768
+ try {
2769
+ return evidenceStore.addNode("function", label, metadata).id;
2770
+ } catch {
2771
+ return "evidence-unavailable";
2772
+ }
2773
+ }
2774
+ //#endregion
2775
+ //#region src/server/domains/canvas/handlers.impl.ts
2776
+ var CanvasToolHandlers = class {
2777
+ pageController;
2778
+ debuggerManager;
2779
+ evidenceStore;
2780
+ constructor(deps) {
2781
+ this.pageController = deps.pageController;
2782
+ this.debuggerManager = deps.debuggerManager;
2783
+ this.evidenceStore = deps.evidenceStore;
2784
+ }
2785
+ async handleFingerprint(args) {
2786
+ return asJsonResponse(await handleFingerprint(this.pageController, args));
2787
+ }
2788
+ async handleSceneDump(args) {
2789
+ return asJsonResponse(await handleSceneDump(this.pageController, args));
2790
+ }
2791
+ async handlePick(args) {
2792
+ const x = argNumber(args, "x", 0);
2793
+ const y = argNumber(args, "y", 0);
2794
+ return asJsonResponse(await handlePick(this.pageController, {
2795
+ ...args,
2796
+ x,
2797
+ y
2798
+ }));
2799
+ }
2800
+ async handleTraceClick(args) {
2801
+ return asJsonResponse(await handleTraceClick(this.pageController, this.debuggerManager, this.evidenceStore, args));
2802
+ }
2803
+ };
2804
+ //#endregion
2805
+ export { CanvasToolHandlers };