oneshotlm
Compare all models on this prompt →

Aquarium breach · nex-n2-pro

Create a large glass aquarium whose side panel develops a visible crack and then bursts. The simulation must include: - Water escaping through the opening with flow strength based on water depth and decreasing as the tank drains - A curved water jet affected by gravity - A spreading puddle that collides with the room boundaries - Fish, rocks, plants, and a floating toy reacting differently according to density, buoyancy, drag, and current - Objects transitioning correctly from underwater motion to airborne motion and then to floor collisions - Fish attempting to swim against the current before being swept through the breach - Glass fragments with angular velocity, collisions, and water resistance - A visible waterline that lowers continuously rather than disappearing all at once Let the user drag the crack vertically before triggering the failure. A lower crack should initially produce a stronger jet than a higher crack. Give me 1 html file

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

Create a large glass aquarium whose side panel develops a visible crack and then bursts. The simulation must include: - Water escaping through the opening with flow strength based on water depth and decreasing as the tank drains - A curved water jet affected by gravity - A spreading puddle that collides with the room boundaries - Fish, rocks, plants, and a floating toy reacting differently according to density, buoyancy, drag, and current - Objects transitioning correctly from underwater motion to airborne motion and then to floor collisions - Fish attempting to swim against the current before being swept through the breach - Glass fragments with angular velocity, collisions, and water resistance - A visible waterline that lowers continuously rather than disappearing all at once Let the user drag the crack vertically before triggering the failure. A lower crack should initially produce a stronger jet than a higher crack. Give me 1 html file

assistant
thinking
We need respond by creating index.html single-page web app in current directory /tmp/ws. Need implement simulation. Need inspect maybe empty. Need use tools. We need build app. Need no external. We need likely write complete HTML with CSS/JS canvas. Need satisfy requirements. Need include user can drag crack vertically before triggering failure, lower crack stronger. Need visible crack and burst. Need water escaping through opening flow strength based on depth and decreasing as tank drains. Curved water jet gravity. Spreading puddle collides room boundaries. Fish, rocks, plants, floating toy reacting differently density buoyancy drag current. Objects transition underwater to airborne to floor collisions. Fish swim against current before swept. Glass fragments angular velocity collisions water resistance. Visible waterline lowers continuously.

Need create single index.html. We can implement 2D canvas simulation. Need robust. Let's design.

Need use no external. We can write HTML with canvas full viewport, UI. Need maybe use 2D canvas.

Scene: aquarium rectangular on floor against back wall. Tank dimensions. Water in tank. Crack on side panel, user can drag vertical before failure. Button trigger failure. Before trigger: crack visible as line on right side panel. Drag up/down. Simulation maybe starts with tank full. Failure after trigger: crack opens, water jet from crack. Physics.

Coordinate system canvas pixels. Tank: x=120, y=130, w=560, h=390 maybe, floor y=520. right side at x+w. Waterline y variable inside tank. Initially y=150. Crack y draggable from y+40 to bottom-20 maybe. Breach width/height maybe 16. Water depth from waterline to crack center: crackY - waterline if positive. Flow strength proportional sqrt(depth)*opening area? Also water pressure. If crack below waterline, q = k * breachWidth * sqrt(depth) * fillRatio? Decreases as waterline lowers. If crack above waterline no flow. Tank drains. Need waterline lowering continuously: maybe water volume decreases due flow. Use volume (height*width). dV/dt = -flow. waterlineY = top + volume/(w). Actually water height = volume / tankW. At full, waterlineY=150. Tank bottom y=520? If tank h=390, bottom=540. Let's set.

Water escaping: spawn water particles from breach (right side). Flow speed horizontal v = sqrt(2*g*depth) maybe. Use scale. Particle has x,y, vx, vy, radius, water. Gravity downward. Use current in jet. Jet curves due gravity. Spawn rate based flow. Puddle: on floor outside tank, spread horizontally. Could model as puddle front with particles? Need collides with room boundaries. Implement puddle as set of puddle droplets or a semi-transparent polygon spreading on floor. Since "spreading puddle that collides with room boundaries", maybe simulate puddle particles on floor with radius/area and bounce/slide at boundaries. Or use grid of puddle cells? Simpler: puddle object with x,y, radiusX, radiusY, volume. It expands until floor boundaries, clamps. But "collides" maybe visible splash water on floor with particles constrained. We can create puddleParticles on floor, spreading outward with velocities, bounce off room walls. Accumulate water volume. Draw as semi-transparent blue ellipse/polygon, boundaries.

Objects: Fish, rocks, plants, floating toy reacting differently density buoyancy drag current. Need fish attempt to swim against current before swept. We can create objects in tank: fish (density slightly > water? with buoyancy maybe near neutral, swimming), rocks (dense, sink), plants (anchored but bend/float? maybe leaf particles), floating toy (density low, floats). Objects have position, velocity, density, buoyancy, drag, shape. Physics: if in water: gravity downward, buoyancy upward = g*(1 - density/waterDensity?)? Actually net acceleration = g*(1 - rhoObject/rhoWater) plus drag and current. Fish attempts to swim against current near breach if flow current strong and not yet swept: acceleration opposite current up to swimPower; if swept beyond threshold, stops and is carried. Rock: sinks, dragged by current if near. Plant: anchored at base, top bends under current, maybe water drag; if uprooted? Could have plant segments? Simpler: plant object anchored, top position affected by current, draw stem/leaves. Floating toy: density low, floats near waterline, affected by current and eventually exits through crack. Need objects transition from underwater motion to airborne motion then floor collisions. When water exits through crack, objects may be carried to breach. If they cross breach, set airborne, gravity, drag, collisions with floor/walls. Need fish, toy maybe swept. Rocks likely not through crack? Maybe if crack low, maybe not. But need objects transitioning correctly: implement general object states: underwater, airborne, floor. If object near breach and current speed high and x>crackX-? maybe crosses to outside. Then state airborne with vx = current/outflow, vy maybe. Then gravity, drag, bounce floor. Underwater motion: apply physics. Airborne: gravity, drag, collisions floor/walls. Floor collisions: y>=floor, bounce/damp, friction. For underwater inside tank, collisions with tank walls/floor, but after burst opening, breach lets out.

Fish: shape drawn, state. Current: compute from water flow: inside tank near breach current toward right/down? Maybe current vector: near breach, x component proportional to flowStrength and decays with distance to breach and waterline. For fish, if near breach, current rightward. Fish attempts to swim against current: if state underwater and distance to breach < threshold and flowStrength>threshold, fish accelerates left/up against current. If swept (distance too close/current too high) state airborne. Need fish "before being swept" maybe fish swims left for a while then swept.

Glass fragments: when burst, create fragments around crack with angular velocity, velocity. Collisions with floor/walls, water resistance if in puddle/underwater? Need glass fragments with angular velocity, collisions, water resistance. Spawn around breach on failure. They fall, bounce, rotate, damp if y near puddle/water. Draw shards.

Crack drag: before trigger, user can drag crack vertically. Need pointer events on canvas. Draw crack line on side panel. On pointer down near crack, set dragging. On pointer move update crackY. Button "Trigger failure"; maybe also reset.

Need perhaps include instructions. Need use all in one file.

Need implement animation loop. Need careful code.

Let's plan HTML:

<!doctype html>
<html lang=en>
<head>
<meta charset=utf-8>
<meta name=viewport content="width=device-width,initial-scale=1">
<title>Glass Aquarium Failure Simulation</title>
<style> ... body, canvas, panel, button, range? maybe not. </style>
</head>
<body>
<canvas id=sim></canvas>
<div class=overlay>
<h1>Glass Aquarium Breach</h1>
<p>Drag the crack up/down on the right panel, then trigger failure.</p>
<button id=trigger>Trigger failure</button><button id=reset>Reset</button>
<div id=status></div>
</div>
<script>
(() => { ... })();
</script>
</body>
</html>

