Make the run to the net and back a double tap
Oliver: "das Vorlaufen und Zurücklaufen sollte durch einen Doppeltipp ausgelöst werden". It replaces v374's flick-length rule: the long up-flick is only the deep ball again, the long down-flick only the lob. While his own ball flies there is nothing to hit, so a tap there is not a swing. Two inside 0.35 s take him in to the net from the back, or back from the net. "TO THE NET" or "BACK" pops under him. An incoming ball keeps every tap a swing, so the two meanings never share a moment. After his own serve it is serve and volley. The short-ball approach stays automatic. Measured: the base ladder is unchanged, and following the ball in still pays against pushers and costs against big hitters. Three tests replace v374's two; v376. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bb72a01a3e
commit
48d1069e40
7 files changed
+107
-37
No files matched your search
+29
-1
@@ -5709,6 +5709,33 @@ flipping by metres, every other frame, between two candidates:
|
||||
200 ms. The old code makes 26. The early-tap test's floor moved 0.45 → 0.42: he now stops 0.12 m from his
|
||||
mark instead of 0.35, so contact comes a hair later. The old curve still reads 0.33.
|
||||
|
||||
### The run in and out is a DOUBLE TAP (2026-10-14, v376)
|
||||
Oliver on v375: *"das Vorlaufen und Zurücklaufen sollte durch einen Doppeltipp ausgelöst werden"*. It
|
||||
replaces v374's flick-length rule. The shot and the run are separate decisions now: the long up-flick is
|
||||
only the deep hard ball, the long down-flick only the lob.
|
||||
- **While his OWN ball flies** (rally modes, `ball.lastHit === 'P'`) there is nothing to hit, so a tap there
|
||||
is not a swing at all. Two taps inside `dblTap` (0.35 s) are `moveCall`: from the back to the net
|
||||
(`ball.approach = 'P'`), from the net (or while already running in) back (`ball.retreat`). A word pops
|
||||
under him ("TO THE NET" / "BACK"). A single tap there does nothing (it used to be a pointless swish).
|
||||
- **An incoming ball keeps every tap a swing**, so the two meanings never share a moment. The rule from
|
||||
the 2026-08-22 lob holds: the meaning depends on whose ball is in the air, which he always knows, never
|
||||
on where the opponent stands.
|
||||
- **After his own serve it is serve and volley.** `P.rushing` no longer excludes the serve. His reply is a
|
||||
net man's plan from wherever his run has got to.
|
||||
- **The short-ball approach stays automatic** (step 1, decision (c)). A double tap during that run takes him
|
||||
back.
|
||||
- Input: a tap fires at lift if under 250 ms (input.js), and the canvas has `touch-action: none`, so the
|
||||
browser's double-tap zoom never sees it.
|
||||
- Measured (σ60, level 26, 8 seeds; the bot follows 30% of its deep flicks in with a double tap): the base
|
||||
ladder is unchanged. Attacking still pays against the pushers (Steward 52 → 61) and costs against the big
|
||||
hitters and the Champion (Brute 29 → 20, Champion 33 → 25). It is a little weaker than v374's, because he
|
||||
sets off after the shot, not with it.
|
||||
- Tests (172): a double tap from the back takes him in, one tap or two too far apart do nothing and never
|
||||
swing, the long up-flick alone no longer runs; a double tap at the net takes him back, the lob alone does
|
||||
not; an incoming ball keeps both taps swings.
|
||||
- **Not built:** a lesson for it. The ghost hand teaches the tap and the side flick only; the double tap
|
||||
is undiscoverable without being told. Add one if the phone test says so.
|
||||
|
||||
# OPEN — leftovers, in rough priority order
|
||||
|
||||
Nothing here is broken; these are known gaps and unverified risks.
|
||||
@@ -5723,7 +5750,8 @@ Nothing here is broken; these are known gaps and unverified risks.
|
||||
v374 adds the player's own net game: long up-flick = attack and follow in, long down-flick = lob and
|
||||
go back, and the SMASH. See "The player's way to the net and back" above.
|
||||
v375: the auto-run's shake is gone (a latched chase plan); he is a little stronger for it, so the bottom
|
||||
of the Climb and classic easy may now be too easy. See "The auto-run stops shaking". Fallback tag `rally-baseline-v360`.
|
||||
of the Climb and classic easy may now be too easy. See "The auto-run stops shaking".
|
||||
v376: the run to the net and back is a DOUBLE TAP while his own ball flies (replaces v374's flick length). Fallback tag `rally-baseline-v360`.
|
||||
|
||||
1. **`RES` 4 is unverified on a real phone under load.** It nearly doubled the fill rate (810 -> 1080
|
||||
device px across). Oliver has not reported stutter, but he also has not played a late horde since.
|
||||
|
||||
+1
-1
@@ -1140,6 +1140,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
|
||||
</div>
|
||||
</div>
|
||||
<span id="recLamp" hidden>REC</span>
|
||||
<script type="module" src="src/main.js?v=375"></script>
|
||||
<script type="module" src="src/main.js?v=376"></script>
|
||||
</body>
|
||||
</html>
|
||||
+1
-3
@@ -107,9 +107,7 @@ export const TUNE = {
|
||||
softErr: 0.8, // ...and how far toward it at quality 0
|
||||
lobTurn: 0.6, // leg speed of an opponent lobbed at the net: he turns and backpedals
|
||||
netReact: 0.22, // s an opponent at the net stands reading the pass before he moves
|
||||
attackMin: 90, // px: an UP-flick this long is the ATTACK — the deep ball and you follow it to the
|
||||
// net (shorter: the deep ball, you stay). Mirror of dropMax: a long DOWN-flick is
|
||||
// the lob and takes you back to the baseline. One meaning each, wherever you stand
|
||||
dblTap: 0.35, // s: two taps this close while his OWN ball flies are the move — to the net, or back
|
||||
smashMinY: 1.9, // m: a ball the player takes OUT OF THE AIR at least this high is a SMASH...
|
||||
smashMaxY: 2.5, // ...up to this high (above CONTACT.maxY: the overhead reaches higher than a volley —
|
||||
// but under the 2.8 m the opponent's lob clears a net man by, or he smashes it standing)
|
||||
|
||||
+22
-8
@@ -393,13 +393,7 @@ function doHit(ch, quality, aim) {
|
||||
// Never against a man already at the net — that ball is a PASS, and two volleyers is the old
|
||||
// reflex duel at a quarter of a second per shot. ball.from is the striker's distance from the net.
|
||||
ball.approach = rallyFeel() && ball.rx && ball.rx.short && !(ball.from < TUNE.netFrom) ? ch.side : null;
|
||||
// THE PLAYER'S OWN WAY IN AND OUT (2026-10-14, Oliver: "Steuerung ans Netz gehen"). A LONG up-flick
|
||||
// is the attack: the deep ball, and he follows it in — even onto a man at the net, it is his call.
|
||||
// A long DOWN-flick is the lob, and he goes back behind it. Gesture length only, never context: the
|
||||
// 2026-08-22 lob taught that a gesture whose meaning depends on where the opponent stands is noise.
|
||||
const longFlick = aim && (aim.mag === undefined || aim.mag >= TUNE.attackMin);
|
||||
if (ch.side === 'P' && rallyFeel() && aim && aim.az < 0 && !aim.smash && longFlick) ball.approach = 'P';
|
||||
ball.retreat = ch.side === 'P' && rallyFeel() && soft !== null && soft >= 1 ? 'P' : null;
|
||||
ball.retreat = null; // his own way back is a DOUBLE TAP while this ball flies (moveCall), never the shot
|
||||
ball.smash = !!(aim && aim.smash);
|
||||
ball.from = Math.abs(ch.z - COURT.netZ);
|
||||
ball.lastHit = ch.side; ball.bounces = 0; ball.rx = null; // a new shot: the receiver plans it afresh
|
||||
@@ -2139,6 +2133,15 @@ export function keyPressed(code, lead = 0, flick = false) {
|
||||
hitServe(P, q, sway || hard ? { sx: sway, hard } : null);
|
||||
return;
|
||||
}
|
||||
// THE DOUBLE TAP IS HIS LEGS (2026-10-14, Oliver: "das Vorlaufen und Zurücklaufen sollte durch einen
|
||||
// Doppeltipp ausgelöst werden"). While his OWN ball is flying there is nothing to hit, so a tap there is
|
||||
// not a swing: two inside dblTap are the move — in to the net from the back, back from the net. An
|
||||
// incoming ball keeps every tap a swing, so the two meanings never share a moment. (v374 put this on the
|
||||
// flick's length; the shot and the run are separate decisions now.)
|
||||
if (rallyFeel() && G.phase === 'rally' && ball.active && ball.lastHit === 'P' && !flick) {
|
||||
if (G.moveTapT > 0) { G.moveTapT = 0; moveCall(); } else G.moveTapT = TUNE.dblTap;
|
||||
return;
|
||||
}
|
||||
if (G.phase === 'rally' && P.swingT < 0) {
|
||||
recordEvent('swing', { window: TUNE.swingWindow * winLevel(), assist: assistK(), lead });
|
||||
// A TAP COUNTS FROM THE MOMENT THE FINGER LANDS (rally modes). A tap is only known to BE a tap
|
||||
@@ -2156,6 +2159,16 @@ export function keyPressed(code, lead = 0, flick = false) {
|
||||
}
|
||||
}
|
||||
|
||||
// In to the net, or back from it: the double tap's one meaning is "the other end of my half". At the net
|
||||
// (or already running in) it is the way back; anywhere else it is the way in. rallyHome reads the flags
|
||||
// while his ball flies, the run goes at full pace, and his reply is planned from wherever he has got to.
|
||||
function moveCall() {
|
||||
const goingIn = ball.approach === 'P' || (Math.abs(P.z - COURT.netZ) < TUNE.netFrom && ball.retreat !== 'P');
|
||||
ball.approach = goingIn ? null : 'P';
|
||||
ball.retreat = goingIn ? 'P' : null;
|
||||
popText(P.x, 0, P.z + TUNE.praiseBehind, goingIn ? 'BACK' : 'TO THE NET', UI.warm, true);
|
||||
}
|
||||
|
||||
function expirePose(ch, dt) {
|
||||
if (ch.pose === 'swing' || ch.pose === 'serve') { ch.poseT += dt; if (ch.poseT > POSE_TIME) ch.pose = 'idle'; }
|
||||
}
|
||||
@@ -2225,6 +2238,7 @@ function mopRound(b, dt) {
|
||||
// ---------------------------------------------------------------- update ----
|
||||
export function update(dt) {
|
||||
recordFrame(dt);
|
||||
if (G.moveTapT > 0) G.moveTapT -= dt; // the double tap's window (keyPressed)
|
||||
if (G.hitstop > 0) { G.hitstop -= dt; return; }
|
||||
// THE OTHER ENDING: a classic match played out. The world holds still — the man is standing, the
|
||||
// crowd is still clapping it down — and the only thing running is the clock the summary and its
|
||||
@@ -2353,7 +2367,7 @@ export function update(dt) {
|
||||
// ...and lobbed at the net he backpedals, exactly as slowly as the CPU does (TUNE.lobTurn)
|
||||
// HIS OWN WAY IN AND OUT runs at full pace (the attack's follow-in, the lob's retreat), and while
|
||||
// his attack flies he is RUSHING: the reply is planned as a net man's from wherever he has got to
|
||||
if (ball.lastHit === 'P') P.rushing = ball.approach === 'P' && !G.wasServe;
|
||||
if (ball.lastHit === 'P') P.rushing = ball.approach === 'P'; // a double tap behind his serve is serve and volley
|
||||
const ownRun = !chasing && rallyFeel() && ball.lastHit === 'P' && (ball.approach === 'P' || ball.retreat === 'P');
|
||||
let rate = TUNE.playerSpeed * aK * dt * (chasing || ownRun ? 1 : 0.55) * (chasing && ball.rx && ball.rx.lob ? TUNE.lobTurn : 1);
|
||||
// ON HIS HEELS: a put-away of his own sitter is read late (step 4 (b)). Without this the auto-run
|
||||
|
||||
@@ -184,6 +184,7 @@ export const G = {
|
||||
rallyNear: null, // closest the incoming ball got this rally (for never-swung passes)
|
||||
swung: false, // player swung at least once this rally — whiff log supersedes PASSED
|
||||
lastMiss: null, // {text, at} — most recent miss reason, drawn on the HUD briefly
|
||||
moveTapT: 0, // the double tap's open window (gameplay.keyPressed, moveCall)
|
||||
ring: null, // the drawn gold ring's steady cue (gameplay update smooths ringCue)
|
||||
logShow: false, // tap the scoreboard to toggle the on-screen miss-log dump
|
||||
};
|
||||
|
||||
@@ -1521,11 +1521,11 @@ test('LOBBED at the net, the player backpedals too, and only blocks it back', ()
|
||||
} finally { TUNE.autoAssist = assist; }
|
||||
});
|
||||
|
||||
// ---- THE PLAYER'S WAY TO THE NET AND BACK, AND THE SMASH (2026-10-14, Oliver: "Steuerung ans Netz gehen"
|
||||
// ---- THE PLAYER'S WAY TO THE NET AND BACK (the double tap), AND THE SMASH (2026-10-14, Oliver: "Steuerung ans Netz gehen"
|
||||
// ... "wenn man einen Lob vom Gegner als Volley annimmt, soll man derb schmettern") ---------------------
|
||||
// One player shot with a given swipe from (x 0, z pz), then `secs` of flight with the opponent parked out
|
||||
// of play. Returns the shot's flags and where the player's own run took him.
|
||||
function ownShot(swipe, pz, secs = 1.2) {
|
||||
function ownShot(swipe, pz, secs = 1.2, taps = []) {
|
||||
freshSplatter(); startMatch(); G.interlude.introT = 0;
|
||||
const assist = TUNE.autoAssist; TUNE.autoAssist = 1.15;
|
||||
try {
|
||||
@@ -1538,30 +1538,55 @@ function ownShot(swipe, pz, secs = 1.2) {
|
||||
G.swipe = swipe;
|
||||
update(DT); G.swipe = null;
|
||||
assert.equal(ball.lastHit, 'P', 'the swing connects');
|
||||
const out = { approach: ball.approach, retreat: ball.retreat };
|
||||
for (let i = 0; i < secs * 60 && ball.active; i++) { rz.swingCool = 999; update(DT); }
|
||||
out.fromNet = Math.abs(P.z - COURT.netZ);
|
||||
return out;
|
||||
let swung = false;
|
||||
for (let i = 0; i < secs * 60 && ball.active; i++) {
|
||||
rz.swingCool = 999;
|
||||
if (taps.includes(i)) { keyPressed('Space', 0.1); swung ||= P.swingT >= 0; }
|
||||
update(DT);
|
||||
}
|
||||
return { approach: ball.approach, retreat: ball.retreat, fromNet: Math.abs(P.z - COURT.netZ), swung };
|
||||
} finally { TUNE.autoAssist = assist; }
|
||||
}
|
||||
|
||||
test('THE ATTACK: a long up-flick follows the ball to the net, a short one stays back', () => {
|
||||
const up = mag => ({ ax: 0, az: -1, fx: 0, fy: -1, mag });
|
||||
const attack = ownShot(up(TUNE.attackMin + 30), 24), deep = ownShot(up(TUNE.attackMin - 30), 24);
|
||||
assert.equal(attack.approach, 'P', 'the long up-flick is the attack');
|
||||
assert.ok(attack.fromNet < TUNE.netFrom, `...and he is at the net by the time it gets there: ${attack.fromNet.toFixed(1)} m`);
|
||||
assert.equal(deep.approach, null, 'the short one is the deep ball');
|
||||
assert.ok(deep.fromNet > TUNE.netFrom + 3, `...and he stays back: ${deep.fromNet.toFixed(1)} m`);
|
||||
test('THE DOUBLE TAP is his legs: from the back it takes him to the net, a single tap does nothing', () => {
|
||||
// Oliver, 2026-10-14: "das Vorlaufen und Zurücklaufen sollte durch einen Doppeltipp ausgelöst werden".
|
||||
// While his own ball flies there is nothing to hit, so taps there are the move, never a swing.
|
||||
const deep = { ax: 0, az: -1, fx: 0, fy: -1, mag: TUNE.flickFull + 20 };
|
||||
const plain = ownShot(deep, 24), one = ownShot(deep, 24, 1.2, [6]), two = ownShot(deep, 24, 1.2, [6, 6 + 10]);
|
||||
assert.equal(plain.approach, null, 'a long up-flick is only a shot now: no run');
|
||||
assert.ok(plain.fromNet > TUNE.netFrom + 3, `...he stays back: ${plain.fromNet.toFixed(1)} m`);
|
||||
assert.ok(!one.swung && one.approach === null && one.fromNet > TUNE.netFrom + 3, 'one tap while his ball flies: nothing at all');
|
||||
assert.equal(two.approach, 'P', 'the double tap is the way in');
|
||||
assert.ok(!two.swung, '...and neither tap swung');
|
||||
assert.ok(two.fromNet < TUNE.netFrom, `...and he is at the net by the time the ball gets there: ${two.fromNet.toFixed(1)} m`);
|
||||
const slow = ownShot(deep, 24, 1.2, [6, 6 + Math.ceil(TUNE.dblTap * 60) + 3]);
|
||||
assert.equal(slow.approach, null, 'two taps further apart than dblTap are not a double tap');
|
||||
});
|
||||
|
||||
test('THE WAY BACK: a long down-flick from the net is the lob, and he goes back behind it; a drop keeps the net', () => {
|
||||
const down = mag => ({ ax: 0, az: 1, fx: 0, fy: 1, mag });
|
||||
const lob = ownShot(down(TUNE.flickFull + 20), COURT.netZ + TUNE.netStand, 2);
|
||||
const drop = ownShot(down(TUNE.dropMax - 30), COURT.netZ + TUNE.netStand, 1);
|
||||
assert.equal(lob.retreat, 'P', 'the lob is the way back');
|
||||
assert.ok(lob.fromNet > TUNE.netFrom, `...and he is back behind the net zone: ${lob.fromNet.toFixed(1)} m`);
|
||||
assert.equal(drop.retreat, null, 'a drop volley is not a retreat');
|
||||
assert.ok(drop.fromNet < TUNE.netFrom, `...he holds the net: ${drop.fromNet.toFixed(1)} m`);
|
||||
test('THE DOUBLE TAP at the net is the way back; a long down-flick is only the lob', () => {
|
||||
const down = { ax: 0, az: 1, fx: 0, fy: 1, mag: TUNE.flickFull + 20 };
|
||||
const lob = ownShot(down, COURT.netZ + TUNE.netStand, 1.5);
|
||||
assert.equal(lob.retreat, null, 'the lob alone does not take him back');
|
||||
assert.ok(lob.fromNet < TUNE.netFrom, `...he holds the net: ${lob.fromNet.toFixed(1)} m`);
|
||||
const back = ownShot(down, COURT.netZ + TUNE.netStand, 1.5, [6, 14]);
|
||||
assert.equal(back.retreat, 'P', 'the double tap at the net is the way back');
|
||||
assert.ok(back.fromNet > TUNE.netFrom, `...and he is back behind the net zone: ${back.fromNet.toFixed(1)} m`);
|
||||
});
|
||||
|
||||
test('an INCOMING ball keeps every tap a swing: the double tap never eats a shot', () => {
|
||||
freshSplatter(); startMatch(); G.interlude.introT = 0;
|
||||
oppOf().swingCool = 999;
|
||||
G.phase = 'rally'; G.wasServe = false;
|
||||
const v = solveShot(0, 1, 2, 0.5, 21, 20);
|
||||
Object.assign(ball, { active: true, scored: false, lastHit: 'O', bounces: 0, rx: null, spin: 1, soft: false,
|
||||
x: 0, y: 1, z: 2, vx: v.vx, vy: v.vy, vz: v.vz, g: TUNE.gravity });
|
||||
P.swingT = -1;
|
||||
keyPressed('Space', 0.1);
|
||||
assert.ok(P.swingT >= 0, 'the first tap swings');
|
||||
P.swingT = -1;
|
||||
keyPressed('Space', 0.1);
|
||||
assert.ok(P.swingT >= 0, 'and so does the second');
|
||||
assert.equal(ball.approach, null, 'no run was called');
|
||||
});
|
||||
|
||||
test('THE SMASH: a lob taken out of the air is struck hard to the open court; a deep lob still beats him', () => {
|
||||
|
||||
+7
-3
@@ -93,7 +93,7 @@ const med = a => { const s = [...a].sort((x, y) => x - y); return s.length ? s[M
|
||||
// 120 ms tap), and plays the game a little — a lob or a pass against a net man, the odd drop when
|
||||
// the opponent hangs back. BOT=dist is the original fixed-distance tapper with random flicks.
|
||||
const HUMAN = (process.env.BOT || 'human') === 'human';
|
||||
const ATTACK = Number(process.env.ATTACK || 0); // share of baseline shots played as the attack (long up-flick)
|
||||
const ATTACK = Number(process.env.ATTACK || 0); // share of baseline shots followed in: a deep flick, then a DOUBLE TAP
|
||||
const JIT = Number(process.env.JIT ?? 60) / 1000, LAG = 7;
|
||||
const gauss = () => Math.sqrt(-2 * Math.log(1 - Math.random())) * Math.cos(2 * Math.PI * Math.random());
|
||||
const netx = { attacks: 0, smash: 0, smashWon: 0, lobbed: 0, open: null, p0: 0 };
|
||||
@@ -108,7 +108,7 @@ function humanAim() {
|
||||
const away = rz && rz.x > 0 ? -1 : 1; // the side he is not on
|
||||
if (oz < TUNE.netFrom) return r < 0.4 ? sw(0, 1, 150) : sw(away, -0.3, 120); // net man: lob him or pass him
|
||||
if (oz > COURT.netZ + 0.5 && Math.abs(P.z - COURT.netZ) < COURT.netZ - 1.5 && r < 0.15) return sw(0, 1, 50); // drop
|
||||
if (ATTACK && Math.abs(P.z - COURT.netZ) >= TUNE.netFrom && r < ATTACK) return sw(away * 0.3, -1, 130); // the attack: follow it in
|
||||
if (ATTACK && Math.abs(P.z - COURT.netZ) >= TUNE.netFrom && Math.random() < ATTACK) { bot.dbl = true; return sw(away * 0.3, -1, 130); } // the attack
|
||||
if (r < 0.45) return sw(away, -0.2, 110); // to the open side
|
||||
if (r < 0.65) return sw(0, -1, 80); // deep through the middle
|
||||
return null; // a plain tap
|
||||
@@ -162,6 +162,8 @@ for (const rung of RUNGS) {
|
||||
}
|
||||
}
|
||||
if (i === bot.fire) { G.swipe = bot.swipe; keyPressed('Space', bot.swipe ? 0 : LAG / 60, !!bot.swipe); }
|
||||
if (bot.dbl && ball.lastHit === 'P' && ball.active) { bot.dbl = false; bot.taps = [i + 4, i + 12]; } // follow it in: the double tap
|
||||
if (bot.taps && bot.taps.includes(i)) keyPressed('Space', 0.1);
|
||||
}
|
||||
else if (G.phase === 'rally' && ball.active && ball.lastHit === 'O') {
|
||||
const d = Math.hypot(ball.x - P.x, (ball.z - P.z) * 0.8);
|
||||
@@ -178,7 +180,9 @@ for (const rung of RUNGS) {
|
||||
const px0 = P.x, pz0 = P.z, tg0 = P.tgt;
|
||||
if (process.env.PROBE && lh === 'O' && bot.land && P.swingT >= 0) bot.land.sw = P.swingT, bot.land.d = Math.hypot(ball.x - P.x, (ball.z - P.z) * 0.8), bot.land.y = ball.y, bot.land.bb = ball.bounces;
|
||||
update(DT);
|
||||
if (lh !== 'P' && ball.lastHit === 'P' && ball.active) { if (ball.approach === 'P' && !G.wasServe) netx.attacks++; if (ball.smash) { netx.smash++; netx.open = 'smash'; } }
|
||||
if (lh !== 'P' && ball.lastHit === 'P' && ball.active && ball.smash) { netx.smash++; netx.open = 'smash'; }
|
||||
if (ball.lastHit === 'P' && ball.approach === 'P' && !netx.wasIn) netx.attacks++; // a run in (the double tap, or a short ball)
|
||||
netx.wasIn = ball.lastHit === 'P' && ball.approach === 'P';
|
||||
if (lh === 'P' && ball.lastHit === 'O' && ball.active && ball.soft && Math.abs(P.z - COURT.netZ) < TUNE.netFrom) netx.lobbed++;
|
||||
if (netx.open && (G.phase !== 'rally' || !ball.active)) { const d0 = score.pts[0] - netx.p0; if (d0 > 0) netx.smashWon++; netx.open = null; }
|
||||
if (netx.open === null) netx.p0 = score.pts[0];
|
||||
|
||||
Reference in new issue
Block a user