The marker stops lying, and the rule announces itself
"the marker flips all the time even during a ball being midair.. i am not sure that a player would get the mechanic intuitively let alone without the marker". The flip had a specific cause and it was a design bug, not jitter. With lanes held and speeds constant, arrival differences never change while bodies walk — the queue only reorders on a kill or an arrival. The marker flipped because it showed only q[0] while the rule accepts any near-tie within the slack: every kill made it jump between tied bodies, so the marker was stricter than the rule it was explaining. drillLegal() marks the whole slack set now, bright head and dim ties, which is stable by construction. Measured: zero head-changes across 12s of walking with no kills. Learnability, two answers. The rule gets one modal, once per run, on the mode's own calm court — CLEARANCE DRILL / DROP WHOEVER WOULD REACH YOU FIRST / FAST BEATS CLOSE / STREAKS PAY — because "most urgent first" is not guessable from a chevron alone. And the markers are training wheels that take themselves off: hidden at streak >= 8 (the streak is the evidence you can read the court), back on any fault. The teacher steps in exactly when you demonstrably need it. 31 tests, 15 seeds clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6a16e77480
commit
be191dba82
6 files changed
+100
-24
No files matched your search
+23
-2
@@ -22,10 +22,11 @@
|
||||
// THE MODE TRANSITIONS ARE NOT HERE — startDrill/endDrill live in beats.js with every other one
|
||||
// (CLAUDE.md rule 3). This file is the stream, the urgency rule and the scoring.
|
||||
|
||||
import { TUNE, COURT } from './config.js';
|
||||
import { TUNE, COURT, UI } from './config.js';
|
||||
import { G, ball } from './state.js';
|
||||
import { zombies, spawnZombie } from './zombies.js';
|
||||
import { voice } from './audio.js';
|
||||
import { showModal } from './gameplay.js'; // runtime-only cycle (gameplay imports drill), same pattern as beats
|
||||
|
||||
// The roster in the stream, weighted: shamblers are the crowd, heavies are events. vMul is the
|
||||
// drill's own speed character (deliberately narrower than the roster's 3.2x..0.45x so the queue
|
||||
@@ -52,8 +53,20 @@ const spawnGap = d => lerp(TUNE.drillSpawnEvery, TUNE.drillSpawnMin, ramp(d));
|
||||
// ---- THE URGENCY RULE, and it is the whole mode. Seconds until this body crosses your baseline at
|
||||
// its own pace down its own lane (drill bodies hold their lane, so this is honest — nothing swerves).
|
||||
export const arrival = zb => (COURT.L - zb.z) / Math.max(0.01, zb.v);
|
||||
// The queue, most urgent first. Exported: render marks the head, tests assert against it.
|
||||
// The queue, most urgent first. Exported: render marks the legal set, tests assert against it.
|
||||
export const drillQueue = () => zombies.filter(z => z.drill).sort((a, b) => arrival(a) - arrival(b));
|
||||
// EVERY legal kill right now — the head plus anything within the slack of it. THE MARKER MUST DRAW
|
||||
// THIS SET, not the bare head: the rule accepts any near-tie, so a marker showing only q[0] was
|
||||
// STRICTER than the rule it explained, and every kill made it jump between tied bodies in a way
|
||||
// that read as the rule changing mid-flight. (With lanes held and speeds constant, arrival
|
||||
// differences never change while bodies walk — the queue only reorders on a kill or an arrival, so
|
||||
// marking the whole legal set is stable by construction.)
|
||||
export const drillLegal = () => {
|
||||
const q = drillQueue();
|
||||
if (!q.length) return q;
|
||||
const t0 = arrival(q[0]);
|
||||
return q.filter(z => arrival(z) <= t0 + TUNE.drillSlack);
|
||||
};
|
||||
|
||||
function pickType() {
|
||||
const pool = DRILL_TYPES.flatMap(t => Array(t.w).fill(t));
|
||||
@@ -87,6 +100,14 @@ function spawnDrillBody(d) {
|
||||
export function startDrillStream() {
|
||||
const d = G.drill; if (!d) return;
|
||||
zombies.length = 0;
|
||||
// The rule gets ONE sentence, once per run, on the sanctioned calm court (the seeded bodies are
|
||||
// all at the far baseline — nothing is near). Oliver's worry is right: "most urgent first" is NOT
|
||||
// guessable from a chevron alone, and this is the modal policy's exact use case.
|
||||
if (!G.seen.drillRule) {
|
||||
G.seen.drillRule = true;
|
||||
showModal({ title: 'CLEARANCE DRILL', color: UI.pick,
|
||||
body: ['DROP WHOEVER WOULD REACH YOU FIRST', 'FAST BEATS CLOSE · STREAKS PAY'] });
|
||||
}
|
||||
d.t = 0; d.spawnT = 0; d.score = 0; d.streak = 0; d.kills = 0; d.faults = 0; d.lost = 0; d.msg = null;
|
||||
try { d.best = Number(localStorage.getItem('drillBest')) || 0; } catch { d.best = 0; }
|
||||
// seed a small spread so there is a queue to read from the first second
|
||||
|
||||
Reference in new issue
Block a user