Need maybe overlay not intercept canvas except buttons. Use pointer-events none for overlay, controls pointer-events auto. Canvas full.

Coordinate scale: Need responsive. Could use fixed virtual coordinates and scale to fit. Simpler: set canvas width = innerWidth, height=innerHeight; use virtual coordinate system 1000x650 scaled to fit? But objects positions in CSS pixels if canvas pixel. Use actual canvas CSS dimensions. On resize update canvas dimensions and maybe scale positions? Better use world coordinate 1000x650, scale to fit. But pointer mapping and drawing. Use canvas.width=window.innerWidth*dpr, canvas.height=...; ctx.setTransform(dpr*scale,0,0,dpr*scale, offsetX, offsetY). But if UI overlay. We'll define world W=1000,H=650. Tank x=170,y=120,w=560,h=390. Floor y=530. Room boundaries x=0..1000, y=0..650 maybe. Tank bottom at 520? floor=530. Need room boundaries for puddle: floor line y=530; walls left/right.

Let's set:
const W=1000,H=650;
const tank={x:160,y:120,w:560,h:390,right:720,bottom:510};
floorY=530; maybe tank bottom at 510, stand? Need floor. Tank rests on floor? side panel from y=120 to 510, bottom at 510. Floor at 530. A base at 520. Waterline y=145. Tank water full height=365? bottom=510 top=120.
Maybe tank bottom=510. Waterline=145. Crack initial y=380. User can drag between 170 and 490. Lower crack stronger due depth.

Need draw room: back wall gradient, floor. Aquarium: back glass rectangle, frame. Water inside with clipped area. Draw water fill: rectangle x+10,yWater,w-20,bottom-yWater clipped. Draw waterline. Draw glass side panel right, crack. Need crack visible.

Water inside: water surface horizontal line.

Crack: before failure, draw jagged line at right panel x=tank.right-10 maybe. When triggered, draw opening as black/glass fracture and water jet from crack.

Flow physics:
Variables:
let failed=false, elapsed=0;
let waterHeight = tank.h - (waterlineY - tank.y); // full 365.
let waterVolume = waterHeight*tank.w;
let breach = {x:tank.right-12, y: crackY, width: 24, height: maybe 42};
function computeFlow() {
const depth = Math.max(0, breach.y - waterlineY);
const pressure = depth / tank.h; // 0..1
const flowStrength = failed ? Math.min(1, pressure*1.5 + 0.15) * (waterHeight/tank.h) maybe : 0;
const q = failed ? (failed? 1.0:0) * (0.15 + 2.2*Math.sqrt(depth/tank.h)) * Math.max(0.1, waterHeight/tank.h) : 0;
return {depth, q, speed: 80 + 360*Math.sqrt(depth/tank.h), spawnRate: 18+90*q}
}
Need q for volume update: dWaterHeight/dt = -flowRatePixelsPerSec. Use q maybe 20-100 px^2? Better:
const pressureHead = Math.max(0, breach.y - waterlineY);
const base = 18 + 70 * Math.sqrt(pressureHead / tank.h);
const waterRemaining = waterHeight / tank.h;
const flow = failed ? base * waterRemaining * breach.width/20 : 0;
waterHeight -= flow*dt; clamp. This means lower crack has larger depth -> stronger. As waterline lowers, q decreases. If crack y below bottom? okay.
Maybe if waterline below crack, flow=0. If crack lower, initially depth ~235 (380-145), flow high. If high crack y=180, depth 35, weak. Good.
Need waterline lowers continuously: update waterHeight each frame.

Spawn water particles:
waterParticles array: {x,y,vx,vy,r,life,alpha,fromJet}
At failure, spawn n = Math.ceil(flow.spawnRate*dt) maybe. x=breach.x + random 12? Actually crack on right panel, water exits right side. x=tank.right+5. y=breach.y + random(-height/2,height/2). vx = flow.speed * (0.75+random*.5); vy = random(-30,30) maybe plus current. Gravity. Radius based flow.
They move with gravity: vy += g*dt; x+=vx*dt; y+=vy*dt; air drag; if y>floorY, becomes puddle particle? We can convert to floor puddle particles.
Puddle:
Implement puddleParticles: on floor y=floorY-4, x, vx outward, radius, volume, color, life. Spawn when water particle hits floor: create puddle droplet with x, vx=random sign maybe, radius=2-6. Puddle object:
let puddles = [{x, y, rx, ry, volume, spreadVxLeft, spreadVxRight, color...}] maybe.
Simpler: a dynamic puddle object with left/right extents and volume. It grows from breach x. When jet hits floor, add volume. Spread velocities outward. Boundaries left=0,right=W. Update:
puddle.x maybe center; puddle.left/right. Add volume increases area. If no boundaries, left -= spread*dt, right += spread*dt. Spread = min(120, 25 + volume/1000). Collide boundaries: if left<0 left=0, bounce? set leftVel=abs; right>W right=W. Draw as polygon/ellipse on floor with semi-transparent. Need "spreading puddle collides with room boundaries": draw puddle reaching walls, with edge highlights and particles bounce. Could have floor water particles that bounce off side walls. We can combine:
- puddle = {left:breachX,right:breachX, volume:0, spread:0}
- On update if volume>0, targetWidth = 40 + sqrt(volume)*1.8; spread outward with velocities. left -= spread*dt, right += spread*dt; clamp. If collision with wall, set velocity 0 and draw splash.
- Additionally floorDrops with x,y=floorY, vx, radius, bounce off walls, fade.

Need water jet: waterParticles curved.

Objects:
Define object classes maybe generic. Need draw and update.

Tank interior water region: x in [tank.x+12,tank.right-12], y in [waterlineY,tank.bottom]. Underwater if !airborne and x within tank interior and y >= waterlineY and y<=tank.bottom. If y < waterlineY but inside tank, airborne? maybe above waterline but inside tank. For floating toy, if not swept, stays at waterline. For fish, can swim.

We need objects transition:
- Underwater objects have state 'underwater', 'airborne', 'floor'. If object.x > tank.right-20 and near breach and failed, and current strong, transition to airborne. For fish and toy; rock maybe if current strong and low crack maybe could also? But rock heavy, maybe not. But requirement includes objects; fish and toy can be swept. Rock maybe falls inside. Plants anchored.
- When airborne, no buoyancy, gravity, drag. Collide floor/walls. When hits floor, state='floor', y=floorY, vx*=friction, vy=-vy*0.2 maybe, angular velocity damp. Draw shadow.
- Underwater: apply gravity/buoyancy/current/drag. Collide with tank walls/floor. If object is floating toy and not failed: y = waterlineY - 12 + bob, x maybe. If failed and near breach: current.
- Fish: underwater, swim against current. Fish has desired: if near breach and failed, swimAccel = -normalize(current)*swimPower; if current too strong or close to breach, swept.
- Rock: density 2.7, buoyancy small, sinks. It can move along floor under current if near breach? We can set if y>=bottom-20, floor friction. It may roll.
- Plant: anchored at base (x,y=bottom-20). Top position influenced by current, draw stem curve. Could be swept? Maybe leaves detach? Requirement "plants reacting differently according to density, buoyancy, drag, and current". We can model plant as anchored with top displacement, maybe if current strong leaves detach? But transition? Not necessarily. We'll draw plant stem/leaves bending.
- Floating toy: density 0.35, floats, buoyant; bob. It will be swept through breach.

Need current field:
function currentAt(x,y) returns vector.
Inside tank:
if failed:
depthFactor = computeFlow().strength.
dx = tank.right - x;
nearBreach = exp(-dx/120) * exp(-abs(y-breach.y)/120)
surface? current rightward and maybe down/up? For lower crack, current to right and slightly down? Use vx = flowSpeedInside * nearBreach; vy = (breach.y - y)*0.15*nearBreach? Actually current converges to breach: vx positive, vy = (breach.y-y)*0.6*nearBreach (toward crack y).
outside jet current: near water particles maybe not need for objects.
else current 0.
Fish swim against: currentAt fish. If current magnitude > 10 and fish not swept and x < tank.right-80, fish acceleration = -current normalized * fishPower. If current mag > fishPower*? and distance < threshold, swept.

