oneshotlm
Compare all models on this prompt →

Fluid simulation · qwen3.8-max

Create a real-time fluid simulation on a full-screen canvas where dragging the mouse pushes swirling colored dye through the fluid.

⛨ sandboxed Open in new tab ↗
transcript · 10 messages
user

Create a real-time fluid simulation on a full-screen canvas where dragging the mouse pushes swirling colored dye through the fluid.

assistant
thinking
Build a fluid simulation (stable fluids style) inside a single HTML file. Use a WebGL fluid simulation (classic GPU fluid simulation). Or write a Canvas 2D Navier-Stokes simulation on a grid. For a nice result, a WebGL implementation is best. Write a compact WebGL stable-fluids simulation: advection, vorticity, pressure solve, dye. Based on the well-known Pavel Dobryakov-style implementation, but write it myself.

Need to be careful about completeness. Write a self-contained WebGL fluid simulation with the following shaders:

- Base quad program
- Advection
- Divergence
- Pressure (Jacobi)
- Gradient subtract
- Curl + vorticity confinement
- Splat
- Display

Use half-float framebuffers, linear filtering with fallback. Keep it reasonably robust: check for half-float linear support, and fall back to nearest if needed.

Let's write the code.

Key components:

1. WebGL context, extensions (OES_texture_half_float, OES_texture_half_float_linear for WebGL1, or use WebGL2 with EXT_color_buffer_float).
2. Framebuffer object (FBO) creation.
3. Shaders.
4. Simulation loop: velocity advection, curl, vorticity, divergence, pressure Jacobi about 20 iterations, gradient subtract, dye advection, draw.
5. Mouse pointer events, splats on move/drag (colorful dyes). Also an initial random splat.

Support manual filtering fallback: use NEAREST when linear filtering is not supported, and sample with bilinear interpolation inside the shader (the classic implementation does this). To keep the code small, do this: if linear is not supported, use NEAREST and handle bilinear inside the advection shader (like the original). Include that too.

Let's write the complete file.

Shaders (GLSL ES 1.0 style for both WebGL1 and WebGL2? Use WebGL2 with a fallback to WebGL1, and use `#define` for texture... easier to write in GLSL 100 and use WebGL1-style code that works in both contexts via a getContext('webgl2') fallback... actually simpler: try webgl2, if not, webgl. In webgl2, EXT_color_buffer_float is required for rendering half-float; the texture format is RGBA16F / HALF_FLOAT. In WebGL1, use OES_texture_half_float with format RGBA, type ext.HALF_FLOAT_OES.

Write a getSupportedFormat function similar to the famous implementation.

Let's write it. Aim for a file of about 500 lines.

Shader list:

baseVertexShader: position, texcoord varying.

copy/clear shader for pressure dissipation — use a clear shader that multiplies pressure by dissipation.

splatShader: draw a gaussian splat of color onto the target.

advectionShader: with MANUAL_FILTERING define option.

divergence, curl, vorticity, pressure, gradientSubtract, display.

Pointer handling: pointermove while dragging -> compute delta, splat velocity and dye. Also splat when pressed. Cycle color based on hue over time.

Touch events too.

Handle resize by recreating framebuffers while preserving? Just reinitialize.

Now let's write the code.

Simulation resolution: dye at 1024 (or based on device), simulation at 128/256.

Let's carefully write out the complete code.

const canvas = document.getElementById('c');
getWebGLContext: try webgl2, then webgl; set up extensions and texture parameters.

Implement the following:

function getWebGLContext(canvas) {
  const params = { alpha: true, depth: false, stencil: false, antialias: false, preserveDrawingBuffer: false };
  let gl = canvas.getContext('webgl2', params);
  const isWebGL2 = !!gl;
  if (!isWebGL2) gl = canvas.getContext('webgl', params) || canvas.getContext('experimental-webgl', params);
  let halfFloat, supportLinearFiltering;
  if (isWebGL2) {
    gl.getExtension('EXT_color_buffer_float');
    supportLinearFiltering = gl.getExtension('OES_texture_float_linear');
  } else {
    halfFloat = gl.getExtension('OES_texture_half_float');
    supportLinearFiltering = gl.getExtension('OES_texture_half_float_linear');
  }
  gl.clearColor(0,0,0,1);
  const halfFloatTexType = isWebGL2 ? gl.HALF_FLOAT : halfFloat.HALF_FLOAT_OES;
  let formatRGBA, formatRG, formatR;
  if (isWebGL2) {
    formatRGBA = getSupportedFormat(gl, gl.RGBA16F, gl.RGBA, halfFloatTexType);
    formatRG = getSupportedFormat(gl, gl.RG16F, gl.RG, halfFloatTexType);
    formatR = getSupportedFormat(gl, gl.R16F, gl.RED, halfFloatTexType);
  } else {
    formatRGBA = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType);
    ... same
  }
  return { gl, ext: {...} };
}

