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,