Need water resistance for glass fragments: if in water/puddle, drag. For glass fragments, state airborne/floor. Spawn with angular velocity. They collide. If y >= floorY, floor state. If x,y within puddle (puddle contains x and y near floor), apply drag and reduce angular velocity; maybe if fragment lands in puddle, splash, vx damp.

Need draw:
- Background wall/floor.
- Puddle behind tank? Actually puddle outside to right/left. Draw before tank? floor water should appear on floor. Draw puddle after floor before objects maybe.
- Tank base.
- Tank back glass/water. Need draw water inside tank clipped.
- Objects inside tank maybe draw after water? Underwater objects should appear within water with refraction. Draw water fill then objects? If draw objects after water, visible. For underwater, maybe clip to tank interior and water region. For objects above water in tank, draw separately. Simpler draw all objects before front glass? We'll draw water fill then objects then glass frame. For waterline lower continuous.
- Water jet outside drawn after tank maybe before floor? It should be visible over background. Draw water particles.
- Glass fragments.
- Front glass frame/crack.

Need draw tank:
ctx.save(); fill back panel; water fill clipped; draw water surface; draw objects inside maybe after water.
Maybe draw water fill:
ctx.save();
ctx.beginPath(); rect(tank.x+8,tank.y+8,tank.w-16,tank.bottom-tank.y-8); clip();
draw water gradient from waterline to bottom with alpha; wave surface; bubbles maybe.
draw underwater objects (state underwater) clipped.
ctx.restore();
draw waterline line.
draw glass frame: left/right/top/bottom, highlights.
draw crack: if not failed, jagged line on right panel at crackY. If failed, opening and jagged cracks.
Need objects drawn in correct layer: Underwater objects inside water; floating toy maybe partially above water. Airborne/floor outside after.

Need maybe draw fish shapes. Fish object:
{x,y,vx,vy,angle,size,density,drag,buoyancy,swimPower,swept:false,color}
Update underwater:
let c = currentAt(x,y);
let buoy = (1 - density/waterDensity)*g; For fish density 1.03, net -0.03g (slightly sinks); toy density .45, buoy=0.55g upward. Rock density 2.6, buoy=-1.6g (down).
But use acceleration: ay = g*(1-density/1) maybe. For toy, upward acceleration when underwater; but constrained by surface. For fish, near neutral.
Add current drag: ax += (c.vx-vx)*drag; ay += (c.vy-vy)*drag.
Fish swim: if failed and c.mag>15 and !swept: ax += -c.vx/c.mag * swimPower; ay += -c.vy/c.mag * swimPower*0.3; angle = atan2(vy,vx) maybe. If swept: no swim.
Swept condition: if failed and distance to breach < 80 and c.mag>45 and Math.random()<0.02 per frame? Better deterministic: if c.mag>120 and dist<100 then swept=true and transition.
Transition to airborne:
function ejectObject(obj):
obj.state='airborne'; obj.x=tank.right+20+random; obj.y=breach.y+random(-20,20); obj.vx=flow.speed*0.35+obj.vx; obj.vy=(breach.y-obj.y)*0.2+random(-40,40); obj.angle=random; obj.av=random.
For fish, maybe face right.
Need "Fish attempting to swim against current before being swept through breach": fish state underwater, when failed, it swims left. Then if swept, ejected. We can show maybe fish orientation changes and path.

Need toy:
density=0.35, drag high, buoyant. Underwater/floating: ay = g*(1-density) upward; if y<waterline-15, vy*= -0.2, y=waterline-15; bob. When current strong near breach, swept.
Rock:
density=2.7, drag low, sink. If near breach, current may push along bottom; if y>bottom-20, vx += c.vx*0.1*dt; maybe if c strong and crack low, rock could roll to opening? But rock likely not ejected. Maybe if crack low, rock near bottom can be pushed and ejected? But maybe not. To show transition, toy and fish. Plants anchored.
Plant:
base {x,y=bottom-10}. top {x+dx,y-120}. dx influenced by current near top. Draw stem quadratic from base to top; leaves.
Maybe plant density/buoyancy? It is anchored, but leaves have buoyancy/drag. We can set top displacement = current.vx*0.03 * clamp(flow strength). If current strong, leaves flutter/detach? Maybe create plant fragments? Not required but "plants reacting differently according to density, buoyancy, drag, and current". We can draw leaves bending and some leaves detaching as airborne when current high. But not necessary. Maybe implement plantLeaves as small ellipses with density .8, buoyancy, drag; anchored via spring. If current high, leaves flutter; some detach and swept. But complexity.

Maybe create plant objects:
plants = [{x:250, baseY:bottom-12, height:100, topX, topY, leaves:[...]}]. Update topX += ((currentAt(top).vx*0.02) - (topX-x)*0.02)*dt; topY = baseY-height + sin(time)*2. Draw stem. Leaves at top, draw green ellipses rotated. If current strong, leaves can detach: if flow strength > .5 and random < dt*0.2 create leaf object density .7, buoyancy .5, drag high, state underwater/airborne. It may float/sweep. This satisfies plants.
But "Fish, rocks, plants, and a floating toy reacting differently according to density, buoyancy, drag, and current" can be visually clear with labels? Maybe status text. But simulation.

Need maybe implement "room boundaries" for puddle: floor extends, side walls. Draw left/right walls? The canvas has boundaries. Puddle left/right extents clamp. Draw wall boundary lines.

Need physics details:
Time step: Use requestAnimationFrame; dt = min(0.033,(now-last)/1000). World units px. Gravity maybe 650 px/s².

Water particles update:
for each p:
p.vy += gravity*dt
p.x += p.vx*dt
p.y += p.vy*dt
p.vx *= (1 - 0.03*dt)? Actually drag.
p.life -= dt
if p.y >= floorY:
p.y=floorY
puddle.volume += p.r*p.r*0.15
create floorDrop {x:p.x, vx:random(-80,80), r:p.r*1.5, life:random}
remove.
else if p.x > W+50 or p.life<=0 remove.
Need spawn rate maybe up to 160/s. Use accumulator:
waterSpawnAcc += flow.spawnRate*dt; while >1 spawn and decrement.
Need not too many. Max 900.

Puddle update:
if volume > 0:
const targetWidth = 30 + Math.sqrt(volume)*2.2; // volume maybe from water particles; if spawn rate high, volume can be large. Need calibrate.
puddle.spread += (targetWidth/2 - currentHalf)*0.2*dt? Simpler:
const half = (right-left)/2;
let desired = Math.min((W-20)/2, 15 + Math.sqrt(volume)*1.4);
let speed = 160 * Math.min(1, Math.max(0, desired-half)/desired);
left -= speed*dt; right += speed*dt;
if left<=0 {left=0; leftVel=0; splash}
if right>=W {right=W}
if volume decays? Puddle persists. Maybe evaporation negligible.
Draw puddle:
ctx.save(); ctx.globalAlpha=0.35; ctx.fillStyle=blue; beginPath; moveTo(left,floorY); bezier to center lower? Actually ellipse on floor: ctx.ellipse((left+right)/2, floorY+2, (right-left)/2, 10+volume/12000, 0,0,2pi); fill. Clip to floor. Draw waves.
Need maybe puddle collides with room boundaries: if left=0/right=W, draw wall splash at boundaries.

FloorDrops:
update x+=vx*dt; vx*=0.98; if x<0 or >W bounce vx=-vx*0.5; life-=dt; remove. Draw small ellipses.

Waterline lowering:
waterHeight = Math.max(0, waterHeight - flow*dt)
waterlineY = tank.bottom - waterHeight
If waterHeight<=0, no flow.
Need maybe if waterline below crack, flow stops. But if crack lower, waterline may lower below crack. Good.

