The receiver waits behind his line, and a missed serve buys a second one
Oliver, three notes; two of them are here. "when the opponent serves the player does not stand at the baseline - but that's how it should be." setupPoint's receiving marks sat over a metre INSIDE the court on both sides (22.5 near, 1.2 far) — where you stand to attack a second serve, not where you wait for a first one. They mirror the server's depth now, in the one line every serving mode shares. "when serving results in an out there should be a second service... the umpire should announce 2nd service and the player must get another chance." True in tennis mode since forever and absent everywhere else: the duel, the match and the death match had no service rule at all, so a serve long or into the net was judged as an ordinary ground stroke — a point, and in the match a point costs blood. The out branch of onBounce's interlude block routes a serve to fault() instead, so the second serve, the HUD line, the umpire and the double fault all come for free. Three things it dragged out with it: - fault()'s double-fault branch called pointTo — TENNIS scoring — which in an interlude banks a number and skips the mode that owns the court. It goes through resolveInterlude there now. Nothing had ever reached that line from a duel or a match, because those modes could not fault. - startDuel never reset G.serveNum: harmless while a duel could not fault, the next duel's death loop the moment it can. - the match puts the body back on its receiving mark for the second serve (placeReceiver, split out of matchReady, which resets the serve number). "Fault. Second service." is one voiceSeq — a second voice() call would be dropped, one man one microphone. New recording via the usual say/afconvert. The service BOX is still not enforced in the match, deliberately: this is strictly more forgiving than what shipped. The lob gesture is OPEN 14. 4 new tests, 110 pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3d7e66449a
commit
6e07dc74bb
7 files changed
+182
-19
No files matched your search
+57
@@ -491,6 +491,46 @@ zombie (now an occasional opponent, `TUNE.racketEvery`, not the every-game fixtu
|
||||
`RETURNED`, `DOUBLE KILL`) is a footnote to something you already watched happen. Sharing the loud
|
||||
FACE with the milestone cards is right; sharing their SIZE made every footnote read as a milestone.
|
||||
|
||||
## Session log: THE SERVICE RULES ARRIVE IN THE OTHER MODES (2026-08-22)
|
||||
|
||||
Three notes from Oliver, two of them fixed here; the third (the lob) is under discussion in OPEN.
|
||||
|
||||
- **A RECEIVER STANDS BEHIND HIS BASELINE.** *"when the opponent serves the player does not stand at
|
||||
the baseline — but that's how it should be."* `setupPoint`'s receiving marks were z 22.5 near and
|
||||
1.2 far — over a metre INSIDE the court on both sides, which is where you stand to attack a second
|
||||
serve, not where you wait for a first one, and next to a server drawn behind his own line it read
|
||||
as a man who had wandered in. They now mirror the server's depth (24.2 / -0.43). It is one line, so
|
||||
every mode that serves through the tennis branch got it: classic, gore's opener, the match, and
|
||||
netplay. The duel is unaffected by design — it takes setupPoint's splatter early-out and is always
|
||||
your serve, from wherever you are standing.
|
||||
- **A MISSED SERVE BUYS A SECOND ONE.** *"when serving results in an out there should be a second
|
||||
service... the umpire should announce 2nd service and the player must get another chance."* True
|
||||
in tennis mode since forever, and **absent from every other mode**: the duel, the match and the
|
||||
death match had no service rule at all, so the first serve was judged as an ordinary ground stroke
|
||||
and a serve long or into the net handed the point away — in the match that is a point AND blood, on
|
||||
the very first ball of it. The out branch of `onBounce`'s interlude block now routes a serve to
|
||||
`fault()` instead of `resolveInterlude`, so the second serve, the HUD's SECOND SERVE line, the
|
||||
umpire and the double fault all come for free. Three things it dragged out with it:
|
||||
1. **`fault()`'s double-fault branch called `pointTo`** — TENNIS scoring — which in an interlude
|
||||
would bank a number and skip the mode that owns the court (blood, the fall, the ladder). It
|
||||
routes through `resolveInterlude` there now. Nothing had ever reached that line from a duel or a
|
||||
match, because those modes could not fault.
|
||||
2. **`startDuel` never reset `G.serveNum`.** Harmless while a duel could not fault; the moment it
|
||||
can, a fault in one duel arms second-serve in the NEXT one — the exact death loop `matchReady`
|
||||
already carries a note about, one mode over.
|
||||
3. **The match puts the body back on its receiving mark for the second serve** (`placeReceiver`,
|
||||
split out of `matchReady` because that call resets the serve number a fault has just spent).
|
||||
Not in netplay: the called ball is left rolling there, and `matchReady` would freeze it.
|
||||
- **THE BOX IS STILL NOT ENFORCED IN THE MATCH, deliberately.** Classic checks the service box; the
|
||||
match's line call only ever checked the court, and a deep-but-in serve stays legal there. That is
|
||||
the concession the mode was tuned around (see the "classic tennis gives double fault very often"
|
||||
note in `keyPressed`) and this change is strictly more forgiving than what shipped. Enforcing it
|
||||
is a one-line change if Oliver wants real service boxes in zombie tennis — ask before doing it.
|
||||
- **One new recording: `second_service.m4a`** (`say -v Daniel -r 168` -> `afconvert`, the existing
|
||||
pipeline). It plays as a SEQUENCE with the fault call — `voiceSeq(['fault', 'second_service'])` —
|
||||
because `voice()` drops any line that would start while the umpire is still talking, so a second
|
||||
`voice()` on the next line would have been silently swallowed. "Fault. Second service."
|
||||
|
||||
## ZOMBIE TENNIS — the mode, end to end (folded from HANDOFF-MATCH.md, 2026-08-17)
|
||||
|
||||
`HANDOFF-MATCH.md` was a temporary file written when a session ran out of context mid-slice. The
|
||||
@@ -3901,6 +3941,23 @@ Nothing here is broken; these are known gaps and unverified risks.
|
||||
from a tin). At 12 rounds/s a per-pellet sample will machine-gun the mixer, not just the wave.
|
||||
- **boss sounds** — footfall, wind-up, throw, wound, death. Three designs in `BOSS_ART`; decide
|
||||
whether they share one set or get one each before recording anything.
|
||||
14. **THE LOB ("lupfen") — asked for 2026-08-22, UNDER DISCUSSION, do not build it yet.** Oliver:
|
||||
*"when you're close to the net in a duel with another player there should be a gesture allowing
|
||||
to make a ball flying high from below... I don't know how to implement this gesture wise — this
|
||||
likely needs to be discussed."* The shot itself is nearly free: `trySwing` already solves every
|
||||
return through `solveShot` with a target depth from `SHOT_DEPTH` (up = deep, none = mid, down =
|
||||
short), so a lob is a deep target with a much slower `s` and the net-clearance loop doing the
|
||||
rest — the same maths, different two numbers. **The GESTURE is the whole question**, and every
|
||||
obvious answer is already spoken for: the flick is the rally aim (up = deep), the pull is the
|
||||
slingshot, a hold is nothing yet but is also invisible. Candidates to put to him, cheapest first:
|
||||
(a) the flick that is already there, but STEEPER THAN THE DEEP ONE — the up-flick keeps meaning
|
||||
"deep" and a near-vertical up-flick means "over him"; costs one threshold and no new input;
|
||||
(b) the same flick, but only ARMED while the opponent is inside his own service box (the shot
|
||||
only exists as an answer to a man at the net, so context can select it instead of the gesture);
|
||||
(c) a two-finger tap or an up-SWIPE from below the ball, both of which are new input surfaces.
|
||||
Whatever wins needs the receiving side to be answerable too, or a lob at the net is an ace: the
|
||||
zombie's chase reads the BOUNCE (`aiRead`), so a high slow ball gives it time — probably fine,
|
||||
but it must be measured, not assumed.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Binary file not shown.
+1
-1
@@ -664,6 +664,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="src/main.js?v=305"></script>
|
||||
<script type="module" src="src/main.js?v=306"></script>
|
||||
</body>
|
||||
</html>
|
||||
+21
-12
@@ -71,6 +71,10 @@ export function startDuel() {
|
||||
G.weapon = 0; // tennis rules for the duel: plain balls, no tins
|
||||
G.mgT = 0; endBurst(); pellets.length = 0; // ...and a burst in progress is over. The umpire has restored order.
|
||||
G.interlude = { introT: 2.4 }; // the intro beat: serving blocked while the umpire announces
|
||||
G.serveNum = 1; // ...on a FIRST serve. A duel now faults (gameplay's onBounce), and
|
||||
// nothing else resets it here — a fault in the last duel would have
|
||||
// armed second-serve in the next one, which is the death loop this
|
||||
// file already carries a note about, one mode over.
|
||||
G.boy = null; // ...and the mop boy is waved off the court (sendBoy is barred for the duration)
|
||||
ball.active = false; setupPoint();
|
||||
zombieDuck(true); // fade the zombie music out for the 1:1 duel
|
||||
@@ -420,18 +424,23 @@ export function matchReady() { // exported for net.js: a paired death match re-p
|
||||
// when it is the one serving. setupPoint has just placed O — the invisible tennis body the serve
|
||||
// machinery is driven off — on exactly that spot, so the zombie simply takes it. One source for
|
||||
// the mark, which is what stops the picture and the ballistics disagreeing about where it stands.
|
||||
if (rz) {
|
||||
// WHERE A RETURNER ACTUALLY STANDS. The tennis mark is 1.3 m off the centre line, and from
|
||||
// there he bisects nothing: the T is one step away and the corner of the box is four. Measured
|
||||
// headless, a flush serve with full sway into that corner was returned 0 times out of 20 at
|
||||
// EVERY rung of the ladder — Oliver: "the first ten zombies can be killed the moment you
|
||||
// serve". He now stands out on the bisector of the two serves he has to cover, which is what a
|
||||
// real receiver does, and O takes the same mark so the picture and the ballistics still agree.
|
||||
if (score.server === 'P') O.x = Math.sign(O.x || 1) * TUNE.matchRecvX;
|
||||
rz.x = O.x; rz.z = score.server === 'O' ? O.z : 1.0;
|
||||
rz.pose = 'idle'; rz.swingT = -1; rz.swingCool = 0.3;
|
||||
rz.showFrame = null; rz.serveHold = 0;
|
||||
}
|
||||
if (rz) placeReceiver(rz);
|
||||
}
|
||||
|
||||
// WHERE A RETURNER ACTUALLY STANDS. The tennis mark is 1.3 m off the centre line, and from there he
|
||||
// bisects nothing: the T is one step away and the corner of the box is four. Measured headless, a
|
||||
// flush serve with full sway into that corner was returned 0 times out of 20 at EVERY rung of the
|
||||
// ladder — Oliver: "the first ten zombies can be killed the moment you serve". He now stands out on
|
||||
// the bisector of the two serves he has to cover, which is what a real receiver does, and O takes
|
||||
// the same mark so the picture and the ballistics still agree.
|
||||
// Split out of matchReady because a SECOND SERVE needs the same mark without matchReady's rest:
|
||||
// that call resets G.serveNum, which is the one thing a fault has just spent (see fault()).
|
||||
export function placeReceiver(rz = zombies.find(z => z.variant === 'r')) {
|
||||
if (!rz) return;
|
||||
if (score.server === 'P') O.x = Math.sign(O.x || 1) * TUNE.matchRecvX;
|
||||
rz.x = O.x; rz.z = score.server === 'O' ? O.z : 1.0;
|
||||
rz.pose = 'idle'; rz.swingT = -1; rz.swingCool = 0.3;
|
||||
rz.showFrame = null; rz.serveHold = 0;
|
||||
}
|
||||
|
||||
// ---- THE FALL. A point won knocks the body DOWN and it drags itself back up MINUS THE LIMB.
|
||||
|
||||
+29
-4
@@ -2,14 +2,14 @@
|
||||
|
||||
import { TUNE, UI, bossArt, APPLAUSE_T, EATEN_BURST, EATEN_SUMMARY, COURT, CONTACT, VOLLEY, ZONE_DEPTH, HOME, P_BOUNDS, TOSS, SHOT_DEPTH, POSE_TIME, BOUNCE_IDLE, zombieH, Z_CHEST , WEAPONS, RACK_MAX , BARREL_WEAPONS, racketStage, matchWound, CUP_ROUNDS } from './config.js';
|
||||
import { P, O, ball, score, crowd, G, court, tour, other, deuceSide, setupPoint, sendBoy, resetState } from './state.js';
|
||||
import { audio, pock, swish, whoosh, applause, voice, sfx, aww, zombieMusic, zombieDuck, menuDuck, splat, shriek, boom } from './audio.js';
|
||||
import { audio, pock, swish, whoosh, applause, voice, voiceSeq, sfx, aww, zombieMusic, zombieDuck, menuDuck, splat, shriek, boom } from './audio.js';
|
||||
import { haptic } from './haptics.js';
|
||||
import { axisX, axisZ, takeFlickAim, touchPX, resetInput } from './input.js';
|
||||
import { W } from './render.js'; // runtime-only cycle (render imports gameplay), same pattern as input/zombies
|
||||
import { updateDrill } from './drill.js';
|
||||
import { endRun, beginRun, MODE_CLASSIC } from './stats.js';
|
||||
import { updateZombies, updateParts, spawnZombie, spawnRacketZombie, spawnMachine, spawnChapterBoss, zombies, gore, explode, meleeSwipe, extras, parts, pellets, shots, playerGore, eatenBleed, resetZombies , firePellet , spawnBarrel , fireBeam , pops, popText } from './zombies.js';
|
||||
import { proceedAfterClear, startDuel, startHorde, warnHorde, resolveInterlude, watchdog, updateBench, benchKey, tickMatchFall } from './beats.js'; // runtime-only cycle: beats calls back into cheer/aside/endBurst/deliverTin/showModal/logSwing
|
||||
import { proceedAfterClear, startDuel, startHorde, warnHorde, resolveInterlude, watchdog, updateBench, benchKey, tickMatchFall, placeReceiver } from './beats.js'; // runtime-only cycle: beats calls back into cheer/aside/endBurst/deliverTin/showModal/logSwing
|
||||
import { netSendShot, netSendToss, netTick, netJudging, netSendPoint, netSendFault, netClose } from './net.js'; // runtime-only cycle: net calls back into pointTo/fault/tossBall/showModal
|
||||
|
||||
// ------------------------------------------------------------- ballistics ----
|
||||
@@ -1048,8 +1048,22 @@ export function fault() { // exported for net.js: the receiver's fault call re-a
|
||||
if (G.serveNum === 1) {
|
||||
if (netJudging()) netSendFault(); // netplay: only the first-serve branch wires — the double
|
||||
// fault below goes out through pointTo as the point it is
|
||||
G.serveNum = 2; voice('fault'); G.phase = 'over'; G.overT = 1.4; sendBoy();
|
||||
// "Fault. Second service." — the second half is the whole point of the call: a lone "fault"
|
||||
// never said that the player still had a serve coming (Oliver, 2026-08-22). voiceSeq, not two
|
||||
// voice() calls: the umpire is one man with one microphone and the second line would be DROPPED.
|
||||
G.serveNum = 2; voiceSeq(['fault', 'second_service']); G.phase = 'over'; G.overT = 1.4; sendBoy();
|
||||
// ...and in a MATCH the body goes back on its receiving mark for the second serve, exactly as
|
||||
// matchReady puts it there for a first one — it is standing wherever it chased the fault to.
|
||||
// Not in netplay: the called ball is left rolling there (net.js), and matchReady's ball.active
|
||||
// = false would freeze it mid-roll on the far phone.
|
||||
if (G.match && !G.net) placeReceiver();
|
||||
}
|
||||
// A DOUBLE FAULT IS A POINT, AND A POINT IS SCORED BY THE MODE THAT OWNS THE COURT. pointTo is
|
||||
// TENNIS scoring; the duel and the match settle points through resolveInterlude (blood, the fall,
|
||||
// the ladder) and a raw pointTo there would bank a number and skip all of it — the same seam the
|
||||
// interlude branch of onBounce is written around. Nothing reached this line from an interlude
|
||||
// before, because those modes had no fault in them at all.
|
||||
else if (G.interlude) { voice('double_fault'); resolveInterlude(other(score.server)); }
|
||||
else pointTo(other(score.server), 'double_fault');
|
||||
}
|
||||
|
||||
@@ -1147,7 +1161,18 @@ function onBounce() {
|
||||
if (G.net && ball.lastHit === 'P') return;
|
||||
if (ball.bounces === 1) {
|
||||
const okSide = ball.lastHit === 'P' ? ball.z < COURT.netZ : ball.z > COURT.netZ;
|
||||
if (!inCourt(ball.x, ball.z) || !okSide) { endFlight(); resolveInterlude(other(ball.lastHit)); voice('out'); }
|
||||
// A MISSED SERVE BUYS A SECOND ONE, IT DOES NOT LOSE THE POINT (Oliver, 2026-08-22: "when
|
||||
// serving results in an out there should be a second service"). These modes had no service
|
||||
// rule at all: the first serve was judged as an ordinary ground stroke, so a serve into the
|
||||
// net or over the baseline handed the point away on the spot — in the match, that is a point
|
||||
// and blood for a first serve. It goes through the same fault() the tennis modes use, so the
|
||||
// second serve, the umpire's call and the double fault all come for free.
|
||||
// The BOX is deliberately still not enforced here (see the note in hitServe): a deep-but-in
|
||||
// serve stays legal in the match, which is the concession that mode was tuned around.
|
||||
if (!inCourt(ball.x, ball.z) || !okSide) {
|
||||
endFlight();
|
||||
if (G.wasServe) fault(); else { resolveInterlude(other(ball.lastHit)); voice('out'); }
|
||||
}
|
||||
// THE FALL BEATS THE BALL (Oliver: "the zombie falls after the ball is lost although we know
|
||||
// this earlier — it could play out as ball lost BECAUSE of the falling"). A winner that just
|
||||
// bounced in and is provably beyond the body's reach has already decided the point, so it
|
||||
|
||||
+6
-1
@@ -217,7 +217,12 @@ export function setupPoint() {
|
||||
const sx = deuceSide() ? 1.3 : -1.3;
|
||||
sv.x = sv.side === 'P' ? sx : -sx; // deuce side mirrors across the net
|
||||
sv.z = sv.side === 'P' ? 24.3 : -0.5;
|
||||
rc.x = -sv.x; rc.z = rc.side === 'P' ? 22.5 : HOME.O.z - 1.3;
|
||||
// A RECEIVER STANDS BEHIND HIS BASELINE (Oliver, 2026-08-22: "when the opponent serves the
|
||||
// player does not stand at the baseline — but that's how it should be"). Both marks used to sit
|
||||
// over a metre INSIDE the court (22.5 near, 1.2 far), which is where you stand to return a second
|
||||
// serve, not a first one — and next to a server drawn behind his own line it read as a man who
|
||||
// had wandered in. They now mirror the server's depth: 0.4-0.5 m behind the line on both sides.
|
||||
rc.x = -sv.x; rc.z = rc.side === 'P' ? 24.2 : -0.43;
|
||||
P.pose = O.pose = 'idle'; P.swingT = O.swingT = -1;
|
||||
P.facing = O.facing = 1; // canonical righty stance — bounce/serve sprites must not render mirrored
|
||||
G.aiServeT = score.server === 'O' ? 1.4 : 0;
|
||||
|
||||
@@ -89,7 +89,7 @@ globalThis.getComputedStyle = () => ({ paddingTop: '0' });
|
||||
// ---- game modules (real code) ----------------------------------------------
|
||||
const state = await import('./src/state.js');
|
||||
const { G, P, O, score, ball, setupPoint } = state;
|
||||
const { update, forceZombies, startGore, splatPoint, restartRun, flickTarget, flatCount, swayOf, keyPressed, classicGame, serveApexY, assistK, rallyFeel, barrelGap, machCap, hitServe, cheer } = await import('./src/gameplay.js');
|
||||
const { update, forceZombies, startGore, splatPoint, restartRun, flickTarget, flatCount, swayOf, keyPressed, classicGame, serveApexY, assistK, rallyFeel, barrelGap, machCap, hitServe, cheer, inCourt } = await import('./src/gameplay.js');
|
||||
const { startDuel, resolveInterlude, startBossFight, endBossFight, startDrill: _startDrill, endDrill, startMatch: _startMatch, endMatch, FALL_LEN, startNetVs, endNetVs } = await import('./src/beats.js');
|
||||
const { mirror, netMsg, netTest, netTick } = await import('./src/net.js');
|
||||
// the drill's first-sight modal freezes the world (that is what modals do) — dismiss it the way a
|
||||
@@ -668,6 +668,73 @@ test('THE DEATH LOOP: a fault before a lost point must not arm second-serve fore
|
||||
assert.equal(G.serveNum, 1, '...whoever won it');
|
||||
});
|
||||
|
||||
test('SECOND SERVICE: a missed serve in the match faults instead of losing the point', () => {
|
||||
// Oliver, 2026-08-22: "when serving results in an out there should be a second service... the
|
||||
// umpire should announce 2nd service and the player must get another chance." The duel and the
|
||||
// match had no service rule at all — the first serve was judged as an ordinary ground stroke, so
|
||||
// a serve long or into the net was a point, and in the match a point costs blood.
|
||||
startMatch();
|
||||
for (let i = 0; i < 2.6 * 60; i++) update(DT); // the walk-on beat, then his serve
|
||||
const zb = oppOf();
|
||||
const pts = score.pts.slice(), hp = G.hp;
|
||||
zb.x = 3.9; zb.z = 5.5; // it chased the fault out to the wing
|
||||
const serveOut = () => { // a serve that lands past the far baseline
|
||||
G.phase = 'rally'; G.wasServe = true;
|
||||
ball.active = true; ball.scored = false; ball.lastHit = 'P'; ball.bounces = 0;
|
||||
ball.x = 0.4; ball.z = 4; ball.y = 0.5; ball.vx = 0; ball.vy = -1; ball.vz = -20; ball.g = TUNE.gravity;
|
||||
for (let i = 0; i < 60 && ball.active; i++) update(DT);
|
||||
};
|
||||
serveOut();
|
||||
assert.equal(G.serveNum, 2, 'the first miss buys a second serve');
|
||||
assert.deepEqual(score.pts, pts, '...and costs no point');
|
||||
assert.equal(G.hp, hp, '...and no blood');
|
||||
assert.equal(G.matchFall, null, 'nobody falls over a fault');
|
||||
assert.equal(zb.z, 1.0, 'the body is back on its receiving mark for the second serve');
|
||||
assert.equal(zb.x, O.x, '...on the same mark the invisible tennis body serves against');
|
||||
serveOut(); // the double fault
|
||||
assert.equal(score.pts[1], pts[1] + 1, 'two misses is the point, theirs');
|
||||
assert.equal(G.hp, hp - 1, '...and the match scores it in blood, like every other lost point');
|
||||
assert.equal(G.serveNum, 1, 'the next point starts on a first serve');
|
||||
});
|
||||
|
||||
test('SECOND SERVICE: a serve that lands IN is still played, fault or not', () => {
|
||||
// The guard is on the OUT branch only — the mode's other rules (the early-decided winner, the
|
||||
// second bounce) must not learn about serves.
|
||||
startMatch();
|
||||
for (let i = 0; i < 2.6 * 60; i++) update(DT);
|
||||
const pts = score.pts.slice();
|
||||
G.phase = 'rally'; G.wasServe = true;
|
||||
ball.active = true; ball.scored = false; ball.lastHit = 'P'; ball.bounces = 0;
|
||||
ball.x = 0.4; ball.z = 8; ball.y = 0.5; ball.vx = 0; ball.vy = -1; ball.vz = -8; ball.g = TUNE.gravity;
|
||||
for (let i = 0; i < 30 && ball.bounces < 1; i++) update(DT);
|
||||
assert.equal(ball.bounces, 1, 'setup: it bounced');
|
||||
assert.ok(inCourt(ball.x, ball.z), '...in court');
|
||||
assert.equal(G.serveNum, 1, 'a legal serve is no fault');
|
||||
assert.deepEqual(score.pts, pts, 'and no point either — it is a rally now');
|
||||
});
|
||||
|
||||
test('a duel opens on a FIRST serve, whatever the last one ended on', () => {
|
||||
// Same shape as the match's death loop, one mode over: nothing in startDuel reset G.serveNum, and
|
||||
// now that a duel can fault, a fault in one duel would have armed second-serve in the next.
|
||||
restartRun();
|
||||
G.mode = 'splatter';
|
||||
G.serveNum = 2;
|
||||
startDuel();
|
||||
assert.equal(G.serveNum, 1, 'the duel is served for the first time');
|
||||
});
|
||||
|
||||
test('a receiver stands BEHIND his baseline, on both sides of the net', () => {
|
||||
// Oliver: "when the opponent serves the player does not stand at the baseline — but that's how it
|
||||
// should be." Both marks used to sit over a metre inside the court.
|
||||
restartRun();
|
||||
score.server = 'O'; setupPoint();
|
||||
assert.ok(P.z > COURT.L, 'receiving, the player waits behind his own baseline: z=' + P.z);
|
||||
assert.ok(O.z < -0.2, '...and the server is behind his: z=' + O.z);
|
||||
score.server = 'P'; setupPoint();
|
||||
assert.ok(O.z < 0, 'and mirrored, the far receiver is behind the far baseline: z=' + O.z);
|
||||
assert.ok(P.z > COURT.L, '...with the server behind his own line');
|
||||
});
|
||||
|
||||
test('the match brings its own legs: auto-run works even where autoAssist is 0', () => {
|
||||
// The desktop pane (pointer: fine) has autoAssist 0, so the match played as a statue with a
|
||||
// racket — the flight recorder showed a swing in every lost point and an x that never moved.
|
||||
|
||||
Reference in new issue
Block a user