Build a single self-contained HTML file containing a GPU flu...
Prompt
Build a single self-contained HTML file containing a GPU fluid simulation playground with a light game layer. It must run by double-clicking the file with no server, no build step, and no network requests. DELIVERABLE - One file: index.html. All HTML, CSS, GLSL, and JS inline. - Zero external dependencies. No CDN links, no three.js, no fonts, no images. - Works from file:// in current Chrome, Firefox, and Safari. TECHNICAL REQUIREMENTS - WebGL2 with GLSL ES 3.00 shaders. No WebGL1 path. - Stable Fluids (Stam) solved entirely on the GPU using ping-pong framebuffers: advection, divergence, pressure Jacobi iterations, gradient subtraction. - Add vorticity confinement and a velocity dissipation term. - Separate simulation grid from render resolution (sim grid ~256–512, display at device pixel ratio clamped to 2). - Float/half-float render targets via EXT_color_buffer_float. Detect support at startup and fall back to RGBA8 with reduced precision rather than failing. - Fixed simulation timestep with accumulator; clamp delta time to avoid blowups after tab switches. - Rendering: dye field advection, a bloom pass, and tone mapping. No visible grid artifacts or banding. INTERACTION (layer these; each must work independently) - Pointer drag: inject velocity and dye along the drag path, with a smooth falloff radius. Works for both mouse and touch. - Click/tap: radial impulse burst. - Mouse wheel or pinch: change brush radius; reflect it in a visible cursor ring. - Keys: R reset, Space pause, H toggle HUD, C clear dye. - Optional microphone input (getUserMedia) that injects forces from audio amplitude. Must be behind a button, must handle denied permission gracefully with a visible message, and must not be required for anything else to work. Note in a comment that getUserMedia needs https:// or localhost. GAME LAYER (light, not a full game) - A draggable goal region and drifting targets. Fluid velocity pushes targets. - Score = targets delivered into the goal. Timer, score, and best score in a HUD. - Win state with a clear "play again" affordance. - One-line on-screen instructions, dismissible. VISUAL DIRECTION - Dark background, high-contrast iridescent dye, soft bloom. - Clean minimal HUD in system fonts. No emoji, no clip art. - Respect prefers-reduced-motion by dampening bloom and dye intensity. PERFORMANCE TARGETS - 60 fps at 1080p on integrated graphics; never below 30 fps. - Adaptive quality: measure frame time and scale the simulation grid down automatically if it exceeds budget, up again when there is headroom. - Allocate all textures and buffers once at startup. No per-frame allocations, no per-frame shader compilation, no per-frame closures. CODE QUALITY - Tuneable constants grouped at the top of the script. - Clearly commented sections: setup, shaders, framebuffers, sim passes, input, game logic, render loop. - No console errors or warnings in any supported browser. - Handle context loss: listen for webglcontextlost and offer a reload prompt. - If WebGL2 is unavailable, show a readable message instead of a blank canvas. ACCEPTANCE CRITERIA List at the end of your response how each of these is satisfied: 1. Opens and runs from file:// with no network activity. 2. Pointer drag visibly stirs the fluid and dye persists and advects correctly. 3. Frame rate stays at or above 30 fps after two minutes of continuous dragging. 4. Resizing the window keeps the aspect ratio correct and the sim stable. 5. Denying microphone permission leaves every other feature working. 6. No external requests appear in the network tab. DO NOT - Do not use requestAnimationFrame with unclamped delta time. - Do not put the simulation on the CPU. - Do not stub features with TODO or placeholder comments; if something is genuinely out of scope, say so explicitly in your response. - Do not exceed one file. Before writing code, state in 3–5 lines which input source you are treating as primary and which visual style you are committing to. Then write the file.
Response not available