Give the friend's racket a sound and the duel a rematch
Two netplay-only finds from Oliver's two-phone session. THE FRIEND'S STRIKE was silent: every other opponent is simulated locally and cracks through gameplay's shared return path, but the friend's swing runs on their phone alone and arrives as a 'shot' message that wrote the ball and nothing else. netApplyShot now plays the same sample at the same volume, pitched off the pace that was actually sent (strikeRate, read after the ball is written). Both netplay modes, not just the death match. THE REMATCH: the summary's only netplay door was THE CLUBHOUSE, so playing the same friend again meant two trips through the lobby and a fresh code. The relay cannot re-pair, so the rematch is the socket that is still open — one 'again' frame and both phones reset the run under the decisions the room already carries (same side, same mode, same court, host serves). netAgain keeps G.net across restartRun(true) and replays netFresh, netStart's own tail split out so pairing and rematch cannot drift into two courts. Applied, never echoed; a 2 s window swallows the second frame when both tap at once; a dead line refuses and falls through to the clubhouse. Also: the dev server runs with SPLAT_AUTH= (server.py always said local dev should), which the shared password had been 401-ing. audio.sfxTest is a new test seam in netTest's shape. 146 tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b53fc07dd1
commit
78946015b4
9 files changed
+189
-17
No files matched your search
+5
-3
@@ -22,7 +22,7 @@ import { netSendShot, netSendToss, netTick, netJudging, netSendPoint, netSendFau
|
||||
// dink at 0.93, a 52 m/s serve at 1.09. Deliberately narrow: past about a tenth either way the
|
||||
// sample stops sounding like the same racket. Read off the LIVE ball, so it is one call site's
|
||||
// worth of code for every strike in the game.
|
||||
const strikeRate = () => 0.93 + Math.max(0, Math.min(0.16, (Math.hypot(ball.vx, ball.vz) - 20) / 200));
|
||||
export const strikeRate = () => 0.93 + Math.max(0, Math.min(0.16, (Math.hypot(ball.vx, ball.vz) - 20) / 200));
|
||||
// ...and a fast ball KICKS THE CAMERA as it leaves. The third answer to the same report: the trail
|
||||
// says how fast the ball is, the crack says how hard it was hit, and this says it in the one channel
|
||||
// the striker cannot miss, because it moves the whole picture rather than something inside it.
|
||||
@@ -1031,9 +1031,11 @@ const xLimit = () => Math.max(xVisible(), COURT.W / 2);
|
||||
// is supposed to pick up the track that was already playing over the death summary. Every mutable
|
||||
// bag has a pristine copy (state.resetState) and the two modules with timers of their own put them
|
||||
// back themselves, so nothing here has to know the shape of any of it.
|
||||
export function restartRun() {
|
||||
export function restartRun(keepNet = false) {
|
||||
recordEvent('run_end', { outcome: G.done?.won ? 'won' : G.mode === 'eaten' ? 'lost' : 'abandoned' });
|
||||
netClose(); // netplay: resetState wipes G.net below; the socket must not outlive the run it carried
|
||||
// netplay: resetState wipes G.net below; the socket must not outlive the run it carried — unless
|
||||
// the REMATCH is what asked for the reset, in which case the same pairing carries the next one
|
||||
if (!keepNet) netClose();
|
||||
resetZombies(); resetInput(); resetState(); setupPoint();
|
||||
beginRun(); // ...and the stats bag, which is the only state that does not live on G
|
||||
beginRunXp(); // ...and the profile's run delta, same rule (the xp itself is already banked)
|
||||
|
||||
Reference in new issue
Block a user