Need "visible waterline lowers continuously rather than disappearing all at once" maybe waterline drawn.

Need failure trigger:
When user clicks trigger: failed=true; create initial glass fragments around crack; maybe crack opening.

Need "drag crack vertically before triggering" implement pointerdown near crack. Need status.
Pointer mapping:
function worldFromEvent(e) { const rect=canvas.getBoundingClientRect(); return {x:(e.clientX-rect.left)*W/rect.width, y:(e.clientY-rect.top)*H/rect.height};}
canvas.addEventListener('pointerdown', e => { if (!failed && dist to crack < 35) dragging=true; canvas.setPointerCapture(e.pointerId);});
pointermove if dragging: crackY=clamp(y,tank.y+40,tank.bottom-25)
pointerup dragging=false.
Need cursor change maybe.

Need reset:
reset all variables and objects.

Need maybe initial objects:
fish1, fish2? Requirement fish plural. Create two fish with different densities? Maybe fish have density 1.02/1.08, drag etc. One near top, one mid. Rocks 3. Plants 4. Toy 1.
Objects array includes fish, rocks, toy, leaves? Plants separate.
Fish:
{type:'fish',x:300,y:300,vx:0,vy:0,state:'underwater',density:1.02,drag:0.9,swimPower:180,angle:0,size:28,color:'#f97316',swept:false}
Fish2 maybe density 1.05, color '#facc15'.
Rocks:
{x:420,y:bottom-18,vx:0,vy:0,state:'floor' maybe underwater but resting on tank bottom, density:2.6,drag:0.25,size:22,angle:0,av:0, color}
Underwater floor state? We can state 'underwater' but collision keeps at bottom. For transition, if swept maybe eject. But rocks not. Need draw rocks.
Toy:
{x:560,y:waterlineY-15,state:'underwater',density:.35,drag:1.2,size:30}
Plants:
base positions.
Leaves maybe in objects? We'll draw plants after water.

Need update object generic:
function updateObjects(dt) {
for each fish/rock/toy/leaf:
if obj.state==='underwater':
updateUnderwater(obj,dt)
else if airborne...
else if floor...
}
Need skip plants? Plant leaves if detached in objects.

Underwater update:
if obj.type==='toy' and !failed:
obj.y += Math.sin(time*3)*?; but update with physics.
const c=currentAt(obj.x,obj.y)
let ax=0, ay=0
const g=650
ay += g*(1 - obj.density) // density relative to water
// buoyancy if object above? If underwater only.
ax += (c.vx - obj.vx)*obj.drag
ay += (c.vy - obj.vy)*obj.drag
if fish:
if failed:
const dist = hypot(obj.x - tank.right, obj.y - breach.y)
const danger = dist < 170 && c.mag > 25
if danger && !obj.swept:
const nx = -Math.sign(c.vx || 1)
ax += -Math.sign(c.vx || 1)*obj.swimPower
ay += -Math.sign(c.vy || 0)*obj.swimPower*0.25
// If c.mag > swimPower+120 and dist<90 -> swept
if (danger && dist<95 && c.mag > obj.swimPower+120) obj.swept=true
else if !failed:
// small swimming around
obj.vx += Math.sin(time*...)*20*dt maybe
if toy:
if obj.y < waterlineY - 18: ay += 400; // keep at surface
if obj.y > bottom-10: ay -= 300
// rock:
if obj.y > tank.bottom - obj.size - 4: // bottom
obj.vy *= -0.15; obj.y = tank.bottom - obj.size - 4; obj.vx *= 0.85;
// maybe if c.vx strong and y near bottom, vx += c.vx*0.15*dt
// integrate
obj.vx += ax*dt; obj.vy += ay*dt;
obj.vx *= Math.exp(-0.1*dt) maybe
obj.x += obj.vx*dt; obj.y += obj.vy*dt;
// angle based on velocity
if fish/toy/rock: angle = atan2(obj.vy,obj.vx)
// collisions tank:
if x<tank.x+size/2 {x=...; vx*=-0.4}
if x>tank.right-size/2 { if failed && near breach && swept? else bounce; }
if y<waterlineY? For fish can be above? fish inside water; if y<waterlineY+size/2 {y=...; vy*=-0.2}
if y>bottom-size/2 {y=...; vy*=-0.2}
// eject if failed and not swept? For fish, if swept:
if obj.type fish/toy/leaf && failed && obj.x > tank.right - 25 && Math.abs(obj.y-breach.y)<80 && c.mag>80: ejectObject
// If swept but not ejected?
if obj.swept && obj.x> tank.right-60...
}
Need c.mag maybe high. If waterline below crack, current stops.

Airborne update:
ax = -vx*0.02? drag; ay=g; integrate; collisions:
if y>floorY: y=floorY; vy=-vy*0.25; vx*=0.65; state='floor'; angularVelocity*=0.4; splash maybe.
if x<0 or x>W: x clamp; vx=-vx*0.5.
if state floor: vx*=Math.exp(-2.5*dt); vy=0; y=floorY; angle += av*dt; av*=Math.exp(-3*dt)
Need glass fragments have angular velocity, collisions, water resistance. For glass fragments:
state airborne/floor; shape triangle; update same; if in puddle (y>=floorY-12 and x between puddle.left/right), drag more, av damp, vx damp.

Transition from underwater to airborne to floor: object ejected -> airborne -> floor. Need ensure toy/fish can. Fish swept through breach. Rock maybe floor inside, not outside. Maybe a rock can be ejected if crack low and current strong? But rock density high; maybe if near bottom and low crack, current drags it to breach and ejects if c strong. But flow may not enough. To show, perhaps one small pebble near crack low? But user can lower crack. However initial crack maybe mid. Could include "pebble" near breach and if crack low enough, it may roll out. But not necessary? Requirement says objects transitioning correctly from underwater motion to airborne motion and then to floor collisions. We can ensure fish and toy. Maybe if crack low, rocks can be ejected too? But rocks have density high and drag low; current at bottom near crack maybe pushes. If crack y=450, depth=305, flow strong. A small rock at bottom near right could be swept. We can set one small rock near right with density 2.0, drag 0.4, size 12, and if c.mag>220 and dist<60, eject. But real rock may not. For demonstration, okay. Need "rocks reacting differently" maybe dense rock resists.

Need "Fish attempting to swim against current before being swept through breach": Fish state. Maybe add a small "swim effort" arrow? Could draw fish with blue arrow opposite current? Maybe not. But status can show "Fish swim against current; toy floats; rocks sink; plant bends." We can draw fish color and orientation.

Need "Glass fragments angular velocity, collisions, and water resistance": We'll create 28 shards at failure. Each has state airborne, x=tank.right+random(-5,10), y=breach.y+random(-40,40), vx=random(30,180)+flow.speed*0.1, vy=random(-150,120), angle, av=random(-8,8), size, sides. They collide floor/walls. If in puddle, water resistance.

Need "A lower crack should initially produce a stronger jet than a higher crack." We can show status: "Initial pressure head: X px; jet strength: Y%". Maybe include in status. Lower crack has greater depth.

Need maybe "side panel develops a visible crack and then bursts." Initially crack visible. On trigger, crack widens, glass fragments. Draw crack line before. On trigger, draw jagged radial cracks and opening.

Need maybe "large glass aquarium" visually. We'll draw.

Need code size maybe large but okay.

Need ensure no external resources. Use canvas only.

Need maybe use emoji? No external. Could use text.

Need implement with robust JS. Let's craft.

Potential issue: overlay pointer events. The canvas is full; overlay with controls. Need controls not block pointer except buttons. CSS .hud pointer-events:none; .controls pointer-events:auto.

