Split backend into tavolo-platform, tavolo-scopone and tavolo-app packages

Move the game-independent machinery (lobby, live-game store, websocket,
deadline scheduler, match history, leaderboards) into a new
tavolo-platform distribution behind a GameEngine contract, the scopone
scientifico rules plus a platform adapter into tavolo-scopone, and keep
only the composition root in tavolo-app. The three distributions share
the tavolo namespace (PEP 420, kaya-style monorepo).

Match history becomes fully generic: Match carries the engine's result
JSON and MatchPlayer points/details instead of scopone-shaped team
columns (migration 3 backfills existing rows). Lobby creation takes an
opaque per-game options object and websocket actions dispatch to the
session's engine.

Tests: platform suite runs against a DummyEngine toy game, scopone
keeps the rules tests plus new adapter tests, server/tests covers the
wired stack end to end (194 tests, was 143).
This commit is contained in:
2026-09-19 07:28:58 +00:00
parent b2b514ab91
commit 5a73601ddf
72 changed files with 4490 additions and 2102 deletions
+2 -2
View File
@@ -42,7 +42,7 @@ class StaticRouteTest(unittest.TestCase):
(Path(dist) / "index.html").write_text("<html>spa</html>")
patched = dataclasses.replace(settings, static_dir=dist)
with mock.patch("tavolo.routes.static.settings", patched):
with mock.patch("tavolo.static.settings", patched):
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://127.0.0.1") as client:
index = await client.get("/")
@@ -58,7 +58,7 @@ class StaticRouteTest(unittest.TestCase):
@async_test
async def test_missing_dist_returns_404(self) -> None:
patched = dataclasses.replace(settings, static_dir="/nonexistent-dist")
with mock.patch("tavolo.routes.static.settings", patched):
with mock.patch("tavolo.static.settings", patched):
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://127.0.0.1") as client:
response = await client.get("/")