jarvising · entry 004 jarvising.com

be·low the lay·er

/bɪˈləʊ ðə ˈleɪ.ə/ adverbial phrase; of a submarine, under the thermocline, where the ping loses it Entry 004

A British submarine of the Second World War, hunted by an escort's sonar: take the boat down under the layer of cold water and watch the ping lose it, then open the boat up and look inside.

A T-class submarine seen from below at night, lit by faint shafts of light: the escort's ping is passing along its hull in a pale band, a red echo is spreading from the hull, and the ping's edge sweeps across the seabed and a wreck far beneath. Dive → WebGL · sound optional
keyboard works

An Adapt Progress Evolve Limited project by Jack Stovell (registered in England and Wales, company no. 10047328, VAT GB362654684) · Source: github.com/StovBuilds/jarvising · src/submarine/

What it does

It is night, well under the sea. An early British T-class submarine hangs in the dark, and an enemy escort circles on the surface above, sending pulses of sound down to find it. Every few seconds the ping goes out as a pale shell of light, and where it touches something (the hull, a mine's cable, a wreck on the bottom, the seabed) the contact lights up and an echo starts back.

You have the depth. Drag the slider or press 1, 2 or 3: periscope depth, above the layer, below it. Above the layer the echo comes back strong and the escort has a fix, with the real round-trip time of the echo beside it. Take the boat down through the bright sheet of the thermocline at 180 feet and the ping bends away: the echo fades to a whisper, the status reads "Hidden below the layer", and the escort's trace goes quiet. Past 300 feet, the early boats' rated depth, the depth readout turns red. The pressure readout counts pounds per square inch above the surface, from the depth, the way the sea loads the hull.

Press X and the boat opens up. The casing and deck lift away, the keel and bulbous bow drop, the saddle tanks swing out, and the pressure hull splits into its six compartments, lit warm from inside, with labels tracked to the model: the tube space, torpedo stowage and mess, the control room, wardroom and galley over the battery, the engine room and the motor room.

Along the bottom, an echo trace draws the escort's view: strength against range, drawn as each ping comes back, with the boat, the wreck and the seabed marked. Sound is optional and synthesised in the browser. Cinematic cameras, a hidden interface for a clean picture, and every control on the keyboard. For exhibitions, ?kiosk=1 runs it unattended: it pings, works through its depths and opens up on its own.

Etymology

First attested as a single-file page Jack built in a conversation with Claude and published as a claude.ai artifact on 29 September 2026; moved here the same day. The artifact has no git history, and the artifact service keeps only its final version, so there are no in-between frames to show and none are invented. The replay starts with the published file itself, rendered with the libraries it loaded from a CDN, and carries on through this site's port, every frame shot in the same state.

  • Artifact published07:18 UTC, inferred
  • Port logged10:22 UTC
  • Port first commit11:04 155076a
  • Artifact to port3 h 46 min

Day one, from the records. The artifact's version id, 1790666297, read as a Unix timestamp is 07:18:17 UTC on 29 September; the artifact service does not document what the id means, so that time is an inference, not a record. The port's times come from the commitment ledger (a747b94e, opened 10:22:13) and git log on this site.

The submarine piece as ported to jarvising: the boat opened up into its compartments below the layer, labels tracked to each part.

The latest frame, 29 Sep 2026. With JavaScript on, drag across the picture or use the slider to replay the build.

  1. 1790666297claude.ai: published as an artifact, one HTML file (time inferred from the version id)
  2. 155076aPorted to jarvising.com as entry 004

Frames rendered by tools/render-build-frames.mjs in software WebGL. The port is shot pinned and frozen at ?depth=280&view=open&ping=0.3 (opened up, below the layer) and ?depth=40&ping=0.75 (periscope depth). The published artifact has no such pin, so it was loaded as published, with its libraries from their CDNs, driven with its own buttons and left to sink to depth in real time; its ping, and the slow orbit it starts when left alone, are wherever they had got to. The file is kept in the repo at tools/build-frames/sources/ so the frame can be rendered again.

How it was made

One file, then modules. The piece began as a single HTML page of about 1,550 lines, with three.js loaded from a CDN through an import map and its fonts from Google. Here it is the same code split into nine TypeScript modules and a stylesheet under src/submarine/, bundled by Vite against this site's own three.js. The look is deliberately its own: a night-sea palette, a stencil display face, a news serif and a mono for the instruments, all inside the live page only.

A hull from five curves. The boat is lofted, not modelled. Five one-dimensional curves along its length give the half-breadth, the keel line, the widest point, the deck and the casing's width; a superellipse between them makes each cross-section, and a sweep joins the sections into the upper and lower hull. The bulbous bow is just where those curves swell at the stem. Saddle tanks are lathed, the fairwater is extruded from a plan shape and then raked in code, and the free-flooding holes along the casing are one instanced mesh placed on the hull's own curve.

Every material hears the ping. The sonar is not a mesh that happens to be in front of things. Four expanding spheres carry their centres and radii in a uniform array that every lit material in the scene reads: an onBeforeCompile patch adds a thin bright line wherever a surface sits on a shell's radius, so the ping visibly slides over the hull, the mines and the seabed. The same patch adds caustics from above, the layer's glowing line and depth fog that fades into the colour of the water.

The layer is one number. Anything on the far side of the thermocline from a shell's origin sees that shell at 14% strength. The echo's strength is how much of the boat's height, keel to periscope standards, sits below the layer: all of it above gives a strong echo, all of it below gives about an eighth. The trace, the readouts, the status line and the sound all read that one value.

Opened up is a single eased number too. Every part group has a direction and a distance; one value from 0 to 1 moves them all, spreads the compartments along the boat, fades in dashed leader lines and warms the interior. Labels are HTML, projected from 3D points each frame, and hide themselves wherever the interface would cover them.

The port. The CDN and Google Fonts are gone: this site's content security policy allows no third-party requests, so three.js is bundled (0.185 here, up from 0.169) and the fonts are self-hosted. Two things had moved in three.js between those versions: PCFSoftShadowMap was retired, so the shadows use PCFShadowMap, its successor, and THREE.Clock was deprecated, so the scene keeps its own clock. The inline script became modules, since the policy forbids inline scripts. For testing, the piece gained a pinned, frozen state (?depth=280&view=open&ping=0.4), which also meant stamping the echo with the moment it hit the hull rather than the frame that noticed it, and seeding the marine snow so every load draws the same picture. A sourcing pass corrected four lines of the copy (see History notes).

Built by Jack in one conversation with Claude, then ported to this site with Claude Code.

How to make your own

  1. Loft the hull from curves. Write down a handful of numbers per station along the boat (half-breadth, keel, deck) and fit a smooth 1D curve through each. A cross-section function turns those into a ring of points; a sweep turns the rings into a mesh. Changing the bow is changing five numbers.
  2. Put the physics in the shaders, not in meshes. Keep the sonar pulses as a small uniform array (centre and radius) and patch every material to draw a line where its surface meets a radius. Then the pulse lights up whatever it touches without a single collision test.
  3. Make the story one number. Here, how much of the boat is below the layer. Drive the echo, the words, the trace and the sound from it, and the whole piece agrees with itself.
  4. Slow the physics down, but say so. Sound is drawn 36 times slower than life so you can watch it travel, but the readout gives the real round trip. Label every liberty on the page.
  5. Explode with one eased value. Give each part group a direction and a distance and multiply by one number from 0 to 1. Warm the interior and fade the leader lines on the same value.
  6. Pin a frozen state from day one. A URL that sets depth, view and the ping's progress, and stops the clock, turns a live scene into something you can screenshot, measure and compare between versions.
  7. Check the history before you publish it. Write the claims down, find a source for each, and correct the copy where the sources disagree. Ours turned up a bow that was only cut away on two boats, and a pressure that was 133, not 130.

The shader patch that makes every surface hear the ping (the full piece is in src/submarine/ on GitHub):

// four pulses: xyz = where it was sent from, w = how far it has got
uniform vec4 uWave[4];
uniform vec4 uWaveC[4];   // rgb = colour, a = strength
uniform float uLayerY;    // the thermocline

// a pulse that crosses the layer arrives at 14%
float layerAtt(vec3 o, vec3 p) {
  float oAbove = step(uLayerY, o.y);
  float pBelow = smoothstep(uLayerY + 3.0, uLayerY - 3.0, p.y);
  float crossing = oAbove * pBelow + (1.0 - oAbove) * (1.0 - pBelow);
  return mix(1.0, 0.14, crossing);
}

vec3 waveGlow(vec3 p, float sharp) {
  vec3 acc = vec3(0.0);
  for (int i = 0; i < 4; i++) {
    if (uWaveC[i].a <= 0.0005) continue;
    float d = length(p - uWave[i].xyz) - uWave[i].w;  // distance to the shell
    float line = exp(-d * d * sharp);
    float trail = d < 0.0 ? exp(d * 0.035) * 0.16 : 0.0;
    acc += uWaveC[i].rgb * uWaveC[i].a * (line + trail) * layerAtt(uWave[i].xyz, p);
  }
  return acc;
}
// in each material's onBeforeCompile, after the emissive chunk:
//   totalEmissiveRadiance += waveGlow(vWorldPos, 0.25);

The shader reads the world position the vertex patch passes down; the JavaScript side only grows each pulse's radius by the shown speed of sound each frame.

History notes

What the piece shows, checked against the sources below. Where they disagreed with the original copy, the copy was changed.

  • The T-class. The Royal Navy's standard ocean-going patrol submarine of the war: 53 were built in three groups (15, 7 and 31), and nine more were cancelled in 1945. About 84 m (276 ft) long, with a wartime crew of around 60. The early boats were riveted and rated to 300 feet; the welded boats of the last group, to 350.
  • The bow. The first group had ten torpedo tubes facing forward: six inside the bow, two outside it in the bulbous bow casing, and two outside amidships. The bulge cost speed on the surface in rough weather and threw up a bow wave at periscope depth. The original copy said it was "later removed"; in fact it was cut away on two boats, Triumph and Thunderbolt (the salvaged Thetis), during repairs, and the second group moved those tubes aft so the bow could be finer. The piece shows the bow as built.
  • A mid-war fit. The early boats were built with machine guns, not the Oerlikon shown aft of the bridge, and radar came mid-war. The model shows that later fit on an early hull, and says so.
  • ASDIC. The British name for active sonar, fitted to their submarines as well as their escorts. The piece does not expand the acronym: the familiar "Allied Submarine Detection Investigation Committee" has no trace in the Admiralty's archives.
  • The layer. Sound bends toward slower water, so at a sharp drop in temperature much of a ping is refracted downward, leaving a shadow zone beneath the layer that the sound barely reaches. The original copy said the ping was "bent away and scattered"; refraction, not scattering, does the work, and the copy now says so. U-boats are recorded diving under layers to shake off ASDIC. The layer's depth changes with the season and the sea; the piece's 180 feet is illustrative.
  • The pressure. Seawater (1,025 kg/m³) weighs 1,025 × 9.807 = 10,052 pascals per metre of depth, or 1.458 psi per metre: about one atmosphere (14.7 psi) every 33 feet. At 300 ft, 91.44 m, that is 919,100 Pa, or 133 psi more than at the surface, 148 psi with the air on top. The original copy said "about 130"; it now says 133. The readout is the same sum: 1.4585 psi per metre, gauge.
  • The echo. The escort's range comes from the echo's time for the round trip, out and back: time = 2 × range ÷ 1,500 m/s. Sound in seawater runs from about 1,440 to 1,570 m/s with temperature, salinity and depth. On screen the pulse moves at 42 m/s, about 36 times slower than life; the status line gives the real time.

Drawn loosely, and not verified: the net cutter at the stem, and the exact compartment layout (the broad order, torpedo spaces forward, control room under the bridge, engines then motors aft, is standard for the period, but we found no T-class plan to check it against). The escort, the wreck and the mines are generic.

Sources

A claim-by-claim ledger with verdicts lives in the repo: docs/submarine-sources.md. Three of the pages listed refused our fetcher and were read only through search summaries; the ledger marks which.

See also

Entry 001, Enigma: the same war from Bletchley Park, a cipher machine taken apart and then typed on.

Entry 002, Cortex: a fleet’s memory as a living 3D war table, handed on as a library.

Entry 003, Inside the Rig: an exploded view at the scale of a whole gaming PC.

← all entries