Need maybe use DPR scaling. We'll set canvas width/height actual = rect*dpr; ctx.setTransform(dpr,0,0,dpr,0,0) plus world scale? If canvas CSS width/height, easier: canvas.width = innerWidth*dpr; canvas.height=innerHeight*dpr; ctx.setTransform(dpr,0,0,dpr,0,0); Then world coordinates equal CSS pixels? But world W fixed 1000, H=650. If viewport not same, need scale. Use fit:
function resize(){
dpr=Math.min(2,window.devicePixelRatio||1);
cssW=window.innerWidth; cssH=window.innerHeight;
canvas.width=cssW*dpr; canvas.height=cssH*dpr;
scale=Math.min(cssW/W, cssH/H);
offsetX=(cssW-W*scale)/2; offsetY=(cssH-H*scale)/2;
}
draw:
ctx.setTransform(dpr*scale,0,0,dpr*scale,dpr*offsetX,dpr*offsetY); clearRect? Actually clear in CSS coords? If set transform, clearRect(0,0,W,H) clears world area. But outside letterbox remains. Use ctx.setTransform(dpr,0,0,dpr,0,0); clearRect(0,0,cssW,cssH); then set world transform.
Pointer mapping: (clientX-rect.left-offsetX)/scale.
Need if scale maybe <1. Good.

Need UI status. Use status DOM.

Need maybe handle high dt after tab hidden; clamp 0.033.

Need maybe "large aquarium" if viewport small; world scale.

Need perhaps include no network. Good.

Let's write code. Need maybe test syntax? We can write file and maybe run no browser. Use bash maybe check file exists. We can't execute JS in browser. Could use node? index.html contains browser APIs; can maybe not. But can use grep. Need ensure syntax.

Let's compose index.html carefully.

Detailed code structure:

(() => {
'use strict';
const canvas=document.getElementById('sim'), ctx=canvas.getContext('2d');
const statusEl=document.getElementById('status');
const triggerBtn=document.getElementById('trigger'), resetBtn=document.getElementById('reset');
const W=1000,H=650;
const tank={x:155,y:115,w:575,h:395}; tank.right=tank.x+tank.w; tank.bottom=tank.y+tank.h;
const floorY=530;
const WATER_DENSITY=1;
const g=620;
let dpr=1,scale=1,offsetX=0,offsetY=0;
let last=0, t=0, failed=false, dragging=false;
let crackY, waterHeight, waterlineY, waterSpawnAcc=0;
let waterParticles=[], puddleDrops=[], glassShards=[], objects=[], plants=[];
let puddle={left:tank.right+20,right:tank.right+20,volume:0,velL:0,velR:0,wallHits:0};
let flowCache={depth:0,strength:0,speed:0,spawnRate:0};
const rand=(a,b)=>a+Math.random()*(b-a);
const clamp=(v,a,b)=>Math.max(a,Math.min(b,v));
const lerp=(a,b,u)=>a+(b-a)*u;

function resize(){...}
window.addEventListener('resize',resize); resize();

function reset(){
failed=false; dragging=false; t=0; last=performance.now();
crackY=385; waterHeight=tank.h-28; waterlineY=tank.bottom-waterHeight; waterSpawnAcc=0;
waterParticles.length=...; puddleDrops=[]; glassShards=[];
puddle={left:tank.right+20,right:tank.right+20,volume:0,velL:0,velR:0,wallHits:0};
objects=[
{type:'fish', name:'fish', x:310,y:300,vx:35,vy:-5,state:'underwater',density:1.02,drag:0.75,swimPower:190,size:30,angle:-0.1,color:'#ff8a3d',tail:'#ffd166',swept:false},
...
{type:'toy', x:540,y:waterlineY-16,...}
];
plants = [...]
updateStatus();
}
Need object properties: density relative to water, buoyancy maybe explicitly. Use density.
Rocks:
{type:'rock', x:390,y:tank.bottom-22,... density:2.7,drag:0.25,size:24,angle:0.2,av:0}
{type:'rock', x:455,y:tank.bottom-12, density:2.4,drag:0.22,size:18}
{type:'rock', x:620,y:tank.bottom-10, density:2.8,drag:0.2,size:20}
Maybe one small pebble near right: x=675,y:bottom-9,size:10,density:2.2,drag:0.35. But if crack initial y=385, bottom current maybe not. If user lower crack, strong.
Toy:
{type:'toy', x:560,y:waterlineY-18,... density:0.38,drag:1.4,size:28,angle:0,color:'#ff4d6d'}
Leaf objects? Maybe not in objects initially; plant leaves drawn.

Plants:
plants=[{x:235,baseY:tank.bottom-8,height:120,phase:...}, ...]
Need updatePlants(dt):
for each plant:
const topY=baseY-height;
const c=currentAt(x+20, topY+40);
const targetDx=clamp(c.vx*0.035,-35,55) * (failed?1:0.25) + Math.sin(t*2+phase)*5;
p.topX=lerp(p.topX||p.x, p.x+targetDx, 1-Math.exp(-4*dt));
p.topY=lerp(p.topY||topY, topY+Math.sin(t*3+p.phase)*4, ...);
// detach leaves
if failed && flowCache.strength>0.45 && Math.random()<dt*(flowCache.strength-0.45)*0.8) {
create leaf object at top with density .75, drag 1.7, size 8, color green
}
Need p.topX initial p.x.

function currentAt(x,y){
let vx=0,vy=0;
if(!failed || waterHeight<=0) return {vx,vy,mag:0};
const flow=computeFlow();
if(flow.strength<=0) return...
// inside tank
const inTank = x>=tank.x+8 && x<=tank.right-8 && y>=waterlineY && y<=tank.bottom;
if(inTank){
const dx = tank.right - x;
const dy = y - breachY?;
const nearX = Math.exp(-dx/115);
const nearY = Math.exp(-Math.pow((y-crackY)/130,2));
const surfaceInfluence = clamp((tank.bottom-y)/tank.h,0,1); // current lower?
const magScale=flow.strength;
vx = (180+flow.speed*0.55)*nearX*nearY*magScale;
vy = (crackY-y)*0.65*nearX*nearY*magScale; // toward crack
}
// outside jet current maybe for objects outside near jet:
const outside = x>tank.right && x<tank.right+260 && y>crackY-100 && y<floorY+30;
if(outside){
const along = Math.exp(-Math.abs(y-(crackY + 0.18*(x-tank.right)))/90);
const fade = Math.exp(-(x-tank.right)/230);
vx = (flow.speed*0.45)*fade*along;
vy = 120*fade*along + g*0.1;
}
return {vx,vy,mag:Math.hypot(vx,vy)};
}
Need computeFlow uses crackY and waterlineY:
function computeFlow(){
const depth = clamp(crackY-waterlineY,0,tank.h);
const pressure = depth/tank.h;
const remaining = clamp(waterHeight/tank.h,0,1);
const strength = failed ? remaining * (0.08 + 1.15*Math.sqrt(pressure)) : 0;
const speed = 90 + 520*Math.sqrt(pressure); // px/s
const spawnRate = failed ? (8 + 135*strength) : 0;
return {depth, pressure, remaining, strength:clamp(strength,0,1.35), speed, spawnRate};
}
But if depth=0, strength .08*remaining, causing flow even above waterline. Need if depth<=0 strength=0.
const strength = failed && depth>1 ? remaining*(0.05+1.15*Math.sqrt(pressure)) : 0;
Maybe if high crack above waterline, no flow. Good.
Need update flowCache = computeFlow at each frame.

Update:
function step(dt){
t += dt;
flowCache=computeFlow();
if(failed){
const drain = flowCache.strength * (38 + 30*flowCache.pressure) * dt? Need calibrate. If strength up to 1.2, drain ~80 px/s, tank height 395 drains ~5s. Maybe too fast. Use 22+55*strength = 88 px/s -> 4.5s. Better 10+35*strength = 52 -> 7.5s. Let's choose drain = (12 + 48*flowCache.strength)*dt. Strength lower for high crack.
waterHeight = Math.max(0, waterHeight - drain);
waterlineY = tank.bottom - waterHeight;
// spawn water particles
waterSpawnAcc += flowCache.spawnRate * dt;
const maxSpawn = Math.floor(waterSpawnAcc);
for i<maxSpawn spawnWaterParticle(); waterSpawnAcc-=maxSpawn;
}
updatePuddle(dt);
updateWaterParticles(dt);
updatePlants(dt);
updateObjects(dt);
updateGlassShards(dt);
updateStatus maybe every frame.
}
Need if failed and waterHeight<=0, spawn stops.

Spawn water:
function spawnWaterParticle(){
const flow=flowCache;
const opening=38;
const x=tank.right+rand(0,8);
const y=crackY+rand(-opening/2,opening/2);
const speed=flow.speed*rand(0.75,1.15);
const p={x,y,vx:speed*rand(.85,1.15),vy:rand(-45,45)+(crackY-y)*0.15,r:rand(2.2,5.5)*(0.7+flow.strength*.6),life:rand(2.5,5),age:0};
waterParticles.push(p)
}
Need if waterline below crack, no spawn.

UpdateWaterParticles:
for loop backwards:
p.age += dt; p.vy += g*dt; p.vx *= Math.exp(-0.05*dt); p.vy *= Math.exp(-0.015*dt); p.x += p.vx*dt; p.y += p.vy*dt;
if p.y>=floorY:
p.y=floorY;
addPuddleVolume(p.x,p.r);
if(Math.random()<0.8) puddleDrops.push({x:p.x,y:floorY-2,vx:rand(-120,120),r:rand(3,8),life:rand(1.2,3),age:0});
remove.
else if p.x>W+80 || p.life<0 remove.

AddPuddleVolume:
puddle.volume += p.r*p.r*0.6;
if puddle.left>puddle.right? initial. Need expand around x:
const center=puddle.volume? (puddle.left+puddle.right)/2 : x;
if x<puddle.left || puddle.volume<10) puddle.left = Math.min(puddle.left, x);
if x>puddle.right || puddle.volume<10) puddle.right = Math.max(puddle.right, x);
But if first hit near tank right, set left/right around x.
function addPuddleVolume(x,r){
puddle.volume += r*r*0.8;
const half = Math.max(12, Math.sqrt(puddle.volume)*0.8);
const center = (puddle.left+puddle.right)/2;
puddle.left = Math.max(0, Math.min(puddle.left, x-half));
puddle.right = Math.min(W, Math.max(puddle.right, x+half));
}
But this can shrink left if new hits center? Math.min keeps left. right max. okay.

