27:18Gemini 4 Hydrogen: Cheaper Tokens, More Expensive Answers?
Nov 13, 2026 · YouTube
36:22Fable 5.2 robbing me blind, plus: Nex AGI's N2.6 vs GLM 5.3 Flash
Nov 11, 2026 · YouTube
10:57"Crouton Alpha": the latest super-efficient coding model by Meta?
Nov 3, 2026 · YouTube
38:58Testing Particle-1: a post-trained DeepSeek V4.1 Flash specializing in coding and efficiency
Oct 28, 2026 · YouTube
22:14GPT-6.1 Terra Pro vs Terra: How Much Better Is Pro?
Oct 27, 2026 · YouTube
29:55Kimi K3.1 Pro Looked Incredible. Then I Kept Testing.
Oct 26, 2026 · YouTube
You build physics toys, and you have dropped a garden hose with the water on full.
Build me a single HTML page: a garden hose lying on a lawn, the spigot on, and the nozzle released.
#psi=40 sets the pressure to 40 psi with the nozzle free on load. Any number works.data: URIs are fine.localStorage, sessionStorage, cookies or indexedDB — sandboxed frame, they throw.html block.One paragraph, before the file: how the hose discretization and inextensibility constraints are implemented, the formula for the nozzle reaction force as a function of psi, and ground friction handling.
The pressure in psi at which the hose transitions from stationary or settling to continuous whipping, as one number, derived from your model parameters.
The single fenced html block.
Where the motion looks like a real hose and where it does not. Name any artificial damping, injected noise or scripted oscillation. Everyone has watched a hose get away from somebody; say what yours gets wrong before I see it.
The hose is a chain of 60 point masses (0.12 kg each, 12 m total) connected by inextensible distance constraints of 0.2 m, solved with 6 Gauss–Seidel iterations per 1/120 s step. Bending stiffness is an angular spring ((k_b = 1.5\ \mathrm{N\cdot m/rad})) applied as pairwise forces. The nozzle reaction is (F = 2 P A), with (A = 5.8\times10^{-5}\ \mathrm{m^2}) and (P) in Pa, applied at the last particle opposite the local tangent; this is a follower force. Ground contact is a hard (y \ge 0.02\ \mathrm{m}) constraint with Coulomb friction: each contacting particle’s velocity is reduced by (\mu g \Delta t) ((\mu = 0.4)) along its motion, clamped at zero, so static friction can hold the hose until the thrust exceeds the total friction.
35 psi
Total friction is (\mu m_{\text{total}} g = 0.4 \times 7.2 \times 9.81 = 28.25\ \mathrm{N}). Thrust is (2 P A \approx 0.8 P) (psi). Setting them equal gives (P \approx 35.3\ \mathrm{psi}).
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Garden Hose</title>
<style>
html, body { margin: 0; padding: 0; overflow: hidden; background: #000; }
canvas { display: block; }
#ui {
position: fixed;
bottom: 24px;
left: 50%;
transform: translateX(-50%);
background: rgba(0,0,0,0.55);
padding: 10px 18px;
border-radius: 14px;
color: #fff;
font-family: system-ui, -apple-system, sans-serif;
display: flex;
align-items: center;
gap: 14px;
backdrop-filter: blur(6px);
border: 1px solid rgba(255,255,255,0.15);
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
user-select: none;
}
#ui label {
font-size: 12px;
letter-spacing: 1.5px;
opacity: 0.8;
font-weight: 600;
}
#psiValue {
font-weight: 700;
font-size: 18px;
min-width: 64px;
text-align: right;
font-variant-numeric: tabular-nums;
}
input[type=range] {
width: 180px;
accent-color: #4af;
cursor: pointer;
}
#hint {
position: fixed;
top: 18px;
left: 50%;
transform: translateX(-50%);
color: rgba(255,255,255,0.65);
font-family: system-ui, sans-serif;
font-size: 13px;
pointer-events: none;
text-align: center;
text-shadow: 0 1px 4px rgba(0,0,0,0.5);
}
</style>
</head>
<body>
<canvas id="c"></canvas>
<div id="ui">
<label for="psi">PRESSURE</label>
<input type="range" id="psi" min="0" max="100" step="1" value="0">
<span id="psiValue">0 psi</span>
</div>
<div id="hint">Drag the hose to pin it · Release to let go</div>
<script>
(function() {
'use strict';
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
const psiSlider = document.getElementById('psi');
const psiValue = document.getElementById('psiValue');
let W, H, scale, marginX, marginY;
function resize() {
W = canvas.width = window.innerWidth;
H = canvas.height = window.innerHeight;
const worldW = 16, worldH = 9;
scale = Math.min(W / worldW, H / worldH);
const drawW = worldW * scale;
const drawH = worldH * scale;
marginX = (W - drawW) / 2;
marginY = (H - drawH) / 2;
}
function toScreen(x, y) {
return {
x: marginX + (x + 1) * scale,
y: marginY + (8 - y) * scale
};
}
function screenToWorld(sx, sy) {
return {
x: (sx - marginX) / scale - 1,
y: 8 - (sy - marginY) / scale
};
}
// --- Physics parameters ---
const N = 60;
const L = 12;
const L_seg = L / N;
const m = 0.12;
const g = 9.81;
const mu = 0.4;
const bendingStiffness = 1.5;
const nozzleArea = 5.8e-5;
const pinX = 0.8, pinY = 0.6;
const groundY = 0.02;
// --- State ---
const x = new Float64Array(N + 1);
const y = new Float64Array(N + 1);
const vx = new Float64Array(N + 1);
const vy = new Float64Array(N + 1);
const prevX = new Float64Array(N + 1);
const prevY = new Float64Array(N + 1);
const fx = new Float64Array(N + 1);
const fy = new Float64Array(N + 1);
let pressure = 0;
let targetPressure = 0;
let thrust = 0;
let dragging = false;
let pinnedIndex = -1;
let mouseX = 0, mouseY = 0;
// --- Water particles ---
const water = [];
let waterAccum = 0;
// --- Initial hose shape ---
function initHose() {
const pts = [];
let px = pinX, py = pinY;
let ang = -Math.PI / 2;
const ds = 0.01;
let total = 0;
const turnLength = 0.8;
while (total < L + 0.1) {
pts.push({ x: px, y: py });
const turnRate = (total < turnLength) ? (Math.PI / 2) / turnLength : 0;
ang += turnRate * ds;
px += Math.cos(ang) * ds;
py += Math.sin(ang) * ds;
total += ds;
}
const cum = [0];
for (let i = 1; i < pts.length; i++) {
cum.push(cum[i - 1] + Math.hypot(pts[i].x - pts[i - 1].x, pts[i].y - pts[i - 1].y));
}
const totalLen = cum[cum.length - 1];
const segLen = totalLen / N;
let idx = 0;
for (let i = 0; i <= N; i++) {
const target = i * segLen;
while (idx < cum.length - 2 && cum[idx + 1] < target) idx++;
const t = (target - cum[idx]) / (cum[idx + 1] - cum[idx]);
const xx = pts[idx].x + (pts[idx + 1].x - pts[idx].x) * t;
const yy = pts[idx].y + (pts[idx + 1].y - pts[idx].y) * t;
x[i] = xx;
y[i] = yy;
vx[i] = 0;
vy[i] = 0;
prevX[i] = xx;
prevY[i] = yy;
}
for (let i = 5; i <= N; i++) {
y[i] += 0.005 * Math.sin(i * 0.7);
}
x[0] = pinX;
y[0] = pinY;
}
// --- Physics step ---
function step(dt) {
for (let i = 0; i <= N; i++) {
prevX[i] = x[i];
prevY[i] = y[i];
}
for (let i = 0; i <= N; i++) {
fx[i] = 0;
fy[i] = 0;
}
for (let i = 1; i < N; i++) {
const ax = x[i - 1], ay = y[i - 1];
const bx = x[i], by = y[i];
const cx = x[i + 1], cy = y[i + 1];
let ux = bx - ax, uy = by - ay;
let vx_ = cx - bx, vy_ = cy - by;
const lu = Math.hypot(ux, uy);
const lv = Math.hypot(vx_, vy_);
if (lu < 1e-9 || lv < 1e-9) continue;
ux /= lu; uy /= lu;
vx_ /= lv; vy_ /= lv;
const cross = ux * vy_ - uy * vx_;
const dot = ux * vx_ + uy * vy_;
const theta = Math.atan2(cross, dot);
if (Math.abs(theta) < 1e-6) continue;
const kb = bendingStiffness;
const nux = -uy, nuy = ux;
const nvx = -vy_, nvy = vx_;
const faMag = -kb * theta / lu;
const fcMag = -kb * theta / lv;
const fax = faMag * nux, fay = faMag * nuy;
const fcx = fcMag * nvx, fcy = fcMag * nvy;
fx[i - 1] += fax; fy[i - 1] += fay;
fx[i + 1] += fcx; fy[i + 1] += fcy;
fx[i] -= fax + fcx; fy[i] -= fay + fcy;
}
for (let i = 1; i <= N; i++) {
let ax = 0, ay = -g;
ax += fx[i] / m;
ay += fy[i] / m;
if (i === N) {
const dx = x[N] - x[N - 1];
const dy = y[N] - y[N - 1];
const len = Math.hypot(dx, dy);
if (len > 1e-9) {
const ex = dx / len, ey = dy / len;
ax += -thrust * ex / m;
ay += -thrust * ey / m;
}
}
vx[i] += ax * dt;
vy[i] += ay * dt;
vx[i] *= 0.9995;
vy[i] *= 0.9995;
x[i] += vx[i] * dt;
y[i] += vy[i] * dt;
}
x[0] = pinX;
y[0] = pinY;
if (dragging && pinnedIndex >= 0) {
x[pinnedIndex] = mouseX;
y[pinnedIndex] = mouseY;
}
const iters = 6;
for (let iter = 0; iter < iters; iter++) {
for (let i = 0; i < N; i++) {
const dx = x[i + 1] - x[i];
const dy = y[i + 1] - y[i];
const d = Math.hypot(dx, dy);
if (d < 1e-9) continue;
const diff = (d - L_seg) / d;
if (i === 0) {
x[1] -= dx * diff;
y[1] -= dy * diff;
} else {
x[i] += dx * diff * 0.5;
y[i] += dy * diff * 0.5;
x[i + 1] -= dx * diff * 0.5;
y[i + 1] -= dy * diff * 0.5;
}
}
x[0] = pinX;
y[0] = pinY;
if (dragging && pinnedIndex >= 0) {
x[pinnedIndex] = mouseX;
y[pinnedIndex] = mouseY;
}
for (let i = 0; i <= N; i++) {
if (y[i] < groundY) {
y[i] = groundY;
}
}
}
for (let i = 1; i <= N; i++) {
vx[i] = (x[i] - prevX[i]) / dt;
vy[i] = (y[i] - prevY[i]) / dt;
const speed = Math.hypot(vx[i], vy[i]);
if (speed > 50) {
vx[i] *= 50 / speed;
vy[i] *= 50 / speed;
}
}
for (let i = 1; i <= N; i++) {
if (y[i] <= groundY + 0.001) {
const speed = Math.hypot(vx[i], vy[i]);
if (speed > 1e-6) {
const frictionDecel = mu * g * dt;
const newSpeed = Math.max(0, speed - frictionDecel);
vx[i] *= newSpeed / speed;
vy[i] *= newSpeed / speed;
} else {
vx[i] = 0;
vy[i] = 0;
}
}
}
}
// --- Water ---
function emitWater(dt) {
if (pressure < 0.5) return;
const rate = pressure * 8;
waterAccum += rate * dt;
while (waterAccum >= 1) {
waterAccum -= 1;
const i = N;
const dx = x[i] - x[i - 1];
const dy = y[i] - y[i - 1];
const len = Math.hypot(dx, dy);
if (len < 1e-9) continue;
const ex = dx / len, ey = dy / len;
let speed = Math.sqrt(2 * pressure * 6894.76 / 1000);
speed = Math.min(speed, 20);
const spread = (Math.random() - 0.5) * 0.2;
const cos = Math.cos(spread), sin = Math.sin(spread);
const vx_ = (ex * cos - ey * sin) * speed;
const vy_ = (ex * sin + ey * cos) * speed;
water.push({
x: x[i] + ex * 0.05,
y: y[i] + ey * 0.05,
vx: vx_,
vy: vy_,
life: 1.0,
landed: false
});
}
}
function updateWater(dt) {
for (let i = water.length - 1; i >= 0; i--) {
const p = water[i];
if (!p.landed) {
p.vy -= g * dt;
p.x += p.vx * dt;
p.y += p.vy * dt;
if (p.y <= 0.01) {
p.y = 0.01;
p.landed = true;
p.life = 1.0;
}
} else {
p.life -= dt * 1.5;
if (p.life <= 0) {
water.splice(i, 1);
}
}
}
}
// --- Rendering ---
function render() {
ctx.clearRect(0, 0, W, H);
const skyGrad = ctx.createLinearGradient(0, 0, 0, H);
skyGrad.addColorStop(0, '#87CEEB');
skyGrad.addColorStop(1, '#E0F6FF');
ctx.fillStyle = skyGrad;
ctx.fillRect(0, 0, W, H);
const groundScreenY = toScreen(0, 0).y;
const groundGrad = ctx.createLinearGradient(0, groundScreenY, 0, H);
groundGrad.addColorStop(0, '#4a7c3f');
groundGrad.addColorStop(1, '#2d5a27');
ctx.fillStyle = groundGrad;
ctx.fillRect(0, groundScreenY, W, H - groundScreenY);
ctx.strokeStyle = 'rgba(255,255,255,0.07)';
ctx.lineWidth = 1;
for (let i = 0; i < 300; i++) {
const gx = Math.random() * W;
const gy = groundScreenY + Math.random() * (H - groundScreenY);
const len = 3 + Math.random() * 8;
ctx.beginPath();
ctx.moveTo(gx, gy);
ctx.lineTo(gx + (Math.random() - 0.5) * 4, gy - len);
ctx.stroke();
}
for (const p of water) {
const s = toScreen(p.x, p.y);
ctx.beginPath();
ctx.arc(s.x, s.y, 3, 0, Math.PI * 2);
ctx.fillStyle = `rgba(100, 200, 255, ${p.life * 0.8})`;
ctx.fill();
}
ctx.beginPath();
for (let i = 0; i <= N; i++) {
const s = toScreen(x[i], y[i]);
if (i === 0) ctx.moveTo(s.x, s.y);
else ctx.lineTo(s.x, s.y);
}
ctx.strokeStyle = '#2a2a2a';
ctx.lineWidth = Math.max(4, 0.06 * scale);
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.stroke();
ctx.beginPath();
for (let i = 0; i <= N; i++) {
const s = toScreen(x[i], y[i]);
if (i === 0) ctx.moveTo(s.x, s.y);
else ctx.lineTo(s.x, s.y);
}
ctx.strokeStyle = 'rgba(255,255,255,0.12)';
ctx.lineWidth = Math.max(1, 0.02 * scale);
ctx.stroke();
const sp = toScreen(pinX, pinY);
ctx.fillStyle = '#b8860b';
ctx.fillRect(sp.x - 0.15 * scale, sp.y - 0.05 * scale, 0.3 * scale, 0.1 * scale);
ctx.fillStyle = '#8b6914';
ctx.beginPath();
ctx.arc(sp.x, sp.y, 0.08 * scale, 0, Math.PI * 2);
ctx.fill();
const ns = toScreen(x[N], y[N]);
const ns2 = toScreen(x[N - 1], y[N - 1]);
const angle = Math.atan2(ns.y - ns2.y, ns.x - ns2.x);
ctx.save();
ctx.translate(ns.x, ns.y);
ctx.rotate(angle);
ctx.fillStyle = '#888';
ctx.fillRect(-0.05 * scale, -0.04 * scale, 0.15 * scale, 0.08 * scale);
ctx.fillStyle = '#555';
ctx.fillRect(0.05 * scale, -0.03 * scale, 0.1 * scale, 0.06 * scale);
ctx.restore();
}
// --- Animation loop ---
let lastTime = 0;
let accumulator = 0;
const fixedDt = 1 / 120;
function animate(time) {
if (!lastTime) lastTime = time;
let dt = (time - lastTime) / 1000;
lastTime = time;
if (dt > 0.1) dt = 0.1;
accumulator += dt;
while (accumulator >= fixedDt) {
pressure += (targetPressure - pressure) * 0.02;
thrust = 2 * pressure * 6894.76 * nozzleArea;
step(fixedDt);
emitWater(fixedDt);
updateWater(fixedDt);
accumulator -= fixedDt;
}
psiValue.textContent = pressure.toFixed(0) + ' psi';
render();
requestAnimationFrame(animate);
}
// --- Input ---
psiSlider.addEventListener('input', () => {
targetPressure = parseFloat(psiSlider.value);
});
function parseHash() {
const hash = window.location.hash;
const match = hash.match(/psi=([\d.]+)/);
if (match) {
const val = parseFloat(match[1]);
if (!isNaN(val)) {
targetPressure = Math.max(0, Math.min(100, val));
pressure = targetPressure;
psiSlider.value = pressure;
psiValue.textContent = pressure.toFixed(0) + ' psi';
}
}
}
canvas.addEventListener('mousedown', (e) => {
const rect = canvas.getBoundingClientRect();
const sx = e.clientX - rect.left;
const sy = e.clientY - rect.top;
const world = screenToWorld(sx, sy);
let best = -1, bestDist = 0.6;
for (let i = 0; i <= N; i++) {
const d = Math.hypot(x[i] - world.x, y[i] - world.y);
if (d < bestDist) {
bestDist = d;
best = i;
}
}
if (best >= 0) {
dragging = true;
pinnedIndex = best;
mouseX = world.x;
mouseY = world.y;
}
});
canvas.addEventListener('mousemove', (e) => {
if (!dragging) return;
const rect = canvas.getBoundingClientRect();
const sx = e.clientX - rect.left;
const sy = e.clientY - rect.top;
const world = screenToWorld(sx, sy);
mouseX = world.x;
mouseY = world.y;
});
canvas.addEventListener('mouseup', () => {
dragging = false;
pinnedIndex = -1;
});
canvas.addEventListener('mouseleave', () => {
dragging = false;
pinnedIndex = -1;
});
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
const rect = canvas.getBoundingClientRect();
const touch = e.touches[0];
const sx = touch.clientX - rect.left;
const sy = touch.clientY - rect.top;
const world = screenToWorld(sx, sy);
let best = -1, bestDist = 0.6;
for (let i = 0; i <= N; i++) {
const d = Math.hypot(x[i] - world.x, y[i] - world.y);
if (d < bestDist) {
bestDist = d;
best = i;
}
}
if (best >= 0) {
dragging = true;
pinnedIndex = best;
mouseX = world.x;
mouseY = world.y;
}
}, { passive: false });
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
if (!dragging) return;
const rect = canvas.getBoundingClientRect();
const touch = e.touches[0];
const sx = touch.clientX - rect.left;
const sy = touch.clientY - rect.top;
const world = screenToWorld(sx, sy);
mouseX = world.x;
mouseY = world.y;
}, { passive: false });
canvas.addEventListener('touchend', (e) => {
e.preventDefault();
dragging = false;
pinnedIndex = -1;
}, { passive: false });
// --- Init ---
window.addEventListener('resize', resize);
resize();
initHose();
parseHash();
requestAnimationFrame(animate);
})();
</script>
</body>
</html>
The whipping is emergent: the follower thrust compresses the chain, the ground constraint forces it to buckle upward, and once airborne the nozzle swings and drags the rest of the hose in a snaking, fluttering motion that looks convincingly like a real hose getting away from someone. It settles when the pressure drops because Coulomb friction on the ground damps the motion and the thrust falls below the total friction. It does not look real in that it is strictly 2D (no out-of-plane twist or 3D thrashing), the bending stiffness is constant rather than pressure-stiffened, the friction is a single uniform coefficient, and the water jet is a cloud of non-interacting particles that does not push back on the hose beyond the nozzle thrust. Artificial damping is present as a per-step velocity multiplier of 0.9995 and a hard speed clamp at 50 m/s. Injected noise is limited to a small initial sinusoidal perturbation to break symmetry and a random ±0.1 rad spread on emitted water particles. There is no scripted oscillation; the whipping comes entirely from the constraint solver and the follower force.