PC3 Phase 2.2: game-server orchestration + POST /api/worlds/{id}/join

- backend/app/services/game_servers.py: an in-process supervisor. One
  headless Godot dedicated-server process per world, keyed by world_id,
  spawned on demand from the join endpoint, port-allocated from a
  configured range, with a background reaper that kills a world's server
  once its status file reports 0 players for the idle timeout. Off by
  default (GAME_SERVERS_ENABLED); the checkpoint/playtest launcher enables it.
- POST /api/worlds/{id}/join -> {world_id, host, port}; 404 for
  unknown/hidden worlds, 503 when orchestration is disabled. Auth required.
- main.py lifespan starts/stops the supervisor.
- config.py: GODOT_BIN, GAME_PROJECT_DIR, GAME_SERVER_HOST, port range,
  idle timeout.
- tests/test_world_join.py (auth / 404 / visibility / 503) -- suite 72 passed.
- checkpoint runner also runs the 2p multiplayer shell test.

Verified end to end: enabled orchestration, POST /join spawned the Godot
server, a real client connected to the returned address and moved; backend
shutdown reaped the process.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
This commit is contained in:
2026-09-05 22:02:55 +00:00
parent 682f1f89f7
commit 260b7a38da
5 changed files with 286 additions and 2 deletions
+5 -2
View File
@@ -9,12 +9,15 @@ from app.config import settings
from app.routers import auth, users, worlds, assets, avatar
from app.services.game_servers import supervisor
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Application lifespan: startup and shutdown events."""
# Startup: initialize database, connect to Redis, etc.
supervisor.start()
yield
# Shutdown: close connections, cleanup
await supervisor.shutdown()
app = FastAPI(