oneshotlm
Compare all models on this prompt →

Aquarium breach · mimo-v2.6-flash

No runnable output Cost / run $0.0000 Runtime 900.0s

Prompt for this run

Build an interactive 2D aquarium-breach simulation on a canvas. Use HTML, CSS, and JavaScript as needed. Write it to `index.html` in the current working directory using your file tools. Inline your own CSS and JavaScript. Do not use external assets, CDNs, or network requests. No third-party libraries are provided. Required behavior: - Initially show an intact glass tank containing water, fish, rocks, plants, and a floating toy. Let the user drag a visible crack vertically on one side below the initial waterline, then press Breach. Provide Reset to restore the same initial scene. - On Breach, show the panel breaking around the selected opening. Water exits in a gravity-curved jet, and the waterline falls continuously. Outflow weakens as the water level approaches the opening and stops when water no longer reaches it. - Escaped water forms a spreading puddle constrained by the floor and room walls. - Model distinct responses to buoyancy, drag, and current: rocks sink, the toy floats, plants bend or drift, and fish initially swim against the current before some are swept through the breach. Movable objects that fit the opening can be carried out by the flow. - Objects leaving the water transition to gravity-driven motion and collide with the floor. Glass fragments rotate, collide, and experience resistance in water. - Simplified, internally consistent physics is sufficient; a full computational-fluid-dynamics solver is not required. Verify: Position the crack, trigger a breach, and sample the early outflow. Check that the jet curves downward, the waterline begins to fall, and nearby objects respond to the current. Use Reset and check that the tank returns to its intact initial state.

⚠ no runnable output

This one-shot run produced no runnable artifact.

Agent exceeded 15 minutes

The transcript below shows what the model did — read it to see why the one shot fell short.

TAKE IT TO CODE

This run produced no artifact — reuse the prompt with a different model or your own pi-agent run.

RUN

modelmimo-v2.6-flash
vendorXiaomi · MiMo
harnesspi 0.87.1 · browser iteration
generated2026-10-22

METRICS

runtime900.0s
tokens in0
tokens out0
cached0
cost / run$0.0000
Transcript · 3 messages
system

user

Build an interactive 2D aquarium-breach simulation on a canvas. Use HTML, CSS, and JavaScript as needed. Write it to `index.html` in the current working directory using your file tools. Inline your own CSS and JavaScript. Do not use external assets, CDNs, or network requests. No third-party libraries are provided. Required behavior: - Initially show an intact glass tank containing water, fish, rocks, plants, and a floating toy. Let the user drag a visible crack vertically on one side below the initial waterline, then press Breach. Provide Reset to restore the same initial scene. - On Breach, show the panel breaking around the selected opening. Water exits in a gravity-curved jet, and the waterline falls continuously. Outflow weakens as the water level approaches the opening and stops when water no longer reaches it. - Escaped water forms a spreading puddle constrained by the floor and room walls. - Model distinct responses to buoyancy, drag, and current: rocks sink, the toy floats, plants bend or drift, and fish initially swim against the current before some are swept through the breach. Movable objects that fit the opening can be carried out by the flow. - Objects leaving the water transition to gravity-driven motion and collide with the floor. Glass fragments rotate, collide, and experience resistance in water. - Simplified, internally consistent physics is sufficient; a full computational-fluid-dynamics solver is not required. Verify: Position the crack, trigger a breach, and sample the early outflow. Check that the jet curves downward, the waterline begins to fall, and nearby objects respond to the current. Use Reset and check that the tank returns to its intact initial state.

assistant
thinking
Let me build this. I'll write a single index.html with canvas simulation.

Key features:
- Intact tank with water, fish, rocks, plants, floating toy.
- Drag crack on left or right side panel below waterline: user drags vertically to position/size the crack opening on one side.
- Breach button, Reset button.
- On breach: panel breaks around opening — glass fragments spawn, rotating, colliding, water resistance in water.
- Water jet exits through opening in gravity-curved arc (particles), waterline falls continuously (volume model).
- Outflow rate proportional to sqrt(head above opening) — Torricelli; stops when waterline reaches opening bottom.
- Puddle on floor spreading, constrained by room walls (room has left/right walls).
- Objects: buoyancy, drag, current. Rocks sink, toy floats, plants bend/drift, fish swim against current initially then some get swept out through breach.
- Movable objects that fit opening carried out by flow.
- Objects leaving water → gravity, collide with floor.

Let me design the simulation carefully but keep it manageable.

Scene layout: canvas 1200x800 (or responsive). Room: floor at y=760, room walls at x=40 and x=1160. Tank sits on floor: tank from x=250..850? Let's make tank glass walls. Tank outer: left wall x=300, right wall x=900, bottom y=740 (tank bottom glass), top open at y=180. Glass thickness 10.

Waterline initially y=260 (water surface). Water fills from y=260 to y=740 inside tank (inner x 310..890).

Crack placement: user drags vertically on one side — determine side by where drag starts (left or right half of tank?). Simpler: user drags on left side or right side of tank interior near a glass wall; the drag start x determines side (within 60px of a wall), drag vertical extent defines opening. Let's do: mousedown near either glass wall inside tank (within 60px of a wall), drag vertically → creates crack marker spanning from startY to currentY. Must be below initial waterline. On mouseup, crack is set (visible, dashed glowing line with jagged crack pattern drawn on the glass).

Actually "drag a visible crack vertically on one side below the initial waterline" — so drag creates a crack whose vertical extent is defined by drag. If drag ends above waterline, clamp to below waterline.

Breach: opening region = crack segment expanded? Panel breaking "around the selected opening": the glass panel section around crack shatters into fragments. Let's define the opening as the dragged segment (crack), and on breach, the panel area from crack start to crack end expands: fragments spawn from panel region (say crack region plus margin) and the hole is created spanning the crack's y-range (maybe slightly extended, e.g., full crack length plus small margin). Water can only flow out where water level overlaps the opening.