UpdatePuddle:
if volume<=0 return;
const maxHalf=(W-20)/2;
const currentHalf=(puddle.right-puddle.left)/2;
const desiredHalf=Math.min(maxHalf, 12 + Math.sqrt(puddle.volume)*1.15);
const gap=desiredHalf-currentHalf;
if(gap>0.1){
const speed=180*Math.min(1,gap/desiredHalf);
puddle.left -= speed*dt;
puddle.right += speed*dt;
}
if(puddle.left<0){puddle.left=0; puddle.velL=0; puddle.wallHits=...}
if(puddle.right>W){puddle.right=W; ...}
// floor drops
for each:
d.age += dt; d.x += d.vx*dt; d.vx *= Math.exp(-1.4*dt);
if d.x<4 {d.x=4; d.vx=Math.abs(d.vx)*0.45; d.life+=0.2}
if d.x>W-4 {d.x=W-4; d.vx=-Math.abs(d.vx)*0.45}
if age>life remove.
Need draw floor drops before/after puddle.

UpdateObjects:
Need maybe update toy before waterline? waterline updated.
Generic:
function updateObjects(dt){
for (let i=objects.length-1;i>=0;i--){
const o=objects[i];
if(o.state==='underwater') updateUnderwater(o,dt);
else if(o.state==='airborne') updateAirborne(o,dt);
else if(o.state==='floor') updateFloor(o,dt);
if(o.remove) objects.splice(i,1);
}
}
Need updateUnderwater:
const c=currentAt(o.x,o.y);
let ax=0, ay=g*(1-o.density); // buoyancy
// drag
ax += (c.vx-o.vx)*o.drag;
ay += (c.vy-o.vy)*o.drag;
if(o.type==='fish'){
// autonomous swimming
if(!failed){
ax += Math.sin(t*1.7+o.phase)*12;
ay += Math.sin(t*2.1+o.phase)*5;
} else if(c.mag>18 && !o.swept){
// swim against current, strongest near breach
const distToBreach=Math.hypot(o.x-tank.right,o.y-crackY);
const danger = distToBreach < 210 && c.mag > 25;
if(danger){
ax += -Math.sign(c.vx || 1)*o.swimPower*(0.55+0.45*clamp(1-distToBreach/210,0,1));
ay += -Math.sign(c.vy || 0)*o.swimPower*0.18;
}
if(danger && distToBreach<90 && c.mag>o.swimPower+120){
o.swept=true;
}
}
} else if(o.type==='toy'){
// bobbing and float
ay += Math.sin(t*4+o.phase)*18;
if(o.y < waterlineY-20) ay += 360;
} else if(o.type==='rock'){
// bottom friction
if(o.y > tank.bottom-o.size-5){
o.vx *= Math.exp(-2.2*dt);
// current can roll if strong
ax += c.vx*0.08*o.drag;
}
} else if(o.type==='leaf'){
ay += Math.sin(t*5+o.phase)*8;
}
// integrate
o.vx += ax*dt; o.vy += ay*dt;
const dragWater=Math.exp(-0.15*o.drag*dt);
o.vx *= dragWater; o.vy *= dragWater;
o.x += o.vx*dt; o.y += o.vy*dt;
if(o.type!=='plant') o.angle = Math.atan2(o.vy,o.vx) + (o.type==='fish'?0:0); // fish faces direction, but if swimming left maybe.
// collisions
const half=o.size||12;
if(o.x<tank.x+12+half){o.x=...; o.vx=Math.abs(o.vx)*0.45;}
if(o.x>tank.right-12-half){
if(failed && (o.type==='fish'||o.type==='toy'||o.type==='leaf'||(o.type==='rock' && o.size<13 && o.swept)) && Math.abs(o.y-crackY)<75 && currentAt(o.x,o.y).mag>70){
ejectObject(o);
} else { o.x=tank.right-12-half; o.vx=-Math.abs(o.vx)*0.55; }
}
if(o.y<waterlineY+half){ o.y=waterlineY+half; o.vy=Math.abs(o.vy)*0.25; }
if(o.y>tank.bottom-half){ o.y=tank.bottom-half; o.vy=-Math.abs(o.vy)*0.25; o.vx*=0.85; }
// swept ejection if already swept and near breach
if(failed && o.swept && Math.hypot(o.x-tank.right,o.y-crackY)<105 && c.mag>60) ejectObject(o);
}
Potential issue: For toy, if y<waterlineY+half? Floating toy should be at surface, but if half=14, y=waterline-18, y<waterline+14 triggers collision, setting y=waterline+14 (under water). Need separate surface. For toy, allow y between waterline-18 and bottom. Collisions should be y<waterlineY-10 maybe. For fish, waterline+size/2. For rock bottom. For leaf.
So:
const topLimit = o.type==='toy' ? waterlineY-18 : waterlineY+half;
if(o.y<topLimit){ o.y=topLimit; o.vy=Math.abs(o.vy)*0.2; }
For toy, if y above surface, bounce down. Good.
For fish, if y<waterline+half, bounce.
For rock bottom.
Need if object is swept and x>right, eject.

ejectObject(o):
if(o.state==='airborne') return;
const flow=flowCache;
o.state='airborne';
o.x=tank.right+rand(10,35);
o.y=crackY+rand(-30,30);
const exitSpeed=flow.speed*rand(0.45,0.85);
o.vx=Math.max(80, exitSpeed + Math.abs(o.vx)*0.2);
o.vy=(crackY-o.y)*0.55 + rand(-70,80);
if(o.type==='fish') o.angle=0;
if(o.type==='rock') o.av=rand(-5,5);
o.swept=true;
// splash
for maybe waterParticles push.
Need if fish ejected, angle = 0 (facing right). But updateAirborne uses angle += av*dt. Need set av.

