Grabbing a live WebGL effect's actual compiled code and self-hosting it

This is the method DD Pointer generalizes. The goal is not to reimplement or approximate an effect, and never to screenshot it. It's to take the real shipped code + assets off a live page and run that on your own page. Proven on the ElevenLabs voice orb (now live, self-hosted, at disdat.dev/research).

The core insight: a WebGL effect on a page is just (1) a JS module + (2) GPU assets (textures/models/shaders) + (3) a per-frame driver. All three are public — they were shipped to the browser. So you can take all three.


Part A — What we did for the ElevenLabs orb (the worked example)

  1. Got the real page. Loaded elevenlabs.io/agents/ai-outbound-calling in a headless Chrome (SwiftShader WebGL2). Their cookie banner gates the canvas, so we clicked Reject All then scrolled to trigger the lazy mount.

  2. Found the source module, not the pixels. Listed every .js the page loaded (performance.getEntriesByType('resource') + the network log), then fetch()-ed each chunk and grep-ed for tokens that only the effect would contain — here the shader uniform names uFluidSimTexture, uFbmAmplitude, uCumulativeAudio. Exactly one 46 KB chunk matched: it held their OrbShader, an Orb engine class, and a FluidSim class.

  3. Identified the runtime library. Read the chunk's constructor calls: new u.A({dpr,…}) (renderer), new c.B(gl,{vertex,fragment,uniforms}) (program), new g.V(gl,{position,uv}) (geometry with the tell-tale [-1,-1, 3,-1, -1,3] fullscreen triangle), new f.O(...) (render target), program.uniforms.x.value, renderer.render({scene,target}). That signature is OGL (a public MIT WebGL lib). So the chunk's dependencies are OGL, just compiled into their bundle.

  4. Grabbed the real assets. Their base gradient (uTexture) is a loaded image, not generated — so we read its real pixels straight off the GPU (framebufferTexture2D → readPixels) into a PNG. Pixel-for-pixel what shipped.

  5. Re-hosted the actual bytes. Vendored their chunk verbatim (one edit: exposed the internal Orb class in the module's exports). Wrote a ~40-line loader that runs their webpack module factory with a require shim mapping each dependency module-id/key to the matching npm ogl export, plus a 6-function gl-matrix-vec4 shim for their audio-math module. React-only deps (jsx runtime, logger, error boundary) are Proxy-stubbed — the engine never touches them. new Orb(div,{animated:true}).loadTexture(png).play().

  6. Drove the inputs. It self-animates (their fluid sim ticks on an internal timer, independent of audio). The mic is wired into their own setAudioData(), the same entry point their TTS uses.

Result: their real compiled engine, running on our page, with their real texture. Not a clone of the look — the actual thing.

Files: landing/public/orb-engine.js (their chunk, verbatim+1 line), landing/src/orbEngine.ts (loader/shim), landing/src/OrbCanvas.tsx (mount). Provenance + capture scripts in product/scratch/orb-*.


Part B — The general method (any WebGL effect, any page)

Phase 0 — Get the real page

Phase 1 — Locate the effect

Phase 2 — Find the source module (not the pixels)

Phase 3 — Identify the runtime library (or decide there isn't one)

Phase 4 — Grab the assets

Phase 5 — Re-host the actual bytes (two routes)

Phase 6 — Drive the inputs

When it's genuinely not grabbable


Part C — How to do this without an AI model (the deterministic tool)

Almost every step above is mechanical. Here is the same pipeline as a deterministic program — no model in the loop:

  1. Render farm with residential egress. Headless Chrome behind a residential proxy pool. Deterministic: navigate, auto-click consent by matching a maintained list of CMP button selectors/texts, auto-scroll.

  2. Instrument by injection (CDP Page.addScriptToEvaluateOnNewDocument). A fixed instrumentation script wraps getContext, shaderSource, attachShader, linkProgram, useProgram, bindFramebuffer, framebufferTexture2D, texImage2D, bindTexture, activeTexture, the draw* calls, and uniform*. This is static code; no model needed. It yields: every program's GLSL, the per-frame draw order, FBO sizes, sampler→texture bindings, and texture upload sources.

  3. Module location = string search. Collect all loaded scripts (CDP Network/Debugger.scriptParsed), and for each captured shader source, substring-search the scripts for a unique slice of that GLSL. The script that contains it is the effect module. Pure text matching.

  4. Library identification = fingerprint table. Maintain a fingerprint DB: for each known lib (three, OGL, babylon, regl, pixi…) a set of structural signatures (characteristic method names, the fullscreen-triangle vertex array, the program.uniforms.x.value access shape, loader file extensions). Match the chunk's AST/usage against the table → a library id + version guess. Or skip identification entirely by taking Route 1 below.

  5. Dependency resolution — fully deterministic via the webpack graph. This is the step that looks like it needs intelligence (mapping minified v.e → Mesh) but doesn't:

    • Route 1 (no mapping at all): webpack records, in the runtime/build manifest, which chunk file defines each module id. Parse the manifest, download the transitive closure of chunks for the effect module, load them into a stock webpack runtime, and __webpack_require__(effectModuleId). The effect runs on its own compiled dependencies. No library identification, no key mapping — a graph walk. This is the most robust automated path.
    • Route 2 (mapping, still deterministic): to map minified export keys to a known lib, instantiate each dependency module in isolation and probe it: construct each exported member and check structural shape (constructor arity, instance property names, prototype methods) against the fingerprinted lib's classes. the export that, when new-ed with a gl context, produces an object with .uniformsand.attributeLocations`` ⇒ Program. This is a property -matching routine, not a judgment call.
  6. Asset extraction = recorded I/O. Texture upload sources and model/env/wasm URLs come straight from the hooks + network log; GPU-resident textures are readPixels-dumped. Save with content-hash filenames. Deterministic.

  7. Emit a self-contained bundle. A generator template: index.js (webpack-runtime + vendored chunks, Route 1; or effect chunk + shim, Route 2), the asset files, and a mount(el, opts) that constructs the entry and starts the loop. Wire uTime/pointer/audio to standard inputs.

  8. Verify deterministically. Mount the output headless, confirm: a WebGL context is created, ≥1 program links, the framebuffer is non-empty (readPixels coverage > threshold), and pixels change across frames (motion > threshold) — exactly the checks used to verify the orb. No human/AI judgment; thresholds decide pass/fail.

Net: the one step people assume needs an AI — "understand the minified code and wire it up" — is replaced by the webpack module graph (Route 1: load the real dependency chunks, require by id) or structural property-probing (Route 2). Everything else is hooks, string search, network capture, and templated code generation. A model is a convenience for one-offs and weird edge cases; it is not required for the pipeline.


Appendix — the verification thresholds we actually used

Search documentation

Type to find a guide or section.

↑ ↓ to choose · Enter to open · Esc to close