v0.5.1 — multiplayer Phase 4: lobby, sessions, reconnection
A real server existed since v0.5.0 but nobody could reach it without a hand-built ?seat=&secret= URL. This is what makes it a game you can actually create or join. The server now hosts more than one game: src/server/lobby.ts (new) is pure logic — creating, joining, bot seats, host transfer, starting — same split session.ts already draws for a running game. persistence.ts gained one directory per gameId plus a top-level index so index.ts resumes every saved game on boot. /api/stream and /api/intent now authenticate by session token instead of ?seat=&secret= — the token alone proves identity (lobby-and-sessions.md §1), so the join secret's job ends at the lobby door. Bots fill empty seats at Lobby.Start only, never take over a disconnected human (D8): session.ts gained driveBots(), playing developerBot forward through consecutive bot seats after every accepted intent. Disconnect keeps the seat and says so — Push gained an optional presence field, built entirely by http.ts and never routed through the engine, since a disconnect is transport news, not a GameEvent. Host rights pass to the earliest-joined remaining player if the host drops before start. Client: src/web/lobby.ts adds create/join forms and a live seating screen; localStorage replaces ?seat= for reconnecting straight back into a game already joined. A Multiplayer button sits beside New game; the New Game dialog itself is untouched. Found only by the live smoke test, not by typechecking: /api/intent read its token from the JSON body while the client sends it in the query string (matching /api/stream) — every intent failed "no such game" until caught by curl-level verification. Doc fix: multiplayer.md's D18 said the player cap was 6; lobby-and-sessions.md §2 says 2-4 with the reasoning and the test coverage to back it. The two had drifted apart. D18 now reads 2-4. Not verified: an actual browser walking through the lobby screens — none available in this environment, same limitation Phase 2's RemoteSession shipped under. 656 tests, 0 failures. tools/jitsi-harness/ deliberately left untracked — unrelated side-project work, not part of this release.
This commit is contained in:
@@ -85,6 +85,33 @@ export const SOLO_CONFIG: GameConfig = {
|
||||
houseRules: DEFAULT_HOUSE_RULES,
|
||||
};
|
||||
|
||||
/**
|
||||
* The lobby's (`lobby.ts`) starting point for a Competitive or Co-op `Lobby.Create` — same formula
|
||||
* `SOLO_CONFIG` uses, at a nominal player count. `minCombinedRevenue` is necessarily a guess at
|
||||
* create time: nobody is seated yet, so there is no real headcount to size it against. It stays a
|
||||
* guess rather than being fixed up at `Lobby.Start`, matching what the New Game dialog already told
|
||||
* players before there was a lobby at all ("assumes a 4-player table until there's a lobby to ask
|
||||
* who's actually seated") — Phase 5 or a follow-up can revisit sizing it to the seats actually
|
||||
* filled once that is worth the complexity.
|
||||
*/
|
||||
export function defaultMultiplayerConfig(mode: 'competitive' | 'coop', players = 4): GameConfig {
|
||||
return {
|
||||
mode,
|
||||
days: DEFAULT_DAYS,
|
||||
minCombinedRevenue: collectiveRevenueFloor(players, DEFAULT_DAYS),
|
||||
maxCollisionsPerDay: DEFAULT_MAX_COLLISIONS_PER_DAY,
|
||||
maxCollisionsTotal: DEFAULT_MAX_COLLISIONS_TOTAL,
|
||||
pvpCardsAllowed: mode === 'competitive',
|
||||
optionalRules: {
|
||||
reducedVisibility: false,
|
||||
sisterTrains: false,
|
||||
employeeRotation: false,
|
||||
emergencyToolbox: false,
|
||||
},
|
||||
houseRules: DEFAULT_HOUSE_RULES,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Everything the New Game dialog can set for a solitaire game — the opening deal and the three
|
||||
* revenue rates (`houseRules`, unchanged), plus the four victory-condition dials added 2026-08-20.
|
||||
|
||||
Reference in New Issue
Block a user