Water model: 
- Volume V, tank inner width W, water height h measured from tank bottom inner (y=740-10=730... let's set inner bottom yB=730). Actually let's keep numbers: tank glass: bottom bar from y=730 to 740; inner floor y=730. Water surface yS. Water height h = 730 - yS. Initial yS = 260 → h0 = 470.
- Opening: y range [oy0, oy1] (in canvas coords), with oy1 > oy0 (oy1 lower). Opening exists only while breached.
- Effective head: water flows if yS < oy0 (surface above opening top). Outflow rate Q = C * sqrt(2 g (oy0 - yS)) * min(opening height, water depth submerged)... simplified: Q = k * sqrt(max(0, oy0 - yS)) but also limited by submerged opening height: subH = clamp(yS, oy0, oy1) - oy0... Actually submerged opening height = clamp(yS - oy0, 0, oy1 - oy0) when yS > oy0? Water surface yS is smaller (higher) when more water. Submerged part of opening = from max(yS, oy0) to oy1 if yS < oy1. subH = clamp(oy1 - max(yS, oy0), 0, oy1-oy0).
- Q = k * subH * sqrt(2*g*max(0, (yS+oy0)/2 - oy0))... simpler: Q = k * subH * sqrt(max(0, oy0 - yS)) hmm that's zero when surface at opening top. Head h = waterDepth above opening top = oy0 - yS (positive when surface above opening top). If surface below opening top but above opening bottom, head ~ 0 but still some flow due to subH. Use Q = k * subH * sqrt(2*g*max(0.001, ...)). Let's use Q = k*subH*sqrt(max(0, oy0 - yS + 20)) * something — small residual while opening partly submerged. Stop when yS >= oy1 (water no longer reaches opening). Good: "stops when water no longer reaches it" — when waterline falls to bottom of opening, outflow stops.

dh/dt = -Q / W. Also jet velocity vy = sqrt(2 g head), direction horizontal outward.

Also waterline should fall only while outflow; also the volume lost goes into jet particles and puddle volume.

Puddle: volume on floor, spread as a height field or as a simple shape: puddle volume Pv, spreads to width constrained by room walls. Model as a 1D height profile? Simpler: puddle as a shape centered under the jet landing point, spreading outward: area = Pv / rho; puddle is a semi-ellipse with radius r = sqrt(area/ (pi/2))... plus constraint: clipped by room walls x=40, x=1160 and floor. Let's do simple: puddle height profile array over x bins (e.g., 120 bins across room width), water added at impact point, then diffuses/spreads (shallow-water-ish: flow from higher bins to lower neighbors), clamped to walls. That gives spreading puddle constrained by floor and walls. Nice and simple: bins with height array, on each frame: add volume from jet impact, then relax: for each neighbor pair, flow proportional to height difference (and cap), conserving volume. Also prevent exceeding some max height? Not needed.

Actually jet particles fall and when they hit floor (y > floorY) deposit into puddle bin at that x. Some jet particles hit the tank's outer side / floor earlier. Fine.

Jet: emit particles at opening with velocity: horizontal outward speed v = k*sqrt(head), vertical initial 0 (water at rest vertically), then gravity accelerates → gravity-curved trajectory. Emit rate proportional to Q.

Objects:

Entities with position, velocity, radius/size, type: rock, toy, plant, fish.

Physics per object:
- If in water (object center below water surface AND inside tank x-range, or in puddle? puddle too shallow — only tank water): buoyancy force upward proportional to submerged volume; gravity; drag; current force.
- Current: near the opening, flow velocity field: v_flow directed toward opening, magnitude ~ Q-based, stronger near opening. Also global slow drift toward opening when draining. Fish respond: swim against current (they add swim velocity opposite flow) until current exceeds their max swim speed → swept out.
- When object passes through opening (x beyond wall at opening y-range), it exits: no longer in water → gravity, drag from air (small), collide with floor (bounce/damp), collide with room walls and tank outer walls? Tank outer wall: once outside, object falls to floor and slides; keep it simple: collide with floor and room walls; also collide with tank outer box? Could skip, but objects exiting at opening are already outside the tank wall plane, so they'd fall along outside wall. Fine — collide with floor and room walls.

- Rocks: heavy, sink to tank bottom, sit there. Can they be carried out? "Movable objects that fit the opening can be carried out by the flow" — if opening is large and flow strong, rocks could be dragged. Give rocks high mass/density; current force needs to be strong near opening. Let's allow it if opening height > rock size and flow strong (early). Make flow force reasonably strong.

- Toy: floats at surface (buoyancy > gravity), bobs; drifts with current toward opening; if fits and reaches opening, gets carried out; once out, gravity drops it, it bounces, floats in puddle? Could make it float in puddle if puddle height > toy radius — cute but optional. Let's have it land on floor and rest (or float in puddle if puddle deep enough — skip).

- Plants: anchored at base to tank bottom; segments bend with current (draw as chain of segments with angle influenced by flow + sway), some plants uprooted and drift if current strong. "plants bend or drift" — implement: plant = chain of nodes; each node has rest position; flow applies force; roots hold node0. If flow force exceeds threshold for a while, uproot → becomes drifting object.

- Fish: simple: have target behavior — swim against current (add thrust opposite flow direction), wander. If |current| > swimMax, get swept (velocity follows flow), and if near opening y-range and carried past wall → exits, becomes flopping fish on floor (gravity, bounce, wiggle). "some are swept through the breach" — make fish vary in swim strength: some weak fish get swept, strong ones keep swimming.

Glass fragments: spawn from panel region around opening; each: pos, vel (outward + random), angular velocity, rotation; gravity; collision with floor (bounce, settle), water resistance when inside water region (drag high when submerged), collide with tank walls? Keep: floor collision, room wall collision, and when inside water, extra drag + buoyancy-ish (glass denser than water, sinks slowly with drag). Also fragments colliding with each other — "Glass fragments rotate, collide" — do simple circle-ish (polygon bounding radius) collision pairs resolution — with ~40 fragments O(n²) fine.

Panel breaking: the glass side panel — we have left wall and right wall drawn as rectangles from y=180 (rim) to y=740. The breach opens a hole spanning crack y-range (extend a bit, say the crack segment plus 15% margin, min 60px). Fragments spawn from the panel segment covering the hole region (plus margin). Draw remaining panel with a jagged hole: we can clip out the hole region using path with jagged edges. Simpler: draw wall rectangle, then use destination-out or draw hole as jagged polygon in background color... On canvas, easiest: draw glass panel as path with a hole (rect with jagged notch cut out). Build jagged hole edges once at breach: array of points along the hole perimeter. Draw panel via path: outer rect + hole path with 'evenodd' fill → hole appears. Then background/water behind? Behind the panel is the room wall (outside the tank). We can fill background first (room), then draw puddle, then tank interior water clipped... Hmm ordering.

Let's design rendering order:
1. Room background (walls, floor gradient).
2. Puddle on floor.
3. Tank interior: water body (clipped to tank interior region ∩ above waterline, minus hole? water inside), plants, objects, fish.
4. Glass panels (with hole if breached), drawn semi-transparent over interior.
5. Jet particles (outside tank, in front).
6. Fragments.
7. UI overlays (crack marker before breach).

Water inside tank: fill rect from yS to inner bottom, inner x range, clipped also where the hole is below waterline? Water would pour out the hole — visually, the hole region should show the room behind rather than water when water level... Actually if water is at the hole, water is exiting there; drawing water up to hole would look fine since hole shows water too (it's the opening). But we want to see through the hole when empty. Water fills interior; hole shows whatever is inside (water) — that's physically right. The panel hole is transparent anyway (glass is transparent). So just: fill water rect; draw panel with hole cut (panel semi-transparent, hole = no fill). Good.

Also glass panel is drawn semi-transparent — through it we see water. Fine.

Water surface: draw with slight wave (sine), plus meniscus line.

Volume ↔ waterline: h = V / (innerW * 1) with innerW constant (tank cross-section uniform) → yS = innerBottom - V/innerW * (scale). Let's just track h directly and integrate dh/dt = -Q/innerW. Volume conservation for puddle: lost volume = Q*dt*innerW (in px² units), added to puddle bins. Jet particles visual only but should correspond: emit particles carrying volume; deposit into puddle on landing. To keep consistency simpler: reduce water, spawn jet particles; each particle has volume qv = totalVolume/expectedParticles... Simpler: deposit into puddle when particle lands; tune emission so it roughly matches. Or deposit immediately? Visual of falling jet is important. I'll give each particle volume equal to (Q*dt / emittedCount) and add to puddle on landing. That conserves.

Crack drag interaction: Before breach, mouse down inside tank near a side wall (within 50px horizontally of either glass wall) and below waterline start... The spec: "drag a visible crack vertically on one side below the initial waterline". So drag must start below initial waterline presumably. I'll allow start anywhere on the glass panel; clamp segment to [waterline+10, innerBottom-10]. Side determined by nearest wall. As you drag, show a glowing crack line with jagged small branches and text hint "Drag on a side panel to place the crack".

Crack representation: {side:'left'|'right', y0, y1}. If user drags up (y1<y0), normalize.

Minimum crack height, e.g., 50px. If drag too small, use 50px.

Breach: hole y-range = [y0 - margin, y1 + margin] clamped to panel bounds, margin ~ 12. Actually "panel breaking around the selected opening" — the opening is the crack; panel breaks around it, meaning fragments from region around it and hole grows to include the crack. I'll make hole = crack span expanded by ~15% each way (min total 70px).

Now physics details/numbers. Canvas: let's use fixed 1200x800 with CSS scaling to fit. Use devicePixelRatio? Keep simple: canvas width=1200 height=800, CSS max-width 100%.

Coordinates:
- Room: left wall x=0..30, right wall x=1170..1200, floor y=755..800. Back wall background.
- Tank outer: x from 260 to 940, top rim y=150, bottom y=755 (sits on floor). Glass thickness 12.
  - left panel: x 260..272, y 150..755
  - right panel: x 928..940, y 150..755
  - bottom panel: y 743..755, x 260..940
  - inner: x 272..928 (width 656), inner bottom y=743.
- Initial waterline yS0 = 300 → water depth 443.

Hmm wait: jet exits horizontally from opening with velocity; it must travel outside the tank and land on floor. Tank right wall at 940, floor at 755. Fine.

Gravity g = 1500 px/s² (scaled). dt fixed 1/60.

Water flow:
- opening [oy0, oy1].
- head = max(0, oy0 - yS) (px above opening top).
- submerged opening height subH = clamp(oy1 - max(yS, oy0), 0, oy1-oy0); if yS >= oy1 → 0 → stop.
- Q (px²/s volume flux in cross-section... actually area flux) = Cd * subH * sqrt(2*g*max(head,0)) — but when head=0 and subH>0 (surface at opening top), flow = 0? Physically with surface exactly at opening top, flow ~0 and then surface drops below top... then water surface is below opening top so opening partly submerged, outflow driven by pressure at depth — for a submerged opening with free surface above the opening top: standard orifice Q = Cd*A*sqrt(2g*ho) where ho = depth from surface to opening centroid. When surface is between oy0 and oy1, ho = (max(yS,oy0)+oy1)/2 - yS... let me define depth to centroid: centroid y_c = (oy0+oy1)/2; ho = y_c - yS; if yS > y_c, ho could be small. Use ho = max(0, y_c - yS) but ensure flow continues until yS >= oy1: define effective head = max(2, y_c - yS) clamped ≥ some min while yS < oy1. Let's do:

if yS >= oy1: Q = 0 (water no longer reaches opening — stop).
else: ho = max(6, (y_c - yS)) hmm if yS close to oy1, y_c - yS ≈ -(oy1-oy0)/2 negative → clamp to small positive like 4 (residual flow). subH = clamp(oy1 - yS, 0, H_open) but also can't exceed... if yS < oy0, subH = full open height.
Q = 0.6 * subH * sqrt(2*g*ho).

dh/dt = -Q / innerW.

That decreases yS over time; as yS → oy1, Q → small (subH→0, ho→4) → Q→ ~0.6*0*.. = 0. Good, stops.

Jet velocity: v = sqrt(2*g*ho), horizontal outward. Emit count ∝ Q.

Also waterline falls "continuously" — yes.

Outflow also should stop if... fine.

Current field inside tank: potential flow toward a sink at the opening. Simplify: for a point p inside tank, flow direction toward opening center, magnitude = base * f(dist) where base ∝ Q/... Let's define: strength S = Q (px²/s). Flow speed at distance d from opening ≈ S / (2π d) *2D-ish... For a line sink in 2D cross-section: v = S/(2π d)... but our tank is 3D. Use v = k*S/(d + 60), capped. Direction: from p toward opening center. Also add a mild vertical downward component as water level drops? Keep radial-to-sink. Cap at like 400 px/s near opening.

Actually near the opening, speed should be high enough to push objects out. v_max cap ~ 500.

Also flow should be stronger when water level well above opening (early) — Q starts high (head ~200-400 px), Q = 0.6 * subH * sqrt(2*1500*ho): ho~ (y_c - yS): y_c ≈ 500, yS=300 → ho=200 → sqrt(600000)=775 → Q=0.6*80*775 = 37,200 px²/s. dh/dt = -37200/656 = -57 px/s. Good — visible fall.

Flow speed at d=100: v = k*Q/(d+60): with k=0.01 → 37200*0.01/160 = 2.3 px/s — too slow. Need better model. Let's define flow speed = min(vmax, k2 * Q / (subH_or...)). Alternative: use Bernoulli-ish: speed at opening exit = v_exit = sqrt(2 g ho) ≈ 775 px/s (that's the exit velocity, quite fast). Inside the tank, speed decays with distance from opening: v(d) = v_exit * r0/(d + r0), with r0 ~ 250. At d=250 → 388, at d=600 → 224. Hmm too strong far away? Fish need to swim against it initially: fish swim speed ~ 150-260. Far from opening (d=600) flow 224 — fish at top far side swept? "fish initially swim against the current before some are swept" — so early on fish should mostly hold, later weak ones get swept. Flow near top-left corner while breach at bottom-right: d ≈ sqrt(650² + 400²) ≈ 760 → v = 775*250/1010 ≈ 192. Fish swim 170-240. Some fish (weak, 150) get swept when flow high; later as head decreases flow decreases... hmm but then fewer get swept later. Actually as water drops, near-opening flow persists but overall field weakens (v_exit decreases with head). But objects get closer to opening as they're drawn in → higher local speed. That works: fish near opening get swept.

Better: scale flow by local water presence: only apply flow to points below waterline (obviously objects in water).

Let me use: v(d) = v_exit * (r0/(d+r0))^1.2 with r0=180, cap 600. Also direction toward opening center.

Additionally objects drift toward opening; combined with fish swimming, they'll funnel.

Objects carried out: when an object's x crosses the tank wall plane (left: x < 272, right: x > 928) while its y within [oy0, oy1] and it's outside water region → transition to "dry" state (gravity). If it's at wall but y outside opening range, it can't pass — collision with glass panel (excluding hole). Implement collision: if object intersects panel rect and not within hole y-range → push back.

Object sizes: rock r 18-26, toy r ~20 (ball), fish length ~40 (treat radius ~14), plant segments.

Opening must be > object size for it to fit: hole height min ~70, so most fit. Rocks r 26 diameter 52 < 70 ok.

Plants: 3 plants at tank bottom with chain of 6 nodes each, node length 30. Bend: apply flow force to nodes, root node pinned. If flow*nodeCount > threshold sustained → uproot, becomes drifting object (chain still drawn, tumbling). Simplify: plant object has nodes; if not uprooted, simulate nodes with spring to rest shape + flow force + gravity-ish; root fixed. If uprooted, whole plant becomes free body (position + rotation) with buoyancy (plants ~ neutral/slightly buoyant), drift with current, can exit.

Fish: 5 fish with different maxSwim (120..250). Behavior: compute flow at fish; desired velocity = -flowDir * swimSpeed (hold station) + slight wander + avoid walls/opening. If swimSpeed < |flow|, fish velocity = flow*factor (swept) with some struggle. When fish crosses outside through hole → flopping on floor: gravity, bounce, wiggle rotate, eventually rest.

Buoyancy/drag model for a body in water:
- submerged = object fully in water region (inside tank, y > yS) → partial if near surface: sub = clamp((y + r - yS)/(2r),0,1).
- F_gravity = m*g down.
- F_buoy = rho_water * g * sub * displacedArea... use: a_grav = g; buoy accel = -g * (rho_w/rho_obj) * sub. Density ratio: rock 2.5, toy 0.3 (floats), wood, glass 2.5, fish 1.05, plant 1.1.
- Drag: a_drag = -Cd * sub * |v_rel| * v_rel where v_rel = v - flowVelocity (in water). Cd such that terminal velocities reasonable.
- Current: handled via v_rel in drag + explicit advection: add flow*dt to velocity (like being carried) — physically drag toward flow does that. Use relative drag: a = -k*|v_rel|*v_rel*sub/m-ish. Simpler: v += (flow - v) * sub * k * dt  (drag relaxes velocity toward flow speed) plus gravity/buoyancy net accel.

So in water:
  netA = g*(1 - buoyRatio*sub) downward... careful sign: y down positive. gravity accel +g (down). Buoyancy accel = -g * buoyRatio * sub where buoyRatio = rho_w/rho_obj (rock 0.4, toy 3.0 (>1 → floats up), fish 0.95, glass 0.4).
  v.y += netA*dt
  drag: rel = v - flowVec; v -= rel * min(1, kDrag*sub*dt) with kDrag ~ 3 for heavy, more for small.
  also v += (flowVec - v)*... same thing. Use: v = v + (flow - v)*sub*kdt + gravityTerm*dt. Let's just: 
  accelGravity = g*(1 - buoyRatio*sub)
  v.y += accelGravity*dt
  rel = v - flow
  lam = drag * sub (per second)
  v -= rel * (1 - exp(-lam*dt))  → exponential relaxation.
  drag values: rock 2.5/s, toy 4/s, fish 3/s, glass fragment in water 3/s, plant nodes 6/s.

Toy floats: buoyRatio 3 → net accel = g*(1-3*sub). At surface, sub settles so buoyancy ≈ gravity → equilibrium where sub = 1/3. Good.

Rock: buoyRatio 0.4 → always sinks (accel = 0.6g down while submerged), drag limits terminal speed: 0.6*1500 = 900, drag 2.5/s → terminal rel speed = 900/2.5 = 360 px/s. Too fast maybe; increase rock drag to 4 → 225. Fine, tank depth 443 → falls in ~2s. OK.

In air (not in water): v.y += g*dt, air drag small (relax toward 0 with lam 0.2), floor collision: if y + r > floorY: y = floorY - r; vy = -vy*rest (0.3); vx *= 0.8; if |vy| small → rest. Also collide with room walls (x < 30+r etc.) and puddle? Objects on floor in puddle: could float if buoyant (toy). Let's implement puddle interaction: if object on floor and puddle height at its x > object's sub depth and object buoyant → float. Nice touch: toy lands in puddle and floats. Optional; implement simply: compute puddle height H(x); if object bottom below floorY - H... Let's implement: for toy/buoyant objects, if puddleH(x) > 0.25*? Keep simple: if object is in air and its bottom (y+r) >= floorY - puddleH(x) and puddleH > 4 and buoyRatio > 1 → treat as water with sub = clamp((puddleTop - (y-r))/(2r)). Hmm adds complexity. I'll add minimal: puddle drag + buoyancy for objects resting there — maybe skip to save time. Requirement doesn't mention puddle interaction with objects. Skip but add slight drag on floor contact.

Also object should collide with tank bottom glass from inside (inner bottom y=743): if object inside tank (x within inner range) and y + r > 743 → rest on inner floor. And outside tank region, floor y=755... tank bottom panel occupies y 743..755 across x 260..940; outside tank, floor top = 755. So ground height function: groundY(x) = (x in [260,940]) ? 743 if above tank? Actually outside objects fall beside the tank: ground = 755. Objects inside tank: ground = 743. Objects that exit through hole fall outside the wall → ground 755. But an object exiting right wall at x>928 falls outside — its ground is 755. However the tank's bottom panel top is 743 for x in 260..940 — an object at x=935 outside... it's beside the tank wall; ground there is 755 (floor) since tank bottom panel top at 743 spans to 940. Eh, physically object would rest on the tank's bottom glass edge at 743 if above it. Simplify: groundY(x) = (x > 260 && x < 940) ? 743 : 755. Fine for both.

Wait but also an object that exits through the left wall at x<272 must not re-enter the tank: collision with panel excluding hole handles that: if object center outside and it moves toward panel into the panel rect while y not in hole range → block. Since it exits through the hole (y in hole range), it could come back in — fine/realistic.

Also objects inside tank pushed against panel near hole but y outside hole → blocked.

Fish in air → flopping: gravity, bounce, small wiggle; rest on floor.

Glass fragments: spawn ~40 pieces from the panel area around the hole: positions within panel rect region [y0-40, y1+40] (and thin x band), velocity outward horizontal (±(120..420)), vy random -100..150, angular vel ±8. Shape: irregular polygons (random radii 6..22, 4-6 vertices). Collisions: floor, room walls, tank panels (simple AABB), pairwise circle collision with impulse (equal-ish mass). In water (if fragment position inside water region): apply water drag & buoyancy (glass sinks: buoyRatio 0.4). Note fragments spawn outside the panel mostly (they blow outward) — some might fall back into water? Fragments spawn moving outward, so they land outside the tank on the floor. But "experience resistance in water" — if the hole is low, near water, fragments may arc back? Let's have some fragments spawn with low outward velocity and drop into the water through the hole? Hmm, spawn them outward; those whose trajectory passes back... Simpler: spawn half blowing outward, half falling straight down (into the water inside? no, they're at the panel plane...). Actually the panel is at the tank wall; fragments fall along the wall; outside the wall there's no water. To have fragments in water, spawn some fragments with inward velocity (into the tank water) — plausible (shattering blows shards both ways). Do: 60% outward, 40% inward with smaller speed. Inward ones land in water → resistance. Also fragments could be outside and the puddle... ignore puddle.

