howone 0.1.36 → 0.1.39

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 (24) hide show
  1. package/package.json +1 -1
  2. package/templates/vite/.howone/skills/howone/SKILL.md +2 -1
  3. package/templates/vite/.howone/skills/web-shader-extractor/SKILL.md +123 -0
  4. package/templates/vite/.howone/skills/web-shader-extractor/references/capture-backends.md +201 -0
  5. package/templates/vite/.howone/skills/web-shader-extractor/references/evidence-policy.md +93 -0
  6. package/templates/vite/.howone/skills/web-shader-extractor/references/operating-contract.md +82 -0
  7. package/templates/vite/.howone/skills/web-shader-extractor/references/qa-failure-policy.md +100 -0
  8. package/templates/vite/.howone/skills/web-shader-extractor/references/recon-kernel.md +206 -0
  9. package/templates/vite/.howone/skills/web-shader-extractor/references/replay-policy.md +145 -0
  10. package/templates/vite/.howone/skills/web-shader-extractor/references/shaders-com.md +212 -0
  11. package/templates/vite/.howone/skills/web-shader-extractor/references/source-analysis.md +112 -0
  12. package/templates/vite/.howone/skills/web-shader-extractor/references/surface-discovery.md +113 -0
  13. package/templates/vite/.howone/skills/web-shader-extractor/references/target-lock.md +205 -0
  14. package/templates/vite/.howone/skills/web-shader-extractor/references/three-shader-reconstruction.md +155 -0
  15. package/templates/vite/.howone/skills/web-shader-extractor/references/tool-capability-matrix.md +57 -0
  16. package/templates/vite/.howone/skills/web-shader-extractor/references/unicorn-studio.md +387 -0
  17. package/templates/vite/.howone/skills/web-shader-extractor/scripts/fetch-rendered-dom.mjs +178 -0
  18. package/templates/vite/.howone/skills/web-shader-extractor/scripts/scan-bundle.sh +80 -0
  19. package/templates/vite/.howone/skills/web-shader-extractor/templates/extraction-report.md +41 -0
  20. package/templates/vite/.howone/skills/web-shader-extractor/templates/known-gaps.md +14 -0
  21. package/templates/vite/.howone/skills/web-shader-extractor/templates/qa-report.md +74 -0
  22. package/templates/vite/.howone/skills/web-shader-extractor/templates/replay-manifest.json +147 -0
  23. package/templates/vite/.howone/skills/web-shader-extractor/templates/run-state.json +75 -0
  24. package/templates/vite/.howone/skills/web-shader-extractor/templates/scout-card.json +106 -0
