mirror of
https://github.com/tiennm99/noitu.git
synced 2026-10-11 03:13:45 +00:00
docs(journal): record the phase 7 online play session
This commit is contained in:
1 parent
b2cad42b0c
commit
67e7405252
1 file changed
+70
+70
@@ -0,0 +1,70 @@
|
||||
---
|
||||
title: "Phase 7: online play, and a room that had to outlive its game"
|
||||
date: 2026-09-05
|
||||
summary: "Shipped online 1v1, browser E2E and the container image; review found two defects caused by keeping a room alive past game over, plus a pre-existing room leak."
|
||||
---
|
||||
|
||||
# Phase 7: online play, and a room that had to outlive its game
|
||||
|
||||
## What happened
|
||||
|
||||
Phase 7 in one pass: a rematch protocol, online 1v1 in the browser, 21 Playwright
|
||||
specs, a container image, CI, and deployment docs.
|
||||
|
||||
The one real design change was on the server. Rooms used to die the instant
|
||||
`engine.Over()` was true, so a rematch needed the room to survive its own game.
|
||||
It does now, but only while both seats hold live connections — a bot room still
|
||||
closes immediately, because there is nothing to negotiate with a bot and keeping
|
||||
it alive leaks a goroutine and an engine per finished game.
|
||||
|
||||
## What that new state broke
|
||||
|
||||
Two of the three defects review found were direct consequences of it:
|
||||
|
||||
- **A game ending on the turn clock never offered a rematch.** The offer was
|
||||
opened from the message arm of the room loop, and the timer branch `return`ed
|
||||
before reaching it. Resign and no-legal-move endings worked; the most common
|
||||
natural ending did not, and the UI still showed the button. The end-of-game
|
||||
decision now sits after the whole `select`.
|
||||
|
||||
- **A second connection with the same resume token killed the room.**
|
||||
`resumeFrom` retired the old connection as soon as `r.send` succeeded, but the
|
||||
room decides asynchronously and refuses a resume into a finished game — which
|
||||
is exactly the rematch window. A duplicated tab was enough. The room now
|
||||
retires the old connection only once it has agreed to the swap.
|
||||
|
||||
The third predated the phase: `attach` overwrote the session's room pointer and
|
||||
nothing told the old room, so one connection creating four rooms stranded three
|
||||
of them forever. Fixed here because this is the phase that puts the server on
|
||||
the internet.
|
||||
|
||||
## What building the browser suite found
|
||||
|
||||
Four more, none of which the unit tests could reach: a refreshed tab could not
|
||||
rejoin its game, a restored opponent never cleared the other player's disconnect
|
||||
banner, a socket that failed without closing was invisible to the client, and
|
||||
the nickname typed on the online screen never reached the server because the
|
||||
handshake had already gone out.
|
||||
|
||||
## Decision
|
||||
|
||||
Two testing tools do not work here, and both are now documented in
|
||||
`e2e/socket-cut.js` rather than left to be rediscovered. Offline emulation does
|
||||
not cut a loopback socket. A routed socket's close does not reliably reach the
|
||||
server, so the grace window never starts — those tests take the page away
|
||||
instead.
|
||||
|
||||
## A duplicate I wrote and removed
|
||||
|
||||
I added a bot difficulty-ladder test to satisfy a plan criterion, then found
|
||||
`simulate_test.go` had covered it since phase 3 with better thresholds and a
|
||||
real corpus variant. I had grepped one file and concluded the coverage was
|
||||
missing. Deleted mine.
|
||||
|
||||
## Next steps
|
||||
|
||||
- CI has never run on GitHub; every step passes locally.
|
||||
- The Dockerfile's upstream-downloading branch is unexercised — only the fixture
|
||||
branch has been built.
|
||||
|
||||
> Historical work record — not durable authority. Prefer docs/specs/ADRs for current decisions.
|
||||
Reference in new issue
Block a user