getSupportedFormat: create a test fbo and check framebuffer completeness; if R fails, fall back R->RG->RGBA.

Then programs, FBOs, blit.

Now write the whole thing and make sure names are consistent.

Also add a subtle overlay instruction text ("drag to swirl"). Good.

Let's write the file.

I'll build a GPU-accelerated Navier-Stokes fluid simulation (WebGL, stable-fluids method) in a single self-contained file — velocity advection, vorticity confinement, pressure projection, and dye transport, with mouse dragging injecting swirling colored dye.

write /tmp/ws/index.html (28012 bytes)
toolResult

Successfully wrote 28012 bytes to /tmp/ws/index.html

assistant
thinking
Quick sanity check: make sure the file is roughly valid — maybe run a simple syntax check on the script portion with node? Might be able to extract the script and run node --check on it.

Let me verify the file is valid — extract the script and syntax-check it:

bash {"command":"cd /tmp/ws && python3 -c \"\nimport re\nhtml = open('index.html').read()\nm = re.search(r'<script>(.*)</script>', html, re.S)\no
toolResult

/bin/bash: line 1: python3: command not found Command exited with code 127

assistant
thinking
No Python. Either use Node itself to extract it, or use sed/awk.
bash {"command":"cd /tmp/ws && node -e \"\nconst fs = require('fs');\nconst html = fs.readFileSync('index.html','utf8');\nconst m = html.match(/<
toolResult

JS syntax OK -rw-r--r--. 1 node node 28014 Aug 3 23:55 index.html

assistant
thinking
Also, verify that no external resources are being used — quick grep for http. And confirm that it's a single file.
bash {"command":"cd /tmp/ws && grep -cE 'https?://|cdn|fetch|XMLHttpRequest|import ' index.html; ls"}
toolResult

0 index.html

assistant
thinking
No external references, single file, valid syntax. Done. Summarize.

Done. `/tmp/ws/index.html` is a single self-contained file (no external resources, zero network requests, JS syntax verified) implementing a real-time GPU fluid simulation: **How it works** - **Physics** — a full Navier–Stokes "stable fluids" solver running entirely on the GPU via WebGL fragment shaders: - Velocity advection (semi-Lagrangian, with manual bilinear fallback for GPUs lacking linear filtering) - Vorticity confinement (`CURL: 32`) to amplify and preserve swirling eddies - Pressure projection via 22 Jacobi iterations + gradient subtraction to keep the fluid divergence-free - Dye transport on a separate high-res (1024) texture with slow dissipation so colors linger and blend - **Interaction** — dragging the mouse (or touch, multi-touch supported) injects velocity force and vivid hue-cycling dye at the cursor; clicking gives a small burst. An initial random splat burst fills the screen with swirling color on load. - **Rendering** — full-screen canvas with a subtle dye-gradient lighting pass for depth, DPR-aware sizing, and live framebuffer resizing on window resize. Just open `index.html` in a browser and drag to swirl colored dye through the fluid.

TAKE IT TO CODE

A single self-contained HTML file — copy it and open in any browser, or reuse the prompt with your own pi-agent run.

RUN

modelqwen3.8-max
vendorQwen
harnesspi-agent
generated2026-08-03

EVALUATION

3/5 working

Fluid simulation reacts to click, drag, and wheel but oversaturates into flat pink after wheel scroll, losing detail.

Vision grade · anthropic/claude-sonnet-5

MEASUREMENTS

Raw readings from the capture, not a verdict — the grade above is the only judgement. Percentages are how much of the screen changed; the measure under-reports sparse motion on a dark background.

idle motion32.2%
on click54.6%
on wheel20.5%
on enter + space1.3%
on arrow keys0.4%
on w/a/s/d0.1%
frame spread44.8 / 255
console errors0
js errors none

METRICS

runtime181.2s
tokens in51k
tokens out11k
cached36k
cost / run$0.1061