From 3379e83efc41a39d715c283ed416770c6abfdfdf Mon Sep 17 00:00:00 2001 From: Oliver Rieger Date: Tue, 11 Aug 2026 20:08:58 +0200 Subject: [PATCH] Clear the court of everything that is neither the puzzle nor the threat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "remove the mob boy - make the normal zombies rarer and not so close to the color gang". sendBoy refuses during a drill, guarded at the function rather than at the call sites — same shape as its existing G.interlude guard, so every present and future caller inherits it. He is one more body crossing the lanes you are reading colours down, and unlike everything else out there he is neither the puzzle nor the threat. Measured: 0 boy-frames in 90 seconds. Gore no longer gets mopped in this mode; the existing 40/30 caps are what bound it. The eaters are rarer — every 12s falling to 5s, capped at 3 on court. At the old rate they were a second crowd rather than an interruption, and a threat you break off to deal with only works if breaking off is occasional. 8 arrivals in 90s where the old numbers gave ~13. And they enter wide of the colour gang: best-of-9 lane sampling against the bibbed bodies, the same trick the batch uses. Narrowest clearance measured across 8 spawns was 0.91 m. They still converge on you afterwards — that is what they are for — this only decides where they walk in, so they stop screening the bodies you are trying to read. Co-Authored-By: Claude Opus 5 --- PROJECT.md | 17 +++++++++++++++++ index.html | 2 +- src/config.js | 11 ++++++++--- src/drill.js | 15 +++++++++++++-- src/state.js | 8 ++++++-- 5 files changed, 45 insertions(+), 8 deletions(-) diff --git a/PROJECT.md b/PROJECT.md index 7bf9f6b..5f86de3 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -677,6 +677,23 @@ hits"*. Four things, and one of them was a straight bug of mine. kill — and the strip carries the list. **Do not add a third noise here without a reason**: both attempts read as the mode talking over itself. +**EIGHTH PASS — the court gets quieter.** *"remove the mob boy - make the normal zombies rarer and +not so close to the color gang"*. +- **`sendBoy` refuses during a drill**, at the function rather than at the call sites (same shape as + its existing `G.interlude` guard, so every present and future caller inherits it). He is one more + body crossing the lanes you are reading colours down, and unlike everything else on that court he + is neither the puzzle nor the threat — pure noise. Measured: 0 boy-frames in 90 s. + Note this means **gore no longer gets mopped in the drill**; the `gore` 40 / `piles` 30 caps are + what keep it bounded, which they already did. +- **The eaters are rarer**: `drillEaterEvery` 7 -> 12 s, `drillEaterMin` 2.2 -> 5, cap 4 -> 3. At the + old rate they were a second crowd rather than an interruption, and a threat you break off to deal + with only works if breaking off is occasional. Measured: 8 arrivals in 90 s where the old numbers + gave ~13. +- **And they enter WIDE of the colour gang** — best-of-9 lane sampling against the bibbed bodies, the + same trick the batch spacing uses. Measured narrowest clearance at entry across 8 spawns: 0.91 m. + They still converge on you afterwards, which is what they are for; this only decides where they + come in, so they stop screening the bodies you are trying to read. + **Open, and every one of them is Oliver's call after he plays it:** 1. ~~**A ball that carries through the lane can complete two steps at once.**~~ Largely CLOSED by the slice removal (seventh pass) — a cut ball picking its own next victim was the main way this diff --git a/index.html b/index.html index a9f7087..9dd8435 100644 --- a/index.html +++ b/index.html @@ -399,6 +399,6 @@ - + diff --git a/src/config.js b/src/config.js index 4cc8bce..05a88fb 100644 --- a/src/config.js +++ b/src/config.js @@ -360,9 +360,14 @@ export const TUNE = { drillSpeed: 0.80, // m/s a bibbed body walks at the bottom of the ramp... drillSpeedMax: 1.9,// ...and at the top. Stamped per body AT SPAWN, so the ramp shows up as new // arrivals outpacing the ones already walking, not the court accelerating - drillEaterEvery: 7.0, // s between unbibbed eaters at the bottom of the ramp... - drillEaterMin: 2.2, // ...and at the top. THE difficulty dial now that the puzzle plays fair - drillEaterMax: 4, // eaters allowed on court at once — past this the puzzle is unreadable + // The eaters are RARE on purpose. At every 7 s / 2.2 s they were a second crowd rather than an + // interruption, and the puzzle is the point of the mode — a threat you have to break off and deal + // with only works if breaking off is occasional. They also enter wide of the colour gang + // (spawnEater), because the one body out there that is neither the puzzle nor worth anything has + // no business standing in front of the puzzle. + drillEaterEvery: 12.0, // s between unbibbed eaters at the bottom of the ramp... + drillEaterMin: 5.0, // ...and at the top. Still THE difficulty dial, just a slower one + drillEaterMax: 3, // eaters allowed on court at once — past this the puzzle is unreadable drillEaterSpeed: 1.15,// they walk a touch faster than the batch: the batch is a list, this is a threat drillRampT: 210, // s to go from the bottom of every curve above to the top drillRingR: 0.62, // m — radius of the colour disc a bibbed body walks on. Big enough to read at diff --git a/src/drill.js b/src/drill.js index 81be078..fa75a41 100644 --- a/src/drill.js +++ b/src/drill.js @@ -101,8 +101,19 @@ function spawnBatch(d) { // mode was missing. function spawnEater(d) { if (zombies.filter(z => z.bib === undefined).length >= TUNE.drillEaterMax) return; - spawnZombie((Math.random() * 2 - 1) * TUNE.drillSpread, TUNE.drillSpawnZ + Math.random() * 0.8, - Math.random() < 0.3 ? 'b' : 'a'); + // THEY ENTER WIDE OF THE COLOUR GANG. An eater walking in on top of the batch screens the very + // bodies you are trying to read, and it is the one body on court that is neither the puzzle nor + // worth points — so it has no business standing in front of the puzzle. Best-of-N: sample lanes + // across the width and take the one furthest from every coloured body. They still converge on you + // afterwards (that is what they are for); this only decides where they come IN. + const gang = bibs(); + let bx = 0, bs = -1; + for (let k = 0; k < 9; k++) { + const x = -TUNE.drillSpread + (2 * TUNE.drillSpread) * (k / 8) + (Math.random() - 0.5) * 0.4; + const s = gang.length ? Math.min(...gang.map(z => Math.abs(z.x - x))) : 1e9; + if (s > bs) { bs = s; bx = x; } + } + spawnZombie(bx, TUNE.drillSpawnZ + Math.random() * 0.8, Math.random() < 0.3 ? 'b' : 'a'); const zb = zombies[zombies.length - 1]; zb.v = drillSpeed(d) * TUNE.drillEaterSpeed * (0.9 + Math.random() * 0.2); } diff --git a/src/state.js b/src/state.js index 8985dff..5866a84 100644 --- a/src/state.js +++ b/src/state.js @@ -104,8 +104,9 @@ export const G = { interlude: false, // sudden-death duel vs the racket zombie: falsy, or {introT} while announcing duelLerp: 0, // 0..1 — how far the court has returned to tennis colors for the duel horde: null, // active horde wave {t, spawnT, mgT} — machine-gun racket, no serving - drill: null, // the coaching drill (src/drill.js): {seq, step, faults, callT, beat, rush, won, lost, msg}. - // Owns the court like every other mode — see beats.startDrill. + drill: null, // the coaching drill (src/drill.js): {seq, step, faults, t, eaterT, rounds, lost, msg}. + // Owns the court like every other mode — see beats.startDrill. `t` is time + // survived and it is the whole difficulty curve; there is no round clock. killCount: 0, // splatter: kills banked toward the next point (see TUNE.killsPerPoint) runGames: 0, // games finished this run (either side) — the difficulty director sets: [0, 0], // splatter sets won [you, them]; 6 games = a set = a horde @@ -181,6 +182,9 @@ const mopWorthIt = () => gore.some(d => d.a >= 0.15 && d.z >= COURT.netZ - 2); export function sendBoy(tx = ball.x, tz = ball.z, mop = G.mode === 'splatter') { if (G.interlude) return; // the sudden-death duel is clean tennis: nobody mops through it + if (G.drill) return; // ...and nobody walks onto a drill. He is one more body crossing the + // lanes you are reading colours down, and unlike everything else out + // there he is not part of the puzzle OR the threat — pure noise. if (mop && !mopWorthIt()) return; // no job, no boy if (tz < -3 || tz > 27) return; // target left the field lengthwise, let it go // ONE boy, one slot. Every kill used to call this and teleport him back to the wing mid-stride,