Wait — but fragments falling inward inside the tank: they'd rest on inner floor (743). Fine.

Also: water flows out through the hole at the panel plane; fragments near the hole get pushed by flow (add flow force if in water region inside tank).

Rendering:
- Crack before breach: draw jagged glowing crack line along the panel at that y range (on top of panel).
- Hole after breach: jagged polygon hole in panel (path with evenodd).
- Jet: particles as small circles/streaks with alpha, drawn from opening outward; gravity curve visible.
- Waterline: surface with waves, plus foam near hole.
- Puddle: filled shape from height bins, dark blue translucent, with surface highlight.
- UI: buttons Breach (disabled after breach), Reset, status text, and readouts (water level, flow rate) for sampling verification.

Also add a "flow meter" HUD showing waterline height and outflow — helps verification.

Let's also handle mouse: pointer events on canvas; scale coordinates from CSS size to canvas coordinates.

Let me now write the code.

Structure:

```html
<!doctype html><html><head><meta charset>...<style>...</style></head>
<body>
<div id="wrap">
  <header>title + buttons + status</header>
  <canvas id="c" width="1200" height="800"></canvas>
  <footer>instructions</footer>
</div>
<script> ... </script>
```

JS outline:

```js
const W=1200,H=800;
const G=1500;
const TANK={x0:260,x1:940,top:150,gt:12}; // glass thickness
inner: ix0=272, ix1=928, innerBottom=743, floorY=755, rimY=150
const ROOM={left:30,right:1170};
const WATER0 = 300; // initial surface y
let state = 'ready' | 'breached';
let crack = null; // {side, y0, y1}
let hole = null; // {side,y0,y1} after breach
let yS = WATER0;
let jet = []; // particles
let puddle = bins;
let frags = [];
let objects = []; // rocks, toy, plants, fish
```

Bins: binCount = 150 across room floor from ROOM.left to ROOM.right.

Puddle update: each frame, for i: flow to neighbors: dh = (h[i]-h[i+1]) * rate; transfer half each with clamping to non-negative. Use symmetric exchange:
```
for i in 0..n-2: 
  const d = (h[i]-h[i+1]);
  const transfer = d * 0.25 * dt * spread? 
```
Hmm diffusion rate: standard: transfer = k*dt*(h[i]-h[i+1]), but must be limited by available: max transfer = 0.5*min(h[i],h[i+1])? Actually water flows from high to low: if h[i] > h[i+1], moving amount up to h[i]*something. Use transfer = min( max(0, h[i]-h[i+1]) * 0.5, h[i] ) * min(1, dt*speed)... Let's do simple relaxation:
```
for pass in 2 directions:
  for i: if h[i] > h[i+1]: t = (h[i]-h[i+1]) * Math.min(1, 4*dt) * 0.5; h[i]-=t; h[i+1]+=t;
```
Also puddle should spread to zero eventually — with pure diffusion it spreads and flattens, conserving volume. Constrained by walls: bins only span room interior → walls naturally constrain. But diffusion never stops (keeps flattening) — physically spreading slows as it flattens, which diffusion mimics-ish. Also add slight threshold: only transfer if difference > 0.3 to avoid infinite thin film. Good.

Also puddle resistance: nothing else.

Jet particles: {x,y,vx,vy,vol,r,life}. Update: vy += G*dt; x+=vx*dt; y+=vy*dt. If y >= floorY - puddleH → deposit vol into bin at x, remove. If x outside room → deposit at nearest bin. Also if particle hits tank exterior... skip. Also particle could pass through tank region? Jet starts at opening moving outward, so it moves away from tank. If crack on left side, jet goes left. Deposit at bin index for x clamped.

Actually also water pouring out runs down the outside of the tank wall a bit — skip.

