diff --git a/HANDOFF.md b/HANDOFF.md
index a081050..ebcfc73 100644
--- a/HANDOFF.md
+++ b/HANDOFF.md
@@ -418,6 +418,41 @@ free to be the KILL range instead of a number tuned to one camera.
**Weapon sounds** are a `sfx` field on the WEAPONS row: `thump` (beacon, `boom`), `swoosh` (rocket,
`whoosh`), otherwise the gun's every-other-round tick.
+## The laser plane, and cutting bodies with it (2026-08-03)
+
+Settled with Oliver in discussion, then built. **The beam is a real plane at `TUNE.beamY` (1.15 m)**
+that holds that height all the way out instead of sloping to the ground — that single change is what
+makes the rest possible. Sloping, it lay flat on the grass across the whole far half, so there was
+nothing for it to cut exactly where the crowd stands.
+
+- **One flat translucent triangle**, apex at the player's CHEST (`beamOriginY` 1.25 — emitted from
+ the racket head at 2.2 m the apex hung in the air and read as detached), opening out down the
+ court, alpha falling with distance. It was four nested wedges composited `lighter`, and that
+ layering WAS the banding: additive light blowing out to white where layers overlapped. Flat alpha
+ also means a body under the plane is TINTED rather than brightened, which is what makes the cut read.
+- **Bodies are sliced by it.** `drawBeamCuts` runs after the beam: for each body in the cone, the
+ part above `proj(zb.x, beamY, zb.z).y` is redrawn clipped, so it sits ON TOP of the plane while the
+ part below stays tinted underneath. `drawZombie` mutates nothing, so drawing it twice is safe.
+ Measured: the plane takes a shambler at 64% of its height (sternum), a brute at 43% (belly), the
+ roller man at 36% (thigh) — bigger bodies visibly take it lower.
+- **The cut line follows the SPRITE, not a rectangle.** A `fillRect` spanned the body's bounding
+ width and so crossed the empty gaps between the legs and under a held-out arm, reading as a bar
+ lying behind him. The line is the sprite itself, redrawn clipped to a thin band and recoloured flat
+ by `ctx.filter`. Thickness scales with `h * s²` so it is a hairline at the ads and a real band at
+ your feet — the camera's scale only spans ~1.5x across the court, so a linear falloff was invisible.
+- **Getting a filter to a specific colour is a MEASUREMENT, not a guess.** The chain has to leave a
+ MID grey for `sepia` to tint: `brightness(0) invert(1)` goes to white, and sepia adds almost no
+ chroma to white while saturating a colourless pixel does nothing — the first attempt rendered
+ #a0b4c3, a grey. `brightness(0) invert(0.45) sepia(1) saturate(1600%) hue-rotate(185deg)` renders
+ (32,154,255) against the laser icon's (29,153,232). Probe candidate chains by filling a rect and
+ reading the pixel back; do not eyeball it.
+- **`HAS_FILTER` is feature-detected** and the line falls back to a narrow bar without it (older
+ WebKit has no `ctx.filter`).
+- **The cut pass is BOUNDED** (`CUT_MAX` 8 nearest bodies, `CUT_MIN_PX` 14): it draws each body twice
+ more and the second pass runs through a filter, which is slow on iOS, and it runs every frame you
+ are AIMING rather than only on the shot. Measured on desktop: 0.27 ms/frame with 20 bodies against
+ a 16.7 ms budget.
+
## TEST MODE — triple-tap the scoreboard
A sandbox for trying new content without playing twenty games to reach it. Three taps on the
diff --git a/config.js b/config.js
index 45b1367..25605e3 100644
--- a/config.js
+++ b/config.js
@@ -243,6 +243,15 @@ export const TUNE = {
beamLen: 34, // m it reaches. Was 26, which stopped at the far baseline and read as the beam
// being CUT OFF in mid-court; the player stands at z~24, so this carries it past
// the baseline and up into the courtside ads, which is where light should stop.
+ beamY: 1.15, // m — the height the plane HOLDS down the court, and the line it cuts bodies at.
+ // It used to slope to the ground by about 20 m out, which meant that across the
+ // whole far half it lay flat on the grass: nothing to cut, and the split would
+ // have been invisible exactly where the crowd stands. Chest on a shambler, belly
+ // on a brute, thigh on the roller man — so a bigger body visibly takes it lower.
+ beamOriginY: 1.25,// m — it leaves the PLAYER at chest height, not the racket head. Emitted from
+ // the racket (2.2 m) the apex hung in the air above him and read as detached;
+ // out of his chest, level with the plane it becomes, it reads as HIS shot.
+ beamRise: 0.14, // fraction of the length it takes to settle from the origin to that height
beamW0: 0.06, // m half-width at the racket — a POINT. At 0.35 it left the racket as a 0.7 m
// bar hanging in the air rather than as something emitted from the strings.
beamW1: 5.0, // ...and at the far end. It is a CONE: the spread is the point of it
diff --git a/index.html b/index.html
index eef9520..dc95043 100644
--- a/index.html
+++ b/index.html
@@ -179,6 +179,6 @@
-
+