@@ -0,0 +1,387 @@
1
+ # Unicorn Studio Adapter
2
+
3
+ Use this adapter only after the target surface group has been attributed to a Unicorn Studio embed, remix, or runtime renderer.
4
+ Public APIs and bundle details change.
5
+ Verify keys, shader variable names, and initialization routes against the current target.
6
+
7
+ Stability: volatile. Treat endpoint names, variable names, keys, shader templates, and build-specific mappings as hints until verified on the current target.
8
+
9
+ Unicorn Studio is a no-code WebGL design tool. Observed runtimes use curtains.js-style multi-pass WebGL rendering and may load scene definitions from Firestore, GCS, or CDN embed data.
10
+
11
+ ## Adapter Interface
12
+
13
+ - `detect(context)`: target surface is created by Unicorn runtime, target embed/project ID matches the canvas, or public definition maps to the target visual.
14
+ - `preferredEvidence(context)`: published embed JSON or public remix/version definition, then target-bound runtime/source route.
15
+ - `capture(context)`: scene/history data, compiled shaders or shader templates, layer graph, FBO chain, assets/fonts/textures, timing rules, output composite.
16
+ - `replay(context)`: `SOURCE_REPLAY` from embed/version data when possible; `PIPELINE_REPLAY` if only compiled shaders and runtime graph are available.
17
+ - `validationHints(context)`: element/child effect FBO chains, transparent `showBg=0`, frame-based `uTime`, glyph atlas compatibility, WebGL environment issues.
18
+ - `fallback(context)`: dynamic bundle slice around target effects, then frame capture.
19
+
20
+ ## Recognition Signals
21
+
22
+ - URL patterns such as `unicorn.studio/remix/{remixId}` or `unicorn.studio/edit/{designId}`
23
+ - meta tags or runtime clues referencing Unicorn Studio
24
+ - embed SDK paths such as `unicornStudio-*.js`
25
+ - application bundle paths such as `index-*.js` or the current dynamic chunk graph
26
+ - data attributes such as `data-us-project` or `data-us-project-src`
27
+
28
+ ## Data Acquisition Routes
29
+
30
+ ### Route 1: Firestore REST API
31
+
32
+ Use this route for remix URLs when public Firestore access is available. Extract Firebase/public API config dynamically from the current frontend bundle.
33
+
34
+ ```bash
35
+ # Example discovery pattern; validate against the current site.
36
+ curl -s https://www.unicorn.studio/ | grep -oP 'apiKey:"[^"]+"' | head -1
37
+
38
+ API_KEY="<extract dynamically from current public source>"
39
+ PROJECT="unicorn-studio"
40
+
41
+ # Step 1: Fetch remix metadata, including versionId, designId, and creator metadata.
42
+ curl -s "https://firestore.googleapis.com/v1/projects/$PROJECT/databases/(default)/documents/remixes/{REMIX_ID}?key=$API_KEY"
43
+
44
+ # Step 2: Fetch version data with layers, parameters, and texture references.
45
+ curl -s "https://firestore.googleapis.com/v1/projects/$PROJECT/databases/(default)/documents/versions/{VERSION_ID}?key=$API_KEY"
46
+ ```
47
+
48
+ ### Route 2: GCS/CDN Embed Data
49
+
50
+ ```bash
51
+ # Non-Pro projects observed in older samples.
52
+ curl -s "https://storage.googleapis.com/unicornstudio-production/embeds/{DESIGN_ID}"
53
+
54
+ # Pro projects observed in older samples.
55
+ curl -s "https://assets.unicorn.studio/embeds/{DESIGN_ID}"
56
+ ```
57
+
58
+ Embed JSON may look like `{ options: {...}, layers/history: [...], modules: [...] }` and may include `compiledFragmentShaders[]` and `compiledVertexShaders[]`.
59
+
60
+ ### Route 3: Inline Page JSON
61
+
62
+ Unicorn Studio embeds may use `data-us-project` or `data-us-project-src` HTML attributes. The SDK `init()` path scans these attributes and loads the corresponding project.
63
+
64
+ ## Identify The Data Shape First
65
+
66
+ Unicorn Studio data commonly appears in at least two shapes.
67
+
68
+ ### 1. Embed/export scene
69
+
70
+ - Usually the final scene JSON passed to `addScene()`.
71
+ - Often already includes `compiledFragmentShaders[]` and `compiledVertexShaders[]`.
72
+ - Can often be replayed through the embed runtime.
73
+
74
+ ### 2. Editor/version history
75
+
76
+ - Commonly from Firestore `versions/{id}` `history`.
77
+ - Represents editor source layer data.
78
+ - Do not pass it directly to `addScene()`.
79
+
80
+ If `history` is misused as an embed scene, common symptoms include:
81
+
82
+ - `Plane: No fragment shader provided, will use a default one`
83
+ - `Plane: No vertex shader provided, will use a default one`
84
+ - `No composite shader data for element`
85
+ - the canvas exists but renders black or default layers only
86
+
87
+ ## Firestore Collections
88
+
89
+ | Collection | Purpose | Key fields |
90
+ |---|---|---|
91
+ | `designs` | design metadata | creatorId, name, versionId, hasEmbed |
92
+ | `versions` | core version data | history[], options |
93
+ | `remixes` | remixable design metadata | designId, versionId, creatorId, thumbnail |
94
+
95
+ Firestore REST wraps values as `{stringValue, integerValue, arrayValue, mapValue, ...}`. Parse recursively before implementation.
96
+
97
+ Each `history` entry is usually one layer:
98
+
99
+ ```text
100
+ layerType: "effect" | "text" | "image" | "model" | "shape"
101
+ type: effect type such as gradient, noiseFill, sdf_shape, glyphDither, bloomFast
102
+ ```
103
+
104
+ Common layer parameters:
105
+
106
+ - `pos`, `scale`, `speed`, `opacity`, `blendMode`
107
+ - `trackMouse`, `trackAxes`, `mouseMomentum`
108
+ - `parentLayer`: UUID or false
109
+ - `breakpoints[]` for responsive behavior
110
+ - `states` for appear, scroll, hover, and mousemove animation
111
+ - `customFragmentShaders[]`, `customVertexShaders[]`; often empty when built-in effects are used
112
+
113
+ ## Initialization Strategy
114
+
115
+ If the source is Firestore `version/history`, prefer the site's own initialization chain instead of forcing public embed APIs.
116
+
117
+ Typical observed sequence:
118
+
119
+ 1. `unpackageHistory()` or `unpackVersion()`
120
+ 2. `createFontScript()`
121
+ 3. `createCurtains()`
122
+ 4. `handleItemPlanes()`
123
+ 5. `fullRedraw()`
124
+
125
+ If the bundle contains a Remix/Preview component, trace that path before relying on UMD/SDK documentation.
126
+
127
+ ## Resource Localization
128
+
129
+ - Download image, font, and texture resources locally when permitted.
130
+ - Rewrite `history` `src` and `fontCSS.src` to local paths for local replay.
131
+ - Normalize numeric-key maps into arrays when needed before writing local JSON.
132
+
133
+ ## Effect-Type Parameters
134
+
135
+ | Effect | Key parameters |
136
+ |---|---|
137
+ | gradient | fill[], stops[], gradientType, gradientAngle, wrap |
138
+ | noiseFill | noiseType, turbulence, color1, color2, colorPhase, chroma, direction |
139
+ | sdf_shape | shape 0-22, refraction, extrude, smoothing, axis, animationDirection, lightPosition |
140
+ | glyphDither | characters, glyphSet, scale, gamma, monochrome, sprite atlas texture |
141
+ | bloomFast | amount, intensity, exposure, tint |
142
+
143
+ ## Shader Extraction
144
+
145
+ Some embed SDKs do not contain GLSL shader code.
146
+ Shader templates may live in the main application bundle or a dynamic chunk.
147
+ They may be compiled into embed JSON through a processing pipeline.
148
+ Use the current target's network and source evidence.
149
+
150
+ ### Shader Template Locations
151
+
152
+ Observed older bundles used string literals identified by minified variables:
153
+
154
+ ```text
155
+ Effect name observed variable
156
+ glyphDither X$ fragment
157
+ noiseFill WY fragment
158
+ sdf_shape XY fragment
159
+ gradient eX fragment
160
+ bloomFast Hj fragment
161
+ generic vertex ye
162
+ gradient vertex ko
163
+ composite frag Uz
164
+ composite vertex Nz
165
+ ```
166
+
167
+ Variable names change with builds. Search by semantic shader features rather than fixed names.
168
+
169
+ ### Template Variables
170
+
171
+ Shader templates may include `${variable}` placeholders replaced at compile time:
172
+
173
+ | Placeholder | Observed meaning |
174
+ |---|---|
175
+ | `${fe}` | mask-related uniform declarations |
176
+ | `${Vt}` | layer blending helpers such as `applyLayerMix`, `applyLayerMixAlpha`, `applyLayerMixClip` |
177
+ | `${gt}` | PCG hash / random helpers such as `pcg2d`, `randFibo` |
178
+ | `${ht}` | blend mode helpers |
179
+ | `${pe("var")}` | mask application plus `fragColor` output |
180
+ | `${wf}` | BCC noise derivatives / OpenSimplex2S |
181
+ | `${Aa}` | Perlin noise helpers |
182
+ | `${yr}` | debanding dither |
183
+ | `${cm}` | gradient color/stop uniform declarations |
184
+ | `${xz}` | Gaussian weights for bloom blur |
185
+
186
+ ### Observed Compile Pipeline
187
+
188
+ ```text
189
+ 1. Fz(): replace uniform values with constants
190
+ 2. Dz(): handle gradient color counts and switch-case pruning
191
+ 3. Mz(): evaluate constant switches / dead code elimination
192
+ 4. Rz(): handle #ifelseopen / #ifelseclose blocks
193
+ 5. Iz(): remove unused functions
194
+ 6. Cz(): remove unused uniform declarations
195
+ 7. Bp(): strip comments and normalize whitespace
196
+ ```
197
+
198
+ ## Render Pipeline
199
+
200
+ Replay must restore the pipeline, not only individual shaders.
201
+
202
+ ```text
203
+ curtains.js-style WebGL2 renderer
204
+ |- each effect layer = one Plane + independent FBO
205
+ |- layers render linearly by renderOrder, each plane reading previous FBO as uTexture
206
+ |- Element layers (shape/text/image) plus child effects form a render group:
207
+ | 1. Element plane renders first -> FBO_elem
208
+ | 2. Child effects render in effects[] order -> FBO_child1, FBO_child2, ...
209
+ | 3. Composite plane alpha-blends child output back into the background scene
210
+ |- standalone post effects with parentLayer=false process the global scene
211
+ `- final plane outputs directly to canvas without an FBO
212
+ ```
213
+
214
+ ### Element + Child Effect FBO Chain
215
+
216
+ Shape group example:
217
+
218
+ ```text
219
+ FBO_before ----------------------------------------------------.
220
+ |
221
+ Shape plane -> FBO_shape |
222
+ | |
223
+ Child noiseFill -> FBO_noise (uBgTexture = FBO_shape) |
224
+ | |
225
+ Child sdf_shape -> FBO_sdf (uTexture = FBO_noise) |
226
+ | showBg=0 means outside shape = vec4(0) transparent |
227
+ | |
228
+ Composite plane -> FBO_result |
229
+ uTexture = FBO_sdf |
230
+ uBgTexture = FBO_before ---------------------------------'
231
+ output = alpha_blend(fg, bg) = fg + bg * (1.0 - fg.a)
232
+ ```
233
+
234
+ ### Child Effect Association
235
+
236
+ ```js
237
+ // Element effects[] lists child-effect parentLayer UUIDs.
238
+ shape.effects = ["e270a7cd-...", "fb591190-..."];
239
+
240
+ // Each child effect references one parent element UUID.
241
+ noiseFill.parentLayer = "e270a7cd-..."; // effects[0]
242
+ sdf_shape.parentLayer = "fb591190-..."; // effects[1]
243
+
244
+ // Embed runtime lookup pattern:
245
+ getChildEffectItems() {
246
+ return this.effects.map(uuid =>
247
+ state.layers.find(l => l.parentLayer === uuid)
248
+ ).filter(Boolean);
249
+ }
250
+ ```
251
+
252
+ ## Time Base
253
+
254
+ Observed embed SDKs may use frame accumulation for `uTime`, not seconds:
255
+
256
+ ```js
257
+ // In setEffectPlaneUniforms():
258
+ t.uniforms.time.value += speed * 60 / this.fps;
259
+ ```
260
+
261
+ At 60 fps, `uTime += speed` each frame. After one second, `uTime = speed * 60`.
262
+
263
+ | Effect layer | speed | uTime after one second |
264
+ |---|---:|---:|
265
+ | noiseFill | 0.25 | 15 |
266
+ | sdf_shape | 0.5 | 30 |
267
+ | gradient | 0.25 | 15 |
268
+
269
+ Replay must match the target time model:
270
+
271
+ ```js
272
+ // Correct for frame-accumulation samples:
273
+ uni1f(prog, 'uTime', elapsedSeconds * speed * 60);
274
+
275
+ // Wrong for those samples:
276
+ uni1f(prog, 'uTime', elapsedSeconds);
277
+ ```
278
+
279
+ ## `showBg`
280
+
281
+ - `showBg=0`: ray miss outputs `vec4(0)`, fully transparent rather than opaque black.
282
+ - `showBg=1`: ray miss samples `uTexture` / `uBgTexture`, showing background content.
283
+
284
+ If `showBg=0` is incorrectly replayed as `vec4(0,0,0,1)`, composite alpha blending is blocked and the lower layer disappears. Ensure `alpha=0`.
285
+
286
+ ## Replay Strategy
287
+
288
+ 1. Simple 2D post effects such as `glyphDither` or `bloomFast`: native WebGL2 fullscreen quad can be suitable.
289
+ 2. Generative effects such as `noiseFill` or `gradient`: native WebGL2 can be suitable when facts are complete.
290
+ 3. 3D SDF such as `sdf_shape`: native WebGL2 raymarching can be suitable.
291
+ 4. Complex scenes: preserve a multi-pass FBO graph.
292
+ 5. Text layers: render text with Canvas2D and upload it as a WebGL texture.
293
+
294
+ ## Playwright Verification
295
+
296
+ Headless Playwright may not have a usable WebGL environment. Treat these symptoms as environment issues before changing extraction logic:
297
+
298
+ - `Renderer: WebGL context could not be created`
299
+ - `0 canvas(es) found`
300
+ - `Error creating Curtains instance`
301
+ - black screenshot
302
+
303
+ Try SwiftShader-backed validation when needed:
304
+
305
+ ```bash
306
+ --use-angle=swiftshader
307
+ --use-gl=angle
308
+ --enable-unsafe-swiftshader
309
+ --ignore-gpu-blocklist
310
+ ```
311
+
312
+ Suggested validation order:
313
+
314
+ 1. Determine whether console output shows shader/runtime errors or WebGL context creation failure.
315
+ 2. Confirm that the DOM actually creates a `canvas`.
316
+ 3. Capture a SwiftShader screenshot.
317
+ 4. Compare composition against the source thumbnail or first viewport screenshot.
318
+
319
+ ## Glyph Atlas Generation
320
+
321
+ Original glyph atlases may be base64 PNGs with cross-browser compatibility issues. Canvas2D generation can be more portable:
322
+
323
+ ```js
324
+ function createGlyphAtlas(chars, size = 40) {
325
+ const canvas = document.createElement('canvas');
326
+ canvas.width = size * chars.length;
327
+ canvas.height = size;
328
+ const ctx = canvas.getContext('2d');
329
+ ctx.fillStyle = '#000';
330
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
331
+ ctx.fillStyle = '#fff';
332
+ ctx.font = `bold ${size * 0.8}px monospace`;
333
+ ctx.textAlign = 'center';
334
+ ctx.textBaseline = 'middle';
335
+ for (let i = 0; i < chars.length; i++) {
336
+ ctx.fillText(chars[i], size * i + size / 2, size / 2);
337
+ }
338
+ return canvas;
339
+ }
340
+ ```
341
+
342
+ Upload the generated atlas with:
343
+
344
+ ```js
345
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas);
346
+ ```
347
+
348
+ ## Cloud Functions
349
+
350
+ Observed endpoint base:
351
+
352
+ ```text
353
+ https://us-central1-unicorn-studio.cloudfunctions.net/
354
+ ```
355
+
356
+ Examples:
357
+
358
+ - `publishEmbedTest`: publish/update embed; requires authentication
359
+ - `getUserIdByUsername`: username to userId
360
+ - `handleVideos`, `handleModels`, `handleImages`: asset handling
361
+ - `generateImprovedMSDF`: MSDF text rendering
362
+ - `generateDepthMap`: depth map generation
363
+ - `copyRemixAssets`: remix asset copy
364
+
365
+ Do not call authenticated endpoints unless the user has explicitly authorized the session and the action is within scope.
366
+
367
+ ## Example Extraction Skeleton
368
+
369
+ ```bash
370
+ # 1. Extract remix ID from URL.
371
+ REMIX_ID="QZxhNFb1X1OaUqaJLT9S"
372
+
373
+ # 2. Fetch remix metadata.
374
+ curl -s "https://firestore.googleapis.com/v1/projects/unicorn-studio/databases/(default)/documents/remixes/$REMIX_ID?key=$API_KEY" > remix.json
375
+
376
+ # 3. Extract versionId.
377
+ VERSION_ID=$(python3 -c "import json; print(json.load(open('remix.json'))['fields']['versionId']['stringValue'])")
378
+
379
+ # 4. Fetch version data.
380
+ curl -s "https://firestore.googleapis.com/v1/projects/unicorn-studio/databases/(default)/documents/versions/$VERSION_ID?key=$API_KEY" > version.json
381
+
382
+ # 5. Recursively parse Firestore REST wrappers into layer and parameter data.
383
+
384
+ # 6. Fetch the current target-bound app bundle or source slice and locate shader templates by effect type.
385
+
386
+ # 7. Combine parameters and shader templates into the chosen replay route.
387
+ ```
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Optional inventory helper that uses an explicitly installed Playwright runtime
4
+ * to record DOM/canvas/network observations. It does not perform
5
+ * Surface Attribution, Target Lock, Replay Ready, or QA gates, and it does not
6
+ * install dependencies automatically.
7
+ *
8
+ * Usage:
9
+ * node fetch-rendered-dom.mjs <URL> [outDir]
10
+ *
11
+ * Dependencies:
12
+ * npm install playwright
13
+ * npx playwright install chromium
14
+ *
15
+ * Inventory outputs to outDir (default ./web-shader-extractor-scout):
16
+ * dom.html - full rendered HTML
17
+ * canvas-info.json - all canvas element metadata
18
+ * webgl-info.json - lightweight metadata for discovered canvases
19
+ * console.log - page console output
20
+ * screenshot.png - viewport screenshot
21
+ * network.json - runtime-loaded JS/resource/asset URLs
22
+ */
23
+
24
+ import { writeFileSync, mkdirSync } from 'fs';
25
+ import { join, resolve } from 'path';
26
+
27
+ const url = process.argv[2];
28
+ const outDir = resolve(process.argv[3] || 'web-shader-extractor-scout');
29
+
30
+ if (!url) {
31
+ console.error('Usage: node fetch-rendered-dom.mjs <URL> [outDir]');
32
+ process.exit(1);
33
+ }
34
+
35
+ let chromium;
36
+ try {
37
+ ({ chromium } = await import('playwright'));
38
+ } catch (error) {
39
+ console.error('Playwright is not available. Install it explicitly before using this optional scout script:');
40
+ console.error(' npm install playwright');
41
+ console.error(' npx playwright install chromium');
42
+ console.error(`Original error: ${error.message}`);
43
+ process.exit(1);
44
+ }
45
+
46
+ mkdirSync(outDir, { recursive: true });
47
+
48
+ const consoleLogs = [];
49
+ const networkRequests = [];
50
+
51
+ const browser = await chromium.launch({ headless: true });
52
+ const context = await browser.newContext({
53
+ viewport: { width: 1920, height: 1080 },
54
+ deviceScaleFactor: 2,
55
+ });
56
+ const page = await context.newPage();
57
+
58
+ // Capture console
59
+ page.on('console', msg => {
60
+ consoleLogs.push(`[${msg.type()}] ${msg.text()}`);
61
+ });
62
+
63
+ // Capture network (JS, WASM, shader, media, image, and likely data files)
64
+ page.on('response', async response => {
65
+ const reqUrl = response.url();
66
+ const type = response.headers()['content-type'] || '';
67
+ if (/\.(js|mjs|wasm|bin|glsl|frag|vert|svg|png|jpe?g|webp|avif|gif|mp4|webm|ktx2?)(\?|$)/i.test(reqUrl) || type.includes('javascript') || type.startsWith('image/') || type.startsWith('video/')) {
68
+ networkRequests.push({
69
+ url: reqUrl,
70
+ status: response.status(),
71
+ type: type.split(';')[0],
72
+ size: parseInt(response.headers()['content-length'] || '0'),
73
+ });
74
+ }
75
+ });
76
+
77
+ try {
78
+ console.log(`Navigating to ${url} ...`);
79
+ await page.goto(url, { waitUntil: 'networkidle', timeout: 30000 });
80
+
81
+ // Wait for common renderer initialization.
82
+ await page.waitForTimeout(3000);
83
+
84
+ // 1. Full rendered DOM
85
+ const html = await page.content();
86
+ writeFileSync(join(outDir, 'dom.html'), html);
87
+ console.log(`dom.html - ${html.length} bytes`);
88
+
89
+ // 2. Surface info. This is inventory evidence, not target attribution.
90
+ const canvasInfo = await page.evaluate(() => {
91
+ return Array.from(document.querySelectorAll('canvas')).map((c, i) => {
92
+ const style = getComputedStyle(c);
93
+ const rect = c.getBoundingClientRect();
94
+ const parentStyle = c.parentElement ? getComputedStyle(c.parentElement) : null;
95
+ return {
96
+ index: i,
97
+ outerHTML: c.outerHTML.slice(0, 500),
98
+ width: c.width,
99
+ height: c.height,
100
+ clientWidth: c.clientWidth,
101
+ clientHeight: c.clientHeight,
102
+ boundingRect: {
103
+ x: rect.x,
104
+ y: rect.y,
105
+ width: rect.width,
106
+ height: rect.height,
107
+ top: rect.top,
108
+ left: rect.left,
109
+ right: rect.right,
110
+ bottom: rect.bottom,
111
+ },
112
+ dataEngine: c.dataset.engine || null,
113
+ dataRenderer: c.dataset.renderer || null,
114
+ id: c.id || null,
115
+ className: c.className || null,
116
+ parentTag: c.parentElement?.tagName || null,
117
+ parentClass: c.parentElement?.className?.slice(0, 100) || null,
118
+ style: {
119
+ display: style.display,
120
+ position: style.position,
121
+ opacity: style.opacity,
122
+ visibility: style.visibility,
123
+ pointerEvents: style.pointerEvents,
124
+ transform: style.transform,
125
+ zIndex: style.zIndex,
126
+ clipPath: style.clipPath,
127
+ maskImage: style.maskImage,
128
+ mixBlendMode: style.mixBlendMode,
129
+ },
130
+ parentStyle: parentStyle ? {
131
+ position: parentStyle.position,
132
+ overflow: parentStyle.overflow,
133
+ clipPath: parentStyle.clipPath,
134
+ maskImage: parentStyle.maskImage,
135
+ zIndex: parentStyle.zIndex,
136
+ } : null,
137
+ };
138
+ });
139
+ });
140
+ writeFileSync(join(outDir, 'canvas-info.json'), JSON.stringify(canvasInfo, null, 2));
141
+ console.log(`canvas-info.json - ${canvasInfo.length} canvas(es) found`);
142
+
143
+ // 3. Lightweight canvas metadata. Do not create new contexts here.
144
+ const webglInfo = await page.evaluate(() => {
145
+ const canvases = Array.from(document.querySelectorAll('canvas'));
146
+ if (!canvases.length) return { error: 'no canvas found', canvases: [] };
147
+ return {
148
+ found: true,
149
+ canvases: canvases.map((canvas, index) => ({
150
+ index,
151
+ width: canvas.width,
152
+ height: canvas.height,
153
+ dataEngine: canvas.dataset.engine || null,
154
+ dataRenderer: canvas.dataset.renderer || null,
155
+ id: canvas.id || null,
156
+ className: canvas.className || null,
157
+ })),
158
+ };
159
+ });
160
+ writeFileSync(join(outDir, 'webgl-info.json'), JSON.stringify(webglInfo, null, 2));
161
+ console.log(`webgl-info.json - ${JSON.stringify(webglInfo).slice(0, 100)}`);
162
+
163
+ // 4. Console logs
164
+ writeFileSync(join(outDir, 'console.log'), consoleLogs.join('\n'));
165
+ console.log(`console.log - ${consoleLogs.length} entries`);
166
+
167
+ // 5. Screenshot
168
+ await page.screenshot({ path: join(outDir, 'screenshot.png'), fullPage: false });
169
+ console.log(`screenshot.png - saved`);
170
+
171
+ // 6. Network requests
172
+ writeFileSync(join(outDir, 'network.json'), JSON.stringify(networkRequests, null, 2));
173
+ console.log(`network.json - ${networkRequests.length} JS/resource requests captured`);
174
+
175
+ console.log(`\nDone. Files saved to ${outDir}`);
176
+ } finally {
177
+ await browser.close();
178
+ }
@@ -0,0 +1,80 @@
1
+ #!/bin/bash
2
+ # Scan JS bundle slice(s) for WebGL/shader keywords and report hypotheses.
3
+ # Usage: scan-bundle.sh <file1.js> [file2.js ...]
4
+ # Output: keyword counts + tech stack hypotheses. These are not target-bound conclusions.
5
+
6
+ set -eu
7
+
8
+ if [ $# -eq 0 ]; then
9
+ echo "Usage: scan-bundle.sh <file1.js> [file2.js ...]"
10
+ echo "Scans JS files for WebGL/shader keywords and produces hypotheses."
11
+ exit 1
12
+ fi
13
+
14
+ FILES=("$@")
15
+
16
+ echo "=== SHADER/WEBGL KEYWORD SCAN ==="
17
+ echo ""
18
+
19
+ # Core GLSL keywords
20
+ echo "--- GLSL Keywords ---"
21
+ for kw in "gl_FragColor" "gl_Position" "gl_PointSize" "gl_PointCoord" \
22
+ "precision" "uniform" "varying" "attribute" \
23
+ "FRAGMENT_SHADER" "VERTEX_SHADER" "createShader" \
24
+ "sampler2D" "texture2D" "smoothstep" "discard"; do
25
+ count=$(grep -o "$kw" "${FILES[@]}" 2>/dev/null | wc -l | tr -d ' ')
26
+ [ "$count" -gt 0 ] && printf " %-25s %s\n" "$kw" "$count" || true
27
+ done
28
+
29
+ echo ""
30
+ echo "--- WebGL/Canvas Keywords ---"
31
+ for kw in "canvas" "webgl" "webgl2" "getContext" "shader" "glsl" \
32
+ "framebuffer" "renderbuffer" "drawArrays" "drawElements" \
33
+ "bufferData" "texImage2D" "POINTS"; do
34
+ count=$(grep -oi "$kw" "${FILES[@]}" 2>/dev/null | wc -l | tr -d ' ')
35
+ [ "$count" -gt 0 ] && printf " %-25s %s\n" "$kw" "$count" || true
36
+ done
37
+
38
+ echo ""
39
+ echo "--- Noise/Math Keywords ---"
40
+ for kw in "snoise" "simplex" "perlin" "noise" "PoissonDisk" "Poisson"; do
41
+ count=$(grep -o "$kw" "${FILES[@]}" 2>/dev/null | wc -l | tr -d ' ')
42
+ [ "$count" -gt 0 ] && printf " %-25s %s\n" "$kw" "$count" || true
43
+ done
44
+
45
+ echo ""
46
+ echo "=== TECH STACK HYPOTHESES (NOT TARGET LOCK) ==="
47
+
48
+ detect() {
49
+ local label="$1" pattern="$2"
50
+ local count
51
+ count=$(grep -oE "$pattern" "${FILES[@]}" 2>/dev/null | wc -l | tr -d ' ')
52
+ [ "$count" -gt 0 ] && printf " %-25s %s hits (hypothesis)\n" "$label" "$count" || true
53
+ }
54
+
55
+ # Frameworks (patterns specific enough to avoid false positives)
56
+ detect "Three.js" "THREE\.|WebGLRenderer|ShaderMaterial|BufferGeometry|PerspectiveCamera|OrthographicCamera"
57
+ detect "Three.js (minified)" "setRenderTarget|DataTexture|setPixelRatio|setClearColor"
58
+ detect "PixiJS" "PIXI\.|pixi\.js|PixiJS"
59
+ detect "Babylon.js" "BABYLON\.|babylonjs"
60
+ detect "Raw WebGL" "gl\.bindBuffer|gl\.bindTexture|gl\.useProgram|gl\.attachShader|gl\.linkProgram"
61
+ detect "Regl" "regl\(|regl\.frame|regl\.texture"
62
+ detect "OGL" "ogl\.|ogl/"
63
+
64
+ # Patterns. Generic RenderTarget hits are common post-processing evidence, not GPGPU.
65
+ detect "GPGPU/simulation clues" "ping[-_.]?pong|GPUComputationRenderer|DataTexture|feedback|simulation|compute"
66
+ detect "Particles" "gl_PointSize|gl_PointCoord|PointSize|particl"
67
+ detect "Post-processing" "EffectComposer|RenderPass|ShaderPass|postprocess"
68
+ detect "Ray marching" "rayMarch|sdSphere|sdBox|sdRoundBox"
69
+ detect "Instancing" "InstancedMesh|InstancedBufferGeometry|instanceMatrix"
70
+
71
+ echo ""
72
+ echo "=== FILE SIZES ==="
73
+ for f in "${FILES[@]}"; do
74
+ size=$(wc -c < "$f" | tr -d ' ')
75
+ echo " $(basename "$f"): ${size} bytes"
76
+ done
77
+
78
+ echo ""
79
+ echo "Reminder: bind any hypothesis to the target surface group before using it as implementation evidence."
80
+ echo "GPGPU requires combined evidence such as feedback loops, ping-pong pairs, data-texture simulation, or compute-like update passes."
@@ -0,0 +1,41 @@
1
+ # Extraction Report
2
+
3
+ ## Source
4
+
5
+ - URL:
6
+ - Captured at:
7
+ - Permission boundary:
8
+
9
+ ## Target Visual
10
+
11
+ ## Target Surface Group
12
+
13
+ ## Evidence Summary
14
+
15
+ ## Replay Route
16
+
17
+ `SOURCE_REPLAY | PIPELINE_REPLAY | BEHAVIOR_REBUILD`
18
+
19
+ ## Captured Facts
20
+
21
+ - Surface:
22
+ - Runtime/backend:
23
+ - Render graph:
24
+ - Resources:
25
+ - Timing:
26
+ - Inputs:
27
+ - Output/composite:
28
+
29
+ ## Baseline
30
+
31
+ - Path: `capture-baseline/`
32
+ - Status:
33
+
34
+ ## Editable Project
35
+
36
+ - Path: `editable-project/`
37
+ - Status:
38
+
39
+ ## Known Gaps
40
+
41
+ ## Deferred Work