From 9b9f18d312ed9fcc5dcd4f73146b4083db42d1e0 Mon Sep 17 00:00:00 2001 From: Oliver Rieger Date: Wed, 12 Aug 2026 00:13:53 +0200 Subject: [PATCH] =?UTF-8?q?LATER=20item=207:=201v1=20multiplayer=20?= =?UTF-8?q?=E2=80=94=20shot-relay=20architecture,=20ghost=20match=20first?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Decided with Oliver 2026-08-12. Records why tennis is the friendly case (the sync unit is the shot, alternation already enforced by ball.lastHit, each client authoritative for its own contacts), what the codebase already brings (deterministic sim, fixed dt, beats.js ownership contract), the zero-dep WebSocket path (~120 lines of stdlib RFC6455 in server.py, friend-code rooms), the honest cost (reconnect-and-resume is the real work), and that step one is the GHOST MATCH over plain HTTP — half the netcode falls out of it as a byproduct. Plus the hook: score-is-the-body on both players, losing is zombification. Co-Authored-By: Claude Opus 5 --- PROJECT.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/PROJECT.md b/PROJECT.md index f2943d2..24e2b45 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -2349,6 +2349,35 @@ never an interstitial. An interstitial after death would land exactly in the "on is not), and canvas perf at `RES` 4 — the third place that risk lands, after OPEN 1 and item 2 above. `matchMedia('(pointer: coarse)')` does still work there, so the mobile branches are fine. +7. **MULTIPLAYER: 1v1 zombie tennis (decided worth pursuing, 2026-08-12).** Tennis is one of the + friendliest genres in existence for netcode, and this codebase is unusually ready for it — record + the decision and the architecture so neither is re-derived: + - **The sync unit is THE SHOT, not world state.** One ball, one legal hitter at a time + (`ball.lastHit` already enforces alternation), ~one interaction per second, and each flight's + ~1 s swallows 100-150 ms of latency before the opponent needs the data. Each client is + authoritative for its OWN contacts (your swing window is judged on your phone — lag never eats + your tap); a dumb relay forwards shot events (~9 numbers: launch pos/vel/gravity/quality) and + point results. No rollback, no shared physics contention. + - **What already exists toward it**: the sim is deterministic-driven (the suite plays the real + modules headless on seeded randomness), `update()` steps fixed dt, ball state is one small + object, `beats.js` is where `startNetMatch()` would live under the court-ownership contract, + and the whole serve/swing/volley system tuned for the zombie match IS the netplay input model. + - **Zero-dep server**: python stdlib has no WebSocket, but RFC6455 handshake + framing is ~120 + lines of stdlib and `server.py` is already the one process holding state. Rooms = a 4-letter + friend code in a dict. No accounts, no matchmaking service. + - **The honest cost is the last 20%**: phone radios (screens lock, apps background, networks + switch — reconnect-and-resume is the bulk of the real work), cross-continent mush, and + hitter-authoritative trust (a plausibility check server-side is enough for friend codes; do + not build ranked-stranger anti-cheat). Prototype = a few focused sessions; robust = weeks of + paper cuts. + - **STEP ONE IS THE GHOST MATCH, not the socket.** Record a run's shot timings + placements + (tiny data), serve it over plain HTTP from the existing server, play against the recording — + racing-game ghost logic. Zero realtime infrastructure, feels close to a duel, and half the + netcode (shot serialization, replay determinism) falls out of it as a byproduct. + - **The hook that makes it ours**: score-is-the-body applied to BOTH players — every point you + lose, YOUR sprite decays a wound stage, so the loser of a long match is a shambling one-armed + wreck by match point. Losing IS zombification. Nobody else's tennis game does that. + **Expectations, so nobody is surprised:** with no marketing, 20-100 units in year one is the honest central estimate (~$40-210 net, i.e. under Apple's fee at the low end). The monetization model is worth maybe $200 either way; the video channel is worth ~100x that. Ship paid because it is cheap and