Airborne update:
function updateAirborne(o,dt){
const inPuddle = puddle.contains? y>floorY-20 && x between left/right;
const airDrag = inPuddle ? 2.5 : 0.06;
o.vx *= Math.exp(-airDrag*dt);
o.vy *= Math.exp(-(inPuddle?0.6:0.02)*dt);
o.vy += g*dt;
o.x += o.vx*dt; o.y += o.vy*dt;
if(o.av) o.angle += o.av*dt; else if(o.vx||o.vy) o.angle=...
if(o.y>=floorY){
o.y=floorY;
if(o.vy>80) { splash; }
o.vy = -o.vy*0.22;
o.vx *= 0.55;
o.state='floor';
o.av *= 0.35;
if(o.type==='fish') o.remove=true? Maybe fish lands floor and flops? Requirement floor collisions. Could keep as floor object with flopping. But fish on floor should die? We can keep and draw flat. Maybe remove after? Better keep with state floor and flops.
}
if(o.x<0||o.x>W){ clamp bounce; o.vx*=-0.45;}
}
Floor update:
if(o.type==='fish') { o.phase? flopping: o.angle=Math.sin(t*10)*0.25; o.x += Math.sin(t*8)*0.03? maybe no; o.vx*=0.9; }
else { o.vx *= Math.exp(-2.5*dt); o.angle += (o.av||0)*dt; o.av*=...}
if in puddle: stronger drag and av damp.
Need if object on floor in puddle, water resistance. Good.

Glass shards update:
similar to airborne/floor but shape.
function updateGlassShards(dt){
for each s:
const inPuddle=s.y>floorY-18 && s.x>puddle.left && s.x<puddle.right;
const drag=inPuddle?2.8:0.08;
s.vx *= Math.exp(-drag*dt); s.vy *= Math.exp(-(inPuddle?0.7:0.02)*dt); s.vy += g*dt;
s.x += s.vx*dt; s.y += s.vy*dt; s.angle += s.av*dt; s.av *= Math.exp(-(inPuddle?3.5:0.04)*dt);
if(s.y>=floorY){ s.y=floorY; if(s.vy>100) splash; s.vy=-s.vy*0.25; s.vx*=0.5; s.state='floor'; s.av*=0.45; }
if x out bounce.
}
Need draw glass shards:
function drawGlassShard(s){
ctx.save(); translate; rotate; beginPath; triangle/quadrilateral; fill rgba; stroke.
}

Draw:
function draw(){
ctx.setTransform(dpr,0,0,dpr,0,0); ctx.clearRect(0,0,cssW,cssH); // background letterbox maybe.
ctx.setTransform(dpr*scale,0,0,dpr*scale,dpr*offsetX,dpr*offsetY);
ctx.clearRect(0,0,W,H);
drawRoom();
drawPuddle();
drawTankBackAndWater();
drawWaterJet();
drawObjects maybe inside? Need draw underwater objects inside tank in drawTank. But draw all objects maybe? Let's organize:
drawRoom();
drawPuddle();
drawTank(false)
drawWaterJet();
drawAirborneAndFloorObjects();
drawGlassShards();
drawTankFront();
drawCrack();
drawLabels?
}
Need draw underwater objects inside water. We can draw all objects in drawTankWater with clip:
- Draw tank back panel
- Draw water fill clipped.
- Draw plants (underwater parts)
- Draw objects whose state underwater (and maybe toy if underwater)
- Draw water surface/waterline
- Draw tank front frame maybe.
But airborne/floor objects should not be drawn inside. So:
drawRoom();
drawPuddle();
drawTankBase();
drawTankInterior(); // water, underwater objects, plants
drawWaterJet(); // outside
drawObjectsOutside(); // state airborne/floor (fish, toy, leaves)
drawGlassShards();
drawTankFront(); // glass frame and crack
drawHUD? status DOM.
Need draw tank front after water jet? The jet emerges from crack, should appear behind front glass maybe but visible. Draw jet after tank interior before front. Good.
Draw puddle before tank? Puddle on floor behind tank, but water exiting should appear in front. Draw puddle before tank base, then tank.
Draw water jet maybe after tank interior, before front.
Draw floorDrops after puddle maybe.

drawRoom:
gradient wall, floor. Draw room boundaries lines at x=0/W and floorY.
drawPuddle:
if volume>0:
ctx.save(); clip floor y>=floorY; draw ellipse. Need floor region.
const left=puddle.left, right=puddle.right, cx=(left+right)/2, rx=(right-left)/2, ry=clamp(8+puddle.volume/2500,8,28)
fillStyle rgba(42,160,255,0.35); ellipse; fill.
stroke edge. Draw small waves lines.
Draw floorDrops.
Need if puddle behind tank, tank base covers? Actually water on floor should surround tank base. Draw puddle before tank base, then tank base on top. But water should be in front of base? Maybe okay. We can draw puddle after tank base too. But puddle outside right. Draw tank base before puddle? If water under base, not. Let's draw room, tankBase shadow, puddle, water jet, tank interior. Puddle appears in front of base? Fine.
Maybe draw puddle after room and before tank so base covers part. Good.

drawTankInterior:
- Draw tank back panel shadow.
- Clip to tank inner.
- Draw water fill:
ctx.beginPath(); rect inner; clip();
const waterGrad=ctx.createLinearGradient(0,waterlineY,0,tank.bottom); stops.
fillRect(tank.x+10,waterlineY,tank.w-20,tank.bottom-waterlineY);
draw wave surface with path.
draw bubbles maybe.
draw plants and underwater objects.
ctx.restore();
- Draw waterline: line across inner with white/blue.
- Draw maybe volume labels.

Need draw plants:
function drawPlant(p){
ctx.save();
const topX=p.topX, topY=p.topY;
ctx.strokeStyle='rgba(31,166,91,.9)'; ctx.lineWidth=6; ctx.lineCap='round';
ctx.beginPath(); moveTo(p.x,p.baseY); quadraticCurveTo((p.x+topX)/2, p.baseY-p.height*0.5, topX, topY); stroke();
draw leaves at positions along stem: for k 0..3, u=(k+1)/4; sx=lerp(p.x,topX,u), sy=lerp(baseY,topY,u); drawLeaf(sx,sy, angle + sin...);
ctx.restore();
}
Need drawLeaf function.

Draw objects underwater:
fish:
ctx.translate(o.x,o.y); rotate(o.angle); draw body ellipse, tail triangle, eye. Need if angle points direction of velocity. For fish swimming left, angle pi. Good.
toy:
draw small boat/duck? "floating toy" maybe rubber duck. Draw yellow duck/boat. ctx.translate, rotate; body ellipse, sail? Use simple toy boat: red hull, yellow cabin.
rock:
draw irregular polygon with angle.
leaf:
draw leaf.

Airborne/floor objects:
draw same, but if fish floor maybe draw flat.

Need draw water jet:
for p waterParticles:
alpha=clamp(1-p.age/p.life,0,1); radius p.r*(0.7+0.3*alpha); fill blue with alpha.
Maybe draw continuous jet as quadratic curve? Use particles enough. Also draw a translucent stream path:
ctx.beginPath(); moveTo(tank.right+5, crackY); for i 0..40: x=tank.right+5+i*7; y=crackY + 0.18*i*7 + 0.5*g*(x/speed)^2? But flow changes. Could draw faint curve based on current flow. But particles show. Need "curved water jet affected by gravity" visible. We can draw a bezier/stream:
const flow=flowCache; const speed=flow.speed; ctx.strokeStyle rgba; ctx.lineWidth=10+18*strength; beginPath; for s from 0..1: x=tank.right+10+s*330; time=x/speed; y=crackY + 0.5*g*time*time + (s*40); lineTo. stroke. alpha strength.
Then particles.
Need if flow=0 no.

