mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-08 14:41:28 -07:00
108 lines
6.9 KiB
Markdown
108 lines
6.9 KiB
Markdown
# match
|
|
|
|
Matchmaking Worker served on the `match` subdomain (`match.recflare.net`) — a Hono app
|
|
that handles player presence and places players into room instances. Rooms, room
|
|
instances and presence all live in the shared `recflare` D1 database.
|
|
|
|
## Routes
|
|
|
|
| Method | Path | Auth | Description |
|
|
| ------ | ------------------------------------ | ---- | ------------------------------------------------ |
|
|
| POST | `/player/login` | | Login ack (no-op; must not touch presence) |
|
|
| POST | `/player/exclusivelogin` | | Exclusive-login ack (no-op) → `{ errorCode: 0 }` |
|
|
| POST | `/player/logout` | ✓\* | Clear presence (except the Orientation seed) |
|
|
| POST | `/player/notifydisconnect` | | Disconnect notification (no-op ack) |
|
|
| GET | `/player?id=1&id=2,3` | | Batch player presence lookup |
|
|
| POST | `/player/heartbeat` | ✓ | Presence heartbeat (JSON body) |
|
|
| PUT | `/player/statusvisibility` | ✓\* | Set status visibility |
|
|
| POST | `/goto/room/:room` | ✓ | Go to a room (`dormroom` → personal dorm) |
|
|
| POST | `/matchmake/none` | | Preserve current instance, else dorm |
|
|
| POST | `/matchmake/room/:roomId/:subRoomId` | ✓ | Matchmake into a specific subroom |
|
|
| POST | `/matchmake/room/:roomId` | ✓ | Matchmake into a room (default subroom) |
|
|
| POST | `/matchmake/:room` | ✓ | Matchmake by id or name (`dorm` → personal dorm) |
|
|
| POST | `/goto/none` | | Go to the dorm |
|
|
| PUT | `/player/photonregionpings` | | Region ping report (no-op ack) |
|
|
| PUT | `/player/gameserverregionpings` | | Region ping report (no-op ack) |
|
|
| POST | `/roominstance/:id/reportjoinresult` | | Report join result (no-op ack) |
|
|
| PUT | `/roominstance/:id/inprogress` | ✓ | Set the instance's in-progress flag |
|
|
| GET | `/room/:roomId/instances` | ✓ | A room's live instances (owner/co-owner only) |
|
|
| GET | `/rooms/requiring/developer` | | Rooms requiring a developer → `[]` |
|
|
| GET | `/rooms/requiring/rrplus` | | Rooms requiring RR+ → `[]` |
|
|
| GET | `/openapi.json` | | Generated OpenAPI 3.1 spec (see below) |
|
|
|
|
\* `logout` and `statusvisibility` read the token when present but never 401 — an
|
|
unauthenticated call is a no-op ack. The other ✓ routes return an empty-body 401 when
|
|
the Bearer JWT (issued by the `auth` worker) is missing or invalid.
|
|
|
|
## API documentation
|
|
|
|
`GET /openapi.json` serves a spec generated from `describeRoute` blocks that sit
|
|
alongside each handler, with the schemas in `src/openapi.ts`.
|
|
|
|
**The spec is descriptive, not enforced** — same rationale as the `auth`/`accounts`
|
|
workers: a reverse-engineered protocol, lenient handlers, no runtime validation. A test
|
|
asserts every route appears in the spec, so adding one without documenting it fails.
|
|
|
|
## Presence
|
|
|
|
Presence is a per-player row in the shared `presence` table recording the room instance
|
|
that player is currently in, plus status fields (visibility, device class, VR movement
|
|
mode, platform, app version). It's written by matchmake/goto and refreshed by the
|
|
heartbeat, and read by the heartbeat and the batch `GET /player`.
|
|
|
|
- **`isOnline` means "has a live presence row"**, not "is in a room". Rows expire on a
|
|
TTL, so a player who stops heartbeating drops offline; a player can be online in the
|
|
lobby with `roomInstance` null.
|
|
- **The heartbeat is write-thrifty.** An unchanged heartbeat re-writes the row (to
|
|
extend its TTL) only once the TTL is within `PRESENCE_REFRESH_THRESHOLD` seconds of
|
|
lapsing — a still player is refreshed periodically rather than on every beat.
|
|
- **A cron sweep** (`scheduled`) clears presence past its TTL and, crucially,
|
|
recomputes the fullness of the instances those rows pointed at. Nothing else notices
|
|
a player who crashed or hard-quit, so without the sweep their instance can stay
|
|
flagged full — and unjoinable — with nobody in it.
|
|
|
|
The heartbeat also accepts a non-JSON (LoginLock form) body, which it reads and ignores;
|
|
only a JSON body carries status fields.
|
|
|
|
## Matchmaking and room instances
|
|
|
|
A matchmake resolves the room (by numeric id or name) from D1, then finds a joinable
|
|
public instance of the requested subroom or creates a new `room_instance`. The result
|
|
is persisted as the player's presence so the heartbeat can replay it — keeping the
|
|
client's local presence in sync. `errorCode` 0 with a `roomInstance` is success; an
|
|
unknown room returns `errorCode` 20 (NoSuchRoom) with `roomInstance: null`.
|
|
|
|
Several behaviours are load-bearing and reverse-engineered from the client:
|
|
|
|
- **Instance names are `^`-prefixed** so the client resolves the new scene; personal
|
|
dorms are the exception (`@owner's Dorm`, no `^`). An empty `location` (the SubRoom's
|
|
Unity scene id) makes the client reject the session.
|
|
- **Never re-place a player into their current instance.** The client keys the room
|
|
transition off a _changing_ `roomInstanceId`; returning the same id hangs it mid-join,
|
|
so the join search excludes the caller's current instance.
|
|
- **Subrooms are separate places.** Joining one must never land you in an instance of
|
|
another, so instance reuse is scoped to the exact `(roomId, subRoomId)`.
|
|
- **The dorm is a single stable instance** with a constant Photon room id, returned
|
|
identically by every dorm entry point and the heartbeat, so the client's whole-instance
|
|
presence check never reads out-of-sync.
|
|
- **Two dorm keywords:** `goto/room/dormroom` and `matchmake/dorm` — different spellings
|
|
the 2023 client uses for the same destination.
|
|
- **`matchmake/none` preserves existing presence** (it's how the client establishes the
|
|
solo Orientation room) and only falls back to the dorm when the player has none.
|
|
`goto/none` always goes to the dorm.
|
|
|
|
## Bindings
|
|
|
|
| Binding | Type | Notes |
|
|
| ------------ | ------------- | ------------------------------------------------------------ |
|
|
| `DB` | D1 | Shared `recflare` database — rooms, room instances, presence |
|
|
| `JWT_SECRET` | Secrets Store | Shared HS256 signing key (see the `auth` README) |
|
|
|
|
The `presence` and `room_instance` tables are owned/migrated by the `rooms` worker;
|
|
this worker has no migrations of its own.
|
|
|
|
## Known gaps
|
|
|
|
- `/rooms/requiring/developer` and `/rooms/requiring/rrplus` always return `[]` — no
|
|
such gating queue exists yet.
|