Volume conservation: total water volume (in px² * depth unit) — track yS change: dV = -Q*dt (area units, since width scaling: dV_column = Q*dt... let's define Q as volumetric per unit depth? Let's define volume in "px² × 1" units where tank cross-section width = innerW: V = yS-to-bottom depth * innerW. Losing volume dV = Q*dt where Q has px²/s. Waterline drop = dV/innerW. Jet particles each carry vol = dV * share. Puddle bins hold volume/1 (bin width w → height = vol/w). Deposit: h[bin] += vol/binW.

Emission: per frame, dV = Q*dt. Number of particles n = clamp(round(dV/8), 1, 12)? each vol = dV/n. Distribute across frames fine.

Now current field function:

```js
function flowAt(x,y){
  if(!hole || yS>=hole.y1) return {x:0,y:0};
  const cx = hole.side==='left'? TANK.x0 : TANK.x1; // approx opening plane
  const cy = (hole.y0+hole.y1)/2;
  const ho = currentHead(); // based on yS
  const vexit = Math.sqrt(2*G*Math.max(ho,2));
  let dx = cx - x, dy = cy - y;
  const d = Math.hypot(dx,dy)+1;
  const sp = Math.min(650, vexit * 200/(d+200));
  return {x: dx/d*sp, y: dy/d*sp};
}
```
But only apply if point is inside tank water (x within inner, y>yS). flowAt used for objects in water only.

Hmm — flow direction toward opening center: for objects far, direction mostly horizontal toward the wall + down toward hole. Good.

head(): ho = max(4, (hole.y0+hole.y1)/2 - yS) while yS < hole.y1.

Q = 0.55 * subH * sqrt(2*G*ho).

Fish behavior:
```
const fl = flowAt(fish);
const speed = Math.hypot(fl.x,fl.y);
// desired: counter current
let thrust = fish.swim; 
if (speed <= thrust*0.95) { // can hold
  desiredV = -normalize(fl)*thrust + wander
  fish.v -> lerp toward desiredV (accel)
} else {
  // swept: mostly carried, with struggling
  fish.v = fl*0.85 + random struggle; plus swim attempts
}
```
Simpler unified: fishAccel: v += (-flNorm * thrust*... ) Let's do: fish adds propulsion vector p = -fl_dir * thrust (if speed>5) else wander dir. Then fish velocity dynamics: v = v + (flow - v)*kdrag*dt (water drag, kdrag=3) + p*dt? Propulsion as acceleration: a_p = thrust * someAccel. Hmm, easier: fish target velocity = -flDir*thrust + flow? No...

Simplest robust: fish movement in water:
- propulsion dir: if current speed > 20: opposite to current; else wander heading (updated occasionally), also steer away from walls and toward tank interior (avoid being pushed to wall? actually being pushed to wall near opening is what we want).
- fish.v relaxes toward (flow*carry + propDir*thrust_eff) where carry = 1 if weak... 

Define: fish.v target = flow*alpha + propDir*thrust where propDir = -flowDir if speed>20. If thrust > speed: target = flow - flowDir*thrust = component-wise: along-flow speed = speed - thrust (negative → moves against current). Plus perpendicular wander small. If thrust < speed: target along-flow = speed - thrust > 0 → carried forward at reduced rate, plus struggle wobble. That's a clean formulation: 

targetAlong = speed - thrust  (signed along flowDir)
targetV = flowDir * targetAlong + perp*wobble.
Then v relaxes toward targetV (rate ~2.5/s) + gravity/buoyancy (fish ~ neutral: buoyRatio 1.0 → neutral, slight sink 0.98).

Plus vertical: fish keep depth: add slight vertical steering to stay off floor/surface. Include in wander.

Then swept fish near opening: speed high (say 500) and thrust 150 → carried at 350 → hits opening → exits. 

Weak fish (thrust 100) get swept early when speed is high; strong (260) resist until speed < 260 near end... but later speed near opening still high (vexit decreasing though: head at end small). Since speed depends on distance, fish far from opening experience less. Fish that drift near opening get carried out. "some are swept through" ✓.

Exit condition: fish x beyond wall plane within hole range → state 'out': then gravity etc. Fish flopping: apply gravity, floor bounce, horizontal wiggle (vx persists with friction), rotate tail wiggle animation.

Plants: implement as object type 'plant' with base x, nodes[] each {x,y,vx,vy}, rest angle slight. Update: node0 pinned to base (unless uprooted). For each node: apply gravity-ish toward rest via spring to previous node (distance constraint) + stiffness toward rest shape + flow force. Simplify with verlet-ish: each node has position; force = flow*flowCoupling + spring to parent (maintain segment length) + spring pulling toward rest position + gravity*0.2. If uprooted: whole plant treated as a free object (single position + angle) with buoyancy ratio 1.2 (slightly buoyant → drifts up) and drag; nodes drawn rotated around position.

Uprooting: if flow speed at base > 260 for cumulative 1.5s → uprooted.

Actually to save complexity: plants bend always; drift only if uprooted. When uprooted convert to freeBody with nodes preserved, rotating slowly with current.

Hmm, time budget. Let's simplify plant: free-standing "kelp" drawn as chain; if uprooted, plant gets position (base) and velocity, whole chain rigid-ish with lag: nodes follow with delay (each node eases toward parent-offset). That's fine.

Rendering plants: draw as tapered path through nodes (quadratic), color green gradient.

Let's just implement plants as arrays of nodes with physics, root pinned; when uprooted, root node becomes free (gravity/buoyancy/drag like other bodies) and other nodes follow via distance constraints. Actually if I keep the same node physics but unpin node0 and give node0 mass dynamics, everything else follows naturally through constraints! Nice — unified: each node: apply forces (gravity*(1-buoy), flow drag), then constraint iterations: keep distance to parent = segLen (move child mostly, parent less; if parent pinned, move child fully). Root node dynamics when unpinned: it's a body.

Constraint solve: for i=1..n: dx = x[i]-x[i-1]; d=len; diff=(d-seg)/d; if parent pinned: x[i] -= dx*diff; else split: x[i]-=dx*diff*0.5, x[i-1]+=dx*diff*0.5.

Plants only need to fit through hole if uprooted — carried out with flow. Node collision with floor: clamp each node y <= groundY - 2.

OK. And "plants bend or drift" ✓.

Now buoyancy for nodes: plant buoyRatio ~ 1.15 (slightly buoyant), drag 5.

Rocks: they sit at bottom; near opening flow can push them: flow speed near opening ~ vexit (up to 700). Rock drag 2.5 → relaxation toward flow speed at rate 2.5/s: it would move. Rocks are heavy: gravity keeps them on floor; friction. Let's give floor friction: when resting, vx *= (1-3*dt), and flow force must overcome... just let drag relaxation move them: v relaxes toward flow velocity at rate proportional to... for a rock on the floor, horizontal flow drag vs gravity/friction. Use: horizontal: v.x relaxes toward flow.x at rate dragH (rock 1.2/s) — with flow 600 → rock accelerates toward 600 px/s quickly? relax rate 1.2/s means time constant 0.83s → rock moves significant. It might slide along floor toward opening and get pushed out — that's the "movable objects that fit the opening can be carried out" requirement. Add friction when in contact with floor: v.x relax toward flow at rate dragH, but friction: if resting, v.x *= exp(-fric*dt) with fric=2.5 → net: rock moves if flow*dragH > friction effect... equilibrium vx ≈ flow * dragH/fric = 600*1.2/2.5 = 288 px/s. It'll slide out. OK — maybe too easy; set rock dragH such that only strong near-flow moves it: friction 4, dragH 1.0 → equilibrium at flow 600: 150 px/s. Fine.

But rocks should "sink" initially — they start on the bottom anyway. Maybe start one rock on a ledge/float? "rocks sink" — could start one rock slightly dropped? Not necessary; they're on the bottom and if lifted by... they never leave the bottom. To demonstrate sinking, maybe one rock starts mid-water? That'd be odd. Alternatively rocks dropped in — keep them resting on bottom; when flow picks them up and carries out, they sink on the floor outside. It's fine. Actually maybe give one rock a gentle demonstration: initial scene has rocks resting on the bottom (sunk). Requirement satisfied by density (they sink if displaced).

To make sinking visible: when a rock is carried out through hole it falls to floor outside — visible sinking. Good enough.

Toy: ball with buoyRatio 3 → floats; sits at surface with sub=1/3. Bobbing. Drifts with current toward opening; if opening top is below surface... The toy is at the water surface; as water falls, the surface falls. The toy can only exit if surface reaches opening — which happens near the end (outflow stops when yS >= hole.y1, so surface never goes below opening bottom). Toy floats at surface; when surface is within opening range, flow there is strong → toy carried out ✓. "Movable objects that fit the opening can be carried out" ✓.

Note the toy at the surface far from opening: flow at surface = flowAt → carries it toward the hole horizontally ✓.

Also the toy must stay inside tank walls: collision with panels (excluding hole).

Collision with panels for all objects inside: if obj crosses ix0 (left inner wall) — actually inner wall x=272..260? Left panel spans x0=260..272. Object inside tank must satisfy x - r >= 272 (left) unless its y overlaps hole range and hole side left → then can pass to x - r < 260 (outside). Implement: for object in water/inside state:

```
function collidePanels(o, r){
  const inHoleY = hole && o.y + r*0 > hole.y0 && o.y < hole.y1; // use center y within hole range (approx, or overlap)
  // left
  if(o.x - r < ix0 && !(hole && hole.side==='left' && inHoleY && o.x - r < ix0)){
     ...
  }
}
```
Cleaner:
```
const canPass = (side) => hole && hole.side===side && (o.y > hole.y0 && o.y < hole.y1);
// left wall
if(o.x - r < ix0){
  if(canPass('left') && o.x < ix0){ /* allow if already past? */ }
  else { o.x = ix0 + r; o.vx = Math.abs(o.vx)*0.3; }
}
```
But passing: while object moves left through the wall region, y must remain in hole range; once o.x + something < 260 (fully outside), mark 'out'. If object's y drifts out of hole range while straddling, clamp.

Simplify: treat wall collision only when object center is within tank interior region (x > ix0) vs outside (x < TANK.x0). Transition zone x in [260,272] = wall thickness; let object pass if y in hole range.

```
if(o.x - r < ix0 && o.x > TANK.x0){ // touching left wall from inside
   if(!(hole && hole.side==='left' && o.y>=hole.y0 && o.y<=hole.y1)){ o.x = ix0 + r; o.vx=max(0,...) }
}
if(o.x + r > ix1 && o.x < TANK.x1){ symmetric }
// Once o.x < TANK.x0 - 2 → outside: then no tank collisions except re-entry blocking:
if(o.x + r > TANK.x0 && o.x < TANK.x0 && !(hole left && y in range)){ o.x = TANK.x0 - r; ... }
```
Also bottom glass: inside tank, y + r > innerBottom → rest at innerBottom - r (if x within inner). Outside tank (x < 260 or > 940), floor 755.

Also the region under tank: outside objects can't go under the tank; groundY as defined earlier covers it (x in [260,940] → 743). If object outside at x=950, ground 755 ✓. If object at x=250 (just left of tank), ground 755 ✓.

Fish exiting: same panel logic; once out, fish state 'out' → flopping.

Also fish inside shouldn't exit except through hole: panel logic covers.

Now crack drag: mousedown on canvas; if state ready: find nearest side: if click x within [x0-30, x0+40] → left; [x1-40, x1+30] → right; else ignore (or also allow anywhere in tank? spec says on one side). Store start y (clamped below waterline+15 and above innerBottom-15), track current y, draw live crack. On mouseup: finalize crack with min length 60.

Waterline: objects only "in water" if y > yS and x inside tank.

Buoyancy sub: sub = clamp((o.y + r - yS)/(2r),0,1) but only if x inside tank inner and o.y < innerBottom.

Let's define inWater(o): inside = o.x > ix0 && o.x < ix1 && o.y < innerBottom && o.y > yS - r*2... compute sub:
```
let sub = 0;
if(o.x > ix0 - o.r && o.x < ix1 + o.r){
  sub = clamp(((o.y + o.r) - yS) / (2*o.r), 0, 1);
  if(o.y - o.r > innerBottom) sub = 0; // below tank? no
}
```
Hmm if object below tank bottom (outside), not in tank water. Only if o.x inside inner range and o.y < innerBottom + something. Objects resting at innerBottom are still in water (sub=1) ✓.

Now fragment collisions pairwise: with 40 frags, 780 pairs, fine.

Also fragments vs objects? Skip.

HUD: display waterline y / depth %, outflow Q, state. Helps verification.

Buttons: Breach (enabled when crack set and ready), Reset.

Reset: recreate scene: yS=WATER0, clear jet, puddle, frags, recreate objects (fresh copies), state ready, keep crack? Spec: "Provide Reset to restore the same initial scene" — restore intact tank; crack should be cleared too (initial scene has no crack). I'll clear crack and let user re-place it.

Let me now write the code. I'll be careful but efficient.

Drawing details:

Room background: wall gradient (#1b2a3a → #0d1520), floor: dark tile with perspective lines? Keep simple: floor with subtle stripes, walls with a faint horizontal line. Room walls: left rectangle x<30 with lighter color, right similar. Also "room walls" constrain puddle — bins span 30..1170 ✓.

Tank: draw glass panels: semi-transparent light blue-white with highlights. Bottom panel. Rim highlight.

Water: fill rect ix0..ix1, yS..innerBottom with gradient (rgba(60,150,220,.55) → deeper). Surface line with waves: path across with sin waves animated. Also caustics? skip. Water only drawn where y > yS.

Also water should not be drawn above yS obviously; and where hole is, water visible through hole ✓ (same fill).

Wait — one issue: water fill covers the full interior rect; the glass panels drawn on top are semi-transparent so interior visible. Good.

Draw order inside: water fill, then objects/plants/fish (clipped to interior? fish drawn over water fine), then surface wave line on top? Objects near surface should be partially over/under — skip clipping, draw water, then objects, then a translucent surface band to fake refraction? Keep simple: water fill, surface wave highlight, then plants/objects/fish.

Fish drawn with body ellipse, tail wiggle, eye.

Jet particles: draw as small elongated streaks (line from prev pos) with color rgba(150,210,255,alpha).

Puddle: path along bins: moveTo(left, floorY) then for each bin lineTo(x, floorY - h) then to right, floorY. Fill with rgba(70,150,210,0.5), plus top highlight line.

Also puddle should be drawn behind tank? Tank sits on floor; puddle around tank base. Draw puddle after floor, before tank ✓. But jet particles in front of tank drawn later ✓.

Foam at hole: small white arcs.

Crack (pre-breach): draw jagged line along panel: generate crack polyline with random offsets seeded — regenerate each frame with slight shimmer? Generate once when crack set (store points).

Hole jagged polygon: generated at breach (store points around hole rect perimeter with random inset/outset ±8).

Panel drawing with hole:
```
function panelPath(side){
  const p = new Path2D();
  if(side==='left'){ p.rect(x0, top, gt, bottomH); }
  ...
}
function drawPanel(side){
  ctx.save();
  const path = outer rect;
  if(hole && hole.side===side){
    const hp = new Path2D(); hp.moveTo(...points); hp.closePath();
    ctx.fill(path,'evenodd') requires single path with both subpaths: build one path: rect(...) then hole points as subpath → ctx.fill(p,'evenodd') → hole excluded ✓ (opposite winding not needed with evenodd).
  }
}
```
Use evenodd ✓.

Also add jagged glass edge highlight: stroke hole path lightly.

Breach animation: fragments spawn, small screen flash/shake maybe. Add brief white flash overlay.

Status messages: "Ready — drag on a side panel to place the crack", "Breach!", etc.

Let's write it.

Also need pointer handling: mousedown/mousemove/mouseup on canvas with coordinate mapping (getBoundingClientRect scaling).

Let me write the JS in one go.

```js
(function(){
const cvs=document.getElementById('c'), ctx=cvs.getContext('2d');
const W=cvs.width, H=cvs.height;
const G=1500;
// geometry
const TANK={x0:260,x1:940,top:150,gt:12};
const IX0=TANK.x0+TANK.gt, IX1=TANK.x1-TANK.gt;
const INNER_BOTTOM=TANK.top? ... 
```
Tank: top rim y=150, bottom glass from 743 to 755. So bottom panel y0=743,y1=755. Panels: left rect (260,150,12,605) down to 755. innerBottom=743.

floorY=755 (room floor top).
Initial water surface yS0=300.

Room: {x0:30, x1:1170}. Floor y 755..800.

Bins: from 30 to 1170, n=150, binW=760/150≈5.07.

Objects init:
- rocks: 4 rocks: positions along bottom, radii 14..24, density 2.6.
- toy: rubber duck/ball at (560, yS0+ something) radius 18, density 0.3. Draw as a beach ball (colorful) — "floating toy". Draw ball with stripes.
- plants: 3 kelp at x=340, 480(?), 860 with node counts 7,6,8; also a short one. Plant base at innerBottom.
- fish: 5 fish with swim speeds [260, 210, 175, 145, 120], colors varied, start positions varied, depth targets.

Fish drawn facing velocity direction.

Let me write code now, carefully, then test in browser.

Physics update function step(dt):

```
function step(dt){
  if(state==='breached'){
    updateWater(dt);
    updateJet(dt);
  }
  updatePuddle(dt);
  for(o of bodies) updateBody(o,dt);   // rocks, toy, out-fish
  updatePlants(dt);
  updateFish(dt);
  updateFrags(dt);
}
```

updateWater:
```
if(!hole) return;
const oy0=hole.y0, oy1=hole.y1;
if(yS >= oy1) { Q=0; return; }
const subH = Math.max(0, oy1 - Math.max(yS,oy0));
const yc = (oy0+oy1)/2;
const ho = Math.max(3, yc - yS);
Q = 0.55 * subH * Math.sqrt(2*G*ho);
let dV = Q*dt;
const newDepth = depth - dV/innerW... 
```
Careful: if dV would take yS below oy1, clamp: maxDrop = (oy1 - yS)*innerW? Actually if yS < oy1 currently, allowed drop... water can drop below oy0; stop at oy1. Clamp yS increase (drop) so yS <= oy1: maxdy = oy1 - yS... but that stops exactly when reaching oy1 ✓. Wait yS increases (falls) toward oy1. maxdy = oy1 - yS (≥0). If computed dy > maxdy → dy = maxdy, and adjust dV accordingly.

Hmm but physically the last bit should asymptotically approach — clamp fine (subH→0 anyway).

dy = dV/innerW; if(yS+dy > oy1){ dy = oy1 - yS; dV = dy*innerW; }
yS += dy;

Emit particles with total vol dV.

Emission direction: outward normal: left → vx negative, right → positive. vexit = sqrt(2*G*ho). Emission y: spread across submerged opening: y = random between max(yS,oy0) and oy1. vx = ±vexit*(0.9+0.2rand), vy = small random (±40). Slight upward? Water at rest inside → vy≈0; but pressure pushes horizontally. Add small ±30.

Particle count: n = clamp(Math.round(dV/6),1,14). vol = dV/n.

Also add foam/spray: some particles smaller with higher speed. Fine.

Jet visual: also draw a continuous "stream" core? Particles at 60fps with n up to 14 → 840/s, life ~1.5s → ~1000 particles. Fine. To make the jet look continuous, size r 3-6.

Actually dV per frame: Q≈37000 * 0.0167 = 620 px² → n = clamp(103,1,14)=14 → vol 44 each. Then particles carry total 620 ✓ (vol=dV/n).

Puddle deposit: h[bin] += vol/binW. binW≈5 → height +8.8 per particle landing... total dV/innerW... consistent.

Puddle heights: total volume ≈ initial water above opening: depth above opening maybe 400*656 = 262,400 px² spread over 1140 px → avg height 230?? That's a deep puddle (230px). Too deep. Hmm — units: 1 "px² of volume" corresponds to 1px depth across 1px width; the tank is 656 wide but also extends into the screen z-axis (say 400px deep in 3D). Puddle spreads over floor area — in 2D cross-section we only model the visible slice. If we pretend the room is 1 unit thick in z (like the tank is 656 wide but puddle spread area = width × room depth). To keep puddle shallow, multiply apparent spread: treat puddle volume as dV * zscale where zscale ~ 0.25 (room extends further in z, or puddle spreads both toward/away viewer). Let's just scale: puddleVol += dV * 0.22. Total ~58k px² over 1140 → avg 50px depth... still deep-ish. Actually total initial water = 443 depth × 656 = 290,608. Not all drains (stops at opening). Say 200px above opening drains → 131k. × 0.22 = 29k → over 1140px avg height 25px. Reasonable puddle (25px avg, deeper where it lands). Also puddle max height: cap bins at e.g. 60px (spread the excess) to avoid towers. With diffusion it flattens anyway.

Hmm, also flow rate into bins is concentrated → temporary spike. Diffusion rate must be fast enough to spread: transfer rate ~ 0.25*(diff) per frame with dt-scaled... We'll tune: transfer coefficient k=3.0/s: t = (h[i]-h[i+1]) * min(1, k*dt) * 0.5 — at dt=1/60, k*dt=0.05 → slow. Over 1 second (60 frames), diffusion length ~ sqrt(k*t)*binW ≈ sqrt(3*1)*5 ≈ 8.7 bins ≈ 44px. Too slow — puddle would pile up. Increase k to 40/s → length over 2s ≈ sqrt(40*2)=8.9 bins... hmm diffusion length in bins = sqrt(2*D*t) with D = k*(binW²)... Let's compute properly: transfer t = diff * a where a = min(1,k*dt)*0.5 per frame. Effective D (bins²/s) ≈ a * 60 * (1/2)? For diffusion eq dh/dt = D h_xx with explicit scheme h_i += a*(h_{i+1}-2h_i+h_{i-1}), D = a*60 (bins²/s) where a per frame. If a = 0.05 → D=3 bins²/s → in 3s spreads sqrt(2*3*3)=4.2 bins = 21px. Too slow.

Need faster: a = 0.5 → D = 30 bins²/s → 3s: sqrt(180)=13 bins=67px. Still slowish. Use more aggressive: a capped at 0.45 (stability requires a<0.5) → D=27. Hmm explicit diffusion stability limit a<0.5 per neighbor sum.

Alternative: model puddle not as diffusion but as a spreading slab: keep track of total volume Vp and a spread radius computed from physics: puddle as shape with height profile h(x) = maxDepth * shape((x-c)/R) with R growing over time: dR/dt = sqrt(g*h_avg) (wave speed) capped by room walls, and volume conserved: given Vp and R (clamped to walls), profile = elliptical: h(x) = H*(1-((x-c)/R)²) for |x-c|<R, with volume = (2/3)*H*R... but when clamped by walls it gets complex with two-sided spread.

Alternative simpler: use bins but with a "shallow water" step: for each bin, compute outflow to lower neighbor: f = c*dt*sqrt(g*h)*dh... Honestly, easier: use bins with transfer t = min(diff, h_available) * min(1, 12*dt)... still limited by stability? For height-field shallow water explicit, stability requires CFL: c*dt/dx ≤ 1 with c=sqrt(g*h)≈sqrt(1500*30)=212 px/s, dx=5px → dt ≤ 0.023 — ok at 1/60? 212/60=3.5px < dx=5 ✓ barely. So shallow-water-ish flux: 

flux between i,i+1 = dx * h_avg * v where v = -C*(h[i+1]-h[i])/dx * ... this is just diffusion again.

Let's do proper shallow water 1D: velocity field u per bin; update:
- slope: a_i = -g*(h[i+1]-h[i])/dx (accel on u at interface)
- interface velocity: u_{i+1/2} += a*dt; then flux = u * min(h_left,h_right) * dt... 

Implement: arrays u (interface velocities, length n-1):
```
for j: const dh = h[j+1]-h[j]; u[j] += -G*dh/DX*dt; // water accelerates downhill: if h[j]>h[j+1], dh<0 → u positive → flows right ✓
// upwind height for flux
const flux = u[j] * dt * (u[j]>0 ? h[j] : h[j+1]);  // px² per ... times binWidth? 
```
h is height (px), bin width DX px, velocity px/s → volume flux per second = u*h*DX. So dV = u*h*DX*dt. Then h[j] -= dV/(DX*dt... ) → dh = -u*h*dt/DX... let's write:
```
dV = u[j]*dt*( (u[j]>0?h[j]:h[j+1]) ) * DX;
h[j] -= dV/(DX*dt) * dt ... 
```
Simplify: dh_j = -u[j]*dt*hUpwind/DX; dh_{j+1} += same.

Also add friction: u *= exp(-fric*dt) with fric ~ 0.8/s; and u clamp; and if h very small, u *= 0.9 to settle. Also prevent negative h: clamp and redistribute.

CFL: u max = sqrt(g*h) ~ 212 plus, dt* u/DX = 3.5/5 = 0.7 < 1 ✓ (marginal but ok since typical h smaller).

That gives realistic spreading wave constrained by walls, ponding at walls. Also water piles where jet lands and spreads as a wave ✓. I like it.

Also add: water depth can't be negative: after updates, if h[i]<0 → clamp to 0 (small volume error, negligible).

Also: puddle should wet the floor area where the jet lands and spread — yes via waves.

Let's set DX = roomWidth/n = 1140/150 = 7.6. Then CFL: dt*u/DX = 212/60/7.6 = 0.46 ✓.

Objects in puddle: skip interaction (maybe apply floor friction only).

Now fragments collision with puddle: skip.

OK, also "water exits in a gravity-curced jet" ✓ particles with gravity.

Waterline falling continuously ✓.

Let me now also handle "Outflow weakens as the water level approaches the opening" ✓ (Q formula) "and stops when water no longer reaches it" ✓ (yS>=oy1 → Q=0, no particles).

Edge: if the crack is placed entirely below... it must be below waterline; hole top oy0 < oy1 ≤ innerBottom. Water drains until yS=oy1 → remaining water depth = innerBottom - oy1 (below opening) remains ✓ realistic.

Now fish update details:

```
function updateFish(dt){
 for(const f of fish){
  if(f.state==='out'){ // flopping on floor
     f.vy += G*dt; f.x+=f.vx*dt; f.y+=f.vy*dt;
     // wiggle
     f.angle = Math.sin(f.t*18)*0.4 + Math.atan2(f.vy,f.vx)?? 
     floor collision: gy=groundY(f.x); if(f.y+r>gy){ f.y=gy-r; f.vy*=-0.35; f.vx*=0.85; if(Math.abs(f.vy)<40) f.vy=0; f.wiggle decays }
     wall collisions room;
     continue;
  }
  // in water
  const sub = subOf(f);
  const fl = inTankWater(f.x,f.y) ? flowAt(f.x,f.y) : {x:0,y:0};
  ...
 }
}
```

Fish exit detection: if f.x < TANK.x0 - 1 || f.x > TANK.x1 + 1 → out. But only possible through hole due to panel collision. Also ensure fish's y within hole when passing (panel collision enforces).

Fish steering:
```
const fs = Math.hypot(fl.x,fl.y);
let prop; // propulsion target vector
if(fs > 15){
  const dirx=-fl.x/fs, diry=-fl.y/fs;
  // perpendicular wander
  const wob = Math.sin(f.t*f.wobSpeed)*f.wobAmp;
  prop = {x: dirx*f.swim - diry*wob, y: diry*f.swim + dirx*wob};
  target = {x: fl.x + prop.x*k?? }
```
Hmm my earlier formulation: target velocity = flow + propulsion where propulsion = counterDir*swim gives along-flow speed = fs - swim ✓ (if swim>fs → negative → moves upstream ✓). Plus perpendicular wobble. So target = fl + prop where prop = counterDir*swim + perp*wob. Wait but then perpendicular component = wob only ✓.

If fs < 15: use wander heading: target = wanderDir*swim*0.6 + (small flow). Fish wanders, avoids walls (steer away if near walls/surface/floor), avoid the hole region? "initially swim against current" — early flow is significant everywhere so fs>15 mostly.

Then v relax toward target at rate 3/s; add gravity: fish density ~1.02 → slight sink: v.y += G*0.03*dt? plus buoyancy sub. Use generic: v.y += G*(1-buoyRatio*sub)*dt with buoyRatio=0.97 → in water: 1-0.97=0.03 → sinks slowly ✓; at surface sub<1 → more sink. Hmm buoyRatio = rho_w/rho_fish = 1/1.02 ≈ 0.98.

Wait earlier definition: buoy accel = -g*rho_w/rho_obj*sub... net = g*(1 - buoyRatio*sub) where buoyRatio = rho_w/rho_obj. Rock rho 2.6 → 0.38 → sinks at 0.62g ✓. Toy rho 0.3 → buoyRatio 3.3 → at sub=0.3 net ≈ 0 ✓ floats.

Fish relaxation toward target v: v += (target - v)*(1-exp(-lam*dt)), lam=3.

Then position += v*dt.

Panel collision and floor collision: fish shouldn't rest on floor normally — add steering up if y > innerBottom - 60.

Also fish avoid the glass walls: if near wall and not in hole → steer inward (collision handles bounce anyway).

Also fish avoid the surface: if y < yS + 30 → steer down (target.y += 120).

Fine.

Body (rock/toy) update:

```
function updateBody(o,dt){
  const sub = subOf(o); // 0 if outside tank water
  if(sub>0){
    o.vy += G*(1 - o.buoy*sub)*dt;
    const fl = flowAt(o.x,o.y);
    // horizontal drag toward flow, vertical relax
    relax(o, fl, o.drag, dt, sub);
  } else {
    o.vy += G*dt;
    o.vx *= Math.exp(-0.15*dt); // air drag
  }
  // puddle float? skip
  integrate; collide panels; collide ground with friction;
}
```
relax: v += (flow - v) * (1-exp(-drag*sub*dt)).

Rock on floor friction: after ground collision, if resting: vx *= exp(-fric*dt). And flow still pulls via relax with rock.drag (1.2) → equilibrium as computed. Also gravity keeps contact. Also when rock is carried by flow up against wall... fine.

Toy: buoy 3.3, drag 4 → bobs at surface, relaxes toward flow quickly ✓.

Hmm but flow at the surface: if the toy is far from opening, flow is weak → toy drifts slowly ✓.

Also need to prevent objects sinking below innerBottom inside tank and panel collisions.

subOf(o): 
```
function subOf(o){
  if(o.x < IX0 - o.r || o.x > IX1 + o.r) return 0;
  if(o.y - o.r > innerBottom) return 0;
  return clamp((o.y + o.r - yS)/(2*o.r),0,1);
}
```
But once yS falls below object top... fine.

Note: after exit, object outside → sub 0 ✓.

Plants: nodes each treated like tiny bodies with sub computed per node; root pinned unless uprooted.

Uproot condition: flow speed at base > 300 sustained 1.2s.

Plants should also "drift" — maybe some plant is not rooted strongly: uproot threshold varies.

Plant rendering: draw leaves: path through nodes with quadratic smoothing, stroke with lineWidth tapering — draw as multiple segments with decreasing width.

Frags:
```
{id, x,y,vx,vy,a,va, pts:[{x,y}...], r (bounding), inWater}
```
Update: gravity; if inside water region (sub>0 by point test): drag relax toward flow (drag 3.5), buoy ratio 0.4 → sinks; else air drag. Collisions: groundY(x) bounce (restitution 0.35, friction 0.7), room walls, pairwise.

Pairwise collision: circles radius = r*0.8: resolve overlap, exchange velocities (equal mass, restitution 0.3).

Fragments vs tank panels: they're spawned at panel; outward ones fly out; inward ones fall into tank. Could skip panel collision for frags except ground — but inward ones should land inside; outward ones outside. Just make sure not to fall through the floor.

Now, initial scene drawing "intact glass tank containing water, fish, rocks, plants, and a floating toy" ✓.

UI: header with title, buttons [Breach] [Reset], and status + readouts (Waterline, Outflow, Elapsed). Also instructions.

Let me write the file now. I'll aim for clean, working code.

Additional detail: crack placement interaction — show hint text near tank when ready: "Drag vertically on a side panel below the waterline".

Crack rendering (pre-breach): jagged polyline along the panel vertical, with small branches, glowing cyan/white, and pulsing.

Crack points: generate from y0..y1 with x jitter around panel center line: for left panel center x = 266; jitter ±4; 8 segments. Store in crack.pts.

Hole generation at breach: polygon around [x0..x1] hole rect: points along the rect perimeter with jitter. For left panel: x from 258..274 (slightly beyond glass to ensure full cut), y hole.y0..hole.y1 with jitter inward/outward ±6, and x jitter.

Hole y-range: cy-based expansion: y0' = crack.y0 - m, y1' = crack.y1 + m where m = max(6, (len)*0.12), clamped to [top+10, 743-6].

Wait — should the hole equal crack extent? "show the panel breaking around the selected opening" — the opening is where the crack was; breaking around it expands it. Good.

Let's decide panel bounds: panel y from 150 to 755 (glass). Water region 300..743. Crack clamped to [yS0+12=312, 743-8=735].

Now write.

Also fragments spawn: count ~ 46. Source: panel rect around hole (y from hole.y0-50 to hole.y1+50, x = panel band). Each frag: random y in that range, x = panelX + rand(-6,18)± ; velocity outward vx = side==='left' ? -(80+rand*380) : +(80+rand*380), vy = -120+rand*260; va = (rand-0.5)*14. Some (35%) inward with vx *0.4 inward.

Flash + shake: add `shake` value, offset ctx by random during breach for ~0.4s.

Now HUD sampling: show "Waterline: 300 → falling", "Outflow: 37,200 px²/s". Update DOM each ~200ms.

Verification steps I'll do in browser: place crack by drag, click Breach, sample readouts over time (inspect DOM text), screenshot; check jet curve visually (screenshot), check reset.

Let me write the code.

One more: "fish initially swim against the current before some are swept through the breach" — need fish to visibly counter-flow early. Their velocity = flow + propulsion where propulsion opposes — net upstream if swim > flow. Visual: fish stay in place while water rushes ✓. Then weak ones swept ✓.

I need flow speed high enough early near fish but fish swim speeds tuned. flowAt: vexit early = sqrt(2*1500*ho). ho with hole center at, say, y=600, yS=300 → ho=300 → vexit=949. At d=400: sp = 949*200/600 = 316. Fish swim 260 max → strong fish barely hold near-mid. Hmm too strong everywhere. Increase decay: sp = vexit * 120/(d+120): d=400 → 949*120/520=219; d=150→530; d=700→140; d=1000→101. Cap 700. Fish swim: [270,225,190,160,130]. Early: far fish (d~700, 140) hold easily ✓; mid fish (d 400, 219): strong hold, weak (130-160) drift ✓. Near opening everything gets swept ✓. As head drops, vexit drops: at ho=50 → vexit=387; near opening d=100 → 387*120/220=211 — still sweeps weak ones. Good.

Also flow vertical component pulls objects down toward hole center ✓ (hole is low).

Toy at surface far: flow ~100-200 → drifts toward hole over time ✓ and when surface reaches opening region, strong flow carries it out ✓. But will the toy reach the opening before water stops? Toy floats at surface; surface falls to oy1; the toy at the surface will be right at the opening when yS→oy1 (opening region) → flow strong → carried out likely ✓. Good.

Now the initial toy position: near middle-top of water, say (620, 330) with r=20.

Also "floating toy" bobbing animation ✓.

Time to write. I'll write the whole file.

Also drag: mouse near left panel x in [230, 300] → left; right [900, 970] → right. Also require state 'ready'.

Let me write helper clamp, rand.

Code for drawing fish:

```
function drawFish(f){
  ctx.save(); ctx.translate(f.x,f.y); 
  const dir = f.vx>=0?1:-1; ctx.scale(dir,1);
  const t = f.t;
  const wig = f.state==='out' ? Math.sin(t*20)*0.5 : Math.sin(t*8)*0.25;
  ctx.rotate(f.state==='out'? wig : wig*0.3);
  // tail
  ...
}
```
Simpler: draw body ellipse, tail triangle with wiggle offset, stripe, eye.

Fish flopping: rotate by sin wiggle.

Plants drawing with nodes.

Let's write everything now. Also make sure Reset rebuilds fish/rocks/etc. via factory functions.

Potential pitfall: `flowAt` used inside plant/fish when outside water → guard (only apply when sub>0 and inside tank).

Write the file.

Let me write out the full HTML.

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Aquarium Breach Simulator</title>
<style>
*{box-sizing:border-box}
body{margin:0;background:#070c12;color:#cfe6f5;font-family:...;}
#wrap{max-width:1240px;margin:0 auto;padding:10px}
header{display:flex;gap:12px;align-items:center;flex-wrap:wrap}
h1{font-size:18px;margin:0 12px 0 0;letter-spacing:1px}
button{...}
#stage{position:relative}
canvas{width:100%;display:block;border-radius:8px;background:#0a121b;touch-action:none;cursor:crosshair}
.hud{display:flex;gap:18px;font-family:monospace;font-size:13px}
#status{...}
footer{font-size:13px;color:#7fa3bd;margin-top:6px}
</style>
```

Canvas display: aspect ratio 1200x800, width:100%, height auto.

Now JS. Let me write it fully in the write call. I'll be thorough.

Fish initial positions: avoid overlapping rocks/plants.

Let me define scene factory:

```js
function makeScene(){
  yS = Y_WATER0; Q=0; jet.length=0; frags.length=0;
  resetPuddle();
  objects = [];
  rocks: [{type:'rock', x:360,y:720,r:20,...},{480,730,14},{700,725,24},{840,735,17}]
  toy: {type:'toy', x:640, y:330, r:20, buoy:3.3, drag:4}
  plants: [{type:'plant', x:320, nodes:7, seg:26, hue:..}, {x:560...}, {x:880,...}]
  fish: [ {x:520,y:420,swim:270,color...}, ... ]
}
```
Plant y base = innerBottom (743).

Also rocks start resting: y = innerBottom - r.

Rock buoy 0.38, drag 2.2.

Fragment rendering: polygon with fill rgba(190,230,255,0.55), stroke white 0.6, plus shine.

Let's finalize numbers: 
- innerW = IX1-IX0 = 928-272=656.
- innerBottom = 743, floorY=755.

Waterline display: depth = innerBottom - yS.

Now the update loop with fixed timestep accumulation: dt = min(0.033, elapsed). Use fixed 1/120 substeps? Simpler: run up to 2 substeps of dt/2 for stability of shallow water. Let's use fixed step 1/60 with accumulator, max 3 steps.

Now write. Also need pointer coordinate mapping:

```js
function evPos(e){ const r=cvs.getBoundingClientRect(); return {x:(e.clientX-r.left)*W/r.width, y:(e.clientY-r.top)*H/r.height}; }
```

Crack drag: on pointerdown (state ready): determine side by proximity; set dragging={side, y0: clamp(y)}; on pointermove update y1; on pointerup finalize (normalize, min length 60, clamp within [yS0+12, innerBottom-8]).

If the user's drag start y > waterline? crack must be below waterline — clamp y0 ≥ yS0+12 always. If drag start above waterline, still clamp (or reject). I'll clamp and show hint.

Live crack drawing while dragging.

Buttons: breach → if crack set & ready: state='breached'; spawn frags; flash; shake.

Status text updates.

Readouts: #waterLevel (px & %), #flow (px²/s), #state.

Let me write the code now for real. I'll produce the complete file in one write, then test.

For the fish "swept through the breach": once fish x passes panel, state 'out'. Then flopping on floor outside. Also should the fish possibly land in puddle? skip.

Fish in 'out' state: gravity, ground collision at groundY(x) (755 outside tank or 743 if under tank region — but fish exiting left at x<260 → ground 755 ✓).

Edge: fish exits through hole at y between oy0/oy1 which might be well above floor → falls ✓.

Now, panel collision function shared:

```js
function collideTank(o, r){
  const inHoleY = hole && o.y > hole.y0 - r*0.3 && o.y < hole.y1 + r*0.3;
  // left
  if(o.x - r < IX0){
    const passLeft = hole && hole.side==='left' && inHoleY;
    if(!passLeft || o.x - r > TANK.x0){  // hmm
```
Let me think again. Left panel occupies x ∈ [260,272]. An object inside tank (x>272) moving left: blocked if not (holeLeft && y in hole range). If allowed, it passes through 272..260 and continues to x<260 (outside). While in the wall band, allow free movement if in hole range. If NOT in hole range and x-r < 272 → clamp x = 272+r.

Once outside (x + r < 260), object is outside; if it moves right back: if x + r > 260 and not (holeLeft && y in range) → clamp x = 260 - r.

So:
```
if(o.x < IX0){ // center left of inner edge → interacting with left panel region or beyond
  if(!(hole && hole.side==='left' && inHoleY)){
    if(o.x > TANK.x0 - r && o.x <= IX0){ o.x = Math.max(o.x, ...); }
  }
}
```
Getting messy. Cleaner approach: define wall barrier plane at panel center x=266 with thickness ignored for collision:

```
const barrierX = TANK.x0 + TANK.gt/2; // 266 for left
const canCross = hole && hole.side===side && o.y>hole.y0 && o.y<hole.y1;
// left side handling (object either inside or outside near left panel):
if(o.x + o.r > TANK.x0 && o.x - o.r < IX0){  // overlapping panel band
   if(!canCross){
     if(o.x > barrierX) o.x = IX0 + o.r;   // push right (inside)
     else o.x = TANK.x0 - o.r;             // push left (outside)
   }
}
```
That works: if can't cross, object gets pushed to whichever side its center is on. If can cross, free.

Similarly right with barrier 934.

Ground: 
```
function groundY(x){ return (x > TANK.x0 && x < TANK.x1) ? INNER_BOTTOM : FLOOR_Y; }
```
Hmm for an object just outside left wall at x=250, ground = 755 ✓; overlapping band x=262 → ground 743 (tank bottom top) — plausible.

But wait: an object outside at x=255 with r=20 spans 235..275; it overlaps the tank bottom panel region... whatever, acceptable.

Bottom collision:
```
const gy = groundY(o.x);
if(o.y + r > gy){ o.y = gy - r; if(o.vy>0){o.vy = -o.vy*0.3; if(Math.abs(o.vy)<30)o.vy=0;} o.vx *= ... friction }
```
Friction: apply only when in contact: vx *= exp(-fric*dt), fric=3 for rocks, 1.5 toy.

Also objects inside water resting on inner floor get pushed by flow via relax toward flow — with friction they may still slide when flow strong ✓.

Now, one concern: rock drag relax toward flow velocity — flow has downward component near hole (toward hole center below?) hole center y ~ maybe 600, rocks at y~720 → dy = 600-720 = -120 → upward component! flow pulls rocks up toward hole center → rocks would lift off floor. Hmm, physically flow converges toward the opening, and yes there'd be an upward component from below. Rocks lifting looks odd but is defensible; but relax with buoy 0.38 gravity 0.62g=930 down vs relax rate 2.2/s toward flow... The relax pulls velocity toward flow velocity: if flow.y = -300 (upward), velocity would trend to -300 but gravity adds +930*dt each frame; equilibrium: v_y where (flow.y - v)*2.2 + 930 = 0 → v = flow.y + 930/2.2 = -300+423 = +123 → still sinking ✓. Good, gravity dominates. Horizontal: v = flow.x + 0 (no gravity horizontally) → wait friction: relax gives v_x → flow.x*... with friction multiplying: equilibrium vx ≈ flow.x * drag/(drag+fric) = flow.x*2.2/5.2 = 0.42*flow.x. Near hole flow.x up to 700 → vx 290 → rock slides out ✓. But only when near hole (flow.x large only close). Rocks at bottom far from hole: flow.x ~140 → vx 59 → slow crawl toward hole. Rocks slowly slide toward the drain over the breach duration ✓ "movable objects that fit the opening can be carried out by the flow" ✓.

Slight issue: relax is computed as v += (flow - v)*(1-exp(-drag*sub*dt)); with sub=1, drag 2.2, dt=1/60 → factor 0.036. Then friction multiplier exp(-3/60)=0.951 multiplies vx. Steady state: vx = (vx + (fx - vx)*0.036)*0.951 → vx(1 - 0.951 + 0.951*0.036) = 0.951*0.036*fx → vx * 0.0332 = 0.0342 fx → vx ≈ 1.03 fx?? That can't be right — friction applied only when in contact; with friction factor 0.951 per frame = 3/s decay, and drag relaxation 2.2/s toward flow, steady vx = flow * (2.2/(3 - 2.2))? Let's redo continuous: dvx/dt = drag*(fx - vx) - fric*vx = drag*fx - (drag+fric)vx → vx_ss = drag*fx/(drag+fric) = 2.2/5.2 fx = 0.42fx. My discrete calc gave 1.03 — because I mis-multiplied: vx_new = (vx + (fx-vx)*a)*(1-b) with a=0.036, b=0.0488(exp(-3/60)=0.9512 → b=0.0488). vx(1-b+ a*b... let's expand: (vx(1-a) + a fx)(1-b) = vx(1-a)(1-b) + a fx (1-b). Steady: vx[1 - (1-a)(1-b)] = a(1-b) fx → vx = a(1-b)fx/(a+b-ab) = 0.036*0.951 fx/(0.036+0.0488-0.0018) = 0.0342/0.0830 = 0.412 fx ✓. Good, matches.

So rock slides at 0.42*flow.x. Near hole, flow.x ~500 → 210 px/s ✓ carried out.

Toy drag 4, no friction (floating) → v ≈ flow ✓.

Now fish check with relaxation lam=3 and target = flow + prop: independent of friction ✓.

Alright — plants: node drag 5, gravity small.

Plant uproot: threshold flow > 280 for 1.0s.

I think we're ready. Also must handle: when uprooted, root node no longer pinned; nodes still collide with ground; plant can exit through hole if nodes fit — plant width ~ small ✓.

Let me write the code.

Additional: display "objects out" counter? Not needed.

Let me also add subtle animation of water surface waves (time-based).

Write now. Also make sure Reset rebuilds.

JS code plan (final):

```js
(()=>{
const cvs=..., ctx=...;
const W=1200,H=800;
const G=1500;
const TANK={x0:260,x1:940,top:150,gt:12};
const IX0=272, IX1=928, IB=743, FLOOR=755;
const ROOM={x0:30,x1:1170};
const Y0=300;
const INNER_W=IX1-IX0;
let yS=Y0, Q=0, t=0, state='ready', crack=null, hole=null, shake=0, flash=0;
let dragging=null;
let rocks=[],toy=null,plants=[],fish=[],frags=[],jet=[];
// puddle
const NB=150, DX=(ROOM.x1-ROOM.x0)/NB, ph=new Float32Array(NB), pu=new Float32Array(NB-1);
```

puddle add: `function addPuddle(x, vol){ const i=clamp(Math.floor((x-ROOM.x0)/DX),0,NB-1); ph[i]+=vol/DX; }`

updatePuddle(dt):
```
for(let j=0;j<NB-1;j++){
  const dh=(ph[j+1]-ph[j]);
  let u = pu[j] - G*dh/DX*dt;   // accel: if left higher (dh<0) → u>0 flows right ✓
  u *= Math.exp(-0.6*dt);       // friction
  // limit
  pu[j]=u;
}
// fluxes
for(let j=0;j<NB-1;j++){
  const u=pu[j];
  let h = u>0? ph[j] : ph[j+1];
  let dV = u*DT*h*DX ... 
}
```
Careful with variable dt: flux dh = u*dt*hUpwind/DX.

```
for j: 
  const u=pu[j];
  if(u===0) continue;
  const h = u>0?ph[j]:ph[j+1];
  if(h<=0){pu[j]=0;continue;}
  let dh = u*h*DT/DX;
  // limit so we don't drain more than available
  const avail = u>0?ph[j]:ph[j+1];
  const frac = Math.min(1, Math.abs(dh)/Math.max(avail,1e-6));
  ...
```
Simplify: dh computed; if u>0: ph[j]-=dh; ph[j+1]+=dh (dh could be negative if u<0 handled by branch). Bound: if(ph[j]<0){ph[j+1]+=ph[j]; ph[j]=0;} to conserve.

Also CFL clamp: |u| ≤ 0.45*DX/DT (=0.45*7.6*60=205). Hmm that limits wave speed to 205 px/s which equals sqrt(1500*28) — fine for typical depths (deeper piles get clamped slightly slower).

Wait but the jet deposits create tall piles (h maybe 60px → c=300). Clamp at 205 → slight slowdown, ok.

Also to avoid jitter at edges: if ph[j] very small (<0.2) and u pulls negative → clamp handled.

Also initial ph all zeros ✓, constrained by walls (no flux out of array bounds) ✓.

Jet particle deposit: find bin, add vol/DX ✓.

Now render puddle path: for i: x=ROOM.x0+i*DX+DX/2, height ph[i]; draw smooth-ish polygon: moveTo(ROOM.x0,FLOOR) then for each i lineTo(x_i, FLOOR-ph[i]) ... to (ROOM.x1, FLOOR), close. Fill gradient.

Highlight: stroke top line with rgba(160,220,255,0.5).

Good. Now writing everything out. Let me be careful about ordering of function definitions (function declarations hoisted ✓).

Main loop:
```
let last=performance.now(), acc=0;
function frame(now){
  let dt=(now-last)/1000; last=now; if(dt>0.1)dt=0.1;
  acc+=dt;
  const step=1/60;
  let n=0;
  while(acc>=step && n<4){ sim(step); acc-=step; n++; }
  draw();
  updateHUD();
  requestAnimationFrame(frame);
}
```

sim(dt): t+=dt; if breached updateWater; updateJet; updatePuddle; plants; fish; rocks; toy; frags; flash/shake decay.

Order: flow uses Q? flowAt uses head/vexit directly — fine.

Let me write draw():

1. clear; shake transform.
2. room bg (wall gradient), side walls, floor tiles.
3. puddle.
4. tank interior: clip rect (IX0..IX1, yS..IB) fill water gradient; surface wave; (clip to tank interior region).
   - Actually also draw water shadow/reflection.
5. plants, rocks, toy, fish (drawn over water; but should be clipped to interior? fish can be outside after exit → drawn later in world layer). I'll draw all objects in a world layer after water — objects outside tank are on the floor in front — fine, no clipping needed since drawing order is: water, then objects. Objects inside water drawn over water ✓; objects outside drawn over floor/puddle — but puddle drawn earlier ✓ ok.

Wait: objects outside the tank drawn after tank glass? If an object is behind the tank (it can't be — all in front plane). Fine.

6. glass panels (with hole if breached) — but objects inside the tank should be behind glass (glass semi-transparent so it just tints them) ✓ draw panels after objects.
7. crack (pre-breach) on panel.
8. jet particles (in front of everything except HUD).
9. frags (in front).
10. flash overlay, hint text.

Hmm: jet exits at the panel plane and travels outward in front of the wall — drawing jet after panels ✓.

Fish drawn before panels → tinted by glass ✓ nice.

Now write code. Also HUD in DOM below canvas.

Let me write the final file.

For the crack polyline: 
```
function makeCrackPts(side,y0,y1){
  const cx = side==='left'? TANK.x0+TANK.gt/2 : TANK.x1-TANK.gt/2;
  const n=Math.max(5,Math.round((y1-y0)/14));
  const pts=[];
  for(let i=0;i<=n;i++){ const y=y0+(y1-y0)*i/n; const jx=(i===0||i===n)?0:(Math.random()*2-1)*4.5; pts.push({x:cx+jx,y}); }
  return pts;
}
```
Draw with glow: shadowBlur.

Branches: 2-3 small diagonal lines from random points.

Hole pts:
```
function makeHolePts(side,y0,y1){
  // rect band x from TANK.x0-6 to IX0+6 (left)
  const xa = side==='left'? TANK.x0-5 : IX1-1... 
```
Let's define for left: xa = TANK.x0-5 (=255), xb = IX0+5 (=277). Points: go down along xb (inner edge) from y0 to y1 with jitter, then up along xa. Actually the hole is a 2D cut through the panel thickness; in elevation view, the hole is the region [y0,y1] across panel width. So polygon: left edge x=255, right edge x=277, top/bottom jagged in y.

pts: start (xa, y0+j), for k along xb: (xb + jitter, y0 + (y1-y0)*k/n) with jitter ±5 in x; then along top? Let's construct: 
- top edge: from (xa,yt) to (xb,yt) with jagged y jitter
- right side down (xb, y) with x jitter
- bottom edge from (xb,yb) to (xa,yb) jagged
- left side up (xa,y) x jitter.
Simple enough: build points around perimeter with per-point jitter.

Since panel is only 12px wide, x jitter ±3.

Draw panel: path = rect(outer) + hole polygon; fill evenodd ✓.

I'll now write it. For glass rendering: fill rgba(180,225,255,0.16), stroke rgba(220,245,255,0.5) 1.5px, plus a vertical highlight line.

Also tank top rim: ellipse/line showing opening (water surface visible from top? no). Draw rim highlight across top.

Also draw back panel? Tank back glass: draw a subtle rect for the whole tank interior region with light tint before water — gives glass tank look. Do: back glass rect (260..940,150..743) fill rgba(150,200,240,0.07), stroke. Then water clipped inside. Then objects. Then side panels + bottom.

Good. Let's write.

Also need hint text drawn on canvas: "Drag ↓ on a side panel to place the crack" with arrow near left panel when state ready && !crack; when crack placed: "Press Breach".

I'll write now — final code.

Regarding updateHUD: update every frame is fine (cheap) but DOM writes each frame ok. Do every 6 frames.

Let me write the file.

Also add keyboard: B = breach, R = reset? Nice but optional; add.

Write it.

One more consideration: fish drawn facing: use f.dir stored from vx.

Let me write the whole thing carefully.

```js
function flowAt(x,y){
  if(!hole) return {x:0,y:0};
  if(yS >= hole.y1) return {x:0,y:0};
  const cy=(hole.y0+hole.y1)/2;
  const cx = hole.side==='left'? TANK.x0 : TANK.x1;
  const ho = Math.max(3, cy - yS);
  const vexit = Math.sqrt(2*G*ho);
  let dx=cx-x, dy=cy-y; const d=Math.hypot(dx,dy);
  const sp = Math.min(700, vexit*120/(d+120));
  return {x:dx/d*sp, y:dy/d*sp};
}
```
Note if d=0 → guard d<1 → d=1.

Also flow only applies when point in water: callers check sub>0.

Head used by flowAt: ho = cy - yS, but early the surface may be above hole top... cy-yS ✓ positive. When yS approaches cy, ho small → weak. But water still flows until oy1. If hole is tall (y0=450,y1=700, cy=575) and yS=575, ho=3 → vexit tiny, but subH still = oy1-yS =125 → Q = 0.55*125*sqrt(2*1500*3)=0.55*125*95=6530 ✓ still flowing, weak jet ✓. Use same ho in both ✓ consistent.

Edge: if user places crack high (y0=312,y1=380) → hole top 306 → huge initial head... ho = cy - yS = 346-300=46 → Q=0.55*74*sqrt(138000)=0.55*74*371=15,077 → dy/dt = 15077/656=23 px/s ✓ moderate. Jet vexit=371 ✓.

If crack placed low (y0=650,y1=735): cy=692, ho=392, vexit=1084, subH=93 → Q=0.55*93*1084=55,300 → dh/dt=84 px/s fast drain ✓; jet strong ✓.

Good.

Now finalize fish swim speeds vs flow. Early with low hole: flow near fish maybe 300-500 close to hole; fish far ~150. swim [275,230,195,165,135].

Wobble amplitude ~ 40.

Also fish should not be able to leave through top/bottom: panel collision + surface clamp (keep y > yS + r, y < IB - r).

Implement: after integration, clamp fish y to [yS + r*0.7, IB - r] if inside tank x-range.

Hmm if fish is at the surface and water falls, fish follows ✓ (yS+r).

Now frags in water: they can also block... skip.

Let's write the file now. I'll write and then test.

Also: `updateJet` — particles: prev position for streak drawing. Store px,py.

Let me finally write out everything.

For status text: elements: #btnBreach, #btnReset, #stState, #stWater, #stFlow, #stTime, #stHint.

Write file.

I'll now compose the complete HTML content.

Also: on breach, also spawn a few "splash" droplets from the water surface inside near the hole? skip.

Draw jet particles also while inside the tank? They spawn at the opening plane — half inside half outside. Fine.

Also, the water surface inside should tilt toward the hole? skip (spec doesn't require).

Let me write.

Plant nodes: node i from base (i=0) to tip (i=n). Rest positions: base.x + sin(i*0.5+phase)*small, base.y - i*seg.

Plant update:
```
function updatePlant(p,dt){
  const rooted = !p.uprooted;
  for(let i=0;i<p.n;i++){
    const nd=p.nodes[i];
    if(rooted && i===0){ nd.x=p.bx; nd.y=IB; nd.vx=0; nd.vy=0; continue; }
    const sub = nodeSub(nd);
    if(sub>0){
      nd.vy += G*(1-p.buoy*sub)*dt;
      const fl=flowAt(nd.x,nd.y);
      const k=(1-Math.exp(-p.drag*sub*dt));
      nd.vx += (fl.x-nd.vx)*k; nd.vy += (fl.y-nd.vy)*k;
      // flow coupling strong for leafy tips: extra push
      nd.vx += (fl.x)*0.6*dt*sub*p.flex;  // hmm optional
    } else {
      nd.vy += G*dt;
      nd.vx *= Math.exp(-0.5*dt);
    }
    nd.x+=nd.vx*dt; nd.y+=nd.vy*dt;
    const gy=groundY(nd.x);
    if(nd.y>gy-3){nd.y=gy-3; nd.vy*=-0.2; nd.vx*=0.7;}
  }
  // spring to rest shape (bending resistance)
  for i>0: pull toward parent + rest angle:
     nd.x += (target.x-nd.x)*p.stiff*dt...
  // constraints
  for(let i=1;i<p.n;i++){ ... distance constraint ... }
}
```
Rest-shape spring: pull each node toward its rest offset relative to parent direction? Simpler: pull node toward position above base: restX = p.bx + Math.sin(t*1.1 + i*0.4 + p.phase)*4 (animated sway), restY = IB - i*seg. Force = (rest - nd)*stiff. When uprooted, skip rest spring (or use p.cx anchored moving with plant centroid).

Bend: with rest spring stiff ~ 6/s and flow drag pulling, nodes bend downstream ✓. stiff must be strong enough to resist flow but flow near opening is huge → bends a lot / uproots ✓.

Uproot: if flowAt(base).speed > 280 → p.rootTimer += dt, else decay; if > 1.0 → uprooted = true; also on uproot give nodes initial velocity = flow*0.5.

When uprooted, skip rest spring (nodes fully free, only distance constraints) → plant tumbles/drifts ✓.

nodeSub: same as subOf but radius ~5: if x in [IX0,IX1] and y < IB → clamp((nd.y - yS)/10 ...). Use r=5: clamp((nd.y + 5 - yS)/10,0,1) and y<IB.

Plants drawn: stroke segments with lineWidth from 10 (base) to 3 (tip), color gradient; add leaf blobs.

OK writing now for real. I'll produce the file.

For rocks drawing: irregular polygon (store pts), gray gradient, highlight.

Toy: beach ball: circle with colored wedges rotating slightly based on vx? Draw 6 wedges.

Fish colors: orange, blue, yellow, teal, pink.

Let me write.

HUD readouts format: 
- Water: `300 px (74%)` where % = depth/initialDepth.
- Outflow: `55,300 px²/s` and `JET 1084 px/s`.
- objectsOut count.

I'll include a small "sample log"? Not needed; I'll read HUD via inspect.

Let's write the code.

Final check on required verification: "sample the early outflow. Check that the jet curves downward" — visually via screenshot (trajectory arcs down), and jet vy increasing — I can expose `jetStats` in HUD: e.g., avg vy of particles increasing. Maybe show `Jet: v=(vx, vy)` of newest particle? Simpler: HUD shows "Jet exit speed" and "Jet drop (avg particle Δy after 0.5s)". Too much. I'll show flow rate + waterline + a "jet arc" readout: mean vy of airborne particles — increases over time as they fall ✓ demonstrates gravity curve. I'll include: `Jet: vx=-1084, vy=+320` (avg of live particles) — sampled over time shows vy going from ~0 → large positive ✓ nice verification metric.

Now write.

I'll build the simulation in a single self-contained `index.html`.