Files
client/matchmaking-api/README.md
T
anekdotin c0b5f421c5 Add in-game chat, bot fill for casual, and borderless fullscreen
- In-game chat: T=all, Y=team (race doubles as team), server-relayed and
  team-filtered via new ChatManager autoload; last 10 messages shown
  bottom-left (chat/chat_box.gd).
- Bot fill for casual (bots/): keeps each of the match's 2 offered races
  at a minimum of 7 total humans+bots, spawning/despawning reactively as
  players join/leave. Bots always fly their race's fighter and use
  negative peer_ids so they ride the existing networked-ship stack
  (spawning, loadout sync, health/position sync, bullet attribution) with
  no special-casing. Casual matchmaking now forms with just 1 real player
  queued instead of waiting for a second (matchmaking-api/).
- Borderless fullscreen with stretch scaling disabled instead of scaling
  the Steam-Deck-matched 1280x800 canvas up to fill PC monitors (which
  read as zoomed in) — PC monitors now reveal more world/HUD at native
  size instead. Reworked main_menu/team_select/chat_box to position via
  anchors relative to the real window instead of hardcoded coordinates.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-13 17:02:12 -04:00

87 lines
4.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Spacewar Matchmaking API
FastAPI service for matchmaking, stats, and ranks (chat planned — see below).
One process, one Postgres database, no Redis: matchmaking tickets live
in-memory in the API process, which is enough at this scale — add Redis
later only if the API needs to run as more than one instance.
## Run it
```
cp .env.example .env # already done for local dev; edit if needed
docker compose up -d
```
API is at `http://localhost:8100`, interactive docs at `/docs`. Postgres is
exposed on host port `5433` (mapped from its usual `5432`, to avoid clashing
with any other local Postgres) for local inspection.
```
docker compose down # stop
docker compose down -v # stop + wipe the postgres volume
```
Code in `app/` is bind-mounted with `--reload`, so edits take effect without
rebuilding the image. Rebuild (`docker compose up -d --build`) only when
`requirements.txt` changes.
## How matchmaking works right now
- `POST /matchmaking/queue/join` `{callsign, mode, mmr?}` — creates the
player row if it doesn't exist yet, returns a `ticket_id`.
- `GET /matchmaking/queue/status/{ticket_id}` — poll this; once matched it
returns the assigned server's `server_ip`/`server_port`/`team`.
- `POST /matchmaking/queue/leave?ticket_id=...` — cancel a queued ticket.
A background loop (`app/queue_manager.py`) runs every 2s and forms a match
once enough players are queued for a mode, then splits them into two teams
and assigns the first available `GameServer` for that mode. Casual and
ranked differ here: casual has bot fill (`spacewar/bots/bot_manager.gd`
pads both teams up to `GameConfig.bot_min_team_size` in-game), so 1 queued
player is enough to form a casual match — `CASUAL_TEAM_SIZE` just caps how
many real players can group into one match, it no longer gates formation.
Ranked has no bots, so it still waits for the full `RANKED_TEAM_SIZE × 2`
real players before forming.
There's no real game-server pool yet — one dev server is auto-seeded on API
startup from `DEV_SEED_SERVER_IP`/`DEV_SEED_SERVER_PORT` (defaults to
`127.0.0.1:7777`, i.e. a Godot server run on the host via
`godot4 --headless --path spacewar -- --server`). That IP is handed straight
to game clients to connect to, so it must be reachable from wherever the
*client* runs, not the API container — `host.docker.internal` would resolve
inside the api container but not on the client's machine, which is why this
isn't a docker-internal address. Real servers should eventually call
`POST /servers/register` on boot and periodically as a heartbeat — that
endpoint exists but nothing calls it yet.
## Client integration
The Godot client is wired up (`spacewar/autoload/matchmaking_client.gd`):
CASUAL/RANKED on the main menu calls `POST /matchmaking/queue/join`, polls
`GET /matchmaking/queue/status/{ticket_id}` every 1.5s, and once `matched`
connects `NetworkManager` directly to the returned `server_ip`/`server_port`.
Point it at a non-default API with `godot4 ... -- --matchmaking-api=http://host:port`.
## Adding a new domain (stats/ranks did this; chat will too)
1. Add/extend a model in `app/models.py` if it needs its own table.
2. Add request/response shapes to `app/schemas.py`.
3. Add a router file under `app/routers/`, `include_router()` it in
`app/main.py`.
Chat isn't scaffolded yet (no data model or requirements decided), but it
fits the same shape — likely a WebSocket router using FastAPI's native
support, in this same service.
## Not set up yet, on purpose
- **Migrations**: tables are created via `Base.metadata.create_all()` on
startup. Fine while the schema is still moving; switch to Alembic before
this holds real data.
- **Auth**: `callsign` is the only player identity, matching the game
client's current state (no accounts yet). Real identity arrives with the
GodotSteam auth checklist item in `overview/tech.md`.
- **Ranked MMR-window widening / bot-fill timeouts**: current matching is a
simple threshold (enough players queued → form a match). Refine once
there's real queue volume to tune against.