Draw crack:
if !failed:
draw jagged crack on right panel: points around x=tank.right-10, y=crackY. Use deterministic based crackY? Maybe use sin.
ctx.strokeStyle rgba(220,245,255,.9); lineWidth 2.5; draw path with branches.
else:
draw opening: dark ellipse/rect at crack; jagged radial cracks.
Need draw front glass:
ctx.strokeRect tank.x,tank.y,tank.w,tank.h; lineWidth 8; strokeStyle rgba(180,230,255,.45);
top/bottom frames.
Draw right panel side maybe with gradient.

Need draw status:
updateStatus each frame:
let msg;
if(!failed) msg=`Drag the crack: current pressure head ${Math.round(crackY-waterlineY)} px. Lower crack = stronger initial jet.`;
else msg=`Failed: waterline ${Math.round(tank.bottom-waterlineY)} px high; jet strength ${Math.round(flowCache.strength*100)}%; puddle ${Math.round(puddle.volume)} units.`;
statusEl.textContent=msg;
Maybe include objects state.

Need buttons:
triggerBtn.addEventListener('click',()=>{ if(!failed){ failed=true; createGlassShards(); spawn initial water burst; }});
resetBtn...
Need if trigger when failed maybe no.

Need createGlassShards:
const n=34;
for i:
const y=crackY+rand(-50,50); const x=tank.right+rand(-8,12);
glassShards.push({x,y,vx:rand(30,180)+flowCache.speed*0.12,vy:rand(-180,120),size:rand(10,26),angle:rand(0,TAU),av:rand(-8,8),state:'airborne',points:...});
Need maybe shards on left side? Fine.

Need "side panel develops visible crack and then bursts": On trigger, maybe create screen shake? Not necessary. Could set burstFlash. Use burstFlash variable decays. Draw bright burst ring. Add burstFlash=1 on trigger. update decays. Draw radial cracks.

Need "water escaping through opening with flow strength based on water depth and decreasing as tank drains": Need maybe show a small gauge? Could include in status.

Need maybe "A lower crack should initially produce stronger jet than higher crack": We can set crackY initial mid and allow drag. Need maybe if user drags lower, flow stronger. Good.

Potential issues:
- If waterlineY > crackY? depth negative, flow 0. But if user drags high crack above waterline, no failure. Maybe okay.
- If user drags crack below waterline but waterline lowers below crack, flow stops. Good.
- Objects update underwater after waterline; if waterline lowers below object, object becomes airborne? Need transition correctly from underwater to airborne. If waterline falls below an object, it should become airborne (fall inside tank) before maybe swept. Currently underwater objects collision topLimit waterline+half will push object down, so they remain just below waterline, not airborne. But as tank drains, floating toy should be carried, fish maybe. But for objects that are not swept, waterline lowering should leave them? In real, if waterline falls below fish, fish would be on bottom? Hmm. Requirement "Objects transitioning correctly from underwater motion to airborne motion and then to floor collisions" likely refers ejected objects. But maybe if water drains and objects are no longer submerged, should transition to airborne. We can implement if waterlineY > obj.y - half (object above waterline inside tank) and not floating? For fish/rock/leaf, if y < waterlineY + half, they are no longer underwater; should become airborne inside tank (fall) unless ejected. For toy, floats at surface until ejected; when water drains, toy rests on bottom? Actually toy could be carried out. Need maybe if waterline falls below object and not swept, set state airborne. But if waterline is lowering, fish may be stranded/airborne? The requirement wants transition. Let's implement:
In updateUnderwater after collisions:
if(failed && o.type!=='toy' && o.y < waterlineY + o.size*0.4 && waterHeight < tank.h*0.45) { // water has drained below object
o.state='airborne'; o.vx += currentAt(...).vx*0.2; o.vy += 20; o.av=rand(-3,3); continue;
}
For toy, if waterline falls below toy and not ejected, set airborne/floor? But toy should float on surface. If waterline drops below bottom? It will be carried by current. If waterline below toy, set airborne.
Maybe before collision:
const topLimit = o.type==='toy' ? waterlineY-18 : waterlineY+half;
if(failed && o.y < topLimit - 8 && waterHeight < tank.h*0.65) { // no longer submerged
o.state='airborne'; o.vy = Math.max(o.vy, -20); o.vx += currentAt(o.x,o.y).vx*0.15; continue;
}
But for fish, if waterline lowers, it may become airborne and fall inside tank. Good.
For toy, if waterline lowers, toy should fall/land on bottom or be ejected. But if current strong near breach, ejected. If not, falls to floor inside tank? Floor inside tank bottom not room floor. It should collide with tank bottom. Our airborne collisions only floorY, not tank bottom. Need if airborne inside tank, collide with tank bottom/walls. But ejected objects outside. For objects becoming airborne inside tank due draining, they should collide with tank bottom, then maybe floor? Hmm. Requirement says floor collisions, likely outside room floor. But if airborne inside tank, tank bottom is floor? Better treat tank bottom as collision until maybe through breach. For airborne objects, if x inside tank and y>=tank.bottom-size, bounce on tank bottom. If x outside and y>=floorY, floor. Implement updateAirborne:
if(o.x>tank.x && o.x<tank.right && o.y>=tank.bottom-o.size) { o.y=tank.bottom-o.size; if(o.vy>60) o.vy=-o.vy*0.25; o.vx*=0.6; if(Math.abs(o.vy)<30) o.state='floor'; }
else if(o.y>=floorY)...
For floor objects inside tank, maybe rest on tank bottom; outside rest on floor. But draw floor collisions outside. Fine.
For floor update, if x inside tank and y>=tank.bottom-size, stay; else if outside floorY.
Need if object on tank bottom and water gone, maybe state floor. Good.

Need currentAt outside for ejected objects maybe not needed.

Need if objects underwater and waterline below them, they become airborne before collision. Good.

Need if fish is swept, ejected. If not, as water drains, it falls.

Need "fish attempting to swim against current before being swept through breach": We should maybe not let waterline drain too fast before fish swept. Fish near right, current strong. Good.

Need maybe "plants" anchored: as waterline lowers, plants may become airborne? Anchored to bottom; water drains, leaves may dry/bend. We can keep.

Need "water resistance for glass fragments": if shard in puddle, drag. Good.

Need "collisions" for glass fragments with room boundaries and floor. Good.

Potential issue with draw order: Underwater objects drawn before front. Airborne objects drawn after water jet; if ejected fish passes through front, visible. Good.

Need maybe use "TAU" constant.

Need maybe "no external resources" no fonts. Good.

Need maybe "large glass aquarium" with glass transparency.

Let's write file.
write /tmp/ws/index.html (42045 bytes)
toolResult

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

assistant
thinking
Need final concise. But before final maybe verify file present. Use bash ls.
bash {"command":"ls -l /tmp/ws/index.html && wc -c /tmp/ws/index.html","timeout":5}
toolResult

-rw-r--r--. 1 node node 42045 Aug 2 16:02 /tmp/ws/index.html 42045 /tmp/ws/index.html

assistant
thinking

Created `/tmp/ws/index.html` — a single self-contained HTML file with inline CSS and JS, no external resources.

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

modelnex-n2-pro
vendorNex AGI
harnesspi-agent
generated2026-08-02

EVALUATION

3/5 partial

Aquarium with fish, plants, and a crack indicator renders and animates slightly, but no burst/jet/puddle was triggered in any panel.

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 motion1.7%
on click1.5%
on drag1.6%
on wheel1.7%
on enter + space1.5%
on arrow keys1.6%
on w/a/s/d1.5%
frame spread25.4 / 255
console errors0
js errors none

METRICS

runtime235.4s
tokens in37k
tokens out33k
cached35k
cost / run$0.0432